From 65e5b2b754a4d9bb9b231ab61ef0af437b556fbe Mon Sep 17 00:00:00 2001
From: Parsa Bahraminejad
Date: Tue, 10 Sep 2024 20:22:18 -0400
Subject: [PATCH] feat: added upstream miette support (#1038)
* feat: conditionally implement `miette::Diagnostic` for `pest::error::Error`
* fix: use `into_miette` method for turning `Error` into `miette::Diagnostic`
* chore: clean up `parens` example
* Added test for miette support
* Update miette version
* Fixes and cargo fmt
* Fixed dependencies for miette
---------
Co-authored-by: Bobbbay
---
pest/Cargo.toml | 4 +++
pest/examples/parens.rs | 15 ++++++--
pest/src/error.rs | 77 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/pest/Cargo.toml b/pest/Cargo.toml
index e15d3253..a56a5343 100644
--- a/pest/Cargo.toml
+++ b/pest/Cargo.toml
@@ -21,6 +21,8 @@ std = ["ucd-trie/std", "dep:thiserror"]
pretty-print = ["dep:serde", "dep:serde_json"]
# Enable const fn constructor for `PrecClimber`
const_prec_climber = []
+# Enable miette error
+miette-error = ["std", "pretty-print", "dep:miette", "dep:thiserror"]
[dependencies]
ucd-trie = { version = "0.1.5", default-features = false }
@@ -28,9 +30,11 @@ serde = { version = "1.0.145", optional = true }
serde_json = { version = "1.0.85", optional = true }
thiserror = { version = "1.0.37", optional = true }
memchr = { version = "2", optional = true }
+miette = { version = "7.2.0", optional = true, features = ["fancy"] }
[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
+miette = { version = "7.2.0", features = ["fancy"] }
[[bench]]
name = "stack"
diff --git a/pest/examples/parens.rs b/pest/examples/parens.rs
index 172d1e36..f91cb53b 100644
--- a/pest/examples/parens.rs
+++ b/pest/examples/parens.rs
@@ -68,9 +68,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-error")]
+ 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-error"))]
+ Err(e) => eprintln!("\n{}", e),
+ // To print miette errors, use Debug formatting.
+ #[cfg(feature = "miette-error")]
+ Err(e) => eprintln!("\n{:?}", e),
};
}
}
diff --git a/pest/src/error.rs b/pest/src/error.rs
index 7ad4a1c8..0142b342 100644
--- a/pest/src/error.rs
+++ b/pest/src/error.rs
@@ -671,6 +671,12 @@ impl Error {
)
}
}
+
+ #[cfg(feature = "miette-error")]
+ /// Turns an error into a [miette](crates.io/miette) Diagnostic.
+ pub fn into_miette(self) -> impl ::miette::Diagnostic {
+ miette_adapter::MietteAdapter(self)
+ }
}
impl ErrorVariant {
@@ -728,6 +734,47 @@ fn visualize_whitespace(input: &str) -> String {
input.to_owned().replace('\r', "␍").replace('\n', "␊")
}
+#[cfg(feature = "miette-error")]
+mod miette_adapter {
+ use alloc::string::ToString;
+ use std::boxed::Box;
+
+ use crate::error::LineColLocation;
+
+ use super::{Error, 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.line_col {
+ LineColLocation::Pos((_, c)) => (c - 1, 1),
+ LineColLocation::Span((_, start_c), (_, end_c)) => {
+ (start_c - 1, end_c - start_c + 1)
+ }
+ };
+
+ 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.message()))
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -1104,4 +1151,34 @@ mod tests {
span.into()
);
}
+
+ #[cfg(feature = "miette-error")]
+ #[test]
+ fn miette_error() {
+ let input = "abc\ndef";
+ let pos = Position::new(input, 4).unwrap();
+ let error: Error = Error::new_from_pos(
+ ErrorVariant::ParsingError {
+ positives: vec![1, 2, 3],
+ negatives: vec![4, 5, 6],
+ },
+ pos,
+ );
+
+ let miette_error = miette::Error::new(error.into_miette());
+
+ assert_eq!(
+ format!("{:?}", miette_error),
+ vec![
+ " \u{1b}[31m×\u{1b}[0m Failure to parse at (2, 1)",
+ " ╭────",
+ " \u{1b}[2m1\u{1b}[0m │ def",
+ " · \u{1b}[35;1m┬\u{1b}[0m",
+ " · \u{1b}[35;1m╰── \u{1b}[35;1munexpected 4, 5, or 6; expected 1, 2, or 3\u{1b}[0m\u{1b}[0m",
+ " ╰────",
+ "\u{1b}[36m help: \u{1b}[0munexpected 4, 5, or 6; expected 1, 2, or 3\n"
+ ]
+ .join("\n")
+ );
+ }
}