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