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