kcl_lib/parsing/
parser.rs

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