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)
+ the Free Software Foundation; either version 2, or (at your option)
any later version.
Bash is distributed in the hope that it will be useful, but WITHOUT
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. */
+ Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
/*
All arithmetic is done as long integers with no checking for overflow
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"
- "=", "*=", "/=", "%=",
- "+=", "-=", "<<=", ">>=",
- "&=", "^=", "|="
+ "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="
(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
#include <stdio.h>
#include "bashansi.h"
+
#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
# include <unistd.h>
#endif
+#include <ctype.h>
+
#include "shell.h"
/* Because of the $((...)) construct, expressions may include newlines.
#define LSH 9 /* "<<" Left SHift */
#define RSH 10 /* ">>" Right SHift */
#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */
-#define COND 12
+#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 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
static char *expression; /* The current expression */
static char *tp; /* token lexical position */
static procenv_t evalbuf;
static void readtok (); /* lexical analyzer */
-static long expassign (), exp0 (), exp1 (), exp2 (), exp3 (),
+static long subexpr (), expassign (), exp0 (), exp1 (), exp2 (), exp3 (),
exp4 (), exp5 (), expshift (), expland (), explor (),
- expband (), expbor (), expbxor (), expcond ();
+ expband (), expbor (), expbxor (), expcond (), exppower (),
+ expcomma ();
static long strlong ();
static void evalerror ();
/* A structure defining a single expression context. */
typedef struct {
int curtok, lasttok;
- char *expression, *tp;
+ char *expression, *tp, *lasttp;
int tokval;
char *tokstr;
+ int noeval;
} EXPR_CONTEXT;
+typedef struct {
+ char *tokstr;
+ int tokval;
+} LVALUE;
+
/* Global var which contains the stack of expression contexts. */
static EXPR_CONTEXT **expr_stack;
static int expr_depth; /* Location in the stack. */
extern char *this_command_name;
+#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; \
+ } 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; \
+ } while (0)
+
/* Push and save away the contents of the globals describing the
current expression context. */
static void
{
EXPR_CONTEXT *context;
- context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT));
-
if (expr_depth >= MAX_EXPR_RECURSION_LEVEL)
evalerror ("expression recursion level exceeded");
* sizeof (EXPR_CONTEXT *));
}
- context->curtok = curtok;
- context->lasttok = lasttok;
+ context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT));
+
context->expression = expression;
- context->tp = tp;
- context->tokval = tokval;
- context->tokstr = tokstr;
+ SAVETOK(context);
+
expr_stack[expr_depth++] = context;
}
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;
+ RESTORETOK (context);
+
free (context);
}
-/* Evaluate EXPR, and return the arithmetic result.
+/* 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
safe to let the loop terminate when expr_depth == 0, without freeing up
any of the expr_depth[0] stuff. */
long
-evalexp (expr)
+evalexp (expr, validp)
char *expr;
+ int *validp;
{
- long val = 0L;
+ long val;
+#if 0
procenv_t old_evalbuf;
- char *p;
-
- for (p = expr; p && *p && cr_whitespace (*p); p++)
- ;
+#endif
- if (p == NULL || *p == '\0')
- return (0);
+ val = 0L;
+#if 0
/* Save the value of evalbuf to protect it around possible recursive
calls to evalexp (). */
COPY_PROCENV (evalbuf, old_evalbuf);
+#endif
if (setjmp (evalbuf))
{
- if (tokstr) /* Clean up local allocation. */
- free (tokstr);
-
- if (expression)
- free (expression);
+ FREE (tokstr);
+ FREE (expression);
+ tokstr = expression = (char *)NULL;
- while (--expr_depth)
+ 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]);
}
- jump_to_top_level (DISCARD);
+ free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */
+
+ if (validp)
+ *validp = 0;
+ return (0L);
}
+ val = subexpr (expr);
+
+#if 0
+ /* Restore the value of evalbuf so that any subsequent longjmp calls
+ will have a valid location to jump to. */
+ COPY_PROCENV (old_evalbuf, evalbuf);
+#endif
+
+ if (validp)
+ *validp = 1;
+
+ return (val);
+}
+
+static long
+subexpr (expr)
+ char *expr;
+{
+ long val;
+ char *p;
+
+ for (p = expr; p && *p && cr_whitespace (*p); p++)
+ ;
+
+ if (p == NULL || *p == '\0')
+ return (0L);
+
pushexp ();
curtok = lasttok = 0;
expression = savestring (expr);
tp = expression;
tokstr = (char *)NULL;
- tokval = 0l;
+ tokval = 0L;
readtok ();
- val = expassign ();
+ val = EXP_HIGHEST ();
if (curtok != 0)
evalerror ("syntax error in expression");
- if (tokstr)
- free (tokstr);
- if (expression)
- free (expression);
+ FREE (tokstr);
+ FREE (expression);
popexp ();
- /* Restore the value of evalbuf so that any subsequent longjmp calls
- will have a valid location to jump to. */
- COPY_PROCENV (old_evalbuf, evalbuf);
-
- return (val);
+ 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;
+static long
+expcomma ()
{
- register SHELL_VAR *v;
- int isint = 0;
+ register long value;
- v = find_variable (lhs);
- if (v)
+ value = expassign ();
+ while (curtok == COMMA)
{
- isint = integer_p (v);
- v->attributes &= ~att_integer;
+ readtok ();
+ value = expassign ();
}
- v = bind_variable (lhs, rhs);
- if (isint)
- v->attributes |= att_integer;
+ return value;
}
-
+
static long
expassign ()
{
case BOR:
lvalue |= value;
break;
+ case BXOR:
+ lvalue ^= value;
+ break;
default:
+ free (lhs);
evalerror ("bug: bad expassign token");
break;
}
rhs = itos (value);
if (noeval == 0)
- bind_int_variable (lhs, rhs);
+ (void)bind_int_variable (lhs, rhs);
free (rhs);
free (lhs);
- free (tokstr);
+ FREE (tokstr);
tokstr = (char *)NULL; /* For freeing on errors. */
}
return (value);
expcond ()
{
long cval, val1, val2, rval;
+ int set_noeval;
+
+ set_noeval = 0;
rval = cval = explor ();
if (curtok == QUES) /* found conditional expr */
{
if (curtok == 0 || curtok == COL)
evalerror ("expression expected");
if (cval == 0)
- noeval++;
+ {
+ set_noeval = 1;
+ noeval++;
+ }
#if 0
val1 = explor ();
#else
- val1 = expassign ();
+ val1 = EXP_HIGHEST ();
#endif
- if (cval == 0)
- noeval--;
+ if (set_noeval)
+ noeval--;
if (curtok != COL)
- evalerror ("`:' expected for conditional expression");
+ evalerror ("`:' expected for conditional expression");
readtok ();
if (curtok == 0)
evalerror ("expression expected");
+ set_noeval = 0;
if (cval)
- noeval++;
+ {
+ set_noeval = 1;
+ noeval++;
+ }
val2 = explor ();
- if (cval)
- noeval--;
+ if (set_noeval)
+ noeval--;
rval = cval ? val1 : val2;
lasttok = COND;
}
explor ()
{
register long val1, val2;
+ int set_noeval;
val1 = expland ();
while (curtok == LOR)
{
- readtok ();
+ set_noeval = 0;
if (val1 != 0)
- noeval++;
+ {
+ noeval++;
+ set_noeval = 1;
+ }
+ readtok ();
val2 = expland ();
- if (val1 != 0)
+ if (set_noeval)
noeval--;
val1 = val1 || val2;
+ lasttok = LOR;
}
return (val1);
expland ()
{
register long val1, val2;
+ int set_noeval;
val1 = expbor ();
while (curtok == LAND)
{
- readtok ();
+ set_noeval = 0;
if (val1 == 0)
- noeval++;
+ {
+ set_noeval = 1;
+ noeval++;
+ }
+ readtok ();
val2 = expbor ();
- if (val1 == 0)
+ if (set_noeval)
noeval--;
val1 = val1 && val2;
+ lasttok = LAND;
}
return (val1);
val1 = val1 >= val2;
else if (op == LT)
val1 = val1 < val2;
- else if (op == GT)
+ else /* (op == GT) */
val1 = val1 > val2;
}
return (val1);
{
register long val1, val2;
- val1 = exp1 ();
+ val1 = exppower ();
while ((curtok == MUL) ||
- (curtok == DIV) ||
- (curtok == MOD))
+ (curtok == DIV) ||
+ (curtok == MOD))
{
int op = curtok;
readtok ();
- val2 = exp1 ();
+ val2 = exppower ();
if (((op == DIV) || (op == MOD)) && (val2 == 0))
evalerror ("division by 0");
if (op == MUL)
- val1 *= val2;
+ val1 *= val2;
else if (op == DIV)
- val1 /= val2;
+ val1 /= val2;
else if (op == MOD)
- val1 %= val2;
+ val1 %= val2;
+ }
+ return (val1);
+}
+
+static long
+exppower ()
+{
+ register long val1, val2, c;
+
+ val1 = exp1 ();
+ if (curtok == POWER)
+ {
+ readtok ();
+ val2 = exp1 ();
+ if (val2 == 0)
+ return (1L);
+ if (val2 < 0)
+ evalerror ("exponent less than 0");
+ for (c = 1; val2--; c *= val1)
+ ;
+ val1 = c;
}
return (val1);
}
static long
exp0 ()
{
- register long val = 0L;
+ register long val = 0L, v2;
+ char *vincdec;
+ int stok;
+
+ /* 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");
- if (curtok == MINUS)
+ v2 = tokval + ((stok == PREINC) ? 1 : -1);
+ vincdec = itos (v2);
+ if (noeval == 0)
+ (void)bind_int_variable (tokstr, vincdec);
+ free (vincdec);
+ val = v2;
+
+ curtok = NUM; /* make sure --x=7 is flagged as an error */
+ readtok ();
+ }
+ else if (curtok == MINUS)
{
readtok ();
val = - exp0 ();
else if (curtok == LPAR)
{
readtok ();
- val = expassign ();
+ val = EXP_HIGHEST ();
if (curtok != RPAR)
evalerror ("missing `)'");
else if ((curtok == NUM) || (curtok == STR))
{
val = tokval;
+ if (curtok == STR && (*tp == '+' || *tp == '-') && tp[1] == *tp &&
+ (tp[2] == '\0' || (isalnum (tp[2]) == 0)))
+ {
+ /* post-increment or post-decrement */
+ v2 = val + ((*tp == '+') ? 1 : -1);
+ vincdec = itos (v2);
+ if (noeval == 0)
+ (void)bind_int_variable (tokstr, vincdec);
+ free (vincdec);
+ tp += 2;
+ curtok = NUM; /* make sure x++=7 is flagged as an error */
+ }
+
readtok ();
}
else
if (legal_variable_starter (c))
{
- /* Semi-bogus ksh compatibility feature -- variable names
- not preceded with a dollar sign are shell variables. */
- char *value;
+ /* variable names not preceded with a dollar sign are shell variables. */
+ char *value, *savecp;
+ EXPR_CONTEXT ec;
+ int peektok;
while (legal_variable_char (c))
c = *cp++;
#endif /* ARRAY_VARS */
*cp = '\0';
-
FREE (tokstr);
tokstr = savestring (tp);
+ *cp = c;
+ SAVETOK (&ec);
+ tokstr = (char *)NULL; /* keep it from being freed */
+ tp = savecp = cp;
+ noeval = 1;
+ 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)
+ {
#if defined (ARRAY_VARS)
- value = (e == ']') ? get_array_value (tokstr, 0) : get_string_value (tokstr);
+ value = (e == ']') ? get_array_value (tokstr, 0) : get_string_value (tokstr);
#else
- value = get_string_value (tokstr);
+ value = get_string_value (tokstr);
#endif
- tokval = (value && *value) ? evalexp (value) : 0;
+ tokval = (value && *value) ? subexpr (value) : 0;
+
+#if defined (ARRAY_VARS)
+ if (e == ']')
+ FREE (value); /* get_array_value returns newly-allocated memory */
+#endif
+ }
+ else
+ tokval = 0;
- *cp = c;
lasttok = curtok;
curtok = STR;
}
c = LAND;
else if ((c == BOR) && (c1 == BOR))
c = LOR;
- else if (c1 == EQ && member(c, "*/%+-&^|"))
+ else if ((c == '*') && (c1 == '*'))
+ c = POWER;
+ else if ((c == '-') && (c1 == '-') && legal_variable_starter (*cp))
+ c = PREDEC;
+ else if ((c == '+') && (c1 == '+') && legal_variable_starter (*cp))
+ c = PREINC;
+ else if (c1 == EQ && member (c, "*/%+-&^|"))
{
assigntok = c; /* a OP= b */
c = OP_ASSIGN;
/* Convert a string to a long integer, with an arbitrary base.
0nnn -> base 8
- 0xnn -> base 16
+ 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
{
register int i;
long v;
+ int expok;
if (setjmp (top_level))
exit (0);
for (i = 1; i < argc; i++)
{
- v = evalexp (argv[i]);
- printf ("'%s' -> %ld\n", argv[i], v);
+ 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);
}