Imported from ../bash-2.0.tar.gz.
[platform/upstream/bash.git] / parse.y
diff --git a/parse.y b/parse.y
index 9c4c75a..1a949b0 100644 (file)
--- a/parse.y
+++ b/parse.y
    Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
 
 %{
-#include <stdio.h>
+#include "config.h"
+
 #include "bashtypes.h"
-#include <signal.h>
 #include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_LOCALE_H)
+#  include <locale.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+
+#include "memalloc.h"
+
 #include "shell.h"
+#include "trap.h"
 #include "flags.h"
-#include "input.h"
+#include "parser.h"
+#include "mailcheck.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
 
 #if defined (READLINE)
+#  include "bashline.h"
 #  include <readline/readline.h>
 #endif /* READLINE */
 
 #include "maxpath.h"
 #endif /* PROMPT_STRING_DECODE */
 
-#define YYDEBUG 1
+#define RE_READ_TOKEN  -99
+#define NO_EXPANSION   -100
+
+#define YYDEBUG 0
+
 extern int eof_encountered;
-extern int no_line_editing;
+extern int no_line_editing, running_under_emacs;
 extern int current_command_number;
 extern int interactive, interactive_shell, login_shell;
+extern int sourcelevel;
 extern int posixly_correct;
 extern int last_command_exit_value;
 extern int interrupt_immediately;
 extern char *shell_name, *current_host_name;
+extern char *dist_version;
+extern int patch_level;
+extern int dump_translatable_strings;
 extern Function *last_shell_builtin, *this_shell_builtin;
-#if defined (READLINE)
-extern int bash_readline_initialized;
-#endif
 #if defined (BUFFERED_INPUT)
 extern int bash_input_fd_changed;
 #endif
@@ -73,12 +97,13 @@ extern int bash_input_fd_changed;
 /*                                                                 */
 /* **************************************************************** */
 
-/* This is kind of sickening.  In order to let these variables be seen by
-   all the functions that need them, I am forced to place their declarations
-   far away from the place where they should logically be found. */
-
+static char *ansiexpand ();
+static char *localeexpand ();
 static int reserved_word_acceptable ();
 static int read_token ();
+static int yylex ();
+static int read_token_word ();
+static void discard_parser_constructs ();
 
 static void report_syntax_error ();
 static void handle_eof_input_unit ();
@@ -86,6 +111,10 @@ static void prompt_again ();
 static void reset_readline_prompt ();
 static void print_prompt ();
 
+/* Default prompt strings */
+char *primary_prompt = PPROMPT;
+char *secondary_prompt = SPROMPT;
+
 /* PROMPT_STRING_POINTER points to one of these, never to an actual string. */
 char *ps1_prompt, *ps2_prompt;
 
@@ -94,27 +123,42 @@ char *ps1_prompt, *ps2_prompt;
 char **prompt_string_pointer = (char **)NULL;
 char *current_prompt_string;
 
+/* Non-zero means we expand aliases in commands. */
+int expand_aliases = 0;
+
+/* If non-zero, the decoded prompt string undergoes parameter and
+   variable substitution, command substitution, arithmetic substitution,
+   string expansion, process substitution, and quote removal in
+   decode_prompt_string. */
+int promptvars = 1;
+
 /* 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 = 0;
+int current_command_line_count;
 
 /* Variables to manage the task of reading here documents, because we need to
    defer the reading until after a complete command has been collected. */
 static REDIRECT *redir_stack[10];
-int need_here_doc = 0;
+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 = 0;
-static int shell_input_line_size = 0;  /* Amount allocated for shell_input_line. */
-static int shell_input_line_len = 0;   /* strlen (shell_input_line) */
+static int shell_input_line_index;
+static int shell_input_line_size;      /* Amount allocated for shell_input_line. */
+static int shell_input_line_len;       /* strlen (shell_input_line) */
 
 /* Either zero or EOF. */
-static int shell_input_line_terminator = 0;
+static int shell_input_line_terminator;
+
+/* The line number in a script on which a function definition starts. */
+static int function_dstart;
+
+/* The line number in a script on which a function body starts. */
+static int function_bstart;
 
 static REDIRECTEE redir;
 %}
@@ -133,7 +177,7 @@ static REDIRECTEE redir;
    in the case that they are preceded by a list_terminator.  Members
    of the second group are recognized only under special circumstances. */
 %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION
-%token IN BANG
+%token IN BANG TIME TIMEOPT
 
 /* More general tokens. yylex () knows how to make these. */
 %token <word> WORD ASSIGNMENT_WORD
@@ -144,14 +188,16 @@ static REDIRECTEE redir;
 
 /* The types that the various syntactical units return. */
 
-%type <command> inputunit command pipeline
-%type <command> list list0 list1 simple_list simple_list1
-%type <command> simple_command shell_command_1 shell_command select_command
-%type <command> group_command function_def if_command elif_clause subshell
-%type <redirect> redirection redirections
+%type <command> inputunit command pipeline pipeline_command
+%type <command> list list0 list1 compound_list simple_list simple_list1
+%type <command> simple_command shell_command
+%type <command> for_command select_command case_command group_command
+%type <command> function_def if_command elif_clause subshell
+%type <redirect> redirection redirection_list
 %type <element> simple_command_element
-%type <word_list> words pattern 
-%type <pattern> pattern_list case_clause_sequence case_clause_1 pattern_list_1
+%type <word_list> word_list pattern
+%type <pattern> pattern_list case_clause_sequence case_clause
+%type <number> timespec
 
 %start inputunit
 
@@ -176,8 +222,7 @@ inputunit:  simple_list '\n'
                          global_command = (COMMAND *)NULL;
                          YYACCEPT;
                        }
-       |
-               error '\n'
+       |       error '\n'
                        {
                          /* Error during parsing.  Return NULL command. */
                          global_command = (COMMAND *)NULL;
@@ -194,7 +239,7 @@ inputunit:  simple_list '\n'
                        }
        |       yacc_EOF
                        {
-                         /* Case of EOF seen by itself.  Do ignoreeof or 
+                         /* Case of EOF seen by itself.  Do ignoreeof or
                             not. */
                          global_command = (COMMAND *)NULL;
                          handle_eof_input_unit ();
@@ -202,9 +247,9 @@ inputunit:  simple_list '\n'
                        }
        ;
 
-words: 
-                       { $$ = (WORD_LIST *)NULL; }
-       |       words WORD
+word_list:     WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       word_list WORD
                        { $$ = make_word_list ($2, $1); }
        ;
 
@@ -336,20 +381,9 @@ redirection:       '>' WORD
                        }
        |       LESS_GREATER WORD
                        {
-                         REDIRECT *t1, *t2;
-
                          redir.filename = $2;
-                         if (posixly_correct)
-                           $$ = make_redirection (0, r_input_output, redir);
-                         else
-                           {
-                             t1 = make_redirection (0, r_input_direction, redir);
-                             redir.filename = copy_word ($2);
-                             t2 = make_redirection (1, r_output_direction, redir);
-                             t1->next = t2;
-                             $$ = t1;
-                           }
-                       }                         
+                         $$ = make_redirection (0, r_input_output, redir);
+                       }
        |       GREATER_BAR WORD
                        {
                          redir.filename = $2;
@@ -370,17 +404,17 @@ simple_command_element: WORD
                        { $$.redirect = $1; $$.word = 0; }
        ;
 
-redirections:  redirection
+redirection_list: redirection
                        {
                          $$ = $1;
                        }
-       |       redirections redirection
-                       { 
-                         register REDIRECT *t = $1;
+       |       redirection_list redirection
+                       {
+                         register REDIRECT *t;
 
-                         while (t->next)
-                           t = t->next;
-                         t->next = $2; 
+                         for (t = $1; t->next; t = t->next)
+                           ;
+                         t->next = $2;
                          $$ = $1;
                        }
        ;
@@ -395,47 +429,42 @@ command:  simple_command
                        { $$ = clean_simple_command ($1); }
        |       shell_command
                        { $$ = $1; }
-       ;
-
-shell_command: shell_command_1
-                       { $$ = $1; }
-       |       shell_command_1 redirections
+       |       shell_command redirection_list
                        {
-                         if ($1->redirects)
+                         COMMAND *tc;
+
+                         tc = $1;
+                         /* According to Posix.2 3.9.5, redirections
+                            specified after the body of a function should
+                            be attached to the function and performed when
+                            the function is executed, not as part of the
+                            function definition command. */
+                         if (tc->type == cm_function_def)
+                           {
+                             tc = tc->value.Function_def->command;
+                             if (tc->type == cm_group)
+                               tc = tc->value.Group->command;
+                           }
+                         if (tc->redirects)
                            {
                              register REDIRECT *t;
-                             for (t = $1->redirects; t->next; t = t->next)
+                             for (t = tc->redirects; t->next; t = t->next)
                                ;
                              t->next = $2;
                            }
                          else
-                           $1->redirects = $2;
+                           tc->redirects = $2;
                          $$ = $1;
                        }
        ;
 
-shell_command_1: FOR WORD newlines DO list DONE
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5); }
-       |       FOR WORD newlines '{' list '}'
-                       { $$ = make_for_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5); }
-       |       FOR WORD ';' newlines DO list DONE
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
-       |       FOR WORD ';' newlines '{' list '}'
-                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
-       |       FOR WORD newlines IN words list_terminator newlines DO list DONE
-                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
-       |       FOR WORD newlines IN words list_terminator newlines '{' list '}'
-                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
-
-       |       CASE WORD newlines IN newlines ESAC
-                       { $$ = make_case_command ($2, (PATTERN_LIST *)NULL); }
-       |       CASE WORD newlines IN case_clause_sequence newlines ESAC
-                       { $$ = make_case_command ($2, $5); }
-       |       CASE WORD newlines IN case_clause_1 ESAC
-                       { $$ = make_case_command ($2, $5); }
-       |       WHILE list DO list DONE
+shell_command: for_command
+                       { $$ = $1; }
+       |       case_command
+                       { $$ = $1; }
+       |       WHILE compound_list DO compound_list DONE
                        { $$ = make_while_command ($2, $4); }
-       |       UNTIL list DO list DONE
+       |       UNTIL compound_list DO compound_list DONE
                        { $$ = make_until_command ($2, $4); }
        |       select_command
                        { $$ = $1; }
@@ -449,72 +478,74 @@ shell_command_1: FOR WORD newlines DO list DONE
                        { $$ = $1; }
        ;
 
-select_command:        SELECT WORD newlines DO list DONE
+for_command:   FOR WORD newline_list DO list DONE
+                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5); }
+       |       FOR WORD newline_list '{' list '}'
+                       { $$ = make_for_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5); }
+       |       FOR WORD ';' newline_list DO list DONE
+                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
+       |       FOR WORD ';' newline_list '{' list '}'
+                       { $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6); }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list DO list DONE
+                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list '{' list '}'
+                       { $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9); }
+       ;
+
+select_command:        SELECT WORD newline_list DO list DONE
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5);
-#endif
                        }
-       |       SELECT WORD newlines '{' list '}'
+       |       SELECT WORD newline_list '{' list '}'
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, add_string_to_list ("$@", (WORD_LIST *)NULL), $5);
-#endif
                        }
-       |       SELECT WORD ';' newlines DO list DONE
+       |       SELECT WORD ';' newline_list DO list DONE
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6);
-#endif
                        }
-       |       SELECT WORD ';' newlines '{' list '}'
+       |       SELECT WORD ';' newline_list '{' list '}'
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6);
-#endif
                        }
-       |       SELECT WORD newlines IN words list_terminator newlines DO list DONE
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, (WORD_LIST *)reverse_list ($5), $9);
-#endif
                        }
-       |       SELECT WORD newlines IN words list_terminator newlines '{' list '}'
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
                        {
-#if defined (SELECT_COMMAND)
                          $$ = make_select_command ($2, (WORD_LIST *)reverse_list ($5), $9);
-#endif
                        }
        ;
 
-function_def:  WORD '(' ')' newlines group_command
-                       { $$ = make_function_def ($1, $5); }
-
-       |       WORD '(' ')' newlines group_command redirections
-                       { $5->redirects = $6; $$ = make_function_def ($1, $5); }
+case_command:  CASE WORD newline_list IN newline_list ESAC
+                       { $$ = make_case_command ($2, (PATTERN_LIST *)NULL); }
+       |       CASE WORD newline_list IN case_clause_sequence newline_list ESAC
+                       { $$ = make_case_command ($2, $5); }
+       |       CASE WORD newline_list IN case_clause ESAC
+                       { $$ = make_case_command ($2, $5); }
+       ;
 
