]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - parse.y
commit bash-20120907 snapshot
[thirdparty/bash.git] / parse.y
diff --git a/parse.y b/parse.y
index 740721f9b19c092a9a8f51d7fe09dc21b817cf49..c0da6301fe078b2863c4da52c0f76f674c151286 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -1,6 +1,6 @@
 /* parse.y - Yacc grammar for bash. */
 
-/* Copyright (C) 1989-2008 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2012 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -115,9 +115,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 pid_t last_command_subst_pid;
 extern char *shell_name, *current_host_name;
 extern char *dist_version;
 extern int patch_level;
@@ -246,19 +247,21 @@ int promptvars = 1;
    quotes. */
 int extended_quote = 1;
 
-/* The decoded prompt string.  Used if READLINE is not defined or if
-   editing is turned off.  Analogous to current_readline_prompt. */
-static char *current_decoded_prompt;
-
 /* The number of lines read from input while creating the current command. */
 int current_command_line_count;
 
+/* The 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];
@@ -283,8 +286,9 @@ static int function_bstart;
 /* The line number in a script at which an arithmetic for command starts. */
 static int arith_for_lineno;
 
-/* The current parser state. */
-static int parser_state;
+/* The 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. */
@@ -296,6 +300,8 @@ 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. */
@@ -310,6 +316,7 @@ static int word_top = -1;
 static int token_to_read;
 static WORD_DESC *word_desc_to_read;
 
+static REDIRECTEE source;
 static REDIRECTEE redir;
 %}
 
@@ -329,10 +336,10 @@ static REDIRECTEE redir;
    third group are recognized only under special circumstances. */
 %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
@@ -392,7 +399,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;
                            }
