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