]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20080605 snapshot
authorChet Ramey <chet.ramey@case.edu>
Wed, 7 Dec 2011 14:24:46 +0000 (09:24 -0500)
committerChet Ramey <chet.ramey@case.edu>
Wed, 7 Dec 2011 14:24:46 +0000 (09:24 -0500)
36 files changed:
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
MANIFEST
MANIFEST~
braces.c
builtins/break.def
builtins/common.c
builtins/common.h
builtins/history.def
builtins/read.def
builtins/read.def~
builtins/shift.def
builtins/ulimit.def
builtins/ulimit.def~
doc/bash.1
doc/bash.1~
doc/bashref.texi
doc/bashref.texi~
parse.y
parse.y.save2 [new file with mode: 0644]
parse.y~
subst.c
subst.c.save1
subst.c.save2
subst.c~
subst.h
subst.h~
tests/comsub-posix.right [new file with mode: 0644]
tests/comsub-posix.tests [new file with mode: 0644]
tests/comsub-posix.tests~ [new file with mode: 0644]
tests/comsub-posix1.sub [new file with mode: 0644]
tests/errors.right
tests/redir8.sub
tests/run-comsub-posix [new file with mode: 0644]
variables.h
variables.h~

index 8682715eb91028aee020ce3093ad158556692eeb..dcf0a1382a36b0b0d96f8fc0d8bb64c781ce1b9c 100644 (file)
@@ -15989,3 +15989,82 @@ parse.y
 
 doc/{bash.1,bashref.texi}
        - document the effect of setting PROMPT_DIRTRIM
+
+                                   6/3
+                                   ---
+builtins/ulimit.def
+       - make the multiplier (block size) for -c and -f 512 bytes only if in
+         Posix mode and 1024 bytes otherwise (as in previous versions).  Uses
+         POSIXBLK and BLOCK_SIZE defines to parameterize size based on value
+         of posixly_correct
+
+doc/bashref.texi
+       - document this addition to posix mode
+
+builtins/common.c
+       - change get_numeric_arg to have a calling sequence and return value
+         more closely mimicking general.c:legal_number(), with the addition
+         of a flags word
+       - add extra value for `fatal' argument to get_numeric_arg to force it
+         to return failure to the caller rather than longjmping
+
+builtins/common.h
+       - change prototype declaration for get_numeric_arg
+
+builtins/{break,shift}.def
+       - change calls to get_numeric_arg to deal with new semantics and calling
+         sequence
+
+builtins/history.def
+       - display_history now returns an int
+       - change calling sequence for get_numeric_arg in display_history
+       - display_history now returns failure to the caller if get_numeric_arg
+         detects an invalid number, rather than jumping back to the top level
+       - use value returned by display_history as return status of history
+         builtin, filtered through sh_chkwrite
+       - history no longer aborts compound commands on invalid arguments.
+         fixes problem reported by Chu Li <chul@cn.fujitsu.com>
+
+{braces,subst}.c
+      - extract_command_subst now takes a third flags argument; passed flags
+        are ORd into flags passed to other functions; changed callers
+
+subst.h
+       - move SX_* defines here from subst.c so parse.y:xparse_dolparen can
+         see them and behave appropriately
+       - extract_command_subst now takes a third flags argument; change
+         prototype
+
+subst.c
+       - change extract_command_subst to call xparse_dolparen when extracting
+         a $() construct
+       - change calls to extract_delimited_string to extract_command_subst
+         as appropriate
+       - if command_substitute returns a NULL word desc, don't call
+         dispose_word_desc on it
+
+parse.y
+       - change xparse_dolparen to use the SX_* flags now in subst.h
+
+                                  6/16
+                                  ----
+subst.c
+       - in quote_list, set W_HASQUOTEDNULL flag in the word if quote_string
+         turns "" into CTLNUL
+       - in dequote_list, turn off W_HASQUOTEDNULL flag in the word if
+         dequote_string turns CTLNUL into ""
+       - new function, string_list_pos_params, encapsulates everything
+         needed to turn the positional parameters or an array indexed with
+         '@' or '*' into a string, including taking care of quoting and
+         using the first char of $IFS, when used in another expansion like
+         pattern removal or pattern substitution
+       - change list_remove_pattern, pos_params, pos_params_pat_subst to
+         call string_list_pos_params.  Fixes problems reported by
+         Stephane Chazelas <stephane_chazelas@yahoo.fr>
+
+                                  6/22
+                                  ----
+variables.h
+       - include assoc.h for associative arrays
+       - defines for case-modifying expansions and associative array variables
+       - sh_var_assign_func_t functions now take an extra char * parameter
index 1a438c5af0868b317a00582ec01ba37d5c161c92..d6a8a2b32f24efa421dbf154b8517e1064d60b5f 100644 (file)
@@ -15979,3 +15979,85 @@ parse.y
          substitution.  From bash-4.0-devel branch
        - changes to parse_matched_pair to combine most of the flag variables
          (was_dollar, in_comment, and so on) into a local flags word
+
+                                   6/2
+                                   ---
+parse.y
+       - call trim_pathname, which retains only the last $PROMPT_DIRTRIM
+         directories and replaces the intervening characters with `...',
+         when expanding \w and \W
+
+doc/{bash.1,bashref.texi}
+       - document the effect of setting PROMPT_DIRTRIM
+
+                                   6/3
+                                   ---
+builtins/ulimit.def
+       - make the multiplier (block size) for -c and -f 512 bytes only if in
+         Posix mode and 1024 bytes otherwise (as in previous versions).  Uses
+         POSIXBLK and BLOCK_SIZE defines to parameterize size based on value
+         of posixly_correct
+
+doc/bashref.texi
+       - document this addition to posix mode
+
+builtins/common.c
+       - change get_numeric_arg to have a calling sequence and return value
+         more closely mimicking general.c:legal_number(), with the addition
+         of a flags word
+       - add extra value for `fatal' argument to get_numeric_arg to force it
+         to return failure to the caller rather than longjmping
+
+builtins/common.h
+       - change prototype declaration for get_numeric_arg
+
+builtins/{break,shift}.def
+       - change calls to get_numeric_arg to deal with new semantics and calling
+         sequence
+
+builtins/history.def
+       - display_history now returns an int
+       - change calling sequence for get_numeric_arg in display_history
+       - display_history now returns failure to the caller if get_numeric_arg
+         detects an invalid number, rather than jumping back to the top level
+       - use value returned by display_history as return status of history
+         builtin, filtered through sh_chkwrite
+       - history no longer aborts compound commands on invalid arguments.
+         fixes problem reported by Chu Li <chul@cn.fujitsu.com>
+
+{braces,subst}.c
+      - extract_command_subst now takes a third flags argument; passed flags
+        are ORd into flags passed to other functions; changed callers
+
+subst.h
+       - move SX_* defines here from subst.c so parse.y:xparse_dolparen can
+         see them and behave appropriately
+       - extract_command_subst now takes a third flags argument; change
+         prototype
+
+subst.c
+       - change extract_command_subst to call xparse_dolparen when extracting
+         a $() construct
+       - change calls to extract_delimited_string to extract_command_subst
+         as appropriate
+       - if command_substitute returns a NULL word desc, don't call
+         dispose_word_desc on it
+
+parse.y
+       - change xparse_dolparen to use the SX_* flags now in subst.h
+
+                                  6/16
+                                  ----
+subst.c
+       - in quote_list, set W_HASQUOTEDNULL flag in the word if quote_string
+         turns "" into CTLNUL
+       - in dequote_list, turn off W_HASQUOTEDNULL flag in the word if
+         dequote_string turns CTLNUL into ""
+       - new function, string_list_pos_params, encapsulates everything
+         needed to turn the positional parameters or an array indexed with
+         '@' or '*' into a string, including taking care of quoting and
+         using the first char of $IFS, when used in another expansion like
+         pattern removal or pattern substitution
+       - change list_remove_pattern, pos_params, pos_params_pat_subst to
+         call string_list_pos_params.  Fixes problems reported by
+         Stephane Chazelas <stephane_chazelas@yahoo.fr>
index 6eb4ec7270c423d08f0230cd7249d2bd9a08c9a8..37d2b92607cdfee39a1178fca08a4791a1ff9a14 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -763,6 +763,9 @@ tests/case.tests    f
 tests/case.right       f
 tests/comsub.tests     f
 tests/comsub.right     f
+tests/comsub-posix.tests       f
+tests/comsub-posix.right       f
+tests/comsub-posix1.sub        f
 tests/cond.tests       f
 tests/cond.right       f
 tests/cond-regexp.sub  f
@@ -925,6 +928,7 @@ tests/run-braces    f
 tests/run-builtins     f
 tests/run-case         f
 tests/run-comsub       f
+tests/run-comsub-posix f
 tests/run-cond         f
 tests/run-cprint       f
 tests/run-dbg-support  f
index 2e09b62bd29e77dce9f78ffee494c20e6d765b5f..fa6eafc7bf407d2aa3aa882cd90af3afb0f44fdb 100644 (file)
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -759,8 +759,12 @@ tests/source3.sub  f
 tests/source4.sub      f
 tests/source5.sub      f
 tests/source6.sub      f
+tests/case.tests       f
+tests/case.right       f
 tests/comsub.tests     f
 tests/comsub.right     f
+tests/comsub-posix.tests       f
+tests/comsub-posix.right       f
 tests/cond.tests       f
 tests/cond.right       f
 tests/cond-regexp.sub  f
@@ -921,7 +925,9 @@ tests/run-array             f
 tests/run-array2       f
 tests/run-braces       f
 tests/run-builtins     f
+tests/run-case         f
 tests/run-comsub       f
+tests/run-comsub-posix f
 tests/run-cond         f
 tests/run-cprint       f
 tests/run-dbg-support  f
index 5810d0f5ee5a83da250c15c677452de0fd069e8f..86d1413a103cdb291c4bc5237e0d534afb52d90d 100644 (file)
--- a/braces.c
+++ b/braces.c
@@ -538,7 +538,7 @@ brace_gobbler (text, tlen, indx, satisfy)
       if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(')                      /* ) */
        {
          si = i + 2;
-         t = extract_command_subst (text, &si);
+         t = extract_command_subst (text, &si, 0);
          i = si;
          free (t);
          i++;
index 76326f0a489db77b054b0c262dde9f5f61e8b0c6..fbfe40eb253bac6094dc375713e9489bfed8b4e0 100644 (file)
@@ -71,7 +71,7 @@ break_builtin (list)
   if (check_loop_level () == 0)
     return (EXECUTION_SUCCESS);
 
-  newbreak = get_numeric_arg (list, 1);
+  (void)get_numeric_arg (list, 1, &newbreak);
 
   if (newbreak <= 0)
     {
@@ -111,7 +111,7 @@ continue_builtin (list)
   if (check_loop_level () == 0)
     return (EXECUTION_SUCCESS);
 
-  newcont = get_numeric_arg (list, 1);
+  (void)get_numeric_arg (list, 1, &newcont);
 
   if (newcont <= 0)
     {
index 8f047ce5202f88dfd5661265527dd3d889cc181c..1a802c3ceb37ee86de28e36aff7e7717fde309c4 100644 (file)
@@ -399,30 +399,35 @@ set_dollar_vars_changed ()
 /* Read a numeric arg for this_command_name, the name of the shell builtin
    that wants it.  LIST is the word list that the arg is to come from.
    Accept only the numeric argument; report an error if other arguments
-   follow.  If FATAL is true, call throw_to_top_level, which exits the
-   shell; if not, call jump_to_top_level (DISCARD), which aborts the
-   current command. */
-intmax_t
-get_numeric_arg (list, fatal)
+   follow.  If FATAL is 1, call throw_to_top_level, which exits the
+   shell; if it's 2, call jump_to_top_level (DISCARD), which aborts the
+   current command; if FATAL is 0, return an indication of an invalid
+   number by setting *NUMOK == 0 and return -1. */
+int
+get_numeric_arg (list, fatal, count)
      WORD_LIST *list;
      int fatal;
+     intmax_t *count;
 {
-  intmax_t count = 1;
+  char *arg;
+
+  if (count)
+    *count = 1;
 
   if (list && list->word && ISOPTION (list->word->word, '-'))
     list = list->next;
 
   if (list)
     {
-      register char *arg;
-
       arg = list->word->word;
-      if (arg == 0 || (legal_number (arg, &count) == 0))
+      if (arg == 0 || (legal_number (arg, count) == 0))
        {
-         sh_neednumarg (list->word->word);
-         if (fatal)
+         sh_neednumarg (list->word->word ? list->word->word : "`'");
+         if (fatal == 0)
+           return 0;
+         else if (fatal == 1)          /* fatal == 1; abort */
            throw_to_top_level ();
-         else
+         else                          /* fatal == 2; discard current command */
            {
              top_level_cleanup ();
              jump_to_top_level (DISCARD);
@@ -431,7 +436,7 @@ get_numeric_arg (list, fatal)
       no_args (list->next);
     }
 
-  return (count);
+  return (1);
 }
 
 /* Get an eight-bit status value from LIST */
index ec1200b5ffb642fdfad0ad21f024b8056c4a9a11..190ae41e8e7872b12e81925f4d15e255d7c1ef75 100644 (file)
@@ -89,7 +89,7 @@ extern int dollar_vars_changed __P((void));
 extern void set_dollar_vars_unchanged __P((void));
 extern void set_dollar_vars_changed __P((void));
 
-extern intmax_t get_numeric_arg __P((WORD_LIST *, int));
+extern int get_numeric_arg __P((WORD_LIST *, int, intmax_t *));
 extern int get_exitstat __P((WORD_LIST *));
 extern int read_octal __P((char *));
 
index f21e7c9e3ccb73bd7c9a2c55eee1364e491008c3..19c2b81c08a5131db73456265230ddb3354a6225 100644 (file)
@@ -88,7 +88,7 @@ extern int current_command_line_count;
 extern int force_append_history;       /* shopt -s histappend */
 
 static char *histtime __P((HIST_ENTRY *, const char *));
-static void display_history __P((WORD_LIST *));
+static int display_history __P((WORD_LIST *));
 static void push_history __P((WORD_LIST *));
 static int expand_and_print_history __P((WORD_LIST *));
 
@@ -197,8 +197,8 @@ history_builtin (list)
     }
   else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0)
     {
-      display_history (list);
-      return (sh_chkwrite (EXECUTION_SUCCESS));
+      result = display_history (list);
+      return (sh_chkwrite (result));
     }
 
   filename = list ? list->word->word : get_string_value ("HISTFILE");
@@ -260,7 +260,7 @@ histtime (hlist, histtimefmt)
   return timestr;
 }
 
-static void
+static int
 display_history (list)
      WORD_LIST *list;
 {
@@ -271,7 +271,9 @@ display_history (list)
 
   if (list)
     {
-      limit = get_numeric_arg (list, 0);
+      if (get_numeric_arg (list, 0, &limit) == 0)
+       return (EXECUTION_FAILURE);
+
       if (limit < 0)
        limit = -limit;
     }
@@ -305,6 +307,8 @@ display_history (list)
          i++;
        }
     }
+
+  return (EXECUTION_SUCCESS);
 }
 
 /* Remove the last entry in the history list and add each argument in
index d7a43202a9617ddf9efa735c83d0192fe5002810..4945538dd6bf44884e2cc892d82488b423e79d52 100644 (file)
@@ -51,7 +51,8 @@ Options:
   -t timeout   time out and return failure if a complete line of input is
                not read withint TIMEOUT seconds.  The value of the TMOUT
                variable is the default timeout.  TIMEOUT may be a
-               fractional number.
+               fractional number.  The exit status is greater than 128 if
+               the timeout is exceeded
   -u fd                read from file descriptor FD instead of the standard input
 
 Exit Status:
@@ -374,7 +375,7 @@ read_builtin (list)
          return (EXECUTION_FAILURE);
 #else
          input_string[i] = '\0';       /* make sure it's terminated */
-         retval = EXECUTION_FAILURE;
+         retval = 128+SIGALRM;;
          goto assign_vars;
 #endif
        }
index bf1e030044e83e5b952a55615498ae8ff0a4a3d6..d7a43202a9617ddf9efa735c83d0192fe5002810 100644 (file)
@@ -573,7 +573,7 @@ add_char:
   if (tmsec > 0 || tmusec > 0)
     reset_alarm ();
 