-       |       FUNCTION WORD '(' ')' newlines group_command
-                       { $$ = make_function_def ($2, $6); }
+function_def:  WORD '(' ')' newline_list group_command
+                       { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
 
-       |       FUNCTION WORD '(' ')' newlines group_command redirections
-                       { $6->redirects = $7; $$ = make_function_def ($2, $6); }
 
-       |       FUNCTION WORD newlines group_command
-                       { $$ = make_function_def ($2, $4); }
+       |       FUNCTION WORD '(' ')' newline_list group_command
+                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
 
-       |       FUNCTION WORD newlines group_command redirections
-                       { $4->redirects = $5; $$ = make_function_def ($2, $4); }
+       |       FUNCTION WORD newline_list group_command
+                       { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
        ;
 
-subshell:      '(' list ')'
+subshell:      '(' compound_list ')'
                        { $2->flags |= CMD_WANT_SUBSHELL; $$ = $2; }
        ;
-       
-if_command:    IF list THEN list FI
+
+if_command:    IF compound_list THEN compound_list FI
                        { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
-       |       IF list THEN list ELSE list FI
+       |       IF compound_list THEN compound_list ELSE compound_list FI
                        { $$ = make_if_command ($2, $4, $6); }
-       |       IF list THEN list elif_clause FI
+       |       IF compound_list THEN compound_list elif_clause FI
                        { $$ = make_if_command ($2, $4, $5); }
        ;
 
@@ -523,44 +554,34 @@ group_command:    '{' list '}'
                        { $$ = make_group_command ($2); }
        ;
 
-elif_clause:   ELIF list THEN list
+elif_clause:   ELIF compound_list THEN compound_list
                        { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
-       |       ELIF list THEN list ELSE list
+       |       ELIF compound_list THEN compound_list ELSE compound_list
                        { $$ = make_if_command ($2, $4, $6); }
-       |       ELIF list THEN list elif_clause
+       |       ELIF compound_list THEN compound_list elif_clause
                        { $$ = make_if_command ($2, $4, $5); }
        ;
 
-case_clause_1: pattern_list_1
-       |       case_clause_sequence pattern_list_1
+case_clause:   pattern_list
+       |       case_clause_sequence pattern_list
                        { $2->next = $1; $$ = $2; }
        ;
 
-pattern_list_1:        newlines pattern ')' list
+pattern_list:  newline_list pattern ')' compound_list
                        { $$ = make_pattern_list ($2, $4); }
-       |       newlines pattern ')' newlines
+       |       newline_list pattern ')' newline_list
                        { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
-       |       newlines '(' pattern ')' list
+       |       newline_list '(' pattern ')' compound_list
                        { $$ = make_pattern_list ($3, $5); }
-       |       newlines '(' pattern ')' newlines
+       |       newline_list '(' pattern ')' newline_list
                        { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
        ;
 
-case_clause_sequence:  pattern_list
-       |       case_clause_sequence pattern_list
+case_clause_sequence:  pattern_list SEMI_SEMI
+       |       case_clause_sequence pattern_list SEMI_SEMI
                        { $2->next = $1; $$ = $2; }
        ;
 
-pattern_list:  newlines pattern ')' list SEMI_SEMI
-                       { $$ = make_pattern_list ($2, $4); }
-       |       newlines pattern ')' newlines SEMI_SEMI
-                       { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
-       |       newlines '(' pattern ')' list SEMI_SEMI
-                       { $$ = make_pattern_list ($3, $5); }
-       |       newlines '(' pattern ')' newlines SEMI_SEMI
-                       { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
-       ;
-
 pattern:       WORD
                        { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
        |       pattern '|' WORD
@@ -572,7 +593,7 @@ pattern:    WORD
    It must end with a newline or semicolon.
    Lists are used within commands such as if, for, while.  */
 
-list:          newlines list0
+list:          newline_list list0
                        {
                          $$ = $2;
                          if (need_here_doc)
@@ -580,41 +601,42 @@ list:             newlines list0
                         }
        ;
 
-list0:         list1
-       |       list1 '\n' newlines
-       |       list1 '&' newlines
+compound_list: list
+       |       newline_list list1
+                       {
+                         $$ = $2;
+                       }
+       ;
+
+list0:         list1 '\n' newline_list
+       |       list1 '&' newline_list
                        {
                          if ($1->type == cm_connection)
                            $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
                          else
                            $$ = command_connect ($1, (COMMAND *)NULL, '&');
                        }
-       |       list1 ';' newlines
+       |       list1 ';' newline_list
 
        ;
 
-list1:         list1 AND_AND newlines list1
+list1:         list1 AND_AND newline_list list1
                        { $$ = command_connect ($1, $4, AND_AND); }
-       |       list1 OR_OR newlines list1
+       |       list1 OR_OR newline_list list1
                        { $$ = command_connect ($1, $4, OR_OR); }
-       |       list1 '&' newlines list1
+       |       list1 '&' newline_list list1
                        {
                          if ($1->type == cm_connection)
                            $$ = connect_async_list ($1, $4, '&');
                          else
                            $$ = command_connect ($1, $4, '&');
                        }
-       |       list1 ';' newlines list1
+       |       list1 ';' newline_list list1
                        { $$ = command_connect ($1, $4, ';'); }
-       |       list1 '\n' newlines list1
+       |       list1 '\n' newline_list list1
                        { $$ = command_connect ($1, $4, ';'); }
-       |       pipeline
+       |       pipeline_command
                        { $$ = $1; }
-       |       BANG pipeline
-                       {
-                         $2->flags |= CMD_INVERT_RETURN;
-                         $$ = $2;
-                       }
        ;
 
 list_terminator:'\n'
@@ -622,8 +644,8 @@ list_terminator:'\n'
        |       yacc_EOF
        ;
 
-newlines:
-       |       newlines '\n'
+newline_list:
+       |       newline_list '\n'
        ;
 
 /* A simple_list is a list that contains no significant newlines
@@ -655,9 +677,9 @@ simple_list:        simple_list1
                        }
        ;
 
-simple_list1:  simple_list1 AND_AND newlines simple_list1
+simple_list1:  simple_list1 AND_AND newline_list simple_list1
                        { $$ = command_connect ($1, $4, AND_AND); }
-       |       simple_list1 OR_OR newlines simple_list1
+       |       simple_list1 OR_OR newline_list simple_list1
                        { $$ = command_connect ($1, $4, OR_OR); }
        |       simple_list1 '&' simple_list1
                        {
@@ -668,45 +690,104 @@ simple_list1:    simple_list1 AND_AND newlines simple_list1
                        }
        |       simple_list1 ';' simple_list1
                        { $$ = command_connect ($1, $3, ';'); }
-       |       pipeline
+
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+pipeline_command: pipeline
                        { $$ = $1; }
        |       BANG pipeline
                        {
                          $2->flags |= CMD_INVERT_RETURN;
                          $$ = $2;
                        }
+       |       timespec pipeline
+                       {
+                         $2->flags |= $1;
+                         $$ = $2;
+                       }
+       |       timespec BANG pipeline
+                       {
+                         $3->flags |= $1;
+                         $$ = $3;
+                       }
+       |       BANG timespec pipeline
+                       {
+                         $3->flags |= $2|CMD_INVERT_RETURN;
+                         $$ = $3;
+                       }
        ;
 
 pipeline:
-               pipeline '|' newlines pipeline
+               pipeline '|' newline_list pipeline
                        { $$ = command_connect ($1, $4, '|'); }
        |       command
                        { $$ = $1; }
        ;
+
+timespec:      TIME
+                       { $$ = CMD_TIME_PIPELINE; }
+       |       TIME TIMEOPT
+                       { $$ = 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 */
+
 /* Initial size to allocate for tokens, and the
    amount to grow them by. */
 #define TOKEN_DEFAULT_GROW_SIZE 512
 
+/* Shell meta-characters that, when unquoted, separate words. */
+#define shellmeta(c)   (strchr ("()<>;&|", (c)) != 0)
+#define shellbreak(c)  (strchr ("()<>;&| \t\n", (c)) != 0)
+#define shellquote(c)  ((c) == '"' || (c) == '`' || (c) == '\'')
+#define shellexp(c)    ((c) == '$' || (c) == '<' || (c) == '>')
+
 /* The token currently being read. */
-static int current_token = 0;
+static int current_token;
 
 /* The last read token, or NULL.  read_token () uses this for context
    checking. */
-static int last_read_token = 0;
+static int last_read_token;
 
 /* The token read prior to last_read_token. */
-static int token_before_that = 0;
+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. */
-static int token_to_read = 0;
+   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;
+
+/* The current parser state. */
+static int parser_state;
 
 /* Global var is non-zero when end of file has been reached. */
 int EOF_Reached = 0;
 
+void
+debug_parser (i)
+     int i;
+{
+#if YYDEBUG != 0
+  yydebug = i;
+#endif
+}
+
 /* yy_getc () returns the next available character from input or EOF.
    yy_ungetc (c) makes `c' the next character to read.
    init_yy_io (get, unget, type, location) makes the function GET the
@@ -716,6 +797,7 @@ int EOF_Reached = 0;
    the input is coming from. */
 
 /* Unconditionally returns end-of-file. */
+int
 return_EOF ()
 {
   return (EOF);
@@ -725,11 +807,13 @@ return_EOF ()
    See ./input.h for a clearer description. */
 BASH_INPUT bash_input;
 
-/* Set all of the fields in BASH_INPUT to NULL. */
+/* Set all of the fields in BASH_INPUT to NULL.  Free bash_input.name if it
+   is non-null, avoiding a memory leak. */
 void
 initialize_bash_input ()
 {
-  bash_input.type = 0;
+  bash_input.type = st_none;
+  FREE (bash_input.name);
   bash_input.name = (char *)NULL;
   bash_input.location.file = (FILE *)NULL;
   bash_input.location.string = (char *)NULL;
@@ -748,12 +832,9 @@ init_yy_io (get, unget, type, name, location)
 {
   bash_input.type = type;
   FREE (bash_input.name);
+  bash_input.name = name ? savestring (name) : (char *)NULL;
 
-  if (name)
-    bash_input.name = savestring (name);
-  else
-    bash_input.name = (char *)NULL;
-
+  /* XXX */
 #if defined (CRAY)
   memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location));
 #else
@@ -764,6 +845,7 @@ init_yy_io (get, unget, type, name, location)
 }
 
 /* Call this to get the next character of input. */
+int
 yy_getc ()
 {
   return (*(bash_input.getter)) ();
@@ -771,6 +853,7 @@ yy_getc ()
 
 /* Call this to unget C.  That is, to make C the next character
    to be read. */
+int
 yy_ungetc (c)
      int c;
 {
@@ -787,6 +870,7 @@ input_file_descriptor ()
       return (fileno (bash_input.location.file));
     case st_bstream:
       return (bash_input.location.buffered_fd);
+    case st_stdin:
     default:
       return (fileno (stdin));
     }
@@ -807,11 +891,11 @@ int current_readline_line_index = 0;
 static int
 yy_readline_get ()
 {
+  SigHandler *old_sigint;
+  int line_len, c;
+
   if (!current_readline_line)
     {
-      SigHandler *old_sigint;
-      int line_len;
-
       if (!bash_readline_initialized)
        initialize_readline ();
 
@@ -826,10 +910,8 @@ yy_readline_get ()
          interrupt_immediately++;
        }
 
-      if (!current_readline_prompt)
-       current_readline_line = readline ("");
-      else
-       current_readline_line = readline (current_readline_prompt);
+      current_readline_line = readline (current_readline_prompt ?
+                                         current_readline_prompt : "");
 
       if (signal_is_ignored (SIGINT) == 0)
        {
@@ -837,22 +919,23 @@ yy_readline_get ()
          set_signal_handler (SIGINT, old_sigint);
        }
 
-      /* Reset the prompt to whatever is in the decoded value of
-        prompt_string_pointer. */
+#if 0
+      /* Reset the prompt to the decoded value of prompt_string_pointer. */
       reset_readline_prompt ();
+#endif
 
-      current_readline_line_index = 0;
-
-      if (!current_readline_line)
+      if (current_readline_line == 0)
        return (EOF);
 
+      current_readline_line_index = 0;
       line_len = strlen (current_readline_line);
+
       current_readline_line = xrealloc (current_readline_line, 2 + line_len);
       current_readline_line[line_len++] = '\n';
       current_readline_line[line_len] = '\0';
     }
 
-  if (!current_readline_line[current_readline_line_index])
+  if (current_readline_line[current_readline_line_index] == 0)
     {
       free (current_readline_line);
       current_readline_line = (char *)NULL;
@@ -860,20 +943,21 @@ yy_readline_get ()
     }
   else
     {
-      int c = (unsigned char)current_readline_line[current_readline_line_index++];
+      c = (unsigned char)current_readline_line[current_readline_line_index++];
       return (c);
     }
 }
 
 static int
 yy_readline_unget (c)
+     int c;
 {
   if (current_readline_line_index && current_readline_line)
     current_readline_line[--current_readline_line_index] = c;
   return (c);
 }
 
-void  
+void
 with_input_from_stdin ()
 {
   INPUT_STREAM location;
@@ -904,7 +988,7 @@ with_input_from_stdin ()
 static int
 yy_string_get ()
 {
-  register unsigned char *string;
+  register char *string;
   register int c;
 
   string = bash_input.location.string;
@@ -913,7 +997,7 @@ yy_string_get ()
   /* If the string doesn't exist, or is empty, EOF found. */
   if (string && *string)
     {
-      c = *string++;
+      c = *(unsigned char *)string++;
       bash_input.location.string = string;
     }
   return (c);
@@ -929,13 +1013,11 @@ yy_string_unget (c)
 
 void
 with_input_from_string (string, name)
-     char *string;
-     char *name;
+     char *string, *name;
 {
   INPUT_STREAM location;
 
   location.string = string;
-
   init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
 }
 
@@ -951,11 +1033,14 @@ yy_stream_get ()
   int result = EOF;
 
   if (bash_input.location.file)
-#if defined (NO_READ_RESTART_ON_SIGNAL)
-    result = (unsigned char)getc_with_restart (bash_input.location.file);
-#else
-    result = (unsigned char)getc (bash_input.location.file);
-#endif /* !NO_READ_RESTART_ON_SIGNAL */
+    {
+#if !defined (HAVE_RESTARTABLE_SYSCALLS)
+      result = getc_with_restart (bash_input.location.file);
+#else /* HAVE_RESTARTABLE_SYSCALLS */
+      result = getc (bash_input.location.file);
+      result = (feof (bash_input.location.file)) ? EOF : (unsigned char)result;
+#endif /* HAVE_RESTARTABLE_SYSCALLS */
+    }
   return (result);
 }
 
@@ -963,11 +1048,11 @@ static int
 yy_stream_unget (c)
      int c;
 {
-#if defined (NO_READ_RESTART_ON_SIGNAL)
+#if !defined (HAVE_RESTARTABLE_SYSCALLS)
   return (ungetc_with_restart (c, bash_input.location.file));
-#else
+#else /* HAVE_RESTARTABLE_SYSCALLS */
   return (ungetc (c, bash_input.location.file));
-#endif
+#endif /* HAVE_RESTARTABLE_SYSCALLS */
 }
 
 void
@@ -995,7 +1080,9 @@ int line_number = 0;
 
 STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL;
 
-push_stream ()
+void
+push_stream (reset_lineno)
+     int reset_lineno;
 {
   STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER));
 
@@ -1015,13 +1102,14 @@ push_stream ()
   bash_input.name = (char *)NULL;
   saver->next = stream_list;
   stream_list = saver;
-  EOF_Reached = line_number = 0;
+  EOF_Reached = 0;
+  if (reset_lineno)
+    line_number = 0;
 }
 
+void
 pop_stream ()
 {
-  int temp;
-
   if (!stream_list)
     EOF_Reached = 1;
   else
@@ -1067,36 +1155,35 @@ pop_stream ()
 /* Return 1 if a stream of type TYPE is saved on the stack. */
 int
 stream_on_stack (type)
-     int type;
+     enum stream_type type;
 {
   register STREAM_SAVER *s;
+
   for (s = stream_list; s; s = s->next)
     if (s->bash_input.type == type)
       return 1;
   return 0;
 }
 
-\f
 /*
  * This is used to inhibit alias expansion and reserved word recognition
- * inside case statement pattern lists.  A `case statement pattern list'
- * is:
+ * inside case statement pattern lists.  A `case statement pattern list' is:
+ *
  *     everything between the `in' in a `case word in' and the next ')'
  *     or `esac'
  *     everything between a `;;' and the next `)' or `esac'
  */
-static int in_case_pattern_list = 0;
 
 #if defined (ALIAS)
+
+#define END_OF_ALIAS 0
+
 /*
  * Pseudo-global variables used in implementing token-wise alias expansion.
  */
 
-static int expand_next_token = 0;
-
 /*
- * Pushing and popping strings.  This works together with shell_getc to 
+ * Pushing and popping strings.  This works together with shell_getc to
  * implement alias expansion on a per-token basis.
  */
 
@@ -1104,13 +1191,12 @@ typedef struct string_saver {
   struct string_saver *next;
   int expand_alias;  /* Value to set expand_alias to when string is popped. */
   char *saved_line;
+  alias_t *expander;   /* alias that caused this line to be pushed. */
   int saved_line_size, saved_line_index, saved_line_terminator;
 } STRING_SAVER;
 
 STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
 
-static void save_expansion ();
-
 /*
  * Push the current shell_input_line onto a stack of such lines and make S
  * the current input.  Used when expanding aliases.  EXPAND is used to set
@@ -1120,10 +1206,10 @@ static void save_expansion ();
  * into S; it is saved and used to prevent infinite recursive expansion.
  */
 static void
-push_string (s, expand, token)
+push_string (s, expand, ap)
      char *s;
      int expand;
-     char *token;
+     alias_t *ap;
 {
   STRING_SAVER *temp = (STRING_SAVER *) xmalloc (sizeof (STRING_SAVER));
 
@@ -1132,16 +1218,17 @@ push_string (s, expand, token)
   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->expander = ap;
   temp->next = pushed_string_list;
   pushed_string_list = temp;
 
-  save_expansion (token);
+  ap->flags |= AL_BEINGEXPANDED;
 
   shell_input_line = s;
   shell_input_line_size = strlen (s);
   shell_input_line_index = 0;
   shell_input_line_terminator = '\0';
-  expand_next_token = 0;
+  parser_state &= ~PST_ALEXPNEXT;
 }
 
 /*
@@ -1160,102 +1247,38 @@ pop_string ()
   shell_input_line_index = pushed_string_list->saved_line_index;
   shell_input_line_size = pushed_string_list->saved_line_size;
   shell_input_line_terminator = pushed_string_list->saved_line_terminator;
-  expand_next_token = pushed_string_list->expand_alias;
+
+  if (pushed_string_list->expand_alias)
+    parser_state |= PST_ALEXPNEXT;
+  else
+    parser_state &= ~PST_ALEXPNEXT;
 
   t = pushed_string_list;
   pushed_string_list = pushed_string_list->next;
-  free((char *)t);
+
+  t->expander->flags &= ~AL_BEINGEXPANDED;
+
+  free ((char *)t);
 }
 
 static void
 free_string_list ()
 {
-  register STRING_SAVER *t = pushed_string_list, *t1;
+  register STRING_SAVER *t, *t1;
 
-  while (t)
+  for (t = pushed_string_list; t; )
     {
       t1 = t->next;
       FREE (t->saved_line);
+      t->expander->flags &= ~AL_BEINGEXPANDED;
       free ((char *)t);
       t = t1;
     }
   pushed_string_list = (STRING_SAVER *)NULL;
 }
 
-/* This is a stack to save the values of all tokens for which alias
-   expansion has been performed during the current call to read_token ().
-   It is used to prevent alias expansion loops:
-
-      alias foo=bar
-      alias bar=baz
-      alias baz=foo
-
-   Ideally this would be taken care of by push and pop string, but because
-   of when strings are popped the stack will not contain the correct
-   strings to test against.  (The popping is done in shell_getc, so that when
-   the current string is exhausted, shell_getc can simply pop that string off
-   the stack, restore the previous string, and continue with the character
-   following the token whose expansion was originally pushed on the stack.)
-
-   What we really want is a record of all tokens that have been expanded for
-   aliases during the `current' call to read_token().  This does that, at the
-   cost of being somewhat special-purpose (OK, OK vile and unclean). */
-
-typedef struct _exp_saver {
-      struct _exp_saver *next;
-      char *saved_token;
-} EXPANSION_SAVER;
-
-EXPANSION_SAVER *expanded_token_stack = (EXPANSION_SAVER *)NULL;
-
-static void
-save_expansion (s)
-     char *s;
-{
-  EXPANSION_SAVER *t;
-
-  t = (EXPANSION_SAVER *) xmalloc (sizeof (EXPANSION_SAVER));
-  t->saved_token = savestring (s);
-  t->next = expanded_token_stack;
-  expanded_token_stack = t;
-}
-
-/* Return 1 if TOKEN has already been expanded in the current `stack' of
-   expansions.  If it has been expanded already, it will appear as the value
-   of saved_token for some entry in the stack of expansions created for the
-   current token being expanded. */
-static int
-token_has_been_expanded (token)
-     char *token;
-{
-  register EXPANSION_SAVER *t = expanded_token_stack;
-
-  while (t)
-    {
-      if (STREQ (token, t->saved_token))
-       return (1);
-      t = t->next;
-    }
-  return (0);
-}
-
-static void
-free_expansion_stack ()
-{
-  register EXPANSION_SAVER *t = expanded_token_stack, *t1;
-
-  while (t)
-    {
-      t1 = t->next;
-      free (t->saved_token);
-      free (t);
-      t = t1;
-    }
-  expanded_token_stack = (EXPANSION_SAVER *)NULL;
-}
-
 #endif /* ALIAS */
-\f
+
 /* 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
@@ -1268,6 +1291,13 @@ read_a_line (remove_quoted_newline)
   static int buffer_size = 0;
   int indx = 0, c, peekc, pass_next;
 
+#if defined (READLINE)
+  if (interactive && bash_input.type != st_string && no_line_editing)
+#else
+  if (interactive && bash_input.type != st_string)
+#endif
+    print_prompt ();
+
   pass_next = 0;
   while (1)
     {
@@ -1282,17 +1312,15 @@ read_a_line (remove_quoted_newline)
       /* If there is no more input, then we return NULL. */
       if (c == EOF)
        {
+         if (interactive && bash_input.type == st_stream)
+           clearerr (stdin);
          if (indx == 0)
            return ((char *)NULL);
          c = '\n';
        }
 
       /* `+2' in case the final character in the buffer is a newline. */
-      if (indx + 2 > buffer_size)
-       if (!buffer_size)
-         line_buffer = xmalloc (buffer_size = 128);
-       else
-         line_buffer = xrealloc (line_buffer, buffer_size += 128);
+      RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128);
 
       /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a
         here document with an unquoted delimiter.  In this case,
@@ -1342,7 +1370,6 @@ read_secondary_line (remove_quoted_newline)
   return (read_a_line (remove_quoted_newline));
 }
 
-\f
 /* **************************************************************** */
 /*                                                                 */
 /*                             YYLEX ()                            */
@@ -1369,21 +1396,56 @@ STRING_INT_ALIST word_token_alist[] = {
   { "done", DONE },
   { "in", IN },
   { "function", FUNCTION },
+#if defined (COMMAND_TIMING)
+  { "time", TIME },
+#endif
   { "{", '{' },
   { "}", '}' },
   { "!", BANG },
   { (char *)NULL, 0}
 };
 
+/* These are used by read_token_word, but appear up here so that shell_getc
+   can use them to decide when to add otherwise blank lines to the history. */
+
+/* The primary delimiter stack. */
+struct dstack dstack = {  (char *)NULL, 0, 0 };
+
+/* A temporary delimiter stack to be used when decoding prompt strings.
+   This is needed because command substitutions in prompt strings (e.g., PS2)
+   can screw up the parser's quoting state. */
+static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
+
+/* Macro for accessing the top delimiter on the stack.  Returns the
+   delimiter or zero if none. */
+#define current_delimiter(ds) \
+  (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0)
+
+#define push_delimiter(ds, character) \
+  do \
+    { \
+      if (ds.delimiter_depth + 2 > ds.delimiter_space) \
+       ds.delimiters = xrealloc \
+         (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \
+      ds.delimiters[ds.delimiter_depth] = character; \
+      ds.delimiter_depth++; \
+    } \
+  while (0)
+
+#define pop_delimiter(ds)      ds.delimiter_depth--
+
 /* Return the next shell input character.  This always reads characters
    from shell_input_line; when that line is exhausted, it is time to
    read the next line.  This is called by read_token when the shell is
    processing normal command input. */
+
 static int
 shell_getc (remove_quoted_newline)
      int remove_quoted_newline;
 {
+  register int i;
   int c;
+  static int mustpop = 0;
 
   QUIT;
 
@@ -1398,10 +1460,6 @@ shell_getc (remove_quoted_newline)
   if (!shell_input_line || !shell_input_line[shell_input_line_index])
 #endif /* !ALIAS */
     {
-      register int i, l;
-
-      restart_read_next_line:
-
       line_number++;
 
     restart_read:
@@ -1437,16 +1495,14 @@ shell_getc (remove_quoted_newline)
          /* Allow immediate exit if interrupted during input. */
          QUIT;
 
-         if (i + 2 > shell_input_line_size)
-           shell_input_line =
-             xrealloc (shell_input_line, shell_input_line_size += 256);
+         RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
 
          if (c == EOF)
            {
              if (bash_input.type == st_stream)
                clearerr (stdin);
 
-             if (!i)
+             if (i == 0)
                shell_input_line_terminator = EOF;
 
              shell_input_line[i] = '\0';
@@ -1466,11 +1522,23 @@ shell_getc (remove_quoted_newline)
       shell_input_line_len = i;                /* == strlen (shell_input_line) */
 
 #if defined (HISTORY)
-      if (interactive && shell_input_line && shell_input_line[0])
+      if (remember_on_history && shell_input_line && shell_input_line[0])
        {
          char *expansions;
-
+#  if defined (BANG_HISTORY)
+         int old_hist;
+
+         /* If the current delimiter is a single quote, we should not be
+            performing history expansion, even if we're on a different
+            line from the original single quote. */
+         old_hist = history_expansion_inhibited;
+         if (current_delimiter (dstack) == '\'')
+           history_expansion_inhibited = 1;
+#  endif
          expansions = pre_process_line (shell_input_line, 1, 1);
+#  if defined (BANG_HISTORY)
+         history_expansion_inhibited = old_hist;
+#  endif
 
          free (shell_input_line);
          shell_input_line = expansions;
@@ -1484,6 +1552,18 @@ shell_getc (remove_quoted_newline)
             true allocated size of shell_input_line anymore. */
          shell_input_line_size = shell_input_line_len;
        }
+      /* XXX - this is grotesque */
+      else if (remember_on_history && shell_input_line &&
+              shell_input_line[0] == '\0' &&
+              current_command_line_count > 1 && current_delimiter (dstack))
+       {
+         /* We know shell_input_line[0] == 0 and we're reading some sort of
+            quoted string.  This means we've got a line consisting of only
+            a newline in a quoted string.  We want to make sure this line
+            gets added to the history. */
+         maybe_add_history (shell_input_line);
+       }
+
 #endif /* HISTORY */
 
       if (shell_input_line)
@@ -1506,17 +1586,15 @@ shell_getc (remove_quoted_newline)
         not already end in an EOF character.  */
       if (shell_input_line_terminator != EOF)
        {
-         l = shell_input_line_len;     /* was a call to strlen */
-
-         if (l + 3 > shell_input_line_size)
+         if (shell_input_line_len + 3 > shell_input_line_size)
            shell_input_line = xrealloc (shell_input_line,
                                        1 + (shell_input_line_size += 2));
 
-         shell_input_line[l] = '\n';
-         shell_input_line[l + 1] = '\0';
+         shell_input_line[shell_input_line_len] = '\n';
+         shell_input_line[shell_input_line_len + 1] = '\0';
        }
     }
-  
+
   c = shell_input_line[shell_input_line_index];
 
   if (c)
@@ -1526,7 +1604,8 @@ shell_getc (remove_quoted_newline)
       shell_input_line[shell_input_line_index] == '\n')
     {
        prompt_again ();
-       goto restart_read_next_line;
+       line_number++;
+       goto restart_read;
     }
 
 #if defined (ALIAS)
@@ -1537,20 +1616,24 @@ shell_getc (remove_quoted_newline)
      to. */
   if (!c && (pushed_string_list != (STRING_SAVER *)NULL))
     {
-      pop_string ();
-      c = shell_input_line[shell_input_line_index];
-      if (c)
-       shell_input_line_index++;
+      if (mustpop)
+        {
+          pop_string ();
+          c = shell_input_line[shell_input_line_index];
+         if (c)
+           shell_input_line_index++;
+         mustpop--;
+        }
+      else
+        {
+          mustpop++;
+          c = ' ';
+        }
     }
 #endif /* ALIAS */
 
   if (!c && shell_input_line_terminator == EOF)
-    {
-      if (shell_input_line_index != 0)
-       return ('\n');
-      else
-       return (EOF);
-    }
+    return ((shell_input_line_index != 0) ? '\n' : EOF);
 
   return ((unsigned char)c);
 }
@@ -1564,7 +1647,15 @@ shell_ungetc (c)
     shell_input_line[--shell_input_line_index] = c;
 }
 
-/* Discard input until CHARACTER is seen. */
+static void
+shell_ungetchar ()
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line_index--;
+}
+
+/* Discard input until CHARACTER is seen, then push that character back
+   onto the input stream. */
 static void
 discard_until (character)
      int character;
@@ -1577,13 +1668,6 @@ discard_until (character)
   if (c != EOF)
     shell_ungetc (c);
 }
-\f
-/* Place to remember the token.  We try to keep the buffer
-   at a reasonable size, but it can grow. */
-static char *token = (char *)NULL;
-
-/* Current size of the token buffer. */
-static int token_buffer_size = 0;
 
 void
 execute_prompt_command (command)
@@ -1611,10 +1695,17 @@ execute_prompt_command (command)
   bind_variable ("_", last_lastarg);
   FREE (last_lastarg);
 
-  if (token_to_read == '\n')
+  if (token_to_read == '\n')   /* reset_parser was called */
     token_to_read = 0;
 }
 
+/* Place to remember the token.  We try to keep the buffer
+   at a reasonable size, but it can grow. */
+static char *token = (char *)NULL;
+
+/* Current size of the token buffer. */
+static int token_buffer_size;
+
 /* Command to read_token () explaining what we want it to do. */
 #define READ 0
 #define RESET 1
@@ -1623,10 +1714,10 @@ execute_prompt_command (command)
 
 /* Function for yyparse to call.  yylex keeps track of
    the last two tokens read, and calls read_token.  */
-
+static int
 yylex ()
 {
-  if (interactive && (!current_token || current_token == '\n'))
+  if (interactive && (current_token == 0 || current_token == '\n'))
     {
       /* Before we print a prompt, we might have to check mailboxes.
         We do this only if it is time to do so. Notice that only here
@@ -1644,35 +1735,16 @@ yylex ()
        prompt_again ();
     }
 
+  two_tokens_ago = token_before_that;
   token_before_that = last_read_token;
   last_read_token = current_token;
   current_token = read_token (READ);
   return (current_token);
 }
 
-/* Called from shell.c when Control-C is typed at top level.  Or
-   by the error rule at top level. */
-reset_parser ()
-{
-  read_token (RESET);
-}
-  
 /* When non-zero, we have read the required tokens
    which allow ESAC to be the next one read. */
-static int allow_esac_as_next = 0;
-
-/* When non-zero, accept single '{' as a token itself. */
-static int allow_open_brace = 0;
-
-/* DELIMITERS is a stack of the nested delimiters that we have
-   encountered so far. */
-static char *delimiters = (char *)NULL;
-
-/* Offset into the stack of delimiters. */
-int delimiter_depth = 0;
-
-/* How many slots are allocated to DELIMITERS. */
-static int delimiter_space = 0;
+static int esacs_needed_count;
 
 void
 gather_here_documents ()
@@ -1685,32 +1757,16 @@ gather_here_documents ()
     }
 }
 
-/* Macro for accessing the top delimiter on the stack.  Returns the
-   delimiter or zero if none. */
-#define current_delimiter() \
-  (delimiter_depth ? delimiters[delimiter_depth - 1] : 0)
-
-#define push_delimiter(character) \
-  do \
-    { \
-      if (delimiter_depth + 2 > delimiter_space) \
-       delimiters = xrealloc \
-         (delimiters, (delimiter_space += 10) * sizeof (char)); \
-      delimiters[delimiter_depth] = character; \
-      delimiter_depth++; \
-    } \
-  while (0)
-
 /* When non-zero, an open-brace used to create a group is awaiting a close
    brace partner. */
-static int open_brace_awaiting_satisfaction = 0;
+static int open_brace_count;
 
 #define command_token_position(token) \
   (((token) == ASSIGNMENT_WORD) || \
    ((token) != SEMI_SEMI && reserved_word_acceptable(token)))
 
 #define assignment_acceptable(token) command_token_position(token) && \
-                                       (in_case_pattern_list == 0)
+                                       ((parser_state & PST_CASEPAT) == 0)
 
 /* Check to see if TOKEN is a reserved word and return the token
    value if it is. */
@@ -1723,101 +1779,217 @@ static int open_brace_awaiting_satisfaction = 0;
        for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \
          if (STREQ (tok, word_token_alist[i].word)) \
            { \
-             if (in_case_pattern_list && (word_token_alist[i].token != ESAC)) \
+             if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
                break; \
-\
              if (word_token_alist[i].token == ESAC) \
-               in_case_pattern_list = 0; \
-\
-             if (word_token_alist[i].token == '{') \
-               open_brace_awaiting_satisfaction++; \
-\
-             if (word_token_alist[i].token == '}' && open_brace_awaiting_satisfaction) \
-               open_brace_awaiting_satisfaction--; \
-\
+               parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
+             else if (word_token_alist[i].token == CASE) \
+               parser_state |= PST_CASESTMT; \
+             else if (word_token_alist[i].token == '{') \
+               open_brace_count++; \
+             else if (word_token_alist[i].token == '}' && open_brace_count) \
+               open_brace_count--; \
              return (word_token_alist[i].token); \
            } \
       } \
   } while (0)
 
-/* Read the next token.  Command can be READ (normal operation) or 
-   RESET (to normalize state). */
-static int
-read_token (command)
-     int command;
+#if defined (ALIAS)
+
+    /* OK, we have a token.  Let's try to alias expand it, if (and only if)
+       it's eligible.
+
+       It is eligible for expansion if the shell is in interactive mode, and
+       the token is unquoted and the last token read was a command
+       separator (or expand_next_token is set), and we are currently
+       processing an alias (pushed_string_list is non-empty) and this
+       token is not the same as the current or any previously
+       processed alias.
+
+       Special cases that disqualify:
+        In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+static int
+alias_expand_token (token)
+     char *token;
 {
-  int character;               /* Current character. */
-  int peek_char;               /* Temporary look-ahead character. */
-  int result;                  /* The thing to return. */
-  WORD_DESC *the_word;         /* The value for YYLVAL when a WORD is read. */
+  char *expanded;
+  alias_t *ap;
 
-  if (token_buffer_size < TOKEN_DEFAULT_GROW_SIZE)
+  if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
+       (parser_state & PST_CASEPAT) == 0)
     {
-      FREE (token);
-      token = xmalloc (token_buffer_size = TOKEN_DEFAULT_GROW_SIZE);
+      ap = find_alias (token);
+
+      /* Currently expanding this token. */
+      if (ap && (ap->flags & AL_BEINGEXPANDED))
+       return (NO_EXPANSION);
+
+      expanded = ap ? savestring (ap->value) : (char *)NULL;
+      if (expanded)
+       {
+         push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
+         return (RE_READ_TOKEN);
+       }
+      else
+       /* This is an eligible token that does not have an expansion. */
+       return (NO_EXPANSION);
     }
+  return (NO_EXPANSION);
+}
+#endif /* ALIAS */
 
