Skip to main content

yash_semantics/
runner.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of the read-eval loop
18
19use 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
32/// Reads input, parses it, and executes commands in a loop.
33///
34/// A read-eval loop uses a [`Lexer`] for reading and parsing input and [`Env`]
35/// for executing parsed commands. It creates a [`Parser`] from the lexer to
36/// parse [command lines](Parser::command_line). The loop executes each command
37/// line before parsing the next one. The loop continues until the parser
38/// reaches the end of input or encounters a parser error, or the command
39/// execution results in a `Break(Divert::...)`.
40///
41/// This function takes a `RefCell` containing the mutable reference to the
42/// environment. The `RefCell` should be shared only with the [`Input`]
43/// implementor used in the `Lexer` to avoid conflicting borrows.
44///
45/// If the input source code contains no commands, the exit status is set to
46/// zero. Otherwise, the exit status reflects the result of the last executed
47/// command.
48///
49/// [Pending traps are run](run_traps_for_caught_signals) and [subshell statuses
50/// are updated](Env::update_all_subshell_statuses) between parsing input and
51/// running commands.
52///
53/// For the top-level read-eval loop of an interactive shell, see
54/// [`interactive_read_eval_loop`].
55///
56/// # Example
57///
58/// Executing a command:
59///
60/// ```
61/// # futures_executor::block_on(async {
62/// # use std::cell::RefCell;
63/// # use std::ops::ControlFlow::Continue;
64/// # use yash_env::Env;
65/// # use yash_semantics::ExitStatus;
66/// # use yash_semantics::read_eval_loop;
67/// # use yash_syntax::parser::lex::Lexer;
68/// let mut env = Env::new_virtual();
69/// let mut lexer = Lexer::with_code("case foo in (bar) ;; esac");
70/// let result = read_eval_loop(&RefCell::new(&mut env), &mut lexer).await;
71/// assert_eq!(result, Continue(()));
72/// assert_eq!(env.exit_status, ExitStatus::SUCCESS);
73/// # })
74/// ```
75///
76/// Using the [`Echo`] decorator with the shared environment:
77///
78/// ```
79/// # futures_executor::block_on(async {
80/// # use std::cell::RefCell;
81/// # use std::ops::ControlFlow::Continue;
82/// # use yash_env::Env;
83/// # use yash_env::input::Echo;
84/// # use yash_semantics::ExitStatus;
85/// # use yash_semantics::read_eval_loop;
86/// # use yash_syntax::input::Memory;
87/// # use yash_syntax::parser::lex::Lexer;
88/// let mut env = Env::new_virtual();
89/// let mut ref_env = RefCell::new(&mut env);
90/// let input = Box::new(Echo::new(Memory::new("case foo in (bar) ;; esac"), &ref_env));
91/// let mut lexer = Lexer::new(input);
92/// let result = read_eval_loop(&ref_env, &mut lexer).await;
93/// drop(lexer);
94/// assert_eq!(result, Continue(()));
95/// assert_eq!(env.exit_status, ExitStatus::SUCCESS);
96/// # })
97/// ```
98///
99/// [`Echo`]: yash_env::input::Echo
100/// [`Input`]: yash_syntax::input::Input
101pub 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, /* is_interactive */ false).await
106}
107
108/// [`read_eval_loop`] for interactive shells
109///
110/// This function extends the [`read_eval_loop`] function to act as an
111/// interactive shell. The difference is that this function suppresses
112/// [`Interrupt`]s and continues the loop if the parser fails with a syntax
113/// error or if the command execution results in an interrupt. Note that I/O
114/// errors detected by the parser are not recovered from.
115///
116/// Also note that the following aspects of the interactive shell are *not*
117/// implemented in this function:
118///
119/// - Prompting the user for input (see the `yash-prompt` crate)
120/// - Reporting job status changes before the prompt (see [`Reporter`])
121/// - Applying the `ignore-eof` option (see [`IgnoreEof`])
122///
123/// This function is intended to be used as the top-level read-eval loop in an
124/// interactive shell. It is not suitable for non-interactive command execution
125/// such as scripts. See [`read_eval_loop`] for non-interactive execution.
126///
127/// [`Interrupt`]: crate::Divert::Interrupt
128/// [`Reporter`]: yash_env::input::Reporter
129/// [`IgnoreEof`]: yash_env::input::IgnoreEof
130pub 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, /* is_interactive */ 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            // No more commands
164            Ok(None) => {
165                if !executed {
166                    env.exit_status = ExitStatus::SUCCESS;
167                }
168                return Continue(());
169            }
170
171            // Execute the command
172            Ok(Some(command)) => (run_command(env, &command).await, true),
173
174            // Parser error
175            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            // Recover from errors
184            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        // Break the loop if the command execution results in a divert
194        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        // If the command execution results in an interrupt in interactive mode,
311        // the loop continues
312        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        // If the command execution results in a divert other than an interrupt in
329        // interactive mode, the loop breaks
330        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        // If the command execution results in an interrupt in non-interactive mode,
348        // the loop breaks
349        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        // The ";;" causes a syntax error, the following "(" is ignored, and the
394        // loop continues with the command "echo $?" on the next line.
395        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}