X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=parse.y;h=9cf7be80873964273c9b8a186e86beea0b562170;hb=30a978b7d808c067219c95be88c4979b6a7aa251;hp=eebd54953ace9ab77b46ad85f97d7e479a2a8844;hpb=89a92869e56aba4e4cab2d639c00a86f0545c862;p=thirdparty%2Fbash.git diff --git a/parse.y b/parse.y index eebd54953..9cf7be808 100644 --- a/parse.y +++ b/parse.y @@ -1,6 +1,6 @@ /* parse.y - Yacc grammar for bash. */ -/* Copyright (C) 1989-2009 Free Software Foundation, Inc. +/* Copyright (C) 1989-2012 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -45,6 +45,7 @@ #define NEED_STRFTIME_DECL /* used in externs.h */ #include "shell.h" +#include "typemax.h" /* SIZE_MAX if needed */ #include "trap.h" #include "flags.h" #include "parser.h" @@ -118,6 +119,7 @@ extern int current_command_number; extern int sourcelevel, parse_and_execute_level; extern int posixly_correct; extern int last_command_exit_value; +extern pid_t last_command_subst_pid; extern char *shell_name, *current_host_name; extern char *dist_version; extern int patch_level; @@ -166,6 +168,9 @@ static char *read_a_line __P((int)); static int reserved_word_acceptable __P((int)); static int yylex __P((void)); + +static void push_heredoc __P((REDIRECT *)); +static char *mk_alexpansion __P((char *)); static int alias_expand_token __P((char *)); static int time_command_acceptable __P((void)); static int special_case_tokens __P((char *)); @@ -246,30 +251,34 @@ int promptvars = 1; quotes. */ int extended_quote = 1; -/* The decoded prompt string. Used if READLINE is not defined or if - editing is turned off. Analogous to current_readline_prompt. */ -static char *current_decoded_prompt; - /* The number of lines read from input while creating the current command. */ int current_command_line_count; +/* The number of lines in a command saved while we run parse_and_execute */ +int saved_command_line_count; + /* The token that currently denotes the end of parse. */ int shell_eof_token; /* The token currently being read. */ int current_token; +/* The current parser state. */ +int parser_state; + /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ -static REDIRECT *redir_stack[10]; +#define HEREDOC_MAX 16 + +static REDIRECT *redir_stack[HEREDOC_MAX]; int need_here_doc; /* Where shell input comes from. History expansion is performed on each line when the shell is interactive. */ static char *shell_input_line = (char *)NULL; -static int shell_input_line_index; -static int shell_input_line_size; /* Amount allocated for shell_input_line. */ -static int shell_input_line_len; /* strlen (shell_input_line) */ +static size_t shell_input_line_index; +static size_t shell_input_line_size; /* Amount allocated for shell_input_line. */ +static size_t shell_input_line_len; /* strlen (shell_input_line) */ /* Either zero or EOF. */ static int shell_input_line_terminator; @@ -283,8 +292,9 @@ static int function_bstart; /* The line number in a script at which an arithmetic for command starts. */ static int arith_for_lineno; -/* The current parser state. */ -static int parser_state; +/* The decoded prompt string. Used if READLINE is not defined or if + editing is turned off. Analogous to current_readline_prompt. */ +static char *current_decoded_prompt; /* The last read token, or NULL. read_token () uses this for context checking. */ @@ -296,11 +306,13 @@ static int token_before_that; /* The token read prior to token_before_that. */ static int two_tokens_ago; +static int global_extglob; + /* The line number in a script where the word in a `case WORD', `select WORD' or `for WORD' begins. This is a nested command maximum, since the array index is decremented after a case, select, or for command is parsed. */ #define MAX_CASE_NEST 128 -static int word_lineno[MAX_CASE_NEST]; +static int word_lineno[MAX_CASE_NEST+1]; static int word_top = -1; /* If non-zero, it is the token that we want read_token to return @@ -310,6 +322,7 @@ static int word_top = -1; static int token_to_read; static WORD_DESC *word_desc_to_read; +static REDIRECTEE source; static REDIRECTEE redir; %} @@ -329,10 +342,10 @@ static REDIRECTEE redir; third group are recognized only under special circumstances. */ %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC %token COND_START COND_END COND_ERROR -%token IN BANG TIME TIMEOPT +%token IN BANG TIME TIMEOPT TIMEIGN /* More general tokens. yylex () knows how to make these. */ -%token WORD ASSIGNMENT_WORD +%token WORD ASSIGNMENT_WORD REDIR_WORD %token NUMBER %token ARITH_CMD ARITH_FOR_EXPRS %token COND_CMD @@ -419,159 +432,273 @@ word_list: WORD redirection: '>' WORD { + source.dest = 1; redir.filename = $2; - $$ = make_redirection (1, r_output_direction, redir); + $$ = make_redirection (source, r_output_direction, redir, 0); } | '<' WORD { + source.dest = 0; redir.filename = $2; - $$ = make_redirection (0, r_input_direction, redir); + $$ = make_redirection (source, r_input_direction, redir, 0); } | NUMBER '>' WORD { + source.dest = $1; redir.filename = $3; - $$ = make_redirection ($1, r_output_direction, redir); + $$ = make_redirection (source, r_output_direction, redir, 0); } | NUMBER '<' WORD { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_input_direction, redir, 0); + } + | REDIR_WORD '>' WORD + { + source.filename = $1; redir.filename = $3; - $$ = make_redirection ($1, r_input_direction, redir); + $$ = make_redirection (source, r_output_direction, redir, REDIR_VARASSIGN); + } + | REDIR_WORD '<' WORD + { + source.filename = $1; + redir.filename = $3; + $$ = make_redirection (source, r_input_direction, redir, REDIR_VARASSIGN); } | GREATER_GREATER WORD { + source.dest = 1; redir.filename = $2; - $$ = make_redirection (1, r_appending_to, redir); + $$ = make_redirection (source, r_appending_to, redir, 0); } | NUMBER GREATER_GREATER WORD { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_appending_to, redir, 0); + } + | REDIR_WORD GREATER_GREATER WORD + { + source.filename = $1; + redir.filename = $3; + $$ = make_redirection (source, r_appending_to, redir, REDIR_VARASSIGN); + } + | GREATER_BAR WORD + { + source.dest = 1; + redir.filename = $2; + $$ = make_redirection (source, r_output_force, redir, 0); + } + | NUMBER GREATER_BAR WORD + { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_output_force, redir, 0); + } + | REDIR_WORD GREATER_BAR WORD + { + source.filename = $1; + redir.filename = $3; + $$ = make_redirection (source, r_output_force, redir, REDIR_VARASSIGN); + } + | LESS_GREATER WORD + { + source.dest = 0; + redir.filename = $2; + $$ = make_redirection (source, r_input_output, redir, 0); + } + | NUMBER LESS_GREATER WORD + { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_input_output, redir, 0); + } + | REDIR_WORD LESS_GREATER WORD + { + source.filename = $1; redir.filename = $3; - $$ = make_redirection ($1, r_appending_to, redir); + $$ = make_redirection (source, r_input_output, redir, REDIR_VARASSIGN); } | LESS_LESS WORD { + source.dest = 0; redir.filename = $2; - $$ = make_redirection (0, r_reading_until, redir); - redir_stack[need_here_doc++] = $$; + $$ = make_redirection (source, r_reading_until, redir, 0); + push_heredoc ($$); } | NUMBER LESS_LESS WORD { + source.dest = $1; redir.filename = $3; - $$ = make_redirection ($1, r_reading_until, redir); - redir_stack[need_here_doc++] = $$; + $$ = make_redirection (source, r_reading_until, redir, 0); + push_heredoc ($$); + } + | REDIR_WORD LESS_LESS WORD + { + source.filename = $1; + redir.filename = $3; + $$ = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN); + push_heredoc ($$); + } + | LESS_LESS_MINUS WORD + { + source.dest = 0; + redir.filename = $2; + $$ = make_redirection (source, r_deblank_reading_until, redir, 0); + push_heredoc ($$); + } + | NUMBER LESS_LESS_MINUS WORD + { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_deblank_reading_until, redir, 0); + push_heredoc ($$); + } + | REDIR_WORD LESS_LESS_MINUS WORD + { + source.filename = $1; + redir.filename = $3; + $$ = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN); + push_heredoc ($$); } | LESS_LESS_LESS WORD { + source.dest = 0; redir.filename = $2; - $$ = make_redirection (0, r_reading_string, redir); + $$ = make_redirection (source, r_reading_string, redir, 0); } | NUMBER LESS_LESS_LESS WORD { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_reading_string, redir, 0); + } + | REDIR_WORD LESS_LESS_LESS WORD + { + source.filename = $1; redir.filename = $3; - $$ = make_redirection ($1, r_reading_string, redir); + $$ = make_redirection (source, r_reading_string, redir, REDIR_VARASSIGN); } | LESS_AND NUMBER { + source.dest = 0; redir.dest = $2; - $$ = make_redirection (0, r_duplicating_input, redir); + $$ = make_redirection (source, r_duplicating_input, redir, 0); } | NUMBER LESS_AND NUMBER { + source.dest = $1; redir.dest = $3; - $$ = make_redirection ($1, r_duplicating_input, redir); + $$ = make_redirection (source, r_duplicating_input, redir, 0); + } + | REDIR_WORD LESS_AND NUMBER + { + source.filename = $1; + redir.dest = $3; + $$ = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN); } | GREATER_AND NUMBER { + source.dest = 1; redir.dest = $2; - $$ = make_redirection (1, r_duplicating_output, redir); + $$ = make_redirection (source, r_duplicating_output, redir, 0); } | NUMBER GREATER_AND NUMBER { + source.dest = $1; + redir.dest = $3; + $$ = make_redirection (source, r_duplicating_output, redir, 0); + } + | REDIR_WORD GREATER_AND NUMBER + { + source.filename = $1; redir.dest = $3; - $$ = make_redirection ($1, r_duplicating_output, redir); + $$ = make_redirection (source, r_duplicating_output, redir, REDIR_VARASSIGN); } | LESS_AND WORD { + source.dest = 0; redir.filename = $2; - $$ = make_redirection (0, r_duplicating_input_word, redir); + $$ = make_redirection (source, r_duplicating_input_word, redir, 0); } | NUMBER LESS_AND WORD { + source.dest = $1; + redir.filename = $3; + $$ = make_redirection (source, r_duplicating_input_word, redir, 0); + } + | REDIR_WORD LESS_AND WORD + { + source.filename = $1; redir.filename = $3; - $$ = make_redirection ($1, r_duplicating_input_word, redir); + $$ = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN); } | GREATER_AND WORD { + source.dest = 1; redir.filename = $2; - $$ = make_redirection (1, r_duplicating_output_word, redir); + $$ = make_redirection (source, r_duplicating_output_word, redir, 0); } | NUMBER GREATER_AND WORD { + source.dest = $1; redir.filename = $3; - $$ = make_redirection ($1, r_duplicating_output_word, redir); + $$ = make_redirection (source, r_duplicating_output_word, redir, 0); } - | LESS_LESS_MINUS WORD - { - redir.filename = $2; - $$ = make_redirection - (0, r_deblank_reading_until, redir); - redir_stack[need_here_doc++] = $$; - } - | NUMBER LESS_LESS_MINUS WORD + | REDIR_WORD GREATER_AND WORD { + source.filename = $1; redir.filename = $3; - $$ = make_redirection - ($1, r_deblank_reading_until, redir); - redir_stack[need_here_doc++] = $$; + $$ = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN); } | GREATER_AND '-' { + source.dest = 1; redir.dest = 0; - $$ = make_redirection (1, r_close_this, redir); + $$ = make_redirection (source, r_close_this, redir, 0); } | NUMBER GREATER_AND '-' { + source.dest = $1; redir.dest = 0; - $$ = make_redirection ($1, r_close_this, redir); + $$ = make_redirection (source, r_close_this, redir, 0); } - | LESS_AND '-' + | REDIR_WORD GREATER_AND '-' { + source.filename = $1; redir.dest = 0; - $$ = make_redirection (0, r_close_this, redir); + $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN); } - | NUMBER LESS_AND '-' + | LESS_AND '-' { + source.dest = 0; redir.dest = 0; - $$ = make_redirection ($1, r_close_this, redir); - } - | AND_GREATER WORD - { - redir.filename = $2; - $$ = make_redirection (1, r_err_and_out, redir); + $$ = make_redirection (source, r_close_this, redir, 0); } - | AND_GREATER_GREATER WORD + | NUMBER LESS_AND '-' { - redir.filename = $2; - $$ = make_redirection (1, r_append_err_and_out, redir); + source.dest = $1; + redir.dest = 0; + $$ = make_redirection (source, r_close_this, redir, 0); } - | NUMBER LESS_GREATER WORD + | REDIR_WORD LESS_AND '-' { - redir.filename = $3; - $$ = make_redirection ($1, r_input_output, redir); + source.filename = $1; + redir.dest = 0; + $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN); } - | LESS_GREATER WORD + | AND_GREATER WORD { + source.dest = 1; redir.filename = $2; - $$ = make_redirection (0, r_input_output, redir); + $$ = make_redirection (source, r_err_and_out, redir, 0); } - | GREATER_BAR WORD + | AND_GREATER_GREATER WORD { + source.dest = 1; redir.filename = $2; - $$ = make_redirection (1, r_output_force, redir); - } - | NUMBER GREATER_BAR WORD - { - redir.filename = $3; - $$ = make_redirection ($1, r_output_force, redir); + $$ = make_redirection (source, r_append_err_and_out, redir, 0); } ; @@ -1068,31 +1195,19 @@ simple_list1: simple_list1 AND_AND newline_list simple_list1 ; pipeline_command: pipeline - { $$ = $1; } - | BANG pipeline + { $$ = $1; } + | BANG pipeline_command { if ($2) - $2->flags |= CMD_INVERT_RETURN; + $2->flags ^= CMD_INVERT_RETURN; /* toggle */ $$ = $2; } - | timespec pipeline + | timespec pipeline_command { if ($2) $2->flags |= $1; $$ = $2; } - | timespec BANG pipeline - { - if ($3) - $3->flags |= $1|CMD_INVERT_RETURN; - $$ = $3; - } - | BANG timespec pipeline - { - if ($3) - $3->flags |= $2|CMD_INVERT_RETURN; - $$ = $3; - } | timespec list_terminator { ELEMENT x; @@ -1110,7 +1225,24 @@ pipeline_command: pipeline if ($2 == '\n') token_to_read = '\n'; } - + | BANG list_terminator + { + ELEMENT x; + + /* This is just as unclean. Posix says that `!' + by itself should be equivalent to `false'. + We cheat and push a + newline back if the list_terminator was a newline + to avoid the double-newline problem (one to + terminate this, one to terminate the command) */ + x.word = 0; + x.redirect = 0; + $$ = make_simple_command (x, (COMMAND *)NULL); + $$->flags |= CMD_INVERT_RETURN; + /* XXX - let's cheat and push a newline back */ + if ($2 == '\n') + token_to_read = '\n'; + } ; pipeline: pipeline '|' newline_list pipeline @@ -1119,12 +1251,13 @@ pipeline: pipeline '|' newline_list pipeline { /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */ COMMAND *tc; - REDIRECTEE rd; + REDIRECTEE rd, sd; REDIRECT *r; tc = $1->type == cm_simple ? (COMMAND *)$1->value.Simple : $1; + sd.dest = 2; rd.dest = 1; - r = make_redirection (2, r_duplicating_output, rd); + r = make_redirection (sd, r_duplicating_output, rd, 0); if (tc->redirects) { register REDIRECT *t; @@ -1145,6 +1278,8 @@ timespec: TIME { $$ = CMD_TIME_PIPELINE; } | TIME TIMEOPT { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } + | TIME TIMEOPT TIMEIGN + { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } ; %% @@ -1303,22 +1438,22 @@ yy_readline_get () give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ - old_sigint = (SigHandler *)NULL; + old_sigint = (SigHandler *)IMPOSSIBLE_TRAP_HANDLER; if (signal_is_ignored (SIGINT) == 0) { + /* interrupt_immediately++; */ old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler); - interrupt_immediately++; } - terminate_immediately = 1; current_readline_line = readline (current_readline_prompt ? current_readline_prompt : ""); - terminate_immediately = 0; - if (signal_is_ignored (SIGINT) == 0 && old_sigint) + CHECK_TERMSIG; + if (signal_is_ignored (SIGINT) == 0) { - interrupt_immediately--; - set_signal_handler (SIGINT, old_sigint); + /* interrupt_immediately--; */ + if (old_sigint != IMPOSSIBLE_TRAP_HANDLER) + set_signal_handler (SIGINT, old_sigint); } #if 0 @@ -1472,17 +1607,19 @@ yy_stream_get () result = EOF; if (bash_input.location.file) { +#if 0 if (interactive) - { - interrupt_immediately++; - terminate_immediately++; - } + interrupt_immediately++; +#endif + + /* XXX - don't need terminate_immediately; getc_with_restart checks + for terminating signals itself if read returns < 0 */ result = getc_with_restart (bash_input.location.file); + +#if 0 if (interactive) - { - interrupt_immediately--; - terminate_immediately--; - } + interrupt_immediately--; +#endif } return (result); } @@ -1517,6 +1654,9 @@ typedef struct stream_saver { /* The globally known line number. */ int line_number = 0; +/* The line number offset set by assigning to LINENO. Not currently used. */ +int line_number_base = 0; + #if defined (COND_COMMAND) static int cond_lineno; static int cond_token; @@ -1657,6 +1797,10 @@ restore_token_state (ts) * implement alias expansion on a per-token basis. */ +#define PSH_ALIAS 0x01 +#define PSH_DPAREN 0x02 +#define PSH_SOURCE 0x04 + typedef struct string_saver { struct string_saver *next; int expand_alias; /* Value to set expand_alias to when string is popped. */ @@ -1664,7 +1808,9 @@ typedef struct string_saver { #if defined (ALIAS) alias_t *expander; /* alias that caused this line to be pushed. */ #endif - int saved_line_size, saved_line_index, saved_line_terminator; + size_t saved_line_size, saved_line_index; + int saved_line_terminator; + int flags; } STRING_SAVER; STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL; @@ -1690,8 +1836,11 @@ push_string (s, expand, ap) temp->saved_line_size = shell_input_line_size; temp->saved_line_index = shell_input_line_index; temp->saved_line_terminator = shell_input_line_terminator; + temp->flags = 0; #if defined (ALIAS) temp->expander = ap; + if (ap) + temp->flags = PSH_ALIAS; #endif temp->next = pushed_string_list; pushed_string_list = temp; @@ -1702,7 +1851,7 @@ push_string (s, expand, ap) #endif shell_input_line = s; - shell_input_line_size = strlen (s); + shell_input_line_size = STRLEN (s); shell_input_line_index = 0; shell_input_line_terminator = '\0'; #if 0 @@ -1776,6 +1925,34 @@ free_pushed_string_input () #endif } +int +parser_expanding_alias () +{ + return (expanding_alias ()); +} + +void +parser_save_alias () +{ +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + push_string ((char *)NULL, 0, (alias_t *)NULL); + pushed_string_list->flags = PSH_SOURCE; /* XXX - for now */ +#else + ; +#endif +} + +void +parser_restore_alias () +{ +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + if (pushed_string_list) + pop_string (); +#else + ; +#endif +} + /* Return a line of text, taken from wherever yylex () reads input. If there is no more input, then we return NULL. If REMOVE_QUOTED_NEWLINE is non-zero, we remove unquoted \ pairs. This is used by @@ -1786,7 +1963,7 @@ read_a_line (remove_quoted_newline) { static char *line_buffer = (char *)NULL; static int buffer_size = 0; - int indx = 0, c, peekc, pass_next; + int indx, c, peekc, pass_next; #if defined (READLINE) if (no_line_editing && SHOULD_PROMPT ()) @@ -1795,7 +1972,7 @@ read_a_line (remove_quoted_newline) #endif print_prompt (); - pass_next = 0; + pass_next = indx = 0; while (1) { /* Allow immediate exit if interrupted during input. */ @@ -1838,6 +2015,7 @@ read_a_line (remove_quoted_newline) } else if (c == '\\' && remove_quoted_newline) { + QUIT; peekc = yy_getc (); if (peekc == '\n') { @@ -1939,6 +2117,7 @@ STRING_INT_ALIST word_token_alist[] = { /* other tokens that can be returned by read_token() */ STRING_INT_ALIST other_token_alist[] = { /* Multiple-character tokens with special values */ + { "--", TIMEIGN }, { "-p", TIMEOPT }, { "&&", AND_AND }, { "||", OR_OR }, @@ -2025,7 +2204,7 @@ shell_getc (remove_quoted_newline) int remove_quoted_newline; { register int i; - int c; + int c, truncating; unsigned char uc; QUIT; @@ -2056,12 +2235,20 @@ shell_getc (remove_quoted_newline) { line_number++; + /* Let's not let one really really long line blow up memory allocation */ + if (shell_input_line && shell_input_line_size >= 32768) + { + free (shell_input_line); + shell_input_line = 0; + shell_input_line_size = 0; + } + restart_read: /* Allow immediate exit if interrupted during input. */ QUIT; - i = 0; + i = truncating = 0; shell_input_line_terminator = 0; /* If the shell is interatctive, but not currently printing a prompt @@ -2106,7 +2293,30 @@ shell_getc (remove_quoted_newline) continue; } - RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256); + /* Theoretical overflow */ + /* If we can't put 256 bytes more into the buffer, allocate + everything we can and fill it as full as we can. */ + /* XXX - we ignore rest of line using `truncating' flag */ + if (shell_input_line_size > (SIZE_MAX - 256)) + { + size_t n; + + n = SIZE_MAX - i; /* how much more can we put into the buffer? */ + if (n <= 2) /* we have to save 1 for the newline added below */ + { + if (truncating == 0) + internal_warning("shell_getc: shell_input_line_size (%zu) exceeds SIZE_MAX (%llu): line truncated", shell_input_line_size, SIZE_MAX); + shell_input_line[i] = '\0'; + truncating = 1; + } + if (shell_input_line_size < SIZE_MAX) + { + shell_input_line_size = SIZE_MAX; + shell_input_line = xrealloc (shell_input_line, shell_input_line_size); + } + } + else + RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256); if (c == EOF) { @@ -2120,7 +2330,8 @@ shell_getc (remove_quoted_newline) break; } - shell_input_line[i++] = c; + if (truncating == 0 || c == '\n') + shell_input_line[i++] = c; if (c == '\n') { @@ -2159,7 +2370,7 @@ shell_getc (remove_quoted_newline) shell_input_line = expansions; shell_input_line_len = shell_input_line ? strlen (shell_input_line) : 0; - if (!shell_input_line_len) + if (shell_input_line_len == 0) current_command_line_count--; /* We have to force the xrealloc below because we don't know @@ -2184,7 +2395,7 @@ shell_getc (remove_quoted_newline) else { char *hdcs; - hdcs = history_delimiting_chars (); + hdcs = history_delimiting_chars (shell_input_line); if (hdcs && hdcs[0] == ';') maybe_add_history (shell_input_line); } @@ -2195,9 +2406,14 @@ shell_getc (remove_quoted_newline) if (shell_input_line) { /* Lines that signify the end of the shell's input should not be - echoed. */ + echoed. We should not echo lines while parsing command + substitutions with recursive calls into the parsing engine; those + should only be echoed once when we read the word. That is the + reason for the test against shell_eof_token, which is set to a + right paren when parsing the contents of command substitutions. */ if (echo_input_at_read && (shell_input_line[0] || - shell_input_line_terminator != EOF)) + shell_input_line_terminator != EOF) && + shell_eof_token == 0) fprintf (stderr, "%s\n", shell_input_line); } else @@ -2213,7 +2429,7 @@ shell_getc (remove_quoted_newline) not already end in an EOF character. */ if (shell_input_line_terminator != EOF) { - if (shell_input_line_len + 3 > shell_input_line_size) + if (shell_input_line_size < SIZE_MAX-3 && (shell_input_line_len+3 > shell_input_line_size)) shell_input_line = (char *)xrealloc (shell_input_line, 1 + (shell_input_line_size += 2)); @@ -2224,6 +2440,7 @@ shell_getc (remove_quoted_newline) } } +next_alias_char: uc = shell_input_line[shell_input_line_index]; if (uc) @@ -2235,7 +2452,13 @@ shell_getc (remove_quoted_newline) because we have fully consumed the result of the last alias expansion. Do it transparently; just return the next character of the string popped to. */ - if (!uc && (pushed_string_list != (STRING_SAVER *)NULL)) + /* If pushed_string_list != 0 but pushed_string_list->expander == 0 (not + currently tested) and the flags value is not PSH_SOURCE, we are not + parsing an alias, we have just saved one (push_string, when called by + the parse_dparen code) In this case, just go on as well. The PSH_SOURCE + case is handled below. */ +pop_alias: + if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE) { pop_string (); uc = shell_input_line[shell_input_line_index]; @@ -2249,12 +2472,54 @@ shell_getc (remove_quoted_newline) if (SHOULD_PROMPT ()) prompt_again (); line_number++; - goto restart_read; + /* What do we do here if we're expanding an alias whose definition + includes an escaped newline? If that's the last character in the + alias expansion, we just pop the pushed string list (recall that + we inhibit the appending of a space in mk_alexpansion() if newline + is the last character). If it's not the last character, we need + to consume the quoted newline and move to the next character in + the expansion. */ +#if defined (ALIAS) + if (expanding_alias () && shell_input_line[shell_input_line_index+1] == '\0') + { + uc = 0; + goto pop_alias; + } + else if (expanding_alias () && shell_input_line[shell_input_line_index+1] != '\0') + { + shell_input_line_index++; /* skip newline */ + goto next_alias_char; /* and get next character */ + } + else +#endif + goto restart_read; } - if (!uc && shell_input_line_terminator == EOF) + if (uc == 0 && shell_input_line_terminator == EOF) return ((shell_input_line_index != 0) ? '\n' : EOF); +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + /* We already know that we are not parsing an alias expansion because of the + check for expanding_alias() above. This knows how parse_and_execute + handles switching to st_string input while an alias is being expanded, + hence the check for pushed_string_list without pushed_string_list->expander + and the check for PSH_SOURCE as pushed_string_list->flags. + parse_and_execute and parse_string both change the input type to st_string + and place the string to be parsed and executed into location.string, so + we should not stop reading that until the pointer is '\0'. + The check for shell_input_line_terminator may be superfluous. + + This solves the problem of `.' inside a multi-line alias with embedded + newlines executing things out of order. */ + if (uc == 0 && bash_input.type == st_string && *bash_input.location.string && + pushed_string_list && pushed_string_list->flags == PSH_SOURCE && + shell_input_line_terminator == 0) + { + shell_input_line_index = 0; + goto restart_read; + } +#endif + return (uc); } @@ -2273,6 +2538,16 @@ shell_ungetc (c) eol_ungetc_lookahead = c; } +char * +parser_remaining_input () +{ + if (shell_input_line == 0) + return 0; + if (shell_input_line_index < 0 || shell_input_line_index >= shell_input_line_len) + return '\0'; /* XXX */ + return (shell_input_line + shell_input_line_index); +} + #ifdef INCLUDE_UNUSED /* Back the input pointer up by one, effectively `ungetting' a character. */ static void @@ -2344,7 +2619,7 @@ yylex () We do this only if it is time to do so. Notice that only here is the mail alarm reset; nothing takes place in check_mail () except the checking of mail. Please don't change this. */ - if (prompt_is_ps1 && time_to_check_mail ()) + if (prompt_is_ps1 && parse_and_execute_level == 0 && time_to_check_mail ()) { check_mail (); reset_mail_timer (); @@ -2376,13 +2651,28 @@ yylex () which allow ESAC to be the next one read. */ static int esacs_needed_count; +static void +push_heredoc (r) + REDIRECT *r; +{ + if (need_here_doc >= HEREDOC_MAX) + { + last_command_exit_value = EX_BADUSAGE; + need_here_doc = 0; + report_syntax_error (_("maximum here-document count exceeded")); + reset_parser (); + exit_shell (last_command_exit_value); + } + redir_stack[need_here_doc++] = r; +} + void gather_here_documents () { int r; r = 0; - while (need_here_doc) + while (need_here_doc > 0) { parser_state |= PST_HEREDOC; make_here_document (redir_stack[r++], line_number); @@ -2396,7 +2686,7 @@ gather_here_documents () static int open_brace_count; #define command_token_position(token) \ - (((token) == ASSIGNMENT_WORD) || \ + (((token) == ASSIGNMENT_WORD) || (parser_state&PST_REDIRLIST) || \ ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token))) #define assignment_acceptable(token) \ @@ -2459,7 +2749,9 @@ mk_alexpansion (s) l = strlen (s); r = xmalloc (l + 2); strcpy (r, s); - if (r[l -1] != ' ') + /* If the last character in the alias is a newline, don't add a trailing + space to the expansion. Works with shell_getc above. */ + if (r[l - 1] != ' ' && r[l - 1] != '\n') r[l++] = ' '; r[l] = '\0'; return r; @@ -2504,6 +2796,20 @@ static int time_command_acceptable () { #if defined (COMMAND_TIMING) + int i; + + if (posixly_correct && shell_compatibility_level > 41) + { + /* Quick check of the rest of the line to find the next token. If it + begins with a `-', Posix says to not return `time' as the token. + This was interp 267. */ + i = shell_input_line_index; + while (i < shell_input_line_len && (shell_input_line[i] == ' ' || shell_input_line[i] == '\t')) + i++; + if (shell_input_line[i] == '-') + return 0; + } + switch (last_read_token) { case 0: @@ -2512,11 +2818,20 @@ time_command_acceptable () case AND_AND: case OR_OR: case '&': + case WHILE: case DO: + case UNTIL: + case IF: case THEN: + case ELIF: case ELSE: case '{': /* } */ - case '(': /* ) */ + case '(': /* )( */ + case ')': /* only valid in case statement */ + case BANG: /* ! time pipeline */ + case TIME: /* time time pipeline */ + case TIMEOPT: /* time -p time pipeline */ + case TIMEIGN: /* time -p -- ... */ return 1; default: return 0; @@ -2543,6 +2858,7 @@ time_command_acceptable () `}' is recognized if there is an unclosed `{' present. `-p' is returned as TIMEOPT if the last read token was TIME. + `--' is returned as TIMEIGN if the last read token was TIMEOPT. ']]' is returned as COND_END if the parser is currently parsing a conditional expression ((parser_state & PST_CONDEXPR) != 0) @@ -2628,13 +2944,9 @@ special_case_tokens (tokstr) /* Handle -p after `time'. */ if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2]) return (TIMEOPT); -#endif - -#if 0 -#if defined (COMMAND_TIMING) - if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ()) - return (TIME); -#endif /* COMMAND_TIMING */ + /* Handle -- after `time -p'. */ + if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2]) + return (TIMEIGN); #endif #if defined (COND_COMMAND) /* [[ */ @@ -2653,6 +2965,12 @@ reset_parser () dstack.delimiter_depth = 0; /* No delimiters found so far. */ open_brace_count = 0; +#if defined (EXTENDED_GLOB) + /* Reset to global value of extended glob */ + if (parser_state & PST_EXTPAT) + extended_glob = global_extglob; +#endif + parser_state = 0; #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) @@ -2670,6 +2988,8 @@ reset_parser () FREE (word_desc_to_read); word_desc_to_read = (WORD_DESC *)NULL; + eol_ungetc_lookahead = 0; + current_token = '\n'; /* XXX */ last_read_token = '\n'; token_to_read = '\n'; @@ -2913,12 +3233,13 @@ tokword: * reprompting the user, if necessary, after reading a newline, and returning * correct error values if it reads EOF. */ -#define P_FIRSTCLOSE 0x01 -#define P_ALLOWESC 0x02 -#define P_DQUOTE 0x04 -#define P_COMMAND 0x08 /* parsing a command, so look for comments */ -#define P_BACKQUOTE 0x10 /* parsing a backquoted command substitution */ -#define P_ARRAYSUB 0x20 /* parsing a [...] array subscript for assignment */ +#define P_FIRSTCLOSE 0x0001 +#define P_ALLOWESC 0x0002 +#define P_DQUOTE 0x0004 +#define P_COMMAND 0x0008 /* parsing a command, so look for comments */ +#define P_BACKQUOTE 0x0010 /* parsing a backquoted command substitution */ +#define P_ARRAYSUB 0x0020 /* parsing a [...] array subscript for assignment */ +#define P_DOLBRACE 0x0040 /* parsing a ${...} construct */ /* Lexical state while parsing a grouping construct or $(...). */ #define LEX_WASDOL 0x001 @@ -2966,8 +3287,11 @@ parse_matched_pair (qc, open, close, lenp, flags) int nestlen, ttranslen, start_lineno; char *ret, *nestret, *ttrans; int retind, retsize, rflags; + int dolbrace_state; + + dolbrace_state = (flags & P_DOLBRACE) ? DOLBRACE_PARAM : 0; -/* itrace("parse_matched_pair: open = %c close = %c flags = %d", open, close, flags); */ +/*itrace("parse_matched_pair[%d]: open = %c close = %c flags = %d", line_number, open, close, flags);*/ count = 1; tflags = 0; @@ -2983,7 +3307,7 @@ parse_matched_pair (qc, open, close, lenp, flags) start_lineno = line_number; while (count) { - ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0); + ch = shell_getc (qc != '\'' && (tflags & (LEX_PASSNEXT)) == 0); if (ch == EOF) { @@ -3028,7 +3352,7 @@ parse_matched_pair (qc, open, close, lenp, flags) } RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); - if MBTEST(ch == CTLESC || ch == CTLNUL) + if MBTEST(ch == CTLESC) ret[retind++] = CTLESC; ret[retind++] = ch; continue; @@ -3076,14 +3400,43 @@ parse_matched_pair (qc, open, close, lenp, flags) if MBTEST(ch == '\\') /* backslashes */ tflags |= LEX_PASSNEXT; -#if 0 + /* Based on which dolstate is currently in (param, op, or word), + decide what the op is. We're really only concerned if it's % or + #, so we can turn on a flag that says whether or not we should + treat single quotes as special when inside a double-quoted + ${...}. This logic must agree with subst.c:extract_dollar_brace_string + since they share the same defines. */ + /* FLAG POSIX INTERP 221 */ + if (flags & P_DOLBRACE) + { + /* ${param%[%]word} */ + if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param#[#]word} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param/[/]pat/rep} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE2; /* XXX */ + /* ${param^[^]pat} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param,[,]pat} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0) + dolbrace_state = DOLBRACE_OP; + else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0) + dolbrace_state = DOLBRACE_WORD; + } + /* The big hammer. Single quotes aren't special in double quotes. The - problem is that Posix says the single quotes are semi-special: + problem is that Posix used to say the single quotes are semi-special: within a double-quoted ${...} construct "an even number of unescaped double-quotes or single-quotes, if any, shall occur." */ - if MBTEST(open == '{' && (flags & P_DQUOTE) && ch == '\'') /* } */ + /* This was changed in Austin Group Interp 221 */ + if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2 && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'') continue; -#endif /* Could also check open == '`' if we want to parse grouping constructs inside old-style command substitution. */ @@ -3106,7 +3459,18 @@ parse_matched_pair (qc, open, close, lenp, flags) ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen); xfree (nestret); - if ((rflags & P_DQUOTE) == 0) + /* If we're parsing a double-quoted brace expansion and we are + not in a place where single quotes are treated specially, + make sure we single-quote the results of the ansi + expansion because quote removal should remove them later */ + /* FLAG POSIX INTERP 221 */ + if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2) && (flags & P_DOLBRACE)) + { + nestret = sh_single_quote (ttrans); + free (ttrans); + nestlen = strlen (nestret); + } + else if ((rflags & P_DQUOTE) == 0) { nestret = sh_single_quote (ttrans); free (ttrans); @@ -3158,7 +3522,7 @@ parse_dollar_word: if (ch == '(') /* ) */ nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE); else if (ch == '{') /* } */ - nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags); + nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags); else if (ch == '[') /* ] */ nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags); @@ -3176,6 +3540,7 @@ parse_dollar_word: ret[retind] = '\0'; if (lenp) *lenp = retind; +/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/ return ret; } @@ -3192,6 +3557,13 @@ parse_comsub (qc, open, close, lenp, flags) char *ret, *nestret, *ttrans, *heredelim; int retind, retsize, rflags, hdlen; + /* Posix interp 217 says arithmetic expressions have precedence, so + assume $(( introduces arithmetic expansion and parse accordingly. */ + peekc = shell_getc (0); + shell_ungetc (peekc); + if (peekc == '(') + return (parse_matched_pair (qc, open, close, lenp, 0)); + /*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/ count = 1; tflags = LEX_RESWDOK; @@ -3216,7 +3588,7 @@ parse_comsub (qc, open, close, lenp, flags) while (count) { comsub_readchar: - ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0); + ch = shell_getc (qc != '\'' && (tflags & (LEX_INCOMMENT|LEX_PASSNEXT)) == 0); if (ch == EOF) { @@ -3251,6 +3623,8 @@ eof_error: { tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC); /*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/ + free (heredelim); + heredelim = 0; lex_firstind = -1; } else @@ -3262,6 +3636,25 @@ eof_error: if (ch == '\n' && SHOULD_PROMPT ()) prompt_again (); + /* XXX -- possibly allow here doc to be delimited by ending right + paren. */ + if ((tflags & LEX_INHEREDOC) && ch == close && count == 1) + { + int tind; +/*itrace("parse_comsub: in here doc, ch == close, retind - firstind = %d hdlen = %d retind = %d", retind-lex_firstind, hdlen, retind);*/ + tind = lex_firstind; + while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t') + tind++; + if (retind-tind == hdlen && STREQN (ret + tind, heredelim, hdlen)) + { + tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC); +/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/ + free (heredelim); + heredelim = 0; + lex_firstind = -1; + } + } + /* Don't bother counting parens or doing anything else if in a comment */ if (tflags & (LEX_INCOMMENT|LEX_INHEREDOC)) { @@ -3270,7 +3663,10 @@ eof_error: ret[retind++] = ch; if ((tflags & LEX_INCOMMENT) && ch == '\n') - tflags &= ~LEX_INCOMMENT; + { +/*itrace("parse_comsub:%d: lex_incomment -> 0 ch = `%c'", line_number, ch);*/ + tflags &= ~LEX_INCOMMENT; + } continue; } @@ -3287,7 +3683,7 @@ eof_error: } RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); - if MBTEST(ch == CTLESC || ch == CTLNUL) + if MBTEST(ch == CTLESC) ret[retind++] = CTLESC; ret[retind++] = ch; continue; @@ -3312,11 +3708,13 @@ eof_error: /*itrace("parse_comsub:%d: lex_inword -> 1 ch = `%c' (%d)", line_number, ch, __LINE__);*/ tflags |= LEX_INWORD; lex_wlen = 0; + if (tflags & LEX_RESWDOK) + lex_rwlen = 0; } } /* Skip whitespace */ - if MBTEST(shellblank (ch) && lex_rwlen == 0) + if MBTEST(shellblank (ch) && (tflags & LEX_HEREDELIM) == 0 && lex_rwlen == 0) { /* Add this character. */ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); @@ -3335,13 +3733,24 @@ eof_error: { if (lex_firstind == -1 && shellbreak (ch) == 0) lex_firstind = retind; +#if 0 + else if (heredelim && (tflags & LEX_PASSNEXT) == 0 && ch == '\n') + { + tflags |= LEX_INHEREDOC; + tflags &= ~LEX_HEREDELIM; + lex_firstind = retind + 1; + } +#endif else if (lex_firstind >= 0 && (tflags & LEX_PASSNEXT) == 0 && shellbreak (ch)) { - nestret = substring (ret, lex_firstind, retind); - heredelim = string_quote_removal (nestret, 0); - free (nestret); - hdlen = STRLEN(heredelim); + if (heredelim == 0) + { + nestret = substring (ret, lex_firstind, retind); + heredelim = string_quote_removal (nestret, 0); + free (nestret); + hdlen = STRLEN(heredelim); /*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/ + } if (ch == '\n') { tflags |= LEX_INHEREDOC; @@ -3364,7 +3773,7 @@ eof_error: { RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); ret[retind++] = peekc; -/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch); */ +/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/ tflags |= LEX_RESWDOK; lex_rwlen = 0; continue; @@ -3372,8 +3781,8 @@ eof_error: else if (ch == '\n' || COMSUB_META(ch)) { shell_ungetc (peekc); - tflags |= LEX_RESWDOK; /*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/ + tflags |= LEX_RESWDOK; lex_rwlen = 0; continue; } @@ -3401,36 +3810,57 @@ eof_error: else if MBTEST(lex_rwlen == 4 && shellbreak (ch)) { if (STREQN (ret + retind - 4, "case", 4)) -{ - tflags |= LEX_INCASE; -/*itrace("parse_comsub:%d: found `case', lex_incase -> 1", line_number);*/ -} + { + tflags |= LEX_INCASE; +/*itrace("parse_comsub:%d: found `case', lex_incase -> 1 lex_reswdok -> 0", line_number);*/ + } else if (STREQN (ret + retind - 4, "esac", 4)) -{ - tflags &= ~LEX_INCASE; -/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0", line_number);*/ -} + { + tflags &= ~LEX_INCASE; +/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0 lex_reswdok -> 0", line_number);*/ + } tflags &= ~LEX_RESWDOK; } else if MBTEST((tflags & LEX_CKCOMMENT) && ch == '#' && (lex_rwlen == 0 || ((tflags & LEX_INWORD) && lex_wlen == 0))) ; /* don't modify LEX_RESWDOK if we're starting a comment */ + /* Allow `do' followed by space, tab, or newline to preserve the + RESWDOK flag, but reset the reserved word length counter so we + can read another one. */ + else if MBTEST(((tflags & LEX_INCASE) == 0) && + (isblank(ch) || ch == '\n') && + lex_rwlen == 2 && + STREQN (ret + retind - 2, "do", 2)) + { +/*itrace("parse_comsub:%d: lex_incase == 1 found `%c', found \"do\"", line_number, ch);*/ + lex_rwlen = 0; + } else if MBTEST((tflags & LEX_INCASE) && ch != '\n') /* If we can read a reserved word and we're in case, we're at the point where we can read a new pattern list or an esac. We handle the esac case above. If we read a newline, we want to leave LEX_RESWDOK alone. If we read anything else, we want to turn off LEX_RESWDOK, since we're going to read a pattern list. */ -{ - tflags &= ~LEX_RESWDOK; + { + tflags &= ~LEX_RESWDOK; /*itrace("parse_comsub:%d: lex_incase == 1 found `%c', lex_reswordok -> 0", line_number, ch);*/ -} + } else if MBTEST(shellbreak (ch) == 0) -{ - tflags &= ~LEX_RESWDOK; + { + tflags &= ~LEX_RESWDOK; /*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/ -} + } +#if 0 + /* If we find a space or tab but have read something and it's not + `do', turn off the reserved-word-ok flag */ + else if MBTEST(isblank (ch) && lex_rwlen > 0) + { + tflags &= ~LEX_RESWDOK; +/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/ + } +#endif } + /* Might be the start of a here-doc delimiter */ if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<') { /* Add this character. */ @@ -3465,10 +3895,10 @@ eof_error: ch = peekc; /* fall through and continue XXX */ } else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (((tflags & LEX_RESWDOK) && lex_rwlen == 0) || ((tflags & LEX_INWORD) && lex_wlen == 0))) -{ + { /*itrace("parse_comsub:%d: lex_incomment -> 1 (%d)", line_number, __LINE__);*/ - tflags |= LEX_INCOMMENT; -} + tflags |= LEX_INCOMMENT; + } if MBTEST(ch == CTLESC || ch == CTLNUL) /* special shell escapes */ { @@ -3482,12 +3912,15 @@ eof_error: tflags &= ~LEX_INCASE; /* XXX */ #endif else if MBTEST(ch == close && (tflags & LEX_INCASE) == 0) /* ending delimiter */ -{ - count--; + { + count--; /*itrace("parse_comsub:%d: found close: count = %d", line_number, count);*/ -} + } else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && (tflags & LEX_INCASE) == 0 && ch == open) /* nested begin */ - count++; + { + count++; +/*itrace("parse_comsub:%d: found open: count = %d", line_number, count);*/ + } /* Add this character. */ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); @@ -3553,7 +3986,7 @@ eof_error: if (ch == '(') /* ) */ nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE); else if (ch == '{') /* } */ - nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags); + nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags); else if (ch == '[') /* ] */ nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags); @@ -3576,8 +4009,7 @@ eof_error: return ret; } -/* XXX - this needs to handle functionality like subst.c:no_longjmp_on_fatal_error; - maybe extract_command_subst should handle it. */ +/* Recursively call the parser to parse a $(...) command substitution. */ char * xparse_dolparen (base, string, indp, flags) char *base; @@ -3586,27 +4018,34 @@ xparse_dolparen (base, string, indp, flags) int flags; { sh_parser_state_t ps; - int orig_ind, nc, sflags; + sh_input_line_state_t ls; + int orig_ind, nc, sflags, orig_eof_token; char *ret, *s, *ep, *ostring; /*yydebug = 1;*/ orig_ind = *indp; ostring = string; +/*itrace("xparse_dolparen: size = %d shell_input_line = `%s'", shell_input_line_size, shell_input_line);*/ sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE; if (flags & SX_NOLONGJMP) sflags |= SEVAL_NOLONGJMP; save_parser_state (&ps); + save_input_line_state (&ls); + orig_eof_token = shell_eof_token; /*(*/ parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*(*/ shell_eof_token = ')'; parse_string (string, "command substitution", sflags, &ep); + shell_eof_token = orig_eof_token; restore_parser_state (&ps); reset_parser (); - if (interactive) - token_to_read = 0; + /* reset_parser clears shell_input_line and associated variables */ + restore_input_line_state (&ls); + + token_to_read = 0; /* Need to find how many characters parse_and_execute consumed, update *indp, if flags != 0, copy the portion of the string parsed into RET @@ -3692,6 +4131,7 @@ parse_dparen (c) else if (cmdtyp == 0) /* nested subshell */ { push_string (wval, 0, (alias_t *)NULL); + pushed_string_list->flags = PSH_DPAREN; if ((parser_state & PST_CASEPAT) == 0) parser_state |= PST_SUBSHELL; return (c); @@ -3903,7 +4343,13 @@ cond_term () /* binop */ tok = read_token (READ); if (tok == WORD && test_binop (yylval.word->word)) - op = yylval.word; + { + op = yylval.word; + if (op->word[0] == '=' && (op->word[1] == '\0' || (op->word[1] == '=' && op->word[2] == '\0'))) + parser_state |= PST_EXTPAT; + else if (op->word[0] == '!' && op->word[1] == '=' && op->word[2] == '\0') + parser_state |= PST_EXTPAT; + } #if defined (COND_REGEXP) else if (tok == WORD && STREQ (yylval.word->word, "=~")) { @@ -3939,8 +4385,13 @@ cond_term () } /* rhs */ + if (parser_state & PST_EXTPAT) + extended_glob = 1; tok = read_token (READ); - parser_state &= ~PST_REGEXP; + if (parser_state & PST_EXTPAT) + extended_glob = global_extglob; + parser_state &= ~(PST_REGEXP|PST_EXTPAT); + if (tok == WORD) { tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); @@ -3985,6 +4436,7 @@ parse_cond_command () { COND_COM *cexp; + global_extglob = extended_glob; cexp = cond_expr (); return (make_cond_command (cexp)); } @@ -4163,7 +4615,7 @@ read_token_word (character) pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ - RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; @@ -4185,11 +4637,11 @@ read_token_word (character) { peek_char = shell_getc (1); /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */ - if MBTEST(peek_char == '(' || \ + if MBTEST(peek_char == '(' || ((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */ { if (peek_char == '{') /* } */ - ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE); + ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE); else if (peek_char == '(') /* ) */ { /* XXX - push and pop the `(' as a delimiter for use by @@ -4205,7 +4657,7 @@ read_token_word (character) ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ - RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; @@ -4256,7 +4708,7 @@ read_token_word (character) ttrans = ttok; } - RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2, + RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 1, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); strcpy (token + token_index, ttrans); @@ -4270,17 +4722,13 @@ read_token_word (character) shell's single-character parameter expansions, and set flags.*/ else if MBTEST(character == '$' && peek_char == '$') { - ttok = (char *)xmalloc (3); - ttok[0] = ttok[1] = '$'; - ttok[2] = '\0'; RESIZE_MALLOCED_BUFFER (token, token_index, 3, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); - strcpy (token + token_index, ttok); - token_index += 2; + token[token_index++] = '$'; + token[token_index++] = peek_char; dollar_present = 1; all_digit_token = 0; - FREE (ttok); goto next_character; } else @@ -4350,21 +4798,24 @@ read_token_word (character) goto got_token; } - got_character: +got_character: if (character == CTLESC || character == CTLNUL) - token[token_index++] = CTLESC; + { + RESIZE_MALLOCED_BUFFER (token, token_index, 2, token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = CTLESC; + } + else +got_escaped_character: + RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); - got_escaped_character: + token[token_index++] = character; all_digit_token &= DIGIT (character); dollar_present |= character == '$'; - token[token_index++] = character; - - RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size, - TOKEN_DEFAULT_GROW_SIZE); - next_character: if (character == '\n' && SHOULD_PROMPT ()) prompt_again (); @@ -4378,21 +4829,22 @@ read_token_word (character) got_token: + /* Calls to RESIZE_MALLOCED_BUFFER ensure there is sufficient room. */ token[token_index] = '\0'; /* Check to see what thing we should return. If the last_read_token is a `<', or a `&', or the character which ended this token is a '>' or '<', then, and ONLY then, is this input token a NUMBER. Otherwise, it is just a word, and should be returned as such. */ - if MBTEST(all_digit_token && (character == '<' || character == '>' || \ - last_read_token == LESS_AND || \ + if MBTEST(all_digit_token && (character == '<' || character == '>' || + last_read_token == LESS_AND || last_read_token == GREATER_AND)) { if (legal_number (token, &lvalue) && (int)lvalue == lvalue) - yylval.number = lvalue; - else - yylval.number = -1; - return (NUMBER); + { + yylval.number = lvalue; + return (NUMBER); + } } /* Check for special case tokens. */ @@ -4442,7 +4894,11 @@ got_token: the_word->flags |= W_ASSIGNMENT; /* Don't perform word splitting on assignment statements. */ if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0) - the_word->flags |= W_NOSPLIT; + { + the_word->flags |= W_NOSPLIT; + if (parser_state & PST_COMPASSIGN) + the_word->flags |= W_NOGLOB; /* XXX - W_NOBRACE? */ + } } if (command_token_position (last_read_token)) @@ -4457,6 +4913,23 @@ got_token: yylval.word = the_word; + if (token[0] == '{' && token[token_index-1] == '}' && + (character == '<' || character == '>')) + { + /* can use token; already copied to the_word */ + token[token_index-1] = '\0'; +#if defined (ARRAY_VARS) + if (legal_identifier (token+1) || valid_array_reference (token+1)) +#else + if (legal_identifier (token+1)) +#endif + { + strcpy (the_word->word, token+1); +/*itrace("read_token_word: returning REDIR_WORD for %s", the_word->word);*/ + return (REDIR_WORD); + } + } + result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT)) ? ASSIGNMENT_WORD : WORD; @@ -4511,6 +4984,7 @@ reserved_word_acceptable (toksym) case THEN: case TIME: case TIMEOPT: + case TIMEIGN: case COPROC: case UNTIL: case WHILE: @@ -4521,6 +4995,8 @@ reserved_word_acceptable (toksym) if (last_read_token == WORD && token_before_that == COPROC) return 1; #endif + if (last_read_token == WORD && token_before_that == FUNCTION) + return 1; return 0; } } @@ -4538,6 +5014,14 @@ find_reserved_word (tokstr) return -1; } +/* An interface to let the rest of the shell (primarily the completion + system) know what the parser is expecting. */ +int +parser_in_command_position () +{ + return (command_token_position (last_read_token)); +} + #if 0 #if defined (READLINE) /* Called after each time readline is called. This insures that whatever @@ -4581,20 +5065,38 @@ static const int no_semi_successors[] = { /* If we are not within a delimited expression, try to be smart about which separators can be semi-colons and which must be newlines. Returns the string that should be added into the - history entry. */ + history entry. LINE is the line we're about to add; it helps + make some more intelligent decisions in certain cases. */ char * -history_delimiting_chars () +history_delimiting_chars (line) + const char *line; { + static int last_was_heredoc = 0; /* was the last entry the start of a here document? */ register int i; + if ((parser_state & PST_HEREDOC) == 0) + last_was_heredoc = 0; + if (dstack.delimiter_depth != 0) return ("\n"); /* We look for current_command_line_count == 2 because we are looking to add the first line of the body of the here document (the second line - of the command). */ + of the command). We also keep LAST_WAS_HEREDOC as a private sentinel + variable to note when we think we added the first line of a here doc + (the one with a "<<" somewhere in it) */ if (parser_state & PST_HEREDOC) - return (current_command_line_count == 2 ? "\n" : ""); + { + if (last_was_heredoc) + { + last_was_heredoc = 0; + return "\n"; + } + return (current_command_line_count == 2 ? "\n" : ""); + } + + if (parser_state & PST_COMPASSIGN) + return (" "); /* First, handle some special cases. */ /*(*/ @@ -4617,6 +5119,15 @@ history_delimiting_chars () else if (token_before_that == WORD && two_tokens_ago == FUNCTION) return " "; /* function def using `function name' without `()' */ + /* If we're not in a here document, but we think we're about to parse one, + and we would otherwise return a `;', return a newline to delimit the + line with the here-doc delimiter */ + else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && last_read_token == '\n' && strstr (line, "<<")) + { + last_was_heredoc = 1; + return "\n"; + } + else if (token_before_that == WORD && two_tokens_ago == FOR) { /* Tricky. `for i\nin ...' should not have a semicolon, but @@ -4647,7 +5158,7 @@ prompt_again () { char *temp_prompt; - if (interactive == 0 || expanding_alias()) /* XXX */ + if (interactive == 0 || expanding_alias ()) /* XXX */ return; ps1_prompt = get_string_value ("PS1"); @@ -4743,7 +5254,7 @@ decode_prompt_string (string) WORD_LIST *list; char *result, *t; struct dstack save_dstack; - int last_exit_value; + int last_exit_value, last_comsub_pid; #if defined (PROMPT_STRING_DECODE) int result_size, result_index; int c, n, i; @@ -4827,6 +5338,9 @@ decode_prompt_string (string) case 'A': /* Make the current time/date into a string. */ (void) time (&the_time); +#if defined (HAVE_TZSET) + sv_tz ("TZ"); /* XXX -- just make sure */ +#endif tm = localtime (&the_time); if (c == 'd') @@ -4930,6 +5444,13 @@ decode_prompt_string (string) } t_string[tlen] = '\0'; +#if defined (MACOSX) + /* Convert from "fs" format to "input" format */ + temp = fnx_fromfs (t_string, strlen (t_string)); + if (temp != t_string) + strcpy (t_string, temp); +#endif + #define ROOT_PATH(x) ((x)[0] == '/' && (x)[1] == 0) #define DOUBLE_SLASH_ROOT(x) ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0) /* Abbreviate \W as ~ if $PWD == $HOME */ @@ -4939,15 +5460,19 @@ decode_prompt_string (string) { t = strrchr (t_string, '/'); if (t) - strcpy (t_string, t + 1); + memmove (t_string, t + 1, strlen (t)); /* strlen(t) to copy NULL */ } } #undef ROOT_PATH #undef DOUBLE_SLASH_ROOT else - /* polite_directory_format is guaranteed to return a string - no longer than PATH_MAX - 1 characters. */ - strcpy (t_string, polite_directory_format (t_string)); + { + /* polite_directory_format is guaranteed to return a string + no longer than PATH_MAX - 1 characters. */ + temp = polite_directory_format (t_string); + if (temp != t_string) + strcpy (t_string, temp); + } temp = trim_pathname (t_string, PATH_MAX - 1); /* If we're going to be expanding the prompt string later, @@ -5084,11 +5609,13 @@ not_escape: if (promptvars || posixly_correct) { last_exit_value = last_command_exit_value; + last_comsub_pid = last_command_subst_pid; list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0); free (result); result = string_list (list); dispose_words (list); last_command_exit_value = last_exit_value; + last_command_subst_pid = last_comsub_pid; } else { @@ -5224,14 +5751,14 @@ static void report_syntax_error (message) char *message; { - char *msg; + char *msg, *p; if (message) { parser_error (line_number, "%s", message); if (interactive && EOF_Reached) EOF_Reached = 0; - last_command_exit_value = EX_USAGE; + last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE; return; } @@ -5240,13 +5767,19 @@ report_syntax_error (message) parser's complaining about by looking at current_token. */ if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token))) { + if (ansic_shouldquote (msg)) + { + p = ansic_quote (msg, 0, NULL); + free (msg); + msg = p; + } parser_error (line_number, _("syntax error near unexpected token `%s'"), msg); free (msg); if (interactive == 0) print_offending_line (); - last_command_exit_value = EX_USAGE; + last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE; return; } @@ -5277,7 +5810,7 @@ report_syntax_error (message) EOF_Reached = 0; } - last_command_exit_value = EX_USAGE; + last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE; } /* ??? Needed function. ??? We have to be able to discard the constructs @@ -5549,10 +6082,6 @@ sh_parser_state_t * save_parser_state (ps) sh_parser_state_t *ps; { -#if defined (ARRAY_VARS) - SHELL_VAR *v; -#endif - if (ps == 0) ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t)); if (ps == 0) @@ -5564,6 +6093,8 @@ save_parser_state (ps) ps->input_line_terminator = shell_input_line_terminator; ps->eof_encountered = eof_encountered; + ps->prompt_string_pointer = prompt_string_pointer; + ps->current_command_line_count = current_command_line_count; #if defined (HISTORY) @@ -5575,11 +6106,7 @@ save_parser_state (ps) ps->last_command_exit_value = last_command_exit_value; #if defined (ARRAY_VARS) - v = find_variable ("PIPESTATUS"); - if (v && array_p (v) && array_cell (v)) - ps->pipestatus = array_copy (array_cell (v)); - else - ps->pipestatus = (ARRAY *)NULL; + ps->pipestatus = save_pipestatus_array (); #endif ps->last_shell_builtin = last_shell_builtin; @@ -5587,6 +6114,13 @@ save_parser_state (ps) ps->expand_aliases = expand_aliases; ps->echo_input_at_read = echo_input_at_read; + ps->need_here_doc = need_here_doc; + + ps->token = token; + ps->token_buffer_size = token_buffer_size; + /* Force reallocation on next call to read_token_word */ + token = 0; + token_buffer_size = 0; return (ps); } @@ -5595,10 +6129,6 @@ void restore_parser_state (ps) sh_parser_state_t *ps; { -#if defined (ARRAY_VARS) - SHELL_VAR *v; -#endif - if (ps == 0) return; @@ -5612,6 +6142,8 @@ restore_parser_state (ps) shell_input_line_terminator = ps->input_line_terminator; eof_encountered = ps->eof_encountered; + prompt_string_pointer = ps->prompt_string_pointer; + current_command_line_count = ps->current_command_line_count; #if defined (HISTORY) @@ -5623,12 +6155,7 @@ restore_parser_state (ps) last_command_exit_value = ps->last_command_exit_value; #if defined (ARRAY_VARS) - v = find_variable ("PIPESTATUS"); - if (v && array_p (v) && array_cell (v)) - { - array_dispose (array_cell (v)); - var_setarray (v, ps->pipestatus); - } + restore_pipestatus_array (ps->pipestatus); #endif last_shell_builtin = ps->last_shell_builtin; @@ -5636,6 +6163,45 @@ restore_parser_state (ps) expand_aliases = ps->expand_aliases; echo_input_at_read = ps->echo_input_at_read; + need_here_doc = ps->need_here_doc; + + FREE (token); + token = ps->token; + token_buffer_size = ps->token_buffer_size; +} + +sh_input_line_state_t * +save_input_line_state (ls) + sh_input_line_state_t *ls; +{ + if (ls == 0) + ls = (sh_input_line_state_t *)xmalloc (sizeof (sh_input_line_state_t)); + if (ls == 0) + return ((sh_input_line_state_t *)NULL); + + ls->input_line = shell_input_line; + ls->input_line_size = shell_input_line_size; + ls->input_line_len = shell_input_line_len; + ls->input_line_index = shell_input_line_index; + + /* force reallocation */ + shell_input_line = 0; + shell_input_line_size = shell_input_line_len = shell_input_line_index = 0; + + return ls; +} + +void +restore_input_line_state (ls) + sh_input_line_state_t *ls; +{ + FREE (shell_input_line); + shell_input_line = ls->input_line; + shell_input_line_size = ls->input_line_size; + shell_input_line_len = ls->input_line_len; + shell_input_line_index = ls->input_line_index; + + set_line_mbstate (); } /************************************************ @@ -5648,7 +6214,8 @@ restore_parser_state (ps) static void set_line_mbstate () { - int i, previ, len, c; + int c; + size_t i, previ, len; mbstate_t mbs, prevs; size_t mbclen; @@ -5666,7 +6233,7 @@ set_line_mbstate () c = shell_input_line[i]; if (c == EOF) { - int j; + size_t j; for (j = i; j < len; j++) shell_input_line_property[j] = 1; break; @@ -5689,7 +6256,7 @@ set_line_mbstate () else { /* XXX - what to do if mbrlen returns 0? (null wide character) */ - int j; + size_t j; for (j = i; j < len; j++) shell_input_line_property[j] = 1; break;