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