Execute the conditions of if and while statements outside of their block

Variables set in if and while conditions are in the enclosing block, not
the if/while statement block. For example:

    if set -l var (somecommand) ; end
    echo $var

will now work as expected.

Fixes #4820. Fixes #1212.
This commit is contained in:
ridiculousfish
2018-03-31 14:57:24 -07:00
parent ba06a89923
commit 4b079e16e5
9 changed files with 90 additions and 36 deletions

View File

@@ -226,10 +226,7 @@ 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) {
// Push an if block.
if_block_t *ib = parser->push_block<if_block_t>();
tnode_t<g::if_statement> statement, const block_t *associated_block) {
parse_execution_result_t result = parse_execution_success;
// We have a sequence of if clauses, with a final else, resulting in a single job list that we
@@ -238,7 +235,7 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
tnode_t<g::if_clause> if_clause = statement.child<0>();
tnode_t<g::else_clause> else_clause = statement.child<1>();
for (;;) {
if (should_cancel_execution(ib)) {
if (should_cancel_execution(associated_block)) {
result = parse_execution_cancelled;
break;
}
@@ -249,9 +246,9 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
// Check the condition and the tail. We treat parse_execution_errored here as failure, in
// accordance with historic behavior.
parse_execution_result_t cond_ret = run_job_conjunction(condition_head, ib);
parse_execution_result_t cond_ret = run_job_conjunction(condition_head, associated_block);
if (cond_ret == parse_execution_success) {
cond_ret = run_job_list(condition_boolean_tail, ib);
cond_ret = run_job_list(condition_boolean_tail, associated_block);
}
const bool take_branch =
(cond_ret == parse_execution_success) && proc_get_last_status() == EXIT_SUCCESS;
@@ -284,20 +281,22 @@ parse_execution_result_t parse_execution_context_t::run_if_statement(
// Execute any job list we got.
if (job_list_to_execute) {
if_block_t *ib = parser->push_block<if_block_t>();
run_job_list(job_list_to_execute, ib);
if (should_cancel_execution(ib)) {
result = parse_execution_cancelled;
}
parser->pop_block(ib);
} else {
// No job list means no sucessful conditions, so return 0 (issue #1443).
proc_set_last_status(STATUS_CMD_OK);
}
// It's possible there's a last-minute cancellation (issue #1297).
if (should_cancel_execution(ib)) {
if (should_cancel_execution(associated_block)) {
result = parse_execution_cancelled;
}
// Done
parser->pop_block(ib);
// Otherwise, take the exit status of the job list. Reversal of issue #1061.
return result;
}
@@ -337,7 +336,7 @@ parse_execution_result_t parse_execution_context_t::run_function_statement(
}
parse_execution_result_t parse_execution_context_t::run_block_statement(
tnode_t<g::block_statement> 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>();
@@ -345,7 +344,7 @@ parse_execution_result_t parse_execution_context_t::run_block_statement(
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>()) {
ret = run_while_statement(header, contents);
ret = run_while_statement(header, contents, associated_block);
} else if (auto header = bheader.try_get_child<g::function_header, 0>()) {
ret = run_function_statement(header, contents);
} else if (auto header = bheader.try_get_child<g::begin_header, 0>()) {
@@ -520,10 +519,8 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(
}
parse_execution_result_t parse_execution_context_t::run_while_statement(
tnode_t<grammar::while_header> header, tnode_t<grammar::job_list> contents) {
// Push a while block.
while_block_t *wb = parser->push_block<while_block_t>();
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;
// The conditions of the while loop.
@@ -533,9 +530,10 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
// Run while the condition is true.
for (;;) {
// Check the condition.
parse_execution_result_t cond_ret = this->run_job_conjunction(condition_head, wb);
parse_execution_result_t cond_ret =
this->run_job_conjunction(condition_head, associated_block);
if (cond_ret == parse_execution_success) {
cond_ret = run_job_list(condition_boolean_tail, wb);
cond_ret = run_job_list(condition_boolean_tail, associated_block);
}
// We only continue on successful execution and EXIT_SUCCESS.
@@ -544,21 +542,23 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
}
// Check cancellation.
if (this->should_cancel_execution(wb)) {
if (this->should_cancel_execution(associated_block)) {
ret = parse_execution_cancelled;
break;
}
// The block ought to go inside the loop (see issue #1212).
// Push a while block and then check its cancellation reason.
while_block_t *wb = parser->push_block<while_block_t>();
this->run_job_list(contents, wb);
auto loop_status = wb->loop_status;
auto cancel_reason = this->cancellation_reason(wb);
parser->pop_block(wb);
if (this->cancellation_reason(wb) == execution_cancellation_loop_control) {
if (cancel_reason == execution_cancellation_loop_control) {
// Handle break or continue.
if (wb->loop_status == LOOP_CONTINUE) {
// Reset the loop state.
wb->loop_status = LOOP_NORMAL;
if (loop_status == LOOP_CONTINUE) {
continue;
} else if (wb->loop_status == LOOP_BREAK) {
} else if (loop_status == LOOP_BREAK) {
break;
}
}
@@ -569,9 +569,6 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
break;
}
}
// Done
parser->pop_block(wb);
return ret;
}
@@ -1092,11 +1089,12 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
assert(specific_statement_type_is_redirectable_block(specific_statement));
switch (specific_statement.type) {
case symbol_block_statement: {
result = this->run_block_statement({&tree(), &specific_statement});
result =
this->run_block_statement({&tree(), &specific_statement}, associated_block);
break;
}
case symbol_if_statement: {
result = this->run_if_statement({&tree(), &specific_statement});
result = this->run_if_statement({&tree(), &specific_statement}, associated_block);
break;
}
case symbol_switch_statement: {
@@ -1265,9 +1263,9 @@ parse_execution_result_t parse_execution_context_t::eval_node(tnode_t<g::stateme
scoped_push<io_chain_t> block_io_push(&block_io, io);
enum parse_execution_result_t status = parse_execution_success;
if (auto block = statement.try_get_child<g::block_statement, 0>()) {
status = this->run_block_statement(block);
status = this->run_block_statement(block, associated_block);
} else if (auto ifstat = statement.try_get_child<g::if_statement, 0>()) {
status = this->run_if_statement(ifstat);
status = this->run_if_statement(ifstat, associated_block);
} else if (auto switchstat = statement.try_get_child<g::switch_statement, 0>()) {
status = this->run_switch_statement(switchstat);
} else {