yash_semantics/command/
and_or.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of the and-or list semantics.
18
19use 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
29/// Executes the and-or list.
30///
31/// The `&&` operator first executes the left-hand-side pipeline, and if and
32/// only if the exit status is zero, executes the right-hand-side. The `||`
33/// operator works similarly but runs the right-hand-side if and only if the
34/// left-hand-side exit status is non-zero. The `&&` and `||` operators are
35/// left-associative and have equal precedence.
36///
37/// The exit status of the and-or list will be that of the last executed
38/// pipeline.
39///
40/// [`Frame::Condition`] is pushed to the environment's stack while the
41/// execution of the pipelines except for the last.
42impl<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        // Execute `first`
49        let mut env2 = env.push_frame(Frame::Condition);
50        self.first.execute(&mut env2).await?;
51
52        // Execute `rest` but last
53        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 last
65        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}