@@ -419,159 +426,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 ($1, r_appending_to, redir);
+                         $$ = 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 (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 ($1, r_reading_until, redir);
+                         $$ = 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 (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 ($1, r_duplicating_input, redir);
+                         $$ = make_redirection (source, r_duplicating_input, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND NUMBER
+                       {
+                         source.filename = $1;
+                         redir.dest = $3;
+                         $$ = 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);
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
                        }
-       |       LESS_LESS_MINUS WORD
-                       {
-                         redir.filename = $2;
-                         $$ = make_redirection
-                           (0, r_deblank_reading_until, redir);
-                         redir_stack[need_here_doc++] = $$;
-                       }
-       |       NUMBER LESS_LESS_MINUS WORD
+       |       REDIR_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
-                       {
-                         redir.filename = $2;
-                         $$ = make_redirection (1, r_err_and_out, redir);
-                       }
-       |       AND_GREATER_GREATER WORD
+       |       NUMBER LESS_AND '-'
                        {
-                         redir.filename = $2;
-                         $$ = make_redirection (1, r_append_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);
                        }
        ;
 
@@ -1068,31 +1189,19 @@ simple_list1:   simple_list1 AND_AND newline_list simple_list1
        ;
 
 pipeline_command: pipeline
-                       { $$ = $1; }
-       |       BANG pipeline
+                       { $$ = $1; }                    
+       |       BANG pipeline_command
                        {
                          if ($2)
-                           $2->flags |= CMD_INVERT_RETURN;
+                           $2->flags ^= CMD_INVERT_RETURN;     /* toggle */
                          $$ = $2;
                        }
-       |       timespec pipeline
+       |       timespec pipeline_command
                        {
                          if ($2)
                            $2->flags |= $1;
                          $$ = $2;
                        }
-       |       timespec BANG pipeline
-                       {
-                         if ($3)
-                           $3->flags |= $1|CMD_INVERT_RETURN;
-                         $$ = $3;
-                       }
-       |       BANG timespec pipeline
-                       {
-                         if ($3)
-                           $3->flags |= $2|CMD_INVERT_RETURN;
-                         $$ = $3;
-                       }
        |       timespec list_terminator
                        {
                          ELEMENT x;
@@ -1110,7 +1219,24 @@ pipeline_command: pipeline
                          if ($2 == '\n')
                            token_to_read = '\n';
                        }
-                       
+       |       BANG list_terminator
+                       {
+                         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
@@ -1119,12 +1245,13 @@ pipeline:       pipeline '|' newline_list pipeline
                        {
                          /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */
                          COMMAND *tc;
-                         REDIRECTEE rd;
+                         REDIRECTEE rd, sd;
                          REDIRECT *r;
 
-                         tc = $1;
+                         tc = $1->type == cm_simple ? (COMMAND *)$1->value.Simple : $1;
+                         sd.dest = 2;
                          rd.dest = 1;
-                         r = make_redirection (2, r_duplicating_output, rd);
+                         r = make_redirection (sd, r_duplicating_output, rd, 0);
                          if (tc->redirects)
                            {
                              register REDIRECT *t;
@@ -1145,6 +1272,8 @@ timespec: TIME
                        { $$ = CMD_TIME_PIPELINE; }
        |       TIME TIMEOPT
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEOPT TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        ;
 %%
 
@@ -1303,22 +1432,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)
        {
-         old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
          interrupt_immediately++;
+         old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
        }
-      terminate_immediately = 1;
 
       current_readline_line = readline (current_readline_prompt ?
                                          current_readline_prompt : "");
 
-      terminate_immediately = 0;
-      if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+      CHECK_TERMSIG;
+      if (signal_is_ignored (SIGINT) == 0)
        {
          interrupt_immediately--;
-         set_signal_handler (SIGINT, old_sigint);
+         if (old_sigint != IMPOSSIBLE_TRAP_HANDLER)
+           set_signal_handler (SIGINT, old_sigint);
        }
 
 #if 0
@@ -1430,7 +1559,7 @@ with_input_from_string (string, name)
    That is the true input location.  Rewind bash_input.location.string by
    that number of characters, so it points to the last character actually
    consumed by the parser. */
-void
+static void
 rewind_input_string ()
 {
   int xchars;
@@ -1438,6 +1567,8 @@ rewind_input_string ()
   /* 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
@@ -1471,16 +1602,15 @@ yy_stream_get ()
   if (bash_input.location.file)
     {
       if (interactive)
-       {
-         interrupt_immediately++;
-         terminate_immediately++;
-       }
+       interrupt_immediately++;
+
+      /* 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 (interactive)
-       {
-         interrupt_immediately--;
-         terminate_immediately--;
-       }
+       interrupt_immediately--;
+
     }
   return (result);
 }
@@ -1515,6 +1645,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;
@@ -1613,10 +1746,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;
 }
 
@@ -1629,6 +1763,7 @@ restore_token_state (ts)
   last_read_token = ts[0];
   token_before_that = ts[1];
   two_tokens_ago = ts[2];
+  current_token = ts[3];
 }
 
 /*
@@ -1782,7 +1917,7 @@ read_a_line (remove_quoted_newline)
 {
   static char *line_buffer = (char *)NULL;
   static int buffer_size = 0;
-  int indx = 0, c, peekc, pass_next;
+  int indx, c, peekc, pass_next;
 
 #if defined (READLINE)
   if (no_line_editing && SHOULD_PROMPT ())
@@ -1791,7 +1926,7 @@ read_a_line (remove_quoted_newline)
 #endif
     print_prompt ();
 
-  pass_next = 0;
+  pass_next = indx = 0;
   while (1)
     {
       /* Allow immediate exit if interrupted during input. */
@@ -1834,6 +1969,7 @@ read_a_line (remove_quoted_newline)
        }
       else if (c == '\\' && remove_quoted_newline)
        {
+         QUIT;
          peekc = yy_getc ();
          if (peekc == '\n')
            {
@@ -1874,7 +2010,8 @@ read_secondary_line (remove_quoted_newline)
   if (SHOULD_PROMPT())
     prompt_again ();
   ret = read_a_line (remove_quoted_newline);
-  if (remember_on_history && (parser_state & PST_HEREDOC))
+#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
@@ -1885,6 +2022,7 @@ read_secondary_line (remove_quoted_newline)
       current_command_line_count++;
       maybe_add_history (ret);
     }
+#endif /* HISTORY */
   return ret;
 }
 
@@ -1933,6 +2071,7 @@ STRING_INT_ALIST word_token_alist[] = {
 /* 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 },
@@ -2153,7 +2292,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
@@ -2178,7 +2317,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);
            }
@@ -2218,6 +2357,7 @@ shell_getc (remove_quoted_newline)
        }
     }
 
+next_alias_char:
   uc = shell_input_line[shell_input_line_index];
 
   if (uc)
@@ -2229,7 +2369,8 @@ shell_getc (remove_quoted_newline)
      because we have fully consumed the result of the last alias expansion.
      Do it transparently; just return the next character of the string popped
      to. */
-  if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+pop_alias:
+  if (uc == 0 && (pushed_string_list != (STRING_SAVER *)NULL))
     {
       pop_string ();
       uc = shell_input_line[shell_input_line_index];
@@ -2243,10 +2384,30 @@ shell_getc (remove_quoted_newline)
        if (SHOULD_PROMPT ())
          prompt_again ();
        line_number++;
-       goto restart_read;
+       /* 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 && shell_input_line_terminator == EOF)
+  if (uc == 0 && shell_input_line_terminator == EOF)
     return ((shell_input_line_index != 0) ? '\n' : EOF);
 
   return (uc);
@@ -2338,7 +2499,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 ();
@@ -2390,7 +2551,7 @@ gather_here_documents ()
 static int open_brace_count;
 
 #define command_token_position(token) \
-  (((token) == ASSIGNMENT_WORD) || \
+  (((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) \
@@ -2453,7 +2614,9 @@ mk_alexpansion (s)
   l = strlen (s);
   r = xmalloc (l + 2);
   strcpy (r, s);
-  if (r[l -1] != ' ')
+  /* 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;
@@ -2498,6 +2661,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:
@@ -2511,6 +2688,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;
@@ -2537,6 +2718,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)
@@ -2622,13 +2804,9 @@ special_case_tokens (tokstr)
   /* Handle -p after `time'. */
   if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
     return (TIMEOPT);
-#endif
-
-#if 0
-#if defined (COMMAND_TIMING)
-  if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
-    return (TIME);
-#endif /* COMMAND_TIMING */
+  /* Handle -- after `time -p'. */
+  if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
 #endif
 
 #if defined (COND_COMMAND) /* [[ */
@@ -2647,6 +2825,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)
@@ -2664,6 +2848,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';
 }
@@ -2906,11 +3091,13 @@ tokword:
  * reprompting the user, if necessary, after reading a newline, and returning
  * correct error values if it reads EOF.
  */
-#define P_FIRSTCLOSE   0x01
-#define P_ALLOWESC     0x02
-#define P_DQUOTE       0x04
-#define P_COMMAND      0x08    /* parsing a command, so look for comments */
-#define P_BACKQUOTE    0x10    /* parsing a backquoted command substitution */
+#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
@@ -2923,6 +3110,7 @@ tokword:
 #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) == '|')
 
@@ -2957,8 +3145,11 @@ parse_matched_pair (qc, open, close, lenp, flags)
   int nestlen, ttranslen, start_lineno;
   char *ret, *nestret, *ttrans;
   int retind, retsize, rflags;
+  int dolbrace_state;
+
+  dolbrace_state = (flags & P_DOLBRACE) ? DOLBRACE_PARAM : 0;
 
-/* itrace("parse_matched_pair: open = %c close = %c flags = %d", open, close, flags); */
+/*itrace("parse_matched_pair[%d]: open = %c close = %c flags = %d", line_number, open, close, flags);*/
   count = 1;
   tflags = 0;
 
@@ -2974,7 +3165,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
   start_lineno = line_number;
   while (count)
     {
-      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+      ch = shell_getc (qc != '\'' && (tflags & (LEX_PASSNEXT)) == 0);
 
       if (ch == EOF)
        {
@@ -3019,11 +3210,25 @@ parse_matched_pair (qc, open, close, lenp, flags)
            }
 
          RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+#if 0
          if MBTEST(ch == CTLESC || ch == CTLNUL)
+#else
+         if MBTEST(ch == CTLESC)
+#endif
            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);
@@ -3057,14 +3262,42 @@ parse_matched_pair (qc, open, close, lenp, flags)
       if MBTEST(ch == '\\')                    /* backslashes */
        tflags |= LEX_PASSNEXT;
 
-#if 0
+      /* 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. */
+      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_QUOTE;
+          /* ${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 says the single quotes are semi-special:
+         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." */
-      if MBTEST(open == '{' && (flags & P_DQUOTE) && ch == '\'')       /* } */
+      /* 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;
-#endif
 
       /* Could also check open == '`' if we want to parse grouping constructs
         inside old-style command substitution. */
@@ -3115,6 +3348,8 @@ parse_matched_pair (qc, open, close, lenp, flags)
              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. */
@@ -3131,12 +3366,13 @@ parse_matched_pair (qc, open, close, lenp, flags)
       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|rflags);
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
          else if (ch == '[')           /* ] */
            nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
 
@@ -3154,6 +3390,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
   ret[retind] = '\0';
   if (lenp)
     *lenp = retind;
+/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/
   return ret;
 }
 
@@ -3165,11 +3402,18 @@ parse_comsub (qc, open, close, lenp, flags)
      int open, close;
      int *lenp, flags;
 {
-  int count, ch, peekc, tflags, lex_rwlen, lex_firstind;
+  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;
@@ -3186,7 +3430,7 @@ parse_comsub (qc, open, close, lenp, flags)
   retind = 0;
 
   start_lineno = line_number;
-  lex_rwlen = 0;
+  lex_rwlen = lex_wlen = 0;
 
   heredelim = 0;
   lex_firstind = -1;
@@ -3194,7 +3438,7 @@ parse_comsub (qc, open, close, lenp, flags)
   while (count)
     {
 comsub_readchar:
-      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+      ch = shell_getc (qc != '\'' && (tflags & (LEX_INCOMMENT|LEX_PASSNEXT)) == 0);
 
       if (ch == EOF)
        {
@@ -3229,6 +3473,8 @@ eof_error:
                {
                  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
@@ -3240,6 +3486,25 @@ eof_error:
       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))
        {
@@ -3248,13 +3513,60 @@ eof_error:
          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 0
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+#else
+         if MBTEST(ch == CTLESC)
+#endif
+           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) && lex_rwlen == 0)
+      if MBTEST(shellblank (ch) && (tflags & LEX_HEREDELIM) == 0 && lex_rwlen == 0)
         {
          /* Add this character. */
          RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
@@ -3273,13 +3585,24 @@ eof_error:
        {
          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))
            {
-             nestret = substring (ret, lex_firstind, retind);
-             heredelim = string_quote_removal (nestret, 0);
-             free (nestret);
-             hdlen = STRLEN(heredelim);
+             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;
@@ -3292,7 +3615,7 @@ eof_error:
        }
 
       /* 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))
+      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);
@@ -3302,7 +3625,7 @@ eof_error:
            {
              RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
              ret[retind++] = peekc;
-/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch); */
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
              tflags |= LEX_RESWDOK;
              lex_rwlen = 0;
              continue;
@@ -3310,8 +3633,8 @@ eof_error:
          else if (ch == '\n' || COMSUB_META(ch))
            {
              shell_ungetc (peekc);
-             tflags |= LEX_RESWDOK;
 /*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
+             tflags |= LEX_RESWDOK;
              lex_rwlen = 0;
              continue;
            }
@@ -3341,22 +3664,35 @@ eof_error:
              if (STREQN (ret + retind - 4, "case", 4))
 {
                tflags |= LEX_INCASE;
-/*itrace("parse_comsub:%d: found `case', lex_incase -> 1", line_number);*/
+/*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", line_number);*/
+/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0 lex_reswdok -> 0", line_number);*/
 }              
              tflags &= ~LEX_RESWDOK;
            }
-         else if (shellbreak (ch) == 0)
+         else if MBTEST((tflags & LEX_CKCOMMENT) && ch == '#' && (lex_rwlen == 0 || ((tflags & LEX_INWORD) && lex_wlen == 0)))
+           ;   /* don't modify LEX_RESWDOK if we're starting a comment */
+         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;
+           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);*/
 }
        }
 
