Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / parse.y
diff --git a/parse.y b/parse.y
index 0f6d930..82595de 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -1,22 +1,22 @@
-/* Yacc grammar for bash. */
+/* parse.y - Yacc grammar for bash. */
 
-/* Copyright (C) 1989-2002 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2012 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it under
-   the terms of the GNU General Public License as published by the Free
-   Software Foundation; either version 2, or (at your option) any later
-   version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or
-   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-   for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU General Public License along
-   with Bash; see the file LICENSE.  If not, write to the Free Software
-   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 %{
 #include "config.h"
 
 #include "memalloc.h"
 
+#include "bashintl.h"
+
 #define NEED_STRFTIME_DECL     /* used in externs.h */
 
 #include "shell.h"
+#include "typemax.h"           /* SIZE_MAX if needed */
 #include "trap.h"
 #include "flags.h"
 #include "parser.h"
 #include "mailcheck.h"
 #include "test.h"
+#include "builtins.h"
 #include "builtins/common.h"
 #include "builtins/builtext.h"
 
@@ -69,6 +73,8 @@
 
 #if defined (ALIAS)
 #  include "alias.h"
+#else
+typedef void *alias_t;
 #endif /* ALIAS */
 
 #if defined (PROMPT_STRING_DECODE)
@@ -110,10 +116,10 @@ extern int extended_glob;
 extern int eof_encountered;
 extern int no_line_editing, running_under_emacs;
 extern int current_command_number;
-extern int sourcelevel;
+extern int sourcelevel, parse_and_execute_level;
 extern int posixly_correct;
 extern int last_command_exit_value;
-extern int interrupt_immediately;
+extern pid_t last_command_subst_pid;
 extern char *shell_name, *current_host_name;
 extern char *dist_version;
 extern int patch_level;
@@ -144,6 +150,7 @@ static int yy_readline_unget __P((int));
 
 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));
 
@@ -166,12 +173,13 @@ static int time_command_acceptable __P((void));
 static int special_case_tokens __P((char *));
 static int read_token __P((int));
 static char *parse_matched_pair __P((int, int, int, int *, int));
+static char *parse_comsub __P((int, int, int, int *, int));
 #if defined (ARRAY_VARS)
 static char *parse_compound_assignment __P((int *));
 #endif
 #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
 static int parse_dparen __P((int));
-static int parse_arith_cmd __P((char **));
+static int parse_arith_cmd __P((char **, int));
 #endif
 #if defined (COND_COMMAND)
 static void cond_error __P((void));
@@ -201,10 +209,6 @@ static void reset_readline_prompt __P((void));
 #endif
 static void print_prompt __P((void));
 
-#if defined (HISTORY)
-char *history_delimiting_chars __P((void));
-#endif
-
 #if defined (HANDLE_MULTIBYTE)
 static void set_line_mbstate __P((void));
 static char *shell_input_line_property = NULL;
@@ -239,13 +243,26 @@ int expand_aliases = 0;
    decode_prompt_string. */
 int promptvars = 1;
 
-/* The decoded prompt string.  Used if READLINE is not defined or if
-   editing is turned off.  Analogous to current_readline_prompt. */
-static char *current_decoded_prompt;
+/* If non-zero, $'...' and $"..." are expanded when they appear within
+   a ${...} expansion, even when the expansion appears within double
+   quotes. */
+int extended_quote = 1;
 
 /* The number of lines read from input while creating the current command. */
 int current_command_line_count;
 
+/* The number of lines in a command saved while we run parse_and_execute */
+int saved_command_line_count;
+
+/* The token that currently denotes the end of parse. */
+int shell_eof_token;
+
+/* The token currently being read. */
+int current_token;
+
+/* The current parser state. */
+int parser_state;
+
 /* Variables to manage the task of reading here documents, because we need to
    defer the reading until after a complete command has been collected. */
 static REDIRECT *redir_stack[10];
@@ -254,9 +271,9 @@ int need_here_doc;
 /* Where shell input comes from.  History expansion is performed on each
    line when the shell is interactive. */
 static char *shell_input_line = (char *)NULL;
-static int shell_input_line_index;
-static int shell_input_line_size;      /* Amount allocated for shell_input_line. */
-static int shell_input_line_len;       /* strlen (shell_input_line) */
+static size_t shell_input_line_index;
+static size_t shell_input_line_size;   /* Amount allocated for shell_input_line. */
+static size_t shell_input_line_len;    /* strlen (shell_input_line) */
 
 /* Either zero or EOF. */
 static int shell_input_line_terminator;
@@ -270,6 +287,37 @@ static int function_bstart;
 /* The line number in a script at which an arithmetic for command starts. */
 static int arith_for_lineno;
 
+/* The decoded prompt string.  Used if READLINE is not defined or if
+   editing is turned off.  Analogous to current_readline_prompt. */
+static char *current_decoded_prompt;
+
+/* The last read token, or NULL.  read_token () uses this for context
+   checking. */
+static int last_read_token;
+
+/* The token read prior to last_read_token. */
+static int token_before_that;
+
+/* The token read prior to token_before_that. */
+static int two_tokens_ago;
+
+static int global_extglob;
+
+/* The line number in a script where the word in a `case WORD', `select WORD'
+   or `for WORD' begins.  This is a nested command maximum, since the array
+   index is decremented after a case, select, or for command is parsed. */
+#define MAX_CASE_NEST  128
+static int word_lineno[MAX_CASE_NEST];
+static int word_top = -1;
+
+/* If non-zero, it is the token that we want read_token to return
+   regardless of what text is (or isn't) present to be read.  This
+   is reset by read_token.  If token_to_read == WORD or
+   ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
+static int token_to_read;
+static WORD_DESC *word_desc_to_read;
+
+static REDIRECTEE source;
 static REDIRECTEE redir;
 %}
 
@@ -287,18 +335,19 @@ static REDIRECTEE redir;
    in the case that they are preceded by a list_terminator.  Members
    of the second group are for [[...]] commands.  Members of the
    third group are recognized only under special circumstances. */
-%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION
+%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC
 %token COND_START COND_END COND_ERROR
-%token IN BANG TIME TIMEOPT
+%token IN BANG TIME TIMEOPT TIMEIGN
 
 /* More general tokens. yylex () knows how to make these. */
-%token <word> WORD ASSIGNMENT_WORD
+%token <word> WORD ASSIGNMENT_WORD REDIR_WORD
 %token <number> NUMBER
 %token <word_list> ARITH_CMD ARITH_FOR_EXPRS
 %token <command> COND_CMD
 %token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS
-%token GREATER_AND SEMI_SEMI LESS_LESS_MINUS AND_GREATER LESS_GREATER
-%token GREATER_BAR
+%token GREATER_AND SEMI_SEMI SEMI_AND SEMI_SEMI_AND
+%token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
+%token GREATER_BAR BAR_AND
 
 /* The types that the various syntactical units return. */
 
@@ -309,18 +358,20 @@ static REDIRECTEE redir;
 %type <command> arith_command
 %type <command> cond_command
 %type <command> arith_for_command
+%type <command> coproc
 %type <command> function_def function_body if_command elif_clause subshell
 %type <redirect> redirection redirection_list
 %type <element> simple_command_element
 %type <word_list> word_list pattern
 %type <pattern> pattern_list case_clause_sequence case_clause
 %type <number> timespec
+%type <number> list_terminator
 
 %start inputunit
 
 %left '&' ';' '\n' yacc_EOF
 %left AND_AND OR_OR
-%right '|'
+%right '|' BAR_AND
 %%
 
 inputunit:     simple_list simple_list_terminator
@@ -330,6 +381,8 @@ inputunit:  simple_list simple_list_terminator
                          global_command = $1;
                          eof_encountered = 0;
                          /* discard_parser_constructs (0); */
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       '\n'
@@ -337,6 +390,8 @@ inputunit:  simple_list simple_list_terminator
                          /* Case of regular command, but not a very
                             interesting one.  Return a NULL command. */
                          global_command = (COMMAND *)NULL;
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
                          YYACCEPT;
                        }
        |       error '\n'
@@ -345,7 +400,7 @@ inputunit:  simple_list simple_list_terminator
                          global_command = (COMMAND *)NULL;
                          eof_encountered = 0;
                          /* discard_parser_constructs (1); */
-                         if (interactive)
+                         if (interactive && parse_and_execute_level == 0)
                            {
                              YYACCEPT;
                            }
@@ -372,154 +427,273 @@ word_list:      WORD
 
 redirection:   '>' WORD
                        {
+                         source.dest = 1;
                          redir.filename = $2;
-                         $$ = make_redirection (1, r_output_direction, redir);
+                         $$ = make_redirection (source, r_output_direction, redir, 0);
                        }
        |       '<' WORD
                        {
+                         source.dest = 0;
                          redir.filename = $2;
-                         $$ = make_redirection (0, r_input_direction, redir);
+                         $$ = make_redirection (source, r_input_direction, redir, 0);
                        }
        |       NUMBER '>' WORD
                        {
+                         source.dest = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_output_direction, redir);
+                         $$ = make_redirection (source, r_output_direction, redir, 0);
                        }
        |       NUMBER '<' WORD
                        {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_direction, redir, 0);
+                       }
+       |       REDIR_WORD '>' WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_direction, redir, REDIR_VARASSIGN);
+                       }
+       |       REDIR_WORD '<' WORD
+                       {
+                         source.filename = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_input_direction, redir);
+                         $$ = make_redirection (source, r_input_direction, redir, REDIR_VARASSIGN);
                        }
        |       GREATER_GREATER WORD
                        {
+                         source.dest = 1;
                          redir.filename = $2;
-                         $$ = make_redirection (1, r_appending_to, redir);
+                         $$ = make_redirection (source, r_appending_to, redir, 0);
                        }
        |       NUMBER GREATER_GREATER WORD
                        {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_appending_to, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_GREATER WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_appending_to, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_BAR WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_output_force, redir, 0);
+                       }
+       |       NUMBER GREATER_BAR WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_force, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_BAR WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_force, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_GREATER WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_input_output, redir, 0);
+                       }
+       |       NUMBER LESS_GREATER WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_output, redir, 0);
+                       }
+       |       REDIR_WORD LESS_GREATER WORD
+                       {
+                         source.filename = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_appending_to, redir);
+                         $$ = make_redirection (source, r_input_output, redir, REDIR_VARASSIGN);
                        }
        |       LESS_LESS WORD
                        {
+                         source.dest = 0;
                          redir.filename = $2;
-                         $$ = make_redirection (0, r_reading_until, redir);
+                         $$ = make_redirection (source, r_reading_until, redir, 0);
                          redir_stack[need_here_doc++] = $$;
                        }
        |       NUMBER LESS_LESS WORD
                        {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       REDIR_WORD LESS_LESS WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       LESS_LESS_MINUS WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS_MINUS WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       REDIR_WORD  LESS_LESS_MINUS WORD
+                       {
+                         source.filename = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_reading_until, redir);
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN);
                          redir_stack[need_here_doc++] = $$;
                        }
        |       LESS_LESS_LESS WORD
                        {
+                         source.dest = 0;
                          redir.filename = $2;
-                         $$ = make_redirection (0, r_reading_string, redir);
+                         $$ = make_redirection (source, r_reading_string, redir, 0);
                        }
        |       NUMBER LESS_LESS_LESS WORD
                        {
+                         source.dest = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_reading_string, redir);
+                         $$ = make_redirection (source, r_reading_string, redir, 0);
+                       }
+       |       REDIR_WORD LESS_LESS_LESS WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_string, redir, REDIR_VARASSIGN);
                        }
        |       LESS_AND NUMBER
                        {
+                         source.dest = 0;
                          redir.dest = $2;
-                         $$ = make_redirection (0, r_duplicating_input, redir);
+                         $$ = make_redirection (source, r_duplicating_input, redir, 0);
                        }
        |       NUMBER LESS_AND NUMBER
                        {
+                         source.dest = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_input, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND NUMBER
+                       {
+                         source.filename = $1;
                          redir.dest = $3;
-                         $$ = make_redirection ($1, r_duplicating_input, redir);
+                         $$ = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN);
                        }
        |       GREATER_AND NUMBER
                        {
+                         source.dest = 1;
                          redir.dest = $2;
-                         $$ = make_redirection (1, r_duplicating_output, redir);
+                         $$ = make_redirection (source, r_duplicating_output, redir, 0);
                        }
        |       NUMBER GREATER_AND NUMBER
                        {
+                         source.dest = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_output, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_AND NUMBER
+                       {
+                         source.filename = $1;
                          redir.dest = $3;
-                         $$ = make_redirection ($1, r_duplicating_output, redir);
+                         $$ = make_redirection (source, r_duplicating_output, redir, REDIR_VARASSIGN);
                        }
        |       LESS_AND WORD
                        {
+                         source.dest = 0;
                          redir.filename = $2;
-                         $$ = make_redirection (0, r_duplicating_input_word, redir);
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, 0);
                        }
        |       NUMBER LESS_AND WORD
                        {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND WORD
+                       {
+                         source.filename = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_duplicating_input_word, redir);
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN);
                        }
        |       GREATER_AND WORD
                        {
+                         source.dest = 1;
                          redir.filename = $2;
-                         $$ = make_redirection (1, r_duplicating_output_word, redir);
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
                        }
        |       NUMBER GREATER_AND WORD
                        {
+                         source.dest = $1;
                          redir.filename = $3;
-                         $$ = make_redirection ($1, r_duplicating_output_word, redir);
-                       }
-       |       LESS_LESS_MINUS WORD
-                       {
-                         redir.filename = $2;
-                         $$ = make_redirection
-                           (0, r_deblank_reading_until, redir);
-                         redir_stack[need_here_doc++] = $$;
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
                        }
-       |       NUMBER LESS_LESS_MINUS WORD
+       |       REDIR_WORD GREATER_AND WORD
                        {
+                         source.filename = $1;
                          redir.filename = $3;
-                         $$ = make_redirection
-                           ($1, r_deblank_reading_until, redir);
-                         redir_stack[need_here_doc++] = $$;
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN);
                        }
        |       GREATER_AND '-'
                        {
+                         source.dest = 1;
                          redir.dest = 0;
-                         $$ = make_redirection (1, r_close_this, redir);
+                         $$ = make_redirection (source, r_close_this, redir, 0);
                        }
        |       NUMBER GREATER_AND '-'
                        {
+                         source.dest = $1;
                          redir.dest = 0;
-                         $$ = make_redirection ($1, r_close_this, redir);
+                         $$ = make_redirection (source, r_close_this, redir, 0);
                        }
