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}