+      /* Might be the start of a here-doc delimiter */
       if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<')
        {
          /* Add this character. */
@@ -3380,36 +3716,23 @@ eof_error:
                }
              else
                shell_ungetc (peekc);
-             tflags |= LEX_HEREDELIM;
-             lex_firstind = -1;
+             if (peekc != '<')
+               {
+                 tflags |= LEX_HEREDELIM;
+                 lex_firstind = -1;
+               }
              continue;
            }
          else
-           ch = peekc;         /* fall through and continue XXX - this skips comments if peekc == '#' */
+           ch = peekc;         /* fall through and continue XXX */
        }
-      /* 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])))
+      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 (tflags & LEX_PASSNEXT)               /* last char was backslash */
-       {
-         tflags &= ~LEX_PASSNEXT;
-         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
-           {
-             if (retind > 0)
-               retind--;       /* swallow previously-added backslash */
-             continue;
-           }
-
-         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
-         if MBTEST(ch == CTLESC || ch == CTLNUL)
-           ret[retind++] = CTLESC;
-         ret[retind++] = ch;
-         continue;
-       }
-      else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
+      if MBTEST(ch == CTLESC || ch == CTLNUL)  /* special shell escapes */
        {
          RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
          ret[retind++] = CTLESC;
@@ -3426,7 +3749,10 @@ eof_error:
 /*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);
@@ -3492,7 +3818,7 @@ eof_error:
          if (ch == '(')                /* ) */
            nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
          else if (ch == '{')           /* } */
-           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
          else if (ch == '[')           /* ] */
            nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
 
@@ -3515,8 +3841,7 @@ eof_error:
   return ret;
 }
 
