diff --git a/src/parse/errors.rs b/src/parse/errors.rs index ad7ad84..fbab4a1 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -237,6 +237,7 @@ impl ProjectError for GlobExport { pub struct LexError { pub errors: Vec>, + pub source: Rc, pub file: Rc>, } impl ProjectError for LexError { @@ -246,7 +247,11 @@ impl ProjectError for LexError { fn positions(&self, _i: &Interner) -> BoxedIter { let file = self.file.clone(); Box::new(self.errors.iter().map(move |s| ErrorPosition { - location: Location::Range { file: file.clone(), range: s.span() }, + location: Location::Range { + file: file.clone(), + range: s.span(), + source: self.source.clone(), + }, message: Some(format!("{}", s)), })) } diff --git a/src/parse/facade.rs b/src/parse/facade.rs index 444ff7f..08f03cb 100644 --- a/src/parse/facade.rs +++ b/src/parse/facade.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use chumsky::Parser; use super::context::Context; @@ -9,9 +11,11 @@ use crate::error::{ParseErrorWithTokens, ProjectError, ProjectResult}; use crate::representations::sourcefile::FileEntry; pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult> { - let lexie = lexer(ctx.clone()); - let tokens = (lexie.parse(data)) - .map_err(|errors| LexError { errors, file: ctx.file() }.rc())?; + let source = Rc::new(data.to_string()); + let lexie = lexer(ctx.clone(), source.clone()); + let tokens = (lexie.parse(data)).map_err(|errors| { + LexError { errors, file: ctx.file(), source: source.clone() }.rc() + })?; if tokens.is_empty() { Ok(Vec::new()) } else { diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 44fbfdf..f940475 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -209,6 +209,7 @@ pub static BASE_OPS: &[&str] = &[",", ".", "..", "..."]; pub fn lexer<'a>( ctx: impl Context + 'a, + source: Rc, ) -> impl SimpleParser> + 'a { let all_ops = ctx .ops() @@ -247,7 +248,11 @@ pub fn lexer<'a>( )) .map_with_span(move |lexeme, range| Entry { lexeme, - location: Location::Range { range, file: ctx.file() }, + location: Location::Range { + range, + file: ctx.file(), + source: source.clone(), + }, }) .padded_by(one_of(" \t").repeated()) .repeated() diff --git a/src/representations/location.rs b/src/representations/location.rs index 174f536..878595d 100644 --- a/src/representations/location.rs +++ b/src/representations/location.rs @@ -18,6 +18,8 @@ pub enum Location { file: Rc>, /// Index of the unicode code points associated with the code range: Range, + /// The full source code as received by the parser + source: Rc, }, } @@ -40,6 +42,15 @@ impl Location { } } + /// Associated source code, if known + pub fn source(&self) -> Option> { + if let Self::Range { source, .. } = self { + Some(source.clone()) + } else { + None + } + } + /// If the two locations are ranges in the same file, connect them. /// Otherwise choose the more accurate, preferring lhs if equal. pub fn to(self, other: Self) -> Self { @@ -49,13 +60,13 @@ impl Location { Location::Range { .. } => other, _ => Location::File(f), }, - Location::Range { file: f1, range: r1 } => Location::Range { - range: match other { - Location::Range { file: f2, range: r2 } if f1 == f2 => + Location::Range { file, range: r1, source } => { + let range = match other { + Location::Range { file: f2, range: r2, .. } if file == f2 => r1.start..r2.end, _ => r1, - }, - file: f1, + }; + Location::Range { file, source, range } }, } } @@ -65,7 +76,7 @@ impl Location { pub fn or(self, alt: Self) -> Self { match (&self, &alt) { (Self::Unknown, _) => alt, - (Self::File(_), Self::Range { .. }) => alt, + (Self::File { .. }, Self::Range { .. }) => alt, _ => self, } } @@ -76,13 +87,23 @@ impl Display for Location { match self { Self::Unknown => write!(f, "unknown"), Self::File(file) => write!(f, "{}.orc", file.iter().join("/")), - Self::Range { file, range } => write!( - f, - "{}.orc:{}..{}", - file.iter().join("/"), - range.start, - range.end - ), + Self::Range { file, range, source } => { + let (sl, sc) = pos2lc(source, range.start); + let (el, ec) = pos2lc(source, range.end); + write!(f, "{}.orc ", file.iter().join("/"))?; + write!(f, "{sl}:{sc}")?; + if el == sl { + if sc + 1 == ec { Ok(()) } else { write!(f, "..{ec}") } + } else { + write!(f, "..{el}:{ec}") + } + }, } } } + +fn pos2lc(s: &str, i: usize) -> (usize, usize) { + s.chars().take(i).fold((1, 1), |(line, col), char| { + if char == '\n' { (line + 1, 1) } else { (line, col + 1) } + }) +}