use super::Command;
use crate::Handle;
use crate::Runtime;
use crate::redir::RedirGuard;
use crate::xtrace::XTrace;
use crate::xtrace::finish;
use std::ops::ControlFlow::Continue;
use yash_env::Env;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Result;
use yash_env::stack::Frame;
#[cfg(doc)]
use yash_env::subshell::Subshell;
use yash_syntax::syntax;
use yash_syntax::syntax::Redir;
async fn perform_redirs<S: Runtime + 'static>(
env: &mut RedirGuard<'_, S>,
redirs: &[Redir],
) -> std::result::Result<Option<ExitStatus>, crate::redir::Error> {
let mut xtrace = XTrace::from_options(&env.options);
let result = env.perform_redirs(redirs, xtrace.as_mut()).await;
let xtrace = finish(env, xtrace).await;
env.system.print_error(&xtrace).await;
result
}
async fn evaluate_condition<S: Runtime + 'static>(
env: &mut Env<S>,
condition: &syntax::List,
) -> Result<bool> {
let mut env = env.push_frame(Frame::Condition);
condition.execute(&mut env).await?;
Continue(env.exit_status.is_successful())
}
mod case;
mod for_loop;
mod r#if;
mod subshell;
mod while_loop;
impl<S: Runtime + 'static> Command<S> for syntax::FullCompoundCommand {
async fn execute(&self, env: &mut Env<S>) -> Result {
let mut env = RedirGuard::new(env);
match perform_redirs(&mut env, &self.redirs).await {
Ok(_) => self.command.execute(&mut env).await,
Err(error) => {
error.handle(&mut env).await?;
env.apply_errexit()
}
}
}
}
impl<S: Runtime + 'static> Command<S> for syntax::CompoundCommand {
async fn execute(&self, env: &mut Env<S>) -> Result {
use syntax::CompoundCommand::*;
match self {
Grouping(list) => list.execute(env).await,
Subshell { body, location } => subshell::execute(env, body.clone(), location).await,
For { name, values, body } => for_loop::execute(env, name, values, body).await,
While { condition, body } => while_loop::execute_while(env, condition, body).await,
Until { condition, body } => while_loop::execute_until(env, condition, body).await,
If {
condition,
body,
elifs,
r#else,
} => r#if::execute(env, condition, body, elifs, r#else).await,
Case { subject, items } => case::execute(env, subject, items).await,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::echo_builtin;
use crate::tests::return_builtin;
use assert_matches::assert_matches;
use futures_util::FutureExt;
use std::ops::ControlFlow::{Break, Continue};
use std::pin::Pin;
use std::rc::Rc;
use std::str::from_utf8;
use yash_env::VirtualSystem;
use yash_env::builtin::Builtin;
use yash_env::builtin::Type::Special;
use yash_env::option::Option::ErrExit;
use yash_env::option::State::On;
use yash_env::semantics::Divert;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::system::r#virtual::FileBody;
use yash_env::test_helper::assert_stderr;
use yash_env::test_helper::assert_stdout;
#[test]
fn stack_in_condition() {
fn stub_builtin(
env: &mut Env<VirtualSystem>,
_args: Vec<Field>,
) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
Box::pin(async move {
assert_matches!(
env.stack.as_slice(),
[Frame::Condition, Frame::Builtin { .. }]
);
Default::default()
})
}
let mut env = Env::new_virtual();
env.builtins
.insert("foo", Builtin::new(Special, stub_builtin));
let condition = "foo".parse().unwrap();
let result = evaluate_condition(&mut env, &condition)
.now_or_never()
.unwrap();
assert_eq!(result, Continue(true));
}
#[test]
fn redirecting_compound_command() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
env.builtins.insert("echo", echo_builtin());
let command: syntax::FullCompoundCommand = "{ echo 1; echo 2; } > /file".parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus::SUCCESS);
let file = state.borrow().file_system.get("/file").unwrap();
let file = file.borrow();
assert_matches!(&file.body, FileBody::Regular { content, .. } => {
assert_eq!(from_utf8(content).unwrap(), "1\n2\n");
});
}
#[test]
fn tracing_redirections() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
env.builtins.insert("echo", echo_builtin());
env.options.set(yash_env::option::Option::XTrace, On);
let command: syntax::FullCompoundCommand = "{ echo X; } > /file < /file".parse().unwrap();
let _ = command.execute(&mut env).now_or_never().unwrap();
assert_stderr(&state, |stderr| {
assert_eq!(stderr, "1>/file 0</file\necho X\n");
});
}
#[test]
fn redirection_error_prevents_command_execution() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
env.builtins.insert("echo", echo_builtin());
let command: syntax::FullCompoundCommand =
"{ echo not reached; } < /no/such/file".parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus::ERROR);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
}
#[test]
fn redirection_error_triggers_errexit() {
let mut env = Env::new_virtual();
env.builtins.insert("echo", echo_builtin());
env.options.set(ErrExit, On);
let command: syntax::FullCompoundCommand =
"{ echo not reached; } < /no/such/file".parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Exit(None)));
assert_eq!(env.exit_status, ExitStatus::ERROR);
}
#[test]
fn grouping_executes_list() {
let mut env = Env::new_virtual();
env.builtins.insert("return", return_builtin());
let command: syntax::CompoundCommand = "{ return -n 42; }".parse().unwrap();
let result = command.execute(&mut env).now_or_never().unwrap();
assert_eq!(result, Continue(()));
assert_eq!(env.exit_status, ExitStatus(42));
}
}