]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - parse.y
Bash-5.2-rc4 release
[thirdparty/bash.git] / parse.y
diff --git a/parse.y b/parse.y
index 3ff87bccc06c1f9ace9cdf2d0dd02ab506e36f9c..d887eecb6cddba6877952d6f60944e913e626b1b 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -1,6 +1,6 @@
 /* parse.y - Yacc grammar for bash. */
 
-/* Copyright (C) 1989-2017 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2022 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -71,7 +71,7 @@
 #if defined (JOB_CONTROL)
 #  include "jobs.h"
 #else
-extern int cleanup_dead_jobs __P((void));
+extern int cleanup_dead_jobs PARAMS((void));
 #endif /* JOB_CONTROL */
 
 #if defined (ALIAS)
@@ -114,11 +114,24 @@ typedef void *alias_t;
 #  define MBTEST(x)    ((x))
 #endif
 
+#define EXTEND_SHELL_INPUT_LINE_PROPERTY() \
+do { \
+    if (shell_input_line_len + 2 > shell_input_line_propsize) \
+      { \
+       shell_input_line_propsize = shell_input_line_len + 2; \
+       shell_input_line_property = (char *)xrealloc (shell_input_line_property, \
+                                   shell_input_line_propsize); \
+      } \
+} while (0)
+
 #if defined (EXTENDED_GLOB)
 extern int extended_glob;
 #endif
 
+#if defined (TRANSLATABLE_STRINGS)
 extern int dump_translatable_strings, dump_po_strings;
+extern int singlequote_translations;
+#endif /* TRANSLATABLE_STRINGS */
 
 #if !defined (errno)
 extern int errno;
@@ -131,90 +144,87 @@ extern int errno;
 /* **************************************************************** */
 
 #ifdef DEBUG
-static void debug_parser __P((int));
+static void debug_parser PARAMS((int));
 #endif
 
-static int yy_getc __P((void));
-static int yy_ungetc __P((int));
+static int yy_getc PARAMS((void));
+static int yy_ungetc PARAMS((int));
 
 #if defined (READLINE)
-static int yy_readline_get __P((void));
-static int yy_readline_unget __P((int));
+static int yy_readline_get PARAMS((void));
+static int yy_readline_unget PARAMS((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 yy_string_get PARAMS((void));
+static int yy_string_unget PARAMS((int));
+static int yy_stream_get PARAMS((void));
+static int yy_stream_unget PARAMS((int));
 
-static int shell_getc __P((int));
-static void shell_ungetc __P((int));
-static void discard_until __P((int));
+static int shell_getc PARAMS((int));
+static void shell_ungetc PARAMS((int));
+static void discard_until PARAMS((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 void push_string PARAMS((char *, int, alias_t *));
+static void pop_string PARAMS((void));
+static void free_string_list PARAMS((void));
 
-static char *read_a_line __P((int));
+static char *read_a_line PARAMS((int));
 
-static int reserved_word_acceptable __P((int));
-static int yylex __P((void));
+static int reserved_word_acceptable PARAMS((int));
+static int yylex PARAMS((void));
 
-static void push_heredoc __P((REDIRECT *));
-static char *mk_alexpansion __P((char *));
-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));
+static void push_heredoc PARAMS((REDIRECT *));
+static char *mk_alexpansion PARAMS((char *));
+static int alias_expand_token PARAMS((char *));
+static int time_command_acceptable PARAMS((void));
+static int special_case_tokens PARAMS((char *));
+static int read_token PARAMS((int));
+static char *parse_matched_pair PARAMS((int, int, int, int *, int));
+static char *parse_comsub PARAMS((int, int, int, int *, int));
 #if defined (ARRAY_VARS)
-static char *parse_compound_assignment __P((int *));
+static char *parse_compound_assignment PARAMS((int *));
 #endif
 #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
-static int parse_dparen __P((int));
-static int parse_arith_cmd __P((char **, int));
+static int parse_dparen PARAMS((int));
+static int parse_arith_cmd PARAMS((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));
+static void cond_error PARAMS((void));
+static COND_COM *cond_expr PARAMS((void));
+static COND_COM *cond_or PARAMS((void));
+static COND_COM *cond_and PARAMS((void));
+static COND_COM *cond_term PARAMS((void));
+static int cond_skip_newlines PARAMS((void));
+static COMMAND *parse_cond_command PARAMS((void));
 #endif
 #if defined (ARRAY_VARS)
-static int token_is_assignment __P((char *, int));
-static int token_is_ident __P((char *, int));
+static int token_is_assignment PARAMS((char *, int));
+static int token_is_ident PARAMS((char *, int));
 #endif
-static int read_token_word __P((int));
-static void discard_parser_constructs __P((int));
+static int read_token_word PARAMS((int));
+static void discard_parser_constructs PARAMS((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 char *error_token_from_token PARAMS((int));
+static char *error_token_from_text PARAMS((void));
+static void print_offending_line PARAMS((void));
+static void report_syntax_error PARAMS((char *));
 
-static void handle_eof_input_unit __P((void));
-static void prompt_again __P((void));
+static void handle_eof_input_unit PARAMS((void));
+static void prompt_again PARAMS((int));
 #if 0
-static void reset_readline_prompt __P((void));
+static void reset_readline_prompt PARAMS((void));
 #endif
-static void print_prompt __P((void));
+static void print_prompt PARAMS((void));
 
 #if defined (HANDLE_MULTIBYTE)
-static void set_line_mbstate __P((void));
+static void set_line_mbstate PARAMS((void));
 static char *shell_input_line_property = NULL;
 static size_t shell_input_line_propsize = 0;
 #else
 #  define set_line_mbstate()
 #endif
 
-extern int yyerror __P((const char *));
+extern int yyerror PARAMS((const char *));
 
 #ifdef DEBUG
 extern int yydebug;
@@ -353,16 +363,20 @@ static FILE *yyerrstream;
 %token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
 %token GREATER_BAR BAR_AND
 
+/* Special; never created by yylex; only set by parse_comsub and xparse_dolparen */
+%token DOLPAREN
+
 /* 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> 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> coproc
+%type <command> comsub
 %type <command> function_def function_body if_command elif_clause subshell
 %type <redirect> redirection redirection_list
 %type <element> simple_command_element
@@ -389,6 +403,14 @@ inputunit: simple_list simple_list_terminator
                            parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
+       |       comsub
+                       {
+                         /* This is special; look at the production and how
+                            parse_comsub sets token_to_read */
+                         global_command = $1;
+                         eof_encountered = 0;
+                         YYACCEPT;
+                       }
        |       '\n'
                        {
                          /* Case of regular command, but not a very
@@ -420,9 +442,9 @@ inputunit:  simple_list simple_list_terminator
                          global_command = (COMMAND *)NULL;
                          if (last_command_exit_value == 0)
                            last_command_exit_value = EX_BADUSAGE;      /* force error return */
-                         handle_eof_input_unit ();
                          if (interactive && parse_and_execute_level == 0)
                            {
+                             handle_eof_input_unit ();
                              YYACCEPT;
                            }
                          else
@@ -865,32 +887,32 @@ arith_for_command:        FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_
                                }
        ;
 
-select_command:        SELECT WORD newline_list DO list DONE
+select_command:        SELECT WORD newline_list DO compound_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 '}'
+       |       SELECT WORD newline_list '{' compound_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
+       |       SELECT WORD ';' newline_list DO compound_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 '}'
+       |       SELECT WORD ';' newline_list '{' compound_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
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list DO compound_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 '}'
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
                        {
                          $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
                          if (word_top > 0) word_top--;
@@ -926,12 +948,12 @@ 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 function_body
+                       { $$ = make_function_def ($2, $3, function_dstart, function_bstart); }
+       |       FUNCTION WORD '\n' newline_list function_body
+                       { $$ = make_function_def ($2, $5, function_dstart, function_bstart); }
        ;
 
 function_body: shell_command
@@ -974,6 +996,16 @@ subshell:  '(' compound_list ')'
                        }
        ;
 
+comsub:                DOLPAREN compound_list ')'
+                       {
+                         $$ = $2;
+                       }
+       |       DOLPAREN newline_list ')'
+                       {
+                         $$ = (COMMAND *)NULL;
+                       }
+       ;
+
 coproc:                COPROC shell_command
                        {
                          $$ = make_coproc_command ("COPROC", $2);
@@ -1094,15 +1126,12 @@ pattern:        WORD
    It must end with a newline or semicolon.
    Lists are used within commands such as if, for, while.  */
 
-list:          newline_list list0
+compound_list: newline_list list0
                        {
                          $$ = $2;
-                         if (need_here_doc)
+                         if (need_here_doc && last_read_token == '\n')
                            gather_here_documents ();
                         }
-       ;
-
-compound_list: list
        |       newline_list list1
                        {
                          $$ = $2;
@@ -1135,7 +1164,12 @@ list1:           list1 AND_AND newline_list list1
        |       list1 ';' newline_list list1
                        { $$ = command_connect ($1, $4, ';'); }
        |       list1 '\n' newline_list list1
-                       { $$ = command_connect ($1, $4, ';'); }
+                       {
+                         if (parser_state & PST_CMDSUBST)
+                           $$ = command_connect ($1, $4, '\n');
+                         else
+                           $$ = command_connect ($1, $4, ';');
+                       }
        |       pipeline_command
                        { $$ = $1; }
        ;
@@ -1166,12 +1200,14 @@ simple_list:    simple_list1
                        {
                          $$ = $1;
                          if (need_here_doc)
-                           gather_here_documents ();
+                           gather_here_documents ();   /* XXX */
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
+INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 -> simple_list"));
                              global_command = $1;
                              eof_encountered = 0;
-                             rewind_input_string ();
+                             if (bash_input.type == st_string)
+                               rewind_input_string ();
                              YYACCEPT;
                            }
                        }
@@ -1182,12 +1218,14 @@ simple_list:    simple_list1
                          else
                            $$ = command_connect ($1, (COMMAND *)NULL, '&');
                          if (need_here_doc)
-                           gather_here_documents ();
+                           gather_here_documents (); /* XXX */
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
+INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 '&' -> simple_list"));
                              global_command = $1;
                              eof_encountered = 0;
-                             rewind_input_string ();
+                             if (bash_input.type == st_string)
+                               rewind_input_string ();
                              YYACCEPT;
                            }
                        }
@@ -1195,12 +1233,14 @@ simple_list:    simple_list1
                        {
                          $$ = $1;
                          if (need_here_doc)
-                           gather_here_documents ();
+                           gather_here_documents ();   /* XXX */
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
+INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 ';' -> simple_list"));
                              global_command = $1;
                              eof_encountered = 0;
-                             rewind_input_string ();
+                             if (bash_input.type == st_string)
+                               rewind_input_string ();
                              YYACCEPT;
                            }
                        }
@@ -1314,6 +1354,8 @@ timespec: TIME
                        { $$ = CMD_TIME_PIPELINE; }
        |       TIME TIMEOPT
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        |       TIME TIMEOPT TIMEIGN
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        ;
@@ -1479,7 +1521,6 @@ yy_readline_get ()
       old_sigint = IMPOSSIBLE_TRAP_HANDLER;
       if (signal_is_ignored (SIGINT) == 0)
        {
-         /* interrupt_immediately++; */
          old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
        }
 
@@ -1490,7 +1531,6 @@ yy_readline_get ()
       CHECK_TERMSIG;
       if (signal_is_ignored (SIGINT) == 0)
        {
-         /* interrupt_immediately--; */
          if (old_sigint != IMPOSSIBLE_TRAP_HANDLER)
            set_signal_handler (SIGINT, old_sigint);
        }
@@ -1546,6 +1586,16 @@ with_input_from_stdin ()
     }
 }
 
