1use super::Command;
20use crate::Runtime;
21use std::ops::ControlFlow::Continue;
22use yash_env::Env;
23use yash_env::semantics::Result;
24use yash_env::stack::Frame;
25use yash_syntax::syntax::AndOr::{self, AndThen, OrElse};
26use yash_syntax::syntax::AndOrList;
27use yash_syntax::syntax::Pipeline;
28
29impl<S: Runtime + 'static> Command<S> for AndOrList {
43 async fn execute(&self, env: &mut Env<S>) -> Result {
44 if self.rest.is_empty() {
45 return self.first.execute(env).await;
46 }
47
48 let mut env2 = env.push_frame(Frame::Condition);
50 self.first.execute(&mut env2).await?;
51
52 let mut i = self.rest.iter().peekable();
54 let mut pipeline;
55 loop {
56 pipeline = i.next().unwrap();
57 if i.peek().is_none() {
58 break;
59 }
60 execute_conditional_pipeline(&mut env2, pipeline).await?;
61 }
62 drop(env2);
63
64 execute_conditional_pipeline(env, pipeline).await
66 }
67}
68
69async fn execute_conditional_pipeline<S: Runtime + 'static>(
70 env: &mut Env<S>,
71 (and_or, pipeline): &(AndOr, Pipeline),
72) -> Result {
73 let success = env.exit_status.is_successful();
74 let run = match and_or {
75 AndThen => success,
76 OrElse => !success,
77 };
78 if run {
79 pipeline.execute(env).await
80 } else {
81 Continue(())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::tests::echo_builtin;
89 use crate::tests::return_builtin;
90 use assert_matches::assert_matches;
91 use futures_util::FutureExt;
92 use std::ops::ControlFlow::Break;
93 use std::pin::Pin;
94 use std::rc::Rc;
95 use yash_env::VirtualSystem;
96 use yash_env::builtin::Builtin;
97 use yash_env::builtin::Type::Special;
98 use yash_env::semantics::Divert;
99 use yash_env::semantics::ExitStatus;
100 use yash_env::semantics::Field;
101 use yash_env_test_helper::assert_stdout;
102
103 #[test]
104 fn single_pipeline_list() {
105 let mut env = Env::new_virtual();
106 env.builtins.insert("return", return_builtin());
107 let list: AndOrList = "return -n 36".parse().unwrap();
108 let result = list.execute(&mut env).now_or_never().unwrap();
109 assert_eq!(result, Continue(()));
110 assert_eq!(env.exit_status, ExitStatus(36));
111 }
112
113 #[test]
114 fn true_and_true() {
115 let system = VirtualSystem::new();
116 let state = Rc::clone(&system.state);
117 let mut env = Env::with_system(system);
118 env.builtins.insert("echo", echo_builtin());
119 let list: AndOrList = "echo one && echo two".parse().unwrap();
120
121 let result = list.execute(&mut env).now_or_never().unwrap();
122 assert_eq!(result, Continue(()));
123 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
124 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
125 }
126
127 #[test]
128 fn true_and_false() {
129 let mut env = Env::new_virtual();
130 env.builtins.insert("return", return_builtin());
131 let list: AndOrList = "return -n 0 && return -n 5".parse().unwrap();
132 let result = list.execute(&mut env).now_or_never().unwrap();
133 assert_eq!(result, Continue(()));
134 assert_eq!(env.exit_status, ExitStatus(5));
135 }
136
137 #[test]
138 fn false_and_true() {
139 let system = VirtualSystem::new();
140 let state = Rc::clone(&system.state);
141 let mut env = Env::with_system(system);
142 env.builtins.insert("echo", echo_builtin());
143 env.builtins.insert("return", return_builtin());
144 let list: AndOrList = "return -n 1 && echo !".parse().unwrap();
145
146 let result = list.execute(&mut env).now_or_never().unwrap();
147 assert_eq!(result, Continue(()));
148 assert_eq!(env.exit_status, ExitStatus(1));
149 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
150 }
151
152 #[test]
153 fn true_and_true_and_true() {
154 let system = VirtualSystem::new();
155 let state = Rc::clone(&system.state);
156 let mut env = Env::with_system(system);
157 env.builtins.insert("echo", echo_builtin());
158 let list: AndOrList = "echo 1 && echo 2 && echo 3".parse().unwrap();
159
160 let result = list.execute(&mut env).now_or_never().unwrap();
161 assert_eq!(result, Continue(()));
162 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
163 assert_stdout(&state, |stdout| assert_eq!(stdout, "1\n2\n3\n"));
164 }
165
166 #[test]
167 fn true_and_false_and_true() {
168 let system = VirtualSystem::new();
169 let state = Rc::clone(&system.state);
170 let mut env = Env::with_system(system);
171 env.builtins.insert("echo", echo_builtin());
172 env.builtins.insert("return", return_builtin());
173 let list: AndOrList = "return -n 0 && return -n 2 && echo !".parse().unwrap();
174
175 let result = list.execute(&mut env).now_or_never().unwrap();
176 assert_eq!(result, Continue(()));
177 assert_eq!(env.exit_status, ExitStatus(2));
178 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
179 }
180
181 #[test]
182 fn false_and_any_or_true() {
183 let mut env = Env::new_virtual();
184 env.builtins.insert("return", return_builtin());
185 let list: AndOrList = "return -n 8 && X || return -n 0".parse().unwrap();
186 let result = list.execute(&mut env).now_or_never().unwrap();
187 assert_eq!(result, Continue(()));
188 assert_eq!(env.exit_status, ExitStatus(0));
189 }
190
191 #[test]
192 fn true_or_false() {
193 let system = VirtualSystem::new();
194 let state = Rc::clone(&system.state);
195 let mut env = Env::with_system(system);
196 env.builtins.insert("echo", echo_builtin());
197 env.builtins.insert("return", return_builtin());
198 let list: AndOrList = "echo + || return -n 100".parse().unwrap();
199
200 let result = list.execute(&mut env).now_or_never().unwrap();
201 assert_eq!(result, Continue(()));
202 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
203 assert_stdout(&state, |stdout| assert_eq!(stdout, "+\n"));
204 }
205
206 #[test]
207 fn false_or_true() {
208 let system = VirtualSystem::new();
209 let state = Rc::clone(&system.state);
210 let mut env = Env::with_system(system);
211 env.builtins.insert("echo", echo_builtin());
212 env.builtins.insert("return", return_builtin());
213 let list: AndOrList = "{ echo one; return -n 1; } || { echo two; }"
214 .parse()
215 .unwrap();
216
217 let result = list.execute(&mut env).now_or_never().unwrap();
218 assert_eq!(result, Continue(()));
219 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
220 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
221 }
222
223 #[test]
224 fn false_or_false() {
225 let system = VirtualSystem::new();
226 let state = Rc::clone(&system.state);
227 let mut env = Env::with_system(system);
228 env.builtins.insert("echo", echo_builtin());
229 env.builtins.insert("return", return_builtin());
230 let list: AndOrList = "{ echo one; return -n 1; } || { echo two; return -n 2; }"
231 .parse()
232 .unwrap();
233
234 let result = list.execute(&mut env).now_or_never().unwrap();
235 assert_eq!(result, Continue(()));
236 assert_eq!(env.exit_status, ExitStatus(2));
237 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
238 }
239
240 #[test]
241 fn false_or_false_or_false() {
242 let mut env = Env::new_virtual();
243 env.builtins.insert("return", return_builtin());
244 let list: AndOrList = "return -n 1 || return -n 2 || return -n 3".parse().unwrap();
245
246 let result = list.execute(&mut env).now_or_never().unwrap();
247 assert_eq!(result, Continue(()));
248 assert_eq!(env.exit_status, ExitStatus(3));
249 }
250
251 #[test]
252 fn false_or_true_or_false() {
253 let system = VirtualSystem::new();
254 let state = Rc::clone(&system.state);
255 let mut env = Env::with_system(system);
256 env.builtins.insert("echo", echo_builtin());
257 env.builtins.insert("return", return_builtin());
258 let list: AndOrList = "return -n 3 || echo + || return -n 4".parse().unwrap();
259
260 let result = list.execute(&mut env).now_or_never().unwrap();
261 assert_eq!(result, Continue(()));
262 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
263 assert_stdout(&state, |stdout| assert_eq!(stdout, "+\n"));
264 }
265
266 #[test]
267 fn true_or_any_and_false() {
268 let mut env = Env::new_virtual();
269 env.builtins.insert("return", return_builtin());
270 let list: AndOrList = "return -n 0 || X && return -n 9".parse().unwrap();
271 let result = list.execute(&mut env).now_or_never().unwrap();
272 assert_eq!(result, Continue(()));
273 assert_eq!(env.exit_status, ExitStatus(9));
274 }
275
276 #[test]
277 fn diverting_first() {
278 let mut env = Env::new_virtual();
279 env.builtins.insert("return", return_builtin());
280 env.exit_status = ExitStatus(77);
281 let list: AndOrList = "return 97".parse().unwrap();
282 let result = list.execute(&mut env).now_or_never().unwrap();
283 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(97)))));
284 assert_eq!(env.exit_status, ExitStatus(77));
285 }
286
287 #[test]
288 fn diverting_rest() {
289 let mut env = Env::new_virtual();
290 env.builtins.insert("return", return_builtin());
291 let list: AndOrList = "return -n 7 || return 0 && X".parse().unwrap();
292 let result = list.execute(&mut env).now_or_never().unwrap();
293 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(0)))));
294 assert_eq!(env.exit_status, ExitStatus(7));
295 }
296
297 #[test]
298 fn stack_in_list() {
299 fn stub_builtin_condition(
300 env: &mut Env<VirtualSystem>,
301 _args: Vec<Field>,
302 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
303 Box::pin(async move {
304 assert_matches!(
305 env.stack.as_slice(),
306 [Frame::Condition, Frame::Builtin { .. }]
307 );
308 Default::default()
309 })
310 }
311 fn stub_builtin_no_condition(
312 env: &mut Env<VirtualSystem>,
313 _args: Vec<Field>,
314 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
315 Box::pin(async move {
316 assert_matches!(env.stack.as_slice(), [Frame::Builtin { .. }]);
317 Default::default()
318 })
319 }
320
321 let mut env = Env::new_virtual();
322 env.builtins
323 .insert("head", Builtin::new(Special, stub_builtin_condition));
324 env.builtins
325 .insert("tail", Builtin::new(Special, stub_builtin_no_condition));
326
327 let list: AndOrList = "tail".parse().unwrap();
328 let result = list.execute(&mut env).now_or_never().unwrap();
329 assert_eq!(result, Continue(()));
330
331 let list: AndOrList = "head && head && tail".parse().unwrap();
332 let result = list.execute(&mut env).now_or_never().unwrap();
333 assert_eq!(result, Continue(()));
334 }
335}