-       |       LESS_AND '-'
+       |       REDIR_WORD GREATER_AND '-'
                        {
+                         source.filename = $1;
                          redir.dest = 0;
-                         $$ = make_redirection (0, r_close_this, redir);
+                         $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
                        }
-       |       NUMBER LESS_AND '-'
+       |       LESS_AND '-'
                        {
+                         source.dest = 0;
                          redir.dest = 0;
-                         $$ = make_redirection ($1, r_close_this, redir);
+                         $$ = make_redirection (source, r_close_this, redir, 0);
                        }
-       |       AND_GREATER WORD
+       |       NUMBER LESS_AND '-'
                        {
-                         redir.filename = $2;
-                         $$ = make_redirection (1, r_err_and_out, redir);
+                         source.dest = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, 0);
                        }
-       |       NUMBER LESS_GREATER WORD
+       |       REDIR_WORD LESS_AND '-'
                        {
-                         redir.filename = $3;
-                         $$ = make_redirection ($1, r_input_output, redir);
+                         source.filename = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
                        }
-       |       LESS_GREATER WORD
+       |       AND_GREATER WORD
                        {
+                         source.dest = 1;
                          redir.filename = $2;
-                         $$ = make_redirection (0, r_input_output, redir);
+                         $$ = make_redirection (source, r_err_and_out, redir, 0);
                        }
-       |       GREATER_BAR WORD
+       |       AND_GREATER_GREATER WORD
                        {
+                         source.dest = 1;
                          redir.filename = $2;
-                         $$ = make_redirection (1, r_output_force, redir);
-                       }
-       |       NUMBER GREATER_BAR WORD
-                       {
-                         redir.filename = $3;
-                         $$ = make_redirection ($1, r_output_force, redir);
+                         $$ = make_redirection (source, r_append_err_and_out, redir, 0);
                        }
        ;
 
@@ -574,6 +748,8 @@ command:    simple_command
                        }
        |       function_def
                        { $$ = $1; }
+       |       coproc
+                       { $$ = $1; }
        ;
 
 shell_command: for_command
@@ -601,65 +777,116 @@ shell_command:   for_command
        ;
 
 for_command:   FOR WORD newline_list DO compound_list DONE
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5); }
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD newline_list '{' compound_list '}'
-                       { $$ = make_for_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5); }
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD ';' newline_list DO compound_list DONE
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD ';' newline_list '{' compound_list '}'
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE
-                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
-                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE
-                       { $$ = make_for_command ($2, (WORD_LIST *)NULL, $8); }
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}'
-                       { $$ = make_for_command ($2, (WORD_LIST *)NULL, $8); }
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        ;
 
 arith_for_command:     FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE
-                               { $$ = make_arith_for_command ($2, $6, arith_for_lineno); }
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
        |               FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}'
-                               { $$ = make_arith_for_command ($2, $6, arith_for_lineno); }
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
        |               FOR ARITH_FOR_EXPRS DO compound_list DONE
-                               { $$ = make_arith_for_command ($2, $4, arith_for_lineno); }
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
        |               FOR ARITH_FOR_EXPRS '{' compound_list '}'
-                               { $$ = make_arith_for_command ($2, $4, arith_for_lineno); }
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
        ;
 
 select_command:        SELECT WORD newline_list DO list DONE
                        {
-                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5);
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        |       SELECT WORD newline_list '{' list '}'
                        {
-                         $$ = make_select_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5);
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        |       SELECT WORD ';' newline_list DO list DONE
                        {
-                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6);
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        |       SELECT WORD ';' newline_list '{' list '}'
                        {
-                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6);
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        |       SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
                        {
-                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9);
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
                        {
-                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9);
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
                        }
        ;
 
 case_command:  CASE WORD newline_list IN newline_list ESAC