-/* XXX - this needs to handle functionality like subst.c:no_longjmp_on_fatal_error;
-   maybe extract_command_subst should handle it. */
+/* Recursively call the parser to parse a $(...) command substitution. */
 char *
 xparse_dolparen (base, string, indp, flags)
      char *base;
@@ -3525,6 +3850,7 @@ xparse_dolparen (base, string, indp, flags)
      int flags;
 {
   sh_parser_state_t ps;
+  sh_input_line_state_t ls;
   int orig_ind, nc, sflags;
   char *ret, *s, *ep, *ostring;
 
@@ -3532,10 +3858,12 @@ xparse_dolparen (base, string, indp, flags)
   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);
 
   /*(*/
   parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
@@ -3544,6 +3872,8 @@ xparse_dolparen (base, string, indp, flags)
 
   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;
 
@@ -3842,7 +4172,13 @@ 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, "=~"))
        {
@@ -3878,8 +4214,13 @@ cond_term ()
        }
 
       /* rhs */
+      if (parser_state & PST_EXTPAT)
+       extended_glob = 1;
       tok = read_token (READ);
-      parser_state &= ~PST_REGEXP;
+      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);
@@ -3924,6 +4265,7 @@ parse_cond_command ()
 {
   COND_COM *cexp;
 
+  global_extglob = extended_glob;
   cexp = cond_expr ();
   return (make_cond_command (cexp));
 }
