1use crate::command::Command as _;
20use crate::trap::run_traps_for_caught_signals;
21use crate::{Handle as _, Runtime};
22use std::cell::RefCell;
23use std::ops::ControlFlow::{Break, Continue};
24use yash_env::Env;
25use yash_env::parser::Mode;
26use yash_env::semantics::Divert;
27use yash_env::semantics::ExitStatus;
28use yash_env::semantics::Result;
29use yash_syntax::parser::lex::Lexer;
30use yash_syntax::parser::{ErrorCause, Parser};
31use yash_syntax::syntax::List;
32
33pub async fn read_eval_loop<S: Runtime + 'static>(
103 env: &RefCell<&mut Env<S>>,
104 lexer: &mut Lexer<'_>,
105) -> Result {
106 read_eval_loop_impl(env, lexer, false).await
107}
108
109pub async fn interactive_read_eval_loop<S: Runtime + 'static>(
132 env: &RefCell<&mut Env<S>>,
133 lexer: &mut Lexer<'_>,
134) -> Result {
135 read_eval_loop_impl(env, lexer, true).await
136}
137
138#[allow(
139 clippy::await_holding_refcell_ref,
140 reason = "the parser does not run concurrently with the executor"
141)]
142async fn read_eval_loop_impl<S: Runtime + 'static>(
143 env: &RefCell<&mut Env<S>>,
144 lexer: &mut Lexer<'_>,
145 is_interactive: bool,
146) -> Result {
147 let mut executed = false;
148
149 loop {
150 if !lexer.pending() {
151 lexer.flush();
152 }
153
154 lexer.set_mode(Mode::from(&env.borrow().options));
158
159 let command = Parser::config()
160 .aliases(env)
161 .declaration_utilities(env)
162 .input(lexer)
163 .command_line()
164 .await;
165
166 let env = &mut **env.borrow_mut();
167
168 let (mut result, error_recoverable) = match command {
169 Ok(None) => {
171 if !executed {
172 env.exit_status = ExitStatus::SUCCESS;
173 }
174 return Continue(());
175 }
176
177 Ok(Some(command)) => (run_command(env, &command).await, true),
179
180 Err(error) => {
182 let result = error.handle(env).await;
183 let error_recoverable = matches!(error.cause, ErrorCause::Syntax(_));
184 (result, error_recoverable)
185 }
186 };
187
188 if is_interactive && error_recoverable {
189 if let Break(Divert::Interrupt(exit_status)) = result {
191 if let Some(exit_status) = exit_status {
192 env.exit_status = exit_status;
193 }
194 result = Continue(());
195 lexer.flush();
196 }
197 }
198
199 result?;
201
202 executed = true;
203 }
204}
205
206async fn run_command<S: Runtime + 'static>(env: &mut Env<S>, command: &List) -> Result {
207 run_traps_for_caught_signals(env).await?;
208 env.update_all_subshell_statuses();
209 command.execute(env).await
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use crate::tests::echo_builtin;
216 use crate::tests::return_builtin;
217 use futures_util::FutureExt as _;
218 use std::rc::Rc;
219 use yash_env::input::Echo;
220 use yash_env::input::Memory;
221 use yash_env::option::Option::Verbose;
222 use yash_env::option::State::On;
223 use yash_env::system::Concurrent;
224 use yash_env::system::r#virtual::SIGUSR1;
225 use yash_env::system::r#virtual::VirtualSystem;
226 use yash_env::test_helper::assert_stderr;
227 use yash_env::test_helper::assert_stdout;
228 use yash_env::trap::Action;
229 use yash_syntax::input::Context;
230 use yash_syntax::source::Location;
231
232 #[test]
233 fn exit_status_zero_with_no_commands() {
234 let mut env = Env::new_virtual();
235 env.exit_status = ExitStatus(5);
236 let mut lexer = Lexer::with_code("");
237 let ref_env = RefCell::new(&mut env);
238
239 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
240 assert_eq!(result, Continue(()));
241 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
242 }
243
244 #[test]
245 fn exit_status_in_out() {
246 let system = VirtualSystem::new();
247 let state = Rc::clone(&system.state);
248 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
249 env.exit_status = ExitStatus(42);
250 env.builtins.insert("echo", echo_builtin());
251 env.builtins.insert("return", return_builtin());
252 let mut lexer = Lexer::with_code("echo $?; return -n 7");
253 let ref_env = RefCell::new(&mut env);
254
255 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
256 assert_eq!(result, Continue(()));
257 assert_eq!(env.exit_status, ExitStatus(7));
258 assert_stdout(&state, |stdout| assert_eq!(stdout, "42\n"));
259 }
260
261 #[test]
262 fn executing_many_lines_of_code() {
263 let system = VirtualSystem::new();
264 let state = Rc::clone(&system.state);
265 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
266 env.builtins.insert("echo", echo_builtin());
267 let mut lexer = Lexer::with_code("echo 1\necho 2\necho 3;");
268 let ref_env = RefCell::new(&mut env);
269
270 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
271 assert_eq!(result, Continue(()));
272 assert_stdout(&state, |stdout| assert_eq!(stdout, "1\n2\n3\n"));
273 }
274
275 #[test]
276 fn parsing_with_aliases() {
277 use yash_syntax::alias::{Alias, HashEntry};
278 let system = VirtualSystem::new();
279 let state = Rc::clone(&system.state);
280 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
281 env.aliases.insert(HashEntry(Rc::new(Alias {
282 name: "echo".to_string(),
283 replacement: "echo alias\necho ok".to_string(),
284 global: false,
285 origin: Location::dummy(""),
286 })));
287 env.builtins.insert("echo", echo_builtin());
288 let mut lexer = Lexer::with_code("echo");
289 let ref_env = RefCell::new(&mut env);
290
291 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
292 assert_eq!(result, Continue(()));
293 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
294 assert_stdout(&state, |stdout| assert_eq!(stdout, "alias\nok\n"));
295 }
296
297 #[test]
298 fn verbose_option() {
299 let system = VirtualSystem::new();
300 let state = Rc::clone(&system.state);
301 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
302 env.options.set(Verbose, On);
303 let ref_env = RefCell::new(&mut env);
304 let input = Box::new(Echo::new(Memory::new("case _ in esac"), &ref_env));
305 let mut lexer = Lexer::new(input);
306
307 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
308 drop(lexer);
309 assert_eq!(result, Continue(()));
310 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
311 assert_stderr(&state, |stderr| assert_eq!(stderr, "case _ in esac"));
312 }
313
314 #[test]
315 fn command_interrupt_interactive() {
316 let system = VirtualSystem::new();
319 let state = Rc::clone(&system.state);
320 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
321 env.builtins.insert("echo", echo_builtin());
322 let mut lexer = Lexer::with_code("${X?}\necho $?\n");
323 let ref_env = RefCell::new(&mut env);
324
325 let result = interactive_read_eval_loop(&ref_env, &mut lexer)
326 .now_or_never()
327 .unwrap();
328 assert_eq!(result, Continue(()));
329 assert_stdout(&state, |stdout| assert_eq!(stdout, "2\n"));
330 }
331
332 #[test]
333 fn command_other_divert_interactive() {
334 let system = VirtualSystem::new();
337 let state = Rc::clone(&system.state);
338 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
339 env.builtins.insert("echo", echo_builtin());
340 env.builtins.insert("return", return_builtin());
341 let mut lexer = Lexer::with_code("return 123\necho $?\n");
342 let ref_env = RefCell::new(&mut env);
343
344 let result = interactive_read_eval_loop(&ref_env, &mut lexer)
345 .now_or_never()
346 .unwrap();
347 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(123)))));
348 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
349 }
350
351 #[test]
352 fn command_interrupt_non_interactive() {
353 let system = VirtualSystem::new();
356 let state = Rc::clone(&system.state);
357 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
358 env.builtins.insert("echo", echo_builtin());
359 let mut lexer = Lexer::with_code("${X?}\necho $?\n");
360 let ref_env = RefCell::new(&mut env);
361
362 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
363 assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
364 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
365 }
366
367 #[test]
368 fn handling_syntax_error() {
369 let system = VirtualSystem::new();
370 let state = Rc::clone(&system.state);
371 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
372 let mut lexer = Lexer::with_code(";;");
373 let ref_env = RefCell::new(&mut env);
374 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
375 assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
376 assert_stderr(&state, |stderr| assert_ne!(stderr, ""));
377 }
378
379 #[test]
380 fn syntax_error_aborts_non_interactive_loop() {
381 let system = VirtualSystem::new();
382 let state = Rc::clone(&system.state);
383 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
384 env.builtins.insert("echo", echo_builtin());
385 let mut lexer = Lexer::with_code(";;\necho !");
386 let ref_env = RefCell::new(&mut env);
387
388 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
389 assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
390 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
391 }
392
393 #[test]
394 fn syntax_error_continues_interactive_loop() {
395 let system = VirtualSystem::new();
396 let state = Rc::clone(&system.state);
397 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
398 env.builtins.insert("echo", echo_builtin());
399 let mut lexer = Lexer::with_code(";; (\necho $?");
402 let ref_env = RefCell::new(&mut env);
403
404 let result = interactive_read_eval_loop(&ref_env, &mut lexer)
405 .now_or_never()
406 .unwrap();
407 assert_eq!(result, Continue(()));
408 assert_stdout(&state, |stdout| assert_eq!(stdout, "2\n"));
409 }
410
411 #[test]
412 fn input_error_aborts_loop() {
413 struct BrokenInput;
414 impl yash_syntax::input::Input for BrokenInput {
415 async fn next_line(&mut self, _context: &Context) -> std::io::Result<String> {
416 Err(std::io::Error::other("broken"))
417 }
418 }
419
420 let mut lexer = Lexer::new(Box::new(BrokenInput));
421 let mut env = Env::new_virtual();
422 let ref_env = RefCell::new(&mut env);
423
424 let result = interactive_read_eval_loop(&ref_env, &mut lexer)
425 .now_or_never()
426 .unwrap();
427 assert_eq!(
428 result,
429 Break(Divert::Interrupt(Some(ExitStatus::READ_ERROR)))
430 );
431 }
432
433 #[test]
434 fn running_traps_between_parsing_and_executing() {
435 let system = VirtualSystem::new();
436 let pid = system.process_id;
437 let state = Rc::clone(&system.state);
438 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
439 env.builtins.insert("echo", echo_builtin());
440 env.traps
441 .set_action(
442 &env.system,
443 SIGUSR1,
444 Action::Command("echo USR1".into()),
445 Location::dummy(""),
446 false,
447 )
448 .now_or_never()
449 .unwrap()
450 .unwrap();
451 let _ = state
452 .borrow_mut()
453 .processes
454 .get_mut(&pid)
455 .unwrap()
456 .raise_signal(SIGUSR1);
457 let mut lexer = Lexer::with_code("echo $?");
458 let ref_env = RefCell::new(&mut env);
459
460 let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
461 assert_eq!(result, Continue(()));
462 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
463 assert_stdout(&state, |stdout| assert_eq!(stdout, "USR1\n0\n"));
464 }
465}