-                       { $$ = make_case_command ($2, (PATTERN_LIST *)NULL); }
+                       {
+                         $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       CASE WORD newline_list IN case_clause_sequence newline_list ESAC
-                       { $$ = make_case_command ($2, $5); }
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        |       CASE WORD newline_list IN case_clause ESAC
-                       { $$ = make_case_command ($2, $5); }
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
        ;
 
 function_def:  WORD '(' ')' newline_list function_body
@@ -672,7 +899,6 @@ function_def:       WORD '(' ')' newline_list function_body
                        { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
        ;
 
-
 function_body: shell_command
                        { $$ = $1; }
        |       shell_command redirection_list
@@ -713,6 +939,57 @@ subshell:  '(' compound_list ')'
                        }
        ;
 
+coproc:                COPROC shell_command
+                       {
+                         $$ = make_coproc_command ("COPROC", $2);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $2;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $3;
+                           }
+                         else
+                           tc->redirects = $3;
+                         $$ = make_coproc_command ("COPROC", $2);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC WORD shell_command
+                       {
+                         $$ = make_coproc_command ($2->word, $3);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC WORD shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $3;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $4;
+                           }
+                         else
+                           tc->redirects = $4;
+                         $$ = make_coproc_command ($2->word, $3);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC simple_command
+                       {
+                         $$ = make_coproc_command ("COPROC", clean_simple_command ($2));
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       ;
+
 if_command:    IF compound_list THEN compound_list FI
                        { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
        |       IF compound_list THEN compound_list ELSE compound_list FI
@@ -758,8 +1035,17 @@ pattern_list:     newline_list pattern ')' compound_list
        ;
 
 case_clause_sequence:  pattern_list SEMI_SEMI
+                       { $$ = $1; }
        |       case_clause_sequence pattern_list SEMI_SEMI
                        { $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_AND
+                       { $1->flags |= CASEPAT_FALLTHROUGH; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_AND
+                       { $2->flags |= CASEPAT_FALLTHROUGH; $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_SEMI_AND
+                       { $1->flags |= CASEPAT_TESTNEXT; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_SEMI_AND
+                       { $2->flags |= CASEPAT_TESTNEXT; $2->next = $1; $$ = $2; }      
        ;
 
 pattern:       WORD
@@ -824,8 +1110,11 @@ simple_list_terminator:   '\n'
        ;
 
 list_terminator:'\n'
+               { $$ = '\n'; }
        |       ';'
+               { $$ = ';'; }
        |       yacc_EOF
+               { $$ = yacc_EOF; }
        ;
 
 newline_list:
@@ -843,6 +1132,13 @@ simple_list:      simple_list1
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 '&'
                        {
@@ -852,12 +1148,26 @@ simple_list:     simple_list1
                            $$ = command_connect ($1, (COMMAND *)NULL, '&');
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        |       simple_list1 ';'
                        {
                          $$ = $1;
                          if (need_here_doc)
                            gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
                        }
        ;
 
@@ -880,32 +1190,81 @@ simple_list1:    simple_list1 AND_AND newline_list simple_list1
        ;
 
 pipeline_command: pipeline
-                       { $$ = $1; }
-       |       BANG pipeline
+                       { $$ = $1; }                    
+       |       BANG pipeline_command
                        {
-                         $2->flags |= CMD_INVERT_RETURN;
+                         if ($2)
+                           $2->flags ^= CMD_INVERT_RETURN;     /* toggle */
                          $$ = $2;
                        }
-       |       timespec pipeline
+       |       timespec pipeline_command
                        {
-                         $2->flags |= $1;
+                         if ($2)
+                           $2->flags |= $1;
                          $$ = $2;
                        }
-       |       timespec BANG pipeline
+       |       timespec list_terminator
                        {
-                         $3->flags |= $1|CMD_INVERT_RETURN;
-                         $$ = $3;
+                         ELEMENT x;
+
+                         /* Boy, this is unclean.  `time' by itself can
+                            time a null command.  We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= $1;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
                        }
-       |       BANG timespec pipeline
+       |       BANG list_terminator
                        {
-                         $3->flags |= $2|CMD_INVERT_RETURN;
-                         $$ = $3;
+                         ELEMENT x;
+
+                         /* This is just as unclean.  Posix says that `!'
+                            by itself should be equivalent to `false'.
+                            We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= CMD_INVERT_RETURN;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
                        }
        ;
 
-pipeline:
-               pipeline '|' newline_list pipeline
+pipeline:      pipeline '|' newline_list pipeline
                        { $$ = command_connect ($1, $4, '|'); }
+       |       pipeline BAR_AND newline_list pipeline
+                       {
+                         /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */
+                         COMMAND *tc;
+                         REDIRECTEE rd, sd;
+                         REDIRECT *r;
+
+                         tc = $1->type == cm_simple ? (COMMAND *)$1->value.Simple : $1;
+                         sd.dest = 2;
+                         rd.dest = 1;
+                         r = make_redirection (sd, r_duplicating_output, rd, 0);
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = r;
+                           }
+                         else
+                           tc->redirects = r;
+
+                         $$ = command_connect ($1, $4, '|');
+                       }
        |       command
                        { $$ = $1; }
        ;
@@ -914,49 +1273,25 @@ timespec:        TIME
                        { $$ = CMD_TIME_PIPELINE; }
        |       TIME TIMEOPT
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEOPT TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        ;
 %%
 
-/* Possible states for the parser that require it to do special things. */
-#define PST_CASEPAT    0x001           /* in a case pattern list */
-#define PST_ALEXPNEXT  0x002           /* expand next word for aliases */
-#define PST_ALLOWOPNBRC        0x004           /* allow open brace for function def */
-#define PST_NEEDCLOSBRC        0x008           /* need close brace */
-#define PST_DBLPAREN   0x010           /* double-paren parsing */
-#define PST_SUBSHELL   0x020           /* ( ... ) subshell */
-#define PST_CMDSUBST   0x040           /* $( ... ) command substitution */
-#define PST_CASESTMT   0x080           /* parsing a case statement */
-#define PST_CONDCMD    0x100           /* parsing a [[...]] command */
-#define PST_CONDEXPR   0x200           /* parsing the guts of [[...]] */
-#define PST_ARITHFOR   0x400           /* parsing an arithmetic for command */
-
 /* Initial size to allocate for tokens, and the
    amount to grow them by. */
 #define TOKEN_DEFAULT_INITIAL_SIZE 496
 #define TOKEN_DEFAULT_GROW_SIZE 512
 
-/* The token currently being read. */
-static int current_token;
-
-/* The last read token, or NULL.  read_token () uses this for context
-   checking. */
-static int last_read_token;
-
-/* The token read prior to last_read_token. */
-static int token_before_that;
-
-/* The token read prior to token_before_that. */
-static int two_tokens_ago;
-
-/* If non-zero, it is the token that we want read_token to return
-   regardless of what text is (or isn't) present to be read.  This
-   is reset by read_token.  If token_to_read == WORD or
-   ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
-static int token_to_read;
-static WORD_DESC *word_desc_to_read;
+/* Should we call prompt_again? */
+#define SHOULD_PROMPT() \
+  (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
 
-/* The current parser state. */
-static int parser_state;
+#if defined (ALIAS)
+#  define expanding_alias() (pushed_string_list && pushed_string_list->expander)
+#else
+#  define expanding_alias() 0
+#endif
 
 /* Global var is non-zero when end of file has been reached. */
 int EOF_Reached = 0;
@@ -1098,20 +1433,22 @@ yy_readline_get ()
        give_terminal_to (shell_pgrp, 0);
 #endif /* JOB_CONTROL */
 
-      old_sigint = (SigHandler *)NULL;
+      old_sigint = (SigHandler *)IMPOSSIBLE_TRAP_HANDLER;
       if (signal_is_ignored (SIGINT) == 0)
        {
+         /* interrupt_immediately++; */
          old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
-         interrupt_immediately++;
        }
 
       current_readline_line = readline (current_readline_prompt ?
                                          current_readline_prompt : "");
 
-      if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+      CHECK_TERMSIG;
+      if (signal_is_ignored (SIGINT) == 0)
        {
-         interrupt_immediately--;
-         set_signal_handler (SIGINT, old_sigint);
+         /* interrupt_immediately--; */
+         if (old_sigint != IMPOSSIBLE_TRAP_HANDLER)
+           set_signal_handler (SIGINT, old_sigint);
        }
 
 #if 0
@@ -1218,6 +1555,33 @@ with_input_from_string (string, name)
   init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
 }
 
+/* Count the number of characters we've consumed from bash_input.location.string
+   and read into shell_input_line, but have not returned from shell_getc.
+   That is the true input location.  Rewind bash_input.location.string by
+   that number of characters, so it points to the last character actually
+   consumed by the parser. */
+static void
+rewind_input_string ()
+{
+  int xchars;
+
+  /* number of unconsumed characters in the input -- XXX need to take newlines
+     into account, e.g., $(...\n) */
+  xchars = shell_input_line_len - shell_input_line_index;
+  if (bash_input.location.string[-1] == '\n')
+    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
+     far into the string we parsed.  parse_and_execute knows where bash_input.
+     location.string is, and how far from orig_string that is -- that's the
+     number of characters the command consumed. */
+
+  /* bash_input.location.string - xchars should be where we parsed to */
+  /* need to do more validation on xchars value for sanity -- test cases. */
+  bash_input.location.string -= xchars;
+}
+
 /* **************************************************************** */
 /*                                                                 */
 /*                  Let input come from STREAM.                    */
@@ -1237,8 +1601,21 @@ yy_stream_get ()
 
   result = EOF;
   if (bash_input.location.file)
-    result = getc_with_restart (bash_input.location.file);
+    {
+#if 0
+      if (interactive)
+       interrupt_immediately++;
+#endif
+
+      /* XXX - don't need terminate_immediately; getc_with_restart checks
+        for terminating signals itself if read returns < 0 */
+      result = getc_with_restart (bash_input.location.file);
 
+#if 0
+      if (interactive)
+       interrupt_immediately--;
+#endif
+    }
   return (result);
 }
 
@@ -1272,6 +1649,9 @@ typedef struct stream_saver {
 /* The globally known line number. */
 int line_number = 0;
 
+/* The line number offset set by assigning to LINENO.  Not currently used. */
+int line_number_base = 0;
+
 #if defined (COND_COMMAND)
 static int cond_lineno;
 static int cond_token;
@@ -1370,10 +1750,11 @@ save_token_state ()
 {
   int *ret;
 
-  ret = (int *)xmalloc (3 * sizeof (int));
+  ret = (int *)xmalloc (4 * sizeof (int));
   ret[0] = last_read_token;
   ret[1] = token_before_that;
   ret[2] = two_tokens_ago;
+  ret[3] = current_token;
   return ret;
 }
 
@@ -1386,6 +1767,7 @@ restore_token_state (ts)
   last_read_token = ts[0];
   token_before_that = ts[1];
   two_tokens_ago = ts[2];
+  current_token = ts[3];
 }
 
 /*
@@ -1399,10 +1781,6 @@ restore_token_state (ts)
 
 #if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
 
-#if !defined (ALIAS)
-typedef void *alias_t;
-#endif
-
 #define END_OF_ALIAS 0
 
 /*
@@ -1414,6 +1792,10 @@ typedef void *alias_t;
  * implement alias expansion on a per-token basis.
  */
 
+#define PSH_ALIAS      0x01
+#define PSH_DPAREN     0x02
+#define PSH_SOURCE     0x04
+
 typedef struct string_saver {
   struct string_saver *next;
   int expand_alias;  /* Value to set expand_alias to when string is popped. */
@@ -1421,7 +1803,9 @@ typedef struct string_saver {
 #if defined (ALIAS)
   alias_t *expander;   /* alias that caused this line to be pushed. */
 #endif
-  int saved_line_size, saved_line_index, saved_line_terminator;
+  size_t saved_line_size, saved_line_index;
+  int saved_line_terminator;
+  int flags;
 } STRING_SAVER;
 
 STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
@@ -1447,8 +1831,11 @@ push_string (s, expand, ap)
   temp->saved_line_size = shell_input_line_size;
   temp->saved_line_index = shell_input_line_index;
   temp->saved_line_terminator = shell_input_line_terminator;
+  temp->flags = 0;
 #if defined (ALIAS)
   temp->expander = ap;
+  if (ap)
+    temp->flags = PSH_ALIAS;
 #endif
   temp->next = pushed_string_list;
   pushed_string_list = temp;
@@ -1459,10 +1846,12 @@ push_string (s, expand, ap)
 #endif
 
   shell_input_line = s;
-  shell_input_line_size = strlen (s);
+  shell_input_line_size = STRLEN (s);
   shell_input_line_index = 0;
   shell_input_line_terminator = '\0';
-  parser_state &= ~PST_ALEXPNEXT;
+#if 0
+  parser_state &= ~PST_ALEXPNEXT;      /* XXX */
+#endif
 
   set_line_mbstate ();
 }
@@ -1523,33 +1912,69 @@ free_string_list ()
 
 #endif /* ALIAS || DPAREN_ARITHMETIC */
 
-/* Return a line of text, taken from wherever yylex () reads input.
-   If there is no more input, then we return NULL.  If REMOVE_QUOTED_NEWLINE
-   is non-zero, we remove unquoted \<newline> pairs.  This is used by
-   read_secondary_line to read here documents. */
-static char *
-read_a_line (remove_quoted_newline)
-     int remove_quoted_newline;
+void
+free_pushed_string_input ()
 {
-  static char *line_buffer = (char *)NULL;
-  static int buffer_size = 0;
-  int indx = 0, c, peekc, pass_next;
-
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  free_string_list ();
+#endif
+}
+
+int
+parser_expanding_alias ()
+{
+  return (expanding_alias ());
+}
+
+void
+parser_save_alias ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  push_string ((char *)NULL, 0, (alias_t *)NULL);
+  pushed_string_list->flags = PSH_SOURCE;      /* XXX - for now */
+#else
+  ;
+#endif
+}
+
+void
+parser_restore_alias ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  if (pushed_string_list)
+    pop_string ();
+#else
+  ;
+#endif
+}
+
+/* Return a line of text, taken from wherever yylex () reads input.
+   If there is no more input, then we return NULL.  If REMOVE_QUOTED_NEWLINE
+   is non-zero, we remove unquoted \<newline> pairs.  This is used by
+   read_secondary_line to read here documents. */
+static char *
+read_a_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  static char *line_buffer = (char *)NULL;
+  static int buffer_size = 0;
+  int indx, c, peekc, pass_next;
+
 #if defined (READLINE)
-  if (interactive && bash_input.type != st_string && no_line_editing)
+  if (no_line_editing && SHOULD_PROMPT ())
 #else
-  if (interactive && bash_input.type != st_string)
+  if (SHOULD_PROMPT ())
 #endif
     print_prompt ();
 
-  pass_next = 0;
+  pass_next = indx = 0;
   while (1)
     {
-      c = yy_getc ();
-
       /* Allow immediate exit if interrupted during input. */
       QUIT;
 
+      c = yy_getc ();
+
       /* Ignore null bytes in input. */
       if (c == 0)
        {
@@ -1585,9 +2010,13 @@ read_a_line (remove_quoted_newline)
        }
       else if (c == '\\' && remove_quoted_newline)
        {
+         QUIT;
          peekc = yy_getc ();
          if (peekc == '\n')
-           continue;   /* Make the unquoted \<newline> pair disappear. */
+           {
+             line_number++;
+             continue; /* Make the unquoted \<newline> pair disappear. */
+           }
          else
            {
              yy_ungetc (peekc);
@@ -1615,9 +2044,27 @@ char *
 read_secondary_line (remove_quoted_newline)
      int remove_quoted_newline;
 {
+  char *ret;
+  int n, c;
+
   prompt_string_pointer = &ps2_prompt;
-  prompt_again ();
-  return (read_a_line (remove_quoted_newline));
+  if (SHOULD_PROMPT())
+    prompt_again ();
+  ret = read_a_line (remove_quoted_newline);
+#if defined (HISTORY)
+  if (ret && remember_on_history && (parser_state & PST_HEREDOC))
+    {
+      /* To make adding the the here-document body right, we need to rely
+        on history_delimiting_chars() returning \n for the first line of
+        the here-document body and the null string for the second and
+        subsequent lines, so we avoid double newlines.
+        current_command_line_count == 2 for the first line of the body. */
+
+      current_command_line_count++;
+      maybe_add_history (ret);
+    }
+#endif /* HISTORY */
+  return ret;
 }
 
 /* **************************************************************** */
@@ -1656,12 +2103,16 @@ STRING_INT_ALIST word_token_alist[] = {
   { "[[", COND_START },
   { "]]", COND_END },
 #endif
+#if defined (COPROCESS_SUPPORT)
+  { "coproc", COPROC },
+#endif
   { (char *)NULL, 0}
 };
 
 /* other tokens that can be returned by read_token() */
 STRING_INT_ALIST other_token_alist[] = {
   /* Multiple-character tokens with special values */
+  { "--", TIMEIGN },
   { "-p", TIMEOPT },
   { "&&", AND_AND },
   { "||", OR_OR },
@@ -1670,11 +2121,15 @@ STRING_INT_ALIST other_token_alist[] = {
   { "<&", LESS_AND },
   { ">&", GREATER_AND },
   { ";;", SEMI_SEMI },
+  { ";&", SEMI_AND },
+  { ";;&", SEMI_SEMI_AND },
   { "<<-", LESS_LESS_MINUS },
   { "<<<", LESS_LESS_LESS },
   { "&>", AND_GREATER },
+  { "&>>", AND_GREATER_GREATER },
   { "<>", LESS_GREATER },
   { ">|", GREATER_BAR },
+  { "|&", BAR_AND },
   { "EOF", yacc_EOF },
   /* Tokens whose value is the character itself */
   { ">", '>' },
@@ -1744,12 +2199,17 @@ shell_getc (remove_quoted_newline)
      int remove_quoted_newline;
 {
   register int i;
-  int c;
+  int c, truncating;
   unsigned char uc;
-  static int mustpop = 0;
 
   QUIT;
 
+  if (sigwinch_received)
+    {
+      sigwinch_received = 0;
+      get_new_window_size (0, (int *)0, (int *)0);
+    }
+      
   if (eol_ungetc_lookahead)
     {
       c = eol_ungetc_lookahead;
@@ -1770,28 +2230,43 @@ shell_getc (remove_quoted_newline)
     {
       line_number++;
 
+      /* Let's not let one really really long line blow up memory allocation */
+      if (shell_input_line && shell_input_line_size >= 32768)
+       {
+         free (shell_input_line);
+         shell_input_line = 0;
+         shell_input_line_size = 0;
+       }
+
     restart_read:
 
       /* Allow immediate exit if interrupted during input. */
       QUIT;
 
-      i = 0;
+      i = truncating = 0;
       shell_input_line_terminator = 0;
 
+      /* If the shell is interatctive, but not currently printing a prompt
+         (interactive_shell && interactive == 0), we don't want to print
+         notifies or cleanup the jobs -- we want to defer it until we do
+         print the next prompt. */
+      if (interactive_shell == 0 || SHOULD_PROMPT())
+       {
 #if defined (JOB_CONTROL)
       /* This can cause a problem when reading a command as the result
         of a trap, when the trap is called from flush_child.  This call
         had better not cause jobs to disappear from the job table in
         that case, or we will have big trouble. */
-      notify_and_cleanup ();
+         notify_and_cleanup ();
 #else /* !JOB_CONTROL */
-      cleanup_dead_jobs ();
+         cleanup_dead_jobs ();
 #endif /* !JOB_CONTROL */
+       }
 
 #if defined (READLINE)
-      if (interactive && bash_input.type != st_string && no_line_editing)
+      if (no_line_editing && SHOULD_PROMPT())
 #else
-      if (interactive && bash_input.type != st_string)
+      if (SHOULD_PROMPT())
 #endif
        print_prompt ();
 
@@ -1813,7 +2288,30 @@ shell_getc (remove_quoted_newline)
              continue;
            }
 
-         RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
+         /* Theoretical overflow */
+         /* If we can't put 256 bytes more into the buffer, allocate
+            everything we can and fill it as full as we can. */
+         /* XXX - we ignore rest of line using `truncating' flag */
+         if (shell_input_line_size > (SIZE_MAX - 256))
+           {
+             size_t n;
+
+             n = SIZE_MAX - i; /* how much more can we put into the buffer? */
+             if (n <= 2)       /* we have to save 1 for the newline added below */
+               {
+                 if (truncating == 0)
+                   internal_warning("shell_getc: shell_input_line_size (%zu) exceeds SIZE_MAX (%llu): line truncated", shell_input_line_size, SIZE_MAX);
+                 shell_input_line[i] = '\0';
+                 truncating = 1;
+               }
+             if (shell_input_line_size < SIZE_MAX)
+               {
+                 shell_input_line_size = SIZE_MAX;
+                 shell_input_line = xrealloc (shell_input_line, shell_input_line_size);
+               }
+           }
+         else
+           RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
 
          if (c == EOF)
            {
@@ -1827,7 +2325,8 @@ shell_getc (remove_quoted_newline)
              break;
            }
 
-         shell_input_line[i++] = c;
+         if (truncating == 0 || c == '\n')
+           shell_input_line[i++] = c;
 
          if (c == '\n')
            {
@@ -1866,7 +2365,7 @@ shell_getc (remove_quoted_newline)
              shell_input_line = expansions;
              shell_input_line_len = shell_input_line ?
                                        strlen (shell_input_line) : 0;
-             if (!shell_input_line_len)
+             if (shell_input_line_len == 0)
                current_command_line_count--;
 
              /* We have to force the xrealloc below because we don't know
@@ -1891,7 +2390,7 @@ shell_getc (remove_quoted_newline)
          else
            {
              char *hdcs;
-             hdcs = history_delimiting_chars ();
+             hdcs = history_delimiting_chars (shell_input_line);
              if (hdcs && hdcs[0] == ';')
                maybe_add_history (shell_input_line);
            }
@@ -1902,16 +2401,22 @@ shell_getc (remove_quoted_newline)
       if (shell_input_line)
        {
          /* Lines that signify the end of the shell's input should not be
-            echoed. */
+            echoed.  We should not echo lines while parsing command
+            substitutions with recursive calls into the parsing engine; those
+            should only be echoed once when we read the word.  That is the
+            reason for the test against shell_eof_token, which is set to a
+            right paren when parsing the contents of command substitutions. */
          if (echo_input_at_read && (shell_input_line[0] ||
-                                    shell_input_line_terminator != EOF))
+                                      shell_input_line_terminator != EOF) &&
+                                    shell_eof_token == 0)
            fprintf (stderr, "%s\n", shell_input_line);
        }
       else
        {
          shell_input_line_size = 0;
          prompt_string_pointer = &current_prompt_string;
-         prompt_again ();
+         if (SHOULD_PROMPT ())
+           prompt_again ();
          goto restart_read;
        }
 
@@ -1919,7 +2424,7 @@ shell_getc (remove_quoted_newline)
         not already end in an EOF character.  */
       if (shell_input_line_terminator != EOF)
        {
-         if (shell_input_line_len + 3 > shell_input_line_size)
+         if (shell_input_line_size < SIZE_MAX && shell_input_line_len > shell_input_line_size - 3)
            shell_input_line = (char *)xrealloc (shell_input_line,
                                        1 + (shell_input_line_size += 2));
 
@@ -1930,45 +2435,86 @@ shell_getc (remove_quoted_newline)
        }
     }
 
+next_alias_char:
   uc = shell_input_line[shell_input_line_index];
 
   if (uc)
     shell_input_line_index++;
 
-  if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
-    {
-       prompt_again ();
-       line_number++;
-       goto restart_read;
-    }
-
 #if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
   /* If UC is NULL, we have reached the end of the current input string.  If
      pushed_string_list is non-empty, it's time to pop to the previous string
      because we have fully consumed the result of the last alias expansion.
      Do it transparently; just return the next character of the string popped
      to. */
-  if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+  /* If pushed_string_list != 0 but pushed_string_list->expander == 0 (not
+     currently tested) and the flags value is not PSH_SOURCE, we are not
+     parsing an alias, we have just saved one (push_string, when called by
+     the parse_dparen code) In this case, just go on as well.  The PSH_SOURCE
+     case is handled below. */
+pop_alias:
+  if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE)
     {
-      if (mustpop)
-       {
-         pop_string ();
-         uc = shell_input_line[shell_input_line_index];
-         if (uc)
-           shell_input_line_index++;
-         mustpop--;
-       }
-      else
-       {
-         mustpop++;
-         uc = ' ';
-       }
+      pop_string ();
+      uc = shell_input_line[shell_input_line_index];
+      if (uc)
+       shell_input_line_index++;
     }
 #endif /* ALIAS || DPAREN_ARITHMETIC */
 
-  if (!uc && shell_input_line_terminator == EOF)
+  if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
+    {
+       if (SHOULD_PROMPT ())
+         prompt_again ();
+       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
+          we inhibit the appending of a space in mk_alexpansion() if newline
+          is the last character).  If it's not the last character, we need
+          to consume the quoted newline and move to the next character in
+          the expansion. */
+#if defined (ALIAS)
+       if (expanding_alias () && shell_input_line[shell_input_line_index+1] == '\0')
+         {
+           uc = 0;
+           goto pop_alias;
+         }
+       else if (expanding_alias () && shell_input_line[shell_input_line_index+1] != '\0')
+         {
+           shell_input_line_index++;   /* skip newline */
+           goto next_alias_char;       /* and get next character */
+         }
+       else
+#endif 
+         goto restart_read;
+    }
+
+  if (uc == 0 && shell_input_line_terminator == EOF)
     return ((shell_input_line_index != 0) ? '\n' : EOF);
 
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* We already know that we are not parsing an alias expansion because of the
+     check for expanding_alias() above.  This knows how parse_and_execute
+     handles switching to st_string input while an alias is being expanded,
+     hence the check for pushed_string_list without pushed_string_list->expander
+     and the check for PSH_SOURCE as pushed_string_list->flags.
+     parse_and_execute and parse_string both change the input type to st_string
+     and place the string to be parsed and executed into location.string, so
+     we should not stop reading that until the pointer is '\0'.
+     The check for shell_input_line_terminator may be superfluous.
+
+     This solves the problem of `.' inside a multi-line alias with embedded
+     newlines executing things out of order. */
+  if (uc == 0 && bash_input.type == st_string && *bash_input.location.string &&
+      pushed_string_list && pushed_string_list->flags == PSH_SOURCE &&
+      shell_input_line_terminator == 0)
+    {
+      shell_input_line_index = 0;
+      goto restart_read;
+    }
+#endif
+
   return (uc);
 }
 
@@ -2013,29 +2559,21 @@ discard_until (character)
 }
 
 void
-execute_prompt_command (command)
-     char *command;
+execute_variable_command (command, vname)
+     char *command, *vname;
 {
-  sh_builtin_func_t *temp_last, *temp_this;
   char *last_lastarg;
-  int temp_exit_value, temp_eof_encountered;
+  sh_parser_state_t ps;
 
-  temp_last = last_shell_builtin;
-  temp_this = this_shell_builtin;
-  temp_exit_value = last_command_exit_value;
-  temp_eof_encountered = eof_encountered;
+  save_parser_state (&ps);
   last_lastarg = get_string_value ("_");
   if (last_lastarg)
     last_lastarg = savestring (last_lastarg);
 
-  parse_and_execute (savestring (command), "PROMPT_COMMAND", SEVAL_NONINT|SEVAL_NOHIST);
-
-  last_shell_builtin = temp_last;
-  this_shell_builtin = temp_this;
-  last_command_exit_value = temp_exit_value;
-  eof_encountered = temp_eof_encountered;
+  parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST);
 
-  bind_variable ("_", last_lastarg);
+  restore_parser_state (&ps);
+  bind_variable ("_", last_lastarg, 0);
   FREE (last_lastarg);
 
   if (token_to_read == '\n')   /* reset_parser was called */
@@ -2066,7 +2604,7 @@ yylex ()
         We do this only if it is time to do so. Notice that only here
         is the mail alarm reset; nothing takes place in check_mail ()
         except the checking of mail.  Please don't change this. */
-      if (prompt_is_ps1 && time_to_check_mail ())
+      if (prompt_is_ps1 && parse_and_execute_level == 0 && time_to_check_mail ())
        {
          check_mail ();
          reset_mail_timer ();
@@ -2074,7 +2612,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 && interactive)
+      if (token_to_read == 0 && SHOULD_PROMPT ())
        prompt_again ();
     }
 
@@ -2082,6 +2620,15 @@ yylex ()
   token_before_that = last_read_token;
   last_read_token = current_token;
   current_token = read_token (READ);
+
+  if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+    {
+      current_token = yacc_EOF;
+      if (bash_input.type == st_string)
+       rewind_input_string ();
+    }
+  parser_state &= ~PST_EOFTOKEN;
+
   return (current_token);
 }
 
@@ -2092,10 +2639,14 @@ static int esacs_needed_count;
 void
 gather_here_documents ()
 {
-  int r = 0;
+  int r;
+
+  r = 0;
   while (need_here_doc)
     {
-      make_here_document (redir_stack[r++]);
+      parser_state |= PST_HEREDOC;
+      make_here_document (redir_stack[r++], line_number);
+      parser_state &= ~PST_HEREDOC;
       need_here_doc--;
     }
 }
@@ -2105,11 +2656,11 @@ gather_here_documents ()
 static int open_brace_count;
 
 #define command_token_position(token) \
-  (((token) == ASSIGNMENT_WORD) || \
-   ((token) != SEMI_SEMI && reserved_word_acceptable(token)))
+  (((token) == ASSIGNMENT_WORD) || (parser_state&PST_REDIRLIST) || \
+   ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token)))
 
-#define assignment_acceptable(token) command_token_position(token) && \
-                                       ((parser_state & PST_CASEPAT) == 0)
+#define assignment_acceptable(token) \
+  (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
 
 /* Check to see if TOKEN is a reserved word and return the token
    value if it is. */
@@ -2124,7 +2675,7 @@ static int open_brace_count;
            { \
              if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
                break; \
-             if (word_token_alist[i].token == TIME) \
+             if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \
                break; \
              if (word_token_alist[i].token == ESAC) \
                parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
@@ -2157,6 +2708,25 @@ static int open_brace_count;
 
        Special cases that disqualify:
         In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+
+static char *
+mk_alexpansion (s)
+     char *s;
+{
+  int l;
+  char *r;
+
+  l = strlen (s);
+  r = xmalloc (l + 2);
+  strcpy (r, s);
+  /* If the last character in the alias is a newline, don't add a trailing
+     space to the expansion.  Works with shell_getc above. */
+  if (r[l - 1] != ' ' && r[l - 1] != '\n')
+    r[l++] = ' ';
+  r[l] = '\0';
+  return r;
+}
+
 static int
 alias_expand_token (tokstr)
      char *tokstr;
@@ -2173,7 +2743,12 @@ alias_expand_token (tokstr)
       if (ap && (ap->flags & AL_BEINGEXPANDED))
        return (NO_EXPANSION);
 
-      expanded = ap ? savestring (ap->value) : (char *)NULL;
+      /* mk_alexpansion puts an extra space on the end of the alias expansion,
+         so the lookahead by the parser works right.  If this gets changed,
+         make sure the code in shell_getc that deals with reaching the end of
+         an expanded alias is changed with it. */
+      expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL;
+
       if (expanded)
        {
          push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
@@ -2191,6 +2766,20 @@ static int
 time_command_acceptable ()
 {
 #if defined (COMMAND_TIMING)
+  int i;
+
+  if (posixly_correct && shell_compatibility_level > 41)
+    {
+      /* Quick check of the rest of the line to find the next token.  If it
+        begins with a `-', Posix says to not return `time' as the token.
+        This was interp 267. */
+      i = shell_input_line_index;
+      while (i < shell_input_line_len && (shell_input_line[i] == ' ' || shell_input_line[i] == '\t'))
+        i++;
+      if (shell_input_line[i] == '-')
+       return 0;
+    }
+
   switch (last_read_token)
     {
     case 0:
@@ -2204,6 +2793,10 @@ time_command_acceptable ()
     case ELSE:
     case '{':          /* } */
     case '(':          /* ) */
+    case BANG:         /* ! time pipeline */
+    case TIME:         /* time time pipeline */
+    case TIMEOPT:      /* time -p time pipeline */
+    case TIMEIGN:      /* time -p -- ... */
       return 1;
     default:
       return 0;
@@ -2230,6 +2823,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 COND_END if the parser is currently parsing
        a conditional expression ((parser_state & PST_CONDEXPR) != 0)
@@ -2315,13 +2909,11 @@ 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 -p'. */
+  if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
 #endif
 
-#if defined (COMMAND_TIMING)
-  if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
-    return (TIME);
-#endif /* COMMAND_TIMING */
-
 #if defined (COND_COMMAND) /* [[ */
   if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
     return (COND_END);
@@ -2338,6 +2930,12 @@ reset_parser ()
   dstack.delimiter_depth = 0;  /* No delimiters found so far. */
   open_brace_count = 0;
 
+#if defined (EXTENDED_GLOB)
+  /* Reset to global value of extended glob */
+  if (parser_state & PST_EXTPAT)
+    extended_glob = global_extglob;
+#endif
+
   parser_state = 0;
 
 #if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
@@ -2355,6 +2953,7 @@ reset_parser ()
   FREE (word_desc_to_read);
   word_desc_to_read = (WORD_DESC *)NULL;
 
+  current_token = '\n';                /* XXX */
   last_read_token = '\n';
   token_to_read = '\n';
 }
@@ -2411,7 +3010,7 @@ read_token (command)
 #endif /* ALIAS */
 
   /* Read a single word from input.  Start by skipping blanks. */
-  while ((character = shell_getc (1)) != EOF && whitespace (character))
+  while ((character = shell_getc (1)) != EOF && shellblank (character))
     ;
 
   if (character == EOF)
@@ -2439,9 +3038,14 @@ read_token (command)
       parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
 
+      parser_state &= ~PST_ASSIGNOK;
+
       return (character);
     }
 
+  if (parser_state & PST_REGEXP)
+    goto tokword;
+
   /* Shell meta-characters. */
   if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
     {
@@ -2452,6 +3056,8 @@ read_token (command)
        parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
 
+      parser_state &= ~PST_ASSIGNOK;
+
       peek_char = shell_getc (1);
       if (character == peek_char)
        {
@@ -2461,9 +3067,9 @@ read_token (command)
              /* If '<' then we could be at "<<" or at "<<-".  We have to
                 look ahead one more character. */
              peek_char = shell_getc (1);
-             if (peek_char == '-')
+             if MBTEST(peek_char == '-')
                return (LESS_LESS_MINUS);
-             else if (peek_char == '<')
+             else if MBTEST(peek_char == '<')
                return (LESS_LESS_LESS);
              else
                {
@@ -2479,7 +3085,15 @@ read_token (command)
 #if defined (ALIAS)
              parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
-             return (SEMI_SEMI);
+
+             peek_char = shell_getc (1);
+             if MBTEST(peek_char == '&')
+               return (SEMI_SEMI_AND);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (SEMI_SEMI);
+               }
 
            case '&':
              return (AND_AND);
@@ -2505,8 +3119,27 @@ read_token (command)
        return (LESS_GREATER);
       else if MBTEST(character == '>' && peek_char == '|')
        return (GREATER_BAR);
-      else if MBTEST(peek_char == '>' && character == '&')
-       return (AND_GREATER);
+      else if MBTEST(character == '&' && peek_char == '>')
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '>')
+           return (AND_GREATER_GREATER);
+         else
+           {
+             shell_ungetc (peek_char);
+             return (AND_GREATER);
+           }
+       }
+      else if MBTEST(character == '|' && peek_char == '&')
+       return (BAR_AND);
+      else if MBTEST(character == ';' && peek_char == '&')
+       {
+         parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         return (SEMI_AND);
+       }
 
       shell_ungetc (peek_char);
 
@@ -2546,6 +3179,7 @@ read_token (command)
   if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
     return (character);
 
+tokword:
   /* Okay, if we got this far, we have to read a word.  Read one,
      and then check it against the known ones. */
   result = read_token_word (character);
@@ -2559,27 +3193,76 @@ read_token (command)
 /*
  * Match a $(...) or other grouping construct.  This has to handle embedded
  * quoted strings ('', ``, "") and nested constructs.  It also must handle
- * reprompting the user, if necessary, after reading a newline (unless the
- * P_NONL flag is passed), and returning correct error values if it reads
- * EOF.
+ * reprompting the user, if necessary, after reading a newline, and returning
+ * correct error values if it reads EOF.
  */
-#define P_FIRSTCLOSE   0x01
-#define P_ALLOWESC     0x02
+#define P_FIRSTCLOSE   0x0001
+#define P_ALLOWESC     0x0002
+#define P_DQUOTE       0x0004
+#define P_COMMAND      0x0008  /* parsing a command, so look for comments */
+#define P_BACKQUOTE    0x0010  /* parsing a backquoted command substitution */
+#define P_ARRAYSUB     0x0020  /* parsing a [...] array subscript for assignment */
+#define P_DOLBRACE     0x0040  /* parsing a ${...} construct */
+
+/* Lexical state while parsing a grouping construct or $(...). */
+#define LEX_WASDOL     0x001
+#define LEX_CKCOMMENT  0x002
+#define LEX_INCOMMENT  0x004
+#define LEX_PASSNEXT   0x008
+#define LEX_RESWDOK    0x010
+#define LEX_CKCASE     0x020
+#define LEX_INCASE     0x040
+#define LEX_INHEREDOC  0x080
+#define LEX_HEREDELIM  0x100           /* reading here-doc delimiter */
+#define LEX_STRIPDOC   0x200           /* <<- strip tabs from here doc delim */
+#define LEX_INWORD     0x400
+
+#define COMSUB_META(ch)                ((ch) == ';' || (ch) == '&' || (ch) == '|')
+
+#define CHECK_NESTRET_ERROR() \
+  do { \
+    if (nestret == &matched_pair_error) \
+      { \
+       free (ret); \
+       return &matched_pair_error; \
+      } \
+  } while (0)
+
+#define APPEND_NESTRET() \
+  do { \
+    if (nestlen) \
+      { \
+       RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); \
+       strcpy (ret + retind, nestret); \
+       retind += nestlen; \
+      } \
+  } while (0)
 
 static char matched_pair_error;
+
 static char *
 parse_matched_pair (qc, open, close, lenp, flags)
      int qc;   /* `"' if this construct is within double quotes */
      int open, close;
      int *lenp, flags;
 {
-  int count, ch, was_dollar;
-  int pass_next_character, nestlen, ttranslen, start_lineno;
+  int count, ch, tflags;
+  int nestlen, ttranslen, start_lineno;
   char *ret, *nestret, *ttrans;
-  int retind, retsize;
+  int retind, retsize, rflags;
+  int dolbrace_state;
+
+  dolbrace_state = (flags & P_DOLBRACE) ? DOLBRACE_PARAM : 0;
 
+/*itrace("parse_matched_pair[%d]: open = %c close = %c flags = %d", line_number, open, close, flags);*/
   count = 1;
-  pass_next_character = was_dollar = 0;
+  tflags = 0;
+
+  if ((flags & P_COMMAND) && qc != '`' && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
 
   ret = (char *)xmalloc (retsize = 64);
   retind = 0;
@@ -2587,35 +3270,66 @@ parse_matched_pair (qc, open, close, lenp, flags)
   start_lineno = line_number;
   while (count)
     {
-      ch = shell_getc ((qc != '\'' || (flags & P_ALLOWESC)) && pass_next_character == 0);
+      ch = shell_getc (qc != '\'' && (tflags & (LEX_PASSNEXT)) == 0);
+
       if (ch == EOF)
        {
          free (ret);
-         parser_error (start_lineno, "unexpected EOF while looking for matching `%c'", close);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
          EOF_Reached = 1;      /* XXX */
          return (&matched_pair_error);
        }
 
       /* Possible reprompting. */
-      if (ch == '\n' && interactive &&
-           (bash_input.type == st_stdin || bash_input.type == st_stream))
+      if (ch == '\n' && SHOULD_PROMPT ())
        prompt_again ();
 
-      if (pass_next_character)         /* last char was backslash */
+      /* Don't bother counting parens or doing anything else if in a comment
+        or part of a case statement */
+      if (tflags & LEX_INCOMMENT)
        {
-         pass_next_character = 0;
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if (ch == '\n')
+           tflags &= ~LEX_INCOMMENT;
+
+         continue;
+       }
+
+      /* Not exactly right yet, should handle shell metacharacters, too.  If
+        any changes are made to this test, make analogous changes to subst.c:
+        extract_delimited_string(). */
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+       tflags |= LEX_INCOMMENT;
+
+      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
+       {
+         tflags &= ~LEX_PASSNEXT;
          if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
            {
-             if (retind > 0) retind--; /* swallow previously-added backslash */
+             if (retind > 0)
+               retind--;       /* swallow previously-added backslash */
              continue;
            }
 
          RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
-         if MBTEST(ch == CTLESC || ch == CTLNUL)
+         if MBTEST(ch == CTLESC)
            ret[retind++] = CTLESC;
          ret[retind++] = ch;
          continue;
        }
+      /* If we're reparsing the input (e.g., from parse_string_to_word_list),
+        we've already prepended CTLESC to single-quoted results of $'...'.
+        We may want to do this for other CTLESC-quoted characters in
+        reparse, too. */
+      else if MBTEST((parser_state & PST_REPARSE) && open == '\'' && (ch == CTLESC || ch == CTLNUL))
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         continue;
+       }
       else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
        {
          RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
@@ -2625,11 +3339,9 @@ parse_matched_pair (qc, open, close, lenp, flags)
        }
       else if MBTEST(ch == close)              /* ending delimiter */
        count--;
-#if 1
       /* handle nested ${...} specially. */
-      else if MBTEST(open != close && was_dollar && open == '{' && ch == open) /* } */
+      else if MBTEST(open != close && (tflags & LEX_WASDOL) && open == '{' && ch == open) /* } */
        count++;
-#endif
       else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open)      /* nested begin */
        count++;
 
@@ -2637,115 +3349,699 @@ parse_matched_pair (qc, open, close, lenp, flags)
       RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
       ret[retind++] = ch;
 
-      if (open == '\'')                        /* '' inside grouping construct */
-       {
-         if MBTEST((flags & P_ALLOWESC) && ch == '\\')
-           pass_next_character++;
-         continue;
-       }
+      /* If we just read the ending character, don't bother continuing. */
+      if (count == 0)
+       break;
+
+      if (open == '\'')                        /* '' inside grouping construct */
+       {
+         if MBTEST((flags & P_ALLOWESC) && ch == '\\')
+           tflags |= LEX_PASSNEXT;
+         continue;
+       }
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       tflags |= LEX_PASSNEXT;
+
+      /* Based on which dolstate is currently in (param, op, or word),
+        decide what the op is.  We're really only concerned if it's % or
+        #, so we can turn on a flag that says whether or not we should
+        treat single quotes as special when inside a double-quoted
+        ${...}. This logic must agree with subst.c:extract_dollar_brace_string
+        since they share the same defines. */
+      /* FLAG POSIX INTERP 221 */
+      if (flags & P_DOLBRACE)
+        {
+          /* ${param%[%]word} */
+         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param#[#]word} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param/[/]pat/rep} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE2;   /* XXX */
+          /* ${param^[^]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param,[,]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
+           dolbrace_state = DOLBRACE_OP;
+         else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
+           dolbrace_state = DOLBRACE_WORD;
+        }
+
+      /* The big hammer.  Single quotes aren't special in double quotes.  The
+         problem is that Posix used to say the single quotes are semi-special:
+         within a double-quoted ${...} construct "an even number of
+         unescaped double-quotes or single-quotes, if any, shall occur." */
+      /* This was changed in Austin Group Interp 221 */
+      if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
+       continue;
+
+      /* Could also check open == '`' if we want to parse grouping constructs
+        inside old-style command substitution. */
+      if (open != close)               /* a grouping construct */
+       {
+         if MBTEST(shellquote (ch))
+           {
+             /* '', ``, or "" inside $(...) or other grouping construct. */
+             push_delimiter (dstack, ch);
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'')    /* $'...' inside group */
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+             else
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+             pop_delimiter (dstack);
+             CHECK_NESTRET_ERROR ();
+
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Translate $'...' here. */
+                 ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+                 xfree (nestret);
+
+                 /* If we're parsing a double-quoted brace expansion and we are
+                    not in a place where single quotes are treated specially,
+                    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))
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else if ((rflags & P_DQUOTE) == 0)
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else
+                   {
+                     nestret = ttrans;
+                     nestlen = ttranslen;
+                   }
+                 retind -= 2;          /* back up before the $' */
+               }
+             else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Locale expand $"..." here. */
+                 ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+                 xfree (nestret);
+
+                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 nestlen = ttranslen + 2;
+                 retind -= 2;          /* back up before the $" */
+               }
+
+             APPEND_NESTRET ();
+             FREE (nestret);
+           }
+         else if ((flags & P_ARRAYSUB) && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))      /* ) } ] */
+           goto parse_dollar_word;
+       }
+      /* Parse an old-style command substitution within double quotes as a
+        single word. */
+      /* XXX - sh and ksh93 don't do this - XXX */
+      else if MBTEST(open == '"' && ch == '`')
+       {
+         nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      else if MBTEST(open != '`' && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))    /* ) } ] */
+       /* check for $(), $[], or ${} inside quoted string. */
+       {
+parse_dollar_word:
+         if (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);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
+    }
+
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/
+  return ret;
+}
+
+/* Parse a $(...) command substitution.  This is messier than I'd like, and
+   reproduces a lot more of the token-reading code than I'd like. */
+static char *
+parse_comsub (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, peekc, tflags, lex_rwlen, lex_wlen, lex_firstind;
+  int nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans, *heredelim;
+  int retind, retsize, rflags, hdlen;
+
+  /* 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));
+
+/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
+  count = 1;
+  tflags = LEX_RESWDOK;
+
+  if ((flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCASE;
+  if ((tflags & LEX_CKCASE) && (interactive == 0 || interactive_comments))
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  lex_rwlen = lex_wlen = 0;
+
+  heredelim = 0;
+  lex_firstind = -1;
+
+  while (count)
+    {
+comsub_readchar:
+      ch = shell_getc (qc != '\'' && (tflags & (LEX_INCOMMENT|LEX_PASSNEXT)) == 0);
+
+      if (ch == EOF)
+       {
+eof_error:
+         free (ret);
+         FREE (heredelim);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* If we hit the end of a line and are reading the contents of a here
+        document, and it's not the same line that the document starts on,
+        check for this line being the here doc delimiter.  Otherwise, if
+        we're in a here document, mark the next character as the beginning
+        of a line. */
+      if (ch == '\n')
+       {
+         if ((tflags & LEX_HEREDELIM) && heredelim)
+           {
+             tflags &= ~LEX_HEREDELIM;
+             tflags |= LEX_INHEREDOC;
+             lex_firstind = retind + 1;
+           }
+         else if (tflags & LEX_INHEREDOC)
+           {
+             int tind;
+             tind = lex_firstind;
+             while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
+               tind++;
+             if (STREQN (ret + tind, heredelim, hdlen))
+               {
+                 tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+                 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: in here doc, ch == close, retind - firstind = %d hdlen = %d retind = %d", 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);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+             free (heredelim);
+             heredelim = 0;
+             lex_firstind = -1;
+           }
+       }
+
+      /* Don't bother counting parens or doing anything else if in a comment */
+      if (tflags & (LEX_INCOMMENT|LEX_INHEREDOC))
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if ((tflags & LEX_INCOMMENT) && ch == '\n')
+           {
+/*itrace("parse_comsub:%d: lex_incomment -> 0 ch = `%c'", line_number, ch);*/
+             tflags &= ~LEX_INCOMMENT;
+           }
+
+         continue;
+       }
+
+      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;
+           }
+
+         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;
+           }
+       }
+
+      /* 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;
+        }
+
+      /* 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;
+           }
+#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);
+                 free (nestret);
+                 hdlen = STRLEN(heredelim);
+/*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/
+               }
+             if (ch == '\n')
+               {
+                 tflags |= LEX_INHEREDOC;
+                 tflags &= ~LEX_HEREDELIM;
+                 lex_firstind = retind + 1;
+               }
+             else
+               lex_firstind = -1;
+           }
+       }
+
+      /* Meta-characters that can introduce a reserved word.  Not perfect yet. */
+      if MBTEST((tflags & LEX_RESWDOK) == 0 && (tflags & LEX_CKCASE) && (tflags & LEX_INCOMMENT) == 0 && (shellmeta(ch) || 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);
+           }
+       }
+
+      /* If we can read a reserved word, try to read one. */
+      if (tflags & LEX_RESWDOK)
+       {
+         if MBTEST(islower (ch))
+           {
+             /* Add this character. */
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = ch;
+             lex_rwlen++;
+             continue;
+           }
+         else if MBTEST(lex_rwlen == 4 && shellbreak (ch))
+           {
+             if (STREQN (ret + retind - 4, "case", 4))
+               {
+                 tflags |= LEX_INCASE;
+/*itrace("parse_comsub:%d: found `case', lex_incase -> 1 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 -> 0", line_number);*/
+               }
+             tflags &= ~LEX_RESWDOK;
+           }
+         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(ch) || ch == '\n') &&
+                         lex_rwlen == 2 &&
+                         STREQN (ret + retind - 2, "do", 2))
+           {
+/*itrace("parse_comsub:%d: lex_incase == 1 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 (ch) && lex_rwlen > 0)
+           {
+             tflags &= ~LEX_RESWDOK;
+/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
+           }
+#endif
+       }
+
+      /* 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
+           ch = peekc;         /* fall through and continue XXX */
+       }
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (((tflags & LEX_RESWDOK) && lex_rwlen == 0) || ((tflags & LEX_INWORD) && lex_wlen == 0)))
+       {
+/*itrace("parse_comsub:%d: lex_incomment -> 1 (%d)", line_number, __LINE__);*/
+         tflags |= LEX_INCOMMENT;
+       }
+
+      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);*/
+       }
+
+      /* 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 */
-       pass_next_character++;
+       tflags |= LEX_PASSNEXT;
 
