1 /* $NetBSD: expr.y,v 1.55 2025/06/29 00:24:23 rillig Exp $ */ 2 3 /*- 4 * Copyright (c) 2000, 2025 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 (at) NetBSD.org>, J.T. Conklin <jtc (at) NetBSD.org> 9 * and Roland Illig <rillig (at) NetBSD.org>. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 %{ 34 #include <sys/cdefs.h> 35 __RCSID("$NetBSD: expr.y,v 1.55 2025/06/29 00:24:23 rillig Exp $"); 36 37 #include <sys/types.h> 38 39 #include <err.h> 40 #include <errno.h> 41 #include <limits.h> 42 #include <locale.h> 43 #include <regex.h> 44 #include <stdarg.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <wchar.h> 49 50 static const char * const *av; 51 static unsigned skip_level; 52 53 static void yyerror(const char *, ...) __dead; 54 static int yylex(void); 55 static int is_empty_or_zero(const char *); 56 static int is_integer(const char *); 57 static const char *eval_arith(const char *, const char *, const char *); 58 static int eval_compare(const char *, const char *, const char *); 59 static const char *eval_match(const char *, const char *); 60 static size_t mbs_len(const char *, const char *); 61 62 #define YYSTYPE const char * 63 64 %} 65 66 %expect 0 67 68 %token STRING LPAREN RPAREN 69 %left SPEC_OR 70 %left SPEC_AND 71 %left COMPARE 72 %left ADD_SUB_OPERATOR 73 %left MUL_DIV_MOD_OPERATOR 74 %left SPEC_REG 75 %left LENGTH 76 77 %% 78 79 exp: expr { 80 (void)printf("%s\n", $1); 81 return is_empty_or_zero($1); 82 } 83 ; 84 85 expr: item 86 | LPAREN expr RPAREN { 87 $$ = $2; 88 } 89 | expr SPEC_OR { 90 $$ = is_empty_or_zero($1) ? NULL : "1"; 91 if ($$) 92 skip_level++; 93 } expr { 94 $$ = $3 ? $1 : $4[0] != '\0' ? $4 : "0"; 95 if ($3) 96 skip_level--; 97 } 98 | expr SPEC_AND { 99 $$ = is_empty_or_zero($1) ? NULL : "1"; 100 if (!$$) 101 skip_level++; 102 } expr { 103 $$ = $3 && !is_empty_or_zero($4) ? $1 : "0"; 104 if (!$3) 105 skip_level--; 106 } 107 | expr COMPARE expr { 108 $$ = skip_level == 0 && eval_compare($1, $2, $3) ? "1" : "0"; 109 } 110 | expr ADD_SUB_OPERATOR expr { 111 $$ = skip_level == 0 ? eval_arith($1, $2, $3) : ""; 112 } 113 | expr MUL_DIV_MOD_OPERATOR expr { 114 $$ = skip_level == 0 ? eval_arith($1, $2, $3) : ""; 115 } 116 | expr SPEC_REG expr { 117 $$ = skip_level == 0 ? eval_match($1, $3) : ""; 118 } 119 | LENGTH expr { 120 char *ln; 121 122 asprintf(&ln, "%zu", mbs_len($2, $2 + strlen($2))); 123 if (ln == NULL) 124 err(1, NULL); 125 $$ = ln; 126 } 127 ; 128 129 item: STRING 130 | SPEC_OR 131 | SPEC_AND 132 | COMPARE 133 | ADD_SUB_OPERATOR 134 | MUL_DIV_MOD_OPERATOR 135 | SPEC_REG 136 | LENGTH 137 ; 138 139 %% 140 141 static int 142 is_empty_or_zero(const char *str) 143 { 144 char *endptr; 145 146 return str[0] == '\0' 147 || (strtoll(str, &endptr, 10) == 0 && endptr[0] == '\0'); 148 } 149 150 static int 151 is_integer(const char *str) 152 { 153 char *endptr; 154 155 (void)strtoll(str, &endptr, 10); 156 /* note we treat empty string as valid number */ 157 return endptr[0] == '\0'; 158 } 159 160 static int64_t 161 to_integer(const char *str) 162 { 163 errno = 0; 164 int64_t num = strtoll(str, NULL, 10); 165 if (errno == ERANGE) { 166 yyerror("value '%s' is too %s is %lld", str, 167 num > 0 ? "big, maximum" : "small, minimum", 168 num > 0 ? LLONG_MAX : LLONG_MIN); 169 } 170 return num; 171 } 172 173 static const char * 174 eval_arith(const char *left, const char *op, const char *right) 175 { 176 int64_t res, l, r; 177 178 res = 0; 179 180 if (!is_integer(left)) 181 yyerror("non-integer argument '%s'", left); 182 if (!is_integer(right)) 183 yyerror("non-integer argument '%s'", right); 184 185 l = to_integer(left); 186 r = to_integer(right); 187 188 switch (op[0]) { 189 case '+': 190 if ((r > 0 && l > INT64_MAX - r) || 191 (r < 0 && l < INT64_MIN - r)) 192 goto integer_overflow; 193 res = l + r; 194 break; 195 case '-': 196 if ((r > 0 && l < INT64_MIN + r) || 197 (r < 0 && l > INT64_MAX + r)) 198 goto integer_overflow; 199 res = l - r; 200 break; 201 case '/': 202 if (r == 0) 203 goto invalid_zero; 204 if (l == INT64_MIN && r == -1) 205 goto integer_overflow; 206 res = l / r; 207 break; 208 case '%': 209 if (r == 0) 210 goto invalid_zero; 211 if (l == INT64_MIN && r == -1) 212 goto integer_overflow; 213 res = l % r; 214 break; 215 case '*': 216 if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) { 217 l = -l; 218 r = -r; 219 } 220 221 if (l < 0 && r >= 0) { 222 int64_t tmp = l; 223 l = r; 224 r = tmp; 225 } 226 227 if ((l < 0 && r < 0) || 228 (r > 0 && l > INT64_MAX / r) || 229 (r <= 0 && l != 0 && r < INT64_MIN / l)) 230 goto integer_overflow; 231 res = l * r; 232 break; 233 } 234 235 char *val; 236 (void)asprintf(&val, "%lld", (long long int)res); 237 if (val == NULL) 238 err(1, NULL); 239 return val; 240 241 integer_overflow: 242 yyerror("integer overflow or underflow occurred for " 243 "operation '%s %s %s'", left, op, right); 244 245 invalid_zero: 246 yyerror("second argument to '%s' must not be zero", op); 247 } 248 249 static int 250 eval_compare(const char *left, const char *op, const char *right) 251 { 252 int64_t l, r; 253 254 if (is_integer(left) && is_integer(right)) { 255 l = strtoll(left, NULL, 10); 256 r = strtoll(right, NULL, 10); 257 } else { 258 l = strcoll(left, right); 259 r = 0; 260 } 261 262 switch (op[0]) { 263 case '=': 264 return l == r; 265 case '>': 266 if (op[1] == '=') 267 return l >= r; 268 else 269 return l > r; 270 case '<': 271 if (op[1] == '=') 272 return l <= r; 273 else 274 return l < r; 275 default: 276 return l != r; 277 } 278 } 279 280 static size_t 281 mbs_len(const char *s, const char *e) 282 { 283 int len = 0; 284 size_t m = MB_CUR_MAX; 285 mbstate_t st; 286 287 memset(&st, 0, sizeof(st)); 288 for (const char *p = s; p < e;) { 289 size_t n = mbrlen(p, (size_t)(e - p), &st); 290 if (n > m) 291 return strlen(s); 292 len++; 293 p += n; 294 } 295 return len; 296 } 297 298 static const char * 299 eval_match(const char *str, const char *re) 300 { 301 regex_t rp; 302 regmatch_t rm[2]; 303 int rc; 304 305 if ((rc = regcomp(&rp, re, REG_BASIC)) != 0) { 306 char errbuf[256]; 307 (void)regerror(rc, &rp, errbuf, sizeof(errbuf)); 308 yyerror("%s", errbuf); 309 } 310 311 if (regexec(&rp, str, 2, rm, 0) == 0 && rm[0].rm_so == 0) { 312 char *val; 313 if (rm[1].rm_so >= 0) { 314 (void)asprintf(&val, "%.*s", 315 (int)(rm[1].rm_eo - rm[1].rm_so), 316 str + rm[1].rm_so); 317 } else { 318 (void)asprintf(&val, "%zu", 319 mbs_len(str + rm[0].rm_so, str + rm[0].rm_eo)); 320 } 321 if (val == NULL) 322 err(1, NULL); 323 return val; 324 } 325 326 if (rp.re_nsub == 0) 327 return "0"; 328 else 329 return ""; 330 } 331 332 static const char x[] = "|&=<>+-*/%:()"; 333 static const int x_token[] = { 334 SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR, 335 ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR, 336 MUL_DIV_MOD_OPERATOR, SPEC_REG, LPAREN, RPAREN 337 }; 338 339 static int handle_ddash = 1; 340 341 int 342 yylex(void) 343 { 344 const char *p = *av++; 345 int retval; 346 347 if (p == NULL) 348 retval = 0; 349 else if (p[0] == '\0') 350 retval = STRING; 351 else if (p[1] == '\0') { 352 const char *w = strchr(x, p[0]); 353 retval = w != NULL ? x_token[w - x] : STRING; 354 } else if (p[1] == '=' && p[2] == '\0' 355 && (p[0] == '>' || p[0] == '<' || p[0] == '!')) 356 retval = COMPARE; 357 else if (handle_ddash && strcmp(p, "--") == 0) { 358 handle_ddash = 0; 359 retval = yylex(); 360 if (retval != STRING && retval != LPAREN && retval != RPAREN) { 361 retval = STRING; 362 av--; /* was increased in call to yylex() above */ 363 p = "--"; 364 } else 365 p = yylval; 366 } else if (strcmp(p, "length") == 0) 367 retval = LENGTH; 368 else 369 retval = STRING; 370 371 handle_ddash = 0; 372 yylval = p; 373 374 return retval; 375 } 376 377 /* 378 * Print error message and exit with error 2 (syntax error). 379 */ 380 static __printflike(1, 2) void 381 yyerror(const char *fmt, ...) 382 { 383 va_list arg; 384 385 va_start(arg, fmt); 386 verrx(2, fmt, arg); 387 va_end(arg); 388 } 389 390 int 391 main(int argc, const char * const *argv) 392 { 393 setprogname(argv[0]); 394 (void)setlocale(LC_ALL, ""); 395 396 if (argc == 1) { 397 (void)fprintf(stderr, "usage: %s expression\n", 398 getprogname()); 399 exit(2); 400 } 401 402 av = argv + 1; 403 404 return yyparse(); 405 } 406