+/* Will we be collecting another input line and printing a prompt? This uses
+   different conditions than SHOULD_PROMPT(), since readline allows a user to
+   embed a newline in the middle of the line it collects, which the parser
+   will interpret as a line break and command delimiter. */
+int
+parser_will_prompt ()
+{
+  return (current_readline_line == 0 || current_readline_line[current_readline_line_index] == 0);
+}
+  
 #else  /* !READLINE */
 
 void
@@ -1604,7 +1654,7 @@ with_input_from_string (string, name)
    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. */
-static void
+void
 rewind_input_string ()
 {
   int xchars;
@@ -1616,7 +1666,7 @@ rewind_input_string ()
     xchars++;
 
   /* 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
+     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. */
@@ -1634,9 +1684,9 @@ rewind_input_string ()
 
 /* 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. */
+   installs most of its signal handlers without the SA_RESTART flag, some
+   signals received during a read(2) will not cause the read to be restarted.
+   We will need to restart it ourselves. */
 
 static int
 yy_stream_get ()
@@ -1813,8 +1863,6 @@ restore_token_state (ts)
  *     everything between a `;;' and the next `)' or `esac'
  */
 
-#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
-
 #define END_OF_ALIAS 0
 
 /*
@@ -1829,6 +1877,7 @@ restore_token_state (ts)
 #define PSH_ALIAS      0x01
 #define PSH_DPAREN     0x02
 #define PSH_SOURCE     0x04
+#define PSH_ARRAY      0x08
 
 typedef struct string_saver {
   struct string_saver *next;
@@ -1837,7 +1886,7 @@ typedef struct string_saver {
 #if defined (ALIAS)
   alias_t *expander;   /* alias that caused this line to be pushed. */
 #endif
-  size_t saved_line_size, saved_line_index;
+  size_t saved_line_size, saved_line_index, saved_line_len;
   int saved_line_terminator;
   int flags;
 } STRING_SAVER;
@@ -1863,6 +1912,7 @@ push_string (s, expand, ap)
   temp->expand_alias = expand;
   temp->saved_line = shell_input_line;
   temp->saved_line_size = shell_input_line_size;
+  temp->saved_line_len = shell_input_line_len;
   temp->saved_line_index = shell_input_line_index;
   temp->saved_line_terminator = shell_input_line_terminator;
   temp->flags = 0;
@@ -1880,7 +1930,7 @@ push_string (s, expand, ap)
 #endif
 
   shell_input_line = s;
-  shell_input_line_size = STRLEN (s);
+  shell_input_line_size = shell_input_line_len = STRLEN (s);
   shell_input_line_index = 0;
   shell_input_line_terminator = '\0';
 #if 0
@@ -1905,12 +1955,15 @@ pop_string ()
   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_len = pushed_string_list->saved_line_len;
   shell_input_line_terminator = pushed_string_list->saved_line_terminator;
 
+#if defined (ALIAS)
   if (pushed_string_list->expand_alias)
     parser_state |= PST_ALEXPNEXT;
   else
     parser_state &= ~PST_ALEXPNEXT;
+#endif
 
   t = pushed_string_list;
   pushed_string_list = pushed_string_list->next;
@@ -1944,8 +1997,6 @@ free_string_list ()
   pushed_string_list = (STRING_SAVER *)NULL;
 }
 
-#endif /* ALIAS || DPAREN_ARITHMETIC */
-
 void
 free_pushed_string_input ()
 {
@@ -2035,12 +2086,7 @@ read_a_line (remove_quoted_newline)
 
       /* Ignore null bytes in input. */
       if (c == 0)
-       {
-#if 0
-         internal_warning ("read_a_line: ignored null byte in input");
-#endif
-         continue;
-       }
+       continue;
 
       /* If there is no more input, then we return NULL. */
       if (c == EOF)
@@ -2114,8 +2160,8 @@ read_secondary_line (remove_quoted_newline)
   int n, c;
 
   prompt_string_pointer = &ps2_prompt;
-  if (SHOULD_PROMPT())
-    prompt_again ();
+  if (SHOULD_PROMPT ())
+    prompt_again (0);
   ret = read_a_line (remove_quoted_newline);
 #if defined (HISTORY)
   if (ret && remember_on_history && (parser_state & PST_HEREDOC))
@@ -2260,6 +2306,8 @@ static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
    shell_ungetc when we're at the start of a line. */
 static int eol_ungetc_lookahead = 0;
 
+static int unquoted_backslash = 0;
+
 static int
 shell_getc (remove_quoted_newline)
      int remove_quoted_newline;
@@ -2313,7 +2361,7 @@ shell_getc (remove_quoted_newline)
       i = truncating = 0;
       shell_input_line_terminator = 0;
 
-      /* If the shell is interatctive, but not currently printing a prompt
+      /* If the shell is interactive, 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. */
@@ -2349,9 +2397,6 @@ shell_getc (remove_quoted_newline)
 
          if (c == '\0')
            {
-#if 0
-             internal_warning ("shell_getc: ignored null byte in input");
-#endif
              /* If we get EOS while parsing a string, treat it as EOF so we
                 don't just keep looping. Happens very rarely */
              if (bash_input.type == st_string)
@@ -2498,7 +2543,7 @@ shell_getc (remove_quoted_newline)
          shell_input_line_size = 0;
          prompt_string_pointer = &current_prompt_string;
          if (SHOULD_PROMPT ())
-           prompt_again ();
+           prompt_again (0);
          goto restart_read;
        }
 
@@ -2520,15 +2565,27 @@ shell_getc (remove_quoted_newline)
            shell_input_line[shell_input_line_len] = '\n';
          shell_input_line[shell_input_line_len + 1] = '\0';
 
-         set_line_mbstate ();
+#if defined (HANDLE_MULTIBYTE)
+         /* This is kind of an abstraction violation, but there's no need to
+            go through the entire shell_input_line again with a call to
+            set_line_mbstate(). */
+         EXTEND_SHELL_INPUT_LINE_PROPERTY();
+         shell_input_line_property[shell_input_line_len] = 1;
+#endif
        }
     }
 
 next_alias_char:
+  if (shell_input_line_index == 0)
+    unquoted_backslash = 0;
+
   uc = shell_input_line[shell_input_line_index];
 
   if (uc)
-    shell_input_line_index++;
+    {
+      unquoted_backslash = unquoted_backslash == 0 && 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
@@ -2557,32 +2614,53 @@ next_alias_char:
   if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE &&
       pushed_string_list->flags != PSH_DPAREN &&
       (parser_state & PST_COMMENT) == 0 &&
+      (parser_state & PST_ENDALIAS) == 0 &&    /* only once */
       shell_input_line_index > 0 &&
-      shell_input_line[shell_input_line_index-1] != ' ' &&
+      shellblank (shell_input_line[shell_input_line_index-1]) == 0 &&
       shell_input_line[shell_input_line_index-1] != '\n' &&
+      unquoted_backslash == 0 &&
       shellmeta (shell_input_line[shell_input_line_index-1]) == 0 &&
       (current_delimiter (dstack) != '\'' && current_delimiter (dstack) != '"'))
     {
+      parser_state |= PST_ENDALIAS;
+      /* We need to do this to make sure last_shell_getc_is_singlebyte returns
+        true, since we are returning a single-byte space. */
+      if (shell_input_line_index == shell_input_line_len && last_shell_getc_is_singlebyte == 0)
+       {
+#if 0
+         EXTEND_SHELL_INPUT_LINE_PROPERTY();
+         shell_input_line_property[shell_input_line_len++] = 1;
+         /* extend shell_input_line to accommodate the shell_ungetc that
+            read_token_word() will perform, since we're extending the index */
+         RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, 2, shell_input_line_size, 16);
+          shell_input_line[++shell_input_line_index] = '\0';   /* XXX */
+#else
+         shell_input_line_property[shell_input_line_index - 1] = 1;
+#endif
+       }
       return ' ';      /* END_ALIAS */
     }
 #endif
 
 pop_alias:
