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