use std::fmt;
use thiserror::Error;
use crate::diagnostic::{Diagnostic, DiagnosticKind};
use crate::fix_hint::FixHint;
use crate::span::Span;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[error("{message}")]
pub struct RantlrError {
pub kind: ErrorKind,
pub message: String,
pub line: usize,
pub column: usize,
pub snippet: String,
pub help: Option<String>,
pub code: Option<String>,
pub fix_hint: Option<FixHint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
Lex,
Parse,
Lint,
}
impl RantlrError {
pub fn from_diagnostic(source: &str, diag: Diagnostic) -> Self {
let kind = match diag.kind {
DiagnosticKind::LexError => ErrorKind::Lex,
DiagnosticKind::ParseError => ErrorKind::Parse,
DiagnosticKind::Lint => ErrorKind::Lint,
};
let (line, column) = diag.span.line_col(source);
let snippet = context_snippet(source, diag.span);
Self {
kind,
message: diag.message,
line,
column,
snippet,
help: diag.help,
code: diag.code,
fix_hint: diag.fix,
}
}
pub fn parse(source: &str, message: impl Into<String>, span: Span) -> Self {
Self::from_diagnostic(
source,
Diagnostic::error(DiagnosticKind::ParseError, message, span),
)
}
pub fn parse_with_help(
source: &str,
message: impl Into<String>,
span: Span,
help: impl Into<String>,
) -> Self {
Self::from_diagnostic(
source,
Diagnostic::error(DiagnosticKind::ParseError, message, span).with_help(help),
)
}
pub fn format(&self, file: &str) -> String {
let mut out = format!(
"{file}:{}:{}: {:?}: {}\n{}",
self.line, self.column, self.kind, self.message, self.snippet
);
if let Some(help) = &self.help {
out.push_str("\n help: ");
out.push_str(help);
}
out
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Lex => write!(f, "Lex"),
Self::Parse => write!(f, "Parse"),
Self::Lint => write!(f, "Lint"),
}
}
}
pub fn context_snippet(source: &str, span: Span) -> String {
let (line_no, col) = span.line_col(source);
let line_text = source
.lines()
.nth(line_no.saturating_sub(1))
.unwrap_or("");
let gutter = format!("{line_no}");
let pad = " ".repeat(gutter.len());
let caret_col = col.saturating_sub(1);
let caret = format!("{}{}", " ".repeat(caret_col), "^");
format!(" {gutter} | {line_text}\n {pad} | {caret}")
}