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