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