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