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