-  /* This case works for PSH_DPAREN as well */
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+  /* This case works for PSH_DPAREN as well as the shell_ungets() case that uses
+     push_string */
   if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE)
     {
+      parser_state &= ~PST_ENDALIAS;
       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 ();
+         prompt_again (0);
        line_number++;
+
        /* What do we do here if we're expanding an alias whose definition
           includes an escaped newline?  If that's the last character in the
           alias expansion, we just pop the pushed string list (recall that
@@ -2648,6 +2726,60 @@ shell_ungetc (c)
     eol_ungetc_lookahead = c;
 }
 
+/* Push S back into shell_input_line; updating shell_input_line_index */
+void
+shell_ungets (s)
+     char *s;
+{
+  size_t slen, chars_left;
+
+  slen = strlen (s);
+
+  if (shell_input_line[shell_input_line_index] == '\0')
+    {
+      /* Easy, just overwrite shell_input_line. This is preferred because it
+        saves on set_line_mbstate () and other overhead like push_string */
+      if (shell_input_line_size <= slen)
+       RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, slen + 1, shell_input_line_size, 64);
+      strcpy (shell_input_line, s);
+      shell_input_line_index = 0;
+      shell_input_line_len = slen;
+      shell_input_line_terminator = 0;
+    }
+  else if (shell_input_line_index >= slen)
+    {
+      /* Just as easy, just back up shell_input_line_index, but it means we
+        will re-process some characters in set_line_mbstate(). Need to
+        watch pushing back newlines here. */
+      while (slen > 0)
+        shell_input_line[--shell_input_line_index] = s[--slen];
+    }
+  else if (s[slen - 1] == '\n')
+    {
+      push_string (savestring (s), 0, (alias_t *)NULL);
+      /* push_string does set_line_mbstate () */
+      return;
+    }
+  else
+    {
+      /* Harder case: pushing back input string that's longer than what we've
+        consumed from shell_input_line so far. */
+      INTERNAL_DEBUG (("shell_ungets: not at end of shell_input_line"));
+
+      chars_left = shell_input_line_len - shell_input_line_index;
+      if (shell_input_line_size <= (slen + chars_left))
+       RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, chars_left + slen + 1, shell_input_line_size, 64);
+      memmove (shell_input_line + slen, shell_input_line + shell_input_line_index, shell_input_line_len - shell_input_line_index);
+      strcpy (shell_input_line, s);
+      shell_input_line_index = 0;
+      shell_input_line_len = strlen (shell_input_line);        /* chars_left + slen? */
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  set_line_mbstate (); /* XXX */
+#endif
+}
+
 char *
 parser_remaining_input ()
 {
@@ -2721,7 +2853,7 @@ push_token (x)
 static char *token = (char *)NULL;
 
 /* Current size of the token buffer. */
-static int token_buffer_size;
+static size_t token_buffer_size;
 
 /* Command to read_token () explaining what we want it to do. */
 #define READ 0
@@ -2749,7 +2881,7 @@ yylex ()
       /* 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 ();
+       prompt_again (0);
     }
 
   two_tokens_ago = token_before_that;
@@ -2759,11 +2891,16 @@ yylex ()
 
   if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
     {
-      current_token = yacc_EOF;
-      if (bash_input.type == st_string)
-       rewind_input_string ();
+      /* placeholder for any special handling. */
+      return (current_token);
     }
-  parser_state &= ~PST_EOFTOKEN;       /* ??? */
+
+  if (current_token < 0)
+#if defined (YYERRCODE) && !defined (YYUNDEF)
+    current_token = YYERRCODE;
+#else
+    current_token = YYerror;
+#endif
 
   return (current_token);
 }
@@ -2854,9 +2991,12 @@ static int open_brace_count;
                break; \
              if ((parser_state & PST_CASEPAT) && last_read_token == '|' && word_token_alist[i].token == ESAC) \
                break; /* Posix grammar rule 4 */ \
-             if (word_token_alist[i].token == ESAC) \
+             if ((parser_state & PST_CASEPAT) && last_read_token == '(' && word_token_alist[i].token == ESAC) /*)*/ \
+               break; /* phantom Posix grammar rule 4 */ \
+             if (word_token_alist[i].token == ESAC) { \
                parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
-             else if (word_token_alist[i].token == CASE) \
+               esacs_needed_count--; \
+             } 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); \
@@ -2916,8 +3056,12 @@ alias_expand_token (tokstr)
   char *expanded;
   alias_t *ap;
 
+#if 0
   if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
        (parser_state & PST_CASEPAT) == 0)
+#else
+  if ((parser_state & PST_ALEXPNEXT) || assignment_acceptable (last_read_token))
+#endif
     {
       ap = find_alias (tokstr);
 
@@ -3017,7 +3161,7 @@ time_command_acceptable ()
        `}' is recognized if there is an unclosed `{' present.
 
        `-p' is returned as TIMEOPT if the last read token was TIME.
-       `--' is returned as TIMEIGN if the last read token was TIMEOPT.
+       `--' is returned as TIMEIGN if the last read token was TIME or TIMEOPT.
 
        ']]' is returned as COND_END if the parser is currently parsing
        a conditional expression ((parser_state & PST_CONDEXPR) != 0)
@@ -3133,6 +3277,9 @@ special_case_tokens (tokstr)
   /* Handle -p after `time'. */
   if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
     return (TIMEOPT);
+  /* Handle -- after `time'. */
+  if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
   /* Handle -- after `time -p'. */
   if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
     return (TIMEIGN);
@@ -3156,7 +3303,7 @@ reset_parser ()
 
 #if defined (EXTENDED_GLOB)
   /* Reset to global value of extended glob */
-  if (parser_state & PST_EXTPAT)
+  if (parser_state & (PST_EXTPAT|PST_CMDSUBST))
     extended_glob = global_extglob;
 #endif
 
@@ -3181,6 +3328,11 @@ reset_parser ()
 
   eol_ungetc_lookahead = 0;
 
+  /* added post-bash-5.1 */
+  need_here_doc = 0;
+  redir_stack[0] = 0;
+  esacs_needed_count = expecting_in_token = 0;
+
   current_token = '\n';                /* XXX */
   last_read_token = '\n';
   token_to_read = '\n';
@@ -3258,9 +3410,7 @@ read_token (command)
      we are eval'ing a string that is an incomplete command), return EOF */
   if (character == '\0' && bash_input.type == st_string && expanding_alias() == 0)
     {
-#if defined (DEBUG)
-itrace("shell_getc: bash_input.location.string = `%s'", bash_input.location.string);
-#endif
+      INTERNAL_DEBUG (("shell_getc: bash_input.location.string = `%s'", bash_input.location.string));
       EOF_Reached = 1;
       return (yacc_EOF);
     }
@@ -3275,7 +3425,7 @@ itrace("shell_getc: bash_input.location.string = `%s'", bash_input.location.stri
       character = '\n';        /* this will take the next if statement and return. */
     }
 
-  if (character == '\n')
+  if MBTEST(character == '\n')
     {
       /* If we're about to return an unquoted newline, we can go and collect
         the text of any pending here document. */
@@ -3295,7 +3445,7 @@ itrace("shell_getc: bash_input.location.string = `%s'", bash_input.location.stri
     goto tokword;
 
   /* Shell meta-characters. */
-  if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
+  if MBTEST(shellmeta (character))
     {
 #if defined (ALIAS)
       /* Turn off alias tokenization iff this character sequence would
@@ -3316,7 +3466,7 @@ itrace("shell_getc: bash_input.location.string = `%s'", bash_input.location.stri
       else
        peek_char = shell_getc (1);
 
-      if (character == peek_char)
+      if MBTEST(character == peek_char)
        {
          switch (character)
            {
@@ -3427,7 +3577,7 @@ itrace("shell_getc: bash_input.location.string = `%s'", bash_input.location.stri
 #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 != '(')) /*)*/
+      if MBTEST((character != '>' && character != '<') || peek_char != '(') /*)*/
 #endif /* PROCESS_SUBSTITUTION */
        return (character);
     }
@@ -3475,6 +3625,9 @@ tokword:
 #define LEX_QUOTEDDOC  0x0400          /* here doc with quoted delim */
 #define LEX_INWORD     0x0800
 #define LEX_GTLT       0x1000
+#define LEX_CKESAC     0x2000          /* check esac after in -- for later */
+#define LEX_CASEWD     0x4000          /* word after case */
+#define LEX_PATLIST    0x8000          /* case statement pattern list */
 
 #define COMSUB_META(ch)                ((ch) == ';' || (ch) == '&' || (ch) == '|')
 
@@ -3542,8 +3695,8 @@ parse_matched_pair (qc, open, close, lenp, flags)
        }
 
       /* Possible reprompting. */
-      if (ch == '\n' && SHOULD_PROMPT ())
-       prompt_again ();
+      if MBTEST(ch == '\n' && SHOULD_PROMPT ())
+       prompt_again (0);
 
       /* Don't bother counting parens or doing anything else if in a comment
         or part of a case statement */
@@ -3553,7 +3706,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
          RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
          ret[retind++] = ch;
 
-         if (ch == '\n')
+         if MBTEST(ch == '\n')
            tflags &= ~LEX_INCOMMENT;
 
          continue;
@@ -3568,7 +3721,8 @@ parse_matched_pair (qc, open, close, lenp, flags)
       if (tflags & LEX_PASSNEXT)               /* last char was backslash */
        {
          tflags &= ~LEX_PASSNEXT;
-         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+         /* XXX - PST_NOEXPAND? */
+         if MBTEST(qc != '\'' && ch == '\n')   /* double-quoted \<newline> disappears. */
            {
              if (retind > 0)
                retind--;       /* swallow previously-added backslash */
@@ -3677,9 +3831,10 @@ parse_matched_pair (qc, open, close, lenp, flags)
              pop_delimiter (dstack);
              CHECK_NESTRET_ERROR ();
 
-             if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0 || dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2))
                {
                  /* Translate $'...' here. */
+                 /* PST_NOEXPAND */
                  ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
                  free (nestret);
 
@@ -3688,12 +3843,22 @@ parse_matched_pair (qc, open, close, lenp, flags)
                     make sure we single-quote the results of the ansi
                     expansion because quote removal should remove them later */
                  /* FLAG POSIX INTERP 221 */
-                 if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2) && (flags & P_DOLBRACE))
+                 if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_QUOTE) && (flags & P_DOLBRACE))
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+#if 0 /* TAG:bash-5.3 */
+                 /* This single-quotes PARAM in ${PARAM OP WORD} when PARAM
+                    contains a $'...' even when extended_quote is set. */
+                 else if ((rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_PARAM) && (flags & P_DOLBRACE))
                    {
                      nestret = sh_single_quote (ttrans);
                      free (ttrans);
                      nestlen = strlen (nestret);
                    }