-      if (open != close)               /* a grouping construct */
-       {
-         if MBTEST(shellquote (ch))
+      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))
            {
-             /* '', ``, or "" inside $(...) or other grouping construct. */
-             push_delimiter (dstack, ch);
-             if MBTEST(was_dollar && ch == '\'')       /* $'...' inside group */
-               nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC);
-             else
-               nestret = parse_matched_pair (ch, ch, ch, &nestlen, 0);
-             pop_delimiter (dstack);
-             if (nestret == &matched_pair_error)
-               {
-                 free (ret);
-                 return &matched_pair_error;
-               }
-             if MBTEST(was_dollar && ch == '\'')
+             /* Translate $'...' here. */
+             ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+             xfree (nestret);
+
+             if ((rflags & P_DQUOTE) == 0)
                {
-                 /* Translate $'...' here. */
-                 ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
-                 xfree (nestret);
                  nestret = sh_single_quote (ttrans);
                  free (ttrans);
                  nestlen = strlen (nestret);
-                 retind -= 2;          /* back up before the $' */
                }
-             else if MBTEST(was_dollar && ch == '"')
+             else
                {
-                 /* Locale expand $"..." here. */
-                 ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
-                 xfree (nestret);
-                 nestret = (char *)xmalloc (ttranslen + 3);
-                 nestret[0] = '"';
-                 strcpy (nestret + 1, ttrans);
-                 nestret[ttranslen + 1] = '"';
-                 nestret[ttranslen += 2] = '\0';
-                 free (ttrans);
+                 nestret = ttrans;
                  nestlen = ttranslen;
-                 retind -= 2;          /* back up before the $" */
-               }
-
-             if (nestlen)
-               {
-                 RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64);
-                 strcpy (ret + retind, nestret);
-                 retind += nestlen;
                }
