summaryrefslogtreecommitdiff
path: root/oryxc/src/errors.rs
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2026-03-02 16:27:17 +0100
committerThomas Voss <mail@thomasvoss.com> 2026-03-02 18:37:57 +0100
commitf09e816aee0513031656734cc3cded9827e0b22b (patch)
tree06edab862eceb829dd235c1f39265f26b15ed386 /oryxc/src/errors.rs
parent048a4e588c01f65c94d7a6d93c555ca11e0230ae (diff)
Significantly improve lexer error reporting
Diffstat (limited to 'oryxc/src/errors.rs')
-rw-r--r--oryxc/src/errors.rs122
1 files changed, 121 insertions, 1 deletions
diff --git a/oryxc/src/errors.rs b/oryxc/src/errors.rs
index b3e6013..b9b5955 100644
--- a/oryxc/src/errors.rs
+++ b/oryxc/src/errors.rs
@@ -1,8 +1,15 @@
+use std::borrow::Cow;
+use std::convert::AsRef;
+use std::error::Error;
use std::ffi::{
OsStr,
OsString,
};
-use std::fmt::Display;
+use std::fmt::{
+ self,
+ Display,
+ Formatter,
+};
use std::ops::Deref;
use std::path::Path;
use std::sync::OnceLock;
@@ -11,6 +18,13 @@ use std::{
process,
};
+use unicode_width::UnicodeWidthStr;
+
+use crate::unicode;
+
+const TAB_AS_SPACES: &'static str = " ";
+const TABSIZE: usize = TAB_AS_SPACES.len();
+
pub fn progname() -> &'static OsString {
static ARGV0: OnceLock<OsString> = OnceLock::new();
return ARGV0.get_or_init(|| {
@@ -65,3 +79,109 @@ where
eprintln!("{}: \x1b[31;1mError:\x1b[0m {}", filename.display(), s);
process::exit(1);
}
+
+#[derive(Debug)]
+pub struct OryxError {
+ pub span: (usize, usize),
+ pub msg: Cow<'static, str>,
+}
+
+impl OryxError {
+ pub fn new<T>(beg: usize, end: usize, msg: T) -> Self
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ return Self {
+ span: (beg, end),
+ msg: msg.into(),
+ };
+ }
+
+ pub fn report<Tf, Tb>(&self, filename: &Tf, buffer: &Tb)
+ where
+ Tf: AsRef<OsStr>,
+ Tb: AsRef<str>,
+ {
+ fn nspaces(n: i32) -> i32 {
+ return match () {
+ () if n < 10000 => 6,
+ () if n < 100000 => 7,
+ () if n < 1000000 => 8,
+ () if n < 10000000 => 9,
+ () if n < 100000000 => 10,
+ () if n < 1000000000 => 11,
+ () => 12,
+ };
+ }
+
+ let buffer = buffer.as_ref();
+ let (mut line, mut linebeg, mut lineend) = (1, 0, buffer.len());
+ for (i, c) in buffer.char_indices() {
+ if unicode::line_terminator_p(c) {
+ if i >= self.span.0 {
+ lineend = i;
+ break;
+ }
+ line += 1;
+ linebeg = i + c.len_utf8();
+ }
+ }
+
+ let (spanbeg, spanend) = (self.span.0, self.span.1.min(lineend));
+
+ let errbeg = new_string_with_spaces(&buffer[linebeg..spanbeg]);
+ let errmid = new_string_with_spaces(&buffer[spanbeg..spanend]);
+ let errend = new_string_with_spaces(&buffer[spanend..lineend]);
+
+ let errmid = match errmid.len() {
+ 0 => "_".to_string(),
+ _ => errmid,
+ };
+
+ /* TODO: Do tab math */
+ let col = errbeg.width() + 1;
+
+ const FNAMEBEG: &str = "\x1b[37;1m";
+ const ERRORBEG: &str = "\x1b[31;1m";
+ const FMTEND: &str = "\x1b[0m";
+
+ eprintln!(
+ "{FNAMEBEG}{}:{line}:{col}:{FMTEND} {ERRORBEG}error:{FMTEND} {self}",
+ filename.as_ref().display()
+ );
+ eprintln!(" {line:>4} │ {errbeg}{ERRORBEG}{errmid}{FMTEND}{errend}");
+ for _ in 0..nspaces(line) {
+ eprint!(" ");
+ }
+ eprint!("│ ");
+ for _ in 1..col {
+ eprint!(" ");
+ }
+ eprint!("{ERRORBEG}");
+ for _ in 0..errmid.width().max(1) {
+ eprint!("^");
+ }
+ eprint!("{FMTEND}");
+ eprintln!();
+ }
+}
+
+fn new_string_with_spaces(s: &str) -> String {
+ let ntabs = s.bytes().filter(|b| *b == b'\t').count();
+ let mut buf = String::with_capacity(s.len() + ntabs * (TABSIZE - 1));
+ for c in s.chars() {
+ match c {
+ '\t' => buf.push_str(TAB_AS_SPACES),
+ _ => buf.push(c),
+ }
+ }
+ return buf;
+}
+
+impl Display for OryxError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ return write!(f, "{}", self.msg);
+ }
+}
+
+impl Error for OryxError {}