+#endif
                  else if ((rflags & P_DQUOTE) == 0)
                    {
                      nestret = sh_single_quote (ttrans);
@@ -3702,22 +3867,41 @@ parse_matched_pair (qc, open, close, lenp, flags)
                    }
                  else
                    {
+                     /* Should we quote CTLESC here? */
                      nestret = ttrans;
                      nestlen = ttranslen;
                    }
                  retind -= 2;          /* back up before the $' */
                }
+#if defined (TRANSLATABLE_STRINGS)
              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);
+                 /* PST_NOEXPAND */
+                 ttrans = locale_expand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
                  free (nestret);
 
-                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 /* If we're supposed to single-quote translated strings,
+                    check whether the translated result is different from
+                    the original and single-quote the string if it is. */
+                 if (singlequote_translations &&
+                       ((nestlen - 1) != ttranslen || STREQN (nestret, ttrans, ttranslen) == 0))
+                   {
+                     if ((rflags & P_DQUOTE) == 0)
+                       nestret = sh_single_quote (ttrans);
+                     else if ((rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2) && (flags & P_DOLBRACE))
+                       nestret = sh_single_quote (ttrans);
+                     else
+                       /* single quotes aren't special, use backslash instead */
+                       nestret = sh_backslash_quote_for_double_quotes (ttrans, 0);
+                   }
+                 else
+                   nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
                  free (ttrans);
-                 nestlen = ttranslen + 2;
+                 nestlen = strlen (nestret);
                  retind -= 2;          /* back up before the $" */
                }
+#endif /* TRANSLATABLE_STRINGS */
 
              APPEND_NESTRET ();
              FREE (nestret);
@@ -3819,11 +4003,26 @@ dump_tflags (flags)
       f &= ~LEX_CKCASE;
       fprintf (stderr, "LEX_CKCASE%s", f ? "|" : "");
     }
+  if (f & LEX_CKESAC)
+    {
+      f &= ~LEX_CKESAC;
+      fprintf (stderr, "LEX_CKESAC%s", f ? "|" : "");
+    }
   if (f & LEX_INCASE)
     {
       f &= ~LEX_INCASE;
       fprintf (stderr, "LEX_INCASE%s", f ? "|" : "");
     }
+  if (f & LEX_CASEWD)
+    {
+      f &= ~LEX_CASEWD;
+      fprintf (stderr, "LEX_CASEWD%s", f ? "|" : "");
+    }
+  if (f & LEX_PATLIST)
+    {
+      f &= ~LEX_PATLIST;
+      fprintf (stderr, "LEX_PATLIST%s", f ? "|" : "");
+    }
   if (f & LEX_INHEREDOC)
     {
       f &= ~LEX_INHEREDOC;
@@ -3855,522 +4054,172 @@ dump_tflags (flags)
 }
 #endif
 
-/* 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. */
+/* Parse a $(...) command substitution.  This reads input from the current
+   input stream. */
 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_wlen, lex_firstind;
-  int nestlen, ttranslen, start_lineno;
-  char *ret, *nestret, *ttrans, *heredelim;
-  int retind, retsize, rflags, hdlen;
+  int peekc, r;
+  int start_lineno, local_extglob, was_extpat;
+  char *ret, *tcmd;
+  int retlen;
+  sh_parser_state_t ps;
+  STRING_SAVER *saved_strings;
+  COMMAND *saved_global, *parsed_command;
 
   /* Posix interp 217 says arithmetic expressions have precedence, so
      assume $(( introduces arithmetic expansion and parse accordingly. */
-  peekc = shell_getc (0);
-  shell_ungetc (peekc);
-  if (peekc == '(')
-    return (parse_matched_pair (qc, open, close, lenp, 0));
+  if (open == '(')             /*)*/
+    {
+      peekc = shell_getc (1);
+      shell_ungetc (peekc);
+      if (peekc == '(')                /*)*/
+       return (parse_matched_pair (qc, open, close, lenp, 0));
+    }
 
 /*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;
 
+  /*debug_parser(1);*/
   start_lineno = line_number;
-  lex_rwlen = lex_wlen = 0;
 
-  heredelim = 0;
-  lex_firstind = -1;
+  save_parser_state (&ps);
 
-  while (count)
+  was_extpat = (parser_state & PST_EXTPAT);
+
+  /* State flags we don't want to persist into command substitutions. */
+  parser_state &= ~(PST_REGEXP|PST_EXTPAT|PST_CONDCMD|PST_CONDEXPR|PST_COMPASSIGN);
+  /* Could do PST_CASESTMT too, but that also affects history. Setting
+     expecting_in_token below should take care of the parsing requirements.
+     Unsetting PST_REDIRLIST isn't strictly necessary because of how we set
+     token_to_read below, but we do it anyway. */
+  parser_state &= ~(PST_CASEPAT|PST_ALEXPNEXT|PST_SUBSHELL|PST_REDIRLIST);
+  /* State flags we want to set for this run through the parser. */
+  parser_state |= PST_CMDSUBST|PST_EOFTOKEN|PST_NOEXPAND;
+
+  /* leave pushed_string_list alone, since we might need to consume characters
+     from it to satisfy this command substitution (in some perverse case). */
+  shell_eof_token = close;
+
+  saved_global = global_command;               /* might not be necessary */
+  global_command = (COMMAND *)NULL;
+
+  /* These are reset by reset_parser() */
+  need_here_doc = 0;
+  esacs_needed_count = expecting_in_token = 0;
+
+  /* We want to expand aliases on this pass if we're in posix mode, since the
+     standard says you have to take aliases into account when looking for the
+     terminating right paren. Otherwise, we defer until execution time for
+     backwards compatibility. */
+  if (expand_aliases)
+    expand_aliases = posixly_correct != 0;
+#if defined (EXTENDED_GLOB)
+  /* If (parser_state & PST_EXTPAT), we're parsing an extended pattern for a
+     conditional command and have already set global_extglob appropriately. */
+  if (shell_compatibility_level <= 51 && was_extpat == 0)
     {
-comsub_readchar:
-      ch = shell_getc (qc != '\'' && (tflags & (LEX_INCOMMENT|LEX_PASSNEXT|LEX_QUOTEDDOC)) == 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 (retind-tind == hdlen && STREQN (ret + tind, heredelim, hdlen))
-               {
-                 tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC|LEX_QUOTEDDOC);
-/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
-                 free (heredelim);
-                 heredelim = 0;
-                 lex_firstind = -1;
-               }
-             else
-               lex_firstind = retind + 1;
-           }
-       }
-
-      /* Possible reprompting. */
-      if (ch == '\n' && SHOULD_PROMPT ())
-       prompt_again ();
-
-      /* XXX -- possibly allow here doc to be delimited by ending right
-        paren. */
-      if ((tflags & LEX_INHEREDOC) && ch == close && count == 1)
-       {
-         int tind;
-/*itrace("parse_comsub:%d: in here doc, ch == close, retind - firstind = %d hdlen = %d retind = %d", line_number, retind-lex_firstind, hdlen, retind);*/
-         tind = lex_firstind;
-         while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
-           tind++;
-         if (retind-tind == hdlen && STREQN (ret + tind, heredelim, hdlen))
-           {
-             tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC|LEX_QUOTEDDOC);
-/*itrace("parse_comsub:%d: found here doc end `%*s'", line_number, hdlen, ret + tind);*/
-             free (heredelim);
-             heredelim = 0;
-             lex_firstind = -1;
-           }
-       }
-
-      /* Don't bother counting parens or doing anything else if in a comment or
-        here document (not exactly right for here-docs -- if we want to allow
-        recursive calls to parse_comsub to have their own here documents,
-        change the LEX_INHEREDOC to LEX_QUOTEDDOC here and uncomment the next
-        clause below.  Note that to make this work completely, we need to make
-        additional changes to allow xparse_dolparen to work right when the
-        command substitution is parsed, because read_secondary_line doesn't know
-        to recursively parse through command substitutions embedded in here-
-        documents */
-      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')
-           {
-/*itrace("parse_comsub:%d: lex_incomment -> 0 ch = `%c'", line_number, ch);*/
-             tflags &= ~LEX_INCOMMENT;
-           }
-
-         continue;
-       }
-#if 0
-      /* If we're going to recursively parse a command substitution inside a
-        here-document, make sure we call parse_comsub recursively below.  See
-        above for additional caveats. */
-      if ((tflags & LEX_INHEREDOC) && ((tflags & LEX_WASDOL) == 0 || ch != '(')) /*)*/
-       {
-         /* Add this character. */
-         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
-         ret[retind++] = ch;
-         if MBTEST(ch == '$')
-           tflags |= LEX_WASDOL;
-         else
-           tflags &= ~LEX_WASDOL;
-       }
+      local_extglob = global_extglob = extended_glob;
+      extended_glob = 1;
+    }
 #endif
 
