diff options
Diffstat (limited to 'parser.cpp')
-rw-r--r-- | parser.cpp | 3477 |
1 files changed, 434 insertions, 3043 deletions
@@ -17,7 +17,6 @@ The fish parser. Contains functions for parsing and evaluating code. #include <termios.h> #include <pwd.h> #include <dirent.h> -#include <signal.h> #include <algorithm> #include "fallback.h" @@ -37,28 +36,14 @@ The fish parser. Contains functions for parsing and evaluating code. #include "expand.h" #include "reader.h" #include "sanity.h" -#include "env_universal.h" #include "event.h" #include "intern.h" #include "parse_util.h" #include "path.h" #include "signal.h" #include "complete.h" - -/** - Maximum number of function calls, i.e. recursion depth. -*/ -#define MAX_RECURSION_DEPTH 128 - -/** - Error message for unknown builtin -*/ -#define UNKNOWN_BUILTIN_ERR_MSG _(L"Unknown builtin '%ls'") - -/** - Error message for improper use of the exec builtin -*/ -#define EXEC_ERR_MSG _(L"This command can not be used in a pipeline") +#include "parse_tree.h" +#include "parse_execution.h" /** Error message for tokenizer error. The tokenizer message is @@ -67,116 +52,10 @@ The fish parser. Contains functions for parsing and evaluating code. #define TOK_ERR_MSG _( L"Tokenizer error: '%ls'") /** - Error message for short circuit command error. -*/ -#define COND_ERR_MSG _( L"An additional command is required" ) - -/** - Error message on a function that calls itself immediately -*/ -#define INFINITE_RECURSION_ERR_MSG _( L"The function calls itself immediately, which would result in an infinite loop.") - -/** - Error message on reaching maximum recursion depth -*/ -#define OVERFLOW_RECURSION_ERR_MSG _( L"Maximum recursion depth reached. Accidental infinite loop?") - -/** - Error message used when the end of a block can't be located -*/ -#define BLOCK_END_ERR_MSG _( L"Could not locate end of block. The 'end' command is missing, misspelled or a ';' is missing.") - -/** - Error message on reaching maximum number of block calls -*/ -#define BLOCK_ERR_MSG _( L"Maximum number of nested blocks reached.") - -/** - Error message when a non-string token is found when expecting a command name -*/ -#define CMD_ERR_MSG _( L"Expected a command name, got token of type '%ls'") - -/** - Error message when a non-string token is found when expecting a command name -*/ -#define CMD_OR_ERR_MSG _( L"Expected a command name, got token of type '%ls'. Did you mean 'COMMAND; or COMMAND'? See the help section for the 'or' builtin command by typing 'help or'.") - -/** - Error message when a non-string token is found when expecting a command name -*/ -#define CMD_AND_ERR_MSG _( L"Expected a command name, got token of type '%ls'. Did you mean 'COMMAND; and COMMAND'? See the help section for the 'and' builtin command by typing 'help and'.") - -/** - Error message when encountering an illegal command name -*/ -#define ILLEGAL_CMD_ERR_MSG _( L"Illegal command name '%ls'") - -/** - Error message when encountering an illegal file descriptor -*/ -#define ILLEGAL_FD_ERR_MSG _( L"Illegal file descriptor '%ls'") - -/** - Error message for wildcards with no matches -*/ -#define WILDCARD_ERR_MSG _( L"No matches for wildcard '%ls'.") - -/** - Error when using case builtin outside of switch block -*/ -#define INVALID_CASE_ERR_MSG _( L"'case' builtin not inside of switch block") - -/** - Error when using loop control builtins (break or continue) outside of loop -*/ -#define INVALID_LOOP_ERR_MSG _( L"Loop control command while not inside of loop" ) - -/** - Error when using return builtin outside of function definition -*/ -#define INVALID_RETURN_ERR_MSG _( L"'return' builtin command outside of function definition" ) - -/** - Error when using else builtin outside of if block -*/ -#define INVALID_ELSE_ERR_MSG _( L"'%ls' builtin not inside of if block" ) - -/** - Error when using 'else if' past a naked 'else' -*/ -#define INVALID_ELSEIF_PAST_ELSE_ERR_MSG _( L"'%ls' used past terminating 'else'" ) - -/** - Error when using end builtin outside of block -*/ -#define INVALID_END_ERR_MSG _( L"'end' command outside of block") - -/** - Error message for Posix-style assignment: foo=bar -*/ -#define COMMAND_ASSIGN_ERR_MSG _( L"Unknown command '%ls'. Did you mean 'set %ls %ls'? See the help section on the set command by typing 'help set'.") - -/** - Error for invalid redirection token -*/ -#define REDIRECT_TOKEN_ERR_MSG _( L"Expected redirection specification, got token of type '%ls'") - -/** - Error when encountering redirection without a command -*/ -#define INVALID_REDIRECTION_ERR_MSG _( L"Encountered redirection when expecting a command name. Fish does not allow a redirection operation before a command.") - -/** - Error for evaluating null pointer -*/ -#define EVAL_NULL_ERR_MSG _( L"Tried to evaluate null pointer." ) - -/** Error for evaluating in illegal scope */ #define INVALID_SCOPE_ERR_MSG _( L"Tried to evaluate commands using invalid block type '%ls'" ) - /** Error for wrong token type */ @@ -197,20 +76,16 @@ The fish parser. Contains functions for parsing and evaluating code. */ #define BREAKPOINT_BLOCK N_( L"Block created by breakpoint" ) - - /** If block description */ #define IF_BLOCK N_( L"'if' conditional block" ) - /** Function definition block description */ #define FUNCTION_DEF_BLOCK N_( L"function definition block" ) - /** Function invocation block description */ @@ -221,37 +96,31 @@ The fish parser. Contains functions for parsing and evaluating code. */ #define FUNCTION_CALL_NO_SHADOW_BLOCK N_( L"function invocation block with no variable shadowing" ) - /** Switch block description */ #define SWITCH_BLOCK N_( L"'switch' block" ) - /** Fake block description */ #define FAKE_BLOCK N_( L"unexecutable block" ) - /** Top block description */ #define TOP_BLOCK N_( L"global root block" ) - /** Command substitution block description */ #define SUBST_BLOCK N_( L"command substitution block" ) - /** Begin block description */ #define BEGIN_BLOCK N_( L"'begin' unconditional block" ) - /** Source block description */ @@ -262,7 +131,6 @@ The fish parser. Contains functions for parsing and evaluating code. */ #define EVENT_BLOCK N_( L"event handler block" ) - /** Unknown block description */ @@ -314,21 +182,19 @@ static const struct block_lookup_entry block_lookup[]= { (block_type_t)0, 0, 0 } }; -static bool job_should_skip_elseif(const job_t *job, const block_t *current_block); +// Given a file path, return something nicer. Currently we just "unexpand" tildes. +static wcstring user_presentable_path(const wcstring &path) +{ + return replace_home_directory_with_tilde(path); +} + parser_t::parser_t(enum parser_type_t type, bool errors) : parser_type(type), show_errors(errors), - error_code(0), - err_pos(0), - current_tokenizer(NULL), - current_tokenizer_pos(0), - job_start_pos(0), - eval_level(-1), - current_block(NULL), - block_io(shared_ptr<io_data_t>()) + cancellation_requested(false), + is_within_fish_initialization(false) { - } /* A pointer to the principal parser (which is a static local) */ @@ -346,45 +212,56 @@ parser_t &parser_t::principal_parser(void) return parser; } +void parser_t::set_is_within_fish_initialization(bool flag) +{ + is_within_fish_initialization = flag; +} + void parser_t::skip_all_blocks(void) { /* Tell all blocks to skip */ if (s_principal_parser) { + s_principal_parser->cancellation_requested = true; + //write(2, "Cancelling blocks\n", strlen("Cancelling blocks\n")); - block_t *c = s_principal_parser->current_block; - while (c) + for (size_t i=0; i < s_principal_parser->block_count(); i++) { - c->skip = true; - //fprintf(stderr, " Cancelled %p\n", c); - c = c->outer; + s_principal_parser->block_at_index(i)->skip = true; } } } -void parser_t::push_block(block_t *newv) +void parser_t::push_block(block_t *new_current) { - const enum block_type_t type = newv->type(); - newv->src_lineno = parser_t::get_lineno(); - newv->src_filename = parser_t::current_filename()?intern(parser_t::current_filename()):0; + const enum block_type_t type = new_current->type(); + new_current->src_lineno = parser_t::get_lineno(); - newv->outer = current_block; - if (current_block && current_block->skip) - newv->mark_as_fake(); + const wchar_t *filename = parser_t::current_filename(); + if (filename != NULL) + { + new_current->src_filename = intern(filename); + } + + const block_t *old_current = this->current_block(); + if (old_current && old_current->skip) + { + new_current->skip = true; + } /* New blocks should be skipped if the outer block is skipped, except TOP ans SUBST block, which open up new environments. Fake blocks should always be skipped. Rather complicated... :-( */ - newv->skip=current_block?current_block->skip:0; + new_current->skip = old_current ? old_current->skip : 0; /* Type TOP and SUBST are never skipped */ if (type == TOP || type == SUBST) { - newv->skip = 0; + new_current->skip = 0; } /* @@ -392,27 +269,32 @@ void parser_t::push_block(block_t *newv) */ if (type == FAKE || type == FUNCTION_DEF) { - newv->skip = 1; + new_current->skip = 1; } - newv->job = 0; - newv->loop_status=LOOP_NORMAL; + new_current->job = 0; + new_current->loop_status=LOOP_NORMAL; - current_block = newv; + this->block_stack.push_back(new_current); - if ((newv->type() != FUNCTION_DEF) && - (newv->type() != FAKE) && - (newv->type() != TOP)) + // Types TOP and SUBST are not considered blocks for the purposes of `status -b` + if (type != TOP && type != SUBST) + { + is_block = 1; + } + + if ((new_current->type() != FUNCTION_DEF) && + (new_current->type() != FAKE) && + (new_current->type() != TOP)) { env_push(type == FUNCTION_CALL); - newv->wants_pop_env = true; + new_current->wants_pop_env = true; } } void parser_t::pop_block() { - block_t *old = current_block; - if (!current_block) + if (block_stack.empty()) { debug(1, L"function %s called on empty block stack.", @@ -421,12 +303,32 @@ void parser_t::pop_block() return; } - current_block = current_block->outer; + block_t *old = block_stack.back(); + block_stack.pop_back(); if (old->wants_pop_env) env_pop(); delete old; + + // Figure out if `status -b` should consider us to be in a block now + int new_is_block=0; + for (std::vector<block_t*>::const_iterator it = block_stack.begin(), end = block_stack.end(); it != end; ++it) + { + const enum block_type_t type = (*it)->type(); + if (type != TOP && type != SUBST) + { + new_is_block = 1; + break; + } + } + is_block = new_is_block; +} + +void parser_t::pop_block(const block_t *expected) +{ + assert(expected == this->current_block()); + this->pop_block(); } const wchar_t *parser_t::get_block_desc(int block) const @@ -441,105 +343,49 @@ const wchar_t *parser_t::get_block_desc(int block) const return _(UNKNOWN_BLOCK); } -/** - Returns 1 if the specified command is a builtin that may not be used in a pipeline -*/ -static int parser_is_pipe_forbidden(const wcstring &word) +wcstring parser_t::block_stack_description() const { - return contains(word, - L"exec", - L"case", - L"break", - L"return", - L"continue"); -} - -/** - Search the text for the end of the current block -*/ -static const wchar_t *parser_find_end(const wchar_t * buff) -{ - int had_cmd=0; - int count = 0; - int error=0; - int mark=0; - - CHECK(buff, 0); - - tokenizer_t tok(buff, 0); - for (; tok_has_next(&tok) && !error; tok_next(&tok)) + wcstring result; + size_t idx = this->block_count(); + size_t spaces = 0; + while (idx--) { - int last_type = tok_last_type(&tok); - switch (last_type) + if (spaces > 0) { - case TOK_STRING: - { - if (!had_cmd) - { - if (wcscmp(tok_last(&tok), L"end")==0) - { - count--; - } - else if (parser_keywords_is_block(tok_last(&tok))) - { - count++; - } - - if (count < 0) - { - error = 1; - } - had_cmd = 1; - } - break; - } - - case TOK_END: - { - had_cmd = 0; - break; - } - - case TOK_PIPE: - case TOK_BACKGROUND: - { - if (had_cmd) - { - had_cmd = 0; - } - else - { - error = 1; - } - break; - - } - - case TOK_ERROR: - error = 1; - break; - - default: - break; - + result.push_back(L'\n'); } - if (!count) + for (size_t j=0; j < spaces; j++) { - tok_next(&tok); - mark = tok_get_pos(&tok); - break; + result.push_back(L' '); } - + result.append(this->block_at_index(idx)->description()); + spaces++; } - if (!count && !error) - { + return result; +} - return buff+mark; - } - return 0; +const block_t *parser_t::block_at_index(size_t idx) const +{ + /* 0 corresponds to the last element in our vector */ + size_t count = block_stack.size(); + return idx < count ? block_stack.at(count - idx - 1) : NULL; +} +block_t *parser_t::block_at_index(size_t idx) +{ + size_t count = block_stack.size(); + return idx < count ? block_stack.at(count - idx - 1) : NULL; } +const block_t *parser_t::current_block() const +{ + return block_stack.empty() ? NULL : block_stack.back(); +} + +block_t *parser_t::current_block() +{ + return block_stack.empty() ? NULL : block_stack.back(); +} void parser_t::forbid_function(const wcstring &function) { @@ -548,36 +394,16 @@ void parser_t::forbid_function(const wcstring &function) void parser_t::allow_function() { - /* - if( al_peek( &forbidden_function) ) - debug( 2, L"Allow %ls\n", al_peek( &forbidden_function) ); - */ forbidden_function.pop_back(); } -void parser_t::error(int ec, int p, const wchar_t *str, ...) -{ - va_list va; - - CHECK(str,); - - error_code = ec; - err_pos = p; - - va_start(va, str); - err_buff = vformat_string(str, va); - va_end(va); - -} - /** Print profiling information to the specified stream */ static void print_profile(const std::vector<profile_item_t*> &items, FILE *out) { - size_t pos; - for (pos = 0; pos < items.size(); pos++) + for (size_t pos = 0; pos < items.size(); pos++) { const profile_item_t *me, *prev; size_t i; @@ -634,187 +460,98 @@ static void print_profile(const std::vector<profile_item_t*> &items, } } - delete me; } } } -void parser_t::destroy() +void parser_t::emit_profiling(const char *path) const { - if (profile) + /* Save profiling information. OK to not use CLO_EXEC here because this is called while fish is dying (and hence will not fork) */ + FILE *f = fopen(path, "w"); + if (!f) + { + debug(1, + _(L"Could not write profiling information to file '%s'"), + path); + } + else { - /* Save profiling information. OK to not use CLO_EXEC here because this is called while fish is dying (and hence will not fork) */ - FILE *f = fopen(profile, "w"); - if (!f) + if (fwprintf(f, + _(L"Time\tSum\tCommand\n"), + profile_items.size()) < 0) { - debug(1, - _(L"Could not write profiling information to file '%s'"), - profile); + wperror(L"fwprintf"); } else { - if (fwprintf(f, - _(L"Time\tSum\tCommand\n"), - profile_items.size()) < 0) - { - wperror(L"fwprintf"); - } - else - { - print_profile(profile_items, f); - } - - if (fclose(f)) - { - wperror(L"fclose"); - } + print_profile(profile_items, f); } - } - - lineinfo.clear(); - - forbidden_function.clear(); - -} - -/** - Print error message to string if an error has occured while parsing - - \param target the buffer to write to - \param prefix: The string token to prefix the each line with. Usually the name of the command trying to parse something. -*/ -void parser_t::print_errors(wcstring &target, const wchar_t *prefix) -{ - CHECK(prefix,); - - if (error_code && ! err_buff.empty()) - { - int tmp; - - append_format(target, L"%ls: %ls\n", prefix, err_buff.c_str()); - - tmp = current_tokenizer_pos; - current_tokenizer_pos = err_pos; - - append_format(target, L"%ls", this->current_line()); - - current_tokenizer_pos=tmp; - } -} - -/** - Print error message to stderr if an error has occured while parsing -*/ -void parser_t::print_errors_stderr() -{ - if (error_code && ! err_buff.empty()) - { - debug(0, L"%ls", err_buff.c_str()); - int tmp; - - tmp = current_tokenizer_pos; - current_tokenizer_pos = err_pos; - - fwprintf(stderr, L"%ls", this->current_line()); - current_tokenizer_pos=tmp; + if (fclose(f)) + { + wperror(L"fclose"); + } } - } -int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args) +void parser_t::expand_argument_list(const wcstring &arg_list_src, std::vector<completion_t> &output_arg_list) { - expand_flags_t eflags = 0; if (! show_errors) eflags |= EXPAND_NO_DESCRIPTIONS; if (this->parser_type != PARSER_TYPE_GENERAL) eflags |= EXPAND_SKIP_CMDSUBST; - int do_loop=1; - - CHECK(line, 1); -// CHECK( args, 1 ); - - // PCA we need to suppress calling proc_push_interactive off of the main thread. I'm not sure exactly what it does. + /* Suppress calling proc_push_interactive off of the main thread. */ if (this->parser_type == PARSER_TYPE_GENERAL) + { proc_push_interactive(0); + } - tokenizer_t tok(line, (show_errors ? 0 : TOK_SQUASH_ERRORS)); - - /* - eval_args may be called while evaulating another command, so we - save the previous tokenizer and restore it on exit - */ - scoped_push<tokenizer_t*> tokenizer_push(¤t_tokenizer, &tok); - scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos, 0); + /* Parse the string as an argument list */ + parse_node_tree_t tree; + if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, NULL /* errors */, symbol_freestanding_argument_list)) + { + /* Failed to parse. Here we expect to have reported any errors in test_args */ + return; + } - error_code=0; + /* Get the root argument list */ + assert(! tree.empty()); + const parse_node_t *arg_list = &tree.at(0); + assert(arg_list->type == symbol_freestanding_argument_list); - for (; do_loop && tok_has_next(&tok) ; tok_next(&tok)) + /* Extract arguments from it */ + while (arg_list != NULL) { - current_tokenizer_pos = tok_get_pos(&tok); - switch (tok_last_type(&tok)) + const parse_node_t *arg_node = tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list); + if (arg_node != NULL) { - case TOK_STRING: - { - const wcstring tmp = tok_last(&tok); - if (expand_string(tmp, args, eflags) == EXPAND_ERROR) - { - err_pos=tok_get_pos(&tok); - do_loop=0; - } - break; - } - - case TOK_END: - { - break; - } - - case TOK_ERROR: - { - if (show_errors) - error(SYNTAX_ERROR, - tok_get_pos(&tok), - TOK_ERR_MSG, - tok_last(&tok)); - - do_loop=0; - break; - } - - default: + const wcstring arg_src = arg_node->get_source(arg_list_src); + if (expand_string(arg_src, output_arg_list, eflags, NULL) == EXPAND_ERROR) { - if (show_errors) - error(SYNTAX_ERROR, - tok_get_pos(&tok), - UNEXPECTED_TOKEN_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - - do_loop=0; + /* Failed to expand a string */ break; } } } - if (show_errors) - this->print_errors_stderr(); - if (this->parser_type == PARSER_TYPE_GENERAL) + { proc_pop_interactive(); - - return 1; + } } -void parser_t::stack_trace(block_t *b, wcstring &buff) +void parser_t::stack_trace(size_t block_idx, wcstring &buff) const { /* Check if we should end the recursion */ - if (!b) + if (block_idx >= this->block_count()) return; + const block_t *b = this->block_at_index(block_idx); + if (b->type()==EVENT) { /* @@ -836,7 +573,7 @@ void parser_t::stack_trace(block_t *b, wcstring &buff) return; } - if (b->type() == FUNCTION_CALL || b->type()==SOURCE || b->type()==SUBST) + if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW || b->type()==SOURCE || b->type()==SUBST) { /* These types of blocks should be printed @@ -850,13 +587,14 @@ void parser_t::stack_trace(block_t *b, wcstring &buff) { const source_block_t *sb = static_cast<const source_block_t*>(b); const wchar_t *source_dest = sb->source_file; - append_format(buff, _(L"in . (source) call of file '%ls',\n"), source_dest); + append_format(buff, _(L"from sourcing file %ls\n"), user_presentable_path(source_dest).c_str()); break; } case FUNCTION_CALL: + case FUNCTION_CALL_NO_SHADOW: { const function_block_t *fb = static_cast<const function_block_t*>(b); - append_format(buff, _(L"in function '%ls',\n"), fb->name.c_str()); + append_format(buff, _(L"in function '%ls'\n"), fb->name.c_str()); break; } case SUBST: @@ -874,14 +612,17 @@ void parser_t::stack_trace(block_t *b, wcstring &buff) if (file) { append_format(buff, - _(L"\tcalled on line %d of file '%ls',\n"), + _(L"\tcalled on line %d of file %ls\n"), b->src_lineno, - file); + user_presentable_path(file).c_str()); + } + else if (is_within_fish_initialization) + { + append_format(buff, _(L"\tcalled during startup\n")); } else { - append_format(buff, - _(L"\tcalled on standard input,\n")); + append_format(buff, _(L"\tcalled on standard input\n")); } if (b->type() == FUNCTION_CALL) @@ -908,7 +649,7 @@ void parser_t::stack_trace(block_t *b, wcstring &buff) /* Recursively print the next block */ - parser_t::stack_trace(b->outer, buff); + parser_t::stack_trace(block_idx + 1, buff); } /** @@ -921,263 +662,132 @@ const wchar_t *parser_t::is_function() const { // PCA: Have to make this a string somehow ASSERT_IS_MAIN_THREAD(); - wcstring result; - block_t *b = current_block; - while (1) + const wchar_t *result = NULL; + for (size_t block_idx = 0; block_idx < this->block_count(); block_idx++) { - if (!b) + const block_t *b = this->block_at_index(block_idx); + if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) { - return NULL; + const function_block_t *fb = static_cast<const function_block_t *>(b); + result = fb->name.c_str(); + break; } - if (b->type() == FUNCTION_CALL) + else if (b->type() == SOURCE) { - const function_block_t *fb = static_cast<const function_block_t *>(b); - return fb->name.c_str(); + /* If a function sources a file, obviously that function's offset doesn't contribute */ + break; } - b=b->outer; } + return result; } int parser_t::get_lineno() const { - int lineno; - - if (! current_tokenizer || ! tok_string(current_tokenizer)) - return -1; + int lineno = -1; + if (! execution_contexts.empty()) + { + lineno = execution_contexts.back()->get_current_line_number(); - lineno = current_tokenizer->line_number_of_character_at_offset(current_tokenizer_pos); + /* If we are executing a function, we have to add in its offset */ + const wchar_t *function_name = is_function(); + if (function_name != NULL) + { + lineno += function_get_definition_offset(function_name); + } - const wchar_t *function_name; - if ((function_name = is_function())) - { - lineno += function_get_definition_offset(function_name); } - return lineno; } -int parser_t::line_number_of_character_at_offset(size_t idx) const -{ - if (! current_tokenizer) - return -1; - - int result = current_tokenizer->line_number_of_character_at_offset(idx); - //assert(result == parse_util_lineno(tok_string( current_tokenizer ), idx)); - return result; -} - const wchar_t *parser_t::current_filename() const { - /* We query a global array for the current file name, so it only makes sense to ask this on the principal parser. */ ASSERT_IS_MAIN_THREAD(); - assert(this == &principal_parser()); - - block_t *b = current_block; - while (1) + for (size_t i=0; i < this->block_count(); i++) { - if (!b) - { - return reader_current_filename(); - } - if (b->type() == FUNCTION_CALL) + const block_t *b = this->block_at_index(i); + if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) { const function_block_t *fb = static_cast<const function_block_t *>(b); return function_get_definition_file(fb->name); } - b=b->outer; - } -} - -/** - Calculates the on-screen width of the specified substring of the - specified string. This function takes into account the width and - alignment of the tab character, but other wise behaves like - repeatedly calling wcwidth. -*/ -static int printed_width(const wchar_t *str, int len) -{ - int res=0; - int i; - - CHECK(str, 0); - - for (i=0; str[i] && i<len; i++) - { - if (str[i] == L'\t') - { - res=(res+8)&~7; - } - else + else if (b->type() == SOURCE) { - res += fish_wcwidth(str[i]); + const source_block_t *sb = static_cast<const source_block_t *>(b); + return sb->source_file; } } - return res; -} - -const wchar_t *parser_t::current_line() -{ - int lineno=1; - - const wchar_t *file; - const wchar_t *whole_str; - const wchar_t *line; - const wchar_t *line_end; - int i; - int offset; - int current_line_width; - const wchar_t *function_name=0; - int current_line_start=0; - - if (!current_tokenizer) + /* We query a global array for the current file name, but only do that if we are the principal parser */ + if (this == &principal_parser()) { - return L""; + return reader_current_filename(); } + return NULL; +} - file = parser_t::current_filename(); - whole_str = tok_string(current_tokenizer); - line = whole_str; - - if (!line) - return L""; - - - lineinfo.clear(); - - /* - Calculate line number, line offset, etc. - */ - for (i=0; i<current_tokenizer_pos && whole_str[i]; i++) +wcstring parser_t::current_line() +{ + if (execution_contexts.empty()) { - if (whole_str[i] == L'\n') - { - lineno++; - current_line_start=i+1; - line = &whole_str[i+1]; - } + return wcstring(); } + const parse_execution_context_t *context = execution_contexts.back(); + assert(context != NULL); -// lineno = current_tokenizer_pos; - - - current_line_width=printed_width(whole_str+current_line_start, - current_tokenizer_pos-current_line_start); - - if ((function_name = is_function())) + int source_offset = context->get_current_source_offset(); + if (source_offset < 0) { - lineno += function_get_definition_offset(function_name); + return wcstring(); } - /* - Copy current line from whole string - */ - line_end = wcschr(line, L'\n'); - if (!line_end) - line_end = line+wcslen(line); + const int lineno = this->get_lineno(); + const wchar_t *file = this->current_filename(); - line = wcsndup(line, line_end-line); + wcstring prefix; - /** - If we are not going to print a stack trace, at least print the line number and filename - */ + /* If we are not going to print a stack trace, at least print the line number and filename */ if (!get_is_interactive() || is_function()) { - int prev_width = my_wcswidth(lineinfo.c_str()); if (file) - append_format(lineinfo, - _(L"%ls (line %d): "), - file, - lineno); - else - append_format(lineinfo, - L"%ls: ", - _(L"Standard input"), - lineno); - offset = my_wcswidth(lineinfo.c_str()) - prev_width; - } - else - { - offset=0; - } - -// debug( 1, L"Current pos %d, line pos %d, file_length %d, is_interactive %d, offset %d\n", current_tokenizer_pos, current_line_pos, wcslen(whole_str), is_interactive, offset); - /* - Skip printing character position if we are in interactive mode - and the error was on the first character of the line. - */ - if (!get_is_interactive() || is_function() || (current_line_width!=0)) - { - // Workaround since it seems impossible to print 0 copies of a character using %*lc - if (offset+current_line_width) { - append_format(lineinfo, - L"%ls\n%*lc^\n", - line, - offset+current_line_width, - L' '); + append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(), lineno); + } + else if (is_within_fish_initialization) + { + append_format(prefix, L"%ls: ", _(L"Startup"), lineno); } else { - append_format(lineinfo, - L"%ls\n^\n", - line); + append_format(prefix, L"%ls: ", _(L"Standard input"), lineno); } } - free((void *)line); - parser_t::stack_trace(current_block, lineinfo); - - return lineinfo.c_str(); -} - -int parser_t::get_pos() const -{ - return tok_get_pos(current_tokenizer); -} - -int parser_t::get_job_pos() const -{ - return job_start_pos; -} + bool is_interactive = get_is_interactive(); + bool skip_caret = is_interactive && ! is_function(); + /* Use an error with empty text */ + assert(source_offset >= 0); + parse_error_t empty_error = {}; + empty_error.source_start = source_offset; -void parser_t::set_pos(int p) -{ - tok_set_pos(current_tokenizer, p); -} - -const wchar_t *parser_t::get_buffer() const -{ - return tok_string(current_tokenizer); -} - - -int parser_t::is_help(const wchar_t *s, int min_match) const -{ - CHECK(s, 0); - - size_t len = wcslen(s); - - min_match = maxi(min_match, 3); + wcstring line_info = empty_error.describe_with_prefix(context->get_source(), prefix, is_interactive, skip_caret); + if (! line_info.empty()) + { + line_info.push_back(L'\n'); + } - return (wcscmp(L"-h", s) == 0) || - (len >= (size_t)min_match && (wcsncmp(L"--help", s, len) == 0)); + parser_t::stack_trace(0, line_info); + return line_info; } -job_t *parser_t::job_create() +void parser_t::job_add(job_t *job) { - job_t *res = new job_t(acquire_job_id(), this->block_io); - this->my_job_list.push_front(res); - - job_set_flag(res, - JOB_CONTROL, - (job_control_mode==JOB_CONTROL_ALL) || - ((job_control_mode == JOB_CONTROL_INTERACTIVE) && (get_is_interactive()))); - return res; + assert(job != NULL); + assert(job->first_process != NULL); + this->my_job_list.push_front(job); } bool parser_t::job_remove(job_t *j) @@ -1198,14 +808,11 @@ bool parser_t::job_remove(job_t *j) void parser_t::job_promote(job_t *job) { - signal_block(); - job_list_t::iterator loc = std::find(my_job_list.begin(), my_job_list.end(), job); assert(loc != my_job_list.end()); /* Move the job to the beginning */ my_job_list.splice(my_job_list.begin(), my_job_list, loc); - signal_unblock(); } job_t *parser_t::job_get(job_id_t id) @@ -1232,1381 +839,94 @@ job_t *parser_t::job_get_from_pid(int pid) return 0; } -/** - Parse options for the specified job - - \param p the process to parse options for - \param j the job to which the process belongs to - \param tok the tokenizer to read options from - \param args the argument list to insert options into - \param args unskip whether we should ignore current_block->skip. Big hack because of our dumb handling of if statements. -*/ -void parser_t::parse_job_argument_list(process_t *p, - job_t *j, - tokenizer_t *tok, - std::vector<completion_t> &args, - bool unskip) +profile_item_t *parser_t::create_profile_item() { - int is_finished=0; - - int proc_is_count=0; - - int matched_wildcard = 0, unmatched_wildcard = 0; - - wcstring unmatched; - int unmatched_pos=0; - - /* The set of IO redirections that we construct for the process */ - io_chain_t process_io_chain; - - /* - Test if this is the 'count' command. We need to special case - count in the shell, since it should display a help message on - 'count -h', but not on 'set foo -h; count $foo'. This is an ugly - workaround and a huge hack, but as near as I can tell, the - alternatives are worse. - */ - proc_is_count = (args.at(0).completion == L"count"); - - while (1) - { - - switch (tok_last_type(tok)) - { - case TOK_PIPE: - { - wchar_t *end; - - if (p->type == INTERNAL_EXEC) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - EXEC_ERR_MSG); - return; - } - - errno = 0; - p->pipe_write_fd = fish_wcstoi(tok_last(tok), &end, 10); - if (p->pipe_write_fd < 0 || errno || *end) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - ILLEGAL_FD_ERR_MSG, - tok_last(tok)); - return; - } - - p->set_argv(completions_to_wcstring_list(args)); - p->next = new process_t(); - - tok_next(tok); - - /* - Don't do anything on failure. parse_job will notice - the error flag and report any errors for us - */ - parse_job(p->next, j, tok); - - is_finished = 1; - break; - } - - case TOK_BACKGROUND: - { - job_set_flag(j, JOB_FOREGROUND, 0); - } - - case TOK_END: - { - if (!p->get_argv()) - p->set_argv(completions_to_wcstring_list(args)); - if (tok_has_next(tok)) - tok_next(tok); - - is_finished = 1; - - break; - } - - case TOK_STRING: - { - int skip=0; - - if (job_get_flag(j, JOB_SKIP)) - { - skip = 1; - } - else if (current_block->skip && ! unskip) - { - /* - If this command should be skipped, we do not expand the arguments - */ - skip=1; - - /* But if this is in fact a case statement or an elseif statement, then it should be evaluated */ - block_type_t type = current_block->type(); - if (type == SWITCH && args.at(0).completion == L"case" && p->type == INTERNAL_BUILTIN) - { - skip=0; - } - else if (job_get_flag(j, JOB_ELSEIF) && ! job_should_skip_elseif(j, current_block)) - { - skip=0; - } - } - else - { - /* If this is an else if, and we should skip it, then don't expand any arguments */ - if (job_get_flag(j, JOB_ELSEIF) && job_should_skip_elseif(j, current_block)) - { - skip = 1; - } - } - - if (!skip) - { - if ((proc_is_count) && - (args.size() == 1) && - (parser_t::is_help(tok_last(tok), 0)) && - (p->type == INTERNAL_BUILTIN)) - { - /* - Display help for count - */ - p->count_help_magic = 1; - } - - switch (expand_string(tok_last(tok), args, 0)) - { - case EXPAND_ERROR: - { - err_pos=tok_get_pos(tok); - if (error_code == 0) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - _(L"Could not expand string '%ls'"), - tok_last(tok)); - - } - break; - } - - case EXPAND_WILDCARD_NO_MATCH: - { - unmatched_wildcard = 1; - if (unmatched.empty()) - { - unmatched = tok_last(tok); - unmatched_pos = tok_get_pos(tok); - } - - break; - } - - case EXPAND_WILDCARD_MATCH: - { - matched_wildcard = 1; - break; - } - - case EXPAND_OK: - { - break; - } - - } - - } - - break; - } - - case TOK_REDIRECT_OUT: - case TOK_REDIRECT_IN: - case TOK_REDIRECT_APPEND: - case TOK_REDIRECT_FD: - case TOK_REDIRECT_NOCLOB: - { - int type = tok_last_type(tok); - shared_ptr<io_data_t> new_io; - wcstring target; - bool has_target = false; - wchar_t *end; - - /* - Don't check redirections in skipped part - - Otherwise, bogus errors may be the result. (Do check - that token is string, though) - */ - if (current_block->skip && ! unskip) - { - tok_next(tok); - if (tok_last_type(tok) != TOK_STRING) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - REDIRECT_TOKEN_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - - break; - } - - - errno = 0; - int fd = fish_wcstoi(tok_last(tok), - &end, - 10); - if (fd < 0 || errno || *end) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - ILLEGAL_FD_ERR_MSG, - tok_last(tok)); - } - else - { - - tok_next(tok); - - switch (tok_last_type(tok)) - { - case TOK_STRING: - { - target = tok_last(tok); - has_target = expand_one(target, no_exec ? EXPAND_SKIP_VARIABLES : 0); - - if (! has_target && error_code == 0) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - REDIRECT_TOKEN_ERR_MSG, - tok_last(tok)); - - } - break; - } - - default: - error(SYNTAX_ERROR, - tok_get_pos(tok), - REDIRECT_TOKEN_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - - if (! has_target || target.empty()) - { - if (error_code == 0) - error(SYNTAX_ERROR, - tok_get_pos(tok), - _(L"Invalid IO redirection")); - tok_next(tok); - } - else if (type == TOK_REDIRECT_FD) - { - if (target == L"-") - { - new_io.reset(new io_close_t(fd)); - } - else - { - wchar_t *end; - - errno = 0; - - int old_fd = fish_wcstoi(target.c_str(), &end, 10); - - if (old_fd < 0 || errno || *end) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - _(L"Requested redirection to something that is not a file descriptor %ls"), - target.c_str()); - - tok_next(tok); - } - else - { - new_io.reset(new io_fd_t(fd, old_fd)); - } - } - } - else - { - int flags = 0; - switch (type) - { - case TOK_REDIRECT_APPEND: - flags = O_CREAT | O_APPEND | O_WRONLY; - break; - - case TOK_REDIRECT_OUT: - flags = O_CREAT | O_WRONLY | O_TRUNC; - break; - - case TOK_REDIRECT_NOCLOB: - flags = O_CREAT | O_EXCL | O_WRONLY; - break; - - case TOK_REDIRECT_IN: - flags = O_RDONLY; - break; - - } - io_file_t *new_io_file = new io_file_t(fd, target, flags); - new_io.reset(new_io_file); - } - } - - if (new_io.get() != NULL) - { - process_io_chain.push_back(new_io); - } - - } - break; - - case TOK_ERROR: - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - TOK_ERR_MSG, - tok_last(tok)); - - return; - } - - default: - error(SYNTAX_ERROR, - tok_get_pos(tok), - UNEXPECTED_TOKEN_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - - tok_next(tok); - break; - } - - if ((is_finished) || (error_code != 0)) - break; - - tok_next(tok); - } - - if (!error_code) + profile_item_t *result = NULL; + if (g_profiling_active) { - if (unmatched_wildcard && !matched_wildcard) - { - job_set_flag(j, JOB_WILDCARD_ERROR, 1); - proc_set_last_status(STATUS_UNMATCHED_WILDCARD); - if (get_is_interactive() && !is_block) - { - int tmp; - - debug(1, WILDCARD_ERR_MSG, unmatched.c_str()); - tmp = current_tokenizer_pos; - current_tokenizer_pos = unmatched_pos; - - fwprintf(stderr, L"%ls", parser_t::current_line()); - - current_tokenizer_pos=tmp; - } - - } + result = new profile_item_t(); + profile_items.push_back(result); } - - /* Store our IO chain. The existing chain should be empty. */ - assert(p->io_chain().empty()); - p->set_io_chain(process_io_chain); + return result; } -/* - static void print_block_stack( block_t *b ) - { - if( !b ) - return; - print_block_stack( b->outer ); - debug( 0, L"Block type %ls, skip: %d", parser_get_block_desc( b->type ), b->skip ); - } -*/ - -/** - Fully parse a single job. Does not call exec on it, but any command substitutions in the job will be executed. - - \param p The process structure that should be used to represent the first process in the job. - \param j The job structure to contain the parsed job - \param tok tokenizer to read from -f - \return 1 on success, 0 on error -*/ -int parser_t::parse_job(process_t *p, - job_t *j, - tokenizer_t *tok) +int parser_t::eval(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type) { - std::vector<completion_t> args; // The list that will become the argv array for the program - int use_function = 1; // May functions be considered when checking what action this command represents - int use_builtin = 1; // May builtins be considered when checking what action this command represents - int use_command = 1; // May commands be considered when checking what action this command represents - int is_new_block=0; // Does this command create a new block? - bool unskip = false; // Maybe we are an elseif inside an if block; if so we may want to evaluate this even if the if block is currently set to skip - bool allow_bogus_command = false; // If we are an elseif that will not be executed, or an AND or OR that will have been short circuited, don't complain about non-existent commands - - block_t *prev_block = current_block; - scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos, tok_get_pos(tok)); - - while (args.empty()) - { - wcstring nxt; - bool has_nxt = false; - bool consumed = false; // Set to one if the command requires a second command, like e.g. while does - int mark; // Use to save the position of the beginning of the token - - switch (tok_last_type(tok)) - { - case TOK_STRING: - { - nxt = tok_last(tok); - has_nxt = expand_one(nxt, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES); - - if (! has_nxt) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - ILLEGAL_CMD_ERR_MSG, - tok_last(tok)); - - return 0; - } - break; - } - - case TOK_ERROR: - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - TOK_ERR_MSG, - tok_last(tok)); - - return 0; - } - - case TOK_PIPE: - { - const wchar_t *str = tok_string(tok); - if (tok_get_pos(tok)>0 && str[tok_get_pos(tok)-1] == L'|') - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_OR_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - else - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - - return 0; - } - - default: - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - - return 0; - } - } - - mark = tok_get_pos(tok); - - if (contains(nxt, - L"command", - L"builtin", - L"not", - L"and", - L"or", - L"exec")) - { - int sw; - int is_exec = nxt == L"exec"; - - if (is_exec && (p != j->first_process)) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - EXEC_ERR_MSG); - return 0; - } - - tok_next(tok); - sw = parser_keywords_is_switch(tok_last(tok)); - - if (sw == ARG_SWITCH) - { - tok_set_pos(tok, mark); - } - else - { - if (sw == ARG_SKIP) - { - tok_next(tok); - } - - consumed = true; - - if (nxt == L"command" || nxt == L"builtin") - { - use_function = 0; - if (nxt == L"command") - { - use_builtin = 0; - use_command = 1; - } - else - { - use_builtin = 1; - use_command = 0; - } - } - else if (nxt == L"not") - { - job_set_flag(j, JOB_NEGATE, !job_get_flag(j, JOB_NEGATE)); - } - else if (nxt == L"and") - { - bool skip = (proc_get_last_status() != 0); - job_set_flag(j, JOB_SKIP, skip); - allow_bogus_command = skip; - } - else if (nxt == L"or") - { - bool skip = (proc_get_last_status() == 0); - job_set_flag(j, JOB_SKIP, skip); - allow_bogus_command = skip; - } - else if (is_exec) - { - use_function = 0; - use_builtin=0; - p->type=INTERNAL_EXEC; - tokenizer_pos_push.restore(); - } - } - } - else if (nxt == L"while") - { - bool new_block = false; - tok_next(tok); - while_block_t *wb = NULL; - - if ((current_block->type() != WHILE)) - { - new_block = true; - } - else if ((wb = static_cast<while_block_t*>(current_block))->status == WHILE_TEST_AGAIN) - { - wb->status = WHILE_TEST_FIRST; - } - else - { - new_block = true; - } - - if (new_block) - { - while_block_t *wb = new while_block_t(); - wb->status = WHILE_TEST_FIRST; - wb->tok_pos = mark; - this->push_block(wb); - } - - consumed = true; - is_new_block=1; - - } - else if (nxt == L"if") - { - tok_next(tok); - - if_block_t *ib = new if_block_t(); - this->push_block(ib); - ib->tok_pos = mark; - - is_new_block=1; - consumed = true; - } - else if (nxt == L"else") - { - /* Record where the else is for error reporting */ - const int else_pos = tok_get_pos(tok); - /* See if we have any more arguments, that is, whether we're ELSE IF ... or just ELSE. */ - tok_next(tok); - if (tok_last_type(tok) == TOK_STRING && current_block->type() == IF) - { - const if_block_t *ib = static_cast<const if_block_t *>(current_block); - - /* If we've already encountered an else, complain */ - if (ib->else_evaluated) - { - error(SYNTAX_ERROR, - else_pos, - INVALID_ELSEIF_PAST_ELSE_ERR_MSG, - L"else if"); - - } - else - { - - job_set_flag(j, JOB_ELSEIF, 1); - consumed = true; - - /* We're at the IF. Go past it. */ - tok_next(tok); - - /* We want to execute this ELSEIF if the IF expression was evaluated, it failed, and so has every other ELSEIF (if any) */ - unskip = (ib->if_expr_evaluated && ! ib->any_branch_taken); - - /* But if we're not executing it, don't complain about its command if it doesn't exist */ - if (! unskip) - allow_bogus_command = true; - } - } - } - - /* - Test if we need another command - */ - if (consumed) - { - /* - Yes we do, around in the loop for another lap, then! - */ - continue; - } - - if (use_function && (unskip || ! current_block->skip)) - { - bool nxt_forbidden=false; - wcstring forbid; - - int is_function_call=0; - - /* - This is a bit fragile. It is a test to see if we are - inside of function call, but not inside a block in that - function call. If, in the future, the rules for what - block scopes are pushed on function invocation changes, - then this check will break. - */ - if ((current_block->type() == TOP) && - (current_block->outer) && - (current_block->outer->type() == FUNCTION_CALL)) - is_function_call = 1; - - /* - If we are directly in a function, and this is the first - command of the block, then the function we are executing - may not be called, since that would mean an infinite - recursion. - */ - if (is_function_call && !current_block->had_command) - { - forbid = forbidden_function.empty() ? wcstring(L"") : forbidden_function.back(); - if (forbid == nxt) - { - /* Infinite recursive loop */ - nxt_forbidden = true; - error(SYNTAX_ERROR, tok_get_pos(tok), INFINITE_RECURSION_ERR_MSG); - } - } - - if (!nxt_forbidden && has_nxt && function_exists(nxt)) - { - /* - Check if we have reached the maximum recursion depth - */ - if (forbidden_function.size() > MAX_RECURSION_DEPTH) - { - error(SYNTAX_ERROR, tok_get_pos(tok), OVERFLOW_RECURSION_ERR_MSG); - } - else - { - p->type = INTERNAL_FUNCTION; - } - } - } - args.push_back(completion_t(nxt)); - } - - if (error_code == 0) - { - if (!p->type) - { - if (use_builtin && - builtin_exists(args.at(0).completion)) - { - p->type = INTERNAL_BUILTIN; - is_new_block |= parser_keywords_is_block(args.at(0).completion); - } - } - - if ((!p->type || (p->type == INTERNAL_EXEC))) - { - /* - If we are not executing the current block, allow - non-existent commands. - */ - if (current_block->skip && ! unskip) - allow_bogus_command = true; //note this may already be true for other reasons - - if (allow_bogus_command) - { - p->actual_cmd.clear(); - } - else - { - int err; - bool has_command = path_get_path(args.at(0).completion, &p->actual_cmd); - err = errno; - - bool use_implicit_cd = false; - if (! has_command) - { - /* If the specified command does not exist, try using an implicit cd. */ - wcstring implicit_cd_path; - use_implicit_cd = path_can_be_implicit_cd(args.at(0).completion, &implicit_cd_path); - if (use_implicit_cd) - { - args.clear(); - args.push_back(completion_t(L"cd")); - args.push_back(completion_t(implicit_cd_path)); - - /* If we have defined a wrapper around cd, use it, otherwise use the cd builtin */ - if (use_function && function_exists(L"cd")) - p->type = INTERNAL_FUNCTION; - else - p->type = INTERNAL_BUILTIN; - } - } - - // Disabled pending discussion in https://github.com/fish-shell/fish-shell/issues/367 -#if 0 - if (! has_command && ! use_implicit_cd) - { - if (fish_openSUSE_dbus_hack_hack_hack_hack(&args)) - { - has_command = true; - p->type = INTERNAL_BUILTIN; - } - } -#endif - - /* Check if the specified command exists */ - if (! has_command && ! use_implicit_cd) - { - - const wchar_t *cmd = args.at(0).completion.c_str(); - - /* - We couldn't find the specified command. - - What we want to happen now is that the - specified job won't get executed, and an - error message is printed on-screen, but - otherwise, the parsing/execution of the - file continues. Because of this, we don't - want to call error(), since that would stop - execution of the file. Instead we let - p->actual_command be 0 (null), which will - cause the job to silently not execute. We - also print an error message and set the - status to 127 (This is the standard number - for this, used by other shells like bash - and zsh). - */ - - const wchar_t * const equals_ptr = wcschr(cmd, L'='); - if (equals_ptr != NULL) - { - /* Try to figure out if this is a pure variable assignment (foo=bar), or if this appears to be running a command (foo=bar ruby...) */ - - const wcstring name_str = wcstring(cmd, equals_ptr - cmd); //variable name, up to the = - const wcstring val_str = wcstring(equals_ptr + 1); //variable value, past the = - - wcstring next_str; - if (tok_peek_next(tok, &next_str) == TOK_STRING && ! next_str.empty()) - { - wcstring ellipsis_str = wcstring(1, ellipsis_char); - if (ellipsis_str == L"$") - ellipsis_str = L"..."; - - /* Looks like a command */ - debug(0, - _( L"Unknown command '%ls'. Did you mean to run %ls with a modified environment? Try 'env %ls=%ls %ls%ls'. See the help section on the set command by typing 'help set'."), - cmd, - next_str.c_str(), - name_str.c_str(), - val_str.c_str(), - next_str.c_str(), - ellipsis_str.c_str()); - } - else - { - debug(0, - COMMAND_ASSIGN_ERR_MSG, - cmd, - name_str.c_str(), - val_str.c_str()); - } - } - else if (cmd[0]==L'$' || cmd[0] == VARIABLE_EXPAND || cmd[0] == VARIABLE_EXPAND_SINGLE) - { - - const env_var_t val_wstr = env_get_string(cmd+1); - const wchar_t *val = val_wstr.missing() ? NULL : val_wstr.c_str(); - if (val) - { - debug(0, - _(L"Variables may not be used as commands. Instead, define a function like 'function %ls; %ls $argv; end' or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'."), - cmd+1, - val, - cmd, - cmd); - } - else - { - debug(0, - _(L"Variables may not be used as commands. Instead, define a function or use the eval builtin instead, like 'eval %ls'. See the help section for the function command by typing 'help function'."), - cmd, - cmd); - } - } - else if (wcschr(cmd, L'$')) - { - debug(0, - _(L"Commands may not contain variables. Use the eval builtin instead, like 'eval %ls'. See the help section for the eval command by typing 'help eval'."), - cmd, - cmd); - } - else if (err!=ENOENT) - { - debug(0, - _(L"The file '%ls' is not executable by this user"), - cmd?cmd:L"UNKNOWN"); - } - else - { - /* - Handle unrecognized commands with standard - command not found handler that can make better - error messages - */ - - wcstring_list_t event_args; - event_args.push_back(args.at(0).completion); - event_fire_generic(L"fish_command_not_found", &event_args); - } - - int tmp = current_tokenizer_pos; - current_tokenizer_pos = tok_get_pos(tok); - - fwprintf(stderr, L"%ls", parser_t::current_line()); - - current_tokenizer_pos=tmp; - - job_set_flag(j, JOB_SKIP, 1); - - proc_set_last_status(err==ENOENT?STATUS_UNKNOWN_COMMAND:STATUS_NOT_EXECUTABLE); - } - } - } - - if ((p->type == EXTERNAL) && !use_command) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - UNKNOWN_BUILTIN_ERR_MSG, - args.back().completion.c_str()); - } - } - + CHECK_BLOCK(1); - if (is_new_block) + if (block_type != TOP && block_type != SUBST) { - - const wchar_t *end=parser_find_end(tok_string(tok) + - current_tokenizer_pos); - int make_sub_block = j->first_process != p; - - if (!end) - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - BLOCK_END_ERR_MSG); - - } - else - { - - if (!make_sub_block) - { - int done=0; - - tokenizer_t subtok(end, 0); - for (; ! done && tok_has_next(&subtok); tok_next(&subtok)) - { - - switch (tok_last_type(&subtok)) - { - case TOK_END: - done = 1; - break; - - case TOK_REDIRECT_OUT: - case TOK_REDIRECT_NOCLOB: - case TOK_REDIRECT_APPEND: - case TOK_REDIRECT_IN: - case TOK_REDIRECT_FD: - case TOK_PIPE: - { - done = 1; - make_sub_block = 1; - break; - } - - case TOK_STRING: - { - break; - } - - default: - { - done = 1; - error(SYNTAX_ERROR, - current_tokenizer_pos, - BLOCK_END_ERR_MSG); - } - } - } - } - - if (make_sub_block) - { - - long end_pos = end-tok_string(tok); - const wcstring sub_block(tok_string(tok) + current_tokenizer_pos, end_pos - current_tokenizer_pos); - - p->type = INTERNAL_BLOCK; - args.at(0) = completion_t(sub_block); - - tok_set_pos(tok, (int)end_pos); - - while (prev_block != current_block) - { - parser_t::pop_block(); - } - - } - else tok_next(tok); - } - + debug(1, INVALID_SCOPE_ERR_MSG, parser_t::get_block_desc(block_type)); + bugreport(); + return 1; } - else tok_next(tok); - if (!error_code) + /* Parse the source into a tree, if we can */ + parse_node_tree_t tree; + parse_error_list_t error_list; + if (! parse_tree_from_string(cmd, parse_flag_none, &tree, this->show_errors ? &error_list : NULL)) { - if (p->type == INTERNAL_BUILTIN && parser_keywords_skip_arguments(args.at(0).completion)) + if (this->show_errors) { - if (!p->get_argv()) - p->set_argv(completions_to_wcstring_list(args)); - } - else - { - parse_job_argument_list(p, j, tok, args, unskip); - } - } + /* Get a backtrace */ + wcstring backtrace_and_desc; + this->get_backtrace(cmd, error_list, &backtrace_and_desc); - if (!error_code) - { - if (!is_new_block) - { - current_block->had_command = true; + /* Print it */ + fprintf(stderr, "%ls", backtrace_and_desc.c_str()); } - } - if (error_code) - { - /* - Make sure the block stack is consistent - */ - while (prev_block != current_block) - { - parser_t::pop_block(); - } + return 1; } - return !error_code; -} -/** - Do skipped execution of command. This means that only limited - execution of block level commands such as end and switch should be - preformed. + //print_stderr(block_stack_description()); - \param j the job to execute -*/ -void parser_t::skipped_exec(job_t * j) -{ - process_t *p; + /* Determine the initial eval level. If this is the first context, it's -1; otherwise it's the eval level of the top context. This is sort of wonky because we're stitching together a global notion of eval level from these separate objects. A better approach would be some profile object that all contexts share, and that tracks the eval levels on its own. */ + int exec_eval_level = (execution_contexts.empty() ? -1 : execution_contexts.back()->current_eval_level()); - /* Handle other skipped guys */ - for (p = j->first_process; p; p=p->next) - { - if (p->type == INTERNAL_BUILTIN) - { - if ((wcscmp(p->argv0(), L"for")==0) || - (wcscmp(p->argv0(), L"switch")==0) || - (wcscmp(p->argv0(), L"begin")==0) || - (wcscmp(p->argv0(), L"function")==0)) - { - this->push_block(new fake_block_t()); - } - else if (wcscmp(p->argv0(), L"end")==0) - { - if (!current_block->outer->skip) - { - exec_job(*this, j); - return; - } - parser_t::pop_block(); - } - else if (wcscmp(p->argv0(), L"else")==0) - { - if (current_block->type() == IF) - { - /* Evaluate this ELSE if the IF expression failed, and so has every ELSEIF (if any) expression thus far */ - const if_block_t *ib = static_cast<const if_block_t*>(current_block); - if (ib->if_expr_evaluated && ! ib->any_branch_taken) - { - exec_job(*this, j); - return; - } - } - } - else if (wcscmp(p->argv0(), L"case")==0) - { - if (current_block->type() == SWITCH) - { - exec_job(*this, j); - return; - } - } - } - } - job_free(j); -} + /* Append to the execution context stack */ + parse_execution_context_t *ctx = new parse_execution_context_t(tree, cmd, this, exec_eval_level); + execution_contexts.push_back(ctx); -/* Return whether we should skip the current block, if it is an elseif. */ -static bool job_should_skip_elseif(const job_t *job, const block_t *current_block) -{ - if (current_block->type() != IF) + /* Execute the first node */ + if (! tree.empty()) { - /* Not an IF block, so just honor the skip property */ - return current_block->skip; + this->eval_block_node(0, io, block_type); } - else - { - /* We are an IF block */ - const if_block_t *ib = static_cast<const if_block_t *>(current_block); - /* Execute this ELSEIF if the IF expression has been evaluated, it evaluated to false, and all ELSEIFs so far have evaluated to false. */ - bool execute_elseif = (ib->if_expr_evaluated && ! ib->any_branch_taken); + /* Clean up the execution context stack */ + assert(! execution_contexts.empty() && execution_contexts.back() == ctx); + execution_contexts.pop_back(); + delete ctx; - /* Invert the sense */ - return ! execute_elseif; - } + return 0; } -/** - Evaluates a job from the specified tokenizer. First calls - parse_job to parse the job and then calls exec to execute it. - - \param tok The tokenizer to read tokens from -*/ - -void parser_t::eval_job(tokenizer_t *tok) +int parser_t::eval_block_node(node_offset_t node_idx, const io_chain_t &io, enum block_type_t block_type) { - ASSERT_IS_MAIN_THREAD(); - - int start_pos = job_start_pos = tok_get_pos(tok); - long long t1=0, t2=0, t3=0; - - - profile_item_t *profile_item = NULL; - bool skip = false; - int job_begin_pos; - const bool do_profile = profile; + /* Paranoia. It's a little frightening that we're given only a node_idx and we interpret this in the topmost execution context's tree. What happens if two trees were to be interleaved? Fortunately that cannot happen (yet); in the future we probably want some sort of reference counted trees. + */ + parse_execution_context_t *ctx = execution_contexts.back(); + assert(ctx != NULL); - if (do_profile) - { - profile_item = new profile_item_t(); - profile_item->skipped = 1; - profile_items.push_back(profile_item); - t1 = get_time(); - } + CHECK_BLOCK(1); - switch (tok_last_type(tok)) + /* Handle cancellation requests. If our block stack is currently empty, then we already did successfully cancel (or there was nothing to cancel); clear the flag. If our block stack is not empty, we are still in the process of cancelling; refuse to evaluate anything */ + if (this->cancellation_requested) { - case TOK_STRING: - { - job_t *j = this->job_create(); - job_set_flag(j, JOB_FOREGROUND, 1); - job_set_flag(j, JOB_TERMINAL, job_get_flag(j, JOB_CONTROL)); - job_set_flag(j, JOB_TERMINAL, job_get_flag(j, JOB_CONTROL) \ - && (!is_subshell && !is_event)); - job_set_flag(j, JOB_SKIP_NOTIFICATION, is_subshell \ - || is_block \ - || is_event \ - || (!get_is_interactive())); - - current_block->job = j; - - if (get_is_interactive()) - { - if (tcgetattr(0, &j->tmodes)) - { - tok_next(tok); - wperror(L"tcgetattr"); - job_free(j); - break; - } - } - - j->first_process = new process_t(); - job_begin_pos = tok_get_pos(tok); - - if (parse_job(j->first_process, j, tok) && - j->first_process->get_argv()) - { - if (job_start_pos < tok_get_pos(tok)) - { - long stop_pos = tok_get_pos(tok); - const wchar_t *newline = wcschr(tok_string(tok)+start_pos, L'\n'); - if (newline) - stop_pos = mini<long>(stop_pos, newline - tok_string(tok)); - - j->set_command(wcstring(tok_string(tok)+start_pos, stop_pos-start_pos)); - } - else - j->set_command(L""); - - if (do_profile) - { - t2 = get_time(); - profile_item->cmd = j->command(); - profile_item->skipped=current_block->skip; - } - - /* If we're an ELSEIF, then we may want to unskip, if we're skipping because of an IF */ - if (job_get_flag(j, JOB_ELSEIF)) - { - bool skip_elseif = job_should_skip_elseif(j, current_block); - - /* Record that we're entering an elseif */ - if (! skip_elseif) - { - /* We must be an IF block here */ - assert(current_block->type() == IF); - static_cast<if_block_t *>(current_block)->is_elseif_entry = true; - } - - /* Record that in the block too. This is similar to what builtin_else does. */ - current_block->skip = skip_elseif; - } - - skip = skip || current_block->skip; - skip = skip || job_get_flag(j, JOB_WILDCARD_ERROR); - skip = skip || job_get_flag(j, JOB_SKIP); - - if (!skip) - { - int was_builtin = 0; - if (j->first_process->type==INTERNAL_BUILTIN && !j->first_process->next) - was_builtin = 1; - scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos, job_begin_pos); - exec_job(*this, j); - - /* Only external commands require a new fishd barrier */ - if (!was_builtin) - set_proc_had_barrier(false); - } - else - { - this->skipped_exec(j); - } - - if (do_profile) - { - t3 = get_time(); - profile_item->level=eval_level; - profile_item->parse = (int)(t2-t1); - profile_item->exec=(int)(t3-t2); - } - - if (current_block->type() == WHILE) - { - while_block_t *wb = static_cast<while_block_t *>(current_block); - switch (wb->status) - { - case WHILE_TEST_FIRST: - { - // PCA I added the 'wb->skip ||' part because we couldn't reliably - // control-C out of loops like this: while test 1 -eq 1; end - wb->skip = wb->skip || proc_get_last_status()!= 0; - wb->status = WHILE_TESTED; - } - break; - } - } - - if (current_block->type() == IF) - { - if_block_t *ib = static_cast<if_block_t *>(current_block); - - if (ib->skip) - { - /* Nothing */ - } - else if (! ib->if_expr_evaluated) - { - /* Execute the IF */ - bool if_result = (proc_get_last_status() == 0); - ib->any_branch_taken = if_result; - - /* Don't execute if the expression failed */ - current_block->skip = ! if_result; - ib->if_expr_evaluated = true; - } - else if (ib->is_elseif_entry && ! ib->any_branch_taken) - { - /* Maybe mark an ELSEIF branch as taken */ - bool elseif_taken = (proc_get_last_status() == 0); - ib->any_branch_taken = elseif_taken; - current_block->skip = ! elseif_taken; - ib->is_elseif_entry = false; - } - } - - } - else - { - /* - This job could not be properly parsed. We free it - instead, and set the status to 1. This should be - rare, since most errors should be detected by the - ahead of time validator. - */ - job_free(j); - - proc_set_last_status(1); - } - current_block->job = 0; - break; - } - - case TOK_END: - { - if (tok_has_next(tok)) - tok_next(tok); - break; - } - - case TOK_BACKGROUND: - { - const wchar_t *str = tok_string(tok); - if (tok_get_pos(tok)>0 && str[tok_get_pos(tok)-1] == L'&') - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_AND_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - else - { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - } - - return; - } - - case TOK_ERROR: + if (! block_stack.empty()) { - error(SYNTAX_ERROR, - tok_get_pos(tok), - TOK_ERR_MSG, - tok_last(tok)); - - return; + return 1; } - - default: + else { - error(SYNTAX_ERROR, - tok_get_pos(tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(tok))); - - return; + this->cancellation_requested = false; } } - job_reap(0); - -} - -int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type) -{ - const wchar_t * const cmd = cmdStr.c_str(); - size_t forbid_count; - int code; - block_t *start_current_block = current_block; - - /* Record the current chain so we can put it back later */ - scoped_push<io_chain_t> block_io_push(&block_io, io); - - scoped_push<wcstring_list_t> forbidden_function_push(&forbidden_function); - - if (block_type == SUBST) - { - forbidden_function.clear(); - } - - CHECK_BLOCK(1); - - forbid_count = forbidden_function.size(); - - job_reap(0); - - debug(4, L"eval: %ls", cmd); - - if (!cmd) - { - debug(1, - EVAL_NULL_ERR_MSG); - bugreport(); - return 1; - } - + /* Only certain blocks are allowed */ if ((block_type != TOP) && (block_type != SUBST)) { @@ -2617,31 +937,20 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type return 1; } - eval_level++; - - this->push_block(new scope_block_t(block_type)); - - tokenizer_t local_tokenizer(cmd, 0); - scoped_push<tokenizer_t *> tokenizer_push(¤t_tokenizer, &local_tokenizer); - - error_code = 0; - - event_fire(NULL); - - while (tok_has_next(current_tokenizer) && - !error_code && - !sanity_check() && - !exit_status()) - { - this->eval_job(current_tokenizer); - event_fire(NULL); - } + /* Not sure why we reap jobs here */ + job_reap(0); - parser_t::pop_block(); + /* Start it up */ + const block_t * const start_current_block = current_block(); + block_t *scope_block = new scope_block_t(block_type); + this->push_block(scope_block); + int result = ctx->eval_node_at_offset(node_idx, scope_block, io); - while (start_current_block != current_block) + /* Clean up the block stack */ + this->pop_block(); + while (start_current_block != current_block()) { - if (current_block == 0) + if (current_block() == NULL) { debug(0, _(L"End of block mismatch. Program terminating.")); @@ -2649,1112 +958,212 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type FATAL_EXIT(); break; } - - if ((!error_code) && (!exit_status()) && (!proc_get_last_status())) - { - - //debug( 2, L"Status %d\n", proc_get_last_status() ); - - debug(1, - L"%ls", parser_t::get_block_desc(current_block->type())); - debug(1, - BLOCK_END_ERR_MSG); - fwprintf(stderr, L"%ls", parser_t::current_line()); - - const wcstring h = builtin_help_get(*this, L"end"); - if (h.size()) - fwprintf(stderr, L"%ls", h.c_str()); - break; - - } - parser_t::pop_block(); + this->pop_block(); } - this->print_errors_stderr(); - - tokenizer_push.restore(); - - while (forbidden_function.size() > forbid_count) - parser_t::allow_function(); - - /* - Restore previous eval state - */ - eval_level--; - - code=error_code; - error_code=0; - + /* Reap again */ job_reap(0); - return code; + return result; } - -/** - \return the block type created by the specified builtin, or -1 on error. -*/ -block_type_t parser_get_block_type(const wcstring &cmd) +bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcstring *out, const wchar_t *prefix) { - for (size_t i=0; block_lookup[i].desc; i++) - { - if (block_lookup[i].name && cmd == block_lookup[i].name) - { - return block_lookup[i].type; - } - } - return (block_type_t)-1; -} + bool errored = false; + parse_error_list_t errors; -/** - \return the block command that createa the specified block type, or null on error. -*/ -const wchar_t *parser_get_block_command(int type) -{ - for (size_t i=0; block_lookup[i].desc; i++) + /* Use empty string for the prefix if it's NULL */ + if (prefix == NULL) { - if (block_lookup[i].type == type) - { - return block_lookup[i].name; - } + prefix = L""; } - return NULL; -} - -/** - Test if this argument contains any errors. Detected errors include - syntax errors in command substitutions, improperly escaped - characters and improper use of the variable expansion operator. -*/ -int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wchar_t *prefix, int offset) -{ - wchar_t *unesc; - wchar_t *pos; - int err=0; - - wchar_t *paran_begin, *paran_end; - wchar_t *arg_cpy; - int do_loop = 1; - CHECK(arg, 1); - - arg_cpy = wcsdup(arg); - - while (do_loop) + /* Parse the string as an argument list */ + parse_node_tree_t tree; + if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors, symbol_freestanding_argument_list)) { - switch (parse_util_locate_cmdsubst(arg_cpy, - ¶n_begin, - ¶n_end, - false)) - { - case -1: - err=1; - if (out) - { - error(SYNTAX_ERROR, - offset, - L"Mismatched parenthesis"); - this->print_errors(*out, prefix); - } - free(arg_cpy); - return err; - - case 0: - do_loop = 0; - break; - - case 1: - { - - wchar_t *subst = wcsndup(paran_begin+1, paran_end-paran_begin-1); - wcstring tmp; - - tmp.append(arg_cpy, paran_begin - arg_cpy); - tmp.push_back(INTERNAL_SEPARATOR); - tmp.append(paran_end+1); - -// debug( 1, L"%ls -> %ls %ls", arg_cpy, subst, tmp.buff ); - - err |= parser_t::test(subst, 0, out, prefix); - - free(subst); - free(arg_cpy); - arg_cpy = wcsdup(tmp.c_str()); - - /* - Do _not_ call sb_destroy on this stringbuffer - it's - buffer is used as the new 'arg_cpy'. It is free'd at - the end of the loop. - */ - break; - } - } + /* Failed to parse. */ + errored = true; } - unesc = unescape(arg_cpy, 1); - if (!unesc) - { - if (out) - { - error(SYNTAX_ERROR, - offset, - L"Invalid token '%ls'", arg_cpy); - print_errors(*out, prefix); - } - return 1; - } - else + if (! errored) { - /* - Check for invalid variable expansions - */ - for (pos = unesc; *pos; pos++) + /* Get the root argument list */ + assert(! tree.empty()); + const parse_node_t *arg_list = &tree.at(0); + assert(arg_list->type == symbol_freestanding_argument_list); + + /* Extract arguments from it */ + while (arg_list != NULL && ! errored) { - switch (*pos) + const parse_node_t *arg_node = tree.next_node_in_node_list(*arg_list, symbol_argument, &arg_list); + if (arg_node != NULL) { - case VARIABLE_EXPAND: - case VARIABLE_EXPAND_SINGLE: + const wcstring arg_src = arg_node->get_source(arg_list_src); + if (parse_util_detect_errors_in_argument(*arg_node, arg_src, &errors)) { - wchar_t n = *(pos+1); - - if (n != VARIABLE_EXPAND && - n != VARIABLE_EXPAND_SINGLE && - !wcsvarchr(n)) - { - err=1; - if (out) - { - expand_variable_error(*this, unesc, pos-unesc, offset); - print_errors(*out, prefix); - } - } - - break; + errored = true; } } } } - free(arg_cpy); - - free(unesc); - return err; - + if (! errors.empty() && out != NULL) + { + out->assign(errors.at(0).describe_with_prefix(arg_list_src, prefix, false /* not interactive */, false /* don't skip caret */)); + } + return errored; } -int parser_t::test_args(const wchar_t * buff, wcstring *out, const wchar_t *prefix) +void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const { - int do_loop = 1; - int err = 0; - - CHECK(buff, 1); + assert(output != NULL); + if (! errors.empty()) + { + const parse_error_t &err = errors.at(0); - tokenizer_t tok(buff, 0); - scoped_push<tokenizer_t*> tokenizer_push(¤t_tokenizer, &tok); - scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos); + const bool is_interactive = get_is_interactive(); - for (; do_loop && tok_has_next(&tok); tok_next(&tok)) - { - current_tokenizer_pos = tok_get_pos(&tok); - switch (tok_last_type(&tok)) + // Determine if we want to try to print a caret to point at the source error + // The err.source_start <= src.size() check is due to the nasty way that slices work, + // which is by rewriting the source (!) + size_t which_line = 0; + bool skip_caret = true; + if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size()) { + // Determine which line we're on + which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n'); - case TOK_STRING: - { - err |= parser_test_argument(tok_last(&tok), out, prefix, tok_get_pos(&tok)); - break; - } + // Don't include the caret if we're interactive, this is the first line of text, and our source is at its beginning, because then it's obvious + skip_caret = (is_interactive && which_line == 1 && err.source_start == 0); + } - case TOK_END: + wcstring prefix; + const wchar_t *filename = this->current_filename(); + if (filename) + { + if (which_line > 0) { - break; + prefix = format_string(_(L"%ls (line %lu): "), user_presentable_path(filename).c_str(), which_line); } - - case TOK_ERROR: + else { - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - TOK_ERR_MSG, - tok_last(&tok)); - print_errors(*out, prefix); - } - err=1; - do_loop=0; - break; + prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str()); } + } + else + { + prefix = L"fish: "; + } - default: - { - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - UNEXPECTED_TOKEN_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - print_errors(*out, prefix); - } - err=1; - do_loop=0; - break; - } + const wcstring description = err.describe_with_prefix(src, prefix, is_interactive, skip_caret); + if (! description.empty()) + { + output->append(description); + output->push_back(L'\n'); } + this->stack_trace(0, *output); } - - error_code=0; - - return err; } -// helper type used in parser::test below -struct block_info_t +block_t::block_t(block_type_t t) : + block_type(t), + skip(), + tok_pos(), + node_offset(NODE_OFFSET_INVALID), + loop_status(LOOP_NORMAL), + job(), + src_filename(), + src_lineno(), + wants_pop_env(false), + event_blocks() { - int position; //tokenizer position - block_type_t type; //type of the block - int indentation; //indentation associated with the block - - bool has_had_case; //if we are a switch, whether we've encountered a case -}; +} -int parser_t::test(const wchar_t *buff, int *block_level, wcstring *out, const wchar_t *prefix) +block_t::~block_t() { - ASSERT_IS_MAIN_THREAD(); - - /* - Set to one if a command name has been given for the currently - parsed process specification - */ - int had_cmd=0; - int err=0; - int unfinished = 0; - - // These are very nearly stacks, but sometimes we have to inspect non-top elements (e.g. return) - std::vector<struct block_info_t> block_infos; - int indentation_sum = 0; //sum of indentation in block_infos - int res = 0; - - /* - Set to 1 if the current command is inside a pipeline - */ - int is_pipeline = 0; - - /* - Set to one if the currently specified process can not be used inside a pipeline - */ - int forbid_pipeline = 0; - - /* - Set to one if an additional process specification is needed - */ - bool needs_cmd = false; - - /* - Counter on the number of arguments this function has encountered - so far. Is set to -1 when the count is unknown, i.e. after - encountering an argument that contains substitutions that can - expand to more/less arguemtns then 1. - */ - int arg_count=0; - - /* - The currently validated command. - */ - wcstring command; - bool has_command = false; - - CHECK(buff, 1); - - if (block_level) - { - size_t len = wcslen(buff); - for (size_t i=0; i<len; i++) - { - block_level[i] = -1; - } - - } - - tokenizer_t tok(buff, 0); - - scoped_push<tokenizer_t*> tokenizer_push(¤t_tokenizer, &tok); - scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos); +} - for (;; tok_next(&tok)) +wcstring block_t::description() const +{ + wcstring result; + switch (this->type()) { - current_tokenizer_pos = tok_get_pos(&tok); - - int last_type = tok_last_type(&tok); - int end_of_cmd = 0; - - switch (last_type) - { - case TOK_STRING: - { - if (!had_cmd) - { - int mark = tok_get_pos(&tok); - had_cmd = 1; - arg_count=0; - - command = tok_last(&tok); - - // Pass SKIP_HOME_DIRECTORIES for https://github.com/fish-shell/fish-shell/issues/512 - has_command = expand_one(command, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_HOME_DIRECTORIES); - if (! has_command) - { - command = L""; - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - ILLEGAL_CMD_ERR_MSG, - tok_last(&tok)); - - print_errors(*out, prefix); - } - break; - } - - if (needs_cmd) - { - /* - end is not a valid command when a followup - command is needed, such as after 'and' or - 'while' - */ - if (contains(command, - L"end")) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - COND_ERR_MSG); - - print_errors(*out, prefix); - } - } - - needs_cmd = false; - } - - /* - Decrement block count on end command - */ - if (command == L"end") - { - tok_next(&tok); - tok_set_pos(&tok, mark); - - /* Test that end is not used when not inside any block */ - if (block_infos.empty()) - { - err = 1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_END_ERR_MSG); - print_errors(*out, prefix); - const wcstring h = builtin_help_get(*this, L"end"); - if (! h.empty()) - append_format(*out, L"%ls", h.c_str()); - } - } - else - { - indentation_sum -= block_infos.back().indentation; - block_infos.pop_back(); - - } - } - - /* - Store the block level. This needs to be done - _after_ checking for end commands, but _before_ - checking for block opening commands. - */ - if (block_level != NULL) - { - int indentation_adjust = 0; - if (command == L"else") - { - // if or else if goes back - indentation_adjust = -1; - } - else if (command == L"case") - { - if (! block_infos.empty() && block_infos.back().type == SWITCH) - { - // mark that we've encountered a case, and increase the indentation - // by doing this now, we avoid overly indenting the first case as the user types it - if (! block_infos.back().has_had_case) - { - block_infos.back().has_had_case = true; - block_infos.back().indentation += 1; - indentation_sum += 1; - } - // unindent this case - indentation_adjust = -1; - } - } - - block_level[tok_get_pos(&tok)] = indentation_sum + indentation_adjust; - } - - /* - Handle block commands - */ - if (parser_keywords_is_block(command)) - { - struct block_info_t info = {current_tokenizer_pos, parser_get_block_type(command), 1 /* indent */}; - block_infos.push_back(info); - indentation_sum += info.indentation; - tok_next(&tok); - tok_set_pos(&tok, mark); - } - - /* - If parser_keywords_is_subcommand is true, the command - accepts a second command as it's first - argument. If parser_skip_arguments is true, the - second argument is optional. - */ - if (parser_keywords_is_subcommand(command) && !parser_keywords_skip_arguments(command)) - { - needs_cmd = true; - had_cmd = 0; - } - - if (contains(command, - L"or", - L"and")) - { - /* - 'or' and 'and' can not be used inside pipelines - */ - if (is_pipeline) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - EXEC_ERR_MSG); - - print_errors(*out, prefix); - - } - } - } - - /* - There are a lot of situations where pipelines - are forbidden, including when using the exec - builtin. - */ - if (parser_is_pipe_forbidden(command)) - { - if (is_pipeline) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - EXEC_ERR_MSG); - - print_errors(*out, prefix); - - } - } - forbid_pipeline = 1; - } - - /* - Test that the case builtin is only used directly in a switch block - */ - if (command == L"case") - { - if (block_infos.empty() || block_infos.back().type != SWITCH) - { - err=1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_CASE_ERR_MSG); - - print_errors(*out, prefix); - const wcstring h = builtin_help_get(*this, L"case"); - if (h.size()) - append_format(*out, L"%ls", h.c_str()); - } - } - } - - /* - Test that the return bultin is only used within function definitions - */ - if (command == L"return") - { - bool found_func = false; - size_t block_idx = block_infos.size(); - while (block_idx--) - { - if (block_infos.at(block_idx).type == FUNCTION_DEF) - { - found_func = true; - break; - } - } - - if (!found_func) - { - /* - Peek to see if the next argument is - --help, in which case we'll allow it to - show the help. - */ - - int old_pos = tok_get_pos(&tok); - int is_help = 0; - - tok_next(&tok); - if (tok_last_type(&tok) == TOK_STRING) - { - wcstring first_arg = tok_last(&tok); - if (expand_one(first_arg, EXPAND_SKIP_CMDSUBST) && parser_t::is_help(first_arg.c_str(), 3)) - { - is_help = 1; - } - } - - tok_set_pos(&tok, old_pos); - - if (!is_help) - { - err=1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_RETURN_ERR_MSG); - print_errors(*out, prefix); - } - } - } - } - - - /* - Test that break and continue are only used within loop blocks - */ - if (contains(command, L"break", L"continue")) - { - bool found_loop = false; - size_t block_idx = block_infos.size(); - while (block_idx--) - { - block_type_t type = block_infos.at(block_idx).type; - if (type == WHILE || type == FOR) - { - found_loop = true; - break; - } - } - - if (!found_loop) - { - /* - Peek to see if the next argument is - --help, in which case we'll allow it to - show the help. - */ - - int old_pos = tok_get_pos(&tok); - int is_help = 0; - - tok_next(&tok); - if (tok_last_type(&tok) == TOK_STRING) - { - wcstring first_arg = tok_last(&tok); - if (expand_one(first_arg, EXPAND_SKIP_CMDSUBST) && parser_t::is_help(first_arg.c_str(), 3)) - { - is_help = 1; - } - } - - tok_set_pos(&tok, old_pos); - - if (!is_help) - { - err=1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_LOOP_ERR_MSG); - print_errors(*out, prefix); - } - } - } - } - - /* - Test that else and else-if are only used directly in an if-block - */ - if (command == L"else") - { - if (block_infos.empty() || block_infos.back().type != IF) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_ELSE_ERR_MSG, - command.c_str()); - - print_errors(*out, prefix); - } - } - } - } - else - { - err |= parser_test_argument(tok_last(&tok), out, prefix, tok_get_pos(&tok)); - - /* If possible, keep track of number of supplied arguments */ - if (arg_count >= 0 && expand_is_clean(tok_last(&tok))) - { - arg_count++; - } - else - { - arg_count = -1; - } - - if (has_command) - { - - /* - Try to make sure the second argument to 'for' is 'in' - */ - if (command == L"for") - { - if (arg_count == 1) - { - - if (wcsvarname(tok_last(&tok))) - { - - err = 1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_FOR_ERR_NAME, - L"for", - tok_last(&tok)); - - print_errors(*out, prefix); - } - } - - } - else if (arg_count == 2) - { - if (wcscmp(tok_last(&tok), L"in") != 0) - { - err = 1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_FOR_ERR_IN, - L"for"); - - print_errors(*out, prefix); - } - } - } - } - else if (command == L"else") - { - if (arg_count == 1) - { - /* Any second argument must be "if" */ - if (wcscmp(tok_last(&tok), L"if") != 0) - { - err = 1; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_ELSEIF_ERR_ARGUMENT, - L"else"); - print_errors(*out, prefix); - } - } - else - { - /* Successfully detected "else if". Now we need a new command. */ - needs_cmd = true; - had_cmd = false; - } - } - } - } - - } - - break; - } - - case TOK_REDIRECT_OUT: - case TOK_REDIRECT_IN: - case TOK_REDIRECT_APPEND: - case TOK_REDIRECT_FD: - case TOK_REDIRECT_NOCLOB: - { - if (!had_cmd) - { - err = 1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - INVALID_REDIRECTION_ERR_MSG); - print_errors(*out, prefix); - } - } - break; - } - - case TOK_END: - { - if (needs_cmd && !had_cmd) - { - err = 1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - print_errors(*out, prefix); - } - } - needs_cmd = false; - had_cmd = 0; - is_pipeline=0; - forbid_pipeline=0; - end_of_cmd = 1; - - break; - } - - case TOK_PIPE: - { - if (!had_cmd) - { - err=1; - if (out) - { - if (tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'|') - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - CMD_OR_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - - } - else - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - } - - print_errors(*out, prefix); - } - } - else if (forbid_pipeline) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - EXEC_ERR_MSG); - - print_errors(*out, prefix); - } - } - else - { - needs_cmd = true; - is_pipeline=1; - had_cmd=0; - end_of_cmd = 1; - - } - break; - } - - case TOK_BACKGROUND: - { - if (!had_cmd) - { - err = 1; - if (out) - { - if (tok_get_pos(&tok)>0 && buff[tok_get_pos(&tok)-1] == L'&') - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - CMD_AND_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - - } - else - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - CMD_ERR_MSG, - tok_get_desc(tok_last_type(&tok))); - } - - print_errors(*out, prefix); - } - } - - had_cmd = 0; - end_of_cmd = 1; - - break; - } + case WHILE: + result.append(L"while"); + break; - case TOK_ERROR: - default: - if (tok_get_error(&tok) == TOK_UNTERMINATED_QUOTE) - { - unfinished = 1; - } - else - { - // Only print errors once - if (out && ! err) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - TOK_ERR_MSG, - tok_last(&tok)); + case FOR: + result.append(L"for"); + break; + case IF: + result.append(L"if"); + break; - print_errors(*out, prefix); - } - err = 1; - } + case FUNCTION_DEF: + result.append(L"function_def"); + break; - break; - } + case FUNCTION_CALL: + result.append(L"function_call"); + break; - if (end_of_cmd) - { - if (has_command && command == L"for") - { - if (arg_count >= 0 && arg_count < 2) - { - /* - Not enough arguments to the for builtin - */ - err = 1; + case FUNCTION_CALL_NO_SHADOW: + result.append(L"function_call_no_shadow"); + break; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_FOR_ERR_COUNT, - L"for", - arg_count); + case SWITCH: + result.append(L"switch"); + break; - print_errors(*out, prefix); - } - } - } - else if (has_command && command == L"else") - { - if (arg_count == 1) - { - /* If we have any arguments, we must have at least two...either "else" or "else if foo..." */ - err = true; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_ELSEIF_ERR_COUNT, - L"else", - arg_count); + case FAKE: + result.append(L"fake"); + break; - print_errors(*out, prefix); + case SUBST: + result.append(L"substitution"); + break; - } - } - } + case TOP: + result.append(L"top"); + break; - } + case BEGIN: + result.append(L"begin"); + break; - if (!tok_has_next(&tok)) + case SOURCE: + result.append(L"source"); break; - } + case EVENT: + result.append(L"event"); + break; - if (needs_cmd) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - COND_ERR_MSG); + case BREAKPOINT: + result.append(L"breakpoint"); + break; - print_errors(*out, prefix); - } + default: + append_format(result, L"unknown type %ld", (long)this->type()); + break; } - - if (out != NULL && ! block_infos.empty()) + if (this->src_lineno >= 0) { - const wchar_t *cmd; - int bad_pos = block_infos.back().position; - block_type_t bad_type = block_infos.back().type; - - error(SYNTAX_ERROR, bad_pos, BLOCK_END_ERR_MSG); - - print_errors(*out, prefix); - - cmd = parser_get_block_command(bad_type); - if (cmd) - { - const wcstring h = builtin_help_get(*this, cmd); - if (h.size()) - { - append_format(*out, L"%ls", h.c_str()); - } - } - - + append_format(result, L" (line %d)", this->src_lineno); } - - /* - Fill in the unset block_level entries. Until now, only places - where the block level _changed_ have been filled out. This fills - in the rest. - */ - - if (block_level) + if (this->src_filename != NULL) { - int last_level = 0; - size_t i, len = wcslen(buff); - for (i=0; i<len; i++) - { - if (block_level[i] >= 0) - { - last_level = block_level[i]; - /* - Make all whitespace before a token have the new - level. This avoid using the wrong indentation level - if a new line starts with whitespace. - */ - size_t prev_char_idx = i; - while (prev_char_idx--) - { - if (!wcschr(L" \n\t\r", buff[prev_char_idx])) - break; - block_level[prev_char_idx] = last_level; - } - } - block_level[i] = last_level; - } - - /* - Make all trailing whitespace have the block level that the - validator had at exit. This makes sure a new line is - correctly indented even if it is empty. - */ - int last_indent = block_infos.empty() ? 0 : block_infos.back().indentation; - size_t suffix_idx = len; - while (suffix_idx--) - { - if (!wcschr(L" \n\t\r", buff[suffix_idx])) - break; - block_level[suffix_idx] = last_indent; - } + append_format(result, L" (file %ls)", this->src_filename); } - - /* - Calculate exit status - */ - if (! block_infos.empty()) - unfinished = 1; - - if (err) - res |= PARSER_TEST_ERROR; - - if (unfinished) - res |= PARSER_TEST_INCOMPLETE; - - /* - Cleanup - */ - - error_code=0; - - - return res; - -} - -block_t::block_t(block_type_t t) : - block_type(t), - made_fake(false), - skip(), - had_command(), - tok_pos(), - loop_status(), - job(), - src_filename(), - src_lineno(), - wants_pop_env(false), - event_blocks(), - outer(NULL) -{ -} - -block_t::~block_t() -{ + return result; } /* Various block constructors */ -if_block_t::if_block_t() : - block_t(IF), - if_expr_evaluated(false), - is_elseif_entry(false), - any_branch_taken(false), - else_evaluated(false) +if_block_t::if_block_t() : block_t(IF) { } @@ -3777,45 +1186,27 @@ source_block_t::source_block_t(const wchar_t *src) : { } -for_block_t::for_block_t(const wcstring &var) : - block_t(FOR), - variable(var), - sequence() +for_block_t::for_block_t() : block_t(FOR) { } -while_block_t::while_block_t() : - block_t(WHILE), - status(0) +while_block_t::while_block_t() : block_t(WHILE) { } -switch_block_t::switch_block_t(const wcstring &sv) : - block_t(SWITCH), - switch_taken(false), - switch_value(sv) +switch_block_t::switch_block_t() : block_t(SWITCH) { } -fake_block_t::fake_block_t() : - block_t(FAKE) +fake_block_t::fake_block_t() : block_t(FAKE) { } -function_def_block_t::function_def_block_t() : - block_t(FUNCTION_DEF), - function_data() -{ -} - -scope_block_t::scope_block_t(block_type_t type) : - block_t(type) +scope_block_t::scope_block_t(block_type_t type) : block_t(type) { assert(type == BEGIN || type == TOP || type == SUBST); } -breakpoint_block_t::breakpoint_block_t() : - block_t(BREAKPOINT) +breakpoint_block_t::breakpoint_block_t() : block_t(BREAKPOINT) { } - |