-  if (command == RESET)
+/* Handle special cases of token recognition:
+       IN is recognized if the last token was WORD and the token
+       before that was FOR or CASE or SELECT.
+
+       DO is recognized if the last token was WORD and the token
+       before that was FOR or SELECT.
+
+       ESAC is recognized if the last token caused `esacs_needed_count'
+       to be set
+
+       `{' is recognized if the last token as WORD and the token
+       before that was FUNCTION.
+
+       `}' is recognized if there is an unclosed `{' prsent.
+*/
+
+static int
+special_case_tokens (token)
+     char *token;
+{
+  if ((last_read_token == WORD) &&
+#if defined (SELECT_COMMAND)
+      ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
+#else
+      ((token_before_that == FOR) || (token_before_that == CASE)) &&
+#endif
+      (token[0] == 'i' && token[1] == 'n' && token[2] == 0))
     {
-      delimiter_depth = 0;     /* No delimiters found so far. */
-      open_brace_awaiting_satisfaction = 0;
-      in_case_pattern_list = 0;
+      if (token_before_that == CASE)
+       {
+         parser_state |= PST_CASEPAT;
+         esacs_needed_count++;
+       }
+      return (IN);
+    }
 
-#if defined (ALIAS)
-      if (pushed_string_list)
+  if (last_read_token == WORD &&
+#if defined (SELECT_COMMAND)
+      (token_before_that == FOR || token_before_that == SELECT) &&
+#else
+      (token_before_that == FOR) &&
+#endif
+      (token[0] == 'd' && token[1] == 'o' && token[2] == '\0'))
+    return (DO);
+
+  /* Ditto for ESAC in the CASE case.
+     Specifically, this handles "case word in esac", which is a legal
+     construct, certainly because someone will pass an empty arg to the
+     case construct, and we don't want it to barf.  Of course, we should
+     insist that the case construct has at least one pattern in it, but
+     the designers disagree. */
+  if (esacs_needed_count)
+    {
+      esacs_needed_count--;
+      if (STREQ (token, "esac"))
        {
-         free_string_list ();
-         pushed_string_list = (STRING_SAVER *)NULL;
+         parser_state &= ~PST_CASEPAT;
+         return (ESAC);
        }
+    }
 
-      if (expanded_token_stack)
+  /* The start of a shell function definition. */
+  if (parser_state & PST_ALLOWOPNBRC)
+    {
+      parser_state &= ~PST_ALLOWOPNBRC;
+      if (token[0] == '{' && token[1] == '\0')         /* } */
        {
-         free_expansion_stack ();
-         expanded_token_stack = (EXPANSION_SAVER *)NULL;
+         open_brace_count++;
+         function_bstart = line_number;
+         return ('{');                                 /* } */
        }
+    }
+
+  if (open_brace_count && reserved_word_acceptable (last_read_token) && token[0] == '}' && !token[1])
+    {
+      open_brace_count--;              /* { */
+      return ('}');
+    }
+
+  /* Handle -p after `time'. */
+  if (last_read_token == TIME && token[0] == '-' && token[1] == 'p' && !token[2])
+    return (TIMEOPT);
 
-      expand_next_token = 0;
+  return (-1);
+}
+
+/* Called from shell.c when Control-C is typed at top level.  Or
+   by the error rule at top level. */
+void
+reset_parser ()
+{
+  dstack.delimiter_depth = 0;  /* No delimiters found so far. */
+  open_brace_count = 0;
+
+  parser_state = 0;
+
+#if defined (ALIAS)
+  if (pushed_string_list)
+    {
+      free_string_list ();
+      pushed_string_list = (STRING_SAVER *)NULL;
+    }
 #endif /* ALIAS */
 
-      if (shell_input_line)
-       {
-         free (shell_input_line);
-         shell_input_line = (char *)NULL;
-         shell_input_line_size = shell_input_line_index = 0;
-       }
-      last_read_token = '\n';
-      token_to_read = '\n';
+  if (shell_input_line)
+    {
+      free (shell_input_line);
+      shell_input_line = (char *)NULL;
+      shell_input_line_size = shell_input_line_index = 0;
+    }
+
+  FREE (word_desc_to_read);
+  word_desc_to_read = (WORD_DESC *)NULL;
+
+  last_read_token = '\n';
+  token_to_read = '\n';
+}
+
+/* Read the next token.  Command can be READ (normal operation) or
+   RESET (to normalize state). */
+static int
+read_token (command)
+     int command;
+{
+  int character;               /* Current character. */
+  int peek_char;               /* Temporary look-ahead character. */
+  int result;                  /* The thing to return. */
+
+  if (command == RESET)
+    {
+      reset_parser ();
       return ('\n');
     }
 
   if (token_to_read)
     {
-      int rt = token_to_read;
+      result = token_to_read;
+      if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD)
+       yylval.word = word_desc_to_read;
       token_to_read = 0;
-      return (rt);
+      return (result);
     }
 
 #if defined (ALIAS)
