Skip to main content

microcad_lang_parse/parser/
mod.rs

1// Copyright © 2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4mod error;
5mod helpers;
6mod parse_context;
7pub mod parsers;
8
9pub use error::{ParseError, ParseErrorKind, ParseErrors, RichError};
10pub use parse_context::ParseContext;
11
12use crate::ast;
13use crate::parser::{error::Rich, helpers::*};
14use crate::tokens::*;
15
16use chumsky::{
17    Parser, extra,
18    input::{Input, MappedInput},
19    prelude::*,
20    select_ref,
21};
22
23use std::str::FromStr;
24
25use microcad_lang_base::Span;
26
27/// Extra error.
28pub type Extra<'tokens> = extra::Err<RichError<'tokens>>;
29
30pub type InputMap<'input, 'token> =
31    fn(&'input SpannedToken<Token<'token>>) -> (&'input Token<'token>, &'input Span);
32
33pub type ParserInput<'input, 'token> = MappedInput<
34    'input,
35    Token<'token>,
36    Span,
37    &'input [SpannedToken<Token<'token>>],
38    InputMap<'input, 'token>,
39>;
40
41/// Get parser input from tokens
42pub fn input<'input, 'tokens>(
43    input: &'input [SpannedToken<Token<'tokens>>],
44) -> ParserInput<'input, 'tokens> {
45    fn map_token_input<'a, 'token>(
46        spanned: &'a SpannedToken<Token<'token>>,
47    ) -> (&'a Token<'token>, &'a Span) {
48        (&spanned.token, &spanned.span)
49    }
50
51    let end = input.last().map(|t| t.span.end).unwrap_or_default();
52    Input::map(input, end..end, map_token_input)
53}
54
55/// Build an abstract syntax tree from a list of tokens
56pub fn parse<'tokens>(
57    tokens: &'tokens [SpannedToken<Token<'tokens>>],
58) -> Result<ast::Program, ParseErrors> {
59    parser()
60        .parse(input(tokens))
61        .into_result()
62        .map_err(|errors| errors.into())
63}
64
65const STRUCTURAL_TOKENS: &[Token] = &[
66    Token::SigilOpenCurlyBracket,
67    Token::SigilCloseCurlyBracket,
68    Token::SigilOpenBracket,
69    Token::SigilCloseBracket,
70    Token::SigilOpenSquareBracket,
71    Token::SigilCloseSquareBracket,
72    Token::SigilSemiColon,
73];
74
75fn parser<'tokens>()
76-> impl Parser<'tokens, ParserInput<'tokens, 'tokens>, ast::Program, Extra<'tokens>> {
77    use crate::ast::Dummy;
78
79    let mut statement_list_parser = Recursive::declare();
80    let mut statement_parser = Recursive::declare();
81    let mut expression_parser = Recursive::declare();
82    let mut type_parser = Recursive::declare();
83    let mut outer_attribute_parser = Recursive::declare();
84    let mut if_inner = Recursive::declare();
85
86    let semi_recovery = none_of(Token::SigilSemiColon).repeated().ignored();
87
88    let reserved_keyword = select_ref! {
89        token @ (
90            Token::KeywordPlugin |
91            Token::KeywordAssembly |
92            Token::KeywordMaterial |
93            Token::KeywordUnit |
94            Token::KeywordEnum |
95            Token::KeywordStruct |
96            Token::KeywordMatch |
97            Token::KeywordType |
98            Token::KeywordExtern
99        ) => token.kind(),
100    }
101    .boxed();
102    let keyword = select_ref! {
103        token @ (
104            Token::KeywordMod |
105            Token::KeywordPart |
106            Token::KeywordSketch |
107            Token::KeywordOp |
108            Token::KeywordFn |
109            Token::KeywordIf |
110            Token::KeywordElse |
111            Token::KeywordUse |
112            Token::KeywordAs |
113            Token::KeywordReturn |
114            Token::KeywordPub |
115            Token::KeywordConst |
116            Token::KeywordProp |
117            Token::KeywordInit
118        ) => token.kind(),
119    }
120    .boxed();
121
122    statement_list_parser.define({
123        let trailing_expr = outer_attribute_parser
124            .clone()
125            .then(expression_parser.clone())
126            .with_extras()
127            .map_with(
128                |((attributes, expression), extras), e| ast::ExpressionStatement {
129                    span: e.span(),
130                    extras,
131                    attributes,
132                    expression,
133                },
134            )
135            .map(Box::new)
136            .or_not()
137            .boxed();
138
139        parsers::whitespace()
140            .or_not()
141            .ignore_then(statement_parser.clone())
142            .then(parsers::trailing_extras())
143            .repeated()
144            .collect::<Vec<(ast::Statement, ast::TrailingExtras)>>()
145            .then(trailing_expr)
146            .with_extras()
147            .map_with(|((statements, tail), extras), e| ast::StatementList {
148                span: e.span(),
149                extras,
150                statements,
151                tail,
152            })
153            .boxed()
154    });
155
156    let block_recovery =
157        ignore_till_matched_curly().map_with(|_, e| ast::StatementList::dummy(e.span()));
158
159    let body = parsers::whitespace()
160        .or_not()
161        .ignore_then(statement_list_parser.clone().delimited_with_spanned_error(
162            just(Token::SigilOpenCurlyBracket),
163            just(Token::SigilCloseCurlyBracket),
164            |err: RichError, open, end| {
165                Rich::custom(
166                    err.span().clone(),
167                    ParseErrorKind::UnclosedBracket {
168                        open,
169                        end,
170                        kind: "code block",
171                        close_token: Token::SigilCloseCurlyBracket,
172                    },
173                )
174            },
175        ))
176        .recover_with(via_parser(block_recovery))
177        .map_with(|statements, e| ast::Body {
178            span: e.span(),
179            statements,
180        })
181        .boxed();
182
183    let identifier_parser = select_ref! { Token::Identifier(ident) = e => ast::Identifier {
184        span: e.span(),
185        name: ident.as_ref().into(),
186    } }
187    .or(reserved_keyword
188        .clone()
189        .validate(|kind, e, emitter| {
190            emitter.emit(Rich::custom(
191                e.span(),
192                ParseErrorKind::ReservedKeywordAsIdentifier(kind),
193            ));
194            kind
195        })
196        .map_with(|kind, e| ast::Identifier {
197            span: e.span(),
198            name: kind.into(),
199        }))
200    .or(keyword
201        .validate(|kind, e, emitter| {
202            emitter.emit(Rich::custom(
203                e.span(),
204                ParseErrorKind::KeywordAsIdentifier(kind),
205            ));
206            kind
207        })
208        .map_with(|kind, e| ast::Identifier {
209            span: e.span(),
210            name: kind.into(),
211        }))
212    .labelled("identifier")
213    .boxed();
214
215    let qualified_name = identifier_parser
216        .clone()
217        .separated_by(just(Token::SigilDoubleColon))
218        .at_least(1)
219        .collect::<Vec<_>>()
220        .with_extras()
221        .map_with(|(parts, extras), e| ast::QualifiedName {
222            span: e.span(),
223            parts,
224            extras,
225        })
226        .labelled("qualified name")
227        .boxed();
228
229    let unit = parsers::unit().boxed();
230
231    type_parser.define({
232        let single = select_ref! {
233            Token::Identifier(ident) = e => ast::SingleType {
234                span: e.span(),
235                name: ident.as_ref().into()
236            },
237        }
238        .map(ast::Type::Single)
239        .labelled("single type")
240        .boxed();
241
242        let array = parsers::whitespace()
243            .or_not()
244            .ignore_then(type_parser.clone())
245            .then_maybe_whitespace()
246            .delimited_by(
247                just(Token::SigilOpenSquareBracket),
248                just(Token::SigilCloseSquareBracket),
249            )
250            .map_with(|inner, e| {
251                ast::Type::Array(ast::ArrayType {
252                    span: e.span(),
253                    inner: Box::new(inner),
254                })
255            })
256            .labelled("array type")
257            .boxed();
258
259        let tuple = parsers::whitespace()
260            .or_not()
261            .ignore_then(identifier_parser.clone())
262            .then_ignore(just(Token::SigilColon))
263            .or_not()
264            .then_maybe_whitespace()
265            .then(type_parser.clone())
266            .then_maybe_whitespace()
267            .separated_by(just(Token::SigilComma))
268            .allow_trailing()
269            .collect::<Vec<_>>()
270            .then_maybe_whitespace()
271            .delimited_with_spanned_error(
272                just(Token::SigilOpenBracket),
273                just(Token::SigilCloseBracket),
274                |err: RichError, open, end| {
275                    Rich::custom(
276                        err.span().clone(),
277                        ParseErrorKind::UnclosedBracket {
278                            open,
279                            end,
280                            kind: "tuple type",
281                            close_token: Token::SigilCloseBracket,
282                        },
283                    )
284                },
285            )
286            .map_with(|inner, e| {
287                ast::Type::Tuple(ast::TupleType {
288                    span: e.span(),
289                    inner,
290                })
291            })
292            .labelled("tuple type")
293            .boxed();
294
295        single.or(array).or(tuple).labelled("type").boxed()
296    });
297
298    let unary_operator_parser = select_ref! {
299        Token::OperatorSubtract = e => ast::UnaryOperator { span: e.span(), operation: ast::UnaryOperatorType::Minus },
300        Token::OperatorAdd = e => ast::UnaryOperator { span: e.span(), operation: ast::UnaryOperatorType::Plus },
301        Token::OperatorNot = e => ast::UnaryOperator { span: e.span(), operation: ast::UnaryOperatorType::Not },
302    }
303    .labelled("unary operator")
304    .boxed();
305
306    // Parse for a DocBlock, starting with `///`
307    let doc_block = select_ref! {
308        Token::DocComment(comment) => comment.to_string(),
309    }
310    .then_whitespace()
311    .repeated()
312    .collect::<Vec<_>>()
313    .map_with(|lines, e| ast::DocBlock {
314        span: e.span(),
315        lines,
316    })
317    .labelled("doc block")
318    .boxed();
319
320    let tuple_recovery = nested_delimiters(
321        Token::SigilOpenBracket,
322        Token::SigilCloseBracket,
323        [
324            (
325                Token::SigilOpenSquareBracket,
326                Token::SigilCloseSquareBracket,
327            ),
328            (Token::SigilOpenCurlyBracket, Token::SigilCloseCurlyBracket),
329        ],
330        |_| (),
331    )
332    .map_with(|_, e| {
333        (
334            vec![ast::TupleItem::dummy(e.span())],
335            ast::ItemExtras::default(),
336        )
337    });
338
339    let tuple_body = identifier_parser
340        .clone()
341        .then_maybe_whitespace()
342        .then_ignore(just(Token::OperatorAssignment).then_maybe_whitespace())
343        .or_not()
344        .then(expression_parser.clone())
345        .with_extras()
346        .map_with(|((name, value), extras), e| ast::TupleItem {
347            span: e.span(),
348            extras,
349            name,
350            value,
351        })
352        .then_maybe_whitespace()
353        .separated_by(just(Token::SigilComma).then_maybe_whitespace())
354        .allow_trailing()
355        .collect::<Vec<_>>()
356        .boxed();
357
358    let call_inner = qualified_name
359        .clone()
360        .then(
361            tuple_body
362                .clone()
363                .with_extras()
364                .map_with(|(arguments, extras), e| ast::ArgumentList {
365                    span: e.span(),
366                    extras,
367                    arguments: arguments
368                        .into_iter()
369                        .map(|item| match item.name {
370                            Some(name) => ast::Argument::Named(ast::NamedArgument {
371                                span: item.span,
372                                extras: item.extras,
373                                name,
374                                value: item.value,
375                            }),
376                            None => ast::Argument::Unnamed(ast::UnnamedArgument {
377                                span: item.span,
378                                extras: item.extras,
379                                value: item.value,
380                            }),
381                        })
382                        .collect::<Vec<_>>(),
383                })
384                .labelled("function arguments")
385                .delimited_with_spanned_error(
386                    just(Token::SigilOpenBracket),
387                    just(Token::SigilCloseBracket),
388                    |err: RichError, open, end| {
389                        Rich::custom(
390                            err.span().clone(),
391                            ParseErrorKind::UnclosedBracket {
392                                open,
393                                end,
394                                kind: "function arguments",
395                                close_token: Token::SigilCloseBracket,
396                            },
397                        )
398                    },
399                )
400                .recover_with(via_parser(
401                    tuple_recovery
402                        .clone()
403                        .map_with(|_, e| ast::ArgumentList::dummy(e.span())),
404                )),
405        )
406        .with_extras()
407        .map_with(|((name, arguments), extras), e| ast::Call {
408            span: e.span(),
409            extras,
410            name,
411            arguments,
412        })
413        .boxed();
414
415    statement_parser.define({
416        let visibility = select_ref! {
417            Token::KeywordPub => ast::Visibility::Public,
418        }
419        .labelled("visibility");
420
421        let expression = outer_attribute_parser
422            .clone()
423            .then(expression_parser.clone())
424            .with_extras()
425            .map_with(
426                |((attributes, expression), extras), e| ast::ExpressionStatement {
427                    span: e.span(), // FIXME: This should only return the span of attributes and expression
428                    extras,
429                    attributes,
430                    expression,
431                },
432            )
433            .map(ast::Statement::Expression)
434            .boxed();
435
436        let expression_without_semi = outer_attribute_parser
437            .clone()
438            .then(expression_parser.clone().try_map(|expr, span| {
439                if expr.is_also_statement() {
440                    Ok(expr)
441                } else {
442                    Err(Rich::custom(
443                        span.clone(),
444                        ParseErrorKind::ExpressionMissingSemicolon { span },
445                    ))
446                }
447            }))
448            .with_extras()
449            .map_with(
450                |((attributes, expression), extras), e| ast::ExpressionStatement {
451                    span: e.span(), // FIXME: This should only return the span of attributes and expression
452                    extras,
453                    attributes,
454                    expression,
455                },
456            )
457            .map(ast::Statement::Expression)
458            .boxed();
459
460        let local_assignment_inner = outer_attribute_parser
461            .clone()
462            .then(identifier_parser.clone())
463            .then_maybe_whitespace()
464            .then(
465                just(Token::SigilColon)
466                    .then_maybe_whitespace()
467                    .ignore_then(type_parser.clone())
468                    .then_maybe_whitespace()
469                    .or_not(),
470            )
471            .then_ignore(just(Token::OperatorAssignment))
472            .then_maybe_whitespace()
473            .then(
474                expression_parser.clone().recover_with(via_parser(
475                    semi_recovery
476                        .clone()
477                        .map_with(|_, e| ast::Expression::Error(e.span())),
478                )),
479            )
480            .with_extras()
481            .map_with(
482                |((((attributes, name), ty), value), extras), e| ast::LocalAssignment {
483                    span: e.span(),
484                    extras,
485                    attributes,
486                    name,
487                    value: Box::new(value),
488                    ty,
489                },
490            )
491            .boxed();
492
493        let local_assignment = local_assignment_inner
494            .clone()
495            .map(ast::Statement::LocalAssignment)
496            .labelled("local assignment");
497
498        let const_assignment_inner = doc_block
499            .clone()
500            .then(outer_attribute_parser.clone())
501            .then(visibility.then_whitespace().or_not())
502            .then(just(Token::KeywordConst).map_with(|_, e| e.span()))
503            .then_maybe_whitespace()
504            .then(identifier_parser.clone())
505            .then_maybe_whitespace()
506            .then(
507                just(Token::SigilColon)
508                    .then_maybe_whitespace()
509                    .ignore_then(type_parser.clone())
510                    .then_maybe_whitespace()
511                    .or_not(),
512            )
513            .then_ignore(just(Token::OperatorAssignment))
514            .then_maybe_whitespace()
515            .then(
516                expression_parser.clone().recover_with(via_parser(
517                    semi_recovery
518                        .clone()
519                        .map_with(|_, e| ast::Expression::Error(e.span())),
520                )),
521            )
522            .with_extras()
523            .map_with(
524                |(
525                    ((((((doc, attributes), visibility), keyword_span), name), ty), value),
526                    extras,
527                ),
528                 e| {
529                    ast::ConstAssignment {
530                        span: e.span(),
531                        keyword_span,
532                        extras,
533                        doc,
534                        attributes,
535                        visibility,
536                        name,
537                        value: Box::new(value),
538                        ty,
539                    }
540                },
541            )
542            .boxed();
543
544        let const_assignment = const_assignment_inner
545            .clone()
546            .map(ast::Statement::Const)
547            .labelled("const assignment");
548
549        // A pub assignment without the `const` keyword will eventually become a const assignment
550        let pub_assignment_inner = doc_block
551            .clone()
552            .then(outer_attribute_parser.clone())
553            .then(just(Token::KeywordPub).map_with(|_, e| e.span()))
554            .then_maybe_whitespace()
555            .then(identifier_parser.clone())
556            .then_maybe_whitespace()
557            .then(
558                just(Token::SigilColon)
559                    .then_maybe_whitespace()
560                    .ignore_then(type_parser.clone())
561                    .then_maybe_whitespace()
562                    .or_not(),
563            )
564            .then_ignore(just(Token::OperatorAssignment))
565            .then_maybe_whitespace()
566            .then(
567                expression_parser.clone().recover_with(via_parser(
568                    semi_recovery
569                        .clone()
570                        .map_with(|_, e| ast::Expression::Error(e.span())),
571                )),
572            )
573            .with_extras()
574            .map_with(
575                |((((((doc, attributes), keyword_span), name), ty), value), extras), e| {
576                    ast::ConstAssignment {
577                        span: e.span(),
578                        keyword_span,
579                        extras,
580                        doc,
581                        attributes,
582                        visibility: Some(ast::Visibility::Public),
583                        name,
584                        value: Box::new(value),
585                        ty,
586                    }
587                },
588            )
589            .boxed();
590
591        let pub_assignment = pub_assignment_inner
592            .clone()
593            .map(ast::Statement::Const)
594            .labelled("pub const assignment");
595
596        let property_assignment_inner = doc_block
597            .clone()
598            .then(outer_attribute_parser.clone())
599            .then(just(Token::KeywordProp).map_with(|_, e| e.span()))
600            .then_maybe_whitespace()
601            .then(identifier_parser.clone())
602            .then_maybe_whitespace()
603            .then(
604                just(Token::SigilColon)
605                    .then_maybe_whitespace()
606                    .ignore_then(type_parser.clone())
607                    .then_maybe_whitespace()
608                    .or_not(),
609            )
610            .then_ignore(just(Token::OperatorAssignment))
611            .then_maybe_whitespace()
612            .then(
613                expression_parser.clone().recover_with(via_parser(
614                    semi_recovery
615                        .clone()
616                        .map_with(|_, e| ast::Expression::Error(e.span())),
617                )),
618            )
619            .with_extras()
620            .map_with(
621                |((((((doc, attributes), keyword_span), name), ty), value), extras), e| {
622                    ast::PropertyAssignment {
623                        span: e.span(),
624                        keyword_span,
625                        extras,
626                        doc,
627                        attributes,
628                        name,
629                        value: Box::new(value),
630                        ty,
631                    }
632                },
633            )
634            .boxed();
635
636        let property_assignment = property_assignment_inner
637            .clone()
638            .map(ast::Statement::Property)
639            .labelled("property assignment");
640
641        let attribute_command = local_assignment_inner
642            .clone()
643            .map(ast::AttributeCommand::Assignment)
644            .or(call_inner.clone().map(ast::AttributeCommand::Call))
645            .or(identifier_parser.clone().map(ast::AttributeCommand::Ident));
646
647        let attribute_inner = attribute_command
648            .separated_by(just(Token::SigilComma).then_maybe_whitespace())
649            .at_least(1)
650            .collect::<Vec<_>>()
651            .then_maybe_whitespace()
652            .delimited_by(
653                just(Token::SigilOpenSquareBracket),
654                just(Token::SigilCloseSquareBracket),
655            )
656            .boxed();
657
658        outer_attribute_parser.define({
659            just(Token::SigilHash)
660                .ignore_then(attribute_inner.clone())
661                .then_whitespace()
662                .with_extras()
663                .map_with(|(commands, extras), e| ast::Attribute {
664                    span: e.span(),
665                    is_inner: false,
666                    extras,
667                    commands,
668                })
669                .labelled("attribute")
670                .repeated()
671                .collect::<Vec<ast::Attribute>>()
672                .boxed()
673        });
674
675        let parameter_list_inner = parsers::whitespace()
676            .or_not()
677            .ignore_then(identifier_parser.clone())
678            .then_maybe_whitespace()
679            .then(
680                just(Token::SigilColon)
681                    .then_maybe_whitespace()
682                    .ignore_then(
683                        type_parser.clone().recover_with(via_parser(
684                            recovery_expect_any_except(&[
685                                Token::SigilComma,
686                                Token::OperatorAssignment,
687                                Token::SigilCloseBracket,
688                            ])
689                            .map_with(|_, e| ast::Type::dummy(e.span())),
690                        )),
691                    )
692                    .then_maybe_whitespace()
693                    .or_not(),
694            )
695            .then(
696                just(Token::OperatorAssignment)
697                    .then_maybe_whitespace()
698                    .ignore_then(
699                        expression_parser.clone().recover_with(via_parser(
700                            recovery_expect_any_except(&[
701                                Token::SigilComma,
702                                Token::SigilCloseBracket,
703                            ])
704                            .map_with(|_, e| ast::Expression::Error(e.span())),
705                        )),
706                    )
707                    .then_maybe_whitespace()
708                    .or_not(),
709            )
710            .with_extras()
711            .map_with(|(((name, ty), default), extras), e| ast::Parameter {
712                span: e.span(),
713                extras,
714                name,
715                ty,
716                default,
717            })
718            .separated_by(just(Token::SigilComma))
719            .allow_trailing()
720            .collect::<Vec<_>>()
721            .boxed();
722
723        let parameter_list = parameter_list_inner
724            .then_maybe_whitespace()
725            .with_extras()
726            .map_with(|(parameters, extras), e| ast::ParameterList {
727                span: e.span(),
728                extras,
729                parameters,
730            })
731            .delimited_with_spanned_error(
732                just(Token::SigilOpenBracket),
733                just(Token::SigilCloseBracket),
734                |err: RichError, open, end| {
735                    Rich::custom(
736                        err.span().clone(),
737                        ParseErrorKind::UnclosedBracket {
738                            open,
739                            end,
740                            kind: "function parameters",
741                            close_token: Token::SigilCloseBracket,
742                        },
743                    )
744                },
745            )
746            .recover_with(via_parser(
747                ignore_till_matched_brackets()
748                    .or(none_of(STRUCTURAL_TOKENS).repeated())
749                    .map_with(|_, e| ast::ParameterList::dummy(e.span())),
750            ))
751            .boxed();
752
753        let inline_module = doc_block
754            .clone()
755            .then(outer_attribute_parser.clone())
756            .then(visibility.then_whitespace().or_not())
757            .then(just(Token::KeywordMod).map_with(|_, e| e.span()))
758            .then_whitespace()
759            .then(
760                identifier_parser.clone().recover_with(via_parser(
761                    recovery_expect_any_except(&[Token::SigilOpenCurlyBracket])
762                        .map_with(|_, e| ast::Identifier::dummy(e.span())),
763                )),
764            )
765            .then_maybe_whitespace()
766            .then(body.clone())
767            .with_extras()
768            .map_with(
769                |((((((doc, attributes), visibility), keyword_span), name), body), extras), e| {
770                    ast::Statement::InlineModule(ast::InlineModule {
771                        span: e.span(),
772                        keyword_span,
773                        extras,
774                        doc,
775                        attributes,
776                        visibility,
777                        name,
778                        body,
779                    })
780                },
781            )
782            .boxed();
783
784        let file_module = doc_block
785            .clone()
786            .then(outer_attribute_parser.clone())
787            .then(visibility.then_whitespace().or_not())
788            .then(just(Token::KeywordMod).map_with(|_, e| e.span()))
789            .then_whitespace()
790            .then(
791                identifier_parser.clone().recover_with(via_parser(
792                    recovery_expect_any_except(&[Token::SigilOpenCurlyBracket])
793                        .map_with(|_, e| ast::Identifier::dummy(e.span())),
794                )),
795            )
796            .with_extras()
797            .map_with(
798                |(((((doc, attributes), visibility), keyword_span), name), extras), e| {
799                    ast::Statement::FileModule(ast::FileModule {
800                        span: e.span(),
801                        keyword_span,
802                        extras,
803                        doc,
804                        attributes,
805                        visibility,
806                        name,
807                    })
808                },
809            )
810            .boxed();
811
812        let use_part = identifier_parser
813            .clone()
814            .map(ast::UseStatementPart::Identifier)
815            .or(just(Token::OperatorMultiply)
816                .map_with(|_, e| ast::UseStatementPart::Glob(e.span())))
817            .recover_with(via_parser(
818                recovery_expect_any_except(&[Token::SigilDoubleColon])
819                    .map_with(|_, e| ast::UseStatementPart::Error(e.span())),
820            ))
821            .boxed();
822
823        let use_parts = use_part
824            .separated_by(just(Token::SigilDoubleColon))
825            .at_least(1)
826            .collect::<Vec<_>>()
827            .with_extras()
828            .map_with(|(parts, extras), e| ast::UseName {
829                span: e.span(),
830                extras,
831                parts,
832            })
833            .boxed();
834
835        let use_statement = outer_attribute_parser
836            .clone()
837            .then(visibility.then_whitespace().or_not())
838            .then(just(Token::KeywordUse).map_with(|_, e| e.span()))
839            .then_whitespace()
840            .then(use_parts)
841            .then(
842                select_ref! {
843                    Token::KeywordAs => (),
844                }
845                .then_whitespace()
846                .ignore_then(identifier_parser.clone().recover_with(via_parser(
847                    recovery_expect_any().map_with(|_, e| ast::Identifier::dummy(e.span())),
848                )))
849                .or_not(),
850            )
851            .with_extras()
852            .map_with(
853                |(((((attributes, visibility), keyword_span), name), use_as), extras), e| {
854                    ast::Statement::Use(ast::UseStatement {
855                        span: e.span(),
856                        attributes,
857                        visibility,
858                        keyword_span,
859                        extras,
860                        name,
861                        use_as,
862                    })
863                },
864            )
865            .boxed();
866
867        let workbench_kind = select_ref! {
868            Token::KeywordSketch => ast::WorkbenchKind::Sketch,
869            Token::KeywordPart => ast::WorkbenchKind::Part,
870            Token::KeywordOp => ast::WorkbenchKind::Op,
871        }
872        .boxed();
873
874        let init = doc_block
875            .clone()
876            .then(outer_attribute_parser.clone())
877            .then(just(Token::KeywordInit).map_with(|_, e| e.span()))
878            .then_maybe_whitespace()
879            .then(parameter_list.clone())
880            .then(body.clone())
881            .with_extras()
882            .map_with(
883                |(((((doc, attributes), keyword_span), arguments), body), extras), e| {
884                    ast::Statement::Init(ast::InitDefinition {
885                        span: e.span(),
886                        keyword_span,
887                        extras,
888                        doc,
889                        attributes,
890                        parameters: arguments,
891                        body,
892                    })
893                },
894            )
895            .boxed();
896        let workbench = doc_block
897            .clone()
898            .then(outer_attribute_parser.clone())
899            .then(visibility.then_whitespace().or_not())
900            .then(workbench_kind.map_with(|kind, e| (kind, e.span())))
901            .then_whitespace()
902            .then(
903                identifier_parser.clone().recover_with(via_parser(
904                    recovery_expect_any_except(&[
905                        Token::SigilOpenCurlyBracket,
906                        Token::SigilOpenBracket,
907                    ])
908                    .map_with(|_, e| ast::Identifier::dummy(e.span())),
909                )),
910            )
911            .then_maybe_whitespace()
912            .then(parameter_list.clone())
913            .then_maybe_whitespace()
914            .then(body.clone())
915            .with_extras()
916            .map_with(
917                |(
918                    (
919                        (
920                            ((((doc, attributes), visibility), (kind, keyword_span)), name),
921                            arguments,
922                        ),
923                        body,
924                    ),
925                    extras,
926                ),
927                 e| {
928                    ast::Statement::Workbench(ast::WorkbenchDefinition {
929                        span: e.span(),
930                        keyword_span,
931                        extras,
932                        kind,
933                        doc,
934                        attributes,
935                        visibility,
936                        name,
937                        plan: arguments,
938                        body,
939                    })
940                },
941            )
942            .boxed();
943
944        let return_statement = just(Token::KeywordReturn)
945            .map_with(|_, e| e.span())
946            .then_maybe_whitespace()
947            .then(expression_parser.clone().or_not())
948            .with_extras()
949            .map_with(|((keyword_span, value), extras), e| {
950                ast::Statement::Return(ast::Return {
951                    span: e.span(),
952                    keyword_span,
953                    extras,
954                    value,
955                })
956            })
957            .boxed();
958
959        let function = doc_block
960            .clone()
961            .then(outer_attribute_parser.clone())
962            .then(visibility.then_whitespace().or_not())
963            .then(just(Token::KeywordFn).map_with(|_, e| e.span()))
964            .then_whitespace()
965            .then(
966                identifier_parser.clone().recover_with(via_parser(
967                    recovery_expect_any_except(&[
968                        Token::SigilOpenCurlyBracket,
969                        Token::SigilOpenBracket,
970                    ])
971                    .map_with(|_, e| ast::Identifier::dummy(e.span())),
972                )),
973            )
974            .then_maybe_whitespace()
975            .then(parameter_list.clone())
976            .then_maybe_whitespace()
977            .then(
978                just(Token::SigilSingleArrow)
979                    .then_maybe_whitespace()
980                    .ignore_then(type_parser.clone())
981                    .then_maybe_whitespace()
982                    .or_not(),
983            )
984            .then(body.clone())
985            .with_extras()
986            .map_with(
987                |(
988                    (
989                        (
990                            (((((doc, attributes), visibility), keyword_span), name), arguments),
991                            return_type,
992                        ),
993                        body,
994                    ),
995                    extras,
996                ),
997                 e| {
998                    ast::Statement::Function(ast::FunctionDefinition {
999                        span: e.span(),
1000                        keyword_span,
1001                        extras,
1002                        doc,
1003                        attributes,
1004                        visibility,
1005                        name,
1006                        parameters: arguments,
1007                        return_type,
1008                        body,
1009                    })
1010                },
1011            )
1012            .boxed();
1013
1014        let inner_doc_comment = select_ref! {
1015            Token::InnerDocComment(comment) => comment.to_string(),
1016        }
1017        .labelled("inner doc-block")
1018        .map_with(|line, e| {
1019            ast::Statement::InnerDocComment(ast::InnerDocComment {
1020                span: e.span(),
1021                line,
1022            })
1023        })
1024        .boxed();
1025
1026        let inner_attribute = just(Token::SigilHash)
1027            .ignore_then(just(Token::OperatorNot))
1028            .ignore_then(attribute_inner)
1029            .with_extras()
1030            .map_with(|(commands, extras), e| ast::Attribute {
1031                span: e.span(),
1032                is_inner: true,
1033                extras,
1034                commands,
1035            })
1036            .labelled("inner attribute")
1037            .map(ast::Statement::InnerAttribute)
1038            .boxed();
1039
1040        let not_assignment = parsers::whitespace()
1041            .or_not()
1042            .then(none_of([
1043                Token::OperatorAssignment,
1044                Token::SigilDoubleColon,
1045            ]))
1046            .ignored()
1047            .or(just(Token::SigilSemiColon).ignored())
1048            .rewind()
1049            .boxed();
1050
1051        let reserved_keyword_statement = reserved_keyword
1052            .clone()
1053            .then_ignore(not_assignment.clone())
1054            .try_map_with(|kind, e| {
1055                Err::<(), _>(Rich::custom(
1056                    e.span(),
1057                    ParseErrorKind::ReservedKeyword(kind),
1058                ))
1059            })
1060            .ignored()
1061            .recover_with(via_parser(
1062                reserved_keyword
1063                    .then_ignore(not_assignment)
1064                    .clone()
1065                    .ignore_then(
1066                        none_of(STRUCTURAL_TOKENS)
1067                            .repeated()
1068                            .then_ignore(ignore_till_matched_curly())
1069                            .or(ignore_till_semi().then_ignore(just(Token::SigilSemiColon))),
1070                    ),
1071            ))
1072            .map_with(|_, e| ast::Statement::Error(e.span()))
1073            .boxed();
1074
1075        let with_semi = return_statement
1076            .or(use_statement)
1077            .or(const_assignment)
1078            .or(pub_assignment)
1079            .or(file_module)
1080            .or(property_assignment)
1081            .or(local_assignment)
1082            .or(expression)
1083            .then_ignore(
1084                just(Token::SigilSemiColon)
1085                    .labelled("semicolon")
1086                    .ignored()
1087                    .recover_with(via_parser(
1088                        none_of(STRUCTURAL_TOKENS)
1089                            .repeated()
1090                            .then_ignore(just(Token::SigilSemiColon)),
1091                    )),
1092            )
1093            .boxed();
1094
1095        let without_semi = function
1096            .or(inner_doc_comment)
1097            .or(inner_attribute)
1098            .or(init)
1099            .or(workbench)
1100            .or(inline_module)
1101            .or(expression_without_semi)
1102            .boxed();
1103
1104        with_semi
1105            .or(reserved_keyword_statement)
1106            .or(without_semi)
1107            .boxed()
1108            .labelled("statement")
1109    });
1110
1111    expression_parser.define({
1112        let unclosed_string = select_ref! {
1113            Token::Error(LexerError::UnclosedString(_)) => (),
1114        }
1115        .ignore_then(
1116            semi_recovery
1117                .clone()
1118                .try_map_with(|_, e| {
1119                    let span: Span = e.span();
1120                    Err::<ast::Expression, _>(Rich::custom(
1121                        (span.start - 1)..span.end,
1122                        ParseErrorKind::UnterminatedString,
1123                    ))
1124                })
1125                .recover_with(via_parser(
1126                    semi_recovery
1127                        .clone()
1128                        .map_with(|_, e| ast::Expression::Error(e.span())),
1129                )),
1130        )
1131        .labelled("unclosed string")
1132        .boxed();
1133
1134        let literal = parsers::literal()
1135            .map(ast::Expression::Literal)
1136            .labelled("literal")
1137            .boxed()
1138            .or(unclosed_string);
1139
1140        let marker = just(Token::SigilAt)
1141            .ignore_then(identifier_parser.clone())
1142            .map(ast::Expression::Marker)
1143            .labelled("marker")
1144            .boxed();
1145
1146        let string_content_part = select_ref! {
1147            Token::StringContent(content) = e => ast::StringPart::Content(ast::StringLiteral {
1148                span: e.span(),
1149                content: content.as_ref().into(),
1150            }),
1151            Token::Character(char) = e => ast::StringPart::Char(ast::StringCharacter {
1152                span: e.span(),
1153                character: *char,
1154            }),
1155        }
1156        .labelled("string content")
1157        .boxed();
1158
1159        let format_precision = select_ref!(
1160            Token::StringFormatPrecision(precision) = e => {
1161                u32::from_str(&precision[1..]).map_err(|err| (err, e.span()))
1162            }
1163        );
1164        let format_width = select_ref!(
1165            Token::StringFormatWidth(width) = e => {
1166                u32::from_str(&width[1..]).map_err(|err| (err, e.span()))
1167            }
1168        );
1169        let format_spec = format_width
1170            .or_not()
1171            .then(format_precision.or_not())
1172            .map_with(|(width, precision), e| ast::StringFormatSpecification {
1173                span: e.span(),
1174                width,
1175                precision,
1176            })
1177            .labelled("string format specification")
1178            .boxed();
1179
1180        let string_format_part = expression_parser
1181            .clone()
1182            .then(format_spec)
1183            .with_extras()
1184            .delimited_by(
1185                just(Token::StringFormatOpen),
1186                just(Token::StringFormatClose),
1187            )
1188            .map_with(
1189                |((expression, specification), extras), e| ast::StringExpression {
1190                    span: e.span(),
1191                    extras,
1192                    expression: Box::new(expression),
1193                    specification: Box::new(specification),
1194                },
1195            )
1196            .map(ast::StringPart::Expression)
1197            .labelled("string format expression")
1198            .boxed();
1199        let string_part = string_content_part
1200            .or(string_format_part)
1201            .labelled("format string content");
1202
1203        let string_format = string_part
1204            .repeated()
1205            .collect::<Vec<_>>()
1206            .delimited_by(just(Token::FormatStringStart), just(Token::FormatStringEnd))
1207            .with_extras()
1208            .map_with(|(parts, extras), e| ast::FormatString {
1209                span: e.span(),
1210                extras,
1211                parts,
1212            })
1213            .map(ast::Expression::String)
1214            .boxed();
1215
1216        let tuple = parsers::whitespace()
1217            .or_not()
1218            .ignore_then(tuple_body.clone())
1219            .with_extras()
1220            .delimited_with_spanned_error(
1221                just(Token::SigilOpenBracket),
1222                just(Token::SigilCloseBracket),
1223                |err: RichError, open, end| {
1224                    Rich::custom(
1225                        err.span().clone(),
1226                        ParseErrorKind::UnclosedBracket {
1227                            open,
1228                            end,
1229                            kind: "tuple",
1230                            close_token: Token::SigilCloseBracket,
1231                        },
1232                    )
1233                },
1234            )
1235            .map_with(|(values, extras), e| {
1236                ast::Expression::Tuple(ast::TupleExpression {
1237                    span: e.span(),
1238                    extras,
1239                    values,
1240                })
1241            })
1242            .labelled("tuple");
1243
1244        let bracketed = expression_parser
1245            .clone()
1246            .then_maybe_whitespace()
1247            .delimited_with_spanned_error(
1248                just(Token::SigilOpenBracket).then_maybe_whitespace(),
1249                just(Token::SigilCloseBracket),
1250                |err: RichError, open, end| {
1251                    Rich::custom(
1252                        err.span().clone(),
1253                        ParseErrorKind::UnclosedBracket {
1254                            open,
1255                            end,
1256                            kind: "bracketed expression",
1257                            close_token: Token::SigilCloseBracket,
1258                        },
1259                    )
1260                },
1261            )
1262            .map_with(|expression, e| ast::Expression::Bracketed(Box::new(expression), e.span()))
1263            .boxed();
1264
1265        let array_item = expression_parser
1266            .clone()
1267            .with_extras()
1268            .map_with(|(expression, extras), e| ast::ArrayItem {
1269                span: e.span(),
1270                extras,
1271                expression,
1272            })
1273            .boxed();
1274
1275        let array_range = array_item
1276            .clone()
1277            .then_ignore(just(Token::SigilDoubleDot))
1278            .then(array_item.clone())
1279            .with_extras()
1280            .delimited_by(
1281                just(Token::SigilOpenSquareBracket).then_maybe_whitespace(),
1282                just(Token::SigilCloseSquareBracket),
1283            )
1284            .then(unit.clone().or_not())
1285            .map_with(|(((start, end), extras), unit), e| {
1286                ast::Expression::ArrayRange(ast::ArrayRangeExpression {
1287                    span: e.span(),
1288                    extras,
1289                    start: Box::new(start),
1290                    end: Box::new(end),
1291                    unit,
1292                })
1293            })
1294            .labelled("array range")
1295            .boxed();
1296
1297        let array_list = array_item
1298            .clone()
1299            .separated_by(just(Token::SigilComma).then_maybe_whitespace())
1300            .allow_trailing()
1301            .collect::<Vec<_>>()
1302            .with_extras()
1303            .delimited_by(
1304                just(Token::SigilOpenSquareBracket).then_maybe_whitespace(),
1305                just(Token::SigilCloseSquareBracket),
1306            )
1307            .then(unit.clone().or_not())
1308            .map_with(|((items, extras), unit), e| {
1309                ast::Expression::ArrayList(ast::ArrayListExpression {
1310                    span: e.span(),
1311                    extras,
1312                    items,
1313                    unit,
1314                })
1315            })
1316            .labelled("array")
1317            .boxed();
1318
1319        let body_expression = body
1320            .clone()
1321            .map(ast::Expression::Body)
1322            .labelled("body expression")
1323            .boxed();
1324
1325        if_inner.define(
1326            just(Token::KeywordIf)
1327                .map_with(|_, e| e.span())
1328                .then_whitespace()
1329                .then(expression_parser.clone())
1330                .then_maybe_whitespace()
1331                .then(body.clone())
1332                .then_maybe_whitespace()
1333                .then(
1334                    just(Token::KeywordElse)
1335                        .map_with(|_, e| e.span())
1336                        .then_maybe_whitespace()
1337                        .then(if_inner.clone())
1338                        .map(|(span, inner)| (span, Box::new(inner)))
1339                        .or_not(),
1340                )
1341                .then(
1342                    just(Token::KeywordElse)
1343                        .map_with(|_, e| e.span())
1344                        .then_maybe_whitespace()
1345                        .then(body.clone())
1346                        .or_not(),
1347                )
1348                .with_extras()
1349                .map_with(
1350                    |(((((if_span, condition), body), next_if), else_body), extras), e| {
1351                        let (next_if_span, next_if) = next_if
1352                            .map(|(span, if_expr)| (Some(span), Some(if_expr)))
1353                            .unwrap_or((None, None));
1354                        let (else_span, else_body) = else_body
1355                            .map(|(span, body)| (Some(span), Some(body)))
1356                            .unwrap_or((None, None));
1357                        ast::If {
1358                            span: e.span(),
1359                            if_span,
1360                            extras,
1361                            condition: Box::new(condition),
1362                            body,
1363                            next_if_span,
1364                            next_if,
1365                            else_span,
1366                            else_body,
1367                        }
1368                    },
1369                )
1370                .boxed(),
1371        );
1372        let if_expression = if_inner
1373            .map(ast::Expression::If)
1374            .labelled("if expression")
1375            .boxed();
1376
1377        let qualified_name_expr = identifier_parser
1378            .clone()
1379            .map_with(|ident, e| ast::QualifiedName {
1380                span: e.span(),
1381                parts: vec![ident],
1382                extras: ast::ItemExtras::default(),
1383            })
1384            .foldl_with(
1385                just(Token::SigilDoubleColon)
1386                    .ignore_then(identifier_parser.clone())
1387                    .repeated(),
1388                |mut acc, part, _| {
1389                    acc.span.end = part.span.end;
1390                    acc.parts.push(part);
1391                    acc
1392                },
1393            )
1394            .with_extras()
1395            .map(|(mut name, extras)| {
1396                name.extras = extras;
1397                name
1398            })
1399            .map(ast::Expression::QualifiedName)
1400            .boxed();
1401
1402        let call = call_inner
1403            .clone()
1404            .map(ast::Expression::Call)
1405            .labelled("method call");
1406
1407        let bracket_based = bracketed.or(tuple).recover_with(via_parser(
1408            tuple_recovery
1409                .clone()
1410                .map_with(|_, e| ast::Expression::Error(e.span())),
1411        ));
1412
1413        let base = literal
1414            .or(string_format)
1415            .or(call)
1416            .or(marker)
1417            .or(bracket_based)
1418            .or(array_range)
1419            .or(array_list)
1420            .or(body_expression)
1421            .or(if_expression)
1422            .or(qualified_name_expr)
1423            .boxed();
1424
1425        let access_attribute = just(Token::SigilHash)
1426            .ignore_then(identifier_parser.clone())
1427            .map(ast::ElementInner::Attribute)
1428            .labelled("attribute access")
1429            .boxed();
1430
1431        let access_tuple = just(Token::SigilDot)
1432            .ignore_then(identifier_parser.clone())
1433            .map(ast::ElementInner::Tuple)
1434            .labelled("tuple access")
1435            .boxed();
1436
1437        let access_method = just(Token::SigilDot)
1438            .ignore_then(call_inner)
1439            .map(ast::ElementInner::Method)
1440            .labelled("method call")
1441            .boxed();
1442
1443        let access_array = expression_parser
1444            .clone()
1445            .delimited_by(
1446                just(Token::SigilOpenSquareBracket),
1447                just(Token::SigilCloseSquareBracket),
1448            )
1449            .map(Box::new)
1450            .map(ast::ElementInner::ArrayElement)
1451            .labelled("array access")
1452            .boxed();
1453
1454        let access_item = access_attribute
1455            .or(access_method)
1456            .or(access_tuple)
1457            .or(access_array)
1458            .with_extras()
1459            .map_with(|(inner, extras), e| ast::Element {
1460                span: e.span(),
1461                inner,
1462                extras,
1463            })
1464            .repeated()
1465            .at_least(1)
1466            .collect::<Vec<ast::Element>>();
1467
1468        let element_access = base
1469            .clone()
1470            .foldl_with(access_item.repeated(), |value, element_chain, e| {
1471                ast::Expression::ElementAccess(ast::ElementAccess {
1472                    span: e.span(),
1473                    value: value.into(),
1474                    element_chain,
1475                })
1476            })
1477            .labelled("element access")
1478            .boxed();
1479
1480        let unary_expression = unary_operator_parser
1481            .then_maybe_whitespace()
1482            .then(element_access.clone())
1483            .with_extras()
1484            .map_with(|((op, rhs), extras), e| {
1485                ast::Expression::UnaryOperation(ast::UnaryOperation {
1486                    span: e.span(),
1487                    extras,
1488                    operation: op,
1489                    rhs: rhs.into(),
1490                })
1491            })
1492            .boxed();
1493
1494        let binary_param = element_access.or(unary_expression.clone());
1495
1496        let near = binop(binary_param, &[Token::OperatorNear]);
1497        let xor = binop(near, &[Token::OperatorPowerXor, Token::OperatorXor]);
1498        let union_intersect = binop(xor, &[Token::OperatorUnion, Token::OperatorIntersect]);
1499        let mul_div = binop(
1500            union_intersect,
1501            &[Token::OperatorMultiply, Token::OperatorDivide],
1502        );
1503        let add_sub = binop(mul_div, &[Token::OperatorAdd, Token::OperatorSubtract]);
1504        let less_greater_eq = binop(
1505            add_sub,
1506            &[Token::OperatorLessEqual, Token::OperatorGreaterEqual],
1507        );
1508        let less_greater = binop(
1509            less_greater_eq,
1510            &[Token::OperatorLessThan, Token::OperatorGreaterThan],
1511        );
1512        let eq_neq = binop(
1513            less_greater,
1514            &[Token::OperatorEqual, Token::OperatorNotEqual],
1515        );
1516        let or_and = binop(eq_neq, &[Token::OperatorOr, Token::OperatorAnd]);
1517
1518        or_and.labelled("expression").boxed()
1519    });
1520
1521    statement_list_parser
1522        .then_ignore(end())
1523        .map_with(move |statements, ex| ast::Program {
1524            span: ex.span(),
1525            statements,
1526        })
1527}
1528
1529impl crate::Parse for ast::Literal {
1530    fn parse(context: &ParseContext) -> Result<Self, ParseErrors> {
1531        fn literal<'tokens>()
1532        -> impl Parser<'tokens, ParserInput<'tokens, 'tokens>, ast::Literal, Extra<'tokens>>
1533        {
1534            crate::parsers::literal()
1535        }
1536
1537        match context {
1538            ParseContext::Element(source) => {
1539                use chumsky::Parser;
1540                let tokens = crate::tokens::lex(source.value()).collect::<Vec<_>>();
1541                literal()
1542                    .parse(crate::parser::input(&tokens))
1543                    .into_result()
1544                    .map_err(|errors| errors.into())
1545            }
1546            _ => panic!("Not possible"),
1547        }
1548    }
1549}