1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct SourceLocation {
11 pub line: usize,
12 pub column: usize,
13 pub file: Option<String>,
14}
15
16impl SourceLocation {
17 pub fn new(line: usize, column: usize) -> Self {
18 Self {
19 line,
20 column,
21 file: None,
22 }
23 }
24
25 pub fn with_file(line: usize, column: usize, file: String) -> Self {
26 Self {
27 line,
28 column,
29 file: Some(file),
30 }
31 }
32
33 pub fn unknown() -> Self {
34 Self {
35 line: 0,
36 column: 0,
37 file: None,
38 }
39 }
40}
41
42impl fmt::Display for SourceLocation {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 if let Some(file) = &self.file {
45 write!(f, "{}:{}:{}", file, self.line, self.column)
46 } else {
47 write!(f, "{}:{}", self.line, self.column)
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub enum LexerError {
55 UnexpectedCharacter { ch: char, location: SourceLocation },
56 UnterminatedString { location: SourceLocation },
57 InvalidNumber { text: String, location: SourceLocation },
58 InvalidEscape { sequence: String, location: SourceLocation },
59}
60
61impl fmt::Display for LexerError {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 LexerError::UnexpectedCharacter { ch, location } => {
65 write!(f, "Unexpected character '{}' at {}", ch, location)
66 }
67 LexerError::UnterminatedString { location } => {
68 write!(f, "Unterminated string at {}", location)
69 }
70 LexerError::InvalidNumber { text, location } => {
71 write!(f, "Invalid number '{}' at {}", text, location)
72 }
73 LexerError::InvalidEscape { sequence, location } => {
74 write!(f, "Invalid escape sequence '{}' at {}", sequence, location)
75 }
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
82pub enum ParserError {
83 UnexpectedToken { expected: String, found: String, location: SourceLocation },
84 UnexpectedEOF { expected: String, location: SourceLocation },
85 InvalidSyntax { message: String, location: SourceLocation },
86 IndentationError { message: String, location: SourceLocation },
87 DuplicateDefinition { name: String, location: SourceLocation },
88}
89
90impl fmt::Display for ParserError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 ParserError::UnexpectedToken { expected, found, location } => {
94 write!(f, "Expected {} but found {} at {}", expected, found, location)
95 }
96 ParserError::UnexpectedEOF { expected, location } => {
97 write!(f, "Unexpected end of file, expected {} at {}", expected, location)
98 }
99 ParserError::InvalidSyntax { message, location } => {
100 write!(f, "Invalid syntax: {} at {}", message, location)
101 }
102 ParserError::IndentationError { message, location } => {
103 write!(f, "Indentation error: {} at {}", message, location)
104 }
105 ParserError::DuplicateDefinition { name, location } => {
106 write!(f, "Duplicate definition of '{}' at {}", name, location)
107 }
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
114pub enum ExecutionError {
115 UndefinedVariable { name: String, location: SourceLocation },
116 UndefinedFunction { name: String, location: SourceLocation },
117 TypeError { expected: String, found: String, location: SourceLocation },
118 DivisionByZero { location: SourceLocation },
119 InvalidOperation { operation: String, message: String, location: SourceLocation },
120 ArgumentCountMismatch { expected: usize, found: usize, location: SourceLocation },
121 StackOverflow { location: SourceLocation },
122}
123
124impl fmt::Display for ExecutionError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 ExecutionError::UndefinedVariable { name, location } => {
128 write!(f, "Undefined variable '{}' at {}", name, location)
129 }
130 ExecutionError::UndefinedFunction { name, location } => {
131 write!(f, "Undefined function '{}' at {}", name, location)
132 }
133 ExecutionError::TypeError { expected, found, location } => {
134 write!(f, "Type error: expected {} but found {} at {}", expected, found, location)
135 }
136 ExecutionError::DivisionByZero { location } => {
137 write!(f, "Division by zero at {}", location)
138 }
139 ExecutionError::InvalidOperation { operation, message, location } => {
140 write!(f, "Invalid operation '{}': {} at {}", operation, message, location)
141 }
142 ExecutionError::ArgumentCountMismatch { expected, found, location } => {
143 write!(f, "Function expects {} arguments but {} were provided at {}", expected, found, location)
144 }
145 ExecutionError::StackOverflow { location } => {
146 write!(f, "Stack overflow (possible infinite recursion) at {}", location)
147 }
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub enum GeneratorError {
155 InvalidValue { message: String, location: SourceLocation },
156 UnsupportedFeature { feature: String, location: SourceLocation },
157}
158
159impl fmt::Display for GeneratorError {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 match self {
162 GeneratorError::InvalidValue { message, location } => {
163 write!(f, "Invalid value: {} at {}", message, location)
164 }
165 GeneratorError::UnsupportedFeature { feature, location } => {
166 write!(f, "Unsupported feature '{}' at {}", feature, location)
167 }
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq)]
174pub enum TcssError {
175 Lexer(LexerError),
176 Parser(ParserError),
177 Execution(ExecutionError),
178 Generator(GeneratorError),
179 IO(String),
180}
181
182impl fmt::Display for TcssError {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 match self {
185 TcssError::Lexer(e) => write!(f, "Lexer error: {}", e),
186 TcssError::Parser(e) => write!(f, "Parser error: {}", e),
187 TcssError::Execution(e) => write!(f, "Execution error: {}", e),
188 TcssError::Generator(e) => write!(f, "Generator error: {}", e),
189 TcssError::IO(e) => write!(f, "IO error: {}", e),
190 }
191 }
192}
193
194impl std::error::Error for TcssError {}
195
196impl From<LexerError> for TcssError {
197 fn from(error: LexerError) -> Self {
198 TcssError::Lexer(error)
199 }
200}
201
202impl From<ParserError> for TcssError {
203 fn from(error: ParserError) -> Self {
204 TcssError::Parser(error)
205 }
206}
207
208impl From<ExecutionError> for TcssError {
209 fn from(error: ExecutionError) -> Self {
210 TcssError::Execution(error)
211 }
212}
213
214impl From<GeneratorError> for TcssError {
215 fn from(error: GeneratorError) -> Self {
216 TcssError::Generator(error)
217 }
218}
219
220impl From<std::io::Error> for TcssError {
221 fn from(error: std::io::Error) -> Self {
222 TcssError::IO(error.to_string())
223 }
224}
225
226pub type TcssResult<T> = Result<T, TcssError>;
228
229pub struct ErrorFormatter {
231 source: String,
232}
233
234impl ErrorFormatter {
235 pub fn new(source: String) -> Self {
236 Self { source }
237 }
238
239 pub fn format_error(&self, error: &TcssError) -> String {
241 let location = self.get_error_location(error);
242 let mut output = format!("{}\n", error);
243
244 if let Some(loc) = location {
245 if let Some(snippet) = self.get_code_snippet(&loc, 2) {
246 output.push_str("\n");
247 output.push_str(&snippet);
248 }
249
250 if let Some(suggestion) = self.get_suggestion(error) {
252 output.push_str("\n");
253 output.push_str(&format!("💡 Suggestion: {}\n", suggestion));
254 }
255 }
256
257 output
258 }
259
260 fn get_suggestion(&self, error: &TcssError) -> Option<String> {
262 match error {
263 TcssError::Execution(ExecutionError::UndefinedVariable { name, .. }) => {
264 Some(format!("Did you forget to declare the variable '{}'? Use @var {} to declare it.", name, name))
265 }
266 TcssError::Execution(ExecutionError::UndefinedFunction { name, .. }) => {
267 Some(format!("Did you forget to define the function '{}'? Use @fn {}(...) to define it.", name, name))
268 }
269 TcssError::Execution(ExecutionError::DivisionByZero { .. }) => {
270 Some("Make sure the divisor is not zero.".to_string())
271 }
272 TcssError::Execution(ExecutionError::ArgumentCountMismatch { expected, found, .. }) => {
273 Some(format!("The function expects {} argument(s), but you provided {}.", expected, found))
274 }
275 TcssError::Parser(ParserError::IndentationError { .. }) => {
276 Some("Make sure your indentation is consistent (use spaces or tabs, not both).".to_string())
277 }
278 TcssError::Lexer(LexerError::UnterminatedString { .. }) => {
279 Some("Add a closing quote (\") to terminate the string.".to_string())
280 }
281 TcssError::Parser(ParserError::UnexpectedToken { expected, .. }) => {
282 Some(format!("Expected {} here.", expected))
283 }
284 _ => None,
285 }
286 }
287
288 fn get_error_location(&self, error: &TcssError) -> Option<SourceLocation> {
290 match error {
291 TcssError::Lexer(e) => match e {
292 LexerError::UnexpectedCharacter { location, .. } => Some(location.clone()),
293 LexerError::UnterminatedString { location } => Some(location.clone()),
294 LexerError::InvalidNumber { location, .. } => Some(location.clone()),
295 LexerError::InvalidEscape { location, .. } => Some(location.clone()),
296 },
297 TcssError::Parser(e) => match e {
298 ParserError::UnexpectedToken { location, .. } => Some(location.clone()),
299 ParserError::UnexpectedEOF { location, .. } => Some(location.clone()),
300 ParserError::InvalidSyntax { location, .. } => Some(location.clone()),
301 ParserError::IndentationError { location, .. } => Some(location.clone()),
302 ParserError::DuplicateDefinition { location, .. } => Some(location.clone()),
303 },
304 TcssError::Execution(e) => match e {
305 ExecutionError::UndefinedVariable { location, .. } => Some(location.clone()),
306 ExecutionError::UndefinedFunction { location, .. } => Some(location.clone()),
307 ExecutionError::TypeError { location, .. } => Some(location.clone()),
308 ExecutionError::DivisionByZero { location } => Some(location.clone()),
309 ExecutionError::InvalidOperation { location, .. } => Some(location.clone()),
310 ExecutionError::ArgumentCountMismatch { location, .. } => Some(location.clone()),
311 ExecutionError::StackOverflow { location } => Some(location.clone()),
312 },
313 TcssError::Generator(e) => match e {
314 GeneratorError::InvalidValue { location, .. } => Some(location.clone()),
315 GeneratorError::UnsupportedFeature { location, .. } => Some(location.clone()),
316 },
317 TcssError::IO(_) => None,
318 }
319 }
320
321 fn get_code_snippet(&self, location: &SourceLocation, context_lines: usize) -> Option<String> {
323 let lines: Vec<&str> = self.source.lines().collect();
324
325 if location.line == 0 || location.line > lines.len() {
326 return None;
327 }
328
329 let line_idx = location.line - 1;
330 let start = line_idx.saturating_sub(context_lines);
331 let end = (line_idx + context_lines + 1).min(lines.len());
332
333 let mut snippet = String::new();
334 let max_line_num_width = end.to_string().len();
335
336 for i in start..end {
337 let line_num = i + 1;
338 let line = lines[i];
339
340 if i == line_idx {
341 snippet.push_str(&format!("{:>width$} | {}\n",
343 line_num, line, width = max_line_num_width));
344
345 snippet.push_str(&format!("{:>width$} | ", "", width = max_line_num_width));
347 if location.column > 0 {
348 snippet.push_str(&" ".repeat(location.column - 1));
349 }
350 snippet.push_str("^\n");
351 } else {
352 snippet.push_str(&format!("{:>width$} | {}\n",
353 line_num, line, width = max_line_num_width));
354 }
355 }
356
357 Some(snippet)
358 }
359}
360
361