simple errors for lang-dev
-rw-r--r--.github/example.pngbin0 -> 5230 bytes
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml15
-rw-r--r--README.md22
-rw-r--r--src/lib.rs159
6 files changed, 213 insertions, 0 deletions
diff --git a/.github/example.png b/.github/example.png
new file mode 100644
index 0000000..5ec619f
--- /dev/null
+++ b/.github/example.png
Binary files differ
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
+
+![example error](https://raw.githubusercontent.com/bend-n/lerr/master/.github/example.png)
+
+## 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, "{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");
+}