-<  if (nchars > 0 || delim != '\n')
+  if (nchars > 0 || delim != '\n')
     {
 #if defined (READLINE)
       if (edit)
index 37c12529b80145c00097b14c8c35291cdafa1dc8..fb2cc992d1f0b185a929236fe62cca8a419cbc42 100644 (file)
@@ -62,7 +62,8 @@ shift_builtin (list)
   register int count;
   WORD_LIST *temp;
 
-  times = get_numeric_arg (list, 0);
+  if (get_numeric_arg (list, 0, &times) == 0)
+    return (EXECUTION_FAILURE);
 
   if (times == 0)
     return (EXECUTION_SUCCESS);
index b47dd8239af28ea31cd2eec6f0704936d109a15c..47904394cee745129c6564ade42d59f6f9601596 100644 (file)
@@ -179,6 +179,14 @@ extern int errno;
 #define LIMIT_HARD 0x01
 #define LIMIT_SOFT 0x02
 
+/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes
+   otherwise. */
+#define POSIXBLK       -2
+
+#define BLOCKSIZE(x)   (((x) == POSIXBLK && posixly_correct) ? 512 : 1024)
+
+extern int posixly_correct;
+
 static int _findlim __P((int));
 
 static int ulimit_internal __P((int, char *, int, int));
@@ -212,7 +220,7 @@ static RESOURCE_LIMITS limits[] = {
   { 'b',       RLIMIT_SBSIZE,  1,      "socket buffer size",   "bytes" },
 #endif
 #ifdef RLIMIT_CORE
-  { 'c',       RLIMIT_CORE,  512,      "core file size",       "blocks" },
+  { 'c',       RLIMIT_CORE,  POSIXBLK, "core file size",       "blocks" },
 #endif
 #ifdef RLIMIT_DATA
   { 'd',       RLIMIT_DATA,  1024,     "data seg size",        "kbytes" },
@@ -220,7 +228,7 @@ static RESOURCE_LIMITS limits[] = {
 #ifdef RLIMIT_NICE
   { 'e',       RLIMIT_NICE,  1,        "scheduling priority",  (char *)NULL },
 #endif
-  { 'f',       RLIMIT_FILESIZE, 512,   "file size",            "blocks" },
+  { 'f',       RLIMIT_FILESIZE, POSIXBLK,      "file size",            "blocks" },
 #ifdef RLIMIT_SIGPENDING
   { 'i',       RLIMIT_SIGPENDING, 1,   "pending signals",      (char *)NULL },
 #endif
@@ -438,7 +446,7 @@ ulimit_internal (cmd, cmdarg, mode, multiple)
   else if (all_digits (cmdarg))
     {
       limit = string_to_rlimtype (cmdarg);
-      block_factor = limits[limind].block_factor;
+      block_factor = BLOCKSIZE(limits[limind].block_factor);
       real_limit = limit * block_factor;
 
       if ((real_limit / block_factor) != limit)
@@ -702,7 +710,7 @@ printone (limind, curlim, pdesc)
   char unitstr[64];
   int factor;
 
-  factor = limits[limind].block_factor;
+  factor = BLOCKSIZE(limits[limind].block_factor);
   if (pdesc)
     {
       if (limits[limind].units)
index 8f8fb1aa5b049030a493b9a78b96c0b602cfc4ff..f3d11ac08f174be7f5402afb62007f82ed8fb185 100644 (file)
@@ -61,6 +61,9 @@ no option is given, then -f is assumed.
 Values are in 1024-byte increments, except for -t, which is in seconds,
 -p, which is in increments of 512 bytes, and -u, which is an unscaled
 number of processes.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
 $END
 
 #if !defined (_MINIX)
@@ -176,6 +179,14 @@ extern int errno;
 #define LIMIT_HARD 0x01
 #define LIMIT_SOFT 0x02
 
+/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes
+   otherwise. */
+#define POSIXBLK       -2
+
+#define BLOCKSIZE(x)   (((x) == POSIXBLK && posixly_correct) ? 512 : (x))
+
+extern int posixly_correct;
+
 static int _findlim __P((int));
 
 static int ulimit_internal __P((int, char *, int, int));
@@ -209,7 +220,7 @@ static RESOURCE_LIMITS limits[] = {
   { 'b',       RLIMIT_SBSIZE,  1,      "socket buffer size",   "bytes" },
 #endif
 #ifdef RLIMIT_CORE
-  { 'c',       RLIMIT_CORE,  512,      "core file size",       "blocks" },
+  { 'c',       RLIMIT_CORE,  POSIXBLK, "core file size",       "blocks" },
 #endif
 #ifdef RLIMIT_DATA
   { 'd',       RLIMIT_DATA,  1024,     "data seg size",        "kbytes" },
@@ -217,7 +228,7 @@ static RESOURCE_LIMITS limits[] = {
 #ifdef RLIMIT_NICE
   { 'e',       RLIMIT_NICE,  1,        "scheduling priority",  (char *)NULL },
 #endif
-  { 'f',       RLIMIT_FILESIZE, 512,   "file size",            "blocks" },
+  { 'f',       RLIMIT_FILESIZE, POSIXBLK,      "file size",            "blocks" },
 #ifdef RLIMIT_SIGPENDING
   { 'i',       RLIMIT_SIGPENDING, 1,   "pending signals",      (char *)NULL },
 #endif
@@ -435,7 +446,7 @@ ulimit_internal (cmd, cmdarg, mode, multiple)
   else if (all_digits (cmdarg))
     {
       limit = string_to_rlimtype (cmdarg);
-      block_factor = limits[limind].block_factor;
+      block_factor = BLOCKSIZE(limits[limind].block_factor);
       real_limit = limit * block_factor;
 
       if ((real_limit / block_factor) != limit)
@@ -699,7 +710,7 @@ printone (limind, curlim, pdesc)
   char unitstr[64];
   int factor;
 
-  factor = limits[limind].block_factor;
+  factor = BLOCKSIZE(limits[limind].block_factor);
   if (pdesc)
     {
       if (limits[limind].units)
index 224bdb37d6b9e0679b3efbab57fca4418a064647..32ce9bd33c5ee818f8027af343fe9de27115fe0f 100644 (file)
@@ -4242,6 +4242,8 @@ The
 .I "previous job"
 may be referenced using
 .BR %\- .
+If there is only a single job, \fB%+\fP and \fB%\-\fP can both be used
+to refer to that job.
 In output pertaining to jobs (e.g., the output of the
 .B jobs
 command), the current job is always flagged with a
@@ -7749,8 +7751,10 @@ Cause \fBread\fP to time out and return failure if a complete line of
 input is not read within \fItimeout\fP seconds.
 \fItimeout\fP may be a decimal number with a fractional portion following
 the decimal point.
-This option has no effect if \fBread\fP is not reading input from the
-terminal or a pipe.
+This option is only effective if \fBread\fP is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
+The exit status is greater than 128 if the timeout is exceeded.
 .TP
 .B \-u \fIfd\fP
 Read input from file descriptor \fIfd\fP.
index 30d90470a60e07b97f80f687e7d3ff50ca1c803b..abda317e46cc44e33a4a712c4ec0432ff864483c 100644 (file)
@@ -5,12 +5,12 @@
 .\"    Case Western Reserve University
 .\"    chet@po.cwru.edu
 .\"
-.\"    Last Change: Sun May 25 10:41:29 EDT 2008
+.\"    Last Change: Mon Jun  2 20:32:53 EDT 2008
 .\"
 .\" bash_builtins, strip all but Built-Ins section
 .if \n(zZ=1 .ig zZ
 .if \n(zY=1 .ig zY
-.TH BASH 1 "2008 May 25" "GNU Bash-4.0"
+.TH BASH 1 "2008 June 2" "GNU Bash-4.0"
 .\"
 .\" There's some problem with having a `@'
 .\" in a tagged paragraph with the BSD man macros.
@@ -4242,6 +4242,8 @@ The
 .I "previous job"
 may be referenced using
 .BR %\- .
+If there is only a single job, \fB%+\fP and \fB%\-\fP can both be used
+to refer to that job.
 In output pertaining to jobs (e.g., the output of the
 .B jobs
 command), the current job is always flagged with a
@@ -7749,8 +7751,9 @@ Cause \fBread\fP to time out and return failure if a complete line of
 input is not read within \fItimeout\fP seconds.
 \fItimeout\fP may be a decimal number with a fractional portion following
 the decimal point.
-This option has no effect if \fBread\fP is not reading input from the
-terminal or a pipe.
+This option is only effective if \fBread\fP is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
 .TP
 .B \-u \fIfd\fP
 Read input from file descriptor \fIfd\fP.
index 38fec7fcf5c43fa6ed2875387ee3dfa71893d8ae..98f852bec75dbe992f33a63cfe7ac4cfa7a4734a 100644 (file)
@@ -3661,8 +3661,10 @@ Cause @code{read} to time out and return failure if a complete line of
 input is not read within @var{timeout} seconds.
 @var{timeout}  may be a decimal number with a fractional portion following
 the decimal point.
-This option has no effect if @code{read} is not reading input from the
-terminal or a pipe.
+This option is only effective if @code{read} is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
+The exit status is greater than 128 if the timeout is exceeded.
 
 @item -u @var{fd}
 Read input from file descriptor @var{fd}.
@@ -6351,6 +6353,10 @@ When the @code{xpg_echo} option is enabled, Bash does not attempt to interpret
 any arguments to @code{echo} as options.  Each argument is displayed, after
 escape characters are converted.
 
+@item
+The @code{ulimit} builtin uses a block size of 512 bytes for the @option{-c}
+and @option{-f} options.
+
 @end enumerate
 
 There is other @sc{posix} behavior that Bash does not implement by
@@ -6454,9 +6460,11 @@ current job, which is the last job stopped while it was in the foreground
 or started in the background.
 A single @samp{%} (with no accompanying job specification) also refers
 to the current job.
-The previous job may be referenced using @samp{%-}.  In output
-pertaining to jobs (e.g., the output of the @code{jobs} command),
-the current job is always flagged with a @samp{+}, and the
+The previous job may be referenced using @samp{%-}.
+If there is only a single job, @samp{%+} and @samp{%-} can both be used
+to refer to that job.
+In output pertaining to jobs (e.g., the output of the @code{jobs}
+command), the current job is always flagged with a @samp{+}, and the
 previous job with a @samp{-}. 
 
 A job may also be referred to
index 0ccfa392bda14e8c52756dc9b25ea1df728e2351..64961cd096ef125b0e87d11c154e5d8ebc411d22 100644 (file)
@@ -3661,8 +3661,9 @@ Cause @code{read} to time out and return failure if a complete line of
 input is not read within @var{timeout} seconds.
 @var{timeout}  may be a decimal number with a fractional portion following
 the decimal point.
-This option has no effect if @code{read} is not reading input from the
-terminal or a pipe.
+This option is only effective if @code{read} is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
 
 @item -u @var{fd}
 Read input from file descriptor @var{fd}.
@@ -6056,7 +6057,8 @@ The version of Bash (e.g., 2.00)
 @item \V
 The release of Bash, version + patchlevel (e.g., 2.00.0)
 @item \w
-The current working directory, with @env{$HOME} abbreviated with a tilde.
+The current working directory, with @env{$HOME} abbreviated with a tilde
+(uses the @env{$PROMPT_DIRTRIM} variable).
 @item \W
 The basename of @env{$PWD}, with @env{$HOME} abbreviated with a tilde.
 @item \!
@@ -6350,6 +6352,10 @@ When the @code{xpg_echo} option is enabled, Bash does not attempt to interpret
 any arguments to @code{echo} as options.  Each argument is displayed, after
 escape characters are converted.
 
+@item
+The @code{ulimit} builtin uses a block size of 512 bytes for the @option{-c}
+and @option{-f} options.
+
 @end enumerate
 
 There is other @sc{posix} behavior that Bash does not implement by
@@ -6453,9 +6459,11 @@ current job, which is the last job stopped while it was in the foreground
 or started in the background.
 A single @samp{%} (with no accompanying job specification) also refers
 to the current job.
-The previous job may be referenced using @samp{%-}.  In output
-pertaining to jobs (e.g., the output of the @code{jobs} command),
-the current job is always flagged with a @samp{+}, and the
+The previous job may be referenced using @samp{%-}.
+If there is only a single job, @samp{%+} and @samp{%-} can both be used
+to refer to that job.
+In output pertaining to jobs (e.g., the output of the @code{jobs}
+command), the current job is always flagged with a @samp{+}, and the
 previous job with a @samp{-}. 
 
 A job may also be referred to
diff --git a/parse.y b/parse.y
index 638ad52cb2ea3b249e2eaf5475791d0e23172359..68098628fb0510a9347a12c77e9b4f73f7326dee 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -372,6 +372,8 @@ inputunit:  simple_list simple_list_terminator
                          global_command = $1;
                          eof_encountered = 0;
                          /* discard_parser_constructs (0); */
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       '\n'
@@ -379,6 +381,8 @@ inputunit:  simple_list simple_list_terminator
                          /* Case of regular command, but not a very
                             interesting one.  Return a NULL command. */
                          global_command = (COMMAND *)NULL;
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       error '\n'
@@ -770,7 +774,6 @@ function_def:       WORD '(' ')' newline_list function_body
                        { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
        ;
 
-
 function_body: shell_command
                        { $$ = $1; }
        |       shell_command redirection_list
@@ -953,6 +956,13 @@ simple_list:       simple_list1
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 '&'
                        {
@@ -962,12 +972,26 @@ simple_list:      simple_list1
                            $$ = command_connect ($1, (COMMAND *)NULL, '&');
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 ';'
                        {
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        ;
 
@@ -2258,6 +2282,15 @@ yylex ()
   token_before_that = last_read_token;
   last_read_token = current_token;
   current_token = read_token (READ);
+
+  if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+    {
+      current_token = yacc_EOF;
+      if (bash_input.type == st_string)
+       rewind_input_string ();
+    }
+  parser_state &= ~PST_EOFTOKEN;
+
   return (current_token);
 }
 
@@ -2686,6 +2719,7 @@ read_token (command)
 #if defined (ALIAS)
              parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
+
              peek_char = shell_getc (1);
              if MBTEST(peek_char == '&')
                return (SEMI_SEMI_AND);
@@ -2895,7 +2929,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
       /* Not exactly right yet, should handle shell metacharacters, too.  If
         any changes are made to this test, make analogous changes to subst.c:
         extract_delimited_string(). */
-      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || whitespace (ret[retind - 1])))
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
        tflags |= LEX_INCOMMENT;
 
       if (tflags & LEX_PASSNEXT)               /* last char was backslash */
@@ -3015,7 +3049,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
          if (open == ch)       /* undo previous increment */
            count--;
          if (ch == '(')                /* ) */
-           nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
          else if (ch == '{')           /* } */
            nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
          else if (ch == '[')           /* ] */
@@ -3414,7 +3448,7 @@ xparse_dolparen (base, string, indp, flags)
   ostring = string;
 
   sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
-  if (flags & 0x010)           /* EX_NOLONGJMP */
+  if (flags & SX_NOLONGJMP)
     sflags |= SEVAL_NOLONGJMP;
   save_parser_state (&ps);
 
@@ -3436,7 +3470,8 @@ xparse_dolparen (base, string, indp, flags)
   if (ep[-1] != ')')
     {
 #if DEBUG
-      itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+      if (ep[-1] != '\n')
+       itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
 #endif
       while (ep > ostring && ep[-1] == '\n') ep--;
     }
@@ -3450,7 +3485,7 @@ xparse_dolparen (base, string, indp, flags)
     itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
 #endif
 
-  if (flags & 1
+  if (flags & SX_NOALLOC
     return (char *)NULL;
 
   if (nc == 0)
@@ -4017,7 +4052,7 @@ read_token_word (character)
                     history literally rather than causing a possibly-
                     incorrect `;' to be added. ) */
                  push_delimiter (dstack, peek_char);
-                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+                 ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
                  pop_delimiter (dstack);
                }
              else
@@ -4424,7 +4459,7 @@ history_delimiting_chars ()
     {
       /* Tricky.  `for i\nin ...' should not have a semicolon, but
         `for i\ndo ...' should.  We do what we can. */
-      for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+      for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
        ;
       if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
        return " ";
diff --git a/parse.y.save2 b/parse.y.save2
new file mode 100644 (file)
index 0000000..2b11637
--- /dev/null
@@ -0,0 +1,5538 @@
+/* Yacc grammar for bash. */
+
+/* Copyright (C) 1989-2008 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 2, 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; see the file LICENSE.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+%{
+#include "config.h"
+
+#include "bashtypes.h"
+#include "bashansi.h"
+
+#include "filecntl.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_LOCALE_H)
+#  include <locale.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include <signal.h>
+
+#include "memalloc.h"
+
+#include "bashintl.h"
+
+#define NEED_STRFTIME_DECL     /* used in externs.h */
+
+#include "shell.h"
+#include "trap.h"
+#include "flags.h"
+#include "parser.h"
+#include "mailcheck.h"
+#include "test.h"
+#include "builtins.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
+
+#include "shmbutil.h"
+
+#if defined (READLINE)
+#  include "bashline.h"
+#  include <readline/readline.h>
+#endif /* READLINE */
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#  include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (JOB_CONTROL)
+#  include "jobs.h"
+#endif /* JOB_CONTROL */
+
+#if defined (ALIAS)
+#  include "alias.h"
+#else
+typedef void *alias_t;
+#endif /* ALIAS */
+
+#if defined (PROMPT_STRING_DECODE)
+#  ifndef _MINIX
+#    include <sys/param.h>
+#  endif
+#  include <time.h>
+#  if defined (TM_IN_SYS_TIME)
+#    include <sys/types.h>
+#    include <sys/time.h>
+#  endif /* TM_IN_SYS_TIME */
+#  include "maxpath.h"
+#endif /* PROMPT_STRING_DECODE */
+
+#define RE_READ_TOKEN  -99
+#define NO_EXPANSION   -100
+
+#ifdef DEBUG
+#  define YYDEBUG 1
+#else
+#  define YYDEBUG 0
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+#  define last_shell_getc_is_singlebyte \
+       ((shell_input_line_index > 1) \
+               ? shell_input_line_property[shell_input_line_index - 1] \
+               : 1)
+#  define MBTEST(x)    ((x) && last_shell_getc_is_singlebyte)
+#else
+#  define last_shell_getc_is_singlebyte        1
+#  define MBTEST(x)    ((x))
+#endif
+
+#if defined (EXTENDED_GLOB)
+extern int extended_glob;
+#endif
+
+extern int eof_encountered;
+extern int no_line_editing, running_under_emacs;
+extern int current_command_number;
+extern int sourcelevel;
+extern int posixly_correct;
+extern int last_command_exit_value;
+extern char *shell_name, *current_host_name;
+extern char *dist_version;
+extern int patch_level;
+extern int dump_translatable_strings, dump_po_strings;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+#if defined (BUFFERED_INPUT)
+extern int bash_input_fd_changed;
+#endif
+
+extern int errno;
+/* **************************************************************** */
+/*                                                                 */
+/*                 "Forward" declarations                          */
+/*                                                                 */
+/* **************************************************************** */
+
+#ifdef DEBUG
+static void debug_parser __P((int));
+#endif
+
+static int yy_getc __P((void));
+static int yy_ungetc __P((int));
+
+#if defined (READLINE)
+static int yy_readline_get __P((void));
+static int yy_readline_unget __P((int));
+#endif
+
+static int yy_string_get __P((void));
+static int yy_string_unget __P((int));
+static void rewind_input_string __P((void));
+static int yy_stream_get __P((void));
+static int yy_stream_unget __P((int));
+
+static int shell_getc __P((int));
+static void shell_ungetc __P((int));
+static void discard_until __P((int));
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+static void push_string __P((char *, int, alias_t *));
+static void pop_string __P((void));
+static void free_string_list __P((void));
+#endif
+
+static char *read_a_line __P((int));
+
+static int reserved_word_acceptable __P((int));
+static int yylex __P((void));
+static int alias_expand_token __P((char *));
+static int time_command_acceptable __P((void));
+static int special_case_tokens __P((char *));
+static int read_token __P((int));
+static char *parse_matched_pair __P((int, int, int, int *, int));
+static char *parse_comsub __P((int, int, int, int *, int));
+#if defined (ARRAY_VARS)
+static char *parse_compound_assignment __P((int *));
+#endif
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+static int parse_dparen __P((int));
+static int parse_arith_cmd __P((char **, int));
+#endif
+#if defined (COND_COMMAND)
+static void cond_error __P((void));
+static COND_COM *cond_expr __P((void));
+static COND_COM *cond_or __P((void));
+static COND_COM *cond_and __P((void));
+static COND_COM *cond_term __P((void));
+static int cond_skip_newlines __P((void));
+static COMMAND *parse_cond_command __P((void));
+#endif
+#if defined (ARRAY_VARS)
+static int token_is_assignment __P((char *, int));
+static int token_is_ident __P((char *, int));
+#endif
+static int read_token_word __P((int));
+static void discard_parser_constructs __P((int));
+
+static char *error_token_from_token __P((int));
+static char *error_token_from_text __P((void));
+static void print_offending_line __P((void));
+static void report_syntax_error __P((char *));
+
+static void handle_eof_input_unit __P((void));
+static void prompt_again __P((void));
+#if 0
+static void reset_readline_prompt __P((void));
+#endif
+static void print_prompt __P((void));
+
+#if defined (HANDLE_MULTIBYTE)
+static void set_line_mbstate __P((void));
+static char *shell_input_line_property = NULL;
+#else
+#  define set_line_mbstate()
+#endif
+
+extern int yyerror __P((const char *));
+
+#ifdef DEBUG
+extern int yydebug;
+#endif
+
+/* Default prompt strings */
+char *primary_prompt = PPROMPT;
+char *secondary_prompt = SPROMPT;
+
+/* PROMPT_STRING_POINTER points to one of these, never to an actual string. */
+char *ps1_prompt, *ps2_prompt;
+
+/* Handle on the current prompt string.  Indirectly points through
+   ps1_ or ps2_prompt. */
+char **prompt_string_pointer = (char **)NULL;
+char *current_prompt_string;
+
+/* Non-zero means we expand aliases in commands. */
+int expand_aliases = 0;
+
+/* If non-zero, the decoded prompt string undergoes parameter and
+   variable substitution, command substitution, arithmetic substitution,
+   string expansion, process substitution, and quote removal in
+   decode_prompt_string. */
+int promptvars = 1;
+
+/* If non-zero, $'...' and $"..." are expanded when they appear within
+   a ${...} expansion, even when the expansion appears within double
+   quotes. */
+int extended_quote = 1;
+
+/* The decoded prompt string.  Used if READLINE is not defined or if
+   editing is turned off.  Analogous to current_readline_prompt. */
+static char *current_decoded_prompt;
+
+/* The number of lines read from input while creating the current command. */
+int current_command_line_count;
+
+/* The token that currently denotes the end of parse. */
+int shell_eof_token;
+
+/* The token currently being read. */
+int current_token;
+
+/* Variables to manage the task of reading here documents, because we need to
+   defer the reading until after a complete command has been collected. */
+static REDIRECT *redir_stack[10];
+int need_here_doc;
+
+/* Where shell input comes from.  History expansion is performed on each
+   line when the shell is interactive. */
+static char *shell_input_line = (char *)NULL;
+static int shell_input_line_index;
+static int shell_input_line_size;      /* Amount allocated for shell_input_line. */
+static int shell_input_line_len;       /* strlen (shell_input_line) */
+
+/* Either zero or EOF. */
+static int shell_input_line_terminator;
+
+/* The line number in a script on which a function definition starts. */
+static int function_dstart;
+
+/* The line number in a script on which a function body starts. */
+static int function_bstart;
+
+/* The line number in a script at which an arithmetic for command starts. */
+static int arith_for_lineno;
+
+/* The current parser state. */
+static int parser_state;
+
+/* The last read token, or NULL.  read_token () uses this for context
+   checking. */
+static int last_read_token;
+
+/* The token read prior to last_read_token. */
+static int token_before_that;
+
+/* The token read prior to token_before_that. */
+static int two_tokens_ago;
+
+/* The line number in a script where the word in a `case WORD', `select WORD'
+   or `for WORD' begins.  This is a nested command maximum, since the array
+   index is decremented after a case, select, or for command is parsed. */
+#define MAX_CASE_NEST  128
+static int word_lineno[MAX_CASE_NEST];
+static int word_top = -1;
+
+/* If non-zero, it is the token that we want read_token to return
+   regardless of what text is (or isn't) present to be read.  This
+   is reset by read_token.  If token_to_read == WORD or
+   ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
+static int token_to_read;
+static WORD_DESC *word_desc_to_read;
+
+static REDIRECTEE redir;
+%}
+
+%union {
+  WORD_DESC *word;             /* the word that we read. */
+  int number;                  /* the number that we read. */
+  WORD_LIST *word_list;
+  COMMAND *command;
+  REDIRECT *redirect;
+  ELEMENT element;
+  PATTERN_LIST *pattern;
+}
+
+/* Reserved words.  Members of the first group are only recognized
+   in the case that they are preceded by a list_terminator.  Members
+   of the second group are for [[...]] commands.  Members of the
+   third group are recognized only under special circumstances. */
+%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION
+%token COND_START COND_END COND_ERROR
+%token IN BANG TIME TIMEOPT
+
+/* More general tokens. yylex () knows how to make these. */
+%token <word> WORD ASSIGNMENT_WORD
+%token <number> NUMBER
+%token <word_list> ARITH_CMD ARITH_FOR_EXPRS
+%token <command> COND_CMD
+%token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS
+%token GREATER_AND SEMI_SEMI SEMI_AND SEMI_SEMI_AND
+%token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
+%token GREATER_BAR BAR_AND
+
+/* The types that the various syntactical units return. */
+
+%type <command> inputunit command pipeline pipeline_command
+%type <command> list list0 list1 compound_list simple_list simple_list1
+%type <command> simple_command shell_command
+%type <command> for_command select_command case_command group_command
+%type <command> arith_command
+%type <command> cond_command
+%type <command> arith_for_command
+%type <command> function_def function_body if_command elif_clause subshell
+%type <redirect> redirection redirection_list
+%type <element> simple_command_element
+%type <word_list> word_list pattern
+%type <pattern> pattern_list case_clause_sequence case_clause
+%type <number> timespec
+%type <number> list_terminator
+
+%start inputunit
+
+%left '&' ';' '\n' yacc_EOF
+%left AND_AND OR_OR
+%right '|' BAR_AND
+%%
+
+inputunit:     simple_list simple_list_terminator
+                       {
+                         /* Case of regular command.  Discard the error
+                            safety net,and return the command just parsed. */
+                         global_command = $1;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (0); */
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
+                         YYACCEPT;
+                       }
+       |       '\n'
+                       {
+                         /* Case of regular command, but not a very
+                            interesting one.  Return a NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
+                         YYACCEPT;
+                       }
+       |       error '\n'
+                       {
+                         /* Error during parsing.  Return NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (1); */
+                         if (interactive)
+                           {
+                             YYACCEPT;
+                           }
+                         else
+                           {
+                             YYABORT;
+                           }
+                       }
+       |       yacc_EOF
+                       {
+                         /* Case of EOF seen by itself.  Do ignoreeof or
+                            not. */
+                         global_command = (COMMAND *)NULL;
+                         handle_eof_input_unit ();
+                         YYACCEPT;
+                       }
+       ;
+
+word_list:     WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       word_list WORD
+                       { $$ = make_word_list ($2, $1); }
+       ;
+
+redirection:   '>' WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_output_direction, redir);
+                       }
+       |       '<' WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_input_direction, redir);
+                       }
+       |       NUMBER '>' WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_output_direction, redir);
+                       }
+       |       NUMBER '<' WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_input_direction, redir);
+                       }
+       |       GREATER_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_appending_to, redir);
+                       }
+       |       NUMBER GREATER_GREATER WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_appending_to, redir);
+                       }
+       |       LESS_LESS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       LESS_LESS_LESS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_reading_string, redir);
+                       }
+       |       NUMBER LESS_LESS_LESS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_reading_string, redir);
+                       }
+       |       LESS_AND NUMBER
+                       {
+                         redir.dest = $2;
+                         $$ = make_redirection (0, r_duplicating_input, redir);
+                       }
+       |       NUMBER LESS_AND NUMBER
+                       {
+                         redir.dest = $3;
+                         $$ = make_redirection ($1, r_duplicating_input, redir);
+                       }
+       |       GREATER_AND NUMBER
+                       {
+                         redir.dest = $2;
+                         $$ = make_redirection (1, r_duplicating_output, redir);
+                       }
+       |       NUMBER GREATER_AND NUMBER
+                       {
+                         redir.dest = $3;
+                         $$ = make_redirection ($1, r_duplicating_output, redir);
+                       }
+       |       LESS_AND WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_duplicating_input_word, redir);
+                       }
+       |       NUMBER LESS_AND WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_duplicating_input_word, redir);
+                       }
+       |       GREATER_AND WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_duplicating_output_word, redir);
+                       }
+       |       NUMBER GREATER_AND WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_duplicating_output_word, redir);
+                       }
+       |       LESS_LESS_MINUS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection
+                           (0, r_deblank_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS_MINUS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection
+                           ($1, r_deblank_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       GREATER_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection (1, r_close_this, redir);
+                       }
+       |       NUMBER GREATER_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection ($1, r_close_this, redir);
+                       }
+       |       LESS_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection (0, r_close_this, redir);
+                       }
+       |       NUMBER LESS_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection ($1, r_close_this, redir);
+                       }
+       |       AND_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_err_and_out, redir);
+                       }
+       |       AND_GREATER_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_append_err_and_out, redir);
+                       }
+       |       NUMBER LESS_GREATER WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_input_output, redir);
+                       }
+       |       LESS_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_input_output, redir);
+                       }
+       |       GREATER_BAR WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_output_force, redir);
+                       }
+       |       NUMBER GREATER_BAR WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_output_force, redir);
+                       }
+       ;
+
+simple_command_element: WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       ASSIGNMENT_WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       redirection
+                       { $$.redirect = $1; $$.word = 0; }
+       ;
+
+redirection_list: redirection
+                       {
+                         $$ = $1;
+                       }
+       |       redirection_list redirection
+                       {
+                         register REDIRECT *t;
+
+                         for (t = $1; t->next; t = t->next)
+                           ;
+                         t->next = $2;
+                         $$ = $1;
+                       }
+       ;
+
+simple_command:        simple_command_element
+                       { $$ = make_simple_command ($1, (COMMAND *)NULL); }
+       |       simple_command simple_command_element
+                       { $$ = make_simple_command ($2, $1); }
+       ;
+
+command:       simple_command
+                       { $$ = clean_simple_command ($1); }
+       |       shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       |       function_def
+                       { $$ = $1; }
+       ;
+
+shell_command: for_command
+                       { $$ = $1; }
+       |       case_command
+                       { $$ = $1; }
+       |       WHILE compound_list DO compound_list DONE
+                       { $$ = make_while_command ($2, $4); }
+       |       UNTIL compound_list DO compound_list DONE
+                       { $$ = make_until_command ($2, $4); }
+       |       select_command
+                       { $$ = $1; }
+       |       if_command
+                       { $$ = $1; }
+       |       subshell
+                       { $$ = $1; }
+       |       group_command
+                       { $$ = $1; }
+       |       arith_command
+                       { $$ = $1; }
+       |       cond_command
+                       { $$ = $1; }
+       |       arith_for_command
+                       { $$ = $1; }
+       ;
+
+for_command:   FOR WORD newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+arith_for_command:     FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       ;
+
+select_command:        SELECT WORD newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+case_command:  CASE WORD newline_list IN newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause_sequence newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+function_def:  WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD newline_list function_body
+                       { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+       ;
+
+
+function_body: shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         /* According to Posix.2 3.9.5, redirections
+                            specified after the body of a function should
+                            be attached to the function and performed when
+                            the function is executed, not as part of the
+                            function definition command. */
+                         /* XXX - I don't think it matters, but we might
+                            want to change this in the future to avoid
+                            problems differentiating between a function
+                            definition with a redirection and a function
+                            definition containing a single command with a
+                            redirection.  The two are semantically equivalent,
+                            though -- the only difference is in how the
+                            command printing code displays the redirections. */
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       ;
+
+subshell:      '(' compound_list ')'
+                       {
+                         $$ = make_subshell_command ($2);
+                         $$->flags |= CMD_WANT_SUBSHELL;
+                       }
+       ;
+
+if_command:    IF compound_list THEN compound_list FI
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       IF compound_list THEN compound_list ELSE compound_list FI
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       IF compound_list THEN compound_list elif_clause FI
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+
+group_command: '{' compound_list '}'
+                       { $$ = make_group_command ($2); }
+       ;
+
+arith_command: ARITH_CMD
+                       { $$ = make_arith_command ($1); }
+       ;
+
+cond_command:  COND_START COND_CMD COND_END
+                       { $$ = $2; }
+       ; 
+
+elif_clause:   ELIF compound_list THEN compound_list
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       ELIF compound_list THEN compound_list ELSE compound_list
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       ELIF compound_list THEN compound_list elif_clause
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+case_clause:   pattern_list
+       |       case_clause_sequence pattern_list
+                       { $2->next = $1; $$ = $2; }
+       ;
+
+pattern_list:  newline_list pattern ')' compound_list
+                       { $$ = make_pattern_list ($2, $4); }
+       |       newline_list pattern ')' newline_list
+                       { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
+       |       newline_list '(' pattern ')' compound_list
+                       { $$ = make_pattern_list ($3, $5); }
+       |       newline_list '(' pattern ')' newline_list
+                       { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
+       ;
+
+case_clause_sequence:  pattern_list SEMI_SEMI
+                       { $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_SEMI
+                       { $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_AND
+                       { $1->flags |= CASEPAT_FALLTHROUGH; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_AND
+                       { $2->flags |= CASEPAT_FALLTHROUGH; $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_SEMI_AND
+                       { $1->flags |= CASEPAT_TESTNEXT; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_SEMI_AND
+                       { $2->flags |= CASEPAT_TESTNEXT; $2->next = $1; $$ = $2; }      
+       ;
+
+pattern:       WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       pattern '|' WORD
+                       { $$ = make_word_list ($3, $1); }
+       ;
+
+/* A list allows leading or trailing newlines and
+   newlines as operators (equivalent to semicolons).
+   It must end with a newline or semicolon.
+   Lists are used within commands such as if, for, while.  */
+
+list:          newline_list list0
+                       {
+                         $$ = $2;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                        }
+       ;
+
+compound_list: list
+       |       newline_list list1
+                       {
+                         $$ = $2;
+                       }
+       ;
+
+list0:         list1 '\n' newline_list
+       |       list1 '&' newline_list
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                       }
+       |       list1 ';' newline_list
+
+       ;
+
+list1:         list1 AND_AND newline_list list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       list1 OR_OR newline_list list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       list1 '&' newline_list list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $4, '&');
+                         else
+                           $$ = command_connect ($1, $4, '&');
+                       }
+       |       list1 ';' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       list1 '\n' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+simple_list_terminator:        '\n'
+       |       yacc_EOF
+       ;
+
+list_terminator:'\n'
+               { $$ = '\n'; }
+       |       ';'
+               { $$ = ';'; }
+       |       yacc_EOF
+               { $$ = yacc_EOF; }
+       ;
+
+newline_list:
+       |       newline_list '\n'
+       ;
+
+/* A simple_list is a list that contains no significant newlines
+   and no leading or trailing newlines.  Newlines are allowed
+   only following operators, where they are not significant.
+
+   This is what an inputunit consists of.  */
+
+simple_list:   simple_list1
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       |       simple_list1 '&'
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       |       simple_list1 ';'
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       ;
+
+simple_list1:  simple_list1 AND_AND newline_list simple_list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       simple_list1 OR_OR newline_list simple_list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       simple_list1 '&' simple_list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $3, '&');
+                         else
+                           $$ = command_connect ($1, $3, '&');
+                       }
+       |       simple_list1 ';' simple_list1
+                       { $$ = command_connect ($1, $3, ';'); }
+
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+pipeline_command: pipeline
+                       { $$ = $1; }
+       |       BANG pipeline
+                       {
+                         if ($2)
+                           $2->flags |= CMD_INVERT_RETURN;
+                         $$ = $2;
+                       }
+       |       timespec pipeline
+                       {
+                         if ($2)
+                           $2->flags |= $1;
+                         $$ = $2;
+                       }
+       |       timespec BANG pipeline
+                       {
+                         if ($3)
+                           $3->flags |= $1|CMD_INVERT_RETURN;
+                         $$ = $3;
+                       }
+       |       BANG timespec pipeline
+                       {
+                         if ($3)
+                           $3->flags |= $2|CMD_INVERT_RETURN;
+                         $$ = $3;
+                       }
+       |       timespec list_terminator
+                       {
+                         ELEMENT x;
+
+                         /* Boy, this is unclean.  `time' by itself can
+                            time a null command.  We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= $1;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
+                       }
+                       
+       ;
+
+pipeline:      pipeline '|' newline_list pipeline
+                       { $$ = command_connect ($1, $4, '|'); }
+       |       pipeline BAR_AND newline_list pipeline
+                       {
+                         /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */
+                         COMMAND *tc;
+                         REDIRECTEE rd;
+                         REDIRECT *r;
+
+                         tc = $1;
+                         rd.dest = 1;
+                         r = make_redirection (2, r_duplicating_output, rd);
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = r;
+                           }
+                         else
+                           tc->redirects = r;
+
+                         $$ = command_connect ($1, $4, '|');
+                       }
+       |       command
+                       { $$ = $1; }
+       ;
+
+timespec:      TIME
+                       { $$ = CMD_TIME_PIPELINE; }
+       |       TIME TIMEOPT
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       ;
+%%
+
+/* Initial size to allocate for tokens, and the
+   amount to grow them by. */
+#define TOKEN_DEFAULT_INITIAL_SIZE 496
+#define TOKEN_DEFAULT_GROW_SIZE 512
+
+/* Should we call prompt_again? */
+#define SHOULD_PROMPT() \
+  (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+
+#if defined (ALIAS)
+#  define expanding_alias() (pushed_string_list && pushed_string_list->expander)
+#else
+#  define expanding_alias() 0
+#endif
+
+/* Global var is non-zero when end of file has been reached. */
+int EOF_Reached = 0;
+
+#ifdef DEBUG
+static void
+debug_parser (i)
+     int i;
+{
+#if YYDEBUG != 0
+  yydebug = i;
+#endif
+}
+#endif
+
+/* yy_getc () returns the next available character from input or EOF.
+   yy_ungetc (c) makes `c' the next character to read.
+   init_yy_io (get, unget, type, location) makes the function GET the
+   installed function for getting the next character, makes UNGET the
+   installed function for un-getting a character, sets the type of stream
+   (either string or file) from TYPE, and makes LOCATION point to where
+   the input is coming from. */
+
+/* Unconditionally returns end-of-file. */
+int
+return_EOF ()
+{
+  return (EOF);
+}
+
+/* Variable containing the current get and unget functions.
+   See ./input.h for a clearer description. */
+BASH_INPUT bash_input;
+
+/* Set all of the fields in BASH_INPUT to NULL.  Free bash_input.name if it
+   is non-null, avoiding a memory leak. */
+void
+initialize_bash_input ()
+{
+  bash_input.type = st_none;
+  FREE (bash_input.name);
+  bash_input.name = (char *)NULL;
+  bash_input.location.file = (FILE *)NULL;
+  bash_input.location.string = (char *)NULL;
+  bash_input.getter = (sh_cget_func_t *)NULL;
+  bash_input.ungetter = (sh_cunget_func_t *)NULL;
+}
+
+/* Set the contents of the current bash input stream from
+   GET, UNGET, TYPE, NAME, and LOCATION. */
+void
+init_yy_io (get, unget, type, name, location)
+     sh_cget_func_t *get;
+     sh_cunget_func_t *unget;
+     enum stream_type type;
+     const char *name;
+     INPUT_STREAM location;
+{
+  bash_input.type = type;
+  FREE (bash_input.name);
+  bash_input.name = name ? savestring (name) : (char *)NULL;
+
+  /* XXX */
+#if defined (CRAY)
+  memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location));
+#else
+  bash_input.location = location;
+#endif
+  bash_input.getter = get;
+  bash_input.ungetter = unget;
+}
+
+char *
+yy_input_name ()
+{
+  return (bash_input.name ? bash_input.name : "stdin");
+}
+
+/* Call this to get the next character of input. */
+static int
+yy_getc ()
+{
+  return (*(bash_input.getter)) ();
+}
+
+/* Call this to unget C.  That is, to make C the next character
+   to be read. */
+static int
+yy_ungetc (c)
+     int c;
+{
+  return (*(bash_input.ungetter)) (c);
+}
+
+#if defined (BUFFERED_INPUT)
+#ifdef INCLUDE_UNUSED
+int
+input_file_descriptor ()
+{
+  switch (bash_input.type)
+    {
+    case st_stream:
+      return (fileno (bash_input.location.file));
+    case st_bstream:
+      return (bash_input.location.buffered_fd);
+    case st_stdin:
+    default:
+      return (fileno (stdin));
+    }
+}
+#endif
+#endif /* BUFFERED_INPUT */
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Let input be read from readline ().               */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (READLINE)
+char *current_readline_prompt = (char *)NULL;
+char *current_readline_line = (char *)NULL;
+int current_readline_line_index = 0;
+
+static int
+yy_readline_get ()
+{
+  SigHandler *old_sigint;
+  int line_len;
+  unsigned char c;
+
+  if (!current_readline_line)
+    {
+      if (!bash_readline_initialized)
+       initialize_readline ();
+
+#if defined (JOB_CONTROL)
+      if (job_control)
+       give_terminal_to (shell_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+      old_sigint = (SigHandler *)NULL;
+      if (signal_is_ignored (SIGINT) == 0)
+       {
+         old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
+         interrupt_immediately++;
+       }
+      terminate_immediately = 1;
+
+      current_readline_line = readline (current_readline_prompt ?
+                                         current_readline_prompt : "");
+
+      terminate_immediately = 0;
+      if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+       {
+         interrupt_immediately--;
+         set_signal_handler (SIGINT, old_sigint);
+       }
+
+#if 0
+      /* Reset the prompt to the decoded value of prompt_string_pointer. */
+      reset_readline_prompt ();
+#endif
+
+      if (current_readline_line == 0)
+       return (EOF);
+
+      current_readline_line_index = 0;
+      line_len = strlen (current_readline_line);
+
+      current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len);
+      current_readline_line[line_len++] = '\n';
+      current_readline_line[line_len] = '\0';
+    }
+
+  if (current_readline_line[current_readline_line_index] == 0)
+    {
+      free (current_readline_line);
+      current_readline_line = (char *)NULL;
+      return (yy_readline_get ());
+    }
+  else
+    {
+      c = current_readline_line[current_readline_line_index++];
+      return (c);
+    }
+}
+
+static int
+yy_readline_unget (c)
+     int c;
+{
+  if (current_readline_line_index && current_readline_line)
+    current_readline_line[--current_readline_line_index] = c;
+  return (c);
+}
+
+void
+with_input_from_stdin ()
+{
+  INPUT_STREAM location;
+
+  if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0)
+    {
+      location.string = current_readline_line;
+      init_yy_io (yy_readline_get, yy_readline_unget,
+                 st_stdin, "readline stdin", location);
+    }
+}
+
+#else  /* !READLINE */
+
+void
+with_input_from_stdin ()
+{
+  with_input_from_stream (stdin, "stdin");
+}
+#endif /* !READLINE */
+
+/* **************************************************************** */
+/*                                                                 */
+/*   Let input come from STRING.  STRING is zero terminated.       */
+/*                                                                 */
+/* **************************************************************** */
+
+static int
+yy_string_get ()
+{
+  register char *string;
+  register unsigned char c;
+
+  string = bash_input.location.string;
+
+  /* If the string doesn't exist, or is empty, EOF found. */
+  if (string && *string)
+    {
+      c = *string++;
+      bash_input.location.string = string;
+      return (c);
+    }
+  else
+    return (EOF);
+}
+
+static int
+yy_string_unget (c)
+     int c;
+{
+  *(--bash_input.location.string) = c;
+  return (c);
+}
+
+void
+with_input_from_string (string, name)
+     char *string;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.string = string;
+  init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
+}
+
+/* Count the number of characters we've consumed from bash_input.location.string
+   and read into shell_input_line, but have not returned from shell_getc.
+   That is the true input location.  Rewind bash_input.location.string by
+   that number of characters, so it points to the last character actually
+   consumed by the parser. */
+void
+rewind_input_string ()
+{
+  int xchars;
+
+  /* number of unconsumed characters in the input -- XXX need to take newlines
+     into account, e.g., $(...\n) */
+  xchars = shell_input_line_len - shell_input_line_index;
+
+  /* XXX - how to reflect bash_input.location.string back to string passed to
+     parse_and_execute or xparse_dolparen?  xparse_dolparen needs to know how
+     far into the string we parsed.  parse_and_execute knows where bash_input.
+     location.string is, and how far from orig_string that is -- that's the
+     number of characters the command consumed. */
+
+  /* bash_input.location.string - xchars should be where we parsed to */
+  /* need to do more validation on xchars value for sanity -- test cases. */
+  bash_input.location.string -= xchars;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  Let input come from STREAM.                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS
+   define, and just use getc/ungetc if it was defined, but since bash
+   installs its signal handlers without the SA_RESTART flag, some signals
+   (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause
+   the read to be restarted.  We need to restart it ourselves. */
+
+static int
+yy_stream_get ()
+{
+  int result;
+
+  result = EOF;
+  if (bash_input.location.file)
+    {
+      if (interactive)
+       {
+         interrupt_immediately++;
+         terminate_immediately++;
+       }
+      result = getc_with_restart (bash_input.location.file);
+      if (interactive)
+       {
+         interrupt_immediately--;
+         terminate_immediately--;
+       }
+    }
+  return (result);
+}
+
+static int
+yy_stream_unget (c)
+     int c;
+{
+  return (ungetc_with_restart (c, bash_input.location.file));
+}
+
+void
+with_input_from_stream (stream, name)
+     FILE *stream;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.file = stream;
+  init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location);
+}
+
+typedef struct stream_saver {
+  struct stream_saver *next;
+  BASH_INPUT bash_input;
+  int line;
+#if defined (BUFFERED_INPUT)
+  BUFFERED_STREAM *bstream;
+#endif /* BUFFERED_INPUT */
+} STREAM_SAVER;
+
+/* The globally known line number. */
+int line_number = 0;
+
+#if defined (COND_COMMAND)
+static int cond_lineno;
+static int cond_token;
+#endif
+
+STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL;
+
+void
+push_stream (reset_lineno)
+     int reset_lineno;
+{
+  STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER));
+
+  xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT));
+
+#if defined (BUFFERED_INPUT)
+  saver->bstream = (BUFFERED_STREAM *)NULL;
+  /* If we have a buffered stream, clear out buffers[fd]. */
+  if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+    saver->bstream = set_buffered_stream (bash_input.location.buffered_fd,
+                                         (BUFFERED_STREAM *)NULL);
+#endif /* BUFFERED_INPUT */
+
+  saver->line = line_number;
+  bash_input.name = (char *)NULL;
+  saver->next = stream_list;
+  stream_list = saver;
+  EOF_Reached = 0;
+  if (reset_lineno)
+    line_number = 0;
+}
+
+void
+pop_stream ()
+{
+  if (!stream_list)
+    EOF_Reached = 1;
+  else
+    {
+      STREAM_SAVER *saver = stream_list;
+
+      EOF_Reached = 0;
+      stream_list = stream_list->next;
+
+      init_yy_io (saver->bash_input.getter,
+                 saver->bash_input.ungetter,
+                 saver->bash_input.type,
+                 saver->bash_input.name,
+                 saver->bash_input.location);
+
+#if defined (BUFFERED_INPUT)
+      /* If we have a buffered stream, restore buffers[fd]. */
+      /* If the input file descriptor was changed while this was on the
+        save stack, update the buffered fd to the new file descriptor and
+        re-establish the buffer <-> bash_input fd correspondence. */
+      if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+       {
+         if (bash_input_fd_changed)
+           {
+             bash_input_fd_changed = 0;
+             if (default_buffered_input >= 0)
+               {
+                 bash_input.location.buffered_fd = default_buffered_input;
+                 saver->bstream->b_fd = default_buffered_input;
+                 SET_CLOSE_ON_EXEC (default_buffered_input);
+               }
+           }
+         /* XXX could free buffered stream returned as result here. */
+         set_buffered_stream (bash_input.location.buffered_fd, saver->bstream);
+       }
+#endif /* BUFFERED_INPUT */
+
+      line_number = saver->line;
+
+      FREE (saver->bash_input.name);
+      free (saver);
+    }
+}
+
+/* Return 1 if a stream of type TYPE is saved on the stack. */
+int
+stream_on_stack (type)
+     enum stream_type type;
+{
+  register STREAM_SAVER *s;
+
+  for (s = stream_list; s; s = s->next)
+    if (s->bash_input.type == type)
+      return 1;
+  return 0;
+}
+
+/* Save the current token state and return it in a malloced array. */
+int *
+save_token_state ()
+{
+  int *ret;
+
+  ret = (int *)xmalloc (3 * sizeof (int));
+  ret[0] = last_read_token;
+  ret[1] = token_before_that;
+  ret[2] = two_tokens_ago;
+  return ret;
+}
+
+void
+restore_token_state (ts)
+     int *ts;
+{
+  if (ts == 0)
+    return;
+  last_read_token = ts[0];
+  token_before_that = ts[1];
+  two_tokens_ago = ts[2];
+}
+
+/*
+ * This is used to inhibit alias expansion and reserved word recognition
+ * inside case statement pattern lists.  A `case statement pattern list' is:
+ *
+ *     everything between the `in' in a `case word in' and the next ')'
+ *     or `esac'
+ *     everything between a `;;' and the next `)' or `esac'
+ */
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+
+#define END_OF_ALIAS 0
+
+/*
+ * Pseudo-global variables used in implementing token-wise alias expansion.
+ */
+
+/*
+ * Pushing and popping strings.  This works together with shell_getc to
+ * implement alias expansion on a per-token basis.
+ */
+
+typedef struct string_saver {
+  struct string_saver *next;
+  int expand_alias;  /* Value to set expand_alias to when string is popped. */
+  char *saved_line;
+#if defined (ALIAS)
+  alias_t *expander;   /* alias that caused this line to be pushed. */
+#endif
+  int saved_line_size, saved_line_index, saved_line_terminator;
+} STRING_SAVER;
+
+STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
+
+/*
+ * Push the current shell_input_line onto a stack of such lines and make S
+ * the current input.  Used when expanding aliases.  EXPAND is used to set
+ * the value of expand_next_token when the string is popped, so that the
+ * word after the alias in the original line is handled correctly when the
+ * alias expands to multiple words.  TOKEN is the token that was expanded
+ * into S; it is saved and used to prevent infinite recursive expansion.
+ */
+static void
+push_string (s, expand, ap)
+     char *s;
+     int expand;
+     alias_t *ap;
+{
+  STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER));
+
+  temp->expand_alias = expand;
+  temp->saved_line = shell_input_line;
+  temp->saved_line_size = shell_input_line_size;
+  temp->saved_line_index = shell_input_line_index;
+  temp->saved_line_terminator = shell_input_line_terminator;
+#if defined (ALIAS)
+  temp->expander = ap;
+#endif
+  temp->next = pushed_string_list;
+  pushed_string_list = temp;
+
+#if defined (ALIAS)
+  if (ap)
+    ap->flags |= AL_BEINGEXPANDED;
+#endif
+
+  shell_input_line = s;
+  shell_input_line_size = strlen (s);
+  shell_input_line_index = 0;
+  shell_input_line_terminator = '\0';
+#if 0
+  parser_state &= ~PST_ALEXPNEXT;      /* XXX */
+#endif
+
+  set_line_mbstate ();
+}
+
+/*
+ * Make the top of the pushed_string stack be the current shell input.
+ * Only called when there is something on the stack.  Called from shell_getc
+ * when it thinks it has consumed the string generated by an alias expansion
+ * and needs to return to the original input line.
+ */
+static void
+pop_string ()
+{
+  STRING_SAVER *t;
+
+  FREE (shell_input_line);
+  shell_input_line = pushed_string_list->saved_line;
+  shell_input_line_index = pushed_string_list->saved_line_index;
+  shell_input_line_size = pushed_string_list->saved_line_size;
+  shell_input_line_terminator = pushed_string_list->saved_line_terminator;
+
+  if (pushed_string_list->expand_alias)
+    parser_state |= PST_ALEXPNEXT;
+  else
+    parser_state &= ~PST_ALEXPNEXT;
+
+  t = pushed_string_list;
+  pushed_string_list = pushed_string_list->next;
+
+#if defined (ALIAS)
+  if (t->expander)
+    t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+
+  free ((char *)t);
+
+  set_line_mbstate ();
+}
+
+static void
+free_string_list ()
+{
+  register STRING_SAVER *t, *t1;
+
+  for (t = pushed_string_list; t; )
+    {
+      t1 = t->next;
+      FREE (t->saved_line);
+#if defined (ALIAS)
+      if (t->expander)
+       t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+      free ((char *)t);
+      t = t1;
+    }
+  pushed_string_list = (STRING_SAVER *)NULL;
+}
+
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+void
+free_pushed_string_input ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  free_string_list ();
+#endif
+}
+
+/* Return a line of text, taken from wherever yylex () reads input.
+   If there is no more input, then we return NULL.  If REMOVE_QUOTED_NEWLINE
+   is non-zero, we remove unquoted \<newline> pairs.  This is used by
+   read_secondary_line to read here documents. */
+static char *
+read_a_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  static char *line_buffer = (char *)NULL;
+  static int buffer_size = 0;
+  int indx = 0, c, peekc, pass_next;
+
+#if defined (READLINE)
+  if (no_line_editing && SHOULD_PROMPT ())
+#else
+  if (SHOULD_PROMPT ())
+#endif
+    print_prompt ();
+
+  pass_next = 0;
+  while (1)
+    {
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      c = yy_getc ();
+
+      /* Ignore null bytes in input. */
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_a_line: ignored null byte in input");
+#endif
+         continue;
+       }
+
+      /* If there is no more input, then we return NULL. */
+      if (c == EOF)
+       {
+         if (interactive && bash_input.type == st_stream)
+           clearerr (stdin);
+         if (indx == 0)
+           return ((char *)NULL);
+         c = '\n';
+       }
+
+      /* `+2' in case the final character in the buffer is a newline. */
+      RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128);
+
+      /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a
+        here document with an unquoted delimiter.  In this case,
+        the line will be expanded as if it were in double quotes.
+        We allow a backslash to escape the next character, but we
+        need to treat the backslash specially only if a backslash
+        quoting a backslash-newline pair appears in the line. */
+      if (pass_next)
+       {
+         line_buffer[indx++] = c;
+         pass_next = 0;
+       }
+      else if (c == '\\' && remove_quoted_newline)
+       {
+         peekc = yy_getc ();
+         if (peekc == '\n')
+           {
+             line_number++;
+             continue; /* Make the unquoted \<newline> pair disappear. */
+           }
+         else
+           {
+             yy_ungetc (peekc);
+             pass_next = 1;
+             line_buffer[indx++] = c;          /* Preserve the backslash. */
+           }
+       }
+      else
+       line_buffer[indx++] = c;
+
+      if (c == '\n')
+       {
+         line_buffer[indx] = '\0';
+         return (line_buffer);
+       }
+    }
+}
+
+/* Return a line as in read_a_line (), but insure that the prompt is
+   the secondary prompt.  This is used to read the lines of a here
+   document.  REMOVE_QUOTED_NEWLINE is non-zero if we should remove
+   newlines quoted with backslashes while reading the line.  It is
+   non-zero unless the delimiter of the here document was quoted. */
+char *
+read_secondary_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  prompt_string_pointer = &ps2_prompt;
+  if (SHOULD_PROMPT())
+    prompt_again ();
+  return (read_a_line (remove_quoted_newline));
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                             YYLEX ()                            */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Reserved words.  These are only recognized as the first word of a
+   command. */
+STRING_INT_ALIST word_token_alist[] = {
+  { "if", IF },
+  { "then", THEN },
+  { "else", ELSE },
+  { "elif", ELIF },
+  { "fi", FI },
+  { "case", CASE },
+  { "esac", ESAC },
+  { "for", FOR },
+#if defined (SELECT_COMMAND)
+  { "select", SELECT },
+#endif
+  { "while", WHILE },
+  { "until", UNTIL },
+  { "do", DO },
+  { "done", DONE },
+  { "in", IN },
+  { "function", FUNCTION },
+#if defined (COMMAND_TIMING)
+  { "time", TIME },
+#endif
+  { "{", '{' },
+  { "}", '}' },
+  { "!", BANG },
+#if defined (COND_COMMAND)
+  { "[[", COND_START },
+  { "]]", COND_END },
+#endif
+  { (char *)NULL, 0}
+};
+
+/* other tokens that can be returned by read_token() */
+STRING_INT_ALIST other_token_alist[] = {
+  /* Multiple-character tokens with special values */
+  { "-p", TIMEOPT },
+  { "&&", AND_AND },
+  { "||", OR_OR },
+  { ">>", GREATER_GREATER },
+  { "<<", LESS_LESS },
+  { "<&", LESS_AND },
+  { ">&", GREATER_AND },
+  { ";;", SEMI_SEMI },
+  { ";&", SEMI_AND },
+  { ";;&", SEMI_SEMI_AND },
+  { "<<-", LESS_LESS_MINUS },
+  { "<<<", LESS_LESS_LESS },
+  { "&>", AND_GREATER },
+  { "&>>", AND_GREATER_GREATER },
+  { "<>", LESS_GREATER },
+  { ">|", GREATER_BAR },
+  { "|&", BAR_AND },
+  { "EOF", yacc_EOF },
+  /* Tokens whose value is the character itself */
+  { ">", '>' },
+  { "<", '<' },
+  { "-", '-' },
+  { "{", '{' },
+  { "}", '}' },
+  { ";", ';' },
+  { "(", '(' },
+  { ")", ')' },
+  { "|", '|' },
+  { "&", '&' },
+  { "newline", '\n' },
+  { (char *)NULL, 0}
+};
+
+/* others not listed here:
+       WORD                    look at yylval.word
+       ASSIGNMENT_WORD         look at yylval.word
+       NUMBER                  look at yylval.number
+       ARITH_CMD               look at yylval.word_list
+       ARITH_FOR_EXPRS         look at yylval.word_list
+       COND_CMD                look at yylval.command
+*/
+
+/* These are used by read_token_word, but appear up here so that shell_getc
+   can use them to decide when to add otherwise blank lines to the history. */
+
+/* The primary delimiter stack. */
+struct dstack dstack = {  (char *)NULL, 0, 0 };
+
+/* A temporary delimiter stack to be used when decoding prompt strings.
+   This is needed because command substitutions in prompt strings (e.g., PS2)
+   can screw up the parser's quoting state. */
+static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
+
+/* Macro for accessing the top delimiter on the stack.  Returns the
+   delimiter or zero if none. */
+#define current_delimiter(ds) \
+  (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0)
+
+#define push_delimiter(ds, character) \
+  do \
+    { \
+      if (ds.delimiter_depth + 2 > ds.delimiter_space) \
+       ds.delimiters = (char *)xrealloc \
+         (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \
+      ds.delimiters[ds.delimiter_depth] = character; \
+      ds.delimiter_depth++; \
+    } \
+  while (0)
+
+#define pop_delimiter(ds)      ds.delimiter_depth--
+
+/* Return the next shell input character.  This always reads characters
+   from shell_input_line; when that line is exhausted, it is time to
+   read the next line.  This is called by read_token when the shell is
+   processing normal command input. */
+
+/* This implements one-character lookahead/lookbehind across physical input
+   lines, to avoid something being lost because it's pushed back with
+   shell_ungetc when we're at the start of a line. */
+static int eol_ungetc_lookahead = 0;
+
+static int
+shell_getc (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  register int i;
+  int c;
+  unsigned char uc;
+
+  QUIT;
+
+  if (sigwinch_received)
+    {
+      sigwinch_received = 0;
+      get_new_window_size (0, (int *)0, (int *)0);
+    }
+      
+  if (eol_ungetc_lookahead)
+    {
+      c = eol_ungetc_lookahead;
+      eol_ungetc_lookahead = 0;
+      return (c);
+    }
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If shell_input_line[shell_input_line_index] == 0, but there is
+     something on the pushed list of strings, then we don't want to go
+     off and get another line.  We let the code down below handle it. */
+
+  if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) &&
+                           (pushed_string_list == (STRING_SAVER *)NULL)))
+#else /* !ALIAS && !DPAREN_ARITHMETIC */
+  if (!shell_input_line || !shell_input_line[shell_input_line_index])
+#endif /* !ALIAS && !DPAREN_ARITHMETIC */
+    {
+      line_number++;
+
+    restart_read:
+
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      i = 0;
+      shell_input_line_terminator = 0;
+
+      /* If the shell is interatctive, but not currently printing a prompt
+         (interactive_shell && interactive == 0), we don't want to print
+         notifies or cleanup the jobs -- we want to defer it until we do
+         print the next prompt. */
+      if (interactive_shell == 0 || SHOULD_PROMPT())
+       {
+#if defined (JOB_CONTROL)
+      /* This can cause a problem when reading a command as the result
+        of a trap, when the trap is called from flush_child.  This call
+        had better not cause jobs to disappear from the job table in
+        that case, or we will have big trouble. */
+         notify_and_cleanup ();
+#else /* !JOB_CONTROL */
+         cleanup_dead_jobs ();
+#endif /* !JOB_CONTROL */
+       }
+
+#if defined (READLINE)
+      if (no_line_editing && SHOULD_PROMPT())
+#else
+      if (SHOULD_PROMPT())
+#endif
+       print_prompt ();
+
+      if (bash_input.type == st_stream)
+       clearerr (stdin);
+
+      while (1)
+       {
+         c = yy_getc ();
+
+         /* Allow immediate exit if interrupted during input. */
+         QUIT;
+
+         if (c == '\0')
+           {
+#if 0
+             internal_warning ("shell_getc: ignored null byte in input");
+#endif
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
+
+         if (c == EOF)
+           {
+             if (bash_input.type == st_stream)
+               clearerr (stdin);
+
+             if (i == 0)
+               shell_input_line_terminator = EOF;
+
+             shell_input_line[i] = '\0';
+             break;
+           }
+
+         shell_input_line[i++] = c;
+
+         if (c == '\n')
+           {
+             shell_input_line[--i] = '\0';
+             current_command_line_count++;
+             break;
+           }
+       }
+
+      shell_input_line_index = 0;
+      shell_input_line_len = i;                /* == strlen (shell_input_line) */
+
+      set_line_mbstate ();
+
+#if defined (HISTORY)
+      if (remember_on_history && shell_input_line && shell_input_line[0])
+       {
+         char *expansions;
+#  if defined (BANG_HISTORY)
+         int old_hist;
+
+         /* If the current delimiter is a single quote, we should not be
+            performing history expansion, even if we're on a different
+            line from the original single quote. */
+         old_hist = history_expansion_inhibited;
+         if (current_delimiter (dstack) == '\'')
+           history_expansion_inhibited = 1;
+#  endif
+         expansions = pre_process_line (shell_input_line, 1, 1);
+#  if defined (BANG_HISTORY)
+         history_expansion_inhibited = old_hist;
+#  endif
+         if (expansions != shell_input_line)
+           {
+             free (shell_input_line);
+             shell_input_line = expansions;
+             shell_input_line_len = shell_input_line ?
+                                       strlen (shell_input_line) : 0;
+             if (!shell_input_line_len)
+               current_command_line_count--;
+
+             /* We have to force the xrealloc below because we don't know
+                the true allocated size of shell_input_line anymore. */
+             shell_input_line_size = shell_input_line_len;
+
+             set_line_mbstate ();
+           }
+       }
+      /* Try to do something intelligent with blank lines encountered while
+        entering multi-line commands.  XXX - this is grotesque */
+      else if (remember_on_history && shell_input_line &&
+              shell_input_line[0] == '\0' &&
+              current_command_line_count > 1)
+       {
+         if (current_delimiter (dstack))
+           /* We know shell_input_line[0] == 0 and we're reading some sort of
+              quoted string.  This means we've got a line consisting of only
+              a newline in a quoted string.  We want to make sure this line
+              gets added to the history. */
+           maybe_add_history (shell_input_line);
+         else
+           {
+             char *hdcs;
+             hdcs = history_delimiting_chars ();
+             if (hdcs && hdcs[0] == ';')
+               maybe_add_history (shell_input_line);
+           }
+       }
+
+#endif /* HISTORY */
+
+      if (shell_input_line)
+       {
+         /* Lines that signify the end of the shell's input should not be
+            echoed. */
+         if (echo_input_at_read && (shell_input_line[0] ||
+                                    shell_input_line_terminator != EOF))
+           fprintf (stderr, "%s\n", shell_input_line);
+       }
+      else
+       {
+         shell_input_line_size = 0;
+         prompt_string_pointer = &current_prompt_string;
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         goto restart_read;
+       }
+
+      /* Add the newline to the end of this string, iff the string does
+        not already end in an EOF character.  */
+      if (shell_input_line_terminator != EOF)
+       {
+         if (shell_input_line_len + 3 > shell_input_line_size)
+           shell_input_line = (char *)xrealloc (shell_input_line,
+                                       1 + (shell_input_line_size += 2));
+
+         shell_input_line[shell_input_line_len] = '\n';
+         shell_input_line[shell_input_line_len + 1] = '\0';
+
+         set_line_mbstate ();
+       }
+    }
+
+  uc = shell_input_line[shell_input_line_index];
+
+  if (uc)
+    shell_input_line_index++;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If UC is NULL, we have reached the end of the current input string.  If
+     pushed_string_list is non-empty, it's time to pop to the previous string
+     because we have fully consumed the result of the last alias expansion.
+     Do it transparently; just return the next character of the string popped
+     to. */
+  if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+    {
+      pop_string ();
+      uc = shell_input_line[shell_input_line_index];
+      if (uc)
+       shell_input_line_index++;
+    }
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
+    {
+       if (SHOULD_PROMPT ())
+         prompt_again ();
+       line_number++;
+       goto restart_read;
+    }
+
+  if (!uc && shell_input_line_terminator == EOF)
+    return ((shell_input_line_index != 0) ? '\n' : EOF);
+
+  return (uc);
+}
+
+/* Put C back into the input for the shell.  This might need changes for
+   HANDLE_MULTIBYTE around EOLs.  Since we (currently) never push back a
+   character different than we read, shell_input_line_property doesn't need
+   to change when manipulating shell_input_line.  The define for
+   last_shell_getc_is_singlebyte should take care of it, though. */
+static void
+shell_ungetc (c)
+     int c;
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line[--shell_input_line_index] = c;
+  else
+    eol_ungetc_lookahead = c;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Back the input pointer up by one, effectively `ungetting' a character. */
+static void
+shell_ungetchar ()
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line_index--;
+}
+#endif
+
+/* Discard input until CHARACTER is seen, then push that character back
+   onto the input stream. */
+static void
+discard_until (character)
+     int character;
+{
+  int c;
+
+  while ((c = shell_getc (0)) != EOF && c != character)
+    ;
+
+  if (c != EOF)
+    shell_ungetc (c);
+}
+
+void
+execute_variable_command (command, vname)
+     char *command, *vname;
+{
+  char *last_lastarg;
+  sh_parser_state_t ps;
+
+  save_parser_state (&ps);
+  last_lastarg = get_string_value ("_");
+  if (last_lastarg)
+    last_lastarg = savestring (last_lastarg);
+
+  parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST);
+
+  restore_parser_state (&ps);
+  bind_variable ("_", last_lastarg, 0);
+  FREE (last_lastarg);
+
+  if (token_to_read == '\n')   /* reset_parser was called */
+    token_to_read = 0;
+}
+
+/* Place to remember the token.  We try to keep the buffer
+   at a reasonable size, but it can grow. */
+static char *token = (char *)NULL;
+
+/* Current size of the token buffer. */
+static int token_buffer_size;
+
+/* Command to read_token () explaining what we want it to do. */
+#define READ 0
+#define RESET 1
+#define prompt_is_ps1 \
+      (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt)
+
+/* Function for yyparse to call.  yylex keeps track of
+   the last two tokens read, and calls read_token.  */
+static int
+yylex ()
+{
+  if (interactive && (current_token == 0 || current_token == '\n'))
+    {
+      /* Before we print a prompt, we might have to check mailboxes.
+        We do this only if it is time to do so. Notice that only here
+        is the mail alarm reset; nothing takes place in check_mail ()
+        except the checking of mail.  Please don't change this. */
+      if (prompt_is_ps1 && time_to_check_mail ())
+       {
+         check_mail ();
+         reset_mail_timer ();
+       }
+
+      /* Avoid printing a prompt if we're not going to read anything, e.g.
+        after resetting the parser with read_token (RESET). */
+      if (token_to_read == 0 && SHOULD_PROMPT ())
+       prompt_again ();
+    }
+
+  two_tokens_ago = token_before_that;
+  token_before_that = last_read_token;
+  last_read_token = current_token;
+  current_token = read_token (READ);
+
+  if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+    {
+      current_token = yacc_EOF;
+      if (bash_input.type == st_string)
+       rewind_input_string ();
+    }
+  parser_state &= ~PST_EOFTOKEN;
+
+  return (current_token);
+}
+
+/* When non-zero, we have read the required tokens
+   which allow ESAC to be the next one read. */
+static int esacs_needed_count;
+
+void
+gather_here_documents ()
+{
+  int r = 0;
+  while (need_here_doc)
+    {
+      make_here_document (redir_stack[r++]);
+      need_here_doc--;
+    }
+}
+
+/* When non-zero, an open-brace used to create a group is awaiting a close
+   brace partner. */
+static int open_brace_count;
+
+#define command_token_position(token) \
+  (((token) == ASSIGNMENT_WORD) || \
+   ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token)))
+
+#define assignment_acceptable(token) \
+  (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
+
+/* Check to see if TOKEN is a reserved word and return the token
+   value if it is. */
+#define CHECK_FOR_RESERVED_WORD(tok) \
+  do { \
+    if (!dollar_present && !quoted && \
+       reserved_word_acceptable (last_read_token)) \
+      { \
+       int i; \
+       for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \
+         if (STREQ (tok, word_token_alist[i].word)) \
+           { \
+             if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
+               break; \
+             if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \
+               break; \
+             if (word_token_alist[i].token == ESAC) \
+               parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
+             else if (word_token_alist[i].token == CASE) \
+               parser_state |= PST_CASESTMT; \
+             else if (word_token_alist[i].token == COND_END) \
+               parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \
+             else if (word_token_alist[i].token == COND_START) \
+               parser_state |= PST_CONDCMD; \
+             else if (word_token_alist[i].token == '{') \
+               open_brace_count++; \
+             else if (word_token_alist[i].token == '}' && open_brace_count) \
+               open_brace_count--; \
+             return (word_token_alist[i].token); \
+           } \
+      } \
+  } while (0)
+
+#if defined (ALIAS)
+
+    /* OK, we have a token.  Let's try to alias expand it, if (and only if)
+       it's eligible.
+
+       It is eligible for expansion if EXPAND_ALIASES is set, and
+       the token is unquoted and the last token read was a command
+       separator (or expand_next_token is set), and we are currently
+       processing an alias (pushed_string_list is non-empty) and this
+       token is not the same as the current or any previously
+       processed alias.
+
+       Special cases that disqualify:
+        In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+
+static char *
+mk_alexpansion (s)
+     char *s;
+{
+  int l;
+  char *r;
+
+  l = strlen (s);
+  r = xmalloc (l + 2);
+  strcpy (r, s);
+  if (r[l -1] != ' ')
+    r[l++] = ' ';
+  r[l] = '\0';
+  return r;
+}
+
+static int
+alias_expand_token (tokstr)
+     char *tokstr;
+{
+  char *expanded;
+  alias_t *ap;
+
+  if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
+       (parser_state & PST_CASEPAT) == 0)
+    {
+      ap = find_alias (tokstr);
+
+      /* Currently expanding this token. */
+      if (ap && (ap->flags & AL_BEINGEXPANDED))
+       return (NO_EXPANSION);
+
+      /* mk_alexpansion puts an extra space on the end of the alias expansion,
+         so the lookahead by the parser works right.  If this gets changed,
+         make sure the code in shell_getc that deals with reaching the end of
+         an expanded alias is changed with it. */
+      expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL;
+
+      if (expanded)
+       {
+         push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
+         return (RE_READ_TOKEN);
+       }
+      else
+       /* This is an eligible token that does not have an expansion. */
+       return (NO_EXPANSION);
+    }
+  return (NO_EXPANSION);
+}
+#endif /* ALIAS */
+
+static int
+time_command_acceptable ()
+{
+#if defined (COMMAND_TIMING)
+  switch (last_read_token)
+    {
+    case 0:
+    case ';':
+    case '\n':
+    case AND_AND:
+    case OR_OR:
+    case '&':
+    case DO:
+    case THEN:
+    case ELSE:
+    case '{':          /* } */
+    case '(':          /* ) */
+      return 1;
+    default:
+      return 0;
+    }
+#else
+  return 0;
+#endif /* COMMAND_TIMING */
+}
+
+/* Handle special cases of token recognition:
+       IN is recognized if the last token was WORD and the token
+       before that was FOR or CASE or SELECT.
+
+       DO is recognized if the last token was WORD and the token
+       before that was FOR or SELECT.
+
+       ESAC is recognized if the last token caused `esacs_needed_count'
+       to be set
+
+       `{' is recognized if the last token as WORD and the token
+       before that was FUNCTION, or if we just parsed an arithmetic
+       `for' command.
+
+       `}' is recognized if there is an unclosed `{' present.
+
+       `-p' is returned as TIMEOPT if the last read token was TIME.
+
+       ']]' is returned as COND_END if the parser is currently parsing
+       a conditional expression ((parser_state & PST_CONDEXPR) != 0)
+
+       `time' is returned as TIME if and only if it is immediately
+       preceded by one of `;', `\n', `||', `&&', or `&'.
+*/
+
+static int
+special_case_tokens (tokstr)
+     char *tokstr;
+{
+  if ((last_read_token == WORD) &&
+#if defined (SELECT_COMMAND)
+      ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
+#else
+      ((token_before_that == FOR) || (token_before_that == CASE)) &&
+#endif
+      (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0))
+    {
+      if (token_before_that == CASE)
+       {
+         parser_state |= PST_CASEPAT;
+         esacs_needed_count++;
+       }
+      return (IN);
+    }
+
+  if (last_read_token == WORD &&
+#if defined (SELECT_COMMAND)
+      (token_before_that == FOR || token_before_that == SELECT) &&
+#else
+      (token_before_that == FOR) &&
+#endif
+      (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0'))
+    return (DO);
+
+  /* Ditto for ESAC in the CASE case.
+     Specifically, this handles "case word in esac", which is a legal
+     construct, certainly because someone will pass an empty arg to the
+     case construct, and we don't want it to barf.  Of course, we should
+     insist that the case construct has at least one pattern in it, but
+     the designers disagree. */
+  if (esacs_needed_count)
+    {
+      esacs_needed_count--;
+      if (STREQ (tokstr, "esac"))
+       {
+         parser_state &= ~PST_CASEPAT;
+         return (ESAC);
+       }
+    }
+
+  /* The start of a shell function definition. */
+  if (parser_state & PST_ALLOWOPNBRC)
+    {
+      parser_state &= ~PST_ALLOWOPNBRC;
+      if (tokstr[0] == '{' && tokstr[1] == '\0')               /* } */
+       {
+         open_brace_count++;
+         function_bstart = line_number;
+         return ('{');                                 /* } */
+       }
+    }
+
+  /* We allow a `do' after a for ((...)) without an intervening
+     list_terminator */
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2])
+    return (DO);
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0')     /* } */
+    {
+      open_brace_count++;
+      return ('{');                    /* } */
+    }
+
+  if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1])
+    {
+      open_brace_count--;              /* { */
+      return ('}');
+    }
+
+#if defined (COMMAND_TIMING)
+  /* Handle -p after `time'. */
+  if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
+    return (TIMEOPT);
+#endif
+
+#if 0
+#if defined (COMMAND_TIMING)
+  if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
+    return (TIME);
+#endif /* COMMAND_TIMING */
+#endif
+
+#if defined (COND_COMMAND) /* [[ */
+  if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
+    return (COND_END);
+#endif
+
+  return (-1);
+}
+
+/* Called from shell.c when Control-C is typed at top level.  Or
+   by the error rule at top level. */
+void
+reset_parser ()
+{
+  dstack.delimiter_depth = 0;  /* No delimiters found so far. */
+  open_brace_count = 0;
+
+  parser_state = 0;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  if (pushed_string_list)
+    free_string_list ();
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if (shell_input_line)
+    {
+      free (shell_input_line);
+      shell_input_line = (char *)NULL;
+      shell_input_line_size = shell_input_line_index = 0;
+    }
+
+  FREE (word_desc_to_read);
+  word_desc_to_read = (WORD_DESC *)NULL;
+
+  last_read_token = '\n';
+  token_to_read = '\n';
+}
+
+/* Read the next token.  Command can be READ (normal operation) or
+   RESET (to normalize state). */
+static int
+read_token (command)
+     int command;
+{
+  int character;               /* Current character. */
+  int peek_char;               /* Temporary look-ahead character. */
+  int result;                  /* The thing to return. */
+
+  if (command == RESET)
+    {
+      reset_parser ();
+      return ('\n');
+    }
+
+  if (token_to_read)
+    {
+      result = token_to_read;
+      if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD)
+       {
+         yylval.word = word_desc_to_read;
+         word_desc_to_read = (WORD_DESC *)NULL;
+       }
+      token_to_read = 0;
+      return (result);
+    }
+
+#if defined (COND_COMMAND)
+  if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD)
+    {
+      cond_lineno = line_number;
+      parser_state |= PST_CONDEXPR;
+      yylval.command = parse_cond_command ();
+      if (cond_token != COND_END)
+       {
+         cond_error ();
+         return (-1);
+       }
+      token_to_read = COND_END;
+      parser_state &= ~(PST_CONDEXPR|PST_CONDCMD);
+      return (COND_CMD);
+    }
+#endif
+
+#if defined (ALIAS)
+  /* This is a place to jump back to once we have successfully expanded a
+     token with an alias and pushed the string with push_string () */
+ re_read_token:
+#endif /* ALIAS */
+
+  /* Read a single word from input.  Start by skipping blanks. */
+  while ((character = shell_getc (1)) != EOF && shellblank (character))
+    ;
+
+  if (character == EOF)
+    {
+      EOF_Reached = 1;
+      return (yacc_EOF);
+    }
+
+  if MBTEST(character == '#' && (!interactive || interactive_comments))
+    {
+      /* A comment.  Discard until EOL or EOF, and then return a newline. */
+      discard_until ('\n');
+      shell_getc (0);
+      character = '\n';        /* this will take the next if statement and return. */
+    }
+
+  if (character == '\n')
+    {
+      /* If we're about to return an unquoted newline, we can go and collect
+        the text of any pending here document. */
+      if (need_here_doc)
+       gather_here_documents ();
+
+#if defined (ALIAS)
+      parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      return (character);
+    }
+
+  if (parser_state & PST_REGEXP)
+    goto tokword;
+
+  /* Shell meta-characters. */
+  if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
+    {
+#if defined (ALIAS)
+      /* Turn off alias tokenization iff this character sequence would
+        not leave us ready to read a command. */
+      if (character == '<' || character == '>')
+       parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      peek_char = shell_getc (1);
+      if (character == peek_char)
+       {
+         switch (character)
+           {
+           case '<':
+             /* If '<' then we could be at "<<" or at "<<-".  We have to
+                look ahead one more character. */
+             peek_char = shell_getc (1);
+             if MBTEST(peek_char == '-')
+               return (LESS_LESS_MINUS);
+             else if MBTEST(peek_char == '<')
+               return (LESS_LESS_LESS);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (LESS_LESS);
+               }
+
+           case '>':
+             return (GREATER_GREATER);
+
+           case ';':
+             parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+             parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+             peek_char = shell_getc (1);
+             if MBTEST(peek_char == '&')
+               return (SEMI_SEMI_AND);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (SEMI_SEMI);
+               }
+
+           case '&':
+             return (AND_AND);
+
+           case '|':
+             return (OR_OR);
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+           case '(':           /* ) */
+             result = parse_dparen (character);
+             if (result == -2)
+               break;
+             else
+               return result;
+#endif
+           }
+       }
+      else if MBTEST(character == '<' && peek_char == '&')
+       return (LESS_AND);
+      else if MBTEST(character == '>' && peek_char == '&')
+       return (GREATER_AND);
+      else if MBTEST(character == '<' && peek_char == '>')
+       return (LESS_GREATER);
+      else if MBTEST(character == '>' && peek_char == '|')
+       return (GREATER_BAR);
+      else if MBTEST(character == '&' && peek_char == '>')
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '>')
+           return (AND_GREATER_GREATER);
+         else
+           {
+             shell_ungetc (peek_char);
+             return (AND_GREATER);
+           }
+       }
+      else if MBTEST(character == '|' && peek_char == '&')
+       return (BAR_AND);
+      else if MBTEST(character == ';' && peek_char == '&')
+       {
+         parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         return (SEMI_AND);
+       }
+
+      shell_ungetc (peek_char);
+
+      /* If we look like we are reading the start of a function
+        definition, then let the reader know about it so that
+        we will do the right thing with `{'. */
+      if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD)
+       {
+         parser_state |= PST_ALLOWOPNBRC;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         function_dstart = line_number;
+       }
+
+      /* case pattern lists may be preceded by an optional left paren.  If
+        we're not trying to parse a case pattern list, the left paren
+        indicates a subshell. */
+      if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */
+       parser_state |= PST_SUBSHELL;
+      /*(*/
+      else if MBTEST((parser_state & PST_CASEPAT) && character == ')')
+       parser_state &= ~PST_CASEPAT;
+      /*(*/
+      else if MBTEST((parser_state & PST_SUBSHELL) && character == ')')
+       parser_state &= ~PST_SUBSHELL;
+
+#if defined (PROCESS_SUBSTITUTION)
+      /* Check for the constructs which introduce process substitution.
+        Shells running in `posix mode' don't do process substitution. */
+      if MBTEST(posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) /*)*/
+#endif /* PROCESS_SUBSTITUTION */
+       return (character);
+    }
+
+  /* Hack <&- (close stdin) case.  Also <&N- (dup and close). */
+  if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
+    return (character);
+
+tokword:
+  /* Okay, if we got this far, we have to read a word.  Read one,
+     and then check it against the known ones. */
+  result = read_token_word (character);
+#if defined (ALIAS)
+  if (result == RE_READ_TOKEN)
+    goto re_read_token;
+#endif
+  return result;
+}
+
+/*
+ * Match a $(...) or other grouping construct.  This has to handle embedded
+ * quoted strings ('', ``, "") and nested constructs.  It also must handle
+ * reprompting the user, if necessary, after reading a newline, and returning
+ * correct error values if it reads EOF.
+ */
+#define P_FIRSTCLOSE   0x01
+#define P_ALLOWESC     0x02
+#define P_DQUOTE       0x04
+#define P_COMMAND      0x08    /* parsing a command, so look for comments */
+#define P_BACKQUOTE    0x10    /* parsing a backquoted command substitution */
+
+/* Lexical state while parsing a grouping construct or $(...). */
+#define LEX_WASDOL     0x001
+#define LEX_CKCOMMENT  0x002
+#define LEX_INCOMMENT  0x004
+#define LEX_PASSNEXT   0x008
+#define LEX_RESWDOK    0x010
+#define LEX_CKCASE     0x020
+#define LEX_INCASE     0x040
+#define LEX_INHEREDOC  0x080
+#define LEX_HEREDELIM  0x100           /* reading here-doc delimiter */
+#define LEX_STRIPDOC   0x200           /* <<- strip tabs from here doc delim */
+
+#define COMSUB_META(ch)                ((ch) == ';' || (ch) == '&' || (ch) == '|')
+
+#define CHECK_NESTRET_ERROR() \
+  do { \
+    if (nestret == &matched_pair_error) \
+      { \
+       free (ret); \
+       return &matched_pair_error; \
+      } \
+  } while (0)
+
+#define APPEND_NESTRET() \
+  do { \
+    if (nestlen) \
+      { \
+       RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); \
+       strcpy (ret + retind, nestret); \
+       retind += nestlen; \
+      } \
+  } while (0)
+
+static char matched_pair_error;
+
+static char *
+parse_matched_pair (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, tflags;
+  int nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans;
+  int retind, retsize, rflags;
+
+/* itrace("parse_matched_pair: open = %c close = %c", open, close); */
+  count = 1;
+  tflags = 0;
+
+  if ((flags & P_COMMAND) && qc != '`' && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  while (count)
+    {
+      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+      if (ch == EOF)
+       {
+         free (ret);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* Possible reprompting. */
+      if (ch == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* Don't bother counting parens or doing anything else if in a comment
+        or part of a case statement */
+      if (tflags & LEX_INCOMMENT)
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if (ch == '\n')
+           tflags &= ~LEX_INCOMMENT;
+
+         continue;
+       }
+
+      /* Not exactly right yet, should handle shell metacharacters, too.  If
+        any changes are made to this test, make analogous changes to subst.c:
+        extract_delimited_string(). */
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+       tflags |= LEX_INCOMMENT;
+
+      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
+       {
+         tflags &= ~LEX_PASSNEXT;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0)
+               retind--;       /* swallow previously-added backslash */
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == close)              /* ending delimiter */
+       count--;
+      /* handle nested ${...} specially. */
+      else if MBTEST(open != close && (tflags & LEX_WASDOL) && open == '{' && ch == open) /* } */
+       count++;
+      else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open)      /* nested begin */
+       count++;
+
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
+
+      /* If we just read the ending character, don't bother continuing. */
+      if (count == 0)
+       break;
+
+      if (open == '\'')                        /* '' inside grouping construct */
+       {
+         if MBTEST((flags & P_ALLOWESC) && ch == '\\')
+           tflags |= LEX_PASSNEXT;
+         continue;
+       }
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       tflags |= LEX_PASSNEXT;
+
+      /* Could also check open == '`' if we want to parse grouping constructs
+        inside old-style command substitution. */
+      if (open != close)               /* a grouping construct */
+       {
+         if MBTEST(shellquote (ch))
+           {
+             /* '', ``, or "" inside $(...) or other grouping construct. */
+             push_delimiter (dstack, ch);
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'')    /* $'...' inside group */
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+             else
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+             pop_delimiter (dstack);
+             CHECK_NESTRET_ERROR ();
+
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Translate $'...' here. */
+                 ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+                 xfree (nestret);
+
+                 if ((rflags & P_DQUOTE) == 0)
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else
+                   {
+                     nestret = ttrans;
+                     nestlen = ttranslen;
+                   }
+                 retind -= 2;          /* back up before the $' */
+               }
+             else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Locale expand $"..." here. */
+                 ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+                 xfree (nestret);
+
+                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 nestlen = ttranslen + 2;
+                 retind -= 2;          /* back up before the $" */
+               }
+
+             APPEND_NESTRET ();
+             FREE (nestret);
+           }
+       }
+      /* Parse an old-style command substitution within double quotes as a
+        single word. */
+      /* XXX - sh and ksh93 don't do this - XXX */
+      else if MBTEST(open == '"' && ch == '`')
+       {
+         nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      else if MBTEST(open != '`' && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))    /* ) } ] */
+       /* check for $(), $[], or ${} inside quoted string. */
+       {
+         if (open == ch)       /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+         else if (ch == '[')           /* ] */
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
+    }
+
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+  return ret;
+}
+
+/* Parse a $(...) command substitution.  This is messier than I'd like, and
+   reproduces a lot more of the token-reading code than I'd like. */
+static char *
+parse_comsub (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, peekc, tflags, lex_rwlen, lex_firstind;
+  int nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans, *heredelim;
+  int retind, retsize, rflags, hdlen;
+
+/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
+  count = 1;
+  tflags = LEX_RESWDOK;
+
+  if ((flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCASE;
+  if ((tflags & LEX_CKCASE) && (interactive == 0 || interactive_comments))
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  lex_rwlen = 0;
+
+  heredelim = 0;
+  lex_firstind = -1;
+
+  while (count)
+    {
+comsub_readchar:
+      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+      if (ch == EOF)
+       {
+eof_error:
+         free (ret);
+         FREE (heredelim);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* If we hit the end of a line and are reading the contents of a here
+        document, and it's not the same line that the document starts on,
+        check for this line being the here doc delimiter.  Otherwise, if
+        we're in a here document, mark the next character as the beginning
+        of a line. */
+      if (ch == '\n')
+       {
+         if ((tflags & LEX_HEREDELIM) && heredelim)
+           {
+             tflags &= ~LEX_HEREDELIM;
+             tflags |= LEX_INHEREDOC;
+             lex_firstind = retind + 1;
+           }
+         else if (tflags & LEX_INHEREDOC)
+           {
+             int tind;
+             tind = lex_firstind;
+             while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
+               tind++;
+             if (STREQN (ret + tind, heredelim, hdlen))
+               {
+                 tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+                 lex_firstind = -1;
+               }
+             else
+               lex_firstind = retind + 1;
+           }
+       }
+
+      /* Possible reprompting. */
+      if (ch == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* Don't bother counting parens or doing anything else if in a comment */
+      if (tflags & (LEX_INCOMMENT|LEX_INHEREDOC))
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if ((tflags & LEX_INCOMMENT) && ch == '\n')
+           tflags &= ~LEX_INCOMMENT;
+
+         continue;
+       }
+
+      /* Skip whitespace */
+      if MBTEST(shellblank (ch) && lex_rwlen == 0)
+        {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         continue;
+        }
+
+      /* Either we are looking for the start of the here-doc delimiter
+        (lex_firstind == -1) or we are reading one (lex_firstind >= 0).
+        If this character is a shell break character and we are reading
+        the delimiter, save it and note that we are now reading a here
+        document.  If we've found the start of the delimiter, note it by
+        setting lex_firstind.  Backslashes can quote shell metacharacters
+        in here-doc delimiters. */
+      if (tflags & LEX_HEREDELIM)
+       {
+         if (lex_firstind == -1 && shellbreak (ch) == 0)
+           lex_firstind = retind;
+         else if (lex_firstind >= 0 && (tflags & LEX_PASSNEXT) == 0 && shellbreak (ch))
+           {
+             nestret = substring (ret, lex_firstind, retind);
+             heredelim = string_quote_removal (nestret, 0);
+             free (nestret);
+             hdlen = STRLEN(heredelim);
+/*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/
+             if (ch == '\n')
+               {
+                 tflags |= LEX_INHEREDOC;
+                 tflags &= ~LEX_HEREDELIM;
+                 lex_firstind = retind + 1;
+               }
+             else
+               lex_firstind = -1;
+           }
+       }
+
+      /* Meta-characters that can introduce a reserved word.  Not perfect yet. */
+      if MBTEST((tflags & LEX_RESWDOK) == 0 && (tflags & LEX_CKCASE) && (tflags & LEX_INCOMMENT) == 0 && shellmeta(ch))
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         peekc = shell_getc (1);
+         if (ch == peekc && (ch == '&' || ch == '|' || ch == ';'))     /* two-character tokens */
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = peekc;
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch); */
+             tflags |= LEX_RESWDOK;
+             lex_rwlen = 0;
+             continue;
+           }
+         else if (ch == '\n' || COMSUB_META(ch))
+           {
+             shell_ungetc (peekc);
+             tflags |= LEX_RESWDOK;
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
+             lex_rwlen = 0;
+             continue;
+           }
+         else if (ch == EOF)
+           goto eof_error;
+         else
+           {
+             /* `unget' the character we just added and fall through */
+             retind--;
+             shell_ungetc (peekc);
+           }
+       }
+
+      /* If we can read a reserved word, try to read one. */
+      if (tflags & LEX_RESWDOK)
+       {
+         if MBTEST(islower (ch))
+           {
+             /* Add this character. */
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = ch;
+             lex_rwlen++;
+             continue;
+           }
+         else if MBTEST(lex_rwlen == 4 && shellbreak (ch))
+           {
+             if (STREQN (ret + retind - 4, "case", 4))
+{
+               tflags |= LEX_INCASE;
+/*itrace("parse_comsub:%d: found `case', lex_incase -> 1", line_number);*/
+}
+             else if (STREQN (ret + retind - 4, "esac", 4))
+{
+               tflags &= ~LEX_INCASE;
+/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0", line_number);*/
+}              
+             tflags &= ~LEX_RESWDOK;
+           }
+         else if (shellbreak (ch) == 0)
+{
+             tflags &= ~LEX_RESWDOK;
+/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
+}
+       }
+
+      if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<')
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         peekc = shell_getc (1);
+         if (peekc == EOF)
+           goto eof_error;
+         if (peekc == ch)
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = peekc;
+             peekc = shell_getc (1);
+             if (peekc == EOF)
+               goto eof_error;
+             if (peekc == '-')
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+                 ret[retind++] = peekc;
+                 tflags |= LEX_STRIPDOC;
+               }
+             else
+               shell_ungetc (peekc);
+             tflags |= LEX_HEREDELIM;
+             lex_firstind = -1;
+             continue;
+           }
+         else
+           ch = peekc;         /* fall through and continue XXX - this skips comments if peekc == '#' */
+       }
+      /* Not exactly right yet, should handle shell metacharacters, too.  If
+        any changes are made to this test, make analogous changes to subst.c:
+        extract_delimited_string(). */
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+       tflags |= LEX_INCOMMENT;
+
+      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
+       {
+         tflags &= ~LEX_PASSNEXT;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0)
+               retind--;       /* swallow previously-added backslash */
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+#if 0
+      else if MBTEST((tflags & LEX_INCASE) && ch == close && close == ')')
+        tflags &= ~LEX_INCASE;         /* XXX */
+#endif
+      else if MBTEST(ch == close && (tflags & LEX_INCASE) == 0)                /* ending delimiter */
+{
+       count--;
+/*itrace("parse_comsub:%d: found close: count = %d", line_number, count);*/
+}
+      else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && (tflags & LEX_INCASE) == 0 && ch == open)        /* nested begin */
+       count++;
+
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
+
+      /* If we just read the ending character, don't bother continuing. */
+      if (count == 0)
+       break;
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       tflags |= LEX_PASSNEXT;
+
+      if MBTEST(shellquote (ch))
+        {
+          /* '', ``, or "" inside $(...). */
+          push_delimiter (dstack, ch);
+          if MBTEST((tflags & LEX_WASDOL) && ch == '\'')       /* $'...' inside group */
+           nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+         else
+           nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+         pop_delimiter (dstack);
+         CHECK_NESTRET_ERROR ();
+
+         if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+           {
+             /* Translate $'...' here. */
+             ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+             xfree (nestret);
+
+             if ((rflags & P_DQUOTE) == 0)
+               {
+                 nestret = sh_single_quote (ttrans);
+                 free (ttrans);
+                 nestlen = strlen (nestret);
+               }
+             else
+               {
+                 nestret = ttrans;
+                 nestlen = ttranslen;
+               }
+             retind -= 2;              /* back up before the $' */
+           }
+         else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+           {
+             /* Locale expand $"..." here. */
+             ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+             xfree (nestret);
+
+             nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+             free (ttrans);
+             nestlen = ttranslen + 2;
+             retind -= 2;              /* back up before the $" */
+           }
+
+         APPEND_NESTRET ();
+         FREE (nestret);
+       }
+      else if MBTEST((tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))   /* ) } ] */
+       /* check for $(), $[], or ${} inside command substitution. */
+       {
+         if ((tflags & LEX_INCASE) == 0 && open == ch) /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+         else if (ch == '[')           /* ] */
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
+    }
+
+  FREE (heredelim);
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+/*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/
+  return ret;
+}
+
+/* XXX - this needs to handle functionality like subst.c:no_longjmp_on_fatal_error;
+   maybe extract_command_subst should handle it. */
+char *
+xparse_dolparen (base, string, indp, flags)
+     char *base;
+     char *string;
+     int *indp;
+     int flags;
+{
+  sh_parser_state_t ps;
+  int orig_ind, nc, sflags;
+  char *ret, *s, *ep, *ostring;
+
+  /*yydebug = 1;*/
+  orig_ind = *indp;
+  ostring = string;
+
+  sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
+  if (flags & 0x010)           /* EX_NOLONGJMP */
+    sflags |= SEVAL_NOLONGJMP;
+  save_parser_state (&ps);
+
+  /*(*/
+  parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
+  shell_eof_token = ')';
+  parse_string (string, "command substitution", sflags, &ep);
+
+  restore_parser_state (&ps);
+  reset_parser ();
+  if (interactive)
+    token_to_read = 0;
+
+  /* Need to find how many characters parse_and_execute consumed, update
+     *indp, if flags != 0, copy the portion of the string parsed into RET
+     and return it.  If flags & 1 (EX_NOALLOC) we can return NULL. */
+
+  /*(*/
+  if (ep[-1] != ')')
+    {
+#if DEBUG
+      itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+#endif
+      while (ep > ostring && ep[-1] == '\n') ep--;
+    }
+
+  nc = ep - ostring;
+  *indp = ep - base - 1;
+
+  /*(*/
+#if DEBUG
+  if (base[*indp] != ')')
+    itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
+#endif
+
+  if (flags & 1) 
+    return (char *)NULL;
+
+  if (nc == 0)
+    {
+      ret = xmalloc (1);
+      ret[0] = '\0';
+    }
+  else
+    ret = substring (ostring, 0, nc - 1);
+
+  return ret;
+}
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* Parse a double-paren construct.  It can be either an arithmetic
+   command, an arithmetic `for' command, or a nested subshell.  Returns
+   the parsed token, -1 on error, or -2 if we didn't do anything and
+   should just go on. */
+static int
+parse_dparen (c)
+     int c;
+{
+  int cmdtyp, sline;
+  char *wval;
+  WORD_DESC *wd;
+
+#if defined (ARITH_FOR_COMMAND)
+  if (last_read_token == FOR)
+    {
+      arith_for_lineno = line_number;
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1)
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_FOR_EXPRS);
+       }
+      else
+       return -1;              /* ERROR */
+    }
+#endif
+
+#if defined (DPAREN_ARITHMETIC)
+  if (reserved_word_acceptable (last_read_token))
+    {
+      sline = line_number;
+
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1) /* arithmetic command */
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_CMD);
+       }
+      else if (cmdtyp == 0)    /* nested subshell */
+       {
+         push_string (wval, 0, (alias_t *)NULL);
+         if ((parser_state & PST_CASEPAT) == 0)
+           parser_state |= PST_SUBSHELL;
+         return (c);
+       }
+      else                     /* ERROR */
+       return -1;
+    }
+#endif
+
+  return -2;                   /* XXX */
+}
+
+/* We've seen a `(('.  Look for the matching `))'.  If we get it, return 1.
+   If not, assume it's a nested subshell for backwards compatibility and
+   return 0.  In any case, put the characters we've consumed into a locally-
+   allocated buffer and make *ep point to that buffer.  Return -1 on an
+   error, for example EOF. */
+static int
+parse_arith_cmd (ep, adddq)
+     char **ep;
+     int adddq;
+{
+  int exp_lineno, rval, c;
+  char *ttok, *tokstr;
+  int ttoklen;
+
+  exp_lineno = line_number;
+  ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0);
+  rval = 1;
+  if (ttok == &matched_pair_error)
+    return -1;
+  /* Check that the next character is the closing right paren.  If
+     not, this is a syntax error. ( */
+  c = shell_getc (0);
+  if MBTEST(c != ')')
+    rval = 0;
+
+  tokstr = (char *)xmalloc (ttoklen + 4);
+
+  /* if ADDDQ != 0 then (( ... )) -> "..." */
+  if (rval == 1 && adddq)      /* arith cmd, add double quotes */
+    {
+      tokstr[0] = '"';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = '"';
+      tokstr[ttoklen+1] = '\0';
+    }
+  else if (rval == 1)          /* arith cmd, don't add double quotes */
+    {
+      strncpy (tokstr, ttok, ttoklen - 1);
+      tokstr[ttoklen-1] = '\0';
+    }
+  else                         /* nested subshell */
+    {
+      tokstr[0] = '(';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = ')';
+      tokstr[ttoklen+1] = c;
+      tokstr[ttoklen+2] = '\0';
+    }
+
+  *ep = tokstr;
+  FREE (ttok);
+  return rval;
+}
+#endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */
+
+#if defined (COND_COMMAND)
+static void
+cond_error ()
+{
+  char *etext;
+
+  if (EOF_Reached && cond_token != COND_ERROR)         /* [[ */
+    parser_error (cond_lineno, _("unexpected EOF while looking for `]]'"));
+  else if (cond_token != COND_ERROR)
+    {
+      if (etext = error_token_from_token (cond_token))
+       {
+         parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext);
+         free (etext);
+       }
+      else
+       parser_error (cond_lineno, _("syntax error in conditional expression"));
+    }
+}
+
+static COND_COM *
+cond_expr ()
+{
+  return (cond_or ());  
+}
+
+static COND_COM *
+cond_or ()
+{
+  COND_COM *l, *r;
+
+  l = cond_and ();
+  if (cond_token == OR_OR)
+    {
+      r = cond_or ();
+      l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static COND_COM *
+cond_and ()
+{
+  COND_COM *l, *r;
+
+  l = cond_term ();
+  if (cond_token == AND_AND)
+    {
+      r = cond_and ();
+      l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static int
+cond_skip_newlines ()
+{
+  while ((cond_token = read_token (READ)) == '\n')
+    {
+      if (SHOULD_PROMPT ())
+       prompt_again ();
+    }
+  return (cond_token);
+}
+
+#define COND_RETURN_ERROR() \
+  do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0)
+
+static COND_COM *
+cond_term ()
+{
+  WORD_DESC *op;
+  COND_COM *term, *tleft, *tright;
+  int tok, lineno;
+  char *etext;
+
+  /* Read a token.  It can be a left paren, a `!', a unary operator, or a
+     word that should be the first argument of a binary operator.  Start by
+     skipping newlines, since this is a compound command. */
+  tok = cond_skip_newlines ();
+  lineno = line_number;
+  if (tok == COND_END)
+    {
+      COND_RETURN_ERROR ();
+    }
+  else if (tok == '(')
+    {
+      term = cond_expr ();
+      if (cond_token != ')')
+       {
+         if (term)
+           dispose_cond_node (term);           /* ( */
+         if (etext = error_token_from_token (cond_token))
+           {
+             parser_error (lineno, _("unexpected token `%s', expected `)'"), etext);
+             free (etext);
+           }
+         else
+           parser_error (lineno, _("expected `)'"));
+         COND_RETURN_ERROR ();
+       }
+      term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL);
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0')))
+    {
+      if (tok == WORD)
+       dispose_word (yylval.word);     /* not needed */
+      term = cond_term ();
+      if (term)
+       term->flags |= CMD_INVERT_RETURN;
+    }
+  else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[2] == 0 && test_unop (yylval.word->word))
+    {
+      op = yylval.word;
+      tok = read_token (READ);
+      if (tok == WORD)
+       {
+         tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+       }
+      else
+       {
+         dispose_word (op);
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional unary operator"));
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == WORD)                /* left argument to binary operator */
+    {
+      /* lhs */
+      tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+
+      /* binop */
+      tok = read_token (READ);
+      if (tok == WORD && test_binop (yylval.word->word))
+       op = yylval.word;
+#if defined (COND_REGEXP)
+      else if (tok == WORD && STREQ (yylval.word->word, "=~"))
+       {
+         op = yylval.word;
+         parser_state |= PST_REGEXP;
+       }
+#endif
+      else if (tok == '<' || tok == '>')
+       op = make_word_from_token (tok);  /* ( */
+      /* There should be a check before blindly accepting the `)' that we have
+        seen the opening `('. */
+      else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')')
+       {
+         /* Special case.  [[ x ]] is equivalent to [[ -n x ]], just like
+            the test command.  Similarly for [[ x && expr ]] or
+            [[ x || expr ]] or [[ (x) ]]. */
+         op = make_word ("-n");
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+         cond_token = tok;
+         return (term);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("conditional binary operator expected"));
+         dispose_cond_node (tleft);
+         COND_RETURN_ERROR ();
+       }
+
+      /* rhs */
+      tok = read_token (READ);
+      parser_state &= ~PST_REGEXP;
+      if (tok == WORD)
+       {
+         tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_BINARY, op, tleft, tright);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional binary operator"));
+         dispose_cond_node (tleft);
+         dispose_word (op);
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else
+    {
+      if (tok < 256)
+       parser_error (line_number, _("unexpected token `%c' in conditional command"), tok);
+      else if (etext = error_token_from_token (tok))
+       {
+         parser_error (line_number, _("unexpected token `%s' in conditional command"), etext);
+         free (etext);
+       }
+      else
+       parser_error (line_number, _("unexpected token %d in conditional command"), tok);
+      COND_RETURN_ERROR ();
+    }
+  return (term);
+}      
+
+/* This is kind of bogus -- we slip a mini recursive-descent parser in
+   here to handle the conditional statement syntax. */
+static COMMAND *
+parse_cond_command ()
+{
+  COND_COM *cexp;
+
+  cexp = cond_expr ();
+  return (make_cond_command (cexp));
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* When this is called, it's guaranteed that we don't care about anything
+   in t beyond i.  We do save and restore the chars, though. */
+static int
+token_is_assignment (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c, c1;
+  int r;
+
+  c = t[i]; c1 = t[i+1];
+  t[i] = '='; t[i+1] = '\0';
+  r = assignment (t, (parser_state & PST_COMPASSIGN) != 0);
+  t[i] = c; t[i+1] = c1;
+  return r;
+}
+
+/* XXX - possible changes here for `+=' */
+static int
+token_is_ident (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c;
+  int r;
+
+  c = t[i];
+  t[i] = '\0';
+  r = legal_identifier (t);
+  t[i] = c;
+  return r;
+}
+#endif
+
+static int
+read_token_word (character)
+     int character;
+{
+  /* The value for YYLVAL when a WORD is read. */
+  WORD_DESC *the_word;
+
+  /* Index into the token that we are building. */
+  int token_index;
+
+  /* ALL_DIGITS becomes zero when we see a non-digit. */
+  int all_digit_token;
+
+  /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
+  int dollar_present;
+
+  /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound
+     assignment. */
+  int compound_assignment;
+
+  /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
+  int quoted;
+
+  /* Non-zero means to ignore the value of the next character, and just
+     to add it no matter what. */
+ int pass_next_character;
+
+  /* The current delimiting character. */
+  int cd;
+  int result, peek_char;
+  char *ttok, *ttrans;
+  int ttoklen, ttranslen;
+  intmax_t lvalue;
+
+  if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE)
+    token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE);
+
+  token_index = 0;
+  all_digit_token = DIGIT (character);
+  dollar_present = quoted = pass_next_character = compound_assignment = 0;
+
+  for (;;)
+    {
+      if (character == EOF)
+       goto got_token;
+
+      if (pass_next_character)
+       {
+         pass_next_character = 0;
+         goto got_escaped_character;
+       }
+
+      cd = current_delimiter (dstack);
+
+      /* Handle backslashes.  Quote lots of things when not inside of
+        double-quotes, quote some things inside of double-quotes. */
+      if MBTEST(character == '\\')
+       {
+         peek_char = shell_getc (0);
+
+         /* Backslash-newline is ignored in all cases except
+            when quoted with single quotes. */
+         if (peek_char == '\n')
+           {
+             character = '\n';
+             goto next_character;
+           }
+         else
+           {
+             shell_ungetc (peek_char);
+
+             /* If the next character is to be quoted, note it now. */
+             if (cd == 0 || cd == '`' ||
+                 (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE)))
+               pass_next_character++;
+
+             quoted = 1;
+             goto got_character;
+           }
+       }
+
+      /* Parse a matched pair of quote characters. */
+      if MBTEST(shellquote (character))
+       {
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         all_digit_token = 0;
+         quoted = 1;
+         dollar_present |= (character == '"' && strchr (ttok, '$') != 0);
+         FREE (ttok);
+         goto next_character;
+       }
+
+#ifdef COND_REGEXP
+      /* When parsing a regexp as a single word inside a conditional command,
+        we need to special-case characters special to both the shell and
+        regular expressions.  Right now, that is only '(' and '|'. */ /*)*/
+      if MBTEST((parser_state & PST_REGEXP) && (character == '(' || character == '|'))         /*)*/
+       {
+         if (character == '|')
+           goto got_character;
+
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         dollar_present = all_digit_token = 0;
+         goto next_character;
+       }
+#endif /* COND_REGEXP */
+
+#ifdef EXTENDED_GLOB
+      /* Parse a ksh-style extended pattern matching specification. */
+      if MBTEST(extended_glob && PATTERN_CHAR (character))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = all_digit_token = 0;
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif /* EXTENDED_GLOB */
+
+      /* If the delimiter character is not single quote, parse some of
+        the shell expansions that must be read as a single word. */
+      if (shellexp (character))
+       {
+         peek_char = shell_getc (1);
+         /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
+         if MBTEST(peek_char == '(' || \
+               ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
+           {
+             if (peek_char == '{')             /* } */
+               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE);
+             else if (peek_char == '(')                /* ) */
+               {
+                 /* XXX - push and pop the `(' as a delimiter for use by
+                    the command-oriented-history code.  This way newlines
+                    appearing in the $(...) string get added to the
+                    history literally rather than causing a possibly-
+                    incorrect `;' to be added. ) */
+                 push_delimiter (dstack, peek_char);
+                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+                 pop_delimiter (dstack);
+               }
+             else
+               ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This handles $'...' and $"..." new-style quoted strings. */
+         else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"'))
+           {
+             int first_line;
+
+             first_line = line_number;
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (peek_char, peek_char, peek_char,
+                                        &ttoklen,
+                                        (peek_char == '\'') ? P_ALLOWESC : 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;
+             if (peek_char == '\'')
+               {
+                 ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
+                 free (ttok);
+
+                 /* Insert the single quotes and correctly quote any
+                    embedded single quotes (allowed because P_ALLOWESC was
+                    passed to parse_matched_pair). */
+                 ttok = sh_single_quote (ttrans);
+                 free (ttrans);
+                 ttranslen = strlen (ttok);
+                 ttrans = ttok;
+               }
+             else
+               {
+                 /* Try to locale-expand the converted string. */
+                 ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
+                 free (ttok);
+
+                 /* Add the double quotes back */
+                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 ttranslen += 2;
+                 ttrans = ttok;
+               }
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttrans);
+             token_index += ttranslen;
+             FREE (ttrans);
+             quoted = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This could eventually be extended to recognize all of the
+            shell's single-character parameter expansions, and set flags.*/
+         else if MBTEST(character == '$' && peek_char == '$')
+           {
+             ttok = (char *)xmalloc (3);
+             ttok[0] = ttok[1] = '$';
+             ttok[2] = '\0';
+             RESIZE_MALLOCED_BUFFER (token, token_index, 3,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttok);
+             token_index += 2;
+             dollar_present = 1;
+             all_digit_token = 0;
+             FREE (ttok);
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+
+#if defined (ARRAY_VARS)
+      /* Identify possible array subscript assignment; match [...] */
+      else if MBTEST(character == '[' && token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index))    /* ] */
+        {
+         ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size,
+                                 TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         all_digit_token = 0;
+         goto next_character;
+        }
+      /* Identify possible compound array variable assignment. */
+      else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             ttok = parse_compound_assignment (&ttoklen);
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+
+             token[token_index++] = '=';
+             token[token_index++] = '(';
+             if (ttok)
+               {
+                 strcpy (token + token_index, ttok);
+                 token_index += ttoklen;
+               }
+             token[token_index++] = ')';
+             FREE (ttok);
+             all_digit_token = 0;
+             compound_assignment = 1;
+#if 1
+             goto next_character;
+#else
+             goto got_token;           /* ksh93 seems to do this */
+#endif
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif
+
+      /* When not parsing a multi-character word construct, shell meta-
+        characters break words. */
+      if MBTEST(shellbreak (character))
+       {
+         shell_ungetc (character);
+         goto got_token;
+       }
+
+    got_character:
+
+      if (character == CTLESC || character == CTLNUL)
+       token[token_index++] = CTLESC;
+
+    got_escaped_character:
+
+      all_digit_token &= DIGIT (character);
+      dollar_present |= character == '$';
+
+      token[token_index++] = character;
+
+      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+                             TOKEN_DEFAULT_GROW_SIZE);
+
+    next_character:
+      if (character == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* We want to remove quoted newlines (that is, a \<newline> pair)
+        unless we are within single quotes or pass_next_character is
+        set (the shell equivalent of literal-next). */
+      cd = current_delimiter (dstack);
+      character = shell_getc (cd != '\'' && pass_next_character == 0);
+    }  /* end for (;;) */
+
+got_token:
+
+  token[token_index] = '\0';
+
+  /* Check to see what thing we should return.  If the last_read_token
+     is a `<', or a `&', or the character which ended this token is
+     a '>' or '<', then, and ONLY then, is this input token a NUMBER.
+     Otherwise, it is just a word, and should be returned as such. */
+  if MBTEST(all_digit_token && (character == '<' || character == '>' || \
+                   last_read_token == LESS_AND || \
+                   last_read_token == GREATER_AND))
+      {
+       if (legal_number (token, &lvalue) && (int)lvalue == lvalue)
+         yylval.number = lvalue;
+       else
+         yylval.number = -1;
+       return (NUMBER);
+      }
+
+  /* Check for special case tokens. */
+  result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1;
+  if (result >= 0)
+    return result;
+
+#if defined (ALIAS)
+  /* Posix.2 does not allow reserved words to be aliased, so check for all
+     of them, including special cases, before expanding the current token
+     as an alias. */
+  if MBTEST(posixly_correct)
+    CHECK_FOR_RESERVED_WORD (token);
+
+  /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting
+     inhibits alias expansion. */
+  if (expand_aliases && quoted == 0)
+    {
+      result = alias_expand_token (token);
+      if (result == RE_READ_TOKEN)
+       return (RE_READ_TOKEN);
+      else if (result == NO_EXPANSION)
+       parser_state &= ~PST_ALEXPNEXT;
+    }
+
+  /* If not in Posix.2 mode, check for reserved words after alias
+     expansion. */
+  if MBTEST(posixly_correct == 0)
+#endif
+    CHECK_FOR_RESERVED_WORD (token);
+
+  the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
+  the_word->word = (char *)xmalloc (1 + token_index);
+  the_word->flags = 0;
+  strcpy (the_word->word, token);
+  if (dollar_present)
+    the_word->flags |= W_HASDOLLAR;
+  if (quoted)
+    the_word->flags |= W_QUOTED;               /*(*/
+  if (compound_assignment && token[token_index-1] == ')')
+    the_word->flags |= W_COMPASSIGN;
+  /* A word is an assignment if it appears at the beginning of a
+     simple command, or after another assignment word.  This is
+     context-dependent, so it cannot be handled in the grammar. */
+  if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
+    {
+      the_word->flags |= W_ASSIGNMENT;
+      /* Don't perform word splitting on assignment statements. */
+      if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
+       the_word->flags |= W_NOSPLIT;
+    }
+
+  if (command_token_position (last_read_token))
+    {
+      struct builtin *b;
+      b = builtin_address_internal (token, 0);
+      if (b && (b->flags & ASSIGNMENT_BUILTIN))
+       parser_state |= PST_ASSIGNOK;
+      else if (STREQ (token, "eval") || STREQ (token, "let"))
+       parser_state |= PST_ASSIGNOK;
+    }
+
+  yylval.word = the_word;
+
+  result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
+               ? ASSIGNMENT_WORD : WORD;
+
+  switch (last_read_token)
+    {
+    case FUNCTION:
+      parser_state |= PST_ALLOWOPNBRC;
+      function_dstart = line_number;
+      break;
+    case CASE:
+    case SELECT:
+    case FOR:
+      if (word_top < MAX_CASE_NEST)
+       word_top++;
+      word_lineno[word_top] = line_number;
+      break;
+    }
+
+  return (result);
+}
+
+/* Return 1 if TOKSYM is a token that after being read would allow
+   a reserved word to be seen, else 0. */
+static int
+reserved_word_acceptable (toksym)
+     int toksym;
+{
+  switch (toksym)
+    {
+    case '\n':
+    case ';':
+    case '(':
+    case ')':
+    case '|':
+    case '&':
+    case '{':
+    case '}':          /* XXX */
+    case AND_AND:
+    case BANG:
+    case DO:
+    case DONE:
+    case ELIF:
+    case ELSE:
+    case ESAC:
+    case FI:
+    case IF:
+    case OR_OR:
+    case SEMI_SEMI:
+    case SEMI_AND:
+    case SEMI_SEMI_AND:
+    case THEN:
+    case TIME:
+    case TIMEOPT:
+    case UNTIL:
+    case WHILE:
+    case 0:
+      return 1;
+    default:
+      return 0;
+    }
+}
+    
+/* Return the index of TOKEN in the alist of reserved words, or -1 if
+   TOKEN is not a shell reserved word. */
+int
+find_reserved_word (tokstr)
+     char *tokstr;
+{
+  int i;
+  for (i = 0; word_token_alist[i].word; i++)
+    if (STREQ (tokstr, word_token_alist[i].word))
+      return i;
+  return -1;
+}
+
+#if 0
+#if defined (READLINE)
+/* Called after each time readline is called.  This insures that whatever
+   the new prompt string is gets propagated to readline's local prompt
+   variable. */
+static void
+reset_readline_prompt ()
+{
+  char *temp_prompt;
+
+  if (prompt_string_pointer)
+    {
+      temp_prompt = (*prompt_string_pointer)
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+      if (temp_prompt == 0)
+       {
+         temp_prompt = (char *)xmalloc (1);
+         temp_prompt[0] = '\0';
+       }
+
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+}
+#endif /* READLINE */
+#endif /* 0 */
+
+#if defined (HISTORY)
+/* A list of tokens which can be followed by newlines, but not by
+   semi-colons.  When concatenating multiple lines of history, the
+   newline separator for such tokens is replaced with a space. */
+static const int no_semi_successors[] = {
+  '\n', '{', '(', ')', ';', '&', '|',
+  CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
+  WHILE, AND_AND, OR_OR, IN,
+  0
+};
+
+/* If we are not within a delimited expression, try to be smart
+   about which separators can be semi-colons and which must be
+   newlines.  Returns the string that should be added into the
+   history entry. */
+char *
+history_delimiting_chars ()
+{
+  register int i;
+
+  if (dstack.delimiter_depth != 0)
+    return ("\n");
+    
+  /* First, handle some special cases. */
+  /*(*/
+  /* If we just read `()', assume it's a function definition, and don't
+     add a semicolon.  If the token before the `)' was not `(', and we're
+     not in the midst of parsing a case statement, assume it's a
+     parenthesized command and add the semicolon. */
+  /*)(*/
+  if (token_before_that == ')')
+    {
+      if (two_tokens_ago == '(')       /*)*/   /* function def */
+       return " ";
+      /* This does not work for subshells inside case statement
+        command lists.  It's a suboptimal solution. */
+      else if (parser_state & PST_CASESTMT)    /* case statement pattern */
+       return " ";
+      else     
+       return "; ";                            /* (...) subshell */
+    }
+  else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
+    return " ";                /* function def using `function name' without `()' */
+
+  else if (token_before_that == WORD && two_tokens_ago == FOR)
+    {
+      /* Tricky.  `for i\nin ...' should not have a semicolon, but
+        `for i\ndo ...' should.  We do what we can. */
+      for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
+       ;
+      if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
+       return " ";
+      return ";";
+    }
+  else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
+    return " ";
+
+  for (i = 0; no_semi_successors[i]; i++)
+    {
+      if (token_before_that == no_semi_successors[i])
+       return (" ");
+    }
+
+  return ("; ");
+}
+#endif /* HISTORY */
+
+/* Issue a prompt, or prepare to issue a prompt when the next character
+   is read. */
+static void
+prompt_again ()
+{
+  char *temp_prompt;
+
+  if (interactive == 0 || expanding_alias())   /* XXX */
+    return;
+
+  ps1_prompt = get_string_value ("PS1");
+  ps2_prompt = get_string_value ("PS2");
+
+  if (!prompt_string_pointer)
+    prompt_string_pointer = &ps1_prompt;
+
+  temp_prompt = *prompt_string_pointer
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+  if (temp_prompt == 0)
+    {
+      temp_prompt = (char *)xmalloc (1);
+      temp_prompt[0] = '\0';
+    }
+
+  current_prompt_string = *prompt_string_pointer;
+  prompt_string_pointer = &ps2_prompt;
+
+#if defined (READLINE)
+  if (!no_line_editing)
+    {
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+  else
+#endif /* READLINE */
+    {
+      FREE (current_decoded_prompt);
+      current_decoded_prompt = temp_prompt;
+    }
+}
+
+int
+get_current_prompt_level ()
+{
+  return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1);
+}
+
+void
+set_current_prompt_level (x)
+     int x;
+{
+  prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt;
+  current_prompt_string = *prompt_string_pointer;
+}
+      
+static void
+print_prompt ()
+{
+  fprintf (stderr, "%s", current_decoded_prompt);
+  fflush (stderr);
+}
+
+/* Return a string which will be printed as a prompt.  The string
+   may contain special characters which are decoded as follows:
+
+       \a      bell (ascii 07)
+       \d      the date in Day Mon Date format
+       \e      escape (ascii 033)
+       \h      the hostname up to the first `.'
+       \H      the hostname
+       \j      the number of active jobs
+       \l      the basename of the shell's tty device name
+       \n      CRLF
+       \r      CR
+       \s      the name of the shell
+       \t      the time in 24-hour hh:mm:ss format
+       \T      the time in 12-hour hh:mm:ss format
+       \@      the time in 12-hour hh:mm am/pm format
+       \A      the time in 24-hour hh:mm format
+       \D{fmt} the result of passing FMT to strftime(3)
+       \u      your username
+       \v      the version of bash (e.g., 2.00)
+       \V      the release of bash, version + patchlevel (e.g., 2.00.0)
+       \w      the current working directory
+       \W      the last element of $PWD
+       \!      the history number of this command
+       \#      the command number of this command
+       \$      a $ or a # if you are root
+       \nnn    character code nnn in octal
+       \\      a backslash
+       \[      begin a sequence of non-printing chars
+       \]      end a sequence of non-printing chars
+*/
+#define PROMPT_GROWTH 48
+char *
+decode_prompt_string (string)
+     char *string;
+{
+  WORD_LIST *list;
+  char *result, *t;
+  struct dstack save_dstack;
+  int last_exit_value;
+#if defined (PROMPT_STRING_DECODE)
+  int result_size, result_index;
+  int c, n, i;
+  char *temp, octal_string[4];
+  struct tm *tm;  
+  time_t the_time;
+  char timebuf[128];
+  char *timefmt;
+
+  result = (char *)xmalloc (result_size = PROMPT_GROWTH);
+  result[result_index = 0] = 0;
+  temp = (char *)NULL;
+
+  while (c = *string++)
+    {
+      if (posixly_correct && c == '!')
+       {
+         if (*string == '!')
+           {
+             temp = savestring ("!");
+             goto add_string;
+           }
+         else
+           {
+#if !defined (HISTORY)
+               temp = savestring ("1");
+#else /* HISTORY */
+               temp = itos (history_number ());
+#endif /* HISTORY */
+               string--;       /* add_string increments string again. */
+               goto add_string;
+           }
+       }
+      if (c == '\\')
+       {
+         c = *string;
+
+         switch (c)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+             strncpy (octal_string, string, 3);
+             octal_string[3] = '\0';
+
+             n = read_octal (octal_string);
+             temp = (char *)xmalloc (3);
+
+             if (n == CTLESC || n == CTLNUL)
+               {
+                 temp[0] = CTLESC;
+                 temp[1] = n;
+                 temp[2] = '\0';
+               }
+             else if (n == -1)
+               {
+                 temp[0] = '\\';
+                 temp[1] = '\0';
+               }
+             else
+               {
+                 temp[0] = n;
+                 temp[1] = '\0';
+               }
+
+             for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++)
+               string++;
+
+             c = 0;            /* tested at add_string: */
+             goto add_string;
+
+           case 'd':
+           case 't':
+           case 'T':
+           case '@':
+           case 'A':
+             /* Make the current time/date into a string. */
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+
+             if (c == 'd')
+               n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm);
+             else if (c == 't')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm);
+             else if (c == 'T')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm);
+             else if (c == '@')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm);
+             else if (c == 'A')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             temp = savestring (timebuf);
+             goto add_string;
+
+           case 'D':           /* strftime format */
+             if (string[1] != '{')             /* } */
+               goto not_escape;
+
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+             string += 2;                      /* skip { */
+             timefmt = xmalloc (strlen (string) + 3);
+             for (t = timefmt; *string && *string != '}'; )
+               *t++ = *string++;
+             *t = '\0';
+             c = *string;      /* tested at add_string */
+             if (timefmt[0] == '\0')
+               {
+                 timefmt[0] = '%';
+                 timefmt[1] = 'X';     /* locale-specific current time */
+                 timefmt[2] = '\0';
+               }
+             n = strftime (timebuf, sizeof (timebuf), timefmt, tm);
+             free (timefmt);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             if (promptvars || posixly_correct)
+               /* Make sure that expand_prompt_string is called with a
+                  second argument of Q_DOUBLE_QUOTES if we use this
+                  function here. */
+               temp = sh_backslash_quote_for_double_quotes (timebuf);
+             else
+               temp = savestring (timebuf);
+             goto add_string;
+             
+           case 'n':
+             temp = (char *)xmalloc (3);
+             temp[0] = no_line_editing ? '\n' : '\r';
+             temp[1] = no_line_editing ? '\0' : '\n';
+             temp[2] = '\0';
+             goto add_string;
+
+           case 's':
+             temp = base_pathname (shell_name);
+             temp = savestring (temp);
+             goto add_string;
+
+           case 'v':
+           case 'V':
+             temp = (char *)xmalloc (16);
+             if (c == 'v')
+               strcpy (temp, dist_version);
+             else
+               sprintf (temp, "%s.%d", dist_version, patch_level);
+             goto add_string;
+
+           case 'w':
+           case 'W':
+             {
+               /* Use the value of PWD because it is much more efficient. */
+               char t_string[PATH_MAX];
+               int tlen;
+
+               temp = get_string_value ("PWD");
+
+               if (temp == 0)
+                 {
+                   if (getcwd (t_string, sizeof(t_string)) == 0)
+                     {
+                       t_string[0] = '.';
+                       tlen = 1;
+                     }
+                   else
+                     tlen = strlen (t_string);
+                 }
+               else
+                 {
+                   tlen = sizeof (t_string) - 1;
+                   strncpy (t_string, temp, tlen);
+                 }
+               t_string[tlen] = '\0';
+
+#define ROOT_PATH(x)   ((x)[0] == '/' && (x)[1] == 0)
+#define DOUBLE_SLASH_ROOT(x)   ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0)
+               /* Abbreviate \W as ~ if $PWD == $HOME */
+               if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0))
+                 {
+                   if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0)
+                     {
+                       t = strrchr (t_string, '/');
+                       if (t)
+                         strcpy (t_string, t + 1);
+                     }
+                 }
+#undef ROOT_PATH
+#undef DOUBLE_SLASH_ROOT
+               else
+                 /* polite_directory_format is guaranteed to return a string
+                    no longer than PATH_MAX - 1 characters. */
+                 strcpy (t_string, polite_directory_format (t_string));
+
+               temp = trim_pathname (t_string, PATH_MAX - 1);
+               /* If we're going to be expanding the prompt string later,
+                  quote the directory name. */
+               if (promptvars || posixly_correct)
+                 /* Make sure that expand_prompt_string is called with a
+                    second argument of Q_DOUBLE_QUOTES if we use this
+                    function here. */
+                 temp = sh_backslash_quote_for_double_quotes (t_string);
+               else
+                 temp = savestring (t_string);
+
+               goto add_string;
+             }
+
+           case 'u':
+             if (current_user.user_name == 0)
+               get_current_user_info ();
+             temp = savestring (current_user.user_name);
+             goto add_string;
+
+           case 'h':
+           case 'H':
+             temp = savestring (current_host_name);
+             if (c == 'h' && (t = (char *)strchr (temp, '.')))
+               *t = '\0';
+             goto add_string;
+
+           case '#':
+             temp = itos (current_command_number);
+             goto add_string;
+
+           case '!':
+#if !defined (HISTORY)
+             temp = savestring ("1");
+#else /* HISTORY */
+             temp = itos (history_number ());
+#endif /* HISTORY */
+             goto add_string;
+
+           case '$':
+             t = temp = (char *)xmalloc (3);
+             if ((promptvars || posixly_correct) && (current_user.euid != 0))
+               *t++ = '\\';
+             *t++ = current_user.euid == 0 ? '#' : '$';
+             *t = '\0';
+             goto add_string;
+
+           case 'j':
+             temp = itos (count_all_jobs ());
+             goto add_string;
+
+           case 'l':
+#if defined (HAVE_TTYNAME)
+             temp = (char *)ttyname (fileno (stdin));
+             t = temp ? base_pathname (temp) : "tty";
+             temp = savestring (t);
+#else
+             temp = savestring ("tty");
+#endif /* !HAVE_TTYNAME */
+             goto add_string;
+
+#if defined (READLINE)
+           case '[':
+           case ']':
+             if (no_line_editing)
+               {
+                 string++;
+                 break;
+               }
+             temp = (char *)xmalloc (3);
+             n = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
+             i = 0;
+             if (n == CTLESC || n == CTLNUL)
+               temp[i++] = CTLESC;
+             temp[i++] = n;
+             temp[i] = '\0';
+             goto add_string;
+#endif /* READLINE */
+
+           case '\\':
+           case 'a':
+           case 'e':
+           case 'r':
+             temp = (char *)xmalloc (2);
+             if (c == 'a')
+               temp[0] = '\07';
+             else if (c == 'e')
+               temp[0] = '\033';
+             else if (c == 'r')
+               temp[0] = '\r';
+             else                      /* (c == '\\') */
+               temp[0] = c;
+             temp[1] = '\0';
+             goto add_string;
+
+           default:
+not_escape:
+             temp = (char *)xmalloc (3);
+             temp[0] = '\\';
+             temp[1] = c;
+             temp[2] = '\0';
+
+           add_string:
+             if (c)
+               string++;
+             result =
+               sub_append_string (temp, result, &result_index, &result_size);
+             temp = (char *)NULL; /* Freed in sub_append_string (). */
+             result[result_index] = '\0';
+             break;
+           }
+       }
+      else
+       {
+         RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH);
+         result[result_index++] = c;
+         result[result_index] = '\0';
+       }
+    }
+#else /* !PROMPT_STRING_DECODE */
+  result = savestring (string);
+#endif /* !PROMPT_STRING_DECODE */
+
+  /* Save the delimiter stack and point `dstack' to temp space so any
+     command substitutions in the prompt string won't result in screwing
+     up the parser's quoting state. */
+  save_dstack = dstack;
+  dstack = temp_dstack;
+  dstack.delimiter_depth = 0;
+
+  /* Perform variable and parameter expansion and command substitution on
+     the prompt string. */
+  if (promptvars || posixly_correct)
+    {
+      last_exit_value = last_command_exit_value;
+      list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0);
+      free (result);
+      result = string_list (list);
+      dispose_words (list);
+      last_command_exit_value = last_exit_value;
+    }
+  else
+    {
+      t = dequote_string (result);
+      free (result);
+      result = t;
+    }
+
+  dstack = save_dstack;
+
+  return (result);
+}
+
+/************************************************
+ *                                             *
+ *             ERROR HANDLING                  *
+ *                                             *
+ ************************************************/
+
+/* Report a syntax error, and restart the parser.  Call here for fatal
+   errors. */
+int
+yyerror (msg)
+     const char *msg;
+{
+  report_syntax_error ((char *)NULL);
+  reset_parser ();
+  return (0);
+}
+
+static char *
+error_token_from_token (tok)
+     int tok;
+{
+  char *t;
+
+  if (t = find_token_in_alist (tok, word_token_alist, 0))
+    return t;
+
+  if (t = find_token_in_alist (tok, other_token_alist, 0))
+    return t;
+
+  t = (char *)NULL;
+  /* This stuff is dicy and needs closer inspection */
+  switch (current_token)
+    {
+    case WORD:
+    case ASSIGNMENT_WORD:
+      if (yylval.word)
+       t = savestring (yylval.word->word);
+      break;
+    case NUMBER:
+      t = itos (yylval.number);
+      break;
+    case ARITH_CMD:
+      if (yylval.word_list)
+        t = string_list (yylval.word_list);
+      break;
+    case ARITH_FOR_EXPRS:
+      if (yylval.word_list)
+       t = string_list_internal (yylval.word_list, " ; ");
+      break;
+    case COND_CMD:
+      t = (char *)NULL;                /* punt */
+      break;
+    }
+
+  return t;
+}
+
+static char *
+error_token_from_text ()
+{
+  char *msg, *t;
+  int token_end, i;
+
+  t = shell_input_line;
+  i = shell_input_line_index;
+  token_end = 0;
+  msg = (char *)NULL;
+
+  if (i && t[i] == '\0')
+    i--;
+
+  while (i && (whitespace (t[i]) || t[i] == '\n'))
+    i--;
+
+  if (i)
+    token_end = i + 1;
+
+  while (i && (member (t[i], " \n\t;|&") == 0))
+    i--;
+
+  while (i != token_end && (whitespace (t[i]) || t[i] == '\n'))
+    i++;
+
+  /* Return our idea of the offending token. */
+  if (token_end || (i == 0 && token_end == 0))
+    {
+      if (token_end)
+       msg = substring (t, i, token_end);
+      else     /* one-character token */
+       {
+         msg = (char *)xmalloc (2);
+         msg[0] = t[i];
+         msg[1] = '\0';
+       }
+    }
+
+  return (msg);
+}
+
+static void
+print_offending_line ()
+{
+  char *msg;
+  int token_end;
+
+  msg = savestring (shell_input_line);
+  token_end = strlen (msg);
+  while (token_end && msg[token_end - 1] == '\n')
+    msg[--token_end] = '\0';
+
+  parser_error (line_number, "`%s'", msg);
+  free (msg);
+}
+
+/* Report a syntax error with line numbers, etc.
+   Call here for recoverable errors.  If you have a message to print,
+   then place it in MESSAGE, otherwise pass NULL and this will figure
+   out an appropriate message for you. */
+static void
+report_syntax_error (message)
+     char *message;
+{
+  char *msg;
+
+  if (message)
+    {
+      parser_error (line_number, "%s", message);
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+      last_command_exit_value = EX_USAGE;
+      return;
+    }
+
+  /* If the line of input we're reading is not null, try to find the
+     objectionable token.  First, try to figure out what token the
+     parser's complaining about by looking at current_token. */
+  if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token)))
+    {
+      parser_error (line_number, _("syntax error near unexpected token `%s'"), msg);
+      free (msg);
+
+      if (interactive == 0)
+       print_offending_line ();
+
+      last_command_exit_value = EX_USAGE;
+      return;
+    }
+
+  /* If looking at the current token doesn't prove fruitful, try to find the
+     offending token by analyzing the text of the input line near the current
+     input line index and report what we find. */
+  if (shell_input_line && *shell_input_line)
+    {
+      msg = error_token_from_text ();
+      if (msg)
+       {
+         parser_error (line_number, _("syntax error near `%s'"), msg);
+         free (msg);
+       }
+
+      /* If not interactive, print the line containing the error. */
+      if (interactive == 0)
+        print_offending_line ();
+    }
+  else
+    {
+      msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
+      parser_error (line_number, "%s", msg);
+      /* When the shell is interactive, this file uses EOF_Reached
+        only for error reporting.  Other mechanisms are used to
+        decide whether or not to exit. */
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+    }
+
+  last_command_exit_value = EX_USAGE;
+}
+
+/* ??? Needed function. ??? We have to be able to discard the constructs
+   created during parsing.  In the case of error, we want to return
+   allocated objects to the memory pool.  In the case of no error, we want
+   to throw away the information about where the allocated objects live.
+   (dispose_command () will actually free the command.) */
+static void
+discard_parser_constructs (error_p)
+     int error_p;
+{
+}
+
+/************************************************
+ *                                             *
+ *             EOF HANDLING                    *
+ *                                             *
+ ************************************************/
+
+/* Do that silly `type "bye" to exit' stuff.  You know, "ignoreeof". */
+
+/* A flag denoting whether or not ignoreeof is set. */
+int ignoreeof = 0;
+
+/* The number of times that we have encountered an EOF character without
+   another character intervening.  When this gets above the limit, the
+   shell terminates. */
+int eof_encountered = 0;
+
+/* The limit for eof_encountered. */
+int eof_encountered_limit = 10;
+
+/* If we have EOF as the only input unit, this user wants to leave
+   the shell.  If the shell is not interactive, then just leave.
+   Otherwise, if ignoreeof is set, and we haven't done this the
+   required number of times in a row, print a message. */
+static void
+handle_eof_input_unit ()
+{
+  if (interactive)
+    {
+      /* shell.c may use this to decide whether or not to write out the
+        history, among other things.  We use it only for error reporting
+        in this file. */
+      if (EOF_Reached)
+       EOF_Reached = 0;
+
+      /* If the user wants to "ignore" eof, then let her do so, kind of. */
+      if (ignoreeof)
+       {
+         if (eof_encountered < eof_encountered_limit)
+           {
+             fprintf (stderr, _("Use \"%s\" to leave the shell.\n"),
+                      login_shell ? "logout" : "exit");
+             eof_encountered++;
+             /* Reset the parsing state. */
+             last_read_token = current_token = '\n';
+             /* Reset the prompt string to be $PS1. */
+             prompt_string_pointer = (char **)NULL;
+             prompt_again ();
+             return;
+           }
+       }
+
+      /* In this case EOF should exit the shell.  Do it now. */
+      reset_parser ();
+      exit_builtin ((WORD_LIST *)NULL);
+    }
+  else
+    {
+      /* We don't write history files, etc., for non-interactive shells. */
+      EOF_Reached = 1;
+    }
+}
+
+/************************************************
+ *                                             *
+ *     STRING PARSING FUNCTIONS                *
+ *                                             *
+ ************************************************/
+
+/* It's very important that these two functions treat the characters
+   between ( and ) identically. */
+
+static WORD_LIST parse_string_error;
+
+/* Take a string and run it through the shell parser, returning the
+   resultant word list.  Used by compound array assignment. */
+WORD_LIST *
+parse_string_to_word_list (s, flags, whom)
+     char *s;
+     int flags;
+     const char *whom;
+{
+  WORD_LIST *wl;
+  int tok, orig_current_token, orig_line_number, orig_input_terminator;
+  int orig_line_count;
+  int old_echo_input, old_expand_aliases;
+#if defined (HISTORY)
+  int old_remember_on_history, old_history_expansion_inhibited;
+#endif
+
+#if defined (HISTORY)
+  old_remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  old_history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+  bash_history_disable ();
+#endif
+
+  orig_line_number = line_number;
+  orig_line_count = current_command_line_count;
+  orig_input_terminator = shell_input_line_terminator;
+  old_echo_input = echo_input_at_read;
+  old_expand_aliases = expand_aliases;
+
+  push_stream (1);
+  last_read_token = WORD;              /* WORD to allow reserved words here */
+  current_command_line_count = 0;
+  echo_input_at_read = expand_aliases = 0;
+
+  with_input_from_string (s, whom);
+  wl = (WORD_LIST *)NULL;
+
+  if (flags & 1)
+    parser_state |= PST_COMPASSIGN;
+
+  while ((tok = read_token (READ)) != yacc_EOF)
+    {
+      if (tok == '\n' && *bash_input.location.string == '\0')
+       break;
+      if (tok == '\n')         /* Allow newlines in compound assignments */
+       continue;
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         line_number = orig_line_number + line_number - 1;
+         orig_current_token = current_token;
+         current_token = tok;
+         yyerror (NULL);       /* does the right thing */
+         current_token = orig_current_token;
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+  
+  last_read_token = '\n';
+  pop_stream ();
+
+#if defined (HISTORY)
+  remember_on_history = old_remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = old_history_expansion_inhibited;
+#  endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+  echo_input_at_read = old_echo_input;
+  expand_aliases = old_expand_aliases;
+
+  current_command_line_count = orig_line_count;
+  shell_input_line_terminator = orig_input_terminator;
+
+  if (flags & 1)
+    parser_state &= ~PST_COMPASSIGN;
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  return (REVERSE_LIST (wl, WORD_LIST *));
+}
+
+static char *
+parse_compound_assignment (retlenp)
+     int *retlenp;
+{
+  WORD_LIST *wl, *rl;
+  int tok, orig_line_number, orig_token_size, orig_last_token, assignok;
+  char *saved_token, *ret;
+
+  saved_token = token;
+  orig_token_size = token_buffer_size;
+  orig_line_number = line_number;
+  orig_last_token = last_read_token;
+
+  last_read_token = WORD;      /* WORD to allow reserved words here */
+
+  token = (char *)NULL;
+  token_buffer_size = 0;
+
+  assignok = parser_state&PST_ASSIGNOK;                /* XXX */
+
+  wl = (WORD_LIST *)NULL;      /* ( */
+  parser_state |= PST_COMPASSIGN;
+
+  while ((tok = read_token (READ)) != ')')
+    {
+      if (tok == '\n')                 /* Allow newlines in compound assignments */
+       {
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         continue;
+       }
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         current_token = tok;  /* for error reporting */
+         if (tok == yacc_EOF)  /* ( */
+           parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'"));
+         else
+           yyerror(NULL);      /* does the right thing */
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+
+  FREE (token);
+  token = saved_token;
+  token_buffer_size = orig_token_size;
+
+  parser_state &= ~PST_COMPASSIGN;
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      last_read_token = '\n';  /* XXX */
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  last_read_token = orig_last_token;           /* XXX - was WORD? */
+
+  if (wl)
+    {
+      rl = REVERSE_LIST (wl, WORD_LIST *);
+      ret = string_list (rl);
+      dispose_words (rl);
+    }
+  else
+    ret = (char *)NULL;
+
+  if (retlenp)
+    *retlenp = (ret && *ret) ? strlen (ret) : 0;
+
+  if (assignok)
+    parser_state |= PST_ASSIGNOK;
+
+  return ret;
+}
+
+/************************************************
+ *                                             *
+ *   SAVING AND RESTORING PARTIAL PARSE STATE   *
+ *                                             *
+ ************************************************/
+
+sh_parser_state_t *
+save_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+
+  if (ps == 0)
+    ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
+  if (ps == 0)
+    return ((sh_parser_state_t *)NULL);
+
+  ps->parser_state = parser_state;
+  ps->token_state = save_token_state ();
+
+  ps->input_line_terminator = shell_input_line_terminator;
+  ps->eof_encountered = eof_encountered;
+
+  ps->current_command_line_count = current_command_line_count;
+
+#if defined (HISTORY)
+  ps->remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  ps->history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+#endif
+
+  ps->last_command_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  v = find_variable ("PIPESTATUS");
+  if (v && array_p (v) && array_cell (v))
+    ps->pipestatus = array_copy (array_cell (v));
+  else
+    ps->pipestatus = (ARRAY *)NULL;
+#endif
+    
+  ps->last_shell_builtin = last_shell_builtin;
+  ps->this_shell_builtin = this_shell_builtin;
+
+  ps->expand_aliases = expand_aliases;
+  ps->echo_input_at_read = echo_input_at_read;
+
+  return (ps);
+}
+
+void
+restore_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+
+  if (ps == 0)
+    return;
+
+  parser_state = ps->parser_state;
+  if (ps->token_state)
+    {
+      restore_token_state (ps->token_state);
+      free (ps->token_state);
+    }
+
+  shell_input_line_terminator = ps->input_line_terminator;
+  eof_encountered = ps->eof_encountered;
+
+  current_command_line_count = ps->current_command_line_count;
+
+#if defined (HISTORY)
+  remember_on_history = ps->remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = ps->history_expansion_inhibited;
+#  endif
+#endif
+
+  last_command_exit_value = ps->last_command_exit_value;
+#if defined (ARRAY_VARS)
+  v = find_variable ("PIPESTATUS");
+  if (v && array_p (v) && array_cell (v))
+    {
+      array_dispose (array_cell (v));
+      var_setarray (v, ps->pipestatus);
+    }
+#endif
+
+  last_shell_builtin = ps->last_shell_builtin;
+  this_shell_builtin = ps->this_shell_builtin;
+
+  expand_aliases = ps->expand_aliases;
+  echo_input_at_read = ps->echo_input_at_read;
+}
+
+/************************************************
+ *                                             *
+ *     MULTIBYTE CHARACTER HANDLING            *
+ *                                             *
+ ************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+static void
+set_line_mbstate ()
+{
+  int i, previ, len, c;
+  mbstate_t mbs, prevs;
+  size_t mbclen;
+
+  if (shell_input_line == NULL)
+    return;
+  len = strlen (shell_input_line);     /* XXX - shell_input_line_len ? */
+  FREE (shell_input_line_property);
+  shell_input_line_property = (char *)xmalloc (len + 1);
+
+  memset (&prevs, '\0', sizeof (mbstate_t));
+  for (i = previ = 0; i < len; i++)
+    {
+      mbs = prevs;
+
+      c = shell_input_line[i];
+      if (c == EOF)
+       {
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+      if (mbclen == 1 || mbclen == (size_t)-1)
+       {
+         mbclen = 1;
+         previ = i + 1;
+       }
+      else if (mbclen == (size_t)-2)
+        mbclen = 0;
+      else if (mbclen > 1)
+       {
+         mbclen = 0;
+         previ = i + 1;
+         prevs = mbs;
+       }
+      else
+       {
+         /* XXX - what to do if mbrlen returns 0? (null wide character) */
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      shell_input_line_property[i] = mbclen;
+    }
+}
+#endif /* HANDLE_MULTIBYTE */
index 0d33de538a0196d83c4c713ab1a30d2b0cfac28c..a7279f6140ac1ea603d24e28ed328ddeddb1180c 100644 (file)
--- a/parse.y~
+++ b/parse.y~
@@ -372,6 +372,8 @@ inputunit:  simple_list simple_list_terminator
                          global_command = $1;
                          eof_encountered = 0;
                          /* discard_parser_constructs (0); */
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       '\n'
@@ -379,6 +381,8 @@ inputunit:  simple_list simple_list_terminator
                          /* Case of regular command, but not a very
                             interesting one.  Return a NULL command. */
                          global_command = (COMMAND *)NULL;
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       error '\n'
@@ -762,12 +766,10 @@ case_command:     CASE WORD newline_list IN newline_list ESAC
 
 function_def:  WORD '(' ')' newline_list function_body
                        { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
-
-       |       FUNCTION WORD '(' ')' newline_list function_body
-                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
-
        |       FUNCTION WORD newline_list function_body
                        { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+       |       FUNCTION WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
        ;
 
 
@@ -953,6 +955,13 @@ simple_list:       simple_list1
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 '&'
                        {
@@ -962,12 +971,26 @@ simple_list:      simple_list1
                            $$ = command_connect ($1, (COMMAND *)NULL, '&');
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 ';'
                        {
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        ;
 
@@ -2258,6 +2281,15 @@ yylex ()
   token_before_that = last_read_token;
   last_read_token = current_token;
   current_token = read_token (READ);
+
+  if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+    {
+      current_token = yacc_EOF;
+      if (bash_input.type == st_string)
+       rewind_input_string ();
+    }
+  parser_state &= ~PST_EOFTOKEN;
+
   return (current_token);
 }
 
@@ -2686,6 +2718,7 @@ read_token (command)
 #if defined (ALIAS)
              parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
+
              peek_char = shell_getc (1);
              if MBTEST(peek_char == '&')
                return (SEMI_SEMI_AND);
@@ -2895,7 +2928,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
       /* Not exactly right yet, should handle shell metacharacters, too.  If
         any changes are made to this test, make analogous changes to subst.c:
         extract_delimited_string(). */
-      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || whitespace (ret[retind - 1])))
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
        tflags |= LEX_INCOMMENT;
 
       if (tflags & LEX_PASSNEXT)               /* last char was backslash */
@@ -3015,7 +3048,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
          if (open == ch)       /* undo previous increment */
            count--;
          if (ch == '(')                /* ) */
-           nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
          else if (ch == '{')           /* } */
            nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
          else if (ch == '[')           /* ] */
@@ -3414,7 +3447,7 @@ xparse_dolparen (base, string, indp, flags)
   ostring = string;
 
   sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
-  if (flags & 0x010)           /* EX_NOLONGJMP */
+  if (flags & SX_NOLONGJMP)
     sflags |= SEVAL_NOLONGJMP;
   save_parser_state (&ps);
 
@@ -3436,7 +3469,8 @@ xparse_dolparen (base, string, indp, flags)
   if (ep[-1] != ')')
     {
 #if DEBUG
-      itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+      if (ep[-1] != '\n')
+       itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
 #endif
       while (ep > ostring && ep[-1] == '\n') ep--;
     }
@@ -3450,7 +3484,7 @@ xparse_dolparen (base, string, indp, flags)
     itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
 #endif
 
-  if (flags & 1
+  if (flags & SX_NOALLOC
     return (char *)NULL;
 
   if (nc == 0)
@@ -4017,7 +4051,7 @@ read_token_word (character)
                     history literally rather than causing a possibly-
                     incorrect `;' to be added. ) */
                  push_delimiter (dstack, peek_char);
-                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+                 ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
                  pop_delimiter (dstack);
                }
              else
@@ -4424,7 +4458,7 @@ history_delimiting_chars ()
     {
       /* Tricky.  `for i\nin ...' should not have a semicolon, but
         `for i\ndo ...' should.  We do what we can. */
-      for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+      for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
        ;
       if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
        return " ";
@@ -4752,6 +4786,7 @@ decode_prompt_string (string)
                     no longer than PATH_MAX - 1 characters. */
                  strcpy (t_string, polite_directory_format (t_string));
 
+               temp = trim_pathname (t_string, PATH_MAX - 1);
                /* If we're going to be expanding the prompt string later,
                   quote the directory name. */
                if (promptvars || posixly_correct)
diff --git a/subst.c b/subst.c
index 6cc70a51e4c104928c8978de6debad1367211cbd..e5cc76ccbcb6201622cdb8a76b40408cc3ab15cb 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -69,6 +69,7 @@ extern int errno;
 #define VT_POSPARMS    1
 #define VT_ARRAYVAR    2
 #define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR    4
 
 #define VT_STARSUB     128     /* $* or ${array[*]} -- used to split */
 
@@ -78,14 +79,6 @@ extern int errno;
 #define ST_SQUOTE      0x04    /* unused yet */
 #define ST_DQUOTE      0x08    /* unused yet */
 
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC     0x01    /* just skip; don't return substring */
-#define SX_VARNAME     0x02    /* variable name; for string_extract () */
-#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
-#define SX_COMMAND     0x08    /* extracting a shell script/command */
-#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
-#define SX_NOESCCTLNUL 0x20    /* don't let CTLESC quote CTLNUL */
-
 /* Flags for the `pflags' argument to param_expand() */
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 
@@ -715,7 +708,7 @@ add_one_character:
 
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+           ret = extract_command_subst (string, &si, 0);
          else
            ret = extract_dollar_brace_string (string, &si, 1, 0);
 
@@ -817,7 +810,7 @@ skip_double_quoted (string, slen, sind)
        {
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
            ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
 
@@ -940,6 +933,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
          i += 2;
          continue;
        }
+
 #if defined (HANDLE_MULTIBYTE)
       mblength = MBLEN (string + i, slen - i);
       if (mblength > 1)
@@ -987,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position of the matching ")". ) */
+   Make (SINDEX) get the position of the matching ")". )
+   XFLAGS is additional flags to pass to other extraction functions, */
 char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
      char *string;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+  if (string[*sindex] == '(')  /*)*/
+    return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+    }
 }
 
 /* Extract the $[ construct in STRING, and return a new string. (])
@@ -1100,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
 
       /* Not exactly right yet; should handle shell metacharacters and
         multibyte characters, too. */
