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