yash_semantics/command/simple_command/
function.rs1use super::perform_assignments;
20use crate::Handle;
21use crate::redir::RedirGuard;
22use crate::xtrace::XTrace;
23use crate::xtrace::print;
24use crate::xtrace::trace_fields;
25use std::ops::ControlFlow::{Break, Continue};
26use std::pin::Pin;
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, None).await
58}
59
60type EnvPrepHook = fn(&mut Env) -> Pin<Box<dyn Future<Output = ()>>>;
61
62pub async fn execute_function_body(
72 env: &mut Env,
73 function: Rc<Function>,
74 fields: Vec<Field>,
75 env_prep_hook: Option<EnvPrepHook>,
76) -> Result {
77 let positional_params = PositionalParams::from_fields(fields);
78 let mut env = env.push_context(Context::Regular { positional_params });
79 if let Some(hook) = env_prep_hook {
80 hook(&mut env).await;
81 }
82
83 let result = function.body.execute(&mut env).await;
85 if let Break(Divert::Return(exit_status)) = result {
86 if let Some(exit_status) = exit_status {
87 env.exit_status = exit_status;
88 }
89 Continue(())
90 } else {
91 result
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::command::Command as _;
99 use crate::command::function_definition::BodyImpl;
100 use crate::tests::echo_builtin;
101 use crate::tests::local_builtin;
102 use crate::tests::return_builtin;
103 use assert_matches::assert_matches;
104 use futures_util::FutureExt;
105 use std::rc::Rc;
106 use std::str::from_utf8;
107 use yash_env::VirtualSystem;
108 use yash_env::function::FunctionBodyObject;
109 use yash_env::option::State::On;
110 use yash_env::semantics::ExitStatus;
111 use yash_env::system::r#virtual::FileBody;
112 use yash_env::variable::Scope;
113 use yash_env_test_helper::assert_stderr;
114 use yash_env_test_helper::assert_stdout;
115 use yash_syntax::source::Location;
116 use yash_syntax::syntax::SimpleCommand;
117
118 fn function_body_impl(src: &str) -> Rc<dyn FunctionBodyObject> {
119 Rc::new(BodyImpl(src.parse().unwrap()))
120 }
121
122 #[test]
123 fn simple_command_returns_exit_status_from_function() {
124 let mut env = Env::new_virtual();
125 env.builtins.insert("return", return_builtin());
126 let function = Function::new(
127 "foo",
128 function_body_impl("{ return -n 13; }"),
129 Location::dummy("dummy"),
130 );
131 env.functions.define(function).unwrap();
132 let command: SimpleCommand = "foo".parse().unwrap();
133
134 let result = command.execute(&mut env).now_or_never().unwrap();
135 assert_eq!(result, Continue(()));
136 assert_eq!(env.exit_status, ExitStatus(13));
137 }
138
139 #[test]
140 fn simple_command_applies_redirections_to_function() {
141 let system = VirtualSystem::new();
142 let state = Rc::clone(&system.state);
143 let mut env = Env::with_system(Box::new(system));
144 env.builtins.insert("echo", echo_builtin());
145 let function = Function::new(
146 "foo",
147 function_body_impl("{ echo ok; }"),
148 Location::dummy("dummy"),
149 );
150 env.functions.define(function).unwrap();
151 let command: SimpleCommand = "foo >/tmp/file".parse().unwrap();
152
153 _ = command.execute(&mut env).now_or_never().unwrap();
154 let file = state.borrow().file_system.get("/tmp/file").unwrap();
155 let file = file.borrow();
156 assert_matches!(&file.body, FileBody::Regular { content, .. } => {
157 assert_eq!(from_utf8(content), Ok("ok\n"));
158 });
159 }
160
161 #[test]
162 fn simple_command_skips_running_function_on_redirection_error() {
163 let system = VirtualSystem::new();
164 let state = Rc::clone(&system.state);
165 let mut env = Env::with_system(Box::new(system));
166 env.builtins.insert("echo", echo_builtin());
167 let function = Function::new(
168 "foo",
169 function_body_impl("{ echo ok; }"),
170 Location::dummy("dummy"),
171 );
172 env.functions.define(function).unwrap();
173 let command: SimpleCommand = "a=v foo </no/such/file".parse().unwrap();
174
175 let result = command.execute(&mut env).now_or_never().unwrap();
176 assert_eq!(result, Continue(()));
177 assert_eq!(env.exit_status, ExitStatus::ERROR);
178 assert_eq!(env.variables.get("a"), None);
179 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
180 }
181
182 #[test]
183 fn function_call_consumes_return_without_exit_status() {
184 let mut env = Env::new_virtual();
185 env.builtins.insert("return", return_builtin());
186 let function = Function::new(
187 "foo",
188 function_body_impl("{ return; }"),
189 Location::dummy("dummy"),
190 );
191 env.functions.define(function).unwrap();
192 env.exit_status = ExitStatus(17);
193 let command: SimpleCommand = "foo".parse().unwrap();
194
195 let result = command.execute(&mut env).now_or_never().unwrap();
196 assert_eq!(result, Continue(()));
197 assert_eq!(env.exit_status, ExitStatus(17));
198 }
199
200 #[test]
201 fn function_call_consumes_return_with_exit_status() {
202 let mut env = Env::new_virtual();
203 env.builtins.insert("return", return_builtin());
204 let function = Function::new(
205 "foo",
206 function_body_impl("{ return 26; }"),
207 Location::dummy("dummy"),
208 );
209 env.functions.define(function).unwrap();
210 let command: SimpleCommand = "foo".parse().unwrap();
211
212 let result = command.execute(&mut env).now_or_never().unwrap();
213 assert_eq!(result, Continue(()));
214 assert_eq!(env.exit_status, ExitStatus(26));
215 }
216
217 #[test]
218 fn simple_command_passes_arguments_to_function() {
219 let system = VirtualSystem::new();
220 let state = Rc::clone(&system.state);
221 let mut env = Env::with_system(Box::new(system));
222 env.builtins.insert("echo", echo_builtin());
223 let function = Function::new(
224 "foo",
225 function_body_impl("{ echo $1-$2-$3; }"),
226 Location::dummy("dummy"),
227 );
228 env.functions.define(function).unwrap();
229 let command: SimpleCommand = "foo bar baz".parse().unwrap();
230
231 let result = command.execute(&mut env).now_or_never().unwrap();
232 assert_eq!(result, Continue(()));
233 assert_stdout(&state, |stdout| assert_eq!(stdout, "bar-baz-\n"));
234 }
235
236 #[test]
237 fn simple_command_creates_temporary_context_executing_function() {
238 let system = VirtualSystem::new();
239 let state = Rc::clone(&system.state);
240 let mut env = Env::with_system(Box::new(system));
241 env.builtins.insert("echo", echo_builtin());
242 env.builtins.insert("local", local_builtin());
243 let function = Function::new(
244 "foo",
245 function_body_impl("{ local x=42; echo $x; }"),
246 Location::dummy("dummy"),
247 );
248 env.functions.define(function).unwrap();
249 let command: SimpleCommand = "foo".parse().unwrap();
250
251 _ = command.execute(&mut env).now_or_never().unwrap();
252 assert_eq!(env.variables.get("x"), None);
253 assert_stdout(&state, |stdout| assert_eq!(stdout, "42\n"));
254 }
255
256 #[test]
257 fn simple_command_performs_function_assignment_in_temporary_context() {
258 let system = VirtualSystem::new();
259 let state = Rc::clone(&system.state);
260 let mut env = Env::with_system(Box::new(system));
261 env.builtins.insert("echo", echo_builtin());
262 let function = Function::new(
263 "foo",
264 function_body_impl("{ echo $x; }"),
265 Location::dummy("dummy"),
266 );
267 env.functions.define(function).unwrap();
268 let command: SimpleCommand = "x=hello foo".parse().unwrap();
269
270 _ = command.execute(&mut env).now_or_never().unwrap();
271 assert_eq!(env.variables.get("x"), None);
272 assert_stdout(&state, |stdout| assert_eq!(stdout, "hello\n"));
273 }
274
275 #[test]
276 fn function_fails_on_reassigning_to_read_only_variable() {
277 let mut env = Env::new_virtual();
278 env.builtins.insert("echo", echo_builtin());
279 let function = Function::new(
280 "foo",
281 function_body_impl("{ echo; }"),
282 Location::dummy("dummy"),
283 );
284 env.functions.define(function).unwrap();
285 let mut var = env.variables.get_or_new("x", Scope::Global);
286 var.assign("", None).unwrap();
287 var.make_read_only(Location::dummy("readonly"));
288 let command: SimpleCommand = "x=hello foo".parse().unwrap();
289
290 let result = command.execute(&mut env).now_or_never().unwrap();
291 assert_matches!(result, Break(Divert::Interrupt(Some(exit_status))) => {
292 assert_ne!(exit_status, ExitStatus::SUCCESS);
293 });
294 }
295
296 #[test]
297 fn xtrace_for_function() {
298 let system = VirtualSystem::new();
299 let state = Rc::clone(&system.state);
300 let mut env = Env::with_system(Box::new(system));
301 let function = Function::new(
302 "foo",
303 function_body_impl("for i in; do :; done"),
304 Location::dummy("dummy"),
305 );
306 env.functions.define(function).unwrap();
307 env.options.set(yash_env::option::XTrace, On);
308 let command: SimpleCommand = "x=hello foo bar <>/dev/null".parse().unwrap();
309
310 _ = command.execute(&mut env).now_or_never().unwrap();
311 assert_stderr(&state, |stderr| {
312 assert_eq!(stderr, "x=hello foo bar 0<>/dev/null\nfor i in\n");
313 });
314 }
315}