use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Lex,
Parse,
Unsupported,
Expansion,
Refused,
}
impl Kind {
fn banner(self) -> &'static str {
match self {
Kind::Lex => "lex error",
Kind::Parse => "parse error",
Kind::Unsupported => "not yet supported",
Kind::Expansion => "expansion error",
Kind::Refused => "refused",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuarbError {
kind: Kind,
message: String,
hint: Option<String>,
at: Option<usize>,
source: Option<Box<QuarbError>>,
}
impl QuarbError {
fn new(kind: Kind, message: String) -> QuarbError {
QuarbError {
kind,
message,
hint: None,
at: None,
source: None,
}
}
pub fn lex(message: impl Into<String>) -> QuarbError {
QuarbError::new(Kind::Lex, message.into())
}
pub fn parse(message: impl Into<String>) -> QuarbError {
QuarbError::new(Kind::Parse, message.into())
}
pub fn unsupported(message: impl Into<String>) -> QuarbError {
QuarbError::new(Kind::Unsupported, message.into())
}
pub fn expansion(message: impl Into<String>) -> QuarbError {
QuarbError::new(Kind::Expansion, message.into())
}
pub fn refused(message: impl Into<String>) -> QuarbError {
QuarbError::new(Kind::Refused, message.into())
}
pub fn with_hint(mut self, hint: impl Into<String>) -> QuarbError {
self.hint = Some(hint.into());
self
}
pub fn at(mut self, offset: usize) -> QuarbError {
self.at = Some(offset);
self
}
pub fn wrapping(mut self, inner: QuarbError) -> QuarbError {
self.source = Some(Box::new(inner));
self
}
pub fn kind(&self) -> Kind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn hint(&self) -> Option<&str> {
self.hint.as_deref()
}
pub fn offset(&self) -> Option<usize> {
self.at
}
pub fn inner(&self) -> Option<&QuarbError> {
self.source.as_deref()
}
}
impl fmt::Display for QuarbError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.kind.banner(), self.message)?;
if let Some(hint) = &self.hint {
write!(f, "{hint}")?;
}
if let Some(inner) = &self.source {
write!(f, ": {inner}")?;
}
if let Some(at) = self.at {
write!(f, " (at character {})", at + 1)?;
}
Ok(())
}
}
impl std::error::Error for QuarbError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref().map(|e| e as _)
}
}
pub type Result<T> = std::result::Result<T, QuarbError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_reproduces_the_banners() {
assert_eq!(QuarbError::parse("x").to_string(), "parse error: x");
assert_eq!(
QuarbError::parse("x").at(4).to_string(),
"parse error: x (at character 5)"
);
assert_eq!(
QuarbError::unsupported("pipeline command 'f'")
.with_hint(" ('f' uses '@|')")
.to_string(),
"not yet supported: pipeline command 'f' ('f' uses '@|')"
);
let e =
QuarbError::expansion("in expansion of '&m' ('/a')").wrapping(QuarbError::lex("bad"));
assert_eq!(
e.to_string(),
"expansion error: in expansion of '&m' ('/a'): lex error: bad"
);
assert_eq!(e.inner().map(|i| i.kind()), Some(Kind::Lex));
assert_eq!(QuarbError::refused("r").to_string(), "refused: r");
}
}