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