Imported from ../bash-3.2.48.tar.gz.
[platform/upstream/bash.git] / arrayfunc.c
index 4fe4f33..6bc248a 100644 (file)
@@ -1,6 +1,6 @@
 /* arrayfunc.c -- High-level array functions used by other parts of the shell. */
 
-/* Copyright (C) 2001 Free Software Foundation, Inc.
+/* Copyright (C) 2001-2006 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 #endif
 #include <stdio.h>
 
+#include "bashintl.h"
+
 #include "shell.h"
+
+#include "shmbutil.h"
+
 #include "builtins/common.h"
 
 extern char *this_command_name;
 extern int last_command_exit_value;
+extern int array_needs_making;
+
+static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, int));
 
 static void quote_array_assignment_chars __P((WORD_LIST *));
-static char *array_value_internal __P((char *, int, int));
+static char *array_value_internal __P((char *, int, int, int *));
+
+/* Standard error message to use when encountering an invalid array subscript */
+char *bash_badsub_errmsg = N_("bad array subscript");
 
 /* **************************************************************** */
 /*                                                                 */
@@ -52,13 +63,20 @@ convert_var_to_array (var)
   ARRAY *array;
 
   oldval = value_cell (var);
-  array = new_array ();
-  array_add_element (array, 0, oldval);
+  array = array_create ();
+  if (oldval)
+    array_insert (array, 0, oldval);
 
   FREE (value_cell (var));
-  var->value = (char *)array;
+  var_setarray (var, array);
+
+  /* these aren't valid anymore */
+  var->dynamic_value = (sh_var_value_func_t *)NULL;
+  var->assign_func = (sh_var_assign_func_t *)NULL;
 
   INVALIDATE_EXPORTSTR (var);
+  if (exported_p (var))
+    array_needs_making++;
 
   VSETATTR (var, att_array);
   VUNSETATTR (var, att_invisible);
@@ -66,6 +84,49 @@ convert_var_to_array (var)
   return var;
 }
 
+static SHELL_VAR *
+bind_array_var_internal (entry, ind, value, flags)
+     SHELL_VAR *entry;
+     arrayind_t ind;
+     char *value;
+     int flags;
+{
+  SHELL_VAR *dentry;
+  char *newval;
+
+  /* If we're appending, we need the old value of the array reference, so
+     fake out make_variable_value with a dummy SHELL_VAR */
+  if (flags & ASS_APPEND)
+    {
+      dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+      dentry->name = savestring (entry->name);
+      newval = array_reference (array_cell (entry), ind);
+      if (newval)
+       dentry->value = savestring (newval);
+      else
+       {
+         dentry->value = (char *)xmalloc (1);
+         dentry->value[0] = '\0';
+       }
+      dentry->exportstr = 0;
+      dentry->attributes = entry->attributes & ~(att_array|att_exported);
+      /* Leave the rest of the members uninitialized; the code doesn't look
+        at them. */
+      newval = make_variable_value (dentry, value, flags);      
+      dispose_variable (dentry);
+    }
+  else
+    newval = make_variable_value (entry, value, flags);
+
+  if (entry->assign_func)
+    (*entry->assign_func) (entry, newval, ind);
+  else
+    array_insert (array_cell (entry), ind, newval);
+  FREE (newval);
+
+  return (entry);
+}
+
 /* Perform an array assignment name[ind]=value.  If NAME already exists and
    is not an array, and IND is 0, perform name=value instead.  If NAME exists
    and is not an array, and IND is not 0, convert it into an array with the
@@ -74,13 +135,13 @@ convert_var_to_array (var)
    If NAME does not exist, just create an array variable, no matter what
    IND's value may be. */
 SHELL_VAR *
-bind_array_variable (name, ind, value)
+bind_array_variable (name, ind, value, flags)
      char *name;
      arrayind_t ind;
      char *value;
+     int flags;
 {
   SHELL_VAR *entry;
-  char *newval;
 
   entry = var_lookup (name, shell_variables);
 
@@ -89,28 +150,22 @@ bind_array_variable (name, ind, value)
   else if (readonly_p (entry) || noassign_p (entry))
     {
       if (readonly_p (entry))
-       report_error ("%s: readonly variable", name);
+       err_readonly (name);
       return (entry);
     }
   else if (array_p (entry) == 0)
     entry = convert_var_to_array (entry);
 
   /* ENTRY is an array variable, and ARRAY points to the value. */
-  newval = make_variable_value (entry, value);
-  if (entry->assign_func)
-    (*entry->assign_func) (entry, ind, newval);
-  else
-    array_add_element (array_cell (entry), ind, newval);
-  FREE (newval);
-
-  return (entry);
+  return (bind_array_var_internal (entry, ind, value, flags));
 }
 
 /* Parse NAME, a lhs of an assignment statement of the form v[s], and
    assign VALUE to that array element by calling bind_array_variable(). */
 SHELL_VAR *
