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::Handle;
20use crate::command::Command;
21use crate::trap::run_traps_for_caught_signals;
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(env: &RefCell<&mut Env>, lexer: &mut Lexer<'_>) -> Result {
102    read_eval_loop_impl(env, lexer, /* is_interactive */ false).await
103}
104
105/// [`read_eval_loop`] for interactive shells
106///
107/// This function extends the [`read_eval_loop`] function to act as an
108/// interactive shell. The difference is that this function suppresses
109/// [`Interrupt`]s and continues the loop if the parser fails with a syntax
110/// error or if the command execution results in an interrupt. Note that I/O
111/// errors detected by the parser are not recovered from.
112///
113/// Also note that the following aspects of the interactive shell are *not*
114/// implemented in this function:
115///
116/// - Prompting the user for input (see the `yash-prompt` crate)
117/// - Reporting job status changes before the prompt (see [`Reporter`])
118/// - Applying the `ignore-eof` option (see [`IgnoreEof`])
119///
120/// This function is intended to be used as the top-level read-eval loop in an
121/// interactive shell. It is not suitable for non-interactive command execution
122/// such as scripts. See [`read_eval_loop`] for non-interactive execution.
123///
124/// [`Interrupt`]: crate::Divert::Interrupt
125/// [`Reporter`]: yash_env::input::Reporter
126/// [`IgnoreEof`]: yash_env::input::IgnoreEof
127pub async fn interactive_read_eval_loop(env: &RefCell<&mut Env>, lexer: &mut Lexer<'_>) -> Result {
128    read_eval_loop_impl(env, lexer, /* is_interactive */ true).await
129}
130
131// The RefCell should be local to the loop, so it is safe to keep the mutable
132// borrow across await points.
133#[allow(clippy::await_holding_refcell_ref)]
134async fn read_eval_loop_impl(
135    env: &RefCell<&mut Env>,
136    lexer: &mut Lexer<'_>,
137    is_interactive: bool,
138) -> Result {
139    let mut executed = false;
140
141    loop {
142        if !lexer.pending() {
143            lexer.flush();
144        }
145
146        let command = Parser::config()
147            .aliases(env)
148            .declaration_utilities(env)
149            .input(lexer)
150            .command_line()
151            .await;
152
153        let env = &mut **env.borrow_mut();
154
155        let (mut result, error_recoverable) = match command {
156            // No more commands
157            Ok(None) => {
158                if !executed {
159                    env.exit_status = ExitStatus::SUCCESS;
160                }
161                return Continue(());
162            }
163
164            // Execute the command
165            Ok(Some(command)) => (run_command(env, &command).await, true),
166
167            // Parser error
168            Err(error) => {
169                let result = error.handle(env).await;
170                let error_recoverable = matches!(error.cause, ErrorCause::Syntax(_));
171                (result, error_recoverable)
172            }
173        };
174
175        if is_interactive && error_recoverable {
176            // Recover from errors
177            if let Break(Divert::Interrupt(exit_status)) = result {
178                if let Some(exit_status) = exit_status {
179                    env.exit_status = exit_status;
180                }
181                result = Continue(());
182                lexer.flush();
183            }
184        }
185
186        // Break the loop if the command execution results in a divert
187        result?;
188
189        executed = true;
190    }
191}
192
193async fn run_command(env: &mut Env, command: &List) -> Result {
194    run_traps_for_caught_signals(env).await?;
195    env.update_all_subshell_statuses();
196    command.execute(env).await
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::tests::echo_builtin;
203    use crate::tests::return_builtin;
204    use futures_util::FutureExt;
205    use std::rc::Rc;
206    use yash_env::input::Echo;
207    use yash_env::input::Memory;
208    use yash_env::option::Option::Verbose;
209    use yash_env::option::State::On;
210    use yash_env::system::r#virtual::SIGUSR1;
211    use yash_env::system::r#virtual::VirtualSystem;
212    use yash_env::trap::Action;
213    use yash_env_test_helper::assert_stderr;
214    use yash_env_test_helper::assert_stdout;
215    use yash_syntax::input::Context;
216    use yash_syntax::source::Location;
217
218    #[test]
219    fn exit_status_zero_with_no_commands() {
220        let mut env = Env::new_virtual();
221        env.exit_status = ExitStatus(5);
222        let mut lexer = Lexer::with_code("");
223        let ref_env = RefCell::new(&mut env);
224
225        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
226        assert_eq!(result, Continue(()));
227        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
228    }
229
230    #[test]
231    fn exit_status_in_out() {
232        let system = VirtualSystem::new();
233        let state = Rc::clone(&system.state);
234        let mut env = Env::with_system(Box::new(system));
235        env.exit_status = ExitStatus(42);
236        env.builtins.insert("echo", echo_builtin());
237        env.builtins.insert("return", return_builtin());
238        let mut lexer = Lexer::with_code("echo $?; return -n 7");
239        let ref_env = RefCell::new(&mut env);
240
241        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
242        assert_eq!(result, Continue(()));
243        assert_eq!(env.exit_status, ExitStatus(7));
244        assert_stdout(&state, |stdout| assert_eq!(stdout, "42\n"));
245    }
246
247    #[test]
248    fn executing_many_lines_of_code() {
249        let system = VirtualSystem::new();
250        let state = Rc::clone(&system.state);
251        let mut env = Env::with_system(Box::new(system));
252        env.builtins.insert("echo", echo_builtin());
253        let mut lexer = Lexer::with_code("echo 1\necho 2\necho 3;");
254        let ref_env = RefCell::new(&mut env);
255
256        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
257        assert_eq!(result, Continue(()));
258        assert_stdout(&state, |stdout| assert_eq!(stdout, "1\n2\n3\n"));
259    }
260
261    #[test]
262    fn parsing_with_aliases() {
263        use yash_syntax::alias::{Alias, HashEntry};
264        let system = VirtualSystem::new();
265        let state = Rc::clone(&system.state);
266        let mut env = Env::with_system(Box::new(system));
267        env.aliases.insert(HashEntry(Rc::new(Alias {
268            name: "echo".to_string(),
269            replacement: "echo alias\necho ok".to_string(),
270            global: false,
271            origin: Location::dummy(""),
272        })));
273        env.builtins.insert("echo", echo_builtin());
274        let mut lexer = Lexer::with_code("echo");
275        let ref_env = RefCell::new(&mut env);
276
277        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
278        assert_eq!(result, Continue(()));
279        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
280        assert_stdout(&state, |stdout| assert_eq!(stdout, "alias\nok\n"));
281    }
282
283    #[test]
284    fn verbose_option() {
285        let system = VirtualSystem::new();
286        let state = Rc::clone(&system.state);
287        let mut env = Env::with_system(Box::new(system));
288        env.options.set(Verbose, On);
289        let ref_env = RefCell::new(&mut env);
290        let input = Box::new(Echo::new(Memory::new("case _ in esac"), &ref_env));
291        let mut lexer = Lexer::new(input);
292
293        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
294        drop(lexer);
295        assert_eq!(result, Continue(()));
296        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
297        assert_stderr(&state, |stderr| assert_eq!(stderr, "case _ in esac"));
298    }
299
300    #[test]
301    fn command_interrupt_interactive() {
302        // If the command execution results in an interrupt in interactive mode,
303        // the loop continues
304        let system = VirtualSystem::new();
305        let state = Rc::clone(&system.state);
306        let mut env = Env::with_system(Box::new(system));
307        env.builtins.insert("echo", echo_builtin());
308        let mut lexer = Lexer::with_code("${X?}\necho $?\n");
309        let ref_env = RefCell::new(&mut env);
310
311        let result = interactive_read_eval_loop(&ref_env, &mut lexer)
312            .now_or_never()
313            .unwrap();
314        assert_eq!(result, Continue(()));
315        assert_stdout(&state, |stdout| assert_eq!(stdout, "2\n"));
316    }
317
318    #[test]
319    fn command_other_divert_interactive() {
320        // If the command execution results in a divert other than an interrupt in
321        // interactive mode, the loop breaks
322        let system = VirtualSystem::new();
323        let state = Rc::clone(&system.state);
324        let mut env = Env::with_system(Box::new(system));
325        env.builtins.insert("echo", echo_builtin());
326        env.builtins.insert("return", return_builtin());
327        let mut lexer = Lexer::with_code("return 123\necho $?\n");
328        let ref_env = RefCell::new(&mut env);
329
330        let result = interactive_read_eval_loop(&ref_env, &mut lexer)
331            .now_or_never()
332            .unwrap();
333        assert_eq!(result, Break(Divert::Return(Some(ExitStatus(123)))));
334        assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
335    }
336
337    #[test]
338    fn command_interrupt_non_interactive() {
339        // If the command execution results in an interrupt in non-interactive mode,
340        // the loop breaks
341        let system = VirtualSystem::new();
342        let state = Rc::clone(&system.state);
343        let mut env = Env::with_system(Box::new(system));
344        env.builtins.insert("echo", echo_builtin());
345        let mut lexer = Lexer::with_code("${X?}\necho $?\n");
346        let ref_env = RefCell::new(&mut env);
347
348        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
349        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
350        assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
351    }
352
353    #[test]
354    fn handling_syntax_error() {
355        let system = VirtualSystem::new();
356        let state = Rc::clone(&system.state);
357        let mut env = Env::with_system(Box::new(system));
358        let mut lexer = Lexer::with_code(";;");
359        let ref_env = RefCell::new(&mut env);
360        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
361        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
362        assert_stderr(&state, |stderr| assert_ne!(stderr, ""));
363    }
364
365    #[test]
366    fn syntax_error_aborts_non_interactive_loop() {
367        let system = VirtualSystem::new();
368        let state = Rc::clone(&system.state);
369        let mut env = Env::with_system(Box::new(system));
370        env.builtins.insert("echo", echo_builtin());
371        let mut lexer = Lexer::with_code(";;\necho !");
372        let ref_env = RefCell::new(&mut env);
373
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_stdout(&state, |stdout| assert_eq!(stdout, ""));
377    }
378
379    #[test]
380    fn syntax_error_continues_interactive_loop() {
381        let system = VirtualSystem::new();
382        let state = Rc::clone(&system.state);
383        let mut env = Env::with_system(Box::new(system));
384        env.builtins.insert("echo", echo_builtin());
385        // The ";;" causes a syntax error, the following "(" is ignored, and the
386        // loop continues with the command "echo $?" on the next line.
387        let mut lexer = Lexer::with_code(";; (\necho $?");
388        let ref_env = RefCell::new(&mut env);
389
390        let result = interactive_read_eval_loop(&ref_env, &mut lexer)
391            .now_or_never()
392            .unwrap();
393        assert_eq!(result, Continue(()));
394        assert_stdout(&state, |stdout| assert_eq!(stdout, "2\n"));
395    }
396
397    #[test]
398    fn input_error_aborts_loop() {
399        struct BrokenInput;
400        impl yash_syntax::input::Input for BrokenInput {
401            async fn next_line(&mut self, _context: &Context) -> std::io::Result<String> {
402                Err(std::io::Error::other("broken"))
403            }
404        }
405
406        let mut lexer = Lexer::new(Box::new(BrokenInput));
407        let mut env = Env::new_virtual();
408        let ref_env = RefCell::new(&mut env);
409
410        let result = interactive_read_eval_loop(&ref_env, &mut lexer)
411            .now_or_never()
412            .unwrap();
413        assert_eq!(
414            result,
415            Break(Divert::Interrupt(Some(ExitStatus::READ_ERROR)))
416        );
417    }
418
419    #[test]
420    fn running_traps_between_parsing_and_executing() {
421        let system = VirtualSystem::new();
422        let state = Rc::clone(&system.state);
423        let mut env = Env::with_system(Box::new(system.clone()));
424        env.builtins.insert("echo", echo_builtin());
425        env.traps
426            .set_action(
427                &mut env.system,
428                SIGUSR1,
429                Action::Command("echo USR1".into()),
430                Location::dummy(""),
431                false,
432            )
433            .unwrap();
434        let _ = state
435            .borrow_mut()
436            .processes
437            .get_mut(&system.process_id)
438            .unwrap()
439            .raise_signal(SIGUSR1);
440        let mut lexer = Lexer::with_code("echo $?");
441        let ref_env = RefCell::new(&mut env);
442
443        let result = read_eval_loop(&ref_env, &mut lexer).now_or_never().unwrap();
444        assert_eq!(result, Continue(()));
445        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
446        assert_stdout(&state, |stdout| assert_eq!(stdout, "USR1\n0\n"));
447    }
448}