kcl_lib/parsing/
parser.rs

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