Skip to main content

candela/parser/
parser.rs

1use crate::BOLD;
2use crate::RED;
3use crate::RESET;
4use crate::compiler::compiler_data::Source;
5use crate::compiler::expr::{Expr, Span, var_assign};
6use crate::compiler::type_system::TypeExpr;
7use crate::errors::BLUE;
8use crate::errors::blue;
9use crate::errors::crash;
10use ariadne::Color;
11use ariadne::Label;
12use ariadne::Report;
13use ariadne::ReportKind;
14use blocks::parse_condition_block;
15use blocks::parse_enum_declare;
16use blocks::parse_eval_block;
17use blocks::parse_for_loop;
18use blocks::parse_function;
19use blocks::parse_impl_block;
20use blocks::parse_loop_block;
21use blocks::parse_match;
22use blocks::parse_struct_declare;
23use blocks::parse_try_catch_block;
24use blocks::parse_while_block;
25use lexer::parse_string;
26use logos::SpannedIter;
27use parser_expr::add_op;
28use parser_expr::parse_expr;
29use smol_strc::SmolStr;
30use std::hint::{cold_path, unreachable_unchecked};
31use std::io::Write;
32use std::iter::Peekable;
33
34use lexer::Token;
35use logos::Logos;
36
37mod blocks;
38mod lexer;
39mod parser_expr;
40mod term;
41
42type TokenIter<'a> = Peekable<SpannedIter<'a, Token<'a>>>;
43
44struct ParserCtx<'a> {
45    src: &'a Source,
46}
47
48struct Parser<'a> {
49    input: TokenIter<'a>,
50    ctx: ParserCtx<'a>,
51    last_token_end: usize,
52}
53
54#[derive(Clone)]
55enum ParserErr<'a> {
56    UnexpectedEOF,
57    UnknownToken,
58    /// (expected, received)
59    UnexpectedToken(Token<'a>, Token<'a>, &'static str),
60    /// (expected, received)
61    UnexpectedTokenStr(&'static str, Token<'a>, &'static str),
62    ArrayElementsMissingComma,
63    InlineConditionNoElseBlock,
64    DivisionByZero,
65    ModuloByZero,
66    IntegerNegativeExponent,
67    ArgumentsMissingCommaSeparator,
68    TryBlockNoCatch,
69    MatchBlockNoNonWildcardArm,
70    MatchBlockZeroArms,
71    /// A `fn` declaration written inside a block rather than at the top level
72    /// of a file.
73    NestedFunctionDeclaration,
74    /// The removed `import std::string;` form; carries the path segments
75    /// joined with `/` so the error suggests the exact replacement.
76    LegacyNamespacedImport(String),
77    ImportPathBadExtension,
78}
79
80impl ParserErr<'_> {
81    /// Stable, machine-readable identifier for this parser error, mirroring
82    /// `ErrType::kind` on the runtime side.
83    const fn kind(&self) -> &'static str {
84        match self {
85            ParserErr::UnexpectedEOF => "unexpected_eof",
86            ParserErr::UnknownToken => "unknown_token",
87            ParserErr::UnexpectedToken(..) | ParserErr::UnexpectedTokenStr(..) => {
88                "unexpected_token"
89            }
90            ParserErr::ArrayElementsMissingComma => "array_elements_missing_comma",
91            ParserErr::InlineConditionNoElseBlock => "inline_condition_no_else_block",
92            ParserErr::DivisionByZero => "division_by_zero",
93            ParserErr::ModuloByZero => "modulo_by_zero",
94            ParserErr::IntegerNegativeExponent => "integer_negative_exponent",
95            ParserErr::ArgumentsMissingCommaSeparator => "arguments_missing_comma_separator",
96            ParserErr::TryBlockNoCatch => "try_block_no_catch",
97            ParserErr::MatchBlockNoNonWildcardArm => "match_block_no_non_wildcard_arm",
98            ParserErr::MatchBlockZeroArms => "match_block_zero_arms",
99            ParserErr::NestedFunctionDeclaration => "nested_function_declaration",
100            ParserErr::LegacyNamespacedImport(_) => "legacy_namespaced_import",
101            ParserErr::ImportPathBadExtension => "import_path_bad_extension",
102        }
103    }
104}
105
106#[cold]
107#[inline(never)]
108fn throw_parser_error(src: &Source, Span { start, end }: Span, t: ParserErr) -> ! {
109    let kind = t.kind();
110    let err_message = match t {
111        ParserErr::UnexpectedEOF => "Unexpected EOF",
112        ParserErr::UnknownToken => "Unknown token",
113        ParserErr::UnexpectedToken(expected, received, msg) => &format_args!(
114            "Expected {BLUE}{BOLD}{expected}{RESET}, but got {RED}{BOLD}{received}{RESET}. {msg}"
115        )
116        .to_string(),
117        ParserErr::UnexpectedTokenStr(expected, received, msg) => &format_args!(
118            "Expected {BLUE}{BOLD}{expected}{RESET}, but got {RED}{BOLD}{received}{RESET}. {msg}"
119        )
120        .to_string(),
121        ParserErr::ArrayElementsMissingComma => "Array elements must be separated by a comma",
122        ParserErr::InlineConditionNoElseBlock => "Inline conditions must have an else block",
123        ParserErr::DivisionByZero => "Division by zero",
124        ParserErr::ModuloByZero => "Modulo by zero",
125        ParserErr::IntegerNegativeExponent => "Integers cannot be raised to a negative exponent",
126        ParserErr::ArgumentsMissingCommaSeparator => "Arguments must be separated by a comma",
127        ParserErr::TryBlockNoCatch => {
128            "A {BLUE}{BOLD}try{RESET} block must have at least one {BLUE}{BOLD}catch{RESET} block"
129        }
130        ParserErr::MatchBlockNoNonWildcardArm => {
131            "{BLUE}{BOLD}Match blocks{RESET} must have {BOLD}at least one non-wildcard arm{RESET}"
132        }
133        ParserErr::MatchBlockZeroArms => {
134            "{BLUE}{BOLD}Match blocks{RESET} must have {BOLD}at least one arm{RESET}"
135        }
136        ParserErr::NestedFunctionDeclaration => {
137            "Functions declare at the top level of a file, not inside a block. Move this declaration out of the enclosing block"
138        }
139        ParserErr::LegacyNamespacedImport(path) => &format!(
140            "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"
141        ),
142        ParserErr::ImportPathBadExtension => &format!(
143            "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)"
144        ),
145    };
146    if crate::errors::diagnostics_enabled() {
147        crate::errors::emit_diagnostic(
148            src.filename.as_str(),
149            (start as usize)..(end as usize),
150            crate::errors::strip_ansi(err_message),
151            kind,
152        );
153    }
154    let mut out = candela_vm::captured_output::stderr();
155    let _ = writeln!(out, "{RED}CANDELA ERROR{RESET}");
156    let report = Report::build(
157        ReportKind::Error,
158        (src.filename.as_str(), (start as usize)..(end as usize)),
159    )
160    .with_label(
161        Label::new((src.filename.as_str(), (start as usize)..(end as usize)))
162            .with_message(err_message)
163            .with_color(Color::Red),
164    )
165    .finish();
166
167    report
168        .write(
169            (
170                src.filename.as_str(),
171                ariadne::Source::from(src.contents.as_str()),
172            ),
173            &mut out,
174        )
175        .unwrap();
176
177    #[cfg(debug_assertions)]
178    panic!();
179
180    #[cfg(not(any(debug_assertions, target_arch = "wasm32", feature = "embed")))]
181    std::process::exit(1);
182
183    #[cfg(target_arch = "wasm32")]
184    wasm_bindgen::throw_str("candela_error");
185
186    #[cfg(all(feature = "embed", not(debug_assertions)))]
187    panic!();
188}
189
190impl<'a> Parser<'a> {
191    #[inline(always)]
192    fn eof_span(&self) -> Span {
193        let end = self.ctx.src.contents.len();
194        (end, end).into()
195    }
196    #[cold]
197    #[inline(never)]
198    fn error(&self, span: Span, error: ParserErr) -> ! {
199        throw_parser_error(self.ctx.src, span, error)
200    }
201    #[inline(always)]
202    fn next_token(&mut self) -> (Token<'a>, Span) {
203        let t = self.input.next().unwrap_or_else(
204            #[cold]
205            || {
206                self.error(self.eof_span(), ParserErr::UnexpectedEOF);
207            },
208        );
209        self.last_token_end = t.1.end;
210        (
211            t.0.unwrap_or_else(
212                #[cold]
213                |()| self.error((t.1.start, t.1.end).into(), ParserErr::UnknownToken),
214            ),
215            (t.1.start, t.1.end).into(),
216        )
217    }
218    #[inline(always)]
219    fn peek_token(&mut self) -> Token<'a> {
220        let Some((t, start, end)) = self
221            .input
222            .peek()
223            .map(|(t, span)| (*t, span.start, span.end))
224        else {
225            self.error(self.eof_span(), ParserErr::UnexpectedEOF);
226        };
227        t.unwrap_or_else(
228            #[cold]
229            |()| self.error((start, end).into(), ParserErr::UnknownToken),
230        )
231    }
232    #[inline(always)]
233    fn peek_token_span(&mut self) -> Span {
234        let Some((_, start, end)) = self
235            .input
236            .peek()
237            .map(|(t, span)| (*t, span.start, span.end))
238        else {
239            self.error(self.eof_span(), ParserErr::UnexpectedEOF);
240        };
241        Span {
242            start: start as u32,
243            end: end as u32,
244        }
245    }
246    #[inline(always)]
247    fn peek_token_opt(&mut self) -> Option<Token<'a>> {
248        let (t, start, end) = self
249            .input
250            .peek()
251            .map(|(t, span)| (*t, span.start, span.end))?;
252        Some(t.unwrap_or_else(
253            #[cold]
254            |()| self.error((start, end).into(), ParserErr::UnknownToken),
255        ))
256    }
257    #[inline(always)]
258    fn peek_token_opt_span(&mut self) -> Option<Span> {
259        self.input
260            .peek()
261            .map(|x| (x.1.start as u32, x.1.end as u32).into())
262    }
263    #[inline(always)]
264    fn next_token_expect(&mut self, expected: Token, msg: &'static str) -> Span {
265        let (next_token, span) = self.next_token();
266        if next_token != expected {
267            self.error(span, ParserErr::UnexpectedToken(expected, next_token, msg));
268        }
269        span
270    }
271    #[inline(always)]
272    fn next_token_expect_closer(
273        &mut self,
274        opener: Token,
275        opener_span: Span,
276        expected_closer: Token,
277    ) -> u32 {
278        if let Some(t) = self.peek_token_opt() {
279            if t == expected_closer {
280                self.next_token().1.end
281            } else {
282                let span = self.peek_token_span();
283                error_unclosed_delimiter(
284                    self,
285                    opener,
286                    opener_span,
287                    expected_closer,
288                    Some((t, span)),
289                );
290            }
291        } else {
292            error_unclosed_delimiter(self, opener, opener_span, expected_closer, None);
293        }
294    }
295    #[inline(never)]
296    #[cold]
297    pub fn throw_parser_err<'b, F: Fn() -> Report<'b, (&'b str, core::ops::Range<usize>)>>(
298        &self,
299        report: F,
300        span: Span,
301        message: &str,
302        code: &str,
303    ) -> ! {
304        if crate::errors::diagnostics_enabled() {
305            crate::errors::emit_diagnostic(
306                self.ctx.src.filename.as_str(),
307                (span.start as usize)..(span.end as usize),
308                crate::errors::strip_ansi(message),
309                code,
310            );
311        }
312        let report = report();
313
314        report
315            .write(
316                (
317                    self.ctx.src.filename.as_str(),
318                    ariadne::Source::from(self.ctx.src.contents.as_str()),
319                ),
320                candela_vm::captured_output::stderr(),
321            )
322            .unwrap();
323
324        crash();
325    }
326}
327
328// Call after DoubleColon is skipped
329// Returns end
330fn parse_namespace(parser: &mut Parser<'_>, initial: SmolStr) -> (Box<[SmolStr]>, u32) {
331    let mut namespace: Vec<SmolStr> = Vec::with_capacity(2);
332    namespace.push(initial);
333    let mut end: u32;
334    loop {
335        let (next_token, span) = parser.next_token();
336        if let Token::Identifier(i) = next_token {
337            namespace.push(SmolStr::new(i));
338            end = span.end;
339        } else {
340            cold_path();
341            parser.error(
342                span,
343                ParserErr::UnexpectedToken(
344                    Token::Identifier(""),
345                    next_token,
346                    "Wrong namespace syntax",
347                ),
348            );
349        }
350        let next_token = parser.peek_token();
351        if next_token == Token::DoubleColon {
352            continue;
353        }
354        return (Box::from(namespace), end);
355    }
356}
357
358// Must be called after LParen is skipped
359fn parse_args(parser: &mut Parser<'_>) -> (Box<[Expr]>, Box<[Span]>, u32) {
360    let mut args = Vec::with_capacity(4);
361    let mut arg_markers: Vec<Span> = Vec::with_capacity(4);
362    loop {
363        if parser.peek_token() == Token::RParen {
364            let end = parser.next_token().1.end;
365            return (Box::from(args), Box::from(arg_markers), end);
366        }
367        let arg_start: u32 = parser.peek_token_span().start;
368        args.push(parse_expr(parser));
369        arg_markers.push((arg_start, parser.peek_token_span().start).into());
370        if parser.peek_token() == Token::Comma {
371            parser.next_token();
372        } else if !(parser.peek_token() == Token::RParen) {
373            cold_path();
374            let span = parser.peek_token_span();
375            parser.error(span, ParserErr::ArgumentsMissingCommaSeparator);
376        }
377    }
378}
379
380fn parse_statement(parser: &mut Parser<'_>) -> Option<Expr> {
381    let token = parser.peek_token_opt()?;
382    let t_span = parser.peek_token_span();
383    match token {
384        Token::If => Some(parse_condition_block(parser, t_span.start)),
385        Token::While => Some(parse_while_block(parser)),
386        Token::For => Some(parse_for_loop(parser)),
387        Token::Match => Some(parse_match(parser)),
388        Token::LBrace => Some(parse_eval_block(parser)),
389        // Only `parse_file` accepts a function declaration. Reaching one here
390        // means it was written inside a block, where it would otherwise parse
391        // and then be dropped without ever being registered.
392        Token::Function => {
393            cold_path();
394            parser.error(t_span, ParserErr::NestedFunctionDeclaration);
395        }
396        Token::Loop => Some(parse_loop_block(parser)),
397        Token::Try => Some(parse_try_catch_block(parser)),
398        Token::Struct => Some(parse_struct_declare(parser)),
399        Token::Enum => Some(parse_enum_declare(parser)),
400        Token::RBrace => None,
401        t => Some(parse_line(parser, t)),
402    }
403}
404
405fn parse_var_declare(parser: &mut Parser<'_>) -> Expr {
406    let (t, _) = parser.next_token();
407    debug_assert_eq!(t, Token::Let);
408    let (t, span) = parser.next_token();
409    let var_name = if let Token::Identifier(id) = t {
410        SmolStr::new(id)
411    } else {
412        cold_path();
413        parser.error(
414            span,
415            ParserErr::UnexpectedToken(
416                Token::Identifier(""),
417                t,
418                "Variable names must be identifiers.",
419            ),
420        );
421    };
422    parser.next_token_expect(
423        Token::Equals,
424        "Variable declarations need a '=' to separate the name from the value.",
425    );
426    let var_value = parse_expr(parser);
427    Expr::VarDeclare(var_name, Box::new(var_value))
428}
429
430fn parse_var_assign(input: &mut Parser<'_>, e: Expr, e_start: u32) -> Expr {
431    let (t, _) = input.next_token();
432    debug_assert_eq!(t, Token::Equals);
433    let e_end = input.peek_token_span().end;
434    let v_start = input.peek_token_span().start;
435    let v = parse_expr(input);
436    let v_end = input.peek_token_span().start;
437    var_assign(e, v, (e_start, e_end).into(), (v_start, v_end).into())
438}
439
440fn parse_op_var_assign(input: &mut Parser<'_>, e: Expr, e_start: u32, op: Token<'_>) -> Expr {
441    let operand_end = input.last_token_end as u32;
442    let (t, _) = input.next_token();
443    debug_assert_eq!(t, op);
444    let e_end = input.peek_token_span().end;
445    let v_start = input.peek_token_span().start;
446    let v = parse_expr(input);
447    let v_end = input.last_token_end as u32;
448    let op = match op {
449        Token::AssignOpAdd => Token::OpAdd,
450        Token::AssignOpSub => Token::OpSub,
451        Token::AssignOpMul => Token::OpMul,
452        Token::AssignOpDiv => Token::OpDiv,
453        Token::AssignOpMod => Token::OpMod,
454        Token::AssignOpPow => Token::OpPow,
455        _ => unsafe { unreachable_unchecked() },
456    };
457    var_assign(
458        e.clone(),
459        add_op(
460            input,
461            op,
462            e,
463            v,
464            (e_start, operand_end).into(),
465            (v_start, v_end).into(),
466        ),
467        (e_start, e_end).into(),
468        (v_start, v_end).into(),
469    )
470}
471
472fn parse_return(input: &mut Parser<'_>) -> Expr {
473    let (t, _) = input.next_token();
474    debug_assert_eq!(t, Token::Return);
475    if input.peek_token_opt() == Some(Token::SemiColon) {
476        Expr::ReturnVal(Box::new(None))
477    } else {
478        let e = parse_expr(input);
479        Expr::ReturnVal(Box::new(Some(e)))
480    }
481}
482
483fn parse_line(input: &mut Parser<'_>, peek: Token<'_>) -> Expr {
484    let line_code = match peek {
485        Token::Let => parse_var_declare(input),
486        Token::Return => parse_return(input),
487        Token::Break => {
488            input.next_token();
489            Expr::Break
490        }
491        Token::Continue => {
492            input.next_token();
493            Expr::Continue
494        }
495        _ => {
496            let e_start = input.peek_token_span().start;
497            let e = parse_expr(input);
498            let peek_token = input.peek_token_opt();
499            match peek_token {
500                Some(Token::Equals) => parse_var_assign(input, e, e_start),
501                Some(
502                    op @ (Token::AssignOpAdd
503                    | Token::AssignOpSub
504                    | Token::AssignOpMul
505                    | Token::AssignOpDiv
506                    | Token::AssignOpMod
507                    | Token::AssignOpPow),
508                ) => parse_op_var_assign(input, e, e_start, op),
509                _ => e,
510            }
511        }
512    };
513    if input.peek_token_opt() != Some(Token::SemiColon) {
514        error_missing_semicolon(input);
515    }
516    input.next_token();
517    // input.next_token_expect(Token::SemiColon, "Lines must end with a ';'.");
518    line_code
519}
520
521#[cold]
522#[inline(never)]
523fn error_unclosed_delimiter(
524    parser: &Parser<'_>,
525    opener_token: Token,
526    opener_span: Span,
527    expected_closer_token: Token,
528    end: Option<(Token, Span)>,
529) -> ! {
530    let message = if let Some((actual_closer_token, _)) = end {
531        format!(
532            "This {opener_token} is never closed: expected {expected_closer_token} but found {actual_closer_token}"
533        )
534    } else {
535        format!(
536            "This {opener_token} is never closed: expected {expected_closer_token} but the file ends here"
537        )
538    };
539    parser.throw_parser_err(
540        || {
541            let mut report = Report::build(
542                ariadne::ReportKind::Error,
543                (parser.ctx.src.filename.as_str(), opener_span.into()),
544            )
545            .with_message("Unclosed delimiter")
546            .with_label(
547                Label::new((parser.ctx.src.filename.as_str(), opener_span.into()))
548                    .with_message(format_args!("This {opener_token} is never closed"))
549                    .with_color(ariadne::Color::Red),
550            );
551
552            if let Some((actual_closer_token, actual_closer_token_span)) = end {
553                report = report.with_label(
554                    Label::new((
555                        parser.ctx.src.filename.as_str(),
556                        actual_closer_token_span.into(),
557                    ))
558                    .with_message(format_args!(
559                        "Expected {expected_closer_token} but found {actual_closer_token}"
560                    ))
561                    .with_color(ariadne::Color::Red),
562                );
563            } else {
564                report = report
565                    .with_label(
566                        Label::new((parser.ctx.src.filename.as_str(), parser.eof_span().into()))
567                            .with_message(format_args!(
568                                "Expected {expected_closer_token} but the file ends here"
569                            ))
570                            .with_color(ariadne::Color::Red),
571                    )
572                    .with_help(format_args!(
573                        "Add a {} here to close it",
574                        blue(expected_closer_token)
575                    ));
576            }
577
578            report.finish()
579        },
580        opener_span,
581        &message,
582        "unclosed_delimiter",
583    )
584}
585
586#[cold]
587#[inline(never)]
588fn error_missing_semicolon(parser: &Parser<'_>) -> ! {
589    let span: Span = (parser.last_token_end as u32, parser.last_token_end as u32).into();
590    parser.throw_parser_err(
591        || {
592            Report::build(
593                ariadne::ReportKind::Error,
594                (
595                    parser.ctx.src.filename.as_str(),
596                    (parser.last_token_end..parser.last_token_end),
597                ),
598            )
599            .with_message("Missing semicolon")
600            .with_label(
601                Label::new((
602                    parser.ctx.src.filename.as_str(),
603                    (parser.last_token_end..parser.last_token_end),
604                ))
605                .with_message(format_args!("Add a {} here", blue(';')))
606                .with_color(ariadne::Color::Blue),
607            )
608            .with_help("All statements end with a ';'")
609            .finish()
610        },
611        span,
612        "Missing semicolon",
613        "missing_semicolon",
614    )
615}
616
617fn parse_code(input: &mut Parser<'_>) -> Vec<Expr> {
618    let mut output: Vec<Expr> = Vec::with_capacity(4);
619    while let Some(e) = parse_statement(input) {
620        output.push(e);
621    }
622    output
623}
624
625fn parse_file_import(parser: &mut Parser<'_>) -> Expr {
626    let (t, Span { start, end: _ }) = parser.next_token();
627    debug_assert_eq!(t, Token::Import);
628    let (next_token, span) = parser.next_token();
629    // One import form: a quoted path.
630    //   * A path ending in `.cdl` is a file import, resolved next to the
631    //     importing file (with the shipped library directory as fallback).
632    //   * A path with no extension is a library import: the resolver appends
633    //     `.cdl` and looks it up in the shipped library directory only
634    //     (`import "std/string";` -> `std/string.cdl`).
635    let (path, is_logical, mut end) = if let Token::String(s) = next_token {
636        let raw = parse_string(s);
637        if raw.ends_with(".cdl") {
638            (raw, false, span.end)
639        } else {
640            let last_segment = raw.rsplit(['/', '\\']).next().unwrap_or(raw.as_str());
641            if last_segment.contains('.') {
642                cold_path();
643                parser.error(span, ParserErr::ImportPathBadExtension);
644            }
645            let mut with_ext = String::with_capacity(raw.len() + 4);
646            with_ext.push_str(raw.as_str());
647            with_ext.push_str(".cdl");
648            (SmolStr::new(&with_ext), true, span.end)
649        }
650    } else if let Token::Identifier(first) = next_token {
651        // The old namespaced form (`import std::string;`) parses far enough to
652        // suggest the exact replacement, then errors.
653        let mut segments = String::from(first);
654        let mut end = span.end;
655        while parser.peek_token_opt() == Some(Token::DoubleColon) {
656            parser.next_token();
657            let (seg_token, seg_span) = parser.next_token();
658            if let Token::Identifier(seg) = seg_token {
659                segments.push('/');
660                segments.push_str(seg);
661                end = seg_span.end;
662            } else {
663                break;
664            }
665        }
666        cold_path();
667        parser.error(
668            (span.start, end).into(),
669            ParserErr::LegacyNamespacedImport(segments),
670        );
671    } else {
672        cold_path();
673        parser.error(
674            span,
675            ParserErr::UnexpectedToken(
676                Token::String(""),
677                next_token,
678                "An import is a quoted path: import \"./local.cdl\"; for a file, import \"std/string\"; for a library.",
679            ),
680        );
681    };
682    let peek_token = parser.peek_token_opt();
683    if peek_token == Some(Token::As) {
684        parser.next_token();
685        let (next_token, span) = parser.next_token();
686        let alias = if let Token::Identifier(id) = next_token {
687            SmolStr::new(id)
688        } else {
689            cold_path();
690            parser.error(
691                span,
692                ParserErr::UnexpectedToken(
693                    Token::Identifier(""),
694                    next_token,
695                    "Module aliases must be identifiers.",
696                ),
697            );
698        };
699        end = span.end;
700        parser.next_token_expect(
701            Token::SemiColon,
702            "Import statements must end with a semicolon",
703        );
704        Expr::ImportFile(path, Some(alias), is_logical, (start, end).into())
705    } else {
706        parser.next_token_expect(
707            Token::SemiColon,
708            "Import statements must end with a semicolon",
709        );
710        Expr::ImportFile(path, None, is_logical, (start, end).into())
711    }
712}
713
714fn parse_type(parser: &mut Parser<'_>) -> TypeExpr {
715    let t = parse_atomic_type(parser);
716    if parser.peek_token() == Token::Pipe {
717        let mut poly = Vec::with_capacity(2);
718        poly.push(t);
719        while parser.peek_token() == Token::Pipe {
720            parser.next_token();
721            poly.push(parse_atomic_type(parser));
722        }
723        TypeExpr::Union(poly.into_boxed_slice())
724    } else {
725        t
726    }
727}
728
729fn parse_atomic_type(parser: &mut Parser<'_>) -> TypeExpr {
730    let (next_token, span) = parser.next_token();
731    let mut t = if next_token == Token::LBrace {
732        let key_t = parse_type(parser);
733        parser.next_token_expect(
734            Token::Colon,
735            "A colon must separate key and value types in map types",
736        );
737        let value_t = parse_type(parser);
738        parser.next_token_expect(Token::RBrace, "Unmatched '{'");
739        TypeExpr::Map(Box::new(key_t), Box::new(value_t))
740    } else if let Token::Identifier(i) = next_token {
741        if parser.peek_token() == Token::DoubleColon {
742            parser.next_token();
743            let (namespace, end) = parse_namespace(parser, SmolStr::new(i));
744            TypeExpr::NamespacedIdentifier(namespace, (span.start, end).into())
745        } else {
746            TypeExpr::Identifier(SmolStr::new(i), span)
747        }
748    } else {
749        cold_path();
750        parser.error(
751            span,
752            ParserErr::UnexpectedToken(Token::Identifier(""), next_token, "Invalid type"),
753        );
754    };
755    loop {
756        if parser.peek_token() == Token::LBracket {
757            parser.next_token();
758            parser.next_token_expect(Token::RBracket, "Unmatched '['");
759            t = TypeExpr::Array(Box::new(t));
760        } else {
761            break;
762        }
763    }
764    t
765}
766
767fn parse_dylib_import(parser: &mut Parser<'_>) -> Expr {
768    let (t, Span { start, end: _ }) = parser.next_token();
769    debug_assert_eq!(t, Token::Dylib);
770    let (next_token, span) = parser.next_token();
771    let path = if let Token::String(s) = next_token {
772        SmolStr::new(parse_string(s))
773    } else {
774        cold_path();
775        parser.error(
776            span,
777            ParserErr::UnexpectedToken(Token::String(""), next_token, "Paths must be strings."),
778        );
779    };
780    parser.next_token_expect(Token::LBrace, "Blocks need to start with '{'.");
781    let (fn_signatures, end) = parse_fn_signature_block(parser);
782    Expr::ImportDylib(path, fn_signatures, (start, end).into())
783}
784
785/// Parses a brace-delimited block of typed function signatures shared by
786/// `dylib "..." { ... }` and `host "..." { ... }`. The opening `{` must already
787/// have been consumed; this consumes through the closing `}` and returns the
788/// parsed signatures together with the end offset of the `}`.
789fn parse_fn_signature_block(
790    parser: &mut Parser<'_>,
791) -> (Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>, u32) {
792    let mut fn_signatures: Vec<(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)> = Vec::new();
793    let end: u32;
794    loop {
795        if parser.peek_token() == Token::RBrace {
796            end = parser.next_token().1.end;
797            break;
798        }
799
800        let type_start = parser.peek_token();
801        let span = parser.peek_token_span();
802        let first = parse_type(parser);
803        let fn_name_span: Span;
804        let (return_type, fn_name) = if parser.peek_token() == Token::LParen {
805            if let TypeExpr::Identifier(name, span) = first {
806                fn_name_span = span;
807                (
808                    TypeExpr::Identifier(SmolStr::new_static("null"), span),
809                    name,
810                )
811            } else {
812                parser.error(
813                    span,
814                    ParserErr::UnexpectedToken(
815                        Token::Identifier(""),
816                        type_start,
817                        "Function names must be identifiers.",
818                    ),
819                );
820            }
821        } else {
822            let (next_token, span) = parser.next_token();
823            fn_name_span = span;
824            let fn_name = if let Token::Identifier(name) = next_token {
825                SmolStr::new(name)
826            } else {
827                cold_path();
828                parser.error(
829                    span,
830                    ParserErr::UnexpectedToken(
831                        Token::Identifier(""),
832                        next_token,
833                        "Function names must be identifiers.",
834                    ),
835                );
836            };
837            (first, fn_name)
838        };
839        parser.next_token_expect(
840            Token::LParen,
841            "Function arguments must be delimited by parentheses",
842        );
843        let mut args: Vec<TypeExpr> = Vec::with_capacity(2);
844        loop {
845            if parser.peek_token() == Token::RParen {
846                break;
847            }
848            // `...` marks a variadic host function: it accepts any number of
849            // arguments of any type, delivered to the registered closure as a
850            // slice. Carried through as a reserved sentinel type the host
851            // resolver recognises (meaningless in a `dylib` block).
852            if parser.peek_token() == Token::Ellipsis {
853                let span = parser.peek_token_span();
854                parser.next_token();
855                args.push(TypeExpr::Identifier(SmolStr::new_static("..."), span));
856                break;
857            }
858            args.push(parse_type(parser));
859            if parser.peek_token() == Token::Comma {
860                parser.next_token();
861            } else if !(parser.peek_token() == Token::RParen) {
862                cold_path();
863                let span = parser.peek_token_span();
864                parser.error(span, ParserErr::ArgumentsMissingCommaSeparator);
865            }
866        }
867        parser.next_token_expect(Token::RParen, "Unmatched ')'");
868        parser.next_token_expect(
869            Token::SemiColon,
870            "Function definitions must end with a semicolon",
871        );
872        fn_signatures.push((fn_name, Box::from(args), return_type, fn_name_span));
873    }
874    (fn_signatures.into_boxed_slice(), end)
875}
876
877/// Parses a `host "namespace" { signatures... }` block. The namespace names a
878/// table of Rust closures registered on the embedding [`crate::Engine`]; the
879/// signatures are type-checked at compile time exactly like `dylib` signatures.
880fn parse_host_block(parser: &mut Parser<'_>) -> Expr {
881    let (t, Span { start, end: _ }) = parser.next_token();
882    debug_assert_eq!(t, Token::Host);
883    let (next_token, span) = parser.next_token();
884    let namespace = if let Token::String(s) = next_token {
885        SmolStr::new(parse_string(s))
886    } else {
887        cold_path();
888        parser.error(
889            span,
890            ParserErr::UnexpectedToken(
891                Token::String(""),
892                next_token,
893                "Host namespaces must be strings.",
894            ),
895        );
896    };
897    parser.next_token_expect(Token::LBrace, "Blocks need to start with '{'.");
898    let (fn_signatures, end) = parse_fn_signature_block(parser);
899    Expr::HostBlock(namespace, fn_signatures, (start, end).into())
900}
901
902#[inline(always)]
903fn parse_file(parser: &mut Parser<'_>) -> Vec<Expr> {
904    let mut output: Vec<Expr> = Vec::with_capacity(2);
905    // parse file statements
906    while let Some(t) = parser.peek_token_opt() {
907        // An `impl` block lowers to several top-level function declarations, so
908        // it is expanded directly into `output` rather than yielding one Expr.
909        if t == Token::Impl {
910            parse_impl_block(parser, &mut output);
911            continue;
912        }
913        output.push(match t {
914            Token::Function => parse_function(parser),
915            Token::Import => parse_file_import(parser),
916            Token::Struct => parse_struct_declare(parser),
917            Token::Enum => parse_enum_declare(parser),
918            Token::Dylib => parse_dylib_import(parser),
919            Token::Host => parse_host_block(parser),
920            unexpected => {
921                cold_path();
922                let span = parser.peek_token_span();
923                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."));
924            }
925        });
926    }
927    output
928}
929
930#[must_use]
931pub fn parse(input: &str, src: &Source) -> Vec<Expr> {
932    parse_file(&mut Parser {
933        input: Token::lexer(input).spanned().peekable(),
934        ctx: ParserCtx { src },
935        last_token_end: 0,
936    })
937}