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