use super::evaluate_condition;
use crate::Runtime;
use crate::command::Command as _;
use std::ops::ControlFlow::Continue;
use yash_env::Env;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Result;
use yash_syntax::syntax::ElifThen;
use yash_syntax::syntax::List;
pub async fn execute<S: Runtime + 'static>(
env: &mut Env<S>,
condition: &List,
body: &List,
elifs: &[ElifThen],
r#else: &Option<List>,
) -> Result {
if evaluate_condition(env, condition).await? {
return body.execute(env).await;
}
for ElifThen { condition, body } in elifs {
if evaluate_condition(env, condition).await? {
return body.execute(env).await;
}
}
if let Some(r#else) = r#else {
r#else.execute(env).await
} else {
env.exit_status = ExitStatus::SUCCESS;
Continue(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::echo_builtin;
use crate::tests::return_builtin;
use futures_util::FutureExt as _;
use std::cell::RefCell;
use std::ops::ControlFlow::Break;
use std::rc::Rc;
use yash_env::VirtualSystem;
use yash_env::semantics::Divert;
use yash_env::system::Concurrent;
use yash_env::system::r#virtual::SystemState;
use yash_env::test_helper::assert_stdout;
use yash_syntax::syntax::CompoundCommand;
fn fixture() -> (Env<Rc<Concurrent<VirtualSystem>>>, Rc<RefCell<SystemState>>) {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
env.builtins.insert("echo", echo_builtin());
env.builtins.insert("return", return_builtin());
(env, state)
}
#[test]
fn true_condition_without_else() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if echo $?; then return -n 123; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(123));
assert_stdout(&state, |stdout| assert_eq!(stdout, "15\n"));
}
#[test]
fn true_condition_with_else() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if echo $?; then return -n 123; else echo not reached; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(123));
assert_stdout(&state, |stdout| assert_eq!(stdout, "15\n"));
}
#[test]
fn false_condition_without_else() {
let (mut env, state) = fixture();
let command = "if return -n 1; then echo not reached; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus::SUCCESS);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn false_condition_with_else() {
let (mut env, state) = fixture();
let command = "if return -n 29; then echo not reached; else echo $?; return -n 43; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(43));
assert_stdout(&state, |stdout| assert_eq!(stdout, "29\n"));
}
#[test]
fn true_first_elif_condition() {
let (mut env, state) = fixture();
let command = "if return -n 97; then echo not reached 1
elif echo $?; then return -n 61
elif echo not reached 3; then echo not reached 4; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(61));
assert_stdout(&state, |stdout| assert_eq!(stdout, "97\n"));
}
#[test]
fn true_second_elif_condition() {
let (mut env, state) = fixture();
let command = "if return -n 10; then echo not reached 1
elif return -n 20; then echo not reached 2
elif echo $?; then return -n 9
else echo not reached 3; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(9));
assert_stdout(&state, |stdout| assert_eq!(stdout, "20\n"));
}
#[test]
fn false_elif_conditions_without_else() {
let (mut env, state) = fixture();
let command = "if return -n 1; then echo not reached 1
elif return -n 2; then echo not reached 2
elif return -n 3; then echo not reached 3; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus::SUCCESS);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn false_elif_conditions_with_else() {
let (mut env, state) = fixture();
let command = "if return -n 101; then echo not reached 1
elif return -n 102; then echo not reached 2
elif return -n 103; then echo not reached 3
elif return -n 104; then echo not reached 4
else echo $?; return -n 200; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(200));
assert_stdout(&state, |stdout| assert_eq!(stdout, "104\n"));
}
#[test]
fn return_from_condition() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if return -n 7; return 42; then echo not reached; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Return(Some(ExitStatus(42)))));
assert_eq!(env.exit_status, ExitStatus(7));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn return_from_body() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if return -n 0; then return -n 9; return 73; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Return(Some(ExitStatus(73)))));
assert_eq!(env.exit_status, ExitStatus(9));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn return_from_elif_condition() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if return -n 2; then echo not reached 1
elif return 52; then echo not reached 2; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Return(Some(ExitStatus(52)))));
assert_eq!(env.exit_status, ExitStatus(2));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn return_from_elif_body() {
let (mut env, state) = fixture();
env.exit_status = ExitStatus(15);
let command = "if return -n 2; then echo not reached 1
elif return -n 0; then return -n 6; return 47; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Return(Some(ExitStatus(47)))));
assert_eq!(env.exit_status, ExitStatus(6));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn return_from_else() {
let (mut env, state) = fixture();
let command = "if return -n 13; then echo not reached; else return 17; fi";
let command: CompoundCommand = command.parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Return(Some(ExitStatus(17)))));
assert_eq!(env.exit_status, ExitStatus(13));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
}