-             FREE (nestret);
-           }
-       }
-      /* Parse an old-style command substitution within double quotes as a
-        single word. */
-      /* XXX - sh and ksh93 don't do this - XXX */
-      else if MBTEST(open == '"' && ch == '`')
-       {
-         nestret = parse_matched_pair (0, '`', '`', &nestlen, 0);
-         if (nestret == &matched_pair_error)
-           {
-             free (ret);
-             return &matched_pair_error;
+             retind -= 2;              /* back up before the $' */
            }
-         if (nestlen)
+         else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
            {
-             RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64);
-             strcpy (ret + retind, nestret);
-             retind += nestlen;
+             /* Locale expand $"..." here. */
+             ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+             xfree (nestret);
+
+             nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+             free (ttrans);
+             nestlen = ttranslen + 2;
+             retind -= 2;              /* back up before the $" */
            }
+
+         APPEND_NESTRET ();
          FREE (nestret);
        }
-      else if MBTEST(was_dollar && (ch == '(' || ch == '{' || ch == '['))      /* ) } ] */
-       /* check for $(), $[], or ${} inside quoted string. */
+      else if MBTEST((tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))   /* ) } ] */
+       /* check for $(), $[], or ${} inside command substitution. */
        {
-         if (open == ch)       /* undo previous increment */
+         if ((tflags & LEX_INCASE) == 0 && open == ch) /* undo previous increment */
            count--;
          if (ch == '(')                /* ) */
-           nestret = parse_matched_pair (0, '(', ')', &nestlen, 0);
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
          else if (ch == '{')           /* } */
-           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE);
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
          else if (ch == '[')           /* ] */