-  /* If we hit read_token () and there are no saved strings on the
-     pushed_string_list, then we are no longer currently expanding a
-     token.  This can't be done in pop_stream, because pop_stream
-     may pop the stream before the current token has finished being
-     completely expanded (consider what happens when we alias foo to foo,
-     and then try to expand it). */
-  if (!pushed_string_list && expanded_token_stack)
-    {
-      free_expansion_stack ();
-      expanded_token_stack = (EXPANSION_SAVER *)NULL;
-    }
-
   /* This is a place to jump back to once we have successfully expanded a
      token with an alias and pushed the string with push_string () */
  re_read_token:
-
 #endif /* ALIAS */
 
   /* Read a single word from input.  Start by skipping blanks. */
-  while ((character = shell_getc (1)) != EOF && whitespace (character));
+  while ((character = shell_getc (1)) != EOF && whitespace (character))
+    ;
 
   if (character == EOF)
     {
@@ -1830,17 +2002,7 @@ read_token (command)
       /* A comment.  Discard until EOL or EOF, and then return a newline. */
       discard_until ('\n');
       shell_getc (0);
-
-      /* If we're about to return an unquoted newline, we can go and collect
-        the text of any pending here documents. */
-      if (need_here_doc)
-        gather_here_documents ();
-
-#if defined (ALIAS)
-      expand_next_token = 0;
-#endif /* ALIAS */
-
-      return ('\n');
+      character = '\n';        /* this will take the next if statement and return. */
     }
 
   if (character == '\n')
