kcl_lib/parsing/
parser.rs

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