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