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