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