expr.y revision 1.55 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