1573 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1573 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* expr.c -- arithmetic expression evaluation. */
 | |
| 
 | |
| /* Copyright (C) 1990-2015 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| 
 | |
| /*
 | |
|  All arithmetic is done as intmax_t 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.
 | |
| 
 | |
| 	"id++", "id--"		[post-increment and post-decrement]
 | |
| 	"++id", "--id"		[pre-increment and pre-decrement]
 | |
| 	"-", "+"		[(unary operators)]
 | |
| 	"!", "~"
 | |
| 	"**"			[(exponentiation)]
 | |
| 	"*", "/", "%"
 | |
| 	"+", "-"
 | |
| 	"<<", ">>"
 | |
| 	"<=", ">=", "<", ">"
 | |
| 	"==", "!="
 | |
| 	"&"
 | |
| 	"^"
 | |
| 	"|"
 | |
| 	"&&"
 | |
| 	"||"
 | |
| 	"expr ? expr : expr"
 | |
| 	"=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="
 | |
| 	,			[comma]
 | |
| 
 | |
|  (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@po.cwru.edu
 | |
| */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include "bashansi.h"
 | |
| 
 | |
| #if defined (HAVE_UNISTD_H)
 | |
| #  ifdef _MINIX
 | |
| #    include <sys/types.h>
 | |
| #  endif
 | |
| #  include <unistd.h>
 | |
| #endif
 | |
| 
 | |
| #include "chartypes.h"
 | |
| #include "bashintl.h"
 | |
| 
 | |
| #include "shell.h"
 | |
| #include "typemax.h"		/* INTMAX_MAX, INTMAX_MIN */
 | |
| 
 | |
| /* 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'))
 | |
| 
 | |
| /* Size be which the expression stack grows when necessary. */
 | |
| #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. */
 | |
| #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 COND	12	/* exp1 ? exp2 : exp3 */
 | |
| #define POWER	13	/* exp1**exp2 */
 | |
| #define PREINC	14	/* ++var */
 | |
| #define PREDEC	15	/* --var */
 | |
| #define POSTINC	16	/* var++ */
 | |
| #define POSTDEC	17	/* var-- */
 | |
| #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	'|'	/* Bitwise OR. */
 | |
| #define BXOR	'^'	/* Bitwise eXclusive OR. */
 | |
| #define BNOT	'~'	/* Bitwise NOT; Two's complement. */
 | |
| #define QUES	'?'
 | |
| #define COL	':'
 | |
| #define COMMA	','
 | |
| 
 | |
| /* This should be the function corresponding to the operator with the
 | |
|    highest precedence. */
 | |
| #define EXP_HIGHEST	expcomma
 | |
| 
 | |
| #ifndef MAX_INT_LEN
 | |
| #  define MAX_INT_LEN 32
 | |
| #endif
 | |
| 
 | |
| struct lvalue
 | |
| {
 | |
|   char *tokstr;		/* possibly-rewritten lvalue if not NULL */
 | |
|   intmax_t tokval;	/* expression evaluated value */
 | |
|   SHELL_VAR *tokvar;	/* variable described by array or var reference */
 | |
|   intmax_t ind;		/* array index if not -1 */
 | |
| };
 | |
| 
 | |
| /* A structure defining a single expression context. */
 | |
| typedef struct {
 | |
|   int curtok, lasttok;
 | |
|   char *expression, *tp, *lasttp;
 | |
|   intmax_t tokval;
 | |
|   char *tokstr;
 | |
|   int noeval;
 | |
|   struct lvalue lval;
 | |
| } EXPR_CONTEXT;
 | |
| 
 | |
| static char	*expression;	/* The current expression */
 | |
| static char	*tp;		/* token lexical position */
 | |
| static char	*lasttp;	/* pointer to last token position */
 | |
| static int	curtok;		/* the current token */
 | |
| static int	lasttok;	/* the previous token */
 | |
| static int	assigntok;	/* the OP in OP= */
 | |
| static char	*tokstr;	/* current token string */
 | |
| static intmax_t	tokval;		/* current token value */
 | |
| static int	noeval;		/* set to 1 if no assignment to be done */
 | |
| static procenv_t evalbuf;
 | |
| 
 | |
| static struct lvalue curlval = {0, 0, 0, -1};
 | |
| static struct lvalue lastlval = {0, 0, 0, -1};
 | |
| 
 | |
| static int	_is_arithop __P((int));
 | |
| static void	readtok __P((void));	/* lexical analyzer */
 | |
| 
 | |
| static void	init_lvalue __P((struct lvalue *));
 | |
| static struct lvalue *alloc_lvalue __P((void));
 | |
| static void	free_lvalue __P((struct lvalue *));
 | |
| 
 | |
| static intmax_t	expr_streval __P((char *, int, struct lvalue *));
 | |
| static intmax_t	strlong __P((char *));
 | |
| static void	evalerror __P((const char *));
 | |
| 
 | |
| static void	pushexp __P((void));
 | |
| static void	popexp __P((void));
 | |
| static void	expr_unwind __P((void));
 | |
| static void	expr_bind_variable __P((char *, char *));
 | |
| #if defined (ARRAY_VARS)
 | |
| static void	expr_bind_array_element __P((char *, arrayind_t, char *));
 | |
| #endif
 | |
| 
 | |
| static intmax_t subexpr __P((char *));
 | |
| 
 | |
| static intmax_t	expcomma __P((void));
 | |
| static intmax_t expassign __P((void));
 | |
| static intmax_t	expcond __P((void));
 | |
| static intmax_t explor __P((void));
 | |
| static intmax_t expland __P((void));
 | |
| static intmax_t	expbor __P((void));
 | |
| static intmax_t	expbxor __P((void));
 | |
| static intmax_t	expband __P((void));
 | |
