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}