-           nestret = parse_matched_pair (0, '[', ']', &nestlen, 0);
-         if (nestret == &matched_pair_error)
-           {
-             free (ret);
-             return &matched_pair_error;
-           }
-         if (nestlen)
-           {
-             RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64);
-             strcpy (ret + retind, nestret);
-             retind += nestlen;
-           }
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
          FREE (nestret);
        }
-      was_dollar = MBTEST(ch == '$');
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
     }
 
+  FREE (heredelim);
   ret[retind] = '\0';
   if (lenp)
     *lenp = retind;
+/*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/
+  return ret;
+}
+
+/* Recursively call the parser to parse a $(...) command substitution. */
+char *
+xparse_dolparen (base, string, indp, flags)
+     char *base;
+     char *string;
+     int *indp;
+     int flags;
+{
+  sh_parser_state_t ps;
+  sh_input_line_state_t ls;
+  int orig_ind, nc, sflags, orig_eof_token;
+  char *ret, *s, *ep, *ostring;
+
+  /*yydebug = 1;*/
+  orig_ind = *indp;
+  ostring = string;
+
+/*itrace("xparse_dolparen: size = %d shell_input_line = `%s'", shell_input_line_size, shell_input_line);*/
+  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;
+
+  /*(*/
+  parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
+  shell_eof_token = ')';
+  parse_string (string, "command substitution", sflags, &ep);
+
+  shell_eof_token = orig_eof_token;
+  restore_parser_state (&ps);
+  reset_parser ();
+  /* reset_parser clears shell_input_line and associated variables */
+  restore_input_line_state (&ls);
+  if (interactive)
+    token_to_read = 0;
+
+  /* Need to find how many characters parse_and_execute consumed, update
+     *indp, if flags != 0, copy the portion of the string parsed into RET
+     and return it.  If flags & 1 (EX_NOALLOC) we can return NULL. */
+
+  /*(*/
+  if (ep[-1] != ')')
+    {
+#if DEBUG
+      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 (base[*indp] != ')')
+    itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
+#endif
+
+  if (flags & SX_NOALLOC) 
+    return (char *)NULL;
+
+  if (nc == 0)
+    {
+      ret = xmalloc (1);
+      ret[0] = '\0';
+    }
+  else
+    ret = substring (ostring, 0, nc - 1);
+
   return ret;
 }
 
@@ -2758,27 +4054,20 @@ static int
 parse_dparen (c)
      int c;
 {
-  int cmdtyp, len, sline;
-  char *wval, *wv2;
+  int cmdtyp, sline;
+  char *wval;
   WORD_DESC *wd;
 
 #if defined (ARITH_FOR_COMMAND)
   if (last_read_token == FOR)
     {
       arith_for_lineno = line_number;
-      cmdtyp = parse_arith_cmd (&wval);
+      cmdtyp = parse_arith_cmd (&wval, 0);
       if (cmdtyp == 1)
        {
-         /* parse_arith_cmd adds quotes at the beginning and end
-            of the string it returns; we need to take those out. */
-         len = strlen (wval);
-         wv2 = (char *)xmalloc (len);
-         strncpy (wv2, wval + 1, len - 2);
-         wv2[len - 2] = '\0';
-         wd = make_word (wv2);
+         wd = alloc_word_desc ();
+         wd->word = wval;
          yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
-         free (wval);
-         free (wv2);
          return (ARITH_FOR_EXPRS);
        }
       else
@@ -2790,18 +4079,20 @@ parse_dparen (c)
   if (reserved_word_acceptable (last_read_token))
     {
       sline = line_number;
-      cmdtyp = parse_arith_cmd (&wval);
+
+      cmdtyp = parse_arith_cmd (&wval, 0);
       if (cmdtyp == 1) /* arithmetic command */
        {
-         wd = make_word (wval);
-         wd->flags = W_QUOTED;
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
          yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
-         free (wval);  /* make_word copies it */
          return (ARITH_CMD);
        }
       else if (cmdtyp == 0)    /* nested subshell */
        {
          push_string (wval, 0, (alias_t *)NULL);
+         pushed_string_list->flags = PSH_DPAREN;
          if ((parser_state & PST_CASEPAT) == 0)
            parser_state |= PST_SUBSHELL;
          return (c);
@@ -2820,8 +4111,9 @@ parse_dparen (c)
    allocated buffer and make *ep point to that buffer.  Return -1 on an
    error, for example EOF. */
 static int
-parse_arith_cmd (ep)
+parse_arith_cmd (ep, adddq)
      char **ep;
+     int adddq;
 {
   int exp_lineno, rval, c;
   char *ttok, *tokstr;
@@ -2840,20 +4132,28 @@ parse_arith_cmd (ep)
 
   tokstr = (char *)xmalloc (ttoklen + 4);
 
-  /* (( ... )) -> "..." */
-  tokstr[0] = (rval == 1) ? '"' : '(';
-  strncpy (tokstr + 1, ttok, ttoklen - 1);     /* don't copy the final `)' */
-  if (rval == 1)
+  /* if ADDDQ != 0 then (( ... )) -> "..." */
+  if (rval == 1 && adddq)      /* arith cmd, add double quotes */
     {
+      tokstr[0] = '"';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
       tokstr[ttoklen] = '"';
       tokstr[ttoklen+1] = '\0';
     }
-  else
+  else if (rval == 1)          /* arith cmd, don't add double quotes */
+    {
+      strncpy (tokstr, ttok, ttoklen - 1);
+      tokstr[ttoklen-1] = '\0';
+    }
+  else                         /* nested subshell */
     {
+      tokstr[0] = '(';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
       tokstr[ttoklen] = ')';
       tokstr[ttoklen+1] = c;
       tokstr[ttoklen+2] = '\0';
     }
+
   *ep = tokstr;
   FREE (ttok);
   return rval;
@@ -2867,16 +4167,16 @@ cond_error ()
   char *etext;
 
   if (EOF_Reached && cond_token != COND_ERROR)         /* [[ */
-    parser_error (cond_lineno, "unexpected EOF while looking for `]]'");
+    parser_error (cond_lineno, _("unexpected EOF while looking for `]]'"));
   else if (cond_token != COND_ERROR)
     {
       if (etext = error_token_from_token (cond_token))
        {
-         parser_error (cond_lineno, "syntax error in conditional expression: unexpected token `%s'", etext);
+         parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext);
          free (etext);
        }
       else
-       parser_error (cond_lineno, "syntax error in conditional expression");
+       parser_error (cond_lineno, _("syntax error in conditional expression"));
     }
 }
 
@@ -2919,7 +4219,7 @@ cond_skip_newlines ()
 {
   while ((cond_token = read_token (READ)) == '\n')
     {
-      if (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+      if (SHOULD_PROMPT ())
        prompt_again ();
     }
   return (cond_token);
@@ -2954,11 +4254,11 @@ cond_term ()
            dispose_cond_node (term);           /* ( */
          if (etext = error_token_from_token (cond_token))
            {
-             parser_error (lineno, "unexpected token `%s', expected `)'", etext);
+             parser_error (lineno, _("unexpected token `%s', expected `)'"), etext);
              free (etext);
            }
          else
-           parser_error (lineno, "expected `)'");
+           parser_error (lineno, _("expected `)'"));
          COND_RETURN_ERROR ();
        }
       term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL);
@@ -2972,7 +4272,7 @@ cond_term ()
       if (term)
        term->flags |= CMD_INVERT_RETURN;
     }
-  else if (tok == WORD && test_unop (yylval.word->word))
+  else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[2] == 0 && test_unop (yylval.word->word))
     {
       op = yylval.word;
       tok = read_token (READ);
@@ -2986,11 +4286,11 @@ cond_term ()
          dispose_word (op);
          if (etext = error_token_from_token (tok))
            {
-             parser_error (line_number, "unexpected argument `%s' to conditional unary operator", etext);
+             parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext);
              free (etext);
            }
          else
-           parser_error (line_number, "unexpected argument to conditional unary operator");
+           parser_error (line_number, _("unexpected argument to conditional unary operator"));
          COND_RETURN_ERROR ();
        }
 
@@ -3004,7 +4304,20 @@ cond_term ()
       /* binop */
       tok = read_token (READ);
       if (tok == WORD && test_binop (yylval.word->word))
-       op = yylval.word;
+       {
+         op = yylval.word;
+         if (op->word[0] == '=' && (op->word[1] == '\0' || (op->word[1] == '=' && op->word[2] == '\0')))
+           parser_state |= PST_EXTPAT;
+         else if (op->word[0] == '!' && op->word[1] == '=' && op->word[2] == '\0')
+           parser_state |= PST_EXTPAT;
+       }
+#if defined (COND_REGEXP)
+      else if (tok == WORD && STREQ (yylval.word->word, "=~"))
+       {
+         op = yylval.word;
+         parser_state |= PST_REGEXP;
+       }
+#endif
       else if (tok == '<' || tok == '>')
        op = make_word_from_token (tok);  /* ( */
       /* There should be a check before blindly accepting the `)' that we have
@@ -3023,17 +4336,23 @@ cond_term ()
        {
          if (etext = error_token_from_token (tok))
            {
-             parser_error (line_number, "unexpected token `%s', conditional binary operator expected", etext);
+             parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext);
              free (etext);
            }
          else
-           parser_error (line_number, "conditional binary operator expected");
+           parser_error (line_number, _("conditional binary operator expected"));
          dispose_cond_node (tleft);
          COND_RETURN_ERROR ();
        }
 
       /* rhs */
+      if (parser_state & PST_EXTPAT)
+       extended_glob = 1;
       tok = read_token (READ);
+      if (parser_state & PST_EXTPAT)
+       extended_glob = global_extglob;
+      parser_state &= ~(PST_REGEXP|PST_EXTPAT);
+
       if (tok == WORD)
        {
          tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
@@ -3043,11 +4362,11 @@ cond_term ()
        {
          if (etext = error_token_from_token (tok))
            {
-             parser_error (line_number, "unexpected argument `%s' to conditional binary operator", etext);
+             parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext);
              free (etext);
            }
          else
-           parser_error (line_number, "unexpected argument to conditional binary operator");
+           parser_error (line_number, _("unexpected argument to conditional binary operator"));
          dispose_cond_node (tleft);
          dispose_word (op);
          COND_RETURN_ERROR ();
@@ -3058,14 +4377,14 @@ cond_term ()
   else
     {
       if (tok < 256)
-       parser_error (line_number, "unexpected token `%c' in conditional command", tok);
+       parser_error (line_number, _("unexpected token `%c' in conditional command"), tok);
       else if (etext = error_token_from_token (tok))
        {
-         parser_error (line_number, "unexpected token `%s' in conditional command", etext);
+         parser_error (line_number, _("unexpected token `%s' in conditional command"), etext);
          free (etext);
        }
       else
-       parser_error (line_number, "unexpected token %d in conditional command", tok);
+       parser_error (line_number, _("unexpected token %d in conditional command"), tok);
       COND_RETURN_ERROR ();
     }
   return (term);
@@ -3078,6 +4397,7 @@ parse_cond_command ()
 {
   COND_COM *cexp;
 
+  global_extglob = extended_glob;
   cexp = cond_expr ();
   return (make_cond_command (cexp));
 }
@@ -3096,11 +4416,12 @@ token_is_assignment (t, i)
 
   c = t[i]; c1 = t[i+1];
   t[i] = '='; t[i+1] = '\0';
-  r = assignment (t);
+  r = assignment (t, (parser_state & PST_COMPASSIGN) != 0);
   t[i] = c; t[i+1] = c1;
   return r;
 }
 
+/* XXX - possible changes here for `+=' */
 static int
 token_is_ident (t, i)
      char *t;
@@ -3133,6 +4454,10 @@ read_token_word (character)
   /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
   int dollar_present;
 
+  /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound
+     assignment. */
+  int compound_assignment;
+
   /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
   int quoted;
 
@@ -3152,7 +4477,7 @@ read_token_word (character)
 
   token_index = 0;
   all_digit_token = DIGIT (character);
-  dollar_present = quoted = pass_next_character = 0;
+  dollar_present = quoted = pass_next_character = compound_assignment = 0;
 
   for (;;)
     {
@@ -3162,7 +4487,7 @@ read_token_word (character)
       if (pass_next_character)
        {
          pass_next_character = 0;
-         goto got_character;
+         goto got_escaped_character;
        }
 
       cd = current_delimiter (dstack);
@@ -3198,7 +4523,7 @@ read_token_word (character)
       if MBTEST(shellquote (character))
        {
          push_delimiter (dstack, character);
-         ttok = parse_matched_pair (character, character, character, &ttoklen, 0);
+         ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
          pop_delimiter (dstack);
          if (ttok == &matched_pair_error)
            return -1;          /* Bail immediately. */
@@ -3214,9 +4539,34 @@ read_token_word (character)
          goto next_character;
        }
 
+#ifdef COND_REGEXP
+      /* When parsing a regexp as a single word inside a conditional command,
+        we need to special-case characters special to both the shell and
+        regular expressions.  Right now, that is only '(' and '|'. */ /*)*/
+      if MBTEST((parser_state & PST_REGEXP) && (character == '(' || character == '|'))         /*)*/
+       {
+         if (character == '|')
+           goto got_character;
+
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         dollar_present = all_digit_token = 0;
+         goto next_character;
+       }
+#endif /* COND_REGEXP */
+
 #ifdef EXTENDED_GLOB
       /* Parse a ksh-style extended pattern matching specification. */
-      if (extended_glob && PATTERN_CHAR (character))
+      if MBTEST(extended_glob && PATTERN_CHAR (character))
        {
          peek_char = shell_getc (1);
          if MBTEST(peek_char == '(')           /* ) */
@@ -3226,7 +4576,7 @@ read_token_word (character)
              pop_delimiter (dstack);
              if (ttok == &matched_pair_error)
                return -1;              /* Bail immediately. */
-             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3,
                                      token_buffer_size,
                                      TOKEN_DEFAULT_GROW_SIZE);
              token[token_index++] = character;
@@ -3248,11 +4598,11 @@ read_token_word (character)
        {
          peek_char = shell_getc (1);
          /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
-         if MBTEST(peek_char == '(' || \
+         if MBTEST(peek_char == '(' ||
                ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
            {
              if (peek_char == '{')             /* } */
-               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE);
+               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
              else if (peek_char == '(')                /* ) */
                {
                  /* XXX - push and pop the `(' as a delimiter for use by
@@ -3261,14 +4611,14 @@ read_token_word (character)
                     history literally rather than causing a possibly-
                     incorrect `;' to be added. ) */
                  push_delimiter (dstack, peek_char);
-                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+                 ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
                  pop_delimiter (dstack);
                }
              else
                ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
              if (ttok == &matched_pair_error)
                return -1;              /* Bail immediately. */
-             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3,
                                      token_buffer_size,
                                      TOKEN_DEFAULT_GROW_SIZE);
              token[token_index++] = character;
@@ -3297,13 +4647,14 @@ read_token_word (character)
                {
                  ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
                  free (ttok);
+
                  /* Insert the single quotes and correctly quote any
                     embedded single quotes (allowed because P_ALLOWESC was
                     passed to parse_matched_pair). */
                  ttok = sh_single_quote (ttrans);
                  free (ttrans);
+                 ttranslen = strlen (ttok);
                  ttrans = ttok;
-                 ttranslen = strlen (ttrans);
                }
              else
                {
@@ -3312,16 +4663,13 @@ read_token_word (character)
                  free (ttok);
 
                  /* Add the double quotes back */
-                 ttok = (char *)xmalloc (ttranslen + 3);
-                 ttok[0] = '"';
-                 strcpy (ttok + 1, ttrans);
-                 ttok[ttranslen + 1] = '"';
-                 ttok[ttranslen += 2] = '\0';
+                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
                  free (ttrans);
+                 ttranslen += 2;
                  ttrans = ttok;
                }
 
-             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 1,
                                      token_buffer_size,
                                      TOKEN_DEFAULT_GROW_SIZE);
              strcpy (token + token_index, ttrans);
@@ -3335,17 +4683,13 @@ read_token_word (character)
             shell's single-character parameter expansions, and set flags.*/
          else if MBTEST(character == '$' && peek_char == '$')
            {
-             ttok = (char *)xmalloc (3);
-             ttok[0] = ttok[1] = '$';
-             ttok[2] = '\0';
              RESIZE_MALLOCED_BUFFER (token, token_index, 3,
                                      token_buffer_size,
                                      TOKEN_DEFAULT_GROW_SIZE);
-             strcpy (token + token_index, ttok);
-             token_index += 2;
+             token[token_index++] = '$';
+             token[token_index++] = peek_char;
              dollar_present = 1;
              all_digit_token = 0;
-             FREE (ttok);
              goto next_character;
            }
          else
@@ -3353,10 +4697,14 @@ read_token_word (character)
        }
 
 #if defined (ARRAY_VARS)
-      /* Identify possible array subscript assignment; match [...] */
-      else if MBTEST(character == '[' && token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index))    /* ] */
+      /* Identify possible array subscript assignment; match [...].  If
+        parser_state&PST_COMPASSIGN, we need to parse [sub]=words treating
+        `sub' as if it were enclosed in double quotes. */
+      else if MBTEST(character == '[' &&               /* ] */
+                    ((token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index)) ||
+                     (token_index == 0 && (parser_state&PST_COMPASSIGN))))
         {
-         ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+         ttok = parse_matched_pair (cd, '[', ']', &ttoklen, P_ARRAYSUB);
          if (ttok == &matched_pair_error)
            return -1;          /* Bail immediately. */
          RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
@@ -3370,7 +4718,7 @@ read_token_word (character)
          goto next_character;
         }
       /* Identify possible compound array variable assignment. */
