#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Span { start, end }
}
pub fn to(self, other: Span) -> Span {
Span::new(self.start, other.end)
}
}
#[derive(Debug, Clone)]
pub struct LuxError {
pub message: String,
pub span: Span,
pub note: Option<String>,
pub learn: Option<(&'static str, &'static str)>,
}
impl LuxError {
pub fn new(message: impl Into<String>, span: Span) -> Self {
LuxError {
message: message.into(),
span,
note: None,
learn: None,
}
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
pub fn with_learn(mut self, topic: &'static str, lure: &'static str) -> Self {
self.learn = Some((topic, lure));
self
}
}
pub fn report(filename: &str, source: &str, err: &LuxError) {
let start = err.span.start.min(source.len());
let line_start = source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
let line_end = source[start..]
.find('\n')
.map(|i| start + i)
.unwrap_or(source.len());
let line_no = source[..line_start].bytes().filter(|&b| b == b'\n').count() + 1;
let col = start - line_start + 1;
let line_text = &source[line_start..line_end];
let caret_end = err.span.end.min(line_end);
let caret_len = caret_end.saturating_sub(start).max(1);
eprintln!("error: {}", err.message);
eprintln!(" --> {}:{}:{}", filename, line_no, col);
let gutter = format!(" {} | ", line_no);
let blank: String = gutter.chars().map(|_| ' ').collect();
eprintln!("{}{}", gutter, line_text);
eprintln!("{}{}{}", blank, " ".repeat(col - 1), "^".repeat(caret_len));
if let Some(note) = &err.note {
eprintln!("note: {}", note);
}
if let Some((topic, lure)) = &err.learn {
eprintln!("help: `lux learn {}` — {}", topic, lure);
}
}