/* Yacc grammar for bash. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file LICENSE. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ %{ #include #include #include #include #include "shell.h" #include "flags.h" #define YYDEBUG 1 extern int eof_encountered; extern int no_line_editing; extern int interactive, interactive_shell; extern COMMAND *new_make_simple_command (); extern char *index (); /* **************************************************************** */ /* */ /* "Forward" declarations */ /* */ /* **************************************************************** */ /* This is kind of sickening. In order to let these variables be seen by all the functions that need them, I am forced to place their declarations far away from the place where they should logically be found. */ static int reserved_word_acceptable (); /* PROMPT_STRING_POINTER points to one of these, never to an actual string. */ char *ps1_prompt, *ps2_prompt; /* Handle on the current prompt string. Indirectly points through ps1_ or ps2_prompt. */ char **prompt_string_pointer = (char **)NULL; char *current_prompt_string; /* The number of lines read from input while creating the current command. */ int current_command_line_count = 0; /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ REDIRECT *redirection_needing_here_doc = (REDIRECT *)NULL; int need_here_doc = 0; %} %union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT *element; PATTERN_LIST *pattern; int separator_token; } /* Reserved words. Members of the first group are only recognized in the case that they are preceded by a list_terminator. Members of the second group are recognized only under special circumstances. */ %token IF THEN ELSE ELIF FI CASE ESAC FOR WHILE UNTIL DO DONE FUNCTION %token IN BANG /* More general tokens. yylex () knows how to make these. */ %token WORD ASSIGNMENT_WORD NAME %token NUMBER %token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND %token GREATER_AND SEMI_SEMI LESS_LESS_MINUS AND_GREATER LESS_GREATER %token GREATER_BAR NEWLINE /* The types that the various syntactical units return. */ %type complete_command %type list and_or pipeline pipe_sequence tcommand compound_command %type simple_command subshell compound_list term %type function_definition brace_group %type for_clause case_clause while_clause if_clause until_clause %type do_group else_part %type redirect_list redirect %type wordlist pattern %type separator separator_op linebreak newline_list %type sequential_sep %type case_list case_item %type cmd_prefix cmd_suffix %type cmd_name cmd_word %start inputunit %left yacc_EOF %% inputunit: complete_command NEWLINE { TRACE("reduced complete_command NEWLINE to inputunit"); global_command = $1; YYACCEPT; } | NEWLINE { TRACE("reduced NEWLINE to inputunit"); global_command = (COMMAND *)NULL; YYACCEPT; } ; complete_command : list separator { TRACE("reduced 'list separator' to complete_command"); /* Case of regular command. Discard the error safety net,and return the command just parsed. */ $$ = $1; } | list { TRACE("reduced list to complete_command"); /* Case of regular command. Discard the error safety net,and return the command just parsed. */ $$ = $1; } | error { /* Error during parsing. Return NULL command. */ global_command = (COMMAND *)NULL; eof_encountered = 0; discard_parser_constructs (1); if (interactive) { YYACCEPT; } else { YYABORT; } } | yacc_EOF { /* Case of EOF seen by itself. Do ignoreeof or not. */ global_command = (COMMAND *)NULL; handle_eof_input_unit (); YYACCEPT; } ; list : list separator_op and_or { $$ = command_connect ($1, $3, $2); if (need_here_doc) make_here_document (redirection_needing_here_doc); need_here_doc = 0; } | and_or { $$ = $1; if (need_here_doc) make_here_document (redirection_needing_here_doc); need_here_doc = 0; TRACE("reduced and_or to list"); } ; and_or : pipeline { TRACE("reduced pipeline to and_or"); $$ = $1; } | and_or AND_AND linebreak pipeline { $$ = command_connect ($1, $4, AND_AND); } | and_or OR_OR linebreak pipeline { $$ = command_connect ($1, $4, OR_OR); } ; pipeline: pipe_sequence { TRACE("reduced pipe_sequence to pipeline"); $$ = $1; } | BANG pipe_sequence { $2->flags |= CMD_INVERT_RETURN; $$ = $2; } ; pipe_sequence: tcommand { TRACE("reduced tcommand to pipe_sequence"); $$ = $1; } | pipe_sequence '|' linebreak tcommand { $$ = command_connect ($1, $4, '|'); } ; tcommand: simple_command { TRACE("reduced simple_command to tcommand"); $$ = clean_simple_command ($1); } | compound_command { $$ = $1; } | compound_command redirect_list { $1->redirects = $2; $$ = $1; } | function_definition { $$ = $1; } ; compound_command : brace_group { $$ = $1; } | subshell { $$ = $1; } | for_clause { TRACE("reduced FOR_CLAUSE to COMPOUND_COMMAND"); $$ = $1; } | case_clause { $$ = $1; } | if_clause { TRACE("reduced IF_CLAUSE to COMPOUND_COMMAND"); $$ = $1; } | while_clause { TRACE("reduced WHILE_CLAUSE to COMPOUND_COMMAND"); $$ = $1; } | until_clause { $$ = $1; } ; subshell: '(' list ')' { $2->flags |= CMD_WANT_SUBSHELL; $$ = $2; } ; compound_list : term { $$ = $1; } | newline_list term { $$ = $2; } | term separator { $$ = command_connect ($1, 0, $2); } | newline_list term separator { $$ = command_connect ($2, 0, $3); } ; term : term separator and_or { $$ = command_connect ($1, $3, $2); } | and_or { $$ = $1; } ; for_clause : FOR WORD do_group { $$ = make_for_command ($2, (WORD_LIST *)add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $3); } | FOR WORD newline_list IN wordlist sequential_sep do_group { $$ = make_for_command ($2, (WORD_LIST *)reverse_list ($5), $7); } | FOR WORD IN wordlist sequential_sep do_group { TRACE("reduced FOR_CLAUSE"); $$ = make_for_command ($2, (WORD_LIST *)reverse_list ($4), $6); } ; wordlist: wordlist WORD { $$ = make_word_list ($2, $1); } | WORD { $$ = make_word_list ($1, (WORD_LIST *)NULL); } ; case_clause : CASE WORD IN linebreak case_list ESAC { $$ = make_case_command ($2, $5); } | CASE WORD IN linebreak ESAC { $$ = make_case_command ($2, (PATTERN_LIST *)NULL); } ; case_list : case_list case_item { $2->next = $1; $$ = $2; } | case_item { $$ = $1; } ; case_item : pattern ')' linebreak SEMI_SEMI linebreak { $$ = make_pattern_list ($1, (COMMAND *)NULL); } | pattern ')' compound_list SEMI_SEMI linebreak { $$ = make_pattern_list ($1, $3); } | '(' pattern ')' compound_list SEMI_SEMI linebreak { $$ = make_pattern_list ($2, $4); } | '(' pattern ')' linebreak SEMI_SEMI linebreak { $$ = make_pattern_list ($2, (COMMAND *)NULL); } ; pattern: WORD { $$ = make_word_list ($1, (WORD_LIST *)NULL); } | pattern '|' WORD { $$ = make_word_list ($3, $1); } ; if_clause: IF compound_list THEN compound_list else_part FI { $$ = make_if_command ($2, $4, $5); } | IF compound_list THEN compound_list FI { $$ = make_if_command ($2, $4, (COMMAND *)NULL); } ; else_part: ELIF compound_list THEN else_part { $$ = make_if_command ($2, $4, (COMMAND *)NULL); } | ELSE compound_list { $$ = $2; } ; while_clause: WHILE compound_list do_group { TRACE("reduced WHILE_CLAUSE"); $$ = make_while_command ($2, $3); } ; until_clause: UNTIL compound_list do_group { $$ = make_until_command ($2, $3); } ; function_definition : WORD '(' ')' linebreak brace_group { $$ = make_function_def ($1, $5); } ; brace_group: '{' compound_list '}' { $$ = make_group_command ($2); } ; do_group: DO compound_list DONE { TRACE("reduced DO_GROUP"); $$ = $2; } ; cmd_prefix: ASSIGNMENT_WORD { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = $1; temp_element->redirect = (REDIRECT *)NULL; temp_element->next = (struct element *)NULL; TRACE("reduced cmd_prefix to ASSIGNMENT_WORD"); $$ = temp_element; } | cmd_prefix ASSIGNMENT_WORD { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = $2; temp_element->redirect = (REDIRECT *)NULL; temp_element->next = (struct element *)NULL; $1->next = temp_element; TRACE("reduced cmd_prefix to cmd_prefix ASSIGNMENT_WORD"); $$ = $1; } | redirect { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = (WORD_DESC *)NULL; temp_element->redirect = $1; temp_element->next = (struct element *)NULL; TRACE("reduced cmd_prefix to redirect"); $$ = temp_element; } | cmd_prefix redirect { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = (WORD_DESC *)NULL; temp_element->redirect = $2; temp_element->next = (struct element *)NULL; $1->next = temp_element; TRACE("reduced cmd_prefix to cmd_prefix redirect"); $$ = $1; } ; cmd_name: WORD { TRACE ("reduced WORD to cmd_name"); $$ = $1; } ; cmd_word: WORD { TRACE ("reduced WORD to cmd_word"); $$ = $1; } ; cmd_suffix: WORD { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = $1; temp_element->redirect = (REDIRECT *)NULL; temp_element->next = (struct element *)NULL; TRACE("reduced cmd_suffix to WORD"); $$ = temp_element; } | cmd_suffix WORD { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = $2; temp_element->redirect = (REDIRECT *)NULL; temp_element->next = (struct element *)NULL; TRACE("reduced cmd_suffix to cmd_suffix WORD"); $1->next = temp_element; $$ = $1; } | redirect { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = (WORD_DESC *)NULL; temp_element->redirect = $1; temp_element->next = (struct element *)NULL; TRACE("reduced cmd_sufffix to redirect"); $$ = temp_element; } | cmd_suffix redirect { struct element *temp_element; temp_element = (ELEMENT *)xmalloc (sizeof (ELEMENT)); temp_element->word = (WORD_DESC *)NULL; temp_element->redirect = $2; temp_element->next = (struct element *)NULL; $1->next = temp_element; TRACE("reduced cmd_suffix to cmd_suffix redirect"); $$ = $1; } ; simple_command: cmd_prefix cmd_word cmd_suffix { $$ = new_make_simple_command ($2, $1, $3); } | cmd_prefix cmd_word { $$ = new_make_simple_command ($2, $1, (ELEMENT *)NULL); } | cmd_prefix { $$ = new_make_simple_command ((WORD_DESC *)NULL, $1, (ELEMENT *)NULL); } | cmd_name cmd_suffix { $$ = new_make_simple_command ($1, (ELEMENT *)NULL, $2); } | cmd_name { TRACE("reduced cmd_name to simple_command"); $$ = new_make_simple_command ($1, (ELEMENT *)NULL, (ELEMENT *)NULL); } ; redirect_list: redirect { $$ = $1; } | redirect_list redirect { register REDIRECT *t = $1; while (t->next) t = t->next; t->next = $2; $$ = $1; } ; redirect: '>' WORD { $$ = make_redirection ( 1, r_output_direction, $2); } | '<' WORD { $$ = make_redirection ( 0, r_input_direction, $2); } | NUMBER '>' WORD { $$ = make_redirection ($1, r_output_direction, $3); } | NUMBER '<' WORD { $$ = make_redirection ($1, r_input_direction, $3); } | GREATER_GREATER WORD { $$ = make_redirection ( 1, r_appending_to, $2); } | NUMBER GREATER_GREATER WORD { $$ = make_redirection ($1, r_appending_to, $3); } | LESS_AND NUMBER { $$ = make_redirection ( 0, r_duplicating_input, $2); } | NUMBER LESS_AND NUMBER { $$ = make_redirection ($1, r_duplicating_input, $3); } | GREATER_AND NUMBER { $$ = make_redirection ( 1, r_duplicating_output, $2); } | NUMBER GREATER_AND NUMBER { $$ = make_redirection ($1, r_duplicating_output, $3); } | LESS_AND WORD { $$ = make_redirection ( 0, r_duplicating_input_word, $2); } | NUMBER LESS_AND WORD { $$ = make_redirection ($1, r_duplicating_input_word, $3); } | GREATER_AND WORD { $$ = make_redirection ( 1, r_duplicating_output_word, $2); } | NUMBER GREATER_AND WORD { $$ = make_redirection ($1, r_duplicating_output_word, $3); } | GREATER_AND '-' { $$ = make_redirection ( 1, r_close_this, 0); } | NUMBER GREATER_AND '-' { $$ = make_redirection ($1, r_close_this, 0); } | LESS_AND '-' { $$ = make_redirection ( 0, r_close_this, 0); } | NUMBER LESS_AND '-' { $$ = make_redirection ($1, r_close_this, 0); } | AND_GREATER WORD { $$ = make_redirection ( 1, r_err_and_out, $2); } | NUMBER LESS_GREATER WORD { $$ = make_redirection ( $1, r_input_output, $3); } | LESS_GREATER WORD { REDIRECT *t1, *t2; extern WORD_DESC *copy_word (); t1 = make_redirection ( 0, r_input_direction, $2); t2 = make_redirection ( 1, r_output_direction, copy_word ($2)); t1->next = t2; $$ = t1; } | GREATER_BAR WORD { $$ = make_redirection ( 1, r_output_force, $2); } | NUMBER GREATER_BAR WORD { $$ = make_redirection ( $1, r_output_force, $3); } | LESS_LESS WORD { $$ = make_redirection ( 0, r_reading_until, $2); redirection_needing_here_doc = $$; need_here_doc = 1; } | NUMBER LESS_LESS WORD { $$ = make_redirection ($1, r_reading_until, $3); redirection_needing_here_doc = $$; need_here_doc = 1; } | LESS_LESS_MINUS WORD { $$ = make_redirection ( 0, r_deblank_reading_until, $2); redirection_needing_here_doc = $$; need_here_doc = 1; } | NUMBER LESS_LESS_MINUS WORD { $$ = make_redirection ($1, r_deblank_reading_until, $3); redirection_needing_here_doc = $$; need_here_doc = 1; } ; newline_list: NEWLINE { TRACE("reduced NEWLINE to newline_list"); $$ = NEWLINE; /* XXX -- broken */ } | newline_list NEWLINE { TRACE("reduced newline_list newline to newline_list"); $$ = NEWLINE; /* XXX -- broken */ } ; linebreak: newline_list { TRACE("reduced newline_list to linebreak"); $$ = NEWLINE; /* XXX -- broken */ } | { TRACE("reduced empty to linebreak"); $$ = NEWLINE; /* XXX -- broken */ } ; separator_op: '&' { TRACE("reducing '&' to separator_op"); $$ = '&'; /* XXX -- broken */ } | ';' { TRACE("reducing ';' to separator_op"); $$ = ';'; /* XXX -- broken */ } ; separator: separator_op linebreak { TRACE("reduced separator_op linebreak to separator"); $$ = $1; /* XXX -- broken */ } | newline_list { TRACE("reduced newline_list to separator"); $$ = ';'; /* XXX -- broken */ } ; sequential_sep: ';' linebreak { TRACE("reduced ';' linebreak to sequential_sep"); $$ = ';'; /* XXX -- broken */ } | newline_list { TRACE("reduced newline_list to sequential_sep: ';'"); $$ = ';'; /* XXX -- broken */ } ; %% /* Initial size to allocate for tokens, and the amount to grow them by. */ #define TOKEN_DEFAULT_GROW_SIZE 512 /* The token currently being read. */ int current_token = 0; /* The last read token, or NULL. read_token () uses this for context checking. */ int last_read_token = 0; /* The token read prior to last_read_token. */ int token_before_that = 0; /* Global var is non-zero when end of file has been reached. */ int EOF_Reached = 0; /* yy_getc () returns the next available character from input or EOF. yy_ungetc (c) makes `c' the next character to read. init_yy_io (get, unget), makes the function `get' the installed function for getting the next character, and makes `unget' the installed function for un-getting a character. */ return_EOF () /* does nothing good. */ { return (EOF); } /* Variables containing the current get and unget functions. */ /* Some stream `types'. */ #define st_stream 0 #define st_string 1 Function *get_yy_char = return_EOF; Function *unget_yy_char = return_EOF; int yy_input_type = st_stream; FILE *yy_input_dev = (FILE *)NULL; /* The current stream name. In the case of a file, this is a filename. */ char *stream_name = (char *)NULL; /* Function to set get_yy_char and unget_yy_char. */ init_yy_io (get_function, unget_function, type, location) Function *get_function, *unget_function; int type; FILE *location; { get_yy_char = get_function; unget_yy_char = unget_function; yy_input_type = type; yy_input_dev = location; } /* Call this to get the next character of input. */ yy_getc () { return (*get_yy_char) (); } /* Call this to unget C. That is, to make C the next character to be read. */ yy_ungetc (c) { return (*unget_yy_char) (c); } with_input_from_stdin () { with_input_from_stream (stdin, "stdin"); } /* **************************************************************** */ /* */ /* Let input come from STRING. STRING is zero terminated. */ /* */ /* **************************************************************** */ int yy_string_get () { /* If the string doesn't exist, or is empty, EOF found. */ if (!(char *)yy_input_dev || !*(char *)yy_input_dev) return (EOF); else { register char *temp = (char *)yy_input_dev; int c = *temp++; yy_input_dev = (FILE *)temp; return (c); } } int yy_string_unget (c) int c; { register char *temp = (char *)yy_input_dev; *(--temp) = c; yy_input_dev = (FILE *)temp; return (c); } with_input_from_string (string, name) char *string; char *name; { init_yy_io (yy_string_get, yy_string_unget, st_string, (FILE *)string); stream_name = savestring (name); } /* **************************************************************** */ /* */ /* Let input come from STREAM. */ /* */ /* **************************************************************** */ int yy_stream_get () { if (yy_input_dev) return (getc (yy_input_dev)); else return (EOF); } int yy_stream_unget (c) int c; { return (ungetc (c, yy_input_dev)); } with_input_from_stream (stream, name) FILE *stream; char *name; { init_yy_io (yy_stream_get, yy_stream_unget, st_stream, stream); stream_name = savestring (name); } typedef struct stream_saver { struct stream_saver *next; Function *getter, *putter; int type, line; char *location, *name; } STREAM_SAVER; /* The globally known line number. */ int line_number = 0; STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL; push_stream () { STREAM_SAVER *temp = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER)); temp->type = yy_input_type; temp->location = (char *)yy_input_dev; temp->getter = get_yy_char; temp->putter = unget_yy_char; temp->line = line_number; temp->name = stream_name; stream_name = (char *)NULL; temp->next = stream_list; stream_list = temp; EOF_Reached = line_number = 0; } pop_stream () { if (!stream_list) { EOF_Reached = 1; } else { STREAM_SAVER *temp = stream_list; EOF_Reached = 0; stream_list = stream_list->next; if (stream_name) free (stream_name); stream_name = temp->name; init_yy_io (temp->getter, temp->putter, temp->type, (FILE *)temp->location); line_number = temp->line; free (temp); } } static int in_case_pattern_list = 0; /* Return a line of text, taken from wherever yylex () reads input. If there is no more input, then we return NULL. */ char * read_a_line () { char *line_buffer = (char *)NULL; int indx = 0, buffer_size = 0; int c; while (1) { c = yy_getc (); if (c == 0) continue; /* If there is no more input, then we return NULL. */ if (c == EOF) { c = '\n'; if (!line_buffer) return ((char *)NULL); } /* `+2' in case the final (200'th) character in the buffer is a newline; otherwise the code below that NULL-terminates it will write over the 201st slot and kill the range checking in free(). */ if (indx + 2 > buffer_size) if (!buffer_size) line_buffer = (char *)xmalloc (buffer_size = 200); else line_buffer = (char *)xrealloc (line_buffer, buffer_size += 200); line_buffer[indx++] = c; if (c == '\n') { line_buffer[indx] = '\0'; return (line_buffer); } } } /* Return a line as in read_a_line (), but insure that the prompt is the secondary prompt. */ char * read_secondary_line () { prompt_string_pointer = &ps2_prompt; prompt_again (); return (read_a_line ()); } /* **************************************************************** */ /* */ /* YYLEX () */ /* */ /* **************************************************************** */ /* Reserved words. These are only recognized as the first word of a command. TOKEN_WORD_ALIST. */ STRING_INT_ALIST word_token_alist[] = { {"if", IF}, {"then", THEN}, {"else", ELSE}, {"elif", ELIF}, {"fi", FI}, {"case", CASE}, {"esac", ESAC}, {"for", FOR}, {"while", WHILE}, {"until", UNTIL}, {"do", DO}, {"done", DONE}, {"in", IN}, {"function", FUNCTION}, {"{", '{'}, {"}", '}'}, {"!", BANG}, {(char *)NULL, 0} }; /* Where shell input comes from. History expansion is performed on each line when the shell is interactive. */ char *shell_input_line = (char *)NULL; int shell_input_line_index = 0; int shell_input_line_size = 0; /* Amount allocated for shell_input_line. */ int shell_input_line_len = 0; /* strlen (shell_input_line) */ /* Either zero, or EOF. */ int shell_input_line_terminator = 0; /* Return the next shell input character. This always reads characters from shell_input_line; when that line is exhausted, it is time to read the next line. */ int shell_getc (remove_quoted_newline) int remove_quoted_newline; { int c; QUIT; if (!shell_input_line || !shell_input_line[shell_input_line_index]) { register int i, l; char *pre_process_line (), *expansions; restart_read_next_line: line_number++; restart_read: i = 0; shell_input_line_terminator = 0; clearerr (stdin); while (c = yy_getc ()) { if (i + 2 > shell_input_line_size) shell_input_line = (char *) xrealloc (shell_input_line, shell_input_line_size += 256); if (c == EOF) { clearerr (stdin); if (!i) shell_input_line_terminator = EOF; shell_input_line[i] = '\0'; break; } shell_input_line[i++] = c; if (c == '\n') { shell_input_line[--i] = '\0'; current_command_line_count++; break; } } shell_input_line_index = 0; shell_input_line_len = i; /* == strlen (shell_input_line) */ if (!shell_input_line || !shell_input_line[0]) goto after_pre_process; if (interactive) { expansions = pre_process_line (shell_input_line, 1, 1); free (shell_input_line); shell_input_line = expansions; shell_input_line_len = shell_input_line ? strlen (shell_input_line) : 0; /* We have to force the xrealloc below because we don't know the true allocated size of shell_input_line anymore. */ shell_input_line_size = shell_input_line_len; } after_pre_process: #if 0 if (shell_input_line) { fprintf (stderr, "%s\n", shell_input_line); } else { shell_input_line_size = 0; prompt_string_pointer = ¤t_prompt_string; prompt_again (); goto restart_read; } #endif /* Add the newline to the end of this string, iff the string does not already end in an EOF character. */ if (shell_input_line_terminator != EOF) { l = shell_input_line_len; /* was a call to strlen */ if (l + 3 > shell_input_line_size) shell_input_line = (char *)xrealloc (shell_input_line, 1 + (shell_input_line_size += 2)); strcpy (shell_input_line + l, "\n"); } } c = shell_input_line[shell_input_line_index]; if (c) shell_input_line_index++; if (c == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n') { prompt_again (); goto restart_read_next_line; } if (!c && shell_input_line_terminator == EOF) { if (shell_input_line_index != 0) return (NEWLINE); else return (EOF); } return (c); } /* Put C back into the input for the shell. */ shell_ungetc (c) int c; { if (shell_input_line && shell_input_line_index) shell_input_line[--shell_input_line_index] = c; } /* Discard input until CHARACTER is seen. */ discard_until (character) int character; { int c; while ((c = shell_getc (0)) != EOF && c != character) ; if (c != EOF ) shell_ungetc (c); } #if defined (HISTORY_REEDITING) /* Tell readline () that we have some text for it to edit. */ re_edit (text) char *text; { #if defined (READLINE) if (strcmp (stream_name, "readline stdin") == 0) bash_re_edit (text); #endif /* READLINE */ } #endif /* HISTORY_REEDITING */ /* Non-zero means do no history expansion on this line, regardless of what history_expansion says. */ int history_expansion_inhibited = 0; /* Do pre-processing on LINE. If PRINT_CHANGES is non-zero, then print the results of expanding the line if there were any changes. If there is an error, return NULL, otherwise the expanded line is returned. If ADDIT is non-zero the line is added to the history list after history expansion. ADDIT is just a suggestion; REMEMBER_ON_HISTORY can veto, and does. Right now this does history expansion. */ char * pre_process_line (line, print_changes, addit) char *line; int print_changes, addit; { char *return_value; return_value = savestring (line); return (return_value); } /* Place to remember the token. We try to keep the buffer at a reasonable size, but it can grow. */ char *token = (char *)NULL; /* Current size of the token buffer. */ int token_buffer_size = 0; /* Command to read_token () explaining what we want it to do. */ #define READ 0 #define RESET 1 #define prompt_is_ps1 \ (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt) /* Function for yyparse to call. yylex keeps track of the last two tokens read, and calls read_token. */ yylex () { if (interactive && (!current_token || current_token == NEWLINE)) { prompt_again (); } token_before_that = last_read_token; last_read_token = current_token; current_token = read_token (READ); return (current_token); } /* Called from shell.c when Control-C is typed at top level. Or by the error rule at top level. */ reset_parser () { read_token (RESET); } /* When non-zero, we have read the required tokens which allow ESAC to be the next one read. */ static int allow_esac_as_next = 0; /* When non-zero, accept single '{' as a token itself. */ static int allow_open_brace = 0; /* DELIMITER is the value of the delimiter that is currently enclosing, or zero for none. */ static int delimiter = 0; static int old_delimiter = 0; /* When non-zero, an open-brace used to create a group is awaiting a close brace partner. */ static int open_brace_awaiting_satisfaction = 0; /* If non-zero, it is the token that we want read_token to return regardless of what text is (or isn't) present to be read. read_token resets this. */ int token_to_read = 0; /* Read the next token. Command can be READ (normal operation) or RESET (to normalize state). */ read_token (command) int command; { extern int interactive_shell; /* Whether the current shell is interactive. */ int character; /* Current character. */ int peek_char; /* Temporary look-ahead character. */ int result; /* The thing to return. */ WORD_DESC *the_word; /* The value for YYLVAL when a WORD is read. */ if (token_buffer_size < TOKEN_DEFAULT_GROW_SIZE) { if (token) free (token); token = (char *)xmalloc (token_buffer_size = TOKEN_DEFAULT_GROW_SIZE); } if (command == RESET) { delimiter = old_delimiter = 0; open_brace_awaiting_satisfaction = 0; in_case_pattern_list = 0; if (shell_input_line) { free (shell_input_line); shell_input_line = (char *)NULL; shell_input_line_size = shell_input_line_index = 0; } last_read_token = NEWLINE; token_to_read = NEWLINE; return (NEWLINE); } if (token_to_read) { int rt = token_to_read; token_to_read = 0; return (rt); } /* Read a single word from input. Start by skipping blanks. */ while ((character = shell_getc (1)) != EOF && whitespace (character)); if (character == EOF) return (yacc_EOF); if (character == '#' && !interactive) { /* A comment. Discard until EOL or EOF, and then return a newline. */ discard_until ('\n'); shell_getc (0); /* If we're about to return an unquoted newline, we can go and collect the text of any pending here document. */ if (need_here_doc) make_here_document (redirection_needing_here_doc); need_here_doc = 0; return (NEWLINE); } if (character == '\n') { /* If we're about to return an unquoted newline, we can go and collect the text of any pending here document. */ if (need_here_doc) make_here_document (redirection_needing_here_doc); need_here_doc = 0; TRACE ("read_token: returning NEWLINE"); return (NEWLINE); } if (member (character, "()<>;&|")) { /* Please note that the shell does not allow whitespace to appear in between tokens which are character pairs, such as "<<" or ">>". I believe this is the correct behaviour. */ if (character == (peek_char = shell_getc (1))) { switch (character) { /* If '<' then we could be at "<<" or at "<<-". We have to look ahead one more character. */ case '<': peek_char = shell_getc (1); if (peek_char == '-') return (LESS_LESS_MINUS); else { shell_ungetc (peek_char); return (LESS_LESS); } case '>': return (GREATER_GREATER); case ';': in_case_pattern_list = 1; return (SEMI_SEMI); case '&': return (AND_AND); case '|': return (OR_OR); } } else { if (peek_char == '&') { switch (character) { case '<': return (LESS_AND); case '>': return (GREATER_AND); } } if (character == '<' && peek_char == '>') return (LESS_GREATER); if (character == '>' && peek_char == '|') return (GREATER_BAR); if (peek_char == '>' && character == '&') return (AND_GREATER); } shell_ungetc (peek_char); /* If we look like we are reading the start of a function definition, then let the reader know about it so that we will do the right thing with `{'. */ if (character == ')' && last_read_token == '(' && token_before_that == WORD) { allow_open_brace = 1; } if (in_case_pattern_list && (character == ')')) in_case_pattern_list = 0; #if defined (PROCESS_SUBSTITUTION) /* If we are performing process substitution, let <( and >( by and make a word. */ if (!((character == '>' || character == '<') && peek_char == '(')) #endif /* PROCESS_SUBSTITUTION */ TRACE("read_token: returning '%c'", character); return (character); } /* Hack <&- (close stdin) case. */ if (character == '-') { switch (last_read_token) { case LESS_AND: case GREATER_AND: return (character); } } /* Okay, if we got this far, we have to read a word. Read one, and then check it against the known ones. */ { /* Index into the token that we are building. */ int token_index = 0; /* ALL_DIGITS becomes zero when we see a non-digit. */ int all_digits = digit (character); /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */ int dollar_present = 0; /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */ int quoted = 0; /* Non-zero means to ignore the value of the next character, and just to add it no matter what. */ int pass_next_character = 0; /* Non-zero means parsing a dollar-paren construct. It is the count of un-quoted closes we need to see. */ int dollar_paren_level = 0; /* Non-zero means parsing a dollar-bracket construct ($[...]). It is the count of un-quoted `]' characters we need to see. */ int dollar_bracket_level = 0; /* Another level variable. This one is for dollar_parens inside of double-quotes. */ int delimited_paren_level = 0; for (;;) { if (character == EOF) goto got_token; if (pass_next_character) { pass_next_character = 0; goto got_character; } if (delimiter && character == '\\' && delimiter != '\'') { peek_char = shell_getc (0); if (peek_char != '\\') shell_ungetc (peek_char); else { token[token_index++] = character; goto got_character; } } /* Handle backslashes. Quote lots of things when not inside of double-quotes, quote some things inside of double-quotes. */ if (character == '\\' && delimiter != '\'') { peek_char = shell_getc (0); /* Backslash-newline is ignored in all cases excepting when quoted with single quotes. */ if (peek_char == '\n') { character = '\n'; goto next_character; } else { shell_ungetc (peek_char); /* If the next character is to be quoted, do it now. */ if (!delimiter || delimiter == '`' || ((delimiter == '"' ) && (member (peek_char, slashify_in_quotes)))) { pass_next_character++; quoted = 1; goto got_character; } } } /* This is a hack, in its present form. If a backquote substitution appears within double quotes, everything within the backquotes should be read as part of a single word. Jesus. Now I see why Korn introduced the $() form. */ if (delimiter && delimiter == '"' && character == '`') { old_delimiter = delimiter; delimiter = character; goto got_character; } if (delimiter) { if (character == delimiter) { if (delimited_paren_level) { #if defined (NOTDEF) report_error ("Expected ')' before %c", character); return (NEWLINE); #else goto got_character; #endif /* NOTDEF */ } delimiter = 0; if (old_delimiter == '"' && character == '`') { delimiter = old_delimiter; old_delimiter = 0; } goto got_character; } } if (!delimiter || delimiter == '`' || delimiter == '"') { #if defined (PROCESS_SUBSTITUTION) if (character == '$' || character == '<' || character == '>') #else if (character == '$') #endif /* PROCESS_SUBSTITUTION */ { peek_char = shell_getc (1); shell_ungetc (peek_char); if (peek_char == '(') { if (!delimiter) dollar_paren_level++; else delimited_paren_level++; pass_next_character++; goto got_character; } else if (peek_char == '[') { if (!delimiter) dollar_bracket_level++; pass_next_character++; goto got_character; } } /* If we are parsing a $() or $[] construct, we need to balance parens and brackets inside the construct. This whole function could use a rewrite. */ if (character == '(') { if (delimiter && delimited_paren_level) delimited_paren_level++; if (!delimiter && dollar_paren_level) dollar_paren_level++; } if (character == '[') { if (!delimiter && dollar_bracket_level) dollar_bracket_level++; } /* This code needs to take into account whether we are inside a case statement pattern list, and whether this paren is supposed to terminate it (hey, it could happen). It's not as simple as just using in_case_pattern_list, because we're not parsing anything while we're reading a $( ) construct. Maybe we should move that whole mess into the yacc parser. */ if (character == ')') { if (delimiter && delimited_paren_level) delimited_paren_level--; if (!delimiter && dollar_paren_level) { dollar_paren_level--; goto got_character; } } if (character == ']') { if (!delimiter && dollar_bracket_level) { dollar_bracket_level--; goto got_character; } } } if (!dollar_paren_level && !dollar_bracket_level && !delimiter && member (character, " \t\n;&()|<>")) { shell_ungetc (character); goto got_token; } if (!delimiter) { if (character == '"' || character == '`' || character == '\'') { quoted = 1; delimiter = character; goto got_character; } } if (all_digits) all_digits = digit (character); if (character == '$') dollar_present = 1; got_character: token[token_index++] = character; if (token_index == (token_buffer_size - 1)) token = (char *)xrealloc (token, (token_buffer_size += TOKEN_DEFAULT_GROW_SIZE)); { char *decode_prompt_string (); next_character: if (character == '\n' && interactive && yy_input_type != st_string) prompt_again (); } /* We want to remove quoted newlines (that is, a \ pair) unless we are within single quotes or pass_next_character is set (the shell equivalent of literal-next). */ character = shell_getc ((delimiter != '\'') && (!pass_next_character)); } got_token: token[token_index] = '\0'; if ((delimiter || dollar_paren_level || dollar_bracket_level) && character == EOF) { if (dollar_paren_level && !delimiter) delimiter = ')'; else if (dollar_bracket_level && !delimiter) delimiter = ']'; report_error ("Unexpected EOF. Looking for `%c'.", delimiter); return (-1); } if (all_digits) { /* Check to see what thing we should return. If the last_read_token is a `<', or a `&', or the character which ended this token is a '>' or '<', then, and ONLY then, is this input token a NUMBER. Otherwise, it is just a word, and should be returned as such. */ if ((character == '<' || character == '>') || (last_read_token == LESS_AND || last_read_token == GREATER_AND)) { yylval.number = atoi (token); /* was sscanf (token, "%d", &(yylval.number)); */ return (NUMBER); } } /* Handle special case. IN is recognized if the last token was WORD and the token before that was FOR or CASE. */ if ((last_read_token == WORD) && ((token_before_that == FOR) || (token_before_that == CASE)) && (STREQ (token, "in"))) { if (token_before_that == CASE) { in_case_pattern_list = 1; allow_esac_as_next++; } TRACE("returning %s as IN", token); return (IN); } /* Ditto for DO in the FOR case. */ if ((last_read_token == WORD) && (token_before_that == FOR) && (STREQ (token, "do"))) return (DO); /* Ditto for ESAC in the CASE case. Specifically, this handles "case word in esac", which is a legal construct, certainly because someone will pass an empty arg to the case construct, and we don't want it to barf. Of course, we should insist that the case construct has at least one pattern in it, but the designers disagree. */ if (allow_esac_as_next) { allow_esac_as_next--; if (STREQ (token, "esac")) { in_case_pattern_list = 0; return (ESAC); } } /* Ditto for `{' in the FUNCTION case. */ if (allow_open_brace) { allow_open_brace = 0; if (STREQ (token, "{")) { open_brace_awaiting_satisfaction++; return ('{'); } } /* Check to see if it is a reserved word. */ if (!dollar_present && !quoted && reserved_word_acceptable (last_read_token)) { int i; for (i = 0; word_token_alist[i].word != (char *)NULL; i++) if (STREQ (token, word_token_alist[i].word)) { if (in_case_pattern_list && (word_token_alist[i].token != ESAC)) break; if (word_token_alist[i].token == ESAC) in_case_pattern_list = 0; if (word_token_alist[i].token == '{') open_brace_awaiting_satisfaction++; TRACE("returning %s as %d", token, word_token_alist[i].token); return (word_token_alist[i].token); } } /* What if we are attempting to satisfy an open-brace grouper? */ if (open_brace_awaiting_satisfaction && strcmp (token, "}") == 0) { open_brace_awaiting_satisfaction--; return ('}'); } the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); the_word->word = (char *)xmalloc (1 + strlen (token)); strcpy (the_word->word, token); the_word->dollar_present = dollar_present; the_word->quoted = quoted; the_word->assignment = assignment (token); yylval.word = the_word; result = WORD; if ((last_read_token == ASSIGNMENT_WORD || command_word_acceptable (last_read_token)) && the_word->assignment && token[0] != '=' && legal_assignment (token)) result = ASSIGNMENT_WORD; TRACE("read_token: returning %s as %s", token, (result == WORD) ? "WORD" : result == NAME ? "NAME" : "ASSIGNMENT_WORD"); if (last_read_token == FUNCTION) allow_open_brace = 1; } return (result); } legal_assignment (s) char *s; { extern char *index (); char *t = index (s, '='); int result; *t = '\0'; result = legal_identifier (s); *t = '='; return (result); } /* Return 1 if this token is a legal shell `identifier'; that is, it consists solely of letters, digits, and underscores, and does not begin with a digit. */ legal_identifier (name) char *name; { register char *s; if (!name || !*name) return (0); if (digit (*name)) return (0); for (s = name; s && *s; s++) { if (!isletter (*s) && !digit (*s) && (*s != '_')) return (0); } return (1); } /* Return 1 if TOKEN is a token that after being read would allow a reserved word to be seen, else 0. */ static int reserved_word_acceptable (token) int token; { if (member (token, "\n;()|&{") || token == AND_AND || token == BANG || token == DO || token == ELIF || token == ELSE || token == IF || token == FI || token == ESAC || token == OR_OR || token == SEMI_SEMI || token == THEN || token == UNTIL || token == WHILE || token == 0) return (1); else return (0); } /* Return 1 if TOKEN is a token that after being read would allow a reserved word to be seen, else 0. */ static int command_word_acceptable (token) int token; { if (member (token, "\n;()|&{") || token == AND_AND || token == BANG || token == DO || token == ELIF || token == ELSE || token == IF || token == OR_OR || token == THEN || token == UNTIL || token == WHILE || token == 0) return (1); else return (0); } reset_readline_prompt () { } /* Add a line to the history list. The variable COMMAND_ORIENTED_HISTORY controls the style of history remembering; when non-zero, and LINE is not the first line of a complete parser construct, append LINE to the last history line instead of adding it as a new line. */ bash_add_history (line) char *line; { } /* Issue a prompt, or prepare to issue a prompt when the next character is read. */ prompt_again () { char *temp_prompt, *decode_prompt_string (); ps1_prompt = "posix$ "; ps2_prompt = "> "; if (!prompt_string_pointer) prompt_string_pointer = &ps1_prompt; current_prompt_string = *prompt_string_pointer; prompt_string_pointer = &ps2_prompt; fprintf (stderr, "%s", current_prompt_string); fflush (stderr); } char * decode_prompt_string (string) char *string; { char *result; result = savestring (string); return (result); } /* Report a syntax error, and restart the parser. Call here for fatal errors. */ yyerror () { report_syntax_error ((char *)NULL); reset_parser (); } /* Report a syntax error with line numbers, etc. Call here for recoverable errors. If you have a message to print, then place it in MESSAGE, otherwise pass NULL and this will figure out an appropriate message for you. */ report_syntax_error (message) char *message; { if (message) { if (!interactive) { char *name = stream_name ? stream_name : "stdin"; report_error ("%s:%d: `%s'", name, line_number, message); } else report_error ("%s", message); return; } if (shell_input_line && *shell_input_line) { char *error_token, *t = shell_input_line; register int i = shell_input_line_index; int token_end = 0; if (!t[i] && i) i--; while (i && (t[i] == ' ' || t[i] == '\t' || t[i] == '\n')) i--; if (i) token_end = i + 1; while (i && !member (t[i], " \n\t;|&")) i--; while (i != token_end && member (t[i], " \n\t")) i++; if (token_end) { error_token = (char *)alloca (1 + (token_end - i)); strncpy (error_token, t + i, token_end - i); error_token[token_end - i] = '\0'; report_error ("syntax error near `%s'", error_token); } else if ((i == 0) && (token_end == 0)) /* a 1-character token */ { error_token = (char *) alloca (2); strncpy(error_token, t + i, 1); error_token[1] = '\0'; report_error ("syntax error near `%s'", error_token); } if (!interactive) { char *temp = savestring (shell_input_line); char *name = stream_name ? stream_name : "stdin"; int l = strlen (temp); while (l && temp[l - 1] == '\n') temp[--l] = '\0'; report_error ("%s:%d: `%s'", name, line_number, temp); free (temp); } } else report_error ("Syntax error"); } /* ??? Needed function. ??? We have to be able to discard the constructs created during parsing. In the case of error, we want to return allocated objects to the memory pool. In the case of no error, we want to throw away the information about where the allocated objects live. (dispose_command () will actually free the command. */ discard_parser_constructs (error_p) int error_p; { /* if (error_p) { fprintf (stderr, "*"); } */ } handle_eof_input_unit () { EOF_Reached = 1; } TRACE(va_alist) va_dcl { va_list args; char *format; va_start (args); fprintf(stderr, "TRACE: "); format = va_arg (args, char *); vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); }