@@ -1851,31 +2013,30 @@ read_token (command)
        gather_here_documents ();
 
 #if defined (ALIAS)
-      expand_next_token = 0;
+      parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
 
       return (character);
     }
 
-  if (member (character, "()<>;&|"))
+  /* Shell meta-characters. */
+  if (shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
     {
 #if defined (ALIAS)
       /* Turn off alias tokenization iff this character sequence would
         not leave us ready to read a command. */
       if (character == '<' || character == '>')
-       expand_next_token = 0;
+       parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
 
-      /* Please note that the shell does not allow whitespace to
-        appear in between tokens which are character pairs, such as
-        "<<" or ">>".  I believe this is the correct behaviour. */
-      if (character == (peek_char = shell_getc (1)))
+      peek_char = shell_getc (1);
+      if (character == peek_char)
        {
          switch (character)
            {
+           case '<':
              /* If '<' then we could be at "<<" or at "<<-".  We have to
                 look ahead one more character. */
-           case '<':
              peek_char = shell_getc (1);
              if (peek_char == '-')
                return (LESS_LESS_MINUS);
@@ -1889,9 +2050,9 @@ read_token (command)
              return (GREATER_GREATER);
 
            case ';':
-             in_case_pattern_list = 1;
+             parser_state |= PST_CASEPAT;
 #if defined (ALIAS)
-             expand_next_token = 0;
+             parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
              return (SEMI_SEMI);
 
@@ -1900,526 +2061,637 @@ read_token (command)
 
            case '|':
              return (OR_OR);
-           }
-       }
-      else
-       {
-         if (peek_char == '&')
-           {
-             switch (character)
+
+#if defined (DPAREN_ARITHMETIC)
+           case '(':           /* ) */
+             if (reserved_word_acceptable (last_read_token))
                {
-               case '<': return (LESS_AND);
-               case '>': return (GREATER_AND);
+                 parser_state |= PST_DBLPAREN;
+                 yylval.word = make_word ("let");
+                 return (WORD);          
                }
+             break;
+#endif
            }
-         if (character == '<' && peek_char == '>')
-           return (LESS_GREATER);
-         if (character == '>' && peek_char == '|')
-           return (GREATER_BAR);
-         if (peek_char == '>' && character == '&')
-           return (AND_GREATER);
        }
+      else if (character == '<' && peek_char == '&')
+       return (LESS_AND);
+      else if (character == '>' && peek_char == '&')
+       return (GREATER_AND);
+      else if (character == '<' && peek_char == '>')
+       return (LESS_GREATER);
+      else if (character == '>' && peek_char == '|')
+       return (GREATER_BAR);
+      else if (peek_char == '>' && character == '&')
+       return (AND_GREATER);
+
       shell_ungetc (peek_char);
 
       /* If we look like we are reading the start of a function
         definition, then let the reader know about it so that
         we will do the right thing with `{'. */
-      if (character == ')' &&
-         last_read_token == '(' && token_before_that == WORD)
+      if (character == ')' && last_read_token == '(' && token_before_that == WORD)
        {
-         allow_open_brace = 1;
+         parser_state |= PST_ALLOWOPNBRC;
 #if defined (ALIAS)
-         expand_next_token = 0;
+         parser_state &= ~PST_ALEXPNEXT;
 #endif /* ALIAS */
+         function_dstart = line_number;
        }
 
-      if (in_case_pattern_list && (character == ')'))
-       in_case_pattern_list = 0;
+      /* case pattern lists may be preceded by an optional left paren.  If
+        we're not trying to parse a case pattern list, the left paren
+        indicates a subshell. */
+      if (character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */
+       parser_state |= PST_SUBSHELL;
+      /*(*/
+      else if ((parser_state & PST_CASEPAT) && character == ')')
+        parser_state &= ~PST_CASEPAT;
+      /*(*/
+      else if ((parser_state & PST_SUBSHELL) && character == ')')
+       parser_state &= ~PST_SUBSHELL;
 
 #if defined (PROCESS_SUBSTITUTION)
       /* Check for the constructs which introduce process substitution.
         Shells running in `posix mode' don't do process substitution. */
       if (posixly_correct ||
-         (((character == '>' || character == '<') && peek_char == '(') == 0))
+         ((character != '>' && character != '<') || peek_char != '('))
 #endif /* PROCESS_SUBSTITUTION */
        return (character);
     }
 
   /* Hack <&- (close stdin) case. */
-  if (character == '-')
-    {
-      switch (last_read_token)
-       {
-       case LESS_AND:
-       case GREATER_AND:
-         return (character);
-       }
-    }
-  
+  if (character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
+    return (character);
+
   /* Okay, if we got this far, we have to read a word.  Read one,
      and then check it against the known ones. */
-  {
-    /* Index into the token that we are building. */
-    int token_index = 0;
-
-    /* ALL_DIGITS becomes zero when we see a non-digit. */
-    int all_digits = digit (character);
+  result = read_token_word (character);
+#if defined (ALIAS)
+  if (result == RE_READ_TOKEN)
+    goto re_read_token;
+#endif
+  return result;
+}
 
-    /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
-    int dollar_present = 0;
+/* Match a $(...) or other grouping construct.  This has to handle embedded
+   quoted strings ('', ``, "") and nested constructs.  It also must handle
+   reprompting the user, if necessary, after reading a newline, and returning
+   correct error values if it reads EOF. */
+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, start_lineno;
+  char *ret, *nestret;
+  int retind, retsize;
 
-    /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
-    int quoted = 0;
+  count = 1;
+  pass_next_character = was_dollar = 0;
 
-    /* Non-zero means to ignore the value of the next character, and just
-       to add it no matter what. */
-    int pass_next_character = 0;
+  ret = xmalloc (retsize = 64);
+  retind = 0;
 
-    /* Non-zero means parsing a dollar-paren construct.  It is the count of
-       un-quoted closes we need to see. */
-    int dollar_paren_level = 0;
+  start_lineno = line_number;
+  while (count)
+    {
+      ch = shell_getc (qc != '\'' && pass_next_character == 0);
+      if (ch == EOF)
+       {
+         free (ret);
+         parser_error (start_lineno, "unexpected EOF while looking for matching `%c'", close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
 
-    /* Non-zero means parsing a dollar-bracket construct ($[...]).  It is
-       the count of un-quoted `]' characters we need to see. */
-    int dollar_bracket_level = 0;
+      /* Possible reprompting. */
+      if (ch == '\n' && interactive &&
+           (bash_input.type == st_stdin || bash_input.type == st_stream))
+       prompt_again ();
 
-    /* Non-zero means parsing a `${' construct.  It is the count of
-       un-quoted `}' we need to see. */
-    int dollar_brace_level = 0;
+      if (pass_next_character)         /* last char was backslash */
+       {
+         pass_next_character = 0;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0) retind--; /* swallow previously-added backslash */
+             continue;
+           }
 
-    /* A level variable for parsing '${ ... }' constructs inside of double
-       quotes. */
-    int delimited_brace_level = 0;
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if (ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if (ch == CTLESC || ch == CTLNUL)   /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if (ch == close)            /* ending delimiter */
+       count--;
+      else if (ch == open)             /* nested begin */
+       count++;
 
-    /* A boolean variable denoting whether or not we are currently parsing
-       a double-quoted string embedded in a $( ) or ${ } construct. */
-    int embedded_quoted_string = 0;
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
 
-    /* Another level variable.  This one is for dollar_parens inside of
-       double-quotes. */
-    int delimited_paren_level = 0;
+      if (open == '\'')                        /* '' inside grouping construct */
+       continue;
 
-    /* The current delimiting character. */
-    int cd;
+      if (ch == '\\')                  /* backslashes */
+       pass_next_character++;
 
-    for (;;)
-      {
-       if (character == EOF)
-         goto got_token;
+      if (open != close)               /* a grouping construct */
+       {
+         if (shellquote (ch))
+           {
+             /* '', ``, or "" inside $(...) or other grouping construct. */
+             push_delimiter (dstack, ch);
+             nestret = parse_matched_pair (ch, ch, ch, &nestlen, 0);
+             pop_delimiter (dstack);
+             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;
+               }
+             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 (open == '"' && 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;
+           }
+         FREE (nestret);
+       }
+      else if (was_dollar && (ch == '(' || ch == '{' || ch == '['))    /* ) } ] */
+       /* check for $(), $[], or ${} inside quoted string. */
+       {
+         if (open == ch)       /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_matched_pair (0, '(', ')', &nestlen, 0);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, 0);
+         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;
+           }
+         FREE (nestret);
+       }
+      was_dollar = (ch == '$');
+    }
 
-       if (pass_next_character)
-         {
-           pass_next_character = 0;
-           goto got_character;
-         }
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+  return ret;
+}
 
-       cd = current_delimiter ();
+static int
+read_token_word (character)
+     int character;
+{
+  /* The value for YYLVAL when a WORD is read. */
+  WORD_DESC *the_word;
 
-       if (cd && character == '\\' && cd != '\'')
-         {
-           peek_char = shell_getc (0);
-           if (peek_char != '\\')
-             shell_ungetc (peek_char);
-           else
-             {
-               token[token_index++] = character;
-               goto got_character;
-             }
-         }
-
-       /* Handle backslashes.  Quote lots of things when not inside of
-          double-quotes, quote some things inside of double-quotes. */
-          
-       if (character == '\\' && (!delimiter_depth || cd != '\''))
-         {
-           peek_char = shell_getc (0);
-
-           /* Backslash-newline is ignored in all cases excepting
-              when quoted with single quotes. */
-           if (peek_char == '\n')
-             {
-               character = '\n';
-               goto next_character;
-             }
-           else
-             {
-               shell_ungetc (peek_char);
+  /* Index into the token that we are building. */
+  int token_index;
 
-               /* If the next character is to be quoted, do it now. */
-               if (!cd || cd == '`' ||
-                   (cd == '"' && member (peek_char, slashify_in_quotes)))
-                 {
-                   pass_next_character++;
-                   quoted = 1;
-                   goto got_character;
-                 }
-             }
-         }
-
-       /* This is a hack, in its present form.  If a backquote substitution
-          appears within double quotes, everything within the backquotes
-          should be read as part of a single word.  Jesus.  Now I see why
-          Korn introduced the $() form. */
-       if (delimiter_depth && (cd == '"') && (character == '`'))
-         {
-           push_delimiter (character);
-           goto got_character;
-         }
-
-       cd = current_delimiter ();              /* XXX - may not need */
-       if (delimiter_depth)
-         {
-           if (character == cd)
-             {
-               /* If we see a double quote while parsing a double-quoted
-                 $( ) or ${ }, and we have not seen ) or }, respectively,
-                  note that we are in the middle of reading an embedded
-                  quoted string. */
-               if ((delimited_paren_level || delimited_brace_level) &&
-                   (character == '"'))
-                 {
-                   embedded_quoted_string = !embedded_quoted_string;
-                   goto got_character;
-                 }
-               
-               delimiter_depth--;
-               goto got_character;
-             }
-         }
+  /* ALL_DIGITS becomes zero when we see a non-digit. */
+  int all_digits;
 
-       if (cd != '\'')
-         {
-#if defined (PROCESS_SUBSTITUTION)
-           if (character == '$' || character == '<' || character == '>')
-#else
-           if (character == '$')
-#endif /* !PROCESS_SUBSTITUTION */
-             {
-               /* If we're in the middle of parsing a $( ) or ${ }
-                  construct with an embedded quoted string, don't
-                  bother looking at this character any further. */
-               if (embedded_quoted_string)
-                 goto got_character;
-
-               peek_char = shell_getc (1);
-               shell_ungetc (peek_char);
-               if (peek_char == '(')
-                 {
-                   if (!delimiter_depth)
-                     dollar_paren_level++;
-                   else
-                     delimited_paren_level++;
+  /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
+  int dollar_present;
 
-                   pass_next_character++;
-                   goto got_character;
-                 }
-               else if (peek_char == '[' && character == '$')
-                 {
-                   if (!delimiter_depth)
-                     dollar_bracket_level++;
+  /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
+  int quoted;
 
-                   pass_next_character++;
-                   goto got_character;
-                 }
-               /* This handles ${...} constructs. */
-               else if (peek_char == '{' && character == '$')
-                 {
-                   if (!delimiter_depth)
-                     dollar_brace_level++;
-                   else
-                     delimited_brace_level++;
+  /* Non-zero means to ignore the value of the next character, and just
+     to add it no matter what. */
+ int pass_next_character;
 
-                   pass_next_character++;
-                   goto got_character;
-                 }
-             }
+  /* The current delimiting character. */
+  int cd;
+  int result, peek_char;
+  char *ttok, *ttrans;
+  int ttoklen, ttranslen;
 
-           /* If we are parsing a $() or $[] construct, we need to balance
-              parens and brackets inside the construct.  This whole function
-              could use a rewrite. */
-           if (character == '(' && !embedded_quoted_string)
-             {
-               if (delimiter_depth && delimited_paren_level)
-                 delimited_paren_level++;
+  if (token_buffer_size < TOKEN_DEFAULT_GROW_SIZE)
+    {
+      FREE (token);
+      token = xmalloc (token_buffer_size = TOKEN_DEFAULT_GROW_SIZE);
+    }
 
-               if (!delimiter_depth && dollar_paren_level)
-                 dollar_paren_level++;
-             }
+  token_index = 0;
+  all_digits = digit (character);
+  dollar_present = quoted = pass_next_character = 0;
 
-           if (character == '[')
-             {
-               if (!delimiter_depth && dollar_bracket_level)
-                 dollar_bracket_level++;
-             }
+  for (;;)
+    {
+      if (character == EOF)
+       goto got_token;
 
-           if (character == '{' && !embedded_quoted_string)
-             {
-               if (delimiter_depth && delimited_brace_level)
-                 delimited_brace_level++;
+      if (pass_next_character)
+       {
+         pass_next_character = 0;
+         goto got_character;
+       }
 
-               if (!delimiter_depth && dollar_brace_level)
-                 dollar_brace_level++;
-             }
+      cd = current_delimiter (dstack);
 
-           /* This code needs to take into account whether we are inside a
-              case statement pattern list, and whether this paren is supposed
-              to terminate it (hey, it could happen).  It's not as simple
-              as just using in_case_pattern_list, because we're not parsing
-              anything while we're reading a $( ) construct.  Maybe we
-              should move that whole mess into the yacc parser. */
-           if (character == ')' && !embedded_quoted_string)
-             {
-               if (delimiter_depth && delimited_paren_level)
-                 delimited_paren_level--;
+      /* Handle backslashes.  Quote lots of things when not inside of
+        double-quotes, quote some things inside of double-quotes. */
+      if (character == '\\')
+       {
+         peek_char = shell_getc (0);
 
-               if (!delimiter_depth && dollar_paren_level)
-                 {
-                   dollar_paren_level--;
-                   goto got_character;
-                 }
-             }
+         /* Backslash-newline is ignored in all cases except
+            when quoted with single quotes. */
+         if (peek_char == '\n')
+           {
+             character = '\n';
+             goto next_character;
+           }
+         else
+           {
+             shell_ungetc (peek_char);
 
-           if (character == ']')
-             {
-               if (!delimiter_depth && dollar_bracket_level)
-                 {
-                   dollar_bracket_level--;
-                   goto got_character;
-                 }
-             }
+             /* If the next character is to be quoted, note it now. */
+             if (cd == 0 || cd == '`' ||
+                 (cd == '"' && member (peek_char, slashify_in_quotes)))
+               pass_next_character++;
 
-           if (character == '}' && !embedded_quoted_string)
-             {
-               if (delimiter_depth && delimited_brace_level)
-                 delimited_brace_level--;
+             quoted = 1;
+             goto got_character;
+           }
+       }
 
-               if (!delimiter_depth && dollar_brace_level)
-                 {
-                   dollar_brace_level--;
-                   goto got_character;
-                 }
-             }
-         }
+#if defined (DPAREN_ARITHMETIC)
+      /* Parse a ksh-style ((...)) expression. */
+      if (parser_state & PST_DBLPAREN)
+       {
+         int exp_lineno;
 
-       if (!dollar_paren_level && !dollar_bracket_level &&
-           !dollar_brace_level && !delimiter_depth &&
-           member (character, " \t\n;&()|<>"))
-         {
+         /* If we've already consumed a right paren that should be part of
+            the expression, push it back so the paren matching code won't
+            return prematurely. */
+         if (character == '(')         /* ) */
            shell_ungetc (character);
-           goto got_token;
-         }
-    
-       if (!delimiter_depth)
-         {
-           if (character == '"' || character == '`' || character == '\'')
-             {
-               push_delimiter (character);
-
-               quoted = 1;
-               goto got_character;
-             }
-         }
-
-       if (all_digits)
-         all_digits = digit (character);
-       if (character == '$')
-         dollar_present = 1;
+         exp_lineno = line_number;
+         ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0);
+         parser_state &= ~PST_DBLPAREN;
+         if (ttok == &matched_pair_error)
+           return -1;
+         /* Check that the next character is the closing right paren.  If
+            not, this is a syntax error. ( */
+         if (shell_getc (0) != ')')
+           {
+             FREE (ttok);      /* ( */
+             parser_error (exp_lineno, "missing closing `)' for arithmetic expression");
+             return -1;
+           }
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = '"';
+         if (character != '(')         /* ) */
+           token[token_index++] = character;
+         strncpy (token + token_index, ttok, ttoklen - 1);
+         token_index += ttoklen - 1;
+         token[token_index++] = '"';
+         FREE (ttok);
+         dollar_present = all_digits = 0;
+         quoted = 1;
+         goto got_token;
+       }
+#endif /* DPAREN_ARITHMETIC */
 
-      got_character:
+      /* Parse a matched pair of quote characters. */
+      if (shellquote (character))
+       {
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (character, character, character, &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;
+         all_digits = 0;
+         quoted = 1;
+         dollar_present |= (character == '"' && strchr (ttok, '$') != 0);
+         FREE (ttok);
+         goto next_character;
+       }
 
-       if (character == CTLESC || character == CTLNUL)
-         token[token_index++] = CTLESC;
+      /* If the delimiter character is not single quote, parse some of
+        the shell expansions that must be read as a single word. */
+#if defined (PROCESS_SUBSTITUTION)
+      if (character == '$' || character == '<' || character == '>')
+#else
+      if (character == '$')
+#endif /* !PROCESS_SUBSTITUTION */
+       {
+         peek_char = shell_getc (1);
+         /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
+         if (peek_char == '(' ||
+               ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
+           {
+             if (peek_char == '{')             /* } */
+               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, 0);
+             else if (peek_char == '(')                /* ) */
+               ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+             else
+               ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = 1;
+             all_digits = 0;
+             goto next_character;
+           }
+         /* This handles $'...' and $"..." new-style quoted strings. */
+         else if (character == '$' && (peek_char == '\'' || peek_char == '"'))
+           {
+             ttok = parse_matched_pair (peek_char, peek_char, peek_char, &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;
+             if (peek_char == '\'')
+               ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
+             else
+               ttrans = localeexpand (ttok, 0, ttoklen - 1, &ttranslen);
+             free (ttok);
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttrans);
+             token_index += ttranslen;
+             token[token_index++] = peek_char;
+             FREE (ttrans);
+             quoted = 1;
+             all_digits = 0;
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
 
-       token[token_index++] = character;
+#if defined (ARRAY_VARS)
+      /* Identify possible compound array variable assignment. */
+      else if (character == '=')
+       {
+         peek_char = shell_getc (1);
+         if (peek_char == '(')         /* ) */
+           {
+             ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             all_digits = 0;
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif
 
-       if (token_index == (token_buffer_size - 1))
-         {
-           token_buffer_size += TOKEN_DEFAULT_GROW_SIZE;
-           token = xrealloc (token, token_buffer_size);
-         }
-       next_character:
-       if (character == '\n' && interactive && bash_input.type != st_string)
-         prompt_again ();
+      /* When not parsing a multi-character word construct, shell meta-
+        characters break words. */
+      if (shellbreak (character))
+       {
+         shell_ungetc (character);
+         goto got_token;
+       }
 
-       /* We want to remove quoted newlines (that is, a \<newline> pair)
-          unless we are within single quotes or pass_next_character is
-          set (the shell equivalent of literal-next). */
-       character = shell_getc
-         ((current_delimiter () != '\'') && (!pass_next_character));
-      }
+    got_character:
 
-  got_token:
+      all_digits &= digit (character);
+      dollar_present |= character == '$';
 
-    token[token_index] = '\0';
-       
-    if ((delimiter_depth || dollar_paren_level || dollar_bracket_level) &&
-       character == EOF)
-      {
-       char reporter = '\0';
+      if (character == CTLESC || character == CTLNUL)
+       token[token_index++] = CTLESC;
 
-       if (!delimiter_depth)
-         {
-           if (dollar_paren_level)
-             reporter = ')';
-           else if (dollar_bracket_level)
-             reporter = ']';
-         }
+      token[token_index++] = character;
 
-       if (!reporter)
-         reporter = current_delimiter ();
+      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+                             TOKEN_DEFAULT_GROW_SIZE);
 
-       report_error ("unexpected EOF while looking for `%c'", reporter);
-       return (-1);
-      }
+    next_character:
+      if (character == '\n' && interactive &&
+       (bash_input.type == st_stdin || bash_input.type == st_stream))
+       prompt_again ();
 
-    if (all_digits)
-      {
-       /* 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 (character == '<' || character == '>' ||
-           last_read_token == LESS_AND || last_read_token == GREATER_AND)
-         {
-           yylval.number = atoi (token);
-           return (NUMBER);
-         }
-      }
+      /* We want to remove quoted newlines (that is, a \<newline> pair)
+        unless we are within single quotes or pass_next_character is
+        set (the shell equivalent of literal-next). */
+      cd = current_delimiter (dstack);
+      character = shell_getc (cd != '\'' && pass_next_character == 0);
+    }  /* end for (;;) */
 
-    /* Handle special case.  IN is recognized if the last token
-       was WORD and the token before that was FOR or CASE. */
-    if ((last_read_token == WORD) &&
-#if defined (SELECT_COMMAND)
-       ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
-#else
-       ((token_before_that == FOR) || (token_before_that == CASE)) &&
-#endif
-       (token[0] == 'i' && token[1] == 'n' && !token[2]))
-      {
-       if (token_before_that == CASE)
-         {
-           in_case_pattern_list = 1;
-           allow_esac_as_next++;
-         }
-       return (IN);
-      }
+got_token:
 
-    /* Ditto for DO in the FOR case. */
-#if defined (SELECT_COMMAND)
-    if ((last_read_token == WORD) && ((token_before_that == FOR) || (token_before_that == SELECT)) &&
-#else
-    if ((last_read_token == WORD) && (token_before_that == FOR) &&
-#endif
-       (token[0] == 'd' && token[1] == 'o' && !token[2]))
-      return (DO);
-
-    /* Ditto for ESAC in the CASE case. 
-       Specifically, this handles "case word in esac", which is a legal
-       construct, certainly because someone will pass an empty arg to the
-       case construct, and we don't want it to barf.  Of course, we should
-       insist that the case construct has at least one pattern in it, but
-       the designers disagree. */
-    if (allow_esac_as_next)
-      {
-       allow_esac_as_next--;
-       if (STREQ (token, "esac"))
-         {
-           in_case_pattern_list = 0;
-           return (ESAC);
-         }
-      }
+  token[token_index] = '\0';
 
-    /* Ditto for `{' in the FUNCTION case. */
-    if (allow_open_brace)
+  /* 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 (all_digits && (character == '<' || character == '>' ||
+                   last_read_token == LESS_AND ||
+                   last_read_token == GREATER_AND))
       {
-       allow_open_brace = 0;
-       if (token[0] == '{' && !token[1])
-         {
-           open_brace_awaiting_satisfaction++;
-           return ('{');
-         }
+       yylval.number = atoi (token);
+       return (NUMBER);
       }
 
-    if (posixly_correct)
-      CHECK_FOR_RESERVED_WORD (token);
+  /* Check for special case tokens. */
+  result = special_case_tokens (token);
+  if (result >= 0)
+    return result;
 
 #if defined (ALIAS)
-    /* OK, we have a token.  Let's try to alias expand it, if (and only if)
-       it's eligible. 
+  /* Posix.2 does not allow reserved words to be aliased, so check for all
+     of them, including special cases, before expanding the current token
+     as an alias. */
+  if (posixly_correct)
+    CHECK_FOR_RESERVED_WORD (token);
+
+  /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting
+     inhibits alias expansion. */
+  if (expand_aliases && quoted == 0)
+    {
+      result = alias_expand_token (token);
+      if (result == RE_READ_TOKEN)
+       return (RE_READ_TOKEN);
+      else if (result == NO_EXPANSION)
+       parser_state &= ~PST_ALEXPNEXT;
+    }
 
-       It is eligible for expansion if the shell is in interactive mode, and
-       the token is unquoted and the last token read was a command
-       separator (or expand_next_token is set), and we are currently
-       processing an alias (pushed_string_list is non-empty) and this
-       token is not the same as the current or any previously
-       processed alias.
+  /* If not in Posix.2 mode, check for reserved words after alias
+     expansion. */
+  if (posixly_correct == 0)
+#endif
+    CHECK_FOR_RESERVED_WORD (token);
+
+  the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
+  the_word->word = xmalloc (1 + token_index);
+  the_word->flags = 0;
+  strcpy (the_word->word, token);
+  if (dollar_present)
+    the_word->flags |= W_HASDOLLAR;
+  if (quoted)
+    the_word->flags |= W_QUOTED;
+  /* 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))
+    {
+      the_word->flags |= W_ASSIGNMENT;
+      /* Don't perform word splitting on assignment statements. */
+      if (assignment_acceptable (last_read_token))
+       the_word->flags |= W_NOSPLIT;
+    }
 
-       Special cases that disqualify:
-        In a pattern list in a case statement (in_case_pattern_list). */
-    if (interactive_shell && !quoted && !in_case_pattern_list &&
-       (expand_next_token || command_token_position (last_read_token)))
-      {
-       char *alias_expand_word (), *expanded;
+  yylval.word = the_word;
 
-       if (expanded_token_stack && token_has_been_expanded (token))
-         goto no_expansion;
+  result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
+               ? ASSIGNMENT_WORD : WORD;
 
-       expanded = alias_expand_word (token);
-       if (expanded)
-         {
-           int len = strlen (expanded), expand_next;
+  if (last_read_token == FUNCTION)
+    {
+      parser_state |= PST_ALLOWOPNBRC;
+      function_dstart = line_number;
+    }
 
-           /* Erase the current token. */
-           token_index = 0;
+  return (result);
+}
 
-           expand_next = (expanded[len - 1] == ' ') ||
-                         (expanded[len - 1] == '\t');
+/* $'...' ANSI-C expand the portion of STRING between START and END and
+   return the result.  The result cannot be longer than the input string. */
+static char *
+ansiexpand (string, start, end, lenp)
+     char *string;
+     int start, end, *lenp;
+{
+  char *temp, *t;
+  int len, tlen;
 
-           push_string (expanded, expand_next, token);
-           goto re_read_token;
-         }
-       else
-         /* This is an eligible token that does not have an expansion. */
-no_expansion:
-         expand_next_token = 0;
-      }
-    else
-      {
-       expand_next_token = 0;
-      }
-#endif /* ALIAS */
+  temp = xmalloc (end - start + 1);
+  for (tlen = 0, len = start; len < end; )
+    temp[tlen++] = string[len++];
+  temp[tlen] = '\0';
 
-    if (!posixly_correct)
-      CHECK_FOR_RESERVED_WORD (token);
+  if (*temp)
+    {
+      t = ansicstr (temp, tlen, (int *)NULL);
+      free (temp);
+      if (lenp)
+       *lenp = strlen (t);
+      return (t);
+    }
+  else
+    {
+      if (lenp)
+       *lenp = 0;
+      return (temp);
+    }
+}
 
-    /* What if we are attempting to satisfy an open-brace grouper? */
-    if (open_brace_awaiting_satisfaction && token[0] == '}' && !token[1])
-      {
-       open_brace_awaiting_satisfaction--;
-       return ('}');
-      }
+/* $"..." -- Translate the portion of STRING between START and END
+   according to current locale using gettext (if available) and return
+   the result.  The caller will take care of leaving the quotes intact.
+   The string will be left without the leading `$' by the caller.
+   If translation is performed, the translated string will be double-quoted
+   by the caller.  The length of the translated string is returned in LENP,
+   if non-null. */
+static char *
+localeexpand (string, start, end, lenp)
+     char *string;
+     int start, end, *lenp;
+{
+  int len, tlen;
+  char *temp, *t;
 
-    the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
-    the_word->word = xmalloc (1 + token_index);
-    strcpy (the_word->word, token);
-    the_word->dollar_present = dollar_present;
-    the_word->quoted = quoted;
-    the_word->assignment = assignment (token);
-
-    yylval.word = the_word;
-    result = WORD;
-
-    /* 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_acceptable (last_read_token) && the_word->assignment)
-      result = ASSIGNMENT_WORD;
-
-    if (last_read_token == FUNCTION)
-      allow_open_brace = 1;
-  }
-  return (result);
+  temp = xmalloc (end - start + 1);
+  for (tlen = 0, len = start; len < end; )
+    temp[tlen++] = string[len++];
+  temp[tlen] = '\0';
+
+  /* If we're just dumping translatable strings, don't do anything. */
+  if (dump_translatable_strings)
+    {
+      printf ("\"%s\"\n", temp);
+      if (lenp)
+       *lenp = tlen;
+      return (temp);
+    }
+  else if (*temp)
+    {
+      t = localetrans (temp, tlen, &len);
+      free (temp);
+      if (lenp)
+       *lenp = len;
+      return (t);
+    }
+  else
+    {
+      if (lenp)
+       *lenp = 0;
+      return (temp);
+    }
 }
 
 /* Return 1 if TOKEN is a token that after being read would allow
@@ -2428,15 +2700,12 @@ static int
 reserved_word_acceptable (token)
      int token;
 {
-#if 0
-  if (member (token, "\n;()|&{") ||
-#else
   if (token == '\n' || token == ';' || token == '(' || token == ')' ||
       token == '|' || token == '&' || token == '{' ||
-#endif
       token == '}' ||                  /* XXX */
       token == AND_AND ||
       token == BANG ||
+      token == TIME || token == TIMEOPT ||
       token == DO ||
       token == ELIF ||
       token == ELSE ||
@@ -2462,12 +2731,13 @@ find_reserved_word (token)
      char *token;
 {
   int i;
-  for (i = 0; word_token_alist[i].word != (char *)NULL; i++)
+  for (i = 0; word_token_alist[i].word; i++)
     if (STREQ (token, word_token_alist[i].word))
       return i;
   return -1;
 }
 
+#if 0
 #if defined (READLINE)
 /* Called after each time readline is called.  This insures that whatever
    the new prompt string is gets propagated to readline's local prompt
@@ -2475,11 +2745,11 @@ find_reserved_word (token)
 static void
 reset_readline_prompt ()
 {
+  char *temp_prompt;
+
   if (prompt_string_pointer)
     {
-      char *temp_prompt;
-
-      temp_prompt = *prompt_string_pointer
+      temp_prompt = (*prompt_string_pointer)
                        ? decode_prompt_string (*prompt_string_pointer)
                        : (char *)NULL;
 
@@ -2490,11 +2760,11 @@ reset_readline_prompt ()
        }
 
       FREE (current_readline_prompt);
-
       current_readline_prompt = temp_prompt;
     }
 }
 #endif /* READLINE */
+#endif /* 0 */
 
 #if defined (HISTORY)
 /* A list of tokens which can be followed by newlines, but not by
@@ -2502,29 +2772,48 @@ reset_readline_prompt ()
    newline separator for such tokens is replaced with a space. */
 static int no_semi_successors[] = {
   '\n', '{', '(', ')', ';', '&', '|',
-  CASE, DO, ELSE, IF, IN, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR,
+  CASE, DO, ELSE, IF, SEMI_SEMI, 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. */
+   newlines.  Returns the string that should be added into the
+   history entry. */
 char *
 history_delimiting_chars ()
 {
-  if (!delimiter_depth)
+  register int i;
+
+  if (dstack.delimiter_depth != 0)
+    return ("\n");
+    
+  /* First, handle some special cases. */
+  /*(*/
+  /* If we just read `()', assume it's a function definition, and don't
+     add a semicolon.  If the token before the `)' was not `(', and we're
+     not in the midst of parsing a case statement, assume it's a
+     parenthesized command and add the semicolon. */
+  /*)(*/
+  if (token_before_that == ')')
     {
-      register int i;
+      if (two_tokens_ago == '(')       /*)*/   /* function def */
+       return " ";
+      /* This does not work for subshells inside case statement
+        command lists.  It's a suboptimal solution. */
+      else if (parser_state & PST_CASESTMT)    /* case statement pattern */
+        return " ";
+      else     
+        return "; ";                           /* (...) subshell */
+    }
 
-      for (i = 0; no_semi_successors[i]; i++)
-       {
-         if (token_before_that == no_semi_successors[i])
-           return (" ");
-       }
-      return ("; ");
+  for (i = 0; no_semi_successors[i]; i++)
+    {
+      if (token_before_that == no_semi_successors[i])
+       return (" ");
     }
-  else
-    return ("\n");
+
+  return ("; ");
 }
 #endif /* HISTORY */
 
@@ -2544,7 +2833,7 @@ prompt_again ()
   if (!prompt_string_pointer)
     prompt_string_pointer = &ps1_prompt;
 
-  temp_prompt = (*prompt_string_pointer)
+  temp_prompt = *prompt_string_pointer
                        ? decode_prompt_string (*prompt_string_pointer)
                        : (char *)NULL;
 
@@ -2580,37 +2869,47 @@ print_prompt ()
 
 /* Return a string which will be printed as a prompt.  The string
    may contain special characters which are decoded as follows:
-   
-       \t      the time
-       \d      the date
+
+       \a      bell (ascii 07)
+       \e      escape (ascii 033)
+       \d      the date in Day Mon Date format
+       \h      the hostname up to the first `.'
+       \H      the hostname
        \n      CRLF
        \s      the name of the shell
+       \t      the time in 24-hour hh:mm:ss format
+       \T      the time in 12-hour hh:mm:ss format
+       \@      the time in 12-hour am/pm format
+       \v      the version of bash (e.g., 2.00)
+       \V      the release of bash, version + patchlevel (e.g., 2.00.0)
        \w      the current working directory
-       \W      the last element of PWD
+       \W      the last element of $PWD
        \u      your username
-       \h      the hostname
        \#      the command number of this command
        \!      the history number of this command
        \$      a $ or a # if you are root
-       \<octal> character code in octal
+       \nnn    character code nnn in octal
        \\      a backslash
+       \[      begin a sequence of non-printing chars
+       \]      end a sequence of non-printing chars
 */
 #define PROMPT_GROWTH 50
 char *
 decode_prompt_string (string)
      char *string;
 {
-  int result_size = PROMPT_GROWTH;
-  int result_index = 0;
-  char *result;
-  int c;
-  char *temp = (char *)NULL;
   WORD_LIST *list;
-
+  char *result, *t;
+  struct dstack save_dstack;
 #if defined (PROMPT_STRING_DECODE)
+  int result_size, result_index;
+  int c, n;
+  char *temp, octal_string[4];
+  time_t the_time;
 
-  result = xmalloc (PROMPT_GROWTH);
-  result[0] = 0;
+  result = xmalloc (result_size = PROMPT_GROWTH);
+  result[result_index = 0] = 0;
+  temp = (char *)NULL;
 
   while (c = *string++)
     {
@@ -2631,7 +2930,7 @@ decode_prompt_string (string)
                string--;       /* add_string increments string again. */
                goto add_string;
            }
-       } 
+       }
       if (c == '\\')
        {
          c = *string;
@@ -2646,171 +2945,199 @@ decode_prompt_string (string)
            case '5':
            case '6':
            case '7':
-             {
-               char octal_string[4];
-               int n;
+             strncpy (octal_string, string, 3);
+             octal_string[3] = '\0';
 
-               strncpy (octal_string, string, 3);
-               octal_string[3] = '\0';
+             n = read_octal (octal_string);
+             temp = xmalloc (3);
 
-               n = read_octal (octal_string);
-               temp = xmalloc (3);
+             if (n == CTLESC || n == CTLNUL)
+               {
+                 string += 3;
+                 temp[0] = CTLESC;
+                 temp[1] = n;
+                 temp[2] = '\0';
+               }
+             else if (n == -1)
+               {
+                 temp[0] = '\\';
+                 temp[1] = '\0';
+               }
+             else
+               {
+                 string += 3;
+                 temp[0] = n;
+                 temp[1] = '\0';
+               }
 
-               if (n == CTLESC || n == CTLNUL)
-                 {
-                   string += 3;
-                   temp[0] = CTLESC;
-                   temp[1] = n;
-                   temp[2] = '\0';
-                 }
-               else if (n == -1)
-                 {
-                   temp[0] = '\\';
-                   temp[1] = '\0';
-                 }
-               else
-                 {
-                   string += 3;
-                   temp[0] = n;
-                   temp[1] = '\0';
-                 }
+             c = 0;
+             goto add_string;
 
-               c = 0;
-               goto add_string;
-             }
-         
            case 't':
            case 'd':
+           case 'T':
+           case '@':
              /* Make the current time/date into a string. */
-             {
-               time_t the_time = time (0);
-               char *ttemp = ctime (&the_time);
-               temp = savestring (ttemp);
+             the_time = time (0);
+             temp = ctime (&the_time);
 
-               if (c == 't')
-                 {
-                   strcpy (temp, temp + 11);
-                   temp[8] = '\0';
-                 }
-               else
-                 temp[10] = '\0';
+             temp = (c != 'd') ? savestring (temp + 11) : savestring (temp);
+             temp[(c != 'd') ? 8 : 10] = '\0';
 
-               goto add_string;
-             }
+             /* quick and dirty conversion to 12-hour time */
+             if (c == 'T' || c == '@')
+               {
+                 if (c == '@')
+                   {
+                     temp[5] = 'a';    /* am/pm format */
+                     temp[6] = 'm';
+                     temp[7] = '\0';
+                   }
+                 c = temp[2];
+                 temp[2] = '\0';
+                 n = atoi (temp);
+                 temp[2] = c;
+                 n -= 12;
+                 if (n > 0)
+                   {
+                     temp[0] = (n / 10) + '0';
+                     temp[1] = (n % 10) + '0';
+                   }
+                 if (n >= 0 && temp[5] == 'a')
+                   temp[5] = 'p';
+               }
+             goto add_string;
 
            case 'n':
-             if (!no_line_editing)
-               temp = savestring ("\r\n");
-             else
-               temp = savestring ("\n");
+             temp = xmalloc (3);
+             temp[0] = no_line_editing ? '\n' : '\r';
+             temp[1] = no_line_editing ? '\0' : '\n';
+             temp[2] = '\0';
              goto add_string;
 
            case 's':
-             {
-               temp = base_pathname (shell_name);
-               temp = savestring (temp);
-               goto add_string;
-             }
-       
+             temp = base_pathname (shell_name);
+             temp = savestring (temp);
+             goto add_string;
+
+           case 'v':
+           case 'V':
+             temp = xmalloc (8);
+             if (c == 'v')
+               strcpy (temp, dist_version);
+             else
+               sprintf (temp, "%s.%d", dist_version, patch_level);
+             goto add_string;
+
            case 'w':
            case 'W':
              {
-               /* Use the value of PWD because it is much more effecient. */
-#define EFFICIENT
-#ifdef EFFICIENT
-               char *polite_directory_format (), t_string[MAXPATHLEN];
+               /* Use the value of PWD because it is much more efficient. */
+               char t_string[PATH_MAX];
 
                temp = get_string_value ("PWD");
 
-               if (!temp)
-                 getwd (t_string);
+               if (temp == 0)
+                 {
+                   if (getcwd (t_string, sizeof(t_string)) == 0)
+                     {
+                       t_string[0] = '.';
+                       t_string[1] = '\0';
+                     }
+                 }
                else
                  strcpy (t_string, temp);
-#else
-               getwd (t_string);
-#endif /* EFFICIENT */
 
                if (c == 'W')
                  {
-                   char *dir = (char *)strrchr (t_string, '/');
-                   if (dir && dir != t_string)
-                     strcpy (t_string, dir + 1);
-                   temp = savestring (t_string);
+                   t = strrchr (t_string, '/');
+                   if (t && t != t_string)
+                     strcpy (t_string, t + 1);
                  }
                else
-                 temp = savestring (polite_directory_format (t_string));
+                 strcpy (t_string, polite_directory_format (t_string));
+
+               /* If we're going to be expanding the prompt string later,
+                  quote the directory name. */
+               if (promptvars || posixly_correct)
+                 temp = backslash_quote (t_string);
+               else
+                 temp = savestring (t_string);
+
                goto add_string;
              }
-      
+
            case 'u':
-             {
-               temp = savestring (current_user.user_name);
-               goto add_string;
-             }
+             temp = savestring (current_user.user_name);
+             goto add_string;
 
            case 'h':
-             {
-               char *t_string;
-
-               temp = savestring (current_host_name);
-               if (t_string = (char *)strchr (temp, '.'))
-                 *t_string = '\0';
-               goto add_string;
-             }
+           case 'H':
+             temp = savestring (current_host_name);
+             if (c == 'h' && (t = (char *)strchr (temp, '.')))
+               *t = '\0';
+             goto add_string;
 
            case '#':
-             {
-               temp = itos (current_command_number);
-               goto add_string;
-             }
+             temp = itos (current_command_number);
+             goto add_string;
 
            case '!':
-             {
 #if !defined (HISTORY)
-               temp = savestring ("1");
+             temp = savestring ("1");
 #else /* HISTORY */
-               temp = itos (history_number ());
+             temp = itos (history_number ());
 #endif /* HISTORY */
-               goto add_string;
-             }
+             goto add_string;
 
            case '$':
-             temp = savestring (geteuid () == 0 ? "#" : "$");
+             temp = xmalloc (2);
+             temp[0] = current_user.euid == 0 ? '#' : '$';
+             temp[1] = '\0';
              goto add_string;
 
 #if defined (READLINE)
            case '[':
            case ']':
-             temp = xmalloc(3);
+             temp = xmalloc (3);
              temp[0] = '\001';
              temp[1] = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
              temp[2] = '\0';
              goto add_string;
-#endif
+#endif /* READLINE */
 
            case '\\':
-             temp = savestring ("\\");
+             temp = xmalloc (2);
+             temp[0] = c;
+             temp[1] = '\0';
+             goto add_string;
+
+           case 'a':
+           case 'e':
+             temp = xmalloc (2);
+             temp[0] = (c == 'a') ? '\07' : '\033';
+             temp[1] = '\0';
              goto add_string;
 
            default:
-             temp = savestring ("\\ ");
+             temp = xmalloc (3);
+             temp[0] = '\\';
              temp[1] = c;
+             temp[2] = '\0';
 
            add_string:
              if (c)
                string++;
              result =
                sub_append_string (temp, result, &result_index, &result_size);
-             temp = (char *)NULL; /* Free ()'ed in sub_append_string (). */
+             temp = (char *)NULL; /* Freed in sub_append_string (). */
              result[result_index] = '\0';
              break;
            }
        }
       else
        {
-         while (3 + result_index > result_size)
-           result = xrealloc (result, result_size += PROMPT_GROWTH);
-
+         RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH);
          result[result_index++] = c;
          result[result_index] = '\0';
        }
@@ -2819,22 +3146,42 @@ decode_prompt_string (string)
   result = savestring (string);
 #endif /* !PROMPT_STRING_DECODE */
 
+  /* Save the delimiter stack and point `dstack' to temp space so any
+     command substitutions in the prompt string won't result in screwing
+     up the parser's quoting state. */
+  save_dstack = dstack;
+  dstack = temp_dstack;
+  dstack.delimiter_depth = 0;
+
   /* Perform variable and parameter expansion and command substitution on
      the prompt string. */
-  list = expand_string_unsplit (result, 1);
-  free (result);
-  result = string_list (list);
-  dispose_words (list);
+  if (promptvars || posixly_correct)
+    {
+      list = expand_string_unsplit (result, Q_DOUBLE_QUOTES);
+      free (result);
+      result = string_list (list);
+      dispose_words (list);
+    }
+  else
+    {
+      t = dequote_string (result);
+      free (result);
+      result = t;
+    }
+
+  dstack = save_dstack;
 
   return (result);
 }
 
 /* Report a syntax error, and restart the parser.  Call here for fatal
    errors. */
+int
 yyerror ()
 {
   report_syntax_error ((char *)NULL);
   reset_parser ();
+  return (0);
 }
 
 /* Report a syntax error with line numbers, etc.
@@ -2845,96 +3192,85 @@ static void
 report_syntax_error (message)
      char *message;
 {
+  char *msg, *t;
+  int token_end, i;
+  char msg2[2];
+
   if (message)
     {
-      if (!interactive)
-       {
-         char *name = bash_input.name ? bash_input.name : "stdin";
-         report_error ("%s: line %d: `%s'", name, line_number, message);
-       }
-      else
-       {
-         if (EOF_Reached)
-           EOF_Reached = 0;
-         report_error ("%s", message);
-       }
-
+      parser_error (line_number, "%s", message);
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
       last_command_exit_value = EX_USAGE;
       return;
     }
 
+  /* If the line of input we're reading is not null, try to find the
+     objectionable token. */
   if (shell_input_line && *shell_input_line)
     {
-      char *t = shell_input_line;
-      register int i = shell_input_line_index;
-      int token_end = 0;
+      t = shell_input_line;
+      i = shell_input_line_index;
+      token_end = 0;
 
-      if (!t[i] && i)
+      if (i && t[i] == '\0')
        i--;
 
-      while (i && (t[i] == ' ' || t[i] == '\t' || t[i] == '\n'))
+      while (i && (whitespace (t[i]) || t[i] == '\n'))
        i--;
 
       if (i)
        token_end = i + 1;
 
-      while (i && !member (t[i], " \n\t;|&"))
+      while (i && (member (t[i], " \n\t;|&") == 0))
        i--;
 
-      while (i != token_end && member (t[i], " \t\n"))
+      while (i != token_end && (whitespace (t[i]) || t[i] == '\n'))
        i++;
 
-      if (token_end)
+      /* Print the offending token. */
+      if (token_end || (i == 0 && token_end == 0))
        {
-         char *error_token;
-         error_token = xmalloc (1 + (token_end - i));
-         strncpy (error_token, t + i, token_end - i);
-         error_token[token_end - i] = '\0';
+         if (token_end)
+           {
+             msg = xmalloc (1 + (token_end - i));
+             strncpy (msg, t + i, token_end - i);
+             msg[token_end - i] = '\0';
+           }
+         else  /* one-character token */
+           {
+             msg2[0] = t[i];
+             msg2[1] = '\0';
+             msg = msg2;
+           }
 
-         report_error ("syntax error near unexpected token `%s'", error_token);
-         free (error_token);
-       }
-      else if ((i == 0) && (token_end == 0))   /* a 1-character token */
-       {
-         char etoken[2];
-         etoken[0] = t[i];
-         etoken[1] = '\0';
+         parser_error (line_number, "syntax error near unexpected token `%s'", msg);
 
-         report_error ("syntax error near unexpected token `%s'", etoken);
+         if (msg != msg2)
+           free (msg);
        }
 
-      if (!interactive)
+      /* If not interactive, print the line containing the error. */
+      if (interactive == 0)
        {
-         char *temp = savestring (shell_input_line);
-         char *name = bash_input.name ? bash_input.name : "stdin";
-         int l = strlen (temp);
-
-         while (l && temp[l - 1] == '\n')
-           temp[--l] = '\0';
+         msg = savestring (shell_input_line);
+         token_end = strlen (msg);
+         while (token_end && msg[token_end - 1] == '\n')
+           msg[--token_end] = '\0';
 
-         report_error ("%s: line %d: `%s'", name, line_number, temp);
-         free (temp);
+         parser_error (line_number, "`%s'", msg);
+         free (msg);
        }
     }
   else
     {
-      char *name, *msg;
-      if (!interactive)
-       name = bash_input.name ? bash_input.name : "stdin";
-      if (EOF_Reached)
-       msg = "syntax error: unexpected end of file";
-      else
-       msg = "syntax error";
-      if (!interactive)
-       report_error ("%s: line %d: %s", name, line_number, msg);
-      else
-       {
-         /* This file uses EOF_Reached only for error reporting
-            when the shell is interactive.  Other mechanisms are 
-            used to decide whether or not to exit. */
-         EOF_Reached = 0;
-         report_error (msg);
-       }
+      msg = EOF_Reached ? "syntax error: unexpected end of file" : "syntax error";
+      parser_error (line_number, "%s", msg);
+      /* When the shell is interactive, this file uses EOF_Reached
+        only for error reporting.  Other mechanisms are used to
+        decide whether or not to exit. */
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
     }
   last_command_exit_value = EX_USAGE;
 }
@@ -2944,11 +3280,12 @@ report_syntax_error (message)
    allocated objects to the memory pool.  In the case of no error, we want
    to throw away the information about where the allocated objects live.
    (dispose_command () will actually free the command. */
+static void
 discard_parser_constructs (error_p)
      int error_p;
 {
 }
-   
+
 /* Do that silly `type "bye" to exit' stuff.  You know, "ignoreeof". */
 
 /* A flag denoting whether or not ignoreeof is set. */
@@ -2990,7 +3327,7 @@ handle_eof_input_unit ()
              prompt_again ();
              last_read_token = current_token = '\n';
              return;
-           } 
+           }
        }
 
       /* In this case EOF should exit the shell.  Do it now. */