mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-03-27 14:45:13 +08:00
Make while loops evaluate to the last executed command status
A while loop now evaluates to the last executed command in the body, or zero if the loop body is empty. This matches POSIX semantics. Add a bunch of tricky tests. See #4982
This commit is contained in:
parent
e2f2dbf032
commit
1d21e3f470
@ -3,6 +3,7 @@
|
|||||||
### Fixes and improvements
|
### Fixes and improvements
|
||||||
|
|
||||||
- exec now behaves properly inside functions (#5449)
|
- exec now behaves properly inside functions (#5449)
|
||||||
|
- while loops now evaluate to the last executed command in the loop body (or zero if the body was empty), matching POSIX semantics.
|
||||||
|
|
||||||
|
|
||||||
# fish 3.0.0 (released December 28, 2018)
|
# fish 3.0.0 (released December 28, 2018)
|
||||||
|
@ -523,13 +523,28 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
|
|||||||
const block_t *associated_block) {
|
const block_t *associated_block) {
|
||||||
parse_execution_result_t ret = parse_execution_success;
|
parse_execution_result_t ret = parse_execution_success;
|
||||||
|
|
||||||
|
// "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."
|
||||||
|
// Here are more detailed requirements:
|
||||||
|
// - If we execute the loop body zero times, or the loop body is empty, the status is success.
|
||||||
|
// - An empty loop body is treated as true, both in the loop condition and after loop exit.
|
||||||
|
// - The exit status of the last command is visible in the loop condition. (i.e. do not set the
|
||||||
|
// exit status to true BEFORE executing the loop condition).
|
||||||
|
// We achieve this by restoring the status if the loop condition fails, plus a special
|
||||||
|
// affordance for the first condition.
|
||||||
|
bool first_cond_check = true;
|
||||||
|
|
||||||
// The conditions of the while loop.
|
// The conditions of the while loop.
|
||||||
tnode_t<g::job_conjunction> condition_head = header.child<1>();
|
tnode_t<g::job_conjunction> condition_head = header.child<1>();
|
||||||
tnode_t<g::andor_job_list> condition_boolean_tail = header.child<3>();
|
tnode_t<g::andor_job_list> condition_boolean_tail = header.child<3>();
|
||||||
|
|
||||||
// Run while the condition is true.
|
// Run while the condition is true.
|
||||||
bool loop_executed = false;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
// Save off the exit status if it came from the loop body. We'll restore it if the condition
|
||||||
|
// is false.
|
||||||
|
int cond_saved_status = first_cond_check ? EXIT_SUCCESS : proc_get_last_status();
|
||||||
|
first_cond_check = false;
|
||||||
|
|
||||||
// Check the condition.
|
// Check the condition.
|
||||||
parse_execution_result_t cond_ret =
|
parse_execution_result_t cond_ret =
|
||||||
this->run_job_conjunction(condition_head, associated_block);
|
this->run_job_conjunction(condition_head, associated_block);
|
||||||
@ -537,8 +552,13 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
|
|||||||
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only continue on successful execution and EXIT_SUCCESS.
|
// If the loop condition failed to execute, then exit the loop without modifying the exit
|
||||||
if (cond_ret != parse_execution_success || proc_get_last_status() != EXIT_SUCCESS) {
|
// status. If the loop condition executed with a failure status, restore the status and then
|
||||||
|
// exit the loop.
|
||||||
|
if (cond_ret != parse_execution_success) {
|
||||||
|
break;
|
||||||
|
} else if (proc_get_last_status() != EXIT_SUCCESS) {
|
||||||
|
proc_set_last_status(cond_saved_status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,8 +568,6 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_executed = true;
|
|
||||||
|
|
||||||
// Push a while block and then check its cancellation reason.
|
// Push a while block and then check its cancellation reason.
|
||||||
while_block_t *wb = parser->push_block<while_block_t>();
|
while_block_t *wb = parser->push_block<while_block_t>();
|
||||||
this->run_job_list(contents, wb);
|
this->run_job_list(contents, wb);
|
||||||
@ -572,11 +590,6 @@ parse_execution_result_t parse_execution_context_t::run_while_statement(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loop_executed) {
|
|
||||||
proc_set_last_status(STATUS_CMD_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
tests/while.err
Normal file
3
tests/while.err
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
####################
|
||||||
|
# Loops exit status handling
|
73
tests/while.in
Normal file
73
tests/while.in
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# vim: set ft=fish:
|
||||||
|
|
||||||
|
function never_runs
|
||||||
|
while false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function early_return
|
||||||
|
while true
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function runs_once
|
||||||
|
set -l i 1
|
||||||
|
while test $i -ne 0 && set i (math $i - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# this should return 1
|
||||||
|
never_runs; echo "Empty Loop in Function: $status"
|
||||||
|
|
||||||
|
# this should return 0
|
||||||
|
runs_once; echo "Runs Once: $status"
|
||||||
|
|
||||||
|
# this should return 2
|
||||||
|
early_return; echo "Early Return: $status"
|
||||||
|
|
||||||
|
logmsg Loops exit status handling
|
||||||
|
|
||||||
|
function set_status ; return $argv[1]; end
|
||||||
|
|
||||||
|
# The previous status is visible in the loop condition.
|
||||||
|
# This includes both the incoming status, and the last command in the
|
||||||
|
# loop body.
|
||||||
|
set_status 36
|
||||||
|
while begin
|
||||||
|
set -l saved $status
|
||||||
|
echo "Condition Status: $status"
|
||||||
|
set_status $saved
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
# The condition status IS visible in the loop body.
|
||||||
|
set_status 55
|
||||||
|
while true
|
||||||
|
echo "Body Status: $status"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# The status of the last command is visible in the loop condition
|
||||||
|
set_status 13
|
||||||
|
while begin
|
||||||
|
set -l saved $status
|
||||||
|
echo "Condition 2 Status: $saved"
|
||||||
|
test $saved -ne 5
|
||||||
|
end
|
||||||
|
set_status 5
|
||||||
|
end
|
||||||
|
|
||||||
|
# The status of the last command is visible outside the loop
|
||||||
|
set rem 5 7 11
|
||||||
|
while [ (count $rem) -gt 0 ]
|
||||||
|
set_status $rem[1]
|
||||||
|
set rem $rem[2..-1]
|
||||||
|
end
|
||||||
|
echo "Loop Exit Status: $status"
|
||||||
|
|
||||||
|
# Empty loops succeed.
|
||||||
|
false
|
||||||
|
while false; end
|
||||||
|
echo "Empty Loop Status: $status"
|
12
tests/while.out
Normal file
12
tests/while.out
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Empty Loop in Function: 0
|
||||||
|
Runs Once: 0
|
||||||
|
Early Return: 2
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Loops exit status handling
|
||||||
|
Condition Status: 36
|
||||||
|
Body Status: 0
|
||||||
|
Condition 2 Status: 13
|
||||||
|
Condition 2 Status: 5
|
||||||
|
Loop Exit Status: 11
|
||||||
|
Empty Loop Status: 0
|
Loading…
x
Reference in New Issue
Block a user