1use 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
28pub trait Handle<S> {
34 #[allow(async_fn_in_trait, reason = "we don't support Send")]
36 async fn handle(&self, env: &mut Env<S>) -> super::Result;
37}
38
39impl<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
64impl<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
91impl<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}