| static intmax_t exp5 __P((void));
 | |
| static intmax_t exp4 __P((void));
 | |
| static intmax_t expshift __P((void));
 | |
| static intmax_t exp3 __P((void));
 | |
| static intmax_t exp2 __P((void));
 | |
| static intmax_t	exppower __P((void));
 | |
| static intmax_t exp1 __P((void));
 | |
| static intmax_t exp0 __P((void));
 | |
| 
 | |
| /* Global var which contains the stack of expression contexts. */
 | |
| static EXPR_CONTEXT **expr_stack;
 | |
| static int expr_depth;		   /* Location in the stack. */
 | |
| static int expr_stack_size;	   /* Number of slots already allocated. */
 | |
| 
 | |
| extern char *this_command_name;
 | |
| extern int unbound_vars_is_error, last_command_exit_value;
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
| extern const char * const bash_badsub_errmsg;
 | |
| #endif
 | |
| 
 | |
| #define SAVETOK(X) \
 | |
|   do { \
 | |
|     (X)->curtok = curtok; \
 | |
|     (X)->lasttok = lasttok; \
 | |
|     (X)->tp = tp; \
 | |
|     (X)->lasttp = lasttp; \
 | |
|     (X)->tokval = tokval; \
 | |
|     (X)->tokstr = tokstr; \
 | |
|     (X)->noeval = noeval; \
 | |
|     (X)->lval = curlval; \
 | |
|   } while (0)
 | |
| 
 | |
| #define RESTORETOK(X) \
 | |
|   do { \
 | |
|     curtok = (X)->curtok; \
 | |
|     lasttok = (X)->lasttok; \
 | |
|     tp = (X)->tp; \
 | |
|     lasttp = (X)->lasttp; \
 | |
|     tokval = (X)->tokval; \
 | |
|     tokstr = (X)->tokstr; \
 | |
|     noeval = (X)->noeval; \
 | |
|     curlval = (X)->lval; \
 | |
|   } while (0)
 | |
| 
 | |
| /* Push and save away the contents of the globals describing the
 | |
|    current expression context. */
 | |
| static void
 | |
| pushexp ()
 | |
| {
 | |
|   EXPR_CONTEXT *context;
 | |
| 
 | |
|   if (expr_depth >= MAX_EXPR_RECURSION_LEVEL)
 | |
|     evalerror (_("expression recursion level exceeded"));
 | |
| 
 | |
|   if (expr_depth >= expr_stack_size)
 | |
|     {
 | |
|       expr_stack_size += EXPR_STACK_GROW_SIZE;
 | |
|       expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *));
 | |
|     }
 | |
| 
 | |
|   context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT));
 | |
| 
 | |
|   context->expression = expression;
 | |
|   SAVETOK(context);
 | |
| 
 | |
|   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];
 | |
| 
 | |
|   expression = context->expression;
 | |
|   RESTORETOK (context);
 | |
| 
 | |
|   free (context);
 | |
| }
 | |
| 
 | |
| static void
 | |
| expr_unwind ()
 | |
| {
 | |
|   while (--expr_depth > 0)
 | |
|     {
 | |
|       if (expr_stack[expr_depth]->tokstr)
 | |
| 	free (expr_stack[expr_depth]->tokstr);
 | |
| 
 | |
|       if (expr_stack[expr_depth]->expression)
 | |
| 	free (expr_stack[expr_depth]->expression);
 | |
| 
 | |
|       free (expr_stack[expr_depth]);
 | |
|     }
 | |
|   free (expr_stack[expr_depth]);	/* free the allocated EXPR_CONTEXT */
 | |
| 
 | |
|   noeval = 0;	/* XXX */
 | |
| }
 | |
| 
 | |
| static void
 | |
| expr_bind_variable (lhs, rhs)
 | |
|      char *lhs, *rhs;
 | |
| {
 | |
|   SHELL_VAR *v;
 | |
| 
 | |
|   v = bind_int_variable (lhs, rhs);
 | |
|   if (v && (readonly_p (v) || noassign_p (v)))
 | |
|     sh_longjmp (evalbuf, 1);	/* variable assignment error */
 | |
|   stupidly_hack_special_variables (lhs);
 | |
| }
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
| /* Rewrite tok, which is of the form vname[expression], to vname[ind], where
 | |
|    IND is the already-calculated value of expression. */
 | |
| static void
 | |
| expr_bind_array_element (tok, ind, rhs)
 | |
|      char *tok;
 | |
|      arrayind_t ind;
 | |
|      char *rhs;
 | |
| {
 | |
|   char *lhs, *vname;
 | |
|   size_t llen;
 | |
|   char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr;
 | |
| 
 | |
|   istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0);
 | |
|   vname = array_variable_name (tok, (char **)NULL, (int *)NULL);
 | |
| 
 | |
|   llen = strlen (vname) + sizeof (ibuf) + 3;
 | |
|   lhs = xmalloc (llen);
 | |
| 
 | |
|   sprintf (lhs, "%s[%s]", vname, istr);		/* XXX */
 | |
|   
 | |
| /*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/
 | |
|   expr_bind_variable (lhs, rhs);
 | |
|   free (vname);
 | |
|   free (lhs);
 | |
| }
 | |
| #endif /* ARRAY_VARS */
 | |
| 
 | |
| /* Evaluate EXPR, and return the arithmetic result.  If VALIDP is
 | |
|    non-null, a zero is stored into the location to which it points
 | |
|    if the expression is invalid, non-zero otherwise.  If a non-zero
 | |
|    value is returned in *VALIDP, the return value of evalexp() may
 | |
|    be used.
 | |
| 
 | |
|    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. */
 | |
| intmax_t
 | |
| evalexp (expr, validp)
 | |
