#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticCategory {
ParseError,
UnsupportedGrammarForm,
UnresolvedSymbol,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub message: String,
pub offset: Option<usize>,
pub line: Option<u32>,
pub column: Option<usize>,
pub length: Option<usize>,
pub severity: Option<DiagnosticSeverity>,
pub code: Option<String>,
pub expected: Option<String>,
pub found: Option<String>,
pub suggestion: Option<String>,
pub category: Option<DiagnosticCategory>,
pub is_cascade: Option<bool>,
}
impl ParseError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
offset: None,
line: None,
column: None,
length: None,
severity: None,
code: None,
expected: None,
found: None,
suggestion: None,
category: None,
is_cascade: None,
}
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn with_location(mut self, offset: usize, line: u32, column: usize) -> Self {
self.offset = Some(offset);
self.line = Some(line);
self.column = Some(column);
self
}
pub fn with_length(mut self, length: usize) -> Self {
self.length = Some(length);
self
}
pub fn with_severity(mut self, severity: DiagnosticSeverity) -> Self {
self.severity = Some(severity);
self
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
self.expected = Some(expected.into());
self
}
pub fn with_found(mut self, found: impl Into<String>) -> Self {
self.found = Some(found.into());
self
}
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn with_category(mut self, category: DiagnosticCategory) -> Self {
self.category = Some(category);
self
}
pub fn with_is_cascade(mut self, is_cascade: bool) -> Self {
self.is_cascade = Some(is_cascade);
self
}
pub fn to_lsp_range(&self) -> Option<(u32, u32, u32, u32)> {
let (line, column) = (self.line?, self.column?);
let len = self.length.unwrap_or(1);
let start_line = line.saturating_sub(1);
let start_char = column.saturating_sub(1);
let end_line = start_line;
let end_char = start_char.saturating_add(len);
Some((start_line, start_char as u32, end_line, end_char as u32))
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let base = self
.expected
.as_deref()
.map(|e| format!("expected {e}"))
.unwrap_or_else(|| self.message.clone());
let mut msg = base;
if let Some(ref found) = self.found {
if !msg.contains("(found ") {
msg.push_str(&format!(" (found '{found}')"));
}
}
if let Some(ref suggestion) = self.suggestion {
msg.push_str(&format!(" {suggestion}"));
}
match (self.offset, self.line, self.column) {
(Some(_), Some(line), Some(col)) => write!(f, "{msg} at line {line}, column {col}"),
(Some(off), _, _) => write!(f, "{msg} at offset {off}"),
_ => write!(f, "{msg}"),
}
}
}
impl std::error::Error for ParseError {}