yash_semantics/
handle.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//! Error handlers.
18
19use crate::ExitStatus;
20use std::ops::ControlFlow::{Break, Continue};
21use yash_env::Env;
22use yash_env::io::print_report;
23use yash_env::semantics::Divert;
24use yash_syntax::source::Source;
25
26/// Error handler.
27///
28/// Most errors in the shell are handled by printing an error message to the
29/// standard error and returning a non-zero exit status. This trait provides a
30/// standard interface for implementing that behavior.
31pub trait Handle {
32    /// Handles the argument error.
33    #[allow(async_fn_in_trait)] // We don't support Send
34    async fn handle(&self, env: &mut Env) -> super::Result;
35}
36
37/// Prints an error message.
38///
39/// This implementation handles the error by printing an error message to the
40/// standard error and returning `Divert::Interrupt(Some(exit_status))`, where
41/// `exit_status` is [`ExitStatus::ERROR`] if the error cause is a syntax error
42/// or the error location is [`Source::DotScript`], or
43/// [`ExitStatus::READ_ERROR`] otherwise.
44/// Note that other POSIX-compliant implementations may use different non-zero
45/// exit statuses instead of `ExitStatus::ERROR`.
46impl Handle for yash_syntax::parser::Error {
47    async fn handle(&self, env: &mut Env) -> super::Result {
48        print_report(env, &self.to_report()).await;
49
50        use yash_syntax::parser::ErrorCause::*;
51        let exit_status = match (&self.cause, &*self.location.code.source) {
52            (Syntax(_), _) | (Io(_), Source::DotScript { .. }) => ExitStatus::ERROR,
53            (Io(_), _) => ExitStatus::READ_ERROR,
54        };
55        Break(Divert::Interrupt(Some(exit_status)))
56    }
57}
58
59/// Prints an error message and returns a divert result indicating a non-zero
60/// exit status.
61///
62/// This implementation handles the error by printing an error message to the
63/// standard error and returning `Divert::Interrupt(Some(ExitStatus::ERROR))`.
64/// If the [`ErrExit`] option is set, `Divert::Exit(Some(ExitStatus::ERROR))` is
65/// returned instead.
66///
67/// Note that other POSIX-compliant implementations may use different non-zero
68/// exit statuses.
69///
70/// [`ErrExit`]: yash_env::option::Option::ErrExit
71impl Handle for crate::expansion::Error {
72    async fn handle(&self, env: &mut Env) -> super::Result {
73        print_report(env, &self.to_report()).await;
74
75        if env.errexit_is_applicable() {
76            Break(Divert::Exit(Some(ExitStatus::ERROR)))
77        } else {
78            Break(Divert::Interrupt(Some(ExitStatus::ERROR)))
79        }
80    }
81}
82
83/// Prints an error message and sets the exit status to non-zero.
84///
85/// This implementation handles a redirection error by printing an error message
86/// to the standard error and setting the exit status to [`ExitStatus::ERROR`].
87/// Note that other POSIX-compliant implementations may use different non-zero
88/// exit statuses.
89///
90/// This implementation does not return [`Divert::Interrupt`] because a
91/// redirection error does not always mean an interrupt. The shell should
92/// interrupt only on a redirection error during the execution of a special
93/// built-in. The caller is responsible for checking the condition and
94/// interrupting accordingly.
95impl Handle for crate::redir::Error {
96    async fn handle(&self, env: &mut Env) -> super::Result {
97        print_report(env, &self.to_report()).await;
98        env.exit_status = ExitStatus::ERROR;
99        Continue(())
100    }
101}
102
103#[cfg(test)]
104mod parser_error_tests {
105    use super::*;
106    use futures_util::FutureExt as _;
107    use yash_syntax::parser::{Error, ErrorCause, SyntaxError};
108    use yash_syntax::source::{Code, Location};
109
110    #[test]
111    fn handling_syntax_error() {
112        let mut env = Env::new_virtual();
113        let error = Error {
114            cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
115            location: Location::dummy("test"),
116        };
117        let result = error.handle(&mut env).now_or_never().unwrap();
118        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
119    }
120
121    #[test]
122    fn handling_io_error_in_command_file() {
123        let mut env = Env::new_virtual();
124        let code = Code {
125            value: "test".to_string().into(),
126            start_line_number: 1.try_into().unwrap(),
127            source: Source::CommandFile {
128                path: "test".to_string(),
129            }
130            .into(),
131        }
132        .into();
133        let range = 0..0;
134        let location = Location { code, range };
135        let cause = ErrorCause::Io(std::io::Error::other("error").into());
136        let error = Error { cause, location };
137
138        let result = error.handle(&mut env).now_or_never().unwrap();
139        assert_eq!(
140            result,
141            Break(Divert::Interrupt(Some(ExitStatus::READ_ERROR)))
142        );
143    }
144
145    #[test]
146    fn handling_io_error_in_dot_script() {
147        let mut env = Env::new_virtual();
148        let code = Code {
149            value: "test".to_string().into(),
150            start_line_number: 1.try_into().unwrap(),
151            source: Source::DotScript {
152                name: "test".to_string(),
153                origin: Location::dummy("test"),
154            }
155            .into(),
156        }
157        .into();
158        let range = 0..0;
159        let location = Location { code, range };
160        let cause = ErrorCause::Io(std::io::Error::other("error").into());
161        let error = Error { cause, location };
162
163        let result = error.handle(&mut env).now_or_never().unwrap();
164        assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
165    }
166}