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