-      else if MBTEST(character == '=' && token_index > 0 && token_is_assignment (token, token_index))
+      else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index))
        {
          peek_char = shell_getc (1);
          if MBTEST(peek_char == '(')           /* ) */
@@ -3391,7 +4739,12 @@ read_token_word (character)
              token[token_index++] = ')';
              FREE (ttok);
              all_digit_token = 0;
+             compound_assignment = 1;
+#if 1
              goto next_character;
+#else
+             goto got_token;           /* ksh93 seems to do this */
+#endif
            }
          else
            shell_ungetc (peek_char);
@@ -3406,22 +4759,26 @@ read_token_word (character)
          goto got_token;
        }
 
-    got_character:
-
-      all_digit_token &= DIGIT (character);
-      dollar_present |= character == '$';
+got_character:
 
       if (character == CTLESC || character == CTLNUL)
-       token[token_index++] = CTLESC;
+       {
+         RESIZE_MALLOCED_BUFFER (token, token_index, 2, token_buffer_size,
+                                 TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = CTLESC;
+       }
+      else
+got_escaped_character:
+       RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+                               TOKEN_DEFAULT_GROW_SIZE);
 
       token[token_index++] = character;
 
-      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
-                             TOKEN_DEFAULT_GROW_SIZE);
+      all_digit_token &= DIGIT (character);
+      dollar_present |= character == '$';
 
     next_character:
-      if (character == '\n' && interactive &&
-          (bash_input.type == st_stdin || bash_input.type == st_stream))
+      if (character == '\n' && SHOULD_PROMPT ())
        prompt_again ();
 
       /* We want to remove quoted newlines (that is, a \<newline> pair)
@@ -3433,21 +4790,22 @@ read_token_word (character)
 
 got_token:
 
+  /* Calls to RESIZE_MALLOCED_BUFFER ensure there is sufficient room. */
   token[token_index] = '\0';
 
   /* Check to see what thing we should return.  If the last_read_token
      is a `<', or a `&', or the character which ended this token is
      a '>' or '<', then, and ONLY then, is this input token a NUMBER.
      Otherwise, it is just a word, and should be returned as such. */
-  if MBTEST(all_digit_token && (character == '<' || character == '>' || \
-                   last_read_token == LESS_AND || \
+  if MBTEST(all_digit_token && (character == '<' || character == '>' ||
+                   last_read_token == LESS_AND ||
                    last_read_token == GREATER_AND))
       {
        if (legal_number (token, &lvalue) && (int)lvalue == lvalue)
-         yylval.number = lvalue;
-       else
-         yylval.number = -1;
-       return (NUMBER);
+         {
+           yylval.number = lvalue;
+           return (NUMBER);
+         }
       }
 
   /* Check for special case tokens. */
@@ -3486,27 +4844,69 @@ got_token:
   if (dollar_present)
     the_word->flags |= W_HASDOLLAR;
   if (quoted)
-    the_word->flags |= W_QUOTED;
+    the_word->flags |= W_QUOTED;               /*(*/
+  if (compound_assignment && token[token_index-1] == ')')
+    the_word->flags |= W_COMPASSIGN;
   /* A word is an assignment if it appears at the beginning of a
      simple command, or after another assignment word.  This is
      context-dependent, so it cannot be handled in the grammar. */
-  if (assignment (token))
+  if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
     {
       the_word->flags |= W_ASSIGNMENT;
       /* Don't perform word splitting on assignment statements. */
-      if (assignment_acceptable (last_read_token))
-       the_word->flags |= W_NOSPLIT;
+      if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
+       {
+         the_word->flags |= W_NOSPLIT;
+         if (parser_state & PST_COMPASSIGN)
+           the_word->flags |= W_NOGLOB;        /* XXX - W_NOBRACE? */
+       }
+    }
+
+  if (command_token_position (last_read_token))
+    {
+      struct builtin *b;
+      b = builtin_address_internal (token, 0);
+      if (b && (b->flags & ASSIGNMENT_BUILTIN))
+       parser_state |= PST_ASSIGNOK;
+      else if (STREQ (token, "eval") || STREQ (token, "let"))
+       parser_state |= PST_ASSIGNOK;
     }
 
   yylval.word = the_word;
 
+  if (token[0] == '{' && token[token_index-1] == '}' &&
+      (character == '<' || character == '>'))
+    {
+      /* can use token; already copied to the_word */
+      token[token_index-1] = '\0';
+#if defined (ARRAY_VARS)
+      if (legal_identifier (token+1) || valid_array_reference (token+1))
+#else
+      if (legal_identifier (token+1))
+#endif
+       {
+         strcpy (the_word->word, token+1);
+/*itrace("read_token_word: returning REDIR_WORD for %s", the_word->word);*/
+         return (REDIR_WORD);
+       }
+    }
+
   result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
                ? ASSIGNMENT_WORD : WORD;
 
-  if (last_read_token == FUNCTION)
+  switch (last_read_token)
     {
+    case FUNCTION:
       parser_state |= PST_ALLOWOPNBRC;
       function_dstart = line_number;
+      break;
+    case CASE:
+    case SELECT:
+    case FOR:
+      if (word_top < MAX_CASE_NEST)
+       word_top++;
+      word_lineno[word_top] = line_number;
+      break;
     }
 
   return (result);
@@ -3530,6 +4930,7 @@ reserved_word_acceptable (toksym)
     case '}':          /* XXX */
     case AND_AND:
     case BANG:
+    case BAR_AND:
     case DO:
     case DONE:
     case ELIF:
@@ -3539,14 +4940,24 @@ reserved_word_acceptable (toksym)
     case IF:
     case OR_OR:
     case SEMI_SEMI:
+    case SEMI_AND:
+    case SEMI_SEMI_AND:
     case THEN:
     case TIME:
     case TIMEOPT:
+    case TIMEIGN:
+    case COPROC:
     case UNTIL:
     case WHILE:
     case 0:
       return 1;
     default:
+#if defined (COPROCESS_SUPPORT)
+      if (last_read_token == WORD && token_before_that == COPROC)
+       return 1;
+#endif
+      if (last_read_token == WORD && token_before_that == FUNCTION)
+       return 1;
       return 0;
     }
 }
@@ -3564,6 +4975,14 @@ find_reserved_word (tokstr)
   return -1;
 }
 
+/* An interface to let the rest of the shell (primarily the completion
+   system) know what the parser is expecting. */
+int
+parser_in_command_position ()
+{
+  return (command_token_position (last_read_token));
+}
+
 #if 0
 #if defined (READLINE)
 /* Called after each time readline is called.  This insures that whatever
@@ -3597,24 +5016,49 @@ reset_readline_prompt ()
 /* A list of tokens which can be followed by newlines, but not by
    semi-colons.  When concatenating multiple lines of history, the
    newline separator for such tokens is replaced with a space. */
-static int no_semi_successors[] = {
+static const int no_semi_successors[] = {
   '\n', '{', '(', ')', ';', '&', '|',
-  CASE, DO, ELSE, IF, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR, IN,
+  CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
+  WHILE, AND_AND, OR_OR, IN,
   0
 };
 
 /* If we are not within a delimited expression, try to be smart
    about which separators can be semi-colons and which must be
    newlines.  Returns the string that should be added into the
-   history entry. */
+   history entry.  LINE is the line we're about to add; it helps
+   make some more intelligent decisions in certain cases. */
 char *
-history_delimiting_chars ()
+history_delimiting_chars (line)
+     const char *line;
 {
+  static int last_was_heredoc = 0;     /* was the last entry the start of a here document? */
   register int i;
 
+  if ((parser_state & PST_HEREDOC) == 0)
+    last_was_heredoc = 0;
+
   if (dstack.delimiter_depth != 0)
     return ("\n");
-    
+
+  /* We look for current_command_line_count == 2 because we are looking to
+     add the first line of the body of the here document (the second line
+     of the command).  We also keep LAST_WAS_HEREDOC as a private sentinel
+     variable to note when we think we added the first line of a here doc
+     (the one with a "<<" somewhere in it) */
+  if (parser_state & PST_HEREDOC)
+    {
+      if (last_was_heredoc)
+       {
+         last_was_heredoc = 0;
+         return "\n";
+       }
+      return (current_command_line_count == 2 ? "\n" : "");
+    }
+
+  if (parser_state & PST_COMPASSIGN)
+    return (" ");
+
   /* First, handle some special cases. */
   /*(*/
   /* If we just read `()', assume it's a function definition, and don't
@@ -3636,16 +5080,27 @@ history_delimiting_chars ()
   else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
     return " ";                /* function def using `function name' without `()' */
 
+  /* If we're not in a here document, but we think we're about to parse one,
+     and we would otherwise return a `;', return a newline to delimit the
+     line with the here-doc delimiter */
+  else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && last_read_token == '\n' && strstr (line, "<<"))
+    {
+      last_was_heredoc = 1;
+      return "\n";
+    }
+
   else if (token_before_that == WORD && two_tokens_ago == FOR)
     {
       /* Tricky.  `for i\nin ...' should not have a semicolon, but
         `for i\ndo ...' should.  We do what we can. */
-      for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+      for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
        ;
       if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
        return " ";
       return ";";
     }
+  else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
+    return " ";
 
   for (i = 0; no_semi_successors[i]; i++)
     {
@@ -3664,7 +5119,7 @@ prompt_again ()
 {
   char *temp_prompt;
 
-  if (!interactive)    /* XXX */
+  if (interactive == 0 || expanding_alias ())  /* XXX */
     return;
 
   ps1_prompt = get_string_value ("PS1");
@@ -3760,10 +5215,10 @@ decode_prompt_string (string)
   WORD_LIST *list;
   char *result, *t;
   struct dstack save_dstack;
-  int last_exit_value;
+  int last_exit_value, last_comsub_pid;
 #if defined (PROMPT_STRING_DECODE)
   int result_size, result_index;
-  int c, n;
+  int c, n, i;
   char *temp, octal_string[4];
   struct tm *tm;  
   time_t the_time;
@@ -3844,6 +5299,9 @@ decode_prompt_string (string)
            case 'A':
              /* Make the current time/date into a string. */
              (void) time (&the_time);
+#if defined (HAVE_TZSET)
+             sv_tz ("TZ");             /* XXX -- just make sure */
+#endif
              tm = localtime (&the_time);
 
              if (c == 'd')
@@ -3857,7 +5315,11 @@ decode_prompt_string (string)
              else if (c == 'A')
                n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm);
 
-             timebuf[sizeof(timebuf) - 1] = '\0';
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
              temp = savestring (timebuf);
              goto add_string;
 
@@ -3882,7 +5344,11 @@ decode_prompt_string (string)
              n = strftime (timebuf, sizeof (timebuf), timefmt, tm);
              free (timefmt);
 
-             timebuf[sizeof(timebuf) - 1] = '\0';
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
              if (promptvars || posixly_correct)
                /* Make sure that expand_prompt_string is called with a
                   second argument of Q_DOUBLE_QUOTES if we use this
@@ -3939,24 +5405,37 @@ decode_prompt_string (string)
                  }
                t_string[tlen] = '\0';
 
+#if defined (MACOSX)
+               /* Convert from "fs" format to "input" format */
+               temp = fnx_fromfs (t_string, strlen (t_string));
+               if (temp != t_string)
+                 strcpy (t_string, temp);
+#endif
+
 #define ROOT_PATH(x)   ((x)[0] == '/' && (x)[1] == 0)
 #define DOUBLE_SLASH_ROOT(x)   ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0)
-               if (c == 'W')
+               /* Abbreviate \W as ~ if $PWD == $HOME */
+               if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0))
                  {
                    if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0)
                      {
                        t = strrchr (t_string, '/');
                        if (t)
-                         strcpy (t_string, t + 1);
+                         memmove (t_string, t + 1, strlen (t));        /* strlen(t) to copy NULL */
                      }
                  }
 #undef ROOT_PATH
 #undef DOUBLE_SLASH_ROOT
                else
