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::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
60pub 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 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}