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