-assign_array_element (name, value)
+assign_array_element (name, value, flags)
      char *name, *value;
+     int flags;
 {
   char *sub, *vname;
   arrayind_t ind;
@@ -125,7 +180,7 @@ assign_array_element (name, value)
   if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
     {
       free (vname);
-      report_error ("%s: bad array subscript", name);
+      err_badarraysub (name);
       return ((SHELL_VAR *)NULL);
     }
 
@@ -133,11 +188,11 @@ assign_array_element (name, value)
   if (ind < 0)
     {
       free (vname);
-      report_error ("%s: bad array subscript", name);
+      err_badarraysub (name);
       return ((SHELL_VAR *)NULL);
     }
 
-  entry = bind_array_variable (vname, ind, value);
+  entry = bind_array_variable (vname, ind, value, flags);
 
   free (vname);
   return (entry);
@@ -162,7 +217,7 @@ find_or_make_array_variable (name, check_flags)
   else if (check_flags && (readonly_p (var) || noassign_p (var)))
     {
       if (readonly_p (var))
-       report_error ("%s: readonly variable", name);
+       err_readonly (name);
       return ((SHELL_VAR *)NULL);
     }
   else if (array_p (var) == 0)
@@ -174,8 +229,9 @@ find_or_make_array_variable (name, check_flags)
 /* Perform a compound assignment statement for array NAME, where VALUE is
    the text between the parens:  NAME=( VALUE ) */
 SHELL_VAR *
-assign_array_from_string (name, value)
+assign_array_from_string (name, value, flags)
      char *name, *value;
+     int flags;
 {
   SHELL_VAR *var;
 
@@ -183,53 +239,48 @@ assign_array_from_string (name, value)
   if (var == 0)
     return ((SHELL_VAR *)NULL);
 
-  return (assign_array_var_from_string (var, value));
+  return (assign_array_var_from_string (var, value, flags));
 }
 
 /* Sequentially assign the indices of indexed array variable VAR from the
    words in LIST. */
 SHELL_VAR *
-assign_array_var_from_word_list (var, list)
+assign_array_var_from_word_list (var, list, flags)
      SHELL_VAR *var;
      WORD_LIST *list;
+     int flags;
 {
   register arrayind_t i;
   register WORD_LIST *l;
   ARRAY *a;
 
-  for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++)
+  a = array_cell (var);
+  i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
+
+  for (l = list; l; l = l->next, i++)
     if (var->assign_func)
-      (*var->assign_func) (var, i, l->word->word);
+      (*var->assign_func) (var, l->word->word, i);
     else
-      array_add_element (a, i, l->word->word);
+      array_insert (a, i, l->word->word);
   return var;
 }
 
-/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
-   VALUE has already had the parentheses stripped. */
-SHELL_VAR *
-assign_array_var_from_string (var, value)
-     SHELL_VAR *var;
+WORD_LIST *
+expand_compound_array_assignment (value, flags)
      char *value;
