kcl_lib/parsing/
parser.rs

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