1use thiserror::Error;
5
6pub mod pretty;
7pub mod reporter;
8pub mod suggestions;
9
10pub type Result<T> = std::result::Result<T, CompileError>;
11
12#[derive(Error, Debug)]
13pub enum CompileError {
14 #[error("Unexpected character '{ch}' at line {line}, column {col}")]
16 UnexpectedChar {
17 ch: char,
18 line: usize,
19 col: usize,
20 span: Option<Span>,
21 },
22
23 #[error("Unterminated string literal at line {line}")]
24 UnterminatedString { line: usize, span: Option<Span> },
25
26 #[error("Unexpected token: expected {expected}, found {found}")]
28 UnexpectedToken {
29 expected: String,
30 found: String,
31 span: Option<Span>,
32 },
33
34 #[error("Syntax error: {message}")]
35 SyntaxError { message: String, span: Option<Span> },
36
37 #[error("Type mismatch: expected {expected}, found {found}")]
39 TypeMismatch {
40 expected: String,
41 found: String,
42 span: Option<Span>,
43 },
44
45 #[error("Undefined variable: {name}")]
46 UndefinedVariable { name: String, span: Option<Span> },
47
48 #[error("Undefined function: {name}")]
49 UndefinedFunction { name: String, span: Option<Span> },
50
51 #[error("Function {name} expects {expected} arguments, but {found} were provided")]
52 ArgumentCountMismatch {
53 name: String,
54 expected: usize,
55 found: usize,
56 span: Option<Span>,
57 },
58
59 #[error("Code generation failed: {message}")]
61 CodegenError { message: String },
62
63 #[error("IO error: {0}")]
65 IoError(#[from] std::io::Error),
66
67 #[error("{0}")]
69 Generic(String),
70
71 #[error("Missing semicolon after statement")]
73 MissingSemicolon { span: Option<Span> },
74
75 #[error("Invalid function signature")]
77 InvalidFunctionSignature { message: String, span: Option<Span> },
78
79 #[error("Borrow checker error: {message}")]
81 BorrowChecker { message: String, span: Option<Span> },
82
83 #[error("Use of moved value: {name}")]
84 UseOfMovedValue { name: String, span: Option<Span> },
85
86 #[error("Use of uninitialized value: {name}")]
87 UseOfUninitializedValue { name: String, span: Option<Span> },
88
89 #[error("Cannot move out of borrowed content")]
90 CannotMoveOutOfBorrowedContent { span: Option<Span> },
91
92 #[error("Unsafe operation '{operation}' requires unsafe block")]
94 UnsafeOperation { operation: String, span: Span },
95
96 #[error("Conflicting borrows: {message}")]
97 ConflictingBorrows { message: String, span: Option<Span> },
98
99 #[error("Lifetime error: {message}")]
100 LifetimeError { message: String, span: Option<Span> },
101
102 #[error("Non-exhaustive match: missing patterns {}", missing_patterns.join(", "))]
104 NonExhaustiveMatch {
105 missing_patterns: Vec<String>,
106 span: Option<Span>,
107 },
108
109 #[error("Unreachable pattern: {}", patterns.join(", "))]
110 UnreachablePattern {
111 patterns: Vec<String>,
112 span: Option<Span>,
113 },
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct Span {
119 pub start: usize,
120 pub end: usize,
121 pub line: usize,
122 pub column: usize,
123}
124
125impl Span {
126 pub fn new(start: usize, end: usize, line: usize, column: usize) -> Self {
127 Self {
128 start,
129 end,
130 line,
131 column,
132 }
133 }
134
135 pub fn dummy() -> Self {
136 Self {
137 start: 0,
138 end: 0,
139 line: 0,
140 column: 0,
141 }
142 }
143
144 pub fn extend_to(&self, other: &Span) -> Self {
145 Self {
146 start: self.start.min(other.start),
147 end: self.end.max(other.end),
148 line: self.line.min(other.line),
149 column: if self.line < other.line {
150 self.column
151 } else {
152 self.column.min(other.column)
153 },
154 }
155 }
156}
157
158impl CompileError {
159 pub fn to_diagnostic(&self) -> Diagnostic {
161 match self {
162 CompileError::UnexpectedChar {
163 ch,
164 line,
165 col,
166 span,
167 } => Diagnostic::error(format!(
168 "Unexpected character '{}' at line {}, column {}",
169 ch, line, col
170 ))
171 .with_span(span.unwrap_or(Span::new(0, 1, *line, *col)))
172 .with_note("Palladium only allows ASCII letters, numbers, and common symbols")
173 .with_suggestion("Remove or replace this character with a valid one", None),
174
175 CompileError::UnterminatedString { line, span } => {
176 Diagnostic::error(format!("Unterminated string literal at line {}", line))
177 .with_span(span.unwrap_or(Span::new(0, 0, *line, 1)))
178 .with_note("Strings must be closed with a matching quote")
179 .with_suggestion(
180 "Add a closing quote (\") to end the string",
181 Some("\"".to_string()),
182 )
183 }
184
185 CompileError::UnexpectedToken {
186 expected,
187 found,
188 span,
189 } => Diagnostic::error(format!("Expected {}, but found {}", expected, found))
190 .with_span(span.unwrap_or(Span::dummy()))
191 .with_note("The syntax requires a specific token here")
192 .with_suggestion(
193 format!("Replace '{}' with '{}'", found, expected),
194 Some(expected.clone()),
195 ),
196
197 CompileError::SyntaxError { message, span } => Diagnostic::error(message.clone())
198 .with_span(span.unwrap_or(Span::dummy()))
199 .with_note("Check the language syntax rules"),
200
201 CompileError::TypeMismatch {
202 expected,
203 found,
204 span,
205 } => {
206 let mut diag = Diagnostic::error(format!(
207 "Type mismatch: expected {}, found {}",
208 expected, found
209 ))
210 .with_span(span.unwrap_or(Span::dummy()))
211 .with_note("Types must match exactly in Palladium");
212
213 match (expected.as_str(), found.as_str()) {
215 ("int", "string") => {
216 diag = diag.with_suggestion(
217 "Convert the string to an integer using parse_int()",
218 None,
219 );
220 }
221 ("string", "int") => {
222 diag = diag.with_suggestion(
223 "Convert the integer to a string using to_string()",
224 None,
225 );
226 }
227 ("bool", _) => {
228 diag =
229 diag.with_suggestion("Use 'true' or 'false' for boolean values", None);
230 }
231 _ => {}
232 }
233
234 diag
235 }
236
237 CompileError::UndefinedVariable { name, span } => {
238 Diagnostic::error(format!("Undefined variable: {}", name))
239 .with_span(span.unwrap_or(Span::dummy()))
240 .with_note("Variables must be declared before use")
241 .with_suggestion(
242 format!("Did you mean to declare it? Try: let {} = ...;", name),
243 None,
244 )
245 .with_context_lines(3)
246 }
247
248 CompileError::UndefinedFunction { name, span } => {
249 let mut diag = Diagnostic::error(format!("Undefined function: {}", name))
250 .with_span(span.unwrap_or(Span::dummy()))
251 .with_note("Functions must be defined before they are called");
252
253 match name.as_str() {
255 "print" => {
256 diag = diag.with_suggestion(
257 "Did you mean 'println'? The print function is called 'println' in Palladium",
258 Some("println".to_string())
259 );
260 }
261 "printf" => {
262 diag = diag.with_suggestion(
263 "Palladium uses 'println' instead of 'printf'",
264 Some("println".to_string()),
265 );
266 }
267 _ => {
268 diag = diag.with_suggestion(
269 format!("Define the function first: fn {}() {{ ... }}", name),
270 None,
271 );
272 }
273 }
274
275 diag
276 }
277
278 CompileError::ArgumentCountMismatch {
279 name,
280 expected,
281 found,
282 span,
283 } => {
284 let mut diag = Diagnostic::error(format!(
285 "Function '{}' expects {} argument{}, but {} {} provided",
286 name,
287 expected,
288 if *expected == 1 { "" } else { "s" },
289 found,
290 if *found == 1 { "was" } else { "were" }
291 ))
292 .with_span(span.unwrap_or(Span::dummy()));
293
294 if *found < *expected {
295 diag = diag.with_suggestion(
296 format!(
297 "Add {} more argument{}",
298 expected - found,
299 if expected - found == 1 { "" } else { "s" }
300 ),
301 None,
302 );
303 } else {
304 diag = diag.with_suggestion(
305 format!(
306 "Remove {} argument{}",
307 found - expected,
308 if found - expected == 1 { "" } else { "s" }
309 ),
310 None,
311 );
312 }
313
314 diag
315 }
316
317 CompileError::MissingSemicolon { span } => {
318 Diagnostic::error("Missing semicolon after statement")
319 .with_span(span.unwrap_or(Span::dummy()))
320 .with_note("Every statement in Palladium must end with a semicolon")
321 .with_suggestion(
322 "Add a semicolon (;) at the end of this line",
323 Some(";".to_string()),
324 )
325 }
326
327 CompileError::InvalidFunctionSignature { message, span } => Diagnostic::error(format!(
328 "Invalid function signature: {}",
329 message
330 ))
331 .with_span(span.unwrap_or(Span::dummy()))
332 .with_note(
333 "Function signatures must follow the pattern: fn name(param: Type) -> ReturnType",
334 )
335 .with_suggestion(
336 "Example: fn add(x: int, y: int) -> int { return x + y; }",
337 None,
338 ),
339
340 CompileError::NonExhaustiveMatch {
341 missing_patterns,
342 span,
343 } => {
344 let mut diag = Diagnostic::error("Non-exhaustive match expression")
345 .with_span(span.unwrap_or(Span::dummy()))
346 .with_note("All possible patterns must be covered in a match expression");
347
348 if missing_patterns.len() == 1 {
349 diag = diag.with_suggestion(
350 format!("Add a pattern for: {}", missing_patterns[0]),
351 None,
352 );
353 } else if missing_patterns.len() <= 3 {
354 diag = diag.with_suggestion(
355 format!("Add patterns for: {}", missing_patterns.join(", ")),
356 None,
357 );
358 } else {
359 diag = diag.with_suggestion(
360 "Add remaining patterns or use a wildcard pattern (_) to match all other cases",
361 None
362 );
363 }
364
365 diag
366 }
367
368 CompileError::UnreachablePattern { patterns: _, span } => Diagnostic::error(
369 "Unreachable pattern detected",
370 )
371 .with_span(span.unwrap_or(Span::dummy()))
372 .with_note(
373 "This pattern can never be matched because previous patterns cover all cases",
374 )
375 .with_suggestion("Remove this pattern or reorder the patterns", None),
376
377 _ => {
378 Diagnostic::error(self.to_string())
380 }
381 }
382 }
383}
384
385#[derive(Debug)]
387pub struct Diagnostic {
388 pub level: DiagnosticLevel,
389 pub message: String,
390 pub span: Option<Span>,
391 pub notes: Vec<String>,
392 pub suggestions: Vec<Suggestion>,
393 pub context_lines: usize, }
395
396#[derive(Debug)]
398pub struct Suggestion {
399 pub message: String,
400 pub replacement: Option<String>,
401 pub span: Option<Span>,
402}
403
404#[derive(Debug, Clone, Copy)]
405pub enum DiagnosticLevel {
406 Error,
407 Warning,
408 Info,
409 Help,
410}
411
412impl Diagnostic {
413 pub fn error(message: impl Into<String>) -> Self {
414 Self {
415 level: DiagnosticLevel::Error,
416 message: message.into(),
417 span: None,
418 notes: Vec::new(),
419 suggestions: Vec::new(),
420 context_lines: 2,
421 }
422 }
423
424 pub fn with_span(mut self, span: Span) -> Self {
425 self.span = Some(span);
426 self
427 }
428
429 pub fn with_note(mut self, note: impl Into<String>) -> Self {
430 self.notes.push(note.into());
431 self
432 }
433
434 pub fn with_suggestion(
435 mut self,
436 message: impl Into<String>,
437 replacement: Option<String>,
438 ) -> Self {
439 self.suggestions.push(Suggestion {
440 message: message.into(),
441 replacement,
442 span: self.span,
443 });
444 self
445 }
446
447 pub fn with_context_lines(mut self, lines: usize) -> Self {
448 self.context_lines = lines;
449 self
450 }
451}