-      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
        {
           in_comment = 1;
           ADVANCE_CHAR (string, slen, i);
@@ -1265,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       if (string[i] == '$' && string[i+1] == LPAREN)
        {
          si = i + 2;
-         t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
          i = si + 1;
          continue;
        }
@@ -1953,6 +1955,55 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+     int pchar;
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ret;
+  WORD_LIST *tlist;
+
+  if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list_dollar_star (tlist);
+    }
+  else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list (tlist);
+    }
+  else if (pchar == '*')
+    {
+      /* Even when unquoted, string_list_dollar_star does the right thing
+        making sure that the first character of $IFS is used as the
+        separator. */
+      ret = string_list_dollar_star (list);
+    }
+  else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+    /* We use string_list_dollar_at, but only if the string is quoted, since
+       that quotes the escapes if it's not, which we don't want.  We could
+       use string_list (the old code did), but that doesn't do the right
+       thing if the first character of $IFS is not a space.  We use
+       string_list_dollar_star if the string is unquoted so we make sure that
+       the elements of $@ are separated by the first character of $IFS for
+       later splitting. */
+    ret = string_list_dollar_at (list, quoted);
+  else if (pchar == '@')
+    ret = string_list_dollar_star (list);
+  else
+    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+  return ret;
+}
+
 /* Return the list of words present in STRING.  Separate the string into
    words at any of the characters found in SEPARATORS.  If QUOTED is
    non-zero then word in the list will have its quoted flag set, otherwise
@@ -2578,17 +2629,9 @@ pos_params (string, start, end, quoted)
     }
 
   t->next = (WORD_LIST *)NULL;
-  if (string[0] == '*')
-    {
-      if (quoted & Q_DOUBLE_QUOTES)
-       ret = string_list_dollar_star (quote_list (h));
-      else if (quoted & Q_HERE_DOCUMENT)
-       ret = string_list (quote_list (h));
-      else
-       ret = string_list (h);
-    }
-  else
-    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
   if (t != params)
     t->next = params;
 
@@ -3226,9 +3269,10 @@ quote_list (list)
     {
       t = w->word->word;
       w->word->word = quote_string (t);
-      free (t);
+      if (*t == 0)
+       w->word->flags |= W_HASQUOTEDNULL;      /* XXX - turn on W_HASQUOTEDNULL here? */
       w->word->flags |= W_QUOTED;
