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}
47
48impl ParseError {
49 pub fn new(message: impl Into<String>) -> Self {
50 Self {
51 message: message.into(),
52 offset: None,
53 line: None,
54 column: None,
55 length: None,
56 severity: None,
57 code: None,
58 expected: None,
59 found: None,
60 suggestion: None,
61 category: None,
62 }
63 }
64
65 pub fn with_offset(mut self, offset: usize) -> Self {
66 self.offset = Some(offset);
67 self
68 }
69
70 pub fn with_location(mut self, offset: usize, line: u32, column: usize) -> Self {
72 self.offset = Some(offset);
73 self.line = Some(line);
74 self.column = Some(column);
75 self
76 }
77
78 pub fn with_length(mut self, length: usize) -> Self {
79 self.length = Some(length);
80 self
81 }
82
83 pub fn with_severity(mut self, severity: DiagnosticSeverity) -> Self {
84 self.severity = Some(severity);
85 self
86 }
87
88 pub fn with_code(mut self, code: impl Into<String>) -> Self {
89 self.code = Some(code.into());
90 self
91 }
92
93 pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
94 self.expected = Some(expected.into());
95 self
96 }
97
98 pub fn with_found(mut self, found: impl Into<String>) -> Self {
99 self.found = Some(found.into());
100 self
101 }
102
103 pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
104 self.suggestion = Some(suggestion.into());
105 self
106 }
107
108 pub fn with_category(mut self, category: DiagnosticCategory) -> Self {
109 self.category = Some(category);
110 self
111 }
112
113 pub fn to_lsp_range(&self) -> Option<(u32, u32, u32, u32)> {
116 let (line, column) = (self.line?, self.column?);
117 let len = self.length.unwrap_or(1);
118 let start_line = line.saturating_sub(1);
119 let start_char = column.saturating_sub(1);
120 let end_line = start_line;
121 let end_char = start_char.saturating_add(len);
122 Some((start_line, start_char as u32, end_line, end_char as u32))
123 }
124}
125
126impl std::fmt::Display for ParseError {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 let base = self
129 .expected
130 .as_deref()
131 .map(|e| format!("expected {e}"))
132 .unwrap_or_else(|| self.message.clone());
133 let mut msg = base;
134 if let Some(ref found) = self.found {
135 if !msg.contains("(found ") {
136 msg.push_str(&format!(" (found '{found}')"));
137 }
138 }
139 if let Some(ref suggestion) = self.suggestion {
140 msg.push_str(&format!(" {suggestion}"));
141 }
142 match (self.offset, self.line, self.column) {
143 (Some(_), Some(line), Some(col)) => write!(f, "{msg} at line {line}, column {col}"),
144 (Some(off), _, _) => write!(f, "{msg} at offset {off}"),
145 _ => write!(f, "{msg}"),
146 }
147 }
148}
149
150impl std::error::Error for ParseError {}