@@ -4102,7 +4444,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;
@@ -4124,11 +4466,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
@@ -4144,7 +4486,7 @@ read_token_word (character)
                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;
@@ -4195,7 +4537,7 @@ read_token_word (character)
                  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);
@@ -4209,17 +4551,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
@@ -4234,7 +4572,7 @@ read_token_word (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,
@@ -4289,21 +4627,24 @@ read_token_word (character)
          goto got_token;
        }
 
-    got_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);
 
-    got_escaped_character:
+      token[token_index++] = character;
 
       all_digit_token &= DIGIT (character);
       dollar_present |= character == '$';
 
-      token[token_index++] = character;
-
-      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
-                             TOKEN_DEFAULT_GROW_SIZE);
-
     next_character:
       if (character == '\n' && SHOULD_PROMPT ())
        prompt_again ();
@@ -4317,21 +4658,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. */
@@ -4381,7 +4723,11 @@ got_token:
       the_word->flags |= W_ASSIGNMENT;
       /* Don't perform word splitting on assignment statements. */
       if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
-       the_word->flags |= W_NOSPLIT;
+       {
+         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))
@@ -4396,6 +4742,23 @@ got_token:
 
   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;
 
@@ -4435,6 +4798,7 @@ reserved_word_acceptable (toksym)
     case '}':          /* XXX */
     case AND_AND:
     case BANG:
+    case BAR_AND:
     case DO:
     case DONE:
     case ELIF:
@@ -4449,6 +4813,7 @@ reserved_word_acceptable (toksym)
     case THEN:
     case TIME:
     case TIMEOPT:
