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 futures_util::FutureExt as _;
91 use std::assert_matches;
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::system::Concurrent;
102 use yash_env::test_helper::assert_stdout;
103
104 #[test]
105 fn single_pipeline_list() {
106 let mut env = Env::new_virtual();
107 env.builtins.insert("return", return_builtin());
108 let list: AndOrList = "return -n 36".parse().unwrap();
109 let result = list.execute(&mut env).now_or_never().unwrap();
110 assert_eq!(result, Continue(()));
111 assert_eq!(env.exit_status, ExitStatus(36));
112 }
113
114 #[test]
115 fn true_and_true() {
116 let system = VirtualSystem::new();
117 let state = Rc::clone(&system.state);
118 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
119 env.builtins.insert("echo", echo_builtin());
120 let list: AndOrList = "echo one && echo two".parse().unwrap();
121
122 let result = list.execute(&mut env).now_or_never().unwrap();
123 assert_eq!(result, Continue(()));
124 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
125 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
126 }
127
128 #[test]
129 fn true_and_false() {
130 let mut env = Env::new_virtual();
131 env.builtins.insert("return", return_builtin());
132 let list: AndOrList = "return -n 0 && return -n 5".parse().unwrap();
133 let result = list.execute(&mut env).now_or_never().unwrap();
134 assert_eq!(result, Continue(()));
135 assert_eq!(env.exit_status, ExitStatus(5));
136 }
137
138 #[test]
139 fn false_and_true() {
140 let system = VirtualSystem::new();
141 let state = Rc::clone(&system.state);
142 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
143 env.builtins.insert("echo", echo_builtin());
144 env.builtins.insert("return", return_builtin());
145 let list: AndOrList = "return -n 1 && echo !".parse().unwrap();
146
147 let result = list.execute(&mut env).now_or_never().unwrap();
148 assert_eq!(result, Continue(()));
149 assert_eq!(env.exit_status, ExitStatus(1));
150 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
151 }
152
153 #[test]
154 fn true_and_true_and_true() {
155 let system = VirtualSystem::new();
156 let state = Rc::clone(&system.state);
157 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
158 env.builtins.insert("echo", echo_builtin());
159 let list: AndOrList = "echo 1 && echo 2 && echo 3".parse().unwrap();
160
161 let result = list.execute(&mut env).now_or_never().unwrap();
162 assert_eq!(result, Continue(()));
163 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
164 assert_stdout(&state, |stdout| assert_eq!(stdout, "1\n2\n3\n"));
165 }
166
167 #[test]
168 fn true_and_false_and_true() {
169 let system = VirtualSystem::new();
170 let state = Rc::clone(&system.state);
171 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
172 env.builtins.insert("echo", echo_builtin());
173 env.builtins.insert("return", return_builtin());
174 let list: AndOrList = "return -n 0 && return -n 2 && echo !".parse().unwrap();
175
176 let result = list.execute(&mut env).now_or_never().unwrap();
177 assert_eq!(result, Continue(()));
178 assert_eq!(env.exit_status, ExitStatus(2));
179 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
180 }
181
182 #[test]
183 fn false_and_any_or_true() {
184 let mut env = Env::new_virtual();
185 env.builtins.insert("return", return_builtin());
186 let list: AndOrList = "return -n 8 && X || return -n 0".parse().unwrap();
187 let result = list.execute(&mut env).now_or_never().unwrap();
188 assert_eq!(result, Continue(()));
189 assert_eq!(env.exit_status, ExitStatus(0));
190 }
191
192 #[test]
193 fn true_or_false() {
194 let system = VirtualSystem::new();
195 let state = Rc::clone(&system.state);
196 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
197 env.builtins.insert("echo", echo_builtin());
198 env.builtins.insert("return", return_builtin());
199 let list: AndOrList = "echo + || return -n 100".parse().unwrap();
200
201 let result = list.execute(&mut env).now_or_never().unwrap();
202 assert_eq!(result, Continue(()));
203 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
204 assert_stdout(&state, |stdout| assert_eq!(stdout, "+\n"));
205 }
206
207 #[test]
208 fn false_or_true() {
209 let system = VirtualSystem::new();
210 let state = Rc::clone(&system.state);
211 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
212 env.builtins.insert("echo", echo_builtin());
213 env.builtins.insert("return", return_builtin());
214 let list: AndOrList = "{ echo one; return -n 1; } || { echo two; }"
215 .parse()
216 .unwrap();
217
218 let result = list.execute(&mut env).now_or_never().unwrap();
219 assert_eq!(result, Continue(()));
220 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
221 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
222 }
223
224 #[test]
225 fn false_or_false() {
226 let system = VirtualSystem::new();
227 let state = Rc::clone(&system.state);
228 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
229 env.builtins.insert("echo", echo_builtin());
230 env.builtins.insert("return", return_builtin());
231 let list: AndOrList = "{ echo one; return -n 1; } || { echo two; return -n 2; }"
232 .parse()
233 .unwrap();
234
235 let result = list.execute(&mut env).now_or_never().unwrap();
236 assert_eq!(result, Continue(()));
237 assert_eq!(env.exit_status, ExitStatus(2));
238 assert_stdout(&state, |stdout| assert_eq!(stdout, "one\ntwo\n"));
239 }
240
241 #[test]
242 fn false_or_false_or_false() {
243 let mut env = Env::new_virtual();
244 env.builtins.insert("return", return_builtin());
245 let list: AndOrList = "return -n 1 || return -n 2 || return -n 3".parse().unwrap();
246
247 let result = list.execute(&mut env).now_or_never().unwrap();
248 assert_eq!(result, Continue(()));
249 assert_eq!(env.exit_status, ExitStatus(3));
250 }
251
252 #[test]
253 fn false_or_true_or_false() {
254 let system = VirtualSystem::new();
255 let state = Rc::clone(&system.state);
256 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
257 env.builtins.insert("echo", echo_builtin());
258 env.builtins.insert("return", return_builtin());
259 let list: AndOrList = "return -n 3 || echo + || return -n 4".parse().unwrap();
260
261 let result = list.execute(&mut env).now_or_never().unwrap();
262 assert_eq!(result, Continue(()));
263 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
264 assert_stdout(&state, |stdout| assert_eq!(stdout, "+\n"));
265 }
266
267 #[test]
268 fn true_or_any_and_false() {
269 let mut env = Env::new_virtual();
270 env.builtins.insert("return", return_builtin());
271 let list: AndOrList = "return -n 0 || X && return -n 9".parse().unwrap();
272 let result = list.execute(&mut env).now_or_never().unwrap();
273 assert_eq!(result, Continue(()));
274 assert_eq!(env.exit_status, ExitStatus(9));
275 }
276
277 #[test]
278 fn diverting_first() {
279 let mut env = Env::new_virtual();
280 env.builtins.insert("return", return_builtin());
281 env.exit_status = ExitStatus(77);
282 let list: AndOrList = "return 97".parse().unwrap();
283 let result = list.execute(&mut env).now_or_never().unwrap();
284 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(97)))));
285 assert_eq!(env.exit_status, ExitStatus(77));
286 }
287
288 #[test]
289 fn diverting_rest() {
290 let mut env = Env::new_virtual();
291 env.builtins.insert("return", return_builtin());
292 let list: AndOrList = "return -n 7 || return 0 && X".parse().unwrap();
293 let result = list.execute(&mut env).now_or_never().unwrap();
294 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(0)))));
295 assert_eq!(env.exit_status, ExitStatus(7));
296 }
297
298 #[test]
299 fn stack_in_list() {
300 fn stub_builtin_condition(
301 env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
302 _args: Vec<Field>,
303 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
304 Box::pin(async move {
305 assert_matches!(
306 env.stack.as_slice(),
307 [Frame::Condition, Frame::Builtin { .. }]
308 );
309 Default::default()
310 })
311 }
312 fn stub_builtin_no_condition(
313 env: &mut Env<Rc<Concurrent<VirtualSystem>>>,
314 _args: Vec<Field>,
315 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
316 Box::pin(async move {
317 assert_matches!(env.stack.as_slice(), [Frame::Builtin { .. }]);
318 Default::default()
319 })
320 }
321
322 let mut env = Env::new_virtual();
323 env.builtins
324 .insert("head", Builtin::new(Special, stub_builtin_condition));
325 env.builtins
326 .insert("tail", Builtin::new(Special, stub_builtin_no_condition));
327
328 let list: AndOrList = "tail".parse().unwrap();
329 let result = list.execute(&mut env).now_or_never().unwrap();
330 assert_eq!(result, Continue(()));
331
332 let list: AndOrList = "head && head && tail".parse().unwrap();
333 let result = list.execute(&mut env).now_or_never().unwrap();
334 assert_eq!(result, Continue(()));
335 }
336}