1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DiagnosticSeverity {
9 Error,
10 Warning,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum DiagnosticCategory {
16 ParseError,
17 UnsupportedGrammarForm,
18 UnresolvedSymbol,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ParseError {
24 pub message: String,
26 pub offset: Option<usize>,
28 pub line: Option<u32>,
30 pub column: Option<usize>,
32 pub length: Option<usize>,
34 pub severity: Option<DiagnosticSeverity>,
36 pub code: Option<String>,
38 pub expected: Option<String>,
40 pub found: Option<String>,
42 pub suggestion: Option<String>,
44 pub category: Option<DiagnosticCategory>,
46 pub is_cascade: Option<bool>,
48}
49
50impl ParseError {
51 pub fn new(message: impl Into<String>) -> Self {
52 Self {
53 message: message.into(),
54 offset: None,
55 line: None,
56 column: None,
57 length: None,
58 severity: None,
59 code: None,
60 expected: None,
61 found: None,
62 suggestion: None,
63 category: None,
64 is_cascade: None,
65 }
66 }
67
68 pub fn with_offset(mut self, offset: usize) -> Self {
69 self.offset = Some(offset);
70 self
71 }
72
73 pub fn with_location(mut self, offset: usize, line: u32, column: usize) -> Self {
75 self.offset = Some(offset);
76 self.line = Some(line);
77 self.column = Some(column);
78 self
79 }
80
81 pub fn with_length(mut self, length: usize) -> Self {
82 self.length = Some(length);
83 self
84 }
85
86 pub fn with_severity(mut self, severity: DiagnosticSeverity) -> Self {
87 self.severity = Some(severity);
88 self
89 }
90
91 pub fn with_code(mut self, code: impl Into<String>) -> Self {
92 self.code = Some(code.into());
93 self
94 }
95
96 pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
97 self.expected = Some(expected.into());
98 self
99 }
100
101 pub fn with_found(mut self, found: impl Into<String>) -> Self {
102 self.found = Some(found.into());
103 self
104 }
105
106 pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
107 self.suggestion = Some(suggestion.into());
108 self
109 }
110
111 pub fn with_category(mut self, category: DiagnosticCategory) -> Self {
112 self.category = Some(category);
113 self
114 }
115
116 pub fn with_is_cascade(mut self, is_cascade: bool) -> Self {
117 self.is_cascade = Some(is_cascade);
118 self
119 }
120
121 pub fn to_lsp_range(&self) -> Option<(u32, u32, u32, u32)> {
124 let (line, column) = (self.line?, self.column?);
125 let len = self.length.unwrap_or(1);
126 let start_line = line.saturating_sub(1);
127 let start_char = column.saturating_sub(1);
128 let end_line = start_line;
129 let end_char = start_char.saturating_add(len);
130 Some((start_line, start_char as u32, end_line, end_char as u32))
131 }
132}
133
134impl std::fmt::Display for ParseError {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 let base = self
137 .expected
138 .as_deref()
139 .map(|e| format!("expected {e}"))
140 .unwrap_or_else(|| self.message.clone());
141 let mut msg = base;
142 if let Some(ref found) = self.found {
143 if !msg.contains("(found ") {
144 msg.push_str(&format!(" (found '{found}')"));
145 }
146 }
147 if let Some(ref suggestion) = self.suggestion {
148 msg.push_str(&format!(" {suggestion}"));
149 }
150 match (self.offset, self.line, self.column) {
151 (Some(_), Some(line), Some(col)) => write!(f, "{msg} at line {line}, column {col}"),
152 (Some(off), _, _) => write!(f, "{msg} at offset {off}"),
153 _ => write!(f, "{msg}"),
154 }
155 }
156}
157
158impl std::error::Error for ParseError {}