1use tenda_common::span::SourceSpan;
2use tenda_reporting::{Diagnostic, DiagnosticConfig, HasDiagnosticHooks};
3use tenda_reporting_derive::Diagnostic;
4use thiserror::Error;
5
6use crate::{
7 associative_array::AssociativeArrayKey,
8 value::{Value, ValueType},
9};
10
11pub type Result<T> = std::result::Result<T, Box<RuntimeError>>;
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum FunctionName {
15 Anonymous,
16 TopLevel,
17 Named(String),
18}
19
20#[derive(Debug, Clone, PartialEq)]
21pub struct StackFrame {
22 pub function_name: FunctionName,
23 pub location: Option<SourceSpan>,
24}
25
26impl StackFrame {
27 pub fn new(function_name: FunctionName, location: Option<SourceSpan>) -> Self {
28 Self {
29 function_name,
30 location,
31 }
32 }
33}
34
35impl From<StackFrame> for tenda_reporting::StackFrame<SourceSpan> {
36 fn from(val: StackFrame) -> Self {
37 let function_name = match val.function_name {
38 FunctionName::Anonymous => "<anônimo>".to_string(),
39 FunctionName::TopLevel => "<raiz>".to_string(),
40 FunctionName::Named(name) => name,
41 };
42
43 tenda_reporting::StackFrame::new(function_name, val.location)
44 }
45}
46
47#[derive(Error, Debug, PartialEq, Clone, Diagnostic)]
48#[accept_hooks]
49#[report("erro de execução")]
50pub enum RuntimeError {
51 #[error("divisão por zero não é permitida")]
52 DivisionByZero {
53 #[span]
54 span: Option<SourceSpan>,
55
56 #[metadata]
57 stacktrace: Vec<StackFrame>,
58 },
59
60 #[error("operação inválida para os tipos '{}' e '{}'", .first.to_string(), .second.to_string())]
61 TypeMismatch {
62 first: ValueType,
63 second: ValueType,
64
65 #[span]
66 span: Option<SourceSpan>,
67
68 #[message]
69 message: Option<String>,
70
71 #[metadata]
72 stacktrace: Vec<StackFrame>,
73 },
74
75 #[error("esperado valor de tipo '{}', encontrado '{}'", .expected.to_string(), .found.to_string())]
76 UnexpectedTypeError {
77 expected: ValueType,
78 found: ValueType,
79
80 #[message]
81 message: Option<String>,
82
83 #[span]
84 span: Option<SourceSpan>,
85
86 #[metadata]
87 stacktrace: Vec<StackFrame>,
88 },
89
90 #[error("a variável identificada por '{}' não está definida neste escopo", .var_name)]
91 UndefinedReference {
92 var_name: String,
93
94 #[span]
95 span: Option<SourceSpan>,
96
97 #[help]
98 help: Option<String>,
99
100 #[metadata]
101 stacktrace: Vec<StackFrame>,
102 },
103
104 #[error("variável identificada por {0} já está declarada neste escopo", .var_name)]
105 AlreadyDeclared {
106 var_name: String,
107
108 #[span]
109 span: Option<SourceSpan>,
110
111 #[help]
112 help: Option<String>,
113
114 #[metadata]
115 stacktrace: Vec<StackFrame>,
116 },
117
118 #[error("número de argumentos incorreto: esperado {}, encontrado {}", .expected, .found)]
119 WrongNumberOfArguments {
120 expected: usize,
121 found: usize,
122
123 #[span]
124 span: Option<SourceSpan>,
125
126 #[metadata]
127 stacktrace: Vec<StackFrame>,
128 },
129
130 #[error("índice fora dos limites: índice {}, tamanho {}", .index, .len)]
131 IndexOutOfBounds {
132 index: usize,
133 len: usize,
134
135 #[span]
136 span: Option<SourceSpan>,
137
138 #[help]
139 help: Vec<String>,
140
141 #[metadata]
142 stacktrace: Vec<StackFrame>,
143 },
144
145 #[error("não é possível acessar um valor do tipo '{}'", .value.to_string())]
146 WrongIndexType {
147 value: ValueType,
148
149 #[span]
150 span: Option<SourceSpan>,
151
152 #[metadata]
153 stacktrace: Vec<StackFrame>,
154 },
155
156 #[error("limites de intervalo precisam ser números inteiros finitos: encontrado '{}'", .bound)]
157 InvalidRangeBounds {
158 bound: f64,
159
160 #[span]
161 span: Option<SourceSpan>,
162
163 #[metadata]
164 stacktrace: Vec<StackFrame>,
165 },
166
167 #[error("índice de lista precisa ser um número inteiro positivo e finito: encontrado '{}'", .index)]
168 InvalidIndex {
169 index: f64,
170
171 #[span]
172 span: Option<SourceSpan>,
173
174 #[metadata]
175 stacktrace: Vec<StackFrame>,
176 },
177
178 #[error("chave de dicionário precisa ser número inteiro ou texto: encontrado '{}'", .key)]
179 InvalidNumberAssociativeArrayKey {
180 key: f64,
181
182 #[span]
183 span: Option<SourceSpan>,
184
185 #[metadata]
186 stacktrace: Vec<StackFrame>,
187 },
188
189 #[error("chave de dicionário precisa ser número inteiro ou texto: encontrado '{}'", .key)]
190 InvalidTypeAssociativeArrayKey {
191 key: ValueType,
192
193 #[span]
194 span: Option<SourceSpan>,
195
196 #[metadata]
197 stacktrace: Vec<StackFrame>,
198 },
199
200 #[error("chave de dicionário não encontrada: '{}'", .key.to_string())]
201 AssociativeArrayKeyNotFound {
202 key: AssociativeArrayKey,
203
204 #[span]
205 span: Option<SourceSpan>,
206
207 #[metadata]
208 stacktrace: Vec<StackFrame>,
209 },
210
211 #[error("não é possível iterar sobre um valor do tipo '{}'", .value.to_string())]
212 NotIterable {
213 value: ValueType,
214
215 #[span]
216 span: Option<SourceSpan>,
217
218 #[metadata]
219 stacktrace: Vec<StackFrame>,
220 },
221
222 #[error("o valor do tipo '{}' não é um argumento válido para a função", .value.to_string())]
223 InvalidArgument {
224 value: Value,
225
226 #[span]
227 span: Option<SourceSpan>,
228
229 #[metadata]
230 stacktrace: Vec<StackFrame>,
231 },
232
233 #[error("textos são imutáveis e não podem ser modificados")]
234 ImmutableString {
235 #[span]
236 span: Option<SourceSpan>,
237
238 #[help]
239 help: Option<String>,
240
241 #[metadata]
242 stacktrace: Vec<StackFrame>,
243 },
244
245 #[error("timestamp inválido: {}", .timestamp.to_string())]
246 InvalidTimestamp {
247 timestamp: i64,
248
249 #[span]
250 span: Option<SourceSpan>,
251
252 #[metadata]
253 stacktrace: Vec<StackFrame>,
254 },
255
256 #[error("falha ao analisar data ISO: {}", .source)]
257 DateIsoParseError {
258 source: chrono::ParseError,
259
260 #[span]
261 span: Option<SourceSpan>,
262
263 #[metadata]
264 stacktrace: Vec<StackFrame>,
265 },
266
267 #[error("fuso horário inválido: '{tz_str}'")]
268 InvalidTimeZoneString {
269 tz_str: String,
270
271 #[span]
272 span: Option<SourceSpan>,
273
274 #[metadata]
275 stacktrace: Vec<StackFrame>,
276 },
277
278 #[error("valor inválido para conversão para tipo '{}'", .value.to_string())]
279 InvalidValueForConversion {
280 value: Value,
281
282 #[span]
283 span: Option<SourceSpan>,
284
285 #[metadata]
286 stacktrace: Vec<StackFrame>,
287 },
288}
289
290impl HasDiagnosticHooks<SourceSpan> for RuntimeError {
291 fn hooks() -> &'static [fn(&Self, DiagnosticConfig<SourceSpan>) -> DiagnosticConfig<SourceSpan>]
292 {
293 &[add_stacktrace]
294 }
295}
296
297fn build_stack_frames(runtime_error: &RuntimeError) -> Option<Vec<StackFrame>> {
310 let st = runtime_error.get_stacktrace()?;
311
312 if st.is_empty() {
313 return None;
314 }
315
316 let mut frames = Vec::with_capacity(st.len() + 1);
317
318 frames.push(StackFrame::new(
319 st[0].function_name.clone(),
320 runtime_error.get_span().clone(),
321 ));
322
323 for window in st.windows(2) {
324 let (prev, curr) = (&window[0], &window[1]);
325
326 frames.push(StackFrame::new(
327 curr.function_name.clone(),
328 prev.location.clone(),
329 ));
330 }
331
332 frames.push(StackFrame::new(
333 FunctionName::TopLevel,
334 st.last().unwrap().location.clone(),
335 ));
336
337 Some(frames)
338}
339
340fn add_stacktrace(
341 runtime_error: &RuntimeError,
342 config: DiagnosticConfig<SourceSpan>,
343) -> DiagnosticConfig<SourceSpan> {
344 match build_stack_frames(runtime_error) {
345 Some(frames) => config.stacktrace(frames.into_iter().map(Into::into).collect()),
346 None => config,
347 }
348}
349
350macro_rules! attach_span_if_missing {
351 ($err:expr, $span:expr) => {{
352 if $err.get_span().is_none() {
353 $err.set_span($span);
354 }
355
356 $err
357 }};
358}
359
360pub(crate) use attach_span_if_missing;