Imported from ../bash-2.05.tar.gz.
[platform/upstream/bash.git] / expr.c
diff --git a/expr.c b/expr.c
index eb08054..8ce348d 100644 (file)
--- a/expr.c
+++ b/expr.c
@@ -6,7 +6,7 @@
 
    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
@@ -16,7 +16,7 @@
 
    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)]
        "*", "/", "%"
        "+", "-"
        "<<", ">>"
@@ -38,9 +41,7 @@
        "&&"
        "||"
        "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 */
@@ -130,20 +147,27 @@ static int        noeval;         /* set to 1 if no assignment to be done */
 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. */
@@ -151,6 +175,28 @@ static int expr_stack_size;           /* Number of slots already allocated. */
 
 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
@@ -158,8 +204,6 @@ pushexp ()
 {
   EXPR_CONTEXT *context;
 
-  context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT));
-
   if (expr_depth >= MAX_EXPR_RECURSION_LEVEL)
     evalerror ("expression recursion level exceeded");
 
@@ -170,12 +214,11 @@ pushexp ()
                  * 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;
 }
 
@@ -190,16 +233,18 @@ popexp ()
     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
@@ -209,100 +254,111 @@ popexp ()
    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 ()
 {
@@ -361,7 +417,11 @@ expassign ()
            case BOR:
              lvalue |= value;
              break;
+           case BXOR:
+             lvalue ^= value;
+             break;
            default:
+             free (lhs);
              evalerror ("bug: bad expassign token");
              break;
            }
@@ -370,10 +430,10 @@ expassign ()
 
       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);
@@ -384,6 +444,9 @@ static long
 expcond ()
 {
   long cval, val1, val2, rval;
+  int set_noeval;
+
+  set_noeval = 0;
   rval = cval = explor ();
   if (curtok == QUES)          /* found conditional expr */
     {
@@ -391,24 +454,31 @@ expcond ()
       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;
     }
@@ -420,18 +490,24 @@ static long
 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);
@@ -442,18 +518,24 @@ static long
 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);
@@ -556,7 +638,7 @@ exp4 ()
        val1 = val1 >= val2;
       else if (op == LT)
        val1 = val1 < val2;
-      else if (op == GT)
+      else                     /* (op == GT) */
        val1 = val1 > val2;
     }
   return (val1);
@@ -613,27 +695,48 @@ exp2 ()
 {
   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);
 }
@@ -662,9 +765,31 @@ exp1 ()
 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 ();
@@ -677,7 +802,7 @@ exp0 ()
   else if (curtok == LPAR)
     {
       readtok ();
-      val = expassign ();
+      val = EXP_HIGHEST ();
 
       if (curtok != RPAR)
        evalerror ("missing `)'");
@@ -688,6 +813,19 @@ exp0 ()
   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
@@ -727,9 +865,10 @@ readtok ()
 
   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++;
@@ -752,19 +891,41 @@ readtok ()
 #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;
     }
@@ -818,7 +979,13 @@ readtok ()
        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;
@@ -848,7 +1015,7 @@ evalerror (msg)
 
 /* 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
@@ -960,14 +1127,18 @@ main (argc, argv)
 {
   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);
 }