1use crate::ast::span::Span;
9use std::sync::Arc;
10use thiserror::Error;
11
12#[derive(Debug, Clone, Error)]
15#[error("{message}")]
16pub struct ParseError {
17 pub span: Span,
18 pub message: String,
19 pub hint: Option<String>,
20}
21
22impl ParseError {
23 pub fn new(span: Span, message: impl Into<String>) -> Self {
24 Self {
25 span,
26 message: message.into(),
27 hint: None,
28 }
29 }
30
31 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
32 self.hint = Some(hint.into());
33 self
34 }
35
36 pub fn format_with_source(&self, source: &str, entry_name: Option<&str>) -> String {
38 let (line, col) = offset_to_line_col(source, self.span.start);
39 let source_line = source.lines().nth(line.saturating_sub(1)).unwrap_or("");
40
41 let location = if let Some(name) = entry_name {
42 format!(" --> {name}:{line}:{col}")
43 } else {
44 format!(" --> {line}:{col}")
45 };
46
47 let pointer = " ".repeat(col.saturating_sub(1))
48 + &"^".repeat((self.span.end - self.span.start).max(1));
49
50 let mut output = format!("Error: {}\n{location}\n |\n{line:>3} | {source_line}\n | {pointer}", self.message);
51
52 if let Some(hint) = &self.hint {
53 output.push_str(&format!("\n = hint: {hint}"));
54 }
55
56 output
57 }
58}
59
60fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
61 let mut line = 1;
62 let mut col = 1;
63 for (i, ch) in source.char_indices() {
64 if i >= offset {
65 break;
66 }
67 if ch == '\n' {
68 line += 1;
69 col = 1;
70 } else {
71 col += 1;
72 }
73 }
74 (line, col)
75}
76
77#[derive(Debug, Clone, Error)]
99#[error("{message}")]
100pub struct EvalError {
101 pub kind: EvalErrorKind,
102 pub span: Option<Span>,
103 pub message: String,
104 #[source]
108 pub source: Option<Arc<dyn std::error::Error + Send + Sync>>,
109}
110
111impl EvalError {
112 pub fn new(kind: EvalErrorKind, message: impl Into<String>) -> Self {
113 Self {
114 kind,
115 span: None,
116 message: message.into(),
117 source: None,
118 }
119 }
120
121 pub fn with_span(mut self, span: Span) -> Self {
122 self.span = Some(span);
123 self
124 }
125
126 pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
132 self.source = Some(Arc::new(source));
133 self
134 }
135
136 pub fn undefined_variable(scope: &str, name: &str) -> Self {
139 Self::new(
140 EvalErrorKind::UndefinedVariable,
141 format!("undefined variable: {scope}:{name}"),
142 )
143 }
144
145 pub fn undefined_processor(namespace: &str, name: &str) -> Self {
146 Self::new(
147 EvalErrorKind::UndefinedCallable,
148 format!("undefined processor: {namespace}.{name}"),
149 )
150 }
151
152 pub fn undefined_command(name: &str) -> Self {
153 Self::new(
154 EvalErrorKind::UndefinedCallable,
155 format!("undefined command: {name}"),
156 )
157 }
158
159 pub fn type_error(expected: &str, got: &str) -> Self {
160 Self::new(
161 EvalErrorKind::TypeError,
162 format!("expected {expected}, got {got}"),
163 )
164 }
165
166 pub fn not_iterable(got: &str) -> Self {
167 Self::new(
168 EvalErrorKind::NotIterable,
169 format!("foreach requires an array, got {got}"),
170 )
171 }
172
173 pub fn host_error(message: impl Into<String>) -> Self {
174 Self::new(EvalErrorKind::HostError, message)
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum EvalErrorKind {
180 UndefinedVariable,
181 UndefinedCallable,
182 TypeError,
183 NotIterable,
184 ArithmeticError,
185 TriggerNotFound,
186 DocumentNotFound,
187 HostError,
188 RecursionLimit,
189 ResourceLimit,
192 Cancelled,
194}