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 DeprecationKind,
18 ast::types::{AscribedExpression, ImportPath, LabelledExpression},
19 token::{NumericSuffix, RESERVED_WORDS},
20};
21use crate::{
22 IMPORT_FILE_EXTENSIONS, MetaSettings, SourceRange, TypedPath,
23 errors::{CompilationError, Severity, Tag},
24 execution::{annotations, types::ArrayLen},
25 parsing::{
26 PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
27 ast::types::{
28 Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, Block,
29 BodyItem, BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
30 FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
31 ItemVisibility, LabeledArg, Literal, LiteralValue, MemberExpression, Name, Node, NodeList, NonCodeMeta,
32 NonCodeNode, NonCodeValue, NumericLiteral, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
33 PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, SketchBlock, SketchVar, TagDeclarator,
34 Type, TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator,
35 VariableKind,
36 },
37 math::BinaryExpressionToken,
38 token::{Token, TokenSlice, TokenType},
39 },
40};
41
42thread_local! {
43 static CTXT: RefCell<Option<ParseContext>> = const { RefCell::new(None) };
45}
46
47const MISSING_ELSE: &str = "This `if` block needs a matching `else` block";
48const IF_ELSE_CANNOT_BE_EMPTY: &str = "`if` and `else` blocks cannot be empty";
49const ELSE_STRUCTURE: &str = "This `else` should be followed by a {, then a block of code, then a }";
50const ELSE_MUST_END_IN_EXPR: &str = "This `else` block needs to end in an expression, which will be the value if no preceding `if` condition was matched";
51
52const KEYWORD_EXPECTING_IDENTIFIER: &str = "Expected an identifier, but found a reserved keyword.";
53
54pub fn run_parser(i: TokenSlice) -> super::ParseResult {
55 let _stats = crate::log::LogPerfStats::new("Parsing");
56 ParseContext::init();
57
58 let result = match program.parse(i) {
59 Ok(result) => Some(result),
60 Err(e) => {
61 ParseContext::err(error_from_winnow_error(e));
62 None
63 }
64 };
65 let ctxt = ParseContext::take();
66 (result, ctxt.errors).into()
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
70enum CodeKind {
71 #[default]
72 Standard,
73 Sketch,
74}
75
76#[derive(Debug, Clone, Default)]
80struct ParseContext {
81 pub errors: Vec<CompilationError>,
82 settings: MetaSettings,
83 code_kind: CodeKind,
84}
85
86impl ParseContext {
87 fn new() -> Self {
88 ParseContext {
89 errors: Vec::new(),
90 settings: Default::default(),
91 code_kind: Default::default(),
92 }
93 }
94
95 fn init() {
97 assert!(CTXT.with_borrow(|ctxt| ctxt.is_none()));
98 CTXT.with_borrow_mut(|ctxt| *ctxt = Some(ParseContext::new()));
99 }
100
101 fn take() -> ParseContext {
104 CTXT.with_borrow_mut(|ctxt| ctxt.take()).unwrap()
105 }
106
107 fn handle_attribute(attr: &Node<Annotation>) {
108 match attr.name() {
111 Some(annotations::SETTINGS) => {
112 CTXT.with_borrow_mut(|ctxt| {
113 let _ = ctxt.as_mut().unwrap().settings.update_from_annotation(attr);
114 });
115 }
116 Some(annotations::WARNINGS) => {
117 }
119 _ => {}
120 }
121 }
122
123 fn err(err: CompilationError) {
125 CTXT.with_borrow_mut(|ctxt| {
126 let errors = &mut ctxt.as_mut().unwrap().errors;
134 for e in errors.iter_mut().rev() {
135 if e.source_range == err.source_range {
136 *e = err;
137 return;
138 }
139 }
140 errors.push(err);
141 });
142 }
143
144 fn warn(mut e: CompilationError) {
146 e.severity = Severity::Warning;
147 Self::err(e);
148 }
149
150 fn experimental(feature: &str, source_range: SourceRange) {
151 CTXT.with_borrow_mut(|ctxt| {
152 let ctxt = ctxt.as_mut().unwrap();
153 let Some(severity) = ctxt.settings.experimental_features.severity() else {
154 return;
155 };
156 let error = CompilationError {
157 source_range,
158 message: format!("Use of {feature} is experimental and may change or be removed."),
159 suggestion: None,
160 severity,
161 tag: crate::errors::Tag::None,
162 };
163
164 ctxt.errors.push(error);
165 });
166 }
167
168 fn is_in_sketch_block() -> bool {
169 CTXT.with_borrow(|ctxt| {
170 let ctxt = ctxt.as_ref().expect("no parse context");
171 ctxt.code_kind == CodeKind::Sketch
172 })
173 }
174
175 fn in_sketch_block<R, F: FnOnce() -> R>(f: F) -> R {
176 let previous_code_kind = CTXT.with_borrow_mut(|ctxt| {
177 let ctxt = ctxt.as_mut().expect("no parse context");
178 std::mem::replace(&mut ctxt.code_kind, CodeKind::Sketch)
179 });
180 let result = f();
181 CTXT.with_borrow_mut(|ctxt| {
182 let ctxt = ctxt.as_mut().expect("no parse context");
183 ctxt.code_kind = previous_code_kind;
184 });
185 result
186 }
187}
188
189#[derive(Debug, Clone)]
194pub(crate) struct ContextError<C = StrContext> {
195 pub context: Vec<C>,
196 pub cause: Option<CompilationError>,
197}
198
199fn error_from_winnow_error(err: winnow::error::ParseError<TokenSlice<'_>, ContextError>) -> CompilationError {
200 let Some(last_token) = err.input().last() else {
201 return CompilationError::fatal(Default::default(), "file is empty");
202 };
203
204 let (input, offset, err) = (err.input(), err.offset(), err.clone().into_inner());
205
206 if let Some(e) = err.cause {
207 return e;
208 }
209
210 if offset >= input.len() {
212 let context = err.context.first();
213 return CompilationError::fatal(
214 last_token.as_source_range(),
215 match context {
216 Some(what) => format!("Unexpected end of file. The compiler {what}"),
217 None => "Unexpected end of file while still parsing".to_owned(),
218 },
219 );
220 }
221
222 let bad_token = input.token(offset);
223 CompilationError::fatal(
226 bad_token.as_source_range(),
227 format!("Unexpected token: {}", bad_token.value),
228 )
229}
230
231impl<C> From<CompilationError> for ContextError<C> {
232 fn from(e: CompilationError) -> Self {
233 Self {
234 context: Default::default(),
235 cause: Some(e),
236 }
237 }
238}
239
240impl<C> std::default::Default for ContextError<C> {
241 fn default() -> Self {
242 Self {
243 context: Default::default(),
244 cause: None,
245 }
246 }
247}
248
249impl<I, C> winnow::error::ParserError<I> for ContextError<C>
250where
251 I: Stream,
252{
253 type Inner = Self;
257
258 fn from_input(_input: &I) -> Self {
260 Self::default()
261 }
262
263 fn into_inner(self) -> winnow::Result<Self::Inner, Self> {
265 Ok(self)
266 }
267}
268
269impl<C, I> winnow::error::AddContext<I, C> for ContextError<C>
270where
271 I: Stream,
272{
273 #[inline]
274 fn add_context(mut self, _input: &I, _input_checkpoint: &<I as Stream>::Checkpoint, ctx: C) -> Self {
275 self.context.push(ctx);
276 self
277 }
278}
279
280impl<C, I> winnow::error::FromExternalError<I, CompilationError> for ContextError<C> {
281 #[inline]
283 fn from_external_error(_input: &I, e: CompilationError) -> Self {
284 let mut err = Self::default();
285 {
286 err.cause = Some(e);
287 }
288 err
289 }
290}
291
292type ModalResult<O, E = ContextError> = winnow::prelude::ModalResult<O, E>;
293
294fn expected(what: &'static str) -> StrContext {
295 StrContext::Expected(StrContextValue::Description(what))
296}
297
298fn program(i: &mut TokenSlice) -> ModalResult<Node<Program>> {
299 let shebang = opt(shebang).parse_next(i)?;
300 let mut out: Node<Program> = function_body.parse_next(i)?.into();
301 out.shebang = shebang;
302
303 out.end -= 1;
307 Ok(out)
308}
309
310fn pipe_surrounded_by_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
311 (
312 repeat(0.., whitespace).map(|_: Vec<_>| ()),
313 pipe_operator,
314 repeat(0.., whitespace).map(|_: Vec<_>| ()),
315 )
316 .parse_next(i)?;
317 Ok(())
318}
319
320fn count_in(target: char, s: &str) -> usize {
322 s.chars().filter(|&c| c == target).count()
323}
324
325fn non_code_node(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
327 fn non_code_node_leading_whitespace(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
330 let leading_whitespace = one_of(TokenType::Whitespace)
331 .context(expected("whitespace, with a newline"))
332 .parse_next(i)?;
333 let has_empty_line = count_in('\n', &leading_whitespace.value) >= 2;
334 non_code_node_no_leading_whitespace
335 .verify_map(|node: Node<NonCodeNode>| match node.inner.value {
336 NonCodeValue::BlockComment { value, style } => Some(Node::new(
337 NonCodeNode {
338 value: if has_empty_line {
339 NonCodeValue::NewLineBlockComment { value, style }
340 } else {
341 NonCodeValue::BlockComment { value, style }
342 },
343 digest: None,
344 },
345 leading_whitespace.start,
346 node.end + 1,
347 node.module_id,
348 )),
349 _ => None,
350 })
351 .context(expected("a comment or whitespace"))
352 .parse_next(i)
353 }
354
355 alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
356}
357
358fn outer_annotation(i: &mut TokenSlice) -> ModalResult<Node<Annotation>> {
359 peek((at_sign, open_paren)).parse_next(i)?;
360 annotation(i)
361}
362
363fn annotation(i: &mut TokenSlice) -> ModalResult<Node<Annotation>> {
364 let at = at_sign.parse_next(i)?;
365 let name = opt(binding_name).parse_next(i)?;
366 let mut end = name.as_ref().map(|n| n.end).unwrap_or(at.end);
367
368 let properties = if peek(open_paren).parse_next(i).is_ok() {
369 open_paren(i)?;
370 ignore_whitespace(i);
371 let properties: Vec<_> = separated(
372 0..,
373 separated_pair(
374 terminated(identifier, opt(whitespace)),
375 terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
376 expression,
377 )
378 .map(|(key, value)| {
379 Node::new_node(
380 key.start,
381 value.end(),
382 key.module_id,
383 ObjectProperty {
384 key,
385 value,
386 digest: None,
387 },
388 )
389 }),
390 comma_sep,
391 )
392 .parse_next(i)?;
393 ignore_trailing_comma(i);
394 ignore_whitespace(i);
395 end = close_paren(i)?.end;
396 Some(properties)
397 } else {
398 None
399 };
400
401 if name.is_none() && properties.is_none() {
402 return Err(ErrMode::Cut(
403 CompilationError::fatal(at.as_source_range(), format!("Unexpected token: {}", at.value)).into(),
404 ));
405 }
406
407 let value = Node::new(
408 Annotation {
409 name,
410 properties,
411 digest: None,
412 },
413 at.start,
414 end,
415 at.module_id,
416 );
417
418 ParseContext::handle_attribute(&value);
419
420 Ok(value)
421}
422
423fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
425 any.verify_map(|token: Token| {
426 if token.is_code_token() {
427 None
428 } else {
429 let value = match token.token_type {
430 TokenType::Whitespace if token.value.contains("\n\n") || token.value.contains("\n\r\n") => {
431 NonCodeValue::NewLine
432 }
433 TokenType::LineComment => NonCodeValue::BlockComment {
434 value: token.value.trim_start_matches("//").trim().to_owned(),
435 style: CommentStyle::Line,
436 },
437 TokenType::BlockComment => NonCodeValue::BlockComment {
438 style: CommentStyle::Block,
439 value: token
440 .value
441 .trim_start_matches("/*")
442 .trim_end_matches("*/")
443 .trim()
444 .to_owned(),
445 },
446 _ => return None,
447 };
448 Some(Node::new(
449 NonCodeNode { value, digest: None },
450 token.start,
451 token.end,
452 token.module_id,
453 ))
454 }
455 })
456 .context(expected("Non-code token (comments or whitespace)"))
457 .parse_next(i)
458}
459
460fn expression(i: &mut TokenSlice) -> ModalResult<Expr> {
461 let head = expression_but_not_pipe.parse_next(i)?;
462 let head_checkpoint = i.checkpoint();
463
464 let noncode: Result<Vec<_>, _> = terminated(
466 repeat(0.., preceded(whitespace, non_code_node)),
467 peek(pipe_surrounded_by_whitespace),
468 )
469 .parse_next(i);
470
471 let Ok(noncode) = noncode else {
472 i.reset(&head_checkpoint);
474 return Ok(head);
475 };
476 let mut non_code_meta = NonCodeMeta::default();
479 for nc in noncode {
480 non_code_meta.insert(0, nc);
481 }
482 let mut values = vec![head];
483
484 let value_surrounded_by_comments = (
485 repeat(0.., preceded(opt(whitespace), non_code_node)),
487 preceded(
489 opt(whitespace),
490 alt((labelled_fn_call, if_expr.map(Expr::IfExpression))),
491 ),
492 repeat(0.., noncode_just_after_code),
494 );
495 let tail: Vec<(Vec<_>, _, Vec<_>)> = repeat(
496 1..,
497 preceded(pipe_surrounded_by_whitespace, value_surrounded_by_comments),
498 )
499 .context(expected(
500 "a sequence of at least one |> (pipe) operator, followed by an expression",
501 ))
502 .parse_next(i)?;
503
504 let mut code_count = 0;
506 let mut max_noncode_end = 0;
507 for (noncode_before, code, noncode_after) in tail {
508 for nc in noncode_before {
509 max_noncode_end = nc.end.max(max_noncode_end);
510 non_code_meta.insert(code_count, nc);
511 }
512 values.push(code);
513 code_count += 1;
514 for nc in noncode_after {
515 max_noncode_end = nc.end.max(max_noncode_end);
516 non_code_meta.insert(code_count, nc);
517 }
518 }
519 Ok(Expr::PipeExpression(Node::boxed(
520 values.first().unwrap().start(),
521 values.last().unwrap().end().max(max_noncode_end),
522 values.first().unwrap().module_id(),
523 PipeExpression {
524 body: values,
525 non_code_meta,
526 digest: None,
527 },
528 )))
529}
530
531fn bool_value(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
532 let (value, token) = any
533 .try_map(|token: Token| match token.token_type {
534 TokenType::Keyword if token.value == "true" => Ok((true, token)),
535 TokenType::Keyword if token.value == "false" => Ok((false, token)),
536 _ => Err(CompilationError::fatal(
537 token.as_source_range(),
538 "invalid boolean literal",
539 )),
540 })
541 .context(expected("a boolean literal (either true or false)"))
542 .parse_next(i)?;
543 Ok(Node::new(
544 Literal {
545 value: LiteralValue::Bool(value),
546 raw: value.to_string(),
547 digest: None,
548 },
549 token.start,
550 token.end,
551 token.module_id,
552 ))
553}
554
555fn minus_sign(i: &mut TokenSlice) -> ModalResult<Token> {
556 any.verify_map(|token: Token| {
557 if token.token_type == TokenType::Operator && token.value == "-" {
558 Some(token)
559 } else {
560 None
561 }
562 })
563 .context(expected("a minus sign `-`"))
564 .parse_next(i)
565}
566
567fn plus_sign(i: &mut TokenSlice) -> ModalResult<Token> {
568 any.verify_map(|token: Token| {
569 if token.token_type == TokenType::Operator && token.value == "+" {
570 Some(token)
571 } else {
572 None
573 }
574 })
575 .context(expected("a plus sign `+`"))
576 .parse_next(i)
577}
578
579fn numeric_literal(i: &mut TokenSlice) -> ModalResult<Node<NumericLiteral>> {
581 let prefix_token = opt(alt((minus_sign, plus_sign))).parse_next(i)?;
582 let is_negative = prefix_token.as_ref().is_some_and(|tok| tok.value == "-");
583 let (value, suffix, number_token) = any
584 .try_map(|token: Token| match token.token_type {
585 TokenType::Number => {
586 let value: f64 = token.numeric_value().ok_or_else(|| {
587 CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
588 })?;
589
590 let suffix = token.numeric_suffix();
591 if let NumericSuffix::Unknown = suffix {
592 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."));
593 }
594
595 Ok((value, suffix, token))
596 }
597 _ => Err(CompilationError::fatal(token.as_source_range(), "invalid number literal")),
598 })
599 .context(expected("a number literal (e.g. 3 or 12.5)"))
600 .parse_next(i)?;
601 let start = prefix_token.as_ref().map(|t| t.start).unwrap_or(number_token.start);
602 Ok(Node::new(
603 NumericLiteral {
604 value: if is_negative { -value } else { value },
605 suffix,
606 raw: format!(
607 "{}{}",
608 prefix_token.map(|t| t.value).unwrap_or_default(),
609 number_token.value
610 ),
611 digest: None,
612 },
613 start,
614 number_token.end,
615 number_token.module_id,
616 ))
617}
618
619fn literal(i: &mut TokenSlice) -> ModalResult<BoxNode<Literal>> {
620 alt((string_literal, unsigned_number_literal, bool_value))
621 .map(Box::new)
622 .context(expected("a KCL literal, like 'myPart' or 3"))
623 .parse_next(i)
624}
625
626fn string_literal(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
628 let (value, token) = any
629 .try_map(|token: Token| match token.token_type {
630 TokenType::String => {
631 let s = token.value[1..token.value.len() - 1].to_string();
632 Ok((LiteralValue::from(s), token))
633 }
634 _ => Err(CompilationError::fatal(
635 token.as_source_range(),
636 "invalid string literal",
637 )),
638 })
639 .context(expected("string literal (like \"myPart\""))
640 .parse_next(i)?;
641
642 let result = Node::new(
643 Literal {
644 value,
645 raw: token.value,
646 digest: None,
647 },
648 token.start,
649 token.end,
650 token.module_id,
651 );
652
653 if let Some(suggestion) = super::deprecation(result.value.string_value().unwrap(), DeprecationKind::String) {
654 ParseContext::warn(
655 CompilationError::err(
656 result.as_source_range(),
657 format!(
658 "Using `\"{}\"` is deprecated, prefer using `{}`.",
659 result.value.string_value().unwrap(),
660 suggestion
661 ),
662 )
663 .with_suggestion(
664 format!(
665 "Replace `\"{}\"` with `{}`",
666 result.value.string_value().unwrap(),
667 suggestion
668 ),
669 suggestion,
670 None,
671 Tag::Deprecated,
672 ),
673 );
674 }
675
676 Ok(result)
677}
678
679pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> ModalResult<Node<Literal>> {
681 let (value, token) = any
682 .try_map(|token: Token| match token.token_type {
683 TokenType::Number => {
684 let value: f64 = token.numeric_value().ok_or_else(|| {
685 CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
686 })?;
687
688 let suffix = token.numeric_suffix();
689 if let NumericSuffix::Unknown = suffix {
690 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."));
691 }
692
693 Ok((
694 LiteralValue::Number {
695 value,
696 suffix,
697 },
698 token,
699 ))
700 }
701 _ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")),
702 })
703 .context(expected("an unsigned number literal (e.g. 3 or 12.5)"))
704 .parse_next(i)?;
705 Ok(Node::new(
706 Literal {
707 value,
708 raw: token.value,
709 digest: None,
710 },
711 token.start,
712 token.end,
713 token.module_id,
714 ))
715}
716
717fn sketch_var(i: &mut TokenSlice) -> ModalResult<Node<SketchVar>> {
718 let var_token = keyword(i, "var")?;
719 let literal = opt(preceded(require_whitespace, numeric_literal)).parse_next(i)?;
720 let end = literal.as_ref().map(|t| t.end).unwrap_or(var_token.end);
721 if !ParseContext::is_in_sketch_block() {
722 ParseContext::experimental(
723 "sketch var",
724 SourceRange::new(var_token.start, end, var_token.module_id),
725 );
726 }
727
728 Ok(Node::new(
729 SketchVar {
730 initial: literal.map(Box::new),
731 digest: None,
732 },
733 var_token.start,
734 end,
735 var_token.module_id,
736 ))
737}
738
739fn binary_operator(i: &mut TokenSlice) -> ModalResult<BinaryOperator> {
741 any.try_map(|token: Token| {
742 if !matches!(token.token_type, TokenType::Operator) {
743 return Err(CompilationError::fatal(
744 token.as_source_range(),
745 format!("unexpected token, should be an operator but was {}", token.token_type),
746 ));
747 }
748 let op = match token.value.as_str() {
749 "+" => BinaryOperator::Add,
750 "-" => BinaryOperator::Sub,
751 "/" => BinaryOperator::Div,
752 "*" => BinaryOperator::Mul,
753 "%" => BinaryOperator::Mod,
754 "^" => BinaryOperator::Pow,
755 "==" => BinaryOperator::Eq,
756 "!=" => BinaryOperator::Neq,
757 ">" => BinaryOperator::Gt,
758 ">=" => BinaryOperator::Gte,
759 "<" => BinaryOperator::Lt,
760 "<=" => BinaryOperator::Lte,
761 "|" => BinaryOperator::Or,
762 "&" => BinaryOperator::And,
763 "||" => {
764 ParseContext::err(
765 CompilationError::err(
766 token.as_source_range(),
767 "`||` is not an operator, did you mean to use `|`?",
768 )
769 .with_suggestion("Replace `||` with `|`", "|", None, Tag::None),
770 );
771 BinaryOperator::Or
772 }
773 "&&" => {
774 ParseContext::err(
775 CompilationError::err(
776 token.as_source_range(),
777 "`&&` is not an operator, did you mean to use `&`?",
778 )
779 .with_suggestion("Replace `&&` with `&`", "&", None, Tag::None),
780 );
781 BinaryOperator::And
782 }
783 _ => {
784 return Err(CompilationError::fatal(
785 token.as_source_range(),
786 format!("{} is not a binary operator", token.value.as_str()),
787 ));
788 }
789 };
790 Ok(op)
791 })
792 .context(expected("a binary operator (like + or *)"))
793 .parse_next(i)
794}
795
796fn operand(i: &mut TokenSlice) -> ModalResult<BinaryPart> {
798 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)";
799 let op = possible_operands
800 .try_map(|part| {
801 let source_range = SourceRange::from(&part);
802 let expr = match part {
803 Expr::FunctionExpression(_)
807 | Expr::PipeExpression(_)
808 | Expr::PipeSubstitution(_)
809 | Expr::LabelledExpression(..)
810 | Expr::SketchBlock(_) => return Err(CompilationError::fatal(source_range, TODO_783)),
811 Expr::None(_) => {
812 return Err(CompilationError::fatal(
813 source_range,
814 "cannot use a KCL None value as an operand",
818 ));
819 }
820 Expr::TagDeclarator(_) => {
821 return Err(CompilationError::fatal(
822 source_range,
823 "cannot use a KCL tag declaration as an operand",
827 ));
828 }
829 Expr::UnaryExpression(x) => BinaryPart::UnaryExpression(x),
830 Expr::Literal(x) => BinaryPart::Literal(x),
831 Expr::Name(x) => BinaryPart::Name(x),
832 Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
833 Expr::CallExpressionKw(x) => BinaryPart::CallExpressionKw(x),
834 Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
835 Expr::ArrayExpression(x) => BinaryPart::ArrayExpression(x),
836 Expr::ArrayRangeExpression(x) => BinaryPart::ArrayRangeExpression(x),
837 Expr::ObjectExpression(x) => BinaryPart::ObjectExpression(x),
838 Expr::IfExpression(x) => BinaryPart::IfExpression(x),
839 Expr::AscribedExpression(x) => BinaryPart::AscribedExpression(x),
840 Expr::SketchVar(x) => BinaryPart::SketchVar(x),
841 };
842 Ok(expr)
843 })
844 .context(expected("an operand (a value which can be used with an operator)"))
845 .parse_next(i)?;
846 Ok(op)
847}
848
849impl TokenType {
850 fn parse_from(self, i: &mut TokenSlice) -> ModalResult<Token> {
851 any.try_map(|token: Token| {
852 if token.token_type == self {
853 Ok(token)
854 } else {
855 Err(CompilationError::fatal(
856 token.as_source_range(),
857 format!(
858 "expected {self} but found {} which is a {}",
859 token.value.as_str(),
860 token.token_type
861 ),
862 ))
863 }
864 })
865 .parse_next(i)
866 }
867}
868
869fn whitespace(i: &mut TokenSlice) -> ModalResult<Vec<Token>> {
871 repeat(
872 1..,
873 any.try_map(|token: Token| {
874 if token.token_type == TokenType::Whitespace {
875 Ok(token)
876 } else {
877 Err(CompilationError::fatal(
878 token.as_source_range(),
879 format!(
880 "expected whitespace, found '{}' which is {}",
881 token.value.as_str(),
882 token.token_type
883 ),
884 ))
885 }
886 }),
887 )
888 .context(expected("some whitespace (e.g. spaces, tabs, new lines)"))
889 .parse_next(i)
890}
891
892fn shebang(i: &mut TokenSlice) -> ModalResult<Node<Shebang>> {
895 hash.parse_next(i)?;
897 let tok = bang.parse_next(i)?;
898 let tokens = take_till(0.., |token: Token| token.value.contains('\n')).parse_next(i)?;
901 let value = tokens.iter().map(|t| t.value.as_str()).collect::<String>();
902
903 if tokens.is_empty() {
904 return Err(ErrMode::Cut(
905 CompilationError::fatal(tok.as_source_range(), "expected a shebang value after #!").into(),
906 ));
907 }
908
909 opt(whitespace).parse_next(i)?;
911
912 Ok(Node::new(
913 Shebang::new(format!("#!{value}")),
914 0,
915 tokens.last().unwrap().end,
916 tokens.first().unwrap().module_id,
917 ))
918}
919
920#[allow(clippy::large_enum_variant)]
921pub enum NonCodeOr<T> {
922 NonCode(Node<NonCodeNode>),
923 Code(T),
924}
925
926fn array(i: &mut TokenSlice) -> ModalResult<Expr> {
928 alt((
929 array_empty.map(Box::new).map(Expr::ArrayExpression),
930 array_end_start.map(Box::new).map(Expr::ArrayRangeExpression),
931 array_elem_by_elem.map(Box::new).map(Expr::ArrayExpression),
932 ))
933 .parse_next(i)
934}
935
936fn array_empty(i: &mut TokenSlice) -> ModalResult<Node<ArrayExpression>> {
938 let open = open_bracket(i)?;
939 let start = open.start;
940 ignore_whitespace(i);
941 let end = close_bracket(i)?.end;
942 Ok(Node::new(
943 ArrayExpression {
944 elements: Default::default(),
945 non_code_meta: Default::default(),
946 digest: None,
947 },
948 start,
949 end,
950 open.module_id,
951 ))
952}
953
954fn array_separator(i: &mut TokenSlice) -> ModalResult<()> {
956 alt((
957 comma_sep,
959 peek((
961 opt(whitespace),
962 repeat(0.., terminated(non_code_node, opt(whitespace))).map(|_: Vec<_>| ()),
963 opt(whitespace),
964 close_bracket,
965 ))
966 .void(),
967 ))
968 .parse_next(i)
969}
970
971pub(crate) fn array_elem_by_elem(i: &mut TokenSlice) -> ModalResult<Node<ArrayExpression>> {
972 let open = open_bracket(i)?;
973 let start = open.start;
974 ignore_whitespace(i);
975 let elements: Vec<_> = repeat(
976 0..,
977 alt((
978 terminated(expression.map(NonCodeOr::Code), array_separator),
979 terminated(non_code_node.map(NonCodeOr::NonCode), opt(whitespace)),
980 )),
981 )
982 .context(expected("array contents, a list of elements (like [1, 2, 3])"))
983 .parse_next(i)?;
984 ignore_trailing_comma(i);
985 ignore_whitespace(i);
986
987 let maybe_end = close_bracket(i).map_err(|e| {
988 if let Ok(mut err) = e.clone().into_inner() {
989 let start_range = open.as_source_range();
990 let end_range = i.as_source_range();
991 err.cause = Some(CompilationError::fatal(
992 SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
993 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
994 ));
995 ErrMode::Cut(err)
996 } else {
997 e
999 }
1000 });
1001
1002 if maybe_end.is_err() {
1003 let maybe_closing_bracket: ModalResult<((), Token)> = peek(repeat_till(
1006 0..,
1007 none_of(|token: Token| {
1008 RESERVED_WORDS
1012 .keys()
1013 .chain([",,", "{", "}", "["].iter())
1014 .any(|word| *word == token.value)
1015 })
1016 .void(),
1017 one_of(|term: Token| term.value == "]"),
1018 ))
1019 .parse_next(i);
1020 let has_closing_bracket = maybe_closing_bracket.is_ok();
1021 if has_closing_bracket {
1022 let start_range = i.as_source_range();
1023 let end_range = maybe_closing_bracket.unwrap().1.as_source_range();
1025 let e = ContextError {
1026 context: vec![],
1027 cause: Some(CompilationError::fatal(
1028 SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
1029 "Unexpected character encountered. You might be missing a comma in between elements.",
1030 )),
1031 };
1032 return Err(ErrMode::Cut(e));
1033 }
1034 }
1035 let end = maybe_end?.end;
1036
1037 let (elements, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = elements.into_iter().enumerate().fold(
1039 (Vec::new(), BTreeMap::new()),
1040 |(mut elements, mut non_code_nodes), (i, e)| {
1041 match e {
1042 NonCodeOr::NonCode(x) => {
1043 non_code_nodes.insert(i, vec![x]);
1044 }
1045 NonCodeOr::Code(x) => {
1046 elements.push(x);
1047 }
1048 }
1049 (elements, non_code_nodes)
1050 },
1051 );
1052 let non_code_meta = NonCodeMeta {
1053 non_code_nodes,
1054 start_nodes: Vec::new(),
1055 digest: None,
1056 };
1057 Ok(Node::new(
1058 ArrayExpression {
1059 elements,
1060 non_code_meta,
1061 digest: None,
1062 },
1063 start,
1064 end,
1065 open.module_id,
1066 ))
1067}
1068
1069fn array_end_start(i: &mut TokenSlice) -> ModalResult<Node<ArrayRangeExpression>> {
1070 let open = open_bracket(i)?;
1071 let start = open.start;
1072 ignore_whitespace(i);
1073 let start_element = expression.parse_next(i)?;
1074 ignore_whitespace(i);
1075 let end_inclusive = alt((end_inclusive_range.map(|_| true), end_exclusive_range.map(|_| false))).parse_next(i)?;
1076 ignore_whitespace(i);
1077 let end_element = expression.parse_next(i)?;
1078 ignore_whitespace(i);
1079 let end = close_bracket(i)?.end;
1080 Ok(Node::new(
1081 ArrayRangeExpression {
1082 start_element,
1083 end_element,
1084 end_inclusive,
1085 digest: None,
1086 },
1087 start,
1088 end,
1089 open.module_id,
1090 ))
1091}
1092
1093fn object_property_same_key_and_val(i: &mut TokenSlice) -> ModalResult<Node<ObjectProperty>> {
1094 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)?;
1095 ignore_whitespace(i);
1096 Ok(Node::new_node(
1097 key.start,
1098 key.end,
1099 key.module_id,
1100 ObjectProperty {
1101 value: Expr::Name(Box::new(key.clone().into())),
1102 key,
1103 digest: None,
1104 },
1105 ))
1106}
1107
1108fn object_property(i: &mut TokenSlice) -> ModalResult<Node<ObjectProperty>> {
1109 let key_token = identifier_or_keyword.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)?;
1110 ignore_whitespace(i);
1111 let sep = alt((colon, equals))
1113 .context(expected(
1114 "`=`, which separates the property's key from the value you're setting it to, e.g. 'height = 4'",
1115 ))
1116 .parse_next(i)?;
1117 ignore_whitespace(i);
1118 let expr = match expression
1119 .context(expected(
1120 "the value which you're setting the property to, e.g. in 'height = 4', the value is 4",
1121 ))
1122 .parse_next(i)
1123 {
1124 Ok(expr) => expr,
1125 Err(_) => {
1126 return Err(ErrMode::Cut(
1127 CompilationError::fatal(
1128 SourceRange::from(sep),
1129 "This property has a label, but no value. Put some value after the equals sign",
1130 )
1131 .into(),
1132 ));
1133 }
1134 };
1135
1136 let key = Node::<Identifier>::try_from(key_token).map_err(|comp_err| {
1139 ErrMode::Cut(ContextError {
1140 context: Default::default(),
1141 cause: Some(CompilationError::err(
1142 comp_err.source_range,
1143 KEYWORD_EXPECTING_IDENTIFIER,
1144 )),
1145 })
1146 })?;
1147
1148 let result = Node::new_node(
1149 key.start,
1150 expr.end(),
1151 key.module_id,
1152 ObjectProperty {
1153 key,
1154 value: expr,
1155 digest: None,
1156 },
1157 );
1158
1159 if sep.token_type == TokenType::Colon {
1160 ParseContext::err(
1161 CompilationError::err(
1162 sep.into(),
1163 "Using `:` to initialize objects is deprecated, prefer using `=`.",
1164 )
1165 .with_suggestion("Replace `:` with `=`", " =", None, Tag::Deprecated),
1166 );
1167 }
1168
1169 Ok(result)
1170}
1171
1172fn property_separator(i: &mut TokenSlice) -> ModalResult<()> {
1174 alt((
1175 comma_sep,
1177 peek((
1179 opt(whitespace),
1180 repeat(0.., terminated(non_code_node, opt(whitespace))).map(|_: Vec<_>| ()),
1181 opt(whitespace),
1182 close_brace,
1183 ))
1184 .void(),
1185 ))
1186 .parse_next(i)
1187}
1188
1189fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<Option<SourceRange>> {
1192 alt((
1193 comma_sep.map(|_| None),
1195 peek((
1197 opt(whitespace),
1198 repeat(0.., terminated(non_code_node, opt(whitespace))).map(|_: Vec<_>| ()),
1199 opt(whitespace),
1200 close_paren,
1201 ))
1202 .void()
1203 .map(|_| None),
1204 whitespace.map(|mut tokens| {
1205 let first_token = tokens.pop().unwrap();
1207 Some(SourceRange::from(&first_token))
1208 }),
1209 ))
1210 .parse_next(i)
1211}
1212
1213pub(crate) fn object(i: &mut TokenSlice) -> ModalResult<Node<ObjectExpression>> {
1215 let open = open_brace(i)?;
1216 let start = open.start;
1217 ignore_whitespace(i);
1218 let properties: Vec<_> = repeat(
1219 0..,
1220 alt((
1221 terminated(non_code_node.map(NonCodeOr::NonCode), opt(whitespace)),
1222 terminated(
1223 alt((object_property, object_property_same_key_and_val)),
1224 property_separator,
1225 )
1226 .map(NonCodeOr::Code),
1227 )),
1228 )
1229 .context(expected(
1230 "a comma-separated list of key-value pairs, e.g. 'height = 4, width = 3'",
1231 ))
1232 .parse_next(i)?;
1233 ignore_trailing_comma(i);
1234 ignore_whitespace(i);
1235
1236 let maybe_end = close_brace(i).map_err(|e| {
1237 if let Ok(mut err) = e.clone().into_inner() {
1238 let start_range = open.as_source_range();
1239 let end_range = i.as_source_range();
1240 err.cause = Some(CompilationError::fatal(
1241 SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
1242 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
1243 ));
1244 ErrMode::Cut(err)
1245 } else {
1246 e
1248 }
1249 });
1250 if maybe_end.is_err() {
1251 let maybe_closing_brace: ModalResult<((), Token)> = peek(repeat_till(
1254 0..,
1255 none_of(|token: Token| {
1256 RESERVED_WORDS
1260 .keys()
1261 .chain([",,", "[", "]", "{"].iter())
1262 .any(|word| *word == token.value)
1263 })
1264 .void(),
1265 one_of(|c: Token| c.value == "}"),
1266 ))
1267 .parse_next(i);
1268 let has_closing_brace = maybe_closing_brace.is_ok();
1269 if has_closing_brace {
1270 let start_range = i.as_source_range();
1271 let end_range = maybe_closing_brace.unwrap().1.as_source_range();
1273
1274 let e = ContextError {
1275 context: vec![],
1276 cause: Some(CompilationError::fatal(
1277 SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
1278 "Unexpected character encountered. You might be missing a comma in between properties.",
1279 )),
1280 };
1281 return Err(ErrMode::Cut(e));
1282 }
1283 }
1284
1285 let end = maybe_end?.end;
1286 let (properties, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = properties.into_iter().enumerate().fold(
1288 (Vec::new(), BTreeMap::new()),
1289 |(mut properties, mut non_code_nodes), (i, e)| {
1290 match e {
1291 NonCodeOr::NonCode(x) => {
1292 non_code_nodes.insert(i, vec![x]);
1293 }
1294 NonCodeOr::Code(x) => {
1295 properties.push(x);
1296 }
1297 }
1298 (properties, non_code_nodes)
1299 },
1300 );
1301
1302 let non_code_meta = NonCodeMeta {
1303 non_code_nodes,
1304 ..Default::default()
1305 };
1306 Ok(Node::new(
1307 ObjectExpression {
1308 properties,
1309 non_code_meta,
1310 digest: None,
1311 },
1312 start,
1313 end,
1314 open.module_id,
1315 ))
1316}
1317
1318fn pipe_sub(i: &mut TokenSlice) -> ModalResult<Node<PipeSubstitution>> {
1320 any.try_map(|token: Token| {
1321 if matches!(token.token_type, TokenType::Operator) && token.value == PIPE_SUBSTITUTION_OPERATOR {
1322 Ok(Node::new(
1323 PipeSubstitution { digest: None },
1324 token.start,
1325 token.end,
1326 token.module_id,
1327 ))
1328 } else {
1329 Err(CompilationError::fatal(
1330 token.as_source_range(),
1331 format!(
1332 "expected a pipe substitution symbol (%) but found {}",
1333 token.value.as_str()
1334 ),
1335 ))
1336 }
1337 })
1338 .context(expected("the substitution symbol, %"))
1339 .parse_next(i)
1340}
1341
1342fn else_if(i: &mut TokenSlice) -> ModalResult<Node<ElseIf>> {
1343 let else_ = any
1344 .try_map(|token: Token| {
1345 if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
1346 Ok(token)
1347 } else {
1348 Err(CompilationError::fatal(
1349 token.as_source_range(),
1350 format!("{} is not 'else'", token.value.as_str()),
1351 ))
1352 }
1353 })
1354 .context(expected("the 'else' keyword"))
1355 .parse_next(i)?;
1356 ignore_whitespace(i);
1357 let _if = any
1358 .try_map(|token: Token| {
1359 if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
1360 Ok(token.start)
1361 } else {
1362 Err(CompilationError::fatal(
1363 token.as_source_range(),
1364 format!("{} is not 'if'", token.value.as_str()),
1365 ))
1366 }
1367 })
1368 .context(expected("the 'if' keyword"))
1369 .parse_next(i)?;
1370 ignore_whitespace(i);
1371 let cond = expression(i)?;
1372 ignore_whitespace(i);
1373 let _ = open_brace(i)?;
1374 let then_val = program
1375 .verify(|block| block.ends_with_expr())
1376 .parse_next(i)
1377 .map(Box::new)?;
1378 ignore_whitespace(i);
1379 let end = close_brace(i)?.end;
1380 ignore_whitespace(i);
1381 Ok(Node::new(
1382 ElseIf {
1383 cond,
1384 then_val,
1385 digest: Default::default(),
1386 },
1387 else_.start,
1388 end,
1389 else_.module_id,
1390 ))
1391}
1392
1393fn if_expr(i: &mut TokenSlice) -> ModalResult<BoxNode<IfExpression>> {
1394 let if_ = any
1395 .try_map(|token: Token| {
1396 if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
1397 Ok(token)
1398 } else {
1399 Err(CompilationError::fatal(
1400 token.as_source_range(),
1401 format!("{} is not 'if'", token.value.as_str()),
1402 ))
1403 }
1404 })
1405 .context(expected("the 'if' keyword"))
1406 .parse_next(i)?;
1407 let _ = whitespace(i)?;
1408 let cond = expression(i).map(Box::new)?;
1409 let _ = whitespace(i)?;
1410 let _ = open_brace(i)?;
1411 ignore_whitespace(i);
1412 let then_val = program
1413 .verify(|block| block.ends_with_expr())
1414 .parse_next(i)
1415 .map_err(|e| e.cut())
1416 .map(Box::new)?;
1417 ignore_whitespace(i);
1418 let _ = close_brace(i)?;
1419 ignore_whitespace(i);
1420 let else_ifs: NodeList<_> = repeat(0.., else_if).parse_next(i)?;
1421
1422 ignore_whitespace(i);
1423
1424 let if_with_no_else = |cond, then_val, else_ifs| {
1427 Ok(Node::boxed(
1428 if_.start,
1429 if_.end,
1430 if_.module_id,
1431 IfExpression {
1432 cond,
1433 then_val,
1434 else_ifs,
1435 final_else: Node::boxed(0, 0, if_.module_id, Default::default()),
1436 digest: Default::default(),
1437 },
1438 ))
1439 };
1440
1441 let else_: ModalResult<_> = any
1443 .try_map(|token: Token| {
1444 if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
1445 Ok(token.start)
1446 } else {
1447 Err(CompilationError::fatal(
1448 token.as_source_range(),
1449 format!("{} is not 'else'", token.value.as_str()),
1450 ))
1451 }
1452 })
1453 .context(expected("the 'else' keyword"))
1454 .parse_next(i);
1455 let Ok(else_) = else_ else {
1456 ParseContext::err(CompilationError::err(if_.as_source_range(), MISSING_ELSE));
1457 return if_with_no_else(cond, then_val, else_ifs);
1458 };
1459 let else_range = SourceRange::new(else_, else_ + 4, if_.module_id);
1460 ignore_whitespace(i);
1461
1462 if open_brace(i).is_err() {
1464 ParseContext::err(CompilationError::err(else_range, ELSE_STRUCTURE));
1465 return if_with_no_else(cond, then_val, else_ifs);
1466 }
1467 ignore_whitespace(i);
1468 let Ok(final_else) = program.parse_next(i).map(Box::new) else {
1469 ParseContext::err(CompilationError::err(else_range, IF_ELSE_CANNOT_BE_EMPTY));
1470 let _ = opt(close_brace).parse_next(i);
1471 return if_with_no_else(cond, then_val, else_ifs);
1472 };
1473 ignore_whitespace(i);
1474
1475 if final_else.body.is_empty() {
1476 ParseContext::err(CompilationError::err(else_range, IF_ELSE_CANNOT_BE_EMPTY));
1477 let _ = opt(close_brace).parse_next(i);
1478 return if_with_no_else(cond, then_val, else_ifs);
1479 }
1480 if !final_else.ends_with_expr() {
1481 ParseContext::err(CompilationError::err(else_range, ELSE_MUST_END_IN_EXPR));
1482 let _ = opt(close_brace).parse_next(i);
1483 return if_with_no_else(cond, then_val, else_ifs);
1484 }
1485
1486 let end = close_brace(i)?.end;
1487 Ok(Node::boxed(
1488 if_.start,
1489 end,
1490 if_.module_id,
1491 IfExpression {
1492 cond,
1493 then_val,
1494 else_ifs,
1495 final_else,
1496 digest: Default::default(),
1497 },
1498 ))
1499}
1500
1501fn function_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
1502 let fn_tok = opt(fun).parse_next(i)?;
1503 ignore_whitespace(i);
1504 let name = opt(binding_name).parse_next(i)?;
1505 ignore_whitespace(i);
1506 let mut result = function_decl.parse_next(i)?;
1507 result.name = name;
1509 if fn_tok.is_none() {
1510 let err = CompilationError::fatal(result.as_source_range(), "Anonymous function requires `fn` before `(`");
1511 return Err(ErrMode::Cut(err.into()));
1512 }
1513 Ok(Expr::FunctionExpression(Box::new(result)))
1514}
1515
1516fn function_decl(i: &mut TokenSlice) -> ModalResult<Node<FunctionExpression>> {
1522 fn return_type(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
1523 colon(i)?;
1524 ignore_whitespace(i);
1525 type_(i)
1526 }
1527
1528 let open = open_paren(i)?;
1529 let start = open.start;
1530 let params = parameters(i)?;
1531 ignore_whitespace(i);
1532 close_paren(i)?;
1533 ignore_whitespace(i);
1534 let return_type = opt(return_type).parse_next(i)?;
1536 ignore_whitespace(i);
1537 let (_, body, close) = parse_block(i)?;
1538 let end = close.end;
1539 let result = Node::new(
1540 FunctionExpression {
1541 name: None,
1542 params,
1543 body: body.into(),
1544 return_type,
1545 digest: None,
1546 },
1547 start,
1548 end,
1549 open.module_id,
1550 );
1551
1552 Ok(result)
1553}
1554
1555fn parse_block(i: &mut TokenSlice) -> ModalResult<(Token, Node<Block>, Token)> {
1557 let brace = open_brace(i)?;
1558 let close: Option<(Vec<Vec<Token>>, Token)> = opt((repeat(0.., whitespace), close_brace)).parse_next(i)?;
1559 let (body, close) = match close {
1560 Some((_, close)) => (
1561 Node::new(Block::default(), brace.end, brace.end, brace.module_id),
1562 close,
1563 ),
1564 None => (function_body(i)?, close_brace(i)?),
1565 };
1566 Ok((brace, body, close))
1567}
1568
1569fn member_expression_dot(i: &mut TokenSlice) -> ModalResult<(Expr, usize, bool)> {
1571 period.parse_next(i)?;
1572 let property = nameable_identifier
1573 .map(Box::new)
1574 .map(|p| {
1575 let ni: Node<Identifier> = *p;
1576 let nn: Node<Name> = ni.into();
1577 Expr::Name(Box::new(nn))
1578 })
1579 .parse_next(i)?;
1580 let end = property.end();
1581 let computed = false;
1582 Ok((property, end, computed))
1583}
1584
1585fn member_expression_subscript(i: &mut TokenSlice) -> ModalResult<(Expr, usize, bool)> {
1586 let _ = open_bracket.parse_next(i)?;
1587 ignore_whitespace(i);
1588 let property = expression.parse_next(i)?;
1589 ignore_whitespace(i);
1590 let end = close_bracket.parse_next(i)?.end;
1591 let computed = true;
1592 Ok((property, end, computed))
1593}
1594
1595fn find_members(i: &mut TokenSlice) -> ModalResult<Vec<(Expr, usize, bool)>> {
1596 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'"));
1598 repeat(1.., member)
1599 .context(expected("a sequence of at least one members/properties"))
1600 .parse_next(i)
1601}
1602
1603fn build_member_expression(object: Expr, mut members: Vec<(Expr, usize, bool)>) -> Node<MemberExpression> {
1606 let (property, end, computed) = members.remove(0);
1610 let start = object.start();
1611 let module_id = object.module_id();
1612 let initial_member_expression = Node::new(
1613 MemberExpression {
1614 object,
1615 computed,
1616 property,
1617 digest: None,
1618 },
1619 start,
1620 end,
1621 module_id,
1622 );
1623
1624 members
1626 .into_iter()
1627 .fold(initial_member_expression, |accumulated, (property, end, computed)| {
1630 Node::new(
1631 MemberExpression {
1632 object: Expr::MemberExpression(Box::new(accumulated)),
1633 computed,
1634 property,
1635 digest: None,
1636 },
1637 start,
1638 end,
1639 module_id,
1640 )
1641 })
1642}
1643
1644fn noncode_just_after_code(i: &mut TokenSlice) -> ModalResult<Node<NonCodeNode>> {
1647 let ws = opt(whitespace).parse_next(i)?;
1648
1649 let (has_newline, has_empty_line) = if let Some(ref ws) = ws {
1651 (
1652 ws.iter().any(|token| token.value.contains('\n')),
1653 ws.iter().any(|token| count_in('\n', &token.value) >= 2),
1654 )
1655 } else {
1656 (false, false)
1657 };
1658
1659 let nc = non_code_node_no_leading_whitespace
1661 .map(|nc| {
1662 if has_empty_line {
1663 let value = match nc.inner.value {
1666 NonCodeValue::BlockComment { value, style } => NonCodeValue::NewLineBlockComment { value, style },
1668 x @ NonCodeValue::InlineComment { .. } => x,
1670 x @ NonCodeValue::NewLineBlockComment { .. } => x,
1671 x @ NonCodeValue::NewLine => x,
1672 };
1673 Node::new(
1674 NonCodeNode { value, ..nc.inner },
1675 nc.start.saturating_sub(1),
1676 nc.end,
1677 nc.module_id,
1678 )
1679 } else if has_newline {
1680 nc
1682 } else {
1683 let value = match nc.inner.value {
1686 NonCodeValue::BlockComment { value, style } => NonCodeValue::InlineComment { value, style },
1688 x @ NonCodeValue::InlineComment { .. } => x,
1690 x @ NonCodeValue::NewLineBlockComment { .. } => x,
1691 x @ NonCodeValue::NewLine => x,
1692 };
1693 Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
1694 }
1695 })
1696 .map(|nc| Node::new(nc.inner, nc.start.saturating_sub(1), nc.end, nc.module_id))
1697 .parse_next(i)?;
1698 Ok(nc)
1699}
1700
1701#[derive(Debug)]
1705#[allow(clippy::large_enum_variant)]
1706enum WithinFunction {
1707 Annotation(Node<Annotation>),
1708 BodyItem((BodyItem, Option<Node<NonCodeNode>>)),
1709 NonCode(Node<NonCodeNode>),
1710}
1711
1712impl WithinFunction {
1713 fn is_newline(&self) -> bool {
1714 match self {
1715 WithinFunction::NonCode(nc) => nc.value == NonCodeValue::NewLine,
1716 _ => false,
1717 }
1718 }
1719}
1720
1721fn body_items_within_function(i: &mut TokenSlice) -> ModalResult<WithinFunction> {
1722 let item = dispatch! {peek(any);
1725 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),
1726 token if token.value == "type" && matches!(token.token_type, TokenType::Keyword) =>
1727 (ty_decl.map(BodyItem::TypeDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1728 token if token.declaration_keyword().is_some() =>
1729 (declaration.map(BodyItem::VariableDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1730 token if token.value == "import" && matches!(token.token_type, TokenType::Keyword) =>
1731 (import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1732 Token { ref value, .. } if value == "return" =>
1733 (return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
1734 token if !token.is_code_token() => {
1735 non_code_node.map(WithinFunction::NonCode)
1736 },
1737 token if token.token_type == TokenType::At => {
1738 annotation.map(WithinFunction::Annotation)
1739 },
1740 _ =>
1741 alt((
1742 (
1743 declaration.map(BodyItem::VariableDeclaration),
1744 opt(noncode_just_after_code)
1745 ).map(WithinFunction::BodyItem),
1746 (
1747 expression_stmt.map(BodyItem::ExpressionStatement),
1748 opt(noncode_just_after_code)
1749 ).map(WithinFunction::BodyItem),
1750 ))
1751 }
1752 .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"))
1753 .parse_next(i)?;
1754 Ok(item)
1755}
1756
1757fn function_body(i: &mut TokenSlice) -> ModalResult<Node<Block>> {
1759 let leading_whitespace_start = alt((
1760 peek(non_code_node).map(|_| None),
1761 opt(whitespace).map(|tok| tok.and_then(|t| t.first().map(|t| (t.start.saturating_sub(1), t.module_id)))),
1765 ))
1766 .parse_next(i)?;
1767
1768 let mut things_within_body = Vec::new();
1769 things_within_body.push(body_items_within_function.parse_next(i)?);
1771
1772 loop {
1789 let last_match_was_empty_line = things_within_body.last().map(|wf| wf.is_newline()).unwrap_or(false);
1790
1791 use winnow::stream::Stream;
1792
1793 let start = i.checkpoint();
1794 let len = i.eof_offset();
1795
1796 let found_ws = ws_with_newline.parse_next(i);
1797
1798 if let Ok(ref ws_token) = found_ws
1802 && (ws_token.value.contains("\n\n") || ws_token.value.contains("\n\r\n"))
1803 {
1804 things_within_body.push(WithinFunction::NonCode(Node::new(
1805 NonCodeNode {
1806 value: NonCodeValue::NewLine,
1807 digest: None,
1808 },
1809 ws_token.start,
1810 ws_token.end,
1811 ws_token.module_id,
1812 )));
1813 }
1814
1815 match (found_ws, last_match_was_empty_line) {
1816 (Ok(_), _) | (_, true) => {
1817 if i.eof_offset() == len && !last_match_was_empty_line {
1822 use winnow::error::ParserError;
1823 return Err(ErrMode::assert(i, "sep parsers must always consume"));
1824 }
1825
1826 match body_items_within_function.parse_next(i) {
1827 Err(ErrMode::Backtrack(_)) => {
1828 i.reset(&start);
1829 break;
1830 }
1831 Err(e) => return Err(e),
1832 Ok(o) => {
1833 things_within_body.push(o);
1834 }
1835 }
1836 }
1837 (Err(ErrMode::Backtrack(_)), _) => {
1838 i.reset(&start);
1839 break;
1840 }
1841 (Err(e), _) => return Err(e),
1842 }
1843 }
1844
1845 let mut body = Vec::new();
1846 let mut inner_attrs = Vec::new();
1847 let mut pending_attrs = Vec::new();
1848 let mut non_code_meta = NonCodeMeta::default();
1849 let mut pending_non_code: Vec<Node<NonCodeNode>> = Vec::new();
1850 let mut end = 0;
1851 let mut start = leading_whitespace_start;
1852
1853 macro_rules! handle_pending_non_code {
1854 ($node: ident) => {
1855 if !pending_non_code.is_empty() {
1856 let start = pending_non_code[0].start;
1857 let force_disoc = matches!(
1858 &pending_non_code.last().unwrap().inner.value,
1859 NonCodeValue::NewLine
1860 );
1861 let mut comments = Vec::new();
1862 for nc in pending_non_code {
1863 match nc.inner.value {
1864 NonCodeValue::BlockComment { value, style } if !force_disoc => {
1865 comments.push(style.render_comment(&value));
1866 }
1867 NonCodeValue::NewLineBlockComment { value, style } if !force_disoc => {
1868 if comments.is_empty() && nc.start != 0 {
1869 comments.push(String::new());
1870 comments.push(String::new());
1871 }
1872 comments.push(style.render_comment(&value));
1873 }
1874 NonCodeValue::NewLine if !force_disoc && !comments.is_empty() => {
1875 comments.push(String::new());
1876 comments.push(String::new());
1877 }
1878 _ => {
1879 if body.is_empty() {
1880 non_code_meta.start_nodes.push(nc);
1881 } else {
1882 non_code_meta.insert(body.len() - 1, nc);
1883 }
1884 }
1885 }
1886 }
1887 $node.set_comments(comments, start);
1888 pending_non_code = Vec::new();
1889 }
1890 };
1891 }
1892
1893 for thing_in_body in things_within_body {
1894 match thing_in_body {
1895 WithinFunction::Annotation(mut attr) => {
1896 if start.is_none() {
1897 start = Some((attr.start, attr.module_id))
1898 }
1899 handle_pending_non_code!(attr);
1900 if attr.is_inner() {
1901 if !body.is_empty() {
1902 ParseContext::warn(CompilationError::err(
1903 attr.as_source_range(),
1904 "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.",
1905 ));
1906 }
1907 inner_attrs.push(attr);
1908 } else {
1909 pending_attrs.push(attr);
1910 }
1911 }
1912 WithinFunction::BodyItem((mut b, maybe_noncode)) => {
1913 if start.is_none() {
1914 start = Some((b.start(), b.module_id()));
1915 }
1916 end = b.end();
1917 if !pending_attrs.is_empty() {
1918 b.set_attrs(pending_attrs);
1919 pending_attrs = Vec::new();
1920 }
1921 handle_pending_non_code!(b);
1922 body.push(b);
1923 if let Some(nc) = maybe_noncode {
1924 end = nc.end;
1925 pending_non_code.push(nc);
1926 }
1927 }
1928 WithinFunction::NonCode(nc) => {
1929 if start.is_none() {
1930 start = Some((nc.start, nc.module_id));
1931 }
1932 end = nc.end;
1933 pending_non_code.push(nc);
1934 }
1935 }
1936 }
1937
1938 let start = start.expect(
1939 "the `things_within_body` vec should have looped at least once, and each loop overwrites `start` if it is None",
1940 );
1941
1942 if !pending_attrs.is_empty() {
1943 for a in pending_attrs {
1944 ParseContext::err(CompilationError::err(
1945 a.as_source_range(),
1946 "Attribute is not attached to any item",
1947 ));
1948 }
1949 return Err(ErrMode::Cut(
1950 CompilationError::fatal(
1951 SourceRange::new(start.0, end, start.1),
1952 "Block contains un-attached attributes",
1953 )
1954 .into(),
1955 ));
1956 }
1957
1958 for nc in pending_non_code {
1959 if body.is_empty() {
1960 non_code_meta.start_nodes.push(nc);
1961 } else {
1962 non_code_meta.insert(body.len() - 1, nc);
1963 }
1964 }
1965
1966 let end_ws = opt(whitespace)
1969 .parse_next(i)?
1970 .and_then(|ws| ws.first().map(|tok| tok.end));
1971 if let Some(end_ws) = end_ws {
1972 end = end.max(end_ws);
1973 }
1974 end += 1;
1975 Ok(Node::new(
1976 Block {
1977 items: body,
1978 non_code_meta,
1979 inner_attrs,
1980 digest: None,
1981 },
1982 start.0,
1983 end,
1984 start.1,
1985 ))
1986}
1987
1988fn import_items(i: &mut TokenSlice) -> ModalResult<NodeList<ImportItem>> {
1989 separated(1.., import_item, comma_sep)
1990 .parse_next(i)
1991 .map_err(|e| e.cut())
1992}
1993
1994fn glob(i: &mut TokenSlice) -> ModalResult<Token> {
1995 one_of((TokenType::Operator, "*"))
1996 .context(expected("the multiple import operator, *"))
1997 .parse_next(i)
1998}
1999
2000pub(super) fn import_stmt(i: &mut TokenSlice) -> ModalResult<BoxNode<ImportStatement>> {
2001 let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2002 .parse_next(i)?
2003 .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2004 let import_token = any
2005 .try_map(|token: Token| {
2006 if matches!(token.token_type, TokenType::Keyword) && token.value == "import" {
2007 Ok(token)
2008 } else {
2009 Err(CompilationError::fatal(
2010 token.as_source_range(),
2011 format!("{} is not the 'import' keyword", token.value.as_str()),
2012 ))
2013 }
2014 })
2015 .context(expected("the 'import' keyword"))
2016 .parse_next(i)?;
2017
2018 let module_id = import_token.module_id;
2019 let start = visibility_token.unwrap_or(import_token).start;
2020
2021 require_whitespace(i)?;
2022
2023 let (mut selector, path) = alt((
2024 string_literal.map(|s| (ImportSelector::None { alias: None }, Some(s))),
2025 glob.map(|t| {
2026 let s = t.as_source_range();
2027 (
2028 ImportSelector::Glob(Node::new((), s.start(), s.end(), s.module_id())),
2029 None,
2030 )
2031 }),
2032 import_items.map(|items| (ImportSelector::List { items }, None)),
2033 ))
2034 .parse_next(i)?;
2035
2036 let path = match path {
2037 Some(path) => path,
2038 None => {
2039 require_whitespace(i)?;
2040 any.try_map(|token: Token| {
2041 if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "from" {
2042 Ok(())
2043 } else {
2044 Err(CompilationError::fatal(
2045 token.as_source_range(),
2046 format!("{} is not the 'from' keyword", token.value.as_str()),
2047 ))
2048 }
2049 })
2050 .context(expected("the 'from' keyword"))
2051 .parse_next(i)
2052 .map_err(|e: ErrMode<ContextError>| e.cut())?;
2053
2054 require_whitespace(i)?;
2055
2056 string_literal(i)?
2057 }
2058 };
2059
2060 let mut end: usize = path.end;
2061
2062 if let ImportSelector::None {
2063 alias: ref mut selector_alias,
2064 } = selector
2065 && let Some(alias) = opt(preceded(
2066 (whitespace, import_as_keyword, whitespace),
2067 identifier.context(expected("an identifier to alias the import")),
2068 ))
2069 .parse_next(i)?
2070 {
2071 end = alias.end;
2072 *selector_alias = Some(alias);
2073 }
2074
2075 let path_string = match path.inner.value {
2076 LiteralValue::String(s) => s,
2077 _ => unreachable!(),
2078 };
2079 let path = validate_path_string(
2080 path_string,
2081 selector.exposes_imported_name(),
2082 SourceRange::new(path.start, path.end, path.module_id),
2083 )?;
2084
2085 if matches!(path, ImportPath::Foreign { .. }) && selector.imports_items() {
2086 return Err(ErrMode::Cut(
2087 CompilationError::fatal(
2088 SourceRange::new(start, end, module_id),
2089 "individual items can only be imported from KCL files",
2090 )
2091 .into(),
2092 ));
2093 }
2094
2095 Ok(Node::boxed(
2096 start,
2097 end,
2098 module_id,
2099 ImportStatement {
2100 selector,
2101 visibility,
2102 path,
2103 digest: None,
2104 },
2105 ))
2106}
2107
2108fn validate_path_string(path_string: String, var_name: bool, path_range: SourceRange) -> ModalResult<ImportPath> {
2112 if path_string.is_empty() {
2113 return Err(ErrMode::Cut(
2114 CompilationError::fatal(path_range, "import path cannot be empty").into(),
2115 ));
2116 }
2117
2118 if var_name
2119 && (path_string.starts_with("_")
2120 || path_string.contains('-')
2121 || path_string.chars().filter(|c| *c == '.').count() > 1)
2122 {
2123 return Err(ErrMode::Cut(
2124 CompilationError::fatal(path_range, "import path is not a valid identifier and must be aliased.").into(),
2125 ));
2126 }
2127
2128 let path = if path_string.ends_with(".kcl") {
2129 if path_string
2130 .chars()
2131 .any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.' && c != '/' && c != '\\')
2132 {
2133 return Err(ErrMode::Cut(
2134 CompilationError::fatal(
2135 path_range,
2136 "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
2137 )
2138 .into(),
2139 ));
2140 }
2141
2142 if path_string.starts_with("..") {
2143 return Err(ErrMode::Cut(
2144 CompilationError::fatal(
2145 path_range,
2146 "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.",
2147 )
2148 .into(),
2149 ));
2150 }
2151
2152 if path_string.starts_with('/') || path_string.starts_with('\\') {
2154 return Err(ErrMode::Cut(
2155 CompilationError::fatal(
2156 path_range,
2157 "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.",
2158 )
2159 .into(),
2160 ));
2161 }
2162
2163 if (path_string.contains('/') || path_string.contains('\\'))
2164 && !(path_string.ends_with("/main.kcl") || path_string.ends_with("\\main.kcl"))
2165 {
2166 return Err(ErrMode::Cut(
2167 CompilationError::fatal(path_range, "import path to a subdirectory must only refer to main.kcl.")
2168 .into(),
2169 ));
2170 }
2171
2172 ImportPath::Kcl {
2173 filename: TypedPath::new(&path_string),
2174 }
2175 } else if path_string.starts_with("std") {
2176 ParseContext::experimental("explicit imports from the standard library", path_range);
2177
2178 let segments: Vec<String> = path_string.split("::").map(str::to_owned).collect();
2179
2180 for s in &segments {
2181 if s.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') || s.starts_with('_') {
2182 return Err(ErrMode::Cut(
2183 CompilationError::fatal(path_range, "invalid path in import statement.").into(),
2184 ));
2185 }
2186 }
2187
2188 if segments.len() > 2 {
2190 return Err(ErrMode::Cut(
2191 CompilationError::fatal(
2192 path_range,
2193 format!("Invalid import path for import from std: {path_string}."),
2194 )
2195 .into(),
2196 ));
2197 }
2198
2199 ImportPath::Std { path: segments }
2200 } else if path_string.contains('.') {
2201 let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
2202 if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_lowercase()) {
2203 ParseContext::warn(CompilationError::err(
2204 path_range,
2205 format!(
2206 "unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}",
2207 IMPORT_FILE_EXTENSIONS.join(", ")
2208 ),
2209 ))
2210 }
2211 ImportPath::Foreign {
2212 path: TypedPath::new(&path_string),
2213 }
2214 } else {
2215 return Err(ErrMode::Cut(
2216 CompilationError::fatal(
2217 path_range,
2218 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(", ")),
2219 )
2220 .into(),
2221 ));
2222 };
2223
2224 Ok(path)
2225}
2226
2227fn import_item(i: &mut TokenSlice) -> ModalResult<Node<ImportItem>> {
2228 let name = nameable_identifier
2229 .context(expected("an identifier to import"))
2230 .parse_next(i)?;
2231 let start = name.start;
2232 let module_id = name.module_id;
2233 let alias = opt(preceded(
2234 (whitespace, import_as_keyword, whitespace),
2235 identifier.context(expected("an identifier to alias the import")),
2236 ))
2237 .parse_next(i)?;
2238 let end = if let Some(ref alias) = alias {
2239 alias.end
2240 } else {
2241 name.end
2242 };
2243 Ok(Node::new(
2244 ImportItem {
2245 name,
2246 alias,
2247 digest: None,
2248 },
2249 start,
2250 end,
2251 module_id,
2252 ))
2253}
2254
2255fn import_as_keyword(i: &mut TokenSlice) -> ModalResult<Token> {
2256 any.try_map(|token: Token| {
2257 if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "as" {
2258 Ok(token)
2259 } else {
2260 Err(CompilationError::fatal(
2261 token.as_source_range(),
2262 format!("{} is not the 'as' keyword", token.value.as_str()),
2263 ))
2264 }
2265 })
2266 .context(expected("the 'as' keyword"))
2267 .parse_next(i)
2268}
2269
2270fn return_stmt(i: &mut TokenSlice) -> ModalResult<Node<ReturnStatement>> {
2272 let ret = any
2273 .try_map(|token: Token| {
2274 if matches!(token.token_type, TokenType::Keyword) && token.value == "return" {
2275 Ok(token)
2276 } else {
2277 Err(CompilationError::fatal(
2278 token.as_source_range(),
2279 format!("{} is not a return keyword", token.value.as_str()),
2280 ))
2281 }
2282 })
2283 .context(expected(
2284 "the 'return' keyword, which ends your function (and becomes this function's value when it's called)",
2285 ))
2286 .parse_next(i)?;
2287 require_whitespace(i)?;
2288 let argument = expression(i)?;
2289 Ok(Node::new_node(
2290 ret.start,
2291 argument.end(),
2292 ret.module_id,
2293 ReturnStatement { argument, digest: None },
2294 ))
2295}
2296
2297fn expression_but_not_pipe(i: &mut TokenSlice) -> ModalResult<Expr> {
2298 let mut expr = alt((
2299 binary_expression.map(Box::new).map(Expr::BinaryExpression),
2300 unary_expression.map(Box::new).map(Expr::UnaryExpression),
2301 expr_allowed_in_pipe_expr,
2302 ))
2303 .context(expected("a KCL value"))
2304 .parse_next(i)?;
2305
2306 let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2307 if let Some((_, _, ty)) = ty {
2308 expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2309 }
2310 let label = opt(label).parse_next(i)?;
2311 match label {
2312 Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
2313 None => Ok(expr),
2314 }
2315}
2316
2317fn label(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2318 let result = preceded(
2319 (whitespace, import_as_keyword, whitespace),
2320 identifier.context(expected("an identifier")),
2321 )
2322 .parse_next(i)?;
2323
2324 ParseContext::experimental(
2325 "`as` for tagging expressions",
2326 SourceRange::new(result.start, result.end, result.module_id),
2327 );
2328
2329 Ok(result)
2330}
2331
2332fn unnecessarily_bracketed(i: &mut TokenSlice) -> ModalResult<Expr> {
2333 delimited(
2334 terminated(open_paren, opt(whitespace)),
2335 expression,
2336 preceded(opt(whitespace), close_paren),
2337 )
2338 .parse_next(i)
2339}
2340
2341fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
2342 let parsed_expr = alt((
2343 bool_value.map(Box::new).map(Expr::Literal),
2344 tag.map(Box::new).map(Expr::TagDeclarator),
2345 literal.map(Expr::Literal),
2346 sketch_var.map(Box::new).map(Expr::SketchVar),
2347 fn_call_or_sketch_block,
2348 name.map(Box::new).map(Expr::Name),
2349 array,
2350 object.map(Box::new).map(Expr::ObjectExpression),
2351 pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
2352 function_expr,
2353 if_expr.map(Expr::IfExpression),
2354 unnecessarily_bracketed,
2355 ))
2356 .context(expected("a KCL expression (but not a pipe expression)"))
2357 .parse_next(i)?;
2358
2359 if let Ok(Some(members)) = opt(find_members).parse_next(i) {
2360 let mem = build_member_expression(parsed_expr, members);
2361 return Ok(Expr::MemberExpression(Box::new(mem)));
2362 }
2363 Ok(parsed_expr)
2364}
2365
2366fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
2367 let mut expr = alt((
2368 if_expr.map(Expr::IfExpression),
2369 unary_expression.map(Box::new).map(Expr::UnaryExpression),
2370 bool_value.map(Box::new).map(Expr::Literal),
2371 literal.map(Expr::Literal),
2372 sketch_var.map(Box::new).map(Expr::SketchVar),
2373 fn_call_or_sketch_block,
2374 name.map(Box::new).map(Expr::Name),
2375 array,
2376 object.map(Box::new).map(Expr::ObjectExpression),
2377 binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
2378 unnecessarily_bracketed,
2379 ))
2380 .context(expected(
2381 "a KCL value which can be used as an argument/operand to an operator",
2382 ))
2383 .parse_next(i)?;
2384 if let Ok(Some(members)) = opt(find_members).parse_next(i) {
2385 let mem = build_member_expression(expr, members);
2386 expr = Expr::MemberExpression(Box::new(mem));
2387 }
2388
2389 let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2390 if let Some((_, _, ty)) = ty {
2391 expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2392 }
2393
2394 Ok(expr)
2395}
2396
2397fn item_visibility(i: &mut TokenSlice) -> ModalResult<(ItemVisibility, Token)> {
2399 any.verify_map(|token: Token| {
2400 if token.token_type == TokenType::Keyword && token.value == "export" {
2401 Some((ItemVisibility::Export, token))
2402 } else {
2403 None
2404 }
2405 })
2406 .context(expected("item visibility, e.g. 'export'"))
2407 .parse_next(i)
2408}
2409
2410fn declaration_keyword(i: &mut TokenSlice) -> ModalResult<(VariableKind, Token)> {
2411 let res = any
2412 .verify_map(|token: Token| token.declaration_keyword().map(|kw| (kw, token)))
2413 .parse_next(i)?;
2414 Ok(res)
2415}
2416
2417fn declaration(i: &mut TokenSlice) -> ModalResult<BoxNode<VariableDeclaration>> {
2419 let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2420 .parse_next(i)?
2421 .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2422 let decl_token = opt(declaration_keyword).parse_next(i)?;
2423 if decl_token.is_some() {
2424 require_whitespace(i)?;
2427 }
2428
2429 let id = binding_name
2430 .context(expected(
2431 "an identifier, which becomes name you're binding the value to",
2432 ))
2433 .parse_next(i)?;
2434 let (kind, mut start) = if let Some((kind, token)) = &decl_token {
2435 (*kind, token.start)
2436 } else {
2437 (VariableKind::Const, id.start)
2438 };
2439 if let Some(token) = visibility_token {
2440 start = token.start;
2441 }
2442
2443 ignore_whitespace(i);
2444
2445 let val =
2446 if kind == VariableKind::Fn {
2447 let eq = opt(equals).parse_next(i)?;
2448 ignore_whitespace(i);
2449
2450 let val = function_decl
2451 .map(|mut func| {
2452 func.name = Some(id.clone());
2454 func
2455 })
2456 .map(Box::new)
2457 .map(Expr::FunctionExpression)
2458 .context(expected("a KCL function expression, like () { return 1 }"))
2459 .parse_next(i);
2460
2461 if let Some(t) = eq {
2462 ParseContext::warn(
2463 CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
2464 .with_suggestion("Remove `=`", "", None, Tag::Unnecessary),
2465 );
2466 }
2467
2468 val
2469 } else {
2470 equals(i)?;
2471 ignore_whitespace(i);
2472
2473 let val = expression
2474 .try_map(|val| {
2475 if matches!(val, Expr::FunctionExpression(_)) {
2479 let fn_end = val.start();
2480 ParseContext::warn(
2481 CompilationError::err(
2482 SourceRange::new(start, fn_end, id.module_id),
2483 "Define a function with `fn name()` instead of assigning the function to a variable",
2484 )
2485 .with_suggestion(
2486 format!("Use `fn {}`", &id.name),
2487 format!("fn {}", &id.name),
2488 Some(SourceRange::new(start, fn_end, id.module_id)),
2489 Tag::None,
2490 ),
2491 );
2492 }
2493 Ok(val)
2494 })
2495 .context(expected("a KCL value, which is being bound to a variable"))
2496 .parse_next(i);
2497
2498 if let Some((_, tok)) = decl_token {
2499 let range_to_remove = SourceRange::new(tok.start, id.start, id.module_id);
2500 ParseContext::err(
2501 CompilationError::err(
2502 tok.as_source_range(),
2503 format!(
2504 "Using `{}` to declare constants is deprecated; no keyword is required",
2505 tok.value
2506 ),
2507 )
2508 .with_suggestion(
2509 format!("Remove `{}`", tok.value),
2510 "",
2511 Some(range_to_remove),
2512 Tag::Deprecated,
2513 ),
2514 );
2515 }
2516
2517 val
2518 }
2519 .map_err(|e| e.cut())?;
2520
2521 let end = val.end();
2522 let module_id = id.module_id;
2523 Ok(Node::boxed(
2524 start,
2525 end,
2526 module_id,
2527 VariableDeclaration {
2528 declaration: Node::new_node(
2529 id.start,
2530 end,
2531 module_id,
2532 VariableDeclarator {
2533 id,
2534 init: val,
2535 digest: None,
2536 },
2537 ),
2538 visibility,
2539 kind,
2540 digest: None,
2541 },
2542 ))
2543}
2544
2545fn ty_decl(i: &mut TokenSlice) -> ModalResult<BoxNode<TypeDeclaration>> {
2546 let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2547 .parse_next(i)?
2548 .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2549
2550 let decl_token = ty(i)?;
2551 let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
2552 whitespace(i)?;
2553
2554 let name = alt((
2555 fun.map(|t| {
2556 Node::new(
2557 Identifier {
2558 name: "fn".to_owned(),
2559 digest: None,
2560 },
2561 t.start,
2562 t.end,
2563 t.module_id,
2564 )
2565 }),
2566 identifier,
2567 ))
2568 .parse_next(i)?;
2569 let mut end = name.end;
2570
2571 let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
2572 ignore_whitespace(i);
2573 open_paren(i)?;
2574 ignore_whitespace(i);
2575 let args: Vec<_> = separated(0.., identifier, comma_sep).parse_next(i)?;
2576 ignore_trailing_comma(i);
2577 ignore_whitespace(i);
2578 end = close_paren(i)?.end;
2579 Some(args)
2580 } else {
2581 None
2582 };
2583
2584 let alias = if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
2585 ignore_whitespace(i);
2586 equals(i)?;
2587 ignore_whitespace(i);
2588 let ty = type_(i)?;
2589
2590 ParseContext::experimental("type aliases", ty.as_source_range());
2591
2592 Some(ty)
2593 } else {
2594 None
2595 };
2596
2597 let module_id = name.module_id;
2598 let result = Node::boxed(
2599 start,
2600 end,
2601 module_id,
2602 TypeDeclaration {
2603 name,
2604 args,
2605 alias,
2606 visibility,
2607 digest: None,
2608 },
2609 );
2610
2611 ParseContext::experimental("type declarations", result.as_source_range());
2612
2613 Ok(result)
2614}
2615
2616impl TryFrom<Token> for Node<Identifier> {
2617 type Error = CompilationError;
2618
2619 fn try_from(token: Token) -> Result<Self, Self::Error> {
2620 if token.token_type == TokenType::Word {
2621 Ok(Node::new(
2622 Identifier {
2623 name: token.value,
2624 digest: None,
2625 },
2626 token.start,
2627 token.end,
2628 token.module_id,
2629 ))
2630 } else {
2631 Err(CompilationError::fatal(
2632 token.as_source_range(),
2633 format!(
2634 "Cannot assign a variable to a reserved keyword: {}",
2635 token.value.as_str()
2636 ),
2637 ))
2638 }
2639 }
2640}
2641
2642fn identifier(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2644 any.try_map(Node::<Identifier>::try_from)
2645 .context(expected("an identifier, e.g. 'width' or 'myPart'"))
2646 .parse_next(i)
2647}
2648
2649fn identifier_or_keyword(i: &mut TokenSlice) -> ModalResult<Token> {
2650 any.verify(|token: &Token| token.token_type == TokenType::Word || token.token_type == TokenType::Keyword)
2651 .context(expected("an identifier or keyword"))
2652 .parse_next(i)
2653}
2654
2655fn nameable_identifier(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
2656 let result = identifier.parse_next(i)?;
2657
2658 if !result.is_nameable() {
2659 let desc = if result.name == "_" {
2660 "Underscores"
2661 } else {
2662 "Names with a leading underscore"
2663 };
2664 ParseContext::err(CompilationError::err(
2665 SourceRange::new(result.start, result.end, result.module_id),
2666 format!("{desc} cannot be referred to, only declared."),
2667 ));
2668 }
2669
2670 Ok(result)
2671}
2672
2673fn name(i: &mut TokenSlice) -> ModalResult<Node<Name>> {
2674 let abs_path = opt(double_colon).parse_next(i)?;
2675 let mut idents: NodeList<Identifier> = separated(1.., nameable_identifier, double_colon)
2676 .parse_next(i)
2677 .map_err(|e| e.backtrack())?;
2678
2679 let mut start = idents[0].start;
2680 if let Some(abs_path) = &abs_path {
2681 start = abs_path.start;
2682 }
2683 let abs_path = abs_path.is_some();
2684
2685 let name = idents.pop().unwrap();
2686 let end = name.end;
2687 let module_id = name.module_id;
2688 let result = Node::new(
2689 Name {
2690 name,
2691 path: idents,
2692 abs_path,
2693 digest: None,
2694 },
2695 start,
2696 end,
2697 module_id,
2698 );
2699
2700 if let Some(suggestion) = super::deprecation(&result.to_string(), DeprecationKind::Const) {
2701 ParseContext::warn(
2702 CompilationError::err(
2703 result.as_source_range(),
2704 format!("Using `{result}` is deprecated, prefer using `{suggestion}`."),
2705 )
2706 .with_suggestion(
2707 format!("Replace `{result}` with `{suggestion}`"),
2708 suggestion,
2709 None,
2710 Tag::Deprecated,
2711 ),
2712 );
2713 }
2714
2715 Ok(result)
2716}
2717
2718impl TryFrom<Token> for Node<TagDeclarator> {
2719 type Error = CompilationError;
2720
2721 fn try_from(token: Token) -> Result<Self, Self::Error> {
2722 match token.token_type {
2723 TokenType::Word => {
2724 Ok(Node::new(
2725 TagDeclarator {
2726 name: token.value,
2728 digest: None,
2729 },
2730 token.start - 1,
2731 token.end,
2732 token.module_id,
2733 ))
2734 }
2735 TokenType::Number => Err(CompilationError::fatal(
2736 token.as_source_range(),
2737 format!(
2738 "Tag names must not start with a number. Tag starts with `{}`",
2739 token.value.as_str()
2740 ),
2741 )),
2742
2743 TokenType::Brace | TokenType::Whitespace | TokenType::Comma => Err(CompilationError::fatal(
2745 token.as_source_range(),
2746 "Tag names must not be empty".to_string(),
2747 )),
2748
2749 TokenType::Type => Err(CompilationError::fatal(
2750 token.as_source_range(),
2751 format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()),
2752 )),
2753
2754 _ => Err(CompilationError::fatal(
2755 token.as_source_range(),
2756 format!("Tag names must not start with a {}", token.token_type),
2760 )),
2761 }
2762 }
2763}
2764
2765fn tag(i: &mut TokenSlice) -> ModalResult<Node<TagDeclarator>> {
2767 dollar.parse_next(i)?;
2768 any.try_map(Node::<TagDeclarator>::try_from)
2769 .context(expected("a tag, e.g. '$seg01' or '$line01'"))
2770 .parse_next(i)
2771 .map_err(|e: ErrMode<ContextError>| e.cut())
2772}
2773
2774fn ignore_whitespace(i: &mut TokenSlice) {
2776 let _: ModalResult<()> = repeat(0.., whitespace).parse_next(i);
2777}
2778
2779fn ignore_trailing_comma(i: &mut TokenSlice) {
2781 let _ = opt(comma).parse_next(i);
2782}
2783
2784fn require_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
2786 repeat(1.., whitespace).parse_next(i)
2787}
2788
2789fn unary_expression(i: &mut TokenSlice) -> ModalResult<Node<UnaryExpression>> {
2790 const EXPECTED: &str = "expected a unary operator (like '-', the negative-numeric operator),";
2791 let (operator, op_token) = any
2792 .try_map(|token: Token| match token.token_type {
2793 TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
2794 TokenType::Operator if token.value == "+" => Ok((UnaryOperator::Plus, token)),
2795 TokenType::Operator => Err(CompilationError::fatal(
2796 token.as_source_range(),
2797 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(),),
2798 )),
2799 TokenType::Bang => Ok((UnaryOperator::Not, token)),
2800 other => Err(CompilationError::fatal( token.as_source_range(), format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) )),
2801 })
2802 .context(expected("a unary expression, e.g. -x or -3"))
2803 .parse_next(i)?;
2804 ignore_whitespace(i);
2805 let argument = operand.parse_next(i)?;
2806 Ok(Node::new_node(
2807 op_token.start,
2808 argument.end(),
2809 op_token.module_id,
2810 UnaryExpression {
2811 operator,
2812 argument,
2813 digest: None,
2814 },
2815 ))
2816}
2817
2818fn binary_expression_tokens(i: &mut TokenSlice) -> ModalResult<Vec<BinaryExpressionToken>> {
2822 let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
2823 let remaining: Vec<_> = repeat(
2824 1..,
2825 (
2826 preceded(opt(whitespace), binary_operator).map(BinaryExpressionToken::from),
2827 preceded(opt(whitespace), operand).map(BinaryExpressionToken::from),
2828 ),
2829 )
2830 .context(expected(
2831 "one or more binary operators (like + or -) and operands for them, e.g. 1 + 2 - 3",
2832 ))
2833 .parse_next(i)?;
2834 let mut out = Vec::with_capacity(1 + 2 * remaining.len());
2835 out.push(first);
2836 out.extend(remaining.into_iter().flat_map(|(a, b)| [a, b]));
2837 Ok(out)
2838}
2839
2840fn binary_expression(i: &mut TokenSlice) -> ModalResult<Node<BinaryExpression>> {
2842 let tokens = binary_expression_tokens.parse_next(i)?;
2844
2845 let expr = super::math::parse(tokens).map_err(|e| ErrMode::Backtrack(e.into()))?;
2848 Ok(expr)
2849}
2850
2851fn binary_expr_in_parens(i: &mut TokenSlice) -> ModalResult<Node<BinaryExpression>> {
2852 let span_with_brackets = bracketed_section.take().parse_next(i)?;
2853 let mut span_no_brackets = span_with_brackets.without_ends();
2854 let expr = binary_expression.parse_next(&mut span_no_brackets)?;
2855 Ok(expr)
2856}
2857
2858fn bracketed_section(i: &mut TokenSlice) -> ModalResult<usize> {
2862 let _ = open_paren.parse_next(i)?;
2864 let mut opened_braces = 1usize;
2865 let mut tokens_examined = 0;
2866 while opened_braces > 0 {
2867 let tok = any.parse_next(i)?;
2868 tokens_examined += 1;
2869 if matches!(tok.token_type, TokenType::Brace) {
2870 if tok.value == "(" {
2871 opened_braces += 1;
2872 } else if tok.value == ")" {
2873 opened_braces -= 1;
2874 }
2875 }
2876 }
2877 Ok(tokens_examined)
2878}
2879
2880fn expression_stmt(i: &mut TokenSlice) -> ModalResult<Node<ExpressionStatement>> {
2882 let val = expression
2883 .context(expected(
2884 "an expression (i.e. a value, or an algorithm for calculating one), e.g. 'x + y' or '3' or 'width * 2'",
2885 ))
2886 .parse_next(i)?;
2887 Ok(Node::new_node(
2888 val.start(),
2889 val.end(),
2890 val.module_id(),
2891 ExpressionStatement {
2892 expression: val,
2893 digest: None,
2894 },
2895 ))
2896}
2897
2898fn some_brace(symbol: &'static str, i: &mut TokenSlice) -> ModalResult<Token> {
2900 one_of((TokenType::Brace, symbol))
2901 .context(expected(symbol))
2902 .parse_next(i)
2903}
2904
2905fn pipe_operator(i: &mut TokenSlice) -> ModalResult<Token> {
2907 one_of((TokenType::Operator, PIPE_OPERATOR))
2908 .context(expected(
2909 "the |> operator, used for 'piping' one function's output into another function's input",
2910 ))
2911 .parse_next(i)
2912}
2913
2914fn ws_with_newline(i: &mut TokenSlice) -> ModalResult<Token> {
2915 one_of(TokenType::Whitespace)
2916 .verify(|token: &Token| token.value.contains('\n'))
2917 .context(expected("a newline, possibly with whitespace"))
2918 .parse_next(i)
2919}
2920
2921fn open_paren(i: &mut TokenSlice) -> ModalResult<Token> {
2923 some_brace("(", i)
2924}
2925
2926fn close_paren(i: &mut TokenSlice) -> ModalResult<Token> {
2928 some_brace(")", i)
2929}
2930
2931fn open_bracket(i: &mut TokenSlice) -> ModalResult<Token> {
2933 some_brace("[", i)
2934}
2935
2936fn close_bracket(i: &mut TokenSlice) -> ModalResult<Token> {
2938 some_brace("]", i)
2939}
2940
2941fn open_brace(i: &mut TokenSlice) -> ModalResult<Token> {
2943 some_brace("{", i)
2944}
2945
2946fn close_brace(i: &mut TokenSlice) -> ModalResult<Token> {
2948 some_brace("}", i)
2949}
2950
2951fn comma(i: &mut TokenSlice) -> ModalResult<()> {
2952 TokenType::Comma.parse_from(i)?;
2953 Ok(())
2954}
2955
2956fn hash(i: &mut TokenSlice) -> ModalResult<()> {
2957 TokenType::Hash.parse_from(i)?;
2958 Ok(())
2959}
2960
2961fn bang(i: &mut TokenSlice) -> ModalResult<Token> {
2962 TokenType::Bang.parse_from(i)
2963}
2964
2965fn dollar(i: &mut TokenSlice) -> ModalResult<()> {
2966 TokenType::Dollar.parse_from(i)?;
2967 Ok(())
2968}
2969
2970fn period(i: &mut TokenSlice) -> ModalResult<()> {
2971 TokenType::Period.parse_from(i)?;
2972 Ok(())
2973}
2974
2975fn end_inclusive_range(i: &mut TokenSlice) -> ModalResult<Token> {
2976 any.try_map(|token: Token| {
2977 if matches!(token.token_type, TokenType::DoublePeriod) {
2978 Ok(token)
2979 } else {
2980 Err(CompilationError::fatal(
2981 token.as_source_range(),
2982 format!(
2983 "expected a '..' (double period) found {} which is {}",
2984 token.value.as_str(),
2985 token.token_type
2986 ),
2987 ))
2988 }
2989 })
2990 .context(expected("the .. operator, used for array ranges like [0..10]"))
2991 .parse_next(i)
2992}
2993
2994fn end_exclusive_range(i: &mut TokenSlice) -> ModalResult<Token> {
2995 any.try_map(|token: Token| {
2996 if matches!(token.token_type, TokenType::DoublePeriodLessThan) {
2997 Ok(token)
2998 } else {
2999 Err(CompilationError::fatal(
3000 token.as_source_range(),
3001 format!("expected a '..<' but found {}", token.value.as_str()),
3002 ))
3003 }
3004 })
3005 .context(expected("the ..< operator, used for array ranges like [0..<10]"))
3006 .parse_next(i)
3007}
3008
3009fn colon(i: &mut TokenSlice) -> ModalResult<Token> {
3010 TokenType::Colon.parse_from(i)
3011}
3012
3013fn semi_colon(i: &mut TokenSlice) -> ModalResult<Token> {
3014 TokenType::SemiColon.parse_from(i)
3015}
3016
3017fn plus(i: &mut TokenSlice) -> ModalResult<Token> {
3018 one_of((TokenType::Operator, "+")).parse_next(i)
3019}
3020
3021fn double_colon(i: &mut TokenSlice) -> ModalResult<Token> {
3022 TokenType::DoubleColon.parse_from(i)
3023}
3024
3025fn equals(i: &mut TokenSlice) -> ModalResult<Token> {
3026 one_of((TokenType::Operator, "="))
3027 .context(expected("the equals operator, ="))
3028 .parse_next(i)
3029}
3030
3031fn question_mark(i: &mut TokenSlice) -> ModalResult<()> {
3032 TokenType::QuestionMark.parse_from(i)?;
3033 Ok(())
3034}
3035
3036fn at_sign(i: &mut TokenSlice) -> ModalResult<Token> {
3037 TokenType::At.parse_from(i)
3038}
3039
3040fn fun(i: &mut TokenSlice) -> ModalResult<Token> {
3041 keyword(i, "fn")
3042}
3043
3044fn ty(i: &mut TokenSlice) -> ModalResult<Token> {
3045 keyword(i, "type")
3046}
3047
3048fn any_keyword(i: &mut TokenSlice) -> ModalResult<Token> {
3049 any.try_map(|token: Token| match token.token_type {
3050 TokenType::Keyword => Ok(token),
3051 _ => Err(CompilationError::fatal(
3052 token.as_source_range(),
3053 "expected some reserved keyword".to_owned(),
3054 )),
3055 })
3056 .parse_next(i)
3057}
3058
3059fn keyword(i: &mut TokenSlice, expected: &str) -> ModalResult<Token> {
3060 any.try_map(|token: Token| match token.token_type {
3061 TokenType::Keyword if token.value == expected => Ok(token),
3062 _ => Err(CompilationError::fatal(
3063 token.as_source_range(),
3064 format!("expected '{expected}', found {}", token.value.as_str(),),
3065 )),
3066 })
3067 .parse_next(i)
3068}
3069
3070fn comma_sep(i: &mut TokenSlice) -> ModalResult<()> {
3072 (opt(whitespace), comma, opt(whitespace))
3073 .context(expected("a comma, optionally followed by whitespace"))
3074 .parse_next(i)?;
3075 Ok(())
3076}
3077
3078fn pipe_sep(i: &mut TokenSlice) -> ModalResult<()> {
3080 (opt(whitespace), one_of((TokenType::Operator, "|")), opt(whitespace)).parse_next(i)?;
3081 Ok(())
3082}
3083
3084fn labeled_argument(i: &mut TokenSlice) -> ModalResult<LabeledArg> {
3085 (
3086 opt((
3087 terminated(nameable_identifier, opt(whitespace)),
3088 terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
3089 )),
3090 expression,
3091 )
3092 .map(|(label, arg)| LabeledArg {
3093 label: label.map(|(l, _)| l),
3094 arg,
3095 })
3096 .parse_next(i)
3097}
3098
3099fn record_ty_field(i: &mut TokenSlice) -> ModalResult<(Node<Identifier>, Node<Type>)> {
3100 (identifier, colon, opt(whitespace), type_)
3101 .map(|(id, _, _, ty)| (id, ty))
3102 .parse_next(i)
3103}
3104
3105fn type_(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
3107 separated(1.., type_not_union, pipe_sep)
3108 .map(|mut tys: Vec<_>| {
3109 if tys.len() == 1
3110 && let Some(ty) = tys.pop()
3111 {
3112 ty
3113 } else {
3114 let start = tys[0].start;
3115 let module_id = tys[0].module_id;
3116 let end = tys.last().unwrap().end;
3117 Node::new(Type::Union { tys }, start, end, module_id)
3118 }
3119 })
3120 .parse_next(i)
3121}
3122
3123fn type_not_union(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
3124 alt((
3125 (
3127 open_brace,
3128 opt(whitespace),
3129 separated(0.., record_ty_field, comma_sep),
3130 opt(comma),
3131 opt(whitespace),
3132 close_brace,
3133 )
3134 .try_map(|(open, _, params, _, _, close)| {
3135 Ok(Node::new(
3136 Type::Object { properties: params },
3137 open.start,
3138 close.end,
3139 open.module_id,
3140 ))
3141 }),
3142 array_type,
3144 primitive_type.map(|t| t.map(Type::Primitive)),
3146 ))
3147 .parse_next(i)
3148}
3149
3150fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
3151 alt((
3152 (
3154 fun,
3155 opt((
3156 delimited(
3158 open_paren,
3159 opt(alt((
3160 (
3162 type_,
3163 comma,
3164 opt(whitespace),
3165 separated(
3166 1..,
3167 (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
3168 comma_sep,
3169 ),
3170 )
3171 .map(|(t, _, _, args)| (Some(t), args)),
3172 separated(
3174 1..,
3175 (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
3176 comma_sep,
3177 )
3178 .map(|args| (None, args)),
3179 type_.map(|t| (Some(t), Vec::new())),
3181 ))),
3182 close_paren,
3183 ),
3184 opt((colon, opt(whitespace), type_)),
3186 )),
3187 )
3188 .map(|(t, tys)| {
3189 let mut ft = FunctionType::empty_fn_type();
3190
3191 if let Some((args, ret)) = tys {
3192 if let Some((unnamed, named)) = args {
3193 if let Some(unnamed) = unnamed {
3194 ft.unnamed_arg = Some(Box::new(unnamed));
3195 }
3196 ft.named_args = named;
3197 }
3198 if let Some((_, _, ty)) = ret {
3199 ft.return_type = Some(Box::new(ty));
3200 }
3201 }
3202
3203 Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
3204 }),
3205 (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
3207 let result = Node::new_node(
3208 ident.start,
3209 ident.end,
3210 ident.module_id,
3211 PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident }),
3212 );
3213
3214 if *result == PrimitiveType::None {
3215 ParseContext::experimental("none type", result.as_source_range());
3216 }
3217
3218 result
3219 }),
3220 ))
3221 .parse_next(i)
3222}
3223
3224fn array_type(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
3225 fn opt_whitespace(i: &mut TokenSlice) -> ModalResult<()> {
3226 ignore_whitespace(i);
3227 Ok(())
3228 }
3229
3230 open_bracket(i)?;
3231 let ty = type_(i)?;
3232 let len = opt((
3233 semi_colon,
3234 opt_whitespace,
3235 any.try_map(|token: Token| match token.token_type {
3236 TokenType::Number => {
3237 let value = token.uint_value().ok_or_else(|| {
3238 CompilationError::fatal(
3239 token.as_source_range(),
3240 format!("Expected unsigned integer literal, found: {}", token.value),
3241 )
3242 })?;
3243
3244 Ok(value as usize)
3245 }
3246 _ => Err(CompilationError::fatal(token.as_source_range(), "invalid array length")),
3247 }),
3248 opt(plus),
3249 ))
3250 .parse_next(i)?;
3251 close_bracket(i)?;
3252
3253 let len = if let Some((_, _, n, plus)) = len {
3254 if plus.is_some() {
3255 ArrayLen::Minimum(n)
3256 } else {
3257 ArrayLen::Known(n)
3258 }
3259 } else {
3260 ArrayLen::None
3261 };
3262
3263 Ok(ty.map(|ty| Type::Array { ty: Box::new(ty), len }))
3264}
3265
3266fn uom_for_type(i: &mut TokenSlice) -> ModalResult<NumericSuffix> {
3267 any.try_map(|t: Token| t.value.parse()).parse_next(i)
3268}
3269
3270fn comment(i: &mut TokenSlice) -> ModalResult<Node<String>> {
3271 any.verify_map(|token: Token| {
3272 let value = match token.token_type {
3273 TokenType::LineComment => token.value,
3274 TokenType::BlockComment => token.value,
3275 _ => return None,
3276 };
3277 Some(Node::new(value, token.start, token.end, token.module_id))
3278 })
3279 .context(expected("Comment"))
3280 .parse_next(i)
3281}
3282
3283fn comments(i: &mut TokenSlice) -> ModalResult<Node<Vec<String>>> {
3284 let comments: Vec<Node<String>> = repeat(1.., (comment, opt(whitespace)).map(|(c, _)| c)).parse_next(i)?;
3285 let start = comments[0].start;
3286 let module_id = comments[0].module_id;
3287 let end = comments.last().unwrap().end;
3288 let inner = comments.into_iter().map(|n| n.inner).collect();
3289 Ok(Node::new(inner, start, end, module_id))
3290}
3291
3292struct ParamDescription {
3293 labeled: bool,
3294 arg_name: Token,
3295 type_: std::option::Option<Node<Type>>,
3296 default_value: Option<DefaultParamVal>,
3297 attr: Option<Node<Annotation>>,
3298 comments: Option<Node<Vec<String>>>,
3299}
3300
3301fn parameter(i: &mut TokenSlice) -> ModalResult<ParamDescription> {
3302 let (_, comments, _, attr, _, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
3303 opt(whitespace),
3304 opt(comments),
3305 opt(whitespace),
3306 opt(outer_annotation),
3307 opt(whitespace),
3308 opt(at_sign),
3309 any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
3310 opt(question_mark),
3311 opt(whitespace),
3312 opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
3313 opt(whitespace),
3314 opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
3315 )
3316 .parse_next(i)?;
3317
3318 Ok(ParamDescription {
3319 labeled: found_at_sign.is_none(),
3320 arg_name,
3321 type_,
3322 default_value: match (question_mark.is_some(), default_literal) {
3323 (true, Some(lit)) => Some(DefaultParamVal::Literal(*lit)),
3324 (true, None) => Some(DefaultParamVal::none()),
3325 (false, None) => None,
3326 (false, Some(lit)) => {
3327 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.";
3328 let e = CompilationError::fatal((&lit).into(), msg);
3329 return Err(ErrMode::Backtrack(ContextError::from(e)));
3330 }
3331 },
3332 attr,
3333 comments,
3334 })
3335}
3336
3337fn parameters(i: &mut TokenSlice) -> ModalResult<Vec<Parameter>> {
3339 let candidates: Vec<_> = separated(0.., parameter, comma_sep)
3341 .context(expected("function parameters"))
3342 .parse_next(i)?;
3343 opt(comma_sep).parse_next(i)?;
3344
3345 let params: Vec<Parameter> = candidates
3347 .into_iter()
3348 .map(
3349 |ParamDescription {
3350 labeled,
3351 arg_name,
3352 type_,
3353 default_value,
3354 attr,
3355 comments,
3356 }| {
3357 let mut identifier = Node::<Identifier>::try_from(arg_name)?;
3358 if let Some(comments) = comments {
3359 identifier.comment_start = comments.start;
3360 identifier.pre_comments = comments.inner;
3361 }
3362 if let Some(attr) = attr {
3363 identifier.outer_attrs.push(attr);
3364 }
3365
3366 Ok(Parameter {
3367 identifier,
3368 param_type: type_,
3369 default_value,
3370 labeled,
3371 digest: None,
3372 })
3373 },
3374 )
3375 .collect::<Result<_, _>>()
3376 .map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
3377
3378 if let Some(param) = params.iter().skip(1).find(|param| !param.labeled) {
3380 let source_range = SourceRange::from(param);
3381 return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
3382 source_range,
3383 "Only the first parameter can be declared unlabeled",
3384 ))));
3385 }
3386
3387 if let Err(e) = optional_after_required(¶ms) {
3389 return Err(ErrMode::Cut(ContextError::from(e)));
3390 }
3391 Ok(params)
3392}
3393
3394fn optional_after_required(params: &[Parameter]) -> Result<(), CompilationError> {
3395 let mut found_optional = false;
3396 for p in params {
3397 if p.optional() {
3398 found_optional = true;
3399 }
3400 if !p.optional() && found_optional {
3401 let e = CompilationError::fatal(
3402 (&p.identifier).into(),
3403 "mandatory parameters must be declared before optional parameters",
3404 );
3405 return Err(e);
3406 }
3407 }
3408 Ok(())
3409}
3410
3411fn binding_name(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
3413 identifier
3414 .context(expected("an identifier, which will be the name of some value"))
3415 .parse_next(i)
3416}
3417
3418fn labelled_fn_call(i: &mut TokenSlice) -> ModalResult<Expr> {
3419 let expr = fn_call_or_sketch_block.parse_next(i)?;
3420
3421 let label = opt(label).parse_next(i)?;
3422 match label {
3423 Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
3424 None => Ok(expr),
3425 }
3426}
3427
3428fn is_callee_sketch_block(callee: &Name) -> bool {
3429 callee.name.name == SketchBlock::CALLEE_NAME && !callee.abs_path && callee.path.is_empty()
3430}
3431
3432fn fn_call_or_sketch_block(i: &mut TokenSlice) -> ModalResult<Expr> {
3433 let fn_call = fn_call_kw.parse_next(i)?;
3434 if is_callee_sketch_block(&fn_call.callee) && peek((opt(whitespace), open_brace)).parse_next(i).is_ok() {
3435 ignore_whitespace(i);
3437 let (_, body, close) = ParseContext::in_sketch_block(|| parse_block(i))?;
3438 let end = close.end;
3439
3440 let Node {
3441 start,
3442 end: _,
3443 module_id,
3444 pre_comments,
3445 comment_start,
3446 outer_attrs,
3447 inner:
3448 CallExpressionKw {
3449 callee: _,
3450 unlabeled,
3451 arguments,
3452 non_code_meta,
3453 digest: _,
3454 },
3455 } = fn_call;
3456 ParseContext::experimental("sketch blocks", SourceRange::new(start, end, module_id));
3457 if let Some(unlabeled) = unlabeled {
3458 ParseContext::err(CompilationError::err(
3459 unlabeled.into(),
3460 "Sketch blocks cannot have an unlabeled argument. Use a labeled argument instead, like `on = XY`.",
3461 ));
3462 }
3463 return Ok(Expr::SketchBlock(Box::new(Node {
3464 start,
3465 end,
3466 module_id,
3467 outer_attrs,
3468 pre_comments,
3469 comment_start,
3470 inner: SketchBlock {
3471 arguments,
3472 body,
3473 is_being_edited: false,
3474 non_code_meta,
3475 digest: None,
3476 },
3477 })));
3478 }
3479 Ok(Expr::CallExpressionKw(Box::new(fn_call)))
3480}
3481
3482fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
3483 let fn_name = name(i)?;
3484 opt(whitespace).parse_next(i)?;
3485 let _ = open_paren.parse_next(i)?;
3486 ignore_whitespace(i);
3487
3488 let checkpoint = i.checkpoint();
3490 let mut initial_unlabeled_arg = opt(expression).parse_next(i)?;
3491
3492 if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
3495 i.reset(&checkpoint);
3496 initial_unlabeled_arg = None;
3497 } else {
3498 let early_close = peek((opt(whitespace), close_paren)).parse_next(i);
3499 if early_close.is_ok() {
3500 ignore_whitespace(i);
3501 let end = close_paren.parse_next(i)?.end;
3502 let result = Node::new_node(
3503 fn_name.start,
3504 end,
3505 fn_name.module_id,
3506 CallExpressionKw {
3507 callee: fn_name,
3508 unlabeled: initial_unlabeled_arg,
3509 arguments: Default::default(),
3510 digest: None,
3511 non_code_meta: Default::default(),
3512 },
3513 );
3514 return Ok(result);
3515 }
3516 }
3517
3518 if initial_unlabeled_arg.is_some() {
3519 labeled_arg_separator.parse_next(i)?;
3522 }
3523
3524 #[derive(Debug)]
3525 #[allow(clippy::large_enum_variant)]
3526 enum ArgPlace {
3527 NonCode(Node<NonCodeNode>),
3528 LabeledArg((LabeledArg, Option<SourceRange>)),
3529 UnlabeledArg(Expr),
3530 Keyword(Token),
3531 }
3532 let args: Vec<_> = repeat(
3533 0..,
3534 alt((
3535 terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
3536 terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
3537 (labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
3538 expression.map(ArgPlace::UnlabeledArg),
3539 )),
3540 )
3541 .parse_next(i)?;
3542 let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().try_fold(
3543 (Vec::new(), BTreeMap::new()),
3544 |(mut args, mut non_code_nodes), (index, e)| {
3545 match e {
3546 ArgPlace::NonCode(x) => {
3547 non_code_nodes.insert(index, vec![x]);
3548 }
3549 ArgPlace::LabeledArg((x, bad_token_source_range)) => {
3550 if let Some(bad_token_source_range) = bad_token_source_range {
3551 return Err(ErrMode::Cut(
3552 CompilationError::fatal(
3553 bad_token_source_range,
3554 "Missing comma between arguments, try adding a comma in",
3555 )
3556 .into(),
3557 ));
3558 }
3559 args.push(x);
3560 }
3561 ArgPlace::Keyword(kw) => {
3562 return Err(ErrMode::Cut(
3563 CompilationError::fatal(
3564 SourceRange::from(kw.clone()),
3565 format!(
3566 "`{}` is not the name of an argument (it's a reserved keyword)",
3567 kw.value
3568 ),
3569 )
3570 .into(),
3571 ));
3572 }
3573 ArgPlace::UnlabeledArg(arg) => {
3574 let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
3575 if followed_by_equals {
3576 return Err(ErrMode::Cut(
3577 CompilationError::fatal(
3578 SourceRange::from(arg),
3579 "This argument has a label, but no value. Put some value after the equals sign",
3580 )
3581 .into(),
3582 ));
3583 } else {
3584 args.push(LabeledArg { label: None, arg });
3585 }
3586 }
3587 }
3588 Ok((args, non_code_nodes))
3589 },
3590 )?;
3591 ignore_whitespace(i);
3592 opt(comma_sep).parse_next(i)?;
3593 let end = match close_paren.parse_next(i) {
3594 Ok(tok) => tok.end,
3595 Err(e) => {
3596 if let Some(tok) = i.next_token() {
3597 return Err(ErrMode::Cut(
3598 CompilationError::fatal(
3599 SourceRange::from(&tok),
3600 format!("There was an unexpected `{}`. Try removing it.", tok.value),
3601 )
3602 .into(),
3603 ));
3604 } else {
3605 return Err(e);
3606 }
3607 }
3608 };
3609
3610 let mut counted_labels = IndexMap::with_capacity(args.len());
3612 for arg in &args {
3613 if let Some(l) = &arg.label {
3614 *counted_labels.entry(&l.inner.name).or_insert(0) += 1;
3615 }
3616 }
3617 if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
3618 let msg = format!(
3619 "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."
3620 );
3621 ParseContext::err(CompilationError::err(
3622 SourceRange::new(fn_name.start, end, fn_name.module_id),
3623 msg,
3624 ));
3625 }
3626 let non_code_meta = NonCodeMeta {
3627 non_code_nodes,
3628 ..Default::default()
3629 };
3630 let result = Node::new_node(
3631 fn_name.start,
3632 end,
3633 fn_name.module_id,
3634 CallExpressionKw {
3635 callee: fn_name,
3636 unlabeled: initial_unlabeled_arg,
3637 arguments: args,
3638 digest: None,
3639 non_code_meta,
3640 },
3641 );
3642
3643 let callee_str = result.callee.name.name.to_string();
3644 if let Some(suggestion) = super::deprecation(&callee_str, DeprecationKind::Function) {
3645 ParseContext::warn(
3646 CompilationError::err(
3647 result.as_source_range(),
3648 format!("Calling `{callee_str}` is deprecated, prefer using `{suggestion}`."),
3649 )
3650 .with_suggestion(
3651 format!("Replace `{callee_str}` with `{suggestion}`"),
3652 suggestion,
3653 None,
3654 Tag::Deprecated,
3655 ),
3656 );
3657 }
3658
3659 Ok(result)
3660}
3661
3662#[cfg(test)]
3663mod tests {
3664 use itertools::Itertools;
3665 use pretty_assertions::assert_eq;
3666
3667 use super::*;
3668 use crate::{
3669 ModuleId,
3670 parsing::ast::types::{BodyItem, Expr, VariableKind},
3671 };
3672
3673 fn in_ctx<R, F: FnOnce() -> R>(f: F) -> R {
3674 ParseContext::init();
3675 let result = f();
3676 ParseContext::take();
3677 result
3678 }
3679
3680 fn in_sketch_ctx<R, F: FnOnce() -> R>(f: F) -> R {
3681 ParseContext::init();
3682 let result = ParseContext::in_sketch_block(f);
3683 ParseContext::take();
3684 result
3685 }
3686
3687 fn assert_reserved(word: &str) {
3688 let code = format!(r#"{word} = 0"#);
3690 let result = crate::parsing::top_level_parse(code.as_str());
3691 let err = &result.unwrap_errs().next().unwrap();
3692 assert!(
3695 err.message.starts_with("Unexpected token: ")
3696 || err.message.starts_with("= is not")
3697 || err
3698 .message
3699 .starts_with("Cannot assign a variable to a reserved keyword: "),
3700 "Error message is: `{}`",
3701 err.message,
3702 );
3703 }
3704
3705 #[test]
3706 fn reserved_words() {
3707 for word in crate::parsing::token::RESERVED_WORDS.keys().sorted() {
3710 assert_reserved(word);
3711 }
3712 assert_reserved("import");
3713 }
3714
3715 #[test]
3716 fn parse_names() {
3717 for (test, expected_len) in [("someVar", 0), ("::foo", 0), ("foo::bar::baz", 2)] {
3718 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3719 match name.parse(tokens.as_slice()) {
3720 Ok(n) => assert_eq!(n.path.len(), expected_len, "Could not parse name from `{test}`: {n:?}"),
3721 Err(e) => panic!("Could not parse name from `{test}`: {e:?}"),
3722 }
3723 }
3724 }
3725
3726 #[test]
3727 fn weird_program_unclosed_paren() {
3728 let tokens = crate::parsing::token::lex("fn firstPrime(", ModuleId::default()).unwrap();
3729 let tokens = tokens.as_slice();
3730 let last = tokens.last().unwrap().as_source_range();
3731 let err: CompilationError = error_from_winnow_error(program.parse(tokens).unwrap_err());
3732 assert_eq!(err.source_range, last);
3733 assert_eq!(err.message, "Unexpected end of file. The compiler expected )");
3736 }
3737
3738 #[test]
3739 fn kw_call_as_operand() {
3740 let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
3741 let tokens = tokens.as_slice();
3742 operand.parse(tokens).unwrap();
3743 }
3744
3745 #[test]
3746 fn parse_binary_operator_on_array() {
3747 let tokens = crate::parsing::token::lex("[0] + 1", ModuleId::default()).unwrap();
3748 let tokens = tokens.as_slice();
3749 binary_expression.parse(tokens).unwrap();
3750 }
3751
3752 #[test]
3753 fn expression_in_array_index() {
3754 let tokens = crate::parsing::token::lex("arr[x + 1]", ModuleId::default()).unwrap();
3755 let tokens = tokens.as_slice();
3756 let Expr::MemberExpression(expr) = expression.parse(tokens).unwrap() else {
3757 panic!();
3758 };
3759 let Expr::BinaryExpression(be) = expr.inner.property else {
3760 panic!();
3761 };
3762 assert_eq!(be.inner.operator, BinaryOperator::Add);
3763 }
3764
3765 #[test]
3766 fn parse_binary_operator_on_object() {
3767 let tokens = crate::parsing::token::lex("{ a = 1 } + 2", ModuleId::default()).unwrap();
3768 let tokens = tokens.as_slice();
3769 binary_expression.parse(tokens).unwrap();
3770 }
3771
3772 #[test]
3773 fn parse_call_array_operator() {
3774 let tokens = crate::parsing::token::lex("f([0] + 1)", ModuleId::default()).unwrap();
3775 let tokens = tokens.as_slice();
3776 fn_call_kw.parse(tokens).unwrap();
3777 }
3778
3779 #[test]
3780 fn parse_sketch_block_no_args() {
3781 let tokens = crate::parsing::token::lex("sketch() {}", ModuleId::default()).unwrap();
3782 let tokens = tokens.as_slice();
3783 let actual = in_ctx(|| fn_call_or_sketch_block.parse(tokens)).unwrap();
3784 assert!(matches!(actual, Expr::SketchBlock { .. }));
3785 }
3786
3787 #[test]
3788 fn parse_sketch_block_kw_args() {
3789 let tokens = crate::parsing::token::lex("sketch(on = XY) {}", ModuleId::default()).unwrap();
3790 let tokens = tokens.as_slice();
3791 let actual = in_ctx(|| fn_call_or_sketch_block.parse(tokens)).unwrap();
3792 assert!(matches!(actual, Expr::SketchBlock { .. }));
3793 }
3794
3795 #[test]
3796 fn parse_sketch_var_with_initial_value() {
3797 let tokens = crate::parsing::token::lex("var 1.5", ModuleId::default()).unwrap();
3798 let tokens = tokens.as_slice();
3799 let actual = in_sketch_ctx(|| sketch_var.parse(tokens)).unwrap();
3800 let initial = actual.inner.initial.unwrap();
3801 assert_eq!(initial.value, 1.5);
3802 assert_eq!(initial.suffix, NumericSuffix::None);
3803
3804 let tokens = crate::parsing::token::lex("var -1.5", ModuleId::default()).unwrap();
3805 let tokens = tokens.as_slice();
3806 let actual = in_sketch_ctx(|| sketch_var.parse(tokens)).unwrap();
3807 let initial = actual.inner.initial.unwrap();
3808 assert_eq!(initial.value, -1.5);
3809 assert_eq!(initial.suffix, NumericSuffix::None);
3810
3811 let tokens = crate::parsing::token::lex("var 1.5ft", ModuleId::default()).unwrap();
3812 let tokens = tokens.as_slice();
3813 let actual = in_sketch_ctx(|| sketch_var.parse(tokens)).unwrap();
3814 let initial = actual.inner.initial.unwrap();
3815 assert_eq!(initial.value, 1.5);
3816 assert_eq!(initial.suffix, NumericSuffix::Ft);
3817 }
3818
3819 #[test]
3820 fn parse_sketch_var_without_initial_value() {
3821 let tokens = crate::parsing::token::lex("var", ModuleId::default()).unwrap();
3822 let tokens = tokens.as_slice();
3823 let actual = in_sketch_ctx(|| sketch_var.parse(tokens)).unwrap();
3824 assert_eq!(actual.inner.initial, None);
3825 }
3826
3827 #[test]
3828 fn parse_sketch_var_outside_sketch_block_is_experimental() {
3829 let tokens = crate::parsing::token::lex("var 0", ModuleId::default()).unwrap();
3830 let tokens = tokens.as_slice();
3831 ParseContext::init();
3832 sketch_var.parse(tokens).unwrap();
3833 let ctxt = ParseContext::take();
3834 let error = ctxt.errors.first().unwrap();
3835 assert!(
3836 error.message.contains("sketch var is experimental"),
3837 "Actual: {}",
3838 error.message
3839 );
3840 assert_eq!(error.severity, Severity::Error);
3841 }
3842
3843 #[test]
3844 fn parse_sketch_block_sketch_var() {
3845 let tokens = crate::parsing::token::lex(
3846 "sketch() {
3847 x = var 1.5
3848}",
3849 ModuleId::default(),
3850 )
3851 .unwrap();
3852 let tokens = tokens.as_slice();
3853 let actual = in_ctx(|| fn_call_or_sketch_block.parse(tokens)).unwrap();
3854 let Expr::SketchBlock(sketch_block) = actual else {
3855 panic!("not a sketch block")
3856 };
3857 let item = sketch_block.body.items.first().unwrap();
3858 let BodyItem::VariableDeclaration(var_dec) = item else {
3859 panic!("not a variable declaration")
3860 };
3861 let Expr::SketchVar(sketch_var) = &var_dec.inner.declaration.init else {
3862 panic!("not a sketch var")
3863 };
3864 assert_eq!(sketch_var.inner.initial.as_ref().unwrap().value, 1.5);
3865 }
3866
3867 #[test]
3868 fn weird_program_just_a_pipe() {
3869 let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();
3870 let err: CompilationError = error_from_winnow_error(program.parse(tokens.as_slice()).unwrap_err());
3871 assert_eq!(err.source_range, SourceRange::new(0, 1, ModuleId::default()));
3872 assert_eq!(err.message, "Unexpected token: |");
3873 }
3874
3875 #[test]
3876 fn parse_binary_expressions() {
3877 for (i, test_program) in ["1 + 2 + 3"].into_iter().enumerate() {
3878 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3879 let _actual = match binary_expression.parse_next(&mut tokens.as_slice()) {
3880 Ok(x) => x,
3881 Err(e) => panic!("Failed test {i}, could not parse binary expressions from \"{test_program}\": {e:?}"),
3882 };
3883 }
3884 }
3885
3886 #[test]
3887 fn test_vardec_no_keyword() {
3888 let tokens = crate::parsing::token::lex("x = 4", ModuleId::default()).unwrap();
3889 let vardec = declaration(&mut tokens.as_slice()).unwrap();
3890 assert_eq!(vardec.inner.kind, VariableKind::Const);
3891 let vardec = &vardec.declaration;
3892 assert_eq!(vardec.id.name, "x");
3893 let Expr::Literal(init_val) = &vardec.init else {
3894 panic!("weird init value")
3895 };
3896 assert_eq!(init_val.raw, "4");
3897 }
3898
3899 #[test]
3900 fn test_negative_operands() {
3901 let tokens = crate::parsing::token::lex("-leg2", ModuleId::default()).unwrap();
3902 let _s = operand.parse_next(&mut tokens.as_slice()).unwrap();
3903 }
3904
3905 #[test]
3906 fn test_comments_in_function1() {
3907 let test_program = r#"() {
3908 // comment 0
3909 a = 1
3910 // comment 1
3911 b = 2
3912 /// comment 2
3913 return 1
3914 }"#;
3915 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3916 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3917 assert_eq!(expr.params, vec![]);
3918 let comment_start = expr.body.body[0].get_comments();
3919 let comment0 = expr.body.body[1].get_comments();
3920 let comment1 = expr.body.body[2].get_comments();
3921 assert_eq!(comment_start, vec!["// comment 0".to_owned()]);
3922 assert_eq!(comment0, vec!["// comment 1".to_owned()]);
3923 assert_eq!(comment1, vec!["/// comment 2".to_owned()]);
3924 }
3925
3926 #[test]
3927 fn test_comments_in_function2() {
3928 let test_program = r#"() {
3929 yo = { a = { b = { c = '123' } } } /* block
3930comment */
3931}"#;
3932 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3933 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3934 let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
3935 assert_eq!(comment0.value(), "block\ncomment");
3936 }
3937
3938 #[test]
3939 fn test_comment_at_start_of_program() {
3940 let test_program = r#"
3941/* comment at start */
3942
3943mySk1 = startSketchOn(XY)
3944 |> startProfile(at = [0, 0])"#;
3945 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3946 let program = program.parse(tokens.as_slice()).unwrap();
3947 let mut starting_comments = program.inner.non_code_meta.start_nodes;
3948 assert_eq!(starting_comments.len(), 2);
3949 let start0 = starting_comments.remove(0);
3950 let start1 = starting_comments.remove(0);
3951 assert_eq!(
3952 start0.value,
3953 NonCodeValue::BlockComment {
3954 value: "comment at start".to_owned(),
3955 style: CommentStyle::Block
3956 }
3957 );
3958 assert_eq!(start1.value, NonCodeValue::NewLine);
3959 }
3960
3961 #[test]
3962 fn test_comment_in_pipe() {
3963 let tokens = crate::parsing::token::lex(r#"x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
3964 let mut body = program.parse(tokens.as_slice()).unwrap().inner.body;
3965 let BodyItem::VariableDeclaration(item) = body.remove(0) else {
3966 panic!("expected vardec");
3967 };
3968 let val = item.inner.declaration.inner.init;
3969 let Expr::PipeExpression(pipe) = val else {
3970 panic!("expected pipe");
3971 };
3972 let mut noncode = pipe.inner.non_code_meta;
3973 assert_eq!(noncode.non_code_nodes.len(), 1);
3974 let comment = noncode.non_code_nodes.remove(&0).unwrap().pop().unwrap();
3975 assert_eq!(
3976 comment.value,
3977 NonCodeValue::BlockComment {
3978 value: "hi".to_owned(),
3979 style: CommentStyle::Block
3980 }
3981 );
3982 }
3983
3984 #[test]
3985 fn test_whitespace_in_function() {
3986 let test_program = r#"() {
3987 return sg
3988 return sg
3989 }"#;
3990 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3991 let _expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3992 }
3993
3994 #[test]
3995 fn test_empty_lines_in_function() {
3996 let test_program = "() {
3997
3998 return 2
3999 }";
4000 let module_id = ModuleId::from_usize(1);
4001 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
4002 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
4003 assert_eq!(
4004 expr.body.non_code_meta.start_nodes,
4005 vec![Node::new(
4006 NonCodeNode {
4007 value: NonCodeValue::NewLine,
4008 digest: None
4009 },
4010 4,
4011 22,
4012 module_id,
4013 )]
4014 );
4015 }
4016
4017 #[test]
4018 fn inline_comment_pipe_expression() {
4019 let test_input = r#"a(XY)
4020 |> b(%)
4021 |> c(%) // inline-comment
4022 |> d(%)"#;
4023
4024 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
4025 let (body, non_code_meta) = match expression.parse_next(&mut tokens.as_slice()).unwrap() {
4026 Expr::PipeExpression(e) => (e.inner.body, e.inner.non_code_meta),
4027 _ => panic!(),
4028 };
4029
4030 assert_eq!(non_code_meta.non_code_nodes.len(), 1);
4031 assert_eq!(
4032 non_code_meta.non_code_nodes.get(&2).unwrap()[0].value,
4033 NonCodeValue::InlineComment {
4034 value: "inline-comment".to_owned(),
4035 style: CommentStyle::Line
4036 }
4037 );
4038 assert_eq!(body.len(), 4);
4039 }
4040
4041 #[test]
4042 fn many_comments() {
4043 let test_program = r#"// this is a comment
4044 yo = { a = { b = { c = '123' } } } /* block
4045 comment */
4046
4047 key = 'c'
4048 // this is also a comment
4049 return things
4050"#;
4051
4052 let module_id = ModuleId::default();
4053 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
4054 let Block {
4055 items, non_code_meta, ..
4056 } = function_body.parse(tokens.as_slice()).unwrap().inner;
4057 assert_eq!(items[0].get_comments(), vec!["// this is a comment".to_owned()],);
4058
4059 assert_eq!(
4060 Some(&vec![
4061 Node::new(
4062 NonCodeNode {
4063 value: NonCodeValue::InlineComment {
4064 value: "block\n comment".to_owned(),
4065 style: CommentStyle::Block
4066 },
4067 digest: None,
4068 },
4069 57,
4070 79,
4071 module_id,
4072 ),
4073 Node::new(
4074 NonCodeNode {
4075 value: NonCodeValue::NewLine,
4076 digest: None,
4077 },
4078 79,
4079 83,
4080 module_id,
4081 )
4082 ]),
4083 non_code_meta.non_code_nodes.get(&0),
4084 );
4085
4086 assert_eq!(items[2].get_comments(), vec!["// this is also a comment".to_owned()],);
4087 }
4088
4089 #[test]
4090 fn inline_block_comments() {
4091 let test_program = r#"yo = 3 /* block
4092 comment */
4093 return 1"#;
4094
4095 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4096 let actual = program.parse(tokens.as_slice()).unwrap();
4097 assert_eq!(actual.non_code_meta.non_code_nodes.len(), 1);
4098 assert_eq!(
4099 actual.non_code_meta.non_code_nodes.get(&0).unwrap()[0].value,
4100 NonCodeValue::InlineComment {
4101 value: "block\n comment".to_owned(),
4102 style: CommentStyle::Block
4103 }
4104 );
4105 }
4106
4107 #[test]
4108 fn test_bracketed_binary_expression() {
4109 let input = "(2 - 3)";
4110 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4111 let actual = match binary_expr_in_parens.parse(tokens.as_slice()) {
4112 Ok(x) => x,
4113 Err(e) => panic!("{e:?}"),
4114 };
4115 assert_eq!(actual.operator, BinaryOperator::Sub);
4116 }
4117
4118 #[test]
4119 fn test_arg() {
4120 for input in [
4121 "( sigmaAllow * width )",
4122 "6 / ( sigmaAllow * width )",
4123 "sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))",
4124 ] {
4125 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4126 let _actual = match expression.parse(tokens.as_slice()) {
4127 Ok(x) => x,
4128 Err(e) => panic!("{e:?}"),
4129 };
4130 }
4131 }
4132
4133 #[test]
4134 fn test_arithmetic() {
4135 let input = "1 * (2 - 3)";
4136 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4137 let actual = binary_expression.parse(tokens.as_slice()).unwrap();
4139 assert_eq!(actual.operator, BinaryOperator::Mul);
4140 let BinaryPart::BinaryExpression(rhs) = actual.inner.right else {
4141 panic!("Expected RHS to be another binary expression");
4142 };
4143 assert_eq!(rhs.operator, BinaryOperator::Sub);
4144 match &rhs.right {
4145 BinaryPart::Literal(lit) => {
4146 assert!(lit.start == 9 && lit.end == 10);
4147 assert!(
4148 lit.value
4149 == LiteralValue::Number {
4150 value: 3.0,
4151 suffix: NumericSuffix::None
4152 }
4153 && &lit.raw == "3"
4154 && lit.digest.is_none()
4155 );
4156 }
4157 _ => panic!(),
4158 }
4159 }
4160
4161 #[test]
4162 fn assign_brackets() {
4163 for (i, test_input) in [
4164 "thickness_squared = (1 + 1)",
4165 "thickness_squared = ( 1 + 1)",
4166 "thickness_squared = (1 + 1 )",
4167 "thickness_squared = ( 1 + 1 )",
4168 ]
4169 .into_iter()
4170 .enumerate()
4171 {
4172 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
4173 let actual = match declaration.parse(tokens.as_slice()) {
4174 Err(e) => panic!("Could not parse test {i}: {e:#?}"),
4175 Ok(a) => a,
4176 };
4177 let Expr::BinaryExpression(_expr) = &actual.declaration.inner.init else {
4178 panic!(
4179 "Expected test {i} to be a binary expression but it wasn't, it was {:?}",
4180 actual.declaration
4181 );
4182 };
4183 }
4185 }
4186
4187 #[test]
4188 fn test_function_call() {
4189 for (i, test_input) in ["x = f(1)", "x = f( 1 )"].into_iter().enumerate() {
4190 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
4191 let _actual = match declaration.parse(tokens.as_slice()) {
4192 Err(e) => panic!("Could not parse test {i}: {e:#?}"),
4193 Ok(a) => a,
4194 };
4195 }
4196 }
4197
4198 #[test]
4199 fn test_nested_arithmetic() {
4200 let input = "1 * ((2 - 3) / 4)";
4201 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4202 let outer = binary_expression.parse(tokens.as_slice()).unwrap();
4204 assert_eq!(outer.operator, BinaryOperator::Mul);
4205 let BinaryPart::BinaryExpression(middle) = outer.inner.right else {
4206 panic!("Expected RHS to be another binary expression");
4207 };
4208
4209 assert_eq!(middle.operator, BinaryOperator::Div);
4210 let BinaryPart::BinaryExpression(inner) = middle.inner.left else {
4211 panic!("expected nested binary expression");
4212 };
4213 assert_eq!(inner.operator, BinaryOperator::Sub);
4214 }
4215
4216 #[test]
4217 fn binary_expression_ignores_whitespace() {
4218 let tests = ["1 - 2", "1- 2", "1 -2", "1-2"];
4219 for test in tests {
4220 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
4221 let actual = binary_expression.parse(tokens.as_slice()).unwrap();
4222 assert_eq!(actual.operator, BinaryOperator::Sub);
4223 let BinaryPart::Literal(left) = actual.inner.left else {
4224 panic!("should be expression");
4225 };
4226 assert_eq!(
4227 left.value,
4228 LiteralValue::Number {
4229 value: 1.0,
4230 suffix: NumericSuffix::None
4231 }
4232 );
4233 let BinaryPart::Literal(right) = actual.inner.right else {
4234 panic!("should be expression");
4235 };
4236 assert_eq!(
4237 right.value,
4238 LiteralValue::Number {
4239 value: 2.0,
4240 suffix: NumericSuffix::None
4241 }
4242 );
4243 }
4244 }
4245
4246 #[test]
4247 fn some_pipe_expr() {
4248 let test_program = r#"x()
4249 |> y(%) /* this is
4250 a comment
4251 spanning a few lines */
4252 |> z(%)"#;
4253 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4254 let non_code_meta = match expression.parse(tokens.as_slice()).unwrap() {
4255 Expr::PipeExpression(e) => e.non_code_meta.clone(),
4256 _ => panic!(),
4257 };
4258 let n = non_code_meta.non_code_nodes.len();
4259 assert_eq!(n, 1, "expected one comment in pipe expression but found {n}");
4260 let nc = &non_code_meta.non_code_nodes.get(&1).unwrap()[0];
4261 assert!(nc.value().starts_with("this"));
4262 assert!(nc.value().ends_with("lines"));
4263 }
4264
4265 #[test]
4266 fn comments_in_pipe_expr() {
4267 for (i, test_program) in [
4268 r#"y() |> /*hi*/ z(%)"#,
4269 "1 |>/*hi*/ f(%)",
4270 r#"y() |> /*hi*/ z(%)"#,
4271 "1 /*hi*/ |> f(%)",
4272 "1
4273 // Hi
4274 |> f(%)",
4275 "1
4276 /* Hi
4277 there
4278 */
4279 |> f(%)",
4280 ]
4281 .into_iter()
4282 .enumerate()
4283 {
4284 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4285 let actual = expression.parse(tokens.as_slice());
4286 assert!(actual.is_ok(), "could not parse test {i}, '{test_program}'");
4287
4288 match actual.unwrap() {
4289 Expr::PipeExpression(e) => {
4290 let n = e.non_code_meta.non_code_nodes.len();
4291 assert_eq!(n, 1, "expected one comment in pipe expression but found {n}",);
4292 }
4293 _ => panic!(),
4294 }
4295 }
4296 }
4297
4298 #[test]
4299 fn comments() {
4300 let module_id = ModuleId::from_usize(1);
4301 for (i, (test_program, expected)) in [
4302 (
4303 "//hi",
4304 Node::new(
4305 NonCodeNode {
4306 value: NonCodeValue::BlockComment {
4307 value: "hi".to_owned(),
4308 style: CommentStyle::Line,
4309 },
4310 digest: None,
4311 },
4312 0,
4313 4,
4314 module_id,
4315 ),
4316 ),
4317 (
4318 "/*hello*/",
4319 Node::new(
4320 NonCodeNode {
4321 value: NonCodeValue::BlockComment {
4322 value: "hello".to_owned(),
4323 style: CommentStyle::Block,
4324 },
4325 digest: None,
4326 },
4327 0,
4328 9,
4329 module_id,
4330 ),
4331 ),
4332 (
4333 "/* hello */",
4334 Node::new(
4335 NonCodeNode {
4336 value: NonCodeValue::BlockComment {
4337 value: "hello".to_owned(),
4338 style: CommentStyle::Block,
4339 },
4340 digest: None,
4341 },
4342 0,
4343 11,
4344 module_id,
4345 ),
4346 ),
4347 (
4348 "/* \nhello */",
4349 Node::new(
4350 NonCodeNode {
4351 value: NonCodeValue::BlockComment {
4352 value: "hello".to_owned(),
4353 style: CommentStyle::Block,
4354 },
4355 digest: None,
4356 },
4357 0,
4358 12,
4359 module_id,
4360 ),
4361 ),
4362 (
4363 "
4364 /* hello */",
4365 Node::new(
4366 NonCodeNode {
4367 value: NonCodeValue::BlockComment {
4368 value: "hello".to_owned(),
4369 style: CommentStyle::Block,
4370 },
4371 digest: None,
4372 },
4373 0,
4374 29,
4375 module_id,
4376 ),
4377 ),
4378 (
4379 "
4381
4382 /* hello */",
4383 Node::new(
4384 NonCodeNode {
4385 value: NonCodeValue::NewLineBlockComment {
4386 value: "hello".to_owned(),
4387 style: CommentStyle::Block,
4388 },
4389 digest: None,
4390 },
4391 0,
4392 32,
4393 module_id,
4394 ),
4395 ),
4396 (
4397 "
4399
4400 /* hello */",
4401 Node::new(
4402 NonCodeNode {
4403 value: NonCodeValue::NewLineBlockComment {
4404 value: "hello".to_owned(),
4405 style: CommentStyle::Block,
4406 },
4407 digest: None,
4408 },
4409 0,
4410 30,
4411 module_id,
4412 ),
4413 ),
4414 (
4415 r#"/* block
4416 comment */"#,
4417 Node::new(
4418 NonCodeNode {
4419 value: NonCodeValue::BlockComment {
4420 value: "block\n comment".to_owned(),
4421 style: CommentStyle::Block,
4422 },
4423 digest: None,
4424 },
4425 0,
4426 39,
4427 module_id,
4428 ),
4429 ),
4430 ]
4431 .into_iter()
4432 .enumerate()
4433 {
4434 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
4435 let actual = non_code_node.parse(tokens.as_slice());
4436 assert!(actual.is_ok(), "could not parse test {i}: {actual:#?}");
4437 let actual = actual.unwrap();
4438 assert_eq!(actual, expected, "failed test {i}");
4439 }
4440 }
4441
4442 #[test]
4443 fn recognize_invalid_params() {
4444 let test_fn = "(let) => { return 1 }";
4445 let module_id = ModuleId::from_usize(2);
4446 let tokens = crate::parsing::token::lex(test_fn, module_id).unwrap();
4447 let err = function_decl.parse(tokens.as_slice()).unwrap_err().into_inner();
4448 let cause = err.cause.unwrap();
4449 assert_eq!(cause.source_range, SourceRange::new(1, 4, ModuleId::from_usize(2)));
4451 assert_eq!(cause.message, "Cannot assign a variable to a reserved keyword: let");
4452 }
4453
4454 #[test]
4455 fn comment_in_string() {
4456 let string_literal = r#""
4457 // a comment
4458 ""#;
4459 let tokens = crate::parsing::token::lex(string_literal, ModuleId::default()).unwrap();
4460 let parsed_literal = literal.parse(tokens.as_slice()).unwrap();
4461 assert_eq!(
4462 parsed_literal.value,
4463 "
4464 // a comment
4465 "
4466 .into()
4467 );
4468 }
4469
4470 #[test]
4471 fn pipes_on_pipes_minimal() {
4472 let test_program = r#"startSketchOn(XY)
4473 |> startProfile(at = [0, 0])
4474 |> line(endAbsolute = [0, -0]) // MoveRelative
4475
4476 "#;
4477 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4478 let tokens = &mut tokens.as_slice();
4479 let _actual = expression.parse_next(tokens).unwrap();
4480 assert_eq!(tokens.first().unwrap().token_type, TokenType::Whitespace);
4481 }
4482
4483 #[test]
4484 fn test_pipes_on_pipes() {
4485 let test_program = include_str!("../../e2e/executor/inputs/pipes_on_pipes.kcl");
4486 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4487 let _ = run_parser(tokens.as_slice()).unwrap();
4488 }
4489
4490 #[test]
4491 fn test_cube() {
4492 let test_program = include_str!("../../e2e/executor/inputs/cube.kcl");
4493 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4494 match program.parse(tokens.as_slice()) {
4495 Ok(_) => {}
4496 Err(e) => {
4497 panic!("{e:#?}");
4498 }
4499 }
4500 }
4501
4502 #[test]
4503 fn parse_numeric() {
4504 let test_program = "fn foo(x: number(Length)) {}";
4505 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4506 run_parser(tokens.as_slice()).unwrap();
4507
4508 let test_program = "42_mm";
4509 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4510 assert_eq!(tokens.iter().count(), 1);
4511 run_parser(tokens.as_slice()).unwrap();
4512
4513 let test_program = "42_Length";
4514 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4515 assert_eq!(tokens.iter().count(), 2);
4516 assert_eq!(run_parser(tokens.as_slice()).unwrap_errs().count(), 1);
4517 }
4518
4519 #[test]
4520 fn test_parameter_list() {
4521 let tests = [
4522 ("", vec![]),
4523 ("a", vec!["a"]),
4524 ("a, b", vec!["a", "b"]),
4525 ("a,b", vec!["a", "b"]),
4526 ];
4527 for (i, (input, expected)) in tests.into_iter().enumerate() {
4528 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4529 let actual = parameters.parse(tokens.as_slice());
4530 assert!(actual.is_ok(), "could not parse test {i}");
4531 let actual_ids: Vec<_> = actual.unwrap().into_iter().map(|p| p.identifier.inner.name).collect();
4532 assert_eq!(actual_ids, expected);
4533 }
4534 }
4535
4536 #[test]
4537 fn test_user_function() {
4538 let input = "() {
4539 return 2
4540 }";
4541
4542 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4543 let actual = function_decl.parse(tokens.as_slice());
4544 assert!(actual.is_ok(), "could not parse test function");
4545 }
4546
4547 #[test]
4548 fn test_declaration() {
4549 let tests = ["myVar = 5", "myVar=5", "myVar =5", "myVar= 5"];
4550 for test in tests {
4551 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
4553 let mut expected_body = crate::parsing::parse_tokens(tokens.clone()).unwrap().inner.body;
4554 assert_eq!(expected_body.len(), 1);
4555 let BodyItem::VariableDeclaration(expected) = expected_body.pop().unwrap() else {
4556 panic!("Expected variable declaration");
4557 };
4558
4559 let actual = declaration.parse(tokens.as_slice()).unwrap();
4561 assert_eq!(expected, actual);
4562
4563 assert_eq!(actual.inner.kind, VariableKind::Const);
4565 assert_eq!(actual.start, 0);
4566 let decl = &actual.declaration;
4567 assert_eq!(decl.id.name, "myVar");
4568 let Expr::Literal(value) = &decl.inner.init else {
4569 panic!("value should be a literal")
4570 };
4571 assert_eq!(value.end, test.len());
4572 assert_eq!(value.raw, "5");
4573 }
4574 }
4575
4576 #[test]
4577 fn test_math_parse() {
4578 let module_id = ModuleId::default();
4579 let actual = crate::parsing::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
4580 let expr = Node::boxed(
4581 0,
4582 7,
4583 module_id,
4584 BinaryExpression {
4585 operator: BinaryOperator::Add,
4586 left: BinaryPart::Literal(Box::new(Node::new(
4587 Literal {
4588 value: LiteralValue::Number {
4589 value: 5.0,
4590 suffix: NumericSuffix::None,
4591 },
4592 raw: "5".to_owned(),
4593 digest: None,
4594 },
4595 0,
4596 1,
4597 module_id,
4598 ))),
4599 right: BinaryPart::Literal(Box::new(Node::new(
4600 Literal {
4601 value: "a".into(),
4602 raw: r#""a""#.to_owned(),
4603 digest: None,
4604 },
4605 4,
4606 7,
4607 module_id,
4608 ))),
4609 digest: None,
4610 },
4611 );
4612 let expected = vec![BodyItem::ExpressionStatement(Node::new(
4613 ExpressionStatement {
4614 expression: Expr::BinaryExpression(expr),
4615 digest: None,
4616 },
4617 0,
4618 7,
4619 module_id,
4620 ))];
4621 assert_eq!(expected, actual);
4622 }
4623
4624 #[test]
4625 fn test_abstract_syntax_tree() {
4626 let code = "5 +6";
4627 let module_id = ModuleId::default();
4628 let result = crate::parsing::parse_str(code, module_id).unwrap();
4629 let expected_result = Node::new(
4630 Program {
4631 body: vec![BodyItem::ExpressionStatement(Node::new(
4632 ExpressionStatement {
4633 expression: Expr::BinaryExpression(Node::boxed(
4634 0,
4635 4,
4636 module_id,
4637 BinaryExpression {
4638 left: BinaryPart::Literal(Box::new(Node::new(
4639 Literal {
4640 value: LiteralValue::Number {
4641 value: 5.0,
4642 suffix: NumericSuffix::None,
4643 },
4644 raw: "5".to_string(),
4645 digest: None,
4646 },
4647 0,
4648 1,
4649 module_id,
4650 ))),
4651 operator: BinaryOperator::Add,
4652 right: BinaryPart::Literal(Box::new(Node::new(
4653 Literal {
4654 value: LiteralValue::Number {
4655 value: 6.0,
4656 suffix: NumericSuffix::None,
4657 },
4658 raw: "6".to_string(),
4659 digest: None,
4660 },
4661 3,
4662 4,
4663 module_id,
4664 ))),
4665 digest: None,
4666 },
4667 )),
4668 digest: None,
4669 },
4670 0,
4671 4,
4672 module_id,
4673 ))],
4674 shebang: None,
4675 non_code_meta: NonCodeMeta::default(),
4676 inner_attrs: Vec::new(),
4677 digest: None,
4678 },
4679 0,
4680 4,
4681 module_id,
4682 );
4683
4684 assert_eq!(result, expected_result);
4685 }
4686
4687 #[test]
4688 fn test_empty_file() {
4689 let some_program_string = r#""#;
4690 let result = crate::parsing::top_level_parse(some_program_string);
4691 assert!(result.is_ok());
4692 }
4693
4694 #[track_caller]
4695 fn assert_no_err(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4696 let result = crate::parsing::top_level_parse(p);
4697 let result = result.0.unwrap();
4698 assert!(result.1.iter().all(|e| !e.severity.is_err()), "found: {:#?}", result.1);
4699 (result.0.unwrap(), result.1)
4700 }
4701
4702 #[track_caller]
4703 fn assert_no_fatal(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4704 let result = crate::parsing::top_level_parse(p);
4705 let result = result.0.unwrap();
4706 assert!(
4707 result.1.iter().all(|e| e.severity != Severity::Fatal),
4708 "found: {:#?}",
4709 result.1
4710 );
4711 (result.0.unwrap(), result.1)
4712 }
4713
4714 #[track_caller]
4715 fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
4716 let result = crate::parsing::top_level_parse(p);
4717 let err = result.unwrap_errs().next().unwrap();
4718 assert!(
4719 err.message.starts_with(msg),
4720 "Found `{}`, expected `{msg}`",
4721 err.message
4722 );
4723 let src_actual = [err.source_range.start(), err.source_range.end()];
4724 assert_eq!(
4725 src_expected,
4726 src_actual,
4727 "expected error would highlight `{}` but it actually highlighted `{}`",
4728 &p[src_expected[0]..src_expected[1]],
4729 &p[src_actual[0]..src_actual[1]],
4730 );
4731 }
4732
4733 #[track_caller]
4734 fn assert_err_contains(p: &str, expected: &str) {
4735 let result = crate::parsing::top_level_parse(p);
4736 let err = &result.unwrap_errs().next().unwrap().message;
4737 assert!(err.contains(expected), "actual='{err}'");
4738 }
4739
4740 #[test]
4741 fn test_parse_half_pipe_small() {
4742 assert_err_contains(
4743 "secondExtrude = startSketchOn(XY)
4744 |> startProfile(at = [0,0])
4745 |",
4746 "Unexpected token: |",
4747 );
4748 }
4749
4750 #[test]
4751 fn test_parse_member_expression_double_nested_braces() {
4752 let code = r#"prop = yo["one"][two]"#;
4753 crate::parsing::top_level_parse(code).unwrap();
4754 }
4755
4756 #[test]
4757 fn test_parse_member_expression_binary_expression_period_number_first() {
4758 let code = r#"obj = { a: 1, b: 2 }
4759height = 1 - obj.a"#;
4760 crate::parsing::top_level_parse(code).unwrap();
4761 }
4762
4763 #[test]
4764 fn test_parse_member_expression_allowed_type_in_expression() {
4765 let code = r#"obj = { thing: 1 }
4766startSketchOn(obj.sketch)"#;
4767
4768 crate::parsing::top_level_parse(code).unwrap();
4769 }
4770
4771 #[test]
4772 fn test_parse_member_expression_binary_expression_brace_number_first() {
4773 let code = r#"obj = { a: 1, b: 2 }
4774height = 1 - obj["a"]"#;
4775 crate::parsing::top_level_parse(code).unwrap();
4776 }
4777
4778 #[test]
4779 fn test_parse_member_expression_binary_expression_brace_number_second() {
4780 let code = r#"obj = { a: 1, b: 2 }
4781height = obj["a"] - 1"#;
4782 crate::parsing::top_level_parse(code).unwrap();
4783 }
4784
4785 #[test]
4786 fn test_parse_member_expression_binary_expression_in_array_number_first() {
4787 let code = r#"obj = { a: 1, b: 2 }
4788height = [1 - obj["a"], 0]"#;
4789 crate::parsing::top_level_parse(code).unwrap();
4790 }
4791
4792 #[test]
4793 fn test_parse_member_expression_binary_expression_in_array_number_second() {
4794 let code = r#"obj = { a: 1, b: 2 }
4795height = [obj["a"] - 1, 0]"#;
4796 crate::parsing::top_level_parse(code).unwrap();
4797 }
4798
4799 #[test]
4800 fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
4801 let code = r#"obj = { a: 1, b: 2 }
4802height = [obj["a"] -1, 0]"#;
4803 crate::parsing::top_level_parse(code).unwrap();
4804 }
4805
4806 #[test]
4807 fn test_anon_fn() {
4808 crate::parsing::top_level_parse("foo(num=42, closure=fn(x) { return x + 1 })").unwrap();
4809 }
4810
4811 #[test]
4812 fn test_fn_optional_parameter_with_default_value_and_trailing_whitespace() {
4813 let code = r#"fn foo(x?: string = "default" ) {}"#;
4815
4816 crate::parsing::top_level_parse(code).unwrap();
4817 }
4818
4819 #[test]
4820 fn test_annotation_fn() {
4821 crate::parsing::top_level_parse(
4822 r#"fn foo() {
4823 @annotated
4824 return 1
4825}"#,
4826 )
4827 .unwrap();
4828 }
4829
4830 #[test]
4831 fn test_annotation_settings() {
4832 crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
4833 }
4834
4835 #[test]
4836 fn test_anon_fn_no_fn() {
4837 assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
4838 }
4839
4840 #[test]
4841 fn test_parse_half_pipe() {
4842 let code = "height = 10
4843
4844firstExtrude = startSketchOn(XY)
4845 |> startProfile(at = [0,0])
4846 |> line(at = [0, 8])
4847 |> line(at = [20, 0])
4848 |> line(at = [0, -8])
4849 |> close()
4850 |> extrude(length=2)
4851
4852secondExtrude = startSketchOn(XY)
4853 |> startProfile(at = [0,0])
4854 |";
4855 assert_err_contains(code, "Unexpected token: |");
4856 }
4857
4858 #[test]
4859 fn test_parse_greater_bang() {
4860 assert_err(">!", "Unexpected token: >", [0, 1]);
4861 }
4862
4863 #[test]
4864 fn test_parse_unlabeled_param_not_allowed() {
4865 assert_err(
4866 "fn f(@x, @y) { return 1 }",
4867 "Only the first parameter can be declared unlabeled",
4868 [9, 11],
4869 );
4870 assert_err(
4871 "fn f(x, @y) { return 1 }",
4872 "Only the first parameter can be declared unlabeled",
4873 [8, 10],
4874 );
4875 }
4876
4877 #[test]
4878 fn test_parse_z_percent_parens() {
4879 assert_err("z%)", "Unexpected token: %", [1, 2]);
4880 }
4881
4882 #[test]
4883 fn test_parse_parens_unicode() {
4884 let result = crate::parsing::top_level_parse("(ޜ");
4885 let details = result.0.unwrap().1.pop().unwrap();
4886 assert_eq!(details.message, "Unexpected end of file. The compiler expected )");
4889 }
4890
4891 #[test]
4892 fn test_parse_negative_in_array_binary_expression() {
4893 let code = r#"leg1 = 5
4894thickness = 0.56
4895
4896bracket = [-leg2 + thickness, 0]
4897"#;
4898 crate::parsing::top_level_parse(code).unwrap();
4899 }
4900
4901 #[test]
4902 fn test_parse_nested_open_brackets() {
4903 let _ = crate::parsing::top_level_parse(
4904 r#"
4905z(-[["#,
4906 )
4907 .unwrap_errs();
4908 }
4909
4910 #[test]
4911 fn test_parse_weird_new_line_function() {
4912 assert_err(
4913 r#"z
4914(--#"#,
4915 "There was an unexpected `-`. Try removing it.",
4916 [3, 4],
4917 );
4918 }
4919
4920 #[test]
4921 fn test_parse_weird_lots_of_fancy_brackets() {
4922 assert_err(
4923 r#"zz({{{{{{{{)iegAng{{{{{{{##"#,
4924 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
4925 [3, 4],
4926 );
4927 }
4928
4929 #[test]
4930 fn test_parse_weird_close_before_open() {
4931 assert_err_contains(
4932 r#"fn)n
4933e
4934["#,
4935 "expected whitespace, found ')' which is brace",
4936 );
4937 }
4938
4939 #[test]
4940 fn test_parse_weird_close_before_nada() {
4941 assert_err_contains(r#"fn)n-"#, "expected whitespace, found ')' which is brace");
4942 }
4943
4944 #[test]
4945 fn test_optional_param_order() {
4946 for (i, (params, expect_ok)) in [
4947 (
4948 vec![Parameter {
4949 identifier: Node::no_src(Identifier {
4950 name: "a".to_owned(),
4951 digest: None,
4952 }),
4953 param_type: None,
4954 default_value: Some(DefaultParamVal::none()),
4955 labeled: true,
4956 digest: None,
4957 }],
4958 true,
4959 ),
4960 (
4961 vec![Parameter {
4962 identifier: Node::no_src(Identifier {
4963 name: "a".to_owned(),
4964 digest: None,
4965 }),
4966 param_type: None,
4967 default_value: None,
4968 labeled: true,
4969 digest: None,
4970 }],
4971 true,
4972 ),
4973 (
4974 vec![
4975 Parameter {
4976 identifier: Node::no_src(Identifier {
4977 name: "a".to_owned(),
4978 digest: None,
4979 }),
4980 param_type: None,
4981 default_value: None,
4982 labeled: true,
4983 digest: None,
4984 },
4985 Parameter {
4986 identifier: Node::no_src(Identifier {
4987 name: "b".to_owned(),
4988 digest: None,
4989 }),
4990 param_type: None,
4991 default_value: Some(DefaultParamVal::none()),
4992 labeled: true,
4993 digest: None,
4994 },
4995 ],
4996 true,
4997 ),
4998 (
4999 vec![
5000 Parameter {
5001 identifier: Node::no_src(Identifier {
5002 name: "a".to_owned(),
5003 digest: None,
5004 }),
5005 param_type: None,
5006 default_value: Some(DefaultParamVal::none()),
5007 labeled: true,
5008 digest: None,
5009 },
5010 Parameter {
5011 identifier: Node::no_src(Identifier {
5012 name: "b".to_owned(),
5013 digest: None,
5014 }),
5015 param_type: None,
5016 default_value: None,
5017 labeled: true,
5018 digest: None,
5019 },
5020 ],
5021 false,
5022 ),
5023 ]
5024 .into_iter()
5025 .enumerate()
5026 {
5027 let actual = optional_after_required(¶ms);
5028 assert_eq!(actual.is_ok(), expect_ok, "failed test {i}");
5029 }
5030 }
5031
5032 #[test]
5033 fn function_defined_with_var() {
5034 let code = r#"
5035 foo = fn(@x) {
5036 return x
5037 }
5038 answer = foo(2)
5039 "#;
5040 let (_, errs) = assert_no_err(code);
5041 assert!(errs[0].message.contains("Define a function with `fn name()` instead") && errs[0].suggestion.is_some());
5042 }
5043
5044 #[test]
5045 fn function_defined_with_var_and_recursive_name() {
5046 let code = r#"
5047 foo = fn bar(@x) {
5048 return x
5049 }
5050 answer = foo(2)
5051 "#;
5052 let (_, errs) = assert_no_err(code);
5053 assert!(errs[0].message.contains("Define a function with `fn name()` instead") && errs[0].suggestion.is_some());
5054 }
5055
5056 #[test]
5057 fn test_error_keyword_in_variable() {
5058 assert_err(
5059 r#"const let = "thing""#,
5060 "Cannot assign a variable to a reserved keyword: let",
5061 [6, 9],
5062 );
5063 }
5064
5065 #[test]
5066 fn test_error_keyword_in_fn_name() {
5067 assert_err(
5068 r#"fn let = () {}"#,
5069 "Cannot assign a variable to a reserved keyword: let",
5070 [3, 6],
5071 );
5072 }
5073
5074 #[test]
5075 fn test_error_keyword_in_fn_args() {
5076 assert_err(
5077 r#"fn thing = (let) => {
5078 return 1
5079}"#,
5080 "Cannot assign a variable to a reserved keyword: let",
5081 [12, 15],
5082 )
5083 }
5084
5085 #[test]
5086 fn bad_imports() {
5087 assert_err(
5088 r#"import cube from "../cube.kcl""#,
5089 "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.",
5090 [17, 30],
5091 );
5092 assert_err(
5093 r#"import cube from "/cube.kcl""#,
5094 "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.",
5095 [17, 28],
5096 );
5097 assert_err(
5098 r#"import cube from "C:\cube.kcl""#,
5099 "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
5100 [17, 30],
5101 );
5102 assert_err(
5103 r#"import cube from "cube/cube.kcl""#,
5104 "import path to a subdirectory must only refer to main.kcl.",
5105 [17, 32],
5106 );
5107 assert_err(
5108 r#"import * as foo from "dsfs""#,
5109 "as is not the 'from' keyword",
5110 [9, 11],
5111 );
5112 assert_err(
5113 r#"import a from "dsfs" as b"#,
5114 "unsupported import path format",
5115 [14, 20],
5116 );
5117 assert_err(
5118 r#"import * from "dsfs" as b"#,
5119 "unsupported import path format",
5120 [14, 20],
5121 );
5122 assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
5123 assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
5124 assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
5125 assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
5126 assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
5127 assert_err(
5128 r#"import "foo.bar.kcl""#,
5129 "import path is not a valid identifier and must be aliased.",
5130 [7, 20],
5131 );
5132 assert_err(
5133 r#"import "_foo.kcl""#,
5134 "import path is not a valid identifier and must be aliased.",
5135 [7, 17],
5136 );
5137 assert_err(
5138 r#"import "foo-bar.kcl""#,
5139 "import path is not a valid identifier and must be aliased.",
5140 [7, 20],
5141 );
5142 }
5143
5144 #[test]
5145 fn std_fn_decl() {
5146 let code = r#"/// Compute the cosine of a number (in radians).
5147///
5148/// ```
5149/// exampleSketch = startSketchOn(XZ)
5150/// |> startProfile(at = [0, 0])
5151/// |> angledLine(
5152/// angle = 30,
5153/// length = 3 / cos(toRadians(30)),
5154/// )
5155/// |> yLine(endAbsolute = 0)
5156/// |> close(%)
5157///
5158/// example = extrude(exampleSketch, length = 5)
5159/// ```
5160@(impl = std_rust)
5161export fn cos(num: number(rad)): number(_) {}"#;
5162 let _ast = crate::parsing::top_level_parse(code).unwrap();
5163 }
5164
5165 #[test]
5166 fn warn_import() {
5167 let some_program_string = r#"import "foo.bad""#;
5168 let (_, errs) = assert_no_err(some_program_string);
5169 assert_eq!(errs.len(), 1, "{errs:#?}");
5170 }
5171
5172 #[test]
5173 fn warn_late_settings() {
5174 let some_program_string = r#"foo = 42
5175@settings(defaultLengthUnit = mm)
5176"#;
5177 let (_, errs) = assert_no_err(some_program_string);
5178 assert_eq!(errs.len(), 1, "{errs:#?}");
5179 }
5180
5181 #[test]
5182 fn warn_unknown_suffix() {
5183 let some_program_string = r#"foo = 42_?
5184"#;
5185 let (_, errs) = assert_no_err(some_program_string);
5186 assert_eq!(errs.len(), 1, "{errs:#?}");
5187 }
5188
5189 #[test]
5190 fn fn_decl_uom_ty() {
5191 let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
5192 let (_, errs) = assert_no_fatal(some_program_string);
5193 assert!(errs.is_empty(), "Expected no errors, found: {errs:?}");
5194 }
5195
5196 #[test]
5197 fn error_underscore() {
5198 let (_, errs) = assert_no_fatal("_foo(a=_blah, b=_)");
5199 assert_eq!(errs.len(), 3, "found: {errs:#?}");
5200 }
5201
5202 #[test]
5203 fn error_double_and() {
5204 let (_, errs) = assert_no_fatal("foo = true && false");
5205 assert_eq!(errs.len(), 1, "found: {errs:#?}");
5206 assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some());
5207 }
5208
5209 #[test]
5210 fn error_type_ascription() {
5211 let (_, errs) = assert_no_fatal("a + b: number");
5212 assert!(errs.is_empty());
5213 }
5214
5215 #[test]
5216 fn zero_param_function() {
5217 let code = r#"
5218 fn firstPrimeNumber() {
5219 return 2
5220 }
5221 firstPrimeNumber()
5222 "#;
5223 let _ast = crate::parsing::top_level_parse(code).unwrap();
5224 }
5225
5226 #[test]
5227 fn array() {
5228 let program = r#"[1, 2, 3]"#;
5229 let module_id = ModuleId::default();
5230 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5231 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
5232 }
5233
5234 #[test]
5235 fn array_linesep_trailing_comma() {
5236 let program = r#"[
5237 1,
5238 2,
5239 3,
5240 ]"#;
5241 let module_id = ModuleId::default();
5242 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5243 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
5244 }
5245
5246 #[allow(unused)]
5247 #[test]
5248 fn array_linesep_no_trailing_comma() {
5249 let program = r#"[
5250 1,
5251 2,
5252 3
5253 ]"#;
5254 let module_id = ModuleId::default();
5255 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5256 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
5257 }
5258
5259 #[test]
5260 fn array_no_trailing_comma_with_comment() {
5261 let program = r#"[
5262 1, // one
5263 2, // two
5264 3 // three
5265 ]"#;
5266 let module_id = ModuleId::default();
5267 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5268 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
5269 }
5270
5271 #[test]
5272 fn array_block_comment_no_whitespace() {
5273 let program = r#"[1/* comment*/]"#;
5274 let module_id = ModuleId::default();
5275 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5276 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
5277 }
5278
5279 #[test]
5280 fn object_no_trailing_comma_with_comment() {
5281 let program = r#"{
5282 x=1, // one
5283 y=2, // two
5284 z=3 // three
5285 }"#;
5286 let module_id = ModuleId::default();
5287 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5288 let _arr = object(&mut tokens.as_slice()).unwrap();
5289 }
5290
5291 #[test]
5292 fn object_block_comment_no_whitespace() {
5293 let program = r#"{x=1/* comment*/}"#;
5294 let module_id = ModuleId::default();
5295 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
5296 let _arr = object(&mut tokens.as_slice()).unwrap();
5297 }
5298
5299 #[test]
5300 fn basic_if_else() {
5301 let some_program_string = "if true {
5302 3
5303 } else {
5304 4
5305 }";
5306 let module_id = ModuleId::default();
5307 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
5308 let _res = if_expr(&mut tokens.as_slice()).unwrap();
5309 }
5310
5311 #[test]
5312 fn basic_else_if() {
5313 let some_program_string = "else if true {
5314 4
5315 }";
5316 let module_id = ModuleId::default();
5317 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
5318 let _res = else_if(&mut tokens.as_slice()).unwrap();
5319 }
5320
5321 #[test]
5322 fn basic_if_else_if() {
5323 let some_program_string = "if true {
5324 3
5325 } else if true {
5326 4
5327 } else {
5328 5
5329 }";
5330 let module_id = ModuleId::default();
5331 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
5332 let _res = if_expr(&mut tokens.as_slice()).unwrap();
5333 }
5334
5335 #[test]
5336 fn test_keyword_ok_in_fn_args_return() {
5337 let some_program_string = r#"fn thing(param) {
5338 return true
5339}
5340
5341thing(false)
5342"#;
5343 crate::parsing::top_level_parse(some_program_string).unwrap();
5344 }
5345
5346 #[test]
5347 fn test_mul_if() {
5348 let some_program_string = r#"10 * if true { 1 } else { 0}"#;
5349 let tokens = crate::parsing::token::lex(some_program_string, ModuleId::default()).unwrap();
5350 super::binary_expression_tokens.parse(tokens.as_slice()).unwrap();
5351 }
5352
5353 #[test]
5354 fn test_error_define_var_as_function() {
5355 assert_err(r#"fn thing = "thing""#, "Unexpected token: \"thing\"", [11, 18]);
5359 }
5360
5361 #[test]
5362 fn random_words_fail() {
5363 let test_program = r#"part001 = startSketchOn(-XZ)
5364 |> startProfile(at = [8.53, 11.8])
5365 asdasd asdasd
5366 |> line(at = [11.12, -14.82])
5367 |> line(at = [-13.27, -6.98])
5368 |> line(at = [-5.09, 12.33])
5369 asdasd
5370"#;
5371 let _ = crate::parsing::top_level_parse(test_program).unwrap_errs();
5372 }
5373
5374 #[test]
5375 fn test_member_expression_sketch() {
5376 let some_program_string = r#"fn cube(pos, scale) {
5377 sg = startSketchOn(XY)
5378 |> startProfile(pos)
5379 |> line(at = [0, scale])
5380 |> line(at = [scale, 0])
5381 |> line(at = [0, -scale])
5382
5383 return sg
5384}
5385
5386b1 = cube(pos=[0,0], scale=10)
5387b2 = cube(pos=[3,3], scale=4)
5388
5389pt1 = b1[0]
5390pt2 = b2[0]
5391"#;
5392 crate::parsing::top_level_parse(some_program_string).unwrap();
5393 }
5394
5395 #[test]
5396 fn test_math_with_stdlib() {
5397 let some_program_string = r#"d2r = pi() / 2
5398let other_thing = 2 * cos(3)"#;
5399 crate::parsing::top_level_parse(some_program_string).unwrap();
5400 }
5401
5402 #[test]
5403 fn test_negative_arguments() {
5404 let some_program_string = r#"fn box(p, h, l, w) {
5405 myBox = startSketchOn(XY)
5406 |> startProfile(p)
5407 |> line(at = [0, l])
5408 |> line(at = [w, 0])
5409 |> line(at = [0, -l])
5410 |> close()
5411 |> extrude(length=h)
5412
5413 return myBox
5414}
5415let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
5416"#;
5417 crate::parsing::top_level_parse(some_program_string).unwrap();
5418 }
5419
5420 #[test]
5421 fn kw_fn() {
5422 for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
5423 let module_id = ModuleId::default();
5424 let tokens = crate::parsing::token::lex(input, module_id).unwrap();
5425 super::program.parse(tokens.as_slice()).unwrap();
5426 }
5427 }
5428
5429 #[test]
5430 fn test_parse_empty_tag_brace() {
5431 let some_program_string = r#"startSketchOn(XY)
5432 |> startProfile(at = [0, 0])
5433 |> line(%, $)
5434 "#;
5435 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5436 }
5437 #[test]
5438 fn test_parse_empty_tag_whitespace() {
5439 let some_program_string = r#"startSketchOn(XY)
5440 |> startProfile(at = [0, 0])
5441 |> line(%, $ ,01)
5442 "#;
5443 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5444 }
5445
5446 #[test]
5447 fn test_parse_empty_tag_comma() {
5448 let some_program_string = r#"startSketchOn(XY)
5449 |> startProfile(at = [0, 0])
5450 |> line(%, $,)
5451 "#;
5452 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
5453 }
5454 #[test]
5455 fn test_parse_tag_starting_with_digit() {
5456 let some_program_string = r#"
5457 startSketchOn(XY)
5458 |> startProfile(at = [0, 0])
5459 |> line(%, $01)"#;
5460 assert_err(
5461 some_program_string,
5462 "Tag names must not start with a number. Tag starts with `01`",
5463 [72, 74],
5464 );
5465 }
5466 #[test]
5467 fn test_parse_tag_including_digit() {
5468 let some_program_string = r#"
5469 startSketchOn(XY)
5470 |> startProfile(at = [0, 0])
5471 |> line(%, tag = $var01)"#;
5472 assert_no_err(some_program_string);
5473 }
5474
5475 #[test]
5476 fn test_parse_param_bool_default() {
5477 let some_program_string = r#"fn patternTransform(
5478 use_original?: boolean = false,
5479) {}"#;
5480 assert_no_err(some_program_string);
5481 }
5482
5483 #[test]
5484 fn parse_function_types() {
5485 let code = r#"@settings(experimentalFeatures = allow)
5486foo = x: fn
5487foo = x: fn(number)
5488fn foo(x: fn(): number): fn { return 0 }
5489fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
5490type fn
5491type foo = fn
5492type foo = fn(a: string, b: { f: fn(): any })
5493type foo = fn(a: string, b: {})
5494type foo = fn(a: string, b: { })
5495type foo = fn([fn])
5496type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
5497 "#;
5498 assert_no_err(code);
5499 }
5500
5501 #[test]
5502 fn experimental() {
5503 let code = r#"4 as foo"#;
5504 assert_err(code, "Use of `as` for tagging expressions is experimental", [5, 8]);
5505
5506 let code = r#"@settings(experimentalFeatures = allow)
55074 as foo
5508 "#;
5509 assert_no_err(code);
5510
5511 let code = r#"@settings(experimentalFeatures = warn)
55124 as foo
5513 "#;
5514 let (_, errs) = assert_no_err(code);
5515 assert_eq!(errs.len(), 1);
5516 }
5517
5518 #[test]
5519 fn test_parse_tag_starting_with_bang() {
5520 let some_program_string = r#"startSketchOn(XY)
5521 |> startProfile(at = [0, 0])
5522 |> line(%, $!var,01)
5523 "#;
5524 assert_err(some_program_string, "Tag names must not start with a bang", [67, 68]);
5525 }
5526 #[test]
5527 fn test_parse_tag_starting_with_dollar() {
5528 let some_program_string = r#"startSketchOn(XY)
5529 |> startProfile(at = [0, 0])
5530 |> line(%, $$,01)
5531 "#;
5532 assert_err(some_program_string, "Tag names must not start with a dollar", [67, 68]);
5533 }
5534 #[test]
5535 fn test_parse_tag_starting_with_fn() {
5536 let some_program_string = r#"startSketchOn(XY)
5537 |> startProfile(at = [0, 0])
5538 |> line(%, $fn,01)
5539 "#;
5540 assert_err(some_program_string, "Tag names must not start with a keyword", [67, 69]);
5541 }
5542 #[test]
5543 fn test_parse_tag_starting_with_a_comment() {
5544 let some_program_string = r#"startSketchOn(XY)
5545 |> startProfile(at = [0, 0])
5546 |> line(%, $//
5547 ,01)
5548 "#;
5549 assert_err(
5550 some_program_string,
5551 "Tag names must not start with a lineComment",
5552 [67, 69],
5553 );
5554 }
5555
5556 #[test]
5557 fn test_parse_tag_with_reserved_in_middle_works() {
5558 let some_program_string = r#"
5559 startSketchOn(XY)
5560 |> startProfile(at = [0, 0])
5561 |> line(end = [5, 5], tag = $sketching)
5562 "#;
5563 assert_no_err(some_program_string);
5564 }
5565
5566 #[test]
5567 fn test_parse_fn_call_then_field() {
5568 let some_program_string = "myFunction().field";
5569 let module_id = ModuleId::default();
5570 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); let actual = expression.parse(tokens.as_slice()).unwrap();
5572 let Expr::MemberExpression(_expr) = actual else {
5573 panic!("expected member expression")
5574 };
5575 }
5576
5577 #[test]
5578 fn test_parse_array_missing_closing_bracket() {
5579 let some_program_string = r#"
5580sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45, 119.09)"#;
5581 assert_err(
5582 some_program_string,
5583 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5584 [52, 60],
5585 );
5586 }
5587 #[test]
5588 fn test_parse_array_missing_comma() {
5589 let some_program_string = r#"
5590sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 119.09])"#;
5591 assert_err(
5592 some_program_string,
5593 "Unexpected character encountered. You might be missing a comma in between elements.",
5594 [53, 66],
5595 );
5596 }
5597 #[test]
5598 fn test_parse_array_reserved_word_early_exit() {
5599 let some_program_string = r#"
5602sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 $struct])"#;
5603 assert_err(
5604 some_program_string,
5605 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5606 [52, 53],
5607 );
5608 }
5609
5610 #[test]
5611 fn test_parse_array_random_brace() {
5612 let some_program_string = r#"
5613sketch001 = startSketchOn(XZ) |> startProfile(at = [}])"#;
5614 assert_err(
5615 some_program_string,
5616 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5617 [52, 53],
5618 );
5619 }
5620
5621 #[test]
5622 fn test_parse_object_missing_closing_brace() {
5623 let some_program_string = r#"{
5624 foo = bar,"#;
5625
5626 assert_err(
5627 some_program_string,
5628 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5629 [0, 23],
5630 );
5631 }
5632 #[test]
5633 fn test_parse_object_reserved_word_early_exit() {
5634 let some_program_string = r#"{bar = foo struct = man}"#;
5637
5638 assert_err(
5639 some_program_string,
5640 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5641 [0, 1],
5642 );
5643 }
5644 #[test]
5645 fn test_parse_object_missing_comma() {
5646 let some_program_string = r#"{
5647 foo = bar,
5648 bar = foo
5649 bat = man
5650 }"#;
5651
5652 assert_err(
5653 some_program_string,
5654 "Unexpected character encountered. You might be missing a comma in between properties.",
5655 [37, 78],
5656 );
5657 }
5658
5659 #[test]
5660 fn test_parse_object_missing_comma_one_line() {
5661 let some_program_string = r#"{bar = foo bat = man}"#;
5662
5663 assert_err(
5664 some_program_string,
5665 "Unexpected character encountered. You might be missing a comma in between properties.",
5666 [1, 21],
5667 );
5668 }
5669
5670 #[test]
5671 fn test_parse_object_random_bracket() {
5672 let some_program_string = r#"{]}"#;
5673
5674 assert_err(
5675 some_program_string,
5676 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5677 [0, 1],
5678 );
5679 }
5680
5681 #[test]
5682 fn test_parse_object_shorthand_missing_comma() {
5683 let some_program_string = r#"
5684bar = 1
5685 {
5686 foo = bar,
5687 bar
5688 bat = man
5689 }"#;
5690
5691 assert_err(
5692 some_program_string,
5693 "Unexpected character encountered. You might be missing a comma in between properties.",
5694 [54, 89],
5695 );
5696 }
5697
5698 #[test]
5699 fn test_unary_not_on_keyword_bool() {
5700 let some_program_string = r#"!true"#;
5701 let module_id = ModuleId::default();
5702 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); let actual = match unary_expression.parse(tokens.as_slice()) {
5704 Ok(x) => x,
5706 Err(e) => panic!("{e:?}"),
5707 };
5708 assert_eq!(actual.operator, UnaryOperator::Not);
5709 crate::parsing::top_level_parse(some_program_string).unwrap(); }
5711
5712 #[test]
5713 fn test_comments_in_args() {
5714 let code = r#"x = rectangle(
5718 recessSketch,
5719 x = [cx, cy],
5720 y = 1, // y component
5721 z = 1 // axial thickness
5722)"#;
5723 let _result = crate::parsing::top_level_parse(code).unwrap();
5724 }
5725
5726 #[test]
5727 fn test_comments_and_block_in_args() {
5728 let code = r#"shape = rectangle(
5729 recessSketch,
5730 x = [cx, cy],
5731 y = 1,
5732 z = 1 /* axial thickness */
5733 // finishing comment
5734 /* block metadata */
5735)"#;
5736 crate::parsing::top_level_parse(code).unwrap();
5737 }
5738
5739 #[test]
5740 fn test_array_multiple_trailing_comments() {
5741 let code = r#"points = [
5742 0,
5743 1,
5744 2 // radial segment
5745 // keep origin
5746 /* padding */
5747]"#;
5748 crate::parsing::top_level_parse(code).unwrap();
5749 }
5750
5751 #[test]
5752 fn test_object_multiple_trailing_comments() {
5753 let code = r#"config = {
5754 width = 10,
5755 height = 5,
5756 depth = 3 // final face depth
5757 // allow taper
5758 /* tolerance */
5759}"#;
5760 crate::parsing::top_level_parse(code).unwrap();
5761 }
5762
5763 #[test]
5764 fn test_sensible_error_when_missing_comma_between_fn_args() {
5765 let program_source = "startSketchOn(XY)
5766|> arc(
5767 endAbsolute = [0, 50]
5768 interiorAbsolute = [-50, 0]
5769)";
5770 let expected_src_start = program_source.find("]").unwrap();
5771 let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5772 ParseContext::init();
5773 let err = program
5774 .parse(tokens.as_slice())
5775 .expect_err("Program succeeded, but it should have failed");
5776 let cause = err
5777 .inner()
5778 .cause
5779 .as_ref()
5780 .expect("Found an error, but there was no cause. Add a cause.");
5781 assert_eq!(cause.message, "Missing comma between arguments, try adding a comma in",);
5782 assert_eq!(cause.source_range.start() - 1, expected_src_start);
5783 }
5784
5785 #[test]
5786 fn test_sensible_error_when_missing_rhs_of_kw_arg() {
5787 for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
5788 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5789 let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
5790 let cause = err.inner().cause.as_ref().unwrap();
5791 assert_eq!(
5792 cause.message, "This argument has a label, but no value. Put some value after the equals sign",
5793 "failed test {i}: {program}"
5794 );
5795 assert_eq!(
5796 cause.source_range.start(),
5797 program.find("y").unwrap(),
5798 "failed test {i}: {program}"
5799 );
5800 }
5801 }
5802
5803 #[test]
5804 fn test_sensible_error_when_unexpected_token_in_fn_call() {
5805 let program_source = "1
5806|> extrude(
5807 length=depth,
5808})";
5809 let expected_src_start = program_source.find("}").expect("Program should have an extraneous }");
5810 let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5811 ParseContext::init();
5812 let err = program.parse(tokens.as_slice()).unwrap_err();
5813 let cause = err
5814 .inner()
5815 .cause
5816 .as_ref()
5817 .expect("Found an error, but there was no cause. Add a cause.");
5818 assert_eq!(cause.message, "There was an unexpected `}`. Try removing it.",);
5819 assert_eq!(cause.source_range.start(), expected_src_start);
5820 }
5821
5822 #[test]
5823 fn test_sensible_error_when_using_keyword_as_arg_label() {
5824 for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
5825 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5826 let err = match fn_call_kw.parse(tokens.as_slice()) {
5827 Err(e) => e,
5828 Ok(_ast) => {
5829 panic!("Expected this to error but it didn't");
5830 }
5831 };
5832 let cause = err.inner().cause.as_ref().unwrap();
5833 assert_eq!(
5834 cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
5835 "failed test {i}: {program}"
5836 );
5837 assert_eq!(
5838 cause.source_range.start(),
5839 program.find("fn").unwrap(),
5840 "failed test {i}: {program}"
5841 );
5842 }
5843 }
5844
5845 #[test]
5846 fn test_sensible_error_when_missing_rhs_of_obj_property() {
5847 for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {
5848 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5849 let err = object.parse(tokens.as_slice()).unwrap_err();
5850 let cause = err.inner().cause.as_ref().unwrap();
5851 assert_eq!(
5852 cause.message, "This property has a label, but no value. Put some value after the equals sign",
5853 "failed test {i}: {program}"
5854 );
5855 assert_eq!(
5856 cause.source_range.start(),
5857 program.rfind('=').unwrap(),
5858 "failed test {i}: {program}"
5859 );
5860 }
5861 }
5862
5863 #[test]
5864 fn test_sensible_error_duplicated_args() {
5865 let program = r#"f(arg = 1, normal = 44, arg = 2)"#;
5866 let (_, mut errs) = assert_no_fatal(program);
5867 assert_eq!(errs.len(), 1);
5868 let err = errs.pop().unwrap();
5869 assert_eq!(
5870 err.message,
5871 "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.",
5872 );
5873 }
5874
5875 #[test]
5876 fn test_sensible_error_when_missing_else() {
5877 let program_source = "x = if (true) { 2 }";
5878 let expected_src_start = program_source.find("if").unwrap();
5879 let cause = must_fail_compilation(program_source);
5880 assert!(!cause.was_fatal);
5881 assert_eq!(cause.err.message, MISSING_ELSE);
5882 assert_eq!(cause.err.source_range.start(), expected_src_start);
5883 }
5884
5885 #[test]
5886 fn test_sensible_error_when_using_keyword_as_object_key() {
5887 let source = r#"material = {
5888 type = "Steel 45"
5889}"#;
5890 let expected_src_start = source.find("type").unwrap();
5891 let cause = must_fail_compilation(source);
5892 assert!(cause.was_fatal);
5893 assert_eq!(cause.err.message, KEYWORD_EXPECTING_IDENTIFIER);
5894 assert_eq!(cause.err.source_range.start(), expected_src_start);
5895 }
5896
5897 struct MustFail {
5898 err: CompilationError,
5899 was_fatal: bool,
5900 }
5901
5902 fn must_fail_compilation(program_source: &str) -> MustFail {
5903 let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
5904 ParseContext::init();
5905 match program.parse(tokens.as_slice()) {
5906 Err(e) => {
5908 let err = e
5909 .into_inner()
5910 .cause
5911 .expect("found a parse error, but no cause. Add a cause.");
5912 MustFail { err, was_fatal: true }
5913 } Ok(_) => {
5915 let mut e = None;
5916 CTXT.with_borrow(|v| e = v.clone().map(|ctxt| ctxt.errors));
5917 let err = e
5918 .unwrap()
5919 .first()
5920 .expect("Program succeeded, but it should have failed")
5921 .to_owned();
5922 MustFail { err, was_fatal: false }
5923 }
5924 }
5925 }
5926
5927 #[test]
5928 fn test_sensible_error_when_missing_else_brace() {
5929 let program_source = "x = if (true) { 2 } else ";
5930 let expected_src_start = program_source.find("else").unwrap();
5931 let cause = must_fail_compilation(program_source);
5932 assert!(!cause.was_fatal);
5933 assert_eq!(cause.err.message, ELSE_STRUCTURE);
5934 assert_eq!(cause.err.source_range.start(), expected_src_start);
5935 }
5936
5937 #[test]
5938 fn test_sensible_error_when_does_not_end_in_expr() {
5939 let program_source = "x = if (true) { 2 } else {y = 3}";
5940 let expected_src_start = program_source.find("else").unwrap();
5941 let cause = must_fail_compilation(program_source);
5942 assert!(!cause.was_fatal);
5943 assert_eq!(cause.err.message, ELSE_MUST_END_IN_EXPR);
5944 assert_eq!(cause.err.source_range.start(), expected_src_start);
5945 }
5946
5947 #[test]
5948 fn test_if_expr_in_pipeline() {
5949 let code = r#"0
5950|> if true {
5951 f(%)
5952} else {
5953 f(%)
5954}"#;
5955 let _result = crate::parsing::top_level_parse(code).unwrap();
5956 }
5957}
5958
5959#[cfg(test)]
5960mod snapshot_math_tests {
5961 use super::*;
5962
5963 macro_rules! snapshot_test {
5967 ($func_name:ident, $test_kcl_program:expr_2021) => {
5968 #[test]
5969 fn $func_name() {
5970 let module_id = crate::ModuleId::default();
5971 let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5972 ParseContext::init();
5973
5974 let actual = match binary_expression.parse(tokens.as_slice()) {
5975 Ok(x) => x,
5976 Err(_e) => panic!("could not parse test"),
5977 };
5978 insta::assert_json_snapshot!(actual);
5979 let _ = ParseContext::take();
5980 }
5981 };
5982 }
5983
5984 snapshot_test!(a, "1 + 2");
5985 snapshot_test!(b, "1+2");
5986 snapshot_test!(c, "1 -2");
5987 snapshot_test!(d, "1 + 2 * 3");
5988 snapshot_test!(e, "1 * ( 2 + 3 )");
5989 snapshot_test!(f, "1 * ( 2 + 3 ) / 4");
5990 snapshot_test!(g, "1 + ( 2 + 3 ) / 4");
5991 snapshot_test!(h, "1 * (( 2 + 3 ) / 4 + 5 )");
5992 snapshot_test!(i, "1 * ((( 2 + 3 )))");
5993 snapshot_test!(j, "distance * p * FOS * 6 / (sigmaAllow * width)");
5994 snapshot_test!(k, "2 + (((3)))");
5995}
5996
5997#[cfg(test)]
5998mod snapshot_tests {
5999 use super::*;
6000
6001 macro_rules! snapshot_test {
6005 ($func_name:ident, $test_kcl_program:expr_2021) => {
6006 #[test]
6007 fn $func_name() {
6008 let module_id = crate::ModuleId::default();
6009 println!("{}", $test_kcl_program);
6010 let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
6011 print_tokens(tokens.as_slice());
6012 ParseContext::init();
6013 let actual = match program.parse(tokens.as_slice()) {
6014 Ok(x) => x,
6015 Err(e) => panic!("could not parse test: {e:?}"),
6016 };
6017 let mut settings = insta::Settings::clone_current();
6018 settings.set_sort_maps(true);
6019 settings.bind(|| {
6020 insta::assert_json_snapshot!(actual);
6021 });
6022 let _ = ParseContext::take();
6023 }
6024 };
6025 }
6026
6027 snapshot_test!(
6028 a,
6029 r#"boxSketch = startSketchOn(XY)
6030 |> startProfileAt(at = [0, 0])
6031 |> line(at = [0, 10])
6032 |> tangentialArc(end = [-5, 5])
6033 |> line(at = [5, -15])
6034 |> extrude(length=10)
6035"#
6036 );
6037 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)");
6040 snapshot_test!(d, "myVar = 5 + 6 |> myFunc(45)");
6041 snapshot_test!(e, "x = 1 * (3 - 4)");
6042 snapshot_test!(f, r#"x = 1 // this is an inline comment"#);
6043 snapshot_test!(
6044 g,
6045 r#"fn x() {
6046 return sg
6047 return sg
6048 }"#
6049 );
6050 snapshot_test!(d2, r#"x = -leg2 + thickness"#);
6051 snapshot_test!(
6052 h,
6053 r#"obj = { a = 1, b = 2 }
6054 height = 1 - obj.a"#
6055 );
6056 snapshot_test!(
6057 i,
6058 r#"obj = { a = 1, b = 2 }
6059 height = 1 - obj["a"]"#
6060 );
6061 snapshot_test!(
6062 j,
6063 r#"obj = { a = 1, b = 2 }
6064 height = obj["a"] - 1"#
6065 );
6066 snapshot_test!(
6067 k,
6068 r#"obj = { a = 1, b = 2 }
6069 height = [1 - obj["a"], 0]"#
6070 );
6071 snapshot_test!(
6072 l,
6073 r#"obj = { a = 1, b = 2 }
6074 height = [obj["a"] - 1, 0]"#
6075 );
6076 snapshot_test!(
6077 m,
6078 r#"obj = {a = 1, b = 2 }
6079 height = [obj["a"] -1, 0]"#
6080 );
6081 snapshot_test!(n, "height = 1 - obj.a");
6082 snapshot_test!(o, "six = 1 + 2 + 3");
6083 snapshot_test!(p, "five = 3 * 1 + 2");
6084 snapshot_test!(q, r#"height = [ obj["a"], 0 ]"#);
6085 snapshot_test!(
6086 r,
6087 r#"obj = { a = 1, b = 2 }
6088 height = obj["a"]"#
6089 );
6090 snapshot_test!(s, r#"prop = yo["one"][two]"#);
6091 snapshot_test!(t, r#"pt1 = b1[x]"#);
6092 snapshot_test!(u, "prop = yo.one.two.three.four");
6093 snapshot_test!(v, r#"pt1 = b1[0]"#);
6094 snapshot_test!(w, r#"pt1 = b1['zero']"#);
6095 snapshot_test!(x, r#"pt1 = b1.zero"#);
6096 snapshot_test!(y, r#"sg = startSketchOn(XY) |> startProfile(pos)"#);
6097 snapshot_test!(
6098 z,
6099 "sg = startSketchOn(XY)
6100 |> startProfile(pos) |> line([0, -scale])"
6101 );
6102 snapshot_test!(aa, r#"sg = -scale"#);
6103 snapshot_test!(ab, "line(endAbsolute = [0, -1])");
6104 snapshot_test!(
6105 ad,
6106 r#"
6107 fn firstPrimeNumber() {
6108 return 2
6109 }
6110 firstPrimeNumber()"#
6111 );
6112 snapshot_test!(
6113 ae,
6114 r#"fn thing(param) {
6115 return true
6116 }
6117 thing(false)"#
6118 );
6119 snapshot_test!(
6120 af,
6121 r#"mySketch = startSketchOn(XY)
6122 |> startProfile(at = [0,0])
6123 |> line(endAbsolute = [0, 1], tag = $myPath)
6124 |> line(endAbsolute = [1, 1])
6125 |> line(endAbsolute = [1, 0], tag = $rightPath)
6126 |> close()"#
6127 );
6128 snapshot_test!(
6129 ag,
6130 "mySketch = startSketchOn(XY) |> startProfile(at = [0,0]) |> line(endAbsolute = [1, 1]) |> close()"
6131 );
6132 snapshot_test!(ah, "myBox = startSketchOn(XY) |> startProfile(at = p)");
6133 snapshot_test!(ai, r#"myBox = f(1) |> g(2)"#);
6134 snapshot_test!(
6135 aj,
6136 r#"myBox = startSketchOn(XY) |> startProfile(at = p) |> line(end = [0, l])"#
6137 );
6138 snapshot_test!(ak, "line(endAbsolute = [0, 1])");
6139 snapshot_test!(ap, "mySketch = startSketchOn(XY) |> startProfile(at = [0,0])");
6140 snapshot_test!(aq, "log(number = 5, msg = \"hello\", id=aIdentifier)");
6141 snapshot_test!(ar, r#"5 + "a""#);
6142 snapshot_test!(at, "line([0, l])");
6143 snapshot_test!(au, include_str!("../../e2e/executor/inputs/cylinder.kcl"));
6144 snapshot_test!(av, "fn f(angle?) { return default(maybe=angle, otherwise=360) }");
6145 snapshot_test!(
6146 aw,
6147 "numbers = [
6148 1,
6149 // A,
6150 // B,
6151 3,
6152 ]"
6153 );
6154 snapshot_test!(
6155 ax,
6156 "numbers = [
6157 1,
6158 2,
6159 // A,
6160 // B,
6161 ]"
6162 );
6163 snapshot_test!(
6164 ay,
6165 "let props = {
6166 a: 1,
6167 // b: 2,
6168 c: 3,
6169 }"
6170 );
6171 snapshot_test!(
6172 az,
6173 "props = {
6174 a: 1,
6175 // b: 2,
6176 c: 3
6177 }"
6178 );
6179 snapshot_test!(
6180 bb,
6181 r#"
6182my14 = 4 ^ 2 - 3 ^ 2 * 2
6183"#
6184 );
6185 snapshot_test!(
6186 bc,
6187 r#"x = if true {
6188 3
6189 } else {
6190 4
6191 }"#
6192 );
6193 snapshot_test!(
6194 bd,
6195 r#"x = if true {
6196 3
6197 } else if func(radius) {
6198 4
6199 } else {
6200 5
6201 }"#
6202 );
6203 snapshot_test!(be, "x = 3 == 3");
6204 snapshot_test!(bf, "x = 3 != 3");
6205 snapshot_test!(bg, r#"x = 4"#);
6206 snapshot_test!(bh, "obj = {center = [10, 10], radius =5}");
6207 snapshot_test!(
6208 bi,
6209 r#"x = 3
6210 obj = { x, y = 4}"#
6211 );
6212 snapshot_test!(bj, "true");
6213 snapshot_test!(bk, "truee");
6214 snapshot_test!(bl, "x = !true");
6215 snapshot_test!(bm, "x = true & false");
6216 snapshot_test!(bn, "x = true | false");
6217 snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
6218 snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
6219 snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
6220 snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
6221 snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
6222 snapshot_test!(
6223 kw_function_decl_with_default_and_type,
6224 r#"fn foo(x?: number = 2) { return 1 }"#
6225 );
6226 snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#);
6227 snapshot_test!(
6228 kw_function_call_multiline,
6229 r#"val = f(
6230 arg = x,
6231 foo = x,
6232 bar = x,
6233 )"#
6234 );
6235 snapshot_test!(
6236 kw_function_call_multiline_with_comments,
6237 r#"val = f(
6238 arg = x,
6239 // foo = x,
6240 bar = x,
6241 )"#
6242 );
6243 snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#);
6244 snapshot_test!(
6245 array_ranges,
6246 r#"incl = [1..10]
6247 excl = [0..<10]"#
6248 );
6249 snapshot_test!(space_between_unary_and_operand, r#"x = - 1"#);
6250 snapshot_test!(
6251 space_between_expr_and_array,
6252 r#"outer_points = [1, 2, 3]
6253i = 0
6254outer_points[ if i + 1 < 5 { i + 1 } else { 0 } ]"#
6255 );
6256}
6257
6258#[allow(unused)]
6259#[cfg(test)]
6260pub(crate) fn print_tokens(tokens: TokenSlice) {
6261 for (i, tok) in tokens.iter().enumerate() {
6262 println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
6263 }
6264}