/* parse.y - Yacc grammar for bash. */
-/* Copyright (C) 1989-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2010 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
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;
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];
/* 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. */
/* 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. */
static int token_to_read;
static WORD_DESC *word_desc_to_read;
+static REDIRECTEE source;
static REDIRECTEE redir;
%}
third group are recognized only under special circumstances. */
%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC
%token COND_START COND_END COND_ERROR
-%token IN BANG TIME TIMEOPT
+%token IN BANG TIME TIMEOPT TIMEIGN
/* More general tokens. yylex () knows how to make these. */
-%token <word> WORD ASSIGNMENT_WORD
+%token <word> WORD ASSIGNMENT_WORD REDIR_WORD
%token <number> NUMBER
%token <word_list> ARITH_CMD ARITH_FOR_EXPRS
%token <command> COND_CMD
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);
+ $$ = make_redirection (source, r_reading_until, redir, 0);
redir_stack[need_here_doc++] = $$;
}
| NUMBER LESS_LESS WORD
{
+ source.dest = $1;
redir.filename = $3;
- $$ = make_redirection ($1, r_reading_until, redir);
+ $$ = make_redirection (source, r_reading_until, redir, 0);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | REDIR_WORD LESS_LESS WORD
+ {
+ source.filename = $1;
+ redir.filename = $3;
+ $$ = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | LESS_LESS_MINUS WORD
+ {
+ source.dest = 0;
+ redir.filename = $2;
+ $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | NUMBER LESS_LESS_MINUS WORD
+ {
+ source.dest = $1;
+ redir.filename = $3;
+ $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | REDIR_WORD LESS_LESS_MINUS WORD
+ {
+ source.filename = $1;
+ redir.filename = $3;
+ $$ = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN);
redir_stack[need_here_doc++] = $$;
}
| LESS_LESS_LESS WORD
{
+ source.dest = 0;
redir.filename = $2;
- $$ = make_redirection (0, r_reading_string, redir);
+ $$ = make_redirection (source, r_reading_string, redir, 0);
}
| NUMBER LESS_LESS_LESS WORD
{
+ source.dest = $1;
+ redir.filename = $3;
+ $$ = make_redirection (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 (source, r_duplicating_input, redir, 0);
+ }
+ | REDIR_WORD LESS_AND NUMBER
+ {
+ source.filename = $1;
redir.dest = $3;
- $$ = make_redirection ($1, r_duplicating_input, redir);
+ $$ = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN);
}
| GREATER_AND NUMBER
{
+ source.dest = 1;
redir.dest = $2;
- $$ = make_redirection (1, r_duplicating_output, redir);
+ $$ = make_redirection (source, r_duplicating_output, redir, 0);
}
| NUMBER GREATER_AND NUMBER
{
+ source.dest = $1;
redir.dest = $3;
- $$ = make_redirection ($1, r_duplicating_output, redir);
+ $$ = make_redirection (source, r_duplicating_output, redir, 0);
+ }
+ | REDIR_WORD GREATER_AND NUMBER
+ {
+ source.filename = $1;
+ redir.dest = $3;
+ $$ = 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 ($1, r_duplicating_input_word, redir);
+ $$ = make_redirection (source, r_duplicating_input_word, redir, 0);
+ }
+ | REDIR_WORD LESS_AND WORD
+ {
+ source.filename = $1;
+ redir.filename = $3;
+ $$ = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN);
}
| GREATER_AND WORD
{
+ source.dest = 1;
redir.filename = $2;
- $$ = make_redirection (1, r_duplicating_output_word, redir);
+ $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
}
| NUMBER GREATER_AND WORD
{
+ source.dest = $1;
redir.filename = $3;
- $$ = make_redirection ($1, r_duplicating_output_word, redir);
+ $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
}
- | LESS_LESS_MINUS WORD
- {
- redir.filename = $2;
- $$ = make_redirection
- (0, r_deblank_reading_until, redir);
- redir_stack[need_here_doc++] = $$;
- }
- | NUMBER LESS_LESS_MINUS WORD
+ | REDIR_WORD GREATER_AND WORD
{
+ source.filename = $1;
redir.filename = $3;
- $$ = make_redirection
- ($1, r_deblank_reading_until, redir);
- redir_stack[need_here_doc++] = $$;
+ $$ = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN);
}
| GREATER_AND '-'
{
+ source.dest = 1;
redir.dest = 0;
- $$ = make_redirection (1, r_close_this, redir);
+ $$ = make_redirection (source, r_close_this, redir, 0);
}
| NUMBER GREATER_AND '-'
{
+ source.dest = $1;
redir.dest = 0;
- $$ = make_redirection ($1, r_close_this, redir);
+ $$ = make_redirection (source, r_close_this, redir, 0);
}
- | LESS_AND '-'
+ | REDIR_WORD GREATER_AND '-'
{
+ source.filename = $1;
redir.dest = 0;
- $$ = make_redirection (0, r_close_this, redir);
+ $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
}
- | NUMBER LESS_AND '-'
+ | LESS_AND '-'
{
+ source.dest = 0;
redir.dest = 0;
- $$ = make_redirection ($1, r_close_this, redir);
+ $$ = make_redirection (source, r_close_this, redir, 0);
}
- | AND_GREATER WORD
+ | NUMBER LESS_AND '-'
{
- redir.filename = $2;
- $$ = make_redirection (1, r_err_and_out, redir);
+ source.dest = $1;
+ redir.dest = 0;
+ $$ = make_redirection (source, r_close_this, redir, 0);
}
- | AND_GREATER_GREATER WORD
+ | REDIR_WORD LESS_AND '-'
{
- redir.filename = $2;
- $$ = make_redirection (1, r_append_err_and_out, redir);
- }
- | NUMBER LESS_GREATER WORD
- {
- 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);
}
;
;
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;
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
{
/* 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;
{ $$ = CMD_TIME_PIPELINE; }
| TIME TIMEOPT
{ $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+ | TIME TIMEOPT TIMEIGN
+ { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
;
%%
give_terminal_to (shell_pgrp, 0);
#endif /* JOB_CONTROL */
- old_sigint = (SigHandler *)NULL;
+ old_sigint = (SigHandler *)IMPOSSIBLE_TRAP_HANDLER;
if (signal_is_ignored (SIGINT) == 0)
{
- old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
interrupt_immediately++;
+ old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
}
terminate_immediately = 1;
current_readline_prompt : "");
terminate_immediately = 0;
- if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+ if (signal_is_ignored (SIGINT) == 0)
{
interrupt_immediately--;
- set_signal_handler (SIGINT, old_sigint);
+ if (old_sigint != IMPOSSIBLE_TRAP_HANDLER)
+ set_signal_handler (SIGINT, old_sigint);
}
#if 0
/* 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;
{
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 ())
#endif
print_prompt ();
- pass_next = 0;
+ pass_next = indx = 0;
while (1)
{
/* Allow immediate exit if interrupted during input. */
}
else if (c == '\\' && remove_quoted_newline)
{
+ QUIT;
peekc = yy_getc ();
if (peekc == '\n')
{
/* 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 },
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
else
{
char *hdcs;
- hdcs = history_delimiting_chars ();
+ hdcs = history_delimiting_chars (shell_input_line);
if (hdcs && hdcs[0] == ';')
maybe_add_history (shell_input_line);
}
}
}
+next_alias_char:
uc = shell_input_line[shell_input_line_index];
if (uc)
because we have fully consumed the result of the last alias expansion.
Do it transparently; just return the next character of the string popped
to. */
- if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+pop_alias:
+ if (uc == 0 && (pushed_string_list != (STRING_SAVER *)NULL))
{
pop_string ();
uc = shell_input_line[shell_input_line_index];
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 (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
+ goto restart_read;
}
- if (!uc && shell_input_line_terminator == EOF)
+ if (uc == 0 && shell_input_line_terminator == EOF)
return ((shell_input_line_index != 0) ? '\n' : EOF);
return (uc);
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) \
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;
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:
case ELSE:
case '{': /* } */
case '(': /* ) */
+ case BANG: /* ! time pipeline */
+ case TIME: /* time time pipeline */
+ case TIMEOPT: /* time -p time pipeline */
+ case TIMEIGN: /* time -p -- ... */
return 1;
default:
return 0;
`}' 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)
/* 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) /* [[ */
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)
* 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
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;
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)
{
if MBTEST(ch == '\\') /* backslashes */
tflags |= LEX_PASSNEXT;
-#if 0
+ /* Based on which dolstate is currently in (param, op, or word),
+ decide what the op is. We're really only concerned if it's % or
+ #, so we can turn on a flag that says whether or not we should
+ treat single quotes as special when inside a double-quoted
+ ${...}. This logic must agree with subst.c:extract_dollar_brace_string
+ since they share the same defines. */
+ if (flags & P_DOLBRACE)
+ {
+ /* ${param%[%]word} */
+ if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ /* ${param#[#]word} */
+ else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ /* ${param/[/]pat/rep} */
+ else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ /* ${param^[^]pat} */
+ else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ /* ${param,[,]pat} */
+ else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
+ dolbrace_state = DOLBRACE_OP;
+ else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
+ dolbrace_state = DOLBRACE_WORD;
+ }
+
/* The big hammer. Single quotes aren't special in double quotes. The
- problem is that Posix says the single quotes are semi-special:
+ problem is that Posix used to say the single quotes are semi-special:
within a double-quoted ${...} construct "an even number of
unescaped double-quotes or single-quotes, if any, shall occur." */
- if MBTEST(open == '{' && (flags & P_DQUOTE) && ch == '\'') /* } */
+ /* This was changed in Austin Group Interp 221 */
+ if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
continue;
-#endif
/* Could also check open == '`' if we want to parse grouping constructs
inside old-style command substitution. */
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);
ret[retind] = '\0';
if (lenp)
*lenp = retind;
+/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/
return ret;
}
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;
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)
{
{
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
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))
{
ret[retind++] = ch;
if ((tflags & LEX_INCOMMENT) && ch == '\n')
+{
+/*itrace("parse_comsub:%d: lex_incomment -> 0 ch = `%c'", line_number, ch);*/
tflags &= ~LEX_INCOMMENT;
+}
continue;
}
}
/* 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);
{
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;
{
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;
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;
}
if (STREQN (ret + retind - 4, "case", 4))
{
tflags |= LEX_INCASE;
-/*itrace("parse_comsub:%d: found `case', lex_incase -> 1", line_number);*/
+/*itrace("parse_comsub:%d: found `case', lex_incase -> 1 lex_reswdok -> 0", line_number);*/
}
else if (STREQN (ret + retind - 4, "esac", 4))
{
tflags &= ~LEX_INCASE;
-/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0", line_number);*/
+/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0 lex_reswdok -> 0", line_number);*/
}
tflags &= ~LEX_RESWDOK;
}
}
}
+ /* Might be the start of a here-doc delimiter */
if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<')
{
/* Add this character. */
/*itrace("parse_comsub:%d: found close: count = %d", line_number, count);*/
}
else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && (tflags & LEX_INCASE) == 0 && ch == open) /* nested begin */
+{
count++;
+/*itrace("parse_comsub:%d: found open: count = %d", line_number, count);*/
+}
/* Add this character. */
RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
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);
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;
/* 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, "=~"))
{
}
/* 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);
{
COND_COM *cexp;
+ global_extglob = extended_glob;
cexp = cond_expr ();
return (make_cond_command (cexp));
}
((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
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 (legal_identifier (token+1))
+ {
+ 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;
case THEN:
case TIME:
case TIMEOPT:
+ case TIMEIGN:
case COPROC:
case UNTIL:
case WHILE:
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;
}
}
/* 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" : "");
+ }
/* First, handle some special cases. */
/*(*/
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
{
char *temp_prompt;
- if (interactive == 0 || expanding_alias()) /* XXX */
+ if (interactive == 0 || expanding_alias ()) /* XXX */
return;
ps1_prompt = get_string_value ("PS1");
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;
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')
}
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 */
{
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
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
{
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;
}
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;
}
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
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)
ps->input_line_terminator = shell_input_line_terminator;
ps->eof_encountered = eof_encountered;
+ ps->prompt_string_pointer = prompt_string_pointer;
+
ps->current_command_line_count = current_command_line_count;
#if defined (HISTORY)
ps->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;
restore_parser_state (ps)
sh_parser_state_t *ps;
{
-#if defined (ARRAY_VARS)
- SHELL_VAR *v;
-#endif
-
if (ps == 0)
return;
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)
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;