expr.y revision 1.49 1 /* $NetBSD: expr.y,v 1.49 2025/03/15 10:00:56 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.49 2025/03/15 10:00:56 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
49 static const char * const *av;
50
51 static void yyerror(const char *, ...) __dead;
52 static int yylex(void);
53 static int is_empty_or_zero(const char *);
54 static int is_integer(const char *);
55 static const char *eval_arith(const char *, const char *, const char *);
56 static int eval_compare(const char *, const char *, const char *);
57 static const char *eval_match(const char *, const char *);
58
59 #define YYSTYPE const char *
60
61 %}
62 %token STRING
63 %left SPEC_OR
64 %left SPEC_AND
65 %left COMPARE
66 %left ADD_SUB_OPERATOR
67 %left MUL_DIV_MOD_OPERATOR
68 %left SPEC_REG
69 %left LENGTH
70 %left LEFT_PARENT RIGHT_PARENT
71
72 %%
73
74 exp: expr {
75 (void)printf("%s\n", $1);
76 return is_empty_or_zero($1);
77 }
78 ;
79
80 expr: item
81 | expr SPEC_OR expr {
82 if (!is_empty_or_zero($1))
83 $$ = $1;
84 else
85 $$ = $3;
86 }
87 | expr SPEC_AND expr {
88 if (!is_empty_or_zero($1) && !is_empty_or_zero($3))
89 $$ = $1;
90 else
91 $$ = "0";
92 }
93 | expr SPEC_REG expr {
94 $$ = eval_match($1, $3);
95 }
96 | expr ADD_SUB_OPERATOR expr {
97 $$ = eval_arith($1, $2, $3);
98 }
99 | expr MUL_DIV_MOD_OPERATOR expr {
100 $$ = eval_arith($1, $2, $3);
101 }
102 | expr COMPARE expr {
103 $$ = eval_compare($1, $2, $3) ? "1" : "0";
104 }
105 | LEFT_PARENT expr RIGHT_PARENT {
106 $$ = $2;
107 }
108 | LENGTH expr {
109 char *ln;
110
111 asprintf(&ln, "%ld", (long) strlen($2));
112 if (ln == NULL)
113 err(1, NULL);
114 $$ = ln;
115 }
116 ;
117
118 item: STRING
119 | ADD_SUB_OPERATOR
120 | MUL_DIV_MOD_OPERATOR
121 | COMPARE
122 | SPEC_OR
123 | SPEC_AND
124 | SPEC_REG
125 | LENGTH
126 ;
127 %%
128
129 static int
130 is_empty_or_zero(const char *str)
131 {
132 char *endptr;
133
134 return str[0] == '\0'
135 || (strtoll(str, &endptr, 10) == 0 && endptr[0] == '\0');
136 }
137
138 static int
139 is_integer(const char *str)
140 {
141 char *endptr;
142
143 (void)strtoll(str, &endptr, 10);
144 /* note we treat empty string as valid number */
145 return endptr[0] == '\0';
146 }
147
148 static int64_t
149 to_integer(const char *str)
150 {
151 errno = 0;
152 int64_t num = strtoll(str, NULL, 10);
153 if (errno == ERANGE) {
154 yyerror("value '%s' is too %s is %lld", str,
155 num > 0 ? "big, maximum" : "small, minimum",
156 num > 0 ? LLONG_MAX : LLONG_MIN);
157 }
158 return num;
159 }
160
161 static const char *
162 eval_arith(const char *left, const char *op, const char *right)
163 {
164 int64_t res, l, r;
165
166 res = 0;
167
168 if (!is_integer(left))
169 yyerror("non-integer argument '%s'", left);
170 if (!is_integer(right))
171 yyerror("non-integer argument '%s'", right);
172
173 l = to_integer(left);
174 r = to_integer(right);
175
176 switch (op[0]) {
177 case '+':
178 if ((r > 0 && l > INT64_MAX - r) ||
179 (r < 0 && l < INT64_MIN - r))
180 goto integer_overflow;
181 res = l + r;
182 break;
183 case '-':
184 if ((r > 0 && l < INT64_MIN + r) ||
185 (r < 0 && l > INT64_MAX + r))
186 goto integer_overflow;
187 res = l - r;
188 break;
189 case '/':
190 if (r == 0)
191 goto invalid_zero;
192 if (l == INT64_MIN && r == -1)
193 goto integer_overflow;
194 res = l / r;
195 break;
196 case '%':
197 if (r == 0)
198 goto invalid_zero;
199 if (l == INT64_MIN && r == -1)
200 goto integer_overflow;
201 res = l % r;
202 break;
203 case '*':
204 if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) {
205 l = -l;
206 r = -r;
207 }
208
209 if (l < 0 && r >= 0) {
210 int64_t tmp = l;
211 l = r;
212 r = tmp;
213 }
214
215 if ((l < 0 && r < 0) ||
216 (r > 0 && l > INT64_MAX / r) ||
217 (r <= 0 && l != 0 && r < INT64_MIN / l))
218 goto integer_overflow;
219 res = l * r;
220 break;
221 }
222
223 char *val;
224 (void)asprintf(&val, "%lld", (long long int)res);
225 if (val == NULL)
226 err(1, NULL);
227 return val;
228
229 integer_overflow:
230 yyerror("integer overflow or underflow occurred for "
231 "operation '%s %s %s'", left, op, right);
232
233 invalid_zero:
234 yyerror("second argument to '%s' must not be zero", op);
235 }
236
237 static int
238 eval_compare(const char *left, const char *op, const char *right)
239 {
240 int64_t l, r;
241
242 if (is_integer(left) && is_integer(right)) {
243 l = strtoll(left, NULL, 10);
244 r = strtoll(right, NULL, 10);
245 } else {
246 l = strcoll(left, right);
247 r = 0;
248 }
249
250 switch (op[0]) {
251 case '=':
252 return l == r;
253 case '>':
254 if (op[1] == '=')
255 return l >= r;
256 else
257 return l > r;
258 case '<':
259 if (op[1] == '=')
260 return l <= r;
261 else
262 return l < r;
263 default:
264 return l != r;
265 }
266 }
267
268 static const char *
269 eval_match(const char *str, const char *re)
270 {
271 regex_t rp;
272 regmatch_t rm[2];
273 int rc;
274
275 if ((rc = regcomp(&rp, re, REG_BASIC)) != 0) {
276 char errbuf[256];
277 (void)regerror(rc, &rp, errbuf, sizeof(errbuf));
278 yyerror("%s", errbuf);
279 }
280
281 if (regexec(&rp, str, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
282 char *val;
283 if (rm[1].rm_so >= 0) {
284 (void)asprintf(&val, "%.*s",
285 (int)(rm[1].rm_eo - rm[1].rm_so),
286 str + rm[1].rm_so);
287 } else {
288 (void)asprintf(&val, "%d",
289 (int)(rm[0].rm_eo - rm[0].rm_so));
290 }
291 if (val == NULL)
292 err(1, NULL);
293 return val;
294 }
295
296 if (rp.re_nsub == 0)
297 return "0";
298 else
299 return "";
300 }
301
302 static const char *x = "|&=<>+-*/%:()";
303 static const int x_token[] = {
304 SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR,
305 ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR,
306 MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT
307 };
308
309 static int handle_ddash = 1;
310
311 int
312 yylex(void)
313 {
314 const char *p = *av++;
315 int retval;
316
317 if (!p)
318 retval = 0;
319 else if (p[0] == '\0')
320 retval = STRING;
321 else if (p[1] == '\0') {
322 const char *w = strchr(x, p[0]);
323 if (w) {
324 retval = x_token[w-x];
325 } else {
326 retval = STRING;
327 }
328 } else if (p[1] == '=' && p[2] == '\0'
329 && (p[0] == '>' || p[0] == '<' || p[0] == '!'))
330 retval = COMPARE;
331 else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') {
332 /* ignore "--" if passed as first argument and isn't followed
333 * by another STRING */
334 retval = yylex();
335 if (retval != STRING && retval != LEFT_PARENT
336 && retval != RIGHT_PARENT) {
337 /* is not followed by string or parenthesis, use as
338 * STRING */
339 retval = STRING;
340 av--; /* was increased in call to yylex() above */
341 p = "--";
342 } else {
343 /* "--" is to be ignored */
344 p = yylval;
345 }
346 } else if (strcmp(p, "length") == 0)
347 retval = LENGTH;
348 else
349 retval = STRING;
350
351 handle_ddash = 0;
352 yylval = p;
353
354 return retval;
355 }
356
357 /*
358 * Print error message and exit with error 2 (syntax error).
359 */
360 static __printflike(1, 2) void
361 yyerror(const char *fmt, ...)
362 {
363 va_list arg;
364
365 va_start(arg, fmt);
366 verrx(2, fmt, arg);
367 va_end(arg);
368 }
369
370 int
371 main(int argc, const char * const *argv)
372 {
373 setprogname(argv[0]);
374 (void)setlocale(LC_ALL, "");
375
376 if (argc == 1) {
377 (void)fprintf(stderr, "usage: %s expression\n",
378 getprogname());
379 exit(2);
380 }
381
382 av = argv + 1;
383
384 return yyparse();
385 }
386