1use koto_lexer::Span;
2use std::fmt::Write;
3use thiserror::Error;
4
5use crate::string_format_options::StringFormatError;
6
7#[derive(Error, Clone, Debug)]
9#[allow(missing_docs)]
10pub enum InternalError {
11 #[error("there are more nodes in the program than the AST can support")]
12 AstCapacityOverflow,
13 #[error("there are more constants in the program than the runtime can support")]
14 ConstantPoolCapacityOverflow,
15 #[error("expected ':' after map key")]
16 ExpectedMapColon,
17 #[error("failed to parse ID")]
18 IdParseFailure,
19 #[error("failed to parse chain")]
20 ChainParseFailure,
21 #[error("missing assignment target")]
22 MissingAssignmentTarget,
23 #[error("frame unavailable during parsing")]
24 MissingFrame,
25 #[error("failed to parse number")]
26 NumberParseFailure,
27 #[error("failed to parse raw string")]
28 RawStringParseFailure,
29 #[error("unexpected token")]
30 UnexpectedToken,
31}
32
33#[derive(Error, Clone, Debug)]
38#[allow(missing_docs)]
39pub enum ExpectedIndentation {
40 #[error("expected expression after assignment operator")]
41 AssignmentExpression,
42 #[error("expected indented block for catch expression")]
43 CatchBody,
44 #[error("expected indented block for 'else'.")]
45 ElseBlock,
46 #[error("expected indented block for 'else if'.")]
47 ElseIfBlock,
48 #[error("expected indented block for finally expression")]
49 FinallyBody,
50 #[error("expected indented block as for loop body")]
51 ForBody,
52 #[error("expected function body")]
53 FunctionBody,
54 #[error("expected indented block as loop body")]
55 LoopBody,
56 #[error("expected indented arm for match expression")]
57 MatchArm,
58 #[error("expected expression after binary operator")]
59 RhsExpression,
60 #[error("expected indented arm for switch expression")]
61 SwitchArm,
62 #[error("error parsing if expression, expected 'then' keyword or indented block.")]
63 ThenKeywordOrBlock,
64 #[error("expected indented block for try expression")]
65 TryBody,
66 #[error("expected indented block as until loop body")]
67 UntilBody,
68 #[error("expected indented block as while loop body")]
69 WhileBody,
70}
71
72#[derive(Error, Clone, Debug)]
74#[allow(missing_docs)]
75pub enum SyntaxError {
76 #[error("ascii value out of range, the maximum is \\x7f")]
77 AsciiEscapeCodeOutOfRange,
78 #[error("expected end of arguments ')'")]
79 ExpectedArgsEnd,
80 #[error("expected target for assignment")]
81 ExpectedAssignmentTarget,
82 #[error("expected '=' assignment after meta key")]
83 ExpectedAssignmentAfterMetaKey,
84 #[error("expected argument for catch expression")]
85 ExpectedCatchArgument,
86 #[error("expected catch expression after try")]
87 ExpectedCatch,
88 #[error("expected closing parenthesis ')'")]
89 ExpectedCloseParen,
90 #[error("all arguments following a default value must also have a default value")]
91 ExpectedDefaultValue,
92 #[error("expected expression after 'else'.")]
93 ExpectedElseExpression,
94 #[error("expected condition for 'else if'.")]
95 ExpectedElseIfCondition,
96 #[error("expected expression")]
97 ExpectedExpression,
98 #[error("expected arguments in for loop")]
99 ExpectedForArgs,
100 #[error("expected 'in' keyword in for loop")]
101 ExpectedForInKeyword,
102 #[error("expected iterable in for loop")]
103 ExpectedForIterable,
104 #[error("expected format string after ':'")]
105 ExpectedFormatString,
106 #[error("expected end of function arguments '|'")]
107 ExpectedFunctionArgsEnd,
108 #[error("expected ID in import expression")]
109 ExpectedIdInImportExpression,
110 #[error("expected condition after 'if'")]
111 ExpectedIfCondition,
112 #[error("expected import after from")]
113 ExpectedImportAfterFrom,
114 #[error("expected module ID in import expression")]
115 ExpectedImportModuleId,
116 #[error("expected index end ']'")]
117 ExpectedIndexEnd,
118 #[error("expected index expression")]
119 ExpectedIndexExpression,
120 #[error("expected id after 'as'")]
121 ExpectedIdAfterAs,
122 #[error("expected List end ']'")]
123 ExpectedListEnd,
124 #[error("expected ':' after map key")]
125 ExpectedMapColon,
126 #[error("expected '}}' at end of map declaration")]
127 ExpectedMapEnd,
128 #[error("expected map entry")]
129 ExpectedMapEntry,
130 #[error("expected key after '.' in Map access")]
131 ExpectedMapKey,
132 #[error("expected value after ':' in Map")]
133 ExpectedMapValue,
134 #[error("expected expression in match arm")]
135 ExpectedMatchArmExpression,
136 #[error("expected expression after then in match arm")]
137 ExpectedMatchArmExpressionAfterThen,
138 #[error("expected condition after if in match arm")]
139 ExpectedMatchCondition,
140 #[error("expected expression after match")]
141 ExpectedMatchExpression,
142 #[error("expected pattern for match arm")]
143 ExpectedMatchPattern,
144 #[error("expected id after @meta")]
145 ExpectedMetaId,
146 #[error("expected a module path after 'from'")]
147 ExpectedPathAfterFrom,
148 #[error("expected a line break before starting a map block")]
149 ExpectedLineBreakBeforeMapBlock,
150 #[error("expected '}}' at end of string placeholder")]
151 ExpectedStringPlaceholderEnd,
152 #[error("expected expression in switch arm")]
153 ExpectedSwitchArmExpression,
154 #[error("expected expression after 'then' in switch arm")]
155 ExpectedSwitchArmExpressionAfterThen,
156 #[error("expected a test name")]
157 ExpectedTestName,
158 #[error("expected expression after 'then'")]
159 ExpectedThenExpression,
160 #[error("expected condition in until loop")]
161 ExpectedUntilCondition,
162 #[error("expected condition in while loop")]
163 ExpectedWhileCondition,
164 #[error("expected a type after ':'")]
165 ExpectedType,
166 #[error(transparent)]
167 FormatStringError(StringFormatError),
168 #[error("non-inline if expression isn't allowed in this context")]
169 IfBlockNotAllowedInThisContext,
170 #[error("ellipsis found outside of nested match patterns")]
171 MatchEllipsisOutsideOfNestedPatterns,
172 #[error("'else' can only be used in the last arm in a match expression")]
173 MatchElseNotInLastArm,
174 #[error("Missing 'from' for wildcard import")]
175 MissingModuleForWildcardImport,
176 #[error("nested types aren't currently supported")]
177 NestedTypesArentSupported,
178 #[error("keyword reserved for future use")]
179 ReservedKeyword,
180 #[error("'self' doesn't need to be declared as an argument")]
181 SelfArg,
182 #[error("'else' can only be used in the last arm in a switch expression")]
183 SwitchElseNotInLastArm,
184 #[error("unexpected character in numeric escape code")]
185 UnexpectedCharInNumericEscapeCode,
186 #[error("'.' after imported item. You might want a 'from' import instead")]
187 UnexpectedDotAfterImportItem,
188 #[error("unexpected escape pattern in string")]
189 UnexpectedEscapeInString,
190 #[error("unexpected 'else' in match arm")]
191 UnexpectedMatchElse,
192 #[error("unexpected if condition in match arm")]
193 UnexpectedMatchIf,
194 #[error("unexpected meta key")]
195 UnexpectedMetaKey,
196 #[error("unexpected 'else' in switch arm")]
197 UnexpectedSwitchElse,
198 #[error("condition expected before 'then' in switch arm")]
199 UnexpectedSwitchThen,
200 #[error("unexpected '?'")]
201 UnexpectedNullCheck,
202 #[error("unexpected token")]
203 UnexpectedToken,
204 #[error("unicode value out of range, the maximum is \\u{{10ffff}}")]
205 UnicodeEscapeCodeOutOfRange,
206 #[error("unterminated numeric escape code")]
207 UnterminatedNumericEscapeCode,
208 #[error("unterminated string")]
209 UnterminatedString,
210}
211
212#[derive(Error, Clone, Debug)]
214#[allow(missing_docs)]
215pub enum ErrorKind {
216 #[error(transparent)]
217 InternalError(#[from] InternalError),
218 #[error(transparent)]
219 ExpectedIndentation(#[from] ExpectedIndentation),
220 #[error(transparent)]
221 SyntaxError(#[from] SyntaxError),
222 #[error(transparent)]
223 StringFormatError(#[from] StringFormatError),
224}
225
226#[derive(Error, Clone, Debug)]
228#[error("{error}")]
229pub struct Error {
230 pub error: ErrorKind,
232
233 pub span: Span,
235
236 #[cfg(feature = "error_ast")]
238 pub ast: Option<Box<crate::Ast>>,
239}
240
241impl Error {
242 pub fn new(error: ErrorKind, span: Span) -> Self {
244 Self {
245 error,
246 span,
247 #[cfg(feature = "error_ast")]
248 ast: None,
249 }
250 }
251
252 pub fn is_indentation_error(&self) -> bool {
254 matches!(self.error, ErrorKind::ExpectedIndentation(_))
255 }
256}
257
258pub type Result<T> = std::result::Result<T, Error>;
260
261pub fn format_source_excerpt(source: &str, span: &Span, source_path: Option<&str>) -> String {
263 let Span { start, end } = span;
264
265 let (excerpt, padding) = {
266 let excerpt_lines = source
267 .lines()
268 .skip((start.line) as usize)
269 .take((end.line - start.line + 1) as usize)
270 .collect::<Vec<_>>();
271
272 let line_numbers = (start.line..=end.line)
273 .map(|n| (n + 1).to_string())
274 .collect::<Vec<_>>();
275
276 let number_width = line_numbers.iter().max_by_key(|n| n.len()).unwrap().len();
277
278 let padding = " ".repeat(number_width + 2);
279
280 if start.line == end.line {
281 let mut excerpt = format!(
282 " {:>number_width$} | {}\n",
283 line_numbers.first().unwrap(),
284 excerpt_lines.first().unwrap(),
285 );
286
287 write!(
288 excerpt,
289 "{padding}|{}{}",
290 " ".repeat(start.column as usize + 1),
291 "^".repeat((end.column - start.column) as usize)
292 )
293 .ok();
294
295 (excerpt, padding)
296 } else {
297 let mut excerpt = String::new();
298
299 for (excerpt_line, line_number) in excerpt_lines.iter().zip(line_numbers.iter()) {
300 writeln!(excerpt, " {line_number:>number_width$} | {excerpt_line}").ok();
301 }
302
303 (excerpt, padding)
304 }
305 };
306
307 let position_info = if let Some(path) = source_path {
308 let display_path = std::env::current_dir()
309 .ok()
310 .and_then(|dir| dir.to_str().and_then(|dir_str| path.strip_prefix(dir_str)))
311 .unwrap_or(path);
312
313 format!("{display_path} - {}:{}", start.line + 1, start.column + 1)
314 } else {
315 format!("{}:{}", start.line + 1, start.column + 1)
316 };
317
318 format!("{position_info}\n{padding}|\n{excerpt}")
319}