From dc8014562b3128dc3725a822c32a24d6a212180e Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 14 Jan 2014 00:01:26 -0800 Subject: [PATCH] Fix for issue where unterminated quotes would attempt to be executed, instead of continuing edit onto the next line. --- builtin.cpp | 4 ++-- fish_tests.cpp | 3 ++- parse_constants.h | 6 +++++- parse_tree.cpp | 31 ++++++++++++++++++++++++++----- parse_tree.h | 2 +- parse_util.cpp | 20 +++++++++++++++++++- 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/builtin.cpp b/builtin.cpp index 4aab71d43..3e09a1fce 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -4298,7 +4298,7 @@ int builtin_parse(parser_t &parser, wchar_t **argv) const wcstring src = str2wcstring(&txt.at(0), txt.size()); parse_node_tree_t parse_tree; parse_error_list_t errors; - bool success = parse_tree_from_string(src, parse_flag_none, &parse_tree, &errors, true); + bool success = parse_tree_from_string(src, parse_flag_none, &parse_tree, &errors); if (! success) { stdout_buffer.append(L"Parsing failed:\n"); @@ -4311,7 +4311,7 @@ int builtin_parse(parser_t &parser, wchar_t **argv) stdout_buffer.append(L"(Reparsed with continue after error)\n"); parse_tree.clear(); errors.clear(); - parse_tree_from_string(src, parse_flag_continue_after_error, &parse_tree, &errors, true); + parse_tree_from_string(src, parse_flag_continue_after_error, &parse_tree, &errors); } const wcstring dump = parse_dump_tree(parse_tree, src); stdout_buffer.append(dump); diff --git a/fish_tests.cpp b/fish_tests.cpp index fab103351..e2a0ec904 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -2452,7 +2452,8 @@ static void test_new_parser_errors(void) } tests[] = { - {L"echo (abc", parse_error_tokenizer}, + {L"echo 'abc", parse_error_tokenizer_unterminated_quote}, + {L"echo (abc", parse_error_tokenizer_unterminated_subshell}, {L"end", parse_error_unbalancing_end}, {L"echo hi ; end", parse_error_unbalancing_end}, diff --git a/parse_constants.h b/parse_constants.h index 104af27f4..d8ef59a05 100644 --- a/parse_constants.h +++ b/parse_constants.h @@ -114,7 +114,11 @@ enum parse_error_code_t parse_error_generic, // unclassified error types - parse_error_tokenizer, //tokenizer error + //tokenizer errors + parse_error_tokenizer_unterminated_quote, + parse_error_tokenizer_unterminated_subshell, + parse_error_tokenizer_unterminated_escape, + parse_error_tokenizer_other, parse_error_unbalancing_end, //end outside of block parse_error_unbalancing_else, //else outside of if diff --git a/parse_tree.cpp b/parse_tree.cpp index 6bb7a3cd3..ede14f4a5 100644 --- a/parse_tree.cpp +++ b/parse_tree.cpp @@ -583,7 +583,7 @@ class parse_ll_t void accept_tokens(parse_token_t token1, parse_token_t token2); /* Report tokenizer errors */ - void report_tokenizer_error(parse_token_t token, const wchar_t *tok_error); + void report_tokenizer_error(parse_token_t token, int tok_err, const wchar_t *tok_error); /* Indicate if we hit a fatal error */ bool has_fatal_error(void) const @@ -786,10 +786,31 @@ void parse_ll_t::parse_error_failed_production(struct parse_stack_element_t &sta } } -void parse_ll_t::report_tokenizer_error(parse_token_t token, const wchar_t *tok_error) +void parse_ll_t::report_tokenizer_error(parse_token_t token, int tok_err_code, const wchar_t *tok_error) { assert(tok_error != NULL); - this->parse_error(token, parse_error_tokenizer, L"%ls", tok_error); + parse_error_code_t parse_error_code; + switch (tok_err_code) + { + case TOK_UNTERMINATED_QUOTE: + parse_error_code = parse_error_tokenizer_unterminated_quote; + break; + + case TOK_UNTERMINATED_SUBSHELL: + parse_error_code = parse_error_tokenizer_unterminated_subshell; + break; + + case TOK_UNTERMINATED_ESCAPE: + parse_error_code = parse_error_tokenizer_unterminated_escape; + break; + + case TOK_OTHER: + default: + parse_error_code = parse_error_tokenizer_other; + break; + + } + this->parse_error(token, parse_error_code, L"%ls", tok_error); } void parse_ll_t::parse_error(const wchar_t *expected, parse_token_t token) @@ -1076,7 +1097,7 @@ static inline parse_token_t next_parse_token(tokenizer_t *tok) return result; } -bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t parse_flags, parse_node_tree_t *output, parse_error_list_t *errors, bool log_it) +bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t parse_flags, parse_node_tree_t *output, parse_error_list_t *errors) { parse_ll_t parser; parser.set_should_generate_error_messages(errors != NULL); @@ -1116,7 +1137,7 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t parse_flags, /* Handle tokenizer errors. This is a hack because really the parser should report this for itself; but it has no way of getting the tokenizer message */ if (queue[1].type == parse_special_type_tokenizer_error) { - parser.report_tokenizer_error(queue[1], tok_last(&tok)); + parser.report_tokenizer_error(queue[1], tok_get_error(&tok), tok_last(&tok)); } /* Handle errors */ diff --git a/parse_tree.h b/parse_tree.h index f77b87811..f2c6702e5 100644 --- a/parse_tree.h +++ b/parse_tree.h @@ -197,7 +197,7 @@ public: }; /* The big entry point. Parse a string! */ -bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse_node_tree_t *output, parse_error_list_t *errors, bool log_it = false); +bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse_node_tree_t *output, parse_error_list_t *errors); /* Fish grammar: diff --git a/parse_util.cpp b/parse_util.cpp index 491e47328..466217175 100644 --- a/parse_util.cpp +++ b/parse_util.cpp @@ -992,9 +992,27 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars // We detect this via an 'end_command' block without source bool has_unclosed_block = false; + // Whether there's an unclosed quote, and therefore unfinished + bool has_unclosed_quote = false; + // Parse the input string into a parse tree // Some errors are detected here bool parsed = parse_tree_from_string(buff_src, parse_flag_leave_unterminated, &node_tree, &parse_errors); + + for (size_t i=0; i < parse_errors.size(); i++) + { + if (parse_errors.at(i).code == parse_error_tokenizer_unterminated_quote) + { + // Remove this error, since we don't consider it a real error + has_unclosed_quote = true; + parse_errors.erase(parse_errors.begin() + i); + i--; + } + } + // #1238: If the only error was unterminated quote, then consider this to have parsed successfully. A better fix would be to have parse_tree_from_string return this information directly (but it would be a shame to munge up its nice bool return). + if (parse_errors.empty() && has_unclosed_quote) + parsed = true; + if (! parsed) { errored = true; @@ -1120,7 +1138,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars if (errored) res |= PARSER_TEST_ERROR; - if (has_unclosed_block) + if (has_unclosed_block || has_unclosed_quote) res |= PARSER_TEST_INCOMPLETE; if (out_errors)