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