-                 /* polite_directory_format is guaranteed to return a string
-                    no longer than PATH_MAX - 1 characters. */
-                 strcpy (t_string, polite_directory_format (t_string));
+                 {
+                   /* polite_directory_format is guaranteed to return a string
+                      no longer than PATH_MAX - 1 characters. */
+                   temp = polite_directory_format (t_string);
+                   if (temp != t_string)
+                     strcpy (t_string, temp);
+                 }
 
+               temp = trim_pathname (t_string, PATH_MAX - 1);
                /* If we're going to be expanding the prompt string later,
                   quote the directory name. */
                if (promptvars || posixly_correct)
@@ -4020,10 +5499,18 @@ decode_prompt_string (string)
 #if defined (READLINE)
            case '[':
            case ']':
+             if (no_line_editing)
+               {
+                 string++;
+                 break;
+               }
              temp = (char *)xmalloc (3);
-             temp[0] = '\001';
-             temp[1] = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
-             temp[2] = '\0';
+             n = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
+             i = 0;
+             if (n == CTLESC || n == CTLNUL)
+               temp[i++] = CTLESC;
+             temp[i++] = n;
+             temp[i] = '\0';
              goto add_string;
 #endif /* READLINE */
 
@@ -4083,11 +5570,13 @@ not_escape:
   if (promptvars || posixly_correct)
     {
       last_exit_value = last_command_exit_value;
-      list = expand_prompt_string (result, Q_DOUBLE_QUOTES);
+      last_comsub_pid = last_command_subst_pid;
+      list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0);
       free (result);
       result = string_list (list);
       dispose_words (list);
       last_command_exit_value = last_exit_value;
+      last_command_subst_pid = last_comsub_pid;
     }
   else
     {
@@ -4119,15 +5608,15 @@ yyerror (msg)
 }
 
 static char *
-error_token_from_token (token)
-     int token;
+error_token_from_token (tok)
+     int tok;
 {
   char *t;
 
-  if (t = find_token_in_alist (token, word_token_alist, 0))
+  if (t = find_token_in_alist (tok, word_token_alist, 0))
     return t;
 
-  if (t = find_token_in_alist (token, other_token_alist, 0))
+  if (t = find_token_in_alist (tok, other_token_alist, 0))
     return t;
 
   t = (char *)NULL;
@@ -4223,14 +5712,14 @@ static void
 report_syntax_error (message)
      char *message;
 {
-  char *msg;
+  char *msg, *p;
 
   if (message)
     {
       parser_error (line_number, "%s", message);
       if (interactive && EOF_Reached)
        EOF_Reached = 0;
-      last_command_exit_value = EX_USAGE;
+      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
       return;
     }
 
@@ -4239,13 +5728,19 @@ report_syntax_error (message)
      parser's complaining about by looking at current_token. */
   if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token)))
     {
-      parser_error (line_number, "syntax error near unexpected token `%s'", msg);
+      if (ansic_shouldquote (msg))
+       {
+         p = ansic_quote (msg, 0, NULL);
+         free (msg);
+         msg = p;
+       }
+      parser_error (line_number, _("syntax error near unexpected token `%s'"), msg);
       free (msg);
 
       if (interactive == 0)
        print_offending_line ();
 
-      last_command_exit_value = EX_USAGE;
+      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
       return;
     }
 
@@ -4257,7 +5752,7 @@ report_syntax_error (message)
       msg = error_token_from_text ();
       if (msg)
        {
-         parser_error (line_number, "syntax error near `%s'", msg);
+         parser_error (line_number, _("syntax error near `%s'"), msg);
          free (msg);
        }
 
@@ -4267,7 +5762,7 @@ report_syntax_error (message)
     }
   else
     {
-      msg = EOF_Reached ? "syntax error: unexpected end of file" : "syntax error";
+      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
@@ -4276,7 +5771,7 @@ report_syntax_error (message)
        EOF_Reached = 0;
     }
 
-  last_command_exit_value = EX_USAGE;
+  last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
 }
 
 /* ??? Needed function. ??? We have to be able to discard the constructs
@@ -4329,7 +5824,7 @@ handle_eof_input_unit ()
        {
          if (eof_encountered < eof_encountered_limit)
            {
-             fprintf (stderr, "Use \"%s\" to leave the shell.\n",
+             fprintf (stderr, _("Use \"%s\" to leave the shell.\n"),
                       login_shell ? "logout" : "exit");
              eof_encountered++;
              /* Reset the parsing state. */
@@ -4366,8 +5861,9 @@ static WORD_LIST parse_string_error;
 /* Take a string and run it through the shell parser, returning the
    resultant word list.  Used by compound array assignment. */
 WORD_LIST *
-parse_string_to_word_list (s, whom)
+parse_string_to_word_list (s, flags, whom)
      char *s;
+     int flags;
      const char *whom;
 {
   WORD_LIST *wl;
@@ -4399,6 +5895,10 @@ parse_string_to_word_list (s, whom)
 
   with_input_from_string (s, whom);
   wl = (WORD_LIST *)NULL;
+
+  if (flags & 1)
+    parser_state |= PST_COMPASSIGN|PST_REPARSE;
+
   while ((tok = read_token (READ)) != yacc_EOF)
     {
       if (tok == '\n' && *bash_input.location.string == '\0')
@@ -4410,7 +5910,7 @@ parse_string_to_word_list (s, whom)
          line_number = orig_line_number + line_number - 1;
          orig_current_token = current_token;
          current_token = tok;
-         yyerror ((char *)NULL);       /* does the right thing */
+         yyerror (NULL);       /* does the right thing */
          current_token = orig_current_token;
          if (wl)
            dispose_words (wl);
@@ -4436,6 +5936,9 @@ parse_string_to_word_list (s, whom)
   current_command_line_count = orig_line_count;
   shell_input_line_terminator = orig_input_terminator;
 
+  if (flags & 1)
+    parser_state &= ~(PST_COMPASSIGN|PST_REPARSE);
+
   if (wl == &parse_string_error)
     {
       last_command_exit_value = EXECUTION_FAILURE;
@@ -4453,30 +5956,39 @@ parse_compound_assignment (retlenp)
      int *retlenp;
 {
   WORD_LIST *wl, *rl;
-  int tok, orig_line_number, orig_token_size;
+  int tok, orig_line_number, orig_token_size, orig_last_token, assignok;
   char *saved_token, *ret;
 
   saved_token = token;
   orig_token_size = token_buffer_size;
   orig_line_number = line_number;
+  orig_last_token = last_read_token;
 
   last_read_token = WORD;      /* WORD to allow reserved words here */
 
   token = (char *)NULL;
   token_buffer_size = 0;
 
+  assignok = parser_state&PST_ASSIGNOK;                /* XXX */
+
   wl = (WORD_LIST *)NULL;      /* ( */
+  parser_state |= PST_COMPASSIGN;
+
   while ((tok = read_token (READ)) != ')')
     {
       if (tok == '\n')                 /* Allow newlines in compound assignments */
-       continue;
+       {
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         continue;
+       }
       if (tok != WORD && tok != ASSIGNMENT_WORD)
        {
          current_token = tok;  /* for error reporting */
          if (tok == yacc_EOF)  /* ( */
-           parser_error (orig_line_number, "unexpected EOF while looking for matching `)'");
+           parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'"));
          else
-           yyerror ((char *)NULL);     /* does the right thing */
+           yyerror(NULL);      /* does the right thing */
          if (wl)
            dispose_words (wl);
          wl = &parse_string_error;
@@ -4489,6 +6001,8 @@ parse_compound_assignment (retlenp)
   token = saved_token;
   token_buffer_size = orig_token_size;
 
+  parser_state &= ~PST_COMPASSIGN;
+
   if (wl == &parse_string_error)
     {
       last_command_exit_value = EXECUTION_FAILURE;
@@ -4499,7 +6013,8 @@ parse_compound_assignment (retlenp)
        jump_to_top_level (DISCARD);
     }
 
-  last_read_token = WORD;
+  last_read_token = orig_last_token;           /* XXX - was WORD? */
+
   if (wl)
     {
       rl = REVERSE_LIST (wl, WORD_LIST *);
@@ -4511,11 +6026,145 @@ parse_compound_assignment (retlenp)
 
   if (retlenp)
     *retlenp = (ret && *ret) ? strlen (ret) : 0;
+
+  if (assignok)
+    parser_state |= PST_ASSIGNOK;
+
   return ret;
 }
 
 /************************************************
  *                                             *
+ *   SAVING AND RESTORING PARTIAL PARSE STATE   *
+ *                                             *
+ ************************************************/
+
+sh_parser_state_t *
+save_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+  if (ps == 0)
+    ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
+  if (ps == 0)
+    return ((sh_parser_state_t *)NULL);
+
+  ps->parser_state = parser_state;
+  ps->token_state = save_token_state ();
+
+  ps->input_line_terminator = shell_input_line_terminator;
+  ps->eof_encountered = eof_encountered;
+
+  ps->prompt_string_pointer = prompt_string_pointer;
+
+  ps->current_command_line_count = current_command_line_count;
+
+#if defined (HISTORY)
+  ps->remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  ps->history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+#endif
+
+  ps->last_command_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  ps->pipestatus = save_pipestatus_array ();
+#endif
+    
+  ps->last_shell_builtin = last_shell_builtin;
+  ps->this_shell_builtin = this_shell_builtin;
+
+  ps->expand_aliases = expand_aliases;
+  ps->echo_input_at_read = echo_input_at_read;
+
+  ps->token = token;
+  ps->token_buffer_size = token_buffer_size;
+  /* Force reallocation on next call to read_token_word */
+  token = 0;
+  token_buffer_size = 0;
+
+  return (ps);
+}
+
+void
+restore_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+  if (ps == 0)
+    return;
+
+  parser_state = ps->parser_state;
+  if (ps->token_state)
+    {
+      restore_token_state (ps->token_state);
+      free (ps->token_state);
+    }
+
+  shell_input_line_terminator = ps->input_line_terminator;
+  eof_encountered = ps->eof_encountered;
+
+  prompt_string_pointer = ps->prompt_string_pointer;
+
+  current_command_line_count = ps->current_command_line_count;
+
+#if defined (HISTORY)
+  remember_on_history = ps->remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = ps->history_expansion_inhibited;
+#  endif
+#endif
+
+  last_command_exit_value = ps->last_command_exit_value;
+#if defined (ARRAY_VARS)
+  restore_pipestatus_array (ps->pipestatus);
+#endif
+
+  last_shell_builtin = ps->last_shell_builtin;
+  this_shell_builtin = ps->this_shell_builtin;
+
+  expand_aliases = ps->expand_aliases;
+  echo_input_at_read = ps->echo_input_at_read;
+
+  FREE (token);
+  token = ps->token;
+  token_buffer_size = ps->token_buffer_size;
+}
+
+sh_input_line_state_t *
+save_input_line_state (ls)
+     sh_input_line_state_t *ls;
+{
+  if (ls == 0)
+    ls = (sh_input_line_state_t *)xmalloc (sizeof (sh_input_line_state_t));
+  if (ls == 0)
+    return ((sh_input_line_state_t *)NULL);
+
+  ls->input_line = shell_input_line;
+  ls->input_line_size = shell_input_line_size;
+  ls->input_line_len = shell_input_line_len;
+  ls->input_line_index = shell_input_line_index;
+
+  /* force reallocation */
+  shell_input_line = 0;
+  shell_input_line_size = shell_input_line_len = shell_input_line_index = 0;
+
+  return ls;
+}
+
+void
+restore_input_line_state (ls)
+     sh_input_line_state_t *ls;
+{
+  FREE (shell_input_line);
+  shell_input_line = ls->input_line;
+  shell_input_line_size = ls->input_line_size;
+  shell_input_line_len = ls->input_line_len;
+  shell_input_line_index = ls->input_line_index;
+
+  set_line_mbstate ();
+}
+
+/************************************************
+ *                                             *
  *     MULTIBYTE CHARACTER HANDLING            *
  *                                             *
  ************************************************/
@@ -4524,7 +6173,8 @@ parse_compound_assignment (retlenp)
 static void
 set_line_mbstate ()
 {
-  int i, previ, len;
+  int c;
+  size_t i, previ, len;
   mbstate_t mbs, prevs;
   size_t mbclen;
 
@@ -4539,9 +6189,10 @@ set_line_mbstate ()
     {
       mbs = prevs;
 
-      if (shell_input_line[i] == EOF)
+      c = shell_input_line[i];
+      if (c == EOF)
        {
-         int j;
+         size_t j;
          for (j = i; j < len; j++)
            shell_input_line_property[j] = 1;
          break;
@@ -4563,7 +6214,11 @@ set_line_mbstate ()
        }
       else
        {
-         /* mbrlen doesn't return any other values */
+         /* 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;
+         break;
        }
 
       shell_input_line_property[i] = mbclen;