|      char *expr;
 | |
|      int *validp;
 | |
| {
 | |
|   intmax_t val;
 | |
|   int c;
 | |
|   procenv_t oevalbuf;
 | |
| 
 | |
|   val = 0;
 | |
|   noeval = 0;
 | |
| 
 | |
|   FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf));
 | |
| 
 | |
|   c = setjmp_nosigs (evalbuf);
 | |
| 
 | |
|   if (c)
 | |
|     {
 | |
|       FREE (tokstr);
 | |
|       FREE (expression);
 | |
|       tokstr = expression = (char *)NULL;
 | |
| 
 | |
|       expr_unwind ();
 | |
| 
 | |
|       if (validp)
 | |
| 	*validp = 0;
 | |
|       return (0);
 | |
|     }
 | |
| 
 | |
|   val = subexpr (expr);
 | |
| 
 | |
|   if (validp)
 | |
|     *validp = 1;
 | |
| 
 | |
|   FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf));
 | |
| 
 | |
|   return (val);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| subexpr (expr)
 | |
|      char *expr;
 | |
| {
 | |
|   intmax_t val;
 | |
|   char *p;
 | |
| 
 | |
|   for (p = expr; p && *p && cr_whitespace (*p); p++)
 | |
|     ;
 | |
| 
 | |
|   if (p == NULL || *p == '\0')
 | |
|     return (0);
 | |
| 
 | |
|   pushexp ();
 | |
|   expression = savestring (expr);
 | |
|   tp = expression;
 | |
| 
 | |
|   curtok = lasttok = 0;
 | |
|   tokstr = (char *)NULL;
 | |
|   tokval = 0;
 | |
|   init_lvalue (&curlval);
 | |
|   lastlval = curlval;
 | |
| 
 | |
|   readtok ();
 | |
| 
 | |
|   val = EXP_HIGHEST ();
 | |
| 
 | |
|   if (curtok != 0)
 | |
|     evalerror (_("syntax error in expression"));
 | |
| 
 | |
|   FREE (tokstr);
 | |
|   FREE (expression);
 | |
| 
 | |
|   popexp ();
 | |
| 
 | |
|   return val;
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| expcomma ()
 | |
| {
 | |
|   register intmax_t value;
 | |
| 
 | |
|   value = expassign ();
 | |
|   while (curtok == COMMA)
 | |
|     {
 | |
|       readtok ();
 | |
|       value = expassign ();
 | |
|     }
 | |
| 
 | |
|   return value;
 | |
| }
 | |
|   
 | |
| static intmax_t
 | |
| expassign ()
 | |
| {
 | |
|   register intmax_t value;
 | |
|   char *lhs, *rhs;
 | |
|   arrayind_t lind;
 | |
| #if defined (HAVE_IMAXDIV)
 | |
|   imaxdiv_t idiv;
 | |
| #endif
 | |
| 
 | |
|   value = expcond ();
 | |
|   if (curtok == EQ || curtok == OP_ASSIGN)
 | |
|     {
 | |
|       int special, op;
 | |
|       intmax_t lvalue;
 | |
| 
 | |
|       special = curtok == OP_ASSIGN;
 | |
| 
 | |
|       if (lasttok != STR)
 | |
| 	evalerror (_("attempted assignment to non-variable"));
 | |
| 
 | |
|       if (special)
 | |
| 	{
 | |
| 	  op = assigntok;		/* a OP= b */
 | |
| 	  lvalue = value;
 | |
| 	}
 | |
| 
 | |
|       /* XXX - watch out for pointer aliasing issues here */
 | |
|       lhs = savestring (tokstr);
 | |
|       /* save ind in case rhs is string var and evaluation overwrites it */
 | |
|       lind = curlval.ind;
 | |
|       readtok ();
 | |
|       value = expassign ();
 | |
| 
 | |
|       if (special)
 | |
| 	{
 | |
| 	  if ((op == DIV || op == MOD) && value == 0)
 | |
| 	    {
 | |
| 	      if (noeval == 0)
 | |
| 		evalerror (_("division by 0"));
 | |
| 	      else
 | |
| 	        value = 1;
 | |
| 	    }
 | |
| 
 | |
| 	  switch (op)
 | |
| 	    {
 | |
| 	    case MUL:
 | |
| 	      lvalue *= value;
 | |
| 	      break;
 | |
| 	    case DIV:
 | |
| 	    case MOD:
 | |
| 	      if (lvalue == INTMAX_MIN && value == -1)
 | |
| 		lvalue = (op == DIV) ? INTMAX_MIN : 0;
 | |
| 	      else
 | |
| #if HAVE_IMAXDIV
 | |
| 		{
 | |
| 		  idiv = imaxdiv (lvalue, value);
 | |
| 		  lvalue = (op == DIV) ? idiv.quot : idiv.rem;
 | |
| 		}
 | |
| #else
 | |
| 	        lvalue = (op == DIV) ? lvalue / value : lvalue % value;
 | |
| #endif
 | |
| 	      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;
 | |
| 	    case BXOR:
 | |
| 	      lvalue ^= value;
 | |
| 	      break;
 | |
| 	    default:
 | |
| 	      free (lhs);
 | |
| 	      evalerror (_("bug: bad expassign token"));
 | |
| 	      break;
 | |
| 	    }
 | |
| 	  value = lvalue;
 | |
| 	}
 | |
| 
 | |
|       rhs = itos (value);
 | |
|       if (noeval == 0)
 | |
| 	{
 | |
| #if defined (ARRAY_VARS)
 | |
| 	  if (lind != -1)
 | |
| 	    expr_bind_array_element (lhs, lind, rhs);
 | |
| 	  else
 | |
| #endif
 | |
| 	    expr_bind_variable (lhs, rhs);
 | |
| 	}
 | |
|       if (curlval.tokstr && curlval.tokstr == tokstr)
 | |
| 	init_lvalue (&curlval);
 | |
| 
 | |
|       free (rhs);
 | |
|       free (lhs);
 | |
|       FREE (tokstr);
 | |
|       tokstr = (char *)NULL;		/* For freeing on errors. */
 | |
|     }
 | |
| 
 | |
|   return (value);
 | |
| }
 | |
| 
 | |
| /* Conditional expression (expr?expr:expr) */
 | |
| static intmax_t
 | |
| expcond ()
 | |
| {
 | |
|   intmax_t cval, val1, val2, rval;
 | |
|   int set_noeval;
 | |
| 
 | |
|   set_noeval = 0;
 | |
|   rval = cval = explor ();
 | |
|   if (curtok == QUES)		/* found conditional expr */
 | |
|     {
 | |
|       if (cval == 0)
 | |
| 	{
 | |
| 	  set_noeval = 1;
 | |
| 	  noeval++;
 | |
| 	}
 | |
| 
 | |
|       readtok ();
 | |
|       if (curtok == 0 || curtok == COL)
 | |
| 	evalerror (_("expression expected"));
 | |
| 
 | |
|       val1 = EXP_HIGHEST ();
 | |
| 
 | |
|       if (set_noeval)
 | |
| 	noeval--;
 | |
|       if (curtok != COL)
 | |
| 	evalerror (_("`:' expected for conditional expression"));
 | |
| 
 | |
|       set_noeval = 0;
 | |
|       if (cval)
 | |
|  	{
 | |
|  	  set_noeval = 1;
 | |
| 	  noeval++;
 | |
|  	}
 | |
| 
 | |
|       readtok ();
 | |
|       if (curtok == 0)
 | |
| 	evalerror (_("expression expected"));
 | |
|       val2 = expcond ();
 | |
| 
 | |
|       if (set_noeval)
 | |
| 	noeval--;
 | |
|       rval = cval ? val1 : val2;
 | |
|       lasttok = COND;
 | |
|     }
 | |
|   return rval;
 | |
| }
 | |
| 
 | |
| /* Logical OR. */
 | |
| static intmax_t
 | |
| explor ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
|   int set_noeval;
 | |
| 
 | |
|   val1 = expland ();
 | |
| 
 | |
|   while (curtok == LOR)
 | |
|     {
 | |
|       set_noeval = 0;
 | |
|       if (val1 != 0)
 | |
| 	{
 | |
| 	  noeval++;
 | |
| 	  set_noeval = 1;
 | |
| 	}
 | |
|       readtok ();
 | |
|       val2 = expland ();
 | |
|       if (set_noeval)
 | |
| 	noeval--;
 | |
|       val1 = val1 || val2;
 | |
|       lasttok = LOR;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| /* Logical AND. */
 | |
| static intmax_t
 | |
| expland ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
|   int set_noeval;
 | |
| 
 | |
|   val1 = expbor ();
 | |
| 
 | |
|   while (curtok == LAND)
 | |
|     {
 | |
|       set_noeval = 0;
 | |
|       if (val1 == 0)
 | |
| 	{
 | |
| 	  set_noeval = 1;
 | |
| 	  noeval++;
 | |
| 	}
 | |
|       readtok ();
 | |
|       val2 = expbor ();
 | |
|       if (set_noeval)
 | |
| 	noeval--;
 | |
|       val1 = val1 && val2;
 | |
|       lasttok = LAND;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| /* Bitwise OR. */
 | |
| static intmax_t
 | |
| expbor ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
| 
 | |
|   val1 = expbxor ();
 | |
| 
 | |
|   while (curtok == BOR)
 | |
|     {
 | |
|       readtok ();
 | |
|       val2 = expbxor ();
 | |
|       val1 = val1 | val2;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| /* Bitwise XOR. */
 | |
| static intmax_t
 | |
| expbxor ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
| 
 | |
|   val1 = expband ();
 | |
| 
 | |
|   while (curtok == BXOR)
 | |
|     {
 | |
|       readtok ();
 | |
|       val2 = expband ();
 | |
|       val1 = val1 ^ val2;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| /* Bitwise AND. */
 | |
| static intmax_t
 | |
| expband ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
| 
 | |
|   val1 = exp5 ();
 | |
| 
 | |
|   while (curtok == BAND)
 | |
|     {
 | |
|       readtok ();
 | |
|       val2 = exp5 ();
 | |
|       val1 = val1 & val2;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp5 ()
 | |
| {
 | |
|   register intmax_t 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);
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp4 ()
 | |
| {
 | |
|   register intmax_t 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			/* (op == GT) */
 | |
| 	val1 = val1 > val2;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| /* Left and right shifts. */
 | |
| static intmax_t
 | |
| expshift ()
 | |
| {
 | |
|   register intmax_t 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;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
| 
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp3 ()
 | |
| {
 | |
|   register intmax_t 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;
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp2 ()
 | |
| {
 | |
|   register intmax_t val1, val2;
 | |
| #if defined (HAVE_IMAXDIV)
 | |
|   imaxdiv_t idiv;
 | |
| #endif
 | |
| 
 | |
|   val1 = exppower ();
 | |
| 
 | |
|   while ((curtok == MUL) ||
 | |
| 	 (curtok == DIV) ||
 | |
| 	 (curtok == MOD))
 | |
|     {
 | |
|       int op = curtok;
 | |
|       char *stp, *sltp;
 | |
| 
 | |
|       stp = tp;
 | |
|       readtok ();
 | |
| 
 | |
|       val2 = exppower ();
 | |
| 
 | |
|       /* Handle division by 0 and twos-complement arithmetic overflow */
 | |
|       if (((op == DIV) || (op == MOD)) && (val2 == 0))
 | |
| 	{
 | |
| 	  if (noeval == 0)
 | |
| 	    {
 | |
| 	      sltp = lasttp;
 | |
| 	      lasttp = stp;
 | |
| 	      while (lasttp && *lasttp && whitespace (*lasttp))
 | |
| 		lasttp++;
 | |
| 	      evalerror (_("division by 0"));
 | |
| 	      lasttp = sltp;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    val2 = 1;
 | |
| 	}
 | |
|       else if (op == MOD && val1 == INTMAX_MIN && val2 == -1)
 | |
| 	{
 | |
| 	  val1 = 0;
 | |
| 	  continue;
 | |
| 	}
 | |
|       else if (op == DIV && val1 == INTMAX_MIN && val2 == -1)
 | |
| 	val2 = 1;
 | |
| 
 | |
|       if (op == MUL)
 | |
| 	val1 *= val2;
 | |
|       else if (op == DIV || op == MOD)
 | |
| #if defined (HAVE_IMAXDIV)
 | |
| 	{
 | |
| 	  idiv = imaxdiv (val1, val2);
 | |
| 	  val1 = (op == DIV) ? idiv.quot : idiv.rem;
 | |
| 	}
 | |
| #else
 | |
| 	val1 = (op == DIV) ? val1 / val2 : val1 % val2;
 | |
| #endif
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| ipow (base, exp)
 | |
|      intmax_t base, exp;
 | |
| {
 | |
|   intmax_t result;
 | |
| 
 | |
|   result = 1;
 | |
|   while (exp)
 | |
|     {
 | |
|       if (exp & 1)
 | |
| 	result *= base;
 | |
|       exp >>= 1;
 | |
|       base *= base;
 | |
|     }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exppower ()
 | |
| {
 | |
|   register intmax_t val1, val2, c;
 | |
| 
 | |
|   val1 = exp1 ();
 | |
|   while (curtok == POWER)
 | |
|     {
 | |
|       readtok ();
 | |
|       val2 = exppower ();	/* exponentiation is right-associative */
 | |
|       lasttok = NUM;
 | |
|       if (val2 == 0)
 | |
| 	return (1);
 | |
|       if (val2 < 0)
 | |
| 	evalerror (_("exponent less than 0"));
 | |
|       val1 = ipow (val1, val2);
 | |
|     }
 | |
|   return (val1);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp1 ()
 | |
| {
 | |
|   register intmax_t val;
 | |
| 
 | |
|   if (curtok == NOT)
 | |
|     {
 | |
|       readtok ();
 | |
|       val = !exp1 ();
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   else if (curtok == BNOT)
 | |
|     {
 | |
|       readtok ();
 | |
|       val = ~exp1 ();
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   else if (curtok == MINUS)
 | |
|     {
 | |
|       readtok ();
 | |
|       val = - exp1 ();
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   else if (curtok == PLUS)
 | |
|     {
 | |
|       readtok ();
 | |
|       val = exp1 ();
 | |
|       lasttok = NUM;
 | |
|     }
 | |
|   else
 | |
|     val = exp0 ();
 | |
| 
 | |
|   return (val);
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| exp0 ()
 | |
| {
 | |
|   register intmax_t val = 0, v2;
 | |
|   char *vincdec;
 | |
|   int stok;
 | |
|   EXPR_CONTEXT ec;
 | |
| 
 | |
|   /* XXX - might need additional logic here to decide whether or not
 | |
| 	   pre-increment or pre-decrement is legal at this point. */
 | |
|   if (curtok == PREINC || curtok == PREDEC)
 | |
|     {
 | |
|       stok = lasttok = curtok;
 | |
|       readtok ();
 | |
|       if (curtok != STR)
 | |
| 	/* readtok() catches this */
 | |
| 	evalerror (_("identifier expected after pre-increment or pre-decrement"));
 | |
| 
 | |
|       v2 = tokval + ((stok == PREINC) ? 1 : -1);
 | |
|       vincdec = itos (v2);
 | |
|       if (noeval == 0)
 | |
| 	{
 | |
| #if defined (ARRAY_VARS)
 | |
| 	  if (curlval.ind != -1)
 | |
| 	    expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
 | |
| 	  else
 | |
| #endif
 | |
| 	    expr_bind_variable (tokstr, vincdec);
 | |
| 	}
 | |
|       free (vincdec);
 | |
|       val = v2;
 | |
| 
 | |
|       curtok = NUM;	/* make sure --x=7 is flagged as an error */
 | |
|       readtok ();
 | |
|     }
 | |
|   else if (curtok == LPAR)
 | |
|     {
 | |
|       /* XXX - save curlval here?  Or entire expression context? */
 | |
|       readtok ();
 | |
|       val = EXP_HIGHEST ();
 | |
| 
 | |
|       if (curtok != RPAR) /* ( */
 | |
| 	evalerror (_("missing `)'"));
 | |
| 
 | |
|       /* Skip over closing paren. */
 | |
|       readtok ();
 | |
|     }
 | |
|   else if ((curtok == NUM) || (curtok == STR))
 | |
|     {
 | |
|       val = tokval;
 | |
|       if (curtok == STR)
 | |
| 	{
 | |
| 	  SAVETOK (&ec);
 | |
| 	  tokstr = (char *)NULL;	/* keep it from being freed */
 | |
|           noeval = 1;
 | |
|           readtok ();
 | |
|           stok = curtok;
 | |
| 
 | |
| 	  /* post-increment or post-decrement */
 | |
|  	  if (stok == POSTINC || stok == POSTDEC)
 | |
|  	    {
 | |
|  	      /* restore certain portions of EC */
 | |
|  	      tokstr = ec.tokstr;
 | |
|  	      noeval = ec.noeval;
 | |
|  	      curlval = ec.lval;
 | |
|  	      lasttok = STR;	/* ec.curtok */
 | |
| 
 | |
| 	      v2 = val + ((stok == POSTINC) ? 1 : -1);
 | |
| 	      vincdec = itos (v2);
 | |
| 	      if (noeval == 0)
 | |
| 		{
 | |
| #if defined (ARRAY_VARS)
 | |
| 		  if (curlval.ind != -1)
 | |
| 		    expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
 | |
| 		  else
 | |
| #endif
 | |
| 		    expr_bind_variable (tokstr, vincdec);
 | |
| 		}
 | |
| 	      free (vincdec);
 | |
| 	      curtok = NUM;	/* make sure x++=7 is flagged as an error */
 | |
|  	    }
 | |
|  	  else
 | |
|  	    {
 | |
| 	      /* XXX - watch out for pointer aliasing issues here */
 | |
| 	      if (stok == STR)	/* free new tokstr before old one is restored */
 | |
| 		FREE (tokstr);
 | |
| 	      RESTORETOK (&ec);
 | |
|  	    }
 | |
| 	}
 | |
| 	  
 | |
|       readtok ();
 | |
|     }
 | |
|   else
 | |
|     evalerror (_("syntax error: operand expected"));
 | |
| 
 | |
|   return (val);
 | |
| }
 | |
| 
 | |
| static void
 | |
| init_lvalue (lv)
 | |
|      struct lvalue *lv;
 | |
| {
 | |
|   lv->tokstr = 0;
 | |
|   lv->tokvar = 0;
 | |
|   lv->tokval = lv->ind = -1;
 | |
| }
 | |
| 
 | |
| static struct lvalue *
 | |
| alloc_lvalue ()
 | |
| {
 | |
|   struct lvalue *lv;
 | |
| 
 | |
|   lv = xmalloc (sizeof (struct lvalue));
 | |
|   init_lvalue (lv);
 | |
|   return (lv);
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_lvalue (lv)
 | |
|      struct lvalue *lv;
 | |
| {
 | |
|   free (lv);		/* should be inlined */
 | |
| }
 | |
| 
 | |
| static intmax_t
 | |
| expr_streval (tok, e, lvalue)
 | |
|      char *tok;
 | |
|      int e;
 | |
|      struct lvalue *lvalue;
 | |
| {
 | |
|   SHELL_VAR *v;
 | |
|   char *value;
 | |
|   intmax_t tval;
 | |
| #if defined (ARRAY_VARS)
 | |
|   arrayind_t ind;
 | |
| #endif
 | |
| 
 | |
| /*itrace("expr_streval: %s: noeval = %d", tok, noeval);*/
 | |
|   /* If we are suppressing evaluation, just short-circuit here instead of
 | |
|      going through the rest of the evaluator. */
 | |
|   if (noeval)
 | |
|     return (0);
 | |
| 
 | |
|   /* [[[[[ */
 | |
| #if defined (ARRAY_VARS)
 | |
|   v = (e == ']') ? array_variable_part (tok, (char **)0, (int *)0) : find_variable (tok);
 | |
| #else
 | |
|   v = find_variable (tok);
 | |
| #endif
 | |
| 
 | |
|   if ((v == 0 || invisible_p (v)) && unbound_vars_is_error)
 | |
|     {
 | |
| #if defined (ARRAY_VARS)
 | |
|       value = (e == ']') ? array_variable_name (tok, (char **)0, (int *)0) : tok;
 | |
| #else
 | |
|       value = tok;
 | |
| #endif
 | |
| 
 | |
|       last_command_exit_value = EXECUTION_FAILURE;
 | |
|       err_unboundvar (value);
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
|       if (e == ']')
 | |
| 	FREE (value);	/* array_variable_name returns new memory */
 | |
| #endif
 | |
| 
 | |
|       if (no_longjmp_on_fatal_error && interactive_shell)
 | |
| 	sh_longjmp (evalbuf, 1);
 | |
| 
 | |
|       if (interactive_shell)
 | |
| 	{
 | |
| 	  expr_unwind ();
 | |
| 	  top_level_cleanup ();
 | |
| 	  jump_to_top_level (DISCARD);
 | |
| 	}
 | |
|       else
 | |
| 	jump_to_top_level (FORCE_EOF);
 | |
|     }
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
|   ind = -1;
 | |
|   /* Second argument of 0 to get_array_value means that we don't allow
 | |
|      references like array[@].  In this case, get_array_value is just
 | |
|      like get_variable_value in that it does not return newly-allocated
 | |
|      memory or quote the results. */
 | |
|   value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v);
 | |
| #else
 | |
|   value = get_variable_value (v);
 | |
| #endif
 | |
| 
 | |
|   tval = (value && *value) ? subexpr (value) : 0;
 | |
| 
 | |
|   if (lvalue)
 | |
|     {
 | |
|       lvalue->tokstr = tok;	/* XXX */
 | |
|       lvalue->tokval = tval;
 | |
|       lvalue->tokvar = v;	/* XXX */
 | |
| #if defined (ARRAY_VARS)
 | |
|       lvalue->ind = ind;
 | |
| #else
 | |
|       lvalue->ind = -1;
 | |
| #endif
 | |
|     }
 | |
| 	  
 | |
|   return (tval);
 | |
| }
 | |
| 
 | |
| static int
 | |
| _is_multiop (c)
 | |
|      int c;
 | |
| {
 | |
|   switch (c)
 | |
|     {
 | |
|     case EQEQ:
 | |
|     case NEQ:
 | |
|     case LEQ:
 | |
|     case GEQ:
 | |
|     case LAND:
 | |
|     case LOR:
 | |
|     case LSH:
 | |
|     case RSH:
 | |
|     case OP_ASSIGN:
 | |
|     case COND:
 | |
|     case POWER:
 | |
|     case PREINC:
 | |
|     case PREDEC:
 | |
|     case POSTINC:
 | |
|     case POSTDEC:
 | |
|       return 1;
 | |
|     default:
 | |
|       return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int
 | |
| _is_arithop (c)
 | |
|      int c;
 | |
| {
 | |
|   switch (c)
 | |
|     {
 | |
|     case EQ:
 | |
|     case GT:
 | |
|     case LT:
 | |
|     case PLUS:
 | |
|     case MINUS:
 | |
|     case MUL:
 | |
|     case DIV:
 | |
|     case MOD:
 | |
|     case NOT:
 | |
|     case LPAR:
 | |
|     case RPAR:
 | |
|     case BAND:
 | |
|     case BOR:
 | |
|     case BXOR:
 | |
|     case BNOT:
 | |
|       return 1;		/* operator tokens */
 | |
|     case QUES:
 | |
|     case COL:
 | |
|     case COMMA:
 | |
|       return 1;		/* questionable */
 | |
|     default:
 | |
|       return 0;		/* anything else is invalid */
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* 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, *xp;
 | |
|   register unsigned char c, c1;
 | |
|   register int e;
 | |
|   struct lvalue lval;
 | |
| 
 | |
|   /* Skip leading whitespace. */
 | |
|   cp = tp;
 | |
|   c = e = 0;
 | |
|   while (cp && (c = *cp) && (cr_whitespace (c)))
 | |
|     cp++;
 | |
| 
 | |
|   if (c)
 | |
|     cp++;
 | |
| 
 | |
|   if (c == '\0')
 | |
|     {
 | |
|       lasttok = curtok;
 | |
|       curtok = 0;
 | |
|       tp = cp;
 | |
|       return;
 | |
|     }
 | |
|   lasttp = tp = cp - 1;
 | |
| 
 | |
|   if (legal_variable_starter (c))
 | |
|     {
 | |
|       /* variable names not preceded with a dollar sign are shell variables. */
 | |
|       char *savecp;
 | |
|       EXPR_CONTEXT ec;
 | |
|       int peektok;
 | |
| 
 | |
|       while (legal_variable_char (c))
 | |
| 	c = *cp++;
 | |
| 
 | |
|       c = *--cp;
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
|       if (c == '[')
 | |
| 	{
 | |
| 	  e = skipsubscript (cp, 0, 0);
 | |
| 	  if (cp[e] == ']')
 | |
| 	    {
 | |
| 	      cp += e + 1;
 | |
| 	      c = *cp;
 | |
| 	      e = ']';
 | |
| 	    }
 | |
| 	  else
 | |
| 	    evalerror (bash_badsub_errmsg);
 | |
| 	}
 | |
| #endif /* ARRAY_VARS */
 | |
| 
 | |
|       *cp = '\0';
 | |
|       /* XXX - watch out for pointer aliasing issues here */
 | |
|       if (curlval.tokstr && curlval.tokstr == tokstr)
 | |
| 	init_lvalue (&curlval);
 | |
| 
 | |
|       FREE (tokstr);
 | |
|       tokstr = savestring (tp);
 | |
|       *cp = c;
 | |
| 
 | |
|       /* XXX - make peektok part of saved token state? */
 | |
|       SAVETOK (&ec);
 | |
|       tokstr = (char *)NULL;	/* keep it from being freed */
 | |
|       tp = savecp = cp;
 | |
|       noeval = 1;
 | |
|       curtok = STR;
 | |
|       readtok ();
 | |
|       peektok = curtok;
 | |
|       if (peektok == STR)	/* free new tokstr before old one is restored */
 | |
| 	FREE (tokstr);
 | |
|       RESTORETOK (&ec);
 | |
|       cp = savecp;
 | |
| 
 | |
|       /* The tests for PREINC and PREDEC aren't strictly correct, but they
 | |
| 	 preserve old behavior if a construct like --x=9 is given. */
 | |
|       if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ)
 | |
|         {
 | |
|           lastlval = curlval;
 | |
| 	  tokval = expr_streval (tokstr, e, &curlval);
 | |
|         }
 | |
|       else
 | |
| 	tokval = 0;
 | |
| 
 | |
|       lasttok = curtok;
 | |
|       curtok = STR;
 | |
|     }
 | |
|   else if (DIGIT(c))
 | |
|     {
 | |
|       while (ISALNUM (c) || c == '#' || 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 ((c == '*') && (c1 == '*'))
 | |
| 	c = POWER;
 | |
|       else if ((c == '-' || c == '+') && c1 == c && curtok == STR)
 | |
| 	c = (c == '-') ? POSTDEC : POSTINC;
 | |
|       else if ((c == '-' || c == '+') && c1 == c)
 | |
| 	{
 | |
| 	  /* Quickly scan forward to see if this is followed by optional
 | |
| 	     whitespace and an identifier. */
 | |
| 	  xp = cp;
 | |
| 	  while (xp && *xp && cr_whitespace (*xp))
 | |
| 	    xp++;
 | |
| 	  if (legal_variable_starter ((unsigned char)*xp))
 | |
| 	    c = (c == '-') ? PREDEC : PREINC;
 | |
| 	  else
 | |
| 	    cp--;	/* not preinc or predec, so unget the character */
 | |
| 	}
 | |
|       else if (c1 == EQ && member (c, "*/%+-&^|"))
 | |
| 	{
 | |
| 	  assigntok = c;	/* a OP= b */
 | |
| 	  c = OP_ASSIGN;
 | |
| 	}
 | |
|       else if (_is_arithop (c) == 0)
 | |
| 	{
 | |
| 	  cp--;
 | |
| 	  /* use curtok, since it hasn't been copied to lasttok yet */
 | |
| 	  if (curtok == 0 || _is_arithop (curtok) || _is_multiop (curtok))
 | |
| 	    evalerror (_("syntax error: operand expected"));
 | |
| 	  else
 | |
| 	    evalerror (_("syntax error: invalid arithmetic operator"));
 | |
| 	}
 | |
|       else
 | |
| 	cp--;			/* `unget' the character */
 | |
| 
 | |
|       /* Should check here to make sure that the current character is one
 | |
| 	 of the recognized operators and flag an error if not.  Could create
 | |
| 	 a character map the first time through and check it on subsequent
 | |
| 	 calls. */
 | |
|       lasttok = curtok;
 | |
|       curtok = c;
 | |
|     }
 | |
|   tp = cp;
 | |
| }
 | |
| 
 | |
| static void
 | |
| evalerror (msg)
 | |
|      const char *msg;
 | |
| {
 | |
|   char *name, *t;
 | |
| 
 | |
|   name = this_command_name;
 | |
|   for (t = expression; whitespace (*t); t++)
 | |
|     ;
 | |
|   internal_error (_("%s%s%s: %s (error token is \"%s\")"),
 | |
| 		   name ? name : "", name ? ": " : "", t,
 | |
| 		   msg, (lasttp && *lasttp) ? lasttp : "");
 | |
|   sh_longjmp (evalbuf, 1);
 | |
| }
 | |
| 
 | |
| /* Convert a string to an intmax_t integer, with an arbitrary base.
 | |
|    0nnn -> base 8
 | |
|    0[Xx]nn -> base 16
 | |
|    Anything else: [base#]number (this is implemented to match ksh93)
 | |
| 
 | |
|    Base may be >=2 and <=64.  If base is <= 36, the numbers are drawn
 | |
|    from [0-9][a-zA-Z], and lowercase and uppercase letters may be used
 | |
|    interchangably.  If base is > 36 and <= 64, the numbers are drawn
 | |
|    from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, @ = 62, _ = 63 --
 | |
|    you get the picture). */
 | |
| 
 | |
| static intmax_t
 | |
| strlong (num)
 | |
|      char *num;
 | |
| {
 | |
|   register char *s;
 | |
|   register unsigned char c;
 | |
|   int base, foundbase;
 | |
|   intmax_t val;
 | |
| 
 | |
|   s = num;
 | |
| 
 | |
|   base = 10;
 | |
|   foundbase = 0;
 | |
|   if (*s == '0')
 | |
|     {
 | |
|       s++;
 | |
| 
 | |
|       if (*s == '\0')
 | |
| 	return 0;
 | |
| 
 | |
|        /* Base 16? */
 | |
|       if (*s == 'x' || *s == 'X')
 | |
| 	{
 | |
| 	  base = 16;
 | |
| 	  s++;
 | |
| 	}
 | |
|       else
 | |
| 	base = 8;
 | |
|       foundbase++;
 | |
|     }
 | |
| 
 | |
|   val = 0;
 | |
|   for (c = *s++; c; c = *s++)
 | |
|     {
 | |
|       if (c == '#')
 | |
| 	{
 | |
| 	  if (foundbase)
 | |
| 	    evalerror (_("invalid number"));
 | |
| 
 | |
| 	  /* Illegal base specifications raise an evaluation error. */
 | |
| 	  if (val < 2 || val > 64)
 | |
| 	    evalerror (_("invalid arithmetic base"));
 | |
| 
 | |
| 	  base = val;
 | |
| 	  val = 0;
 | |
| 	  foundbase++;
 | |
| 	}
 | |
|       else if (ISALNUM(c) || (c == '_') || (c == '@'))
 | |
| 	{
 | |
| 	  if (DIGIT(c))
 | |
| 	    c = TODIGIT(c);
 | |
| 	  else if (c >= 'a' && c <= 'z')
 | |
| 	    c -= 'a' - 10;
 | |
| 	  else if (c >= 'A' && c <= 'Z')
 | |
| 	    c -= 'A' - ((base <= 36) ? 10 : 36);
 | |
| 	  else if (c == '@')
 | |
| 	    c = 62;
 | |
| 	  else if (c == '_')
 | |
| 	    c = 63;
 | |
| 
 | |
| 	  if (c >= base)
 | |
| 	    evalerror (_("value too great for base"));
 | |
| 
 | |
| 	  val = (val * base) + c;
 | |
| 	}
 | |
|       else
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   return (val);
 | |
| }
 | |
| 
 | |
| #if defined (EXPR_TEST)
 | |
| void *
 | |
| xmalloc (n)
 | |
|      int n;
 | |
| {
 | |
|   return (malloc (n));
 | |
| }
 | |
| 
 | |
| void *
 | |
| 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; }
 | |
| 
 | |
| procenv_t top_level;
 | |
| 
 | |
| main (argc, argv)
 | |
|      int argc;
 | |
|      char **argv;
 | |
| {
 | |
|   register int i;
 | |
|   intmax_t v;
 | |
|   int expok;
 | |
| 
 | |
|   if (setjmp (top_level))
 | |
|     exit (0);
 | |
| 
 | |
|   for (i = 1; i < argc; i++)
 | |
|     {
 | |
|       v = evalexp (argv[i], &expok);
 | |
|       if (expok == 0)
 | |
| 	fprintf (stderr, _("%s: expression error\n"), argv[i]);
 | |
|       else
 | |
| 	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)
 | |
|      intmax_t n;
 | |
| {
 | |
|   return ("42");
 | |
| }
 | |
| 
 | |
| #endif /* EXPR_TEST */
 | 
