yash_semantics/command/simple_command/
function.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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//! Simple command semantics for functions
18
19use super::perform_assignments;
20use crate::Handle;
21use crate::command::Command;
22use crate::redir::RedirGuard;
23use crate::xtrace::XTrace;
24use crate::xtrace::print;
25use crate::xtrace::trace_fields;
26use std::ops::ControlFlow::{Break, Continue};
27use std::rc::Rc;
28use yash_env::Env;
29use yash_env::function::Function;
30use yash_env::semantics::Divert;
31use yash_env::semantics::Field;
32use yash_env::semantics::Result;
33use yash_env::variable::Context;
34use yash_env::variable::PositionalParams;
35use yash_syntax::syntax::Assign;
36use yash_syntax::syntax::Redir;
37
38pub async fn execute_function(
39    env: &mut Env,
40    function: Rc<Function>,
41    assigns: &[Assign],
42    fields: Vec<Field>,
43    redirs: &[Redir],
44) -> Result {
45    let env = &mut RedirGuard::new(env);
46    let mut xtrace = XTrace::from_options(&env.options);
47    if let Err(e) = env.perform_redirs(redirs, xtrace.as_mut()).await {
48        return e.handle(env).await;
49    };
50
51    let mut env = env.push_context(Context::Volatile);
52    perform_assignments(&mut env, assigns, true, xtrace.as_mut()).await?;
53
54    trace_fields(xtrace.as_mut(), &fields);
55    print(&mut env, xtrace).await;
56
57    execute_function_body(&mut env, function, fields, |_| ()).await
58}
59
60/// Executes the body of the function.
61///
62/// The given function is executed in the given environment. The fields are
63/// passed as positional parameters to the function except for the first field
64/// which is the name of the function.
65///
66/// The modifier function is called with the environment after the new variable
67/// context is pushed to the environment. This is useful for assigning custom
68/// local variables before the function body is executed.
69pub async fn execute_function_body<F>(
70    env: &mut Env,
71    function: Rc<Function>,
72    fields: Vec<Field>,
73    modifier: F,
74) -> Result
75where
76    F: FnOnce(&mut Env),
77{
78    let positional_params = PositionalParams::from_fields(fields);
79    let mut env = env.push_context(Context::Regular { positional_params });
80    modifier(&mut env);
81
82    // TODO Update control flow stack
83    let result = function.body.execute(&mut env).await;
84    if let Break(Divert::Return(exit_status)) = result {
85        if let Some(exit_status) = exit_status {
86            env.exit_status = exit_status;
87        }
88        Continue(())
89    } else {
90        result
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use crate::tests::echo_builtin;
98    use crate::tests::local_builtin;
99    use crate::tests::return_builtin;
100    use assert_matches::assert_matches;
101    use futures_util::FutureExt;
102    use std::rc::Rc;
103    use std::str::from_utf8;
104    use yash_env::VirtualSystem;
105    use yash_env::option::State::On;
106    use yash_env::semantics::ExitStatus;
107    use yash_env::system::r#virtual::FileBody;
108    use yash_env::variable::Scope;
109    use yash_env_test_helper::assert_stderr;
110    use yash_env_test_helper::assert_stdout;
111    use yash_syntax::source::Location;
112    use yash_syntax::syntax::FullCompoundCommand;
113    use yash_syntax::syntax::SimpleCommand;
114
115    #[test]
116    fn simple_command_returns_exit_status_from_function() {
117        let mut env = Env::new_virtual();
118        env.builtins.insert("return", return_builtin());
119        let function = Function::new(
120            "foo",
121            "{ return -n 13; }".parse::<FullCompoundCommand>().unwrap(),
122            Location::dummy("dummy"),
123        );
124        env.functions.define(function).unwrap();
125        let command: SimpleCommand = "foo".parse().unwrap();
126
127        let result = command.execute(&mut env).now_or_never().unwrap();
128        assert_eq!(result, Continue(()));
129        assert_eq!(env.exit_status, ExitStatus(13));
130    }
131
132    #[test]
133    fn simple_command_applies_redirections_to_function() {
134        let system = VirtualSystem::new();
135        let state = Rc::clone(&system.state);
136        let mut env = Env::with_system(Box::new(system));
137        env.builtins.insert("echo", echo_builtin());
138        let function = Function::new(
139            "foo",
140            "{ echo ok; }".parse::<FullCompoundCommand>().unwrap(),
141            Location::dummy("dummy"),
142        );
143        env.functions.define(function).unwrap();
144        let command: SimpleCommand = "foo >/tmp/file".parse().unwrap();
145
146        _ = command.execute(&mut env).now_or_never().unwrap();
147        let file = state.borrow().file_system.get("/tmp/file").unwrap();
148        let file = file.borrow();
149        assert_matches!(&file.body, FileBody::Regular { content, .. } => {
150            assert_eq!(from_utf8(content), Ok("ok\n"));
151        });
152    }
153
154    #[test]
155    fn simple_command_skips_running_function_on_redirection_error() {
156        let system = VirtualSystem::new();
157        let state = Rc::clone(&system.state);
158        let mut env = Env::with_system(Box::new(system));
159        env.builtins.insert("echo", echo_builtin());
160        let function = Function::new(
161            "foo",
162            "{ echo ok; }".parse::<FullCompoundCommand>().unwrap(),
163            Location::dummy("dummy"),
164        );
165        env.functions.define(function).unwrap();
166        let command: SimpleCommand = "a=v foo </no/such/file".parse().unwrap();
167
168        let result = command.execute(&mut env).now_or_never().unwrap();
169        assert_eq!(result, Continue(()));
170        assert_eq!(env.exit_status, ExitStatus::ERROR);
171        assert_eq!(env.variables.get("a"), None);
172        assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
173    }
174
175    #[test]
176    fn function_call_consumes_return_without_exit_status() {
177        let mut env = Env::new_virtual();
178        env.builtins.insert("return", return_builtin());
179        let function = Function::new(
180            "foo",
181            "{ return; }".parse::<FullCompoundCommand>().unwrap(),
182            Location::dummy("dummy"),
183        );
184        env.functions.define(function).unwrap();
185        env.exit_status = ExitStatus(17);
186        let command: SimpleCommand = "foo".parse().unwrap();
187
188        let result = command.execute(&mut env).now_or_never().unwrap();
189        assert_eq!(result, Continue(()));
190        assert_eq!(env.exit_status, ExitStatus(17));
191    }
192
193    #[test]
194    fn function_call_consumes_return_with_exit_status() {
195        let mut env = Env::new_virtual();
196        env.builtins.insert("return", return_builtin());
197        let function = Function::new(
198            "foo",
199            "{ return 26; }".parse::<FullCompoundCommand>().unwrap(),
200            Location::dummy("dummy"),
201        );
202        env.functions.define(function).unwrap();
203        let command: SimpleCommand = "foo".parse().unwrap();
204
205        let result = command.execute(&mut env).now_or_never().unwrap();
206        assert_eq!(result, Continue(()));
207        assert_eq!(env.exit_status, ExitStatus(26));
208    }
209
210    #[test]
211    fn simple_command_passes_arguments_to_function() {
212        let system = VirtualSystem::new();
213        let state = Rc::clone(&system.state);
214        let mut env = Env::with_system(Box::new(system));
215        env.builtins.insert("echo", echo_builtin());
216        let function = Function::new(
217            "foo",
218            "{ echo $1-$2-$3; }".parse::<FullCompoundCommand>().unwrap(),
219            Location::dummy("dummy"),
220        );
221        env.functions.define(function).unwrap();
222        let command: SimpleCommand = "foo bar baz".parse().unwrap();
223
224        let result = command.execute(&mut env).now_or_never().unwrap();
225        assert_eq!(result, Continue(()));
226        assert_stdout(&state, |stdout| assert_eq!(stdout, "bar-baz-\n"));
227    }
228
229    #[test]
230    fn simple_command_creates_temporary_context_executing_function() {
231        let system = VirtualSystem::new();
232        let state = Rc::clone(&system.state);
233        let mut env = Env::with_system(Box::new(system));
234        env.builtins.insert("echo", echo_builtin());
235        env.builtins.insert("local", local_builtin());
236        let function = Function::new(
237            "foo",
238            "{ local x=42; echo $x; }"
239                .parse::<FullCompoundCommand>()
240                .unwrap(),
241            Location::dummy("dummy"),
242        );
243        env.functions.define(function).unwrap();
244        let command: SimpleCommand = "foo".parse().unwrap();
245
246        _ = command.execute(&mut env).now_or_never().unwrap();
247        assert_eq!(env.variables.get("x"), None);
248        assert_stdout(&state, |stdout| assert_eq!(stdout, "42\n"));
249    }
250
251    #[test]
252    fn simple_command_performs_function_assignment_in_temporary_context() {
253        let system = VirtualSystem::new();
254        let state = Rc::clone(&system.state);
255        let mut env = Env::with_system(Box::new(system));
256        env.builtins.insert("echo", echo_builtin());
257        let function = Function::new(
258            "foo",
259            "{ echo $x; }".parse::<FullCompoundCommand>().unwrap(),
260            Location::dummy("dummy"),
261        );
262        env.functions.define(function).unwrap();
263        let command: SimpleCommand = "x=hello foo".parse().unwrap();
264
265        _ = command.execute(&mut env).now_or_never().unwrap();
266        assert_eq!(env.variables.get("x"), None);
267        assert_stdout(&state, |stdout| assert_eq!(stdout, "hello\n"));
268    }
269
270    #[test]
271    fn function_fails_on_reassigning_to_read_only_variable() {
272        let mut env = Env::new_virtual();
273        env.builtins.insert("echo", echo_builtin());
274        let function = Function::new(
275            "foo",
276            "{ echo; }".parse::<FullCompoundCommand>().unwrap(),
277            Location::dummy("dummy"),
278        );
279        env.functions.define(function).unwrap();
280        let mut var = env.variables.get_or_new("x", Scope::Global);
281        var.assign("", None).unwrap();
282        var.make_read_only(Location::dummy("readonly"));
283        let command: SimpleCommand = "x=hello foo".parse().unwrap();
284
285        let result = command.execute(&mut env).now_or_never().unwrap();
286        assert_matches!(result, Break(Divert::Interrupt(Some(exit_status))) => {
287            assert_ne!(exit_status, ExitStatus::SUCCESS);
288        });
289    }
290
291    #[test]
292    fn xtrace_for_function() {
293        let system = VirtualSystem::new();
294        let state = Rc::clone(&system.state);
295        let mut env = Env::with_system(Box::new(system));
296        let function = Function::new(
297            "foo",
298            "for i in; do :; done"
299                .parse::<FullCompoundCommand>()
300                .unwrap(),
301            Location::dummy("dummy"),
302        );
303        env.functions.define(function).unwrap();
304        env.options.set(yash_env::option::XTrace, On);
305        let command: SimpleCommand = "x=hello foo bar <>/dev/null".parse().unwrap();
306
307        _ = command.execute(&mut env).now_or_never().unwrap();
308        assert_stderr(&state, |stderr| {
309            assert_eq!(stderr, "x=hello foo bar 0<>/dev/null\nfor i in\n");
310        });
311    }
312}