-      /* XXX - turn on W_HAVEQUOTEDNULL here? */
+      free (t);
     }
   return list;
 }
@@ -3244,9 +3288,10 @@ dequote_list (list)
   for (tlist = list; tlist; tlist = tlist->next)
     {
       s = dequote_string (tlist->word->word);
+      if (QUOTED_NULL (tlist->word->word))
+       tlist->word->flags &= ~W_HASQUOTEDNULL;
       free (tlist->word->word);
       tlist->word->word = s;
-      /* XXX - turn off W_HAVEQUOTEDNULL here? */
     }
   return list;
 }
@@ -3983,12 +4028,9 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
     }
 
   l = REVERSE_LIST (new, WORD_LIST *);
-  if (itype == '*')
-    tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
-  else
-    tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+  tword = string_list_pos_params (itype, l, quoted);
   dispose_words (l);
+
   return (tword);
 }
 
@@ -5813,6 +5855,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   WORD_LIST *save, *params;
   WORD_DESC *w;
   char *ret;
+  int pchar, qflags;
 
   save = params = list_rest_of_args ();
   if (save == 0)
@@ -5827,10 +5870,22 @@ pos_params_pat_subst (string, pat, rep, mflags)
       params->word = w;
     }
 
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
   if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
     ret = string_list_dollar_star (quote_list (save));
