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, TypedPath, 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<Node<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(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, bool_value))
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    let end_inclusive = alt((end_inclusive_range.map(|_| true), end_exclusive_range.map(|_| false))).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,
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
1732pub(super) fn 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 != '.' && c != '/' && c != '\\')
1866        {
1867            return Err(ErrMode::Cut(
1868                CompilationError::fatal(
1869                    path_range,
1870                    "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
1871                )
1872                .into(),
1873            ));
1874        }
1875
1876        if path_string.starts_with("..") {
1877            return Err(ErrMode::Cut(
1878                CompilationError::fatal(
1879                    path_range,
1880                    "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1881                )
1882                .into(),
1883            ));
1884        }
1885
1886        // Make sure they are not using an absolute path.
1887        if path_string.starts_with('/') || path_string.starts_with('\\') {
1888            return Err(ErrMode::Cut(
1889                CompilationError::fatal(
1890                    path_range,
1891                    "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1892                )
1893                .into(),
1894            ));
1895        }
1896
1897        if (path_string.contains('/') || path_string.contains('\\'))
1898            && !(path_string.ends_with("/main.kcl") || path_string.ends_with("\\main.kcl"))
1899        {
1900            return Err(ErrMode::Cut(
1901                CompilationError::fatal(path_range, "import path to a subdirectory must only refer to main.kcl.")
1902                    .into(),
1903            ));
1904        }
1905
1906        ImportPath::Kcl {
1907            filename: TypedPath::new(&path_string),
1908        }
1909    } else if path_string.starts_with("std::") {
1910        ParseContext::warn(CompilationError::err(
1911            path_range,
1912            "explicit imports from the standard library are experimental, likely to be buggy, and likely to change.",
1913        ));
1914
1915        let segments: Vec<String> = path_string.split("::").map(str::to_owned).collect();
1916
1917        for s in &segments {
1918            if s.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') || s.starts_with('_') {
1919                return Err(ErrMode::Cut(
1920                    CompilationError::fatal(path_range, "invalid path in import statement.").into(),
1921                ));
1922            }
1923        }
1924
1925        // For now we only support importing from singly-nested modules inside std.
1926        if segments.len() != 2 {
1927            return Err(ErrMode::Cut(
1928                CompilationError::fatal(
1929                    path_range,
1930                    format!("Invalid import path for import from std: {}.", path_string),
1931                )
1932                .into(),
1933            ));
1934        }
1935
1936        ImportPath::Std { path: segments }
1937    } else if path_string.contains('.') {
1938        let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
1939        if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
1940            ParseContext::warn(CompilationError::err(
1941                path_range,
1942                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(", ")),
1943            ))
1944        }
1945        ImportPath::Foreign {
1946            path: TypedPath::new(&path_string),
1947        }
1948    } else {
1949        return Err(ErrMode::Cut(
1950            CompilationError::fatal(
1951                path_range,
1952                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(", ")),
1953            )
1954            .into(),
1955        ));
1956    };
1957
1958    Ok(path)
1959}
1960
1961fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
1962    let name = nameable_identifier
1963        .context(expected("an identifier to import"))
1964        .parse_next(i)?;
1965    let start = name.start;
1966    let module_id = name.module_id;
1967    let alias = opt(preceded(
1968        (whitespace, import_as_keyword, whitespace),
1969        identifier.context(expected("an identifier to alias the import")),
1970    ))
1971    .parse_next(i)?;
1972    let end = if let Some(ref alias) = alias {
1973        alias.end
1974    } else {
1975        name.end
1976    };
1977    Ok(Node::new(
1978        ImportItem {
1979            name,
1980            alias,
1981            digest: None,
1982        },
1983        start,
1984        end,
1985        module_id,
1986    ))
1987}
1988
1989fn import_as_keyword(i: &mut TokenSlice) -> PResult<Token> {
1990    any.try_map(|token: Token| {
1991        if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "as" {
1992            Ok(token)
1993        } else {
1994            Err(CompilationError::fatal(
1995                token.as_source_range(),
1996                format!("{} is not the 'as' keyword", token.value.as_str()),
1997            ))
1998        }
1999    })
2000    .context(expected("the 'as' keyword"))
2001    .parse_next(i)
2002}
2003
2004/// Parse a return statement of a user-defined function, e.g. `return x`.
2005fn return_stmt(i: &mut TokenSlice) -> PResult<Node<ReturnStatement>> {
2006    let ret = any
2007        .try_map(|token: Token| {
2008            if matches!(token.token_type, TokenType::Keyword) && token.value == "return" {
2009                Ok(token)
2010            } else {
2011                Err(CompilationError::fatal(
2012                    token.as_source_range(),
2013                    format!("{} is not a return keyword", token.value.as_str()),
2014                ))
2015            }
2016        })
2017        .context(expected(
2018            "the 'return' keyword, which ends your function (and becomes this function's value when it's called)",
2019        ))
2020        .parse_next(i)?;
2021    require_whitespace(i)?;
2022    let argument = expression(i)?;
2023    Ok(Node::new_node(
2024        ret.start,
2025        argument.end(),
2026        ret.module_id,
2027        ReturnStatement { argument, digest: None },
2028    ))
2029}
2030
2031/// Parse a KCL expression.
2032fn expression(i: &mut TokenSlice) -> PResult<Expr> {
2033    alt((
2034        pipe_expression.map(Box::new).map(Expr::PipeExpression),
2035        expression_but_not_pipe,
2036    ))
2037    .context(expected("a KCL value"))
2038    .parse_next(i)
2039}
2040
2041fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
2042    let mut expr = alt((
2043        binary_expression.map(Box::new).map(Expr::BinaryExpression),
2044        unary_expression.map(Box::new).map(Expr::UnaryExpression),
2045        expr_allowed_in_pipe_expr,
2046    ))
2047    .context(expected("a KCL value"))
2048    .parse_next(i)?;
2049
2050    let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2051    if let Some((_, _, ty)) = ty {
2052        expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2053    }
2054    let label = opt(label).parse_next(i)?;
2055    match label {
2056        Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
2057        None => Ok(expr),
2058    }
2059}
2060
2061fn label(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2062    let result = preceded(
2063        (whitespace, import_as_keyword, whitespace),
2064        identifier.context(expected("an identifier")),
2065    )
2066    .parse_next(i)?;
2067
2068    ParseContext::warn(CompilationError::err(
2069        SourceRange::new(result.start, result.end, result.module_id),
2070        "Using `as` for tagging expressions is experimental, likely to be buggy, and likely to change",
2071    ));
2072
2073    Ok(result)
2074}
2075
2076fn unnecessarily_bracketed(i: &mut TokenSlice) -> PResult<Expr> {
2077    delimited(
2078        terminated(open_paren, opt(whitespace)),
2079        expression,
2080        preceded(opt(whitespace), close_paren),
2081    )
2082    .parse_next(i)
2083}
2084
2085fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
2086    alt((
2087        member_expression.map(Box::new).map(Expr::MemberExpression),
2088        bool_value.map(Box::new).map(Expr::Literal),
2089        tag.map(Box::new).map(Expr::TagDeclarator),
2090        literal.map(Expr::Literal),
2091        fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2092        name.map(Box::new).map(Expr::Name),
2093        array,
2094        object.map(Box::new).map(Expr::ObjectExpression),
2095        pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
2096        function_expr,
2097        if_expr.map(Expr::IfExpression),
2098        unnecessarily_bracketed,
2099    ))
2100    .context(expected("a KCL expression (but not a pipe expression)"))
2101    .parse_next(i)
2102}
2103
2104fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
2105    let mut expr = alt((
2106        unary_expression.map(Box::new).map(Expr::UnaryExpression),
2107        bool_value.map(Box::new).map(Expr::Literal),
2108        member_expression.map(Box::new).map(Expr::MemberExpression),
2109        literal.map(Expr::Literal),
2110        fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2111        name.map(Box::new).map(Expr::Name),
2112        binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
2113        unnecessarily_bracketed,
2114    ))
2115    .context(expected(
2116        "a KCL value which can be used as an argument/operand to an operator",
2117    ))
2118    .parse_next(i)?;
2119
2120    let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2121    if let Some((_, _, ty)) = ty {
2122        expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2123    }
2124
2125    Ok(expr)
2126}
2127
2128/// Parse an item visibility specifier, e.g. export.
2129fn item_visibility(i: &mut TokenSlice) -> PResult<(ItemVisibility, Token)> {
2130    any.verify_map(|token: Token| {
2131        if token.token_type == TokenType::Keyword && token.value == "export" {
2132            Some((ItemVisibility::Export, token))
2133        } else {
2134            None
2135        }
2136    })
2137    .context(expected("item visibility, e.g. 'export'"))
2138    .parse_next(i)
2139}
2140
2141fn declaration_keyword(i: &mut TokenSlice) -> PResult<(VariableKind, Token)> {
2142    let res = any
2143        .verify_map(|token: Token| token.declaration_keyword().map(|kw| (kw, token)))
2144        .parse_next(i)?;
2145    Ok(res)
2146}
2147
2148/// Parse a variable/constant declaration.
2149fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
2150    let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2151        .parse_next(i)?
2152        .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2153    let decl_token = opt(declaration_keyword).parse_next(i)?;
2154    if decl_token.is_some() {
2155        // If there was a declaration keyword like `fn`, then it must be followed by some spaces.
2156        // `fnx = ...` is not valid!
2157        require_whitespace(i)?;
2158    }
2159
2160    let id = binding_name
2161        .context(expected(
2162            "an identifier, which becomes name you're binding the value to",
2163        ))
2164        .parse_next(i)?;
2165    let (kind, mut start, dec_end) = if let Some((kind, token)) = &decl_token {
2166        (*kind, token.start, token.end)
2167    } else {
2168        (VariableKind::Const, id.start, id.end)
2169    };
2170    if let Some(token) = visibility_token {
2171        start = token.start;
2172    }
2173
2174    ignore_whitespace(i);
2175
2176    let val =
2177        if kind == VariableKind::Fn {
2178            let eq = opt(equals).parse_next(i)?;
2179            ignore_whitespace(i);
2180
2181            let val = function_decl
2182                .map(Box::new)
2183                .map(Expr::FunctionExpression)
2184                .context(expected("a KCL function expression, like () { return 1 }"))
2185                .parse_next(i);
2186
2187            if let Some(t) = eq {
2188                ParseContext::warn(
2189                    CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
2190                        .with_suggestion("Remove `=`", "", None, Tag::Unnecessary),
2191                );
2192            }
2193
2194            val
2195        } else {
2196            equals(i)?;
2197            ignore_whitespace(i);
2198
2199            let val = expression
2200                .try_map(|val| {
2201                    // Function bodies can be used if and only if declaring a function.
2202                    // Check the 'if' direction:
2203                    if matches!(val, Expr::FunctionExpression(_)) {
2204                        return Err(CompilationError::fatal(
2205                            SourceRange::new(start, dec_end, id.module_id),
2206                            format!("Expected a `fn` variable kind, found: `{}`", kind),
2207                        ));
2208                    }
2209                    Ok(val)
2210                })
2211                .context(expected("a KCL value, which is being bound to a variable"))
2212                .parse_next(i);
2213
2214            if let Some((_, tok)) = decl_token {
2215                let range_to_remove = SourceRange::new(tok.start, id.start, id.module_id);
2216                ParseContext::err(
2217                    CompilationError::err(
2218                        tok.as_source_range(),
2219                        format!(
2220                            "Using `{}` to declare constants is deprecated; no keyword is required",
2221                            tok.value
2222                        ),
2223                    )
2224                    .with_suggestion(
2225                        format!("Remove `{}`", tok.value),
2226                        "",
2227                        Some(range_to_remove),
2228                        Tag::Deprecated,
2229                    ),
2230                );
2231            }
2232
2233            val
2234        }
2235        .map_err(|e| e.cut())?;
2236
2237    let end = val.end();
2238    let module_id = id.module_id;
2239    Ok(Node::boxed(
2240        VariableDeclaration {
2241            declaration: Node::new_node(
2242                id.start,
2243                end,
2244                module_id,
2245                VariableDeclarator {
2246                    id,
2247                    init: val,
2248                    digest: None,
2249                },
2250            ),
2251            visibility,
2252            kind,
2253            digest: None,
2254        },
2255        start,
2256        end,
2257        module_id,
2258    ))
2259}
2260
2261fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
2262    let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2263        .parse_next(i)?
2264        .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2265
2266    let decl_token = ty(i)?;
2267    let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
2268    whitespace(i)?;
2269
2270    let name = alt((
2271        fun.map(|t| {
2272            Node::new(
2273                Identifier {
2274                    name: "fn".to_owned(),
2275                    digest: None,
2276                },
2277                t.start,
2278                t.end,
2279                t.module_id,
2280            )
2281        }),
2282        identifier,
2283    ))
2284    .parse_next(i)?;
2285    let mut end = name.end;
2286
2287    let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
2288        ignore_whitespace(i);
2289        open_paren(i)?;
2290        ignore_whitespace(i);
2291        let args: Vec<_> = separated(0.., identifier, comma_sep).parse_next(i)?;
2292        ignore_trailing_comma(i);
2293        ignore_whitespace(i);
2294        end = close_paren(i)?.end;
2295        Some(args)
2296    } else {
2297        None
2298    };
2299
2300    let alias = if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
2301        ignore_whitespace(i);
2302        equals(i)?;
2303        ignore_whitespace(i);
2304        let ty = type_(i)?;
2305
2306        ParseContext::warn(CompilationError::err(
2307            ty.as_source_range(),
2308            "Type aliases are experimental, likely to change in the future, and likely to not work properly.",
2309        ));
2310
2311        Some(ty)
2312    } else {
2313        None
2314    };
2315
2316    let module_id = name.module_id;
2317    let result = Node::boxed(
2318        TypeDeclaration {
2319            name,
2320            args,
2321            alias,
2322            visibility,
2323            digest: None,
2324        },
2325        start,
2326        end,
2327        module_id,
2328    );
2329
2330    ParseContext::warn(CompilationError::err(
2331        result.as_source_range(),
2332        "Type declarations are experimental, likely to change, and may or may not do anything useful.",
2333    ));
2334
2335    Ok(result)
2336}
2337
2338impl TryFrom<Token> for Node<Identifier> {
2339    type Error = CompilationError;
2340
2341    fn try_from(token: Token) -> Result<Self, Self::Error> {
2342        if token.token_type == TokenType::Word {
2343            Ok(Node::new(
2344                Identifier {
2345                    name: token.value,
2346                    digest: None,
2347                },
2348                token.start,
2349                token.end,
2350                token.module_id,
2351            ))
2352        } else {
2353            Err(CompilationError::fatal(
2354                token.as_source_range(),
2355                format!(
2356                    "Cannot assign a variable to a reserved keyword: {}",
2357                    token.value.as_str()
2358                ),
2359            ))
2360        }
2361    }
2362}
2363
2364/// Parse a KCL identifier (name of a constant/variable/function)
2365fn identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2366    any.try_map(Node::<Identifier>::try_from)
2367        .context(expected("an identifier, e.g. 'width' or 'myPart'"))
2368        .parse_next(i)
2369}
2370
2371fn nameable_identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2372    let result = identifier.parse_next(i)?;
2373
2374    if !result.is_nameable() {
2375        let desc = if result.name == "_" {
2376            "Underscores"
2377        } else {
2378            "Names with a leading underscore"
2379        };
2380        ParseContext::err(CompilationError::err(
2381            SourceRange::new(result.start, result.end, result.module_id),
2382            format!("{desc} cannot be referred to, only declared."),
2383        ));
2384    }
2385
2386    Ok(result)
2387}
2388
2389fn name(i: &mut TokenSlice) -> PResult<Node<Name>> {
2390    let abs_path = opt(double_colon).parse_next(i)?;
2391    let mut idents: NodeList<Identifier> = separated(1.., nameable_identifier, double_colon)
2392        .parse_next(i)
2393        .map_err(|e| e.backtrack())?;
2394
2395    let mut start = idents[0].start;
2396    if let Some(abs_path) = &abs_path {
2397        start = abs_path.start;
2398    }
2399    let abs_path = abs_path.is_some();
2400
2401    let name = idents.pop().unwrap();
2402    let end = name.end;
2403    let module_id = name.module_id;
2404    let result = Node::new(
2405        Name {
2406            name,
2407            path: idents,
2408            abs_path,
2409            digest: None,
2410        },
2411        start,
2412        end,
2413        module_id,
2414    );
2415
2416    if let Some(suggestion) = super::deprecation(&result.to_string(), DeprecationKind::Const) {
2417        ParseContext::warn(
2418            CompilationError::err(
2419                result.as_source_range(),
2420                format!("Using `{result}` is deprecated, prefer using `{suggestion}`."),
2421            )
2422            .with_suggestion(
2423                format!("Replace `{result}` with `{suggestion}`"),
2424                suggestion,
2425                None,
2426                Tag::Deprecated,
2427            ),
2428        );
2429    }
2430
2431    Ok(result)
2432}
2433
2434impl TryFrom<Token> for Node<TagDeclarator> {
2435    type Error = CompilationError;
2436
2437    fn try_from(token: Token) -> Result<Self, Self::Error> {
2438        match token.token_type {
2439            TokenType::Word => {
2440                Ok(Node::new(
2441                    TagDeclarator {
2442                        // We subtract 1 from the start because the tag starts with a `$`.
2443                        name: token.value,
2444                        digest: None,
2445                    },
2446                    token.start - 1,
2447                    token.end,
2448                    token.module_id,
2449                ))
2450            }
2451            TokenType::Number => Err(CompilationError::fatal(
2452                token.as_source_range(),
2453                format!(
2454                    "Tag names must not start with a number. Tag starts with `{}`",
2455                    token.value.as_str()
2456                ),
2457            )),
2458
2459            // e.g. `line(%, $)` or `line(%, $ , 5)`
2460            TokenType::Brace | TokenType::Whitespace | TokenType::Comma => Err(CompilationError::fatal(
2461                token.as_source_range(),
2462                "Tag names must not be empty".to_string(),
2463            )),
2464
2465            TokenType::Type => Err(CompilationError::fatal(
2466                token.as_source_range(),
2467                format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()),
2468            )),
2469
2470            _ => Err(CompilationError::fatal(
2471                token.as_source_range(),
2472                // this is `start with` because if most of these cases are in the middle, it ends
2473                // 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
2474                // multiple tokens
2475                format!("Tag names must not start with a {}", token.token_type),
2476            )),
2477        }
2478    }
2479}
2480
2481impl Node<TagDeclarator> {
2482    fn into_valid_binding_name(self) -> Result<Self, CompilationError> {
2483        // Make sure they are not assigning a variable to a stdlib function.
2484        if crate::std::name_in_stdlib(&self.name) {
2485            return Err(CompilationError::fatal(
2486                SourceRange::from(&self),
2487                format!("Cannot assign a tag to a reserved keyword: {}", self.name),
2488            ));
2489        }
2490        Ok(self)
2491    }
2492}
2493
2494/// Parse a Kcl tag that starts with a `$`.
2495fn tag(i: &mut TokenSlice) -> PResult<Node<TagDeclarator>> {
2496    dollar.parse_next(i)?;
2497    let tag_declarator = any
2498        .try_map(Node::<TagDeclarator>::try_from)
2499        .context(expected("a tag, e.g. '$seg01' or '$line01'"))
2500        .parse_next(i)
2501        .map_err(|e| e.cut())?;
2502    // Now that we've parsed a tag declarator, verify that it's not a stdlib
2503    // name.  If it is, stop backtracking.
2504    tag_declarator
2505        .into_valid_binding_name()
2506        .map_err(|e| ErrMode::Cut(ContextError::from(e)))
2507}
2508
2509/// Helper function. Matches any number of whitespace tokens and ignores them.
2510fn ignore_whitespace(i: &mut TokenSlice) {
2511    let _: PResult<()> = repeat(0.., whitespace).parse_next(i);
2512}
2513
2514// A helper function to ignore a trailing comma.
2515fn ignore_trailing_comma(i: &mut TokenSlice) {
2516    let _ = opt(comma).parse_next(i);
2517}
2518
2519/// Matches at least 1 whitespace.
2520fn require_whitespace(i: &mut TokenSlice) -> PResult<()> {
2521    repeat(1.., whitespace).parse_next(i)
2522}
2523
2524fn unary_expression(i: &mut TokenSlice) -> PResult<Node<UnaryExpression>> {
2525    const EXPECTED: &str = "expected a unary operator (like '-', the negative-numeric operator),";
2526    let (operator, op_token) = any
2527        .try_map(|token: Token| match token.token_type {
2528            TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
2529            TokenType::Operator => Err(CompilationError::fatal(
2530                 token.as_source_range(),
2531                 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(),),
2532            )),
2533            TokenType::Bang => Ok((UnaryOperator::Not, token)),
2534            other => Err(CompilationError::fatal(  token.as_source_range(), format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) )),
2535        })
2536        .context(expected("a unary expression, e.g. -x or -3"))
2537        .parse_next(i)?;
2538    let argument = operand.parse_next(i)?;
2539    Ok(Node::new_node(
2540        op_token.start,
2541        argument.end(),
2542        op_token.module_id,
2543        UnaryExpression {
2544            operator,
2545            argument,
2546            digest: None,
2547        },
2548    ))
2549}
2550
2551/// Consume tokens that make up a binary expression, but don't actually return them.
2552/// Why not?
2553/// Because this is designed to be used with .take() within the `binary_expression` parser.
2554fn binary_expression_tokens(i: &mut TokenSlice) -> PResult<Vec<BinaryExpressionToken>> {
2555    let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
2556    let remaining: Vec<_> = repeat(
2557        1..,
2558        (
2559            preceded(opt(whitespace), binary_operator).map(BinaryExpressionToken::from),
2560            preceded(opt(whitespace), operand).map(BinaryExpressionToken::from),
2561        ),
2562    )
2563    .context(expected(
2564        "one or more binary operators (like + or -) and operands for them, e.g. 1 + 2 - 3",
2565    ))
2566    .parse_next(i)?;
2567    let mut out = Vec::with_capacity(1 + 2 * remaining.len());
2568    out.push(first);
2569    out.extend(remaining.into_iter().flat_map(|(a, b)| [a, b]));
2570    Ok(out)
2571}
2572
2573/// Parse an infix binary expression.
2574fn binary_expression(i: &mut TokenSlice) -> PResult<Node<BinaryExpression>> {
2575    // Find the slice of tokens which makes up the binary expression
2576    let tokens = binary_expression_tokens.parse_next(i)?;
2577
2578    // Pass the token slice into the specialized math parser, for things like
2579    // precedence and converting infix operations to an AST.
2580    let expr = super::math::parse(tokens).map_err(|e| ErrMode::Backtrack(e.into()))?;
2581    Ok(expr)
2582}
2583
2584fn binary_expr_in_parens(i: &mut TokenSlice) -> PResult<Node<BinaryExpression>> {
2585    let span_with_brackets = bracketed_section.take().parse_next(i)?;
2586    let mut span_no_brackets = span_with_brackets.without_ends();
2587    let expr = binary_expression.parse_next(&mut span_no_brackets)?;
2588    Ok(expr)
2589}
2590
2591/// Match a starting bracket, then match to the corresponding end bracket.
2592/// Return the count of how many tokens are in that span
2593/// (not including the bracket tokens).
2594fn bracketed_section(i: &mut TokenSlice) -> PResult<usize> {
2595    // Find the start of this bracketed expression.
2596    let _ = open_paren.parse_next(i)?;
2597    let mut opened_braces = 1usize;
2598    let mut tokens_examined = 0;
2599    while opened_braces > 0 {
2600        let tok = any.parse_next(i)?;
2601        tokens_examined += 1;
2602        if matches!(tok.token_type, TokenType::Brace) {
2603            if tok.value == "(" {
2604                opened_braces += 1;
2605            } else if tok.value == ")" {
2606                opened_braces -= 1;
2607            }
2608        }
2609    }
2610    Ok(tokens_examined)
2611}
2612
2613/// Parse a KCL expression statement.
2614fn expression_stmt(i: &mut TokenSlice) -> PResult<Node<ExpressionStatement>> {
2615    let val = expression
2616        .context(expected(
2617            "an expression (i.e. a value, or an algorithm for calculating one), e.g. 'x + y' or '3' or 'width * 2'",
2618        ))
2619        .parse_next(i)?;
2620    Ok(Node::new_node(
2621        val.start(),
2622        val.end(),
2623        val.module_id(),
2624        ExpressionStatement {
2625            expression: val,
2626            digest: None,
2627        },
2628    ))
2629}
2630
2631/// Parse the given brace symbol.
2632fn some_brace(symbol: &'static str, i: &mut TokenSlice) -> PResult<Token> {
2633    one_of((TokenType::Brace, symbol))
2634        .context(expected(symbol))
2635        .parse_next(i)
2636}
2637
2638/// Parse a |> operator.
2639fn pipe_operator(i: &mut TokenSlice) -> PResult<Token> {
2640    one_of((TokenType::Operator, PIPE_OPERATOR))
2641        .context(expected(
2642            "the |> operator, used for 'piping' one function's output into another function's input",
2643        ))
2644        .parse_next(i)
2645}
2646
2647fn ws_with_newline(i: &mut TokenSlice) -> PResult<Token> {
2648    one_of(TokenType::Whitespace)
2649        .verify(|token: &Token| token.value.contains('\n'))
2650        .context(expected("a newline, possibly with whitespace"))
2651        .parse_next(i)
2652}
2653
2654/// (
2655fn open_paren(i: &mut TokenSlice) -> PResult<Token> {
2656    some_brace("(", i)
2657}
2658
2659/// )
2660fn close_paren(i: &mut TokenSlice) -> PResult<Token> {
2661    some_brace(")", i)
2662}
2663
2664/// [
2665fn open_bracket(i: &mut TokenSlice) -> PResult<Token> {
2666    some_brace("[", i)
2667}
2668
2669/// ]
2670fn close_bracket(i: &mut TokenSlice) -> PResult<Token> {
2671    some_brace("]", i)
2672}
2673
2674/// {
2675fn open_brace(i: &mut TokenSlice) -> PResult<Token> {
2676    some_brace("{", i)
2677}
2678
2679/// }
2680fn close_brace(i: &mut TokenSlice) -> PResult<Token> {
2681    some_brace("}", i)
2682}
2683
2684fn comma(i: &mut TokenSlice) -> PResult<()> {
2685    TokenType::Comma.parse_from(i)?;
2686    Ok(())
2687}
2688
2689fn hash(i: &mut TokenSlice) -> PResult<()> {
2690    TokenType::Hash.parse_from(i)?;
2691    Ok(())
2692}
2693
2694fn bang(i: &mut TokenSlice) -> PResult<Token> {
2695    TokenType::Bang.parse_from(i)
2696}
2697
2698fn dollar(i: &mut TokenSlice) -> PResult<()> {
2699    TokenType::Dollar.parse_from(i)?;
2700    Ok(())
2701}
2702
2703fn period(i: &mut TokenSlice) -> PResult<()> {
2704    TokenType::Period.parse_from(i)?;
2705    Ok(())
2706}
2707
2708fn end_inclusive_range(i: &mut TokenSlice) -> PResult<Token> {
2709    any.try_map(|token: Token| {
2710        if matches!(token.token_type, TokenType::DoublePeriod) {
2711            Ok(token)
2712        } else {
2713            Err(CompilationError::fatal(
2714                token.as_source_range(),
2715                format!(
2716                    "expected a '..' (double period) found {} which is {}",
2717                    token.value.as_str(),
2718                    token.token_type
2719                ),
2720            ))
2721        }
2722    })
2723    .context(expected("the .. operator, used for array ranges like [0..10]"))
2724    .parse_next(i)
2725}
2726
2727fn end_exclusive_range(i: &mut TokenSlice) -> PResult<Token> {
2728    any.try_map(|token: Token| {
2729        if matches!(token.token_type, TokenType::DoublePeriodLessThan) {
2730            Ok(token)
2731        } else {
2732            Err(CompilationError::fatal(
2733                token.as_source_range(),
2734                format!("expected a '..<' but found {}", token.value.as_str()),
2735            ))
2736        }
2737    })
2738    .context(expected("the ..< operator, used for array ranges like [0..<10]"))
2739    .parse_next(i)
2740}
2741
2742fn colon(i: &mut TokenSlice) -> PResult<Token> {
2743    TokenType::Colon.parse_from(i)
2744}
2745
2746fn semi_colon(i: &mut TokenSlice) -> PResult<Token> {
2747    TokenType::SemiColon.parse_from(i)
2748}
2749
2750fn plus(i: &mut TokenSlice) -> PResult<Token> {
2751    one_of((TokenType::Operator, "+")).parse_next(i)
2752}
2753
2754fn double_colon(i: &mut TokenSlice) -> PResult<Token> {
2755    TokenType::DoubleColon.parse_from(i)
2756}
2757
2758fn equals(i: &mut TokenSlice) -> PResult<Token> {
2759    one_of((TokenType::Operator, "="))
2760        .context(expected("the equals operator, ="))
2761        .parse_next(i)
2762}
2763
2764fn question_mark(i: &mut TokenSlice) -> PResult<()> {
2765    TokenType::QuestionMark.parse_from(i)?;
2766    Ok(())
2767}
2768
2769fn at_sign(i: &mut TokenSlice) -> PResult<Token> {
2770    TokenType::At.parse_from(i)
2771}
2772
2773fn fun(i: &mut TokenSlice) -> PResult<Token> {
2774    keyword(i, "fn")
2775}
2776
2777fn ty(i: &mut TokenSlice) -> PResult<Token> {
2778    keyword(i, "type")
2779}
2780
2781fn any_keyword(i: &mut TokenSlice) -> PResult<Token> {
2782    any.try_map(|token: Token| match token.token_type {
2783        TokenType::Keyword => Ok(token),
2784        _ => Err(CompilationError::fatal(
2785            token.as_source_range(),
2786            "expected some reserved keyword".to_owned(),
2787        )),
2788    })
2789    .parse_next(i)
2790}
2791
2792fn keyword(i: &mut TokenSlice, expected: &str) -> PResult<Token> {
2793    any.try_map(|token: Token| match token.token_type {
2794        TokenType::Keyword if token.value == expected => Ok(token),
2795        _ => Err(CompilationError::fatal(
2796            token.as_source_range(),
2797            format!("expected '{expected}', found {}", token.value.as_str(),),
2798        )),
2799    })
2800    .parse_next(i)
2801}
2802
2803/// Parse a comma, optionally followed by some whitespace.
2804fn comma_sep(i: &mut TokenSlice) -> PResult<()> {
2805    (opt(whitespace), comma, opt(whitespace))
2806        .context(expected("a comma, optionally followed by whitespace"))
2807        .parse_next(i)?;
2808    Ok(())
2809}
2810
2811/// Parse a `|`, optionally followed by some whitespace.
2812fn pipe_sep(i: &mut TokenSlice) -> PResult<()> {
2813    (opt(whitespace), one_of((TokenType::Operator, "|")), opt(whitespace)).parse_next(i)?;
2814    Ok(())
2815}
2816
2817fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
2818    (
2819        opt((
2820            terminated(nameable_identifier, opt(whitespace)),
2821            terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
2822        )),
2823        expression,
2824    )
2825        .map(|(label, arg)| LabeledArg {
2826            label: label.map(|(l, _)| l),
2827            arg,
2828        })
2829        .parse_next(i)
2830}
2831
2832fn record_ty_field(i: &mut TokenSlice) -> PResult<(Node<Identifier>, Node<Type>)> {
2833    (identifier, colon, opt(whitespace), type_)
2834        .map(|(id, _, _, ty)| (id, ty))
2835        .parse_next(i)
2836}
2837
2838/// Parse a type in various positions.
2839fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> {
2840    let type_ = alt((
2841        // Object types
2842        (
2843            open_brace,
2844            opt(whitespace),
2845            separated(0.., record_ty_field, comma_sep),
2846            opt(whitespace),
2847            close_brace,
2848        )
2849            .try_map(|(open, _, params, _, close)| {
2850                Ok(Node::new(
2851                    Type::Object { properties: params },
2852                    open.start,
2853                    close.end,
2854                    open.module_id,
2855                ))
2856            }),
2857        // Array types
2858        array_type,
2859        // Primitive or union types
2860        separated(1.., primitive_type, pipe_sep).map(|mut tys: Vec<_>| {
2861            if tys.len() == 1 {
2862                tys.pop().unwrap().map(Type::Primitive)
2863            } else {
2864                let start = tys[0].start;
2865                let module_id = tys[0].module_id;
2866                let end = tys.last().unwrap().end;
2867                Node::new(Type::Union { tys }, start, end, module_id)
2868            }
2869        }),
2870    ))
2871    .parse_next(i)?;
2872
2873    Ok(type_)
2874}
2875
2876fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> {
2877    alt((
2878        // A function type: `fn` (`(` type?, (id: type,)* `)` (`:` type)?)?
2879        (
2880            fun,
2881            opt((
2882                // `(` type?, (id: type,)* `)`
2883                delimited(
2884                    open_paren,
2885                    opt(alt((
2886                        // type, (id: type,)+
2887                        (
2888                            type_,
2889                            comma,
2890                            opt(whitespace),
2891                            separated(
2892                                1..,
2893                                (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2894                                comma_sep,
2895                            ),
2896                        )
2897                            .map(|(t, _, _, args)| (Some(t), args)),
2898                        // (id: type,)+
2899                        separated(
2900                            1..,
2901                            (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2902                            comma_sep,
2903                        )
2904                        .map(|args| (None, args)),
2905                        // type
2906                        type_.map(|t| (Some(t), Vec::new())),
2907                    ))),
2908                    close_paren,
2909                ),
2910                // `:` type
2911                opt((colon, opt(whitespace), type_)),
2912            )),
2913        )
2914            .map(|(t, tys)| {
2915                let mut ft = FunctionType::empty_fn_type();
2916
2917                if let Some((args, ret)) = tys {
2918                    if let Some((unnamed, named)) = args {
2919                        if let Some(unnamed) = unnamed {
2920                            ft.unnamed_arg = Some(Box::new(unnamed));
2921                        }
2922                        ft.named_args = named;
2923                    }
2924                    if let Some((_, _, ty)) = ret {
2925                        ft.return_type = Some(Box::new(ty));
2926                    }
2927                }
2928
2929                Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
2930            }),
2931        // A named type, possibly with a numeric suffix.
2932        (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
2933            let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
2934            result.inner =
2935                PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
2936            result
2937        }),
2938    ))
2939    .parse_next(i)
2940}
2941
2942fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
2943    fn opt_whitespace(i: &mut TokenSlice) -> PResult<()> {
2944        ignore_whitespace(i);
2945        Ok(())
2946    }
2947
2948    open_bracket(i)?;
2949    let ty = type_(i)?;
2950    let len = opt((
2951        semi_colon,
2952        opt_whitespace,
2953        any.try_map(|token: Token| match token.token_type {
2954            TokenType::Number => {
2955                let value = token.uint_value().ok_or_else(|| {
2956                    CompilationError::fatal(
2957                        token.as_source_range(),
2958                        format!("Expected unsigned integer literal, found: {}", token.value),
2959                    )
2960                })?;
2961
2962                Ok(value as usize)
2963            }
2964            _ => Err(CompilationError::fatal(token.as_source_range(), "invalid array length")),
2965        }),
2966        opt(plus),
2967    ))
2968    .parse_next(i)?;
2969    close_bracket(i)?;
2970
2971    let len = if let Some((tok, _, n, plus)) = len {
2972        if plus.is_some() {
2973            if n != 1 {
2974                return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
2975                    tok.as_source_range(),
2976                    "Non-empty arrays are specified using `1+`, for a fixed-size array use just an integer",
2977                ))));
2978            } else {
2979                ArrayLen::NonEmpty
2980            }
2981        } else {
2982            ArrayLen::Known(n)
2983        }
2984    } else {
2985        ArrayLen::None
2986    };
2987
2988    Ok(ty.map(|ty| Type::Array { ty: Box::new(ty), len }))
2989}
2990
2991fn uom_for_type(i: &mut TokenSlice) -> PResult<NumericSuffix> {
2992    any.try_map(|t: Token| t.value.parse()).parse_next(i)
2993}
2994
2995fn comment(i: &mut TokenSlice) -> PResult<Node<String>> {
2996    any.verify_map(|token: Token| {
2997        let value = match token.token_type {
2998            TokenType::LineComment => token.value,
2999            TokenType::BlockComment => token.value,
3000            _ => return None,
3001        };
3002        Some(Node::new(value, token.start, token.end, token.module_id))
3003    })
3004    .context(expected("Comment"))
3005    .parse_next(i)
3006}
3007
3008fn comments(i: &mut TokenSlice) -> PResult<Node<Vec<String>>> {
3009    let comments: Vec<Node<String>> = repeat(1.., (comment, opt(whitespace)).map(|(c, _)| c)).parse_next(i)?;
3010    let start = comments[0].start;
3011    let module_id = comments[0].module_id;
3012    let end = comments.last().unwrap().end;
3013    let inner = comments.into_iter().map(|n| n.inner).collect();
3014    Ok(Node::new(inner, start, end, module_id))
3015}
3016
3017struct ParamDescription {
3018    labeled: bool,
3019    arg_name: Token,
3020    type_: std::option::Option<Node<Type>>,
3021    default_value: Option<DefaultParamVal>,
3022    attr: Option<Node<Annotation>>,
3023    comments: Option<Node<Vec<String>>>,
3024}
3025
3026fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
3027    let (_, comments, _, attr, _, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
3028        opt(whitespace),
3029        opt(comments),
3030        opt(whitespace),
3031        opt(outer_annotation),
3032        opt(whitespace),
3033        opt(at_sign),
3034        any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
3035        opt(question_mark),
3036        opt(whitespace),
3037        opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
3038        opt(whitespace),
3039        opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
3040    )
3041        .parse_next(i)?;
3042
3043    Ok(ParamDescription {
3044        labeled: found_at_sign.is_none(),
3045        arg_name,
3046        type_,
3047        default_value: match (question_mark.is_some(), default_literal) {
3048            (true, Some(lit)) => Some(DefaultParamVal::Literal(*lit)),
3049            (true, None) => Some(DefaultParamVal::none()),
3050            (false, None) => None,
3051            (false, Some(lit)) => {
3052                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.";
3053                let e = CompilationError::fatal((&lit).into(), msg);
3054                return Err(ErrMode::Backtrack(ContextError::from(e)));
3055            }
3056        },
3057        attr,
3058        comments,
3059    })
3060}
3061
3062/// Parameters are declared in a function signature, and used within a function.
3063fn parameters(i: &mut TokenSlice) -> PResult<Vec<Parameter>> {
3064    // Get all tokens until the next ), because that ends the parameter list.
3065    let candidates: Vec<_> = separated(0.., parameter, comma_sep)
3066        .context(expected("function parameters"))
3067        .parse_next(i)?;
3068    opt(comma_sep).parse_next(i)?;
3069
3070    // Make sure all those tokens are valid parameters.
3071    let params: Vec<Parameter> = candidates
3072        .into_iter()
3073        .map(
3074            |ParamDescription {
3075                 labeled,
3076                 arg_name,
3077                 type_,
3078                 default_value,
3079                 attr,
3080                 comments,
3081             }| {
3082                let mut identifier = Node::<Identifier>::try_from(arg_name)?;
3083                if let Some(comments) = comments {
3084                    identifier.comment_start = comments.start;
3085                    identifier.pre_comments = comments.inner;
3086                }
3087                if let Some(attr) = attr {
3088                    identifier.outer_attrs.push(attr);
3089                }
3090
3091                Ok(Parameter {
3092                    identifier,
3093                    type_,
3094                    default_value,
3095                    labeled,
3096                    digest: None,
3097                })
3098            },
3099        )
3100        .collect::<Result<_, _>>()
3101        .map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
3102
3103    // Make sure the only unlabeled parameter is the first one.
3104    if let Some(param) = params.iter().skip(1).find(|param| !param.labeled) {
3105        let source_range = SourceRange::from(param);
3106        return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
3107            source_range,
3108            "Only the first parameter can be declared unlabeled",
3109        ))));
3110    }
3111
3112    // Make sure optional parameters are last.
3113    if let Err(e) = optional_after_required(&params) {
3114        return Err(ErrMode::Cut(ContextError::from(e)));
3115    }
3116    Ok(params)
3117}
3118
3119fn optional_after_required(params: &[Parameter]) -> Result<(), CompilationError> {
3120    let mut found_optional = false;
3121    for p in params {
3122        if p.optional() {
3123            found_optional = true;
3124        }
3125        if !p.optional() && found_optional {
3126            let e = CompilationError::fatal(
3127                (&p.identifier).into(),
3128                "mandatory parameters must be declared before optional parameters",
3129            );
3130            return Err(e);
3131        }
3132    }
3133    Ok(())
3134}
3135
3136/// Introduce a new name, which binds some value.
3137fn binding_name(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
3138    identifier
3139        .context(expected("an identifier, which will be the name of some value"))
3140        .parse_next(i)
3141}
3142
3143/// Either a positional or keyword function call.
3144fn fn_call_pos_or_kw(i: &mut TokenSlice) -> PResult<Expr> {
3145    alt((fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),)).parse_next(i)
3146}
3147
3148fn labelled_fn_call(i: &mut TokenSlice) -> PResult<Expr> {
3149    let expr = fn_call_pos_or_kw.parse_next(i)?;
3150
3151    let label = opt(label).parse_next(i)?;
3152    match label {
3153        Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
3154        None => Ok(expr),
3155    }
3156}
3157
3158fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
3159    let fn_name = name(i)?;
3160    opt(whitespace).parse_next(i)?;
3161    let _ = open_paren.parse_next(i)?;
3162    ignore_whitespace(i);
3163
3164    // Special case: no args
3165    let early_close = peek(close_paren).parse_next(i);
3166    if early_close.is_ok() {
3167        let cl = close_paren.parse_next(i)?;
3168        let result = Node::new_node(
3169            fn_name.start,
3170            cl.end,
3171            fn_name.module_id,
3172            CallExpressionKw {
3173                callee: fn_name,
3174                unlabeled: Default::default(),
3175                arguments: Default::default(),
3176                digest: None,
3177                non_code_meta: Default::default(),
3178            },
3179        );
3180        return Ok(result);
3181    }
3182
3183    // Special case: one arg (unlabeled)
3184    let early_close = peek((expression, opt(whitespace), close_paren)).parse_next(i);
3185    if early_close.is_ok() {
3186        let first_expression = expression.parse_next(i)?;
3187        ignore_whitespace(i);
3188        let end = close_paren.parse_next(i)?.end;
3189        let result = Node::new_node(
3190            fn_name.start,
3191            end,
3192            fn_name.module_id,
3193            CallExpressionKw {
3194                callee: fn_name,
3195                unlabeled: Some(first_expression),
3196                arguments: Default::default(),
3197                digest: None,
3198                non_code_meta: Default::default(),
3199            },
3200        );
3201        return Ok(result);
3202    }
3203
3204    #[derive(Debug)]
3205    #[allow(clippy::large_enum_variant)]
3206    enum ArgPlace {
3207        NonCode(Node<NonCodeNode>),
3208        LabeledArg(LabeledArg),
3209        UnlabeledArg(Expr),
3210        Keyword(Token),
3211    }
3212    let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
3213    let args: Vec<_> = repeat(
3214        0..,
3215        alt((
3216            terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
3217            terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
3218            terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
3219            expression.map(ArgPlace::UnlabeledArg),
3220        )),
3221    )
3222    .parse_next(i)?;
3223    let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().try_fold(
3224        (Vec::new(), BTreeMap::new()),
3225        |(mut args, mut non_code_nodes), (index, e)| {
3226            match e {
3227                ArgPlace::NonCode(x) => {
3228                    non_code_nodes.insert(index, vec![x]);
3229                }
3230                ArgPlace::LabeledArg(x) => {
3231                    args.push(x);
3232                }
3233                ArgPlace::Keyword(kw) => {
3234                    return Err(ErrMode::Cut(
3235                        CompilationError::fatal(
3236                            SourceRange::from(kw.clone()),
3237                            format!(
3238                                "`{}` is not the name of an argument (it's a reserved keyword)",
3239                                kw.value
3240                            ),
3241                        )
3242                        .into(),
3243                    ));
3244                }
3245                ArgPlace::UnlabeledArg(arg) => {
3246                    let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
3247                    if followed_by_equals {
3248                        return Err(ErrMode::Cut(
3249                            CompilationError::fatal(
3250                                SourceRange::from(arg),
3251                                "This argument has a label, but no value. Put some value after the equals sign",
3252                            )
3253                            .into(),
3254                        ));
3255                    } else {
3256                        args.push(LabeledArg { label: None, arg });
3257                    }
3258                }
3259            }
3260            Ok((args, non_code_nodes))
3261        },
3262    )?;
3263    ignore_whitespace(i);
3264    opt(comma_sep).parse_next(i)?;
3265    let end = close_paren.parse_next(i)?.end;
3266
3267    // Validate there aren't any duplicate labels.
3268    let mut counted_labels = IndexMap::with_capacity(args.len());
3269    for arg in &args {
3270        if let Some(l) = &arg.label {
3271            *counted_labels.entry(&l.inner.name).or_insert(0) += 1;
3272        }
3273    }
3274    if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
3275        let msg = format!(
3276            "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."
3277        );
3278        ParseContext::err(CompilationError::err(
3279            SourceRange::new(fn_name.start, end, fn_name.module_id),
3280            msg,
3281        ));
3282    }
3283
3284    let non_code_meta = NonCodeMeta {
3285        non_code_nodes,
3286        ..Default::default()
3287    };
3288    let result = Node::new_node(
3289        fn_name.start,
3290        end,
3291        fn_name.module_id,
3292        CallExpressionKw {
3293            callee: fn_name,
3294            unlabeled: initial_unlabeled_arg,
3295            arguments: args,
3296            digest: None,
3297            non_code_meta,
3298        },
3299    );
3300
3301    let callee_str = result.callee.name.name.to_string();
3302    if let Some(suggestion) = super::deprecation(&callee_str, DeprecationKind::Function) {
3303        ParseContext::warn(
3304            CompilationError::err(
3305                result.as_source_range(),
3306                format!("Calling `{}` is deprecated, prefer using `{}`.", callee_str, suggestion),
3307            )
3308            .with_suggestion(
3309                format!("Replace `{}` with `{}`", callee_str, suggestion),
3310                suggestion,
3311                None,
3312                Tag::Deprecated,
3313            ),
3314        );
3315    }
3316
3317    Ok(result)
3318}
3319
3320#[cfg(test)]
3321mod tests {
3322    use itertools::Itertools;
3323    use pretty_assertions::assert_eq;
3324
3325    use super::*;
3326    use crate::{
3327        parsing::ast::types::{BodyItem, Expr, VariableKind},
3328        KclError, ModuleId,
3329    };
3330
3331    fn assert_reserved(word: &str) {
3332        // Try to use it as a variable name.
3333        let code = format!(r#"{} = 0"#, word);
3334        let result = crate::parsing::top_level_parse(code.as_str());
3335        let err = &result.unwrap_errs().next().unwrap();
3336        // Which token causes the error may change.  In "return = 0", for
3337        // example, "return" is the problem.
3338        assert!(
3339            err.message.starts_with("Unexpected token: ")
3340                || err.message.starts_with("= is not")
3341                || err
3342                    .message
3343                    .starts_with("Cannot assign a variable to a reserved keyword: "),
3344            "Error message is: `{}`",
3345            err.message,
3346        );
3347    }
3348
3349    #[test]
3350    fn reserved_words() {
3351        // Since these are stored in a set, we sort to make the tests
3352        // deterministic.
3353        for word in crate::parsing::token::RESERVED_WORDS.keys().sorted() {
3354            assert_reserved(word);
3355        }
3356        assert_reserved("import");
3357    }
3358
3359    #[test]
3360    fn parse_names() {
3361        for (test, expected_len) in [("someVar", 0), ("::foo", 0), ("foo::bar::baz", 2)] {
3362            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3363            match name.parse(tokens.as_slice()) {
3364                Ok(n) => assert_eq!(n.path.len(), expected_len, "Could not parse name from `{test}`: {n:?}"),
3365                Err(e) => panic!("Could not parse name from `{test}`: {e:?}"),
3366            }
3367        }
3368    }
3369
3370    #[test]
3371    fn weird_program_unclosed_paren() {
3372        let tokens = crate::parsing::token::lex("fn firstPrime(", ModuleId::default()).unwrap();
3373        let tokens = tokens.as_slice();
3374        let last = tokens.last().unwrap().as_source_range();
3375        let err: CompilationError = program.parse(tokens).unwrap_err().into();
3376        assert_eq!(err.source_range, last);
3377        // TODO: Better comment. This should explain the compiler expected ) because the user had started declaring the function's parameters.
3378        // Part of https://github.com/KittyCAD/modeling-app/issues/784
3379        assert_eq!(err.message, "Unexpected end of file. The compiler expected )");
3380    }
3381
3382    #[test]
3383    fn kw_call_as_operand() {
3384        let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
3385        let tokens = tokens.as_slice();
3386        let op = operand.parse(tokens).unwrap();
3387        println!("{op:#?}");
3388    }
3389
3390    #[test]
3391    fn weird_program_just_a_pipe() {
3392        let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();
3393        let err: CompilationError = program.parse(tokens.as_slice()).unwrap_err().into();
3394        assert_eq!(err.source_range, SourceRange::new(0, 1, ModuleId::default()));
3395        assert_eq!(err.message, "Unexpected token: |");
3396    }
3397
3398    #[test]
3399    fn parse_binary_expressions() {
3400        for (i, test_program) in ["1 + 2 + 3"].into_iter().enumerate() {
3401            let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3402            let _actual = match binary_expression.parse_next(&mut tokens.as_slice()) {
3403                Ok(x) => x,
3404                Err(e) => panic!("Failed test {i}, could not parse binary expressions from \"{test_program}\": {e:?}"),
3405            };
3406        }
3407    }
3408
3409    #[test]
3410    fn test_vardec_no_keyword() {
3411        let tokens = crate::parsing::token::lex("x = 4", ModuleId::default()).unwrap();
3412        let vardec = declaration(&mut tokens.as_slice()).unwrap();
3413        assert_eq!(vardec.inner.kind, VariableKind::Const);
3414        let vardec = &vardec.declaration;
3415        assert_eq!(vardec.id.name, "x");
3416        let Expr::Literal(init_val) = &vardec.init else {
3417            panic!("weird init value")
3418        };
3419        assert_eq!(init_val.raw, "4");
3420    }
3421
3422    #[test]
3423    fn test_negative_operands() {
3424        let tokens = crate::parsing::token::lex("-leg2", ModuleId::default()).unwrap();
3425        let _s = operand.parse_next(&mut tokens.as_slice()).unwrap();
3426    }
3427
3428    #[test]
3429    fn test_comments_in_function1() {
3430        let test_program = r#"() {
3431            // comment 0
3432            a = 1
3433            // comment 1
3434            b = 2
3435            /// comment 2
3436            return 1
3437        }"#;
3438        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3439        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3440        assert_eq!(expr.params, vec![]);
3441        let comment_start = expr.body.body[0].get_comments();
3442        let comment0 = expr.body.body[1].get_comments();
3443        let comment1 = expr.body.body[2].get_comments();
3444        assert_eq!(comment_start, vec!["// comment 0".to_owned()]);
3445        assert_eq!(comment0, vec!["// comment 1".to_owned()]);
3446        assert_eq!(comment1, vec!["/// comment 2".to_owned()]);
3447    }
3448
3449    #[test]
3450    fn test_comments_in_function2() {
3451        let test_program = r#"() {
3452  yo = { a = { b = { c = '123' } } } /* block
3453comment */
3454}"#;
3455        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3456        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3457        let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
3458        assert_eq!(comment0.value(), "block\ncomment");
3459    }
3460
3461    #[test]
3462    fn test_comment_at_start_of_program() {
3463        let test_program = r#"
3464/* comment at start */
3465
3466mySk1 = startSketchOn(XY)
3467  |> startProfile(at = [0, 0])"#;
3468        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3469        let program = program.parse(tokens.as_slice()).unwrap();
3470        let mut starting_comments = program.inner.non_code_meta.start_nodes;
3471        assert_eq!(starting_comments.len(), 2);
3472        let start0 = starting_comments.remove(0);
3473        let start1 = starting_comments.remove(0);
3474        assert_eq!(
3475            start0.value,
3476            NonCodeValue::BlockComment {
3477                value: "comment at start".to_owned(),
3478                style: CommentStyle::Block
3479            }
3480        );
3481        assert_eq!(start1.value, NonCodeValue::NewLine);
3482    }
3483
3484    #[test]
3485    fn test_comment_in_pipe() {
3486        let tokens = crate::parsing::token::lex(r#"x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
3487        let mut body = program.parse(tokens.as_slice()).unwrap().inner.body;
3488        let BodyItem::VariableDeclaration(item) = body.remove(0) else {
3489            panic!("expected vardec");
3490        };
3491        let val = item.inner.declaration.inner.init;
3492        let Expr::PipeExpression(pipe) = val else {
3493            panic!("expected pipe");
3494        };
3495        let mut noncode = pipe.inner.non_code_meta;
3496        assert_eq!(noncode.non_code_nodes.len(), 1);
3497        let comment = noncode.non_code_nodes.remove(&0).unwrap().pop().unwrap();
3498        assert_eq!(
3499            comment.value,
3500            NonCodeValue::BlockComment {
3501                value: "hi".to_owned(),
3502                style: CommentStyle::Block
3503            }
3504        );
3505    }
3506
3507    #[test]
3508    fn test_whitespace_in_function() {
3509        let test_program = r#"() {
3510            return sg
3511            return sg
3512          }"#;
3513        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3514        let _expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3515    }
3516
3517    #[test]
3518    fn test_empty_lines_in_function() {
3519        let test_program = "() {
3520
3521                return 2
3522            }";
3523        let module_id = ModuleId::from_usize(1);
3524        let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3525        let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3526        assert_eq!(
3527            expr.body.non_code_meta.start_nodes,
3528            vec![Node::new(
3529                NonCodeNode {
3530                    value: NonCodeValue::NewLine,
3531                    digest: None
3532                },
3533                4,
3534                22,
3535                module_id,
3536            )]
3537        );
3538    }
3539
3540    #[test]
3541    fn inline_comment_pipe_expression() {
3542        let test_input = r#"a(XY)
3543        |> b(%)
3544        |> c(%) // inline-comment
3545        |> d(%)"#;
3546
3547        let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3548        let Node {
3549            inner: PipeExpression {
3550                body, non_code_meta, ..
3551            },
3552            ..
3553        } = pipe_expression.parse_next(&mut tokens.as_slice()).unwrap();
3554        assert_eq!(non_code_meta.non_code_nodes.len(), 1);
3555        assert_eq!(
3556            non_code_meta.non_code_nodes.get(&2).unwrap()[0].value,
3557            NonCodeValue::InlineComment {
3558                value: "inline-comment".to_owned(),
3559                style: CommentStyle::Line
3560            }
3561        );
3562        assert_eq!(body.len(), 4);
3563    }
3564
3565    #[test]
3566    fn many_comments() {
3567        let test_program = r#"// this is a comment
3568  yo = { a = { b = { c = '123' } } } /* block
3569  comment */
3570
3571  key = 'c'
3572  // this is also a comment
3573  return things
3574"#;
3575
3576        let module_id = ModuleId::default();
3577        let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3578        let Program {
3579            body, non_code_meta, ..
3580        } = function_body.parse(tokens.as_slice()).unwrap().inner;
3581        assert_eq!(body[0].get_comments(), vec!["// this is a comment".to_owned()],);
3582
3583        assert_eq!(
3584            Some(&vec![
3585                Node::new(
3586                    NonCodeNode {
3587                        value: NonCodeValue::InlineComment {
3588                            value: "block\n  comment".to_owned(),
3589                            style: CommentStyle::Block
3590                        },
3591                        digest: None,
3592                    },
3593                    57,
3594                    79,
3595                    module_id,
3596                ),
3597                Node::new(
3598                    NonCodeNode {
3599                        value: NonCodeValue::NewLine,
3600                        digest: None,
3601                    },
3602                    79,
3603                    83,
3604                    module_id,
3605                )
3606            ]),
3607            non_code_meta.non_code_nodes.get(&0),
3608        );
3609
3610        assert_eq!(body[2].get_comments(), vec!["// this is also a comment".to_owned()],);
3611    }
3612
3613    #[test]
3614    fn inline_block_comments() {
3615        let test_program = r#"yo = 3 /* block
3616  comment */
3617  return 1"#;
3618
3619        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3620        let actual = program.parse(tokens.as_slice()).unwrap();
3621        assert_eq!(actual.non_code_meta.non_code_nodes.len(), 1);
3622        assert_eq!(
3623            actual.non_code_meta.non_code_nodes.get(&0).unwrap()[0].value,
3624            NonCodeValue::InlineComment {
3625                value: "block\n  comment".to_owned(),
3626                style: CommentStyle::Block
3627            }
3628        );
3629    }
3630
3631    #[test]
3632    fn test_bracketed_binary_expression() {
3633        let input = "(2 - 3)";
3634        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3635        let actual = match binary_expr_in_parens.parse(tokens.as_slice()) {
3636            Ok(x) => x,
3637            Err(e) => panic!("{e:?}"),
3638        };
3639        assert_eq!(actual.operator, BinaryOperator::Sub);
3640    }
3641
3642    #[test]
3643    fn test_arg() {
3644        for input in [
3645            "( sigmaAllow * width )",
3646            "6 / ( sigmaAllow * width )",
3647            "sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))",
3648        ] {
3649            let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3650            let _actual = match expression.parse(tokens.as_slice()) {
3651                Ok(x) => x,
3652                Err(e) => panic!("{e:?}"),
3653            };
3654        }
3655    }
3656
3657    #[test]
3658    fn test_arithmetic() {
3659        let input = "1 * (2 - 3)";
3660        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3661        // The RHS should be a binary expression.
3662        let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3663        assert_eq!(actual.operator, BinaryOperator::Mul);
3664        let BinaryPart::BinaryExpression(rhs) = actual.inner.right else {
3665            panic!("Expected RHS to be another binary expression");
3666        };
3667        assert_eq!(rhs.operator, BinaryOperator::Sub);
3668        match &rhs.right {
3669            BinaryPart::Literal(lit) => {
3670                assert!(lit.start == 9 && lit.end == 10);
3671                assert!(
3672                    lit.value
3673                        == LiteralValue::Number {
3674                            value: 3.0,
3675                            suffix: NumericSuffix::None
3676                        }
3677                        && &lit.raw == "3"
3678                        && lit.digest.is_none()
3679                );
3680            }
3681            _ => panic!(),
3682        }
3683    }
3684
3685    #[test]
3686    fn assign_brackets() {
3687        for (i, test_input) in [
3688            "thickness_squared = (1 + 1)",
3689            "thickness_squared = ( 1 + 1)",
3690            "thickness_squared = (1 + 1 )",
3691            "thickness_squared = ( 1 + 1 )",
3692        ]
3693        .into_iter()
3694        .enumerate()
3695        {
3696            let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3697            let actual = match declaration.parse(tokens.as_slice()) {
3698                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3699                Ok(a) => a,
3700            };
3701            let Expr::BinaryExpression(_expr) = &actual.declaration.inner.init else {
3702                panic!(
3703                    "Expected test {i} to be a binary expression but it wasn't, it was {:?}",
3704                    actual.declaration
3705                );
3706            };
3707            // TODO: check both sides are 1... probably not necessary but should do.
3708        }
3709    }
3710
3711    #[test]
3712    fn test_function_call() {
3713        for (i, test_input) in ["x = f(1)", "x = f( 1 )"].into_iter().enumerate() {
3714            let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3715            let _actual = match declaration.parse(tokens.as_slice()) {
3716                Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3717                Ok(a) => a,
3718            };
3719        }
3720    }
3721
3722    #[test]
3723    fn test_nested_arithmetic() {
3724        let input = "1 * ((2 - 3) / 4)";
3725        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3726        // The RHS should be a binary expression.
3727        let outer = binary_expression.parse(tokens.as_slice()).unwrap();
3728        assert_eq!(outer.operator, BinaryOperator::Mul);
3729        let BinaryPart::BinaryExpression(middle) = outer.inner.right else {
3730            panic!("Expected RHS to be another binary expression");
3731        };
3732
3733        assert_eq!(middle.operator, BinaryOperator::Div);
3734        let BinaryPart::BinaryExpression(inner) = middle.inner.left else {
3735            panic!("expected nested binary expression");
3736        };
3737        assert_eq!(inner.operator, BinaryOperator::Sub);
3738    }
3739
3740    #[test]
3741    fn binary_expression_ignores_whitespace() {
3742        let tests = ["1 - 2", "1- 2", "1 -2", "1-2"];
3743        for test in tests {
3744            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3745            let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3746            assert_eq!(actual.operator, BinaryOperator::Sub);
3747            let BinaryPart::Literal(left) = actual.inner.left else {
3748                panic!("should be expression");
3749            };
3750            assert_eq!(
3751                left.value,
3752                LiteralValue::Number {
3753                    value: 1.0,
3754                    suffix: NumericSuffix::None
3755                }
3756            );
3757            let BinaryPart::Literal(right) = actual.inner.right else {
3758                panic!("should be expression");
3759            };
3760            assert_eq!(
3761                right.value,
3762                LiteralValue::Number {
3763                    value: 2.0,
3764                    suffix: NumericSuffix::None
3765                }
3766            );
3767        }
3768    }
3769
3770    #[test]
3771    fn some_pipe_expr() {
3772        let test_program = r#"x()
3773        |> y(%) /* this is
3774        a comment
3775        spanning a few lines */
3776        |> z(%)"#;
3777        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3778        let actual = pipe_expression.parse(tokens.as_slice()).unwrap();
3779        let n = actual.non_code_meta.non_code_nodes.len();
3780        assert_eq!(n, 1, "expected one comment in pipe expression but found {n}");
3781        let nc = &actual.non_code_meta.non_code_nodes.get(&1).unwrap()[0];
3782        assert!(nc.value().starts_with("this"));
3783        assert!(nc.value().ends_with("lines"));
3784    }
3785
3786    #[test]
3787    fn comments_in_pipe_expr() {
3788        for (i, test_program) in [
3789            r#"y() |> /*hi*/ z(%)"#,
3790            "1 |>/*hi*/ f(%)",
3791            r#"y() |> /*hi*/ z(%)"#,
3792            "1 /*hi*/ |> f(%)",
3793            "1
3794        // Hi
3795        |> f(%)",
3796            "1
3797        /* Hi 
3798        there
3799        */
3800        |> f(%)",
3801        ]
3802        .into_iter()
3803        .enumerate()
3804        {
3805            let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3806            let actual = pipe_expression.parse(tokens.as_slice());
3807            assert!(actual.is_ok(), "could not parse test {i}, '{test_program}'");
3808            let actual = actual.unwrap();
3809            let n = actual.non_code_meta.non_code_nodes.len();
3810            assert_eq!(n, 1, "expected one comment in pipe expression but found {n}",)
3811        }
3812    }
3813
3814    #[test]
3815    fn comments() {
3816        let module_id = ModuleId::from_usize(1);
3817        for (i, (test_program, expected)) in [
3818            (
3819                "//hi",
3820                Node::new(
3821                    NonCodeNode {
3822                        value: NonCodeValue::BlockComment {
3823                            value: "hi".to_owned(),
3824                            style: CommentStyle::Line,
3825                        },
3826                        digest: None,
3827                    },
3828                    0,
3829                    4,
3830                    module_id,
3831                ),
3832            ),
3833            (
3834                "/*hello*/",
3835                Node::new(
3836                    NonCodeNode {
3837                        value: NonCodeValue::BlockComment {
3838                            value: "hello".to_owned(),
3839                            style: CommentStyle::Block,
3840                        },
3841                        digest: None,
3842                    },
3843                    0,
3844                    9,
3845                    module_id,
3846                ),
3847            ),
3848            (
3849                "/* hello */",
3850                Node::new(
3851                    NonCodeNode {
3852                        value: NonCodeValue::BlockComment {
3853                            value: "hello".to_owned(),
3854                            style: CommentStyle::Block,
3855                        },
3856                        digest: None,
3857                    },
3858                    0,
3859                    11,
3860                    module_id,
3861                ),
3862            ),
3863            (
3864                "/* \nhello */",
3865                Node::new(
3866                    NonCodeNode {
3867                        value: NonCodeValue::BlockComment {
3868                            value: "hello".to_owned(),
3869                            style: CommentStyle::Block,
3870                        },
3871                        digest: None,
3872                    },
3873                    0,
3874                    12,
3875                    module_id,
3876                ),
3877            ),
3878            (
3879                "
3880                /* hello */",
3881                Node::new(
3882                    NonCodeNode {
3883                        value: NonCodeValue::BlockComment {
3884                            value: "hello".to_owned(),
3885                            style: CommentStyle::Block,
3886                        },
3887                        digest: None,
3888                    },
3889                    0,
3890                    29,
3891                    module_id,
3892                ),
3893            ),
3894            (
3895                // Empty line with trailing whitespace
3896                "
3897  
3898                /* hello */",
3899                Node::new(
3900                    NonCodeNode {
3901                        value: NonCodeValue::NewLineBlockComment {
3902                            value: "hello".to_owned(),
3903                            style: CommentStyle::Block,
3904                        },
3905                        digest: None,
3906                    },
3907                    0,
3908                    32,
3909                    module_id,
3910                ),
3911            ),
3912            (
3913                // Empty line, no trailing whitespace
3914                "
3915
3916                /* hello */",
3917                Node::new(
3918                    NonCodeNode {
3919                        value: NonCodeValue::NewLineBlockComment {
3920                            value: "hello".to_owned(),
3921                            style: CommentStyle::Block,
3922                        },
3923                        digest: None,
3924                    },
3925                    0,
3926                    30,
3927                    module_id,
3928                ),
3929            ),
3930            (
3931                r#"/* block
3932                    comment */"#,
3933                Node::new(
3934                    NonCodeNode {
3935                        value: NonCodeValue::BlockComment {
3936                            value: "block\n                    comment".to_owned(),
3937                            style: CommentStyle::Block,
3938                        },
3939                        digest: None,
3940                    },
3941                    0,
3942                    39,
3943                    module_id,
3944                ),
3945            ),
3946        ]
3947        .into_iter()
3948        .enumerate()
3949        {
3950            let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3951            let actual = non_code_node.parse(tokens.as_slice());
3952            assert!(actual.is_ok(), "could not parse test {i}: {actual:#?}");
3953            let actual = actual.unwrap();
3954            assert_eq!(actual, expected, "failed test {i}");
3955        }
3956    }
3957
3958    #[test]
3959    fn recognize_invalid_params() {
3960        let test_fn = "(let) => { return 1 }";
3961        let module_id = ModuleId::from_usize(2);
3962        let tokens = crate::parsing::token::lex(test_fn, module_id).unwrap();
3963        let err = function_decl.parse(tokens.as_slice()).unwrap_err().into_inner();
3964        let cause = err.cause.unwrap();
3965        // This is the token `let`
3966        assert_eq!(cause.source_range, SourceRange::new(1, 4, ModuleId::from_usize(2)));
3967        assert_eq!(cause.message, "Cannot assign a variable to a reserved keyword: let");
3968    }
3969
3970    #[test]
3971    fn comment_in_string() {
3972        let string_literal = r#""
3973           // a comment
3974             ""#;
3975        let tokens = crate::parsing::token::lex(string_literal, ModuleId::default()).unwrap();
3976        let parsed_literal = literal.parse(tokens.as_slice()).unwrap();
3977        assert_eq!(
3978            parsed_literal.value,
3979            "
3980           // a comment
3981             "
3982            .into()
3983        );
3984    }
3985
3986    #[test]
3987    fn pipes_on_pipes_minimal() {
3988        let test_program = r#"startSketchOn(XY)
3989        |> startProfile(at = [0, 0])
3990        |> line(endAbsolute = [0, -0]) // MoveRelative
3991
3992        "#;
3993        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3994        let tokens = &mut tokens.as_slice();
3995        let _actual = pipe_expression.parse_next(tokens).unwrap();
3996        assert_eq!(tokens.first().unwrap().token_type, TokenType::Whitespace);
3997    }
3998
3999    #[test]
4000    fn test_pipes_on_pipes() {
4001        let test_program = include_str!("../../e2e/executor/inputs/pipes_on_pipes.kcl");
4002        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4003        let _ = run_parser(tokens.as_slice()).unwrap();
4004    }
4005
4006    #[test]
4007    fn test_cube() {
4008        let test_program = include_str!("../../e2e/executor/inputs/cube.kcl");
4009        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4010        match program.parse(tokens.as_slice()) {
4011            Ok(_) => {}
4012            Err(e) => {
4013                panic!("{e:#?}");
4014            }
4015        }
4016    }
4017
4018    #[test]
4019    fn parse_numeric() {
4020        let test_program = "fn foo(x: number(Length)) {}";
4021        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4022        run_parser(tokens.as_slice()).unwrap();
4023
4024        let test_program = "42_mm";
4025        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4026        assert_eq!(tokens.iter().count(), 1);
4027        run_parser(tokens.as_slice()).unwrap();
4028
4029        let test_program = "42_Length";
4030        let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4031        assert_eq!(tokens.iter().count(), 2);
4032        assert_eq!(run_parser(tokens.as_slice()).unwrap_errs().count(), 1);
4033    }
4034
4035    #[test]
4036    fn test_parameter_list() {
4037        let tests = [
4038            ("", vec![]),
4039            ("a", vec!["a"]),
4040            ("a, b", vec!["a", "b"]),
4041            ("a,b", vec!["a", "b"]),
4042        ];
4043        for (i, (input, expected)) in tests.into_iter().enumerate() {
4044            let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4045            let actual = parameters.parse(tokens.as_slice());
4046            assert!(actual.is_ok(), "could not parse test {i}");
4047            let actual_ids: Vec<_> = actual.unwrap().into_iter().map(|p| p.identifier.inner.name).collect();
4048            assert_eq!(actual_ids, expected);
4049        }
4050    }
4051
4052    #[test]
4053    fn test_user_function() {
4054        let input = "() {
4055            return 2
4056        }";
4057
4058        let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4059        let actual = function_decl.parse(tokens.as_slice());
4060        assert!(actual.is_ok(), "could not parse test function");
4061    }
4062
4063    #[test]
4064    fn test_declaration() {
4065        let tests = ["myVar = 5", "myVar=5", "myVar =5", "myVar= 5"];
4066        for test in tests {
4067            // Run the original parser
4068            let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
4069            let mut expected_body = crate::parsing::parse_tokens(tokens.clone()).unwrap().inner.body;
4070            assert_eq!(expected_body.len(), 1);
4071            let BodyItem::VariableDeclaration(expected) = expected_body.pop().unwrap() else {
4072                panic!("Expected variable declaration");
4073            };
4074
4075            // Run the second parser, check it matches the first parser.
4076            let actual = declaration.parse(tokens.as_slice()).unwrap();
4077            assert_eq!(expected, actual);
4078
4079            // Inspect its output in more detail.
4080            assert_eq!(actual.inner.kind, VariableKind::Const);
4081            assert_eq!(actual.start, 0);
4082            let decl = &actual.declaration;
4083            assert_eq!(decl.id.name, "myVar");
4084            let Expr::Literal(value) = &decl.inner.init else {
4085                panic!("value should be a literal")
4086            };
4087            assert_eq!(value.end, test.len());
4088            assert_eq!(value.raw, "5");
4089        }
4090    }
4091
4092    #[test]
4093    fn test_math_parse() {
4094        let module_id = ModuleId::default();
4095        let actual = crate::parsing::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
4096        let expr = Node::boxed(
4097            BinaryExpression {
4098                operator: BinaryOperator::Add,
4099                left: BinaryPart::Literal(Box::new(Node::new(
4100                    Literal {
4101                        value: LiteralValue::Number {
4102                            value: 5.0,
4103                            suffix: NumericSuffix::None,
4104                        },
4105                        raw: "5".to_owned(),
4106                        digest: None,
4107                    },
4108                    0,
4109                    1,
4110                    module_id,
4111                ))),
4112                right: BinaryPart::Literal(Box::new(Node::new(
4113                    Literal {
4114                        value: "a".into(),
4115                        raw: r#""a""#.to_owned(),
4116                        digest: None,
4117                    },
4118                    4,
4119                    7,
4120                    module_id,
4121                ))),
4122                digest: None,
4123            },
4124            0,
4125            7,
4126            module_id,
4127        );
4128        let expected = vec![BodyItem::ExpressionStatement(Node::new(
4129            ExpressionStatement {
4130                expression: Expr::BinaryExpression(expr),
4131                digest: None,
4132            },
4133            0,
4134            7,
4135            module_id,
4136        ))];
4137        assert_eq!(expected, actual);
4138    }
4139
4140    #[test]
4141    fn test_abstract_syntax_tree() {
4142        let code = "5 +6";
4143        let module_id = ModuleId::default();
4144        let result = crate::parsing::parse_str(code, module_id).unwrap();
4145        let expected_result = Node::new(
4146            Program {
4147                body: vec![BodyItem::ExpressionStatement(Node::new(
4148                    ExpressionStatement {
4149                        expression: Expr::BinaryExpression(Node::boxed(
4150                            BinaryExpression {
4151                                left: BinaryPart::Literal(Box::new(Node::new(
4152                                    Literal {
4153                                        value: LiteralValue::Number {
4154                                            value: 5.0,
4155                                            suffix: NumericSuffix::None,
4156                                        },
4157                                        raw: "5".to_string(),
4158                                        digest: None,
4159                                    },
4160                                    0,
4161                                    1,
4162                                    module_id,
4163                                ))),
4164                                operator: BinaryOperator::Add,
4165                                right: BinaryPart::Literal(Box::new(Node::new(
4166                                    Literal {
4167                                        value: LiteralValue::Number {
4168                                            value: 6.0,
4169                                            suffix: NumericSuffix::None,
4170                                        },
4171                                        raw: "6".to_string(),
4172                                        digest: None,
4173                                    },
4174                                    3,
4175                                    4,
4176                                    module_id,
4177                                ))),
4178                                digest: None,
4179                            },
4180                            0,
4181                            4,
4182                            module_id,
4183                        )),
4184                        digest: None,
4185                    },
4186                    0,
4187                    4,
4188                    module_id,
4189                ))],
4190                shebang: None,
4191                non_code_meta: NonCodeMeta::default(),
4192                inner_attrs: Vec::new(),
4193                digest: None,
4194            },
4195            0,
4196            4,
4197            module_id,
4198        );
4199
4200        assert_eq!(result, expected_result);
4201    }
4202
4203    #[test]
4204    fn test_empty_file() {
4205        let some_program_string = r#""#;
4206        let result = crate::parsing::top_level_parse(some_program_string);
4207        assert!(result.is_ok());
4208    }
4209
4210    #[track_caller]
4211    fn assert_no_err(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4212        let result = crate::parsing::top_level_parse(p);
4213        let result = result.0.unwrap();
4214        assert!(result.1.iter().all(|e| !e.severity.is_err()), "found: {:#?}", result.1);
4215        (result.0.unwrap(), result.1)
4216    }
4217
4218    #[track_caller]
4219    fn assert_no_fatal(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4220        let result = crate::parsing::top_level_parse(p);
4221        let result = result.0.unwrap();
4222        assert!(
4223            result.1.iter().all(|e| e.severity != Severity::Fatal),
4224            "found: {:#?}",
4225            result.1
4226        );
4227        (result.0.unwrap(), result.1)
4228    }
4229
4230    #[track_caller]
4231    fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
4232        let result = crate::parsing::top_level_parse(p);
4233        let err = result.unwrap_errs().next().unwrap();
4234        assert!(
4235            err.message.starts_with(msg),
4236            "Found `{}`, expected `{msg}`",
4237            err.message
4238        );
4239        let src_actual = [err.source_range.start(), err.source_range.end()];
4240        assert_eq!(
4241            src_expected,
4242            src_actual,
4243            "expected error would highlight `{}` but it actually highlighted `{}`",
4244            &p[src_expected[0]..src_expected[1]],
4245            &p[src_actual[0]..src_actual[1]],
4246        );
4247    }
4248
4249    #[track_caller]
4250    fn assert_err_contains(p: &str, expected: &str) {
4251        let result = crate::parsing::top_level_parse(p);
4252        let err = &result.unwrap_errs().next().unwrap().message;
4253        assert!(err.contains(expected), "actual='{err}'");
4254    }
4255
4256    #[test]
4257    fn test_parse_half_pipe_small() {
4258        assert_err_contains(
4259            "secondExtrude = startSketchOn(XY)
4260  |> startProfile(at = [0,0])
4261  |",
4262            "Unexpected token: |",
4263        );
4264    }
4265
4266    #[test]
4267    fn test_parse_member_expression_double_nested_braces() {
4268        let code = r#"prop = yo["one"][two]"#;
4269        crate::parsing::top_level_parse(code).unwrap();
4270    }
4271
4272    #[test]
4273    fn test_parse_member_expression_binary_expression_period_number_first() {
4274        let code = r#"obj = { a: 1, b: 2 }
4275height = 1 - obj.a"#;
4276        crate::parsing::top_level_parse(code).unwrap();
4277    }
4278
4279    #[test]
4280    fn test_parse_member_expression_allowed_type_in_expression() {
4281        let code = r#"obj = { thing: 1 }
4282startSketchOn(obj.sketch)"#;
4283
4284        crate::parsing::top_level_parse(code).unwrap();
4285    }
4286
4287    #[test]
4288    fn test_parse_member_expression_binary_expression_brace_number_first() {
4289        let code = r#"obj = { a: 1, b: 2 }
4290height = 1 - obj["a"]"#;
4291        crate::parsing::top_level_parse(code).unwrap();
4292    }
4293
4294    #[test]
4295    fn test_parse_member_expression_binary_expression_brace_number_second() {
4296        let code = r#"obj = { a: 1, b: 2 }
4297height = obj["a"] - 1"#;
4298        crate::parsing::top_level_parse(code).unwrap();
4299    }
4300
4301    #[test]
4302    fn test_parse_member_expression_binary_expression_in_array_number_first() {
4303        let code = r#"obj = { a: 1, b: 2 }
4304height = [1 - obj["a"], 0]"#;
4305        crate::parsing::top_level_parse(code).unwrap();
4306    }
4307
4308    #[test]
4309    fn test_parse_member_expression_binary_expression_in_array_number_second() {
4310        let code = r#"obj = { a: 1, b: 2 }
4311height = [obj["a"] - 1, 0]"#;
4312        crate::parsing::top_level_parse(code).unwrap();
4313    }
4314
4315    #[test]
4316    fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
4317        let code = r#"obj = { a: 1, b: 2 }
4318height = [obj["a"] -1, 0]"#;
4319        crate::parsing::top_level_parse(code).unwrap();
4320    }
4321
4322    #[test]
4323    fn test_anon_fn() {
4324        crate::parsing::top_level_parse("foo(num=42, closure=fn(x) { return x + 1 })").unwrap();
4325    }
4326
4327    #[test]
4328    fn test_annotation_fn() {
4329        crate::parsing::top_level_parse(
4330            r#"fn foo() {
4331  @annotated
4332  return 1
4333}"#,
4334        )
4335        .unwrap();
4336    }
4337
4338    #[test]
4339    fn test_annotation_settings() {
4340        crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
4341    }
4342
4343    #[test]
4344    fn test_anon_fn_no_fn() {
4345        assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
4346    }
4347
4348    #[test]
4349    fn test_parse_half_pipe() {
4350        let code = "height = 10
4351
4352firstExtrude = startSketchOn(XY)
4353  |> startProfile(at = [0,0])
4354  |> line(at = [0, 8])
4355  |> line(at = [20, 0])
4356  |> line(at = [0, -8])
4357  |> close()
4358  |> extrude(length=2)
4359
4360secondExtrude = startSketchOn(XY)
4361  |> startProfile(at = [0,0])
4362  |";
4363        assert_err_contains(code, "Unexpected token: |");
4364    }
4365
4366    #[test]
4367    fn test_parse_greater_bang() {
4368        assert_err(">!", "Unexpected token: >", [0, 1]);
4369    }
4370
4371    #[test]
4372    fn test_parse_unlabeled_param_not_allowed() {
4373        assert_err(
4374            "fn f(@x, @y) { return 1 }",
4375            "Only the first parameter can be declared unlabeled",
4376            [9, 11],
4377        );
4378        assert_err(
4379            "fn f(x, @y) { return 1 }",
4380            "Only the first parameter can be declared unlabeled",
4381            [8, 10],
4382        );
4383    }
4384
4385    #[test]
4386    fn test_parse_z_percent_parens() {
4387        assert_err("z%)", "Unexpected token: %", [1, 2]);
4388    }
4389
4390    #[test]
4391    fn test_parse_parens_unicode() {
4392        let result = crate::parsing::top_level_parse("(ޜ");
4393        let KclError::Lexical(details) = result.0.unwrap_err() else {
4394            panic!();
4395        };
4396        // TODO: Better errors when program cannot tokenize.
4397        // https://github.com/KittyCAD/modeling-app/issues/696
4398        assert_eq!(details.message, "found unknown token 'ޜ'");
4399        assert_eq!(details.source_ranges[0].start(), 1);
4400        assert_eq!(details.source_ranges[0].end(), 2);
4401    }
4402
4403    #[test]
4404    fn test_parse_negative_in_array_binary_expression() {
4405        let code = r#"leg1 = 5
4406thickness = 0.56
4407
4408bracket = [-leg2 + thickness, 0]
4409"#;
4410        crate::parsing::top_level_parse(code).unwrap();
4411    }
4412
4413    #[test]
4414    fn test_parse_nested_open_brackets() {
4415        let _ = crate::parsing::top_level_parse(
4416            r#"
4417z(-[["#,
4418        )
4419        .unwrap_errs();
4420    }
4421
4422    #[test]
4423    fn test_parse_weird_new_line_function() {
4424        assert_err(
4425            r#"z
4426(--#"#,
4427            "Unexpected token: (",
4428            [2, 3],
4429        );
4430    }
4431
4432    #[test]
4433    fn test_parse_weird_lots_of_fancy_brackets() {
4434        assert_err(
4435            r#"zz({{{{{{{{)iegAng{{{{{{{##"#,
4436            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
4437            [3, 4],
4438        );
4439    }
4440
4441    #[test]
4442    fn test_parse_weird_close_before_open() {
4443        assert_err_contains(
4444            r#"fn)n
4445e
4446["#,
4447            "expected whitespace, found ')' which is brace",
4448        );
4449    }
4450
4451    #[test]
4452    fn test_parse_weird_close_before_nada() {
4453        assert_err_contains(r#"fn)n-"#, "expected whitespace, found ')' which is brace");
4454    }
4455
4456    #[test]
4457    fn test_parse_weird_lots_of_slashes() {
4458        assert_err_contains(
4459            r#"J///////////o//+///////////P++++*++++++P///////ËŸ
4460++4"#,
4461            "Unexpected token: +",
4462        );
4463    }
4464
4465    #[test]
4466    fn test_optional_param_order() {
4467        for (i, (params, expect_ok)) in [
4468            (
4469                vec![Parameter {
4470                    identifier: Node::no_src(Identifier {
4471                        name: "a".to_owned(),
4472                        digest: None,
4473                    }),
4474                    type_: None,
4475                    default_value: Some(DefaultParamVal::none()),
4476                    labeled: true,
4477                    digest: None,
4478                }],
4479                true,
4480            ),
4481            (
4482                vec![Parameter {
4483                    identifier: Node::no_src(Identifier {
4484                        name: "a".to_owned(),
4485                        digest: None,
4486                    }),
4487                    type_: None,
4488                    default_value: None,
4489                    labeled: true,
4490                    digest: None,
4491                }],
4492                true,
4493            ),
4494            (
4495                vec![
4496                    Parameter {
4497                        identifier: Node::no_src(Identifier {
4498                            name: "a".to_owned(),
4499                            digest: None,
4500                        }),
4501                        type_: None,
4502                        default_value: None,
4503                        labeled: true,
4504                        digest: None,
4505                    },
4506                    Parameter {
4507                        identifier: Node::no_src(Identifier {
4508                            name: "b".to_owned(),
4509                            digest: None,
4510                        }),
4511                        type_: None,
4512                        default_value: Some(DefaultParamVal::none()),
4513                        labeled: true,
4514                        digest: None,
4515                    },
4516                ],
4517                true,
4518            ),
4519            (
4520                vec![
4521                    Parameter {
4522                        identifier: Node::no_src(Identifier {
4523                            name: "a".to_owned(),
4524                            digest: None,
4525                        }),
4526                        type_: None,
4527                        default_value: Some(DefaultParamVal::none()),
4528                        labeled: true,
4529                        digest: None,
4530                    },
4531                    Parameter {
4532                        identifier: Node::no_src(Identifier {
4533                            name: "b".to_owned(),
4534                            digest: None,
4535                        }),
4536                        type_: None,
4537                        default_value: None,
4538                        labeled: true,
4539                        digest: None,
4540                    },
4541                ],
4542                false,
4543            ),
4544        ]
4545        .into_iter()
4546        .enumerate()
4547        {
4548            let actual = optional_after_required(&params);
4549            assert_eq!(actual.is_ok(), expect_ok, "failed test {i}");
4550        }
4551    }
4552
4553    #[test]
4554    fn test_error_keyword_in_variable() {
4555        assert_err(
4556            r#"const let = "thing""#,
4557            "Cannot assign a variable to a reserved keyword: let",
4558            [6, 9],
4559        );
4560    }
4561
4562    #[test]
4563    fn test_error_keyword_in_fn_name() {
4564        assert_err(
4565            r#"fn let = () {}"#,
4566            "Cannot assign a variable to a reserved keyword: let",
4567            [3, 6],
4568        );
4569    }
4570
4571    #[test]
4572    fn test_error_keyword_in_fn_args() {
4573        assert_err(
4574            r#"fn thing = (let) => {
4575    return 1
4576}"#,
4577            "Cannot assign a variable to a reserved keyword: let",
4578            [12, 15],
4579        )
4580    }
4581
4582    #[test]
4583    fn bad_imports() {
4584        assert_err(
4585            r#"import cube from "../cube.kcl""#,
4586            "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4587            [17, 30],
4588        );
4589        assert_err(
4590            r#"import cube from "/cube.kcl""#,
4591            "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4592            [17, 28],
4593        );
4594        assert_err(
4595            r#"import cube from "C:\cube.kcl""#,
4596            "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
4597            [17, 30],
4598        );
4599        assert_err(
4600            r#"import cube from "cube/cube.kcl""#,
4601            "import path to a subdirectory must only refer to main.kcl.",
4602            [17, 32],
4603        );
4604        assert_err(
4605            r#"import * as foo from "dsfs""#,
4606            "as is not the 'from' keyword",
4607            [9, 11],
4608        );
4609        assert_err(
4610            r#"import a from "dsfs" as b"#,
4611            "unsupported import path format",
4612            [14, 20],
4613        );
4614        assert_err(
4615            r#"import * from "dsfs" as b"#,
4616            "unsupported import path format",
4617            [14, 20],
4618        );
4619        assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
4620        assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
4621        assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
4622        assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
4623        assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
4624        assert_err(
4625            r#"import "foo.bar.kcl""#,
4626            "import path is not a valid identifier and must be aliased.",
4627            [7, 20],
4628        );
4629        assert_err(
4630            r#"import "_foo.kcl""#,
4631            "import path is not a valid identifier and must be aliased.",
4632            [7, 17],
4633        );
4634        assert_err(
4635            r#"import "foo-bar.kcl""#,
4636            "import path is not a valid identifier and must be aliased.",
4637            [7, 20],
4638        );
4639    }
4640
4641    #[test]
4642    fn std_fn_decl() {
4643        let code = r#"/// Compute the cosine of a number (in radians).
4644///
4645/// ```
4646/// exampleSketch = startSketchOn(XZ)
4647///   |> startProfile(at = [0, 0])
4648///   |> angledLine(
4649///        angle = 30,
4650///        length = 3 / cos(toRadians(30)),
4651///      )
4652///   |> yLine(endAbsolute = 0)
4653///   |> close(%)
4654/// 
4655/// example = extrude(exampleSketch, length = 5)
4656/// ```
4657@(impl = std_rust)
4658export fn cos(num: number(rad)): number(_) {}"#;
4659        let _ast = crate::parsing::top_level_parse(code).unwrap();
4660    }
4661
4662    #[test]
4663    fn warn_import() {
4664        let some_program_string = r#"import "foo.bad""#;
4665        let (_, errs) = assert_no_err(some_program_string);
4666        assert_eq!(errs.len(), 1, "{errs:#?}");
4667    }
4668
4669    #[test]
4670    fn warn_late_settings() {
4671        let some_program_string = r#"foo = 42
4672@settings(defaultLengthUnit = mm)
4673"#;
4674        let (_, errs) = assert_no_err(some_program_string);
4675        assert_eq!(errs.len(), 1, "{errs:#?}");
4676    }
4677
4678    #[test]
4679    fn warn_unknown_suffix() {
4680        let some_program_string = r#"foo = 42_?
4681"#;
4682        let (_, errs) = assert_no_err(some_program_string);
4683        assert_eq!(errs.len(), 1, "{errs:#?}");
4684    }
4685
4686    #[test]
4687    fn fn_decl_uom_ty() {
4688        let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
4689        let (_, errs) = assert_no_fatal(some_program_string);
4690        assert!(errs.is_empty(), "Expected no errors, found: {errs:?}");
4691    }
4692
4693    #[test]
4694    fn error_underscore() {
4695        let (_, errs) = assert_no_fatal("_foo(a=_blah, b=_)");
4696        assert_eq!(errs.len(), 3, "found: {errs:#?}");
4697    }
4698
4699    #[test]
4700    fn error_double_and() {
4701        let (_, errs) = assert_no_fatal("foo = true && false");
4702        assert_eq!(errs.len(), 1, "found: {errs:#?}");
4703        assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some());
4704    }
4705
4706    #[test]
4707    fn error_type_ascription() {
4708        let (_, errs) = assert_no_fatal("a + b: number");
4709        assert!(errs.is_empty());
4710    }
4711
4712    #[test]
4713    fn zero_param_function() {
4714        let code = r#"
4715        fn firstPrimeNumber() {
4716            return 2
4717        }
4718        firstPrimeNumber()
4719        "#;
4720        let _ast = crate::parsing::top_level_parse(code).unwrap();
4721    }
4722
4723    #[test]
4724    fn array() {
4725        let program = r#"[1, 2, 3]"#;
4726        let module_id = ModuleId::default();
4727        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4728        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4729    }
4730
4731    #[test]
4732    fn array_linesep_trailing_comma() {
4733        let program = r#"[
4734            1,
4735            2,
4736            3,
4737        ]"#;
4738        let module_id = ModuleId::default();
4739        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4740        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4741    }
4742
4743    #[allow(unused)]
4744    #[test]
4745    fn array_linesep_no_trailing_comma() {
4746        let program = r#"[
4747            1,
4748            2,
4749            3
4750        ]"#;
4751        let module_id = ModuleId::default();
4752        let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4753        let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4754    }
4755
4756    #[test]
4757    fn basic_if_else() {
4758        let some_program_string = "if true {
4759            3
4760        } else {
4761            4
4762        }";
4763        let module_id = ModuleId::default();
4764        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4765        let _res = if_expr(&mut tokens.as_slice()).unwrap();
4766    }
4767
4768    #[test]
4769    fn basic_else_if() {
4770        let some_program_string = "else if true {
4771            4
4772        }";
4773        let module_id = ModuleId::default();
4774        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4775        let _res = else_if(&mut tokens.as_slice()).unwrap();
4776    }
4777
4778    #[test]
4779    fn basic_if_else_if() {
4780        let some_program_string = "if true {
4781            3  
4782        } else if true {
4783            4
4784        } else {
4785            5
4786        }";
4787        let module_id = ModuleId::default();
4788        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4789        let _res = if_expr(&mut tokens.as_slice()).unwrap();
4790    }
4791
4792    #[test]
4793    fn test_keyword_ok_in_fn_args_return() {
4794        let some_program_string = r#"fn thing(param) {
4795    return true
4796}
4797
4798thing(false)
4799"#;
4800        crate::parsing::top_level_parse(some_program_string).unwrap();
4801    }
4802
4803    #[test]
4804    fn test_error_define_var_as_function() {
4805        // TODO: https://github.com/KittyCAD/modeling-app/issues/784
4806        // Improve this error message.
4807        // It should say that the compiler is expecting a function expression on the RHS.
4808        assert_err(r#"fn thing = "thing""#, "Unexpected token: \"thing\"", [11, 18]);
4809    }
4810
4811    #[test]
4812    fn random_words_fail() {
4813        let test_program = r#"part001 = startSketchOn(-XZ)
4814    |> startProfile(at = [8.53, 11.8])
4815    asdasd asdasd
4816    |> line(at = [11.12, -14.82])
4817    |> line(at = [-13.27, -6.98])
4818    |> line(at = [-5.09, 12.33])
4819    asdasd
4820"#;
4821        let _ = crate::parsing::top_level_parse(test_program).unwrap_errs();
4822    }
4823
4824    #[test]
4825    fn test_member_expression_sketch() {
4826        let some_program_string = r#"fn cube(pos, scale) {
4827  sg = startSketchOn(XY)
4828  |> startProfile(pos)
4829    |> line(at = [0, scale])
4830    |> line(at = [scale, 0])
4831    |> line(at = [0, -scale])
4832
4833  return sg
4834}
4835
4836b1 = cube(pos=[0,0], scale=10)
4837b2 = cube(pos=[3,3], scale=4)
4838
4839pt1 = b1[0]
4840pt2 = b2[0]
4841"#;
4842        crate::parsing::top_level_parse(some_program_string).unwrap();
4843    }
4844
4845    #[test]
4846    fn test_math_with_stdlib() {
4847        let some_program_string = r#"d2r = pi() / 2
4848let other_thing = 2 * cos(3)"#;
4849        crate::parsing::top_level_parse(some_program_string).unwrap();
4850    }
4851
4852    #[test]
4853    fn test_negative_arguments() {
4854        let some_program_string = r#"fn box(p, h, l, w) {
4855 myBox = startSketchOn(XY)
4856    |> startProfile(p)
4857    |> line(at = [0, l])
4858    |> line(at = [w, 0])
4859    |> line(at = [0, -l])
4860    |> close()
4861    |> extrude(length=h)
4862
4863  return myBox
4864}
4865let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
4866"#;
4867        crate::parsing::top_level_parse(some_program_string).unwrap();
4868    }
4869
4870    #[test]
4871    fn kw_fn() {
4872        for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
4873            let module_id = ModuleId::default();
4874            let tokens = crate::parsing::token::lex(input, module_id).unwrap();
4875            super::program.parse(tokens.as_slice()).unwrap();
4876        }
4877    }
4878
4879    #[test]
4880    fn test_parse_tag_named_std_lib() {
4881        let some_program_string = r#"startSketchOn(XY)
4882    |> startProfile(at = [0, 0])
4883    |> line(%, end = [5, 5], tag = $xLine)
4884"#;
4885        assert_err(
4886            some_program_string,
4887            "Cannot assign a tag to a reserved keyword: xLine",
4888            [86, 92],
4889        );
4890    }
4891
4892    #[test]
4893    fn test_parse_empty_tag_brace() {
4894        let some_program_string = r#"startSketchOn(XY)
4895    |> startProfile(at = [0, 0])
4896    |> line(%, $)
4897    "#;
4898        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4899    }
4900    #[test]
4901    fn test_parse_empty_tag_whitespace() {
4902        let some_program_string = r#"startSketchOn(XY)
4903    |> startProfile(at = [0, 0])
4904    |> line(%, $ ,01)
4905    "#;
4906        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4907    }
4908
4909    #[test]
4910    fn test_parse_empty_tag_comma() {
4911        let some_program_string = r#"startSketchOn(XY)
4912    |> startProfile(at = [0, 0])
4913    |> line(%, $,)
4914    "#;
4915        assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4916    }
4917    #[test]
4918    fn test_parse_tag_starting_with_digit() {
4919        let some_program_string = r#"
4920    startSketchOn(XY)
4921    |> startProfile(at = [0, 0])
4922    |> line(%, $01)"#;
4923        assert_err(
4924            some_program_string,
4925            "Tag names must not start with a number. Tag starts with `01`",
4926            [72, 74],
4927        );
4928    }
4929    #[test]
4930    fn test_parse_tag_including_digit() {
4931        let some_program_string = r#"
4932    startSketchOn(XY)
4933    |> startProfile(at = [0, 0])
4934    |> line(%, tag = $var01)"#;
4935        assert_no_err(some_program_string);
4936    }
4937
4938    #[test]
4939    fn test_parse_param_bool_default() {
4940        let some_program_string = r#"fn patternTransform(
4941  use_original?: boolean = false,
4942) {}"#;
4943        assert_no_err(some_program_string);
4944    }
4945
4946    #[test]
4947    fn parse_function_types() {
4948        let code = r#"foo = x: fn
4949foo = x: fn(number)
4950fn foo(x: fn(): number): fn { return 0 }
4951fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
4952type fn
4953type foo = fn
4954type foo = fn(a: string, b: { f: fn(): any })
4955type foo = fn(a: string, b: {})
4956type foo = fn(a: string, b: { })
4957type foo = fn([fn])
4958type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
4959    "#;
4960        assert_no_err(code);
4961    }
4962    #[test]
4963    fn test_parse_tag_starting_with_bang() {
4964        let some_program_string = r#"startSketchOn(XY)
4965    |> startProfile(at = [0, 0])
4966    |> line(%, $!var,01)
4967    "#;
4968        assert_err(some_program_string, "Tag names must not start with a bang", [67, 68]);
4969    }
4970    #[test]
4971    fn test_parse_tag_starting_with_dollar() {
4972        let some_program_string = r#"startSketchOn(XY)
4973    |> startProfile(at = [0, 0])
4974    |> line(%, $$,01)
4975    "#;
4976        assert_err(some_program_string, "Tag names must not start with a dollar", [67, 68]);
4977    }
4978    #[test]
4979    fn test_parse_tag_starting_with_fn() {
4980        let some_program_string = r#"startSketchOn(XY)
4981    |> startProfile(at = [0, 0])
4982    |> line(%, $fn,01)
4983    "#;
4984        assert_err(some_program_string, "Tag names must not start with a keyword", [67, 69]);
4985    }
4986    #[test]
4987    fn test_parse_tag_starting_with_a_comment() {
4988        let some_program_string = r#"startSketchOn(XY)
4989    |> startProfile(at = [0, 0])
4990    |> line(%, $//
4991    ,01)
4992    "#;
4993        assert_err(
4994            some_program_string,
4995            "Tag names must not start with a lineComment",
4996            [67, 69],
4997        );
4998    }
4999
5000    #[test]
5001    fn test_parse_tag_with_reserved_in_middle_works() {
5002        let some_program_string = r#"
5003    startSketchOn(XY)
5004    |> startProfile(at = [0, 0])
5005    |> line(end = [5, 5], tag = $sketching)
5006    "#;
5007        assert_no_err(some_program_string);
5008    }
5009
5010    #[test]
5011    fn test_parse_array_missing_closing_bracket() {
5012        let some_program_string = r#"
5013sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45, 119.09)"#;
5014        assert_err(
5015            some_program_string,
5016            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5017            [52, 60],
5018        );
5019    }
5020    #[test]
5021    fn test_parse_array_missing_comma() {
5022        let some_program_string = r#"
5023sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 119.09])"#;
5024        assert_err(
5025            some_program_string,
5026            "Unexpected character encountered. You might be missing a comma in between elements.",
5027            [53, 66],
5028        );
5029    }
5030    #[test]
5031    fn test_parse_array_reserved_word_early_exit() {
5032        // since there is an early exit if encountering a reserved word, the error should be about
5033        // that and not the missing comma
5034        let some_program_string = r#"
5035sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 $struct])"#;
5036        assert_err(
5037            some_program_string,
5038            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5039            [52, 53],
5040        );
5041    }
5042    #[test]
5043    fn test_parse_array_random_brace() {
5044        let some_program_string = r#"
5045sketch001 = startSketchOn(XZ) |> startProfile(at = [}])"#;
5046        assert_err(
5047            some_program_string,
5048            "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5049            [52, 53],
5050        );
5051    }
5052
5053    #[test]
5054    fn test_parse_object_missing_closing_brace() {
5055        let some_program_string = r#"{
5056            foo = bar,"#;
5057
5058        assert_err(
5059            some_program_string,
5060            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5061            [0, 23],
5062        );
5063    }
5064    #[test]
5065    fn test_parse_object_reserved_word_early_exit() {
5066        // since there is an early exit if encountering a reserved word, the error should be about
5067        // that and not the missing comma
5068        let some_program_string = r#"{bar = foo struct = man}"#;
5069
5070        assert_err(
5071            some_program_string,
5072            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5073            [0, 1],
5074        );
5075    }
5076    #[test]
5077    fn test_parse_object_missing_comma() {
5078        let some_program_string = r#"{
5079            foo = bar,
5080            bar = foo
5081            bat = man
5082        }"#;
5083
5084        assert_err(
5085            some_program_string,
5086            "Unexpected character encountered. You might be missing a comma in between properties.",
5087            [37, 78],
5088        );
5089    }
5090
5091    #[test]
5092    fn test_parse_object_missing_comma_one_line() {
5093        let some_program_string = r#"{bar = foo bat = man}"#;
5094
5095        assert_err(
5096            some_program_string,
5097            "Unexpected character encountered. You might be missing a comma in between properties.",
5098            [1, 21],
5099        );
5100    }
5101
5102    #[test]
5103    fn test_parse_object_random_bracket() {
5104        let some_program_string = r#"{]}"#;
5105
5106        assert_err(
5107            some_program_string,
5108            "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5109            [0, 1],
5110        );
5111    }
5112
5113    #[test]
5114    fn test_parse_object_shorthand_missing_comma() {
5115        let some_program_string = r#"
5116bar = 1
5117        {
5118            foo = bar,
5119            bar
5120            bat = man
5121        }"#;
5122
5123        assert_err(
5124            some_program_string,
5125            "Unexpected character encountered. You might be missing a comma in between properties.",
5126            [54, 89],
5127        );
5128    }
5129
5130    #[test]
5131    fn test_unary_not_on_keyword_bool() {
5132        let some_program_string = r#"!true"#;
5133        let module_id = ModuleId::default();
5134        let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
5135        let actual = match unary_expression.parse(tokens.as_slice()) {
5136            // Use tokens.as_slice() for parsing
5137            Ok(x) => x,
5138            Err(e) => panic!("{e:?}"),
5139        };
5140        assert_eq!(actual.operator, UnaryOperator::Not);
5141        crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
5142    }
5143
5144    #[test]
5145    fn test_sensible_error_when_missing_rhs_of_kw_arg() {
5146        for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
5147            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5148            let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
5149            let cause = err.inner().cause.as_ref().unwrap();
5150            assert_eq!(
5151                cause.message, "This argument has a label, but no value. Put some value after the equals sign",
5152                "failed test {i}: {program}"
5153            );
5154            assert_eq!(
5155                cause.source_range.start(),
5156                program.find("y").unwrap(),
5157                "failed test {i}: {program}"
5158            );
5159        }
5160    }
5161
5162    #[test]
5163    fn test_sensible_error_when_using_keyword_as_arg_label() {
5164        for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
5165            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5166            let err = match fn_call_kw.parse(tokens.as_slice()) {
5167                Err(e) => e,
5168                Ok(ast) => {
5169                    eprintln!("{ast:#?}");
5170                    panic!("Expected this to error but it didn't");
5171                }
5172            };
5173            let cause = err.inner().cause.as_ref().unwrap();
5174            assert_eq!(
5175                cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
5176                "failed test {i}: {program}"
5177            );
5178            assert_eq!(
5179                cause.source_range.start(),
5180                program.find("fn").unwrap(),
5181                "failed test {i}: {program}"
5182            );
5183        }
5184    }
5185
5186    #[test]
5187    fn test_sensible_error_when_missing_rhs_of_obj_property() {
5188        for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {
5189            let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5190            let err = object.parse(tokens.as_slice()).unwrap_err();
5191            let cause = err.inner().cause.as_ref().unwrap();
5192            assert_eq!(
5193                cause.message, "This property has a label, but no value. Put some value after the equals sign",
5194                "failed test {i}: {program}"
5195            );
5196            assert_eq!(
5197                cause.source_range.start(),
5198                program.rfind('=').unwrap(),
5199                "failed test {i}: {program}"
5200            );
5201        }
5202    }
5203
5204    #[test]
5205    fn test_sensible_error_duplicated_args() {
5206        let program = r#"f(arg = 1, normal = 44, arg = 2)"#;
5207        let (_, mut errs) = assert_no_fatal(program);
5208        assert_eq!(errs.len(), 1);
5209        let err = errs.pop().unwrap();
5210        assert_eq!(
5211            err.message,
5212            "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.",
5213        );
5214    }
5215}
5216
5217#[cfg(test)]
5218mod snapshot_math_tests {
5219    use super::*;
5220
5221    // This macro generates a test function with the given function name.
5222    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
5223    // its parsed AST to a snapshot (kept in this repo in a file under snapshots/ dir)
5224    macro_rules! snapshot_test {
5225        ($func_name:ident, $test_kcl_program:expr) => {
5226            #[test]
5227            fn $func_name() {
5228                let module_id = crate::ModuleId::default();
5229                let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5230                ParseContext::init();
5231
5232                let actual = match binary_expression.parse(tokens.as_slice()) {
5233                    Ok(x) => x,
5234                    Err(_e) => panic!("could not parse test"),
5235                };
5236                insta::assert_json_snapshot!(actual);
5237                let _ = ParseContext::take();
5238            }
5239        };
5240    }
5241
5242    snapshot_test!(a, "1 + 2");
5243    snapshot_test!(b, "1+2");
5244    snapshot_test!(c, "1 -2");
5245    snapshot_test!(d, "1 + 2 * 3");
5246    snapshot_test!(e, "1 * ( 2 + 3 )");
5247    snapshot_test!(f, "1 * ( 2 + 3 ) / 4");
5248    snapshot_test!(g, "1 + ( 2 + 3 ) / 4");
5249    snapshot_test!(h, "1 * (( 2 + 3 ) / 4 + 5 )");
5250    snapshot_test!(i, "1 * ((( 2 + 3 )))");
5251    snapshot_test!(j, "distance * p * FOS * 6 / (sigmaAllow * width)");
5252    snapshot_test!(k, "2 + (((3)))");
5253}
5254
5255#[cfg(test)]
5256mod snapshot_tests {
5257    use super::*;
5258
5259    // This macro generates a test function with the given function name.
5260    // The macro takes a KCL program, ensures it tokenizes and parses, then compares
5261    // its parsed AST to a snapshot (kept in this repo in a file under snapshots/ dir)
5262    macro_rules! snapshot_test {
5263        ($func_name:ident, $test_kcl_program:expr) => {
5264            #[test]
5265            fn $func_name() {
5266                let module_id = crate::ModuleId::default();
5267                println!("{}", $test_kcl_program);
5268                let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5269                print_tokens(tokens.as_slice());
5270                ParseContext::init();
5271                let actual = match program.parse(tokens.as_slice()) {
5272                    Ok(x) => x,
5273                    Err(e) => panic!("could not parse test: {e:?}"),
5274                };
5275                let mut settings = insta::Settings::clone_current();
5276                settings.set_sort_maps(true);
5277                settings.bind(|| {
5278                    insta::assert_json_snapshot!(actual);
5279                });
5280                let _ = ParseContext::take();
5281            }
5282        };
5283    }
5284
5285    snapshot_test!(
5286        a,
5287        r#"boxSketch = startSketchOn(XY)
5288    |> startProfileAt(at = [0, 0])
5289    |> line(at = [0, 10])
5290    |> tangentialArc(end = [-5, 5])
5291    |> line(at = [5, -15])
5292    |> extrude(length=10)
5293"#
5294    );
5295    snapshot_test!(b, "myVar = min(x=5 , y=-legLen(5, z=4))"); // Space before comma
5296
5297    snapshot_test!(c, "myVar = min(x=-legLen(a=5, b=4), y=5)");
5298    snapshot_test!(d, "myVar = 5 + 6 |> myFunc(45)");
5299    snapshot_test!(e, "x = 1 * (3 - 4)");
5300    snapshot_test!(f, r#"x = 1 // this is an inline comment"#);
5301    snapshot_test!(
5302        g,
5303        r#"fn x() {
5304        return sg
5305        return sg
5306      }"#
5307    );
5308    snapshot_test!(d2, r#"x = -leg2 + thickness"#);
5309    snapshot_test!(
5310        h,
5311        r#"obj = { a = 1, b = 2 }
5312    height = 1 - obj.a"#
5313    );
5314    snapshot_test!(
5315        i,
5316        r#"obj = { a = 1, b = 2 }
5317     height = 1 - obj["a"]"#
5318    );
5319    snapshot_test!(
5320        j,
5321        r#"obj = { a = 1, b = 2 }
5322    height = obj["a"] - 1"#
5323    );
5324    snapshot_test!(
5325        k,
5326        r#"obj = { a = 1, b = 2 }
5327    height = [1 - obj["a"], 0]"#
5328    );
5329    snapshot_test!(
5330        l,
5331        r#"obj = { a = 1, b = 2 }
5332    height = [obj["a"] - 1, 0]"#
5333    );
5334    snapshot_test!(
5335        m,
5336        r#"obj = {a = 1, b = 2 }
5337    height = [obj["a"] -1, 0]"#
5338    );
5339    snapshot_test!(n, "height = 1 - obj.a");
5340    snapshot_test!(o, "six = 1 + 2 + 3");
5341    snapshot_test!(p, "five = 3 * 1 + 2");
5342    snapshot_test!(q, r#"height = [ obj["a"], 0 ]"#);
5343    snapshot_test!(
5344        r,
5345        r#"obj = { a = 1, b = 2 }
5346    height = obj["a"]"#
5347    );
5348    snapshot_test!(s, r#"prop = yo["one"][two]"#);
5349    snapshot_test!(t, r#"pt1 = b1[x]"#);
5350    snapshot_test!(u, "prop = yo.one.two.three.four");
5351    snapshot_test!(v, r#"pt1 = b1[0]"#);
5352    snapshot_test!(w, r#"pt1 = b1['zero']"#);
5353    snapshot_test!(x, r#"pt1 = b1.zero"#);
5354    snapshot_test!(y, r#"sg = startSketchOn(XY) |> startProfile(pos)"#);
5355    snapshot_test!(
5356        z,
5357        "sg = startSketchOn(XY)
5358    |> startProfile(pos) |> line([0, -scale])"
5359    );
5360    snapshot_test!(aa, r#"sg = -scale"#);
5361    snapshot_test!(ab, "line(endAbsolute = [0, -1])");
5362    snapshot_test!(
5363        ad,
5364        r#"
5365    fn firstPrimeNumber() {
5366        return 2
5367    }
5368    firstPrimeNumber()"#
5369    );
5370    snapshot_test!(
5371        ae,
5372        r#"fn thing(param) {
5373        return true
5374    }
5375    thing(false)"#
5376    );
5377    snapshot_test!(
5378        af,
5379        r#"mySketch = startSketchOn(XY)
5380        |> startProfile(at = [0,0])
5381        |> line(endAbsolute = [0, 1], tag = $myPath)
5382        |> line(endAbsolute = [1, 1])
5383        |> line(endAbsolute = [1, 0], tag = $rightPath)
5384        |> close()"#
5385    );
5386    snapshot_test!(
5387        ag,
5388        "mySketch = startSketchOn(XY) |> startProfile(at = [0,0]) |> line(endAbsolute = [1, 1]) |> close()"
5389    );
5390    snapshot_test!(ah, "myBox = startSketchOn(XY) |> startProfile(at = p)");
5391    snapshot_test!(ai, r#"myBox = f(1) |> g(2)"#);
5392    snapshot_test!(
5393        aj,
5394        r#"myBox = startSketchOn(XY) |> startProfile(at = p) |> line(end = [0, l])"#
5395    );
5396    snapshot_test!(ak, "line(endAbsolute = [0, 1])");
5397    snapshot_test!(ap, "mySketch = startSketchOn(XY) |> startProfile(at = [0,0])");
5398    snapshot_test!(aq, "log(number = 5, msg = \"hello\", id=aIdentifier)");
5399    snapshot_test!(ar, r#"5 + "a""#);
5400    snapshot_test!(at, "line([0, l])");
5401    snapshot_test!(au, include_str!("../../e2e/executor/inputs/cylinder.kcl"));
5402    snapshot_test!(av, "fn f(angle?) { return default(maybe=angle, otherwise=360) }");
5403    snapshot_test!(
5404        aw,
5405        "numbers = [
5406            1,
5407            // A,
5408            // B,
5409            3,
5410        ]"
5411    );
5412    snapshot_test!(
5413        ax,
5414        "numbers = [
5415            1,
5416            2,
5417            // A,
5418            // B,
5419        ]"
5420    );
5421    snapshot_test!(
5422        ay,
5423        "let props = {
5424            a: 1,
5425            // b: 2,
5426            c: 3,
5427        }"
5428    );
5429    snapshot_test!(
5430        az,
5431        "props = {
5432            a: 1,
5433            // b: 2,
5434            c: 3
5435        }"
5436    );
5437    snapshot_test!(
5438        bb,
5439        r#"
5440my14 = 4 ^ 2 - 3 ^ 2 * 2
5441"#
5442    );
5443    snapshot_test!(
5444        bc,
5445        r#"x = if true {
5446            3
5447        } else {
5448            4
5449        }"#
5450    );
5451    snapshot_test!(
5452        bd,
5453        r#"x = if true {
5454            3
5455        } else if func(radius) {
5456            4
5457        } else {
5458            5
5459        }"#
5460    );
5461    snapshot_test!(be, "x = 3 == 3");
5462    snapshot_test!(bf, "x = 3 != 3");
5463    snapshot_test!(bg, r#"x = 4"#);
5464    snapshot_test!(bh, "obj = {center = [10, 10], radius =5}");
5465    snapshot_test!(
5466        bi,
5467        r#"x = 3
5468        obj = { x, y = 4}"#
5469    );
5470    snapshot_test!(bj, "true");
5471    snapshot_test!(bk, "truee");
5472    snapshot_test!(bl, "x = !true");
5473    snapshot_test!(bm, "x = true & false");
5474    snapshot_test!(bn, "x = true | false");
5475    snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
5476    snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
5477    snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
5478    snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
5479    snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
5480    snapshot_test!(
5481        kw_function_decl_with_default_and_type,
5482        r#"fn foo(x?: number = 2) { return 1 }"#
5483    );
5484    snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#);
5485    snapshot_test!(
5486        kw_function_call_multiline,
5487        r#"val = f(
5488             arg = x,
5489             foo = x,
5490             bar = x,
5491           )"#
5492    );
5493    snapshot_test!(
5494        kw_function_call_multiline_with_comments,
5495        r#"val = f(
5496             arg = x,
5497             // foo = x,
5498             bar = x,
5499           )"#
5500    );
5501    snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#);
5502    snapshot_test!(
5503        array_ranges,
5504        r#"incl = [1..10]
5505        excl = [0..<10]"#
5506    );
5507}
5508
5509#[allow(unused)]
5510#[cfg(test)]
5511pub(crate) fn print_tokens(tokens: TokenSlice) {
5512    for (i, tok) in tokens.iter().enumerate() {
5513        println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
5514    }
5515}