1use ariadne::{Color, Label, Report, ReportKind, Source};
5use std::fmt;
6use std::ops::Range;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Span {
11 pub start: usize,
12 pub end: usize,
13}
14
15impl Span {
16 pub fn new(start: usize, end: usize) -> Self {
17 Self { start, end }
18 }
19
20 pub fn range(&self) -> Range<usize> {
21 self.start..self.end
22 }
23}
24
25impl From<Range<usize>> for Span {
26 fn from(range: Range<usize>) -> Self {
27 Self {
28 start: range.start,
29 end: range.end,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
38pub enum TlError {
39 Lexer(LexerError),
40 Parser(ParserError),
41 Runtime(RuntimeError),
42 Type(TypeError),
43}
44
45#[derive(Debug, Clone)]
46pub struct TypeError {
47 pub message: String,
48 pub span: Span,
49 pub expected: Option<String>,
50 pub found: Option<String>,
51 pub hint: Option<String>,
52}
53
54#[derive(Debug, Clone)]
55pub struct LexerError {
56 pub message: String,
57 pub span: Span,
58}
59
60#[derive(Debug, Clone)]
61pub struct ParserError {
62 pub message: String,
63 pub span: Span,
64 pub hint: Option<String>,
65}
66
67#[derive(Debug, Clone)]
68pub struct RuntimeError {
69 pub message: String,
70 pub span: Option<Span>,
71 pub stack_trace: Vec<StackFrame>,
72}
73
74#[derive(Debug, Clone)]
76pub struct StackFrame {
77 pub function: String,
78 pub line: u32,
79}
80
81impl fmt::Display for TlError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 TlError::Lexer(e) => write!(f, "Lexer error: {}", e.message),
85 TlError::Parser(e) => write!(f, "Parse error: {}", e.message),
86 TlError::Runtime(e) => write!(f, "Runtime error: {}", e.message),
87 TlError::Type(e) => {
88 write!(f, "Type error: {}", e.message)?;
89 if let (Some(expected), Some(found)) = (&e.expected, &e.found) {
90 write!(f, " (expected `{expected}`, found `{found}`)")?;
91 }
92 Ok(())
93 }
94 }
95 }
96}
97
98impl std::error::Error for TlError {}
99
100pub fn report_parser_error(source: &str, filename: &str, error: &ParserError) {
102 let mut builder = Report::build(ReportKind::Error, filename, error.span.start)
103 .with_message(&error.message)
104 .with_label(
105 Label::new((filename, error.span.range()))
106 .with_message(&error.message)
107 .with_color(Color::Red),
108 );
109
110 if let Some(hint) = &error.hint {
111 builder = builder.with_help(hint);
112 }
113
114 builder
115 .finish()
116 .eprint((filename, Source::from(source)))
117 .unwrap();
118}
119
120pub fn report_type_error(source: &str, filename: &str, error: &TypeError) {
122 let mut builder =
123 Report::build(ReportKind::Error, filename, error.span.start).with_message(&error.message);
124
125 let mut label_msg = error.message.clone();
126 if let (Some(expected), Some(found)) = (&error.expected, &error.found) {
127 label_msg = format!("expected `{expected}`, found `{found}`");
128 }
129
130 builder = builder.with_label(
131 Label::new((filename, error.span.range()))
132 .with_message(&label_msg)
133 .with_color(Color::Red),
134 );
135
136 if let Some(hint) = &error.hint {
137 builder = builder.with_help(hint);
138 }
139
140 builder
141 .finish()
142 .eprint((filename, Source::from(source)))
143 .unwrap();
144}
145
146pub fn report_type_warning(source: &str, filename: &str, error: &TypeError) {
148 let mut builder =
149 Report::build(ReportKind::Warning, filename, error.span.start).with_message(&error.message);
150
151 let mut label_msg = error.message.clone();
152 if let (Some(expected), Some(found)) = (&error.expected, &error.found) {
153 label_msg = format!("expected `{expected}`, found `{found}`");
154 }
155
156 builder = builder.with_label(
157 Label::new((filename, error.span.range()))
158 .with_message(&label_msg)
159 .with_color(Color::Yellow),
160 );
161
162 if let Some(hint) = &error.hint {
163 builder = builder.with_help(hint);
164 }
165
166 builder
167 .finish()
168 .eprint((filename, Source::from(source)))
169 .unwrap();
170}
171
172pub fn report_runtime_error(source: &str, filename: &str, error: &RuntimeError) {
174 if let Some(span) = &error.span {
176 Report::build(ReportKind::Error, filename, span.start)
177 .with_message(&error.message)
178 .with_label(
179 Label::new((filename, span.range()))
180 .with_message(&error.message)
181 .with_color(Color::Red),
182 )
183 .finish()
184 .eprint((filename, Source::from(source)))
185 .unwrap();
186 } else if !error.stack_trace.is_empty() && error.stack_trace[0].line > 0 {
187 let line = error.stack_trace[0].line as usize;
189 let lines: Vec<&str> = source.lines().collect();
190 if line > 0 && line <= lines.len() {
191 let mut offset = 0;
192 for l in &lines[..line - 1] {
193 offset += l.len() + 1; }
195 let line_len = lines[line - 1].len().max(1);
196 let span = Span::new(offset, offset + line_len);
197 Report::build(ReportKind::Error, filename, span.start)
198 .with_message(&error.message)
199 .with_label(
200 Label::new((filename, span.range()))
201 .with_message(&error.message)
202 .with_color(Color::Red),
203 )
204 .finish()
205 .eprint((filename, Source::from(source)))
206 .unwrap();
207 } else {
208 eprintln!("Runtime error (line {}): {}", line, error.message);
209 }
210 } else {
211 eprintln!("Runtime error: {}", error.message);
212 }
213 if error.stack_trace.len() > 1
215 || (error.stack_trace.len() == 1 && error.stack_trace[0].function != "<main>")
216 {
217 eprintln!("Stack trace:");
218 for frame in &error.stack_trace {
219 if frame.line > 0 {
220 eprintln!(" at {} (line {})", frame.function, frame.line);
221 } else {
222 eprintln!(" at {}", frame.function);
223 }
224 }
225 }
226}