NetBSD/bin/expr/expr.y

468 lines
10 KiB
Plaintext

/* $NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $ */
/*_
* Copyright (c) 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jaromir Dolecek <jdolecek@NetBSD.org> and J.T. Conklin <jtc@NetBSD.org>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
%{
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $");
#endif /* not lint */
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <regex.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char * const *av;
static void yyerror(const char *, ...) __dead;
static int yylex(void);
static int is_zero_or_null(const char *);
static int is_integer(const char *);
static int64_t perform_arith_op(const char *, const char *, const char *);
#define YYSTYPE const char *
%}
%token STRING
%left SPEC_OR
%left SPEC_AND
%left COMPARE
%left ADD_SUB_OPERATOR
%left MUL_DIV_MOD_OPERATOR
%left SPEC_REG
%left LENGTH
%left LEFT_PARENT RIGHT_PARENT
%%
exp: expr = {
(void) printf("%s\n", $1);
return (is_zero_or_null($1));
}
;
expr: item { $$ = $1; }
| expr SPEC_OR expr = {
/*
* Return evaluation of first expression if it is neither
* an empty string nor zero; otherwise, returns the evaluation
* of second expression.
*/
if (!is_zero_or_null($1))
$$ = $1;
else
$$ = $3;
}
| expr SPEC_AND expr = {
/*
* Returns the evaluation of first expr if neither expression
* evaluates to an empty string or zero; otherwise, returns
* zero.
*/
if (!is_zero_or_null($1) && !is_zero_or_null($3))
$$ = $1;
else
$$ = "0";
}
| expr SPEC_REG expr = {
/*
* The ``:'' operator matches first expr against the second,
* which must be a regular expression.
*/
regex_t rp;
regmatch_t rm[2];
int eval;
/* compile regular expression */
if ((eval = regcomp(&rp, $3, REG_BASIC)) != 0) {
char errbuf[256];
(void)regerror(eval, &rp, errbuf, sizeof(errbuf));
yyerror("%s", errbuf);
/* NOT REACHED */
}
/* compare string against pattern -- remember that patterns
are anchored to the beginning of the line */
if (regexec(&rp, $1, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
char *val;
if (rm[1].rm_so >= 0) {
(void) asprintf(&val, "%.*s",
(int) (rm[1].rm_eo - rm[1].rm_so),
$1 + rm[1].rm_so);
} else {
(void) asprintf(&val, "%d",
(int)(rm[0].rm_eo - rm[0].rm_so));
}
if (val == NULL)
err(1, NULL);
$$ = val;
} else {
if (rp.re_nsub == 0) {
$$ = "0";
} else {
$$ = "";
}
}
}
| expr ADD_SUB_OPERATOR expr = {
/* Returns the results of addition, subtraction */
char *val;
int64_t res;
res = perform_arith_op($1, $2, $3);
(void) asprintf(&val, "%lld", (long long int) res);
if (val == NULL)
err(1, NULL);
$$ = val;
}
| expr MUL_DIV_MOD_OPERATOR expr = {
/*
* Returns the results of multiply, divide or remainder of
* numeric-valued arguments.
*/
char *val;
int64_t res;
res = perform_arith_op($1, $2, $3);
(void) asprintf(&val, "%lld", (long long int) res);
if (val == NULL)
err(1, NULL);
$$ = val;
}
| expr COMPARE expr = {
/*
* Returns the results of integer comparison if both arguments
* are integers; otherwise, returns the results of string
* comparison using the locale-specific collation sequence.
* The result of each comparison is 1 if the specified relation
* is true, or 0 if the relation is false.
*/
int64_t l, r;
int res;
res = 0;
/*
* Slight hack to avoid differences in the compare code
* between string and numeric compare.
*/
if (is_integer($1) && is_integer($3)) {
/* numeric comparison */
l = strtoll($1, NULL, 10);
r = strtoll($3, NULL, 10);
} else {
/* string comparison */
l = strcoll($1, $3);
r = 0;
}
switch($2[0]) {
case '=': /* equal */
res = (l == r);
break;
case '>': /* greater or greater-equal */
if ($2[1] == '=')
res = (l >= r);
else
res = (l > r);
break;
case '<': /* lower or lower-equal */
if ($2[1] == '=')
res = (l <= r);
else
res = (l < r);
break;
case '!': /* not equal */
/* the check if this is != was done in yylex() */
res = (l != r);
}
$$ = (res) ? "1" : "0";
}
| LEFT_PARENT expr RIGHT_PARENT { $$ = $2; }
| LENGTH expr {
/*
* Return length of 'expr' in bytes.
*/
char *ln;
asprintf(&ln, "%ld", (long) strlen($2));
if (ln == NULL)
err(1, NULL);
$$ = ln;
}
;
item: STRING
| ADD_SUB_OPERATOR
| MUL_DIV_MOD_OPERATOR
| COMPARE
| SPEC_OR
| SPEC_AND
| SPEC_REG
| LENGTH
;
%%
/*
* Returns 1 if the string is empty or contains only numeric zero.
*/
static int
is_zero_or_null(const char *str)
{
char *endptr;
return str[0] == '\0'
|| ( strtoll(str, &endptr, 10) == 0LL
&& endptr[0] == '\0');
}
/*
* Returns 1 if the string is an integer.
*/
static int
is_integer(const char *str)
{
char *endptr;
(void) strtoll(str, &endptr, 10);
/* note we treat empty string as valid number */
return (endptr[0] == '\0');
}
static int64_t
perform_arith_op(const char *left, const char *op, const char *right)
{
int64_t res, l, r;
res = 0;
if (!is_integer(left)) {
yyerror("non-integer argument '%s'", left);
/* NOTREACHED */
}
if (!is_integer(right)) {
yyerror("non-integer argument '%s'", right);
/* NOTREACHED */
}
errno = 0;
l = strtoll(left, NULL, 10);
if (errno == ERANGE) {
yyerror("value '%s' is %s is %lld", left,
(l > 0) ? "too big, maximum" : "too small, minimum",
(l > 0) ? LLONG_MAX : LLONG_MIN);
/* NOTREACHED */
}
errno = 0;
r = strtoll(right, NULL, 10);
if (errno == ERANGE) {
yyerror("value '%s' is %s is %lld", right,
(l > 0) ? "too big, maximum" : "too small, minimum",
(l > 0) ? LLONG_MAX : LLONG_MIN);
/* NOTREACHED */
}
switch(op[0]) {
case '+':
/*
* Check for over-& underflow.
*/
if ((l >= 0 && r <= INT64_MAX - l) ||
(l <= 0 && r >= INT64_MIN - l)) {
res = l + r;
} else {
yyerror("integer overflow or underflow occurred for "
"operation '%s %s %s'", left, op, right);
}
break;
case '-':
/*
* Check for over-& underflow.
*/
if ((r > 0 && l < INT64_MIN + r) ||
(r < 0 && l > INT64_MAX + r)) {
yyerror("integer overflow or underflow occurred for "
"operation '%s %s %s'", left, op, right);
} else {
res = l - r;
}
break;
case '/':
if (r == 0)
yyerror("second argument to '%s' must not be zero", op);
if (l == INT64_MIN && r == -1)
yyerror("integer overflow or underflow occurred for "
"operation '%s %s %s'", left, op, right);
res = l / r;
break;
case '%':
if (r == 0)
yyerror("second argument to '%s' must not be zero", op);
if (l == INT64_MIN && r == -1)
yyerror("integer overflow or underflow occurred for "
"operation '%s %s %s'", left, op, right);
res = l % r;
break;
case '*':
/*
* Check for over-& underflow.
*/
/*
* Simplify the conditions:
* - remove the case of both negative arguments
* unless the operation will cause an overflow
*/
if (l < 0 && r < 0 && l != INT64_MIN && r != INT64_MIN) {
l = -l;
r = -r;
}
/* - remove the case of legative l and positive r */
if (l < 0 && r >= 0) {
/* Use res as a temporary variable */
res = l;
l = r;
r = res;
}
if ((l < 0 && r < 0) ||
(r > 0 && l > INT64_MAX / r) ||
(r <= 0 && l != 0 && r < INT64_MIN / l)) {
yyerror("integer overflow or underflow occurred for "
"operation '%s %s %s'", left, op, right);
/* NOTREACHED */
} else {
res = l * r;
}
break;
}
return res;
}
static const char *x = "|&=<>+-*/%:()";
static const int x_token[] = {
SPEC_OR, SPEC_AND, COMPARE, COMPARE, COMPARE, ADD_SUB_OPERATOR,
ADD_SUB_OPERATOR, MUL_DIV_MOD_OPERATOR, MUL_DIV_MOD_OPERATOR,
MUL_DIV_MOD_OPERATOR, SPEC_REG, LEFT_PARENT, RIGHT_PARENT
};
static int handle_ddash = 1;
int
yylex(void)
{
const char *p = *av++;
int retval;
if (!p)
retval = 0;
else if (p[1] == '\0') {
const char *w = strchr(x, p[0]);
if (w) {
retval = x_token[w-x];
} else {
retval = STRING;
}
} else if (p[1] == '=' && p[2] == '\0'
&& (p[0] == '>' || p[0] == '<' || p[0] == '!'))
retval = COMPARE;
else if (handle_ddash && p[0] == '-' && p[1] == '-' && p[2] == '\0') {
/* ignore "--" if passed as first argument and isn't followed
* by another STRING */
retval = yylex();
if (retval != STRING && retval != LEFT_PARENT
&& retval != RIGHT_PARENT) {
/* is not followed by string or parenthesis, use as
* STRING */
retval = STRING;
av--; /* was increased in call to yylex() above */
p = "--";
} else {
/* "--" is to be ignored */
p = yylval;
}
} else if (strcmp(p, "length") == 0)
retval = LENGTH;
else
retval = STRING;
handle_ddash = 0;
yylval = p;
return retval;
}
/*
* Print error message and exit with error 2 (syntax error).
*/
static __printflike(1, 2) void
yyerror(const char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
verrx(2, fmt, arg);
va_end(arg);
}
int
main(int argc, const char * const *argv)
{
setprogname(argv[0]);
(void)setlocale(LC_ALL, "");
if (argc == 1) {
(void)fprintf(stderr, "usage: %s expression\n",
getprogname());
exit(2);
}
av = argv + 1;
return yyparse();
}