simple errors for lang-dev
init
| -rw-r--r-- | .github/example.png | bin | 0 -> 5230 bytes | |||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 16 | ||||
| -rw-r--r-- | Cargo.toml | 15 | ||||
| -rw-r--r-- | README.md | 22 | ||||
| -rw-r--r-- | src/lib.rs | 159 |
6 files changed, 213 insertions, 0 deletions
diff --git a/.github/example.png b/.github/example.png Binary files differnew file mode 100644 index 0000000..5ec619f --- /dev/null +++ b/.github/example.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4678cd9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "lerr" +version = "0.1.0" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b728082 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lerr" +version = "0.1.0" +edition = "2021" +license = "MIT" +readme = "README.md" +repository = "https://github.com/bend-n/lerr" +authors = ["bend-n <[email protected]>"] +description = "simple errors for lang-dev" +keywords = ["error", "lang-dev", "diagnostics"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +unicode-width = "0.1.10" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ca156e --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# lerr + +extremely barebones error diagnostics for lang-dev + + + +## usage + +heres the code for the sample above. +feel free to add coloring with your favorite coloring crate, or just use raw ansi sequences. + +```rust +use lerr::Error; +let out = Error::new("Strin::new()") + .label((0..5, "a 'strin' you say")) + .note("i think you meant String") + .to_string(); +println!("{out}"); +``` + +Please note that only one label per line is currently supported, and multiline labels are not yet supported. +If that doesnt work for you, use something like [ariadne](https://crates.io/crates/ariadne).
\ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e76a2d1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,159 @@ +#![doc = include_str!("../README.md")] +#![warn( + clippy::multiple_unsafe_ops_per_block, + clippy::missing_const_for_fn, + clippy::redundant_pub_crate, + clippy::missing_safety_doc, + clippy::imprecise_flops, + unsafe_op_in_unsafe_fn, + clippy::dbg_macro, + missing_docs +)] +use std::ops::Range; + +use unicode_width::UnicodeWidthStr; + +/// Span of bytes in the source +pub type Span = Range<usize>; +/// Label around a [`Span`] +#[derive(Debug)] +pub struct Label { + /// The span that this label will draw at + pub span: Span, + /// The message this label will draw with + pub message: String, +} + +impl<S: ToString> From<(Span, S)> for Label { + fn from((span, m): (Span, S)) -> Self { + Self { + span, + message: m.to_string(), + } + } +} + +/// A note at the end of the diagnostic +#[derive(Debug)] +pub struct Note { + /// The note + pub message: String, +} + +/// The source text that the spans "reference" +#[derive(Debug)] +pub struct Source<'s>(&'s str); + +impl<'s> Source<'s> { + fn spans(&self) -> impl Iterator<Item = (&'s str, Span)> { + self.0.split_inclusive('\n').scan(0, |s, x| { + let pos = *s; + *s += x.as_bytes().len(); + Some((x.trim_matches('\n'), pos..pos + *s)) + }) + } +} + +/// The error builder that this crate is all about +#[derive(Debug)] +pub struct Error<'s> { + /// Source text + pub source: Source<'s>, + /// Labels we hold + pub labels: Vec<Label>, + /// Notes + pub notes: Vec<Note>, + /// The message + pub message: String, +} + +impl<'s> Error<'s> { + /// Create a new error with source code attached + pub fn new(source: &'s str) -> Self { + Self { + labels: vec![], + source: Source(source), + notes: vec![], + message: String::new(), + } + } + + /// Add a message to this error + pub fn message(&mut self, message: impl ToString) -> &mut Self { + self.message = message.to_string(); + self + } + + /// Add a label to this error + pub fn label(&mut self, label: impl Into<Label>) -> &mut Self { + self.labels.push(label.into()); + self + } + + /// Note something down + pub fn note(&mut self, note: impl ToString) -> &mut Self { + self.notes.push(Note { + message: note.to_string(), + }); + self + } +} + +impl<'s> std::fmt::Display for Error<'s> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.message)?; + let lines = self.source.0.lines().count(); + if lines == 0 { + return Ok(()); + } + let width = lines.ilog10() as usize + 1; + let space = " "; + let mut found = vec![]; + for (line, (code, span)) in self.source.spans().enumerate() { + for label in &self.labels { + if span.contains(&label.span.start) { + found.push(label); + } + } + if found.len() > 1 { + todo!("only one error per line supported"); + } + let Some(&label) = found.first() else { + continue; + }; + writeln!(f, "[1;34;30m{line:width$} | \x1b[0m{code}")?; + let about = UnicodeWidthStr::width( + &self.source.0[label.span.start - span.start..label.span.end - span.start], + ); + let padding = dbg!(UnicodeWidthStr::width( + &self.source.0[span.start..label.span.start] + )); + write!(f, "\x1b[1;34;30m{space:width$} ¦ \x1b[0m",)?; + for _ in 0..padding { + write!(f, " ")?; + } + write!(f, "\x1b[1;34;31m")?; + for _ in 0..about { + write!(f, "^")?; + } + write!(f, "\x1b[0m ")?; + writeln!(f, "{}", label.message)?; + found.clear(); + } + for note in &self.notes { + writeln!(f, "{space:width$} \x1b[1;34;30m>\x1b[0m {}", note.message)?; + } + Ok(()) + } +} + +#[test] +fn display() { + let out = Error::new("void fn x(void) -> four {\nwierd};") + .label((19..23, "what is 'four'?")) + .note("\x1b[1;34;32mhelp\x1b[0m: change it to 4") + .note("\x1b[1;34;34mnote\x1b[0m: maybe python would be better for you") + .to_string(); + println!("{out}"); + assert_eq!(out, "\n\u{1b}[1;34;30m0 | \u{1b}[0mvoid fn x(void) -> four {\n\u{1b}[1;34;30m ¦ \u{1b}[0m \u{1b}[1;34;31m^^^^\u{1b}[0m what is 'four'?\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;32mhelp\u{1b}[0m: change it to 4\n \u{1b}[1;34;30m>\u{1b}[0m \u{1b}[1;34;34mnote\u{1b}[0m: maybe python would be better for you\n"); +} |