-      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
-       {
-/*itrace("parse_comsub:%d: lex_passnext -> 0 ch = `%c' (%d)", line_number, ch, __LINE__);*/
-         tflags &= ~LEX_PASSNEXT;
-         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
-           {
-             if (retind > 0)
-               retind--;       /* swallow previously-added backslash */
-             continue;
-           }
+  current_token = '\n';                                /* XXX */
+  token_to_read = DOLPAREN;                    /* let's trick the parser */
 
-         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
-         if MBTEST(ch == CTLESC)
-           ret[retind++] = CTLESC;
-         ret[retind++] = ch;
-         continue;
-       }
-
-      /* If this is a shell break character, we are not in a word.  If not,
-        we either start or continue a word. */
-      if MBTEST(shellbreak (ch))
-       {
-         tflags &= ~LEX_INWORD;
-/*itrace("parse_comsub:%d: lex_inword -> 0 ch = `%c' (%d)", line_number, ch, __LINE__);*/
-       }
-      else
-       {
-         if (tflags & LEX_INWORD)
-           {
-             lex_wlen++;
-/*itrace("parse_comsub:%d: lex_inword == 1 ch = `%c' lex_wlen = %d (%d)", line_number, ch, lex_wlen, __LINE__);*/
-           }         
-         else
-           {
-/*itrace("parse_comsub:%d: lex_inword -> 1 ch = `%c' (%d)", line_number, ch, __LINE__);*/
-             tflags |= LEX_INWORD;
-             lex_wlen = 0;
-             if (tflags & LEX_RESWDOK)
-               lex_rwlen = 0;
-           }
-       }
+  r = yyparse ();
 
-      /* Skip whitespace */
-      if MBTEST(shellblank (ch) && (tflags & LEX_HEREDELIM) == 0 && lex_rwlen == 0)
-        {
-         /* Add this character. */
-         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
-         ret[retind++] = ch;
-         continue;
-        }
+  if (need_here_doc > 0)
+    {
+      internal_warning ("command substitution: %d unterminated here-document%s", need_here_doc, (need_here_doc == 1) ? "" : "s");
+      gather_here_documents ();        /* XXX check compatibility level? */
+    }
 
-      /* 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;
-#if 0
-         else if (heredelim && (tflags & LEX_PASSNEXT) == 0 && ch == '\n')
-           {
-             tflags |= LEX_INHEREDOC;
-             tflags &= ~LEX_HEREDELIM;
-             lex_firstind = retind + 1;
-           }
+#if defined (EXTENDED_GLOB)
+  if (shell_compatibility_level <= 51 && was_extpat == 0)
+    extended_glob = local_extglob;
 #endif
-         else if (lex_firstind >= 0 && (tflags & LEX_PASSNEXT) == 0 && shellbreak (ch))
-           {
-             if (heredelim == 0)
-               {
-                 nestret = substring (ret, lex_firstind, retind);
-                 heredelim = string_quote_removal (nestret, 0);
-                 hdlen = STRLEN(heredelim);
-/*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/
-                 if (STREQ (heredelim, nestret) == 0)
-                   tflags |= LEX_QUOTEDDOC;
-                 free (nestret);
-               }
-             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) || ch == '\n'))
-       {
-         /* 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);
-/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
-             tflags |= LEX_RESWDOK;
-             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);
-           }
-       }
+  parsed_command = global_command;
 
-      /* If we can read a reserved word, try to read one. */
-      if (tflags & LEX_RESWDOK)
-       {
-         if MBTEST(islower ((unsigned char)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;
-                 tflags &= ~LEX_RESWDOK;
-/*itrace("parse_comsub:%d: found `case', lex_incase -> 1 lex_reswdok -> 0", line_number);*/
-               }
-             else if (STREQN (ret + retind - 4, "esac", 4))
-               {
-                 tflags &= ~LEX_INCASE;
-/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0 lex_reswdok -> 1", line_number);*/
-                 tflags |= LEX_RESWDOK;
-                 lex_rwlen = 0;
-               }
-             else if (STREQN (ret + retind - 4, "done", 4) ||
-                      STREQN (ret + retind - 4, "then", 4) ||
-                      STREQN (ret + retind - 4, "else", 4) ||
-                      STREQN (ret + retind - 4, "elif", 4) ||
-                      STREQN (ret + retind - 4, "time", 4))
-               {
-                 /* these are four-character reserved words that can be
-                    followed by a reserved word; anything else turns off
-                    the reserved-word-ok flag */
-/*itrace("parse_comsub:%d: found `%.4s', lex_reswdok -> 1", line_number, ret+retind-4);*/
-                 tflags |= LEX_RESWDOK;
-                 lex_rwlen = 0;
-               }
-              else if (shellmeta (ch) == 0)
-               {
-                 tflags &= ~LEX_RESWDOK;
-/*itrace("parse_comsub:%d: found `%.4s', lex_reswdok -> 0", line_number, ret+retind-4);*/
-               }
-             else      /* can't be in a reserved word any more */
-               lex_rwlen = 0;
-           }
-         else if MBTEST((tflags & LEX_CKCOMMENT) && ch == '#' && (lex_rwlen == 0 || ((tflags & LEX_INWORD) && lex_wlen == 0)))
-           ;   /* don't modify LEX_RESWDOK if we're starting a comment */
-         /* Allow `do' followed by space, tab, or newline to preserve the
-            RESWDOK flag, but reset the reserved word length counter so we
-            can read another one. */
-         else if MBTEST(((tflags & LEX_INCASE) == 0) &&
-                         (isblank((unsigned char)ch) || ch == '\n') &&
-                         lex_rwlen == 2 &&
-                         STREQN (ret + retind - 2, "do", 2))
-           {
-/*itrace("parse_comsub:%d: lex_incase == 0 found `%c', found \"do\"", line_number, ch);*/
-             lex_rwlen = 0;
-           }
-         else if MBTEST((tflags & LEX_INCASE) && ch != '\n')
-           /* If we can read a reserved word and we're in case, we're at the
-              point where we can read a new pattern list or an esac.  We
-              handle the esac case above.  If we read a newline, we want to
-              leave LEX_RESWDOK alone.  If we read anything else, we want to
-              turn off LEX_RESWDOK, since we're going to read a pattern list. */
-           {
-             tflags &= ~LEX_RESWDOK;
-/*itrace("parse_comsub:%d: lex_incase == 1 found `%c', lex_reswordok -> 0", line_number, ch);*/
-           }
-         else if MBTEST(shellbreak (ch) == 0)
-           {
-             tflags &= ~LEX_RESWDOK;
-/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
-           }
-#if 0
-         /* If we find a space or tab but have read something and it's not
-            `do', turn off the reserved-word-ok flag */
-         else if MBTEST(isblank ((unsigned char)ch) && lex_rwlen > 0)
-           {
-             tflags &= ~LEX_RESWDOK;
-/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
-           }
-#endif
-       }
+  if (EOF_Reached)
+    {
+      shell_eof_token = ps.eof_token;
+      expand_aliases = ps.expand_aliases;
 
-      /* Might be the start of a here-doc delimiter */
-      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);
-             if (peekc != '<')
-               {
-                 tflags |= LEX_HEREDELIM;
-                 lex_firstind = -1;
-               }
-             continue;
-           }
-         else
-           {
-             shell_ungetc (peekc);     /* not a here-doc, start over */
-             continue;
-           }
-       }
-      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (((tflags & LEX_RESWDOK) && lex_rwlen == 0) || ((tflags & LEX_INWORD) && lex_wlen == 0)))
+      /* yyparse() has already called yyerror() and reset_parser() */
+      return (&matched_pair_error);
+    }
+  else if (r != 0)
+    {
+      /* parser_error (start_lineno, _("could not parse command substitution")); */
+      /* Non-interactive shells exit on parse error in a command substitution. */
+      if (last_command_exit_value == 0)
+       last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (last_command_exit_value);
+      if (interactive_shell == 0)
+       jump_to_top_level (FORCE_EOF);  /* This is like reader_loop() */
+      else
        {
-/*itrace("parse_comsub:%d: lex_incomment -> 1 (%d)", line_number, __LINE__);*/
-         tflags |= LEX_INCOMMENT;
-       }
+         shell_eof_token = ps.eof_token;
+         expand_aliases = ps.expand_aliases;
 
-      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++;
-/*itrace("parse_comsub:%d: found open: count = %d", line_number, count);*/
+         jump_to_top_level (DISCARD);  /* XXX - return (&matched_pair_error)? */
        }
+    }
 
-      /* 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);
-             free (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);
-             free (nestret);
-
-             nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
-             free (ttrans);
-             nestlen = ttranslen + 2;
-             retind -= 2;              /* back up before the $" */
-           }
+  if (current_token != shell_eof_token)
+    {
+INTERNAL_DEBUG(("current_token (%d) != shell_eof_token (%c)", current_token, shell_eof_token));
+      token_to_read = current_token;
 
-         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|P_DOLBRACE|rflags);
-         else if (ch == '[')           /* ] */
-           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+      /* If we get here we can check eof_encountered and if it's 1 but the
+        previous EOF_Reached test didn't succeed, we can assume that the shell
+        is interactive and ignoreeof is set. We might want to restore the
+        parser state in this case. */
+      shell_eof_token = ps.eof_token;
+      expand_aliases = ps.expand_aliases;
 
-         CHECK_NESTRET_ERROR ();
-         APPEND_NESTRET ();
+      return (&matched_pair_error);
+    }
 
