Skip to main content

candela/parser/
parser.rs

1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use crate::BOLD;
5use crate::RED;
6use crate::RESET;
7use crate::cfg;
8use crate::cfg::Cfg;
9use crate::compiler::compiler_data::Source;
10use crate::compiler::expr::OPERATOR_SYMBOLS;
11use crate::compiler::expr::{Expr, ImportBinding, LetAnnotation, Span, var_assign};
12use crate::compiler::type_system::FnTypeExpr;
13use crate::compiler::type_system::GenericType;
14use crate::compiler::type_system::ImplTemplate;
15use crate::compiler::type_system::TypeExpr;
16use crate::compiler::type_system::TypeParams;
17use crate::errors::BLUE;
18use crate::errors::blue;
19use crate::errors::crash;
20use crate::macros;
21use crate::macros::Expansion;
22use crate::macros::MAX_EXPANSION_DEPTH;
23use ariadne::Color;
24use ariadne::Label;
25use ariadne::Report;
26use ariadne::ReportKind;
27use blocks::parse_condition_block;
28use blocks::parse_enum_declare;
29use blocks::parse_eval_block;
30use blocks::parse_for_loop;
31use blocks::parse_function;
32use blocks::parse_impl_block;
33use blocks::parse_loop_block;
34use blocks::parse_match;
35use blocks::parse_struct_declare;
36use blocks::parse_try_catch_block;
37use blocks::parse_while_block;
38use lexer::LexErr;
39use lexer::MacroToken;
40use lexer::parse_string;
41use logos::SpannedIter;
42use parser_expr::add_op;
43use parser_expr::parse_expr;
44use smol_strc::SmolStr;
45use std::hint::{cold_path, unreachable_unchecked};
46use std::io::Write;
47use std::iter::Peekable;
48
49use lexer::Token;
50use logos::Logos;
51
52mod blocks;
53mod lexer;
54mod parser_expr;
55mod term;
56
57type TokenIter<'a> = Peekable<SpannedIter<'a, Token<'a>>>;
58
59struct ParserCtx<'a> {
60    src: &'a Source,
61    /// The configuration `@cfg(...)` attributes are read against.
62    cfg: &'a Cfg,
63}
64
65struct Parser<'a> {
66    input: TokenIter<'a>,
67    ctx: ParserCtx<'a>,
68    last_token_end: usize,
69    /// Set while parsing the source a macro expanded to. That source is not in
70    /// the file, so every span the sub-parse produces is the span of the macro
71    /// invocation instead: expressions spliced into the program point at the
72    /// macro site, and so does any error raised while parsing them.
73    span_override: Option<Span>,
74    /// How many terms, blocks and types enclose the one being parsed. Each
75    /// level is one recursive descent frame, so this is what bounds the stack
76    /// the parser (and the compiler walking the tree it builds) can use.
77    nesting: u32,
78    /// The second `>` of a `>>` that closed one level of a nested type-argument
79    /// list, waiting to be read as a token of its own. `Box<Box<int>>` ends in
80    /// one token and closes two lists, so the inner list takes the first half
81    /// and leaves the second here for the list around it.
82    pending_gt: Option<Span>,
83    /// How many `@cfg` attributes that evaluated false enclose what is being
84    /// parsed. Code under one is parsed, so its syntax is checked on every
85    /// configuration, and then dropped; a macro inside it is not expanded,
86    /// since the expander may exist only where the code is compiled.
87    inactive: u32,
88}
89
90/// The deepest that terms, blocks and types may nest.
91///
92/// A level is one term inside another (`(`, `[`, `-`, a call argument, a
93/// struct field), one block inside another, or one type inside another; a
94/// macro expansion parsed inside an expression counts the levels around the
95/// invocation. The json parser caps documents at the same depth.
96pub const MAX_NESTING_DEPTH: u32 = 128;
97
98#[derive(Clone)]
99enum ParserErr<'a> {
100    UnexpectedEOF,
101    UnknownToken,
102    /// An integer literal outside the range `int` holds.
103    IntLiteralOutOfRange,
104    /// A float literal outside the range `float` holds.
105    FloatLiteralOutOfRange,
106    /// A number whose exponent marker carries no digits.
107    FloatExponentMissingDigits,
108    /// (expected, received)
109    UnexpectedToken(Token<'a>, Token<'a>, &'static str),
110    /// (expected, received)
111    UnexpectedTokenStr(&'static str, Token<'a>, &'static str),
112    ArrayElementsMissingComma,
113    InlineConditionNoElseBlock,
114    DivisionByZero,
115    ModuloByZero,
116    IntegerNegativeExponent,
117    /// A literal shift by a negative count, or by 64 or more, which `int` has
118    /// no result for. Carries the count that was written.
119    ShiftCountOutOfRange(i64),
120    ArgumentsMissingCommaSeparator,
121    TryBlockNoCatch,
122    MatchBlockNoNonWildcardArm,
123    MatchBlockZeroArms,
124    /// An expression arm of a `match` followed by another arm with no `,`
125    /// between them.
126    MatchArmMissingComma,
127    /// `_` written as one of a pattern's `|` alternatives, where it would
128    /// make the others pointless. The wildcard stands alone, as the last arm.
129    MatchWildcardAlternative,
130    /// A `fn` declaration written inside a block rather than at the top level
131    /// of a file.
132    NestedFunctionDeclaration,
133    /// The removed `import std::string;` form; carries the path segments
134    /// joined with `/` so the error suggests the exact replacement.
135    LegacyNamespacedImport(String),
136    ImportPathBadExtension,
137    /// An import with no `as` whose path ends in a segment that cannot be
138    /// written as a name, such as `my-lib`. Carries the segment.
139    ImportNameNotIdentifier(String),
140    /// `as name` and a `{ ... }` list on one import.
141    ImportAliasWithItems,
142    /// `{}` with no item in it.
143    ImportItemsEmpty,
144    /// `name!(` whose region the file ends before closing.
145    UnterminatedMacroRegion(&'a str),
146    /// `name!(...)` with no expander registered for `name`.
147    UnknownMacro(&'a str),
148    /// (macro name, what the expander said)
149    MacroExpansionFailed(&'a str, String),
150    /// The expander returned more than one expression.
151    MacroExpansionTrailingTokens(&'a str),
152    /// Macros expanded into one another past [`MAX_EXPANSION_DEPTH`].
153    MacroRecursionLimit(&'a str),
154    /// Terms, blocks or types nested past [`MAX_NESTING_DEPTH`].
155    NestingTooDeep,
156    /// An operator token after `fn` in an `impl` block that a type cannot
157    /// define. Carries the symbol that was written.
158    OperatorMethodUnknown(&'static str),
159    /// `@name` for a name that is not an attribute.
160    UnknownAttribute(&'a str),
161    /// An attribute with nothing after it to apply to.
162    AttributeWithoutItem,
163    /// `throw(...)`, the call spelling `throw` had before it was a statement.
164    ThrowCall,
165}
166
167impl ParserErr<'_> {
168    /// Stable, machine-readable identifier for this parser error, mirroring
169    /// `ErrType::kind` on the runtime side.
170    const fn kind(&self) -> &'static str {
171        match self {
172            ParserErr::UnexpectedEOF => "unexpected_eof",
173            ParserErr::UnknownToken => "unknown_token",
174            ParserErr::IntLiteralOutOfRange => "int_literal_out_of_range",
175            ParserErr::FloatLiteralOutOfRange => "float_literal_out_of_range",
176            ParserErr::FloatExponentMissingDigits => "float_exponent_missing_digits",
177            ParserErr::UnexpectedToken(..) | ParserErr::UnexpectedTokenStr(..) => {
178                "unexpected_token"
179            }
180            ParserErr::ArrayElementsMissingComma => "array_elements_missing_comma",
181            ParserErr::InlineConditionNoElseBlock => "inline_condition_no_else_block",
182            ParserErr::DivisionByZero => "division_by_zero",
183            ParserErr::ModuloByZero => "modulo_by_zero",
184            ParserErr::IntegerNegativeExponent => "integer_negative_exponent",
185            ParserErr::ShiftCountOutOfRange(_) => "shift_count_out_of_range",
186            ParserErr::ArgumentsMissingCommaSeparator => "arguments_missing_comma_separator",
187            ParserErr::TryBlockNoCatch => "try_block_no_catch",
188            ParserErr::MatchBlockNoNonWildcardArm => "match_block_no_non_wildcard_arm",
189            ParserErr::MatchBlockZeroArms => "match_block_zero_arms",
190            ParserErr::MatchArmMissingComma => "match_arm_missing_comma",
191            ParserErr::MatchWildcardAlternative => "match_wildcard_alternative",
192            ParserErr::NestedFunctionDeclaration => "nested_function_declaration",
193            ParserErr::LegacyNamespacedImport(_) => "legacy_namespaced_import",
194            ParserErr::ImportPathBadExtension => "import_path_bad_extension",
195            ParserErr::ImportNameNotIdentifier(_) => "import_name_not_identifier",
196            ParserErr::ImportAliasWithItems => "import_alias_with_items",
197            ParserErr::ImportItemsEmpty => "import_items_empty",
198            ParserErr::UnterminatedMacroRegion(_) => "unterminated_macro_region",
199            ParserErr::UnknownMacro(_) => "unknown_macro",
200            ParserErr::MacroExpansionFailed(..) => "macro_expansion_failed",
201            ParserErr::MacroExpansionTrailingTokens(_) => "macro_expansion_trailing_tokens",
202            ParserErr::MacroRecursionLimit(_) => "macro_recursion_limit",
203            ParserErr::NestingTooDeep => "nesting_too_deep",
204            ParserErr::OperatorMethodUnknown(_) => "operator_method_unknown",
205            ParserErr::UnknownAttribute(_) => "unknown_attribute",
206            ParserErr::AttributeWithoutItem => "attribute_without_item",
207            ParserErr::ThrowCall => "throw_call",
208        }
209    }
210}
211
212/// The parser error a refused token earns, so a literal the lexer read and
213/// rejected reports what was wrong with it.
214const fn lex_err(e: LexErr) -> ParserErr<'static> {
215    match e {
216        LexErr::UnknownToken => ParserErr::UnknownToken,
217        LexErr::IntOutOfRange => ParserErr::IntLiteralOutOfRange,
218        LexErr::FloatOutOfRange => ParserErr::FloatLiteralOutOfRange,
219        LexErr::FloatExponentMissingDigits => ParserErr::FloatExponentMissingDigits,
220    }
221}
222
223#[cold]
224#[inline(never)]
225fn throw_parser_error(src: &Source, Span { start, end }: Span, t: ParserErr) -> ! {
226    let kind = t.kind();
227    let err_message = match t {
228        ParserErr::UnexpectedEOF => "Unexpected EOF",
229        ParserErr::UnknownToken => "Unknown token",
230        ParserErr::IntLiteralOutOfRange => &format!(
231            "This number does not fit an {BLUE}{BOLD}int{RESET}, which holds {} to {}. A wider value goes in a {BLUE}{BOLD}float{RESET}",
232            i64::MIN,
233            i64::MAX
234        ),
235        ParserErr::FloatLiteralOutOfRange => &format!(
236            "This number does not fit a {BLUE}{BOLD}float{RESET}, which holds {:e} to {:e}",
237            f64::MIN,
238            f64::MAX
239        ),
240        ParserErr::FloatExponentMissingDigits => {
241            "This exponent has no digits after it. Write the power, as in 1e3 or 2.5e-1"
242        }
243        ParserErr::UnexpectedToken(expected, received, msg) => &format_args!(
244            "Expected {BLUE}{BOLD}{expected}{RESET}, but got {RED}{BOLD}{received}{RESET}. {msg}"
245        )
246        .to_string(),
247        ParserErr::UnexpectedTokenStr(expected, received, msg) => &format_args!(
248            "Expected {BLUE}{BOLD}{expected}{RESET}, but got {RED}{BOLD}{received}{RESET}. {msg}"
249        )
250        .to_string(),
251        ParserErr::ArrayElementsMissingComma => "Array elements must be separated by a comma",
252        ParserErr::InlineConditionNoElseBlock => "Inline conditions must have an else block",
253        ParserErr::DivisionByZero => "Division by zero",
254        ParserErr::ModuloByZero => "Modulo by zero",
255        ParserErr::IntegerNegativeExponent => "Integers cannot be raised to a negative exponent",
256        ParserErr::ShiftCountOutOfRange(count) => &format_args!(
257            "An {BLUE}{BOLD}int{RESET} is 64 bits wide, so it cannot be shifted by {RED}{BOLD}{count}{RESET}. The count must be between 0 and 63"
258        )
259        .to_string(),
260        ParserErr::ArgumentsMissingCommaSeparator => "Arguments must be separated by a comma",
261        ParserErr::TryBlockNoCatch => {
262            "A {BLUE}{BOLD}try{RESET} block must have at least one {BLUE}{BOLD}catch{RESET} block"
263        }
264        ParserErr::MatchBlockNoNonWildcardArm => &format!(
265            "{BLUE}{BOLD}Match blocks{RESET} must have {BOLD}at least one non-wildcard arm{RESET}"
266        ),
267        ParserErr::MatchBlockZeroArms => &format!(
268            "{BLUE}{BOLD}Match blocks{RESET} must have {BOLD}at least one arm{RESET}"
269        ),
270        ParserErr::MatchArmMissingComma => &format!(
271            "An arm written {BLUE}{BOLD}pattern => expression{RESET} ends with a {BOLD}','{RESET} before the next arm"
272        ),
273        ParserErr::MatchWildcardAlternative => &format!(
274            "{BLUE}{BOLD}_{RESET} stands alone as the last arm of a match, not as one of a pattern's {BOLD}|{RESET} alternatives"
275        ),
276        ParserErr::ThrowCall => &format!(
277            "{BLUE}{BOLD}throw{RESET} is a statement, not a function. Write {BLUE}{BOLD}throw \"kind\";{RESET} with no parentheses"
278        ),
279        ParserErr::NestedFunctionDeclaration => {
280            "Functions declare at the top level of a file, not inside a block. Move this declaration out of the enclosing block"
281        }
282        ParserErr::LegacyNamespacedImport(path) => &format!(
283            "This import form was removed. Write {BLUE}{BOLD}import \"{path}\";{RESET} instead: a quoted path with no extension imports the library from the shipped library directory"
284        ),
285        ParserErr::ImportPathBadExtension => &format!(
286            "An import path either ends in {BLUE}{BOLD}.cdl{RESET} (a file import) or has no extension (a library import from the shipped library directory)"
287        ),
288        ParserErr::ImportNameNotIdentifier(segment) => &format!(
289            "An import binds the module under the last segment of its path, and {RED}{BOLD}{segment}{RESET} is not a name. Give it one with {BLUE}{BOLD}as{RESET}: {BLUE}{BOLD}import \"...\" as name;{RESET}"
290        ),
291        ParserErr::ImportAliasWithItems => &format!(
292            "An import either binds the module under a name ({BLUE}{BOLD}as name{RESET}) or names the items it brings in ({BLUE}{BOLD}{{ a, b }}{RESET}), not both. Write two imports of the same path"
293        ),
294        ParserErr::ImportItemsEmpty => &format!(
295            "Name at least one item between the braces, or drop them to bind the module: {BLUE}{BOLD}import \"std/json\";{RESET}"
296        ),
297        ParserErr::UnterminatedMacroRegion(name) => &format!(
298            "This {BLUE}{BOLD}{name}!{RESET} region is never closed: the file ends before its ')'"
299        ),
300        ParserErr::UnknownMacro(name) => &format!(
301            "No macro named {RED}{BOLD}{name}!{RESET} is registered. Macros come from the program embedding candela, which decides what each one means"
302        ),
303        ParserErr::MacroExpansionFailed(name, message) => {
304            &format!("The {BLUE}{BOLD}{name}!{RESET} macro rejected this region: {message}")
305        }
306        ParserErr::MacroExpansionTrailingTokens(name) => &format!(
307            "The {BLUE}{BOLD}{name}!{RESET} macro expanded to more than one expression. A macro stands where an expression stands, so it expands to exactly one"
308        ),
309        ParserErr::MacroRecursionLimit(name) => &format!(
310            "Expanding this macro reached {RED}{BOLD}{name}!{RESET} more than {MAX_EXPANSION_DEPTH} macros deep. An expansion may use another macro, but the chain has to end"
311        ),
312        ParserErr::NestingTooDeep => &format!(
313            "This is nested more than {MAX_NESTING_DEPTH} levels deep. Move the inner part into a variable or a function"
314        ),
315        ParserErr::OperatorMethodUnknown(symbol) => &format!(
316            "A type does not define {RED}{BOLD}{symbol}{RESET}. The operators it defines are {BLUE}{BOLD}{OPERATOR_SYMBOLS}{RESET}{}",
317            match symbol {
318                "!=" => ", and != comes from the == it defines",
319                ">" => ", and > comes from the < it defines",
320                ">=" => ", and >= comes from the <= it defines",
321                _ => "",
322            }
323        ),
324        ParserErr::UnknownAttribute(name) => &format!(
325            "There is no attribute named {RED}{BOLD}{name}{RESET}. The attribute candela has is {BLUE}{BOLD}@cfg(...){RESET}"
326        ),
327        ParserErr::AttributeWithoutItem => {
328            "An attribute applies to the declaration or statement after it, and nothing follows this one"
329        }
330    };
331    if crate::errors::diagnostics_enabled() {
332        crate::errors::emit_diagnostic(
333            src.filename.as_str(),
334            (start as usize)..(end as usize),
335            crate::errors::strip_ansi(err_message),
336            kind,
337        );
338    }
339    let mut out = candela_vm::captured_output::stderr();
340    let _ = writeln!(out, "{RED}CANDELA ERROR{RESET}");
341    let report = Report::build(
342        ReportKind::Error,
343        (src.filename.as_str(), (start as usize)..(end as usize)),
344    )
345    .with_label(
346        Label::new((src.filename.as_str(), (start as usize)..(end as usize)))
347            .with_message(err_message)
348            .with_color(Color::Red),
349    )
350    .finish();
351
352    // A stderr that refuses the report costs the report. Raising here would
353    // replace the error in the source with an unrelated one about the stream.
354    let _ = report.write(
355        (
356            src.filename.as_str(),
357            ariadne::Source::from(src.contents.as_str()),
358        ),
359        &mut out,
360    );
361
362    #[cfg(debug_assertions)]
363    panic!();
364
365    #[cfg(not(any(debug_assertions, target_arch = "wasm32", feature = "embed")))]
366    std::process::exit(1);
367
368    #[cfg(target_arch = "wasm32")]
369    wasm_bindgen::throw_str("candela_error");
370
371    #[cfg(all(feature = "embed", not(debug_assertions)))]
372    panic!();
373}
374
375impl<'a> Parser<'a> {
376    /// The span the file sees for a token this parser just read; see
377    /// [`Parser::span_override`].
378    #[inline(always)]
379    const fn placed(&self, span: Span) -> Span {
380        match self.span_override {
381            Some(macro_span) => macro_span,
382            None => span,
383        }
384    }
385    #[inline(always)]
386    fn eof_span(&self) -> Span {
387        let end = self.ctx.src.contents.len();
388        self.placed((end, end).into())
389    }
390    #[cold]
391    #[inline(never)]
392    fn error(&self, span: Span, error: ParserErr) -> ! {
393        throw_parser_error(self.ctx.src, span, error)
394    }
395    /// Opens one nesting level for the construct starting at `span`, or raises
396    /// [`ParserErr::NestingTooDeep`] at it. Pair with [`Parser::leave`].
397    #[inline(always)]
398    fn enter(&mut self, span: Span) {
399        if self.nesting >= MAX_NESTING_DEPTH {
400            cold_path();
401            self.error(span, ParserErr::NestingTooDeep);
402        }
403        self.nesting += 1;
404    }
405    #[inline(always)]
406    const fn leave(&mut self) {
407        self.nesting -= 1;
408    }
409    #[inline(always)]
410    fn next_token(&mut self) -> (Token<'a>, Span) {
411        if let Some(span) = self.pending_gt.take() {
412            let span = self.placed(span);
413            self.last_token_end = span.end as usize;
414            return (Token::OpSup, span);
415        }
416        let t = self.input.next().unwrap_or_else(
417            #[cold]
418            || {
419                self.error(self.eof_span(), ParserErr::UnexpectedEOF);
420            },
421        );
422        let span = self.placed((t.1.start, t.1.end).into());
423        self.last_token_end = span.end as usize;
424        (
425            t.0.unwrap_or_else(
426                #[cold]
427                |e| self.error(span, lex_err(e)),
428            ),
429            span,
430        )
431    }
432    #[inline(always)]
433    fn peek_token(&mut self) -> Token<'a> {
434        if self.pending_gt.is_some() {
435            return Token::OpSup;
436        }
437        let Some((t, start, end)) = self
438            .input
439            .peek()
440            .map(|(t, span)| (*t, span.start, span.end))
441        else {
442            self.error(self.eof_span(), ParserErr::UnexpectedEOF);
443        };
444        t.unwrap_or_else(
445            #[cold]
446            |e| self.error(self.placed((start, end).into()), lex_err(e)),
447        )
448    }
449    #[inline(always)]
450    fn peek_token_span(&mut self) -> Span {
451        if let Some(span) = self.pending_gt {
452            return self.placed(span);
453        }
454        let Some((_, start, end)) = self
455            .input
456            .peek()
457            .map(|(t, span)| (*t, span.start, span.end))
458        else {
459            self.error(self.eof_span(), ParserErr::UnexpectedEOF);
460        };
461        self.placed(Span {
462            start: start as u32,
463            end: end as u32,
464        })
465    }
466    #[inline(always)]
467    fn peek_token_opt(&mut self) -> Option<Token<'a>> {
468        if self.pending_gt.is_some() {
469            return Some(Token::OpSup);
470        }
471        let (t, start, end) = self
472            .input
473            .peek()
474            .map(|(t, span)| (*t, span.start, span.end))?;
475        Some(t.unwrap_or_else(
476            #[cold]
477            |e| self.error(self.placed((start, end).into()), lex_err(e)),
478        ))
479    }
480    #[inline(always)]
481    fn peek_token_opt_span(&mut self) -> Option<Span> {
482        if let Some(span) = self.pending_gt {
483            return Some(self.placed(span));
484        }
485        let span = self
486            .input
487            .peek()
488            .map(|x| (x.1.start as u32, x.1.end as u32).into())?;
489        Some(self.placed(span))
490    }
491    #[inline(always)]
492    fn next_token_expect(&mut self, expected: Token, msg: &'static str) -> Span {
493        let (next_token, span) = self.next_token();
494        if next_token != expected {
495            self.error(span, ParserErr::UnexpectedToken(expected, next_token, msg));
496        }
497        span
498    }
499    /// Reads the `>` that closes a type-argument or type-parameter list.
500    ///
501    /// The two closing `>` of `Box<Box<int>>` lex as one `>>`, the shift
502    /// operator, because the lexer reads the longest token it can and knows
503    /// nothing of where it stands. The inner list takes the first half here and
504    /// leaves the second for the list around it.
505    fn next_type_list_close(&mut self, msg: &'static str) -> Span {
506        if self.peek_token_opt() == Some(Token::OpShr) {
507            let (_, span) = self.next_token();
508            let middle = span.start + 1;
509            self.pending_gt = Some((middle, span.end).into());
510            self.last_token_end = middle as usize;
511            return (span.start, middle).into();
512        }
513        self.next_token_expect(Token::OpSup, msg)
514    }
515    #[inline(always)]
516    fn next_token_expect_closer(
517        &mut self,
518        opener: Token,
519        opener_span: Span,
520        expected_closer: Token,
521    ) -> u32 {
522        if let Some(t) = self.peek_token_opt() {
523            if t == expected_closer {
524                self.next_token().1.end
525            } else {
526                let span = self.peek_token_span();
527                error_unclosed_delimiter(
528                    self,
529                    opener,
530                    opener_span,
531                    expected_closer,
532                    Some((t, span)),
533                );
534            }
535        } else {
536            error_unclosed_delimiter(self, opener, opener_span, expected_closer, None);
537        }
538    }
539    #[inline(never)]
540    #[cold]
541    pub fn throw_parser_err<'b, F: Fn() -> Report<'b, (&'b str, core::ops::Range<usize>)>>(
542        &self,
543        report: F,
544        span: Span,
545        message: &str,
546        code: &str,
547    ) -> ! {
548        if crate::errors::diagnostics_enabled() {
549            crate::errors::emit_diagnostic(
550                self.ctx.src.filename.as_str(),
551                (span.start as usize)..(span.end as usize),
552                crate::errors::strip_ansi(message),
553                code,
554            );
555        }
556        let report = report();
557
558        let _ = report.write(
559            (
560                self.ctx.src.filename.as_str(),
561                ariadne::Source::from(self.ctx.src.contents.as_str()),
562            ),
563            candela_vm::captured_output::stderr(),
564        );
565
566        crash();
567    }
568}
569
570// Call after DoubleColon is skipped
571// Returns end
572fn parse_namespace(parser: &mut Parser<'_>, initial: SmolStr) -> (Box<[SmolStr]>, u32) {
573    let mut namespace: Vec<SmolStr> = Vec::with_capacity(2);
574    namespace.push(initial);
575    let end = extend_namespace(parser, &mut namespace);
576    (Box::from(namespace), end)
577}
578
579/// Reads the segments behind a `::` that is already consumed, appending each to
580/// `namespace` and answering where the last one ends. A path whose middle
581/// segment carries type arguments (`g::Slot<int>::Filled`) comes through here
582/// twice, once for each side of the list.
583fn extend_namespace(parser: &mut Parser<'_>, namespace: &mut Vec<SmolStr>) -> u32 {
584    let mut end: u32;
585    loop {
586        let (next_token, span) = parser.next_token();
587        if let Token::Identifier(i) = next_token {
588            namespace.push(SmolStr::new(i));
589            end = span.end;
590        } else {
591            cold_path();
592            parser.error(
593                span,
594                ParserErr::UnexpectedToken(
595                    Token::Identifier(""),
596                    next_token,
597                    "Wrong namespace syntax",
598                ),
599            );
600        }
601        if parser.peek_token() == Token::DoubleColon {
602            // Consume the separator so the next turn of the loop reads the
603            // segment behind it. Without this the loop takes the `::` itself
604            // for a segment, which caps a path at two of them.
605            parser.next_token();
606            continue;
607        }
608        return end;
609    }
610}
611
612// Must be called after LParen is skipped
613fn parse_args(parser: &mut Parser<'_>) -> (Box<[Expr]>, Box<[Span]>, u32) {
614    let mut args = Vec::with_capacity(4);
615    let mut arg_markers: Vec<Span> = Vec::with_capacity(4);
616    loop {
617        if parser.peek_token() == Token::RParen {
618            let end = parser.next_token().1.end;
619            return (Box::from(args), Box::from(arg_markers), end);
620        }
621        let arg_start: u32 = parser.peek_token_span().start;
622        args.push(parse_expr(parser));
623        arg_markers.push((arg_start, parser.peek_token_span().start).into());
624        if parser.peek_token() == Token::Comma {
625            parser.next_token();
626        } else if !(parser.peek_token() == Token::RParen) {
627            cold_path();
628            let span = parser.peek_token_span();
629            parser.error(span, ParserErr::ArgumentsMissingCommaSeparator);
630        }
631    }
632}
633
634/// Expands one `name!( ... )` invocation and parses what the expander returned
635/// in its place. `span` covers the whole invocation.
636fn expand_macro(parser: &Parser<'_>, region: MacroToken<'_>, span: Span) -> Expr {
637    let Some(body) = region.body else {
638        cold_path();
639        parser.error(span, ParserErr::UnterminatedMacroRegion(region.name));
640    };
641    // Code a `@cfg` drops is never compiled, so what the macro would expand
642    // to does not matter, and the expander may not exist in this build.
643    if parser.inactive > 0 {
644        return Expr::Null;
645    }
646    // Held until this expansion is parsed, so a macro reached through a chain
647    // of other macros counts the whole chain. At the cap `span` is already the
648    // outermost invocation, which is the one the reader wrote.
649    let Some(_level) = macros::Depth::enter() else {
650        parser.error(span, ParserErr::MacroRecursionLimit(region.name));
651    };
652    match macros::expand(region.name, body) {
653        Expansion::Text(expansion) => parse_expansion(parser, region.name, &expansion, span),
654        Expansion::Null => Expr::Null,
655        Expansion::Unknown => {
656            cold_path();
657            parser.error(span, ParserErr::UnknownMacro(region.name));
658        }
659        Expansion::Failed(error) => {
660            cold_path();
661            // `name!(` is the name plus two characters, all ASCII, so the body
662            // starts that many bytes into the invocation.
663            let body_start = span.start + region.name.len() as u32 + 2;
664            let error_span = match error.offset {
665                Some(offset) => {
666                    let start = body_start + (offset.min(body.len()) as u32);
667                    (start, (start + 1).min(body_start + body.len() as u32)).into()
668                }
669                None => span,
670            };
671            parser.error(
672                error_span,
673                ParserErr::MacroExpansionFailed(region.name, error.message),
674            );
675        }
676    }
677}
678
679/// Parses `expansion` as a single expression, placed at the macro invocation
680/// `span`. The expanded text is not in any file, so every span it produces is
681/// the invocation's own; a program compiled from it reports against the macro
682/// the reader wrote, not against text they cannot see.
683fn parse_expansion(parser: &Parser<'_>, macro_name: &str, expansion: &str, span: Span) -> Expr {
684    let mut sub = Parser {
685        input: Token::lexer(expansion).spanned().peekable(),
686        ctx: ParserCtx {
687            src: parser.ctx.src,
688            cfg: parser.ctx.cfg,
689        },
690        last_token_end: span.end as usize,
691        span_override: Some(span),
692        nesting: parser.nesting,
693        pending_gt: None,
694        inactive: parser.inactive,
695    };
696    let expr = parse_expr(&mut sub);
697    if sub.peek_token_opt().is_some() {
698        cold_path();
699        sub.error(span, ParserErr::MacroExpansionTrailingTokens(macro_name));
700    }
701    expr
702}
703
704/// The kinds `src` names in `catch "kind"` clauses, and its other strings.
705///
706/// Each kind comes with where it is written. A kind no other string literal
707/// spells and no built-in error carries is one nothing in the program can
708/// raise.
709#[must_use]
710pub fn catch_kinds_and_strings(src: &str) -> (Vec<(SmolStr, Span)>, Vec<SmolStr>) {
711    let mut kinds = Vec::new();
712    let mut strings = Vec::new();
713    let mut after_catch = false;
714    for (token, range) in Token::lexer(src).spanned() {
715        match token {
716            Ok(Token::String(s)) if after_catch => {
717                kinds.push((
718                    lexer::parse_string(s),
719                    (range.start as u32, range.end as u32).into(),
720                ));
721            }
722            Ok(Token::String(s)) => strings.push(lexer::parse_string(s)),
723            _ => {}
724        }
725        after_catch = matches!(token, Ok(Token::Catch));
726    }
727    (kinds, strings)
728}
729
730fn parse_statement(parser: &mut Parser<'_>) -> Option<Expr> {
731    let mut token = parser.peek_token_opt()?;
732    // A statement a `@cfg` turns off is parsed and dropped, and the statement
733    // after it stands in its place.
734    while token == Token::At {
735        let active = parse_attributes(parser);
736        if matches!(parser.peek_token_opt(), None | Some(Token::RBrace)) {
737            cold_path();
738            let span = parser
739                .peek_token_opt_span()
740                .unwrap_or_else(|| parser.eof_span());
741            parser.error(span, ParserErr::AttributeWithoutItem);
742        }
743        if active {
744            return parse_statement(parser);
745        }
746        parser.inactive += 1;
747        let _ = parse_statement(parser);
748        parser.inactive -= 1;
749        token = parser.peek_token_opt()?;
750    }
751    let t_span = parser.peek_token_span();
752    match token {
753        Token::If => Some(parse_condition_block(parser, t_span.start)),
754        Token::While => Some(parse_while_block(parser)),
755        Token::For => Some(parse_for_loop(parser)),
756        Token::Match => Some(parse_match(parser)),
757        Token::LBrace => Some(parse_eval_block(parser)),
758        // Only `parse_file` accepts a function declaration. Reaching one here
759        // means it was written inside a block, where it would otherwise parse
760        // and then be dropped without ever being registered.
761        Token::Function => {
762            cold_path();
763            parser.error(t_span, ParserErr::NestedFunctionDeclaration);
764        }
765        Token::Loop => Some(parse_loop_block(parser)),
766        Token::Try => Some(parse_try_catch_block(parser)),
767        Token::Struct => Some(parse_struct_declare(parser)),
768        Token::Enum => Some(parse_enum_declare(parser)),
769        Token::RBrace => None,
770        t => Some(parse_line(parser, t)),
771    }
772}
773
774fn parse_var_declare(parser: &mut Parser<'_>) -> Expr {
775    let (t, _) = parser.next_token();
776    debug_assert_eq!(t, Token::Let);
777    let (t, span) = parser.next_token();
778    let var_name = if let Token::Identifier(id) = t {
779        SmolStr::new(id)
780    } else {
781        cold_path();
782        parser.error(
783            span,
784            ParserErr::UnexpectedToken(
785                Token::Identifier(""),
786                t,
787                "Variable names must be identifiers.",
788            ),
789        );
790    };
791    // `let name: Type = value;` declares the variable's type.
792    let annotation = if parser.peek_token_opt() == Some(Token::Colon) {
793        parser.next_token();
794        let start = parser.peek_token_span().start;
795        let ty = parse_type(parser);
796        Some((ty, Span::from((start, parser.last_token_end as u32))))
797    } else {
798        None
799    };
800    parser.next_token_expect(
801        Token::Equals,
802        "Variable declarations need a '=' to separate the name from the value.",
803    );
804    let value_start = parser.peek_token_span().start;
805    let var_value = parse_expr(parser);
806    let value_span = Span::from((value_start, parser.last_token_end as u32));
807    Expr::VarDeclare(
808        var_name,
809        Box::new(var_value),
810        annotation.map(|(ty, span)| {
811            Box::new(LetAnnotation {
812                ty,
813                span,
814                value_span,
815            })
816        }),
817    )
818}
819
820fn parse_var_assign(input: &mut Parser<'_>, e: Expr, e_start: u32) -> Expr {
821    let (t, _) = input.next_token();
822    debug_assert_eq!(t, Token::Equals);
823    let e_end = input.peek_token_span().end;
824    let v_start = input.peek_token_span().start;
825    let v = parse_expr(input);
826    let v_end = input.peek_token_span().start;
827    var_assign(e, v, (e_start, e_end).into(), (v_start, v_end).into())
828}
829
830fn parse_op_var_assign(input: &mut Parser<'_>, e: Expr, e_start: u32, op: Token<'_>) -> Expr {
831    let operand_end = input.last_token_end as u32;
832    let (t, _) = input.next_token();
833    debug_assert_eq!(t, op);
834    let e_end = input.peek_token_span().end;
835    let v_start = input.peek_token_span().start;
836    let v = parse_expr(input);
837    let v_end = input.last_token_end as u32;
838    let op = match op {
839        Token::AssignOpAdd => Token::OpAdd,
840        Token::AssignOpSub => Token::OpSub,
841        Token::AssignOpMul => Token::OpMul,
842        Token::AssignOpDiv => Token::OpDiv,
843        Token::AssignOpMod => Token::OpMod,
844        Token::AssignOpPow => Token::OpPow,
845        Token::AssignOpBitAnd => Token::OpBitAnd,
846        Token::AssignOpBitOr => Token::Pipe,
847        Token::AssignOpBitXor => Token::OpBitXor,
848        Token::AssignOpShl => Token::OpShl,
849        Token::AssignOpShr => Token::OpShr,
850        _ => unsafe { unreachable_unchecked() },
851    };
852    var_assign(
853        e.clone(),
854        add_op(
855            input,
856            op,
857            e,
858            v,
859            (e_start, operand_end).into(),
860            (v_start, v_end).into(),
861        ),
862        (e_start, e_end).into(),
863        (v_start, v_end).into(),
864    )
865}
866
867fn parse_return(input: &mut Parser<'_>) -> Expr {
868    let (t, _) = input.next_token();
869    debug_assert_eq!(t, Token::Return);
870    if input.peek_token_opt() == Some(Token::SemiColon) {
871        Expr::ReturnVal(Box::new(None))
872    } else {
873        let e = parse_expr(input);
874        Expr::ReturnVal(Box::new(Some(e)))
875    }
876}
877
878/// `throw kind;`: raises an error of kind `kind`, a string. The statement is
879/// kept as a call of `throw`, which is a keyword and so names nothing a
880/// program can declare.
881fn parse_throw(input: &mut Parser<'_>) -> Expr {
882    let (t, throw_span) = input.next_token();
883    debug_assert_eq!(t, Token::Throw);
884    if input.peek_token_opt() == Some(Token::LParen) {
885        cold_path();
886        input.error(throw_span, ParserErr::ThrowCall);
887    }
888    let start = input.peek_token_span().start;
889    let kind = parse_expr(input);
890    let end = input.last_token_end as u32;
891    Expr::FunctionCall(
892        Box::new([kind]),
893        Box::from([SmolStr::new_static("throw")]),
894        throw_span.extend((start, end).into()),
895        Box::from([Span::from((start, end))]),
896        Box::from([]),
897    )
898}
899
900fn parse_line(input: &mut Parser<'_>, peek: Token<'_>) -> Expr {
901    let line_code = match peek {
902        Token::Let => parse_var_declare(input),
903        Token::Return => parse_return(input),
904        Token::Throw => parse_throw(input),
905        Token::Break => {
906            input.next_token();
907            Expr::Break
908        }
909        Token::Continue => {
910            input.next_token();
911            Expr::Continue
912        }
913        _ => {
914            let e_start = input.peek_token_span().start;
915            let e = parse_expr(input);
916            let peek_token = input.peek_token_opt();
917            match peek_token {
918                Some(Token::Equals) => parse_var_assign(input, e, e_start),
919                Some(
920                    op @ (Token::AssignOpAdd
921                    | Token::AssignOpSub
922                    | Token::AssignOpMul
923                    | Token::AssignOpDiv
924                    | Token::AssignOpMod
925                    | Token::AssignOpPow
926                    | Token::AssignOpBitAnd
927                    | Token::AssignOpBitOr
928                    | Token::AssignOpBitXor
929                    | Token::AssignOpShl
930                    | Token::AssignOpShr),
931                ) => parse_op_var_assign(input, e, e_start, op),
932                _ => e,
933            }
934        }
935    };
936    if input.peek_token_opt() != Some(Token::SemiColon) {
937        error_missing_semicolon(input);
938    }
939    input.next_token();
940    // input.next_token_expect(Token::SemiColon, "Lines must end with a ';'.");
941    line_code
942}
943
944#[cold]
945#[inline(never)]
946fn error_unclosed_delimiter(
947    parser: &Parser<'_>,
948    opener_token: Token,
949    opener_span: Span,
950    expected_closer_token: Token,
951    end: Option<(Token, Span)>,
952) -> ! {
953    let message = if let Some((actual_closer_token, _)) = end {
954        format!(
955            "This {opener_token} is never closed: expected {expected_closer_token} but found {actual_closer_token}"
956        )
957    } else {
958        format!(
959            "This {opener_token} is never closed: expected {expected_closer_token} but the file ends here"
960        )
961    };
962    parser.throw_parser_err(
963        || {
964            let mut report = Report::build(
965                ariadne::ReportKind::Error,
966                (parser.ctx.src.filename.as_str(), opener_span.into()),
967            )
968            .with_message("Unclosed delimiter")
969            .with_label(
970                Label::new((parser.ctx.src.filename.as_str(), opener_span.into()))
971                    .with_message(format_args!("This {opener_token} is never closed"))
972                    .with_color(ariadne::Color::Red),
973            );
974
975            if let Some((actual_closer_token, actual_closer_token_span)) = end {
976                report = report.with_label(
977                    Label::new((
978                        parser.ctx.src.filename.as_str(),
979                        actual_closer_token_span.into(),
980                    ))
981                    .with_message(format_args!(
982                        "Expected {expected_closer_token} but found {actual_closer_token}"
983                    ))
984                    .with_color(ariadne::Color::Red),
985                );
986            } else {
987                report = report
988                    .with_label(
989                        Label::new((parser.ctx.src.filename.as_str(), parser.eof_span().into()))
990                            .with_message(format_args!(
991                                "Expected {expected_closer_token} but the file ends here"
992                            ))
993                            .with_color(ariadne::Color::Red),
994                    )
995                    .with_help(format_args!(
996                        "Add a {} here to close it",
997                        blue(expected_closer_token)
998                    ));
999            }
1000
1001            report.finish()
1002        },
1003        opener_span,
1004        &message,
1005        "unclosed_delimiter",
1006    )
1007}
1008
1009#[cold]
1010#[inline(never)]
1011fn error_missing_semicolon(parser: &Parser<'_>) -> ! {
1012    let span: Span = (parser.last_token_end as u32, parser.last_token_end as u32).into();
1013    parser.throw_parser_err(
1014        || {
1015            Report::build(
1016                ariadne::ReportKind::Error,
1017                (
1018                    parser.ctx.src.filename.as_str(),
1019                    (parser.last_token_end..parser.last_token_end),
1020                ),
1021            )
1022            .with_message("Missing semicolon")
1023            .with_label(
1024                Label::new((
1025                    parser.ctx.src.filename.as_str(),
1026                    (parser.last_token_end..parser.last_token_end),
1027                ))
1028                .with_message(format_args!("Add a {} here", blue(';')))
1029                .with_color(ariadne::Color::Blue),
1030            )
1031            .with_help("All statements end with a ';'")
1032            .finish()
1033        },
1034        span,
1035        "Missing semicolon",
1036        "missing_semicolon",
1037    )
1038}
1039
1040fn parse_code(input: &mut Parser<'_>) -> Vec<Expr> {
1041    let mut output: Vec<Expr> = Vec::with_capacity(4);
1042    while let Some(e) = parse_statement(input) {
1043        output.push(e);
1044    }
1045    output
1046}
1047
1048fn parse_file_import(parser: &mut Parser<'_>) -> Expr {
1049    let (t, Span { start, end: _ }) = parser.next_token();
1050    debug_assert_eq!(t, Token::Import);
1051    let (next_token, span) = parser.next_token();
1052    // One import form: a quoted path.
1053    //   * A path ending in `.cdl` is a file import, resolved next to the
1054    //     importing file (with the shipped library directory as fallback).
1055    //   * A path with no extension is a library import: the resolver appends
1056    //     `.cdl` and looks it up in the shipped library directory only
1057    //     (`import "std/string";` -> `std/string.cdl`).
1058    let (path, is_logical, mut end) = if let Token::String(s) = next_token {
1059        let raw = parse_string(s);
1060        if raw.ends_with(".cdl") {
1061            (raw, false, span.end)
1062        } else {
1063            let last_segment = raw.rsplit(['/', '\\']).next().unwrap_or(raw.as_str());
1064            if last_segment.contains('.') {
1065                cold_path();
1066                parser.error(span, ParserErr::ImportPathBadExtension);
1067            }
1068            let mut with_ext = String::with_capacity(raw.len() + 4);
1069            with_ext.push_str(raw.as_str());
1070            with_ext.push_str(".cdl");
1071            (SmolStr::new(&with_ext), true, span.end)
1072        }
1073    } else if let Token::Identifier(first) = next_token {
1074        // The old namespaced form (`import std::string;`) parses far enough to
1075        // suggest the exact replacement, then errors.
1076        let mut segments = String::from(first);
1077        let mut end = span.end;
1078        while parser.peek_token_opt() == Some(Token::DoubleColon) {
1079            parser.next_token();
1080            let (seg_token, seg_span) = parser.next_token();
1081            if let Token::Identifier(seg) = seg_token {
1082                segments.push('/');
1083                segments.push_str(seg);
1084                end = seg_span.end;
1085            } else {
1086                break;
1087            }
1088        }
1089        cold_path();
1090        parser.error(
1091            (span.start, end).into(),
1092            ParserErr::LegacyNamespacedImport(segments),
1093        );
1094    } else {
1095        cold_path();
1096        parser.error(
1097            span,
1098            ParserErr::UnexpectedToken(
1099                Token::String(""),
1100                next_token,
1101                "An import is a quoted path: import \"./local.cdl\"; for a file, import \"std/string\"; for a library.",
1102            ),
1103        );
1104    };
1105    let mut binding = if parser.peek_token_opt() == Some(Token::As) {
1106        parser.next_token();
1107        let (next_token, span) = parser.next_token();
1108        let alias = if let Token::Identifier(id) = next_token {
1109            SmolStr::new(id)
1110        } else {
1111            cold_path();
1112            parser.error(
1113                span,
1114                ParserErr::UnexpectedToken(
1115                    Token::Identifier(""),
1116                    next_token,
1117                    "Module aliases must be identifiers.",
1118                ),
1119            );
1120        };
1121        end = span.end;
1122        Some(ImportBinding::Namespace(alias))
1123    } else {
1124        None
1125    };
1126    if parser.peek_token_opt() == Some(Token::LBrace) {
1127        let brace = parser.peek_token_span();
1128        if binding.is_some() {
1129            cold_path();
1130            parser.error(brace, ParserErr::ImportAliasWithItems);
1131        }
1132        parser.next_token();
1133        let mut items: Vec<(SmolStr, Span)> = Vec::new();
1134        loop {
1135            let (token, span) = parser.next_token();
1136            match token {
1137                Token::RBrace => {
1138                    end = span.end;
1139                    break;
1140                }
1141                Token::Identifier(name) => {
1142                    items.push((SmolStr::new(name), span));
1143                    match parser.next_token() {
1144                        (Token::Comma, _) => {}
1145                        (Token::RBrace, span) => {
1146                            end = span.end;
1147                            break;
1148                        }
1149                        (other, span) => {
1150                            cold_path();
1151                            parser.error(
1152                                span,
1153                                ParserErr::UnexpectedToken(
1154                                    Token::RBrace,
1155                                    other,
1156                                    "Items in an import are names separated by commas: import \"std/assert\" { eq, ne };",
1157                                ),
1158                            );
1159                        }
1160                    }
1161                }
1162                other => {
1163                    cold_path();
1164                    parser.error(
1165                        span,
1166                        ParserErr::UnexpectedToken(
1167                            Token::Identifier(""),
1168                            other,
1169                            "Items in an import are names separated by commas: import \"std/assert\" { eq, ne };",
1170                        ),
1171                    );
1172                }
1173            }
1174        }
1175        if items.is_empty() {
1176            cold_path();
1177            parser.error((brace.start, end).into(), ParserErr::ImportItemsEmpty);
1178        }
1179        binding = Some(ImportBinding::Items(items.into_boxed_slice()));
1180    }
1181    // With neither, the module is bound under the last segment of its path:
1182    // `std/json` binds `json`, `./geometry.cdl` binds `geometry`.
1183    let binding = binding.unwrap_or_else(|| {
1184        let segment = module_name(&path);
1185        if !is_identifier(segment) {
1186            cold_path();
1187            parser.error(
1188                (start, end).into(),
1189                ParserErr::ImportNameNotIdentifier(segment.to_owned()),
1190            );
1191        }
1192        ImportBinding::Namespace(SmolStr::new(segment))
1193    });
1194    parser.next_token_expect(
1195        Token::SemiColon,
1196        "Import statements must end with a semicolon",
1197    );
1198    Expr::ImportFile(path, binding, is_logical, (start, end).into())
1199}
1200
1201/// The name an import binds its module under when it says no other: the last
1202/// segment of the path, without the `.cdl` the file name ends in.
1203#[must_use]
1204pub fn module_name(path: &str) -> &str {
1205    let last = path.rsplit(['/', '\\']).next().unwrap_or(path);
1206    last.strip_suffix(".cdl").unwrap_or(last)
1207}
1208
1209/// Whether `text` is one name a program can write: a single identifier token,
1210/// so neither a keyword nor something with a `-` in it.
1211fn is_identifier(text: &str) -> bool {
1212    let mut lexer = Token::lexer(text);
1213    matches!(lexer.next(), Some(Ok(Token::Identifier(_)))) && lexer.next().is_none()
1214}
1215
1216fn parse_type(parser: &mut Parser<'_>) -> TypeExpr {
1217    let span = parser.peek_token_span();
1218    parser.enter(span);
1219    let t = parse_type_inner(parser);
1220    parser.leave();
1221    t
1222}
1223
1224fn parse_type_inner(parser: &mut Parser<'_>) -> TypeExpr {
1225    let t = parse_atomic_type(parser);
1226    if parser.peek_token() == Token::Pipe {
1227        let mut poly = Vec::with_capacity(2);
1228        poly.push(t);
1229        while parser.peek_token() == Token::Pipe {
1230            parser.next_token();
1231            poly.push(parse_atomic_type(parser));
1232        }
1233        TypeExpr::Union(poly.into_boxed_slice())
1234    } else {
1235        t
1236    }
1237}
1238
1239fn parse_atomic_type(parser: &mut Parser<'_>) -> TypeExpr {
1240    let (next_token, span) = parser.next_token();
1241    let mut t = if next_token == Token::Function {
1242        // `fn(A, B) -> R`: a position that holds a function of that shape. The
1243        // return type runs to the end of the type, so the `[]` in
1244        // `fn(int) -> int[]` belongs to it; parenthesise the whole type to make
1245        // a list of functions.
1246        parser.next_token_expect(
1247            Token::LParen,
1248            "A function type needs a parameter list: fn(int, int) -> int",
1249        );
1250        let mut args: Vec<TypeExpr> = Vec::new();
1251        while parser.peek_token() != Token::RParen {
1252            args.push(parse_type(parser));
1253            if parser.peek_token() == Token::Comma {
1254                parser.next_token();
1255            } else {
1256                break;
1257            }
1258        }
1259        parser.next_token_expect(Token::RParen, "Unmatched '(' in a function type");
1260        let return_type = if parser.peek_token() == Token::Arrow {
1261            parser.next_token();
1262            Some(parse_type(parser))
1263        } else {
1264            None
1265        };
1266        TypeExpr::Fn(Box::new(FnTypeExpr {
1267            args: args.into_boxed_slice(),
1268            return_type,
1269            span: (span.start, parser.last_token_end as u32).into(),
1270        }))
1271    } else if next_token == Token::LParen {
1272        // A parenthesised type, which is what puts a function type under a
1273        // list suffix: `(fn(int) -> int)[]`.
1274        let inner = parse_type(parser);
1275        parser.next_token_expect(Token::RParen, "Unmatched '(' in a type");
1276        inner
1277    } else if next_token == Token::LBrace {
1278        let key_t = parse_type(parser);
1279        parser.next_token_expect(
1280            Token::Colon,
1281            "A colon must separate key and value types in map types",
1282        );
1283        let value_t = parse_type(parser);
1284        parser.next_token_expect(Token::RBrace, "Unmatched '{'");
1285        TypeExpr::Map(Box::new(key_t), Box::new(value_t))
1286    } else if let Token::Identifier(i) = next_token {
1287        if parser.peek_token() == Token::DoubleColon {
1288            parser.next_token();
1289            let (path, end) = parse_namespace(parser, SmolStr::new(i));
1290            // A generic type a module declares carries its arguments on the
1291            // name at the end of the path, `g::Slot<int>`, the same place a
1292            // name written on its own carries them. What is left of the path
1293            // is the module it comes from.
1294            if parser.peek_token() == Token::OpInf {
1295                let mut path = path.into_vec();
1296                let name = path.pop().unwrap_or_else(|| SmolStr::new(i));
1297                let args = parse_type_args(parser);
1298                TypeExpr::Generic(Box::new(GenericType {
1299                    namespace: Box::from(path),
1300                    name,
1301                    args,
1302                    span: (span.start, parser.last_token_end as u32).into(),
1303                }))
1304            } else {
1305                TypeExpr::NamespacedIdentifier(path, (span.start, end).into())
1306            }
1307        } else if parser.peek_token() == Token::OpInf {
1308            // A generic type applied to its arguments. A type position is never
1309            // ambiguous, so this needs no lookahead: `<` here can only open a
1310            // type-argument list.
1311            let args = parse_type_args(parser);
1312            TypeExpr::Generic(Box::new(GenericType {
1313                namespace: Box::from([]),
1314                name: SmolStr::new(i),
1315                args,
1316                span: (span.start, parser.last_token_end as u32).into(),
1317            }))
1318        } else {
1319            TypeExpr::Identifier(SmolStr::new(i), span)
1320        }
1321    } else {
1322        cold_path();
1323        parser.error(
1324            span,
1325            ParserErr::UnexpectedToken(Token::Identifier(""), next_token, "Invalid type"),
1326        );
1327    };
1328    loop {
1329        if parser.peek_token() == Token::LBracket {
1330            parser.next_token();
1331            parser.next_token_expect(Token::RBracket, "Unmatched '['");
1332            t = TypeExpr::Array(Box::new(t));
1333        } else {
1334            break;
1335        }
1336    }
1337    t
1338}
1339
1340/// Walks a copy of the token stream to decide whether the `<` an identifier is
1341/// followed by opens a type-argument list or is a comparison.
1342///
1343/// `a < b` and `a < b && c > d` are comparisons and must stay comparisons, so
1344/// the walk accepts only well-formed type tokens and commits only when the list
1345/// closes with `>` immediately followed by `(` (a call), `{` (a struct literal,
1346/// where one is allowed) or `::` (a variant of a generic enum). It reports by
1347/// returning: the parser cannot fail and retry, so a walk that raised an error
1348/// would abort the compile over an ordinary comparison.
1349struct TypeArgScan<'a> {
1350    input: TokenIter<'a>,
1351    /// The second half of a `>>` the walk has already read. See
1352    /// [`Parser::next_type_list_close`], which splits the same token when the
1353    /// list is parsed for real.
1354    pending_gt: bool,
1355}
1356
1357impl<'a> TypeArgScan<'a> {
1358    fn peek(&mut self) -> Option<Token<'a>> {
1359        if self.pending_gt {
1360            return Some(Token::OpSup);
1361        }
1362        self.input.peek().and_then(|(t, _)| t.ok())
1363    }
1364
1365    fn eat(&mut self, expected: Token<'a>) -> bool {
1366        if self.peek() == Some(expected) {
1367            if self.pending_gt {
1368                self.pending_gt = false;
1369            } else {
1370                self.input.next();
1371            }
1372            true
1373        } else {
1374            false
1375        }
1376    }
1377
1378    /// The `>` that closes a list, which is the first half of a `>>` where two
1379    /// lists close at once.
1380    fn eat_gt(&mut self) -> bool {
1381        if self.eat(Token::OpSup) {
1382            return true;
1383        }
1384        if self.peek() == Some(Token::OpShr) {
1385            self.input.next();
1386            self.pending_gt = true;
1387            return true;
1388        }
1389        false
1390    }
1391
1392    fn identifier(&mut self) -> bool {
1393        if matches!(self.peek(), Some(Token::Identifier(_))) {
1394            self.input.next();
1395            true
1396        } else {
1397            false
1398        }
1399    }
1400
1401    /// `<Type, Type, ...>`, the `<` included.
1402    fn args(&mut self) -> bool {
1403        if !self.eat(Token::OpInf) {
1404            return false;
1405        }
1406        loop {
1407            if !self.type_expr() {
1408                return false;
1409            }
1410            if !self.eat(Token::Comma) {
1411                return self.eat_gt();
1412            }
1413        }
1414    }
1415
1416    fn type_expr(&mut self) -> bool {
1417        loop {
1418            if !self.atomic_type() {
1419                return false;
1420            }
1421            if !self.eat(Token::Pipe) {
1422                return true;
1423            }
1424        }
1425    }
1426
1427    fn atomic_type(&mut self) -> bool {
1428        if self.eat(Token::LBrace) {
1429            if !self.type_expr() || !self.eat(Token::Colon) || !self.type_expr() {
1430                return false;
1431            }
1432            if !self.eat(Token::RBrace) {
1433                return false;
1434            }
1435        } else {
1436            if !self.identifier() {
1437                return false;
1438            }
1439            while self.eat(Token::DoubleColon) {
1440                if !self.identifier() {
1441                    return false;
1442                }
1443            }
1444            if self.peek() == Some(Token::OpInf) && !self.args() {
1445                return false;
1446            }
1447        }
1448        while self.eat(Token::LBracket) {
1449            if !self.eat(Token::RBracket) {
1450                return false;
1451            }
1452        }
1453        true
1454    }
1455}
1456
1457/// What a type-argument list may be followed by at the position it is read.
1458///
1459/// The list only counts as one when the `>` closes right before one of these,
1460/// which is what tells `Cell<int>{ .. }` from the comparison `a < b`.
1461#[derive(Clone, Copy, PartialEq, Eq)]
1462pub enum TypeArgFollow {
1463    /// A term: a call, a struct literal, or an enum-variant path.
1464    Term,
1465    /// A term in a position where a struct literal cannot start, which is the
1466    /// header of an `if`, `while` or `for`.
1467    TermNoStruct,
1468    /// A method call, which is always the call parentheses.
1469    Call,
1470}
1471
1472/// Whether the `<` ahead opens a type-argument list. See [`TypeArgScan`].
1473///
1474/// Kept out of line: the walk's own state would otherwise enlarge every
1475/// `parse_term` frame, and expression parsing recurses once per nesting level.
1476#[inline(never)]
1477fn type_args_ahead(parser: &Parser<'_>, follow: TypeArgFollow) -> bool {
1478    debug_assert!(
1479        parser.pending_gt.is_none(),
1480        "the half of a '>>' left over from a type list is read before any expression"
1481    );
1482    let mut scan = TypeArgScan {
1483        input: parser.input.clone(),
1484        pending_gt: false,
1485    };
1486    if !scan.args() {
1487        return false;
1488    }
1489    match scan.peek() {
1490        Some(Token::LParen) => true,
1491        Some(Token::DoubleColon) => follow != TypeArgFollow::Call,
1492        Some(Token::LBrace) => follow == TypeArgFollow::Term,
1493        _ => false,
1494    }
1495}
1496
1497/// Parses `<Type, Type, ...>` where a type-argument list is already known to
1498/// start. The opening `<` has not been consumed.
1499fn parse_type_args(parser: &mut Parser<'_>) -> Box<[TypeExpr]> {
1500    parser.next_token_expect(Token::OpInf, "A type argument list starts with '<'.");
1501    let mut args: Vec<TypeExpr> = Vec::with_capacity(2);
1502    loop {
1503        args.push(parse_type(parser));
1504        if parser.peek_token() == Token::Comma {
1505            parser.next_token();
1506        } else {
1507            break;
1508        }
1509    }
1510    parser.next_type_list_close("A type argument list ends with '>'.");
1511    Box::from(args)
1512}
1513
1514/// Parses the `<T, U>` a declaration may carry after its name, naming the type
1515/// parameters its body may use. Returns an empty list when there is none.
1516fn parse_type_params(parser: &mut Parser<'_>) -> TypeParams {
1517    if parser.peek_token_opt() != Some(Token::OpInf) {
1518        return Box::from([]);
1519    }
1520    parser.next_token();
1521    let mut params: Vec<SmolStr> = Vec::with_capacity(2);
1522    loop {
1523        let (next_token, span) = parser.next_token();
1524        if let Token::Identifier(name) = next_token {
1525            params.push(SmolStr::new(name));
1526        } else {
1527            cold_path();
1528            parser.error(
1529                span,
1530                ParserErr::UnexpectedToken(
1531                    Token::Identifier(""),
1532                    next_token,
1533                    "Type parameters must be identifiers.",
1534                ),
1535            );
1536        }
1537        if parser.peek_token() == Token::Comma {
1538            parser.next_token();
1539        } else {
1540            break;
1541        }
1542    }
1543    parser.next_type_list_close("A type parameter list ends with '>'.");
1544    Box::from(params)
1545}
1546
1547fn parse_dylib_import(parser: &mut Parser<'_>) -> Expr {
1548    let (t, Span { start, end: _ }) = parser.next_token();
1549    debug_assert_eq!(t, Token::Dylib);
1550    let (next_token, span) = parser.next_token();
1551    let path = if let Token::String(s) = next_token {
1552        SmolStr::new(parse_string(s))
1553    } else {
1554        cold_path();
1555        parser.error(
1556            span,
1557            ParserErr::UnexpectedToken(Token::String(""), next_token, "Paths must be strings."),
1558        );
1559    };
1560    parser.next_token_expect(Token::LBrace, "Blocks need to start with '{'.");
1561    let (fn_signatures, end) = parse_fn_signature_block(parser, None);
1562    Expr::ImportDylib(path, fn_signatures, (start, end).into())
1563}
1564
1565/// Parses a brace-delimited block of typed function signatures shared by
1566/// `dylib "..." { ... }` and `host "..." { ... }`. The opening `{` must already
1567/// have been consumed; this consumes through the closing `}` and returns the
1568/// parsed signatures together with the end offset of the `}`.
1569///
1570/// `structs` is where a `host` block collects the structs it declares; a
1571/// `dylib` block passes `None` and declares none.
1572fn parse_fn_signature_block(
1573    parser: &mut Parser<'_>,
1574    mut structs: Option<&mut Vec<Expr>>,
1575) -> (Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>, u32) {
1576    let mut fn_signatures: Vec<(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)> = Vec::new();
1577    let end: u32;
1578    loop {
1579        if parser.peek_token() == Token::RBrace {
1580            end = parser.next_token().1.end;
1581            break;
1582        }
1583        if parser.peek_token() == Token::Struct
1584            && let Some(structs) = structs.as_deref_mut()
1585        {
1586            let declared = parse_struct_declare(parser);
1587            if let Expr::StructDeclare(_, _, span, type_params, _) = &declared
1588                && !type_params.is_empty()
1589            {
1590                cold_path();
1591                parser.error(
1592                    *span,
1593                    ParserErr::UnexpectedToken(
1594                        Token::LBrace,
1595                        Token::OpInf,
1596                        "A struct a host block declares takes no type parameters.",
1597                    ),
1598                );
1599            }
1600            structs.push(declared);
1601            continue;
1602        }
1603
1604        let type_start = parser.peek_token();
1605        let span = parser.peek_token_span();
1606        let first = parse_type(parser);
1607        let fn_name_span: Span;
1608        let (return_type, fn_name) = if parser.peek_token() == Token::LParen {
1609            if let TypeExpr::Identifier(name, span) = first {
1610                fn_name_span = span;
1611                (
1612                    TypeExpr::Identifier(SmolStr::new_static("null"), span),
1613                    name,
1614                )
1615            } else {
1616                parser.error(
1617                    span,
1618                    ParserErr::UnexpectedToken(
1619                        Token::Identifier(""),
1620                        type_start,
1621                        "Function names must be identifiers.",
1622                    ),
1623                );
1624            }
1625        } else {
1626            let (next_token, span) = parser.next_token();
1627            fn_name_span = span;
1628            let fn_name = if let Token::Identifier(name) = next_token {
1629                SmolStr::new(name)
1630            } else {
1631                cold_path();
1632                parser.error(
1633                    span,
1634                    ParserErr::UnexpectedToken(
1635                        Token::Identifier(""),
1636                        next_token,
1637                        "Function names must be identifiers.",
1638                    ),
1639                );
1640            };
1641            (first, fn_name)
1642        };
1643        parser.next_token_expect(
1644            Token::LParen,
1645            "Function arguments must be delimited by parentheses",
1646        );
1647        let mut args: Vec<TypeExpr> = Vec::with_capacity(2);
1648        loop {
1649            if parser.peek_token() == Token::RParen {
1650                break;
1651            }
1652            // `...` marks a variadic host function: it accepts any number of
1653            // arguments of any type, delivered to the registered closure as a
1654            // slice. Carried through as a reserved sentinel type the host
1655            // resolver recognises (meaningless in a `dylib` block).
1656            if parser.peek_token() == Token::Ellipsis {
1657                let span = parser.peek_token_span();
1658                parser.next_token();
1659                args.push(TypeExpr::Identifier(SmolStr::new_static("..."), span));
1660                break;
1661            }
1662            args.push(parse_type(parser));
1663            if parser.peek_token() == Token::Comma {
1664                parser.next_token();
1665            } else if !(parser.peek_token() == Token::RParen) {
1666                cold_path();
1667                let span = parser.peek_token_span();
1668                parser.error(span, ParserErr::ArgumentsMissingCommaSeparator);
1669            }
1670        }
1671        parser.next_token_expect(Token::RParen, "Unmatched ')'");
1672        parser.next_token_expect(
1673            Token::SemiColon,
1674            "Function definitions must end with a semicolon",
1675        );
1676        fn_signatures.push((fn_name, Box::from(args), return_type, fn_name_span));
1677    }
1678    (fn_signatures.into_boxed_slice(), end)
1679}
1680
1681/// Parses a `host "namespace" { signatures... }` block. The namespace names a
1682/// table of Rust closures registered on the embedding [`crate::Engine`]; the
1683/// signatures are type-checked at compile time exactly like `dylib` signatures.
1684fn parse_host_block(parser: &mut Parser<'_>) -> Expr {
1685    let (t, Span { start, end: _ }) = parser.next_token();
1686    debug_assert_eq!(t, Token::Host);
1687    let (next_token, span) = parser.next_token();
1688    let namespace = if let Token::String(s) = next_token {
1689        SmolStr::new(parse_string(s))
1690    } else {
1691        cold_path();
1692        parser.error(
1693            span,
1694            ParserErr::UnexpectedToken(
1695                Token::String(""),
1696                next_token,
1697                "Host namespaces must be strings.",
1698            ),
1699        );
1700    };
1701    parser.next_token_expect(Token::LBrace, "Blocks need to start with '{'.");
1702    let mut structs: Vec<Expr> = Vec::new();
1703    let (fn_signatures, end) = parse_fn_signature_block(parser, Some(&mut structs));
1704    Expr::HostBlock(
1705        namespace,
1706        fn_signatures,
1707        (start, end).into(),
1708        structs.into_boxed_slice(),
1709    )
1710}
1711
1712#[inline(always)]
1713fn parse_file(parser: &mut Parser<'_>, impls: &mut Vec<ImplTemplate>) -> Vec<Expr> {
1714    let mut output: Vec<Expr> = Vec::with_capacity(2);
1715    // parse file statements
1716    while parser.peek_token_opt().is_some() {
1717        if parser.peek_token() == Token::At {
1718            let active = parse_attributes(parser);
1719            if parser.peek_token_opt().is_none() {
1720                cold_path();
1721                let span = parser.eof_span();
1722                parser.error(span, ParserErr::AttributeWithoutItem);
1723            }
1724            if active {
1725                parse_item(parser, &mut output, impls);
1726            } else {
1727                parser.inactive += 1;
1728                parse_item(parser, &mut Vec::new(), &mut Vec::new());
1729                parser.inactive -= 1;
1730            }
1731            continue;
1732        }
1733        parse_item(parser, &mut output, impls);
1734    }
1735    output
1736}
1737
1738/// Parses one top-level declaration into `output`.
1739fn parse_item(parser: &mut Parser<'_>, output: &mut Vec<Expr>, impls: &mut Vec<ImplTemplate>) {
1740    let t = parser.peek_token();
1741    // An `impl` block lowers to several top-level function declarations, so
1742    // it is expanded directly into `output` rather than yielding one Expr.
1743    // A block on a generic type is kept as a template instead.
1744    if t == Token::Impl {
1745        parse_impl_block(parser, output, impls);
1746        return;
1747    }
1748    output.push(match t {
1749        Token::Function => parse_function(parser),
1750        Token::Import => parse_file_import(parser),
1751        Token::Struct => parse_struct_declare(parser),
1752        Token::Enum => parse_enum_declare(parser),
1753        Token::Dylib => parse_dylib_import(parser),
1754        Token::Host => parse_host_block(parser),
1755        unexpected => {
1756            cold_path();
1757            let span = parser.peek_token_span();
1758            parser.error(span, ParserErr::UnexpectedTokenStr("'fn' (function declaration), 'import', 'struct' (struct declaration), 'enum' (enum declaration), 'impl' (method block), 'dylib' (dynamic library import), or 'host' (host function block)", unexpected, "Invalid file statement."));
1759        }
1760    });
1761}
1762
1763/// Reads the run of attributes in front of an item or a statement, and says
1764/// whether the code they apply to is compiled: true when every `@cfg` among
1765/// them holds.
1766fn parse_attributes(parser: &mut Parser<'_>) -> bool {
1767    let mut active = true;
1768    while parser.peek_token_opt() == Some(Token::At) {
1769        parser.next_token();
1770        let (t, span) = parser.next_token();
1771        match t {
1772            Token::Identifier("cfg") => {}
1773            Token::Identifier(name) => {
1774                cold_path();
1775                parser.error(span, ParserErr::UnknownAttribute(name));
1776            }
1777            other => {
1778                cold_path();
1779                parser.error(
1780                    span,
1781                    ParserErr::UnexpectedToken(
1782                        Token::Identifier("cfg"),
1783                        other,
1784                        "An attribute is '@' followed by its name, as in @cfg(web).",
1785                    ),
1786                );
1787            }
1788        }
1789        let open = parser.next_token_expect(
1790            Token::LParen,
1791            "A cfg attribute holds its condition in parentheses, as in @cfg(web).",
1792        );
1793        let holds = parse_cfg_predicate(parser);
1794        parser.next_token_expect_closer(Token::LParen, open, Token::RParen);
1795        active &= holds;
1796    }
1797    active
1798}
1799
1800/// Reads one `@cfg` condition and evaluates it against the active
1801/// configuration: a flag, `key = "value"`, or `not(...)`, `any(...)` and
1802/// `all(...)` around further conditions.
1803fn parse_cfg_predicate(parser: &mut Parser<'_>) -> bool {
1804    const EXPECTED: &str = "a flag, key = \"value\", not(...), any(...), or all(...)";
1805    let (t, span) = parser.next_token();
1806    let Token::Identifier(name) = t else {
1807        cold_path();
1808        parser.error(
1809            span,
1810            ParserErr::UnexpectedTokenStr(EXPECTED, t, "This is the condition of a cfg attribute."),
1811        );
1812    };
1813    match parser.peek_token() {
1814        Token::LParen if matches!(name, "not" | "any" | "all") => {
1815            let (_, open) = parser.next_token();
1816            parser.enter(open);
1817            let mut results = Vec::new();
1818            while parser.peek_token() != Token::RParen {
1819                results.push(parse_cfg_predicate(parser));
1820                if parser.peek_token() == Token::Comma {
1821                    parser.next_token();
1822                } else {
1823                    break;
1824                }
1825            }
1826            parser.leave();
1827            parser.next_token_expect_closer(Token::LParen, open, Token::RParen);
1828            match name {
1829                "not" => {
1830                    if results.len() != 1 {
1831                        cold_path();
1832                        parser.error(
1833                            (span.start, parser.last_token_end as u32).into(),
1834                            ParserErr::UnexpectedTokenStr(
1835                                "exactly one condition",
1836                                Token::RParen,
1837                                "not(...) inverts one condition.",
1838                            ),
1839                        );
1840                    }
1841                    !results[0]
1842                }
1843                "any" => results.contains(&true),
1844                _ => !results.contains(&false),
1845            }
1846        }
1847        Token::Equals => {
1848            parser.next_token();
1849            let (value, value_span) = parser.next_token();
1850            let Token::String(value) = value else {
1851                cold_path();
1852                parser.error(
1853                    value_span,
1854                    ParserErr::UnexpectedToken(
1855                        Token::String(""),
1856                        value,
1857                        "The value a cfg key is compared with is a quoted string.",
1858                    ),
1859                );
1860            };
1861            parser.ctx.cfg.has_value(name, &parse_string(value))
1862        }
1863        _ => parser.ctx.cfg.is_enabled(name),
1864    }
1865}
1866
1867/// A parsed file: its top-level statements and its generic `impl` blocks.
1868///
1869/// A block written against a generic type carries no type of its own to attach
1870/// to, so it travels beside the statements and is lowered per instantiation.
1871pub struct ParsedFile {
1872    pub code: Vec<Expr>,
1873    pub impls: Vec<ImplTemplate>,
1874}
1875
1876#[must_use]
1877pub fn parse(input: &str, src: &Source) -> ParsedFile {
1878    let mut impls: Vec<ImplTemplate> = Vec::new();
1879    let cfg = cfg::active();
1880    let code = parse_file(
1881        &mut Parser {
1882            input: Token::lexer(input).spanned().peekable(),
1883            ctx: ParserCtx { src, cfg: &cfg },
1884            last_token_end: 0,
1885            span_override: None,
1886            nesting: 0,
1887            pending_gt: None,
1888            inactive: 0,
1889        },
1890        &mut impls,
1891    );
1892    ParsedFile { code, impls }
1893}
1894
1895/// What one line of source is, as far as the grammar can tell from its opening
1896/// tokens.
1897///
1898/// The REPL builds a file out of the lines typed at it, and where a line goes
1899/// depends on which of these it is. Only the grammar knows which tokens open a
1900/// declaration, so the answer is read here rather than guessed from the text.
1901#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1902pub enum LineKind {
1903    /// A top-level declaration: one of the forms [`parse`] accepts at file
1904    /// scope, which a block does not.
1905    Declaration,
1906    /// A statement: it belongs in a block and yields no value.
1907    Statement,
1908    /// Anything else, which may be an expression with a value to show. The
1909    /// reading is a guess: the opening tokens rule out the other two, and only
1910    /// a compile settles the rest.
1911    Expression,
1912}
1913
1914/// Reads `line` as far as its first tokens, and says which of [`LineKind`] it
1915/// opens.
1916///
1917/// A line whose first token is unknown to the lexer reads as a statement, so
1918/// the error it raises is the one its own text earns rather than one about
1919/// text wrapped around it.
1920#[must_use]
1921pub fn classify_line(line: &str) -> LineKind {
1922    let mut tokens = Token::lexer(line);
1923    let Some(Ok(mut first)) = tokens.next() else {
1924        return LineKind::Statement;
1925    };
1926    // Attributes in front of a line say nothing about what it is: the line
1927    // is what follows them.
1928    while first == Token::At {
1929        let mut depth = 0_u32;
1930        let mut after = None;
1931        for token in tokens.by_ref() {
1932            let Ok(token) = token else {
1933                return LineKind::Statement;
1934            };
1935            match token {
1936                Token::LParen => depth += 1,
1937                Token::RParen => {
1938                    depth = depth.saturating_sub(1);
1939                    if depth == 0 {
1940                        after = tokens.next();
1941                        break;
1942                    }
1943                }
1944                _ => {}
1945            }
1946        }
1947        let Some(Ok(next)) = after else {
1948            return LineKind::Statement;
1949        };
1950        first = next;
1951    }
1952    match first {
1953        Token::Function
1954        | Token::Import
1955        | Token::Struct
1956        | Token::Enum
1957        | Token::Impl
1958        | Token::Dylib
1959        | Token::Host => LineKind::Declaration,
1960        // Every form `parse_statement` reads on its own, plus `{`, which opens
1961        // an evaluation block there and not a map literal.
1962        Token::Let
1963        | Token::If
1964        | Token::Else
1965        | Token::While
1966        | Token::For
1967        | Token::Match
1968        | Token::Loop
1969        | Token::Try
1970        | Token::Catch
1971        | Token::Return
1972        | Token::Throw
1973        | Token::Break
1974        | Token::Continue
1975        | Token::LBrace => LineKind::Statement,
1976        // An assignment is a statement wherever its target ends, and `=` never
1977        // stands anywhere else in an expression: a struct literal and a map
1978        // both separate with ':', and a call's arguments with ','. A string
1979        // holding an '=' is one token, so its contents cannot be mistaken for
1980        // one.
1981        //
1982        // The scan stops at the first '(' or '{'. An assignment target is a
1983        // name, a field or an index, so its '=' comes before either of those;
1984        // everything past one belongs to a call's arguments or a literal's
1985        // body, where the '=' of a `let` inside a closure passed as an
1986        // argument says nothing about the line holding it.
1987        _ => {
1988            let assigns = tokens
1989                .take_while(|t| !matches!(t, Ok(Token::LParen | Token::LBrace)))
1990                .any(|t| {
1991                    matches!(
1992                        t,
1993                        Ok(Token::Equals
1994                            | Token::AssignOpAdd
1995                            | Token::AssignOpSub
1996                            | Token::AssignOpMul
1997                            | Token::AssignOpDiv
1998                            | Token::AssignOpMod
1999                            | Token::AssignOpPow
2000                            | Token::AssignOpBitAnd
2001                            | Token::AssignOpBitOr
2002                            | Token::AssignOpBitXor
2003                            | Token::AssignOpShl
2004                            | Token::AssignOpShr)
2005                    )
2006                });
2007            if assigns {
2008                LineKind::Statement
2009            } else {
2010                LineKind::Expression
2011            }
2012        }
2013    }
2014}
2015
2016/// How a line at the prompt writes the way out of the session.
2017#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2018pub enum ExitLine {
2019    /// The name on its own. It is not a call, so there is nothing to compile
2020    /// and nothing to work a status out of.
2021    Bare,
2022    /// A call, with an argument or without one. The status is whatever the
2023    /// call names, which the session it is typed into is what works out.
2024    Call,
2025}
2026
2027/// Whether the only statement `line` holds is a call to `exit`, and in which
2028/// of its two forms.
2029///
2030/// The prompt answers `exit` itself: the built-in of that name ends the
2031/// process compiling the session, so a session holding the line would print
2032/// nothing ever again. Reading the line from its tokens is what lets
2033/// `exit(code)` leave with the value of `code` rather than only the forms
2034/// whose status is written out.
2035#[must_use]
2036pub fn exit_line(line: &str) -> Option<ExitLine> {
2037    let mut tokens = Token::lexer(line);
2038    if !matches!(tokens.next(), Some(Ok(Token::Identifier("exit")))) {
2039        return None;
2040    }
2041    let form = match tokens.next() {
2042        None => return Some(ExitLine::Bare),
2043        Some(Ok(Token::SemiColon)) => ExitLine::Bare,
2044        Some(Ok(Token::LParen)) => {
2045            // The argument can be any expression, parentheses of its own
2046            // included, so the list ends where the depth comes back to zero.
2047            let mut depth = 1u32;
2048            loop {
2049                match tokens.next() {
2050                    Some(Ok(Token::LParen)) => depth += 1,
2051                    Some(Ok(Token::RParen)) => {
2052                        depth -= 1;
2053                        if depth == 0 {
2054                            break;
2055                        }
2056                    }
2057                    Some(Ok(_)) => {}
2058                    // Unbalanced, or a token the lexer refuses: the line is
2059                    // one to compile, and it reports what its own text earns.
2060                    _ => return None,
2061                }
2062            }
2063            match tokens.next() {
2064                None => return Some(ExitLine::Call),
2065                Some(Ok(Token::SemiColon)) => ExitLine::Call,
2066                _ => return None,
2067            }
2068        }
2069        _ => return None,
2070    };
2071    // Anything after the statement makes the line more than the way out.
2072    tokens.next().is_none().then_some(form)
2073}