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