-         FREE (nestret);
-       }
-      if MBTEST(ch == '$' && (tflags & LEX_WASDOL) == 0)
-       tflags |= LEX_WASDOL;
-      else
-       tflags &= ~LEX_WASDOL;
+  /* We don't want to restore the old pushed string list, since we might have
+     used it to consume additional input from an alias while parsing this
+     command substitution. */
+  saved_strings = pushed_string_list;
+  restore_parser_state (&ps);
+  pushed_string_list = saved_strings;
+
+  tcmd = print_comsub (parsed_command);                /* returns static memory */
+  retlen = strlen (tcmd);
+  if (tcmd[0] == '(')                  /* ) need a space to prevent arithmetic expansion */
+    retlen++;
+  ret = xmalloc (retlen + 2);
+  if (tcmd[0] == '(')                  /* ) */
+    {
+      ret[0] = ' ';
+      strcpy (ret + 1, tcmd);
     }
+  else
+    strcpy (ret, tcmd);
+  ret[retlen++] = ')';
+  ret[retlen] = '\0';
+
+  dispose_command (parsed_command);
+  global_command = saved_global;
 
-  FREE (heredelim);
-  ret[retind] = '\0';
   if (lenp)
-    *lenp = retind;
+    *lenp = retlen;
+
 /*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/
   return ret;
 }
 
-/* Recursively call the parser to parse a $(...) command substitution. */
+/* Recursively call the parser to parse a $(...) command substitution. This is
+   called by the word expansion code and so does not have to reset as much
+   parser state before calling yyparse(). */
 char *
 xparse_dolparen (base, string, indp, flags)
      char *base;
@@ -4380,15 +4229,13 @@ xparse_dolparen (base, string, indp, flags)
 {
   sh_parser_state_t ps;
   sh_input_line_state_t ls;
-  int orig_ind, nc, sflags, orig_eof_token;
+  int orig_ind, nc, sflags, start_lineno;
   char *ret, *ep, *ostring;
-#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
-  STRING_SAVER *saved_pushed_strings;
-#endif
 
 /*debug_parser(1);*/
   orig_ind = *indp;
   ostring = string;
+  start_lineno = line_number;
 
   if (*string == 0)
     {
@@ -4400,39 +4247,48 @@ xparse_dolparen (base, string, indp, flags)
       return ret;
     }
 
-/*itrace("xparse_dolparen: size = %d shell_input_line = `%s'", shell_input_line_size, shell_input_line);*/
+/*itrace("xparse_dolparen: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/
+
   sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
   if (flags & SX_NOLONGJMP)
     sflags |= SEVAL_NOLONGJMP;
+
   save_parser_state (&ps);
   save_input_line_state (&ls);
-  orig_eof_token = shell_eof_token;
+
 #if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
-  saved_pushed_strings = pushed_string_list;   /* separate parsing context */
   pushed_string_list = (STRING_SAVER *)NULL;
 #endif
-
   /*(*/
   parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
   shell_eof_token = ')';
+  if (flags & SX_COMPLETE)
+    parser_state |= PST_NOERROR;
+
+  /* Don't expand aliases on this pass at all. Either parse_comsub() does it
+     at parse time, in which case this string already has aliases expanded,
+     or command_substitute() does it in the child process executing the
+     command substitution and we want to defer it completely until then. The
+     old value will be restored by restore_parser_state(). */
+  expand_aliases = 0;
+#if defined (EXTENDED_GLOB)
+  global_extglob = extended_glob;              /* for reset_parser() */
+#endif
 
-  /* Should we save and restore the bison/yacc lookahead token (yychar) here?
-     Or only if it's not YYEMPTY? */
+  token_to_read = DOLPAREN;                    /* let's trick the parser */
 
-  nc = parse_string (string, "command substitution", sflags, &ep);
+  nc = parse_string (string, "command substitution", sflags, (COMMAND **)NULL, &ep);
 
+  /* Should we save and restore the bison/yacc lookahead token (yychar) here?
+     Or only if it's not YYEMPTY? */
   if (current_token == shell_eof_token)
     yyclearin;         /* might want to clear lookahead token unconditionally */
 
-  shell_eof_token = orig_eof_token;
-  restore_parser_state (&ps);
-  reset_parser ();
-  /* reset_parser clears shell_input_line and associated variables */
+  reset_parser ();     /* resets extended_glob too */
+  /* reset_parser() clears shell_input_line and associated variables, including
+     parser_state, so we want to reset things, then restore what we need. */
   restore_input_line_state (&ls);
-
-#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
-  pushed_string_list = saved_pushed_strings;
-#endif
+  restore_parser_state (&ps);
 
   token_to_read = 0;
 
@@ -4442,34 +4298,46 @@ xparse_dolparen (base, string, indp, flags)
   if (nc < 0)
     {
       clear_shell_input_line ();       /* XXX */
-      jump_to_top_level (-nc); /* XXX */
+      if (bash_input.type != st_string)        /* paranoia */
+       parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN);
+      if ((flags & SX_NOLONGJMP) == 0)
+       jump_to_top_level (-nc);        /* XXX */
     }
 
-  /* Need to find how many characters parse_and_execute consumed, update
+  /* Need to find how many characters parse_string() consumed, update
      *indp, if flags != 0, copy the portion of the string parsed into RET
      and return it.  If flags & 1 (SX_NOALLOC) we can return NULL. */
 
   /*(*/
   if (ep[-1] != ')')
     {
-#if DEBUG
+#if 0
       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--;
     }
 
   nc = ep - ostring;
   *indp = ep - base - 1;
 
-  /*(*/
-#if DEBUG
+  /*((*/
+#if 0
   if (base[*indp] != ')')
     itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
   if (*indp < orig_ind)
     itrace("xparse_dolparen:%d: *indp (%d) < orig_ind (%d), orig_string = `%s'", line_number, *indp, orig_ind, ostring);
 #endif
 
+  if (base[*indp] != ')' && (flags & SX_NOLONGJMP) == 0)
+    {
+      /*(*/
+      if ((flags & SX_NOERROR) == 0)
+       parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), ')');
+      jump_to_top_level (DISCARD);
+    }
+
   if (flags & SX_NOALLOC) 
     return (char *)NULL;
 
@@ -4484,6 +4352,74 @@ xparse_dolparen (base, string, indp, flags)
   return ret;
 }
 
+/* Recursively call the parser to parse the string from a $(...) command
+   substitution to a COMMAND *. This is called from command_substitute() and
+   has the same parser state constraints as xparse_dolparen(). */
+COMMAND *
+parse_string_to_command (string, flags)
+     char *string;
+     int flags;
+{
+  sh_parser_state_t ps;
+  sh_input_line_state_t ls;
+  int nc, sflags;
+  size_t slen;
+  char *ret, *ep;
+  COMMAND *cmd;
+
+  if (*string == 0)
+    return (COMMAND *)NULL;
+
+  ep = string;
+  slen = STRLEN (string);
+
+/*itrace("parse_string_to_command: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/
+
+  sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
+  if (flags & SX_NOLONGJMP)
+    sflags |= SEVAL_NOLONGJMP;
+
+  save_parser_state (&ps);
+  save_input_line_state (&ls);
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  pushed_string_list = (STRING_SAVER *)NULL;
+#endif
+  if (flags & SX_COMPLETE)
+    parser_state |= PST_NOERROR;
+
+  expand_aliases = 0;
+
+  cmd = 0;
+  nc = parse_string (string, "command substitution", sflags, &cmd, &ep);
+
+  reset_parser ();
+  /* reset_parser() clears shell_input_line and associated variables, including
+     parser_state, so we want to reset things, then restore what we need. */
+  restore_input_line_state (&ls);
+  restore_parser_state (&ps);
+
+  /* If parse_string returns < 0, we need to jump to top level with the
+     negative of the return value. We abandon the rest of this input line
+     first */
+  if (nc < 0)
+    {
+      clear_shell_input_line ();       /* XXX */
+      if ((flags & SX_NOLONGJMP) == 0)
+        jump_to_top_level (-nc);       /* XXX */
+    }
+
+  /* Need to check how many characters parse_string() consumed, make sure it's
+     the entire string. */
+  if (nc < slen)
+    {
+      dispose_command (cmd);
+      return (COMMAND *)NULL;
+    }
+
+  return cmd;
+}
+
 #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
