Skip to main content

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