kcl_lib/parsing/
parser.rs

1// TODO optimise size of CompilationError
2#![allow(clippy::result_large_err)]
3
4use std::{cell::RefCell, collections::BTreeMap};
5
6use indexmap::IndexMap;
7use winnow::{
8    combinator::{alt, delimited, opt, peek, preceded, repeat, repeat_till, separated, separated_pair, terminated},
9    dispatch,
10    error::{ErrMode, StrContext, StrContextValue},
11    prelude::*,
12    stream::Stream,
13    token::{any, none_of, one_of, take_till},
14};
15
16use super::{
17    DeprecationKind,
18    ast::types::{AscribedExpression, ImportPath, LabelledExpression},
19    token::{NumericSuffix, RESERVED_WORDS},
20};
21use crate::{
22    IMPORT_FILE_EXTENSIONS, SourceRange, TypedPath,
23    errors::{CompilationError, Severity, Tag},
24    execution::types::ArrayLen,
25    parsing::{
26        PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
27        ast::types::{
28            Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
29            BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
30            FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
31            ItemVisibility, LabeledArg, Literal, LiteralValue, MemberExpression, Name, Node, NodeList, NonCodeMeta,
32            NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution,
33            PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type, TypeDeclaration, UnaryExpression,
34            UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
35        },
36        math::BinaryExpressionToken,
37        token::{Token, TokenSlice, TokenType},
38    },
39};
40
41thread_local! {
42    /// The current `ParseContext`. `None` if parsing is not currently happening on this thread.
43    static CTXT: RefCell<Option<ParseContext>> = const { RefCell::new(None) };
44}
45
46const MISSING_ELSE: &str = "This `if` block needs a matching `else` block";
47const IF_ELSE_CANNOT_BE_EMPTY: &str = "`if` and `else` blocks cannot be empty";
48const ELSE_STRUCTURE: &str = "This `else` should be followed by a {, then a block of code, then a }";
49const ELSE_MUST_END_IN_EXPR: &str = "This `else` block needs to end in an expression, which will be the value if no preceding `if` condition was matched";
50
51pub fn run_parser(i: TokenSlice) -> super::ParseResult {
52    let _stats = crate::log::LogPerfStats::new("Parsing");
53    ParseContext::init();
54
55    let result = match program.parse(i) {
56        Ok(result) => Some(result),
57        Err(e) => {
58            ParseContext::err(e.into());
59            None
60        }
61    };
62    let ctxt = ParseContext::take();
63    (result, ctxt.errors).into()
64}
65
66/// Context built up while parsing a program.
67///
68/// When returned from parsing contains the errors and warnings from the current parse.
69#[derive(Debug, Clone, Default)]
70struct ParseContext {
71    pub errors: Vec<CompilationError>,
72}
73
74impl ParseContext {
75    fn new() -> Self {
76        ParseContext { errors: Vec::new() }
77    }
78
79    /// Set a new `ParseContext` in thread-local storage. Panics if one already exists.
80    fn init() {
81        assert!(CTXT.with_borrow(|ctxt| ctxt.is_none()));
82        CTXT.with_borrow_mut(|ctxt| *ctxt = Some(ParseContext::new()));
83    }
84
85    /// Take the current `ParseContext` from thread-local storage, leaving `None`. Panics if a `ParseContext`
86    /// is not present.
87    fn take() -> ParseContext {
88        CTXT.with_borrow_mut(|ctxt| ctxt.take()).unwrap()
89    }
90
91    /// Add an error to the current `ParseContext`, panics if there is none.
92    fn err(err: CompilationError) {
93        CTXT.with_borrow_mut(|ctxt| {
94            // Avoid duplicating errors. This is possible since the parser can try one path, find
95            // a warning, then backtrack and decide not to take that path and try another. This can
96            // happen 'high up the stack', so it's impossible to fix where the errors are generated.
97            // Ideally we would pass errors up the call stack rather than use a context object or
98            // have some way to mark errors as speculative or committed, but I don't think Winnow
99            // is flexible enough for that (or at least, not without significant changes to the
100            // parser).
101            let errors = &mut ctxt.as_mut().unwrap().errors;
102            for e in errors.iter_mut().rev() {
103                if e.source_range == err.source_range {
104                    *e = err;
105                    return;
106                }
107            }
108            errors.push(err);
109        });
110    }
111
112    /// Add a warning to the current `ParseContext`, panics if there is none.
113    fn warn(mut e: CompilationError) {
114        e.severity = Severity::Warning;
115        Self::err(e);
116    }
117}
118
119/// Accumulate context while backtracking errors
120/// Very similar to [`winnow::error::ContextError`] type,
121/// but the 'cause' field is always a [`CompilationError`],
122/// instead of a dynamic [`std::error::Error`] trait object.
123#[derive(Debug, Clone)]
124pub(crate) struct ContextError<C = StrContext> {
125    pub context: Vec<C>,
126    pub cause: Option<CompilationError>,
127}
128
129impl From<winnow::error::ParseError<TokenSlice<'_>, ContextError>> for CompilationError {
130    fn from(err: winnow::error::ParseError<TokenSlice<'_>, ContextError>) -> Self {
131        let Some(last_token) = err.input().last() else {
132            return CompilationError::fatal(Default::default(), "file is empty");
133        };
134
135        let (input, offset, err) = (err.input(), err.offset(), err.clone().into_inner());
136
137        if let Some(e) = err.cause {
138            return e;
139        }
140
141        // See docs on `offset`.
142        if offset >= input.len() {
143            let context = err.context.first();
144            return CompilationError::fatal(
145                last_token.as_source_range(),
146                match context {
147                    Some(what) => format!("Unexpected end of file. The compiler {what}"),
148                    None => "Unexpected end of file while still parsing".to_owned(),
149                },
150            );
151        }
152
153        let bad_token = input.token(offset);
154        // TODO: Add the Winnow parser context to the error.
155        // See https://github.com/KittyCAD/modeling-app/issues/784
156        CompilationError::fatal(
157            bad_token.as_source_range(),
158            format!("Unexpected token: {}", bad_token.value),
159        )
160    }
161}
162
163impl<C> From<CompilationError> for ContextError<C> {
164    fn from(e: CompilationError) -> Self {
165        Self {
166            context: Default::default(),
167            cause: Some(e),
168        }
169    }
170}
171
172impl<C> std::default::Default for ContextError<C> {
173    fn default() -> Self {
174        Self {
175            context: Default::default(),
176            cause: None,
177        }
178    }
179}
180
181impl<I, C> winnow::error::ParserError<I> for ContextError<C>
182where
183    I: Stream,
184{
185    /// Generally, `Self`
186    ///
187    /// Mostly used for [`ErrMode`]
188    type Inner = Self;
189
190    /// Creates an error from the input position
191    fn from_input(_input: &I) -> Self {
192        Self::default()
193    }
194
195    /// Unwrap the mode, returning the underlying error, if present
196    fn into_inner(self) -> winnow::Result<Self::Inner, Self> {
197        Ok(self)
198    }
199}
200
201impl<C, I> winnow::error::AddContext<I, C> for ContextError<C>
202where
203    I: Stream,
204{
205    #[inline]
206    fn add_context(mut self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, ctx: C) -> Self {
207        self.context.push(ctx);
208        self
209    }
210}
211
212impl<C, I> winnow::error::FromExternalError<I, CompilationError> for ContextError<C> {
213    /// Like [`ParserError::from_input`] but also include an external error.
214    #[inline]
215    fn from_external_error(_input: &I, e: CompilationError) -> Self {
216        let mut err = Self::default();
217        {
218            err.cause = Some(e);
219        }
220        err
221    }
222}
223
224type ModalResult<O, E = ContextError> = winnow::prelude::ModalResult<O, E>;
225
226fn expected(what: &'static str) -> StrContext {
227    StrContext::Expected(StrContextValue::Description(what))
228}
229
230fn program(i: &mut TokenSlice) -> ModalResult<Node<Program>> {
231    let shebang = opt(shebang).parse_next(i)?;
232    let mut out: Node<Program> = function_body.parse_next(i)?;
233    out.shebang = shebang;
234
235    // Match original parser behaviour, for now.
236    // Once this is merged and stable, consider changing this as I think it's more accurate
237    // without the -1.
238    out.end -= 1;
239    Ok(out)
240}
241
242fn pipe_surrounded_by_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
243    (
244        repeat(0.., whitespace).map(|_: Vec<_>| ()),
245        pipe_operator,
246        repeat(0.., whitespace).map(|_: Vec<_>| ()),
247    )
248        .parse_next(i)?;
249    Ok(())
250}
251
252/// Note this is O(n).
253fn count_in(target: char, s: &str) -> usize {
254    s.chars().filter(|&c| c == target).count()
255}
256
257/// Matches all four cases of NonCodeValue
258fn non_code_node(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
259    /// Matches one case of NonCodeValue
260    /// See docstring on [NonCodeValue::NewLineBlockComment] for why that case is different to the others.
261    fn non_code_node_leading_whitespace(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
262        let leading_whitespace = one_of(TokenType::Whitespace)
263            .context(expected("whitespace, with a newline"))
264            .parse_next(i)?;
265        let has_empty_line = count_in('\n', &leading_whitespace.value) >= 2;
266        non_code_node_no_leading_whitespace
267            .verify_map(|node: Node<NonCodeNode>| match node.inner.value {
268                NonCodeValue::BlockComment { value, style } => Some(Node::new(
269                    NonCodeNode {
270                        value: if has_empty_line {
271                            NonCodeValue::NewLineBlockComment { value, style }
272                        } else {
273                            NonCodeValue::BlockComment { value, style }
274                        },
275                        digest: None,
276                    },
277                    leading_whitespace.start,
278                    node.end + 1,
279                    node.module_id,
280                )),
281                _ => None,
282            })
283            .context(expected("a comment or whitespace"))
284            .parse_next(i)
285    }
286
287    alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
288}
289
290fn outer_annotation(i: &mut TokenSlice) -> ModalResult<Node<Annotation>> {
291    peek((at_sign, open_paren)).parse_next(i)?;
292    annotation(i)
293}
294
295fn annotation(i: &mut TokenSlice) -> ModalResult<Node<Annotation>> {
296    let at = at_sign.parse_next(i)?;
297    let name = opt(binding_name).parse_next(i)?;
298    let mut end = name.as_ref().map(|n| n.end).unwrap_or(at.end);
299
300    let properties = if peek(open_paren).parse_next(i).is_ok() {
301        open_paren(i)?;
302        ignore_whitespace(i);
303        let properties: Vec<_> = separated(
304            0..,
305            separated_pair(
306                terminated(identifier, opt(whitespace)),
307                terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
308                expression,
309            )
310            .map(|(key, value)| {
311                Node::new_node(
312                    key.start,
313                    value.end(),
314                    key.module_id,
315                    ObjectProperty {
316                        key,
317                        value,
318                        digest: None,
319                    },
320                )
321            }),
322            comma_sep,
323        )
324        .parse_next(i)?;
325        ignore_trailing_comma(i);
326        ignore_whitespace(i);
327        end = close_paren(i)?.end;
328        Some(properties)
329    } else {
330        None
331    };
332
333    if name.is_none() && properties.is_none() {
334        return Err(ErrMode::Cut(
335            CompilationError::fatal(at.as_source_range(), format!("Unexpected token: {}", at.value)).into(),
336        ));
337    }
338
339    let value = Annotation {
340        name,
341        properties,
342        digest: None,
343    };
344    Ok(Node::new(value, at.start, end, at.module_id))
345}
346
347// Matches remaining three cases of NonCodeValue
348fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
349    any.verify_map(|token: Token| {
350        if token.is_code_token() {
351            None
352        } else {
353            let value = match token.token_type {
354                TokenType::Whitespace if token.value.contains("\n\n") || token.value.contains("\n\r\n") => {
355                    NonCodeValue::NewLine
356                }
357                TokenType::LineComment => NonCodeValue::BlockComment {
358                    value: token.value.trim_start_matches("//").trim().to_owned(),
359                    style: CommentStyle::Line,
360                },
361                TokenType::BlockComment => NonCodeValue::BlockComment {
362                    style: CommentStyle::Block,
363                    value: token
364                        .value
365                        .trim_start_matches("/*")
366                        .trim_end_matches("*/")
367                        .trim()
368                        .to_owned(),
369                },
370                _ => return None,
371            };
372            Some(Node::new(
373                NonCodeNode { value, digest: None },
374                token.start,
375                token.end,
376                token.module_id,
377            ))
378        }
379    })
380    .context(expected("Non-code token (comments or whitespace)"))
381    .parse_next(i)
382}
383
384fn expression(i: &mut TokenSlice) -> ModalResult<Expr> {
385    let head = expression_but_not_pipe.parse_next(i)?;
386    let head_checkpoint = i.checkpoint();
387
388    // Check for any non-code followed by `|>`
389    let noncode: Result<Vec<_>, _> = terminated(
390        repeat(0.., preceded(whitespace, non_code_node)),
391        peek(pipe_surrounded_by_whitespace),
392    )
393    .parse_next(i);
394
395    let Ok(noncode) = noncode else {
396        // No `|>`, so return a single expression.
397        i.reset(&head_checkpoint);
398        return Ok(head);
399    };
400    // Continue to parse the rest of the pipeline and return a pipeline expression.
401
402    let mut non_code_meta = NonCodeMeta::default();
403    for nc in noncode {
404        non_code_meta.insert(0, nc);
405    }
406    let mut values = vec![head];
407
408    let value_surrounded_by_comments = (
409        repeat(0.., preceded(opt(whitespace), non_code_node)), // Before the expression.
410        preceded(opt(whitespace), labelled_fn_call),           // The expression.
411        repeat(0.., noncode_just_after_code),                  // After the expression.
412    );
413    let tail: Vec<(Vec<_>, _, Vec<_>)> = repeat(
414        1..,
415        preceded(pipe_surrounded_by_whitespace, value_surrounded_by_comments),
416    )
417    .context(expected(
418        "a sequence of at least one |> (pipe) operator, followed by an expression",
419    ))
420    .parse_next(i)?;
421
422    // Time to structure the return value.
423    let mut code_count = 0;
424    let mut max_noncode_end = 0;
425    for (noncode_before, code, noncode_after) in tail {
426        for nc in noncode_before {
427            max_noncode_end = nc.end.max(max_noncode_end);
428            non_code_meta.insert(code_count, nc);
429        }
430        values.push(code);
431        code_count += 1;
432        for nc in noncode_after {
433            max_noncode_end = nc.end.max(max_noncode_end);
434            non_code_meta.insert(code_count, nc);
435        }
436    }
437    Ok(Expr::PipeExpression(Node::boxed(
438        values.first().unwrap().start(),
439        values.last().unwrap().end().max(max_noncode_end),
440        values.first().unwrap().module_id(),
441        PipeExpression {
442            body: values,
443            non_code_meta,
444            digest: None,
445        },
446    )))
447}
448
449fn bool_value(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
450    let (value, token) = any
451        .try_map(|token: Token| match token.token_type {
452            TokenType::Keyword if token.value == "true" => Ok((true, token)),
453            TokenType::Keyword if token.value == "false" => Ok((false, token)),
454            _ => Err(CompilationError::fatal(
455                token.as_source_range(),
456                "invalid boolean literal",
457            )),
458        })
459        .context(expected("a boolean literal (either true or false)"))
460        .parse_next(i)?;
461    Ok(Node::new(
462        Literal {
463            value: LiteralValue::Bool(value),
464            raw: value.to_string(),
465            digest: None,
466        },
467        token.start,
468        token.end,
469        token.module_id,
470    ))
471}
472
473fn literal(i: &mut TokenSlice) -> ModalResult<BoxNode<Literal>> {
474    alt((string_literal, unsigned_number_literal, bool_value))
475        .map(Box::new)
476        .context(expected("a KCL literal, like 'myPart' or 3"))
477        .parse_next(i)
478}
479
480/// Parse a KCL string literal
481fn string_literal(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
482    let (value, token) = any
483        .try_map(|token: Token| match token.token_type {
484            TokenType::String => {
485                let s = token.value[1..token.value.len() - 1].to_string();
486                Ok((LiteralValue::from(s), token))
487            }
488            _ => Err(CompilationError::fatal(
489                token.as_source_range(),
490                "invalid string literal",
491            )),
492        })
493        .context(expected("string literal (like \"myPart\""))
494        .parse_next(i)?;
495
496    let result = Node::new(
497        Literal {
498            value,
499            raw: token.value,
500            digest: None,
501        },
502        token.start,
503        token.end,
504        token.module_id,
505    );
506
507    if let Some(suggestion) = super::deprecation(result.value.string_value().unwrap(), DeprecationKind::String) {
508        ParseContext::warn(
509            CompilationError::err(
510                result.as_source_range(),
511                format!(
512                    "Using `\"{}\"` is deprecated, prefer using `{}`.",
513                    result.value.string_value().unwrap(),
514                    suggestion
515                ),
516            )
517            .with_suggestion(
518                format!(
519                    "Replace `\"{}\"` with `{}`",
520                    result.value.string_value().unwrap(),
521                    suggestion
522                ),
523                suggestion,
524                None,
525                Tag::Deprecated,
526            ),
527        );
528    }
529
530    Ok(result)
531}
532
533/// Parse a KCL literal number, with no - sign.
534pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
535    let (value, token) = any
536        .try_map(|token: Token| match token.token_type {
537            TokenType::Number => {
538                let value: f64 = token.numeric_value().ok_or_else(|| {
539                    CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
540                })?;
541
542                let suffix = token.numeric_suffix();
543                if let NumericSuffix::Unknown = suffix {
544                    ParseContext::warn(CompilationError::err(token.as_source_range(), "The 'unknown' numeric suffix is not properly supported; it is likely to change or be removed, and may be buggy."));
545                }
546
547                Ok((
548                    LiteralValue::Number {
549                        value,
550                        suffix,
551                    },
552                    token,
553                ))
554            }
555            _ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")),
556        })
557        .context(expected("an unsigned number literal (e.g. 3 or 12.5)"))
558        .parse_next(i)?;
559    Ok(Node::new(
560        Literal {
561            value,
562            raw: token.value,
563            digest: None,
564        },
565        token.start,
566        token.end,
567        token.module_id,
568    ))
569}
570
571/// Parse a KCL operator that takes a left- and right-hand side argument.
572fn binary_operator(i: &mut TokenSlice) -> ModalResult<BinaryOperator> {
573    any.try_map(|token: Token| {
574        if !matches!(token.token_type, TokenType::Operator) {
575            return Err(CompilationError::fatal(
576                token.as_source_range(),
577                format!("unexpected token, should be an operator but was {}", token.token_type),
578            ));
579        }
580        let op = match token.value.as_str() {
581            "+" => BinaryOperator::Add,
582            "-" => BinaryOperator::Sub,
583            "/" => BinaryOperator::Div,
584            "*" => BinaryOperator::Mul,
585            "%" => BinaryOperator::Mod,
586            "^" => BinaryOperator::Pow,
587            "==" => BinaryOperator::Eq,
588            "!=" => BinaryOperator::Neq,
589            ">" => BinaryOperator::Gt,
590            ">=" => BinaryOperator::Gte,
591            "<" => BinaryOperator::Lt,
592            "<=" => BinaryOperator::Lte,
593            "|" => BinaryOperator::Or,
594            "&" => BinaryOperator::And,
595            "||" => {
596                ParseContext::err(
597                    CompilationError::err(
598                        token.as_source_range(),
599                        "`||` is not an operator, did you mean to use `|`?",
600                    )
601                    .with_suggestion("Replace `||` with `|`", "|", None, Tag::None),
602                );
603                BinaryOperator::Or
604            }
605            "&&" => {
606                ParseContext::err(
607                    CompilationError::err(
608                        token.as_source_range(),
609                        "`&&` is not an operator, did you mean to use `&`?",
610                    )
611                    .with_suggestion("Replace `&&` with `&`", "&", None, Tag::None),
612                );
613                BinaryOperator::And
614            }
615            _ => {
616                return Err(CompilationError::fatal(
617                    token.as_source_range(),
618                    format!("{} is not a binary operator", token.value.as_str()),
619                ));
620            }
621        };
622        Ok(op)
623    })
624    .context(expected("a binary operator (like + or *)"))
625    .parse_next(i)
626}
627
628/// Parse a KCL operand that can be used with an operator.
629fn operand(i: &mut TokenSlice) -> ModalResult<BinaryPart> {
630    const TODO_783: &str = "found a value, but this kind of value cannot be used as the operand to an operator yet (see https://github.com/KittyCAD/modeling-app/issues/783)";
631    let op = possible_operands
632        .try_map(|part| {
633            let source_range = SourceRange::from(&part);
634            let expr = match part {
635                // TODO: these should be valid operands eventually,
636                // users should be able to run "let x = f() + g()"
637                // see https://github.com/KittyCAD/modeling-app/issues/783
638                Expr::FunctionExpression(_)
639                | Expr::PipeExpression(_)
640                | Expr::PipeSubstitution(_)
641                | Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
642                Expr::None(_) => {
643                    return Err(CompilationError::fatal(
644                        source_range,
645                        // TODO: Better error message here.
646                        // Once we have ways to use None values (e.g. by replacing with a default value)
647                        // we should suggest one of them here.
648                        "cannot use a KCL None value as an operand",
649                    ));
650                }
651                Expr::TagDeclarator(_) => {
652                    return Err(CompilationError::fatal(
653                        source_range,
654                        // TODO: Better error message here.
655                        // Once we have ways to use None values (e.g. by replacing with a default value)
656                        // we should suggest one of them here.
657                        "cannot use a KCL tag declaration as an operand",
658                    ));
659                }
660                Expr::UnaryExpression(x) => BinaryPart::UnaryExpression(x),
661                Expr::Literal(x) => BinaryPart::Literal(x),
662                Expr::Name(x) => BinaryPart::Name(x),
663                Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
664                Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
665                Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
666                Expr::ArrayExpression(x) => BinaryPart::ArrayExpression(x),
667                Expr::ArrayRangeExpression(x) => BinaryPart::ArrayRangeExpression(x),
668                Expr::ObjectExpression(x) => BinaryPart::ObjectExpression(x),
669                Expr::IfExpression(x) => BinaryPart::IfExpression(x),
670                Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x),
671            };
672            Ok(expr)
673        })
674        .context(expected("an operand (a value which can be used with an operator)"))
675        .parse_next(i)?;
676    Ok(op)
677}
678
679impl TokenType {
680    fn parse_from(self, i: &mut TokenSlice) -> ModalResult<Token> {
681        any.try_map(|token: Token| {
682            if token.token_type == self {
683                Ok(token)
684            } else {
685                Err(CompilationError::fatal(
686                    token.as_source_range(),
687                    format!(
688                        "expected {self} but found {} which is a {}",
689                        token.value.as_str(),
690                        token.token_type
691                    ),
692                ))
693            }
694        })
695        .parse_next(i)
696    }
697}
698
699/// Parse some whitespace (i.e. at least one whitespace token)
700fn whitespace(i: &mut TokenSlice) -> ModalResult<Vec<Token>> {
701    repeat(
702        1..,
703        any.try_map(|token: Token| {
704            if token.token_type == TokenType::Whitespace {
705                Ok(token)
706            } else {
707                Err(CompilationError::fatal(
708                    token.as_source_range(),
709                    format!(
710                        "expected whitespace, found '{}' which is {}",
711                        token.value.as_str(),
712                        token.token_type
713                    ),
714                ))
715            }
716        }),
717    )
718    .context(expected("some whitespace (e.g. spaces, tabs, new lines)"))
719    .parse_next(i)
720}
721
722/// A shebang is a line at the start of a file that starts with `#!`.
723/// If the shebang is present it takes up the whole line.
724fn shebang(i: &mut TokenSlice) -> ModalResult<Node<Shebang>> {
725    // Parse the hash and the bang.
726    hash.parse_next(i)?;
727    let tok = bang.parse_next(i)?;
728    // Get the rest of the line.
729    // Parse everything until the next newline.
730    let tokens = take_till(0.., |token: Token| token.value.contains('\n')).parse_next(i)?;
731    let value = tokens.iter().map(|t| t.value.as_str()).collect::<String>();
732
733    if tokens.is_empty() {
734        return Err(ErrMode::Cut(
735            CompilationError::fatal(tok.as_source_range(), "expected a shebang value after #!").into(),
736        ));
737    }
738
739    // Strip all the whitespace after the shebang.
740    opt(whitespace).parse_next(i)?;
741
742    Ok(Node::new(
743        Shebang::new(format!("#!{value}")),
744        0,
745        tokens.last().unwrap().end,
746        tokens.first().unwrap().module_id,
747    ))
748}
749
750#[allow(clippy::large_enum_variant)]
751pub enum NonCodeOr<T> {
752    NonCode(Node<NonCodeNode>),
753    Code(T),
754}
755
756/// Parse a KCL array of elements.
757fn array(i: &mut TokenSlice) -> ModalResult<Expr> {
758    alt((
759        array_empty.map(Box::new).map(Expr::ArrayExpression),
760        array_end_start.map(Box::new).map(Expr::ArrayRangeExpression),
761        array_elem_by_elem.map(Box::new).map(Expr::ArrayExpression),
762    ))
763    .parse_next(i)
764}
765
766/// Match an empty array.
767fn array_empty(i: &mut TokenSlice) -> ModalResult<Node<ArrayExpression>> {
768    let open = open_bracket(i)?;
769    let start = open.start;
770    ignore_whitespace(i);
771    let end = close_bracket(i)?.end;
772    Ok(Node::new(
773        ArrayExpression {
774            elements: Default::default(),
775            non_code_meta: Default::default(),
776            digest: None,
777        },
778        start,
779        end,
780        open.module_id,
781    ))
782}
783
784/// Match something that separates elements of an array.
785fn array_separator(i: &mut TokenSlice) -> ModalResult<()> {
786    alt((
787        // Normally you need a comma.
788        comma_sep,
789        // But, if the array is ending, no need for a comma.
790        peek(preceded((opt(non_code_node), opt(whitespace)), close_bracket)).void(),
791    ))
792    .parse_next(i)
793}
794
795pub(crate) fn array_elem_by_elem(i: &mut TokenSlice) -> ModalResult<Node<ArrayExpression>> {
796    let open = open_bracket(i)?;
797    let start = open.start;
798    ignore_whitespace(i);
799    let elements: Vec<_> = repeat(
800        0..,
801        alt((
802            terminated(expression.map(NonCodeOr::Code), array_separator),
803            terminated(non_code_node.map(NonCodeOr::NonCode), opt(whitespace)),
804        )),
805    )
806    .context(expected("array contents, a list of elements (like [1, 2, 3])"))
807    .parse_next(i)?;
808    ignore_trailing_comma(i);
809    ignore_whitespace(i);
810
811    let maybe_end = close_bracket(i).map_err(|e| {
812        if let Ok(mut err) = e.clone().into_inner() {
813            let start_range = open.as_source_range();
814            let end_range = i.as_source_range();
815            err.cause = Some(CompilationError::fatal(
816                SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
817                "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
818            ));
819            ErrMode::Cut(err)
820        } else {
821            // ErrMode::Incomplete, not sure if it's actually possible to end up with this here
822            e
823        }
824    });
825
826    if maybe_end.is_err() {
827        // if there is a closing bracket at some point, but it wasn't the next token, it's likely that they forgot a comma between some
828        // of the elements
829        let maybe_closing_bracket: ModalResult<((), Token)> = peek(repeat_till(
830            0..,
831            none_of(|token: Token| {
832                // bail out early if we encounter something that is for sure not allowed in an
833                // array, otherwise we could seek to find a closing bracket until the end of the
834                // file
835                RESERVED_WORDS
836                    .keys()
837                    .chain([",,", "{", "}", "["].iter())
838                    .any(|word| *word == token.value)
839            })
840            .void(),
841            one_of(|term: Token| term.value == "]"),
842        ))
843        .parse_next(i);
844        let has_closing_bracket = maybe_closing_bracket.is_ok();
845        if has_closing_bracket {
846            let start_range = i.as_source_range();
847            // safe to unwrap here because we checked it was Ok above
848            let end_range = maybe_closing_bracket.unwrap().1.as_source_range();
849            let e = ContextError {
850                context: vec![],
851                cause: Some(CompilationError::fatal(
852                    SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
853                    "Unexpected character encountered. You might be missing a comma in between elements.",
854                )),
855            };
856            return Err(ErrMode::Cut(e));
857        }
858    }
859    let end = maybe_end?.end;
860
861    // Sort the array's elements (i.e. expression nodes) from the noncode nodes.
862    let (elements, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = elements.into_iter().enumerate().fold(
863        (Vec::new(), BTreeMap::new()),
864        |(mut elements, mut non_code_nodes), (i, e)| {
865            match e {
866                NonCodeOr::NonCode(x) => {
867                    non_code_nodes.insert(i, vec![x]);
868                }
869                NonCodeOr::Code(x) => {
870                    elements.push(x);
871                }
872            }
873            (elements, non_code_nodes)
874        },
875    );
876    let non_code_meta = NonCodeMeta {
877        non_code_nodes,
878        start_nodes: Vec::new(),
879        digest: None,
880    };
881    Ok(Node::new(
882        ArrayExpression {
883            elements,
884            non_code_meta,
885            digest: None,
886        },
887        start,
888        end,
889        open.module_id,
890    ))
891}
892
893fn array_end_start(i: &mut TokenSlice) -> ModalResult<Node<ArrayRangeExpression>> {
894    let open = open_bracket(i)?;
895    let start = open.start;
896    ignore_whitespace(i);
897    let start_element = expression.parse_next(i)?;
898    ignore_whitespace(i);
899    let end_inclusive = alt((end_inclusive_range.map(|_| true), end_exclusive_range.map(|_| false))).parse_next(i)?;
900    ignore_whitespace(i);
901    let end_element = expression.parse_next(i)?;
902    ignore_whitespace(i);
903    let end = close_bracket(i)?.end;
904    Ok(Node::new(
905        ArrayRangeExpression {
906            start_element,
907            end_element,
908            end_inclusive,
909            digest: None,
910        },
911        start,
912        end,
913        open.module_id,
914    ))
915}
916
917fn object_property_same_key_and_val(i: &mut TokenSlice) -> ModalResult<Node<ObjectProperty>> {
918    let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height = 4', 'height' is the property key")).parse_next(i)?;
919    ignore_whitespace(i);
920    Ok(Node::new_node(
921        key.start,
922        key.end,
923        key.module_id,
924        ObjectProperty {
925            value: Expr::Name(Box::new(key.clone().into())),
926            key,
927            digest: None,
928        },
929    ))
930}
931
932fn object_property(i: &mut TokenSlice) -> ModalResult<Node<ObjectProperty>> {
933    let key = identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height = 4', 'height' is the property key")).parse_next(i)?;
934    ignore_whitespace(i);
935    // Temporarily accept both `:` and `=` for compatibility.
936    let sep = alt((colon, equals))
937        .context(expected(
938            "`=`, which separates the property's key from the value you're setting it to, e.g. 'height = 4'",
939        ))
940        .parse_next(i)?;
941    ignore_whitespace(i);
942    let expr = match expression
943        .context(expected(
944            "the value which you're setting the property to, e.g. in 'height = 4', the value is 4",
945        ))
946        .parse_next(i)
947    {
948        Ok(expr) => expr,
949        Err(_) => {
950            return Err(ErrMode::Cut(
951                CompilationError::fatal(
952                    SourceRange::from(sep),
953                    "This property has a label, but no value. Put some value after the equals sign",
954                )
955                .into(),
956            ));
957        }
958    };
959
960    let result = Node::new_node(
961        key.start,
962        expr.end(),
963        key.module_id,
964        ObjectProperty {
965            key,
966            value: expr,
967            digest: None,
968        },
969    );
970
971    if sep.token_type == TokenType::Colon {
972        ParseContext::err(
973            CompilationError::err(
974                sep.into(),
975                "Using `:` to initialize objects is deprecated, prefer using `=`.",
976            )
977            .with_suggestion("Replace `:` with `=`", " =", None, Tag::Deprecated),
978        );
979    }
980
981    Ok(result)
982}
983
984/// Match something that separates properties of an object.
985fn property_separator(i: &mut TokenSlice) -> ModalResult<()> {
986    alt((
987        // Normally you need a comma.
988        comma_sep,
989        // But, if the object is ending, no need for a comma.
990        peek(preceded((opt(non_code_node), opt(whitespace)), close_brace)).void(),
991    ))
992    .parse_next(i)
993}
994
995/// Match something that separates the labeled arguments of a fn call.
996/// Returns the source range of the erroneous separator, if any was found.
997fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<Option<SourceRange>> {
998    alt((
999        // Normally you need a comma.
1000        comma_sep.map(|_| None),
1001        // But, if the argument list is ending, no need for a comma.
1002        peek(preceded(opt(whitespace), close_paren)).void().map(|_| None),
1003        whitespace.map(|mut tokens| {
1004            // Safe to unwrap here because `whitespace` is guaranteed to return at least 1 whitespace.
1005            let first_token = tokens.pop().unwrap();
1006            Some(SourceRange::from(&first_token))
1007        }),
1008    ))
1009    .parse_next(i)
1010}
1011
1012/// Parse a KCL object value.
1013pub(crate) fn object(i: &mut TokenSlice) -> ModalResult<Node<ObjectExpression>> {
1014    let open = open_brace(i)?;
1015    let start = open.start;
1016    ignore_whitespace(i);
1017    let properties: Vec<_> = repeat(
1018        0..,
1019        alt((
1020            terminated(non_code_node.map(NonCodeOr::NonCode), opt(whitespace)),
1021            terminated(
1022                alt((object_property, object_property_same_key_and_val)),
1023                property_separator,
1024            )
1025            .map(NonCodeOr::Code),
1026        )),
1027    )
1028    .context(expected(
1029        "a comma-separated list of key-value pairs, e.g. 'height = 4, width = 3'",
1030    ))
1031    .parse_next(i)?;
1032    ignore_trailing_comma(i);
1033    ignore_whitespace(i);
1034
1035    let maybe_end = close_brace(i).map_err(|e| {
1036        if let Ok(mut err) = e.clone().into_inner() {
1037            let start_range = open.as_source_range();
1038            let end_range = i.as_source_range();
1039            err.cause = Some(CompilationError::fatal(
1040                SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
1041                "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
1042            ));
1043            ErrMode::Cut(err)
1044        } else {
1045            // ErrMode::Incomplete, not sure if it's actually possible to end up with this here
1046            e
1047        }
1048    });
1049    if maybe_end.is_err() {
1050        // if there is a closing brace at some point, but it wasn't the next token, it's likely that they forgot a comma between some
1051        // of the properties
1052        let maybe_closing_brace: ModalResult<((), Token)> = peek(repeat_till(
1053            0..,
1054            none_of(|token: Token| {
1055                // bail out early if we encounter something that is for sure not allowed in an
1056                // object, otherwise we could seek to find a closing brace until the end of the
1057                // file
1058                RESERVED_WORDS
1059                    .keys()
1060                    .chain([",,", "[", "]", "{"].iter())
1061                    .any(|word| *word == token.value)
1062            })
1063            .void(),
1064            one_of(|c: Token| c.value == "}"),
1065        ))
1066        .parse_next(i);
1067        let has_closing_brace = maybe_closing_brace.is_ok();
1068        if has_closing_brace {
1069            let start_range = i.as_source_range();
1070            // okay to unwrap here because we checked it was Ok above
1071            let end_range = maybe_closing_brace.unwrap().1.as_source_range();
1072
1073            let e = ContextError {
1074                context: vec![],
1075                cause: Some(CompilationError::fatal(
1076                    SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
1077                    "Unexpected character encountered. You might be missing a comma in between properties.",
1078                )),
1079            };
1080            return Err(ErrMode::Cut(e));
1081        }
1082    }
1083
1084    let end = maybe_end?.end;
1085    // Sort the object's properties from the noncode nodes.
1086    let (properties, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = properties.into_iter().enumerate().fold(
1087        (Vec::new(), BTreeMap::new()),
1088        |(mut properties, mut non_code_nodes), (i, e)| {
1089            match e {
1090                NonCodeOr::NonCode(x) => {
1091                    non_code_nodes.insert(i, vec![x]);
1092                }
1093                NonCodeOr::Code(x) => {
1094                    properties.push(x);
1095                }
1096            }
1097            (properties, non_code_nodes)
1098        },
1099    );
1100
1101    let non_code_meta = NonCodeMeta {
1102        non_code_nodes,
1103        ..Default::default()
1104    };
1105    Ok(Node::new(
1106        ObjectExpression {
1107            properties,
1108            non_code_meta,
1109            digest: None,
1110        },
1111        start,
1112        end,
1113        open.module_id,
1114    ))
1115}
1116
1117/// Parse the % symbol, used to substitute a curried argument from a |> (pipe).
1118fn pipe_sub(i: &mut TokenSlice) -> ModalResult<Node<PipeSubstitution>> {
1119    any.try_map(|token: Token| {
1120        if matches!(token.token_type, TokenType::Operator) && token.value == PIPE_SUBSTITUTION_OPERATOR {
1121            Ok(Node::new(
1122                PipeSubstitution { digest: None },
1123                token.start,
1124                token.end,
1125                token.module_id,
1126            ))
1127        } else {
1128            Err(CompilationError::fatal(
1129                token.as_source_range(),
1130                format!(
1131                    "expected a pipe substitution symbol (%) but found {}",
1132                    token.value.as_str()
1133                ),
1134            ))
1135        }
1136    })
1137    .context(expected("the substitution symbol, %"))
1138    .parse_next(i)
1139}
1140
1141fn else_if(i: &mut TokenSlice) -> ModalResult<Node<ElseIf>> {
1142    let else_ = any
1143        .try_map(|token: Token| {
1144            if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
1145                Ok(token)
1146            } else {
1147                Err(CompilationError::fatal(
1148                    token.as_source_range(),
1149                    format!("{} is not 'else'", token.value.as_str()),
1150                ))
1151            }
1152        })
1153        .context(expected("the 'else' keyword"))
1154        .parse_next(i)?;
1155    ignore_whitespace(i);
1156    let _if = any
1157        .try_map(|token: Token| {
1158            if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
1159                Ok(token.start)
1160            } else {
1161                Err(CompilationError::fatal(
1162                    token.as_source_range(),
1163                    format!("{} is not 'if'", token.value.as_str()),
1164                ))
1165            }
1166        })
1167        .context(expected("the 'if' keyword"))
1168        .parse_next(i)?;
1169    ignore_whitespace(i);
1170    let cond = expression(i)?;
1171    ignore_whitespace(i);
1172    let _ = open_brace(i)?;
1173    let then_val = program
1174        .verify(|block| block.ends_with_expr())
1175        .parse_next(i)
1176        .map(Box::new)?;
1177    ignore_whitespace(i);
1178    let end = close_brace(i)?.end;
1179    ignore_whitespace(i);
1180    Ok(Node::new(
1181        ElseIf {
1182            cond,
1183            then_val,
1184            digest: Default::default(),
1185        },
1186        else_.start,
1187        end,
1188        else_.module_id,
1189    ))
1190}
1191
1192fn if_expr(i: &mut TokenSlice) -> ModalResult<BoxNode<IfExpression>> {
1193    let if_ = any
1194        .try_map(|token: Token| {
1195            if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
1196                Ok(token)
1197            } else {
1198                Err(CompilationError::fatal(
1199                    token.as_source_range(),
1200                    format!("{} is not 'if'", token.value.as_str()),
1201                ))
1202            }
1203        })
1204        .context(expected("the 'if' keyword"))
1205        .parse_next(i)?;
1206    let _ = whitespace(i)?;
1207    let cond = expression(i).map(Box::new)?;
1208    let _ = whitespace(i)?;
1209    let _ = open_brace(i)?;
1210    ignore_whitespace(i);
1211    let then_val = program
1212        .verify(|block| block.ends_with_expr())
1213        .parse_next(i)
1214        .map_err(|e| e.cut())
1215        .map(Box::new)?;
1216    ignore_whitespace(i);
1217    let _ = close_brace(i)?;
1218    ignore_whitespace(i);
1219    let else_ifs: NodeList<_> = repeat(0.., else_if).parse_next(i)?;
1220
1221    ignore_whitespace(i);
1222
1223    // If there's a non-fatal parser error (e.g. a problem with the `else` branch),
1224    // return this after emitting the nonfatal error.
1225    let if_with_no_else = |cond, then_val, else_ifs| {
1226        Ok(Node::boxed(
1227            if_.start,
1228            if_.end,
1229            if_.module_id,
1230            IfExpression {
1231                cond,
1232                then_val,
1233                else_ifs,
1234                final_else: Node::boxed(0, 0, if_.module_id, Default::default()),
1235                digest: Default::default(),
1236            },
1237        ))
1238    };
1239
1240    // Parse the else keyword
1241    let else_: ModalResult<_> = any
1242        .try_map(|token: Token| {
1243            if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
1244                Ok(token.start)
1245            } else {
1246                Err(CompilationError::fatal(
1247                    token.as_source_range(),
1248                    format!("{} is not 'else'", token.value.as_str()),
1249                ))
1250            }
1251        })
1252        .context(expected("the 'else' keyword"))
1253        .parse_next(i);
1254    let Ok(else_) = else_ else {
1255        ParseContext::err(CompilationError::err(if_.as_source_range(), MISSING_ELSE));
1256        return if_with_no_else(cond, then_val, else_ifs);
1257    };
1258    let else_range = SourceRange::new(else_, else_ + 4, if_.module_id);
1259    ignore_whitespace(i);
1260
1261    // Parse the else clause
1262    if open_brace(i).is_err() {
1263        ParseContext::err(CompilationError::err(else_range, ELSE_STRUCTURE));
1264        return if_with_no_else(cond, then_val, else_ifs);
1265    }
1266    ignore_whitespace(i);
1267    let Ok(final_else) = program.parse_next(i).map(Box::new) else {
1268        ParseContext::err(CompilationError::err(else_range, IF_ELSE_CANNOT_BE_EMPTY));
1269        let _ = opt(close_brace).parse_next(i);
1270        return if_with_no_else(cond, then_val, else_ifs);
1271    };
1272    ignore_whitespace(i);
1273
1274    if final_else.body.is_empty() {
1275        ParseContext::err(CompilationError::err(else_range, IF_ELSE_CANNOT_BE_EMPTY));
1276        let _ = opt(close_brace).parse_next(i);
1277        return if_with_no_else(cond, then_val, else_ifs);
1278    }
1279    if !final_else.ends_with_expr() {
1280        ParseContext::err(CompilationError::err(else_range, ELSE_MUST_END_IN_EXPR));
1281        let _ = opt(close_brace).parse_next(i);
1282        return if_with_no_else(cond, then_val, else_ifs);
1283    }
1284
1285    let end = close_brace(i)?.end;
1286    Ok(Node::boxed(
1287        if_.start,
1288        end,
1289        if_.module_id,
1290        IfExpression {
1291            cond,
1292            then_val,
1293            else_ifs,
1294            final_else,
1295            digest: Default::default(),
1296        },
1297    ))
1298}
1299
1300fn function_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
1301    let fn_tok = opt(fun).parse_next(i)?;
1302    ignore_whitespace(i);
1303    let result = function_decl.parse_next(i)?;
1304    if fn_tok.is_none() {
1305        let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`");
1306        return Err(ErrMode::Cut(err.into()));
1307    }
1308    Ok(Expr::FunctionExpression(Box::new(result)))
1309}
1310
1311// Looks like
1312// (arg0, arg1) {
1313//     const x = arg0 + arg1;
1314//     return x
1315// }
1316fn function_decl(i: &mut TokenSlice) -> ModalResult<Node<FunctionExpression>> {
1317    fn return_type(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
1318        colon(i)?;
1319        ignore_whitespace(i);
1320        type_(i)
1321    }
1322
1323    let open = open_paren(i)?;
1324    let start = open.start;
1325    let params = parameters(i)?;
1326    close_paren(i)?;
1327    ignore_whitespace(i);
1328    // Optional return type.
1329    let return_type = opt(return_type).parse_next(i)?;
1330    ignore_whitespace(i);
1331    let brace = open_brace(i)?;
1332    let close: Option<(Vec<Vec<Token>>, Token)> = opt((repeat(0.., whitespace), close_brace)).parse_next(i)?;
1333    let (body, end) = match close {
1334        Some((_, end)) => (
1335            Node::new(Program::default(), brace.end, brace.end, brace.module_id),
1336            end.end,
1337        ),
1338        None => (function_body(i)?, close_brace(i)?.end),
1339    };
1340    let result = Node::new(
1341        FunctionExpression {
1342            params,
1343            body,
1344            return_type,
1345            digest: None,
1346        },
1347        start,
1348        end,
1349        open.module_id,
1350    );
1351
1352    Ok(result)
1353}
1354
1355/// E.g. `person.name`
1356fn member_expression_dot(i: &mut TokenSlice) -> ModalResult<(Expr, usize, bool)> {
1357    period.parse_next(i)?;
1358    let property = nameable_identifier
1359        .map(Box::new)
1360        .map(|p| {
1361            let ni: Node<Identifier> = *p;
1362            let nn: Node<Name> = ni.into();
1363            Expr::Name(Box::new(nn))
1364        })
1365        .parse_next(i)?;
1366    let end = property.end();
1367    let computed = false;
1368    Ok((property, end, computed))
1369}
1370
1371fn member_expression_subscript(i: &mut TokenSlice) -> ModalResult<(Expr, usize, bool)> {
1372    let _ = open_bracket.parse_next(i)?;
1373    ignore_whitespace(i);
1374    let property = expression.parse_next(i)?;
1375    ignore_whitespace(i);
1376    let end = close_bracket.parse_next(i)?.end;
1377    let computed = true;
1378    Ok((property, end, computed))
1379}
1380
1381fn find_members(i: &mut TokenSlice) -> ModalResult<Vec<(Expr, usize, bool)>> {
1382    // Now a sequence of members.
1383    let member = alt((member_expression_dot, member_expression_subscript)).context(expected("a member/property, e.g. size.x and size['height'] and size[0] are all different ways to access a member/property of 'size'"));
1384    repeat(1.., member)
1385        .context(expected("a sequence of at least one members/properties"))
1386        .parse_next(i)
1387}
1388
1389/// Get a property of an object, or an index of an array, or a member of a collection.
1390/// Can be arbitrarily nested, e.g. `people[i]['adam'].age`.
1391fn build_member_expression(object: Expr, mut members: Vec<(Expr, usize, bool)>) -> Node<MemberExpression> {
1392    // Process the first member.
1393    // It's safe to call remove(0), because the vec is created from repeat(1..),
1394    // which is guaranteed to have >=1 elements.
1395    let (property, end, computed) = members.remove(0);
1396    let start = object.start();
1397    let module_id = object.module_id();
1398    let initial_member_expression = Node::new(
1399        MemberExpression {
1400            object,
1401            computed,
1402            property,
1403            digest: None,
1404        },
1405        start,
1406        end,
1407        module_id,
1408    );
1409
1410    // Each remaining member wraps the current member expression inside another member expression.
1411    members
1412        .into_iter()
1413        // Take the accumulated member expression from the previous iteration,
1414        // and use it as the `object` of a new, bigger member expression.
1415        .fold(initial_member_expression, |accumulated, (property, end, computed)| {
1416            Node::new(
1417                MemberExpression {
1418                    object: Expr::MemberExpression(Box::new(accumulated)),
1419                    computed,
1420                    property,
1421                    digest: None,
1422                },
1423                start,
1424                end,
1425                module_id,
1426            )
1427        })
1428}
1429
1430/// Find a noncode node which occurs just after a body item,
1431/// such that if the noncode item is a comment, it might be an inline comment.
1432fn noncode_just_after_code(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
1433    let ws = opt(whitespace).parse_next(i)?;
1434
1435    // What is the preceding whitespace like?
1436    let (has_newline, has_empty_line) = if let Some(ref ws) = ws {
1437        (
1438            ws.iter().any(|token| token.value.contains('\n')),
1439            ws.iter().any(|token| count_in('\n', &token.value) >= 2),
1440        )
1441    } else {
1442        (false, false)
1443    };
1444
1445    // Look for a non-code node (e.g. comment)
1446    let nc = non_code_node_no_leading_whitespace
1447        .map(|nc| {
1448            if has_empty_line {
1449                // There's an empty line between the body item and the comment,
1450                // This means the comment is a NewLineBlockComment!
1451                let value = match nc.inner.value {
1452                    // Change block comments to inline, as discussed above
1453                    NonCodeValue::BlockComment { value, style } => NonCodeValue::NewLineBlockComment { value, style },
1454                    // Other variants don't need to change.
1455                    x @ NonCodeValue::InlineComment { .. } => x,
1456                    x @ NonCodeValue::NewLineBlockComment { .. } => x,
1457                    x @ NonCodeValue::NewLine => x,
1458                };
1459                Node::new(
1460                    NonCodeNode { value, ..nc.inner },
1461                    nc.start.saturating_sub(1),
1462                    nc.end,
1463                    nc.module_id,
1464                )
1465            } else if has_newline {
1466                // Nothing has to change, a single newline does not need preserving.
1467                nc
1468            } else {
1469                // There's no newline between the body item and comment,
1470                // so if this is a comment, it must be inline with code.
1471                let value = match nc.inner.value {
1472                    // Change block comments to inline, as discussed above
1473                    NonCodeValue::BlockComment { value, style } => NonCodeValue::InlineComment { value, style },
1474                    // Other variants don't need to change.
1475                    x @ NonCodeValue::InlineComment { .. } => x,
1476                    x @ NonCodeValue::NewLineBlockComment { .. } => x,
1477                    x @ NonCodeValue::NewLine => x,
1478                };
1479                Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
1480            }
1481        })
1482        .map(|nc| Node::new(nc.inner, nc.start.saturating_sub(1), nc.end, nc.module_id))
1483        .parse_next(i)?;
1484    Ok(nc)
1485}
1486
1487// the large_enum_variant lint below introduces a LOT of code complexity in a
1488// match!() that's super clean that isn't worth it for the marginal space
1489// savings. revisit if that's a lie.
1490#[derive(Debug)]
1491#[allow(clippy::large_enum_variant)]
1492enum WithinFunction {
1493    Annotation(Node<Annotation>),
1494    BodyItem((BodyItem, Option<Node<NonCodeNode>>)),
1495    NonCode(Node<NonCodeNode>),
1496}
1497
1498impl WithinFunction {
1499    fn is_newline(&self) -> bool {
1500        match self {
1501            WithinFunction::NonCode(nc) => nc.value == NonCodeValue::NewLine,
1502            _ => false,
1503        }
1504    }
1505}
1506
1507fn body_items_within_function(i: &mut TokenSlice) -> ModalResult<WithinFunction> {
1508    // Any of the body item variants, each of which can optionally be followed by a comment.
1509    // If there is a comment, it may be preceded by whitespace.
1510    let item = dispatch! {peek(any);
1511        token if token.visibility_keyword().is_some() => (alt((import_stmt.map(BodyItem::ImportStatement), ty_decl.map(BodyItem::TypeDeclaration), declaration.map(BodyItem::VariableDeclaration))), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1512        token if token.value == "type" && matches!(token.token_type, TokenType::Keyword) =>
1513            (ty_decl.map(BodyItem::TypeDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1514        token if token.declaration_keyword().is_some() =>
1515            (declaration.map(BodyItem::VariableDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1516        token if token.value == "import" && matches!(token.token_type, TokenType::Keyword) =>
1517            (import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1518        Token { ref value, .. } if value == "return" =>
1519            (return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1520        token if !token.is_code_token() => {
1521            non_code_node.map(WithinFunction::NonCode)
1522        },
1523        token if token.token_type == TokenType::At => {
1524            annotation.map(WithinFunction::Annotation)
1525        },
1526        _ =>
1527            alt((
1528                (
1529                    declaration.map(BodyItem::VariableDeclaration),
1530                    opt(noncode_just_after_code)
1531                ).map(WithinFunction::BodyItem),
1532                (
1533                    expression_stmt.map(BodyItem::ExpressionStatement),
1534                    opt(noncode_just_after_code)
1535                ).map(WithinFunction::BodyItem),
1536            ))
1537    }
1538    .context(expected("a function body items (functions are made up of variable declarations, expressions, and return statements, each of those is a possible body item"))
1539    .parse_next(i)?;
1540    Ok(item)
1541}
1542
1543/// Parse the body of a user-defined function.
1544fn function_body(i: &mut TokenSlice) -> ModalResult<Node<Program>> {
1545    let leading_whitespace_start = alt((
1546        peek(non_code_node).map(|_| None),
1547        // Subtract 1 from `t.start` to match behaviour of the old parser.
1548        // Consider removing the -1 in the future because I think it's inaccurate, but for now,
1549        // I prefer to match the old parser exactly when I can.
1550        opt(whitespace).map(|tok| tok.and_then(|t| t.first().map(|t| (t.start.saturating_sub(1), t.module_id)))),
1551    ))
1552    .parse_next(i)?;
1553
1554    let mut things_within_body = Vec::new();
1555    // Parse the first item
1556    things_within_body.push(body_items_within_function.parse_next(i)?);
1557
1558    // This loop is complicated! I'm sorry!
1559    // It's almost identical to the loop in `winnow::combinator::separated1`,
1560    // see <https://docs.rs/winnow/latest/winnow/combinator/fn.separated1.html>,
1561    // where the "main" parser is body_items_within_function and the `sep` (separator) parser is
1562    // ws_with_newline.
1563    //
1564    // Except for one thing.
1565    //
1566    // In this case, one of the body items being matched could be a whitespace with a newline,
1567    // and that could _also_ be the separator.
1568    //
1569    // So, if both the main parser and the `sep` parser within `separated1` try to match the same
1570    // token, the main parser will consume it and then the `sep` parser will fail.
1571    //
1572    // The solution is that this parser should check if the last matched body item was an empty line,
1573    // and if so, then ignore the separator parser for the current iteration.
1574    loop {
1575        let last_match_was_empty_line = things_within_body.last().map(|wf| wf.is_newline()).unwrap_or(false);
1576
1577        use winnow::stream::Stream;
1578
1579        let start = i.checkpoint();
1580        let len = i.eof_offset();
1581
1582        let found_ws = ws_with_newline.parse_next(i);
1583
1584        // The separator whitespace might be important:
1585        // if it has an empty line, it should be considered a noncode token, because the user
1586        // deliberately put an empty line there. We should track this and preserve it.
1587        if let Ok(ref ws_token) = found_ws {
1588            if ws_token.value.contains("\n\n") || ws_token.value.contains("\n\r\n") {
1589                things_within_body.push(WithinFunction::NonCode(Node::new(
1590                    NonCodeNode {
1591                        value: NonCodeValue::NewLine,
1592                        digest: None,
1593                    },
1594                    ws_token.start,
1595                    ws_token.end,
1596                    ws_token.module_id,
1597                )));
1598            }
1599        }
1600
1601        match (found_ws, last_match_was_empty_line) {
1602            (Ok(_), _) | (_, true) => {
1603                // Infinite loop check: this loop must always consume tokens from the input.
1604                // That can either happen through the `sep` parser (i.e. ws_with_newline) or through
1605                // the main parser (body_items_within_function).
1606                // LHS of this checks fht
1607                if i.eof_offset() == len && !last_match_was_empty_line {
1608                    use winnow::error::ParserError;
1609                    return Err(ErrMode::assert(i, "sep parsers must always consume"));
1610                }
1611
1612                match body_items_within_function.parse_next(i) {
1613                    Err(ErrMode::Backtrack(_)) => {
1614                        i.reset(&start);
1615                        break;
1616                    }
1617                    Err(e) => return Err(e),
1618                    Ok(o) => {
1619                        things_within_body.push(o);
1620                    }
1621                }
1622            }
1623            (Err(ErrMode::Backtrack(_)), _) => {
1624                i.reset(&start);
1625                break;
1626            }
1627            (Err(e), _) => return Err(e),
1628        }
1629    }
1630
1631    let mut body = Vec::new();
1632    let mut inner_attrs = Vec::new();
1633    let mut pending_attrs = Vec::new();
1634    let mut non_code_meta = NonCodeMeta::default();
1635    let mut pending_non_code: Vec<Node<NonCodeNode>> = Vec::new();
1636    let mut end = 0;
1637    let mut start = leading_whitespace_start;
1638
1639    macro_rules! handle_pending_non_code {
1640        ($node: ident) => {
1641            if !pending_non_code.is_empty() {
1642                let start = pending_non_code[0].start;
1643                let force_disoc = matches!(
1644                    &pending_non_code.last().unwrap().inner.value,
1645                    NonCodeValue::NewLine
1646                );
1647                let mut comments = Vec::new();
1648                for nc in pending_non_code {
1649                    match nc.inner.value {
1650                        NonCodeValue::BlockComment { value, style } if !force_disoc => {
1651                            comments.push(style.render_comment(&value));
1652                        }
1653                        NonCodeValue::NewLineBlockComment { value, style } if !force_disoc => {
1654                            if comments.is_empty() && nc.start != 0 {
1655                                comments.push(String::new());
1656                                comments.push(String::new());
1657                            }
1658                            comments.push(style.render_comment(&value));
1659                        }
1660                        NonCodeValue::NewLine if !force_disoc && !comments.is_empty() => {
1661                            comments.push(String::new());
1662                            comments.push(String::new());
1663                        }
1664                        _ => {
1665                            if body.is_empty() {
1666                                non_code_meta.start_nodes.push(nc);
1667                            } else {
1668                                non_code_meta.insert(body.len() - 1, nc);
1669                            }
1670                        }
1671                    }
1672                }
1673                $node.set_comments(comments, start);
1674                pending_non_code = Vec::new();
1675            }
1676        };
1677    }
1678
1679    for thing_in_body in things_within_body {
1680        match thing_in_body {
1681            WithinFunction::Annotation(mut attr) => {
1682                if start.is_none() {
1683                    start = Some((attr.start, attr.module_id))
1684                }
1685                handle_pending_non_code!(attr);
1686                if attr.is_inner() {
1687                    if !body.is_empty() {
1688                        ParseContext::warn(CompilationError::err(
1689                            attr.as_source_range(),
1690                            "Named attributes should appear before any declarations or expressions.\n\nBecause named attributes apply to the whole function or module, including code written before them, it can be confusing for readers to not have these attributes at the top of code blocks.",
1691                        ));
1692                    }
1693                    inner_attrs.push(attr);
1694                } else {
1695                    pending_attrs.push(attr);
1696                }
1697            }
1698            WithinFunction::BodyItem((mut b, maybe_noncode)) => {
1699                if start.is_none() {
1700                    start = Some((b.start(), b.module_id()));
1701                }
1702                end = b.end();
1703                if !pending_attrs.is_empty() {
1704                    b.set_attrs(pending_attrs);
1705                    pending_attrs = Vec::new();
1706                }
1707                handle_pending_non_code!(b);
1708                body.push(b);
1709                if let Some(nc) = maybe_noncode {
1710                    end = nc.end;
1711                    pending_non_code.push(nc);
1712                }
1713            }
1714            WithinFunction::NonCode(nc) => {
1715                if start.is_none() {
1716                    start = Some((nc.start, nc.module_id));
1717                }
1718                end = nc.end;
1719                pending_non_code.push(nc);
1720            }
1721        }
1722    }
1723
1724    let start = start.expect(
1725        "the `things_within_body` vec should have looped at least once, and each loop overwrites `start` if it is None",
1726    );
1727
1728    if !pending_attrs.is_empty() {
1729        for a in pending_attrs {
1730            ParseContext::err(CompilationError::err(
1731                a.as_source_range(),
1732                "Attribute is not attached to any item",
1733            ));
1734        }
1735        return Err(ErrMode::Cut(
1736            CompilationError::fatal(
1737                SourceRange::new(start.0, end, start.1),
1738                "Block contains un-attached attributes",
1739            )
1740            .into(),
1741        ));
1742    }
1743
1744    for nc in pending_non_code {
1745        if body.is_empty() {
1746            non_code_meta.start_nodes.push(nc);
1747        } else {
1748            non_code_meta.insert(body.len() - 1, nc);
1749        }
1750    }
1751
1752    // Safe to unwrap `body.first()` because `body` is `separated1` therefore guaranteed
1753    // to have len >= 1.
1754    let end_ws = opt(whitespace)
1755        .parse_next(i)?
1756        .and_then(|ws| ws.first().map(|tok| tok.end));
1757    if let Some(end_ws) = end_ws {
1758        end = end.max(end_ws);
1759    }
1760    end += 1;
1761    Ok(Node::new(
1762        Program {
1763            body,
1764            non_code_meta,
1765            inner_attrs,
1766            shebang: None,
1767            digest: None,
1768        },
1769        start.0,
1770        end,
1771        start.1,
1772    ))
1773}
1774
1775fn import_items(i: &mut TokenSlice) -> ModalResult<NodeList<ImportItem>> {
1776    separated(1.., import_item, comma_sep)
1777        .parse_next(i)
1778        .map_err(|e| e.cut())
1779}
1780
1781fn glob(i: &mut TokenSlice) -> ModalResult<Token> {
1782    one_of((TokenType::Operator, "*"))
1783        .context(expected("the multiple import operator, *"))
1784        .parse_next(i)
1785}
1786
1787pub(super) fn import_stmt(i: &mut TokenSlice) -> ModalResult<BoxNode<ImportStatement>> {
1788    let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
1789        .parse_next(i)?
1790        .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
1791    let import_token = any
1792        .try_map(|token: Token| {
1793            if matches!(token.token_type, TokenType::Keyword) && token.value == "import" {
1794                Ok(token)
1795            } else {
1796                Err(CompilationError::fatal(
1797                    token.as_source_range(),
1798                    format!("{} is not the 'import' keyword", token.value.as_str()),
1799                ))
1800            }
1801        })
1802        .context(expected("the 'import' keyword"))
1803        .parse_next(i)?;
1804
1805    let module_id = import_token.module_id;
1806    let start = visibility_token.unwrap_or(import_token).start;
1807
1808    require_whitespace(i)?;
1809
1810    let (mut selector, path) = alt((
1811        string_literal.map(|s| (ImportSelector::None { alias: None }, Some(s))),
1812        glob.map(|t| {
1813            let s = t.as_source_range();
1814            (
1815                ImportSelector::Glob(Node::new((), s.start(), s.end(), s.module_id())),
1816                None,
1817            )
1818        }),
1819        import_items.map(|items| (ImportSelector::List { items }, None)),
1820    ))
1821    .parse_next(i)?;
1822
1823    let path = match path {
1824        Some(path) => path,
1825        None => {
1826            require_whitespace(i)?;
1827            any.try_map(|token: Token| {
1828                if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "from" {
1829                    Ok(())
1830                } else {
1831                    Err(CompilationError::fatal(
1832                        token.as_source_range(),
1833                        format!("{} is not the 'from' keyword", token.value.as_str()),
1834                    ))
1835                }
1836            })
1837            .context(expected("the 'from' keyword"))
1838            .parse_next(i)
1839            .map_err(|e: ErrMode<ContextError>| e.cut())?;
1840
1841            require_whitespace(i)?;
1842
1843            string_literal(i)?
1844        }
1845    };
1846
1847    let mut end: usize = path.end;
1848
1849    if let ImportSelector::None {
1850        alias: ref mut selector_alias,
1851    } = selector
1852    {
1853        if let Some(alias) = opt(preceded(
1854            (whitespace, import_as_keyword, whitespace),
1855            identifier.context(expected("an identifier to alias the import")),
1856        ))
1857        .parse_next(i)?
1858        {
1859            end = alias.end;
1860            *selector_alias = Some(alias);
1861        }
1862    }
1863
1864    let path_string = match path.inner.value {
1865        LiteralValue::String(s) => s,
1866        _ => unreachable!(),
1867    };
1868    let path = validate_path_string(
1869        path_string,
1870        selector.exposes_imported_name(),
1871        SourceRange::new(path.start, path.end, path.module_id),
1872    )?;
1873
1874    if matches!(path, ImportPath::Foreign { .. }) && selector.imports_items() {
1875        return Err(ErrMode::Cut(
1876            CompilationError::fatal(
1877                SourceRange::new(start, end, module_id),
1878                "individual items can only be imported from KCL files",
1879            )
1880            .into(),
1881        ));
1882    }
1883
1884    Ok(Node::boxed(
1885        start,
1886        end,
1887        module_id,
1888        ImportStatement {
1889            selector,
1890            visibility,
1891            path,
1892            digest: None,
1893        },
1894    ))
1895}
1896
1897/// Validates the path string in an `import` statement.
1898///
1899/// `var_name` is `true` if the path will be used as a variable name.
1900fn validate_path_string(path_string: String, var_name: bool, path_range: SourceRange) -> ModalResult<ImportPath> {
1901    if path_string.is_empty() {
1902        return Err(ErrMode::Cut(
1903            CompilationError::fatal(path_range, "import path cannot be empty").into(),
1904        ));
1905    }
1906
1907    if var_name
1908        && (path_string.starts_with("_")
1909            || path_string.contains('-')
1910            || path_string.chars().filter(|c| *c == '.').count() > 1)
1911    {
1912        return Err(ErrMode::Cut(
1913            CompilationError::fatal(path_range, "import path is not a valid identifier and must be aliased.").into(),
1914        ));
1915    }
1916
1917    let path = if path_string.ends_with(".kcl") {
1918        if path_string
1919            .chars()
1920            .any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.' && c != '/' && c != '\\')
1921        {
1922            return Err(ErrMode::Cut(
1923                CompilationError::fatal(
1924                    path_range,
1925                    "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
1926                )
1927                .into(),
1928            ));
1929        }
1930
1931        if path_string.starts_with("..") {
1932            return Err(ErrMode::Cut(
1933                CompilationError::fatal(
1934                    path_range,
1935                    "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1936                )
1937                .into(),
1938            ));
1939        }
1940
1941        // Make sure they are not using an absolute path.
1942        if path_string.starts_with('/') || path_string.starts_with('\\') {
1943            return Err(ErrMode::Cut(
1944                CompilationError::fatal(
1945                    path_range,
1946                    "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1947                )
1948                .into(),
1949            ));
1950        }
1951
1952        if (path_string.contains('/') || path_string.contains('\\'))
1953            && !(path_string.ends_with("/main.kcl") || path_string.ends_with("\\main.kcl"))
1954        {
1955            return Err(ErrMode::Cut(
1956                CompilationError::fatal(path_range, "import path to a subdirectory must only refer to main.kcl.")
1957                    .into(),
1958            ));
1959        }
1960
1961        ImportPath::Kcl {
1962            filename: TypedPath::new(&path_string),
1963        }
1964    } else if path_string.starts_with("std::") {
1965        ParseContext::warn(CompilationError::err(
1966            path_range,
1967            "explicit imports from the standard library are experimental, likely to be buggy, and likely to change.",
1968        ));
1969
1970        let segments: Vec<String> = path_string.split("::").map(str::to_owned).collect();
1971
1972        for s in &segments {
1973            if s.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') || s.starts_with('_') {
1974                return Err(ErrMode::Cut(
1975                    CompilationError::fatal(path_range, "invalid path in import statement.").into(),
1976                ));
1977            }
1978        }
1979
1980        // For now we only support importing from singly-nested modules inside std.
1981        if segments.len() != 2 {
1982            return Err(ErrMode::Cut(
1983                CompilationError::fatal(
1984                    path_range,
1985                    format!("Invalid import path for import from std: {path_string}."),
1986                )
1987                .into(),
1988            ));
1989        }
1990
1991        ImportPath::Std { path: segments }
1992    } else if path_string.contains('.') {
1993        let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
1994        if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
1995            ParseContext::warn(CompilationError::err(
1996                path_range,
1997                format!(
1998                    "unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}",
1999                    IMPORT_FILE_EXTENSIONS.join(", ")
2000                ),
2001            ))
2002        }
2003        ImportPath::Foreign {
2004            path: TypedPath::new(&path_string),
2005        }
2006    } else {
2007        return Err(ErrMode::Cut(
2008            CompilationError::fatal(
2009                path_range,
2010                format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
2011            )
2012            .into(),
2013        ));
2014    };
2015
2016    Ok(path)
2017}
2018
2019fn import_item(i: &mut TokenSlice) -> ModalResult<Node<ImportItem>> {
2020    let name = nameable_identifier
2021        .context(expected("an identifier to import"))
2022        .parse_next(i)?;
2023    let start = name.start;
2024    let module_id = name.module_id;
2025    let alias = opt(preceded(
2026        (whitespace, import_as_keyword, whitespace),
2027        identifier.context(expected("an identifier to alias the import")),
2028    ))
2029    .parse_next(i)?;
2030    let end = if let Some(ref alias) = alias {
2031        alias.end
2032    } else {
2033        name.end
2034    };
2035    Ok(Node::new(
2036        ImportItem {
2037            name,
2038            alias,
2039            digest: None,
2040        },
2041        start,
2042        end,
2043        module_id,
2044    ))
2045}
2046
2047fn import_as_keyword(i: &mut TokenSlice) -> ModalResult<Token> {
2048    any.try_map(|token: Token| {
2049        if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "as" {
2050            Ok(token)
2051        } else {
2052            Err(CompilationError::fatal(
2053                token.as_source_range(),
2054                format!("{} is not the 'as' keyword", token.value.as_str()),
2055            ))
2056        }
2057    })
2058    .context(expected("the 'as' keyword"))
2059    .parse_next(i)
2060}
2061
2062/// Parse a return statement of a user-defined function, e.g. `return x`.
2063fn return_stmt(i: &mut TokenSlice) -> ModalResult<Node<ReturnStatement>> {
2064    let ret = any
2065        .try_map(|token: Token| {
2066            if matches!(token.token_type, TokenType::Keyword) && token.value == "return" {
2067                Ok(token)
2068            } else {
2069                Err(CompilationError::fatal(
2070                    token.as_source_range(),
2071                    format!("{} is not a return keyword", token.value.as_str()),
2072                ))
2073            }
2074        })
2075        .context(expected(
2076            "the 'return' keyword, which ends your function (and becomes this function's value when it's called)",
2077        ))
2078        .parse_next(i)?;
2079    require_whitespace(i)?;
2080    let argument = expression(i)?;
2081    Ok(Node::new_node(
2082        ret.start,
2083        argument.end(),
2084        ret.module_id,
2085        ReturnStatement { argument, digest: None },
2086    ))
2087}
2088
2089fn expression_but_not_pipe(i: &mut TokenSlice) -> ModalResult<Expr> {
2090    let mut expr = alt((
2091        binary_expression.map(Box::new).map(Expr::BinaryExpression),
2092        unary_expression.map(Box::new).map(Expr::UnaryExpression),
2093        expr_allowed_in_pipe_expr,
2094    ))
2095    .context(expected("a KCL value"))
2096    .parse_next(i)?;
2097
2098    let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2099    if let Some((_, _, ty)) = ty {
2100        expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2101    }
2102    let label = opt(label).parse_next(i)?;
2103    match label {
2104        Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
2105        None => Ok(expr),
2106    }
2107}
2108
2109fn label(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2110    let result = preceded(
2111        (whitespace, import_as_keyword, whitespace),
2112        identifier.context(expected("an identifier")),
2113    )
2114    .parse_next(i)?;
2115
2116    ParseContext::warn(CompilationError::err(
2117        SourceRange::new(result.start, result.end, result.module_id),
2118        "Using `as` for tagging expressions is experimental, likely to be buggy, and likely to change",
2119    ));
2120
2121    Ok(result)
2122}
2123
2124fn unnecessarily_bracketed(i: &mut TokenSlice) -> ModalResult<Expr> {
2125    delimited(
2126        terminated(open_paren, opt(whitespace)),
2127        expression,
2128        preceded(opt(whitespace), close_paren),
2129    )
2130    .parse_next(i)
2131}
2132
2133fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
2134    let parsed_expr = alt((
2135        bool_value.map(Box::new).map(Expr::Literal),
2136        tag.map(Box::new).map(Expr::TagDeclarator),
2137        literal.map(Expr::Literal),
2138        fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2139        name.map(Box::new).map(Expr::Name),
2140        array,
2141        object.map(Box::new).map(Expr::ObjectExpression),
2142        pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
2143        function_expr,
2144        if_expr.map(Expr::IfExpression),
2145        unnecessarily_bracketed,
2146    ))
2147    .context(expected("a KCL expression (but not a pipe expression)"))
2148    .parse_next(i)?;
2149
2150    if let Ok(Some(members)) = opt(find_members).parse_next(i) {
2151        let mem = build_member_expression(parsed_expr, members);
2152        return Ok(Expr::MemberExpression(Box::new(mem)));
2153    }
2154    Ok(parsed_expr)
2155}
2156
2157fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
2158    let mut expr = alt((
2159        if_expr.map(Expr::IfExpression),
2160        unary_expression.map(Box::new).map(Expr::UnaryExpression),
2161        bool_value.map(Box::new).map(Expr::Literal),
2162        literal.map(Expr::Literal),
2163        fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2164        name.map(Box::new).map(Expr::Name),
2165        array,
2166        object.map(Box::new).map(Expr::ObjectExpression),
2167        binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
2168        unnecessarily_bracketed,
2169    ))
2170    .context(expected(
2171        "a KCL value which can be used as an argument/operand to an operator",
2172    ))
2173    .parse_next(i)?;
2174    if let Ok(Some(members)) = opt(find_members).parse_next(i) {
2175        let mem = build_member_expression(expr, members);
2176        expr = Expr::MemberExpression(Box::new(mem));
2177    }
2178
2179    let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2180    if let Some((_, _, ty)) = ty {
2181        expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2182    }
2183
2184    Ok(expr)
2185}
2186
2187/// Parse an item visibility specifier, e.g. export.
2188fn item_visibility(i: &mut TokenSlice) -> ModalResult<(ItemVisibility, Token)> {
2189    any.verify_map(|token: Token| {
2190        if token.token_type == TokenType::Keyword && token.value == "export" {
2191            Some((ItemVisibility::Export, token))
2192        } else {
2193            None
2194        }
2195    })
2196    .context(expected("item visibility, e.g. 'export'"))
2197    .parse_next(i)
2198}
2199
2200fn declaration_keyword(i: &mut TokenSlice) -> ModalResult<(VariableKind, Token)> {
2201    let res = any
2202        .verify_map(|token: Token| token.declaration_keyword().map(|kw| (kw, token)))
2203        .parse_next(i)?;
2204    Ok(res)
2205}
2206
2207/// Parse a variable/constant declaration.
2208fn declaration(i: &mut TokenSlice) -> ModalResult<BoxNode<VariableDeclaration>> {
2209    let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2210        .parse_next(i)?
2211        .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2212    let decl_token = opt(declaration_keyword).parse_next(i)?;
2213    if decl_token.is_some() {
2214        // If there was a declaration keyword like `fn`, then it must be followed by some spaces.
2215        // `fnx = ...` is not valid!
2216        require_whitespace(i)?;
2217    }
2218
2219    let id = binding_name
2220        .context(expected(
2221            "an identifier, which becomes name you're binding the value to",
2222        ))
2223        .parse_next(i)?;
2224    let (kind, mut start, dec_end) = if let Some((kind, token)) = &decl_token {
2225        (*kind, token.start, token.end)
2226    } else {
2227        (VariableKind::Const, id.start, id.end)
2228    };
2229    if let Some(token) = visibility_token {
2230        start = token.start;
2231    }
2232
2233    ignore_whitespace(i);
2234
2235    let val =
2236        if kind == VariableKind::Fn {
2237            let eq = opt(equals).parse_next(i)?;
2238            ignore_whitespace(i);
2239
2240            let val = function_decl
2241                .map(Box::new)
2242                .map(Expr::FunctionExpression)
2243                .context(expected("a KCL function expression, like () { return 1 }"))
2244                .parse_next(i);
2245
2246            if let Some(t) = eq {
2247                ParseContext::warn(
2248                    CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
2249                        .with_suggestion("Remove `=`", "", None, Tag::Unnecessary),
2250                );
2251            }
2252
2253            val
2254        } else {
2255            equals(i)?;
2256            ignore_whitespace(i);
2257
2258            let val = expression
2259                .try_map(|val| {
2260                    // Function bodies can be used if and only if declaring a function.
2261                    // Check the 'if' direction:
2262                    if matches!(val, Expr::FunctionExpression(_)) {
2263                        return Err(CompilationError::fatal(
2264                            SourceRange::new(start, dec_end, id.module_id),
2265                            format!("Expected a `fn` variable kind, found: `{kind}`"),
2266                        ));
2267                    }
2268                    Ok(val)
2269                })
2270                .context(expected("a KCL value, which is being bound to a variable"))
2271                .parse_next(i);
2272
2273            if let Some((_, tok)) = decl_token {
2274                let range_to_remove = SourceRange::new(tok.start, id.start, id.module_id);
2275                ParseContext::err(
2276                    CompilationError::err(
2277                        tok.as_source_range(),
2278                        format!(
2279                            "Using `{}` to declare constants is deprecated; no keyword is required",
2280                            tok.value
2281                        ),
2282                    )
2283                    .with_suggestion(
2284                        format!("Remove `{}`", tok.value),
2285                        "",
2286                        Some(range_to_remove),
2287                        Tag::Deprecated,
2288                    ),
2289                );
2290            }
2291
2292            val
2293        }
2294        .map_err(|e| e.cut())?;
2295
2296    let end = val.end();
2297    let module_id = id.module_id;
2298    Ok(Node::boxed(
2299        start,
2300        end,
2301        module_id,
2302        VariableDeclaration {
2303            declaration: Node::new_node(
2304                id.start,
2305                end,
2306                module_id,
2307                VariableDeclarator {
2308                    id,
2309                    init: val,
2310                    digest: None,
2311                },
2312            ),
2313            visibility,
2314            kind,
2315            digest: None,
2316        },
2317    ))
2318}
2319
2320fn ty_decl(i: &mut TokenSlice) -> ModalResult<BoxNode<TypeDeclaration>> {
2321    let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2322        .parse_next(i)?
2323        .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2324
2325    let decl_token = ty(i)?;
2326    let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
2327    whitespace(i)?;
2328
2329    let name = alt((
2330        fun.map(|t| {
2331            Node::new(
2332                Identifier {
2333                    name: "fn".to_owned(),
2334                    digest: None,
2335                },
2336                t.start,
2337                t.end,
2338                t.module_id,
2339            )
2340        }),
2341        identifier,
2342    ))
2343    .parse_next(i)?;
2344    let mut end = name.end;
2345
2346    let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
2347        ignore_whitespace(i);
2348        open_paren(i)?;
2349        ignore_whitespace(i);
2350        let args: Vec<_> = separated(0.., identifier, comma_sep).parse_next(i)?;
2351        ignore_trailing_comma(i);
2352        ignore_whitespace(i);
2353        end = close_paren(i)?.end;
2354        Some(args)
2355    } else {
2356        None
2357    };
2358
2359    let alias = if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
2360        ignore_whitespace(i);
2361        equals(i)?;
2362        ignore_whitespace(i);
2363        let ty = type_(i)?;
2364
2365        ParseContext::warn(CompilationError::err(
2366            ty.as_source_range(),
2367            "Type aliases are experimental, likely to change in the future, and likely to not work properly.",
2368        ));
2369
2370        Some(ty)
2371    } else {
2372        None
2373    };
2374
2375    let module_id = name.module_id;
2376    let result = Node::boxed(
2377        start,
2378        end,
2379        module_id,
2380        TypeDeclaration {
2381            name,
2382            args,
2383            alias,
2384            visibility,
2385            digest: None,
2386        },
2387    );
2388
2389    ParseContext::warn(CompilationError::err(
2390        result.as_source_range(),
2391        "Type declarations are experimental, likely to change, and may or may not do anything useful.",
2392    ));
2393
2394    Ok(result)
2395}
2396
2397impl TryFrom<Token> for Node<Identifier> {
2398    type Error = CompilationError;
2399
2400    fn try_from(token: Token) -> Result<Self, Self::Error> {
2401        if token.token_type == TokenType::Word {
2402            Ok(Node::new(
2403                Identifier {
2404                    name: token.value,
2405                    digest: None,
2406                },
2407                token.start,
2408                token.end,
2409                token.module_id,
2410            ))
2411        } else {
2412            Err(CompilationError::fatal(
2413                token.as_source_range(),
2414                format!(
2415                    "Cannot assign a variable to a reserved keyword: {}",
2416                    token.value.as_str()
2417                ),
2418            ))
2419        }
2420    }
2421}
2422
2423/// Parse a KCL identifier (name of a constant/variable/function)
2424fn identifier(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2425    any.try_map(Node::<Identifier>::try_from)
2426        .context(expected("an identifier, e.g. 'width' or 'myPart'"))
2427        .parse_next(i)
2428}
2429
2430fn nameable_identifier(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2431    let result = identifier.parse_next(i)?;
2432
2433    if !result.is_nameable() {
2434        let desc = if result.name == "_" {
2435            "Underscores"
2436        } else {
2437            "Names with a leading underscore"
2438        };
2439        ParseContext::err(CompilationError::err(
2440            SourceRange::new(result.start, result.end, result.module_id),
2441            format!("{desc} cannot be referred to, only declared."),
2442        ));
2443    }
2444
2445    Ok(result)
2446}
2447
2448fn name(i: &mut TokenSlice) -> ModalResult<Node<Name>> {
2449    let abs_path = opt(double_colon).parse_next(i)?;
2450    let mut idents: NodeList<Identifier> = separated(1.., nameable_identifier, double_colon)
2451        .parse_next(i)
2452        .map_err(|e| e.backtrack())?;
2453
2454    let mut start = idents[0].start;
2455    if let Some(abs_path) = &abs_path {
2456        start = abs_path.start;
2457    }
2458    let abs_path = abs_path.is_some();
2459
2460    let name = idents.pop().unwrap();
2461    let end = name.end;
2462    let module_id = name.module_id;
2463    let result = Node::new(
2464        Name {
2465            name,
2466            path: idents,
2467            abs_path,
2468            digest: None,
2469        },
2470        start,
2471        end,
2472        module_id,
2473    );
2474
2475    if let Some(suggestion) = super::deprecation(&result.to_string(), DeprecationKind::Const) {
2476        ParseContext::warn(
2477            CompilationError::err(
2478                result.as_source_range(),
2479                format!("Using `{result}` is deprecated, prefer using `{suggestion}`."),
2480            )
2481            .with_suggestion(
2482                format!("Replace `{result}` with `{suggestion}`"),
2483                suggestion,
2484                None,
2485                Tag::Deprecated,
2486            ),
2487        );
2488    }
2489
2490    Ok(result)
2491}
2492
2493impl TryFrom<Token> for Node<TagDeclarator> {
2494    type Error = CompilationError;
2495
2496    fn try_from(token: Token) -> Result<Self, Self::Error> {
2497        match token.token_type {
2498            TokenType::Word => {
2499                Ok(Node::new(
2500                    TagDeclarator {
2501                        // We subtract 1 from the start because the tag starts with a `$`.
2502                        name: token.value,
2503                        digest: None,
2504                    },
2505                    token.start - 1,
2506                    token.end,
2507                    token.module_id,
2508                ))
2509            }
2510            TokenType::Number => Err(CompilationError::fatal(
2511                token.as_source_range(),
2512                format!(
2513                    "Tag names must not start with a number. Tag starts with `{}`",
2514                    token.value.as_str()
2515                ),
2516            )),
2517
2518            // e.g. `line(%, $)` or `line(%, $ , 5)`
2519            TokenType::Brace | TokenType::Whitespace | TokenType::Comma => Err(CompilationError::fatal(
2520                token.as_source_range(),
2521                "Tag names must not be empty".to_string(),
2522            )),
2523
2524            TokenType::Type => Err(CompilationError::fatal(
2525                token.as_source_range(),
2526                format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()),
2527            )),
2528
2529            _ => Err(CompilationError::fatal(
2530                token.as_source_range(),
2531                // this is `start with` because if most of these cases are in the middle, it ends
2532                // up hitting a different error path(e.g. including a bang) or being valid(e.g. including a comment) since it will get broken up into
2533                // multiple tokens
2534                format!("Tag names must not start with a {}", token.token_type),
2535            )),
2536        }
2537    }
2538}
2539
2540/// Parse a Kcl tag that starts with a `$`.
2541fn tag(i: &mut TokenSlice) -> ModalResult<Node<TagDeclarator>> {
2542    dollar.parse_next(i)?;
2543    any.try_map(Node::<TagDeclarator>::try_from)
2544        .context(expected("a tag, e.g. '$seg01' or '$line01'"))
2545        .parse_next(i)
2546        .map_err(|e: ErrMode<ContextError>| e.cut())
2547}
2548
2549/// Helper function. Matches any number of whitespace tokens and ignores them.
2550fn ignore_whitespace(i: &mut TokenSlice) {
2551    let _: ModalResult<()> = repeat(0.., whitespace).parse_next(i);
2552}
2553
2554// A helper function to ignore a trailing comma.
2555fn ignore_trailing_comma(i: &mut TokenSlice) {
2556    let _ = opt(comma).parse_next(i);
2557}
2558
2559/// Matches at least 1 whitespace.
2560fn require_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
2561    repeat(1.., whitespace).parse_next(i)
2562}
2563
2564fn unary_expression(i: &mut TokenSlice) -> ModalResult<Node<UnaryExpression>> {
2565    const EXPECTED: &str = "expected a unary operator (like '-', the negative-numeric operator),";
2566    let (operator, op_token) = any
2567        .try_map(|token: Token| match token.token_type {
2568            TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
2569            TokenType::Operator => Err(CompilationError::fatal(
2570                 token.as_source_range(),
2571                 format!("{EXPECTED} but found {} which is an operator, but not a unary one (unary operators apply to just a single operand, your operator applies to two or more operands)", token.value.as_str(),),
2572            )),
2573            TokenType::Bang => Ok((UnaryOperator::Not, token)),
2574            other => Err(CompilationError::fatal(  token.as_source_range(), format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) )),
2575        })
2576        .context(expected("a unary expression, e.g. -x or -3"))
2577        .parse_next(i)?;
2578    ignore_whitespace(i);
2579    let argument = operand.parse_next(i)?;
2580    Ok(Node::new_node(
2581        op_token.start,
2582        argument.end(),
2583        op_token.module_id,
2584        UnaryExpression {
2585            operator,
2586            argument,
2587            digest: None,
2588        },
2589    ))
2590}
2591
2592/// Consume tokens that make up a binary expression, but don't actually return them.
2593/// Why not?
2594/// Because this is designed to be used with .take() within the `binary_expression` parser.
2595fn binary_expression_tokens(i: &mut TokenSlice) -> ModalResult<Vec<BinaryExpressionToken>> {
2596    let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
2597    let remaining: Vec<_> = repeat(
2598        1..,
2599        (
2600            preceded(opt(whitespace), binary_operator).map(BinaryExpressionToken::from),
2601            preceded(opt(whitespace), operand).map(BinaryExpressionToken::from),
2602        ),
2603    )
2604    .context(expected(
2605        "one or more binary operators (like + or -) and operands for them, e.g. 1 + 2 - 3",
2606    ))
2607    .parse_next(i)?;
2608    let mut out = Vec::with_capacity(1 + 2 * remaining.len());
2609    out.push(first);
2610    out.extend(remaining.into_iter().flat_map(|(a, b)| [a, b]));
2611    Ok(out)
2612}
2613
2614/// Parse an infix binary expression.
2615fn binary_expression(i: &mut TokenSlice) -> ModalResult<Node<BinaryExpression>> {
2616    // Find the slice of tokens which makes up the binary expression
2617    let tokens = binary_expression_tokens.parse_next(i)?;
2618
2619    // Pass the token slice into the specialized math parser, for things like
2620    // precedence and converting infix operations to an AST.
2621    let expr = super::math::parse(tokens).map_err(|e| ErrMode::Backtrack(e.into()))?;
2622    Ok(expr)
2623}
2624
2625fn binary_expr_in_parens(i: &mut TokenSlice) -> ModalResult<Node<BinaryExpression>> {
2626    let span_with_brackets = bracketed_section.take().parse_next(i)?;
2627    let mut span_no_brackets = span_with_brackets.without_ends();
2628    let expr = binary_expression.parse_next(&mut span_no_brackets)?;
2629    Ok(expr)
2630}
2631
2632/// Match a starting bracket, then match to the corresponding end bracket.
2633/// Return the count of how many tokens are in that span
2634/// (not including the bracket tokens).
2635fn bracketed_section(i: &mut TokenSlice) -> ModalResult<usize> {
2636    // Find the start of this bracketed expression.
2637    let _ = open_paren.parse_next(i)?;
2638    let mut opened_braces = 1usize;
2639    let mut tokens_examined = 0;
2640    while opened_braces > 0 {
2641        let tok = any.parse_next(i)?;
2642        tokens_examined += 1;
2643        if matches!(tok.token_type, TokenType::Brace) {
2644            if tok.value == "(" {
2645                opened_braces += 1;
2646            } else if tok.value == ")" {
2647                opened_braces -= 1;
2648            }
2649        }
2650    }
2651    Ok(tokens_examined)
2652}
2653
2654/// Parse a KCL expression statement.
2655fn expression_stmt(i: &mut TokenSlice) -> ModalResult<Node<ExpressionStatement>> {
2656    let val = expression
2657        .context(expected(
2658            "an expression (i.e. a value, or an algorithm for calculating one), e.g. 'x + y' or '3' or 'width * 2'",
2659        ))
2660        .parse_next(i)?;
2661    Ok(Node::new_node(
2662        val.start(),
2663        val.end(),
2664        val.module_id(),
2665        ExpressionStatement {
2666            expression: val,
2667            digest: None,
2668        },
2669    ))
2670}
2671
2672/// Parse the given brace symbol.
2673fn some_brace(symbol: &'static str, i: &mut TokenSlice) -> ModalResult<Token> {
2674    one_of((TokenType::Brace, symbol))
2675        .context(expected(symbol))
2676        .parse_next(i)
2677}
2678
2679/// Parse a |> operator.
2680fn pipe_operator(i: &mut TokenSlice) -> ModalResult<Token> {
2681    one_of((TokenType::Operator, PIPE_OPERATOR))
2682        .context(expected(
2683            "the |> operator, used for 'piping' one function's output into another function's input",
2684        ))
2685        .parse_next(i)
2686}
2687
2688fn ws_with_newline(i: &mut TokenSlice) -> ModalResult<Token> {
2689    one_of(TokenType::Whitespace)
2690        .verify(|token: &Token| token.value.contains('\n'))
2691        .context(expected("a newline, possibly with whitespace"))
2692        .parse_next(i)
2693}
2694
2695/// (
2696fn open_paren(i: &mut TokenSlice) -> ModalResult<Token> {
2697    some_brace("(", i)
2698}
2699
2700/// )
2701fn close_paren(i: &mut TokenSlice) -> ModalResult<Token> {
2702    some_brace(")", i)
2703}
2704
2705/// [
2706fn open_bracket(i: &mut TokenSlice) -> ModalResult<Token> {
2707    some_brace("[", i)
2708}
2709
2710/// ]
2711fn close_bracket(i: &mut TokenSlice) -> ModalResult<Token> {
2712    some_brace("]", i)
2713}
2714
2715/// {
2716fn open_brace(i: &mut TokenSlice) -> ModalResult<Token> {
2717    some_brace("{", i)
2718}
2719
2720/// }
2721fn close_brace(i: &mut TokenSlice) -> ModalResult<Token> {
2722    some_brace("}", i)
2723}
2724
2725fn comma(i: &mut TokenSlice) -> ModalResult<()> {
2726    TokenType::Comma.parse_from(i)?;
2727    Ok(())
2728}
2729
2730fn hash(i: &mut TokenSlice) -> ModalResult<()> {
2731    TokenType::Hash.parse_from(i)?;
2732    Ok(())
2733}
2734
2735fn bang(i: &mut TokenSlice) -> ModalResult<Token> {
2736    TokenType::Bang.parse_from(i)
2737}
2738
2739fn dollar(i: &mut TokenSlice) -> ModalResult<()> {
2740    TokenType::Dollar.parse_from(i)?;
2741    Ok(())
2742}
2743
2744fn period(i: &mut TokenSlice) -> ModalResult<()> {
2745    TokenType::Period.parse_from(i)?;
2746    Ok(())
2747}
2748
2749fn end_inclusive_range(i: &mut TokenSlice) -> ModalResult<Token> {
2750    any.try_map(|token: Token| {
2751        if matches!(token.token_type, TokenType::DoublePeriod) {
2752            Ok(token)
2753        } else {
2754            Err(CompilationError::fatal(
2755                token.as_source_range(),
2756                format!(
2757                    "expected a '..' (double period) found {} which is {}",
2758                    token.value.as_str(),
2759                    token.token_type
2760                ),
2761            ))
2762        }
2763    })
2764    .context(expected("the .. operator, used for array ranges like [0..10]"))
2765    .parse_next(i)
2766}
2767
2768fn end_exclusive_range(i: &mut TokenSlice) -> ModalResult<Token> {
2769    any.try_map(|token: Token| {
2770        if matches!(token.token_type, TokenType::DoublePeriodLessThan) {
2771            Ok(token)
2772        } else {
2773            Err(CompilationError::fatal(
2774                token.as_source_range(),
2775                format!("expected a '..<' but found {}", token.value.as_str()),
2776            ))
2777        }
2778    })
2779    .context(expected("the ..< operator, used for array ranges like [0..<10]"))
2780    .parse_next(i)
2781}
2782
2783fn colon(i: &mut TokenSlice) -> ModalResult<Token> {
2784    TokenType::Colon.parse_from(i)
2785}
2786
2787fn semi_colon(i: &mut TokenSlice) -> ModalResult<Token> {
2788    TokenType::SemiColon.parse_from(i)
2789}
2790
2791fn plus(i: &mut TokenSlice) -> ModalResult<Token> {
2792    one_of((TokenType::Operator, "+")).parse_next(i)
2793}
2794
2795fn double_colon(i: &mut TokenSlice) -> ModalResult<Token> {
2796    TokenType::DoubleColon.parse_from(i)
2797}
2798
2799fn equals(i: &mut TokenSlice) -> ModalResult<Token> {
2800    one_of((TokenType::Operator, "="))
2801        .context(expected("the equals operator, ="))
2802        .parse_next(i)
2803}
2804
2805fn question_mark(i: &mut TokenSlice) -> ModalResult<()> {
2806    TokenType::QuestionMark.parse_from(i)?;
2807    Ok(())
2808}
2809
2810fn at_sign(i: &mut TokenSlice) -> ModalResult<Token> {
2811    TokenType::At.parse_from(i)
2812}
2813
2814fn fun(i: &mut TokenSlice) -> ModalResult<Token> {
2815    keyword(i, "fn")
2816}
2817
2818fn ty(i: &mut TokenSlice) -> ModalResult<Token> {
2819    keyword(i, "type")
2820}
2821
2822fn any_keyword(i: &mut TokenSlice) -> ModalResult<Token> {
2823    any.try_map(|token: Token| match token.token_type {
2824        TokenType::Keyword => Ok(token),
2825        _ => Err(CompilationError::fatal(
2826            token.as_source_range(),
2827            "expected some reserved keyword".to_owned(),
2828        )),
2829    })
2830    .parse_next(i)
2831}
2832
2833fn keyword(i: &mut TokenSlice, expected: &str) -> ModalResult<Token> {
2834    any.try_map(|token: Token| match token.token_type {
2835        TokenType::Keyword if token.value == expected => Ok(token),
2836        _ => Err(CompilationError::fatal(
2837            token.as_source_range(),
2838            format!("expected '{expected}', found {}", token.value.as_str(),),
2839        )),
2840    })
2841    .parse_next(i)
2842}
2843
2844/// Parse a comma, optionally followed by some whitespace.
2845fn comma_sep(i: &mut TokenSlice) -> ModalResult<()> {
2846    (opt(whitespace), comma, opt(whitespace))
2847        .context(expected("a comma, optionally followed by whitespace"))
2848        .parse_next(i)?;
2849    Ok(())
2850}
2851
2852/// Parse a `|`, optionally followed by some whitespace.
2853fn pipe_sep(i: &mut TokenSlice) -> ModalResult<()> {
2854    (opt(whitespace), one_of((TokenType::Operator, "|")), opt(whitespace)).parse_next(i)?;
2855    Ok(())
2856}
2857
2858fn labeled_argument(i: &mut TokenSlice) -> ModalResult<LabeledArg> {
2859    (
2860        opt((
2861            terminated(nameable_identifier, opt(whitespace)),
2862            terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
2863        )),
2864        expression,
2865    )
2866        .map(|(label, arg)| LabeledArg {
2867            label: label.map(|(l, _)| l),
2868            arg,
2869        })
2870        .parse_next(i)
2871}
2872
2873fn record_ty_field(i: &mut TokenSlice) -> ModalResult<(Node<Identifier>, Node<Type>)> {
2874    (identifier, colon, opt(whitespace), type_)
2875        .map(|(id, _, _, ty)| (id, ty))
2876        .parse_next(i)
2877}
2878
2879/// Parse a type in various positions.
2880fn type_(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
2881    separated(1.., type_not_union, pipe_sep)
2882        .map(|mut tys: Vec<_>| {
2883            if tys.len() == 1 {
2884                tys.pop().unwrap()
2885            } else {
2886                let start = tys[0].start;
2887                let module_id = tys[0].module_id;
2888                let end = tys.last().unwrap().end;
2889                Node::new(Type::Union { tys }, start, end, module_id)
2890            }
2891        })
2892        .parse_next(i)
2893}
2894
2895fn type_not_union(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
2896    alt((
2897        // Object types
2898        (
2899            open_brace,
2900            opt(whitespace),
2901            separated(0.., record_ty_field, comma_sep),
2902            opt(whitespace),
2903            close_brace,
2904        )
2905            .try_map(|(open, _, params, _, close)| {
2906                Ok(Node::new(
2907                    Type::Object { properties: params },
2908                    open.start,
2909                    close.end,
2910                    open.module_id,
2911                ))
2912            }),
2913        // Array types
2914        array_type,
2915        // Primitive types
2916        primitive_type.map(|t| t.map(Type::Primitive)),
2917    ))
2918    .parse_next(i)
2919}
2920
2921fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
2922    alt((
2923        // A function type: `fn` (`(` type?, (id: type,)* `)` (`:` type)?)?
2924        (
2925            fun,
2926            opt((
2927                // `(` type?, (id: type,)* `)`
2928                delimited(
2929                    open_paren,
2930                    opt(alt((
2931                        // type, (id: type,)+
2932                        (
2933                            type_,
2934                            comma,
2935                            opt(whitespace),
2936                            separated(
2937                                1..,
2938                                (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2939                                comma_sep,
2940                            ),
2941                        )
2942                            .map(|(t, _, _, args)| (Some(t), args)),
2943                        // (id: type,)+
2944                        separated(
2945                            1..,
2946                            (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2947                            comma_sep,
2948                        )
2949                        .map(|args| (None, args)),
2950                        // type
2951                        type_.map(|t| (Some(t), Vec::new())),
2952                    ))),
2953                    close_paren,
2954                ),
2955                // `:` type
2956                opt((colon, opt(whitespace), type_)),
2957            )),
2958        )
2959            .map(|(t, tys)| {
2960                let mut ft = FunctionType::empty_fn_type();
2961
2962                if let Some((args, ret)) = tys {
2963                    if let Some((unnamed, named)) = args {
2964                        if let Some(unnamed) = unnamed {
2965                            ft.unnamed_arg = Some(Box::new(unnamed));
2966                        }
2967                        ft.named_args = named;
2968                    }
2969                    if let Some((_, _, ty)) = ret {
2970                        ft.return_type = Some(Box::new(ty));
2971                    }
2972                }
2973
2974                Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
2975            }),
2976        // A named type, possibly with a numeric suffix.
2977        (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
2978            let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
2979            result.inner =
2980                PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident });
2981            result
2982        }),
2983    ))
2984    .parse_next(i)
2985}
2986
2987fn array_type(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
2988    fn opt_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
2989        ignore_whitespace(i);
2990        Ok(())
2991    }
2992
2993    open_bracket(i)?;
2994    let ty = type_(i)?;
2995    let len = opt((
2996        semi_colon,
2997        opt_whitespace,
2998        any.try_map(|token: Token| match token.token_type {
2999            TokenType::Number => {
3000                let value = token.uint_value().ok_or_else(|| {
3001                    CompilationError::fatal(
3002                        token.as_source_range(),
3003                        format!("Expected unsigned integer literal, found: {}", token.value),
3004                    )
3005                })?;
3006
3007                Ok(value as usize)
3008            }
3009            _ => Err(CompilationError::fatal(token.as_source_range(), "invalid array length")),
3010        }),
3011        opt(plus),
3012    ))
3013    .parse_next(i)?;
3014    close_bracket(i)?;
3015
3016    let len = if let Some((_, _, n, plus)) = len {
3017        if plus.is_some() {
3018            ArrayLen::Minimum(n)
3019        } else {
3020            ArrayLen::Known(n)
3021        }
3022    } else {
3023        ArrayLen::None
3024    };
3025
3026    Ok(ty.map(|ty| Type::Array { ty: Box::new(ty), len }))
3027}
3028
3029fn uom_for_type(i: &mut TokenSlice) -> ModalResult<NumericSuffix> {
3030    any.try_map(|t: Token| t.value.parse()).parse_next(i)
3031}
3032
3033fn comment(i: &mut TokenSlice) -> ModalResult<Node<String>> {
3034    any.verify_map(|token: Token| {
3035        let value = match token.token_type {
3036            TokenType::LineComment => token.value,
3037            TokenType::BlockComment => token.value,
3038            _ => return None,
3039        };
3040        Some(Node::new(value, token.start, token.end, token.module_id))
3041    })
3042    .context(expected("Comment"))
3043    .parse_next(i)
3044}
3045
3046fn comments(i: &mut TokenSlice) -> ModalResult<Node<Vec<String>>> {
3047    let comments: Vec<Node<String>> = repeat(1.., (comment, opt(whitespace)).map(|(c, _)| c)).parse_next(i)?;
3048    let start = comments[0].start;
3049    let module_id = comments[0].module_id;
3050    let end = comments.last().unwrap().end;
3051    let inner = comments.into_iter().map(|n| n.inner).collect();
3052    Ok(Node::new(inner, start, end, module_id))
3053}
3054
3055struct ParamDescription {
3056    labeled: bool,
3057    arg_name: Token,
3058    type_: std::option::Option<Node<Type>>,
3059    default_value: Option<DefaultParamVal>,
3060    attr: Option<Node<Annotation>>,
3061    comments: Option<Node<Vec<String>>>,
3062}
3063
3064fn parameter(i: &mut TokenSlice) -> ModalResult<ParamDescription> {
3065    let (_, comments, _, attr, _, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
3066        opt(whitespace),
3067        opt(comments),
3068        opt(whitespace),
3069        opt(outer_annotation),
3070        opt(whitespace),
3071        opt(at_sign),
3072        any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
3073        opt(question_mark),
3074        opt(whitespace),
3075        opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
3076        opt(whitespace),
3077        opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
3078    )
3079        .parse_next(i)?;
3080
3081    Ok(ParamDescription {
3082        labeled: found_at_sign.is_none(),
3083        arg_name,
3084        type_,
3085        default_value: match (question_mark.is_some(), default_literal) {
3086            (true, Some(lit)) => Some(DefaultParamVal::Literal(*lit)),
3087            (true, None) => Some(DefaultParamVal::none()),
3088            (false, None) => None,
3089            (false, Some(lit)) => {
3090                let msg = "You're trying to set a default value for an argument, but only optional arguments can have default values, and this argument is mandatory. Try putting a ? after the argument name, to make the argument optional.";
3091                let e = CompilationError::fatal((&lit).into(), msg);
3092                return Err(ErrMode::Backtrack(ContextError::from(e)));
3093            }
3094        },
3095        attr,
3096        comments,
3097    })
3098}
3099
3100/// Parameters are declared in a function signature, and used within a function.
3101fn parameters(i: &mut TokenSlice) -> ModalResult<Vec<Parameter>> {
3102    // Get all tokens until the next ), because that ends the parameter list.
3103    let candidates: Vec<_> = separated(0.., parameter, comma_sep)
3104        .context(expected("function parameters"))
3105        .parse_next(i)?;
3106    opt(comma_sep).parse_next(i)?;
3107
3108    // Make sure all those tokens are valid parameters.
3109    let params: Vec<Parameter> = candidates
3110        .into_iter()
3111        .map(
3112            |ParamDescription {
3113                 labeled,
3114                 arg_name,
3115                 type_,
3116                 default_value,
3117                 attr,
3118                 comments,
3119             }| {
3120                let mut identifier = Node::<Identifier>::try_from(arg_name)?;
3121                if let Some(comments) = comments {
3122                    identifier.comment_start = comments.start;
3123                    identifier.pre_comments = comments.inner;
3124                }
3125                if let Some(attr) = attr {
3126                    identifier.outer_attrs.push(attr);
3127                }
3128
3129                Ok(Parameter {
3130                    identifier,
3131                    type_,
3132                    default_value,
3133                    labeled,
3134                    digest: None,
3135                })
3136            },
3137        )
3138        .collect::<Result<_, _>>()
3139        .map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
3140
3141    // Make sure the only unlabeled parameter is the first one.
3142    if let Some(param) = params.iter().skip(1).find(|param| !param.labeled) {
3143        let source_range = SourceRange::from(param);
3144        return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
3145            source_range,
3146            "Only the first parameter can be declared unlabeled",
3147        ))));
3148    }
3149
3150    // Make sure optional parameters are last.
3151    if let Err(e) = optional_after_required(&params) {
3152        return Err(ErrMode::Cut(ContextError::from(e)));
3153    }
3154    Ok(params)
3155}
3156
3157fn optional_after_required(params: &[Parameter]) -> Result<(), CompilationError> {
3158    let mut found_optional = false;
3159    for p in params {
3160        if p.optional() {
3161            found_optional = true;
3162        }
3163        if !p.optional() && found_optional {
3164            let e = CompilationError::fatal(
3165                (&p.identifier).into(),
3166                "mandatory parameters must be declared before optional parameters",
3167            );
3168            return Err(e);
3169        }
3170    }
3171    Ok(())
3172}
3173
3174/// Introduce a new name, which binds some value.
3175fn binding_name(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
3176    identifier
3177        .context(expected("an identifier, which will be the name of some value"))
3178        .parse_next(i)
3179}
3180
3181fn labelled_fn_call(i: &mut TokenSlice) -> ModalResult<Expr> {
3182    let expr = fn_call_kw.map(Box::new).map(Expr::CallExpressionKw).parse_next(i)?;
3183
3184    let label = opt(label).parse_next(i)?;
3185    match label {
3186        Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
3187        None => Ok(expr),
3188    }
3189}
3190
3191fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
3192    let fn_name = name(i)?;
3193    opt(whitespace).parse_next(i)?;
3194    let _ = open_paren.parse_next(i)?;
3195    ignore_whitespace(i);
3196
3197    // Special case: no args or one arg (unlabeled)
3198    let checkpoint = i.checkpoint();
3199    let mut initial_unlabeled_arg = opt(expression).parse_next(i)?;
3200
3201    // A label would parse as an expression, but if there's an `=` following then it is more likely
3202    // to be a labelled arg.
3203    if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
3204        i.reset(&checkpoint);
3205        initial_unlabeled_arg = None;
3206    } else {
3207        let early_close = peek((opt(whitespace), close_paren)).parse_next(i);
3208        if early_close.is_ok() {
3209            ignore_whitespace(i);
3210            let end = close_paren.parse_next(i)?.end;
3211            let result = Node::new_node(
3212                fn_name.start,
3213                end,
3214                fn_name.module_id,
3215                CallExpressionKw {
3216                    callee: fn_name,
3217                    unlabeled: initial_unlabeled_arg,
3218                    arguments: Default::default(),
3219                    digest: None,
3220                    non_code_meta: Default::default(),
3221                },
3222            );
3223            return Ok(result);
3224        }
3225    }
3226
3227    if initial_unlabeled_arg.is_some() {
3228        // The comma following the unlabelled arg. If there are no more arguments, then we should
3229        // hit the above path, so the comma is not optional.
3230        labeled_arg_separator.parse_next(i)?;
3231    }
3232
3233    #[derive(Debug)]
3234    #[allow(clippy::large_enum_variant)]
3235    enum ArgPlace {
3236        NonCode(Node<NonCodeNode>),
3237        LabeledArg((LabeledArg, Option<SourceRange>)),
3238        UnlabeledArg(Expr),
3239        Keyword(Token),
3240    }
3241    let args: Vec<_> = repeat(
3242        0..,
3243        alt((
3244            terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
3245            terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
3246            (labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
3247            expression.map(ArgPlace::UnlabeledArg),
3248        )),
3249    )
3250    .parse_next(i)?;
3251    let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().try_fold(
3252        (Vec::new(), BTreeMap::new()),
3253        |(mut args, mut non_code_nodes), (index, e)| {
3254            match e {
3255                ArgPlace::NonCode(x) => {
3256                    non_code_nodes.insert(index, vec![x]);
3257                }
3258                ArgPlace::LabeledArg((x, bad_token_source_range)) => {
3259                    if let Some(bad_token_source_range) = bad_token_source_range {
3260                        return Err(ErrMode::Cut(
3261                            CompilationError::fatal(
3262                                bad_token_source_range,
3263                                "Missing comma between arguments, try adding a comma in",
3264                            )
3265                            .into(),
3266                        ));
3267                    }
3268                    args.push(x);
3269                }
3270                ArgPlace::Keyword(kw) => {
3271                    return Err(ErrMode::Cut(
3272                        CompilationError::fatal(
3273                            SourceRange::from(kw.clone()),
3274                            format!(
3275                                "`{}` is not the name of an argument (it's a reserved keyword)",
3276                                kw.value
3277                            ),
3278                        )
3279                        .into(),
3280                    ));
3281                }
3282                ArgPlace::UnlabeledArg(arg) => {
3283                    let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
3284                    if followed_by_equals {
3285                        return Err(ErrMode::Cut(
3286                            CompilationError::fatal(
3287                                SourceRange::from(arg),
3288                                "This argument has a label, but no value. Put some value after the equals sign",
3289                            )
3290                            .into(),
3291                        ));
3292                    } else {
3293                        args.push(LabeledArg { label: None, arg });
3294                    }
3295                }
3296            }
3297            Ok((args, non_code_nodes))
3298        },
3299    )?;
3300    ignore_whitespace(i);
3301    opt(comma_sep).parse_next(i)?;
3302    let end = match close_paren.parse_next(i) {
3303        Ok(tok) => tok.end,
3304        Err(e) => {
3305            if let Some(tok) = i.next_token() {
3306                return Err(ErrMode::Cut(
3307                    CompilationError::fatal(
3308                        SourceRange::from(&tok),
3309                        format!("There was an unexpected `{}`. Try removing it.", tok.value),
3310                    )
3311                    .into(),
3312                ));
3313            } else {
3314                return Err(e);
3315            }
3316        }
3317    };
3318
3319    // Validate there aren't any duplicate labels.
3320    let mut counted_labels = IndexMap::with_capacity(args.len());
3321    for arg in &args {
3322        if let Some(l) = &arg.label {
3323            *counted_labels.entry(&l.inner.name).or_insert(0) += 1;
3324        }
3325    }
3326    if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
3327        let msg = format!(
3328            "You've used the parameter labelled '{duplicated}' {n} times in a single function call. You can only set each parameter once! Remove all but one use."
3329        );
3330        ParseContext::err(CompilationError::err(
3331            SourceRange::new(fn_name.start, end, fn_name.module_id),
3332            msg,
3333        ));
3334    }
3335
3336    let non_code_meta = NonCodeMeta {
3337        non_code_nodes,
3338        ..Default::default()
3339    };
3340    let result = Node::new_node(
3341        fn_name.start,
3342        end,
3343        fn_name.module_id,
3344        CallExpressionKw {
3345            callee: fn_name,
3346            unlabeled: initial_unlabeled_arg,
3347            arguments: args,
3348            digest: None,
3349            non_code_meta,
3350        },
3351    );
3352
3353    let callee_str = result.callee.name.name.to_string();
3354    if let Some(suggestion) = super::deprecation(&callee_str, DeprecationKind::Function) {
3355        ParseContext::warn(
3356            CompilationError::err(
3357                result.as_source_range(),
3358                format!("Calling `{callee_str}` is deprecated, prefer using `{suggestion}`."),
3359            )
3360            .with_suggestion(
3361                format!("Replace `{callee_str}` with `{suggestion}`"),
3362                suggestion,
3363                None,
3364                Tag::Deprecated,
3365            ),
3366        );
3367    }
3368
3369    Ok(result)
3370}
3371
3372#[cfg(test)]
3373mod tests {
3374    use itertools::Itertools;
3375    use pretty_assertions::assert_eq;
3376
3377    use super::*;
3378    use crate::{
3379        ModuleId,
3380        parsing::ast::types::{BodyItem, Expr, VariableKind},
3381    };
3382
3383    fn assert_reserved(word: &str) {
3384        // Try to use it as a variable name.
3385        let code = format!(r#"{word} = 0"#);
3386        let result = crate::parsing::top_level_parse(code.as_str());
3387        let err = &result.unwrap_errs().next().unwrap();
3388        // Which token causes the error may change.  In "return = 0", for
3389        // example, "return" is the problem.
3390        assert!(
3391            err.message.starts_with("Unexpected token: ")
3392                || err.message.starts_with("= is not")
3393                || err
3394                    .message
3395                    .starts_with("Cannot assign a variable to a reserved keyword: "),
3396            "Error message is: `{}`",
3397            err.message,
3398        );
3399    }
3400
3401    #[test]
3402    fn reserved_words() {
3403        // Since these are stored in a set, we sort to make the tests
3404        // deterministic.
3405        for word in crate::parsing::token::RESERVED_WORDS.keys().sorted() {
3406            assert_reserved(word);
3407        }
3408        assert_reserved("import");
3409    }
3410
3411    #[test]
3412    fn parse_names() {
3413        for (test, expected_len) in [("someVar", 0), ("::foo", 0), ("foo::bar::baz", 2)] {
3414            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3415            match name.parse(tokens.as_slice()) {
3416                Ok(n) => assert_eq!(n.path.len(), expected_len, "Could not parse name from `{test}`: {n:?}"),
3417                Err(e) => panic!("Could not parse name from `{test}`: {e:?}"),
3418            }
3419        }
3420    }
3421
3422    #[test]
3423    fn weird_program_unclosed_paren() {
3424        let tokens = crate::parsing::token::lex("fn firstPrime(", ModuleId::default()).unwrap();
3425        let tokens = tokens.as_slice();
3426        let last = tokens.last().unwrap().as_source_range();
3427        let err: CompilationError = program.parse(tokens).unwrap_err().into();
3428        assert_eq!(err.source_range, last);
3429        // TODO: Better comment. This should explain the compiler expected ) because the user had started declaring the function's parameters.
3430        // Part of https://github.com/KittyCAD/modeling-app/issues/784
3431        assert_eq!(err.message, "Unexpected end of file. The compiler expected )");
3432    }
3433
3434    #[test]
3435    fn kw_call_as_operand() {
3436        let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
3437        let tokens = tokens.as_slice();
3438        operand.parse(tokens).unwrap();
3439    }
3440
3441    #[test]
3442    fn parse_binary_operator_on_array() {
3443        let tokens = crate::parsing::token::lex("[0] + 1", ModuleId::default()).unwrap();
3444        let tokens = tokens.as_slice();
3445        binary_expression.parse(tokens).unwrap();
3446    }
3447
3448    #[test]
3449    fn expression_in_array_index() {
3450        let tokens = crate::parsing::token::lex("arr[x + 1]", ModuleId::default()).unwrap();
3451        let tokens = tokens.as_slice();
3452        let Expr::MemberExpression(expr) = expression.parse(tokens).unwrap() else {
3453            panic!();
3454        };
3455        let Expr::BinaryExpression(be) = expr.inner.property else {
3456            panic!();
3457        };
3458        assert_eq!(be.inner.operator, BinaryOperator::Add);
3459    }
3460
3461    #[test]
3462    fn parse_binary_operator_on_object() {
3463        let tokens = crate::parsing::token::lex("{ a = 1 } + 2", ModuleId::default()).unwrap();
3464        let tokens = tokens.as_slice();
3465        binary_expression.parse(tokens).unwrap();
3466    }
3467
3468    #[test]
3469    fn parse_call_array_operator() {
3470        let tokens = crate::parsing::token::lex("f([0] + 1)", ModuleId::default()).unwrap();
3471        let tokens = tokens.as_slice();
3472        fn_call_kw.parse(tokens).unwrap();
3473    }
3474
3475    #[test]
3476    fn weird_program_just_a_pipe() {
3477        let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();
3478        let err: CompilationError = program.parse(tokens.as_slice()).unwrap_err().into();
3479        assert_eq!(err.source_range, SourceRange::new(0, 1, ModuleId::default()));
3480        assert_eq!(err.message, "Unexpected token: |");
3481    }
3482
3483    #[test]
3484    fn parse_binary_expressions() {
3485        for (i, test_program) in ["1 + 2 + 3"].into_iter().enumerate() {
3486            let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3487            let _actual = match binary_expression.parse_next(&mut tokens.as_slice()) {
3488                Ok(x) => x,
3489                Err(e) => panic!("Failed test {i}, could not parse binary expressions from \"{test_program}\": {e:?}"),
3490            };
3491        }
3492    }
3493
3494    #[test]
3495    fn test_vardec_no_keyword() {
3496        let tokens = crate::parsing::token::lex("x = 4", ModuleId::default()).unwrap();
3497        let vardec = declaration(&mut tokens.as_slice()).unwrap();
3498        assert_eq!(vardec.inner.kind, VariableKind::Const);
3499        let vardec = &vardec.declaration;
3500        assert_eq!(vardec.id.name, "x");
3501        let Expr::Literal(init_val) = &vardec.init else {
3502            panic!("weird init value")
3503        };
3504        assert_eq!(init_val.raw, "4");
3505    }
3506
3507    #[test]
3508    fn test_negative_operands() {
3509        let tokens = crate::parsing::token::lex("-leg2", ModuleId::default()).unwrap();
3510        let _s = operand.parse_next(&mut tokens.as_slice()).unwrap();
3511    }
3512
3513    #[test]
3514    fn test_comments_in_function1() {
3515        let test_program = r#"() {
3516            // comment 0
3517            a = 1
3518            // comment 1
3519            b = 2
3520            /// comment 2
3521            return 1
3522        }"#;
3523        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3524        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3525        assert_eq!(expr.params, vec![]);
3526        let comment_start = expr.body.body[0].get_comments();
3527        let comment0 = expr.body.body[1].get_comments();
3528        let comment1 = expr.body.body[2].get_comments();
3529        assert_eq!(comment_start, vec!["// comment 0".to_owned()]);
3530        assert_eq!(comment0, vec!["// comment 1".to_owned()]);
3531        assert_eq!(comment1, vec!["/// comment 2".to_owned()]);
3532    }
3533
3534    #[test]
3535    fn test_comments_in_function2() {
3536        let test_program = r#"() {
3537  yo = { a = { b = { c = '123' } } } /* block
3538comment */
3539}"#;
3540        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3541        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3542        let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
3543        assert_eq!(comment0.value(), "block\ncomment");
3544    }
3545
3546    #[test]
3547    fn test_comment_at_start_of_program() {
3548        let test_program = r#"
3549/* comment at start */
3550
3551mySk1 = startSketchOn(XY)
3552  |> startProfile(at = [0, 0])"#;
3553        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3554        let program = program.parse(tokens.as_slice()).unwrap();
3555        let mut starting_comments = program.inner.non_code_meta.start_nodes;
3556        assert_eq!(starting_comments.len(), 2);
3557        let start0 = starting_comments.remove(0);
3558        let start1 = starting_comments.remove(0);
3559        assert_eq!(
3560            start0.value,
3561            NonCodeValue::BlockComment {
3562                value: "comment at start".to_owned(),
3563                style: CommentStyle::Block
3564            }
3565        );
3566        assert_eq!(start1.value, NonCodeValue::NewLine);
3567    }
3568
3569    #[test]
3570    fn test_comment_in_pipe() {
3571        let tokens = crate::parsing::token::lex(r#"x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
3572        let mut body = program.parse(tokens.as_slice()).unwrap().inner.body;
3573        let BodyItem::VariableDeclaration(item) = body.remove(0) else {
3574            panic!("expected vardec");
3575        };
3576        let val = item.inner.declaration.inner.init;
3577        let Expr::PipeExpression(pipe) = val else {
3578            panic!("expected pipe");
3579        };
3580        let mut noncode = pipe.inner.non_code_meta;
3581        assert_eq!(noncode.non_code_nodes.len(), 1);
3582        let comment = noncode.non_code_nodes.remove(&0).unwrap().pop().unwrap();
3583        assert_eq!(
3584            comment.value,
3585            NonCodeValue::BlockComment {
3586                value: "hi".to_owned(),
3587                style: CommentStyle::Block
3588            }
3589        );
3590    }
3591
3592    #[test]
3593    fn test_whitespace_in_function() {
3594        let test_program = r#"() {
3595            return sg
3596            return sg
3597          }"#;
3598        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3599        let _expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3600    }
3601
3602    #[test]
3603    fn test_empty_lines_in_function() {
3604        let test_program = "() {
3605
3606                return 2
3607            }";
3608        let module_id = ModuleId::from_usize(1);
3609        let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3610        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3611        assert_eq!(
3612            expr.body.non_code_meta.start_nodes,
3613            vec![Node::new(
3614                NonCodeNode {
3615                    value: NonCodeValue::NewLine,
3616                    digest: None
3617                },
3618                4,
3619                22,
3620                module_id,
3621            )]
3622        );
3623    }
3624
3625    #[test]
3626    fn inline_comment_pipe_expression() {
3627        let test_input = r#"a(XY)
3628        |> b(%)
3629        |> c(%) // inline-comment
3630        |> d(%)"#;
3631
3632        let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3633        let (body, non_code_meta) = match expression.parse_next(&mut tokens.as_slice()).unwrap() {
3634            Expr::PipeExpression(e) => (e.inner.body, e.inner.non_code_meta),
3635            _ => panic!(),
3636        };
3637
3638        assert_eq!(non_code_meta.non_code_nodes.len(), 1);
3639        assert_eq!(
3640            non_code_meta.non_code_nodes.get(&2).unwrap()[0].value,
3641            NonCodeValue::InlineComment {
3642                value: "inline-comment".to_owned(),
3643                style: CommentStyle::Line
3644            }
3645        );
3646        assert_eq!(body.len(), 4);
3647    }
3648
3649    #[test]
3650    fn many_comments() {
3651        let test_program = r#"// this is a comment
3652  yo = { a = { b = { c = '123' } } } /* block
3653  comment */
3654
3655  key = 'c'
3656  // this is also a comment
3657  return things
3658"#;
3659
3660        let module_id = ModuleId::default();
3661        let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3662        let Program {
3663            body, non_code_meta, ..
3664        } = function_body.parse(tokens.as_slice()).unwrap().inner;
3665        assert_eq!(body[0].get_comments(), vec!["// this is a comment".to_owned()],);
3666
3667        assert_eq!(
3668            Some(&vec![
3669                Node::new(
3670                    NonCodeNode {
3671                        value: NonCodeValue::InlineComment {
3672                            value: "block\n  comment".to_owned(),
3673                            style: CommentStyle::Block
3674                        },
3675                        digest: None,
3676                    },
3677                    57,
3678                    79,
3679                    module_id,
3680                ),
3681                Node::new(
3682                    NonCodeNode {
3683                        value: NonCodeValue::NewLine,
3684                        digest: None,
3685                    },
3686                    79,
3687                    83,
3688                    module_id,
3689                )
3690            ]),
3691            non_code_meta.non_code_nodes.get(&0),
3692        );
3693
3694        assert_eq!(body[2].get_comments(), vec!["// this is also a comment".to_owned()],);
3695    }
3696
3697    #[test]
3698    fn inline_block_comments() {
3699        let test_program = r#"yo = 3 /* block
3700  comment */
3701  return 1"#;
3702
3703        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3704        let actual = program.parse(tokens.as_slice()).unwrap();
3705        assert_eq!(actual.non_code_meta.non_code_nodes.len(), 1);
3706        assert_eq!(
3707            actual.non_code_meta.non_code_nodes.get(&0).unwrap()[0].value,
3708            NonCodeValue::InlineComment {
3709                value: "block\n  comment".to_owned(),
3710                style: CommentStyle::Block
3711            }
3712        );
3713    }
3714
3715    #[test]
3716    fn test_bracketed_binary_expression() {
3717        let input = "(2 - 3)";
3718        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3719        let actual = match binary_expr_in_parens.parse(tokens.as_slice()) {
3720            Ok(x) => x,
3721            Err(e) => panic!("{e:?}"),
3722        };
3723        assert_eq!(actual.operator, BinaryOperator::Sub);
3724    }
3725
3726    #[test]
3727    fn test_arg() {
3728        for input in [
3729            "( sigmaAllow * width )",
3730            "6 / ( sigmaAllow * width )",
3731            "sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))",
3732        ] {
3733            let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3734            let _actual = match expression.parse(tokens.as_slice()) {
3735                Ok(x) => x,
3736                Err(e) => panic!("{e:?}"),
3737            };
3738        }
3739    }
3740
3741    #[test]
3742    fn test_arithmetic() {
3743        let input = "1 * (2 - 3)";
3744        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3745        // The RHS should be a binary expression.
3746        let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3747        assert_eq!(actual.operator, BinaryOperator::Mul);
3748        let BinaryPart::BinaryExpression(rhs) = actual.inner.right else {
3749            panic!("Expected RHS to be another binary expression");
3750        };
3751        assert_eq!(rhs.operator, BinaryOperator::Sub);
3752        match &rhs.right {
3753            BinaryPart::Literal(lit) => {
3754                assert!(lit.start == 9 && lit.end == 10);
3755                assert!(
3756                    lit.value
3757                        == LiteralValue::Number {
3758                            value: 3.0,
3759                            suffix: NumericSuffix::None
3760                        }
3761                        && &lit.raw == "3"
3762                        && lit.digest.is_none()
3763                );
3764            }
3765            _ => panic!(),
3766        }
3767    }
3768
3769    #[test]
3770    fn assign_brackets() {
3771        for (i, test_input) in [
3772            "thickness_squared = (1 + 1)",
3773            "thickness_squared = ( 1 + 1)",
3774            "thickness_squared = (1 + 1 )",
3775            "thickness_squared = ( 1 + 1 )",
3776        ]
3777        .into_iter()
3778        .enumerate()
3779        {
3780            let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3781            let actual = match declaration.parse(tokens.as_slice()) {
3782                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3783                Ok(a) => a,
3784            };
3785            let Expr::BinaryExpression(_expr) = &actual.declaration.inner.init else {
3786                panic!(
3787                    "Expected test {i} to be a binary expression but it wasn't, it was {:?}",
3788                    actual.declaration
3789                );
3790            };
3791            // TODO: check both sides are 1... probably not necessary but should do.
3792        }
3793    }
3794
3795    #[test]
3796    fn test_function_call() {
3797        for (i, test_input) in ["x = f(1)", "x = f( 1 )"].into_iter().enumerate() {
3798            let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3799            let _actual = match declaration.parse(tokens.as_slice()) {
3800                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3801                Ok(a) => a,
3802            };
3803        }
3804    }
3805
3806    #[test]
3807    fn test_nested_arithmetic() {
3808        let input = "1 * ((2 - 3) / 4)";
3809        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3810        // The RHS should be a binary expression.
3811        let outer = binary_expression.parse(tokens.as_slice()).unwrap();
3812        assert_eq!(outer.operator, BinaryOperator::Mul);
3813        let BinaryPart::BinaryExpression(middle) = outer.inner.right else {
3814            panic!("Expected RHS to be another binary expression");
3815        };
3816
3817        assert_eq!(middle.operator, BinaryOperator::Div);
3818        let BinaryPart::BinaryExpression(inner) = middle.inner.left else {
3819            panic!("expected nested binary expression");
3820        };
3821        assert_eq!(inner.operator, BinaryOperator::Sub);
3822    }
3823
3824    #[test]
3825    fn binary_expression_ignores_whitespace() {
3826        let tests = ["1 - 2", "1- 2", "1 -2", "1-2"];
3827        for test in tests {
3828            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3829            let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3830            assert_eq!(actual.operator, BinaryOperator::Sub);
3831            let BinaryPart::Literal(left) = actual.inner.left else {
3832                panic!("should be expression");
3833            };
3834            assert_eq!(
3835                left.value,
3836                LiteralValue::Number {
3837                    value: 1.0,
3838                    suffix: NumericSuffix::None
3839                }
3840            );
3841            let BinaryPart::Literal(right) = actual.inner.right else {
3842                panic!("should be expression");
3843            };
3844            assert_eq!(
3845                right.value,
3846                LiteralValue::Number {
3847                    value: 2.0,
3848                    suffix: NumericSuffix::None
3849                }
3850            );
3851        }
3852    }
3853
3854    #[test]
3855    fn some_pipe_expr() {
3856        let test_program = r#"x()
3857        |> y(%) /* this is
3858        a comment
3859        spanning a few lines */
3860        |> z(%)"#;
3861        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3862        let non_code_meta = match expression.parse(tokens.as_slice()).unwrap() {
3863            Expr::PipeExpression(e) => e.non_code_meta.clone(),
3864            _ => panic!(),
3865        };
3866        let n = non_code_meta.non_code_nodes.len();
3867        assert_eq!(n, 1, "expected one comment in pipe expression but found {n}");
3868        let nc = &non_code_meta.non_code_nodes.get(&1).unwrap()[0];
3869        assert!(nc.value().starts_with("this"));
3870        assert!(nc.value().ends_with("lines"));
3871    }
3872
3873    #[test]
3874    fn comments_in_pipe_expr() {
3875        for (i, test_program) in [
3876            r#"y() |> /*hi*/ z(%)"#,
3877            "1 |>/*hi*/ f(%)",
3878            r#"y() |> /*hi*/ z(%)"#,
3879            "1 /*hi*/ |> f(%)",
3880            "1
3881        // Hi
3882        |> f(%)",
3883            "1
3884        /* Hi 
3885        there
3886        */
3887        |> f(%)",
3888        ]
3889        .into_iter()
3890        .enumerate()
3891        {
3892            let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3893            let actual = expression.parse(tokens.as_slice());
3894            assert!(actual.is_ok(), "could not parse test {i}, '{test_program}'");
3895
3896            match actual.unwrap() {
3897                Expr::PipeExpression(e) => {
3898                    let n = e.non_code_meta.non_code_nodes.len();
3899                    assert_eq!(n, 1, "expected one comment in pipe expression but found {n}",);
3900                }
3901                _ => panic!(),
3902            }
3903        }
3904    }
3905
3906    #[test]
3907    fn comments() {
3908        let module_id = ModuleId::from_usize(1);
3909        for (i, (test_program, expected)) in [
3910            (
3911                "//hi",
3912                Node::new(
3913                    NonCodeNode {
3914                        value: NonCodeValue::BlockComment {
3915                            value: "hi".to_owned(),
3916                            style: CommentStyle::Line,
3917                        },
3918                        digest: None,
3919                    },
3920                    0,
3921                    4,
3922                    module_id,
3923                ),
3924            ),
3925            (
3926                "/*hello*/",
3927                Node::new(
3928                    NonCodeNode {
3929                        value: NonCodeValue::BlockComment {
3930                            value: "hello".to_owned(),
3931                            style: CommentStyle::Block,
3932                        },
3933                        digest: None,
3934                    },
3935                    0,
3936                    9,
3937                    module_id,
3938                ),
3939            ),
3940            (
3941                "/* hello */",
3942                Node::new(
3943                    NonCodeNode {
3944                        value: NonCodeValue::BlockComment {
3945                            value: "hello".to_owned(),
3946                            style: CommentStyle::Block,
3947                        },
3948                        digest: None,
3949                    },
3950                    0,
3951                    11,
3952                    module_id,
3953                ),
3954            ),
3955            (
3956                "/* \nhello */",
3957                Node::new(
3958                    NonCodeNode {
3959                        value: NonCodeValue::BlockComment {
3960                            value: "hello".to_owned(),
3961                            style: CommentStyle::Block,
3962                        },
3963                        digest: None,
3964                    },
3965                    0,
3966                    12,
3967                    module_id,
3968                ),
3969            ),
3970            (
3971                "
3972                /* hello */",
3973                Node::new(
3974                    NonCodeNode {
3975                        value: NonCodeValue::BlockComment {
3976                            value: "hello".to_owned(),
3977                            style: CommentStyle::Block,
3978                        },
3979                        digest: None,
3980                    },
3981                    0,
3982                    29,
3983                    module_id,
3984                ),
3985            ),
3986            (
3987                // Empty line with trailing whitespace
3988                "
3989  
3990                /* hello */",
3991                Node::new(
3992                    NonCodeNode {
3993                        value: NonCodeValue::NewLineBlockComment {
3994                            value: "hello".to_owned(),
3995                            style: CommentStyle::Block,
3996                        },
3997                        digest: None,
3998                    },
3999                    0,
4000                    32,
4001                    module_id,
4002                ),
4003            ),
4004            (
4005                // Empty line, no trailing whitespace
4006                "
4007
4008                /* hello */",
4009                Node::new(
4010                    NonCodeNode {
4011                        value: NonCodeValue::NewLineBlockComment {
4012                            value: "hello".to_owned(),
4013                            style: CommentStyle::Block,
4014                        },
4015                        digest: None,
4016                    },
4017                    0,
4018                    30,
4019                    module_id,
4020                ),
4021            ),
4022            (
4023                r#"/* block
4024                    comment */"#,
4025                Node::new(
4026                    NonCodeNode {
4027                        value: NonCodeValue::BlockComment {
4028                            value: "block\n                    comment".to_owned(),
4029                            style: CommentStyle::Block,
4030                        },
4031                        digest: None,
4032                    },
4033                    0,
4034                    39,
4035                    module_id,
4036                ),
4037            ),
4038        ]
4039        .into_iter()
4040        .enumerate()
4041        {
4042            let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
4043            let actual = non_code_node.parse(tokens.as_slice());
4044            assert!(actual.is_ok(), "could not parse test {i}: {actual:#?}");
4045            let actual = actual.unwrap();
4046            assert_eq!(actual, expected, "failed test {i}");
4047        }
4048    }
4049
4050    #[test]
4051    fn recognize_invalid_params() {
4052        let test_fn = "(let) => { return 1 }";
4053        let module_id = ModuleId::from_usize(2);
4054        let tokens = crate::parsing::token::lex(test_fn, module_id).unwrap();
4055        let err = function_decl.parse(tokens.as_slice()).unwrap_err().into_inner();
4056        let cause = err.cause.unwrap();
4057        // This is the token `let`
4058        assert_eq!(cause.source_range, SourceRange::new(1, 4, ModuleId::from_usize(2)));
4059        assert_eq!(cause.message, "Cannot assign a variable to a reserved keyword: let");
4060    }
4061
4062    #[test]
4063    fn comment_in_string() {
4064        let string_literal = r#""
4065           // a comment
4066             ""#;
4067        let tokens = crate::parsing::token::lex(string_literal, ModuleId::default()).unwrap();
4068        let parsed_literal = literal.parse(tokens.as_slice()).unwrap();
4069        assert_eq!(
4070            parsed_literal.value,
4071            "
4072           // a comment
4073             "
4074            .into()
4075        );
4076    }
4077
4078    #[test]
4079    fn pipes_on_pipes_minimal() {
4080        let test_program = r#"startSketchOn(XY)
4081        |> startProfile(at = [0, 0])
4082        |> line(endAbsolute = [0, -0]) // MoveRelative
4083
4084        "#;
4085        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4086        let tokens = &mut tokens.as_slice();
4087        let _actual = expression.parse_next(tokens).unwrap();
4088        assert_eq!(tokens.first().unwrap().token_type, TokenType::Whitespace);
4089    }
4090
4091    #[test]
4092    fn test_pipes_on_pipes() {
4093        let test_program = include_str!("../../e2e/executor/inputs/pipes_on_pipes.kcl");
4094        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4095        let _ = run_parser(tokens.as_slice()).unwrap();
4096    }
4097
4098    #[test]
4099    fn test_cube() {
4100        let test_program = include_str!("../../e2e/executor/inputs/cube.kcl");
4101        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4102        match program.parse(tokens.as_slice()) {
4103            Ok(_) => {}
4104            Err(e) => {
4105                panic!("{e:#?}");
4106            }
4107        }
4108    }
4109
4110    #[test]
4111    fn parse_numeric() {
4112        let test_program = "fn foo(x: number(Length)) {}";
4113        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4114        run_parser(tokens.as_slice()).unwrap();
4115
4116        let test_program = "42_mm";
4117        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4118        assert_eq!(tokens.iter().count(), 1);
4119        run_parser(tokens.as_slice()).unwrap();
4120
4121        let test_program = "42_Length";
4122        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4123        assert_eq!(tokens.iter().count(), 2);
4124        assert_eq!(run_parser(tokens.as_slice()).unwrap_errs().count(), 1);
4125    }
4126
4127    #[test]
4128    fn test_parameter_list() {
4129        let tests = [
4130            ("", vec![]),
4131            ("a", vec!["a"]),
4132            ("a, b", vec!["a", "b"]),
4133            ("a,b", vec!["a", "b"]),
4134        ];
4135        for (i, (input, expected)) in tests.into_iter().enumerate() {
4136            let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4137            let actual = parameters.parse(tokens.as_slice());
4138            assert!(actual.is_ok(), "could not parse test {i}");
4139            let actual_ids: Vec<_> = actual.unwrap().into_iter().map(|p| p.identifier.inner.name).collect();
4140            assert_eq!(actual_ids, expected);
4141        }
4142    }
4143
4144    #[test]
4145    fn test_user_function() {
4146        let input = "() {
4147            return 2
4148        }";
4149
4150        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4151        let actual = function_decl.parse(tokens.as_slice());
4152        assert!(actual.is_ok(), "could not parse test function");
4153    }
4154
4155    #[test]
4156    fn test_declaration() {
4157        let tests = ["myVar = 5", "myVar=5", "myVar =5", "myVar= 5"];
4158        for test in tests {
4159            // Run the original parser
4160            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
4161            let mut expected_body = crate::parsing::parse_tokens(tokens.clone()).unwrap().inner.body;
4162            assert_eq!(expected_body.len(), 1);
4163            let BodyItem::VariableDeclaration(expected) = expected_body.pop().unwrap() else {
4164                panic!("Expected variable declaration");
4165            };
4166
4167            // Run the second parser, check it matches the first parser.
4168            let actual = declaration.parse(tokens.as_slice()).unwrap();
4169            assert_eq!(expected, actual);
4170
4171            // Inspect its output in more detail.
4172            assert_eq!(actual.inner.kind, VariableKind::Const);
4173            assert_eq!(actual.start, 0);
4174            let decl = &actual.declaration;
4175            assert_eq!(decl.id.name, "myVar");
4176            let Expr::Literal(value) = &decl.inner.init else {
4177                panic!("value should be a literal")
4178            };
4179            assert_eq!(value.end, test.len());
4180            assert_eq!(value.raw, "5");
4181        }
4182    }
4183
4184    #[test]
4185    fn test_math_parse() {
4186        let module_id = ModuleId::default();
4187        let actual = crate::parsing::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
4188        let expr = Node::boxed(
4189            0,
4190            7,
4191            module_id,
4192            BinaryExpression {
4193                operator: BinaryOperator::Add,
4194                left: BinaryPart::Literal(Box::new(Node::new(
4195                    Literal {
4196                        value: LiteralValue::Number {
4197                            value: 5.0,
4198                            suffix: NumericSuffix::None,
4199                        },
4200                        raw: "5".to_owned(),
4201                        digest: None,
4202                    },
4203                    0,
4204                    1,
4205                    module_id,
4206                ))),
4207                right: BinaryPart::Literal(Box::new(Node::new(
4208                    Literal {
4209                        value: "a".into(),
4210                        raw: r#""a""#.to_owned(),
4211                        digest: None,
4212                    },
4213                    4,
4214                    7,
4215                    module_id,
4216                ))),
4217                digest: None,
4218            },
4219        );
4220        let expected = vec![BodyItem::ExpressionStatement(Node::new(
4221            ExpressionStatement {
4222                expression: Expr::BinaryExpression(expr),
4223                digest: None,
4224            },
4225            0,
4226            7,
4227            module_id,
4228        ))];
4229        assert_eq!(expected, actual);
4230    }
4231
4232    #[test]
4233    fn test_abstract_syntax_tree() {
4234        let code = "5 +6";
4235        let module_id = ModuleId::default();
4236        let result = crate::parsing::parse_str(code, module_id).unwrap();
4237        let expected_result = Node::new(
4238            Program {
4239                body: vec![BodyItem::ExpressionStatement(Node::new(
4240                    ExpressionStatement {
4241                        expression: Expr::BinaryExpression(Node::boxed(
4242                            0,
4243                            4,
4244                            module_id,
4245                            BinaryExpression {
4246                                left: BinaryPart::Literal(Box::new(Node::new(
4247                                    Literal {
4248                                        value: LiteralValue::Number {
4249                                            value: 5.0,
4250                                            suffix: NumericSuffix::None,
4251                                        },
4252                                        raw: "5".to_string(),
4253                                        digest: None,
4254                                    },
4255                                    0,
4256                                    1,
4257                                    module_id,
4258                                ))),
4259                                operator: BinaryOperator::Add,
4260                                right: BinaryPart::Literal(Box::new(Node::new(
4261                                    Literal {
4262                                        value: LiteralValue::Number {
4263                                            value: 6.0,
4264                                            suffix: NumericSuffix::None,
4265                                        },
4266                                        raw: "6".to_string(),
4267                                        digest: None,
4268                                    },
4269                                    3,
4270                                    4,
4271                                    module_id,
4272                                ))),
4273                                digest: None,
4274                            },
4275                        )),
4276                        digest: None,
4277                    },
4278                    0,
4279                    4,
4280                    module_id,
4281                ))],
4282                shebang: None,
4283                non_code_meta: NonCodeMeta::default(),
4284                inner_attrs: Vec::new(),
4285                digest: None,
4286            },
4287            0,
4288            4,
4289            module_id,
4290        );
4291
4292        assert_eq!(result, expected_result);
4293    }
4294
4295    #[test]
4296    fn test_empty_file() {
4297        let some_program_string = r#""#;
4298        let result = crate::parsing::top_level_parse(some_program_string);
4299        assert!(result.is_ok());
4300    }
4301
4302    #[track_caller]
4303    fn assert_no_err(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4304        let result = crate::parsing::top_level_parse(p);
4305        let result = result.0.unwrap();
4306        assert!(result.1.iter().all(|e| !e.severity.is_err()), "found: {:#?}", result.1);
4307        (result.0.unwrap(), result.1)
4308    }
4309
4310    #[track_caller]
4311    fn assert_no_fatal(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4312        let result = crate::parsing::top_level_parse(p);
4313        let result = result.0.unwrap();
4314        assert!(
4315            result.1.iter().all(|e| e.severity != Severity::Fatal),
4316            "found: {:#?}",
4317            result.1
4318        );
4319        (result.0.unwrap(), result.1)
4320    }
4321
4322    #[track_caller]
4323    fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
4324        let result = crate::parsing::top_level_parse(p);
4325        let err = result.unwrap_errs().next().unwrap();
4326        assert!(
4327            err.message.starts_with(msg),
4328            "Found `{}`, expected `{msg}`",
4329            err.message
4330        );
4331        let src_actual = [err.source_range.start(), err.source_range.end()];
4332        assert_eq!(
4333            src_expected,
4334            src_actual,
4335            "expected error would highlight `{}` but it actually highlighted `{}`",
4336            &p[src_expected[0]..src_expected[1]],
4337            &p[src_actual[0]..src_actual[1]],
4338        );
4339    }
4340
4341    #[track_caller]
4342    fn assert_err_contains(p: &str, expected: &str) {
4343        let result = crate::parsing::top_level_parse(p);
4344        let err = &result.unwrap_errs().next().unwrap().message;
4345        assert!(err.contains(expected), "actual='{err}'");
4346    }
4347
4348    #[test]
4349    fn test_parse_half_pipe_small() {
4350        assert_err_contains(
4351            "secondExtrude = startSketchOn(XY)
4352  |> startProfile(at = [0,0])
4353  |",
4354            "Unexpected token: |",
4355        );
4356    }
4357
4358    #[test]
4359    fn test_parse_member_expression_double_nested_braces() {
4360        let code = r#"prop = yo["one"][two]"#;
4361        crate::parsing::top_level_parse(code).unwrap();
4362    }
4363
4364    #[test]
4365    fn test_parse_member_expression_binary_expression_period_number_first() {
4366        let code = r#"obj = { a: 1, b: 2 }
4367height = 1 - obj.a"#;
4368        crate::parsing::top_level_parse(code).unwrap();
4369    }
4370
4371    #[test]
4372    fn test_parse_member_expression_allowed_type_in_expression() {
4373        let code = r#"obj = { thing: 1 }
4374startSketchOn(obj.sketch)"#;
4375
4376        crate::parsing::top_level_parse(code).unwrap();
4377    }
4378
4379    #[test]
4380    fn test_parse_member_expression_binary_expression_brace_number_first() {
4381        let code = r#"obj = { a: 1, b: 2 }
4382height = 1 - obj["a"]"#;
4383        crate::parsing::top_level_parse(code).unwrap();
4384    }
4385
4386    #[test]
4387    fn test_parse_member_expression_binary_expression_brace_number_second() {
4388        let code = r#"obj = { a: 1, b: 2 }
4389height = obj["a"] - 1"#;
4390        crate::parsing::top_level_parse(code).unwrap();
4391    }
4392
4393    #[test]
4394    fn test_parse_member_expression_binary_expression_in_array_number_first() {
4395        let code = r#"obj = { a: 1, b: 2 }
4396height = [1 - obj["a"], 0]"#;
4397        crate::parsing::top_level_parse(code).unwrap();
4398    }
4399
4400    #[test]
4401    fn test_parse_member_expression_binary_expression_in_array_number_second() {
4402        let code = r#"obj = { a: 1, b: 2 }
4403height = [obj["a"] - 1, 0]"#;
4404        crate::parsing::top_level_parse(code).unwrap();
4405    }
4406
4407    #[test]
4408    fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
4409        let code = r#"obj = { a: 1, b: 2 }
4410height = [obj["a"] -1, 0]"#;
4411        crate::parsing::top_level_parse(code).unwrap();
4412    }
4413
4414    #[test]
4415    fn test_anon_fn() {
4416        crate::parsing::top_level_parse("foo(num=42, closure=fn(x) { return x + 1 })").unwrap();
4417    }
4418
4419    #[test]
4420    fn test_annotation_fn() {
4421        crate::parsing::top_level_parse(
4422            r#"fn foo() {
4423  @annotated
4424  return 1
4425}"#,
4426        )
4427        .unwrap();
4428    }
4429
4430    #[test]
4431    fn test_annotation_settings() {
4432        crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
4433    }
4434
4435    #[test]
4436    fn test_anon_fn_no_fn() {
4437        assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
4438    }
4439
4440    #[test]
4441    fn test_parse_half_pipe() {
4442        let code = "height = 10
4443
4444firstExtrude = startSketchOn(XY)
4445  |> startProfile(at = [0,0])
4446  |> line(at = [0, 8])
4447  |> line(at = [20, 0])
4448  |> line(at = [0, -8])
4449  |> close()
4450  |> extrude(length=2)
4451
4452secondExtrude = startSketchOn(XY)
4453  |> startProfile(at = [0,0])
4454  |";
4455        assert_err_contains(code, "Unexpected token: |");
4456    }
4457
4458    #[test]
4459    fn test_parse_greater_bang() {
4460        assert_err(">!", "Unexpected token: >", [0, 1]);
4461    }
4462
4463    #[test]
4464    fn test_parse_unlabeled_param_not_allowed() {
4465        assert_err(
4466            "fn f(@x, @y) { return 1 }",
4467            "Only the first parameter can be declared unlabeled",
4468            [9, 11],
4469        );
4470        assert_err(
4471            "fn f(x, @y) { return 1 }",
4472            "Only the first parameter can be declared unlabeled",
4473            [8, 10],
4474        );
4475    }
4476
4477    #[test]
4478    fn test_parse_z_percent_parens() {
4479        assert_err("z%)", "Unexpected token: %", [1, 2]);
4480    }
4481
4482    #[test]
4483    fn test_parse_parens_unicode() {
4484        let result = crate::parsing::top_level_parse("(ޜ");
4485        let details = result.0.unwrap().1.pop().unwrap();
4486        // TODO: Highlight where the unmatched open parenthesis is.
4487        // https://github.com/KittyCAD/modeling-app/issues/696
4488        assert_eq!(details.message, "Unexpected end of file. The compiler expected )");
4489    }
4490
4491    #[test]
4492    fn test_parse_negative_in_array_binary_expression() {
4493        let code = r#"leg1 = 5
4494thickness = 0.56
4495
4496bracket = [-leg2 + thickness, 0]
4497"#;
4498        crate::parsing::top_level_parse(code).unwrap();
4499    }
4500
4501    #[test]
4502    fn test_parse_nested_open_brackets() {
4503        let _ = crate::parsing::top_level_parse(
4504            r#"
4505z(-[["#,
4506        )
4507        .unwrap_errs();
4508    }
4509
4510    #[test]
4511    fn test_parse_weird_new_line_function() {
4512        assert_err(
4513            r#"z
4514(--#"#,
4515            "There was an unexpected `-`. Try removing it.",
4516            [3, 4],
4517        );
4518    }
4519
4520    #[test]
4521    fn test_parse_weird_lots_of_fancy_brackets() {
4522        assert_err(
4523            r#"zz({{{{{{{{)iegAng{{{{{{{##"#,
4524            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
4525            [3, 4],
4526        );
4527    }
4528
4529    #[test]
4530    fn test_parse_weird_close_before_open() {
4531        assert_err_contains(
4532            r#"fn)n
4533e
4534["#,
4535            "expected whitespace, found ')' which is brace",
4536        );
4537    }
4538
4539    #[test]
4540    fn test_parse_weird_close_before_nada() {
4541        assert_err_contains(r#"fn)n-"#, "expected whitespace, found ')' which is brace");
4542    }
4543
4544    #[test]
4545    fn test_parse_weird_lots_of_slashes() {
4546        assert_err_contains(
4547            r#"J///////////o//+///////////P++++*++++++P///////ËŸ
4548++4"#,
4549            "Unexpected token: +",
4550        );
4551    }
4552
4553    #[test]
4554    fn test_optional_param_order() {
4555        for (i, (params, expect_ok)) in [
4556            (
4557                vec![Parameter {
4558                    identifier: Node::no_src(Identifier {
4559                        name: "a".to_owned(),
4560                        digest: None,
4561                    }),
4562                    type_: None,
4563                    default_value: Some(DefaultParamVal::none()),
4564                    labeled: true,
4565                    digest: None,
4566                }],
4567                true,
4568            ),
4569            (
4570                vec![Parameter {
4571                    identifier: Node::no_src(Identifier {
4572                        name: "a".to_owned(),
4573                        digest: None,
4574                    }),
4575                    type_: None,
4576                    default_value: None,
4577                    labeled: true,
4578                    digest: None,
4579                }],
4580                true,
4581            ),
4582            (
4583                vec![
4584                    Parameter {
4585                        identifier: Node::no_src(Identifier {
4586                            name: "a".to_owned(),
4587                            digest: None,
4588                        }),
4589                        type_: None,
4590                        default_value: None,
4591                        labeled: true,
4592                        digest: None,
4593                    },
4594                    Parameter {
4595                        identifier: Node::no_src(Identifier {
4596                            name: "b".to_owned(),
4597                            digest: None,
4598                        }),
4599                        type_: None,
4600                        default_value: Some(DefaultParamVal::none()),
4601                        labeled: true,
4602                        digest: None,
4603                    },
4604                ],
4605                true,
4606            ),
4607            (
4608                vec![
4609                    Parameter {
4610                        identifier: Node::no_src(Identifier {
4611                            name: "a".to_owned(),
4612                            digest: None,
4613                        }),
4614                        type_: None,
4615                        default_value: Some(DefaultParamVal::none()),
4616                        labeled: true,
4617                        digest: None,
4618                    },
4619                    Parameter {
4620                        identifier: Node::no_src(Identifier {
4621                            name: "b".to_owned(),
4622                            digest: None,
4623                        }),
4624                        type_: None,
4625                        default_value: None,
4626                        labeled: true,
4627                        digest: None,
4628                    },
4629                ],
4630                false,
4631            ),
4632        ]
4633        .into_iter()
4634        .enumerate()
4635        {
4636            let actual = optional_after_required(&params);
4637            assert_eq!(actual.is_ok(), expect_ok, "failed test {i}");
4638        }
4639    }
4640
4641    #[test]
4642    fn test_error_keyword_in_variable() {
4643        assert_err(
4644            r#"const let = "thing""#,
4645            "Cannot assign a variable to a reserved keyword: let",
4646            [6, 9],
4647        );
4648    }
4649
4650    #[test]
4651    fn test_error_keyword_in_fn_name() {
4652        assert_err(
4653            r#"fn let = () {}"#,
4654            "Cannot assign a variable to a reserved keyword: let",
4655            [3, 6],
4656        );
4657    }
4658
4659    #[test]
4660    fn test_error_keyword_in_fn_args() {
4661        assert_err(
4662            r#"fn thing = (let) => {
4663    return 1
4664}"#,
4665            "Cannot assign a variable to a reserved keyword: let",
4666            [12, 15],
4667        )
4668    }
4669
4670    #[test]
4671    fn bad_imports() {
4672        assert_err(
4673            r#"import cube from "../cube.kcl""#,
4674            "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4675            [17, 30],
4676        );
4677        assert_err(
4678            r#"import cube from "/cube.kcl""#,
4679            "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4680            [17, 28],
4681        );
4682        assert_err(
4683            r#"import cube from "C:\cube.kcl""#,
4684            "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
4685            [17, 30],
4686        );
4687        assert_err(
4688            r#"import cube from "cube/cube.kcl""#,
4689            "import path to a subdirectory must only refer to main.kcl.",
4690            [17, 32],
4691        );
4692        assert_err(
4693            r#"import * as foo from "dsfs""#,
4694            "as is not the 'from' keyword",
4695            [9, 11],
4696        );
4697        assert_err(
4698            r#"import a from "dsfs" as b"#,
4699            "unsupported import path format",
4700            [14, 20],
4701        );
4702        assert_err(
4703            r#"import * from "dsfs" as b"#,
4704            "unsupported import path format",
4705            [14, 20],
4706        );
4707        assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
4708        assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
4709        assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
4710        assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
4711        assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
4712        assert_err(
4713            r#"import "foo.bar.kcl""#,
4714            "import path is not a valid identifier and must be aliased.",
4715            [7, 20],
4716        );
4717        assert_err(
4718            r#"import "_foo.kcl""#,
4719            "import path is not a valid identifier and must be aliased.",
4720            [7, 17],
4721        );
4722        assert_err(
4723            r#"import "foo-bar.kcl""#,
4724            "import path is not a valid identifier and must be aliased.",
4725            [7, 20],
4726        );
4727    }
4728
4729    #[test]
4730    fn std_fn_decl() {
4731        let code = r#"/// Compute the cosine of a number (in radians).
4732///
4733/// ```
4734/// exampleSketch = startSketchOn(XZ)
4735///   |> startProfile(at = [0, 0])
4736///   |> angledLine(
4737///        angle = 30,
4738///        length = 3 / cos(toRadians(30)),
4739///      )
4740///   |> yLine(endAbsolute = 0)
4741///   |> close(%)
4742/// 
4743/// example = extrude(exampleSketch, length = 5)
4744/// ```
4745@(impl = std_rust)
4746export fn cos(num: number(rad)): number(_) {}"#;
4747        let _ast = crate::parsing::top_level_parse(code).unwrap();
4748    }
4749
4750    #[test]
4751    fn warn_import() {
4752        let some_program_string = r#"import "foo.bad""#;
4753        let (_, errs) = assert_no_err(some_program_string);
4754        assert_eq!(errs.len(), 1, "{errs:#?}");
4755    }
4756
4757    #[test]
4758    fn warn_late_settings() {
4759        let some_program_string = r#"foo = 42
4760@settings(defaultLengthUnit = mm)
4761"#;
4762        let (_, errs) = assert_no_err(some_program_string);
4763        assert_eq!(errs.len(), 1, "{errs:#?}");
4764    }
4765
4766    #[test]
4767    fn warn_unknown_suffix() {
4768        let some_program_string = r#"foo = 42_?
4769"#;
4770        let (_, errs) = assert_no_err(some_program_string);
4771        assert_eq!(errs.len(), 1, "{errs:#?}");
4772    }
4773
4774    #[test]
4775    fn fn_decl_uom_ty() {
4776        let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
4777        let (_, errs) = assert_no_fatal(some_program_string);
4778        assert!(errs.is_empty(), "Expected no errors, found: {errs:?}");
4779    }
4780
4781    #[test]
4782    fn error_underscore() {
4783        let (_, errs) = assert_no_fatal("_foo(a=_blah, b=_)");
4784        assert_eq!(errs.len(), 3, "found: {errs:#?}");
4785    }
4786
4787    #[test]
4788    fn error_double_and() {
4789        let (_, errs) = assert_no_fatal("foo = true && false");
4790        assert_eq!(errs.len(), 1, "found: {errs:#?}");
4791        assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some());
4792    }
4793
4794    #[test]
4795    fn error_type_ascription() {
4796        let (_, errs) = assert_no_fatal("a + b: number");
4797        assert!(errs.is_empty());
4798    }
4799
4800    #[test]
4801    fn zero_param_function() {
4802        let code = r#"
4803        fn firstPrimeNumber() {
4804            return 2
4805        }
4806        firstPrimeNumber()
4807        "#;
4808        let _ast = crate::parsing::top_level_parse(code).unwrap();
4809    }
4810
4811    #[test]
4812    fn array() {
4813        let program = r#"[1, 2, 3]"#;
4814        let module_id = ModuleId::default();
4815        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4816        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4817    }
4818
4819    #[test]
4820    fn array_linesep_trailing_comma() {
4821        let program = r#"[
4822            1,
4823            2,
4824            3,
4825        ]"#;
4826        let module_id = ModuleId::default();
4827        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4828        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4829    }
4830
4831    #[allow(unused)]
4832    #[test]
4833    fn array_linesep_no_trailing_comma() {
4834        let program = r#"[
4835            1,
4836            2,
4837            3
4838        ]"#;
4839        let module_id = ModuleId::default();
4840        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4841        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4842    }
4843
4844    #[test]
4845    fn array_no_trailing_comma_with_comment() {
4846        let program = r#"[
4847            1, // one
4848            2, // two
4849            3  // three
4850        ]"#;
4851        let module_id = ModuleId::default();
4852        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4853        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4854    }
4855
4856    #[test]
4857    fn array_block_comment_no_whitespace() {
4858        let program = r#"[1/* comment*/]"#;
4859        let module_id = ModuleId::default();
4860        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4861        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4862    }
4863
4864    #[test]
4865    fn object_no_trailing_comma_with_comment() {
4866        let program = r#"{
4867            x=1, // one
4868            y=2, // two
4869            z=3  // three
4870        }"#;
4871        let module_id = ModuleId::default();
4872        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4873        let _arr = object(&mut tokens.as_slice()).unwrap();
4874    }
4875
4876    #[test]
4877    fn object_block_comment_no_whitespace() {
4878        let program = r#"{x=1/* comment*/}"#;
4879        let module_id = ModuleId::default();
4880        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4881        let _arr = object(&mut tokens.as_slice()).unwrap();
4882    }
4883
4884    #[test]
4885    fn basic_if_else() {
4886        let some_program_string = "if true {
4887            3
4888        } else {
4889            4
4890        }";
4891        let module_id = ModuleId::default();
4892        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4893        let _res = if_expr(&mut tokens.as_slice()).unwrap();
4894    }
4895
4896    #[test]
4897    fn basic_else_if() {
4898        let some_program_string = "else if true {
4899            4
4900        }";
4901        let module_id = ModuleId::default();
4902        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4903        let _res = else_if(&mut tokens.as_slice()).unwrap();
4904    }
4905
4906    #[test]
4907    fn basic_if_else_if() {
4908        let some_program_string = "if true {
4909            3  
4910        } else if true {
4911            4
4912        } else {
4913            5
4914        }";
4915        let module_id = ModuleId::default();
4916        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4917        let _res = if_expr(&mut tokens.as_slice()).unwrap();
4918    }
4919
4920    #[test]
4921    fn test_keyword_ok_in_fn_args_return() {
4922        let some_program_string = r#"fn thing(param) {
4923    return true
4924}
4925
4926thing(false)
4927"#;
4928        crate::parsing::top_level_parse(some_program_string).unwrap();
4929    }
4930
4931    #[test]
4932    fn test_mul_if() {
4933        let some_program_string = r#"10 * if true { 1 } else { 0}"#;
4934        let tokens = crate::parsing::token::lex(some_program_string, ModuleId::default()).unwrap();
4935        super::binary_expression_tokens.parse(tokens.as_slice()).unwrap();
4936    }
4937
4938    #[test]
4939    fn test_error_define_var_as_function() {
4940        // TODO: https://github.com/KittyCAD/modeling-app/issues/784
4941        // Improve this error message.
4942        // It should say that the compiler is expecting a function expression on the RHS.
4943        assert_err(r#"fn thing = "thing""#, "Unexpected token: \"thing\"", [11, 18]);
4944    }
4945
4946    #[test]
4947    fn random_words_fail() {
4948        let test_program = r#"part001 = startSketchOn(-XZ)
4949    |> startProfile(at = [8.53, 11.8])
4950    asdasd asdasd
4951    |> line(at = [11.12, -14.82])
4952    |> line(at = [-13.27, -6.98])
4953    |> line(at = [-5.09, 12.33])
4954    asdasd
4955"#;
4956        let _ = crate::parsing::top_level_parse(test_program).unwrap_errs();
4957    }
4958
4959    #[test]
4960    fn test_member_expression_sketch() {
4961        let some_program_string = r#"fn cube(pos, scale) {
4962  sg = startSketchOn(XY)
4963  |> startProfile(pos)
4964    |> line(at = [0, scale])
4965    |> line(at = [scale, 0])
4966    |> line(at = [0, -scale])
4967
4968  return sg
4969}
4970
4971b1 = cube(pos=[0,0], scale=10)
4972b2 = cube(pos=[3,3], scale=4)
4973
4974pt1 = b1[0]
4975pt2 = b2[0]
4976"#;
4977        crate::parsing::top_level_parse(some_program_string).unwrap();
4978    }
4979
4980    #[test]
4981    fn test_math_with_stdlib() {
4982        let some_program_string = r#"d2r = pi() / 2
4983let other_thing = 2 * cos(3)"#;
4984        crate::parsing::top_level_parse(some_program_string).unwrap();
4985    }
4986
4987    #[test]
4988    fn test_negative_arguments() {
4989        let some_program_string = r#"fn box(p, h, l, w) {
4990 myBox = startSketchOn(XY)
4991    |> startProfile(p)
4992    |> line(at = [0, l])
4993    |> line(at = [w, 0])
4994    |> line(at = [0, -l])
4995    |> close()
4996    |> extrude(length=h)
4997
4998  return myBox
4999}
5000let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
5001"#;
5002        crate::parsing::top_level_parse(some_program_string).unwrap();
5003    }
5004
5005    #[test]
5006    fn kw_fn() {
5007        for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
5008            let module_id = ModuleId::default();
5009            let tokens = crate::parsing::token::lex(input, module_id).unwrap();
5010            super::program.parse(tokens.as_slice()).unwrap();
5011        }
5012    }
5013
5014    #[test]
5015    fn test_parse_empty_tag_brace() {
5016        let some_program_string = r#"startSketchOn(XY)
5017    |> startProfile(at = [0, 0])
5018    |> line(%, $)
5019    "#;
5020        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5021    }
5022    #[test]
5023    fn test_parse_empty_tag_whitespace() {
5024        let some_program_string = r#"startSketchOn(XY)
5025    |> startProfile(at = [0, 0])
5026    |> line(%, $ ,01)
5027    "#;
5028        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5029    }
5030
5031    #[test]
5032    fn test_parse_empty_tag_comma() {
5033        let some_program_string = r#"startSketchOn(XY)
5034    |> startProfile(at = [0, 0])
5035    |> line(%, $,)
5036    "#;
5037        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5038    }
5039    #[test]
5040    fn test_parse_tag_starting_with_digit() {
5041        let some_program_string = r#"
5042    startSketchOn(XY)
5043    |> startProfile(at = [0, 0])
5044    |> line(%, $01)"#;
5045        assert_err(
5046            some_program_string,
5047            "Tag names must not start with a number. Tag starts with `01`",
5048            [72, 74],
5049        );
5050    }
5051    #[test]
5052    fn test_parse_tag_including_digit() {
5053        let some_program_string = r#"
5054    startSketchOn(XY)
5055    |> startProfile(at = [0, 0])
5056    |> line(%, tag = $var01)"#;
5057        assert_no_err(some_program_string);
5058    }
5059
5060    #[test]
5061    fn test_parse_param_bool_default() {
5062        let some_program_string = r#"fn patternTransform(
5063  use_original?: boolean = false,
5064) {}"#;
5065        assert_no_err(some_program_string);
5066    }
5067
5068    #[test]
5069    fn parse_function_types() {
5070        let code = r#"foo = x: fn
5071foo = x: fn(number)
5072fn foo(x: fn(): number): fn { return 0 }
5073fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
5074type fn
5075type foo = fn
5076type foo = fn(a: string, b: { f: fn(): any })
5077type foo = fn(a: string, b: {})
5078type foo = fn(a: string, b: { })
5079type foo = fn([fn])
5080type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
5081    "#;
5082        assert_no_err(code);
5083    }
5084    #[test]
5085    fn test_parse_tag_starting_with_bang() {
5086        let some_program_string = r#"startSketchOn(XY)
5087    |> startProfile(at = [0, 0])
5088    |> line(%, $!var,01)
5089    "#;
5090        assert_err(some_program_string, "Tag names must not start with a bang", [67, 68]);
5091    }
5092    #[test]
5093    fn test_parse_tag_starting_with_dollar() {
5094        let some_program_string = r#"startSketchOn(XY)
5095    |> startProfile(at = [0, 0])
5096    |> line(%, $$,01)
5097    "#;
5098        assert_err(some_program_string, "Tag names must not start with a dollar", [67, 68]);
5099    }
5100    #[test]
5101    fn test_parse_tag_starting_with_fn() {
5102        let some_program_string = r#"startSketchOn(XY)
5103    |> startProfile(at = [0, 0])
5104    |> line(%, $fn,01)
5105    "#;
5106        assert_err(some_program_string, "Tag names must not start with a keyword", [67, 69]);
5107    }
5108    #[test]
5109    fn test_parse_tag_starting_with_a_comment() {
5110        let some_program_string = r#"startSketchOn(XY)
5111    |> startProfile(at = [0, 0])
5112    |> line(%, $//
5113    ,01)
5114    "#;
5115        assert_err(
5116            some_program_string,
5117            "Tag names must not start with a lineComment",
5118            [67, 69],
5119        );
5120    }
5121
5122    #[test]
5123    fn test_parse_tag_with_reserved_in_middle_works() {
5124        let some_program_string = r#"
5125    startSketchOn(XY)
5126    |> startProfile(at = [0, 0])
5127    |> line(end = [5, 5], tag = $sketching)
5128    "#;
5129        assert_no_err(some_program_string);
5130    }
5131
5132    #[test]
5133    fn test_parse_fn_call_then_field() {
5134        let some_program_string = "myFunction().field";
5135        let module_id = ModuleId::default();
5136        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
5137        let actual = expression.parse(tokens.as_slice()).unwrap();
5138        let Expr::MemberExpression(_expr) = actual else {
5139            panic!("expected member expression")
5140        };
5141    }
5142
5143    #[test]
5144    fn test_parse_array_missing_closing_bracket() {
5145        let some_program_string = r#"
5146sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45, 119.09)"#;
5147        assert_err(
5148            some_program_string,
5149            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5150            [52, 60],
5151        );
5152    }
5153    #[test]
5154    fn test_parse_array_missing_comma() {
5155        let some_program_string = r#"
5156sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 119.09])"#;
5157        assert_err(
5158            some_program_string,
5159            "Unexpected character encountered. You might be missing a comma in between elements.",
5160            [53, 66],
5161        );
5162    }
5163    #[test]
5164    fn test_parse_array_reserved_word_early_exit() {
5165        // since there is an early exit if encountering a reserved word, the error should be about
5166        // that and not the missing comma
5167        let some_program_string = r#"
5168sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 $struct])"#;
5169        assert_err(
5170            some_program_string,
5171            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5172            [52, 53],
5173        );
5174    }
5175
5176    #[test]
5177    fn test_parse_array_random_brace() {
5178        let some_program_string = r#"
5179sketch001 = startSketchOn(XZ) |> startProfile(at = [}])"#;
5180        assert_err(
5181            some_program_string,
5182            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5183            [52, 53],
5184        );
5185    }
5186
5187    #[test]
5188    fn test_parse_object_missing_closing_brace() {
5189        let some_program_string = r#"{
5190            foo = bar,"#;
5191
5192        assert_err(
5193            some_program_string,
5194            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5195            [0, 23],
5196        );
5197    }
5198    #[test]
5199    fn test_parse_object_reserved_word_early_exit() {
5200        // since there is an early exit if encountering a reserved word, the error should be about
5201        // that and not the missing comma
5202        let some_program_string = r#"{bar = foo struct = man}"#;
5203
5204        assert_err(
5205            some_program_string,
5206            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5207            [0, 1],
5208        );
5209    }
5210    #[test]
5211    fn test_parse_object_missing_comma() {
5212        let some_program_string = r#"{
5213            foo = bar,
5214            bar = foo
5215            bat = man
5216        }"#;
5217
5218        assert_err(
5219            some_program_string,
5220            "Unexpected character encountered. You might be missing a comma in between properties.",
5221            [37, 78],
5222        );
5223    }
5224
5225    #[test]
5226    fn test_parse_object_missing_comma_one_line() {
5227        let some_program_string = r#"{bar = foo bat = man}"#;
5228
5229        assert_err(
5230            some_program_string,
5231            "Unexpected character encountered. You might be missing a comma in between properties.",
5232            [1, 21],
5233        );
5234    }
5235
5236    #[test]
5237    fn test_parse_object_random_bracket() {
5238        let some_program_string = r#"{]}"#;
5239
5240        assert_err(
5241            some_program_string,
5242            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5243            [0, 1],
5244        );
5245    }
5246
5247    #[test]
5248    fn test_parse_object_shorthand_missing_comma() {
5249        let some_program_string = r#"
5250bar = 1
5251        {
5252            foo = bar,
5253            bar
5254            bat = man
5255        }"#;
5256
5257        assert_err(
5258            some_program_string,
5259            "Unexpected character encountered. You might be missing a comma in between properties.",
5260            [54, 89],
5261        );
5262    }
5263
5264    #[test]
5265    fn test_unary_not_on_keyword_bool() {
5266        let some_program_string = r#"!true"#;
5267        let module_id = ModuleId::default();
5268        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
5269        let actual = match unary_expression.parse(tokens.as_slice()) {
5270            // Use tokens.as_slice() for parsing
5271            Ok(x) => x,
5272            Err(e) => panic!("{e:?}"),
5273        };
5274        assert_eq!(actual.operator, UnaryOperator::Not);
5275        crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
5276    }
5277    #[test]
5278    fn test_sensible_error_when_missing_comma_between_fn_args() {
5279        let program_source = "startSketchOn(XY)
5280|> arc(
5281    endAbsolute = [0, 50]
5282    interiorAbsolute = [-50, 0]
5283)";
5284        let expected_src_start = program_source.find("]").unwrap();
5285        let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5286        ParseContext::init();
5287        let err = program
5288            .parse(tokens.as_slice())
5289            .expect_err("Program succeeded, but it should have failed");
5290        let cause = err
5291            .inner()
5292            .cause
5293            .as_ref()
5294            .expect("Found an error, but there was no cause. Add a cause.");
5295        assert_eq!(cause.message, "Missing comma between arguments, try adding a comma in",);
5296        assert_eq!(cause.source_range.start() - 1, expected_src_start);
5297    }
5298
5299    #[test]
5300    fn test_sensible_error_when_missing_rhs_of_kw_arg() {
5301        for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
5302            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5303            let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
5304            let cause = err.inner().cause.as_ref().unwrap();
5305            assert_eq!(
5306                cause.message, "This argument has a label, but no value. Put some value after the equals sign",
5307                "failed test {i}: {program}"
5308            );
5309            assert_eq!(
5310                cause.source_range.start(),
5311                program.find("y").unwrap(),
5312                "failed test {i}: {program}"
5313            );
5314        }
5315    }
5316
5317    #[test]
5318    fn test_sensible_error_when_unexpected_token_in_fn_call() {
5319        let program_source = "1
5320|> extrude(
5321  length=depth,
5322})";
5323        let expected_src_start = program_source.find("}").expect("Program should have an extraneous }");
5324        let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5325        ParseContext::init();
5326        let err = program.parse(tokens.as_slice()).unwrap_err();
5327        let cause = err
5328            .inner()
5329            .cause
5330            .as_ref()
5331            .expect("Found an error, but there was no cause. Add a cause.");
5332        assert_eq!(cause.message, "There was an unexpected `}`. Try removing it.",);
5333        assert_eq!(cause.source_range.start(), expected_src_start);
5334    }
5335
5336    #[test]
5337    fn test_sensible_error_when_using_keyword_as_arg_label() {
5338        for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
5339            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5340            let err = match fn_call_kw.parse(tokens.as_slice()) {
5341                Err(e) => e,
5342                Ok(_ast) => {
5343                    panic!("Expected this to error but it didn't");
5344                }
5345            };
5346            let cause = err.inner().cause.as_ref().unwrap();
5347            assert_eq!(
5348                cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
5349                "failed test {i}: {program}"
5350            );
5351            assert_eq!(
5352                cause.source_range.start(),
5353                program.find("fn").unwrap(),
5354                "failed test {i}: {program}"
5355            );
5356        }
5357    }
5358
5359    #[test]
5360    fn test_sensible_error_when_missing_rhs_of_obj_property() {
5361        for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {
5362            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5363            let err = object.parse(tokens.as_slice()).unwrap_err();
5364            let cause = err.inner().cause.as_ref().unwrap();
5365            assert_eq!(
5366                cause.message, "This property has a label, but no value. Put some value after the equals sign",
5367                "failed test {i}: {program}"
5368            );
5369            assert_eq!(
5370                cause.source_range.start(),
5371                program.rfind('=').unwrap(),
5372                "failed test {i}: {program}"
5373            );
5374        }
5375    }
5376
5377    #[test]
5378    fn test_sensible_error_duplicated_args() {
5379        let program = r#"f(arg = 1, normal = 44, arg = 2)"#;
5380        let (_, mut errs) = assert_no_fatal(program);
5381        assert_eq!(errs.len(), 1);
5382        let err = errs.pop().unwrap();
5383        assert_eq!(
5384            err.message,
5385            "You've used the parameter labelled 'arg' 2 times in a single function call. You can only set each parameter once! Remove all but one use.",
5386        );
5387    }
5388
5389    #[test]
5390    fn test_sensible_error_when_missing_else() {
5391        let program_source = "x = if (true) { 2 }";
5392        let expected_src_start = program_source.find("if").unwrap();
5393        let cause = must_fail_compilation(program_source);
5394        assert!(!cause.was_fatal);
5395        assert_eq!(cause.err.message, MISSING_ELSE);
5396        assert_eq!(cause.err.source_range.start(), expected_src_start);
5397    }
5398
5399    struct MustFail {
5400        err: CompilationError,
5401        was_fatal: bool,
5402    }
5403
5404    fn must_fail_compilation(program_source: &str) -> MustFail {
5405        let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5406        ParseContext::init();
5407        match program.parse(tokens.as_slice()) {
5408            // Fatal parse error
5409            Err(e) => {
5410                let err = e
5411                    .into_inner()
5412                    .cause
5413                    .expect("found a parse error, but no cause. Add a cause.");
5414                MustFail { err, was_fatal: true }
5415            } // Nonfatal parse error
5416            Ok(_) => {
5417                let mut e = None;
5418                CTXT.with_borrow(|v| e = v.clone().map(|ctxt| ctxt.errors));
5419                let err = e
5420                    .unwrap()
5421                    .first()
5422                    .expect("Program succeeded, but it should have failed")
5423                    .to_owned();
5424                MustFail { err, was_fatal: false }
5425            }
5426        }
5427    }
5428
5429    #[test]
5430    fn test_sensible_error_when_missing_else_brace() {
5431        let program_source = "x = if (true) { 2 } else ";
5432        let expected_src_start = program_source.find("else").unwrap();
5433        let cause = must_fail_compilation(program_source);
5434        assert!(!cause.was_fatal);
5435        assert_eq!(cause.err.message, ELSE_STRUCTURE);
5436        assert_eq!(cause.err.source_range.start(), expected_src_start);
5437    }
5438
5439    #[test]
5440    fn test_sensible_error_when_does_not_end_in_expr() {
5441        let program_source = "x = if (true) { 2 } else {y = 3}";
5442        let expected_src_start = program_source.find("else").unwrap();
5443        let cause = must_fail_compilation(program_source);
5444        assert!(!cause.was_fatal);
5445        assert_eq!(cause.err.message, ELSE_MUST_END_IN_EXPR);
5446        assert_eq!(cause.err.source_range.start(), expected_src_start);
5447    }
5448}
5449
5450#[cfg(test)]
5451mod snapshot_math_tests {
5452    use super::*;
5453
5454    // This macro generates a test function with the given function name.
5455    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
5456    // its parsed AST to a snapshot (kept in this repo in a file under snapshots/ dir)
5457    macro_rules! snapshot_test {
5458        ($func_name:ident, $test_kcl_program:expr_2021) => {
5459            #[test]
5460            fn $func_name() {
5461                let module_id = crate::ModuleId::default();
5462                let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5463                ParseContext::init();
5464
5465                let actual = match binary_expression.parse(tokens.as_slice()) {
5466                    Ok(x) => x,
5467                    Err(_e) => panic!("could not parse test"),
5468                };
5469                insta::assert_json_snapshot!(actual);
5470                let _ = ParseContext::take();
5471            }
5472        };
5473    }
5474
5475    snapshot_test!(a, "1 + 2");
5476    snapshot_test!(b, "1+2");
5477    snapshot_test!(c, "1 -2");
5478    snapshot_test!(d, "1 + 2 * 3");
5479    snapshot_test!(e, "1 * ( 2 + 3 )");
5480    snapshot_test!(f, "1 * ( 2 + 3 ) / 4");
5481    snapshot_test!(g, "1 + ( 2 + 3 ) / 4");
5482    snapshot_test!(h, "1 * (( 2 + 3 ) / 4 + 5 )");
5483    snapshot_test!(i, "1 * ((( 2 + 3 )))");
5484    snapshot_test!(j, "distance * p * FOS * 6 / (sigmaAllow * width)");
5485    snapshot_test!(k, "2 + (((3)))");
5486}
5487
5488#[cfg(test)]
5489mod snapshot_tests {
5490    use super::*;
5491
5492    // This macro generates a test function with the given function name.
5493    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
5494    // its parsed AST to a snapshot (kept in this repo in a file under snapshots/ dir)
5495    macro_rules! snapshot_test {
5496        ($func_name:ident, $test_kcl_program:expr_2021) => {
5497            #[test]
5498            fn $func_name() {
5499                let module_id = crate::ModuleId::default();
5500                println!("{}", $test_kcl_program);
5501                let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5502                print_tokens(tokens.as_slice());
5503                ParseContext::init();
5504                let actual = match program.parse(tokens.as_slice()) {
5505                    Ok(x) => x,
5506                    Err(e) => panic!("could not parse test: {e:?}"),
5507                };
5508                let mut settings = insta::Settings::clone_current();
5509                settings.set_sort_maps(true);
5510                settings.bind(|| {
5511                    insta::assert_json_snapshot!(actual);
5512                });
5513                let _ = ParseContext::take();
5514            }
5515        };
5516    }
5517
5518    snapshot_test!(
5519        a,
5520        r#"boxSketch = startSketchOn(XY)
5521    |> startProfileAt(at = [0, 0])
5522    |> line(at = [0, 10])
5523    |> tangentialArc(end = [-5, 5])
5524    |> line(at = [5, -15])
5525    |> extrude(length=10)
5526"#
5527    );
5528    snapshot_test!(b, "myVar = min(x=5 , y=-legLen(5, z=4))"); // Space before comma
5529
5530    snapshot_test!(c, "myVar = min(x=-legLen(a=5, b=4), y=5)");
5531    snapshot_test!(d, "myVar = 5 + 6 |> myFunc(45)");
5532    snapshot_test!(e, "x = 1 * (3 - 4)");
5533    snapshot_test!(f, r#"x = 1 // this is an inline comment"#);
5534    snapshot_test!(
5535        g,
5536        r#"fn x() {
5537        return sg
5538        return sg
5539      }"#
5540    );
5541    snapshot_test!(d2, r#"x = -leg2 + thickness"#);
5542    snapshot_test!(
5543        h,
5544        r#"obj = { a = 1, b = 2 }
5545    height = 1 - obj.a"#
5546    );
5547    snapshot_test!(
5548        i,
5549        r#"obj = { a = 1, b = 2 }
5550     height = 1 - obj["a"]"#
5551    );
5552    snapshot_test!(
5553        j,
5554        r#"obj = { a = 1, b = 2 }
5555    height = obj["a"] - 1"#
5556    );
5557    snapshot_test!(
5558        k,
5559        r#"obj = { a = 1, b = 2 }
5560    height = [1 - obj["a"], 0]"#
5561    );
5562    snapshot_test!(
5563        l,
5564        r#"obj = { a = 1, b = 2 }
5565    height = [obj["a"] - 1, 0]"#
5566    );
5567    snapshot_test!(
5568        m,
5569        r#"obj = {a = 1, b = 2 }
5570    height = [obj["a"] -1, 0]"#
5571    );
5572    snapshot_test!(n, "height = 1 - obj.a");
5573    snapshot_test!(o, "six = 1 + 2 + 3");
5574    snapshot_test!(p, "five = 3 * 1 + 2");
5575    snapshot_test!(q, r#"height = [ obj["a"], 0 ]"#);
5576    snapshot_test!(
5577        r,
5578        r#"obj = { a = 1, b = 2 }
5579    height = obj["a"]"#
5580    );
5581    snapshot_test!(s, r#"prop = yo["one"][two]"#);
5582    snapshot_test!(t, r#"pt1 = b1[x]"#);
5583    snapshot_test!(u, "prop = yo.one.two.three.four");
5584    snapshot_test!(v, r#"pt1 = b1[0]"#);
5585    snapshot_test!(w, r#"pt1 = b1['zero']"#);
5586    snapshot_test!(x, r#"pt1 = b1.zero"#);
5587    snapshot_test!(y, r#"sg = startSketchOn(XY) |> startProfile(pos)"#);
5588    snapshot_test!(
5589        z,
5590        "sg = startSketchOn(XY)
5591    |> startProfile(pos) |> line([0, -scale])"
5592    );
5593    snapshot_test!(aa, r#"sg = -scale"#);
5594    snapshot_test!(ab, "line(endAbsolute = [0, -1])");
5595    snapshot_test!(
5596        ad,
5597        r#"
5598    fn firstPrimeNumber() {
5599        return 2
5600    }
5601    firstPrimeNumber()"#
5602    );
5603    snapshot_test!(
5604        ae,
5605        r#"fn thing(param) {
5606        return true
5607    }
5608    thing(false)"#
5609    );
5610    snapshot_test!(
5611        af,
5612        r#"mySketch = startSketchOn(XY)
5613        |> startProfile(at = [0,0])
5614        |> line(endAbsolute = [0, 1], tag = $myPath)
5615        |> line(endAbsolute = [1, 1])
5616        |> line(endAbsolute = [1, 0], tag = $rightPath)
5617        |> close()"#
5618    );
5619    snapshot_test!(
5620        ag,
5621        "mySketch = startSketchOn(XY) |> startProfile(at = [0,0]) |> line(endAbsolute = [1, 1]) |> close()"
5622    );
5623    snapshot_test!(ah, "myBox = startSketchOn(XY) |> startProfile(at = p)");
5624    snapshot_test!(ai, r#"myBox = f(1) |> g(2)"#);
5625    snapshot_test!(
5626        aj,
5627        r#"myBox = startSketchOn(XY) |> startProfile(at = p) |> line(end = [0, l])"#
5628    );
5629    snapshot_test!(ak, "line(endAbsolute = [0, 1])");
5630    snapshot_test!(ap, "mySketch = startSketchOn(XY) |> startProfile(at = [0,0])");
5631    snapshot_test!(aq, "log(number = 5, msg = \"hello\", id=aIdentifier)");
5632    snapshot_test!(ar, r#"5 + "a""#);
5633    snapshot_test!(at, "line([0, l])");
5634    snapshot_test!(au, include_str!("../../e2e/executor/inputs/cylinder.kcl"));
5635    snapshot_test!(av, "fn f(angle?) { return default(maybe=angle, otherwise=360) }");
5636    snapshot_test!(
5637        aw,
5638        "numbers = [
5639            1,
5640            // A,
5641            // B,
5642            3,
5643        ]"
5644    );
5645    snapshot_test!(
5646        ax,
5647        "numbers = [
5648            1,
5649            2,
5650            // A,
5651            // B,
5652        ]"
5653    );
5654    snapshot_test!(
5655        ay,
5656        "let props = {
5657            a: 1,
5658            // b: 2,
5659            c: 3,
5660        }"
5661    );
5662    snapshot_test!(
5663        az,
5664        "props = {
5665            a: 1,
5666            // b: 2,
5667            c: 3
5668        }"
5669    );
5670    snapshot_test!(
5671        bb,
5672        r#"
5673my14 = 4 ^ 2 - 3 ^ 2 * 2
5674"#
5675    );
5676    snapshot_test!(
5677        bc,
5678        r#"x = if true {
5679            3
5680        } else {
5681            4
5682        }"#
5683    );
5684    snapshot_test!(
5685        bd,
5686        r#"x = if true {
5687            3
5688        } else if func(radius) {
5689            4
5690        } else {
5691            5
5692        }"#
5693    );
5694    snapshot_test!(be, "x = 3 == 3");
5695    snapshot_test!(bf, "x = 3 != 3");
5696    snapshot_test!(bg, r#"x = 4"#);
5697    snapshot_test!(bh, "obj = {center = [10, 10], radius =5}");
5698    snapshot_test!(
5699        bi,
5700        r#"x = 3
5701        obj = { x, y = 4}"#
5702    );
5703    snapshot_test!(bj, "true");
5704    snapshot_test!(bk, "truee");
5705    snapshot_test!(bl, "x = !true");
5706    snapshot_test!(bm, "x = true & false");
5707    snapshot_test!(bn, "x = true | false");
5708    snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
5709    snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
5710    snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
5711    snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
5712    snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
5713    snapshot_test!(
5714        kw_function_decl_with_default_and_type,
5715        r#"fn foo(x?: number = 2) { return 1 }"#
5716    );
5717    snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#);
5718    snapshot_test!(
5719        kw_function_call_multiline,
5720        r#"val = f(
5721             arg = x,
5722             foo = x,
5723             bar = x,
5724           )"#
5725    );
5726    snapshot_test!(
5727        kw_function_call_multiline_with_comments,
5728        r#"val = f(
5729             arg = x,
5730             // foo = x,
5731             bar = x,
5732           )"#
5733    );
5734    snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#);
5735    snapshot_test!(
5736        array_ranges,
5737        r#"incl = [1..10]
5738        excl = [0..<10]"#
5739    );
5740    snapshot_test!(space_between_unary_and_operand, r#"x = - 1"#);
5741    snapshot_test!(
5742        space_between_expr_and_array,
5743        r#"outer_points = [1, 2, 3]
5744i = 0
5745outer_points[ if i + 1 < 5 { i + 1 } else { 0 } ]"#
5746    );
5747}
5748
5749#[allow(unused)]
5750#[cfg(test)]
5751pub(crate) fn print_tokens(tokens: TokenSlice) {
5752    for (i, tok) in tokens.iter().enumerate() {
5753        println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
5754    }
5755}