simple errors for lang-dev
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 159 |
1 files changed, 159 insertions, 0 deletions
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"); +} |