Skip to main content

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