+  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+    ret = string_list_dollar_star (save);
+  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+    ret = string_list_dollar_at (save, qflags);
   else
-    ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+    ret = string_list_dollar_star (save);
+#else
+  ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
   dispose_words (save);
 
   return (ret);
@@ -6137,6 +6192,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       temp = (char *)NULL;
       goto bad_substitution;
     }
+
   /* Indirect expansion begins with a `!'.  A valid indirect expansion is
      either a variable name, one of the positional parameters or a special
      variable that expands to one of the positional parameters. */
@@ -6676,7 +6732,7 @@ param_expand (string, sindex, quoted, expanded_something,
     case LPAREN:
       /* We have to extract the contents of this paren substitution. */
       t_index = zindex + 1;
-      temp = extract_command_subst (string, &t_index);
+      temp = extract_command_subst (string, &t_index, 0);
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6735,7 +6791,8 @@ comsub:
        {
          tdesc = command_substitute (temp, quoted);
          temp1 = tdesc ? tdesc->word : (char *)NULL;
-         dispose_word_desc (tdesc);
+         if (tdesc)
+           dispose_word_desc (tdesc);
        }
       FREE (temp);
       temp = temp1;
@@ -7153,7 +7210,8 @@ add_string:
                de_backslash (temp);
                tword = command_substitute (temp, quoted);
                temp1 = tword ? tword->word : (char *)NULL;
-               dispose_word_desc (tword);
+               if (tword)
+                 dispose_word_desc (tword);
              }
            FREE (temp);
            temp = temp1;
