diff options
Diffstat (limited to 'parser.cpp')
-rw-r--r-- | parser.cpp | 1338 |
1 files changed, 492 insertions, 846 deletions
@@ -44,6 +44,7 @@ The fish parser. Contains functions for parsing and evaluating code. #include "path.h" #include "signal.h" #include "complete.h" +#include "parse_tree.h" /** Maximum number of function calls, i.e. recursion depth. @@ -87,11 +88,6 @@ The fish parser. Contains functions for parsing and evaluating code. #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'") @@ -316,6 +312,13 @@ static const struct block_lookup_entry block_lookup[]= 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), @@ -327,7 +330,6 @@ parser_t::parser_t(enum parser_type_t type, bool errors) : eval_level(-1), block_io(shared_ptr<io_data_t>()) { - } /* A pointer to the principal parser (which is a static local) */ @@ -575,19 +577,20 @@ void parser_t::allow_function() forbidden_function.pop_back(); } -void parser_t::error(int ec, int p, const wchar_t *str, ...) +void parser_t::error(int ec, size_t p, const wchar_t *str, ...) { va_list va; CHECK(str,); error_code = ec; - err_pos = p; + + // note : p may be -1 + err_pos = static_cast<int>(p); va_start(va, str); err_buff = vformat_string(str, va); va_end(va); - } /** @@ -742,7 +745,7 @@ void parser_t::print_errors_stderr() } -int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args) +void parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args) { expand_flags_t eflags = 0; @@ -751,10 +754,9 @@ int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args) if (this->parser_type != PARSER_TYPE_GENERAL) eflags |= EXPAND_SKIP_CMDSUBST; - int do_loop=1; + bool do_loop=1; - CHECK(line, 1); -// CHECK( args, 1 ); + if (! line) return; // PCA we need to suppress calling proc_push_interactive off of the main thread. I'm not sure exactly what it does. if (this->parser_type == PARSER_TYPE_GENERAL) @@ -823,11 +825,9 @@ int parser_t::eval_args(const wchar_t *line, std::vector<completion_t> &args) if (this->parser_type == PARSER_TYPE_GENERAL) proc_pop_interactive(); - - return 1; } -void parser_t::stack_trace(size_t block_idx, wcstring &buff) +void parser_t::stack_trace(size_t block_idx, wcstring &buff) const { /* Check if we should end the recursion @@ -872,13 +872,13 @@ void parser_t::stack_trace(size_t block_idx, 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: { 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: @@ -896,14 +896,14 @@ void parser_t::stack_trace(size_t block_idx, 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 { append_format(buff, - _(L"\tcalled on standard input,\n")); + _(L"\tcalled on standard input\n")); } if (b->type() == FUNCTION_CALL) @@ -1171,7 +1171,7 @@ const wchar_t *parser_t::get_buffer() const } -int parser_t::is_help(const wchar_t *s, int min_match) const +int parser_t::is_help(const wchar_t *s, int min_match) { CHECK(s, 0); @@ -1329,6 +1329,7 @@ void parser_t::parse_job_argument_list(process_t *p, case TOK_BACKGROUND: { job_set_flag(j, JOB_FOREGROUND, 0); + // PCA note fall through, this is deliberate. The background modifier & terminates a command } case TOK_END: @@ -1647,6 +1648,110 @@ void parser_t::parse_job_argument_list(process_t *p, } */ +#if 0 +process_t *parser_t::create_boolean_process(job_t *job, const parse_node_t &bool_statement, const parser_context_t &ctx) +{ + // Handle a boolean statement + bool skip_job = false; + assert(bool_statement.type == symbol_boolean_statement); + switch (specific_statement.production_idx) + { + // These magic numbers correspond to productions for boolean_statement + case 0: + // AND. Skip if the last job failed. + skip_job = (proc_get_last_status() != 0); + break; + + case 1: + // OR. Skip if the last job succeeded. + skip_job = (proc_get_last_status() == 0); + break; + + case 2: + // NOT. Negate it. + job_set_flag(job, JOB_NEGATE, !job_get_flag(job, JOB_NEGATE)); + break; + + default: + { + fprintf(stderr, "Unexpected production in boolean statement\n"); + PARSER_DIE(); + break; + } + } + + process_t *result = NULL; + if (! skip_job) + { + const parse_node_t &subject = *ctx.tree.get_child(bool_statement, 1, symbol_statement); + result = this->create_job_process(job, subject, ctx); + } + return result; +} + +/* Returns a process_t allocated with new. It's the caller's responsibility to delete it (!) */ +process_t *parser_t::create_job_process(job_t *job, const parse_node_t &statement_node, const parser_context_t &ctx) +{ + assert(statement_node.type == symbol_statement); + assert(statement_node.child_count == 1); + + // We may skip this job entirely, e.g. with an 'and' statement + bool skip_job = false; + + // Get the "specific statement" which is boolean / block / if / switch / decorated + const parse_node_t &specific_statement = *ctx.tree.get_child(statement_node, 0); + + process_t *result = NULL; + + switch (specific_statement.type) + { + case symbol_boolean_statement: + { + result = this->create_boolean_process(job, specific_statement, ctx); + break; + } + + case symbol_block_statement: + { + const parse_node_t &header = *ctx.tree.get_child(specific_statement, 0, symbol_block_header); + const parse_node_t &specific_header = *ctx.tree.get_child(header, 0); + switch (specific_header.type) + { + case symbol_for_header: + result = this->create_for_process(job, specific_header, specific_statement, ctx); + break; + + case symbol_while_header: + result = this->create_while_process(job, specific_header, specific_statement, ctx); + break; + + case symbol_function_header: + // No process is associated with creating a function + // TODO: create the darn function! + result = NULL; + break; + + case symbol_begin_header: + + break; + + default: + fprintf(stderr, "Unexpected header type\n"); + PARSER_DIE(); + break; + } + } + } + + // expand_one command + // handle booleans (and, not, or) + // set INTERNAL_EXEC + // implicit CD + + return proc; +} +#endif + /** Fully parse a single job. Does not call exec on it, but any command substitutions in the job will be executed. @@ -1656,9 +1761,7 @@ void parser_t::parse_job_argument_list(process_t *p, 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::parse_job(process_t *p, job_t *j, tokenizer_t *tok) { 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 @@ -2350,6 +2453,206 @@ static bool job_should_skip_elseif(const job_t *job, const block_t *current_bloc } /** + Evaluates a job from a node tree. +*/ + +#if 0 +void parser_t::eval_job(const parse_node_t &job_node, const parser_context_t &ctx) +{ + assert(job_node.type == symbol_job); + this->job_start_pos = (int)job_node.source_start; + + // Get terminal modes + struct termios tmodes = {}; + if (get_is_interactive()) + { + if (tcgetattr(STDIN_FILENO, &tmodes)) + { + // need real error handling here + wperror(L"tcgetattr"); + return; + } + } + + /* Track whether we had an error */ + bool process_errored = false; + + /* Profiling support */ + long long t1 = 0, t2 = 0, t3 = 0; + const bool do_profile = profile; + profile_item_t *profile_item = NULL; + if (do_profile) + { + profile_item = new profile_item_t(); + profile_item->skipped = 1; + profile_items.push_back(profile_item); + t1 = get_time(); + } + + 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; + + /* Tell the job what its command is */ + j->set_command(job_node.get_source(ctx.src)); + + /* Construct process_t structures for every statement in the job */ + const parse_node_t *statement_node = ctx.tree.get_child(job_node, 0, symbol_statement); + assert(statement_node != NULL); + + /* Create the process (may fail!) */ + j->first_process = this->create_job_process(j, *statement_node, ctx); + if (j->first_process == NULL) + process_errored = true; + + /* Construct process_ts for job continuations (pipelines), by walking the list until we hit the terminal (empty) job continuationf */ + const parse_node_t *job_cont = ctx.tree.get_child(job_node, 1, symbol_job_continuation); + process_t *last_process = j->first_process; + while (! process_errored && job_cont != NULL && job_cont->child_count > 0) + { + assert(job_cont->type == symbol_job_continuation); + + /* Get the statement node and make a process from it */ + const parse_node_t *statement_node = ctx.tree.get_child(*job_cont, 1, symbol_statement); + assert(statement_node != NULL); + + /* Store the new process (and maybe with an error) */ + last_process->next = this->create_job_process(j, *statement_node, ctx); + if (last_process->next == NULL) + process_errored = true; + + /* Link the process and get the next continuation */ + last_process = last_process->next; + job_cont = ctx.tree.get_child(*job_cont, 2, symbol_job_continuation); + } + + bool skip = false; + if (this->parse_job(j->first_process, j, job_node, ctx) && j->first_process->get_argv()) + { + 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; +} +#endif + +/** Evaluates a job from the specified tokenizer. First calls parse_job to parse the job and then calls exec to execute it. @@ -2589,9 +2892,141 @@ void parser_t::eval_job(tokenizer_t *tok) } -int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type_t block_type) +#if 0 +int parser_t::eval2(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type) { - const wchar_t * const cmd = cmdStr.c_str(); + parser_context_t mut_ctx; + mut_ctx.src = cmd_str; + + /* Parse the tree */ + if (! parse_t::parse(cmd_str, parse_flag_none, &mut_ctx.tree, NULL)) + { + return 1; + } + + /* Make a const version for safety's sake */ + const parser_context_t &ctx = mut_ctx; + + CHECK_BLOCK(1); + + /* 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); + const size_t forbid_count = forbidden_function.size(); + const block_t *start_current_block = current_block; + + /* Do some stuff I haven't figured out yet */ + job_reap(0); + + /* Only certain blocks are allowed */ + if ((block_type != TOP) && + (block_type != SUBST)) + { + debug(1, + INVALID_SCOPE_ERR_MSG, + parser_t::get_block_desc(block_type)); + bugreport(); + return 1; + } + + eval_level++; + + this->push_block(new scope_block_t(block_type)); + + error_code = 0; + + event_fire(NULL); + + /* Execute the top job list */ + assert(! ctx.tree.empty()); + const parse_node_t *job_list = &ctx.tree.at(0); + assert(job_list->type == symbol_job_list); + while (job_list != NULL) + { + // These correspond to the three productions of job_list + // Try pulling out a job + const parse_node_t *job = NULL; + switch (job_list->production_idx) + { + case 0: // empty + job_list = NULL; + break; + + case 1: //job, job_list + job = ctx.tree.get_child(*job_list, 0, symbol_job); + job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list); + break; + + case 2: //blank line, job_list + job = NULL; + job_list = ctx.tree.get_child(*job_list, 1, symbol_job_list); + break; + + default: //if we get here, it means more productions have been added to job_list, which is bad + PARSER_DIE(); + } + + if (job != NULL) + { + this->eval_job(*job, ctx); + } + } + + parser_t::pop_block(); + + while (start_current_block != current_block) + { + if (current_block == 0) + { + debug(0, + _(L"End of block mismatch. Program terminating.")); + bugreport(); + 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->print_errors_stderr(); + + while (forbidden_function.size() > forbid_count) + parser_t::allow_function(); + + /* + Restore previous eval state + */ + eval_level--; + + int code=error_code; + error_code=0; + + job_reap(0); + + return code; +} +#endif + +int parser_t::eval(const wcstring &cmd_str, const io_chain_t &io, enum block_type_t block_type) +{ + const wchar_t * const cmd = cmd_str.c_str(); size_t forbid_count; int code; const block_t *start_current_block = current_block(); @@ -2614,13 +3049,6 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type debug(4, L"eval: %ls", cmd); - if (!cmd) - { - debug(1, - EVAL_NULL_ERR_MSG); - bugreport(); - return 1; - } if ((block_type != TOP) && (block_type != SUBST)) @@ -2638,6 +3066,7 @@ int parser_t::eval(const wcstring &cmdStr, const io_chain_t &io, enum block_type tokenizer_t local_tokenizer(cmd, 0); scoped_push<tokenizer_t *> tokenizer_push(¤t_tokenizer, &local_tokenizer); + scoped_push<int> tokenizer_pos_push(¤t_tokenizer_pos, 0); error_code = 0; @@ -2779,7 +3208,7 @@ int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wcha case 1: { - wchar_t *subst = wcsndup(paran_begin+1, paran_end-paran_begin-1); + const wcstring subst(paran_begin + 1, paran_end); wcstring tmp; tmp.append(arg_cpy, paran_begin - arg_cpy); @@ -2788,17 +3217,16 @@ int parser_t::parser_test_argument(const wchar_t *arg, wcstring *out, const wcha // debug( 1, L"%ls -> %ls %ls", arg_cpy, subst, tmp.buff ); - err |= parser_t::test(subst, 0, out, prefix); + parse_error_list_t errors; + err |= parse_util_detect_errors(subst, &errors); + if (out && ! errors.empty()) + { + out->append(parse_errors_description(errors, subst, 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; } } @@ -2923,819 +3351,37 @@ struct block_info_t { 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) +void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const { - 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)) - { - 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 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)); - - - print_errors(*out, prefix); - } - err = 1; - } - - 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; - - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - BUILTIN_FOR_ERR_COUNT, - L"for", - arg_count); - - 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); - - print_errors(*out, prefix); - - } - } - } - - } - - if (!tok_has_next(&tok)) - break; - - } - - if (needs_cmd) - { - err=1; - if (out) - { - error(SYNTAX_ERROR, - tok_get_pos(&tok), - COND_ERR_MSG); - - print_errors(*out, prefix); - } - } - - - if (out != NULL && ! block_infos.empty()) - { - 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()); - } - } - - - } - - /* - 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) + assert(output != NULL); + if (! errors.empty()) { - int last_level = 0; - size_t i, len = wcslen(buff); - for (i=0; i<len; i++) + const parse_error_t err = errors.at(0); + + // Determine which line we're on + assert(err.source_start <= src.size()); + size_t which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n'); + + const wchar_t *filename = this->current_filename(); + if (filename) { - 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; + append_format(*output, _(L"fish: line %lu of %ls:\n"), which_line, user_presentable_path(filename).c_str()); } - - /* - 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--) + else { - if (!wcschr(L" \n\t\r", buff[suffix_idx])) - break; - block_level[suffix_idx] = last_indent; + output->append(L"fish: "); } + + // 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 + bool skip_caret = (get_is_interactive() && which_line == 1 && err.source_start == 0); + + output->append(err.describe(src, skip_caret)); + output->push_back(L'\n'); + + this->stack_trace(current_block, *output); } - - /* - 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) : |