Skip to main content

lex_core/lex/ast/
error.rs

1//! Error types for AST operations
2
3use crate::lex::ast::range::Range;
4use std::fmt;
5
6#[cfg(test)]
7use crate::lex::ast::range::Position;
8
9/// Errors that can occur during AST position lookup operations
10#[derive(Debug, Clone)]
11pub enum PositionLookupError {
12    /// Invalid position format string
13    InvalidPositionFormat(String),
14    /// Element not found at the specified position
15    NotFound { line: usize, column: usize },
16}
17
18impl fmt::Display for PositionLookupError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            PositionLookupError::InvalidPositionFormat(msg) => {
22                write!(f, "Invalid position format: {msg}")
23            }
24            PositionLookupError::NotFound { line, column } => {
25                write!(f, "No element found at position {line}:{column}")
26            }
27        }
28    }
29}
30
31impl std::error::Error for PositionLookupError {}
32
33/// Errors that can occur during parsing and AST construction
34#[derive(Debug, Clone)]
35pub enum ParserError {
36    /// Invalid nesting of elements (e.g., Session inside Definition)
37    InvalidNesting {
38        container: String,
39        invalid_child: String,
40        invalid_child_text: String,
41        location: Range,
42        source_context: String,
43    },
44}
45
46impl fmt::Display for ParserError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            ParserError::InvalidNesting {
50                container,
51                invalid_child,
52                invalid_child_text,
53                location: _,
54                source_context,
55            } => {
56                writeln!(f, "Error: Invalid nesting in {container}")?;
57                writeln!(f)?;
58                writeln!(f, "{container} cannot contain {invalid_child} elements")?;
59                writeln!(f)?;
60                write!(f, "{source_context}")?;
61                writeln!(f)?;
62                writeln!(
63                    f,
64                    "The parser identified a {} (\"{}\") inside a {}, which violates lex grammar rules.",
65                    invalid_child, invalid_child_text.trim(), container
66                )
67            }
68        }
69    }
70}
71
72impl std::error::Error for ParserError {}
73
74/// Type alias for parser results with boxed errors (reduces stack size)
75pub type ParserResult<T> = Result<T, Box<ParserError>>;
76
77/// Format source code context around an error location
78///
79/// Shows 2 lines before the error, the error line with >> marker, and 2 lines after.
80/// All lines are numbered for easy reference.
81pub fn format_source_context(source: &str, range: &Range) -> String {
82    let lines: Vec<&str> = source.lines().collect();
83    let error_line = range.start.line;
84
85    let start_line = error_line.saturating_sub(2);
86    let end_line = (error_line + 3).min(lines.len());
87
88    let mut context = String::new();
89
90    for line_num in start_line..end_line {
91        let marker = if line_num == error_line { ">>" } else { "  " };
92        let display_line_num = line_num + 1; // 1-indexed for display
93
94        if line_num < lines.len() {
95            context.push_str(&format!(
96                "{} {:3} | {}\n",
97                marker, display_line_num, lines[line_num]
98            ));
99        }
100    }
101
102    context
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_format_source_context() {
111        let source = "line 1\nline 2\nline 3\nerror line\nline 5\nline 6\nline 7";
112        let range = Range::new(20..30, Position::new(3, 0), Position::new(3, 10));
113
114        let context = format_source_context(source, &range);
115
116        // Should show lines 2-6 (0-indexed: 1-5)
117        assert!(context.contains("line 2"));
118        assert!(context.contains(">> "));
119        assert!(context.contains("error line"));
120        assert!(context.contains("line 5"));
121    }
122}