• Home
  • History
  • Annotate
  • only in this directory
1/* $NetBSD: expr.y,v 1.46 2020/06/11 13:08:07 kamil 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.46 2020/06/11 13:08:07 kamil 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
50static const char * const *av;
51
52static void yyerror(const char *, ...) __dead;
53static int yylex(void);
54static int is_zero_or_null(const char *);
55static int is_integer(const char *);
56static int64_t perform_arith_op(const char *, const char *, const char *);
57
58#define YYSTYPE	const char *
59
60%}
61%token STRING
62%left SPEC_OR
63%left SPEC_AND
64%left COMPARE
65%left ADD_SUB_OPERATOR
66%left MUL_DIV_MOD_OPERATOR
67%left SPEC_REG
68%left LENGTH
69%left LEFT_PARENT RIGHT_PARENT
70
71%%
72
73exp:	expr = {
74		(void) printf("%s\n", $1);
75		return (is_zero_or_null($1));
76		}
77	;
78
79expr:	item { $$ = $1; }
80	| expr SPEC_OR expr = {
81		/*
82		 * Return evaluation of first expression if it is neither
83		 * an empty string nor zero; otherwise, returns the evaluation
84		 * of second expression.
85		 */
86		if (!is_zero_or_null($1))
87			$$ = $1;
88		else
89			$$ = $3;
90		}
91	| expr SPEC_AND expr = {
92		/*
93		 * Returns the evaluation of first expr if neither expression
94		 * evaluates to an empty string or zero; otherwise, returns
95		 * zero.
96		 */
97		if (!is_zero_or_null($1) && !is_zero_or_null($3))
98			$$ = $1;
99		else
100			$$ = "0";
101		}
102	| expr SPEC_REG expr = {
103		/*
104		 * The ``:'' operator matches first expr against the second,
105		 * which must be a regular expression.
106		 */
107		regex_t rp;
108		regmatch_t rm[2];
109		int eval;
110
111		/* compile regular expression */
112		if ((eval = regcomp(&rp, $3, REG_BASIC)) != 0) {
113			char errbuf[256];
114			(void)regerror(eval, &rp, errbuf, sizeof(errbuf));
115			yyerror("%s", errbuf);
116			/* NOT REACHED */
117		}
118
119		/* compare string against pattern --  remember that patterns
120		   are anchored to the beginning of the line */
121		if (regexec(&rp, $1, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
122			char *val;
123			if (rm[1].rm_so >= 0) {
124				(void) asprintf(&val, "%.*s",
125					(int) (rm[1].rm_eo - rm[1].rm_so),
126					$1 + rm[1].rm_so);
127			} else {
128				(void) asprintf(&val, "%d",
129					(int)(rm[0].rm_eo - rm[0].rm_so));
130			}
131			if (val == NULL)
132				err(1, NULL);
133			$$ = val;
134		} else {
135			if (rp.re_nsub == 0) {
136				$$ = "0";
137			} else {
138				$$ = "";
139			}
140		}
141
142		}
143	| expr ADD_SUB_OPERATOR expr = {
144		/* Returns the results of addition, subtraction */
145		char *val;
146		int64_t res;
147
148		res = perform_arith_op($1, $2, $3);
149		(void) asprintf(&val, "%lld", (long long int) res);
150		if (val == NULL)
151			err(1, NULL);
152		$$ = val;
153                }
154
155	| expr MUL_DIV_MOD_OPERATOR expr = {
156		/*
157		 * Returns the results of multiply, divide or remainder of
158		 * numeric-valued arguments.
159		 */
160		char *val;
161		int64_t res;
162
163		res = perform_arith_op($1, $2, $3);
164		(void) asprintf(&val, "%lld", (long long int) res);
165		if (val == NULL)
166			err(1, NULL);
167		$$ = val;
168
169		}
170	| expr COMPARE expr = {
171		/*
172		 * Returns the results of integer comparison if both arguments
173		 * are integers; otherwise, returns the results of string
174		 * comparison using the locale-specific collation sequence.
175		 * The result of each comparison is 1 if the specified relation
176		 * is true, or 0 if the relation is false.
177		 */
178
179		int64_t l, r;
180		int res;
181
182		res = 0;
183
184		/*
185		 * Slight hack to avoid differences in the compare code
186		 * between string and numeric compare.
187		 */
188		if (is_integer($1) && is_integer($3)) {
189			/* numeric comparison */
190			l = strtoll($1, NULL, 10);
191			r = strtoll($3, NULL, 10);
192		} else {
193			/* string comparison */
194			l = strcoll($1, $3);
195			r = 0;
196		}
197
198		switch($2[0]) {
199		case '=': /* equal */
200			res = (l == r);
201			break;
202		case '>': /* greater or greater-equal */
203			if ($2[1] == '=')
204				res = (l >= r);
205			else
206				res = (l > r);
207			break;
208		case '<': /* lower or lower-equal */
209			if ($2[1] == '=')
210				res = (l <= r);
211			else
212				res = (l < r);
213			break;
214		case '!': /* not equal */
215			/* the check if this is != was done in yylex() */
216			res = (l != r);
217		}
218
219		$$ = (res) ? "1" : "0";
220
221		}
222	| LEFT_PARENT expr RIGHT_PARENT { $$ = $2; }
223	| LENGTH expr {
224		/*
225		 * Return length of 'expr' in bytes.
226		 */
227		char *ln;
228
229		asprintf(&ln, "%ld", (long) strlen($2));
230		if (ln == NULL)
231			err(1, NULL);
232		$$ = ln;
233		}
234	;
235
236item:	STRING
237	| ADD_SUB_OPERATOR
238	| MUL_DIV_MOD_OPERATOR
239	| COMPARE
240	| SPEC_OR
241	| SPEC_AND
242	| SPEC_REG
243	| LENGTH
244	;
245%%
246
247/*
248 * Returns 1 if the string is empty or contains only numeric zero.
249 */
250static int
251is_zero_or_null(const char *str)
252{
253	char *endptr;
254
255	return str[0] == '\0'
256		|| ( strtoll(str, &endptr, 10) == 0LL
257			&& endptr[0] == '\0');
258}
259
260/*
261 * Returns 1 if the string is an integer.
262 */
263static int
264is_integer(const char *str)
265{
266	char *endptr;
267
268	(void) strtoll(str, &endptr, 10);
269	/* note we treat empty string as valid number */
270	return (endptr[0] == '\0');
271}
272
273static int64_t
274perform_arith_op(const char *left, const char *op, const char *right)
275{
276	int64_t res, l, r;
277
278	res = 0;
279
280	if (!is_integer(left)) {
281		yyerror("non-integer argument '%s'", left);
282		/* NOTREACHED */
283	}
284	if (!is_integer(right)) {
285		yyerror("non-integer argument '%s'", right);
286		/* NOTREACHED */
287	}
288
289	errno = 0;
290	l = strtoll(left, NULL, 10);
291	if (errno == ERANGE) {
292		yyerror("value '%s' is %s is %lld", left,
293		    (l > 0) ? "too big, maximum" : "too small, minimum",
294		    (l > 0) ? LLONG_MAX : LLONG_MIN);
295		/* NOTREACHED */
296	}
297
298	errno = 0;
299	r = strtoll(right, NULL, 10);
300	if (errno == ERANGE) {
301		yyerror("value '%s' is %s is %lld", right,
302		    (l > 0) ? "too big, maximum" : "too small, minimum",
303	  	    (l > 0) ? LLONG_MAX : LLONG_MIN);
304		/* NOTREACHED */
305	}
306
307	switch(op[0]) {
308	case '+':
309		/*
310		 * Check for over-& underflow.
311		 */
312		if ((l >= 0 && r <= INT64_MAX - l) ||
313		    (l <= 0 && r >= INT64_MIN - l)) {
314			res = l + r;
315		} else {
316			yyerror("integer overflow or underflow occurred for "
317                            "operation '%s %s %s'", left, op, right);
318		}
319		break;
320	case '-':
321		/*
322		 * Check for over-& underflow.
323		 */
324		if ((r > 0 && l < INT64_MIN + r) ||
325		    (r < 0 && l > INT64_MAX + r)) {
326			yyerror("integer overflow or underflow occurred for "
327			    "operation '%s %s %s'", left, op, right);
328		} else {
329			res = l - r;
330		}
331		break;
332	case '/':
333		if (r == 0)
334			yyerror("second argument to '%s' must not be zero", op);
335		if (l == INT64_MIN && r == -1)
336			yyerror("integer overflow or underflow occurred for "
337			    "operation '%s %s %s'", left, op, right);
338		res = l / r;
339
340		break;
341	case '%':
342		if (r == 0)
343			yyerror("second argument to '%s' must not be zero", op);
344		if (l == INT64_MIN && r == -1)
345			yyerror("integer overflow or underflow occurred for "
346			    "operation '%s %s %s'", left, op, right);
347		res = l % r;
348		break;
349	case '*':
350		/*
351		 * Check for over-& underflow.
352		 */
353
354		/*
355		 * Simplify the conditions:
356		 *  - remove the case of both negative arguments
357		 *    unless the operation will cause an overflow
358		 */
359		if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) {
360			l = -l;
361			r = -r;
362		}
363
364		/* - remove the case of negative l and positive r */
365		if (l < 0 && r >= 0) {
366			/* Use res as a temporary variable */
367			res = l;
368			l = r;
369			r = res;
370		}
371
372		if ((l < 0 && r < 0) ||
373		    (r > 0 && l > INT64_MAX / r) ||
374		    (r <= 0 && l != 0 && r < INT64_MIN / l)) {
375			yyerror("integer overflow or underflow occurred for "
376			    "operation '%s %s %s'", left, op, right);
377			/* NOTREACHED */
378		} else {
379			res = l * r;
380		}
381		break;
382	}
383	return res;
384}
385
386static const char *x = "|&=<>+-*/%:()";
387static const int x_token[] = {
388	SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR,
389	ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR,
390	MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT
391};
392
393static int handle_ddash = 1;
394
395int
396yylex(void)
397{
398	const char *p = *av++;
399	int retval;
400
401	if (!p)
402		retval = 0;
403	else if (p[1] == '\0') {
404		const char *w = strchr(x, p[0]);
405		if (w) {
406			retval = x_token[w-x];
407		} else {
408			retval = STRING;
409		}
410	} else if (p[1] == '=' && p[2] == '\0'
411			&& (p[0] == '>' || p[0] == '<' || p[0] == '!'))
412		retval = COMPARE;
413	else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') {
414		/* ignore "--" if passed as first argument and isn't followed
415		 * by another STRING */
416		retval = yylex();
417		if (retval != STRING && retval != LEFT_PARENT
418		    && retval != RIGHT_PARENT) {
419			/* is not followed by string or parenthesis, use as
420			 * STRING */
421			retval = STRING;
422			av--;	/* was increased in call to yylex() above */
423			p = "--";
424		} else {
425			/* "--" is to be ignored */
426			p = yylval;
427		}
428	} else if (strcmp(p, "length") == 0)
429		retval = LENGTH;
430	else
431		retval = STRING;
432
433	handle_ddash = 0;
434	yylval = p;
435
436	return retval;
437}
438
439/*
440 * Print error message and exit with error 2 (syntax error).
441 */
442static __printflike(1, 2) void
443yyerror(const char *fmt, ...)
444{
445	va_list arg;
446
447	va_start(arg, fmt);
448	verrx(2, fmt, arg);
449	va_end(arg);
450}
451
452int
453main(int argc, const char * const *argv)
454{
455	setprogname(argv[0]);
456	(void)setlocale(LC_ALL, "");
457
458	if (argc == 1) {
459		(void)fprintf(stderr, "usage: %s expression\n",
460		    getprogname());
461		exit(2);
462	}
463
464	av = argv + 1;
465
466	return yyparse();
467}
468