@@ -4500,7 +4436,9 @@ parse_dparen (c)
 #if defined (ARITH_FOR_COMMAND)
   if (last_read_token == FOR)
     {
-      arith_for_lineno = line_number;
+      if (word_top < MAX_CASE_NEST)
+       word_top++;
+      arith_for_lineno = word_lineno[word_top] = line_number;
       cmdtyp = parse_arith_cmd (&wval, 0);
       if (cmdtyp == 1)
        {
@@ -4524,7 +4462,7 @@ parse_dparen (c)
        {
          wd = alloc_word_desc ();
          wd->word = wval;
-         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
+         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_NOTILDE|W_NOPROCSUB;
          yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
          return (ARITH_CMD);
        }
@@ -4659,7 +4597,7 @@ cond_skip_newlines ()
   while ((cond_token = read_token (READ)) == '\n')
     {
       if (SHOULD_PROMPT ())
-       prompt_again ();
+       prompt_again (0);
     }
   return (cond_token);
 }
@@ -4672,7 +4610,7 @@ cond_term ()
 {
   WORD_DESC *op;
   COND_COM *term, *tleft, *tright;
-  int tok, lineno;
+  int tok, lineno, local_extglob;
   char *etext;
 
   /* Read a token.  It can be a left paren, a `!', a unary operator, or a
@@ -4709,7 +4647,7 @@ cond_term ()
        dispose_word (yylval.word);     /* not needed */
       term = cond_term ();
       if (term)
-       term->flags |= CMD_INVERT_RETURN;
+       term->flags ^= CMD_INVERT_RETURN;
     }
   else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[1] && yylval.word->word[2] == 0 && test_unop (yylval.word->word))
     {
@@ -4741,6 +4679,7 @@ cond_term ()
       tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
 
       /* binop */
+      /* tok = cond_skip_newlines (); ? */
       tok = read_token (READ);
       if (tok == WORD && test_binop (yylval.word->word))
        {
@@ -4785,11 +4724,12 @@ cond_term ()
        }
 
       /* rhs */
+      local_extglob = extended_glob;
       if (parser_state & PST_EXTPAT)
        extended_glob = 1;
       tok = read_token (READ);
       if (parser_state & PST_EXTPAT)
-       extended_glob = global_extglob;
+       extended_glob = local_extglob;
       parser_state &= ~(PST_REGEXP|PST_EXTPAT);
 
       if (tok == WORD)
@@ -4945,11 +4885,18 @@ read_token_word (character)
         double-quotes, quote some things inside of double-quotes. */
       if MBTEST(character == '\\')
        {
+         if (parser_state & PST_NOEXPAND)
+           {
+             pass_next_character++;
+             quoted = 1;
+             goto got_character;
+           }
+             
          peek_char = shell_getc (0);
 
          /* Backslash-newline is ignored in all cases except
             when quoted with single quotes. */
-         if (peek_char == '\n')
+         if MBTEST(peek_char == '\n')
            {
              character = '\n';
              goto next_character;
@@ -4959,7 +4906,7 @@ read_token_word (character)
              shell_ungetc (peek_char);
 
              /* If the next character is to be quoted, note it now. */
-             if (cd == 0 || cd == '`' ||
+             if MBTEST(cd == 0 || cd == '`' ||
                  (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE)))
                pass_next_character++;
 
@@ -5044,7 +4991,7 @@ read_token_word (character)
 
       /* 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))
+      if MBTEST(shellexp (character))
        {
          peek_char = shell_getc (1);
          /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
@@ -5081,7 +5028,11 @@ read_token_word (character)
              goto next_character;
            }
          /* This handles $'...' and $"..." new-style quoted strings. */
+#if defined (TRANSLATABLE_STRINGS)
          else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"'))
+#else
+         else if MBTEST(character == '$' && peek_char == '\'')
+#endif
            {
              int first_line;
 
@@ -5095,6 +5046,7 @@ read_token_word (character)
                return -1;
              if (peek_char == '\'')
                {
+                 /* PST_NOEXPAND */
                  ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
                  free (ttok);
 
@@ -5106,18 +5058,27 @@ read_token_word (character)
                  ttranslen = strlen (ttok);
                  ttrans = ttok;
                }
+#if defined (TRANSLATABLE_STRINGS)
              else
                {
+                 /* PST_NOEXPAND */
                  /* Try to locale-expand the converted string. */
-                 ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
+                 ttrans = locale_expand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
                  free (ttok);
 
-                 /* Add the double quotes back */
-                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 /* Add the double quotes back (or single quotes if the user
+                    has set that option). */
+                 if (singlequote_translations &&
+                       ((ttoklen - 1) != ttranslen || STREQN (ttok, ttrans, ttranslen) == 0))
+                   ttok = sh_single_quote (ttrans);
+                 else
+                   ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+
                  free (ttrans);
-                 ttranslen += 2;
                  ttrans = ttok;
+                 ttranslen = strlen (ttrans);
                }
+#endif /* TRANSLATABLE_STRINGS */
 
              RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 1,
                                      token_buffer_size,
@@ -5210,7 +5171,7 @@ read_token_word (character)
        }
 
 got_character:
-      if (character == CTLESC || character == CTLNUL)
+      if MBTEST(character == CTLESC || character == CTLNUL)
        {
          RESIZE_MALLOCED_BUFFER (token, token_index, 2, token_buffer_size,
                                  TOKEN_DEFAULT_GROW_SIZE);
@@ -5228,7 +5189,7 @@ got_escaped_character:
 
     next_character:
       if (character == '\n' && SHOULD_PROMPT ())
-       prompt_again ();
+       prompt_again (0);
 
       /* We want to remove quoted newlines (that is, a \<newline> pair)
         unless we are within single quotes or pass_next_character is
@@ -5324,7 +5285,7 @@ got_token:
   yylval.word = the_word;
 
   /* should we check that quoted == 0 as well? */
-  if (token[0] == '{' && token[token_index-1] == '}' &&
+  if MBTEST(token[0] == '{' && token[token_index-1] == '}' &&
       (character == '<' || character == '>'))
     {
       /* can use token; already copied to the_word */
@@ -5386,8 +5347,10 @@ reserved_word_acceptable (toksym)
     case '{':
     case '}':          /* XXX */
     case AND_AND:
+    case ARITH_CMD:
     case BANG:
     case BAR_AND:
+    case COND_END:
     case DO:
     case DONE:
     case ELIF:
@@ -5407,6 +5370,7 @@ reserved_word_acceptable (toksym)
     case UNTIL:
     case WHILE:
     case 0:
+    case DOLPAREN:
       return 1;
     default:
 #if defined (COPROCESS_SUPPORT)
@@ -5566,8 +5530,11 @@ history_delimiting_chars (line)
        return (" ");
     }
 
+  /* Assume that by this point we are reading lines in a multi-line command.
+     If we have multiple consecutive blank lines we want to return only one
+     semicolon. */
   if (line_isblank (line))
-    return ("");
+    return (current_command_line_count > 1 && last_read_token == '\n' && token_before_that != '\n') ? "; " : "";
 
   return ("; ");
 }
@@ -5576,7 +5543,8 @@ history_delimiting_chars (line)
 /* Issue a prompt, or prepare to issue a prompt when the next character
    is read. */
 static void
-prompt_again ()
+prompt_again (force)
+     int force;
 {
   char *temp_prompt;
 
@@ -5706,7 +5674,7 @@ decode_prompt_string (string)
   int last_exit_value, last_comsub_pid;
 #if defined (PROMPT_STRING_DECODE)
   size_t result_size;
-  int result_index;
+  size_t result_index;
   int c, n, i;
   char *temp, *t_host, octal_string[4];
   struct tm *tm;  
@@ -5843,7 +5811,7 @@ decode_prompt_string (string)
                /* 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);
+               temp = sh_backslash_quote_for_double_quotes (timebuf, 0);
              else
                temp = savestring (timebuf);
              goto add_string;
@@ -5859,9 +5827,14 @@ decode_prompt_string (string)
              temp = base_pathname (shell_name);
              /* Try to quote anything the user can set in the file system */
              if (promptvars || posixly_correct)
-               temp = sh_backslash_quote_for_double_quotes (temp);
+               {
+                 char *t;
+                 t = sh_strvis (temp);
+                 temp = sh_backslash_quote_for_double_quotes (t, 0);
+                 free (t);
+               }
              else
-               temp = savestring (temp);
+               temp = sh_strvis (temp);
              goto add_string;
 
            case 'v':
@@ -5936,9 +5909,14 @@ decode_prompt_string (string)
                  /* 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);
+                 {
+                   char *t;
+                   t = sh_strvis (t_string);
+                   temp = sh_backslash_quote_for_double_quotes (t, 0);
+                   free (t);
+                 }
                else
-                 temp = savestring (t_string);
+                 temp = sh_strvis (t_string);
 
                goto add_string;
              }
@@ -5958,7 +5936,7 @@ decode_prompt_string (string)
                /* 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_host);
+               temp = sh_backslash_quote_for_double_quotes (t_host, 0);
              else
                temp = savestring (t_host);
              free (t_host);
@@ -6113,7 +6091,8 @@ int
 yyerror (msg)
      const char *msg;
 {
-  report_syntax_error ((char *)NULL);
+  if ((parser_state & PST_NOERROR) == 0)
+    report_syntax_error ((char *)NULL);
   reset_parser ();
   return (0);
 }
@@ -6230,7 +6209,8 @@ report_syntax_error (message)
       parser_error (line_number, "%s", message);
       if (interactive && EOF_Reached)
        EOF_Reached = 0;
-      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+      last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE;
+      set_pipestatus_from_exit (last_command_exit_value);
       return;
     }
 
@@ -6251,7 +6231,8 @@ report_syntax_error (message)
       if (interactive == 0)
        print_offending_line ();
 
-      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+      last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE;
+      set_pipestatus_from_exit (last_command_exit_value);
       return;
     }
 
@@ -6273,8 +6254,14 @@ report_syntax_error (message)
     }
   else
     {
-      msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
-      parser_error (line_number, "%s", msg);
+      if (EOF_Reached && shell_eof_token && current_token != shell_eof_token)
+       parser_error (line_number, _("unexpected EOF while looking for matching `%c'"), shell_eof_token);
+      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. */
@@ -6282,7 +6269,8 @@ report_syntax_error (message)
        EOF_Reached = 0;
     }
 
-  last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+  last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE;
+  set_pipestatus_from_exit (last_command_exit_value);
 }
 
 /* ??? Needed function. ??? We have to be able to discard the constructs
@@ -6342,13 +6330,16 @@ handle_eof_input_unit ()
              last_read_token = current_token = '\n';
              /* Reset the prompt string to be $PS1. */
              prompt_string_pointer = (char **)NULL;
-             prompt_again ();
+             prompt_again (0);
              return;
            }
        }
 
       /* In this case EOF should exit the shell.  Do it now. */
       reset_parser ();
+
+      last_shell_builtin = this_shell_builtin;
+      this_shell_builtin = exit_builtin;
       exit_builtin ((WORD_LIST *)NULL);
     }
   else
@@ -6378,29 +6369,26 @@ parse_string_to_word_list (s, flags, whom)
      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
+  int tok, orig_current_token, orig_line_number;
+  int orig_parser_state;
+  sh_parser_state_t ps;
+  int ea;
+
+  orig_line_number = line_number;
+  save_parser_state (&ps);
 
 #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 */
+  if (ea = expanding_alias ())
+    parser_save_alias ();
+
+  /* WORD to avoid parsing reserved words as themselves and just parse them as
+     WORDs. */
+  last_read_token = WORD;
+
   current_command_line_count = 0;
   echo_input_at_read = expand_aliases = 0;
 
@@ -6408,7 +6396,13 @@ parse_string_to_word_list (s, flags, whom)
   wl = (WORD_LIST *)NULL;
 
   if (flags & 1)
