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 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
28/// Executes the and-or list.
29///
30/// The `&&` operator first executes the left-hand-side pipeline, and if and
31/// only if the exit status is zero, executes the right-hand-side. The `||`
32/// operator works similarly but runs the right-hand-side if and only if the
33/// left-hand-side exit status is non-zero. The `&&` and `||` operators are
34/// left-associative and have equal precedence.
35///
36/// The exit status of the and-or list will be that of the last executed
37/// pipeline.
38///
39/// [`Frame::Condition`] is pushed to the environment's stack while the
40/// execution of the pipelines except for the last.
41impl 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        // Execute `first`
48        let mut env2 = env.push_frame(Frame::Condition);
49        self.first.execute(&mut env2).await?;
50
51        // Execute `rest` but last
52        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 last
64        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}