+    case TIMEIGN:
     case COPROC:
     case UNTIL:
     case WHILE:
@@ -4459,6 +4824,8 @@ reserved_word_acceptable (toksym)
       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;
     }
 }
@@ -4476,6 +4843,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
@@ -4519,20 +4894,38 @@ static const int no_semi_successors[] = {
 /* 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). */
+     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)
-    return (current_command_line_count == 2 ? "\n" : "");
+    {
+      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. */
   /*(*/
@@ -4555,6 +4948,15 @@ 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
@@ -4585,7 +4987,7 @@ prompt_again ()
 {
   char *temp_prompt;
 
-  if (interactive == 0 || expanding_alias())   /* XXX */
+  if (interactive == 0 || expanding_alias ())  /* XXX */
     return;
 
   ps1_prompt = get_string_value ("PS1");
@@ -4681,7 +5083,7 @@ 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, i;
@@ -4765,6 +5167,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')
@@ -4868,6 +5273,13 @@ 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)
                /* Abbreviate \W as ~ if $PWD == $HOME */
@@ -4877,7 +5289,7 @@ decode_prompt_string (string)
                      {
                        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
@@ -5022,11 +5434,13 @@ not_escape:
   if (promptvars || posixly_correct)
     {
       last_exit_value = last_command_exit_value;
+      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
     {
@@ -5162,14 +5576,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;
     }
 
@@ -5178,13 +5592,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)))
     {
+      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;
     }
 
@@ -5215,7 +5635,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
@@ -5341,7 +5761,7 @@ parse_string_to_word_list (s, flags, whom)
   wl = (WORD_LIST *)NULL;
 
   if (flags & 1)
-    parser_state |= PST_COMPASSIGN;
+    parser_state |= PST_COMPASSIGN|PST_REPARSE;
 
   while ((tok = read_token (READ)) != yacc_EOF)
     {
@@ -5381,7 +5801,7 @@ parse_string_to_word_list (s, flags, whom)
   shell_input_line_terminator = orig_input_terminator;
 
   if (flags & 1)
-    parser_state &= ~PST_COMPASSIGN;
+    parser_state &= ~(PST_COMPASSIGN|PST_REPARSE);
 
   if (wl == &parse_string_error)
     {
@@ -5487,10 +5907,6 @@ sh_parser_state_t *
 save_parser_state (ps)
      sh_parser_state_t *ps;
 {
-#if defined (ARRAY_VARS)
-  SHELL_VAR *v;
-#endif
-
   if (ps == 0)
     ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
   if (ps == 0)
@@ -5502,6 +5918,8 @@ save_parser_state (ps)
   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)
@@ -5513,11 +5931,7 @@ save_parser_state (ps)
 
   ps->last_command_exit_value = last_command_exit_value;
 #if defined (ARRAY_VARS)
-  v = find_variable ("PIPESTATUS");
-  if (v && array_p (v) && array_cell (v))
-    ps->pipestatus = array_copy (array_cell (v));
-  else
-    ps->pipestatus = (ARRAY *)NULL;
+  ps->pipestatus = save_pipestatus_array ();
 #endif
     
   ps->last_shell_builtin = last_shell_builtin;
@@ -5526,6 +5940,12 @@ save_parser_state (ps)
   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);
 }
 
@@ -5533,10 +5953,6 @@ void
 restore_parser_state (ps)
      sh_parser_state_t *ps;
 {
-#if defined (ARRAY_VARS)
-  SHELL_VAR *v;
-#endif
-
   if (ps == 0)
     return;
 
@@ -5550,6 +5966,8 @@ restore_parser_state (ps)
   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)
@@ -5561,12 +5979,7 @@ restore_parser_state (ps)
 
   last_command_exit_value = ps->last_command_exit_value;
 #if defined (ARRAY_VARS)
-  v = find_variable ("PIPESTATUS");
-  if (v && array_p (v) && array_cell (v))
-    {
-      array_dispose (array_cell (v));
-      var_setarray (v, ps->pipestatus);
-    }
+  restore_pipestatus_array (ps->pipestatus);
 #endif
 
   last_shell_builtin = ps->last_shell_builtin;
@@ -5574,6 +5987,44 @@ restore_parser_state (ps)
 
   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 ();
 }
 
 /************************************************