Fix for issue where unterminated quotes would attempt to be executed,

instead of continuing edit onto the next line.
This commit is contained in:
ridiculousfish 2014-01-14 00:01:26 -08:00
parent b9394b9599
commit dc8014562b
6 changed files with 55 additions and 11 deletions

View File

@ -4298,7 +4298,7 @@ int builtin_parse(parser_t &parser, wchar_t **argv)
const wcstring src = str2wcstring(&txt.at(0), txt.size()); const wcstring src = str2wcstring(&txt.at(0), txt.size());
parse_node_tree_t parse_tree; parse_node_tree_t parse_tree;
parse_error_list_t errors; 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) if (! success)
{ {
stdout_buffer.append(L"Parsing failed:\n"); 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"); stdout_buffer.append(L"(Reparsed with continue after error)\n");
parse_tree.clear(); parse_tree.clear();
errors.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); const wcstring dump = parse_dump_tree(parse_tree, src);
stdout_buffer.append(dump); stdout_buffer.append(dump);

View File

@ -2452,7 +2452,8 @@ static void test_new_parser_errors(void)
} }
tests[] = 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"end", parse_error_unbalancing_end},
{L"echo hi ; end", parse_error_unbalancing_end}, {L"echo hi ; end", parse_error_unbalancing_end},

View File

@ -114,7 +114,11 @@ enum parse_error_code_t
parse_error_generic, // unclassified error types 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_end, //end outside of block
parse_error_unbalancing_else, //else outside of if parse_error_unbalancing_else, //else outside of if

View File

@ -583,7 +583,7 @@ class parse_ll_t
void accept_tokens(parse_token_t token1, parse_token_t token2); void accept_tokens(parse_token_t token1, parse_token_t token2);
/* Report tokenizer errors */ /* 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 */ /* Indicate if we hit a fatal error */
bool has_fatal_error(void) const 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); 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) 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; 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; parse_ll_t parser;
parser.set_should_generate_error_messages(errors != NULL); 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 */ /* 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) 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 */ /* Handle errors */

View File

@ -197,7 +197,7 @@ public:
}; };
/* The big entry point. Parse a string! */ /* 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: /* Fish grammar:

View File

@ -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 // We detect this via an 'end_command' block without source
bool has_unclosed_block = false; 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 // Parse the input string into a parse tree
// Some errors are detected here // Some errors are detected here
bool parsed = parse_tree_from_string(buff_src, parse_flag_leave_unterminated, &node_tree, &parse_errors); 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) if (! parsed)
{ {
errored = true; errored = true;
@ -1120,7 +1138,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars
if (errored) if (errored)
res |= PARSER_TEST_ERROR; res |= PARSER_TEST_ERROR;
if (has_unclosed_block) if (has_unclosed_block || has_unclosed_quote)
res |= PARSER_TEST_INCOMPLETE; res |= PARSER_TEST_INCOMPLETE;
if (out_errors) if (out_errors)