use std::ops::ControlFlow::{Break, Continue};
use yash_env::io::Fd;
use yash_env::semantics::Divert;
use yash_env::semantics::ExitStatus;
#[cfg(doc)]
use yash_env::stack::Stack;
use yash_env::Env;
#[cfg(doc)]
use yash_env::SharedSystem;
use yash_syntax::source::pretty::Annotation;
use yash_syntax::source::pretty::AnnotationType;
use yash_syntax::source::pretty::Message;
use yash_syntax::source::pretty::MessageBase;
use yash_syntax::source::Location;
pub mod syntax;
#[must_use]
pub fn arrange_message_and_divert<'e: 'm, 'm>(
env: &'e Env,
mut message: Message<'m>,
) -> (String, yash_env::semantics::Result) {
let is_special_builtin;
if let Some(builtin) = env.stack.current_builtin() {
message.annotations.push(Annotation::new(
AnnotationType::Info,
format!("error occurred in the {} built-in", builtin.name.value).into(),
&builtin.name.origin,
));
let source = &builtin.name.origin.code.source;
source.complement_annotations(&mut message.annotations);
is_special_builtin = builtin.is_special;
} else {
is_special_builtin = false;
}
let message = yash_env::io::message_to_string(env, &message);
let divert = if is_special_builtin {
Break(Divert::Interrupt(None))
} else {
Continue(())
};
(message, divert)
}
async fn report(
env: &mut Env,
message: Message<'_>,
exit_status: ExitStatus,
) -> yash_env::builtin::Result {
let (message, divert) = arrange_message_and_divert(env, message);
_ = env.system.write_all(Fd::STDERR, message.as_bytes()).await;
yash_env::builtin::Result::with_exit_status_and_divert(exit_status, divert)
}
#[inline]
pub async fn report_failure<'a, M>(env: &mut Env, message: M) -> yash_env::builtin::Result
where
M: Into<Message<'a>> + 'a,
{
report(env, message.into(), ExitStatus::FAILURE).await
}
pub async fn report_simple_failure(env: &mut Env, title: &str) -> yash_env::builtin::Result {
let message = Message {
r#type: AnnotationType::Error,
title: title.into(),
annotations: vec![],
footers: vec![],
};
report_failure(env, message).await
}
#[inline]
pub async fn report_error<'a, M>(env: &mut Env, message: M) -> yash_env::builtin::Result
where
M: Into<Message<'a>> + 'a,
{
report(env, message.into(), ExitStatus::ERROR).await
}
pub async fn report_simple_error(env: &mut Env, title: &str) -> yash_env::builtin::Result {
let message = Message {
r#type: AnnotationType::Error,
title: title.into(),
annotations: vec![],
footers: vec![],
};
report_error(env, message).await
}
pub async fn syntax_error(
env: &mut Env,
label: &str,
location: &Location,
) -> yash_env::builtin::Result {
let annotation = Annotation::new(AnnotationType::Error, label.into(), location);
let message = Message {
r#type: AnnotationType::Error,
title: "command argument syntax error".into(),
annotations: vec![annotation],
footers: vec![],
};
report_error(env, message).await
}
pub async fn output(env: &mut Env, content: &str) -> yash_env::builtin::Result {
match env.system.write_all(Fd::STDOUT, content.as_bytes()).await {
Ok(_) => Default::default(),
Err(errno) => {
let message = Message {
r#type: AnnotationType::Error,
title: format!("error printing results to stdout: {errno}").into(),
annotations: vec![],
footers: vec![],
};
report_failure(env, message).await
}
}
}
#[must_use]
pub fn to_single_message<'a, I, M>(errors: I) -> Option<Message<'a>>
where
I: IntoIterator<Item = &'a M>,
M: MessageBase + 'a,
{
let mut errors = errors.into_iter();
let first = errors.next()?;
let mut message = Message::from(first);
let other_errors = errors.map(MessageBase::main_annotation);
message.annotations.extend(other_errors);
Some(message)
}
#[cfg(test)]
mod tests {
use super::*;
use yash_env::semantics::Field;
use yash_env::stack::Builtin;
use yash_env::stack::Frame;
fn dummy_message() -> Message<'static> {
Message {
r#type: AnnotationType::Error,
title: "foo".into(),
annotations: vec![],
footers: vec![],
}
}
#[test]
fn divert_without_builtin() {
let env = Env::new_virtual();
let (_message, divert) = arrange_message_and_divert(&env, dummy_message());
assert_eq!(divert, Continue(()));
}
#[test]
fn divert_with_special_builtin() {
let mut env = Env::new_virtual();
let env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("builtin"),
is_special: true,
}));
let (_message, divert) = arrange_message_and_divert(&env, dummy_message());
assert_eq!(divert, Break(Divert::Interrupt(None)));
}
#[test]
fn divert_with_non_special_builtin() {
let mut env = Env::new_virtual();
let env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("builtin"),
is_special: false,
}));
let (_message, divert) = arrange_message_and_divert(&env, dummy_message());
assert_eq!(divert, Continue(()));
}
}