use crate::ExitStatus;
use crate::expansion::ErrorCause;
use std::ops::ControlFlow::{Break, Continue};
use yash_env::Env;
use yash_env::io::print_report;
use yash_env::semantics::Divert;
use yash_env::system::Isatty;
use yash_env::system::concurrency::WriteAll;
use yash_syntax::source::Source;
pub trait Handle<S> {
#[allow(async_fn_in_trait, reason = "we don't support Send")]
async fn handle(&self, env: &mut Env<S>) -> super::Result;
}
impl<S> Handle<S> for yash_syntax::parser::Error
where
S: Isatty + WriteAll,
{
async fn handle(&self, env: &mut Env<S>) -> super::Result {
print_report(env, &self.to_report()).await;
use yash_syntax::parser::ErrorCause::*;
let exit_status = match (&self.cause, &*self.location.code.source) {
(Syntax(_), _) | (Io(_), Source::DotScript { .. }) => ExitStatus::ERROR,
(Io(_), _) => ExitStatus::READ_ERROR,
};
Break(Divert::Interrupt(Some(exit_status)))
}
}
impl<S> Handle<S> for crate::expansion::Error
where
S: Isatty + WriteAll,
{
async fn handle(&self, env: &mut Env<S>) -> super::Result {
if let ErrorCause::Interrupted(exit_status) = &self.cause {
return Break(Divert::Interrupt(Some(*exit_status)));
}
print_report(env, &self.to_report()).await;
if env.errexit_is_applicable() {
Break(Divert::Exit(Some(ExitStatus::ERROR)))
} else {
Break(Divert::Interrupt(Some(ExitStatus::ERROR)))
}
}
}
impl<S> Handle<S> for crate::redir::Error
where
S: Isatty + WriteAll,
{
async fn handle(&self, env: &mut Env<S>) -> super::Result {
print_report(env, &self.to_report()).await;
env.exit_status = ExitStatus::ERROR;
Continue(())
}
}
#[cfg(test)]
mod parser_error_tests {
use super::*;
use futures_util::FutureExt as _;
use yash_syntax::parser::{Error, ErrorCause, SyntaxError};
use yash_syntax::source::{Code, Location};
#[test]
fn handling_syntax_error() {
let mut env = Env::new_virtual();
let error = Error {
cause: ErrorCause::Syntax(SyntaxError::RedundantToken),
location: Location::dummy("test"),
};
let result = error.handle(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
}
#[test]
fn handling_io_error_in_command_file() {
let mut env = Env::new_virtual();
let code = Code {
value: "test".to_string().into(),
start_line_number: 1.try_into().unwrap(),
source: Source::CommandFile {
path: "test".to_string(),
}
.into(),
}
.into();
let range = 0..0;
let location = Location { code, range };
let cause = ErrorCause::Io(std::io::Error::other("error").into());
let error = Error { cause, location };
let result = error.handle(&mut env).now_or_never().unwrap();
assert_eq!(
result,
Break(Divert::Interrupt(Some(ExitStatus::READ_ERROR)))
);
}
#[test]
fn handling_io_error_in_dot_script() {
let mut env = Env::new_virtual();
let code = Code {
value: "test".to_string().into(),
start_line_number: 1.try_into().unwrap(),
source: Source::DotScript {
name: "test".to_string(),
origin: Location::dummy("test"),
}
.into(),
}
.into();
let range = 0..0;
let location = Location { code, range };
let cause = ErrorCause::Io(std::io::Error::other("error").into());
let error = Error { cause, location };
let result = error.handle(&mut env).now_or_never().unwrap();
assert_eq!(result, Break(Divert::Interrupt(Some(ExitStatus::ERROR))));
}
}