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