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