diff options
| author | Jari Aalto <jari.aalto@cante.net> | 1996-08-26 18:22:31 +0000 |
|---|---|---|
| committer | Jari Aalto <jari.aalto@cante.net> | 2009-09-12 16:46:49 +0000 |
| commit | 726f63884db0132f01745f1fb4465e6621088ccf (patch) | |
| tree | 6c2f7765a890a97e0e513cb539df43283a8f7c4d /expr.c | |
Imported from ../bash-1.14.7.tar.gz.
Diffstat (limited to 'expr.c')
| -rw-r--r-- | expr.c | 902 |
1 files changed, 902 insertions, 0 deletions
@@ -0,0 +1,902 @@ +/* expr.c -- arithmetic expression evaluation. */ + +/* Copyright (C) 1990, 1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + Bash is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* + All arithmetic is done as long integers with no checking for overflow + (though division by 0 is caught and flagged as an error). + + The following operators are handled, grouped into a set of levels in + order of decreasing precedence. + + "-", "+" [(unary operators)] + "!", "~" + "*", "/", "%" + "+", "-" + "<<", ">>" + "<=", ">=", "<", ">" + "==", "!=" + "&" + "^" + "|" + "&&" + "||" + "=" + + (Note that most of these operators have special meaning to bash, and an + entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure + that it is passed intact to the evaluator when using `let'. When using + the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' + is treated as if in double quotes.) + + Sub-expressions within parentheses have a precedence level greater than + all of the above levels and are evaluated first. Within a single prece- + dence group, evaluation is left-to-right, except for the arithmetic + assignment operator (`='), which is evaluated right-to-left (as in C). + + The expression evaluator returns the value of the expression (assignment + statements have as a value what is returned by the RHS). The `let' + builtin, on the other hand, returns 0 if the last expression evaluates to + a non-zero, and 1 otherwise. + + Implementation is a recursive-descent parser. + + Chet Ramey + chet@ins.CWRU.Edu +*/ + +#include <stdio.h> +#include "bashansi.h" +#include "shell.h" + +#define variable_starter(c) (isletter(c) || (c == '_')) +#define variable_character(c) (isletter(c) || (c == '_') || digit(c)) + +/* Because of the $((...)) construct, expressions may include newlines. + Here is a macro which accepts newlines, tabs and spaces as whitespace. */ +#define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) + +extern char *this_command_name; + +static char *expression = (char *) NULL; /* The current expression */ +static char *tp = (char *) NULL; /* token lexical position */ +static char *lasttp; +static int curtok = 0; /* the current token */ +static int lasttok = 0; /* the previous token */ +static int assigntok = 0; /* the OP in OP= */ +static char *tokstr = (char *) NULL; /* current token string */ +static int tokval = 0; /* current token value */ +static jmp_buf evalbuf; + +static void readtok (); /* lexical analyzer */ +static long expassign (), exp0 (), exp1 (), exp2 (), exp3 (), + exp4 (), exp5 (), expshift (), expland (), explor (), + expband (), expbor (), expbxor (); +static long strlong (); +static void evalerror (); + +/* A structure defining a single expression context. */ +typedef struct { + int curtok, lasttok; + char *expression, *tp; + int tokval; + char *tokstr; +} EXPR_CONTEXT; + +/* Global var which contains the stack of expression contexts. */ +static EXPR_CONTEXT **expr_stack; +static int expr_depth = 0; /* Location in the stack. */ +static int expr_stack_size = 0; /* Number of slots already allocated. */ + +/* Size be which the expression stack grows when neccessary. */ +#define EXPR_STACK_GROW_SIZE 10 + +/* Maximum amount of recursion allowed. This prevents a non-integer + variable such as "num=num+2" from infinitely adding to itself when + "let num=num+2" is given. I have to talk to Chet about this hack. */ +#define MAX_EXPR_RECURSION_LEVEL 1024 + +/* The Tokens. Singing "The Lion Sleeps Tonight". */ + +#define EQEQ 1 /* "==" */ +#define NEQ 2 /* "!=" */ +#define LEQ 3 /* "<=" */ +#define GEQ 4 /* ">=" */ +#define STR 5 /* string */ +#define NUM 6 /* number */ +#define LAND 7 /* "&&" Logical AND */ +#define LOR 8 /* "||" Logical OR */ +#define LSH 9 /* "<<" Left SHift */ +#define RSH 10 /* ">>" Right SHift */ +#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ +#define EQ '=' +#define GT '>' +#define LT '<' +#define PLUS '+' +#define MINUS '-' +#define MUL '*' +#define DIV '/' +#define MOD '%' +#define NOT '!' +#define LPAR '(' +#define RPAR ')' +#define BAND '&' /* Bitwise AND */ +#define BOR '|' /* Either Bitwise OR, or what Chet is. */ +#define BXOR '^' /* Bitwise eXclusive OR. */ +#define BNOT '~' /* Bitwise NOT; Two's complement. */ + +/* Push and save away the contents of the globals describing the + current expression context. */ +static void +pushexp () +{ + EXPR_CONTEXT *context; + + context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); + + if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) + evalerror ("expression recursion level exceeded"); + + if (expr_depth >= expr_stack_size) + { + expr_stack = (EXPR_CONTEXT **) + xrealloc (expr_stack, (expr_stack_size += EXPR_STACK_GROW_SIZE) + * sizeof (EXPR_CONTEXT *)); + } + + context->curtok = curtok; + context->lasttok = lasttok; + context->expression = expression; + context->tp = tp; + context->tokval = tokval; + context->tokstr = tokstr; + expr_stack[expr_depth++] = context; +} + +/* Pop the the contents of the expression context stack into the + globals describing the current expression context. */ +static void +popexp () +{ + EXPR_CONTEXT *context; + + if (expr_depth == 0) + evalerror ("Recursion stack underflow"); + + context = expr_stack[--expr_depth]; + curtok = context->curtok; + lasttok = context->lasttok; + expression = context->expression; + tp = context->tp; + tokval = context->tokval; + tokstr = context->tokstr; + free (context); +} + +/* Evaluate EXPR, and return the arithmetic result. + + The `while' loop after the longjmp is caught relies on the above + implementation of pushexp and popexp leaving in expr_stack[0] the + values that the variables had when the program started. That is, + the first things saved are the initial values of the variables that + were assigned at program startup or by the compiler. Therefore, it is + safe to let the loop terminate when expr_depth == 0, without freeing up + any of the expr_depth[0] stuff. */ +long +evalexp (expr) + char *expr; +{ + long val = 0L; + jmp_buf old_evalbuf; + char *p; + + for (p = expr; p && *p && cr_whitespace (*p); p++) + ; + + if (p == NULL || *p == '\0') + return (0); + + /* Save the value of evalbuf to protect it around possible recursive + calls to evalexp (). */ + xbcopy ((char *)evalbuf, (char *)old_evalbuf, sizeof (jmp_buf)); + + if (setjmp (evalbuf)) + { + if (tokstr) /* Clean up local allocation. */ + free (tokstr); + + if (expression) + free (expression); + + while (--expr_depth) + { + if (expr_stack[expr_depth]->tokstr) + free (expr_stack[expr_depth]->tokstr); + + if (expr_stack[expr_depth]->expression) + free (expr_stack[expr_depth]->expression); + } + longjmp (top_level, DISCARD); + } + + pushexp (); + curtok = lasttok = 0; + expression = savestring (expr); + tp = expression; + + tokstr = (char *)NULL; + tokval = 0l; + + readtok (); + + val = expassign (); + + if (curtok != 0) + evalerror ("syntax error in expression"); + + if (tokstr) + free (tokstr); + if (expression) + free (expression); + + popexp (); + + /* Restore the value of evalbuf so that any subsequent longjmp calls + will have a valid location to jump to. */ + xbcopy ((char *)old_evalbuf, (char *)evalbuf, sizeof (jmp_buf)); + + return (val); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This should really be in variables.c, but it is here so that all of the + expression evaluation stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +void +bind_int_variable (lhs, rhs) + char *lhs, *rhs; +{ + register SHELL_VAR *v; + int isint = 0; + + v = find_variable (lhs); + if (v) + { + isint = integer_p (v); + v->attributes &= ~att_integer; + } + + v = bind_variable (lhs, rhs); + if (isint) + v->attributes |= att_integer; +} + +static long +expassign () +{ + register long value; + char *lhs, *rhs; + + value = explor (); + if (curtok == EQ || curtok == OP_ASSIGN) + { + int special = curtok == OP_ASSIGN; + int op; + long lvalue; + + if (lasttok != STR) + evalerror ("attempted expassign to non-variable"); + + if (special) + { + op = assigntok; /* a OP= b */ + lvalue = value; + } + + lhs = savestring (tokstr); + readtok (); + value = expassign (); + + if (special) + { + switch (op) + { + case MUL: + lvalue *= value; + break; + case DIV: + lvalue /= value; + break; + case MOD: + lvalue %= value; + break; + case PLUS: + lvalue += value; + break; + case MINUS: + lvalue -= value; + break; + case LSH: + lvalue <<= value; + break; + case RSH: + lvalue >>= value; + break; + case BAND: + lvalue &= value; + break; + case BOR: + lvalue |= value; + break; + default: + evalerror ("bug: bad expassign token %d", assigntok); + break; + } + value = lvalue; + } + + rhs = itos (value); + bind_int_variable (lhs, rhs); + free (rhs); + free (lhs); + free (tokstr); + tokstr = (char *)NULL; /* For freeing on errors. */ + } + return (value); +} + +/* Logical OR. */ +static long +explor () +{ + register long val1, val2; + + val1 = expland (); + + while (curtok == LOR) + { + readtok (); + val2 = expland (); + val1 = val1 || val2; + } + + return (val1); +} + +/* Logical AND. */ +static long +expland () +{ + register long val1, val2; + + val1 = expbor (); + + while (curtok == LAND) + { + readtok (); + val2 = expbor (); + val1 = val1 && val2; + } + + return (val1); +} + +/* Bitwise OR. */ +static long +expbor () +{ + register long val1, val2; + + val1 = expbxor (); + + while (curtok == BOR) + { + readtok (); + val2 = expbxor (); + val1 = val1 | val2; + } + + return (val1); +} + +/* Bitwise XOR. */ +static long +expbxor () +{ + register long val1, val2; + + val1 = expband (); + + while (curtok == BXOR) + { + readtok (); + val2 = expband (); + val1 = val1 ^ val2; + } + + return (val1); +} + +/* Bitwise AND. */ +static long +expband () +{ + register long val1, val2; + + val1 = exp5 (); + + while (curtok == BAND) + { + readtok (); + val2 = exp5 (); + val1 = val1 & val2; + } + + return (val1); +} + +static long +exp5 () +{ + register long val1, val2; + + val1 = exp4 (); + + while ((curtok == EQEQ) || (curtok == NEQ)) + { + int op = curtok; + + readtok (); + val2 = exp4 (); + if (op == EQEQ) + val1 = (val1 == val2); + else if (op == NEQ) + val1 = (val1 != val2); + } + return (val1); +} + +static long +exp4 () +{ + register long val1, val2; + + val1 = expshift (); + while ((curtok == LEQ) || + (curtok == GEQ) || + (curtok == LT) || + (curtok == GT)) + { + int op = curtok; + + readtok (); + val2 = expshift (); + + if (op == LEQ) + val1 = val1 <= val2; + else if (op == GEQ) + val1 = val1 >= val2; + else if (op == LT) + val1 = val1 < val2; + else if (op == GT) + val1 = val1 > val2; + } + return (val1); +} + +/* Left and right shifts. */ +static long +expshift () +{ + register long val1, val2; + + val1 = exp3 (); + + while ((curtok == LSH) || (curtok == RSH)) + { + int op = curtok; + + readtok (); + val2 = exp3 (); + + if (op == LSH) + val1 = val1 << val2; + else + val1 = val1 >> val2; + } + + return (val1); +} + +static long +exp3 () +{ + register long val1, val2; + + val1 = exp2 (); + + while ((curtok == PLUS) || (curtok == MINUS)) + { + int op = curtok; + + readtok (); + val2 = exp2 (); + + if (op == PLUS) + val1 += val2; + else if (op == MINUS) + val1 -= val2; + } + return (val1); +} + +static long +exp2 () +{ + register long val1, val2; + + val1 = exp1 (); + + while ((curtok == MUL) || + (curtok == DIV) || + (curtok == MOD)) + { + int op = curtok; + + readtok (); + + val2 = exp1 (); + + if (((op == DIV) || (op == MOD)) && (val2 == 0)) + evalerror ("division by 0"); + + if (op == MUL) + val1 *= val2; + else if (op == DIV) + val1 /= val2; + else if (op == MOD) + val1 %= val2; + } + return (val1); +} + +static long +exp1 () +{ + register long val; + + if (curtok == NOT) + { + readtok (); + val = !exp1 (); + } + else if (curtok == BNOT) + { + readtok (); + val = ~exp1 (); + } + else + val = exp0 (); + + return (val); +} + +static long +exp0 () +{ + register long val = 0L; + + if (curtok == MINUS) + { + readtok (); + val = - exp0 (); + } + else if (curtok == PLUS) + { + readtok (); + val = exp0 (); + } + else if (curtok == LPAR) + { + readtok (); + val = expassign (); + + if (curtok != RPAR) + evalerror ("missing `)'"); + + /* Skip over closing paren. */ + readtok (); + } + else if ((curtok == NUM) || (curtok == STR)) + { + val = tokval; + readtok (); + } + else + evalerror ("syntax error in expression"); + + return (val); +} + +/* Lexical analyzer/token reader for the expression evaluator. Reads the + next token and puts its value into curtok, while advancing past it. + Updates value of tp. May also set tokval (for number) or tokstr (for + string). */ +static void +readtok () +{ + register char *cp = tp; + register int c, c1; + + /* Skip leading whitespace. */ + c = 0; + while (cp && (c = *cp) && (cr_whitespace (c))) + cp++; + + if (c) + cp++; + + lasttp = tp = cp - 1; + + if (c == '\0') + { + lasttok = curtok; + curtok = 0; + tp = cp; + return; + } + + if (variable_starter (c)) + { + /* Semi-bogus K*rn shell compatibility feature -- variable + names not preceded with a dollar sign are shell variables. */ + char *value; + + while (variable_character (c)) + c = *cp++; + + c = *--cp; + *cp = '\0'; + + if (tokstr) + free (tokstr); + tokstr = savestring (tp); + value = get_string_value (tokstr); + + if (value && *value) + tokval = evalexp (value); + else + tokval = 0; + + *cp = c; + lasttok = curtok; + curtok = STR; + } + else if (digit(c)) + { + while (digit (c) || isletter (c) || c == '#') + c = *cp++; + + c = *--cp; + *cp = '\0'; + + tokval = strlong (tp); + *cp = c; + lasttok = curtok; + curtok = NUM; + } + else + { + c1 = *cp++; + if ((c == EQ) && (c1 == EQ)) + c = EQEQ; + else if ((c == NOT) && (c1 == EQ)) + c = NEQ; + else if ((c == GT) && (c1 == EQ)) + c = GEQ; + else if ((c == LT) && (c1 == EQ)) + c = LEQ; + else if ((c == LT) && (c1 == LT)) + { + if (*cp == '=') /* a <<= b */ + { + assigntok = LSH; + c = OP_ASSIGN; + cp++; + } + else + c = LSH; + } + else if ((c == GT) && (c1 == GT)) + { + if (*cp == '=') + { + assigntok = RSH; /* a >>= b */ + c = OP_ASSIGN; + cp++; + } + else + c = RSH; + } + else if ((c == BAND) && (c1 == BAND)) + c = LAND; + else if ((c == BOR) && (c1 == BOR)) + c = LOR; + else if (c1 == EQ && member(c, "*/%+-&^|")) + { + assigntok = c; /* a OP= b */ + c = OP_ASSIGN; + } + else + cp--; /* `unget' the character */ + lasttok = curtok; + curtok = c; + } + tp = cp; +} + +static void +evalerror (msg) + char *msg; +{ + char *name, *t; + + name = this_command_name; + if (name == 0) + name = get_name_for_error (); + for (t = expression; whitespace (*t); t++) + ; + fprintf (stderr, "%s: %s: %s (remainder of expression is \"%s\")\n", + name, t, + msg, (lasttp && *lasttp) ? lasttp : ""); + longjmp (evalbuf, 1); +} + +/* Convert a string to a long integer, with an arbitrary base. + 0nnn -> base 8 + 0xnn -> base 16 + Anything else: [base#]number (this is from the ISO Pascal spec). */ +static long +strlong (num) + char *num; +{ + register char *s = num; + register int c; + int base = 10; + long val = 0L; + + if (s == NULL || *s == '\0') + return 0L; + + if (*s == '0') + { + s++; + + if (s == NULL || *s == '\0') + return 0L; + + /* Base 16? */ + if (*s == 'x' || *s == 'X') + { + base = 16; + s++; + } + else + base = 8; + } + + for (c = *s++; c; c = *s++) + { + if (c == '#') + { + base = (int)val; + + /* Illegal base specifications are silently reset to base 10. + I don't think that this is a good idea? */ + if (base < 2 || base > 36) + base = 10; + + val = 0L; + } + else + if (isletter(c) || digit(c)) + { + if (digit(c)) + c = digit_value(c); + else if (c >= 'a' && c <= 'z') + c -= 'a' - 10; + else if (c >= 'A' && c <= 'Z') + c -= 'A' - 10; + + if (c >= base) + evalerror ("value too great for base"); + + val = (val * base) + c; + } + else + break; + } + return (val); +} + +#if defined (EXPR_TEST) +char * +xmalloc (n) + int n; +{ + return (malloc (n)); +} + +char * +xrealloc (s, n) + char *s; + int n; +{ + return (realloc (s, n)); +} + +SHELL_VAR *find_variable () { return 0;} +SHELL_VAR *bind_variable () { return 0; } + +char *get_string_value () { return 0; } + +jmp_buf top_level; + +main (argc, argv) + int argc; + char **argv; +{ + register int i; + long v; + + if (setjmp (top_level)) + exit (0); + + for (i = 1; i < argc; i++) + { + v = evalexp (argv[i]); + printf ("'%s' -> %ld\n", argv[i], v); + } + exit (0); +} + +int +builtin_error (format, arg1, arg2, arg3, arg4, arg5) + char *format; +{ + fprintf (stderr, "expr: "); + fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); + fprintf (stderr, "\n"); + return 0; +} + +char * +itos (n) + int n; +{ + return ("42"); +} + +#endif /* EXPR_TEST */ |
