Skip to main content

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