Imported from ../bash-3.2.48.tar.gz.
[platform/upstream/bash.git] / arrayfunc.c
1 /* arrayfunc.c -- High-level array functions used by other parts of the shell. */
2
3 /* Copyright (C) 2001-2006 Free Software Foundation, Inc.
4
5    This file is part of GNU Bash, the Bourne Again SHell.
6
7    Bash is free software; you can redistribute it and/or modify it under
8    the terms of the GNU General Public License as published by the Free
9    Software Foundation; either version 2, or (at your option) any later
10    version.
11
12    Bash is distributed in the hope that it will be useful, but WITHOUT ANY
13    WARRANTY; without even the implied warranty of MERCHANTABILITY or
14    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15    for more details.
16
17    You should have received a copy of the GNU General Public License along
18    with Bash; see the file COPYING.  If not, write to the Free Software
19    Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
20
21 #include "config.h"
22
23 #if defined (ARRAY_VARS)
24
25 #if defined (HAVE_UNISTD_H)
26 #  include <unistd.h>
27 #endif
28 #include <stdio.h>
29
30 #include "bashintl.h"
31
32 #include "shell.h"
33
34 #include "shmbutil.h"
35
36 #include "builtins/common.h"
37
38 extern char *this_command_name;
39 extern int last_command_exit_value;
40 extern int array_needs_making;
41
42 static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, int));
43
44 static void quote_array_assignment_chars __P((WORD_LIST *));
45 static char *array_value_internal __P((char *, int, int, int *));
46
47 /* Standard error message to use when encountering an invalid array subscript */
48 char *bash_badsub_errmsg = N_("bad array subscript");
49
50 /* **************************************************************** */
51 /*                                                                  */
52 /*  Functions to manipulate array variables and perform assignments */
53 /*                                                                  */
54 /* **************************************************************** */
55
56 /* Convert a shell variable to an array variable.  The original value is
57    saved as array[0]. */
58 SHELL_VAR *
59 convert_var_to_array (var)
60      SHELL_VAR *var;
61 {
62   char *oldval;
63   ARRAY *array;
64
65   oldval = value_cell (var);
66   array = array_create ();
67   if (oldval)
68     array_insert (array, 0, oldval);
69
70   FREE (value_cell (var));
71   var_setarray (var, array);
72
73   /* these aren't valid anymore */
74   var->dynamic_value = (sh_var_value_func_t *)NULL;
75   var->assign_func = (sh_var_assign_func_t *)NULL;
76
77   INVALIDATE_EXPORTSTR (var);
78   if (exported_p (var))
79     array_needs_making++;
80
81   VSETATTR (var, att_array);
82   VUNSETATTR (var, att_invisible);
83
84   return var;
85 }
86
87 static SHELL_VAR *
88 bind_array_var_internal (entry, ind, value, flags)
89      SHELL_VAR *entry;
90      arrayind_t ind;
91      char *value;
92      int flags;
93 {
94   SHELL_VAR *dentry;
95   char *newval;
96
97   /* If we're appending, we need the old value of the array reference, so
98      fake out make_variable_value with a dummy SHELL_VAR */
99   if (flags & ASS_APPEND)
100     {
101       dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
102       dentry->name = savestring (entry->name);
103       newval = array_reference (array_cell (entry), ind);
104       if (newval)
105         dentry->value = savestring (newval);
106       else
107         {
108           dentry->value = (char *)xmalloc (1);
109           dentry->value[0] = '\0';
110         }
111       dentry->exportstr = 0;
112       dentry->attributes = entry->attributes & ~(att_array|att_exported);
113       /* Leave the rest of the members uninitialized; the code doesn't look
114          at them. */
115       newval = make_variable_value (dentry, value, flags);       
116       dispose_variable (dentry);
117     }
118   else
119     newval = make_variable_value (entry, value, flags);
120
121   if (entry->assign_func)
122     (*entry->assign_func) (entry, newval, ind);
123   else
124     array_insert (array_cell (entry), ind, newval);
125   FREE (newval);
126
127   return (entry);
128 }
129
130 /* Perform an array assignment name[ind]=value.  If NAME already exists and
131    is not an array, and IND is 0, perform name=value instead.  If NAME exists
132    and is not an array, and IND is not 0, convert it into an array with the
133    existing value as name[0].
134
135    If NAME does not exist, just create an array variable, no matter what
136    IND's value may be. */
137 SHELL_VAR *
138 bind_array_variable (name, ind, value, flags)
139      char *name;
140      arrayind_t ind;
141      char *value;
142      int flags;
143 {
144   SHELL_VAR *entry;
145
146   entry = var_lookup (name, shell_variables);
147
148   if (entry == (SHELL_VAR *) 0)
149     entry = make_new_array_variable (name);
150   else if (readonly_p (entry) || noassign_p (entry))
151     {
152       if (readonly_p (entry))
153         err_readonly (name);
154       return (entry);
155     }
156   else if (array_p (entry) == 0)
157     entry = convert_var_to_array (entry);
158
159   /* ENTRY is an array variable, and ARRAY points to the value. */
160   return (bind_array_var_internal (entry, ind, value, flags));
161 }
162
163 /* Parse NAME, a lhs of an assignment statement of the form v[s], and
164    assign VALUE to that array element by calling bind_array_variable(). */
165 SHELL_VAR *
166 assign_array_element (name, value, flags)
167      char *name, *value;
168      int flags;
169 {
170   char *sub, *vname;
171   arrayind_t ind;
172   int sublen;
173   SHELL_VAR *entry;
174
175   vname = array_variable_name (name, &sub, &sublen);
176
177   if (vname == 0)
178     return ((SHELL_VAR *)NULL);
179
180   if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
181     {
182       free (vname);
183       err_badarraysub (name);
184       return ((SHELL_VAR *)NULL);
185     }
186
187   ind = array_expand_index (sub, sublen);
188   if (ind < 0)
189     {
190       free (vname);
191       err_badarraysub (name);
192       return ((SHELL_VAR *)NULL);
193     }
194
195   entry = bind_array_variable (vname, ind, value, flags);
196
197   free (vname);
198   return (entry);
199 }
200
201 /* Find the array variable corresponding to NAME.  If there is no variable,
202    create a new array variable.  If the variable exists but is not an array,
203    convert it to an indexed array.  If CHECK_FLAGS is non-zero, an existing
204    variable is checked for the readonly or noassign attribute in preparation
205    for assignment (e.g., by the `read' builtin). */
206 SHELL_VAR *
207 find_or_make_array_variable (name, check_flags)
208      char *name;
209      int check_flags;
210 {
211   SHELL_VAR *var;
212
213   var = find_variable (name);
214
215   if (var == 0)
216     var = make_new_array_variable (name);
217   else if (check_flags && (readonly_p (var) || noassign_p (var)))
218     {
219       if (readonly_p (var))
220         err_readonly (name);
221       return ((SHELL_VAR *)NULL);
222     }
223   else if (array_p (var) == 0)
224     var = convert_var_to_array (var);
225
226   return (var);
227 }
228   
229 /* Perform a compound assignment statement for array NAME, where VALUE is
230    the text between the parens:  NAME=( VALUE ) */
231 SHELL_VAR *
232 assign_array_from_string (name, value, flags)
233      char *name, *value;
234      int flags;
235 {
236   SHELL_VAR *var;
237
238   var = find_or_make_array_variable (name, 1);
239   if (var == 0)
240     return ((SHELL_VAR *)NULL);
241
242   return (assign_array_var_from_string (var, value, flags));
243 }
244
245 /* Sequentially assign the indices of indexed array variable VAR from the
246    words in LIST. */
247 SHELL_VAR *
248 assign_array_var_from_word_list (var, list, flags)
249      SHELL_VAR *var;
250      WORD_LIST *list;
251      int flags;
252 {
253   register arrayind_t i;
254   register WORD_LIST *l;
255   ARRAY *a;
256
257   a = array_cell (var);
258   i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
259
260   for (l = list; l; l = l->next, i++)
261     if (var->assign_func)
262       (*var->assign_func) (var, l->word->word, i);
263     else
264       array_insert (a, i, l->word->word);
265   return var;
266 }
267
268 WORD_LIST *
269 expand_compound_array_assignment (value, flags)
270      char *value;
271      int flags;
272 {
273   WORD_LIST *list, *nlist;
274   char *val;
275   int ni;
276
277   /* I don't believe this condition is ever true any more. */
278   if (*value == '(')    /*)*/
279     {
280       ni = 1;
281       val = extract_array_assignment_list (value, &ni);
282       if (val == 0)
283         return (WORD_LIST *)NULL;
284     }
285   else
286     val = value;
287
288   /* Expand the value string into a list of words, performing all the
289      shell expansions including pathname generation and word splitting. */
290   /* First we split the string on whitespace, using the shell parser
291      (ksh93 seems to do this). */
292   list = parse_string_to_word_list (val, 1, "array assign");
293
294   /* If we're using [subscript]=value, we need to quote each [ and ] to
295      prevent unwanted filename expansion. */
296   if (list)
297     quote_array_assignment_chars (list);
298
299   /* Now that we've split it, perform the shell expansions on each
300      word in the list. */
301   nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL;
302
303   dispose_words (list);
304
305   if (val != value)
306     free (val);
307
308   return nlist;
309 }
310
311 void
312 assign_compound_array_list (var, nlist, flags)
313      SHELL_VAR *var;
314      WORD_LIST *nlist;
315      int flags;
316 {
317   ARRAY *a;
318   WORD_LIST *list;
319   char *w, *val, *nval;
320   int len, iflags;
321   arrayind_t ind, last_ind;
322
323   a = array_cell (var);
324
325   /* Now that we are ready to assign values to the array, kill the existing
326      value. */
327   if (a && (flags & ASS_APPEND) == 0)
328     array_flush (a);
329   last_ind = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
330
331   for (list = nlist; list; list = list->next)
332     {
333       iflags = flags;
334       w = list->word->word;
335
336       /* We have a word of the form [ind]=value */
337       if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
338         {
339           len = skipsubscript (w, 0);
340
341           /* XXX - changes for `+=' */
342           if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
343             {
344               nval = make_variable_value (var, w, flags);
345               if (var->assign_func)
346                 (*var->assign_func) (var, nval, last_ind);
347               else
348                 array_insert (a, last_ind, nval);
349               FREE (nval);
350               last_ind++;
351               continue;
352             }
353
354           if (len == 1)
355             {
356               err_badarraysub (w);
357               continue;
358             }
359
360           if (ALL_ELEMENT_SUB (w[1]) && len == 2)
361             {
362               report_error (_("%s: cannot assign to non-numeric index"), w);
363               continue;
364             }
365
366           ind = array_expand_index (w + 1, len);
367           if (ind < 0)
368             {
369               err_badarraysub (w);
370               continue;
371             }
372           last_ind = ind;
373           /* XXX - changes for `+=' -- just accept the syntax.  ksh93 doesn't do this */
374           if (w[len + 1] == '+' && w[len + 2] == '=')
375             {
376               iflags |= ASS_APPEND;
377               val = w + len + 3;
378             }
379           else
380             val = w + len + 2;
381         }
382       else              /* No [ind]=value, just a stray `=' */
383         {
384           ind = last_ind;
385           val = w;
386         }
387
388       if (integer_p (var))
389         this_command_name = (char *)NULL;       /* no command name for errors */
390       bind_array_var_internal (var, ind, val, iflags);
391       last_ind++;
392     }
393 }
394
395 /* Perform a compound array assignment:  VAR->name=( VALUE ).  The
396    VALUE has already had the parentheses stripped. */
397 SHELL_VAR *
398 assign_array_var_from_string (var, value, flags)
399      SHELL_VAR *var;
400      char *value;
401      int flags;
402 {
403   WORD_LIST *nlist;
404
405   if (value == 0)
406     return var;
407
408   nlist = expand_compound_array_assignment (value, flags);
409   assign_compound_array_list (var, nlist, flags);
410
411   if (nlist)
412     dispose_words (nlist);
413   return (var);
414 }
415
416 /* For each word in a compound array assignment, if the word looks like
417    [ind]=value, quote the `[' and `]' before the `=' to protect them from
418    unwanted filename expansion. */
419 static void
420 quote_array_assignment_chars (list)
421      WORD_LIST *list;
422 {
423   char *s, *t, *nword;
424   int saw_eq;
425   WORD_LIST *l;
426
427   for (l = list; l; l = l->next)
428     {
429       if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
430         continue;       /* should not happen, but just in case... */
431       /* Don't bother if it doesn't look like [ind]=value */
432       if (l->word->word[0] != '[' || xstrchr (l->word->word, '=') == 0) /* ] */
433         continue;
434       s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1);
435       saw_eq = 0;
436       for (t = l->word->word; *t; )
437         {
438           if (*t == '=')
439             saw_eq = 1;
440           if (saw_eq == 0 && (*t == '[' || *t == ']'))
441             *s++ = '\\';
442           *s++ = *t++;
443         }
444       *s = '\0';
445       free (l->word->word);
446       l->word->word = nword;
447     }
448 }
449
450 /* This function assumes s[i] == '['; returns with s[ret] == ']' if
451    an array subscript is correctly parsed. */
452 int
453 skipsubscript (s, i)
454      const char *s;
455      int i;
456 {
457   int count, c;
458 #if defined (HANDLE_MULTIBYTE)
459   mbstate_t state, state_bak;
460   size_t slength, mblength;
461 #endif
462
463 #if defined (HANDLE_MULTIBYTE)
464   memset (&state, '\0', sizeof (mbstate_t));
465   slength = strlen (s + i);
466 #endif
467   
468   count = 1;
469   while (count)
470     {
471       /* Advance one (possibly multibyte) character in S starting at I. */
472 #if defined (HANDLE_MULTIBYTE)
473       if (MB_CUR_MAX > 1)
474         {
475           state_bak = state;
476           mblength = mbrlen (s + i, slength, &state);
477
478           if (MB_INVALIDCH (mblength))
479             {
480               state = state_bak;
481               i++;
482               slength--;
483             }
484           else if (MB_NULLWCH (mblength))
485             return i;
486           else
487             {
488               i += mblength;
489               slength -= mblength;
490             }
491         }
492       else
493 #endif
494       ++i;
495
496       c = s[i];
497
498       if (c == 0)
499         break;
500       else if (c == '[')
501         count++;
502       else if (c == ']')
503         count--;
504     }
505
506   return i;
507 }
508
509 /* This function is called with SUB pointing to just after the beginning
510    `[' of an array subscript and removes the array element to which SUB
511    expands from array VAR.  A subscript of `*' or `@' unsets the array. */
512 int
513 unbind_array_element (var, sub)
514      SHELL_VAR *var;
515      char *sub;
516 {
517   int len;
518   arrayind_t ind;
519   ARRAY_ELEMENT *ae;
520
521   len = skipsubscript (sub, 0);
522   if (sub[len] != ']' || len == 0)
523     {
524       builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
525       return -1;
526     }
527   sub[len] = '\0';
528
529   if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
530     {
531       unbind_variable (var->name);
532       return (0);
533     }
534   ind = array_expand_index (sub, len+1);
535   if (ind < 0)
536     {
537       builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
538       return -1;
539     }
540   ae = array_remove (array_cell (var), ind);
541   if (ae)
542     array_dispose_element (ae);
543   return 0;
544 }
545
546 /* Format and output an array assignment in compound form VAR=(VALUES),
547    suitable for re-use as input. */
548 void
549 print_array_assignment (var, quoted)
550      SHELL_VAR *var;
551      int quoted;
552 {
553   char *vstr;
554
555   vstr = array_to_assign (array_cell (var), quoted);
556
557   if (vstr == 0)
558     printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
559   else
560     {
561       printf ("%s=%s\n", var->name, vstr);
562       free (vstr);
563     }
564 }
565
566 /***********************************************************************/
567 /*                                                                     */
568 /* Utility functions to manage arrays and their contents for expansion */
569 /*                                                                     */
570 /***********************************************************************/
571
572 /* Return 1 if NAME is a properly-formed array reference v[sub]. */
573 int
574 valid_array_reference (name)
575      char *name;
576 {
577   char *t;
578   int r, len;
579
580   t = xstrchr (name, '[');      /* ] */
581   if (t)
582     {
583       *t = '\0';
584       r = legal_identifier (name);
585       *t = '[';
586       if (r == 0)
587         return 0;
588       /* Check for a properly-terminated non-blank subscript. */
589       len = skipsubscript (t, 0);
590       if (t[len] != ']' || len == 1)
591         return 0;
592       for (r = 1; r < len; r++)
593         if (whitespace (t[r]) == 0)
594           return 1;
595       return 0;
596     }
597   return 0;
598 }
599
600 /* Expand the array index beginning at S and extending LEN characters. */
601 arrayind_t
602 array_expand_index (s, len)
603      char *s;
604      int len;
605 {
606   char *exp, *t;
607   int expok;
608   arrayind_t val;
609
610   exp = (char *)xmalloc (len);
611   strncpy (exp, s, len - 1);
612   exp[len - 1] = '\0';
613   t = expand_arith_string (exp, 0);
614   this_command_name = (char *)NULL;
615   val = evalexp (t, &expok);
616   free (t);
617   free (exp);
618   if (expok == 0)
619     {
620       last_command_exit_value = EXECUTION_FAILURE;
621
622       top_level_cleanup ();      
623       jump_to_top_level (DISCARD);
624     }
625   return val;
626 }
627
628 /* Return the name of the variable specified by S without any subscript.
629    If SUBP is non-null, return a pointer to the start of the subscript
630    in *SUBP. If LENP is non-null, the length of the subscript is returned
631    in *LENP.  This returns newly-allocated memory. */
632 char *
633 array_variable_name (s, subp, lenp)
634      char *s, **subp;
635      int *lenp;
636 {
637   char *t, *ret;
638   int ind, ni;
639
640   t = xstrchr (s, '[');
641   if (t == 0)
642     {
643       if (subp)
644         *subp = t;
645       if (lenp)
646         *lenp = 0;
647       return ((char *)NULL);
648     }
649   ind = t - s;
650   ni = skipsubscript (s, ind);
651   if (ni <= ind + 1 || s[ni] != ']')
652     {
653       err_badarraysub (s);
654       if (subp)
655         *subp = t;
656       if (lenp)
657         *lenp = 0;
658       return ((char *)NULL);
659     }
660
661   *t = '\0';
662   ret = savestring (s);
663   *t++ = '[';           /* ] */
664
665   if (subp)
666     *subp = t;
667   if (lenp)
668     *lenp = ni - ind;
669
670   return ret;
671 }
672
673 /* Return the variable specified by S without any subscript.  If SUBP is
674    non-null, return a pointer to the start of the subscript in *SUBP.
675    If LENP is non-null, the length of the subscript is returned in *LENP. */
676 SHELL_VAR *
677 array_variable_part (s, subp, lenp)
678      char *s, **subp;
679      int *lenp;
680 {
681   char *t;
682   SHELL_VAR *var;
683
684   t = array_variable_name (s, subp, lenp);
685   if (t == 0)
686     return ((SHELL_VAR *)NULL);
687   var = find_variable (t);
688
689   free (t);
690   return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
691 }
692
693 /* Return a string containing the elements in the array and subscript
694    described by S.  If the subscript is * or @, obeys quoting rules akin
695    to the expansion of $* and $@ including double quoting.  If RTYPE
696    is non-null it gets 1 if the array reference is name[@] or name[*]
697    and 0 otherwise. */
698 static char *
699 array_value_internal (s, quoted, allow_all, rtype)
700      char *s;
701      int quoted, allow_all, *rtype;
702 {
703   int len;
704   arrayind_t ind;
705   char *retval, *t, *temp;
706   WORD_LIST *l;
707   SHELL_VAR *var;
708
709   var = array_variable_part (s, &t, &len);
710
711   /* Expand the index, even if the variable doesn't exist, in case side
712      effects are needed, like ${w[i++]} where w is unset. */
713 #if 0
714   if (var == 0)
715     return (char *)NULL;
716 #endif
717
718   if (len == 0)
719     return ((char *)NULL);      /* error message already printed */
720
721   /* [ */
722   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
723     {
724       if (rtype)
725         *rtype = (t[0] == '*') ? 1 : 2;
726       if (allow_all == 0)
727         {
728           err_badarraysub (s);
729           return ((char *)NULL);
730         }
731       else if (var == 0 || value_cell (var) == 0)
732         return ((char *)NULL);
733       else if (array_p (var) == 0)
734         l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
735       else
736         {
737           l = array_to_word_list (array_cell (var));
738           if (l == (WORD_LIST *)NULL)
739             return ((char *) NULL);
740         }
741
742       if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
743         {
744           temp = string_list_dollar_star (l);
745           retval = quote_string (temp);
746           free (temp);
747         }
748       else      /* ${name[@]} or unquoted ${name[*]} */
749         retval = string_list_dollar_at (l, quoted);
750
751       dispose_words (l);
752     }
753   else
754     {
755       if (rtype)
756         *rtype = 0;
757       ind = array_expand_index (t, len);
758       if (ind < 0)
759         {
760           if (var)
761             err_badarraysub (var->name);
762           else
763             {
764               t[-1] = '\0';
765               err_badarraysub (s);
766               t[-1] = '[';      /* ] */
767             }
768           return ((char *)NULL);
769         }
770       if (var == 0)
771         return ((char *)NULL);
772       if (array_p (var) == 0)
773         return (ind == 0 ? value_cell (var) : (char *)NULL);
774       retval = array_reference (array_cell (var), ind);
775     }
776
777   return retval;
778 }
779
780 /* Return a string containing the elements described by the array and
781    subscript contained in S, obeying quoting for subscripts * and @. */
782 char *
783 array_value (s, quoted, rtype)
784      char *s;
785      int quoted, *rtype;
786 {
787   return (array_value_internal (s, quoted, 1, rtype));
788 }
789
790 /* Return the value of the array indexing expression S as a single string.
791    If ALLOW_ALL is 0, do not allow `@' and `*' subscripts.  This is used
792    by other parts of the shell such as the arithmetic expression evaluator
793    in expr.c. */
794 char *
795 get_array_value (s, allow_all, rtype)
796      char *s;
797      int allow_all, *rtype;
798 {
799   return (array_value_internal (s, 0, allow_all, rtype));
800 }
801
802 char *
803 array_keys (s, quoted)
804      char *s;
805      int quoted;
806 {
807   int len;
808   char *retval, *t, *temp;
809   WORD_LIST *l;
810   SHELL_VAR *var;
811
812   var = array_variable_part (s, &t, &len);
813
814   /* [ */
815   if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
816     return (char *)NULL;
817
818   if (array_p (var) == 0)
819     l = add_string_to_list ("0", (WORD_LIST *)NULL);
820   else
821     {
822       l = array_keys_to_word_list (array_cell (var));
823       if (l == (WORD_LIST *)NULL)
824         return ((char *) NULL);
825     }
826
827   if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
828     {
829       temp = string_list_dollar_star (l);
830       retval = quote_string (temp);
831       free (temp);
832     }
833   else  /* ${!name[@]} or unquoted ${!name[*]} */
834     retval = string_list_dollar_at (l, quoted);
835
836   dispose_words (l);
837   return retval;
838 }
839 #endif /* ARRAY_VARS */