+     int flags;
 {
-  ARRAY *a;
   WORD_LIST *list, *nlist;
-  char *w, *val, *nval;
-  int ni, len;
-  arrayind_t ind, last_ind;
+  char *val;
+  int ni;
 
-  if (value == 0)
-    return var;
-
-  /* If this is called from declare_builtin, value[0] == '(' and
-     strchr(value, ')') != 0.  In this case, we need to extract
-     the value from between the parens before going on. */
+  /* I don't believe this condition is ever true any more. */
   if (*value == '(')   /*)*/
     {
       ni = 1;
       val = extract_array_assignment_list (value, &ni);
       if (val == 0)
-       return var;
+       return (WORD_LIST *)NULL;
     }
   else
     val = value;
@@ -238,7 +289,7 @@ assign_array_var_from_string (var, value)
      shell expansions including pathname generation and word splitting. */
   /* First we split the string on whitespace, using the shell parser
      (ksh93 seems to do this). */
-  list = parse_string_to_word_list (val, "array assign");
+  list = parse_string_to_word_list (val, 1, "array assign");
 
   /* If we're using [subscript]=value, we need to quote each [ and ] to
      prevent unwanted filename expansion. */
@@ -254,29 +305,47 @@ assign_array_var_from_string (var, value)
   if (val != value)
     free (val);
 
+  return nlist;
+}
+
+void
+assign_compound_array_list (var, nlist, flags)
+     SHELL_VAR *var;
+     WORD_LIST *nlist;
+     int flags;
+{
+  ARRAY *a;
+  WORD_LIST *list;
+  char *w, *val, *nval;
+  int len, iflags;
+  arrayind_t ind, last_ind;
+
   a = array_cell (var);
 
   /* Now that we are ready to assign values to the array, kill the existing
      value. */
-  if (a)
-    empty_array (a);
+  if (a && (flags & ASS_APPEND) == 0)
+    array_flush (a);
+  last_ind = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
 
-  for (last_ind = 0, list = nlist; list; list = list->next)
+  for (list = nlist; list; list = list->next)
     {
+      iflags = flags;
       w = list->word->word;
 
       /* We have a word of the form [ind]=value */
-      if (w[0] == '[')
+      if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
        {
          len = skipsubscript (w, 0);
 
-         if (w[len] != ']' || w[len+1] != '=')
+         /* XXX - changes for `+=' */
+         if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
            {
-             nval = make_variable_value (var, w);
+             nval = make_variable_value (var, w, flags);
              if (var->assign_func)
-               (*var->assign_func) (var, last_ind, nval);
+               (*var->assign_func) (var, nval, last_ind);
              else
-               array_add_element (a, last_ind, nval);
+               array_insert (a, last_ind, nval);
              FREE (nval);
              last_ind++;
              continue;
@@ -284,24 +353,31 @@ assign_array_var_from_string (var, value)
 
          if (len == 1)
            {
-             report_error ("%s: bad array subscript", w);
+             err_badarraysub (w);
              continue;
            }
 
          if (ALL_ELEMENT_SUB (w[1]) && len == 2)
            {
-             report_error ("%s: cannot assign to non-numeric index", w);
+             report_error (_("%s: cannot assign to non-numeric index"), w);
              continue;
            }
 
          ind = array_expand_index (w + 1, len);
          if (ind < 0)
            {
-             report_error ("%s: bad array subscript", w);
+             err_badarraysub (w);
              continue;
            }
          last_ind = ind;
-         val = w + len + 2;
+         /* XXX - changes for `+=' -- just accept the syntax.  ksh93 doesn't do this */
+         if (w[len + 1] == '+' && w[len + 2] == '=')
+           {
+             iflags |= ASS_APPEND;
+             val = w + len + 3;
+           }
+         else
+           val = w + len + 2;
        }
       else             /* No [ind]=value, just a stray `=' */
        {
@@ -311,16 +387,29 @@ assign_array_var_from_string (var, value)
 
       if (integer_p (var))
        this_command_name = (char *)NULL;       /* no command name for errors */
-      nval = make_variable_value (var, val);
-      if (var->assign_func)
-       (*var->assign_func) (var, ind, nval);
-      else
-       array_add_element (a, ind, nval);
-      FREE (nval);
+      bind_array_var_internal (var, ind, val, iflags);
       last_ind++;
     }
+}
+
+/* Perform a compound array assignment:  VAR->name=( VALUE ).  The
+   VALUE has already had the parentheses stripped. */
+SHELL_VAR *
+assign_array_var_from_string (var, value, flags)
+     SHELL_VAR *var;
+     char *value;
+     int flags;
+{
+  WORD_LIST *nlist;
+
+  if (value == 0)
+    return var;
+
+  nlist = expand_compound_array_assignment (value, flags);
+  assign_compound_array_list (var, nlist, flags);
 
-  dispose_words (nlist);
+  if (nlist)
+    dispose_words (nlist);
   return (var);
 }
 
@@ -340,7 +429,7 @@ quote_array_assignment_chars (list)
       if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
        continue;       /* should not happen, but just in case... */
       /* Don't bother if it doesn't look like [ind]=value */
-      if (l->word->word[0] != '[' || strchr (l->word->word, '=') == 0) /* ] */
+      if (l->word->word[0] != '[' || xstrchr (l->word->word, '=') == 0) /* ] */
        continue;
       s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1);
       saw_eq = 0;
@@ -366,14 +455,54 @@ skipsubscript (s, i)
      int i;
 {
   int count, c;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t state, state_bak;
+  size_t slength, mblength;
+#endif
 
-  for (count = 1; count && (c = s[++i]); )
+#if defined (HANDLE_MULTIBYTE)
+  memset (&state, '\0', sizeof (mbstate_t));
+  slength = strlen (s + i);
+#endif
+  
+  count = 1;
+  while (count)
     {
-      if (c == '[')
+      /* Advance one (possibly multibyte) character in S starting at I. */
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1)
+       {
+         state_bak = state;
+         mblength = mbrlen (s + i, slength, &state);
+
+         if (MB_INVALIDCH (mblength))
+           {
+             state = state_bak;
+             i++;
+             slength--;
+           }
+         else if (MB_NULLWCH (mblength))
+           return i;
+         else
+           {
+             i += mblength;
+             slength -= mblength;
+           }
+       }
+      else
+#endif
+      ++i;
+
+      c = s[i];
+
+      if (c == 0)
+        break;
+      else if (c == '[')
        count++;
       else if (c == ']')
        count--;
     }
+
   return i;
 }
 