index 4fcbc60ade50fa77b169bb82a569305756292d5d..75734570e99a576e1219612e46ff38b159f16f38 100644 (file)
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -69,6 +69,7 @@ extern int errno;
 #define VT_POSPARMS    1
 #define VT_ARRAYVAR    2
 #define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR    4
 
 #define VT_STARSUB     128     /* $* or ${array[*]} -- used to split */
 
@@ -78,13 +79,6 @@ extern int errno;
 #define ST_SQUOTE      0x04    /* unused yet */
 #define ST_DQUOTE      0x08    /* unused yet */
 
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC     0x01    /* just skip; don't return substring */
-#define SX_VARNAME     0x02    /* variable name; for string_extract () */
-#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
-#define SX_COMMAND     0x08    /* extracting a shell script/command */
-#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
-
 /* Flags for the `pflags' argument to param_expand() */
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 
@@ -135,6 +129,8 @@ size_t ifs_firstc_len;
 unsigned char ifs_firstc;
 #endif
 
+int assigning_in_environment;
+
 /* Extern functions and variables from different files. */
 extern int last_command_exit_value, last_command_exit_signal;
 extern int subshell_environment;
@@ -206,7 +202,6 @@ static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
 static char *dequote_escapes __P((char *));
 static char *make_quoted_char __P((int));
 static WORD_LIST *quote_list __P((WORD_LIST *));
-static char *remove_quoted_escapes __P((char *));
 static char *remove_quoted_nulls __P((char *));
 
 static int unquoted_substring __P((char *, char *));
@@ -713,7 +708,7 @@ add_one_character:
 
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+           ret = extract_command_subst (string, &si, 0);
          else
            ret = extract_dollar_brace_string (string, &si, 1, 0);
 
@@ -815,7 +810,7 @@ skip_double_quoted (string, slen, sind)
        {
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
            ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
 
@@ -930,6 +925,14 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
          i += 2;
          continue;
        }
+      /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
+        through, to protect the CTLNULs from later calls to
+        remove_quoted_nulls. */
+      else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
+       {
+         i += 2;
+         continue;
+       }
 
 #if defined (HANDLE_MULTIBYTE)
       mblength = MBLEN (string + i, slen - i);
@@ -978,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position of the matching ")". ) */
+   Make (SINDEX) get the position of the matching ")". )
+   XFLAGS is additional flags to pass to other extraction functions, */
 char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
      char *string;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+  if (string[*sindex] == '(')  /*)*/
+    return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+    }
 }
 
 /* Extract the $[ construct in STRING, and return a new string. (])
@@ -1091,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
 
       /* Not exactly right yet; should handle shell metacharacters and
         multibyte characters, too. */
-      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
        {
           in_comment = 1;
           ADVANCE_CHAR (string, slen, i);
@@ -1256,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       if (string[i] == '$' && string[i+1] == LPAREN)
        {
          si = i + 2;
-         t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
          i = si + 1;
          continue;
        }
@@ -1353,6 +1364,91 @@ unquote_bang (string)
 }
 #endif
 
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+   the index of that character.  START is the index into string at which we
+   begin.  This is similar in spirit to strpbrk, but it returns an index into
+   STRING and takes a starting index.  This little piece of code knows quite
+   a lot of shell syntax.  It's very similar to skip_double_quoted and other
+   functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+     char *string;
+     int start;
+     char *delims;
+     int flags;
+{
+  int i, pass_next, backq, si, c;
+  size_t slen;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  if (flags & SD_NOJMP)
+    no_longjmp_on_fatal_error = 1;
+  i = start;
+  pass_next = backq = 0;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == 0)
+           CQ_RETURN(i);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (backq)
+       {
+         if (c == '`')
+           backq = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '`')
+       {
+         backq = 1;
+         i++;
+         continue;
+       }
+      else if (c == '\'' || c == '"')
+       {
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+                         : skip_double_quoted (string, slen, ++i);
+         /* no increment, the skip functions increment past the closing quote. */
+       }
+      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         if (string[i+1] == LPAREN)
+           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+         else
+           temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+      else if (member (c, delims))
+       break;
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+
 #if defined (READLINE)
 /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
    an unclosed quoted string), or if the character at EINDEX is quoted
@@ -1362,8 +1458,6 @@ unquote_bang (string)
    recognizes need to be the same as the contents of
    rl_completer_quote_characters. */
 
-#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
-
 int
 char_is_quoted (string, eindex)
      char *string;
@@ -1456,87 +1550,6 @@ unclosed_pair (string, eindex, openstr)
   return (openc);
 }
 
-/* Skip characters in STRING until we find a character in DELIMS, and return
-   the index of that character.  START is the index into string at which we
-   begin.  This is similar in spirit to strpbrk, but it returns an index into
-   STRING and takes a starting index.  This little piece of code knows quite
-   a lot of shell syntax.  It's very similar to skip_double_quoted and other
-   functions of that ilk. */
-int
-skip_to_delim (string, start, delims)
-     char *string;
-     int start;
-     char *delims;
-{
-  int i, pass_next, backq, si, c;
-  size_t slen;
-  char *temp;
-  DECLARE_MBSTATE;
-
-  slen = strlen (string + start) + start;
-  no_longjmp_on_fatal_error = 1;
-  i = start;
-  pass_next = backq = 0;
-  while (c = string[i])
-    {
-      if (pass_next)
-       {
-         pass_next = 0;
-         if (c == 0)
-           CQ_RETURN(i);
-         ADVANCE_CHAR (string, slen, i);
-         continue;
-       }
-      else if (c == '\\')
-       {
-         pass_next = 1;
-         i++;
-         continue;
-       }
-      else if (backq)
-       {
-         if (c == '`')
-           backq = 0;
-         ADVANCE_CHAR (string, slen, i);
-         continue;
-       }
-      else if (c == '`')
-       {
-         backq = 1;
-         i++;
-         continue;
-       }
-      else if (c == '\'' || c == '"')
-       {
-         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
-                         : skip_double_quoted (string, slen, ++i);
-         /* no increment, the skip functions increment past the closing quote. */
-       }
-      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
-       {
-         si = i + 2;
-         if (string[si] == '\0')
-           CQ_RETURN(si);
-
-         if (string[i+1] == LPAREN)
-           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
-         else
-           temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
-         i = si;
-         if (string[i] == '\0')        /* don't increment i past EOS in loop */
-           break;
-         i++;
-         continue;
-       }
-      else if (member (c, delims))
-       break;
-      else
-       ADVANCE_CHAR (string, slen, i);
-    }
-
-  CQ_RETURN(i);
-}
-
 /* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
    individual words.  If DELIMS is NULL, the current value of $IFS is used
    to split the string, and the function follows the shell field splitting
@@ -1621,7 +1634,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
   cw = -1;
   while (1)
     {
-      te = skip_to_delim (string, ts, d);
+      te = skip_to_delim (string, ts, d, SD_NOJMP);
 
       /* If we have a non-whitespace delimiter character, use it to make a
         separate field.  This is just about what $IFS splitting does and
@@ -1787,6 +1800,42 @@ string_list (list)
   return (string_list_internal (list, " "));
 }
 
+/* An external interface that can be used by the rest of the shell to
+   obtain a string containing the first character in $IFS.  Handles all
+   the multibyte complications.  If LENP is non-null, it is set to the
+   length of the returned string. */
+char *
+ifs_firstchar (lenp)
+     int *lenp;
+{
+  char *ret;
+  int len;
+
+  ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc_len == 1)
+    {
+      ret[0] = ifs_firstc[0];
+      ret[1] = '\0';
+      len = ret[0] ? 1 : 0;
+    }
+  else
+    {
+      memcpy (ret, ifs_firstc, ifs_firstc_len);
+      ret[len = ifs_firstc_len] = '\0';
+    }
+#else
+  ret[0] = ifs_firstc;
+  ret[1] = '\0';
+  len = ret[0] ? 0 : 1;
+#endif
+
+  if (lenp)
+    *lenp = len;
+
+  return ret;
+}
+
 /* Return a single string of all the words present in LIST, obeying the
    quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the
    expansion [of $*] appears within a double quoted string, it expands
@@ -1906,6 +1955,45 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+     int pchar;
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ret;
+
+  if (pchar == '*')
+    {
+      if (quoted & Q_DOUBLE_QUOTES)
+       ret = string_list_dollar_star (quote_list (list));
+      else if (quoted & Q_HERE_DOCUMENT)
+       ret = string_list (quote_list (list));
+      else
+       /* Even when unquoted, string_list_dollar_star does the right thing
+          making sure that the first character of $IFS is used as the
+          separator. */
+       ret = string_list_dollar_star (list);
+    }
+  else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    /* We use string_list_dollar_at, but only if the string is quoted, since
+       that quotes the escapes if it's not, which we don't want.  We could
+       use string_list (the old code did), but that doesn't do the right
+       thing if the first character of $IFS is not a space.  We use
+       string_list_dollar_star if the string is unquoted so we make sure that
+       the elements of $@ are separated by the first character of $IFS for
+       later splitting. */
+    ret = string_list_dollar_at (list, quoted);
+  else
+    ret = string_list_dollar_star (list);
+
+  return ret;
+}
+
 /* Return the list of words present in STRING.  Separate the string into
    words at any of the characters found in SEPARATORS.  If QUOTED is
    non-zero then word in the list will have its quoted flag set, otherwise
@@ -1957,7 +2045,10 @@ list_string (string, separators, quoted)
                                 separators[2] == '\n' &&
                                 separators[3] == '\0';
   for (xflags = 0, s = ifs_value; s && *s; s++)
-    if (*s == CTLESC) xflags |= SX_NOCTLESC;  
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
 
   slen = 0;
   /* Remove sequences of whitespace at the beginning of STRING, as
@@ -2076,7 +2167,10 @@ get_word_from_string (stringp, separators, endptr)
                                 separators[2] == '\n' &&
                                 separators[3] == '\0';
   for (xflags = 0, s = ifs_value; s && *s; s++)
-    if (*s == CTLESC) xflags |= SX_NOCTLESC;  
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
 
   s = *stringp;
   slen = 0;
@@ -2264,7 +2358,7 @@ do_assignment_internal (word, expand)
      const WORD_DESC *word;
      int expand;
 {
-  int offset, tlen, appendop, assign_list, aflags;
+  int offset, tlen, appendop, assign_list, aflags, retval;
   char *name, *value;
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
@@ -2355,11 +2449,27 @@ do_assignment_internal (word, expand)
 
   stupidly_hack_special_variables (name);
 
+#if 1
+  /* Return 1 if the assignment seems to have been performed correctly. */
+  if (entry == 0 || readonly_p (entry))
+    retval = 0;                /* assignment failure */
+  else if (noassign_p (entry))
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      retval = 1;      /* error status, but not assignment failure */
+    }
+  else
+    retval = 1;
+  ASSIGN_RETURN (retval);
+
+  if (entry && retval != 0)
+    VUNSETATTR (entry, att_invisible);
+#else
   if (entry)
     VUNSETATTR (entry, att_invisible);
 
