Reimplement exec parsing. Instead of special-casing exec as a command,

promote it to a decoration (like 'command' or 'builtin'). This makes tab
completion and syntax highlighting treat exec's first argument as a
command and is otherwise a nice simplification. Fixes #1300
This commit is contained in:
ridiculousfish 2014-02-13 10:08:04 -08:00
parent f733dc5eae
commit 1fbf633817
9 changed files with 36 additions and 15 deletions

View file

@ -1915,6 +1915,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
break;
case parse_statement_decoration_command:
case parse_statement_decoration_exec:
use_command = true;
use_function = false;
use_builtin = false;

View file

@ -141,9 +141,16 @@ static void err(const wchar_t *blah, ...)
va_start(va, blah);
err_count++;
// show errors in red
fputs("\x1b[31m", stdout);
wprintf(L"Error: ");
vwprintf(blah, va);
va_end(va);
// return to normal color
fputs("\x1b[0m", stdout);
wprintf(L"\n");
}
@ -2454,6 +2461,7 @@ static void test_new_parser_ll2(void)
{
{L"echo hello", L"echo", L"hello", parse_statement_decoration_none},
{L"command echo hello", L"echo", L"hello", parse_statement_decoration_command},
{L"exec echo hello", L"echo", L"hello", parse_statement_decoration_exec},
{L"command command hello", L"command", L"hello", parse_statement_decoration_command},
{L"builtin command hello", L"command", L"hello", parse_statement_decoration_builtin},
{L"command --help", L"command", L"--help", parse_statement_decoration_none},

View file

@ -1236,7 +1236,7 @@ static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoratio
{
/* Determine which types we check, based on the decoration */
bool builtin_ok = true, function_ok = true, abbreviation_ok = true, command_ok = true, implicit_cd_ok = true;
if (decoration == parse_statement_decoration_command)
if (decoration == parse_statement_decoration_command || decoration == parse_statement_decoration_exec)
{
builtin_ok = false;
function_ok = false;

View file

@ -90,6 +90,7 @@ enum parse_keyword_t
parse_keyword_not,
parse_keyword_command,
parse_keyword_builtin,
parse_keyword_exec,
LAST_KEYWORD = parse_keyword_builtin
};
@ -99,7 +100,8 @@ enum parse_statement_decoration_t
{
parse_statement_decoration_none,
parse_statement_decoration_command,
parse_statement_decoration_builtin
parse_statement_decoration_builtin,
parse_statement_decoration_exec
};
/* Parse error code list */

View file

@ -161,10 +161,9 @@ enum process_type_t parse_execution_context_t::process_type_for_command(const pa
/* Determine the process type, which depends on the statement decoration (command, builtin, etc) */
enum parse_statement_decoration_t decoration = tree.decoration_for_plain_statement(plain_statement);
/* Do the "exec hack" */
if (decoration != parse_statement_decoration_command && cmd == L"exec")
if (decoration == parse_statement_decoration_exec)
{
/* Either 'builtin exec' or just plain 'exec', and definitely not 'command exec'. Note we don't allow overriding exec with a function. */
/* Always exec */
process_type = INTERNAL_EXEC;
}
else if (decoration == parse_statement_decoration_command)
@ -848,7 +847,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(job_t
}
wcstring path_to_external_command;
if (process_type == EXTERNAL)
if (process_type == EXTERNAL || process_type == INTERNAL_EXEC)
{
/* Determine the actual command. This may be an implicit cd. */
bool has_command = path_get_path(cmd, &path_to_external_command);

View file

@ -357,6 +357,7 @@ PRODUCTIONS(decorated_statement) =
{symbol_plain_statement},
{KEYWORD(parse_keyword_command), symbol_plain_statement},
{KEYWORD(parse_keyword_builtin), symbol_plain_statement},
{KEYWORD(parse_keyword_exec), symbol_plain_statement}
};
RESOLVE(decorated_statement)
{
@ -374,6 +375,8 @@ RESOLVE(decorated_statement)
return 1;
case parse_keyword_builtin:
return 2;
case parse_keyword_exec:
return 3;
}
}

View file

@ -237,6 +237,8 @@ wcstring keyword_description(parse_keyword_t k)
return L"command";
case parse_keyword_builtin:
return L"builtin";
case parse_keyword_exec:
return L"exec";
}
return format_string(L"Unknown keyword type %ld", static_cast<long>(k));
}
@ -1049,7 +1051,8 @@ static parse_keyword_t keyword_for_token(token_type tok, const wchar_t *tok_txt)
{L"or", parse_keyword_or},
{L"not", parse_keyword_not},
{L"command", parse_keyword_command},
{L"builtin", parse_keyword_builtin}
{L"builtin", parse_keyword_builtin},
{L"exec", parse_keyword_exec}
};
for (size_t i=0; i < sizeof keywords / sizeof *keywords; i++)

View file

@ -247,9 +247,9 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse
boolean_statement = AND statement | OR statement | NOT statement
# A decorated_statement is a command with a list of arguments_or_redirections, possibly with "builtin" or "command"
# A decorated_statement is a command with a list of arguments_or_redirections, possibly with "builtin" or "command" or "exec"
decorated_statement = plain_statement | COMMAND plain_statement | BUILTIN plain_statement
decorated_statement = plain_statement | COMMAND plain_statement | BUILTIN plain_statement | EXEC plain_statement
plain_statement = <TOK_STRING> arguments_or_redirections_list optional_background
argument_list = <empty> | argument argument_list

View file

@ -1067,6 +1067,15 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars
}
else if (node.type == symbol_plain_statement)
{
// In a few places below, we want to know if we are in a pipeline
const bool is_in_pipeline = node_tree.statement_is_in_pipeline(node, true /* count first */);
// Check that we don't try to pipe through exec
if (is_in_pipeline && node_tree.decoration_for_plain_statement(node) == parse_statement_decoration_exec)
{
errored = append_syntax_error(&parse_errors, node, EXEC_ERR_MSG, L"exec");
}
wcstring command;
if (node_tree.command_for_plain_statement(node, buff_src, &command))
{
@ -1077,14 +1086,10 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src, pars
}
// Check that pipes are sound
if (! errored && parser_is_pipe_forbidden(command))
{
// forbidden commands cannot be in a pipeline at all
if (node_tree.statement_is_in_pipeline(node, true /* count first */))
if (! errored && parser_is_pipe_forbidden(command) && is_in_pipeline)
{
errored = append_syntax_error(&parse_errors, node, EXEC_ERR_MSG, command.c_str());
}
}
// Check that we don't return from outside a function
// But we allow it if it's 'return --help'