Skip to main content

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 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}