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::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
33/// Reads input, parses it, and executes commands in a loop.
34///
35/// A read-eval loop uses a [`Lexer`] for reading and parsing input and [`Env`]
36/// for executing parsed commands. It creates a [`Parser`] from the lexer to
37/// parse [command lines](Parser::command_line). The loop executes each command
38/// line before parsing the next one. The loop continues until the parser
39/// reaches the end of input or encounters a parser error, or the command
40/// execution results in a `Break(Divert::...)`.
41///
42/// This function takes a `RefCell` containing the mutable reference to the
43/// environment. The `RefCell` should be shared only with the [`Input`]
44/// implementor used in the `Lexer` to avoid conflicting borrows.
45///
46/// If the input source code contains no commands, the exit status is set to
47/// zero. Otherwise, the exit status reflects the result of the last executed
48/// command.
49///
50/// [Pending traps are run](run_traps_for_caught_signals) and [subshell statuses
51/// are updated](Env::update_all_subshell_statuses) between parsing input and
52/// running commands.
53///
54/// For the top-level read-eval loop of an interactive shell, see
55/// [`interactive_read_eval_loop`].
56///
57/// # Example
58///
59/// Executing a command:
60///
61/// ```
62/// # futures_executor::block_on(async {
63/// # use std::cell::RefCell;
64/// # use std::ops::ControlFlow::Continue;
65/// # use yash_env::Env;
66/// # use yash_semantics::ExitStatus;
67/// # use yash_semantics::read_eval_loop;
68/// # use yash_syntax::parser::lex::Lexer;
69/// let mut env = Env::new_virtual();
70/// let mut lexer = Lexer::with_code("case foo in (bar) ;; esac");
71/// let result = read_eval_loop(&RefCell::new(&mut env), &mut lexer).await;
72/// assert_eq!(result, Continue(()));
73/// assert_eq!(env.exit_status, ExitStatus::SUCCESS);
74/// # })
75/// ```
76///
77/// Using the [`Echo`] decorator with the shared environment:
78///
79/// ```
80/// # futures_executor::block_on(async {
81/// # use std::cell::RefCell;
82/// # use std::ops::ControlFlow::Continue;
83/// # use yash_env::Env;
84/// # use yash_env::input::Echo;
85/// # use yash_semantics::ExitStatus;
86/// # use yash_semantics::read_eval_loop;
87/// # use yash_syntax::input::Memory;
88/// # use yash_syntax::parser::lex::Lexer;
89/// let mut env = Env::new_virtual();
90/// let mut ref_env = RefCell::new(&mut env);
91/// let input = Box::new(Echo::new(Memory::new("case foo in (bar) ;; esac"), &ref_env));
92/// let mut lexer = Lexer::new(input);
93/// let result = read_eval_loop(&ref_env, &mut lexer).await;
94/// drop(lexer);
95/// assert_eq!(result, Continue(()));
96/// assert_eq!(env.exit_status, ExitStatus::SUCCESS);
97/// # })
98/// ```
99///
100/// [`Echo`]: yash_env::input::Echo
101/// [`Input`]: yash_syntax::input::Input
102pub 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, /* is_interactive */ false).await
107}
108
109/// [`read_eval_loop`] for interactive shells
110///
111/// This function extends the [`read_eval_loop`] function to act as an
112/// interactive shell. The difference is that this function suppresses
113/// [`Interrupt`]s and continues the loop if the parser fails with a syntax
114/// error or if the command execution results in an interrupt. Note that I/O
115/// errors detected by the parser are not recovered from.
116///
117/// Also note that the following aspects of the interactive shell are *not*
118/// implemented in this function:
119///
120/// - Prompting the user for input (see the `yash-prompt` crate)
121/// - Reporting job status changes before the prompt (see [`Reporter`])
122/// - Applying the `ignore-eof` option (see [`IgnoreEof`])
123///
124/// This function is intended to be used as the top-level read-eval loop in an
125/// interactive shell. It is not suitable for non-interactive command execution
126/// such as scripts. See [`read_eval_loop`] for non-interactive execution.
127///
128/// [`Interrupt`]: crate::Divert::Interrupt
129/// [`Reporter`]: yash_env::input::Reporter
130/// [`IgnoreEof`]: yash_env::input::IgnoreEof
131pub 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, /* is_interactive */ 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        // Refresh the parsing mode so that shell options changed by previously
155        // executed commands (e.g. via the `set` built-in) take effect on the
156        // command line parsed below.
157        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            // No more commands
170            Ok(None) => {
171                if !executed {
172                    env.exit_status = ExitStatus::SUCCESS;
173                }
174                return Continue(());
175            }
176
177            // Execute the command
178            Ok(Some(command)) => (run_command(env, &command).await, true),
179
180            // Parser error
181            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            // Recover from errors
190            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        // Break the loop if the command execution results in a divert
200        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        // If the command execution results in an interrupt in interactive mode,
317        // the loop continues
318        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        // If the command execution results in a divert other than an interrupt in
335        // interactive mode, the loop breaks
336        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        // If the command execution results in an interrupt in non-interactive mode,
354        // the loop breaks
355        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        // The ";;" causes a syntax error, the following "(" is ignored, and the
400        // loop continues with the command "echo $?" on the next line.
401        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}