Unify parse_execution_result_t and eval_result_t again

Do other cleanup to better express the difference between cancellation
and control flow.
This commit is contained in:
ridiculousfish
2019-12-17 18:10:29 -08:00
parent b82b111e55
commit b3d2cdc0ff
5 changed files with 212 additions and 232 deletions

View File

@@ -1032,12 +1032,16 @@ static void test_1_cancellation(const wchar_t *src) {
usleep(delay * 1E6);
pthread_kill(thread, SIGINT);
});
parser_t::principal_parser().eval(src, io_chain_t{filler}, TOP);
eval_result_t ret = parser_t::principal_parser().eval(src, io_chain_t{filler}, TOP);
fprintf(stderr, "eval: %d\n", (int)ret);
auto buffer = io_bufferfill_t::finish(std::move(filler));
if (buffer->buffer().size() != 0) {
err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n",
buffer->buffer().size(), src);
}
// TODO: cancelling out of command substitutions is currently reported as an error, not a
// cancellation.
// do_test(ret == eval_result_t::cancelled);
iothread_drain_all();
}

View File

@@ -183,27 +183,21 @@ process_type_t parse_execution_context_t::process_type_for_command(
return process_type;
}
bool parse_execution_context_t::should_cancel_execution(const block_t *block) const {
return cancellation_reason(block) != execution_cancellation_none;
}
parse_execution_context_t::execution_cancellation_reason_t
parse_execution_context_t::cancellation_reason(const block_t *block) const {
UNUSED(block);
maybe_t<eval_result_t> parse_execution_context_t::check_end_execution() const {
if (shell_is_exiting()) {
return execution_cancellation_exit;
return eval_result_t::cancelled;
}
if (parser && parser->cancellation_requested) {
return execution_cancellation_skip;
return eval_result_t::cancelled;
}
const auto &ld = parser->libdata();
if (ld.returning) {
return execution_cancellation_skip;
return eval_result_t::control_flow;
}
if (ld.loop_status != loop_status_t::normals) {
return execution_cancellation_loop_control;
return eval_result_t::control_flow;
}
return execution_cancellation_none;
return none();
}
/// Return whether the job contains a single statement, of block type, with no redirections.
@@ -240,9 +234,9 @@ bool parse_execution_context_t::job_is_simple_block(tnode_t<g::job> job_node) co
}
}
parse_execution_result_t parse_execution_context_t::run_if_statement(
tnode_t<g::if_statement> statement, const block_t *associated_block) {
parse_execution_result_t result = parse_execution_success;
eval_result_t parse_execution_context_t::run_if_statement(tnode_t<g::if_statement> statement,
const block_t *associated_block) {
eval_result_t result = eval_result_t::ok;
// We have a sequence of if clauses, with a final else, resulting in a single job list that we
// execute.
@@ -254,8 +248,8 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
trace_if_enabled(*parser, L"if");
for (;;) {
if (should_cancel_execution(associated_block)) {
result = parse_execution_cancelled;
if (auto ret = check_end_execution()) {
result = *ret;
break;
}
@@ -263,14 +257,14 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
tnode_t<g::job_conjunction> condition_head = if_clause.child<1>();
tnode_t<g::andor_job_list> condition_boolean_tail = if_clause.child<3>();
// Check the condition and the tail. We treat parse_execution_errored here as failure, in
// Check the condition and the tail. We treat eval_result_t::error here as failure, in
// accordance with historic behavior.
parse_execution_result_t cond_ret = run_job_conjunction(condition_head, associated_block);
if (cond_ret == parse_execution_success) {
eval_result_t cond_ret = run_job_conjunction(condition_head, associated_block);
if (cond_ret == eval_result_t::ok) {
cond_ret = run_job_list(condition_boolean_tail, associated_block);
}
const bool take_branch =
(cond_ret == parse_execution_success) && parser->get_last_status() == EXIT_SUCCESS;
(cond_ret == eval_result_t::ok) && parser->get_last_status() == EXIT_SUCCESS;
if (take_branch) {
// Condition succeeded.
@@ -304,8 +298,8 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
if (job_list_to_execute) {
block_t *ib = parser->push_block(block_t::if_block());
run_job_list(job_list_to_execute, ib);
if (should_cancel_execution(ib)) {
result = parse_execution_cancelled;
if (auto ret = check_end_execution()) {
result = *ret;
}
parser->pop_block(ib);
} else {
@@ -316,35 +310,33 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
trace_if_enabled(*parser, L"end if");
// It's possible there's a last-minute cancellation (issue #1297).
if (should_cancel_execution(associated_block)) {
result = parse_execution_cancelled;
if (auto ret = check_end_execution()) {
result = *ret;
}
// Otherwise, take the exit status of the job list. Reversal of issue #1061.
return result;
}
parse_execution_result_t parse_execution_context_t::run_begin_statement(
tnode_t<g::job_list> contents) {
eval_result_t parse_execution_context_t::run_begin_statement(tnode_t<g::job_list> contents) {
// Basic begin/end block. Push a scope block, run jobs, pop it
trace_if_enabled(*parser, L"begin");
block_t *sb = parser->push_block(block_t::scope_block(BEGIN));
parse_execution_result_t ret = run_job_list(contents, sb);
eval_result_t ret = run_job_list(contents, sb);
parser->pop_block(sb);
trace_if_enabled(*parser, L"end begin");
return ret;
}
// Define a function.
parse_execution_result_t parse_execution_context_t::run_function_statement(
tnode_t<g::function_header> header, tnode_t<g::job_list> body) {
eval_result_t parse_execution_context_t::run_function_statement(tnode_t<g::function_header> header,
tnode_t<g::job_list> body) {
// Get arguments.
wcstring_list_t arguments;
argument_node_list_t arg_nodes = header.descendants<g::argument>();
parse_execution_result_t result =
this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob);
eval_result_t result = this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob);
if (result != parse_execution_success) {
if (result != eval_result_t::ok) {
return result;
}
trace_if_enabled(*parser, L"function", arguments);
@@ -355,18 +347,18 @@ parse_execution_result_t parse_execution_context_t::run_function_statement(
wcstring errtext = streams.err.contents();
if (!errtext.empty()) {
this->report_error(header, L"%ls", errtext.c_str());
result = parse_execution_errored;
result = eval_result_t::error;
}
return result;
}
parse_execution_result_t parse_execution_context_t::run_block_statement(
tnode_t<g::block_statement> statement, const block_t *associated_block) {
eval_result_t parse_execution_context_t::run_block_statement(tnode_t<g::block_statement> statement,
const block_t *associated_block) {
tnode_t<g::block_header> bheader = statement.child<0>();
tnode_t<g::job_list> contents = statement.child<1>();
parse_execution_result_t ret = parse_execution_success;
eval_result_t ret = eval_result_t::ok;
if (auto header = bheader.try_get_child<g::for_header, 0>()) {
ret = run_for_statement(header, contents);
} else if (auto header = bheader.try_get_child<g::while_header, 0>()) {
@@ -391,7 +383,7 @@ bool parse_execution_context_t::is_function_context() const {
return is_within_function_call;
}
parse_execution_result_t parse_execution_context_t::run_for_statement(
eval_result_t parse_execution_context_t::run_for_statement(
tnode_t<grammar::for_header> header, tnode_t<grammar::job_list> block_contents) {
// Get the variable name: `for var_name in ...`. We expand the variable name. It better result
// in just one.
@@ -399,14 +391,14 @@ parse_execution_result_t parse_execution_context_t::run_for_statement(
wcstring for_var_name = get_source(var_name_node);
if (!expand_one(for_var_name, expand_flags_t{}, parser->vars(), parser->shared())) {
report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str());
return parse_execution_errored;
return eval_result_t::error;
}
// Get the contents to iterate over.
wcstring_list_t arguments;
parse_execution_result_t ret = this->expand_arguments_from_nodes(
get_argument_nodes(header.child<3>()), &arguments, nullglob);
if (ret != parse_execution_success) {
eval_result_t ret = this->expand_arguments_from_nodes(get_argument_nodes(header.child<3>()),
&arguments, nullglob);
if (ret != eval_result_t::ok) {
return ret;
}
@@ -418,13 +410,13 @@ parse_execution_result_t parse_execution_context_t::run_for_statement(
if (retval != ENV_OK) {
report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop",
for_var_name.c_str());
return parse_execution_errored;
return eval_result_t::error;
}
}
if (!valid_var_name(for_var_name)) {
report_error(var_name_node, BUILTIN_ERR_VARNAME, L"for", for_var_name.c_str());
return parse_execution_errored;
return eval_result_t::error;
}
trace_if_enabled(*parser, L"for", arguments);
@@ -432,8 +424,8 @@ parse_execution_result_t parse_execution_context_t::run_for_statement(
// Now drive the for loop.
for (const wcstring &val : arguments) {
if (should_cancel_execution(fb)) {
ret = parse_execution_cancelled;
if (auto reason = check_end_execution()) {
ret = *reason;
break;
}
@@ -445,7 +437,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement(
ld.loop_status = loop_status_t::normals;
this->run_job_list(block_contents, fb);
if (this->cancellation_reason(fb) == execution_cancellation_loop_control) {
if (check_end_execution() == eval_result_t::control_flow) {
// Handle break or continue.
bool do_break = (ld.loop_status == loop_status_t::breaks);
ld.loop_status = loop_status_t::normals;
@@ -460,9 +452,9 @@ parse_execution_result_t parse_execution_context_t::run_for_statement(
return ret;
}
parse_execution_result_t parse_execution_context_t::run_switch_statement(
eval_result_t parse_execution_context_t::run_switch_statement(
tnode_t<grammar::switch_statement> statement) {
parse_execution_result_t result = parse_execution_success;
eval_result_t result = eval_result_t::ok;
// Get the switch variable.
tnode_t<grammar::argument> switch_value_n = statement.child<1>();
@@ -495,13 +487,13 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
}
}
if (result == parse_execution_success && switch_values_expanded.size() > 1) {
if (result == eval_result_t::ok && switch_values_expanded.size() > 1) {
result =
report_error(switch_value_n, _(L"switch: Expected at most one argument, got %lu\n"),
switch_values_expanded.size());
}
if (result != parse_execution_success) {
if (result != eval_result_t::ok) {
return result;
}
@@ -514,8 +506,8 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
tnode_t<g::case_item_list> case_item_list = statement.child<3>();
tnode_t<g::case_item> matching_case_item{};
while (auto case_item = case_item_list.next_in_list<g::case_item>()) {
if (should_cancel_execution(sb)) {
result = parse_execution_cancelled;
if (auto ret = check_end_execution()) {
result = *ret;
break;
}
@@ -524,9 +516,9 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
// contains an unexpandable process will report and then fail to match.
auto arg_nodes = get_argument_nodes(case_item.child<1>());
wcstring_list_t case_args;
parse_execution_result_t case_result =
eval_result_t case_result =
this->expand_arguments_from_nodes(arg_nodes, &case_args, failglob);
if (case_result == parse_execution_success) {
if (case_result == eval_result_t::ok) {
for (const wcstring &arg : case_args) {
// Unescape wildcards so they can be expanded again.
wcstring unescaped_arg = parse_util_unescape_wildcards(arg);
@@ -544,7 +536,7 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
if (matching_case_item) {
// Success, evaluate the job list.
assert(result == parse_execution_success && "Expected success");
assert(result == eval_result_t::ok && "Expected success");
auto job_list = matching_case_item.child<3>();
result = this->run_job_list(job_list, sb);
}
@@ -553,10 +545,10 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
return result;
}
parse_execution_result_t parse_execution_context_t::run_while_statement(
tnode_t<grammar::while_header> header, tnode_t<grammar::job_list> contents,
const block_t *associated_block) {
parse_execution_result_t ret = parse_execution_success;
eval_result_t parse_execution_context_t::run_while_statement(tnode_t<grammar::while_header> header,
tnode_t<grammar::job_list> contents,
const block_t *associated_block) {
eval_result_t ret = eval_result_t::ok;
// "The exit status of the while loop shall be the exit status of the last compound-list-2
// executed, or zero if none was executed."
@@ -584,16 +576,15 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
first_cond_check = false;
// Check the condition.
parse_execution_result_t cond_ret =
this->run_job_conjunction(condition_head, associated_block);
if (cond_ret == parse_execution_success) {
eval_result_t cond_ret = this->run_job_conjunction(condition_head, associated_block);
if (cond_ret == eval_result_t::ok) {
cond_ret = run_job_list(condition_boolean_tail, associated_block);
}
// If the loop condition failed to execute, then exit the loop without modifying the exit
// status. If the loop condition executed with a failure status, restore the status and then
// exit the loop.
if (cond_ret != parse_execution_success) {
if (cond_ret != eval_result_t::ok) {
break;
} else if (parser->get_last_status() != EXIT_SUCCESS) {
parser->set_last_statuses(cond_saved_status);
@@ -601,8 +592,8 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
}
// Check cancellation.
if (this->should_cancel_execution(associated_block)) {
ret = parse_execution_cancelled;
if (auto reason = check_end_execution()) {
ret = *reason;
break;
}
@@ -612,10 +603,10 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
block_t *wb = parser->push_block(block_t::while_block());
this->run_job_list(contents, wb);
auto cancel_reason = this->cancellation_reason(wb);
auto cancel_reason = this->check_end_execution();
parser->pop_block(wb);
if (cancel_reason == execution_cancellation_loop_control) {
if (cancel_reason == eval_result_t::control_flow) {
// Handle break or continue.
bool do_break = (ld.loop_status == loop_status_t::breaks);
ld.loop_status = loop_status_t::normals;
@@ -636,10 +627,10 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
return ret;
}
// Reports an error. Always returns parse_execution_errored, so you can assign the result to an
// Reports an error. Always returns eval_result_t::error, so you can assign the result to an
// 'errored' variable.
parse_execution_result_t parse_execution_context_t::report_error(const parse_node_t &node,
const wchar_t *fmt, ...) const {
eval_result_t parse_execution_context_t::report_error(const parse_node_t &node, const wchar_t *fmt,
...) const {
// Create an error.
parse_error_list_t error_list = parse_error_list_t(1);
parse_error_t *error = &error_list.at(0);
@@ -653,11 +644,10 @@ parse_execution_result_t parse_execution_context_t::report_error(const parse_nod
va_end(va);
this->report_errors(error_list);
return parse_execution_errored;
return eval_result_t::error;
}
parse_execution_result_t parse_execution_context_t::report_errors(
const parse_error_list_t &error_list) const {
eval_result_t parse_execution_context_t::report_errors(const parse_error_list_t &error_list) const {
if (!parser->cancellation_requested) {
if (error_list.empty()) {
FLOG(error, L"Error reported but no error text found.");
@@ -672,19 +662,19 @@ parse_execution_result_t parse_execution_context_t::report_errors(
std::fwprintf(stderr, L"%ls", backtrace_and_desc.c_str());
}
}
return parse_execution_errored;
return eval_result_t::error;
}
/// Reports an unmatched wildcard error and returns parse_execution_errored.
parse_execution_result_t parse_execution_context_t::report_unmatched_wildcard_error(
/// Reports an unmatched wildcard error and returns eval_result_t::error.
eval_result_t parse_execution_context_t::report_unmatched_wildcard_error(
const parse_node_t &unmatched_wildcard) const {
parser->set_last_statuses(statuses_t::just(STATUS_UNMATCHED_WILDCARD));
report_error(unmatched_wildcard, WILDCARD_ERR_MSG, get_source(unmatched_wildcard).c_str());
return parse_execution_errored;
return eval_result_t::error;
}
/// Handle the case of command not found.
parse_execution_result_t parse_execution_context_t::handle_command_not_found(
eval_result_t parse_execution_context_t::handle_command_not_found(
const wcstring &cmd_str, tnode_t<g::plain_statement> statement, int err_code) {
// We couldn't find the specified command. This is a non-fatal error. We want to set the exit
// status to 127, which is the standard number used by other shells like bash and zsh.
@@ -698,10 +688,10 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found(
wcstring_list_t event_args;
{
auto args = get_argument_nodes(statement.child<1>());
parse_execution_result_t arg_result =
eval_result_t arg_result =
this->expand_arguments_from_nodes(args, &event_args, failglob);
if (arg_result != parse_execution_success) {
if (arg_result != eval_result_t::ok) {
return arg_result;
}
@@ -718,12 +708,12 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found(
int status = err_code == ENOENT ? STATUS_CMD_UNKNOWN : STATUS_NOT_EXECUTABLE;
parser->set_last_statuses(statuses_t::just(status));
return parse_execution_errored;
return eval_result_t::error;
}
parse_execution_result_t parse_execution_context_t::expand_command(
tnode_t<grammar::plain_statement> statement, wcstring *out_cmd,
wcstring_list_t *out_args) const {
eval_result_t parse_execution_context_t::expand_command(tnode_t<grammar::plain_statement> statement,
wcstring *out_cmd,
wcstring_list_t *out_args) const {
// Here we're expanding a command, for example $HOME/bin/stuff or $randomthing. The first
// completion becomes the command itself, everything after becomes arguments. Command
// substitutions are not supported.
@@ -753,11 +743,11 @@ parse_execution_result_t parse_execution_context_t::expand_command(
if (out_cmd->empty()) {
return this->report_error(statement, _(L"The expanded command was empty."));
}
return parse_execution_success;
return eval_result_t::ok;
}
/// Creates a 'normal' (non-block) process.
parse_execution_result_t parse_execution_context_t::populate_plain_process(
eval_result_t parse_execution_context_t::populate_plain_process(
job_t *job, process_t *proc, tnode_t<grammar::plain_statement> statement) {
assert(job != nullptr);
assert(proc != nullptr);
@@ -769,7 +759,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
wcstring cmd;
wcstring_list_t args_from_cmd_expansion;
auto ret = expand_command(statement, &cmd, &args_from_cmd_expansion);
if (ret != parse_execution_success) {
if (ret != eval_result_t::ok) {
return ret;
}
assert(!cmd.empty() && "expand_command should not produce an empty command");
@@ -796,7 +786,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
if (isatty(STDIN_FILENO) && current_run_count - 1 != last_exec_run_count) {
reader_bg_job_warning(*parser);
last_exec_run_count = current_run_count;
return parse_execution_errored;
return eval_result_t::error;
} else {
hup_background_jobs(*parser);
}
@@ -852,15 +842,15 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
cmd_args.insert(cmd_args.end(), args_from_cmd_expansion.begin(),
args_from_cmd_expansion.end());
argument_node_list_t arg_nodes = statement.descendants<g::argument>();
parse_execution_result_t arg_result =
eval_result_t arg_result =
this->expand_arguments_from_nodes(arg_nodes, &cmd_args, glob_behavior);
if (arg_result != parse_execution_success) {
if (arg_result != eval_result_t::ok) {
return arg_result;
}
// The set of IO redirections that we construct for the process.
if (!this->determine_redirections(statement.child<1>(), &redirections)) {
return parse_execution_errored;
return eval_result_t::error;
}
// Determine the process type.
@@ -872,12 +862,12 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
proc->set_argv(cmd_args);
proc->set_redirection_specs(std::move(redirections));
proc->actual_cmd = std::move(path_to_external_command);
return parse_execution_success;
return eval_result_t::ok;
}
// Determine the list of arguments, expanding stuff. Reports any errors caused by expansion. If we
// have a wildcard that could not be expanded, report the error and continue.
parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes(
eval_result_t parse_execution_context_t::expand_arguments_from_nodes(
const argument_node_list_t &argument_nodes, wcstring_list_t *out_arguments,
globspec_t glob_behavior) {
// Get all argument nodes underneath the statement. We guess we'll have that many arguments (but
@@ -897,14 +887,13 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes(
parse_error_offset_source_start(&errors, arg_node.source_range()->start);
switch (expand_ret) {
case expand_result_t::error: {
this->report_errors(errors);
return parse_execution_errored;
return this->report_errors(errors);
}
case expand_result_t::wildcard_no_match: {
if (glob_behavior == failglob) {
// Report the unmatched wildcard error and stop processing.
report_unmatched_wildcard_error(arg_node);
return parse_execution_errored;
return eval_result_t::error;
}
break;
}
@@ -927,11 +916,11 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes(
}
// We may have received a cancellation during this expansion.
if (parser->cancellation_requested) {
return parse_execution_cancelled;
if (auto ret = check_end_execution()) {
return *ret;
}
return parse_execution_success;
return eval_result_t::ok;
}
bool parse_execution_context_t::determine_redirections(
@@ -981,7 +970,7 @@ bool parse_execution_context_t::determine_redirections(
return true;
}
parse_execution_result_t parse_execution_context_t::populate_not_process(
eval_result_t parse_execution_context_t::populate_not_process(
job_t *job, process_t *proc, tnode_t<g::not_statement> not_statement) {
auto &flags = job->mut_flags();
flags.negate = !flags.negate;
@@ -991,9 +980,9 @@ parse_execution_result_t parse_execution_context_t::populate_not_process(
}
template <typename Type>
parse_execution_result_t parse_execution_context_t::populate_block_process(
job_t *job, process_t *proc, tnode_t<g::statement> statement,
tnode_t<Type> specific_statement) {
eval_result_t parse_execution_context_t::populate_block_process(job_t *job, process_t *proc,
tnode_t<g::statement> statement,
tnode_t<Type> specific_statement) {
// We handle block statements by creating process_type_t::block_node, that will bounce back to
// us when it's time to execute them.
UNUSED(job);
@@ -1008,22 +997,22 @@ parse_execution_result_t parse_execution_context_t::populate_block_process(
auto arguments = specific_statement.template find_child<g::arguments_or_redirections_list>();
redirection_spec_list_t redirections;
if (!this->determine_redirections(arguments, &redirections)) {
return parse_execution_errored;
return eval_result_t::error;
}
proc->type = process_type_t::block_node;
proc->block_node_source = pstree;
proc->internal_block_node = statement;
proc->set_redirection_specs(std::move(redirections));
return parse_execution_success;
return eval_result_t::ok;
}
parse_execution_result_t parse_execution_context_t::apply_variable_assignments(
eval_result_t parse_execution_context_t::apply_variable_assignments(
process_t *proc, tnode_t<grammar::variable_assignments> variable_assignments,
const block_t **block) {
variable_assignment_node_list_t assignment_list =
get_variable_assignment_nodes(variable_assignments);
if (assignment_list.empty()) return parse_execution_success;
if (assignment_list.empty()) return eval_result_t::ok;
*block = parser->push_block(block_t::variable_assignment_block());
for (const auto &variable_assignment : assignment_list) {
const wcstring &source = variable_assignment.get_source(pstree->src);
@@ -1042,7 +1031,7 @@ parse_execution_result_t parse_execution_context_t::apply_variable_assignments(
switch (expand_ret) {
case expand_result_t::error: {
this->report_errors(errors);
return parse_execution_errored;
return eval_result_t::error;
}
case expand_result_t::wildcard_no_match: // nullglob (equivalent to set)
case expand_result_t::wildcard_match:
@@ -1061,22 +1050,21 @@ parse_execution_result_t parse_execution_context_t::apply_variable_assignments(
if (proc) proc->variable_assignments.push_back({variable_name, vals});
parser->vars().set(std::move(variable_name), ENV_LOCAL | ENV_EXPORT, std::move(vals));
}
return parse_execution_success;
return eval_result_t::ok;
}
parse_execution_result_t parse_execution_context_t::populate_job_process(
eval_result_t parse_execution_context_t::populate_job_process(
job_t *job, process_t *proc, tnode_t<grammar::statement> statement,
tnode_t<grammar::variable_assignments> variable_assignments) {
// Get the "specific statement" which is boolean / block / if / switch / decorated.
const parse_node_t &specific_statement = statement.get_child_node<0>();
const block_t *block = nullptr;
parse_execution_result_t result =
this->apply_variable_assignments(proc, variable_assignments, &block);
eval_result_t result = this->apply_variable_assignments(proc, variable_assignments, &block);
cleanup_t scope([&]() {
if (block) parser->pop_block(block);
});
if (result != parse_execution_success) return parse_execution_errored;
if (result != eval_result_t::ok) return eval_result_t::error;
switch (specific_statement.type) {
case symbol_not_statement: {
@@ -1113,7 +1101,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_process(
return result;
}
parse_execution_result_t parse_execution_context_t::populate_job_from_job_node(
eval_result_t parse_execution_context_t::populate_job_from_job_node(
job_t *j, tnode_t<grammar::job> job_node, const block_t *associated_block) {
UNUSED(associated_block);
@@ -1128,7 +1116,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node(
// Create processes. Each one may fail.
process_list_t processes;
processes.emplace_back(new process_t());
parse_execution_result_t result =
eval_result_t result =
this->populate_job_process(j, processes.back().get(), statement, variable_assignments);
// Construct process_ts for job continuations (pipelines), by walking the list until we hit the
@@ -1136,7 +1124,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node(
tnode_t<g::job_continuation> job_cont = job_node.child<2>();
assert(job_cont);
while (auto pipe = job_cont.try_get_child<g::tok_pipe, 0>()) {
if (result != parse_execution_success) {
if (result != eval_result_t::ok) {
break;
}
auto variable_assignments = job_cont.require_get_child<g::variable_assignments, 2>();
@@ -1173,7 +1161,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node(
processes.back()->is_last_in_job = true;
// Return what happened.
if (result == parse_execution_success) {
if (result == eval_result_t::ok) {
// Link up the processes.
assert(!processes.empty()); //!OCLINT(multiple unary operator)
j->processes = std::move(processes);
@@ -1191,10 +1179,10 @@ static bool remove_job(parser_t &parser, job_t *job) {
return false;
}
parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_node,
const block_t *associated_block) {
if (should_cancel_execution(associated_block)) {
return parse_execution_cancelled;
eval_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_node,
const block_t *associated_block) {
if (auto ret = check_end_execution()) {
return *ret;
}
// Get terminal modes.
@@ -1202,7 +1190,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
if (parser->is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) {
// Need real error handling here.
wperror(L"tcgetattr");
return parse_execution_errored;
return eval_result_t::error;
}
// Increment the eval_level for the duration of this command.
@@ -1225,7 +1213,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
if (job_is_simple_block(job_node)) {
tnode_t<g::variable_assignments> variable_assignments = job_node.child<0>();
const block_t *block = nullptr;
parse_execution_result_t result =
eval_result_t result =
this->apply_variable_assignments(nullptr, variable_assignments, &block);
cleanup_t scope([&]() {
if (block) parser->pop_block(block);
@@ -1234,7 +1222,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
tnode_t<g::statement> statement = job_node.child<1>();
const parse_node_t &specific_statement = statement.get_child_node<0>();
assert(specific_statement_type_is_redirectable_block(specific_statement));
if (result == parse_execution_success) {
if (result == eval_result_t::ok) {
switch (specific_statement.type) {
case symbol_block_statement: {
result =
@@ -1302,7 +1290,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
// Populate the job. This may fail for reasons like command_not_found. If this fails, an error
// will have been printed.
parse_execution_result_t pop_result =
eval_result_t pop_result =
this->populate_job_from_job_node(job.get(), job_node, associated_block);
assert(libdata.caller_job_id == job->job_id && "Caller job ID unexpectedly changed");
@@ -1314,8 +1302,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
}
// Clean up the job on failure or cancellation.
bool populated_job = (pop_result == parse_execution_success);
if (populated_job) {
if (pop_result == eval_result_t::ok) {
// Success. Give the job to the parser - it will clean it up.
parser->job_add(job);
@@ -1346,21 +1333,24 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
profile_item->parse = static_cast<int>(parse_time - start_time);
profile_item->exec = static_cast<int>(exec_time - parse_time);
profile_item->cmd = job ? job->command() : wcstring();
profile_item->skipped = !populated_job;
profile_item->skipped = (pop_result != eval_result_t::ok);
}
job_reap(*parser, false); // clean up jobs
return populated_job ? parse_execution_success : parse_execution_errored;
return pop_result;
}
parse_execution_result_t parse_execution_context_t::run_job_conjunction(
eval_result_t parse_execution_context_t::run_job_conjunction(
tnode_t<grammar::job_conjunction> job_expr, const block_t *associated_block) {
parse_execution_result_t result = parse_execution_success;
eval_result_t result = eval_result_t::ok;
tnode_t<g::job_conjunction> cursor = job_expr;
// continuation is the parent of the cursor
tnode_t<g::job_conjunction_continuation> continuation;
while (cursor) {
if (should_cancel_execution(associated_block)) break;
if (auto reason = check_end_execution()) {
result = *reason;
break;
}
bool skip = false;
if (continuation) {
// Check the conjunction type.
@@ -1391,20 +1381,23 @@ bool parse_execution_context_t::should_skip(parse_bool_statement_type_t type) co
}
template <typename Type>
parse_execution_result_t parse_execution_context_t::run_job_list(tnode_t<Type> job_list,
const block_t *associated_block) {
eval_result_t parse_execution_context_t::run_job_list(tnode_t<Type> job_list,
const block_t *associated_block) {
// We handle both job_list and andor_job_list uniformly.
static_assert(Type::token == symbol_job_list || Type::token == symbol_andor_job_list,
"Not a job list");
parse_execution_result_t result = parse_execution_success;
eval_result_t result = eval_result_t::ok;
while (auto job_conj = job_list.template next_in_list<g::job_conjunction>()) {
if (should_cancel_execution(associated_block)) break;
if (auto reason = check_end_execution()) {
result = *reason;
break;
}
// Maybe skip the job if it has a leading and/or.
// Skipping is treated as success.
if (should_skip(get_decorator(job_conj))) {
result = parse_execution_success;
result = eval_result_t::ok;
} else {
result = this->run_job_conjunction(job_conj, associated_block);
}
@@ -1414,11 +1407,11 @@ parse_execution_result_t parse_execution_context_t::run_job_list(tnode_t<Type> j
return result;
}
parse_execution_result_t parse_execution_context_t::eval_node(tnode_t<g::statement> statement,
const block_t *associated_block) {
eval_result_t parse_execution_context_t::eval_node(tnode_t<g::statement> statement,
const block_t *associated_block) {
assert(statement && "Empty node in eval_node");
assert(statement.matches_node_tree(tree()) && "statement has unexpected tree");
enum parse_execution_result_t status = parse_execution_success;
enum eval_result_t status = eval_result_t::ok;
if (auto block = statement.try_get_child<g::block_statement, 0>()) {
status = this->run_block_statement(block, associated_block);
} else if (auto ifstat = statement.try_get_child<g::if_statement, 0>()) {
@@ -1433,8 +1426,8 @@ parse_execution_result_t parse_execution_context_t::eval_node(tnode_t<g::stateme
return status;
}
parse_execution_result_t parse_execution_context_t::eval_node(tnode_t<g::job_list> job_list,
const block_t *associated_block) {
eval_result_t parse_execution_context_t::eval_node(tnode_t<g::job_list> job_list,
const block_t *associated_block) {
// Apply this block IO for the duration of this function.
assert(job_list && "Empty node in eval_node");
assert(job_list.matches_node_tree(tree()) && "job_list has unexpected tree");

View File

@@ -13,14 +13,21 @@
class block_t;
class parser_t;
enum parse_execution_result_t {
/// The job was successfully executed (though it have failed on its own).
parse_execution_success,
/// The job did not execute due to some error (e.g. failed to wildcard expand). An error will
/// have been printed and proc_last_status will have been set.
parse_execution_errored,
/// The job was cancelled (e.g. Ctrl-C).
parse_execution_cancelled,
/// An eval_result represents evaluation errors including wildcards which failed to match, syntax
/// errors, or other expansion errors. It also tracks when evaluation was skipped due to signal
/// cancellation. Note it does not track the exit status of commands.
enum class eval_result_t {
/// Evaluation was successfull.
ok,
/// Evaluation was skipped due to control flow (break or return).
control_flow,
/// Evaluation was cancelled, e.g. because of a signal or exit.
cancelled,
/// A parse error or failed expansion (but not an error exit status from a command).
error,
};
class parse_execution_context_t {
@@ -38,31 +45,22 @@ class parse_execution_context_t {
parse_execution_context_t(const parse_execution_context_t &) = delete;
parse_execution_context_t &operator=(const parse_execution_context_t &) = delete;
// Should I cancel?
bool should_cancel_execution(const block_t *block) const;
// Check to see if we should end execution.
// \return the eval result to end with, or none() to continue on.
// This will never return eval_result_t::ok.
maybe_t<eval_result_t> check_end_execution() const;
// Ways that we can stop executing a block. These are in a sort of ascending order of
// importance, e.g. `exit` should trump `break`.
enum execution_cancellation_reason_t {
execution_cancellation_none,
execution_cancellation_loop_control,
execution_cancellation_skip,
execution_cancellation_exit
};
execution_cancellation_reason_t cancellation_reason(const block_t *block) const;
// Report an error. Always returns true.
parse_execution_result_t report_error(const parse_node_t &node, const wchar_t *fmt, ...) const;
parse_execution_result_t report_errors(const parse_error_list_t &error_list) const;
// Report an error. Always returns 'eval_result_t::error'.
eval_result_t report_error(const parse_node_t &node, const wchar_t *fmt, ...) const;
eval_result_t report_errors(const parse_error_list_t &error_list) const;
// Wildcard error helper.
parse_execution_result_t report_unmatched_wildcard_error(
const parse_node_t &unmatched_wildcard) const;
eval_result_t report_unmatched_wildcard_error(const parse_node_t &unmatched_wildcard) const;
/// Command not found support.
parse_execution_result_t handle_command_not_found(const wcstring &cmd,
tnode_t<grammar::plain_statement> statement,
int err_code);
eval_result_t handle_command_not_found(const wcstring &cmd,
tnode_t<grammar::plain_statement> statement,
int err_code);
// Utilities
wcstring get_source(const parse_node_t &node) const;
@@ -72,8 +70,8 @@ class parse_execution_context_t {
// Expand a command which may contain variables, producing an expand command and possibly
// arguments. Prints an error message on error.
parse_execution_result_t expand_command(tnode_t<grammar::plain_statement> statement,
wcstring *out_cmd, wcstring_list_t *out_args) const;
eval_result_t expand_command(tnode_t<grammar::plain_statement> statement, wcstring *out_cmd,
wcstring_list_t *out_args) const;
/// Return whether we should skip a job with the given bool statement type.
bool should_skip(parse_bool_statement_type_t type) const;
@@ -83,58 +81,57 @@ class parse_execution_context_t {
enum process_type_t process_type_for_command(tnode_t<grammar::plain_statement> statement,
const wcstring &cmd) const;
parse_execution_result_t apply_variable_assignments(
eval_result_t apply_variable_assignments(
process_t *proc, tnode_t<grammar::variable_assignments> variable_assignments,
const block_t **block);
// These create process_t structures from statements.
parse_execution_result_t populate_job_process(
job_t *job, process_t *proc, tnode_t<grammar::statement> statement,
tnode_t<grammar::variable_assignments> variable_assignments);
parse_execution_result_t populate_not_process(job_t *job, process_t *proc,
tnode_t<grammar::not_statement> not_statement);
parse_execution_result_t populate_plain_process(job_t *job, process_t *proc,
tnode_t<grammar::plain_statement> statement);
eval_result_t populate_job_process(job_t *job, process_t *proc,
tnode_t<grammar::statement> statement,
tnode_t<grammar::variable_assignments> variable_assignments);
eval_result_t populate_not_process(job_t *job, process_t *proc,
tnode_t<grammar::not_statement> not_statement);
eval_result_t populate_plain_process(job_t *job, process_t *proc,
tnode_t<grammar::plain_statement> statement);
template <typename Type>
parse_execution_result_t populate_block_process(job_t *job, process_t *proc,
tnode_t<grammar::statement> statement,
tnode_t<Type> specific_statement);
eval_result_t populate_block_process(job_t *job, process_t *proc,
tnode_t<grammar::statement> statement,
tnode_t<Type> specific_statement);
// These encapsulate the actual logic of various (block) statements.
parse_execution_result_t run_block_statement(tnode_t<grammar::block_statement> statement,
const block_t *associated_block);
parse_execution_result_t run_for_statement(tnode_t<grammar::for_header> header,
tnode_t<grammar::job_list> contents);
parse_execution_result_t run_if_statement(tnode_t<grammar::if_statement> statement,
const block_t *associated_block);
parse_execution_result_t run_switch_statement(tnode_t<grammar::switch_statement> statement);
parse_execution_result_t run_while_statement(tnode_t<grammar::while_header> header,
tnode_t<grammar::job_list> contents,
const block_t *associated_block);
parse_execution_result_t run_function_statement(tnode_t<grammar::function_header> header,
tnode_t<grammar::job_list> body);
parse_execution_result_t run_begin_statement(tnode_t<grammar::job_list> contents);
eval_result_t run_block_statement(tnode_t<grammar::block_statement> statement,
const block_t *associated_block);
eval_result_t run_for_statement(tnode_t<grammar::for_header> header,
tnode_t<grammar::job_list> contents);
eval_result_t run_if_statement(tnode_t<grammar::if_statement> statement,
const block_t *associated_block);
eval_result_t run_switch_statement(tnode_t<grammar::switch_statement> statement);
eval_result_t run_while_statement(tnode_t<grammar::while_header> header,
tnode_t<grammar::job_list> contents,
const block_t *associated_block);
eval_result_t run_function_statement(tnode_t<grammar::function_header> header,
tnode_t<grammar::job_list> body);
eval_result_t run_begin_statement(tnode_t<grammar::job_list> contents);
enum globspec_t { failglob, nullglob };
using argument_node_list_t = std::vector<tnode_t<grammar::argument>>;
parse_execution_result_t expand_arguments_from_nodes(const argument_node_list_t &argument_nodes,
wcstring_list_t *out_arguments,
globspec_t glob_behavior);
eval_result_t expand_arguments_from_nodes(const argument_node_list_t &argument_nodes,
wcstring_list_t *out_arguments,
globspec_t glob_behavior);
// Determines the list of redirections for a node. Returns none() on failure, for example, an
// invalid fd.
bool determine_redirections(tnode_t<grammar::arguments_or_redirections_list> node,
redirection_spec_list_t *out_redirs);
parse_execution_result_t run_1_job(tnode_t<grammar::job> job, const block_t *associated_block);
parse_execution_result_t run_job_conjunction(tnode_t<grammar::job_conjunction> job_expr,
const block_t *associated_block);
eval_result_t run_1_job(tnode_t<grammar::job> job, const block_t *associated_block);
eval_result_t run_job_conjunction(tnode_t<grammar::job_conjunction> job_expr,
const block_t *associated_block);
template <typename Type>
parse_execution_result_t run_job_list(tnode_t<Type> job_list_node,
const block_t *associated_block);
parse_execution_result_t populate_job_from_job_node(job_t *j, tnode_t<grammar::job> job_node,
const block_t *associated_block);
eval_result_t run_job_list(tnode_t<Type> job_list_node, const block_t *associated_block);
eval_result_t populate_job_from_job_node(job_t *j, tnode_t<grammar::job> job_node,
const block_t *associated_block);
// Returns the line number of the node. Not const since it touches cached_lineno_offset.
int line_offset_of_node(tnode_t<grammar::job> node);
@@ -158,10 +155,8 @@ class parse_execution_context_t {
/// Start executing at the given node. Returns 0 if there was no error, 1 if there was an
/// error.
parse_execution_result_t eval_node(tnode_t<grammar::statement> statement,
const block_t *associated_block);
parse_execution_result_t eval_node(tnode_t<grammar::job_list> job_list,
const block_t *associated_block);
eval_result_t eval_node(tnode_t<grammar::statement> statement, const block_t *associated_block);
eval_result_t eval_node(tnode_t<grammar::job_list> job_list, const block_t *associated_block);
};
#endif

View File

@@ -674,19 +674,18 @@ eval_result_t parser_t::eval_node(parsed_source_ref_t ps, tnode_t<T> node, block
using exc_ctx_ref_t = std::unique_ptr<parse_execution_context_t>;
scoped_push<exc_ctx_ref_t> exc(
&execution_context, make_unique<parse_execution_context_t>(ps, this, std::move(lineage)));
parse_execution_result_t res = execution_context->eval_node(node, scope_block);
eval_result_t res = execution_context->eval_node(node, scope_block);
exc.restore();
this->pop_block(scope_block);
job_reap(*this, false); // reap again
switch (res) {
case parse_execution_success:
return eval_result_t::ok;
case parse_execution_errored:
return eval_result_t::error;
case parse_execution_cancelled:
return eval_result_t::cancelled;
// control_flow is used internally to react to break and return.
// Here we treat that as success.
if (res == eval_result_t::control_flow) {
res = eval_result_t::ok;
}
return res;
}
// Explicit instantiations. TODO: use overloads instead?

View File

@@ -15,6 +15,7 @@
#include "event.h"
#include "expand.h"
#include "parse_constants.h"
#include "parse_execution.h"
#include "parse_tree.h"
#include "proc.h"
@@ -53,18 +54,6 @@ enum class loop_status_t {
continues, /// current loop block should be skipped
};
/// Result of the source code form of eval.
enum class eval_result_t {
/// eval was able to evaluate the source or tree.
ok,
/// Evaluation was cancelled, e.g. because of a signal.
cancelled,
/// Parse or execution error (but not simply a failed command).
error,
};
/// block_t represents a block of commands.
class block_t {
/// Construct from a block type.