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