-  /* Return 1 if the assignment seems to have been performed correctly. */
   ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -2509,17 +2619,9 @@ pos_params (string, start, end, quoted)
     }
 
   t->next = (WORD_LIST *)NULL;
-  if (string[0] == '*')
-    {
-      if (quoted & Q_DOUBLE_QUOTES)
-       ret = string_list_dollar_star (quote_list (h));
-      else if (quoted & Q_HERE_DOCUMENT)
-       ret = string_list (quote_list (h));
-      else
-       ret = string_list (h);
-    }
-  else
-    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
   if (t != params)
     t->next = params;
 
@@ -2825,9 +2927,10 @@ expand_string_assignment (string, quoted)
    passed string when an error occurs.  Might want to trap other calls
    to jump_to_top_level here so we don't endlessly loop. */
 WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
      char *string;
      int quoted;
+     int wflags;
 {
   WORD_LIST *value;
   WORD_DESC td;
@@ -2835,7 +2938,7 @@ expand_prompt_string (string, quoted)
   if (string == 0 || *string == 0)
     return ((WORD_LIST *)NULL);
 
-  td.flags = 0;
+  td.flags = wflags;
   td.word = savestring (string);
 
   no_longjmp_on_fatal_error = 1;
@@ -2953,7 +3056,7 @@ quote_escapes (string)
   register char *s, *t;
   size_t slen;
   char *result, *send;
-  int quote_spaces, skip_ctlesc;
+  int quote_spaces, skip_ctlesc, skip_ctlnul;
   DECLARE_MBSTATE; 
 
   slen = strlen (string);
@@ -2961,15 +3064,15 @@ quote_escapes (string)
 
   quote_spaces = (ifs_value && *ifs_value == 0);
 
-  for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
-    skip_ctlesc |= *s == CTLESC;
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
   t = result = (char *)xmalloc ((slen * 2) + 1);
   s = string;
 
   while (*s)
     {
-      if ((skip_ctlesc == 0 && *s == CTLESC) || *s == CTLNUL || (quote_spaces && *s == ' '))
+      if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
        *t++ = CTLESC;
       COPY_CHAR_P (t, s, send);
     }
@@ -3183,7 +3286,7 @@ dequote_list (list)
 
 /* Remove CTLESC protecting a CTLESC or CTLNUL in place.  Return the passed
    string. */
-static char *
+char *
 remove_quoted_escapes (string)
      char *string;
 {
@@ -3992,7 +4095,11 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
   if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
     patstr++;
 
-  pattern = getpattern (patstr, quoted, 1);
+  /* Need to pass getpattern newly-allocated memory in case of expansion --
+     the expansion code will free the passed string on an error. */
+  temp1 = savestring (patstr);
+  pattern = getpattern (temp1, quoted, 1);
+  free (temp1);
 
   temp1 = (char *)NULL;                /* shut up gcc */
   switch (vtype)
@@ -4356,6 +4463,7 @@ process_substitute (string, open_for_read_in_child)
 #if defined (JOB_CONTROL)
   set_sigchld_handler ();
   stop_making_children ();
+  /* XXX - should we only do this in the parent? (as in command subst) */
   pipeline_pgrp = old_pipeline_pgrp;
 #endif /* JOB_CONTROL */
 
@@ -4474,14 +4582,14 @@ read_comsub (fd, quoted, rflag)
      int *rflag;
 {
   char *istring, buf[128], *bufp, *s;
-  int istring_index, istring_size, c, tflag, skip_ctlesc;
+  int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
   ssize_t bufn;
 
   istring = (char *)NULL;
   istring_index = istring_size = bufn = tflag = 0;
 
-  for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
-    skip_ctlesc |= *s == CTLESC;
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
 #ifdef __CYGWIN__
   setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
@@ -4524,7 +4632,7 @@ read_comsub (fd, quoted, rflag)
          tflag |= W_HASCTLESC;
          istring[istring_index++] = CTLESC;
        }
-      else if (c == CTLNUL || (c == ' ' && (ifs_value && *ifs_value == 0)))
+      else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
        istring[istring_index++] = CTLESC;
 
       istring[istring_index++] = c;
@@ -4645,9 +4753,11 @@ command_substitute (string, quoted)
     reset_signal_handlers ();
 
 #if defined (JOB_CONTROL)
+  /* XXX DO THIS ONLY IN PARENT ? XXX */
   set_sigchld_handler ();
   stop_making_children ();
-  pipeline_pgrp = old_pipeline_pgrp;
+  if (pid != 0)
+    pipeline_pgrp = old_pipeline_pgrp;
 #else
   stop_making_children ();
 #endif /* JOB_CONTROL */
@@ -4937,10 +5047,11 @@ parameter_brace_expand_word (name, var_is_special, quoted)
   char *temp, *tt;
   intmax_t arg_index;
   SHELL_VAR *var;
-  int atype;
+  int atype, rflags;
 
   ret = 0;
   temp = 0;
+  rflags = 0;
 
   /* Handle multiple digit arguments, as in ${11}. */  
   if (legal_number (name, &arg_index))
@@ -4973,6 +5084,8 @@ parameter_brace_expand_word (name, var_is_special, quoted)
        temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
                  ? quote_string (temp)
                  : quote_escapes (temp);
+      else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+       rflags |= W_HASQUOTEDNULL;
     }
 #endif
   else if (var = find_variable (name))
@@ -5000,6 +5113,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
     {
       ret = alloc_word_desc ();
       ret->word = temp;
+      ret->flags |= rflags;
     }
   return ret;
 }
@@ -5749,7 +5863,14 @@ pos_params_pat_subst (string, pat, rep, mflags)
   if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
     ret = string_list_dollar_star (quote_list (save));
   else
+#if 0
     ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+#else
+    if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+      ret = string_list_dollar_star (quote_list (save));
+    else
+      ret = string_list (save);
+#endif
   dispose_words (save);
 
   return (ret);
@@ -5764,7 +5885,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
      char *varname, *value, *patsub;
      int quoted;
 {
-  int vtype, mflags, starsub;
+  int vtype, mflags, starsub, delim;
   char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
   SHELL_VAR *v;
 
@@ -5799,10 +5920,21 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   /* If the pattern starts with a `/', make sure we skip over it when looking
      for the replacement delimiter. */
+#if 0
   if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
     *rep++ = '\0';
   else
     rep = (char *)NULL;
+#else
+  delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+  if (lpatsub[delim] == '/')
+    {
+      lpatsub[delim] = 0;
+      rep = lpatsub + delim + 1;
+    }
+  else
+    rep = (char *)NULL;
+#endif
 
   if (rep && *rep == '\0')
     rep = (char *)NULL;
@@ -5960,7 +6092,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   int t_index, sindex, c, tflag;
   intmax_t number;
 
-  value = (char *)NULL;
+  temp = temp1 = value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
   want_substring = want_indir = want_patsub = 0;
 
@@ -6585,7 +6717,7 @@ param_expand (string, sindex, quoted, expanded_something,
     case LPAREN:
       /* We have to extract the contents of this paren substitution. */
       t_index = zindex + 1;
-      temp = extract_command_subst (string, &t_index);
+      temp = extract_command_subst (string, &t_index, 0);
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6644,7 +6776,8 @@ comsub:
        {
          tdesc = command_substitute (temp, quoted);
          temp1 = tdesc ? tdesc->word : (char *)NULL;
-         dispose_word_desc (tdesc);
+         if (tdesc)
+           dispose_word_desc (tdesc);
        }
       FREE (temp);
       temp = temp1;
@@ -6657,6 +6790,13 @@ comsub:
       t_index = zindex + 1;
       temp = extract_arithmetic_subst (string, &t_index);
       zindex = t_index;
+      if (temp == 0)
+       {
+         temp = savestring (string);
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }         
 
        /* Do initial variable expansion. */
       temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
@@ -6986,7 +7126,7 @@ add_string:
              free (temp);
              temp = temp1;
              sindex += t_index;
-             goto add_string;
+             goto add_quoted_string;           /* XXX was add_string */
            }
          else
            {
@@ -7055,7 +7195,8 @@ add_string:
                de_backslash (temp);
                tword = command_substitute (temp, quoted);
                temp1 = tword ? tword->word : (char *)NULL;
-               dispose_word_desc (tword);
+               if (tword)
+                 dispose_word_desc (tword);
              }
            FREE (temp);
            temp = temp1;
@@ -7483,6 +7624,11 @@ string_quote_removal (string, quoted)
        {
        case '\\':
          c = string[++sindex];
+         if (c == 0)
+           {
+             *r++ = '\\';
+             break;
+           }
          if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
            *r++ = '\\';
          /* FALLTHROUGH */
@@ -7683,7 +7829,9 @@ exp_jump_to_top_level (v)
   /* Cleanup code goes here. */
   expand_no_split_dollar_star = 0;     /* XXX */
   expanding_redir = 0;
+  assigning_in_environment = 0;
 
+  top_level_cleanup ();                        /* from sig.c */
   jump_to_top_level (v);
 }
 
@@ -7901,7 +8049,7 @@ glob_expand_word_list (tlist, eflags)
          else if (fail_glob_expansion != 0)
            {
              report_error (_("no match: %s"), tlist->word->word);
-             jump_to_top_level (DISCARD);
+             exp_jump_to_top_level (DISCARD);
            }
          else if (allow_null_glob_expansion == 0)
            {
@@ -8183,7 +8331,9 @@ expand_word_list_internal (list, eflags)
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
+         assigning_in_environment = (assign_func == assign_in_env);
          tint = (*assign_func) (temp_list->word);
+         assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
          if (tint == 0)
index 9944f5bccc2d10a0add6d5d3abcd26f75a6a9afb..080da0f866e2d44869799779598c8a171334e9f3 100644 (file)
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -69,6 +69,7 @@ extern int errno;
 #define VT_POSPARMS    1
 #define VT_ARRAYVAR    2
 #define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR    4
 
 #define VT_STARSUB     128     /* $* or ${array[*]} -- used to split */
 
@@ -78,13 +79,6 @@ extern int errno;
 #define ST_SQUOTE      0x04    /* unused yet */
 #define ST_DQUOTE      0x08    /* unused yet */
 
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC     0x01    /* just skip; don't return substring */
-#define SX_VARNAME     0x02    /* variable name; for string_extract () */
-#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
-#define SX_COMMAND     0x08    /* extracting a shell script/command */
-#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
-
 /* Flags for the `pflags' argument to param_expand() */
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 
@@ -135,6 +129,8 @@ size_t ifs_firstc_len;
 unsigned char ifs_firstc;
 #endif
 
+int assigning_in_environment;
+
 /* Extern functions and variables from different files. */
 extern int last_command_exit_value, last_command_exit_signal;
 extern int subshell_environment;
@@ -206,7 +202,6 @@ static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
 static char *dequote_escapes __P((char *));
 static char *make_quoted_char __P((int));
 static WORD_LIST *quote_list __P((WORD_LIST *));
-static char *remove_quoted_escapes __P((char *));
 static char *remove_quoted_nulls __P((char *));
 
 static int unquoted_substring __P((char *, char *));
@@ -713,7 +708,7 @@ add_one_character:
 
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+           ret = extract_command_subst (string, &si, 0);
          else
            ret = extract_dollar_brace_string (string, &si, 1, 0);
 
@@ -815,7 +810,7 @@ skip_double_quoted (string, slen, sind)
        {
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
            ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
 
@@ -933,11 +928,12 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
       /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
         through, to protect the CTLNULs from later calls to
         remove_quoted_nulls. */
-      else if (c == CTLESC && string[i+1] == CTLNUL)
+      else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
        {
          i += 2;
          continue;
        }
+
 #if defined (HANDLE_MULTIBYTE)
       mblength = MBLEN (string + i, slen - i);
       if (mblength > 1)
@@ -985,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position of the matching ")". ) */
+   Make (SINDEX) get the position of the matching ")". )
+   XFLAGS is additional flags to pass to other extraction functions, */
 char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
      char *string;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+  if (string[*sindex] == '(')  /*)*/
+    return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+    }
 }
 
 /* Extract the $[ construct in STRING, and return a new string. (])
@@ -1098,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
 
       /* Not exactly right yet; should handle shell metacharacters and
         multibyte characters, too. */
-      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
        {
           in_comment = 1;
           ADVANCE_CHAR (string, slen, i);
@@ -1263,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       if (string[i] == '$' && string[i+1] == LPAREN)
        {
          si = i + 2;
-         t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
          i = si + 1;
          continue;
        }
@@ -1360,6 +1364,91 @@ unquote_bang (string)
 }
 #endif
 
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+   the index of that character.  START is the index into string at which we
+   begin.  This is similar in spirit to strpbrk, but it returns an index into
+   STRING and takes a starting index.  This little piece of code knows quite
+   a lot of shell syntax.  It's very similar to skip_double_quoted and other
+   functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+     char *string;
+     int start;
+     char *delims;
+     int flags;
+{
+  int i, pass_next, backq, si, c;
+  size_t slen;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  if (flags & SD_NOJMP)
+    no_longjmp_on_fatal_error = 1;
+  i = start;
+  pass_next = backq = 0;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == 0)
+           CQ_RETURN(i);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (backq)
+       {
+         if (c == '`')
+           backq = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '`')
+       {
+         backq = 1;
+         i++;
+         continue;
+       }
+      else if (c == '\'' || c == '"')
+       {
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+                         : skip_double_quoted (string, slen, ++i);
+         /* no increment, the skip functions increment past the closing quote. */
+       }
+      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         if (string[i+1] == LPAREN)
+           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+         else
+           temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+      else if (member (c, delims))
+       break;
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+
 #if defined (READLINE)
 /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
    an unclosed quoted string), or if the character at EINDEX is quoted
@@ -1369,8 +1458,6 @@ unquote_bang (string)
    recognizes need to be the same as the contents of
    rl_completer_quote_characters. */
 
-#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
-
 int
 char_is_quoted (string, eindex)
      char *string;
@@ -1463,87 +1550,6 @@ unclosed_pair (string, eindex, openstr)
   return (openc);
 }
 
-/* Skip characters in STRING until we find a character in DELIMS, and return
-   the index of that character.  START is the index into string at which we
-   begin.  This is similar in spirit to strpbrk, but it returns an index into
-   STRING and takes a starting index.  This little piece of code knows quite
-   a lot of shell syntax.  It's very similar to skip_double_quoted and other
-   functions of that ilk. */
-int
-skip_to_delim (string, start, delims)
-     char *string;
-     int start;
-     char *delims;
-{
-  int i, pass_next, backq, si, c;
-  size_t slen;
-  char *temp;
-  DECLARE_MBSTATE;
-
-  slen = strlen (string + start) + start;
-  no_longjmp_on_fatal_error = 1;
-  i = start;
-  pass_next = backq = 0;
-  while (c = string[i])
-    {
-      if (pass_next)
-       {
-         pass_next = 0;
-         if (c == 0)
-           CQ_RETURN(i);
-         ADVANCE_CHAR (string, slen, i);
-         continue;
-       }
-      else if (c == '\\')
-       {
-         pass_next = 1;
-         i++;
-         continue;
-       }
-      else if (backq)
-       {
-         if (c == '`')
-           backq = 0;
-         ADVANCE_CHAR (string, slen, i);
-         continue;
-       }
-      else if (c == '`')
-       {
-         backq = 1;
-         i++;
-         continue;
-       }
-      else if (c == '\'' || c == '"')
-       {
-         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
-                         : skip_double_quoted (string, slen, ++i);
-         /* no increment, the skip functions increment past the closing quote. */
-       }
-      else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
-       {
-         si = i + 2;
-         if (string[si] == '\0')
-           CQ_RETURN(si);
-
-         if (string[i+1] == LPAREN)
-           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
-         else
-           temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
-         i = si;
-         if (string[i] == '\0')        /* don't increment i past EOS in loop */
-           break;
-         i++;
-         continue;
-       }
-      else if (member (c, delims))
-       break;
-      else
-       ADVANCE_CHAR (string, slen, i);
-    }
-
-  CQ_RETURN(i);
-}
-
 /* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
    individual words.  If DELIMS is NULL, the current value of $IFS is used
    to split the string, and the function follows the shell field splitting
@@ -1628,7 +1634,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
   cw = -1;
   while (1)
     {
-      te = skip_to_delim (string, ts, d);
+      te = skip_to_delim (string, ts, d, SD_NOJMP);
 
       /* If we have a non-whitespace delimiter character, use it to make a
         separate field.  This is just about what $IFS splitting does and
@@ -1794,6 +1800,42 @@ string_list (list)
   return (string_list_internal (list, " "));
 }
 
+/* An external interface that can be used by the rest of the shell to
+   obtain a string containing the first character in $IFS.  Handles all
+   the multibyte complications.  If LENP is non-null, it is set to the
+   length of the returned string. */
+char *
+ifs_firstchar (lenp)
+     int *lenp;
+{
+  char *ret;
+  int len;
+
+  ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc_len == 1)
+    {
+      ret[0] = ifs_firstc[0];
+      ret[1] = '\0';
+      len = ret[0] ? 1 : 0;
+    }
+  else
+    {
+      memcpy (ret, ifs_firstc, ifs_firstc_len);
+      ret[len = ifs_firstc_len] = '\0';
+    }
+#else
+  ret[0] = ifs_firstc;
+  ret[1] = '\0';
+  len = ret[0] ? 0 : 1;
+#endif
+
+  if (lenp)
+    *lenp = len;
+
+  return ret;
+}
+
 /* Return a single string of all the words present in LIST, obeying the
    quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the
    expansion [of $*] appears within a double quoted string, it expands
@@ -1913,6 +1955,47 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+     int pchar;
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ret;
+
+  if (pchar == '*')
+    {
+      if (quoted & Q_DOUBLE_QUOTES)
+       ret = string_list_dollar_star (quote_list (list));
+      else if (quoted & Q_HERE_DOCUMENT)
+       ret = string_list (quote_list (list));
+      else
+       /* Even when unquoted, string_list_dollar_star does the right thing
+          making sure that the first character of $IFS is used as the
+          separator. */
+       ret = string_list_dollar_star (list);
+    }
+  else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+    /* We use string_list_dollar_at, but only if the string is quoted, since
+       that quotes the escapes if it's not, which we don't want.  We could
+       use string_list (the old code did), but that doesn't do the right
+       thing if the first character of $IFS is not a space.  We use
+       string_list_dollar_star if the string is unquoted so we make sure that
+       the elements of $@ are separated by the first character of $IFS for
+       later splitting. */
+    ret = string_list_dollar_at (list, quoted);
+  else if (pchar == '@')
+    ret = string_list_dollar_star (list);
+  else
+    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+  return ret;
+}
+
 /* Return the list of words present in STRING.  Separate the string into
    words at any of the characters found in SEPARATORS.  If QUOTED is
    non-zero then word in the list will have its quoted flag set, otherwise
@@ -1964,7 +2047,10 @@ list_string (string, separators, quoted)
                                 separators[2] == '\n' &&
                                 separators[3] == '\0';
   for (xflags = 0, s = ifs_value; s && *s; s++)
-    if (*s == CTLESC) xflags |= SX_NOCTLESC;  
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
 
   slen = 0;
   /* Remove sequences of whitespace at the beginning of STRING, as
@@ -2083,7 +2169,10 @@ get_word_from_string (stringp, separators, endptr)
                                 separators[2] == '\n' &&
                                 separators[3] == '\0';
   for (xflags = 0, s = ifs_value; s && *s; s++)
-    if (*s == CTLESC) xflags |= SX_NOCTLESC;  
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
 
   s = *stringp;
   slen = 0;
@@ -2271,7 +2360,7 @@ do_assignment_internal (word, expand)
      const WORD_DESC *word;
      int expand;
 {
-  int offset, tlen, appendop, assign_list, aflags;
+  int offset, tlen, appendop, assign_list, aflags, retval;
   char *name, *value;
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
@@ -2362,11 +2451,27 @@ do_assignment_internal (word, expand)
 
   stupidly_hack_special_variables (name);
 
+#if 1
+  /* Return 1 if the assignment seems to have been performed correctly. */
+  if (entry == 0 || readonly_p (entry))
+    retval = 0;                /* assignment failure */
+  else if (noassign_p (entry))
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      retval = 1;      /* error status, but not assignment failure */
+    }
+  else
+    retval = 1;
+  ASSIGN_RETURN (retval);
+
+  if (entry && retval != 0)
+    VUNSETATTR (entry, att_invisible);
+#else
   if (entry)
     VUNSETATTR (entry, att_invisible);
 
-  /* Return 1 if the assignment seems to have been performed correctly. */
   ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
 }
 
 /* Perform the assignment statement in STRING, and expand the
@@ -2516,17 +2621,9 @@ pos_params (string, start, end, quoted)
     }
 
   t->next = (WORD_LIST *)NULL;
-  if (string[0] == '*')
-    {
-      if (quoted & Q_DOUBLE_QUOTES)
-       ret = string_list_dollar_star (quote_list (h));
-      else if (quoted & Q_HERE_DOCUMENT)
-       ret = string_list (quote_list (h));
-      else
-       ret = string_list (h);
-    }
-  else
-    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
   if (t != params)
     t->next = params;
 
@@ -2832,9 +2929,10 @@ expand_string_assignment (string, quoted)
    passed string when an error occurs.  Might want to trap other calls
    to jump_to_top_level here so we don't endlessly loop. */
 WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
      char *string;
      int quoted;
+     int wflags;
 {
   WORD_LIST *value;
   WORD_DESC td;
@@ -2842,7 +2940,7 @@ expand_prompt_string (string, quoted)
   if (string == 0 || *string == 0)
     return ((WORD_LIST *)NULL);
 
-  td.flags = 0;
+  td.flags = wflags;
   td.word = savestring (string);
 
   no_longjmp_on_fatal_error = 1;
@@ -2960,7 +3058,7 @@ quote_escapes (string)
   register char *s, *t;
   size_t slen;
   char *result, *send;
-  int quote_spaces, skip_ctlesc;
+  int quote_spaces, skip_ctlesc, skip_ctlnul;
   DECLARE_MBSTATE; 
 
   slen = strlen (string);
@@ -2968,15 +3066,15 @@ quote_escapes (string)
 
   quote_spaces = (ifs_value && *ifs_value == 0);
 
-  for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
-    skip_ctlesc |= *s == CTLESC;
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
   t = result = (char *)xmalloc ((slen * 2) + 1);
   s = string;
 
   while (*s)
     {
-      if ((skip_ctlesc == 0 && *s == CTLESC) || *s == CTLNUL || (quote_spaces && *s == ' '))
+      if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
        *t++ = CTLESC;
       COPY_CHAR_P (t, s, send);
     }
@@ -3190,7 +3288,7 @@ dequote_list (list)
 
 /* Remove CTLESC protecting a CTLESC or CTLNUL in place.  Return the passed
    string. */
-static char *
+char *
 remove_quoted_escapes (string)
      char *string;
 {
@@ -3920,12 +4018,9 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
     }
 
   l = REVERSE_LIST (new, WORD_LIST *);
-  if (itype == '*')
-    tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
-  else
-    tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+  tword = string_list_pos_params (itype, l, quoted);
   dispose_words (l);
+
   return (tword);
 }
 
@@ -3999,7 +4094,11 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
   if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
     patstr++;
 
-  pattern = getpattern (patstr, quoted, 1);
+  /* Need to pass getpattern newly-allocated memory in case of expansion --
+     the expansion code will free the passed string on an error. */
+  temp1 = savestring (patstr);
+  pattern = getpattern (temp1, quoted, 1);
+  free (temp1);
 
   temp1 = (char *)NULL;                /* shut up gcc */
   switch (vtype)
@@ -4363,6 +4462,7 @@ process_substitute (string, open_for_read_in_child)
 #if defined (JOB_CONTROL)
   set_sigchld_handler ();
   stop_making_children ();
+  /* XXX - should we only do this in the parent? (as in command subst) */
   pipeline_pgrp = old_pipeline_pgrp;
 #endif /* JOB_CONTROL */
 
@@ -4481,14 +4581,14 @@ read_comsub (fd, quoted, rflag)
      int *rflag;
 {
   char *istring, buf[128], *bufp, *s;
-  int istring_index, istring_size, c, tflag, skip_ctlesc;
+  int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
   ssize_t bufn;
 
   istring = (char *)NULL;
   istring_index = istring_size = bufn = tflag = 0;
 
-  for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
-    skip_ctlesc |= *s == CTLESC;
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
 
 #ifdef __CYGWIN__
   setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
@@ -4531,7 +4631,7 @@ read_comsub (fd, quoted, rflag)
          tflag |= W_HASCTLESC;
          istring[istring_index++] = CTLESC;
        }
-      else if (c == CTLNUL || (c == ' ' && (ifs_value && *ifs_value == 0)))
+      else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
        istring[istring_index++] = CTLESC;
 
       istring[istring_index++] = c;
@@ -4652,9 +4752,11 @@ command_substitute (string, quoted)
     reset_signal_handlers ();
 
 #if defined (JOB_CONTROL)
+  /* XXX DO THIS ONLY IN PARENT ? XXX */
   set_sigchld_handler ();
   stop_making_children ();
-  pipeline_pgrp = old_pipeline_pgrp;
+  if (pid != 0)
+    pipeline_pgrp = old_pipeline_pgrp;
 #else
   stop_making_children ();
 #endif /* JOB_CONTROL */
@@ -4944,10 +5046,11 @@ parameter_brace_expand_word (name, var_is_special, quoted)
   char *temp, *tt;
   intmax_t arg_index;
   SHELL_VAR *var;
-  int atype;
+  int atype, rflags;
 
   ret = 0;
   temp = 0;
+  rflags = 0;
 
   /* Handle multiple digit arguments, as in ${11}. */  
   if (legal_number (name, &arg_index))
@@ -4980,6 +5083,8 @@ parameter_brace_expand_word (name, var_is_special, quoted)
        temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
                  ? quote_string (temp)
                  : quote_escapes (temp);
+      else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+       rflags |= W_HASQUOTEDNULL;
     }
 #endif
   else if (var = find_variable (name))
@@ -5007,6 +5112,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
     {
       ret = alloc_word_desc ();
       ret->word = temp;
+      ret->flags |= rflags;
     }
   return ret;
 }
@@ -5739,6 +5845,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   WORD_LIST *save, *params;
   WORD_DESC *w;
   char *ret;
+  int pchar;
 
   save = params = list_rest_of_args ();
   if (save == 0)
@@ -5755,8 +5862,11 @@ pos_params_pat_subst (string, pat, rep, mflags)
 
   if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
     ret = string_list_dollar_star (quote_list (save));
+  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+    ret = string_list_dollar_star (quote_list (save));
   else
-    ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+    ret = string_list_dollar_star (save);
+
   dispose_words (save);
 
   return (ret);
@@ -5771,7 +5881,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
      char *varname, *value, *patsub;
      int quoted;
 {
-  int vtype, mflags, starsub;
+  int vtype, mflags, starsub, delim;
   char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
   SHELL_VAR *v;
 
@@ -5806,10 +5916,21 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   /* If the pattern starts with a `/', make sure we skip over it when looking
      for the replacement delimiter. */
+#if 0
   if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
     *rep++ = '\0';
   else
     rep = (char *)NULL;
+#else
+  delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+  if (lpatsub[delim] == '/')
+    {
+      lpatsub[delim] = 0;
+      rep = lpatsub + delim + 1;
+    }
+  else
+    rep = (char *)NULL;
+#endif
 
   if (rep && *rep == '\0')
     rep = (char *)NULL;
@@ -5967,7 +6088,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
   int t_index, sindex, c, tflag;
   intmax_t number;
 
-  value = (char *)NULL;
+  temp = temp1 = value = (char *)NULL;
   var_is_set = var_is_null = var_is_special = check_nullness = 0;
   want_substring = want_indir = want_patsub = 0;
 
@@ -6592,7 +6713,7 @@ param_expand (string, sindex, quoted, expanded_something,
     case LPAREN:
       /* We have to extract the contents of this paren substitution. */
       t_index = zindex + 1;
-      temp = extract_command_subst (string, &t_index);
+      temp = extract_command_subst (string, &t_index, 0);
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6651,7 +6772,8 @@ comsub:
        {
          tdesc = command_substitute (temp, quoted);
          temp1 = tdesc ? tdesc->word : (char *)NULL;
-         dispose_word_desc (tdesc);
+         if (tdesc)
+           dispose_word_desc (tdesc);
        }
       FREE (temp);
       temp = temp1;
@@ -6664,6 +6786,13 @@ comsub:
       t_index = zindex + 1;
       temp = extract_arithmetic_subst (string, &t_index);
       zindex = t_index;
+      if (temp == 0)
+       {
+         temp = savestring (string);
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }         
 
        /* Do initial variable expansion. */
       temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
@@ -6993,7 +7122,7 @@ add_string:
              free (temp);
              temp = temp1;
              sindex += t_index;
-             goto add_string;
+             goto add_quoted_string;           /* XXX was add_string */
            }
          else
            {
@@ -7062,7 +7191,8 @@ add_string:
                de_backslash (temp);
                tword = command_substitute (temp, quoted);
                temp1 = tword ? tword->word : (char *)NULL;
-               dispose_word_desc (tword);
+               if (tword)
+                 dispose_word_desc (tword);
              }
            FREE (temp);
            temp = temp1;
@@ -7490,6 +7620,11 @@ string_quote_removal (string, quoted)
        {
        case '\\':
          c = string[++sindex];
+         if (c == 0)
+           {
+             *r++ = '\\';
+             break;
+           }
          if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
            *r++ = '\\';
          /* FALLTHROUGH */
@@ -7690,7 +7825,9 @@ exp_jump_to_top_level (v)
   /* Cleanup code goes here. */
   expand_no_split_dollar_star = 0;     /* XXX */
   expanding_redir = 0;
+  assigning_in_environment = 0;
 
+  top_level_cleanup ();                        /* from sig.c */
   jump_to_top_level (v);
 }
 
@@ -7908,7 +8045,7 @@ glob_expand_word_list (tlist, eflags)
          else if (fail_glob_expansion != 0)
            {
              report_error (_("no match: %s"), tlist->word->word);
-             jump_to_top_level (DISCARD);
+             exp_jump_to_top_level (DISCARD);
            }
          else if (allow_null_glob_expansion == 0)
            {
@@ -8190,7 +8327,9 @@ expand_word_list_internal (list, eflags)
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
+         assigning_in_environment = (assign_func == assign_in_env);
          tint = (*assign_func) (temp_list->word);
+         assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
          if (tint == 0)
index bd935218d8babcf2dd975473704c4e81d5e17aa7..71ddc8fbe6a8ae1d16911bc0bf6459141ffeeb52 100644 (file)
--- a/subst.c~
+++ b/subst.c~
@@ -4,7 +4,7 @@
 /* ``Have a little faith, there's magic in the night.  You ain't a
      beauty, but, hey, you're alright.'' */
 
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -69,6 +69,7 @@ extern int errno;
 #define VT_POSPARMS    1
 #define VT_ARRAYVAR    2
 #define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR    4
 
 #define VT_STARSUB     128     /* $* or ${array[*]} -- used to split */
 
@@ -78,14 +79,6 @@ extern int errno;
 #define ST_SQUOTE      0x04    /* unused yet */
 #define ST_DQUOTE      0x08    /* unused yet */
 
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC     0x01    /* just skip; don't return substring */
-#define SX_VARNAME     0x02    /* variable name; for string_extract () */
-#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
-#define SX_COMMAND     0x08    /* extracting a shell script/command */
-#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
-#define SX_NOESCCTLNUL 0x20    /* don't let CTLESC quote CTLNUL */
-
 /* Flags for the `pflags' argument to param_expand() */
 #define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
 
@@ -715,7 +708,7 @@ add_one_character:
 
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+           ret = extract_command_subst (string, &si, 0);
          else
            ret = extract_dollar_brace_string (string, &si, 1, 0);
 
@@ -817,7 +810,7 @@ skip_double_quoted (string, slen, sind)
        {
          si = i + 2;
          if (string[i + 1] == LPAREN)
-           ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
          else
            ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
 
@@ -940,6 +933,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
          i += 2;
          continue;
        }
+
 #if defined (HANDLE_MULTIBYTE)
       mblength = MBLEN (string + i, slen - i);
       if (mblength > 1)
@@ -987,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position of the matching ")". ) */
+   Make (SINDEX) get the position of the matching ")". )
+   XFLAGS is additional flags to pass to other extraction functions, */
 char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
      char *string;
      int *sindex;