@@ -392,25 +521,25 @@ unbind_array_element (var, sub)
   len = skipsubscript (sub, 0);
   if (sub[len] != ']' || len == 0)
     {
-      builtin_error ("%s[%s: bad array subscript", var->name, sub);
+      builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
       return -1;
     }
   sub[len] = '\0';
 
   if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
     {
-      makunbound (var->name, shell_variables);
+      unbind_variable (var->name);
       return (0);
     }
   ind = array_expand_index (sub, len+1);
   if (ind < 0)
     {
-      builtin_error ("[%s]: bad array subscript", sub);
+      builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
       return -1;
     }
-  ae = array_delete_element (array_cell (var), ind);
+  ae = array_remove (array_cell (var), ind);
   if (ae)
-    destroy_array_element (ae);
+    array_dispose_element (ae);
   return 0;
 }
 
@@ -423,10 +552,7 @@ print_array_assignment (var, quoted)
 {
   char *vstr;
 
-  if (quoted)
-    vstr = quoted_array_assignment_string (array_cell (var));
-  else
-    vstr = array_to_assignment_string (array_cell (var));
+  vstr = array_to_assign (array_cell (var), quoted);
 
   if (vstr == 0)
     printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
@@ -451,7 +577,7 @@ valid_array_reference (name)
   char *t;
   int r, len;
 
-  t = strchr (name, '[');      /* ] */
+  t = xstrchr (name, '[');     /* ] */
   if (t)
     {
       *t = '\0';
@@ -484,7 +610,7 @@ array_expand_index (s, len)
   exp = (char *)xmalloc (len);
   strncpy (exp, s, len - 1);
   exp[len - 1] = '\0';
-  t = expand_string_to_string (exp, 0);
+  t = expand_arith_string (exp, 0);
   this_command_name = (char *)NULL;
   val = evalexp (t, &expok);
   free (t);
@@ -492,6 +618,8 @@ array_expand_index (s, len)
   if (expok == 0)
     {
       last_command_exit_value = EXECUTION_FAILURE;
+
+      top_level_cleanup ();      
       jump_to_top_level (DISCARD);
     }
   return val;
@@ -509,14 +637,24 @@ array_variable_name (s, subp, lenp)
   char *t, *ret;
   int ind, ni;
 
-  t = strchr (s, '[');
+  t = xstrchr (s, '[');
   if (t == 0)
-    return ((char *)NULL);
+    {
+      if (subp)
+       *subp = t;
+      if (lenp)
+       *lenp = 0;
+      return ((char *)NULL);
+    }
   ind = t - s;
   ni = skipsubscript (s, ind);
   if (ni <= ind + 1 || s[ni] != ']')
     {
-      report_error ("%s: bad array subscript", s);
+      err_badarraysub (s);
+      if (subp)
+       *subp = t;
+      if (lenp)
+       *lenp = 0;
       return ((char *)NULL);
     }
 
@@ -549,16 +687,18 @@ array_variable_part (s, subp, lenp)
   var = find_variable (t);
 
   free (t);
-  return var;
+  return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
 }
 
 /* Return a string containing the elements in the array and subscript
    described by S.  If the subscript is * or @, obeys quoting rules akin
-   to the expansion of $* and $@ including double quoting. */
+   to the expansion of $* and $@ including double quoting.  If RTYPE
+   is non-null it gets 1 if the array reference is name[@] or name[*]
+   and 0 otherwise. */
 static char *
-array_value_internal (s, quoted, allow_all)
+array_value_internal (s, quoted, allow_all, rtype)
      char *s;
-     int quoted, allow_all;
+     int quoted, allow_all, *rtype;
 {
   int len;
   arrayind_t ind;
@@ -568,22 +708,30 @@ array_value_internal (s, quoted, allow_all)
 
   var = array_variable_part (s, &t, &len);
 
+  /* Expand the index, even if the variable doesn't exist, in case side
+     effects are needed, like ${w[i++]} where w is unset. */
+#if 0
   if (var == 0)
     return (char *)NULL;
+#endif
+
+  if (len == 0)
+    return ((char *)NULL);     /* error message already printed */
 
   /* [ */
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
+      if (rtype)
+       *rtype = (t[0] == '*') ? 1 : 2;
       if (allow_all == 0)
        {
-         report_error ("%s: bad array subscript", s);
+         err_badarraysub (s);
          return ((char *)NULL);
        }
+      else if (var == 0 || value_cell (var) == 0)
+       return ((char *)NULL);
       else if (array_p (var) == 0)
-       {
-         l = (WORD_LIST *)NULL;
-         l = add_string_to_list (value_cell (var), l);
-       }
+       l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
       else
        {
          l = array_to_word_list (array_cell (var));
@@ -604,17 +752,26 @@ array_value_internal (s, quoted, allow_all)
     }
   else
     {
+      if (rtype)
+       *rtype = 0;
       ind = array_expand_index (t, len);
       if (ind < 0)
        {
-         report_error ("%s: bad array subscript", var->name);
+         if (var)
+           err_badarraysub (var->name);
+         else
+           {
+             t[-1] = '\0';
+             err_badarraysub (s);
+             t[-1] = '[';      /* ] */
+           }
          return ((char *)NULL);
        }
+      if (var == 0)
+       return ((char *)NULL);
       if (array_p (var) == 0)
-       return (ind == 0 ? savestring (value_cell (var)) : (char *)NULL);
+       return (ind == 0 ? value_cell (var) : (char *)NULL);
       retval = array_reference (array_cell (var), ind);
-      if (retval)
-       retval = quote_escapes (retval);
     }
 
   return retval;
@@ -623,11 +780,11 @@ array_value_internal (s, quoted, allow_all)
 /* Return a string containing the elements described by the array and
    subscript contained in S, obeying quoting for subscripts * and @. */
 char *
-array_value (s, quoted)
+array_value (s, quoted, rtype)
      char *s;
-     int quoted;
+     int quoted, *rtype;
 {
-  return (array_value_internal (s, quoted, 1));
+  return (array_value_internal (s, quoted, 1, rtype));
 }
 
 /* Return the value of the array indexing expression S as a single string.
@@ -635,11 +792,48 @@ array_value (s, quoted)
    by other parts of the shell such as the arithmetic expression evaluator
    in expr.c. */
 char *
-get_array_value (s, allow_all)
+get_array_value (s, allow_all, rtype)
      char *s;
-     int allow_all;
+     int allow_all, *rtype;
 {
-  return (array_value_internal (s, 0, allow_all));
+  return (array_value_internal (s, 0, allow_all, rtype));
 }
 
+char *
+array_keys (s, quoted)
+     char *s;
+     int quoted;
+{
+  int len;
+  char *retval, *t, *temp;
+  WORD_LIST *l;
+  SHELL_VAR *var;
+
+  var = array_variable_part (s, &t, &len);
+
+  /* [ */
+  if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
+    return (char *)NULL;
+
+  if (array_p (var) == 0)
+    l = add_string_to_list ("0", (WORD_LIST *)NULL);
+  else
+    {
+      l = array_keys_to_word_list (array_cell (var));
+      if (l == (WORD_LIST *)NULL)
+        return ((char *) NULL);
+    }
+
+  if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+    {
+      temp = string_list_dollar_star (l);
+      retval = quote_string (temp);
+      free (temp);
+    }
+  else /* ${!name[@]} or unquoted ${!name[*]} */
+    retval = string_list_dollar_at (l, quoted);
+
+  dispose_words (l);
+  return retval;
+}
 #endif /* ARRAY_VARS */