1 /* $NetBSD: expr.y,v 1.36 2009/01/20 14:22:37 joerg Exp $ */ 2 3 /*_ 4 * Copyright (c) 2000 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jaromir Dolecek <jdolecek@NetBSD.org> and J.T. Conklin <jtc@NetBSD.org>. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 %{ 33 #include <sys/cdefs.h> 34 #ifndef lint 35 __RCSID("$NetBSD: expr.y,v 1.36 2009/01/20 14:22:37 joerg Exp $"); 36 #endif /* not lint */ 37 38 #include <sys/types.h> 39 40 #include <err.h> 41 #include <errno.h> 42 #include <limits.h> 43 #include <locale.h> 44 #include <regex.h> 45 #include <stdarg.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 50 static const char * const *av; 51 52 static void yyerror(const char *, ...); 53 static int yylex(void); 54 static int is_zero_or_null(const char *); 55 static int is_integer(const char *); 56 static int64_t perform_arith_op(const char *, const char *, const char *); 57 58 int main(int, const char * const *); 59 60 #define YYSTYPE const char * 61 62 %} 63 %token STRING 64 %left SPEC_OR 65 %left SPEC_AND 66 %left COMPARE 67 %left ADD_SUB_OPERATOR 68 %left MUL_DIV_MOD_OPERATOR 69 %left SPEC_REG 70 %left LENGTH 71 %left LEFT_PARENT RIGHT_PARENT 72 73 %% 74 75 exp: expr = { 76 (void) printf("%s\n", $1); 77 return (is_zero_or_null($1)); 78 } 79 ; 80 81 expr: item { $$ = $1; } 82 | expr SPEC_OR expr = { 83 /* 84 * Return evaluation of first expression if it is neither 85 * an empty string nor zero; otherwise, returns the evaluation 86 * of second expression. 87 */ 88 if (!is_zero_or_null($1)) 89 $$ = $1; 90 else 91 $$ = $3; 92 } 93 | expr SPEC_AND expr = { 94 /* 95 * Returns the evaluation of first expr if neither expression 96 * evaluates to an empty string or zero; otherwise, returns 97 * zero. 98 */ 99 if (!is_zero_or_null($1) && !is_zero_or_null($3)) 100 $$ = $1; 101 else 102 $$ = "0"; 103 } 104 | expr SPEC_REG expr = { 105 /* 106 * The ``:'' operator matches first expr against the second, 107 * which must be a regular expression. 108 */ 109 regex_t rp; 110 regmatch_t rm[2]; 111 int eval; 112 113 /* compile regular expression */ 114 if ((eval = regcomp(&rp, $3, REG_BASIC)) != 0) { 115 char errbuf[256]; 116 (void)regerror(eval, &rp, errbuf, sizeof(errbuf)); 117 yyerror("%s", errbuf); 118 /* NOT REACHED */ 119 } 120 121 /* compare string against pattern -- remember that patterns 122 are anchored to the beginning of the line */ 123 if (regexec(&rp, $1, 2, rm, 0) == 0 && rm[0].rm_so == 0) { 124 char *val; 125 if (rm[1].rm_so >= 0) { 126 (void) asprintf(&val, "%.*s", 127 (int) (rm[1].rm_eo - rm[1].rm_so), 128 $1 + rm[1].rm_so); 129 } else { 130 (void) asprintf(&val, "%d", 131 (int)(rm[0].rm_eo - rm[0].rm_so)); 132 } 133 if (val == NULL) 134 err(1, NULL); 135 $$ = val; 136 } else { 137 if (rp.re_nsub == 0) { 138 $$ = "0"; 139 } else { 140 $$ = ""; 141 } 142 } 143 144 } 145 | expr ADD_SUB_OPERATOR expr = { 146 /* Returns the results of addition, subtraction */ 147 char *val; 148 int64_t res; 149 150 res = perform_arith_op($1, $2, $3); 151 (void) asprintf(&val, "%lld", (long long int) res); 152 if (val == NULL) 153 err(1, NULL); 154 $$ = val; 155 } 156 157 | expr MUL_DIV_MOD_OPERATOR expr = { 158 /* 159 * Returns the results of multiply, divide or remainder of 160 * numeric-valued arguments. 161 */ 162 char *val; 163 int64_t res; 164 165 res = perform_arith_op($1, $2, $3); 166 (void) asprintf(&val, "%lld", (long long int) res); 167 if (val == NULL) 168 err(1, NULL); 169 $$ = val; 170 171 } 172 | expr COMPARE expr = { 173 /* 174 * Returns the results of integer comparison if both arguments 175 * are integers; otherwise, returns the results of string 176 * comparison using the locale-specific collation sequence. 177 * The result of each comparison is 1 if the specified relation 178 * is true, or 0 if the relation is false. 179 */ 180 181 int64_t l, r; 182 int res; 183 184 res = 0; 185 186 /* 187 * Slight hack to avoid differences in the compare code 188 * between string and numeric compare. 189 */ 190 if (is_integer($1) && is_integer($3)) { 191 /* numeric comparison */ 192 l = strtoll($1, NULL, 10); 193 r = strtoll($3, NULL, 10); 194 } else { 195 /* string comparison */ 196 l = strcoll($1, $3); 197 r = 0; 198 } 199 200 switch($2[0]) { 201 case '=': /* equal */ 202 res = (l == r); 203 break; 204 case '>': /* greater or greater-equal */ 205 if ($2[1] == '=') 206 res = (l >= r); 207 else 208 res = (l > r); 209 break; 210 case '<': /* lower or lower-equal */ 211 if ($2[1] == '=') 212 res = (l <= r); 213 else 214 res = (l < r); 215 break; 216 case '!': /* not equal */ 217 /* the check if this is != was done in yylex() */ 218 res = (l != r); 219 } 220 221 $$ = (res) ? "1" : "0"; 222 223 } 224 | LEFT_PARENT expr RIGHT_PARENT { $$ = $2; } 225 | LENGTH expr { 226 /* 227 * Return length of 'expr' in bytes. 228 */ 229 char *ln; 230 231 asprintf(&ln, "%ld", (long) strlen($2)); 232 if (ln == NULL) 233 err(1, NULL); 234 $$ = ln; 235 } 236 ; 237 238 item: STRING 239 | ADD_SUB_OPERATOR 240 | MUL_DIV_MOD_OPERATOR 241 | COMPARE 242 | SPEC_OR 243 | SPEC_AND 244 | SPEC_REG 245 | LENGTH 246 ; 247 %% 248 249 /* 250 * Returns 1 if the string is empty or contains only numeric zero. 251 */ 252 static int 253 is_zero_or_null(const char *str) 254 { 255 char *endptr; 256 257 return str[0] == '\0' 258 || ( strtoll(str, &endptr, 10) == 0LL 259 && endptr[0] == '\0'); 260 } 261 262 /* 263 * Returns 1 if the string is an integer. 264 */ 265 static int 266 is_integer(const char *str) 267 { 268 char *endptr; 269 270 (void) strtoll(str, &endptr, 10); 271 /* note we treat empty string as valid number */ 272 return (endptr[0] == '\0'); 273 } 274 275 static int64_t 276 perform_arith_op(const char *left, const char *op, const char *right) 277 { 278 int64_t res, sign, l, r; 279 u_int64_t temp; 280 281 res = 0; 282 283 if (!is_integer(left)) { 284 yyerror("non-integer argument '%s'", left); 285 /* NOTREACHED */ 286 } 287 if (!is_integer(right)) { 288 yyerror("non-integer argument '%s'", right); 289 /* NOTREACHED */ 290 } 291 292 errno = 0; 293 l = strtoll(left, NULL, 10); 294 if (errno == ERANGE) { 295 yyerror("value '%s' is %s is %lld", left, 296 (l > 0) ? "too big, maximum" : "too small, minimum", 297 (l > 0) ? LLONG_MAX : LLONG_MIN); 298 /* NOTREACHED */ 299 } 300 301 errno = 0; 302 r = strtoll(right, NULL, 10); 303 if (errno == ERANGE) { 304 yyerror("value '%s' is %s is %lld", right, 305 (l > 0) ? "too big, maximum" : "too small, minimum", 306 (l > 0) ? LLONG_MAX : LLONG_MIN); 307 /* NOTREACHED */ 308 } 309 310 switch(op[0]) { 311 case '+': 312 /* 313 * Do the op into an unsigned to avoid overflow and then cast 314 * back to check the resulting signage. 315 */ 316 temp = l + r; 317 res = (int64_t) temp; 318 /* very simplistic check for over-& underflow */ 319 if ((res < 0 && l > 0 && r > 0) 320 || (res > 0 && l < 0 && r < 0)) 321 yyerror("integer overflow or underflow occurred for " 322 "operation '%s %s %s'", left, op, right); 323 break; 324 case '-': 325 /* 326 * Do the op into an unsigned to avoid overflow and then cast 327 * back to check the resulting signage. 328 */ 329 temp = l - r; 330 res = (int64_t) temp; 331 /* very simplistic check for over-& underflow */ 332 if ((res < 0 && l > 0 && l > r) 333 || (res > 0 && l < 0 && l < r) ) 334 yyerror("integer overflow or underflow occurred for " 335 "operation '%s %s %s'", left, op, right); 336 break; 337 case '/': 338 if (r == 0) 339 yyerror("second argument to '%s' must not be zero", op); 340 res = l / r; 341 342 break; 343 case '%': 344 if (r == 0) 345 yyerror("second argument to '%s' must not be zero", op); 346 res = l % r; 347 break; 348 case '*': 349 /* shortcut */ 350 if ((l == 0) || (r == 0)) { 351 res = 0; 352 break; 353 } 354 355 sign = 1; 356 if (l < 0) 357 sign *= -1; 358 if (r < 0) 359 sign *= -1; 360 361 res = l * r; 362 /* 363 * XXX: not the most portable but works on anything with 2's 364 * complement arithmetic. If the signs don't match or the 365 * result was 0 on 2's complement this overflowed. 366 */ 367 if ((res < 0 && sign > 0) || (res > 0 && sign < 0) || 368 (res == 0)) 369 yyerror("integer overflow or underflow occurred for " 370 "operation '%s %s %s'", left, op, right); 371 /* NOTREACHED */ 372 break; 373 } 374 return res; 375 } 376 377 static const char *x = "|&=<>+-*/%:()"; 378 static const int x_token[] = { 379 SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR, 380 ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR, 381 MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT 382 }; 383 384 static int handle_ddash = 1; 385 386 int 387 yylex(void) 388 { 389 const char *p = *av++; 390 int retval; 391 392 if (!p) 393 retval = 0; 394 else if (p[1] == '\0') { 395 const char *w = strchr(x, p[0]); 396 if (w) { 397 retval = x_token[w-x]; 398 } else { 399 retval = STRING; 400 } 401 } else if (p[1] == '=' && p[2] == '\0' 402 && (p[0] == '>' || p[0] == '<' || p[0] == '!')) 403 retval = COMPARE; 404 else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') { 405 /* ignore "--" if passed as first argument and isn't followed 406 * by another STRING */ 407 retval = yylex(); 408 if (retval != STRING && retval != LEFT_PARENT 409 && retval != RIGHT_PARENT) { 410 /* is not followed by string or parenthesis, use as 411 * STRING */ 412 retval = STRING; 413 av--; /* was increased in call to yylex() above */ 414 p = "--"; 415 } else { 416 /* "--" is to be ignored */ 417 p = yylval; 418 } 419 } else if (strcmp(p, "length") == 0) 420 retval = LENGTH; 421 else 422 retval = STRING; 423 424 handle_ddash = 0; 425 yylval = p; 426 427 return retval; 428 } 429 430 /* 431 * Print error message and exit with error 2 (syntax error). 432 */ 433 static void 434 yyerror(const char *fmt, ...) 435 { 436 va_list arg; 437 438 va_start(arg, fmt); 439 verrx(2, fmt, arg); 440 va_end(arg); 441 } 442 443 int 444 main(int argc, const char * const *argv) 445 { 446 setprogname(argv[0]); 447 (void)setlocale(LC_ALL, ""); 448 449 if (argc == 1) { 450 (void)fprintf(stderr, "usage: %s expression\n", 451 getprogname()); 452 exit(2); 453 } 454 455 av = argv + 1; 456 457 exit(yyparse()); 458 /* NOTREACHED */ 459 } 460