yash_semantics/
command.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//! Command execution
18
19mod and_or;
20mod compound_command;
21mod function_definition;
22mod item;
23mod pipeline;
24pub mod simple_command;
25
26use crate::trap::run_traps_for_caught_signals;
27use std::ops::ControlFlow::{Break, Continue};
28use yash_env::Env;
29use yash_env::semantics::Result;
30#[doc(no_inline)]
31pub use yash_env::semantics::command::search;
32use yash_syntax::syntax;
33
34/// Syntactic construct that can be executed.
35pub trait Command {
36    /// Executes this command.
37    ///
38    /// Implementations of this method is expected to update `env.exit_status`
39    /// reflecting the result of the command execution.
40    #[allow(async_fn_in_trait)] // We don't support Send
41    async fn execute(&self, env: &mut Env) -> Result;
42}
43
44/// Executes the command.
45///
46/// After executing the command body, the `execute` function [runs
47/// traps](run_traps_for_caught_signals) if any caught signals are pending, and
48/// [updates subshell statuses](Env::update_all_subshell_statuses).
49impl Command for syntax::Command {
50    async fn execute(&self, env: &mut Env) -> Result {
51        use syntax::Command::*;
52        let main_result = match self {
53            Simple(command) => command.execute(env).await,
54            Compound(command) => command.execute(env).await,
55            Function(definition) => definition.execute(env).await,
56        };
57
58        let trap_result = run_traps_for_caught_signals(env).await;
59        env.update_all_subshell_statuses();
60
61        match (main_result, trap_result) {
62            (_, Continue(())) => main_result,
63            (Continue(()), _) => trap_result,
64            (Break(main_divert), Break(trap_divert)) => Break(main_divert.max(trap_divert)),
65        }
66    }
67}
68
69/// Executes the list.
70///
71/// The list is executed by executing each item in sequence. If any item results
72/// in a [`Divert`](yash_env::semantics::Divert), the remaining items are not
73/// executed.
74impl Command for syntax::List {
75    async fn execute(&self, env: &mut Env) -> Result {
76        // Boxing needed for recursion
77        Box::pin(async move {
78            for item in &self.0 {
79                item.execute(env).await?
80            }
81            Continue(())
82        })
83        .await
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::tests::echo_builtin;
91    use crate::tests::return_builtin;
92    use futures_util::FutureExt;
93    use yash_env::semantics::Divert;
94    use yash_env::semantics::ExitStatus;
95    use yash_env::system::r#virtual::SIGUSR1;
96    use yash_env::system::r#virtual::VirtualSystem;
97    use yash_env::trap::Action;
98    use yash_env_test_helper::assert_stdout;
99    use yash_syntax::source::Location;
100
101    #[test]
102    fn command_handles_traps() {
103        let system = VirtualSystem::new();
104        let mut env = Env::with_system(Box::new(system.clone()));
105        env.builtins.insert("echo", echo_builtin());
106        env.traps
107            .set_action(
108                &mut env.system,
109                SIGUSR1,
110                Action::Command("echo USR1".into()),
111                Location::dummy(""),
112                false,
113            )
114            .unwrap();
115        let _ = system
116            .state
117            .borrow_mut()
118            .processes
119            .get_mut(&system.process_id)
120            .unwrap()
121            .raise_signal(SIGUSR1);
122
123        let command: syntax::Command = "echo main".parse().unwrap();
124        let result = command.execute(&mut env).now_or_never().unwrap();
125        assert_eq!(result, Continue(()));
126        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
127
128        assert_stdout(&system.state, |stdout| assert_eq!(stdout, "main\nUSR1\n"));
129    }
130
131    #[test]
132    fn list_execute_no_divert() {
133        let mut env = Env::new_virtual();
134        env.builtins.insert("return", return_builtin());
135        let list: syntax::List = "return -n 1; return -n 2; return -n 4".parse().unwrap();
136        let result = list.execute(&mut env).now_or_never().unwrap();
137        assert_eq!(result, Continue(()));
138        assert_eq!(env.exit_status, ExitStatus(4));
139    }
140
141    #[test]
142    fn list_execute_divert() {
143        let mut env = Env::new_virtual();
144        env.builtins.insert("return", return_builtin());
145        let list: syntax::List = "return -n 1; return 2; return -n 4".parse().unwrap();
146        let result = list.execute(&mut env).now_or_never().unwrap();
147        assert_eq!(result, Break(Divert::Return(Some(ExitStatus(2)))));
148        assert_eq!(env.exit_status, ExitStatus(1));
149    }
150}