yash_semantics/command/
compound_command.rs1use super::Command;
20use crate::Handle;
21use crate::redir::RedirGuard;
22use crate::xtrace::XTrace;
23use crate::xtrace::finish;
24use std::ops::ControlFlow::Continue;
25use yash_env::Env;
26use yash_env::semantics::ExitStatus;
27use yash_env::semantics::Result;
28use yash_env::stack::Frame;
29#[cfg(doc)]
30use yash_env::subshell::Subshell;
31use yash_syntax::syntax;
32use yash_syntax::syntax::Redir;
33
34async fn perform_redirs(
36 env: &mut RedirGuard<'_>,
37 redirs: &[Redir],
38) -> std::result::Result<Option<ExitStatus>, crate::redir::Error> {
39 let mut xtrace = XTrace::from_options(&env.options);
40 let result = env.perform_redirs(redirs, xtrace.as_mut()).await;
41 let xtrace = finish(env, xtrace).await;
42 env.system.print_error(&xtrace).await;
43 result
44}
45
46async fn evaluate_condition(env: &mut Env, condition: &syntax::List) -> Result<bool> {
48 let mut env = env.push_frame(Frame::Condition);
49 condition.execute(&mut env).await?;
50 Continue(env.exit_status.is_successful())
51}
52
53mod case;
54mod for_loop;
55mod r#if;
56mod subshell;
57mod while_loop;
58
59impl Command for syntax::FullCompoundCommand {
65 async fn execute(&self, env: &mut Env) -> Result {
66 let mut env = RedirGuard::new(env);
67 match perform_redirs(&mut env, &self.redirs).await {
68 Ok(_) => self.command.execute(&mut env).await,
69 Err(error) => {
70 error.handle(&mut env).await?;
71 env.apply_errexit()
72 }
73 }
74 }
75}
76
77impl Command for syntax::CompoundCommand {
131 async fn execute(&self, env: &mut Env) -> Result {
132 use syntax::CompoundCommand::*;
133 match self {
134 Grouping(list) => list.execute(env).await,
135 Subshell { body, location } => subshell::execute(env, body.clone(), location).await,
136 For { name, values, body } => for_loop::execute(env, name, values, body).await,
137 While { condition, body } => while_loop::execute_while(env, condition, body).await,
138 Until { condition, body } => while_loop::execute_until(env, condition, body).await,
139 If {
140 condition,
141 body,
142 elifs,
143 r#else,
144 } => r#if::execute(env, condition, body, elifs, r#else).await,
145 Case { subject, items } => case::execute(env, subject, items).await,
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::tests::echo_builtin;
154 use crate::tests::return_builtin;
155 use assert_matches::assert_matches;
156 use futures_util::FutureExt;
157 use std::ops::ControlFlow::{Break, Continue};
158 use std::pin::Pin;
159 use std::rc::Rc;
160 use std::str::from_utf8;
161 use yash_env::VirtualSystem;
162 use yash_env::builtin::Builtin;
163 use yash_env::builtin::Type::Special;
164 use yash_env::option::Option::ErrExit;
165 use yash_env::option::State::On;
166 use yash_env::semantics::Divert;
167 use yash_env::semantics::ExitStatus;
168 use yash_env::semantics::Field;
169 use yash_env::system::r#virtual::FileBody;
170 use yash_env_test_helper::assert_stderr;
171 use yash_env_test_helper::assert_stdout;
172
173 #[test]
174 fn stack_in_condition() {
175 fn stub_builtin(
176 env: &mut Env,
177 _args: Vec<Field>,
178 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
179 Box::pin(async move {
180 assert_matches!(
181 env.stack.as_slice(),
182 [Frame::Condition, Frame::Builtin { .. }]
183 );
184 Default::default()
185 })
186 }
187
188 let mut env = Env::new_virtual();
189 env.builtins
190 .insert("foo", Builtin::new(Special, stub_builtin));
191 let condition = "foo".parse().unwrap();
192
193 let result = evaluate_condition(&mut env, &condition)
194 .now_or_never()
195 .unwrap();
196 assert_eq!(result, Continue(true));
197 }
198
199 #[test]
200 fn redirecting_compound_command() {
201 let system = VirtualSystem::new();
202 let state = Rc::clone(&system.state);
203 let mut env = Env::with_system(Box::new(system));
204 env.builtins.insert("echo", echo_builtin());
205 let command: syntax::FullCompoundCommand = "{ echo 1; echo 2; } > /file".parse().unwrap();
206 let result = command.execute(&mut env).now_or_never().unwrap();
207 assert_eq!(result, Continue(()));
208 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
209
210 let file = state.borrow().file_system.get("/file").unwrap();
211 let file = file.borrow();
212 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
213 assert_eq!(from_utf8(content).unwrap(), "1\n2\n");
214 });
215 }
216
217 #[test]
218 fn tracing_redirections() {
219 let system = VirtualSystem::new();
220 let state = Rc::clone(&system.state);
221 let mut env = Env::with_system(Box::new(system));
222 env.builtins.insert("echo", echo_builtin());
223 env.options.set(yash_env::option::Option::XTrace, On);
224 let command: syntax::FullCompoundCommand = "{ echo X; } > /file < /file".parse().unwrap();
225 let _ = command.execute(&mut env).now_or_never().unwrap();
226 assert_stderr(&state, |stderr| {
227 assert_eq!(stderr, "1>/file 0</file\necho X\n");
228 });
229 }
230
231 #[test]
232 fn redirection_error_prevents_command_execution() {
233 let system = VirtualSystem::new();
234 let state = Rc::clone(&system.state);
235 let mut env = Env::with_system(Box::new(system));
236 env.builtins.insert("echo", echo_builtin());
237 let command: syntax::FullCompoundCommand =
238 "{ echo not reached; } < /no/such/file".parse().unwrap();
239 let result = command.execute(&mut env).now_or_never().unwrap();
240 assert_eq!(result, Continue(()));
241 assert_eq!(env.exit_status, ExitStatus::ERROR);
242 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
243 }
244
245 #[test]
246 fn redirection_error_triggers_errexit() {
247 let mut env = Env::new_virtual();
248 env.builtins.insert("echo", echo_builtin());
249 env.options.set(ErrExit, On);
250 let command: syntax::FullCompoundCommand =
251 "{ echo not reached; } < /no/such/file".parse().unwrap();
252 let result = command.execute(&mut env).now_or_never().unwrap();
253 assert_eq!(result, Break(Divert::Exit(None)));
254 assert_eq!(env.exit_status, ExitStatus::ERROR);
255 }
256
257 #[test]
258 fn grouping_executes_list() {
259 let mut env = Env::new_virtual();
260 env.builtins.insert("return", return_builtin());
261 let command: syntax::CompoundCommand = "{ return -n 42; }".parse().unwrap();
262 let result = command.execute(&mut env).now_or_never().unwrap();
263 assert_eq!(result, Continue(()));
264 assert_eq!(env.exit_status, ExitStatus(42));
265 }
266}