Skip to main content

telltale_language/compiler/parser/
error.rs

1//! Error types for choreography parsing.
2//!
3//! This module provides structured error types with span information
4//! for helpful error messages during parsing.
5
6use thiserror::Error;
7
8/// Span information for error reporting
9#[derive(Debug, Clone)]
10pub struct ErrorSpan {
11    pub line: usize,
12    pub column: usize,
13    pub line_end: usize,
14    pub column_end: usize,
15    pub snippet: String,
16}
17
18impl ErrorSpan {
19    /// Create an `ErrorSpan` from a Pest span
20    pub fn from_pest_span(span: pest::Span, input: &str) -> Self {
21        let (line, column) = span.start_pos().line_col();
22        let (line_end, column_end) = span.end_pos().line_col();
23
24        // Extract the line containing the error
25        let snippet = input.lines().nth(line - 1).unwrap_or("").to_string();
26
27        Self {
28            line,
29            column,
30            line_end,
31            column_end,
32            snippet,
33        }
34    }
35
36    /// Create an `ErrorSpan` from a line/column pair.
37    pub fn from_line_col(line: usize, column: usize, input: &str) -> Self {
38        let snippet = input
39            .lines()
40            .nth(line.saturating_sub(1))
41            .unwrap_or("")
42            .to_string();
43        Self {
44            line,
45            column,
46            line_end: line,
47            column_end: column + 1,
48            snippet,
49        }
50    }
51
52    /// Format the error with context
53    #[must_use]
54    pub fn format_error(&self, message: &str) -> String {
55        let line_num_width = self.line.to_string().len().max(3);
56        let mut output = String::new();
57
58        // Error message
59        output.push_str(&format!("\n{message}\n"));
60
61        // Location
62        output.push_str(&format!(
63            "  {} {}:{}:{}\n",
64            "-->", "input", self.line, self.column
65        ));
66
67        // Empty line
68        output.push_str(&format!("{:width$} |\n", " ", width = line_num_width));
69
70        // Source line with line number
71        output.push_str(&format!(
72            "{:>width$} | {}\n",
73            self.line,
74            self.snippet,
75            width = line_num_width
76        ));
77
78        // Underline indicator
79        let spaces = " ".repeat(line_num_width + 3 + self.column - 1);
80        let underline_len = if self.line == self.line_end {
81            (self.column_end - self.column).max(1)
82        } else {
83            self.snippet.len() - self.column + 1
84        };
85        let underline = "^".repeat(underline_len);
86        output.push_str(&format!("{spaces}{underline}\n"));
87
88        output
89    }
90}
91
92use super::Rule;
93
94/// Parse errors that can occur during choreography parsing
95#[derive(Error, Debug)]
96pub enum ParseError {
97    #[error("{}", format_pest_error(.0))]
98    Pest(#[from] Box<pest::error::Error<Rule>>),
99
100    #[error("{}", .span.format_error(&format!("Layout error: {}", .message)))]
101    Layout { span: ErrorSpan, message: String },
102
103    #[error("{}", .span.format_error(&format!("Syntax error: {}", .message)))]
104    Syntax { span: ErrorSpan, message: String },
105
106    #[error("{}", .span.format_error(&format!("Undefined role '{}'", .role)))]
107    UndefinedRole { role: String, span: ErrorSpan },
108
109    #[error("{}", .span.format_error(&format!("Duplicate role declaration '{}'", .role)))]
110    DuplicateRole { role: String, span: ErrorSpan },
111
112    #[error("Empty choreography: no statements found")]
113    EmptyChoreography,
114
115    #[error("{}", .span.format_error(&format!("Invalid message format: {}", .message)))]
116    InvalidMessage { message: String, span: ErrorSpan },
117
118    #[error("{}", .span.format_error(&format!("Invalid condition: {}", .message)))]
119    InvalidCondition { message: String, span: ErrorSpan },
120
121    #[error("{}", .span.format_error(&format!("Undefined protocol '{}'", .protocol)))]
122    UndefinedProtocol { protocol: String, span: ErrorSpan },
123
124    #[error("{}", .span.format_error(&format!("Duplicate protocol definition '{}'", .protocol)))]
125    DuplicateProtocol { protocol: String, span: ErrorSpan },
126
127    #[error("Grammar composition failed: {0}")]
128    GrammarComposition(#[from] crate::compiler::grammar::GrammarCompositionError),
129}
130
131/// Format Pest errors nicely
132fn format_pest_error(err: &pest::error::Error<Rule>) -> String {
133    format!("\nParse error:\n{err}")
134}