yash_semantics/
runner_legacy.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::Cell;
23use std::ops::ControlFlow::Continue;
24use std::rc::Rc;
25use yash_env::Env;
26use yash_env::option::Option::Verbose;
27use yash_env::option::State;
28use yash_env::semantics::ExitStatus;
29use yash_env::semantics::Result;
30use yash_syntax::parser::Parser;
31use yash_syntax::parser::lex::Lexer;
32
33/// Read-eval-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 following command line. The loop continues until the
39/// parser reaches the end of input or encounters a syntax error, or the command
40/// execution results in a `Break(Divert::...)`.
41///
42/// If the input source code contains no commands, the exit status is set to
43/// zero. Otherwise, the exit status reflects the result of the last executed
44/// command.
45///
46/// [Pending traps are run](run_traps_for_caught_signals) and [subshell statuses
47/// are updated](Env::update_all_subshell_statuses) between parsing input and
48/// running commands.
49///
50/// # Example
51///
52/// ```
53/// # futures_executor::block_on(async {
54/// # use std::ops::ControlFlow::Continue;
55/// # use yash_env::Env;
56/// # use yash_semantics::*;
57/// # use yash_syntax::parser::lex::Lexer;
58/// let mut env = Env::new_virtual();
59/// let mut lexer = Lexer::with_code("case foo in (bar) ;; esac");
60/// #[allow(deprecated)]
61/// let result = ReadEvalLoop::new(&mut env, &mut lexer).run().await;
62/// assert_eq!(result, Continue(()));
63/// assert_eq!(env.exit_status, ExitStatus::SUCCESS);
64/// # })
65/// ```
66#[deprecated = "use the `read_eval_loop` function instead"]
67#[derive(Debug)]
68#[must_use = "the loop must be run to execute commands"]
69pub struct ReadEvalLoop<'a, 'b> {
70    env: &'a mut Env,
71    lexer: &'a mut Lexer<'b>,
72    verbose: Option<Rc<Cell<State>>>,
73}
74
75#[allow(deprecated)]
76impl<'a, 'b> ReadEvalLoop<'a, 'b> {
77    /// Creates a new read-eval-loop instance.
78    ///
79    /// This constructor requires two parameters: an environment in which the
80    /// loop runs and a lexer that reads input.
81    pub fn new(env: &'a mut Env, lexer: &'a mut Lexer<'b>) -> Self {
82        Self {
83            env,
84            lexer,
85            verbose: None,
86        }
87    }
88
89    /// Sets a shared option state to which the verbose option is reflected.
90    ///
91    /// This function is meant to be used with a lexer with an [`FdReader`]
92    /// input. You should set the same shared cell of an option state to the
93    /// input function and the loop. Before reading each command line, the loop
94    /// copies the value of `env.options.get(Verbose)` to the cell. The input
95    /// function checks it to see if it needs to echo the line it reads to the
96    /// standard error. That achieves the effect of the `Verbose` shell option.
97    ///
98    /// ```
99    /// #![allow(deprecated)]
100    /// # futures_executor::block_on(async {
101    /// # use std::cell::Cell;
102    /// # use std::rc::Rc;
103    /// # use yash_env::Env;
104    /// # use yash_env::input::FdReader;
105    /// # use yash_env::io::Fd;
106    /// # use yash_env::option::{Verbose, State};
107    /// # use yash_semantics::*;
108    /// # use yash_syntax::parser::lex::Lexer;
109    /// let mut env = Env::new_virtual();
110    /// let mut input = Box::new(FdReader::new(Fd::STDIN, Clone::clone(&env.system)));
111    /// let verbose = Rc::new(Cell::new(State::Off));
112    /// input.set_echo(Some(Rc::clone(&verbose)));
113    /// let mut lexer = Lexer::new(input);
114    /// let mut rel = ReadEvalLoop::new(&mut env, &mut lexer);
115    /// rel.set_verbose(Some(Rc::clone(&verbose)));
116    /// let _ = rel.run().await;
117    /// # })
118    /// ```
119    ///
120    /// # Deprecation
121    ///
122    /// This function is deprecated in favor of the [`Echo`] input decorator.
123    ///
124    /// [`Echo`]: yash_env::input::Echo
125    /// [`FdReader`]: yash_env::input::FdReader
126    #[deprecated = "use yash_env::input::Echo instead"]
127    pub fn set_verbose(&mut self, verbose: Option<Rc<Cell<State>>>) {
128        self.verbose = verbose;
129    }
130
131    /// Runs the read-eval-loop.
132    pub async fn run(self) -> Result {
133        let mut executed = false;
134
135        loop {
136            if !self.lexer.pending() {
137                self.lexer.flush();
138            }
139            if let Some(verbose) = &self.verbose {
140                verbose.set(self.env.options.get(Verbose));
141            }
142
143            let mut parser = Parser::config()
144                .aliases(&self.env)
145                .declaration_utilities(&self.env)
146                .input(self.lexer);
147            match parser.command_line().await {
148                Ok(Some(command)) => {
149                    run_traps_for_caught_signals(self.env).await?;
150                    self.env.update_all_subshell_statuses();
151                    command.execute(self.env).await?
152                }
153                Ok(None) => break,
154                Err(error) => error.handle(self.env).await?,
155            };
156            executed = true;
157        }
158
159        if !executed {
160            self.env.exit_status = ExitStatus::SUCCESS;
161        }
162
163        Continue(())
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::tests::echo_builtin;
171    use crate::tests::return_builtin;
172    use futures_util::FutureExt;
173    use std::cell::Cell;
174    use std::ops::ControlFlow::Break;
175    use std::rc::Rc;
176    use yash_env::input::FdReader;
177    use yash_env::io::Fd;
178    use yash_env::option::Option::Verbose;
179    use yash_env::option::State::{Off, On};
180    use yash_env::semantics::Divert;
181    use yash_env::system::r#virtual::FileBody;
182    use yash_env::system::r#virtual::SIGUSR1;
183    use yash_env::system::r#virtual::VirtualSystem;
184    use yash_env::trap::Action;
185    use yash_env_test_helper::assert_stderr;
186    use yash_env_test_helper::assert_stdout;
187    use yash_syntax::source::Location;
188
189    #[test]
190    fn exit_status_zero_with_no_commands() {
191        let mut env = Env::new_virtual();
192        env.exit_status = ExitStatus(5);
193        let mut lexer = Lexer::with_code("");
194        #[allow(deprecated)]
195        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
196        let result = rel.run().now_or_never().unwrap();
197        assert_eq!(result, Continue(()));
198        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
199    }
200
201    #[test]
202    fn exit_status_in_out() {
203        let system = VirtualSystem::new();
204        let state = Rc::clone(&system.state);
205        let mut env = Env::with_system(Box::new(system));
206        env.exit_status = ExitStatus(42);
207        env.builtins.insert("echo", echo_builtin());
208        env.builtins.insert("return", return_builtin());
209        let mut lexer = Lexer::with_code("echo $?; return -n 7");
210        #[allow(deprecated)]
211        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
212        let result = rel.run().now_or_never().unwrap();
213        assert_eq!(result, Continue(()));
214        assert_eq!(env.exit_status, ExitStatus(7));
215        assert_stdout(&state, |stdout| assert_eq!(stdout, "42\n"));
216    }
217
218    #[test]
219    fn executing_many_lines_of_code() {
220        let system = VirtualSystem::new();
221        let state = Rc::clone(&system.state);
222        let mut env = Env::with_system(Box::new(system));
223        env.builtins.insert("echo", echo_builtin());
224        let mut lexer = Lexer::with_code("echo 1\necho 2\necho 3;");
225        #[allow(deprecated)]
226        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
227        let result = rel.run().now_or_never().unwrap();
228        assert_eq!(result, Continue(()));
229        assert_stdout(&state, |stdout| assert_eq!(stdout, "1\n2\n3\n"));
230    }
231
232    #[test]
233    fn parsing_with_aliases() {
234        use yash_syntax::alias::{Alias, HashEntry};
235        let system = VirtualSystem::new();
236        let state = Rc::clone(&system.state);
237        let mut env = Env::with_system(Box::new(system));
238        env.aliases.insert(HashEntry(Rc::new(Alias {
239            name: "echo".to_string(),
240            replacement: "echo alias\necho ok".to_string(),
241            global: false,
242            origin: Location::dummy(""),
243        })));
244        env.builtins.insert("echo", echo_builtin());
245        let mut lexer = Lexer::with_code("echo");
246        #[allow(deprecated)]
247        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
248        let result = rel.run().now_or_never().unwrap();
249        assert_eq!(result, Continue(()));
250        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
251        assert_stdout(&state, |stdout| assert_eq!(stdout, "alias\nok\n"));
252    }
253
254    #[test]
255    fn verbose_option() {
256        let system = VirtualSystem::new();
257        let state = Rc::clone(&system.state);
258        state
259            .borrow_mut()
260            .file_system
261            .get("/dev/stdin")
262            .unwrap()
263            .borrow_mut()
264            .body = FileBody::new(*b"case _ in esac\n");
265        let mut env = Env::with_system(Box::new(system));
266        env.options.set(Verbose, On);
267        let mut input = Box::new(FdReader::new(Fd::STDIN, Clone::clone(&env.system)));
268        let verbose = Rc::new(Cell::new(Off));
269        #[allow(deprecated)]
270        input.set_echo(Some(Rc::clone(&verbose)));
271        let mut lexer = Lexer::new(input);
272        #[allow(deprecated)]
273        let mut rel = ReadEvalLoop::new(&mut env, &mut lexer);
274        #[allow(deprecated)]
275        rel.set_verbose(Some(Rc::clone(&verbose)));
276
277        let result = rel.run().now_or_never().unwrap();
278        assert_eq!(result, Continue(()));
279        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
280        assert_eq!(verbose.get(), On);
281        assert_stderr(&state, |stderr| assert_eq!(stderr, "case _ in esac\n"));
282    }
283
284    #[test]
285    fn handling_syntax_error() {
286        let system = VirtualSystem::new();
287        let state = Rc::clone(&system.state);
288        let mut env = Env::with_system(Box::new(system));
289        let mut lexer = Lexer::with_code(";;");
290        #[allow(deprecated)]
291        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
292        let result = rel.run().now_or_never().unwrap();
293        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
294        assert_stderr(&state, |stderr| assert_ne!(stderr, ""));
295    }
296
297    #[test]
298    fn syntax_error_aborts_loop() {
299        let system = VirtualSystem::new();
300        let state = Rc::clone(&system.state);
301        let mut env = Env::with_system(Box::new(system));
302        env.builtins.insert("echo", echo_builtin());
303        let mut lexer = Lexer::with_code(";;\necho !");
304        #[allow(deprecated)]
305        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
306        let result = rel.run().now_or_never().unwrap();
307        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
308        assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
309    }
310
311    #[test]
312    fn running_traps_between_parsing_and_executing() {
313        let system = VirtualSystem::new();
314        let state = Rc::clone(&system.state);
315        let mut env = Env::with_system(Box::new(system.clone()));
316        env.builtins.insert("echo", echo_builtin());
317        env.traps
318            .set_action(
319                &mut env.system,
320                SIGUSR1,
321                Action::Command("echo USR1".into()),
322                Location::dummy(""),
323                false,
324            )
325            .unwrap();
326        let _ = state
327            .borrow_mut()
328            .processes
329            .get_mut(&system.process_id)
330            .unwrap()
331            .raise_signal(SIGUSR1);
332        let mut lexer = Lexer::with_code("echo $?");
333        #[allow(deprecated)]
334        let rel = ReadEvalLoop::new(&mut env, &mut lexer);
335        let result = rel.run().now_or_never().unwrap();
336        assert_eq!(result, Continue(()));
337        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
338        assert_stdout(&state, |stdout| assert_eq!(stdout, "USR1\n0\n"));
339    }
340}