diff --git a/pest/Cargo.toml b/pest/Cargo.toml index abf1939f..b4d19da0 100644 --- a/pest/Cargo.toml +++ b/pest/Cargo.toml @@ -27,3 +27,7 @@ ucd-trie = { version = "0.1.1", default-features = false } serde = { version = "1.0.89", optional = true } serde_json = { version = "1.0.39", optional = true} thiserror = { version = "1.0.31", optional = true } +miette = { version = "5.1.1", optional = true } + +[dev-dependencies] +miette = { version = "5.1.1", features = ["fancy"] } diff --git a/pest/examples/parens.rs b/pest/examples/parens.rs index 566c119b..aad43706 100644 --- a/pest/examples/parens.rs +++ b/pest/examples/parens.rs @@ -67,9 +67,20 @@ fn main() { io::stdin().read_line(&mut line).unwrap(); line.pop(); - match ParenParser::parse(Rule::expr, &line) { + let parsed = ParenParser::parse(Rule::expr, &line); + #[cfg(feature = "miette")] + let parsed = parsed + .map_err(Error::into_miette) + .map_err(miette::Report::from); + + match parsed { Ok(pairs) => println!("{:?}", expr(pairs)), - Err(e) => println!("\n{}", e), + // To print pest errors, use Display formatting. + #[cfg(not(feature = "miette"))] + Err(e) => eprintln!("\n{}", e), + // To print miette errors, use Debug formatting. + #[cfg(feature = "miette")] + Err(e) => eprintln!("\n{:?}", e), }; } } diff --git a/pest/src/error.rs b/pest/src/error.rs index c2ab8066..cb816614 100644 --- a/pest/src/error.rs +++ b/pest/src/error.rs @@ -16,6 +16,7 @@ use alloc::string::String; use alloc::string::ToString; use alloc::vec::Vec; use core::cmp; +use core::fmt; use core::mem; use crate::position::Position; @@ -76,6 +77,15 @@ pub enum LineColLocation { Span((usize, usize), (usize, usize)), } +impl fmt::Display for LineColLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pos((l, c)) => write!(f, "({}, {})", l, c), + Self::Span((ls, cs), (le, ce)) => write!(f, "({}, {}) to ({}, {})", ls, cs, le, ce), + } + } +} + impl Error { /// Creates `Error` from `ErrorVariant` and `Position`. /// @@ -411,7 +421,7 @@ impl Error { } } - pub(crate) fn format(&self) -> String { + pub fn format(&self) -> String { let spacing = self.spacing(); let path = self .path @@ -482,6 +492,12 @@ impl Error { ) } } + + #[cfg(feature = "miette")] + /// Turns an error into a [miette](crates.io/miette) Diagnostic. + pub fn into_miette(self) -> impl ::miette::Diagnostic { + miette::MietteAdapter(self) + } } impl ErrorVariant { @@ -524,6 +540,43 @@ fn visualize_whitespace(input: &str) -> String { input.to_owned().replace('\r', "␍").replace('\n', "␊") } +#[cfg(feature = "miette")] +mod miette { + use alloc::string::ToString; + use std::boxed::Box; + + use super::{Error, InputLocation, RuleType}; + + use miette::{Diagnostic, LabeledSpan, SourceCode}; + + #[derive(thiserror::Error, Debug)] + #[error("Failure to parse at {}", self.0.line_col)] + pub(crate) struct MietteAdapter(pub(crate) Error); + + impl Diagnostic for MietteAdapter { + fn source_code(&self) -> Option<&dyn SourceCode> { + Some(&self.0.line) + } + + fn labels(&self) -> Option>> { + let message = self.0.variant.message().to_string(); + + let (offset, length) = match self.0.location { + InputLocation::Pos(x) => (x, 0), + InputLocation::Span((x, y)) => (x, y), + }; + + let span = LabeledSpan::new(Some(message), offset, length); + + Some(Box::new(std::iter::once(span))) + } + + fn help<'a>(&'a self) -> Option> { + Some(Box::new(self.0.variant.message())) + } + } +} + #[cfg(test)] mod tests { use super::super::position;