-    parser_state |= PST_COMPASSIGN|PST_REPARSE;
+    {
+      orig_parser_state = parser_state;                /* XXX - not needed? */
+      /* State flags we don't want to persist into compound assignments. */
+      parser_state &= ~PST_NOEXPAND;   /* parse_comsub sentinel */
+      /* State flags we want to set for this run through the tokenizer. */
+      parser_state |= PST_COMPASSIGN|PST_REPARSE;
+    }
 
   while ((tok = read_token (READ)) != yacc_EOF)
     {
@@ -6434,25 +6428,17 @@ parse_string_to_word_list (s, flags, whom)
   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;
+  if (ea)
+    parser_restore_alias ();
 
-  current_command_line_count = orig_line_count;
-  shell_input_line_terminator = orig_input_terminator;
+  restore_parser_state (&ps);
 
   if (flags & 1)
-    parser_state &= ~(PST_COMPASSIGN|PST_REPARSE);
+    parser_state = orig_parser_state;  /* XXX - not needed? */
 
   if (wl == &parse_string_error)
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       if (interactive_shell == 0 && posixly_correct)
        jump_to_top_level (FORCE_EOF);
       else
@@ -6467,30 +6453,37 @@ 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;
+  int tok, orig_line_number, assignok;
+  sh_parser_state_t ps;
+  char *ret;
 
-  saved_token = token;
-  orig_token_size = token_buffer_size;
   orig_line_number = line_number;
-  orig_last_token = last_read_token;
+  save_parser_state (&ps);
 
-  last_read_token = WORD;      /* WORD to allow reserved words here */
+  /* WORD to avoid parsing reserved words as themselves and just parse them as
+     WORDs. Plus it means we won't be in a command position and so alias
+     expansion won't happen. */
+  last_read_token = WORD;
 
   token = (char *)NULL;
   token_buffer_size = 0;
+  wl = (WORD_LIST *)NULL;      /* ( */
 
   assignok = parser_state&PST_ASSIGNOK;                /* XXX */
 
-  wl = (WORD_LIST *)NULL;      /* ( */
+  /* State flags we don't want to persist into compound assignments. */
+  parser_state &= ~(PST_NOEXPAND|PST_CONDCMD|PST_CONDEXPR|PST_REGEXP|PST_EXTPAT);
+  /* State flags we want to set for this run through the tokenizer. */
   parser_state |= PST_COMPASSIGN;
 
+  esacs_needed_count = expecting_in_token = 0;
+
   while ((tok = read_token (READ)) != ')')
     {
       if (tok == '\n')                 /* Allow newlines in compound assignments */
        {
          if (SHOULD_PROMPT ())
-           prompt_again ();
+           prompt_again (0);
          continue;
        }
       if (tok != WORD && tok != ASSIGNMENT_WORD)
@@ -6508,15 +6501,11 @@ parse_compound_assignment (retlenp)
       wl = make_word_list (yylval.word, wl);
     }
 
-  FREE (token);
-  token = saved_token;
-  token_buffer_size = orig_token_size;
-
-  parser_state &= ~PST_COMPASSIGN;
+  restore_parser_state (&ps);
 
   if (wl == &parse_string_error)
     {
-      last_command_exit_value = EXECUTION_FAILURE;
+      set_exit_status (EXECUTION_FAILURE);
       last_read_token = '\n';  /* XXX */
       if (interactive_shell == 0 && posixly_correct)
        jump_to_top_level (FORCE_EOF);
@@ -6524,8 +6513,6 @@ parse_compound_assignment (retlenp)
        jump_to_top_level (DISCARD);
     }
 
-  last_read_token = orig_last_token;           /* XXX - was WORD? */
-
   if (wl)
     {
       rl = REVERSE_LIST (wl, WORD_LIST *);
@@ -6564,6 +6551,7 @@ save_parser_state (ps)
 
   ps->input_line_terminator = shell_input_line_terminator;
   ps->eof_encountered = eof_encountered;
+  ps->eol_lookahead = eol_ungetc_lookahead;
 
   ps->prompt_string_pointer = prompt_string_pointer;
 
@@ -6589,11 +6577,19 @@ save_parser_state (ps)
   ps->need_here_doc = need_here_doc;
   ps->here_doc_first_line = here_doc_first_line;
 
+  ps->esacs_needed = esacs_needed_count;
+  ps->expecting_in = expecting_in_token;
+
   if (need_here_doc == 0)
     ps->redir_stack[0] = 0;
   else
     memcpy (ps->redir_stack, redir_stack, sizeof (redir_stack[0]) * HEREDOC_MAX);
 
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  ps->pushed_strings = pushed_string_list;
+#endif
+
+  ps->eof_token = shell_eof_token;
   ps->token = token;
   ps->token_buffer_size = token_buffer_size;
   /* Force reallocation on next call to read_token_word */
@@ -6621,6 +6617,7 @@ restore_parser_state (ps)
 
   shell_input_line_terminator = ps->input_line_terminator;
   eof_encountered = ps->eof_encountered;
+  eol_ungetc_lookahead = ps->eol_lookahead;
 
   prompt_string_pointer = ps->prompt_string_pointer;
 
@@ -6646,6 +6643,9 @@ restore_parser_state (ps)
   need_here_doc = ps->need_here_doc;
   here_doc_first_line = ps->here_doc_first_line;
 
+  esacs_needed_count = ps->esacs_needed;
+  expecting_in_token = ps->expecting_in;
+
 #if 0
   for (i = 0; i < HEREDOC_MAX; i++)
     redir_stack[i] = ps->redir_stack[i];
@@ -6656,9 +6656,14 @@ restore_parser_state (ps)
     memcpy (redir_stack, ps->redir_stack, sizeof (redir_stack[0]) * HEREDOC_MAX);
 #endif
 
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  pushed_string_list = (STRING_SAVER *)ps->pushed_strings;
+#endif
+
   FREE (token);
   token = ps->token;
   token_buffer_size = ps->token_buffer_size;
+  shell_eof_token = ps->eof_token;
 }
 
 sh_input_line_state_t *
@@ -6675,10 +6680,20 @@ save_input_line_state (ls)
   ls->input_line_len = shell_input_line_len;
   ls->input_line_index = shell_input_line_index;
 
+#if defined (HANDLE_MULTIBYTE)
+  ls->input_property = shell_input_line_property;
+  ls->input_propsize = shell_input_line_propsize;
+#endif
+
   /* force reallocation */
   shell_input_line = 0;
   shell_input_line_size = shell_input_line_len = shell_input_line_index = 0;
 
+#if defined (HANDLE_MULTIBYTE)
+  shell_input_line_property = 0;
+  shell_input_line_propsize = 0;
+#endif
+
   return ls;
 }
 
@@ -6692,7 +6707,15 @@ restore_input_line_state (ls)
   shell_input_line_len = ls->input_line_len;
   shell_input_line_index = ls->input_line_index;
 
+#if defined (HANDLE_MULTIBYTE)
+  FREE (shell_input_line_property);
+  shell_input_line_property = ls->input_property;
+  shell_input_line_propsize = ls->input_propsize;
+#endif
+
+#if 0
   set_line_mbstate ();
+#endif
 }
 
 /************************************************
@@ -6713,10 +6736,11 @@ set_line_mbstate ()
   size_t i, previ, len;
   mbstate_t mbs, prevs;
   size_t mbclen;
+  int ilen;
 
   if (shell_input_line == NULL)
     return;
-  len = strlen (shell_input_line);     /* XXX - shell_input_line_len ? */
+  len = STRLEN (shell_input_line);     /* XXX - shell_input_line_len ? */
   if (len == 0)
     return;
   if (shell_input_line_propsize >= MAX_PROPSIZE && len < MAX_PROPSIZE>>1)
@@ -6731,12 +6755,21 @@ set_line_mbstate ()
       shell_input_line_property = (char *)xrealloc (shell_input_line_property, shell_input_line_propsize);
     }
 
+  if (locale_mb_cur_max == 1)
+    {
+      memset (shell_input_line_property, 1, len);
+      return;
+    }
+
   /* XXX - use whether or not we are in a UTF-8 locale to avoid calls to
      mbrlen */
-  memset (&prevs, '\0', sizeof (mbstate_t));
+  if (locale_utf8locale == 0)
+    memset (&prevs, '\0', sizeof (mbstate_t));
+
   for (i = previ = 0; i < len; i++)
     {
-      mbs = prevs;
+      if (locale_utf8locale == 0)
+       mbs = prevs;
 
       c = shell_input_line[i];
       if (c == EOF)
@@ -6747,10 +6780,20 @@ set_line_mbstate ()
          break;
        }
 
-      /* I'd love to take more advantage of UTF-8's properties in a UTF-8
-         locale, but mbrlen changes the mbstate_t on every call even when
-         presented with single-byte characters. */
-      mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+      if (locale_utf8locale)
+       {
+         if ((unsigned char)shell_input_line[previ] < 128)     /* i != previ */
+           mbclen = 1;
+         else
+           {
+             ilen = utf8_mblen (shell_input_line + previ, i - previ + 1);
+             mbclen = (ilen == -1) ? (size_t)-1
+                                   : ((ilen == -2) ? (size_t)-2 : (size_t)ilen);
+           }
+       }
+      else
+       mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+
       if (mbclen == 1 || mbclen == (size_t)-1)
        {
          mbclen = 1;
@@ -6762,11 +6805,11 @@ set_line_mbstate ()
        {
          mbclen = 0;
          previ = i + 1;
-         prevs = mbs;
+         if (locale_utf8locale == 0)
+           prevs = mbs;
        }
       else
        {
-         /* XXX - what to do if mbrlen returns 0? (null wide character) */
          size_t j;
          for (j = i; j < len; j++)
            shell_input_line_property[j] = 1;