+     int xflags;
 {
-  return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+  if (string[*sindex] == '(')  /*)*/
+    return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+    }
 }
 
 /* Extract the $[ construct in STRING, and return a new string. (])
@@ -1100,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
 
       /* Not exactly right yet; should handle shell metacharacters and
         multibyte characters, too. */
-      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
        {
           in_comment = 1;
           ADVANCE_CHAR (string, slen, i);
@@ -1265,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       if (string[i] == '$' && string[i+1] == LPAREN)
        {
          si = i + 2;
-         t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
          i = si + 1;
          continue;
        }
@@ -1953,6 +1955,55 @@ string_list_dollar_at (list, quoted)
   return ret;
 }
 
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+     int pchar;
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ret;
+  WORD_LIST *tlist;
+
+  if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list_dollar_star (tlist);
+    }
+  else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list (tlist);
+    }
+  else if (pchar == '*')
+    {
+      /* Even when unquoted, string_list_dollar_star does the right thing
+        making sure that the first character of $IFS is used as the
+        separator. */
+      ret = string_list_dollar_star (list);
+    }
+  else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+    /* We use string_list_dollar_at, but only if the string is quoted, since
+       that quotes the escapes if it's not, which we don't want.  We could
+       use string_list (the old code did), but that doesn't do the right
+       thing if the first character of $IFS is not a space.  We use
+       string_list_dollar_star if the string is unquoted so we make sure that
+       the elements of $@ are separated by the first character of $IFS for
+       later splitting. */
+    ret = string_list_dollar_at (list, quoted);
+  else if (pchar == '@')
+    ret = string_list_dollar_star (list);
+  else
+    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+  return ret;
+}
+
 /* Return the list of words present in STRING.  Separate the string into
    words at any of the characters found in SEPARATORS.  If QUOTED is
    non-zero then word in the list will have its quoted flag set, otherwise
@@ -2578,17 +2629,9 @@ pos_params (string, start, end, quoted)
     }
 
   t->next = (WORD_LIST *)NULL;
-  if (string[0] == '*')
-    {
-      if (quoted & Q_DOUBLE_QUOTES)
-       ret = string_list_dollar_star (quote_list (h));
-      else if (quoted & Q_HERE_DOCUMENT)
-       ret = string_list (quote_list (h));
-      else
-       ret = string_list (h);
-    }
-  else
-    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
   if (t != params)
     t->next = params;
 
@@ -2894,9 +2937,10 @@ expand_string_assignment (string, quoted)
    passed string when an error occurs.  Might want to trap other calls
    to jump_to_top_level here so we don't endlessly loop. */
 WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
      char *string;
      int quoted;
+     int wflags;
 {
   WORD_LIST *value;
   WORD_DESC td;
@@ -2904,7 +2948,7 @@ expand_prompt_string (string, quoted)
   if (string == 0 || *string == 0)
     return ((WORD_LIST *)NULL);
 
-  td.flags = 0;
+  td.flags = wflags;
   td.word = savestring (string);
 
   no_longjmp_on_fatal_error = 1;
@@ -3225,9 +3269,10 @@ quote_list (list)
     {
       t = w->word->word;
       w->word->word = quote_string (t);
-      free (t);
+      if (*t == 0)
+       w->word->flags |= W_HASQUOTEDNULL;      /* XXX - turn on W_HASQUOTEDNULL here? */
       w->word->flags |= W_QUOTED;
-      /* XXX - turn on W_HAVEQUOTEDNULL here? */
+      free (t);
     }
   return list;
 }
@@ -3243,9 +3288,10 @@ dequote_list (list)
   for (tlist = list; tlist; tlist = tlist->next)
     {
       s = dequote_string (tlist->word->word);
+      if (QUOTED_NULL (tlist->word->word))
+       tlist->word->flags &= ~W_HASQUOTEDNULL;
       free (tlist->word->word);
       tlist->word->word = s;
-      /* XXX - turn off W_HAVEQUOTEDNULL here? */
     }
   return list;
 }
@@ -3982,12 +4028,22 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
     }
 
   l = REVERSE_LIST (new, WORD_LIST *);
-  if (itype == '*')
-    tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
+#if 0
+  if (itype == '*' && (quoted & Q_DOUBLE_QUOTES))
+    tword = string_list_dollar_star (quote_list (l));
+  else if (itype == '*')
+    tword = string_list_dollar_star (l);
+  else if (itype == '@' && (quoted & Q_DOUBLE_QUOTES))
+    tword = string_list_dollar_at (l, quoted);
+  else if (itype == '@')
+    tword = string_list_dollar_star (l);
   else
-    tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+    tword = string_list (l);
+#else
+  tword = string_list_pos_params (itype, l, quoted);
+#endif
   dispose_words (l);
+
   return (tword);
 }
 
@@ -5812,6 +5868,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
   WORD_LIST *save, *params;
   WORD_DESC *w;
   char *ret;
+  int pchar, qflags;
 
   save = params = list_rest_of_args ();
   if (save == 0)
@@ -5826,10 +5883,22 @@ pos_params_pat_subst (string, pat, rep, mflags)
       params->word = w;
     }
 
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
   if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
     ret = string_list_dollar_star (quote_list (save));
+  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+    ret = string_list_dollar_star (save);
+  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+    ret = string_list_dollar_at (save, qflags);
   else
-    ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+    ret = string_list_dollar_star (save);
+#else
+  ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
   dispose_words (save);
 
   return (ret);
@@ -6136,6 +6205,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
       temp = (char *)NULL;
       goto bad_substitution;
     }
+
   /* Indirect expansion begins with a `!'.  A valid indirect expansion is
      either a variable name, one of the positional parameters or a special
      variable that expands to one of the positional parameters. */
@@ -6675,7 +6745,7 @@ param_expand (string, sindex, quoted, expanded_something,
     case LPAREN:
       /* We have to extract the contents of this paren substitution. */
       t_index = zindex + 1;
-      temp = extract_command_subst (string, &t_index);
+      temp = extract_command_subst (string, &t_index, 0);
       zindex = t_index;
 
       /* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6734,7 +6804,8 @@ comsub:
        {
          tdesc = command_substitute (temp, quoted);
          temp1 = tdesc ? tdesc->word : (char *)NULL;
-         dispose_word_desc (tdesc);
+         if (tdesc)
+           dispose_word_desc (tdesc);
        }
       FREE (temp);
       temp = temp1;
@@ -7152,7 +7223,8 @@ add_string:
                de_backslash (temp);
                tword = command_substitute (temp, quoted);
                temp1 = tword ? tword->word : (char *)NULL;
-               dispose_word_desc (tword);
+               if (tword)
+                 dispose_word_desc (tword);
              }
            FREE (temp);
            temp = temp1;
@@ -7580,6 +7652,11 @@ string_quote_removal (string, quoted)
        {
        case '\\':
          c = string[++sindex];
+         if (c == 0)
+           {
+             *r++ = '\\';
+             break;
+           }
          if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
            *r++ = '\\';
          /* FALLTHROUGH */
diff --git a/subst.h b/subst.h
index fc07a823a9b1f1596a93076c558dfc0405a6b156..6f30b43d615f086ee504c6164b55da0f4254dcd8 100644 (file)
--- a/subst.h
+++ b/subst.h
 #define ASS_APPEND     0x01
 #define ASS_MKLOCAL    0x02
 
+/* Flags for the string extraction functions. */
+#define SX_NOALLOC     0x01    /* just skip; don't return substring */
+#define SX_VARNAME     0x02    /* variable name; for string_extract () */
+#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
+#define SX_COMMAND     0x08    /* extracting a shell script/command */
+#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
+#define SX_NOESCCTLNUL 0x20    /* don't let CTLESC quote CTLNUL */
+#define SX_NOLONGJMP   0x40    /* don't longjmp on fatal error */
+
 /* Remove backslashes which are quoting backquotes from STRING.  Modifies
    STRING, and returns a pointer to it. */
 extern char * de_backslash __P((char *));
@@ -52,8 +61,9 @@ extern void unquote_bang __P((char *));
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position just after the matching ")". */
-extern char *extract_command_subst __P((char *, int *));
+   Make (SINDEX) get the position just after the matching ")".
+   XFLAGS is additional flags to pass to other extraction functions, */
+extern char *extract_command_subst __P((char *, int *, int));
 
 /* Extract the $[ construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$[".
@@ -84,6 +94,12 @@ extern char *string_list_dollar_star __P((WORD_LIST *));
 /* Expand $@ into a single string, obeying POSIX rules. */
 extern char *string_list_dollar_at __P((WORD_LIST *, int));
 
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+extern char *string_list_pos_params __P((int, WORD_LIST *, int));
+
 /* Perform quoted null character removal on each element of LIST.
    This modifies LIST. */
 extern void word_list_remove_quoted_nulls __P((WORD_LIST *));
index f0e417fadbd36e8762a54c557cb32a9e2a915a05..1093ee258f748e38644a6176bf3f354e55951516 100644 (file)
--- a/subst.h~
+++ b/subst.h~
 #define ASS_APPEND     0x01
 #define ASS_MKLOCAL    0x02
 
+/* Flags for the string extraction functions. */
+#define SX_NOALLOC     0x01    /* just skip; don't return substring */
+#define SX_VARNAME     0x02    /* variable name; for string_extract () */
+#define SX_REQMATCH    0x04    /* closing/matching delimiter required */
+#define SX_COMMAND     0x08    /* extracting a shell script/command */
+#define SX_NOCTLESC    0x10    /* don't honor CTLESC quoting */
+#define SX_NOESCCTLNUL 0x20    /* don't let CTLESC quote CTLNUL */
+#define SX_NOLONGJMP   0x40    /* don't longjmp on fatal error */
+
 /* Remove backslashes which are quoting backquotes from STRING.  Modifies
    STRING, and returns a pointer to it. */
 extern char * de_backslash __P((char *));
@@ -52,8 +61,9 @@ extern void unquote_bang __P((char *));
 
 /* Extract the $( construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$(".
-   Make (SINDEX) get the position just after the matching ")". */
-extern char *extract_command_subst __P((char *, int *));
+   Make (SINDEX) get the position just after the matching ")".
+   XFLAGS is additional flags to pass to other extraction functions, */
+extern char *extract_command_subst __P((char *, int *, int));
 
 /* Extract the $[ construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$[".
@@ -136,7 +146,7 @@ extern WORD_LIST *expand_string_unsplit __P((char *, int));
 extern WORD_LIST *expand_string_assignment __P((char *, int));
 
 /* Expand a prompt string. */
-extern WORD_LIST *expand_prompt_string __P((char *, int));
+extern WORD_LIST *expand_prompt_string __P((char *, int, int));
 
 /* Expand STRING just as if you were expanding a word.  This also returns
    a list of words.  Note that filename globbing is *NOT* done for word
diff --git a/tests/comsub-posix.right b/tests/comsub-posix.right
new file mode 100644 (file)
index 0000000..a24f25f
--- /dev/null
@@ -0,0 +1,57 @@
+abmnopyz
+abmnopyz
+ab
+
+
+abcd
+sh_352.26ax
+sh_352.26ay
+sh_352.25a sh_352.25b
+sh_352.27 ) ) )
+abc
+here doc with )
+)
+bad' syntax
+a
+sh_352.26a
+sh_352.26a
+sh_352.26
+sh_352.26
+sh_352.26
+1
+sh_352.28 )
+sh_352.28 )
+k
+abcd
+ab
+ab
+abcde
+
+argv[1] = <abcde^J  >
+abcdefoo
+argv[1] = <abcde>
+argv[2] = <foo>
+argv[1] = <wxabcdeyz>
+argv[1] = <abcde>
+'
+after 1
+'
+after 2
+'
+after 3
+`
+after 4
+hello
+after 5
+'
+after 6
+x
+x
+quoted )
+comment
+here-doc with )
+here-doc terminated with a parenthesis
+' # or a single back- or doublequote
+./comsub-posix1.sub: command substitution: line 2: syntax error near unexpected token `)'
+./comsub-posix1.sub: command substitution: line 2: ` if x; then echo foo )'
+after
diff --git a/tests/comsub-posix.tests b/tests/comsub-posix.tests
new file mode 100644 (file)
index 0000000..abd7396
--- /dev/null
@@ -0,0 +1,199 @@
+
+# works right
+echo ab$(echo mnop)yz
+# works right
+echo ab$(echo mnop
+)yz
+#
+# works right
+echo $(echo ab
+   )
+# works right
+echo $(
+)
+echo $()
+echo ab$()cd
+
+echo $(case a in (a) echo sh_352.26ax; esac )
+echo $(case a in (a) echo sh_352.26ay; esac)
+
+echo $((echo sh_352.25a);(echo sh_352.25b))
+
+echo $(echo sh_352.27 ')' ")" \)
+       # ) comment
+       )
+
+echo $(
+echo abc # a comment with )
+)
+
+echo $(
+cat <<eof
+here doc with )
+eof
+)
+
+echo $(
+echo ')'
+)
+
+unset x
+x=$(cat <<"EOF"
+bad' syntax
+EOF
+)
+echo "$x"
+unset x
+
+echo $(for f in \); do echo a; done )
+echo $(case a in a) echo sh_352.26a; esac )
+echo $(case a in a) echo sh_352.26a; esac)
+
+echo $(case a in
+            (a) echo sh_352.26
+               ;;
+                 esac
+          )
+
+echo $(case a in
+            a) echo sh_352.26
+               ;;
+                 esac
+          )
+
+
+echo $(case a in
+            a) echo sh_352.26
+               ;;
+
+
+                       
+
+    
+                 esac
+
+          )
+
+echo $(( 4<(2+3) ? 1 : 32))
+
+echo $(cat << end
+sh_352.28 )
+end
+)
+
+echo $(cat <<- end  
+sh_352.28 )
+       end
+)
+
+k=$(case x in x) echo k;; esac)
+echo $k
+
+x=$(
+    case $(ls) in
+       example) echo foobix;;
+    esac
+)
+
+echo $( echo ab\
+cd)
+
+echo `echo ab
+cd`
+
+echo `echo ab #xyz
+cd`
+
+echo "$(echo abcde)
+"
+
+recho "$(echo abcde)
+  "
+
+echo $(echo abcde)\
+foo
+
+recho $(echo abcde)\
+   foo
+
+recho "wx$(echo abcde)yz"
+recho "$(echo abcde)"
+
+echo $(cat <<eof
+'
+eof
+)
+
+echo after 1
+
+echo $(cat <<\eof
+'
+eof
+)
+
+echo after 2
+
+echo "$(cat <<\eof
+'
+eof
+)"
+
+echo after 3
+
+echo "$(cat <<\eof
+`
+eof
+)"
+
+echo after 4
+
+echo $(
+cat << ')'
+hello
+)
+)
+
+echo after 5
+
+echo $(cat <<'eof'
+'
+eof
+)
+
+echo after 6
+
+echo $(
+  case x in  x) echo x;; esac
+)
+
+echo $(
+  case x in (x) echo x;; esac
+)
+
+echo $(
+  echo 'quoted )'
+)
+
+echo $(
+  echo comment # with )
+)
+
+echo $(
+cat <<\eof
+  here-doc with )
+eof
+)
+
+echo $(
+cat <<\)
+       here-doc terminated with a parenthesis
+)
+)
+
+echo $(
+cat <<\eof
+       '   # or a single back- or doublequote
+eof
+)
+
+${THIS_SH} ./comsub-posix1.sub
diff --git a/tests/comsub-posix.tests~ b/tests/comsub-posix.tests~
new file mode 100644 (file)
index 0000000..b68668e
--- /dev/null
@@ -0,0 +1,197 @@
+
+# works right
+echo ab$(echo mnop)yz
+# works right
+echo ab$(echo mnop
+)yz
+#
+# works right
+echo $(echo ab
+   )
+# works right
+echo $(
+)
+echo $()
+echo ab$()cd
+
+echo $(case a in (a) echo sh_352.26ax; esac )
+echo $(case a in (a) echo sh_352.26ay; esac)
+
+echo $((echo sh_352.25a);(echo sh_352.25b))
+
+echo $(echo sh_352.27 ')' ")" \)
+       # ) comment
+       )
+
+echo $(
+echo abc # a comment with )
+)
+
+echo $(
+cat <<eof
+here doc with )
+eof
+)
+
+echo $(
+echo ')'
+)
+
+unset x
+x=$(cat <<"EOF"
+bad' syntax
+EOF
+)
+echo "$x"
+unset x
+
+echo $(for f in \); do echo a; done )
+echo $(case a in a) echo sh_352.26a; esac )
+echo $(case a in a) echo sh_352.26a; esac)
+
+echo $(case a in
+            (a) echo sh_352.26
+               ;;
+                 esac
+          )
+
+echo $(case a in
+            a) echo sh_352.26
+               ;;
+                 esac
+          )
+
+
+echo $(case a in
+            a) echo sh_352.26
+               ;;
+
+
+                       
+
+    
+                 esac
+
+          )
+
+echo $(( 4<(2+3) ? 1 : 32))
+
+echo $(cat << end
+sh_352.28 )
+end
+)
+
+echo $(cat <<- end  
+sh_352.28 )
+       end
+)
+
+k=$(case x in x) echo k;; esac)
+echo $k
+
+x=$(
+    case $(ls) in
+       example) echo foobix;;
+    esac
+)
+
+echo $( echo ab\
+cd)
+
+echo `echo ab
+cd`
+
+echo `echo ab #xyz
+cd`
+
+echo "$(echo abcde)
+"
+
+recho "$(echo abcde)
+  "
+
+echo $(echo abcde)\
+foo
+
+recho $(echo abcde)\
+   foo
+
+recho "wx$(echo abcde)yz"
+recho "$(echo abcde)"
+
+echo $(cat <<eof
+'
+eof
+)
+
+echo after 1
+
+echo $(cat <<\eof
+'
+eof
+)
+
+echo after 2
+
+echo "$(cat <<\eof
+'
+eof
+)"
+
+echo after 3
+
+echo "$(cat <<\eof
+`
+eof
+)"
+
+echo after 4
+
+echo $(
+cat << ')'
+hello
+)
+)
+
+echo after 5
+
+echo $(cat <<'eof'
+'
+eof
+)
+
+echo after 6
+
+echo $(
+  case x in  x) echo x;; esac
+)
+
+echo $(
+  case x in (x) echo x;; esac
+)
+
+echo $(
+  echo 'quoted )'
+)
+
+echo $(
+  echo comment # with )
+)
+
+echo $(
+cat <<\eof
+  here-doc with )
+eof
+)
+
+echo $(
+cat <<\)
+       here-doc terminated with a parenthesis
+)
+)
+
+echo $(
+cat <<\eof
+       '   # or a single back- or doublequote
+eof
+)
diff --git a/tests/comsub-posix1.sub b/tests/comsub-posix1.sub
new file mode 100644 (file)
index 0000000..bbcc60f
--- /dev/null
@@ -0,0 +1,3 @@
+echo $( if x; then echo foo )
+
+echo after
index 346d2e97acb9e3b09e9aa0cd79d3433979c63ce6..890e9a53076ad8379b2e5f164bf79199dda145f5 100644 (file)
@@ -53,9 +53,10 @@ umask: usage: umask [-p] [-S] [mode]
 ./errors.tests: line 159: declare: VAR: readonly variable
 ./errors.tests: line 161: declare: unset: not found
 ./errors.tests: line 164: VAR: readonly variable
-./errors.tests: command substitution: line 168: syntax error: unexpected end of file
-./errors.tests: command substitution: line 168: syntax error near unexpected token `done'
-./errors.tests: command substitution: line 168: ` for z in 1 2 3; done '
+./errors.tests: command substitution: line 168: syntax error near unexpected token `)'
+./errors.tests: command substitution: line 168: ` for z in 1 2 3; do )'
+./errors.tests: command substitution: line 169: syntax error near unexpected token `done'
+./errors.tests: command substitution: line 169: ` for z in 1 2 3; done )'
 ./errors.tests: line 171: cd: HOME not set
 ./errors.tests: line 172: cd: /tmp/xyz.bash: No such file or directory
 ./errors.tests: line 174: cd: OLDPWD not set
index cf8862ae90e3fb10934c490254dae7dc5530b4d0..7990a4afb45bc8aee3f6ee94bb36895235475b57 100644 (file)
@@ -54,4 +54,5 @@ exec 8<&0; cat <&8
 exec 0<&7
 exec 7<&-
 
+rm -f infile
 exit 0
diff --git a/tests/run-comsub-posix b/tests/run-comsub-posix
new file mode 100644 (file)
index 0000000..38bf8ee
--- /dev/null
@@ -0,0 +1,2 @@
+${THIS_SH} ./comsub-posix.tests > /tmp/xx 2>&1
+diff /tmp/xx comsub-posix.right && rm -f /tmp/xx
index 9e5ab162073b074e5dc7a41fae10765bfbd1621a..7bcc7f9fa6026b847200b8128fe400c60ce7e700 100644 (file)
@@ -1,6 +1,6 @@
 /* variables.h -- data structures for shell variables. */
 
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -23,6 +23,7 @@
 
 #include "stdc.h"
 #include "array.h"
+#include "assoc.h"
 
 /* Shell variables and functions are stored in hash tables. */
 #include "hashlib.h"
@@ -61,7 +62,7 @@ typedef struct var_context {
 /* What a shell variable looks like. */
 
 typedef struct variable *sh_var_value_func_t __P((struct variable *));
-typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t));
+typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t, char *));
 
 /* For the future */
 union _value {
@@ -108,8 +109,11 @@ typedef struct _vlist {
 #define att_local      0x0000020       /* variable is local to a function */
 #define att_assoc      0x0000040       /* variable is an associative array */
 #define att_trace      0x0000080       /* function is traced with DEBUG trap */
+#define att_uppercase  0x0000100       /* word converted to uppercase on assignment */
+#define att_lowercase  0x0000200       /* word converted to lowercase on assignment */
+#define att_capcase    0x0000400       /* word capitalized on assignment */
 
-#define user_attrs     (att_exported|att_readonly|att_integer|att_local|att_trace)
+#define user_attrs     (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase)
 
 #define attmask_user   0x0000fff
 
@@ -119,6 +123,7 @@ typedef struct _vlist {
 #define att_noassign   0x0004000       /* assignment not allowed */
 #define att_imported   0x0008000       /* came from environment */
 #define att_special    0x0010000       /* requires special handling */
+#define att_nofree     0x0020000       /* do not free value on unset */
 
 #define        attmask_int     0x00ff000
 
@@ -136,12 +141,16 @@ typedef struct _vlist {
 #define local_p(var)           ((((var)->attributes) & (att_local)))
 #define assoc_p(var)           ((((var)->attributes) & (att_assoc)))
 #define trace_p(var)           ((((var)->attributes) & (att_trace)))
+#define uppercase_p(var)       ((((var)->attributes) & (att_uppercase)))
+#define lowercase_p(var)       ((((var)->attributes) & (att_lowercase)))
+#define capcase_p(var)         ((((var)->attributes) & (att_capcase)))
 
 #define invisible_p(var)       ((((var)->attributes) & (att_invisible)))
 #define non_unsettable_p(var)  ((((var)->attributes) & (att_nounset)))
 #define noassign_p(var)                ((((var)->attributes) & (att_noassign)))
 #define imported_p(var)                ((((var)->attributes) & (att_imported)))
 #define specialvar_p(var)      ((((var)->attributes) & (att_special)))
+#define nofree_p(var)          ((((var)->attributes) & (att_nofree)))
 
 #define tempvar_p(var)         ((((var)->attributes) & (att_tempvar)))
 
@@ -149,6 +158,7 @@ typedef struct _vlist {
 #define value_cell(var)                ((var)->value)
 #define function_cell(var)     (COMMAND *)((var)->value)
 #define array_cell(var)                (ARRAY *)((var)->value)
+#define assoc_cell(var)                (HASH_TABLE *)((var)->value)
 
 #define var_isnull(var)                ((var)->value == 0)
 #define var_isset(var)         ((var)->value != 0)
@@ -157,6 +167,7 @@ typedef struct _vlist {
 #define var_setvalue(var, str) ((var)->value = (str))
 #define var_setfunc(var, func) ((var)->value = (char *)(func))
 #define var_setarray(var, arr) ((var)->value = (char *)(arr))
+#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
 
 /* Make VAR be auto-exported. */
 #define set_auto_export(var) \
@@ -316,6 +327,9 @@ extern void print_var_function __P((SHELL_VAR *));
 extern SHELL_VAR *make_new_array_variable __P((char *));
 extern SHELL_VAR *make_local_array_variable __P((char *));
 
+extern SHELL_VAR *make_new_assoc_variable __P((char *));
+extern SHELL_VAR *make_local_assoc_variable __P((char *));
+
 extern void set_pipestatus_array __P((int *, int));
 #endif
 
index cc62174099686a67a479c598f090f1e1a64bbf91..036fe49550b83eb366877a10b5c4447fe9b1bbbf 100644 (file)
@@ -1,6 +1,6 @@
 /* variables.h -- data structures for shell variables. */
 
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -23,6 +23,7 @@
 
 #include "stdc.h"
 #include "array.h"
+#include "assoc.h"
 
 /* Shell variables and functions are stored in hash tables. */
 #include "hashlib.h"
@@ -61,7 +62,7 @@ typedef struct var_context {
 /* What a shell variable looks like. */
 
 typedef struct variable *sh_var_value_func_t __P((struct variable *));
-typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t));
+typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t, char *));
 
 /* For the future */
 union _value {
@@ -108,8 +109,11 @@ typedef struct _vlist {
 #define att_local      0x0000020       /* variable is local to a function */
 #define att_assoc      0x0000040       /* variable is an associative array */
 #define att_trace      0x0000080       /* function is traced with DEBUG trap */
+#define att_uppercase  0x0000100       /* word converted to uppercase on assignment */
+#define att_lowercase  0x0000200       /* word converted to lowercase on assignment */
+#define att_capcase    0x0000400       /* word capitalized  on assignment */
 
-#define user_attrs     (att_exported|att_readonly|att_integer|att_local|att_trace)
+#define user_attrs     (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase)
 
 #define attmask_user   0x0000fff
 
@@ -119,6 +123,7 @@ typedef struct _vlist {
 #define att_noassign   0x0004000       /* assignment not allowed */
 #define att_imported   0x0008000       /* came from environment */
 #define att_special    0x0010000       /* requires special handling */
+#define att_nofree     0x0020000       /* do not free value on unset */
 
 #define        attmask_int     0x00ff000
 
@@ -136,12 +141,16 @@ typedef struct _vlist {
 #define local_p(var)           ((((var)->attributes) & (att_local)))
 #define assoc_p(var)           ((((var)->attributes) & (att_assoc)))
 #define trace_p(var)           ((((var)->attributes) & (att_trace)))
+#define uppercase_p(var)       ((((var)->attributes) & (att_uppercase)))
+#define lowercase_p(var)       ((((var)->attributes) & (att_lowercase)))
+#define capcase_p(var)         ((((var)->attributes) & (att_capcase)))
 
 #define invisible_p(var)       ((((var)->attributes) & (att_invisible)))
 #define non_unsettable_p(var)  ((((var)->attributes) & (att_nounset)))
 #define noassign_p(var)                ((((var)->attributes) & (att_noassign)))
 #define imported_p(var)                ((((var)->attributes) & (att_imported)))
 #define specialvar_p(var)      ((((var)->attributes) & (att_special)))
+#define nofree_p(var)          ((((var)->attributes) & (att_nofree)))
 
 #define tempvar_p(var)         ((((var)->attributes) & (att_tempvar)))
 
@@ -149,6 +158,7 @@ typedef struct _vlist {
 #define value_cell(var)                ((var)->value)
 #define function_cell(var)     (COMMAND *)((var)->value)
 #define array_cell(var)                (ARRAY *)((var)->value)
+#define assoc_cell(var)                (HASH_TABLE *)((var)->value)
 
 #define var_isnull(var)                ((var)->value == 0)
 #define var_isset(var)         ((var)->value != 0)
@@ -157,6 +167,7 @@ typedef struct _vlist {
 #define var_setvalue(var, str) ((var)->value = (str))
 #define var_setfunc(var, func) ((var)->value = (char *)(func))
 #define var_setarray(var, arr) ((var)->value = (char *)(arr))
+#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
 
 /* Make VAR be auto-exported. */
 #define set_auto_export(var) \
@@ -316,6 +327,9 @@ extern void print_var_function __P((SHELL_VAR *));
 extern SHELL_VAR *make_new_array_variable __P((char *));
 extern SHELL_VAR *make_local_array_variable __P((char *));
 
+extern SHELL_VAR *make_new_assoc_variable __P((char *));
+extern SHELL_VAR *make_local_assoc_variable __P((char *));
+
 extern void set_pipestatus_array __P((int *, int));
 #endif
 
@@ -325,6 +339,10 @@ extern void set_pipestatus_from_exit __P((int));
    is one of the special ones where something special happens. */
 extern void stupidly_hack_special_variables __P((char *));
 
+/* Reinitialize some special variables that have external effects upon unset
+   when the shell reinitializes itself. */
+extern void reinit_special_variables __P((void));
+
 extern int get_random_number __P((void));
 
 /* The `special variable' functions that get called when a particular