koto_parser/
error.rs

1use koto_lexer::Span;
2use std::fmt::Write;
3use thiserror::Error;
4
5use crate::string_format_options::StringFormatError;
6
7/// An error that represents a problem with the Parser's internal logic, rather than a user error
8#[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/// Errors that arise from expecting an indented block
34///
35/// Having these errors separated out from [SyntaxError] is useful when working with interactive
36/// input, where an indented continuation can be started in response to an indentation error.
37#[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/// A syntax error encountered by the [Parser][crate::Parser]
73#[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/// See [`Error`][crate::Error]
213#[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/// An error that can be produced by the [Parser](crate::Parser)
227#[derive(Error, Clone, Debug)]
228#[error("{error}")]
229pub struct Error {
230    /// The error itself
231    pub error: ErrorKind,
232
233    /// The span in the source string where the error occurred
234    pub span: Span,
235
236    /// The partially parsed AST up to the point where the error occurred
237    #[cfg(feature = "error_ast")]
238    pub ast: Option<Box<crate::Ast>>,
239}
240
241impl Error {
242    /// Initializes a parser error with the specific error type and its associated span
243    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    /// Returns true if the error was caused by the expectation of indentation
253    pub fn is_indentation_error(&self) -> bool {
254        matches!(self.error, ErrorKind::ExpectedIndentation(_))
255    }
256}
257
258/// The result type used by the [Parser](crate::Parser)
259pub type Result<T> = std::result::Result<T, Error>;
260
261/// Renders the excerpt of the source corresponding to the given span
262pub 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}