1#![allow(clippy::result_large_err)]
3
4use std::{cell::RefCell, collections::BTreeMap};
5
6use indexmap::IndexMap;
7use winnow::{
8 combinator::{alt, delimited, opt, peek, preceded, repeat, repeat_till, separated, separated_pair, terminated},
9 dispatch,
10 error::{ErrMode, StrContext, StrContextValue},
11 prelude::*,
12 stream::Stream,
13 token::{any, none_of, one_of, take_till},
14};
15
16use super::{
17 ast::types::{AscribedExpression, ImportPath, LabelledExpression},
18 token::{NumericSuffix, RESERVED_WORDS},
19 DeprecationKind,
20};
21use crate::{
22 errors::{CompilationError, Severity, Tag},
23 execution::types::ArrayLen,
24 parsing::{
25 ast::types::{
26 Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
27 BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
28 FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
29 ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name,
30 Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
31 PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type,
32 TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
33 },
34 math::BinaryExpressionToken,
35 token::{Token, TokenSlice, TokenType},
36 PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
37 },
38 SourceRange, TypedPath, IMPORT_FILE_EXTENSIONS,
39};
40
41thread_local! {
42 static CTXT: RefCell<Option<ParseContext>> = const { RefCell::new(None) };
44}
45
46pub fn run_parser(i: TokenSlice) -> super::ParseResult {
47 let _stats = crate::log::LogPerfStats::new("Parsing");
48 ParseContext::init();
49
50 let result = match program.parse(i) {
51 Ok(result) => Some(result),
52 Err(e) => {
53 ParseContext::err(e.into());
54 None
55 }
56 };
57 let ctxt = ParseContext::take();
58 (result, ctxt.errors).into()
59}
60
61#[derive(Debug, Clone, Default)]
65struct ParseContext {
66 pub errors: Vec<CompilationError>,
67}
68
69impl ParseContext {
70 fn new() -> Self {
71 ParseContext { errors: Vec::new() }
72 }
73
74 fn init() {
76 assert!(CTXT.with_borrow(|ctxt| ctxt.is_none()));
77 CTXT.with_borrow_mut(|ctxt| *ctxt = Some(ParseContext::new()));
78 }
79
80 fn take() -> ParseContext {
83 CTXT.with_borrow_mut(|ctxt| ctxt.take()).unwrap()
84 }
85
86 fn err(err: CompilationError) {
88 CTXT.with_borrow_mut(|ctxt| {
89 let errors = &mut ctxt.as_mut().unwrap().errors;
97 for e in errors.iter_mut().rev() {
98 if e.source_range == err.source_range {
99 *e = err;
100 return;
101 }
102 }
103 errors.push(err);
104 });
105 }
106
107 fn warn(mut e: CompilationError) {
109 e.severity = Severity::Warning;
110 Self::err(e);
111 }
112}
113
114#[derive(Debug, Clone)]
119pub(crate) struct ContextError<C = StrContext> {
120 pub context: Vec<C>,
121 pub cause: Option<CompilationError>,
122}
123
124impl From<winnow::error::ParseError<TokenSlice<'_>, ContextError>> for CompilationError {
125 fn from(err: winnow::error::ParseError<TokenSlice<'_>, ContextError>) -> Self {
126 let Some(last_token) = err.input().last() else {
127 return CompilationError::fatal(Default::default(), "file is empty");
128 };
129
130 let (input, offset, err) = (err.input(), err.offset(), err.clone().into_inner());
131
132 if let Some(e) = err.cause {
133 return e;
134 }
135
136 if offset >= input.len() {
138 let context = err.context.first();
139 return CompilationError::fatal(
140 last_token.as_source_range(),
141 match context {
142 Some(what) => format!("Unexpected end of file. The compiler {what}"),
143 None => "Unexpected end of file while still parsing".to_owned(),
144 },
145 );
146 }
147
148 let bad_token = input.token(offset);
149 CompilationError::fatal(
152 bad_token.as_source_range(),
153 format!("Unexpected token: {}", bad_token.value),
154 )
155 }
156}
157
158impl<C> From<CompilationError> for ContextError<C> {
159 fn from(e: CompilationError) -> Self {
160 Self {
161 context: Default::default(),
162 cause: Some(e),
163 }
164 }
165}
166
167impl<C> std::default::Default for ContextError<C> {
168 fn default() -> Self {
169 Self {
170 context: Default::default(),
171 cause: None,
172 }
173 }
174}
175
176impl<I, C> winnow::error::ParserError<I> for ContextError<C>
177where
178 I: Stream,
179{
180 #[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<Node<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(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, bool_value))
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 let end_inclusive = alt((end_inclusive_range.map(|_| true), end_exclusive_range.map(|_| false))).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,
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
1732pub(super) fn 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 != '.' && c != '/' && c != '\\')
1866 {
1867 return Err(ErrMode::Cut(
1868 CompilationError::fatal(
1869 path_range,
1870 "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
1871 )
1872 .into(),
1873 ));
1874 }
1875
1876 if path_string.starts_with("..") {
1877 return Err(ErrMode::Cut(
1878 CompilationError::fatal(
1879 path_range,
1880 "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1881 )
1882 .into(),
1883 ));
1884 }
1885
1886 if path_string.starts_with('/') || path_string.starts_with('\\') {
1888 return Err(ErrMode::Cut(
1889 CompilationError::fatal(
1890 path_range,
1891 "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
1892 )
1893 .into(),
1894 ));
1895 }
1896
1897 if (path_string.contains('/') || path_string.contains('\\'))
1898 && !(path_string.ends_with("/main.kcl") || path_string.ends_with("\\main.kcl"))
1899 {
1900 return Err(ErrMode::Cut(
1901 CompilationError::fatal(path_range, "import path to a subdirectory must only refer to main.kcl.")
1902 .into(),
1903 ));
1904 }
1905
1906 ImportPath::Kcl {
1907 filename: TypedPath::new(&path_string),
1908 }
1909 } else if path_string.starts_with("std::") {
1910 ParseContext::warn(CompilationError::err(
1911 path_range,
1912 "explicit imports from the standard library are experimental, likely to be buggy, and likely to change.",
1913 ));
1914
1915 let segments: Vec<String> = path_string.split("::").map(str::to_owned).collect();
1916
1917 for s in &segments {
1918 if s.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') || s.starts_with('_') {
1919 return Err(ErrMode::Cut(
1920 CompilationError::fatal(path_range, "invalid path in import statement.").into(),
1921 ));
1922 }
1923 }
1924
1925 if segments.len() != 2 {
1927 return Err(ErrMode::Cut(
1928 CompilationError::fatal(
1929 path_range,
1930 format!("Invalid import path for import from std: {}.", path_string),
1931 )
1932 .into(),
1933 ));
1934 }
1935
1936 ImportPath::Std { path: segments }
1937 } else if path_string.contains('.') {
1938 let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
1939 if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
1940 ParseContext::warn(CompilationError::err(
1941 path_range,
1942 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(", ")),
1943 ))
1944 }
1945 ImportPath::Foreign {
1946 path: TypedPath::new(&path_string),
1947 }
1948 } else {
1949 return Err(ErrMode::Cut(
1950 CompilationError::fatal(
1951 path_range,
1952 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(", ")),
1953 )
1954 .into(),
1955 ));
1956 };
1957
1958 Ok(path)
1959}
1960
1961fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
1962 let name = nameable_identifier
1963 .context(expected("an identifier to import"))
1964 .parse_next(i)?;
1965 let start = name.start;
1966 let module_id = name.module_id;
1967 let alias = opt(preceded(
1968 (whitespace, import_as_keyword, whitespace),
1969 identifier.context(expected("an identifier to alias the import")),
1970 ))
1971 .parse_next(i)?;
1972 let end = if let Some(ref alias) = alias {
1973 alias.end
1974 } else {
1975 name.end
1976 };
1977 Ok(Node::new(
1978 ImportItem {
1979 name,
1980 alias,
1981 digest: None,
1982 },
1983 start,
1984 end,
1985 module_id,
1986 ))
1987}
1988
1989fn import_as_keyword(i: &mut TokenSlice) -> PResult<Token> {
1990 any.try_map(|token: Token| {
1991 if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "as" {
1992 Ok(token)
1993 } else {
1994 Err(CompilationError::fatal(
1995 token.as_source_range(),
1996 format!("{} is not the 'as' keyword", token.value.as_str()),
1997 ))
1998 }
1999 })
2000 .context(expected("the 'as' keyword"))
2001 .parse_next(i)
2002}
2003
2004fn return_stmt(i: &mut TokenSlice) -> PResult<Node<ReturnStatement>> {
2006 let ret = any
2007 .try_map(|token: Token| {
2008 if matches!(token.token_type, TokenType::Keyword) && token.value == "return" {
2009 Ok(token)
2010 } else {
2011 Err(CompilationError::fatal(
2012 token.as_source_range(),
2013 format!("{} is not a return keyword", token.value.as_str()),
2014 ))
2015 }
2016 })
2017 .context(expected(
2018 "the 'return' keyword, which ends your function (and becomes this function's value when it's called)",
2019 ))
2020 .parse_next(i)?;
2021 require_whitespace(i)?;
2022 let argument = expression(i)?;
2023 Ok(Node::new_node(
2024 ret.start,
2025 argument.end(),
2026 ret.module_id,
2027 ReturnStatement { argument, digest: None },
2028 ))
2029}
2030
2031fn expression(i: &mut TokenSlice) -> PResult<Expr> {
2033 alt((
2034 pipe_expression.map(Box::new).map(Expr::PipeExpression),
2035 expression_but_not_pipe,
2036 ))
2037 .context(expected("a KCL value"))
2038 .parse_next(i)
2039}
2040
2041fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
2042 let mut expr = alt((
2043 binary_expression.map(Box::new).map(Expr::BinaryExpression),
2044 unary_expression.map(Box::new).map(Expr::UnaryExpression),
2045 expr_allowed_in_pipe_expr,
2046 ))
2047 .context(expected("a KCL value"))
2048 .parse_next(i)?;
2049
2050 let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2051 if let Some((_, _, ty)) = ty {
2052 expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2053 }
2054 let label = opt(label).parse_next(i)?;
2055 match label {
2056 Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
2057 None => Ok(expr),
2058 }
2059}
2060
2061fn label(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2062 let result = preceded(
2063 (whitespace, import_as_keyword, whitespace),
2064 identifier.context(expected("an identifier")),
2065 )
2066 .parse_next(i)?;
2067
2068 ParseContext::warn(CompilationError::err(
2069 SourceRange::new(result.start, result.end, result.module_id),
2070 "Using `as` for tagging expressions is experimental, likely to be buggy, and likely to change",
2071 ));
2072
2073 Ok(result)
2074}
2075
2076fn unnecessarily_bracketed(i: &mut TokenSlice) -> PResult<Expr> {
2077 delimited(
2078 terminated(open_paren, opt(whitespace)),
2079 expression,
2080 preceded(opt(whitespace), close_paren),
2081 )
2082 .parse_next(i)
2083}
2084
2085fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
2086 alt((
2087 member_expression.map(Box::new).map(Expr::MemberExpression),
2088 bool_value.map(Box::new).map(Expr::Literal),
2089 tag.map(Box::new).map(Expr::TagDeclarator),
2090 literal.map(Expr::Literal),
2091 fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2092 name.map(Box::new).map(Expr::Name),
2093 array,
2094 object.map(Box::new).map(Expr::ObjectExpression),
2095 pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
2096 function_expr,
2097 if_expr.map(Expr::IfExpression),
2098 unnecessarily_bracketed,
2099 ))
2100 .context(expected("a KCL expression (but not a pipe expression)"))
2101 .parse_next(i)
2102}
2103
2104fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
2105 let mut expr = alt((
2106 unary_expression.map(Box::new).map(Expr::UnaryExpression),
2107 bool_value.map(Box::new).map(Expr::Literal),
2108 member_expression.map(Box::new).map(Expr::MemberExpression),
2109 literal.map(Expr::Literal),
2110 fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
2111 name.map(Box::new).map(Expr::Name),
2112 binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
2113 unnecessarily_bracketed,
2114 ))
2115 .context(expected(
2116 "a KCL value which can be used as an argument/operand to an operator",
2117 ))
2118 .parse_next(i)?;
2119
2120 let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
2121 if let Some((_, _, ty)) = ty {
2122 expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
2123 }
2124
2125 Ok(expr)
2126}
2127
2128fn item_visibility(i: &mut TokenSlice) -> PResult<(ItemVisibility, Token)> {
2130 any.verify_map(|token: Token| {
2131 if token.token_type == TokenType::Keyword && token.value == "export" {
2132 Some((ItemVisibility::Export, token))
2133 } else {
2134 None
2135 }
2136 })
2137 .context(expected("item visibility, e.g. 'export'"))
2138 .parse_next(i)
2139}
2140
2141fn declaration_keyword(i: &mut TokenSlice) -> PResult<(VariableKind, Token)> {
2142 let res = any
2143 .verify_map(|token: Token| token.declaration_keyword().map(|kw| (kw, token)))
2144 .parse_next(i)?;
2145 Ok(res)
2146}
2147
2148fn declaration(i: &mut TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
2150 let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2151 .parse_next(i)?
2152 .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2153 let decl_token = opt(declaration_keyword).parse_next(i)?;
2154 if decl_token.is_some() {
2155 require_whitespace(i)?;
2158 }
2159
2160 let id = binding_name
2161 .context(expected(
2162 "an identifier, which becomes name you're binding the value to",
2163 ))
2164 .parse_next(i)?;
2165 let (kind, mut start, dec_end) = if let Some((kind, token)) = &decl_token {
2166 (*kind, token.start, token.end)
2167 } else {
2168 (VariableKind::Const, id.start, id.end)
2169 };
2170 if let Some(token) = visibility_token {
2171 start = token.start;
2172 }
2173
2174 ignore_whitespace(i);
2175
2176 let val =
2177 if kind == VariableKind::Fn {
2178 let eq = opt(equals).parse_next(i)?;
2179 ignore_whitespace(i);
2180
2181 let val = function_decl
2182 .map(Box::new)
2183 .map(Expr::FunctionExpression)
2184 .context(expected("a KCL function expression, like () { return 1 }"))
2185 .parse_next(i);
2186
2187 if let Some(t) = eq {
2188 ParseContext::warn(
2189 CompilationError::err(t.as_source_range(), "Unnecessary `=` in function declaration")
2190 .with_suggestion("Remove `=`", "", None, Tag::Unnecessary),
2191 );
2192 }
2193
2194 val
2195 } else {
2196 equals(i)?;
2197 ignore_whitespace(i);
2198
2199 let val = expression
2200 .try_map(|val| {
2201 if matches!(val, Expr::FunctionExpression(_)) {
2204 return Err(CompilationError::fatal(
2205 SourceRange::new(start, dec_end, id.module_id),
2206 format!("Expected a `fn` variable kind, found: `{}`", kind),
2207 ));
2208 }
2209 Ok(val)
2210 })
2211 .context(expected("a KCL value, which is being bound to a variable"))
2212 .parse_next(i);
2213
2214 if let Some((_, tok)) = decl_token {
2215 let range_to_remove = SourceRange::new(tok.start, id.start, id.module_id);
2216 ParseContext::err(
2217 CompilationError::err(
2218 tok.as_source_range(),
2219 format!(
2220 "Using `{}` to declare constants is deprecated; no keyword is required",
2221 tok.value
2222 ),
2223 )
2224 .with_suggestion(
2225 format!("Remove `{}`", tok.value),
2226 "",
2227 Some(range_to_remove),
2228 Tag::Deprecated,
2229 ),
2230 );
2231 }
2232
2233 val
2234 }
2235 .map_err(|e| e.cut())?;
2236
2237 let end = val.end();
2238 let module_id = id.module_id;
2239 Ok(Node::boxed(
2240 VariableDeclaration {
2241 declaration: Node::new_node(
2242 id.start,
2243 end,
2244 module_id,
2245 VariableDeclarator {
2246 id,
2247 init: val,
2248 digest: None,
2249 },
2250 ),
2251 visibility,
2252 kind,
2253 digest: None,
2254 },
2255 start,
2256 end,
2257 module_id,
2258 ))
2259}
2260
2261fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> {
2262 let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
2263 .parse_next(i)?
2264 .map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
2265
2266 let decl_token = ty(i)?;
2267 let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start);
2268 whitespace(i)?;
2269
2270 let name = alt((
2271 fun.map(|t| {
2272 Node::new(
2273 Identifier {
2274 name: "fn".to_owned(),
2275 digest: None,
2276 },
2277 t.start,
2278 t.end,
2279 t.module_id,
2280 )
2281 }),
2282 identifier,
2283 ))
2284 .parse_next(i)?;
2285 let mut end = name.end;
2286
2287 let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() {
2288 ignore_whitespace(i);
2289 open_paren(i)?;
2290 ignore_whitespace(i);
2291 let args: Vec<_> = separated(0.., identifier, comma_sep).parse_next(i)?;
2292 ignore_trailing_comma(i);
2293 ignore_whitespace(i);
2294 end = close_paren(i)?.end;
2295 Some(args)
2296 } else {
2297 None
2298 };
2299
2300 let alias = if peek((opt(whitespace), equals)).parse_next(i).is_ok() {
2301 ignore_whitespace(i);
2302 equals(i)?;
2303 ignore_whitespace(i);
2304 let ty = type_(i)?;
2305
2306 ParseContext::warn(CompilationError::err(
2307 ty.as_source_range(),
2308 "Type aliases are experimental, likely to change in the future, and likely to not work properly.",
2309 ));
2310
2311 Some(ty)
2312 } else {
2313 None
2314 };
2315
2316 let module_id = name.module_id;
2317 let result = Node::boxed(
2318 TypeDeclaration {
2319 name,
2320 args,
2321 alias,
2322 visibility,
2323 digest: None,
2324 },
2325 start,
2326 end,
2327 module_id,
2328 );
2329
2330 ParseContext::warn(CompilationError::err(
2331 result.as_source_range(),
2332 "Type declarations are experimental, likely to change, and may or may not do anything useful.",
2333 ));
2334
2335 Ok(result)
2336}
2337
2338impl TryFrom<Token> for Node<Identifier> {
2339 type Error = CompilationError;
2340
2341 fn try_from(token: Token) -> Result<Self, Self::Error> {
2342 if token.token_type == TokenType::Word {
2343 Ok(Node::new(
2344 Identifier {
2345 name: token.value,
2346 digest: None,
2347 },
2348 token.start,
2349 token.end,
2350 token.module_id,
2351 ))
2352 } else {
2353 Err(CompilationError::fatal(
2354 token.as_source_range(),
2355 format!(
2356 "Cannot assign a variable to a reserved keyword: {}",
2357 token.value.as_str()
2358 ),
2359 ))
2360 }
2361 }
2362}
2363
2364fn identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2366 any.try_map(Node::<Identifier>::try_from)
2367 .context(expected("an identifier, e.g. 'width' or 'myPart'"))
2368 .parse_next(i)
2369}
2370
2371fn nameable_identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
2372 let result = identifier.parse_next(i)?;
2373
2374 if !result.is_nameable() {
2375 let desc = if result.name == "_" {
2376 "Underscores"
2377 } else {
2378 "Names with a leading underscore"
2379 };
2380 ParseContext::err(CompilationError::err(
2381 SourceRange::new(result.start, result.end, result.module_id),
2382 format!("{desc} cannot be referred to, only declared."),
2383 ));
2384 }
2385
2386 Ok(result)
2387}
2388
2389fn name(i: &mut TokenSlice) -> PResult<Node<Name>> {
2390 let abs_path = opt(double_colon).parse_next(i)?;
2391 let mut idents: NodeList<Identifier> = separated(1.., nameable_identifier, double_colon)
2392 .parse_next(i)
2393 .map_err(|e| e.backtrack())?;
2394
2395 let mut start = idents[0].start;
2396 if let Some(abs_path) = &abs_path {
2397 start = abs_path.start;
2398 }
2399 let abs_path = abs_path.is_some();
2400
2401 let name = idents.pop().unwrap();
2402 let end = name.end;
2403 let module_id = name.module_id;
2404 let result = Node::new(
2405 Name {
2406 name,
2407 path: idents,
2408 abs_path,
2409 digest: None,
2410 },
2411 start,
2412 end,
2413 module_id,
2414 );
2415
2416 if let Some(suggestion) = super::deprecation(&result.to_string(), DeprecationKind::Const) {
2417 ParseContext::warn(
2418 CompilationError::err(
2419 result.as_source_range(),
2420 format!("Using `{result}` is deprecated, prefer using `{suggestion}`."),
2421 )
2422 .with_suggestion(
2423 format!("Replace `{result}` with `{suggestion}`"),
2424 suggestion,
2425 None,
2426 Tag::Deprecated,
2427 ),
2428 );
2429 }
2430
2431 Ok(result)
2432}
2433
2434impl TryFrom<Token> for Node<TagDeclarator> {
2435 type Error = CompilationError;
2436
2437 fn try_from(token: Token) -> Result<Self, Self::Error> {
2438 match token.token_type {
2439 TokenType::Word => {
2440 Ok(Node::new(
2441 TagDeclarator {
2442 name: token.value,
2444 digest: None,
2445 },
2446 token.start - 1,
2447 token.end,
2448 token.module_id,
2449 ))
2450 }
2451 TokenType::Number => Err(CompilationError::fatal(
2452 token.as_source_range(),
2453 format!(
2454 "Tag names must not start with a number. Tag starts with `{}`",
2455 token.value.as_str()
2456 ),
2457 )),
2458
2459 TokenType::Brace | TokenType::Whitespace | TokenType::Comma => Err(CompilationError::fatal(
2461 token.as_source_range(),
2462 "Tag names must not be empty".to_string(),
2463 )),
2464
2465 TokenType::Type => Err(CompilationError::fatal(
2466 token.as_source_range(),
2467 format!("Cannot assign a tag to a reserved keyword: {}", token.value.as_str()),
2468 )),
2469
2470 _ => Err(CompilationError::fatal(
2471 token.as_source_range(),
2472 format!("Tag names must not start with a {}", token.token_type),
2476 )),
2477 }
2478 }
2479}
2480
2481impl Node<TagDeclarator> {
2482 fn into_valid_binding_name(self) -> Result<Self, CompilationError> {
2483 if crate::std::name_in_stdlib(&self.name) {
2485 return Err(CompilationError::fatal(
2486 SourceRange::from(&self),
2487 format!("Cannot assign a tag to a reserved keyword: {}", self.name),
2488 ));
2489 }
2490 Ok(self)
2491 }
2492}
2493
2494fn tag(i: &mut TokenSlice) -> PResult<Node<TagDeclarator>> {
2496 dollar.parse_next(i)?;
2497 let tag_declarator = any
2498 .try_map(Node::<TagDeclarator>::try_from)
2499 .context(expected("a tag, e.g. '$seg01' or '$line01'"))
2500 .parse_next(i)
2501 .map_err(|e| e.cut())?;
2502 tag_declarator
2505 .into_valid_binding_name()
2506 .map_err(|e| ErrMode::Cut(ContextError::from(e)))
2507}
2508
2509fn ignore_whitespace(i: &mut TokenSlice) {
2511 let _: PResult<()> = repeat(0.., whitespace).parse_next(i);
2512}
2513
2514fn ignore_trailing_comma(i: &mut TokenSlice) {
2516 let _ = opt(comma).parse_next(i);
2517}
2518
2519fn require_whitespace(i: &mut TokenSlice) -> PResult<()> {
2521 repeat(1.., whitespace).parse_next(i)
2522}
2523
2524fn unary_expression(i: &mut TokenSlice) -> PResult<Node<UnaryExpression>> {
2525 const EXPECTED: &str = "expected a unary operator (like '-', the negative-numeric operator),";
2526 let (operator, op_token) = any
2527 .try_map(|token: Token| match token.token_type {
2528 TokenType::Operator if token.value == "-" => Ok((UnaryOperator::Neg, token)),
2529 TokenType::Operator => Err(CompilationError::fatal(
2530 token.as_source_range(),
2531 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(),),
2532 )),
2533 TokenType::Bang => Ok((UnaryOperator::Not, token)),
2534 other => Err(CompilationError::fatal( token.as_source_range(), format!("{EXPECTED} but found {} which is {}", token.value.as_str(), other,) )),
2535 })
2536 .context(expected("a unary expression, e.g. -x or -3"))
2537 .parse_next(i)?;
2538 let argument = operand.parse_next(i)?;
2539 Ok(Node::new_node(
2540 op_token.start,
2541 argument.end(),
2542 op_token.module_id,
2543 UnaryExpression {
2544 operator,
2545 argument,
2546 digest: None,
2547 },
2548 ))
2549}
2550
2551fn binary_expression_tokens(i: &mut TokenSlice) -> PResult<Vec<BinaryExpressionToken>> {
2555 let first = operand.parse_next(i).map(BinaryExpressionToken::from)?;
2556 let remaining: Vec<_> = repeat(
2557 1..,
2558 (
2559 preceded(opt(whitespace), binary_operator).map(BinaryExpressionToken::from),
2560 preceded(opt(whitespace), operand).map(BinaryExpressionToken::from),
2561 ),
2562 )
2563 .context(expected(
2564 "one or more binary operators (like + or -) and operands for them, e.g. 1 + 2 - 3",
2565 ))
2566 .parse_next(i)?;
2567 let mut out = Vec::with_capacity(1 + 2 * remaining.len());
2568 out.push(first);
2569 out.extend(remaining.into_iter().flat_map(|(a, b)| [a, b]));
2570 Ok(out)
2571}
2572
2573fn binary_expression(i: &mut TokenSlice) -> PResult<Node<BinaryExpression>> {
2575 let tokens = binary_expression_tokens.parse_next(i)?;
2577
2578 let expr = super::math::parse(tokens).map_err(|e| ErrMode::Backtrack(e.into()))?;
2581 Ok(expr)
2582}
2583
2584fn binary_expr_in_parens(i: &mut TokenSlice) -> PResult<Node<BinaryExpression>> {
2585 let span_with_brackets = bracketed_section.take().parse_next(i)?;
2586 let mut span_no_brackets = span_with_brackets.without_ends();
2587 let expr = binary_expression.parse_next(&mut span_no_brackets)?;
2588 Ok(expr)
2589}
2590
2591fn bracketed_section(i: &mut TokenSlice) -> PResult<usize> {
2595 let _ = open_paren.parse_next(i)?;
2597 let mut opened_braces = 1usize;
2598 let mut tokens_examined = 0;
2599 while opened_braces > 0 {
2600 let tok = any.parse_next(i)?;
2601 tokens_examined += 1;
2602 if matches!(tok.token_type, TokenType::Brace) {
2603 if tok.value == "(" {
2604 opened_braces += 1;
2605 } else if tok.value == ")" {
2606 opened_braces -= 1;
2607 }
2608 }
2609 }
2610 Ok(tokens_examined)
2611}
2612
2613fn expression_stmt(i: &mut TokenSlice) -> PResult<Node<ExpressionStatement>> {
2615 let val = expression
2616 .context(expected(
2617 "an expression (i.e. a value, or an algorithm for calculating one), e.g. 'x + y' or '3' or 'width * 2'",
2618 ))
2619 .parse_next(i)?;
2620 Ok(Node::new_node(
2621 val.start(),
2622 val.end(),
2623 val.module_id(),
2624 ExpressionStatement {
2625 expression: val,
2626 digest: None,
2627 },
2628 ))
2629}
2630
2631fn some_brace(symbol: &'static str, i: &mut TokenSlice) -> PResult<Token> {
2633 one_of((TokenType::Brace, symbol))
2634 .context(expected(symbol))
2635 .parse_next(i)
2636}
2637
2638fn pipe_operator(i: &mut TokenSlice) -> PResult<Token> {
2640 one_of((TokenType::Operator, PIPE_OPERATOR))
2641 .context(expected(
2642 "the |> operator, used for 'piping' one function's output into another function's input",
2643 ))
2644 .parse_next(i)
2645}
2646
2647fn ws_with_newline(i: &mut TokenSlice) -> PResult<Token> {
2648 one_of(TokenType::Whitespace)
2649 .verify(|token: &Token| token.value.contains('\n'))
2650 .context(expected("a newline, possibly with whitespace"))
2651 .parse_next(i)
2652}
2653
2654fn open_paren(i: &mut TokenSlice) -> PResult<Token> {
2656 some_brace("(", i)
2657}
2658
2659fn close_paren(i: &mut TokenSlice) -> PResult<Token> {
2661 some_brace(")", i)
2662}
2663
2664fn open_bracket(i: &mut TokenSlice) -> PResult<Token> {
2666 some_brace("[", i)
2667}
2668
2669fn close_bracket(i: &mut TokenSlice) -> PResult<Token> {
2671 some_brace("]", i)
2672}
2673
2674fn open_brace(i: &mut TokenSlice) -> PResult<Token> {
2676 some_brace("{", i)
2677}
2678
2679fn close_brace(i: &mut TokenSlice) -> PResult<Token> {
2681 some_brace("}", i)
2682}
2683
2684fn comma(i: &mut TokenSlice) -> PResult<()> {
2685 TokenType::Comma.parse_from(i)?;
2686 Ok(())
2687}
2688
2689fn hash(i: &mut TokenSlice) -> PResult<()> {
2690 TokenType::Hash.parse_from(i)?;
2691 Ok(())
2692}
2693
2694fn bang(i: &mut TokenSlice) -> PResult<Token> {
2695 TokenType::Bang.parse_from(i)
2696}
2697
2698fn dollar(i: &mut TokenSlice) -> PResult<()> {
2699 TokenType::Dollar.parse_from(i)?;
2700 Ok(())
2701}
2702
2703fn period(i: &mut TokenSlice) -> PResult<()> {
2704 TokenType::Period.parse_from(i)?;
2705 Ok(())
2706}
2707
2708fn end_inclusive_range(i: &mut TokenSlice) -> PResult<Token> {
2709 any.try_map(|token: Token| {
2710 if matches!(token.token_type, TokenType::DoublePeriod) {
2711 Ok(token)
2712 } else {
2713 Err(CompilationError::fatal(
2714 token.as_source_range(),
2715 format!(
2716 "expected a '..' (double period) found {} which is {}",
2717 token.value.as_str(),
2718 token.token_type
2719 ),
2720 ))
2721 }
2722 })
2723 .context(expected("the .. operator, used for array ranges like [0..10]"))
2724 .parse_next(i)
2725}
2726
2727fn end_exclusive_range(i: &mut TokenSlice) -> PResult<Token> {
2728 any.try_map(|token: Token| {
2729 if matches!(token.token_type, TokenType::DoublePeriodLessThan) {
2730 Ok(token)
2731 } else {
2732 Err(CompilationError::fatal(
2733 token.as_source_range(),
2734 format!("expected a '..<' but found {}", token.value.as_str()),
2735 ))
2736 }
2737 })
2738 .context(expected("the ..< operator, used for array ranges like [0..<10]"))
2739 .parse_next(i)
2740}
2741
2742fn colon(i: &mut TokenSlice) -> PResult<Token> {
2743 TokenType::Colon.parse_from(i)
2744}
2745
2746fn semi_colon(i: &mut TokenSlice) -> PResult<Token> {
2747 TokenType::SemiColon.parse_from(i)
2748}
2749
2750fn plus(i: &mut TokenSlice) -> PResult<Token> {
2751 one_of((TokenType::Operator, "+")).parse_next(i)
2752}
2753
2754fn double_colon(i: &mut TokenSlice) -> PResult<Token> {
2755 TokenType::DoubleColon.parse_from(i)
2756}
2757
2758fn equals(i: &mut TokenSlice) -> PResult<Token> {
2759 one_of((TokenType::Operator, "="))
2760 .context(expected("the equals operator, ="))
2761 .parse_next(i)
2762}
2763
2764fn question_mark(i: &mut TokenSlice) -> PResult<()> {
2765 TokenType::QuestionMark.parse_from(i)?;
2766 Ok(())
2767}
2768
2769fn at_sign(i: &mut TokenSlice) -> PResult<Token> {
2770 TokenType::At.parse_from(i)
2771}
2772
2773fn fun(i: &mut TokenSlice) -> PResult<Token> {
2774 keyword(i, "fn")
2775}
2776
2777fn ty(i: &mut TokenSlice) -> PResult<Token> {
2778 keyword(i, "type")
2779}
2780
2781fn any_keyword(i: &mut TokenSlice) -> PResult<Token> {
2782 any.try_map(|token: Token| match token.token_type {
2783 TokenType::Keyword => Ok(token),
2784 _ => Err(CompilationError::fatal(
2785 token.as_source_range(),
2786 "expected some reserved keyword".to_owned(),
2787 )),
2788 })
2789 .parse_next(i)
2790}
2791
2792fn keyword(i: &mut TokenSlice, expected: &str) -> PResult<Token> {
2793 any.try_map(|token: Token| match token.token_type {
2794 TokenType::Keyword if token.value == expected => Ok(token),
2795 _ => Err(CompilationError::fatal(
2796 token.as_source_range(),
2797 format!("expected '{expected}', found {}", token.value.as_str(),),
2798 )),
2799 })
2800 .parse_next(i)
2801}
2802
2803fn comma_sep(i: &mut TokenSlice) -> PResult<()> {
2805 (opt(whitespace), comma, opt(whitespace))
2806 .context(expected("a comma, optionally followed by whitespace"))
2807 .parse_next(i)?;
2808 Ok(())
2809}
2810
2811fn pipe_sep(i: &mut TokenSlice) -> PResult<()> {
2813 (opt(whitespace), one_of((TokenType::Operator, "|")), opt(whitespace)).parse_next(i)?;
2814 Ok(())
2815}
2816
2817fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
2818 (
2819 opt((
2820 terminated(nameable_identifier, opt(whitespace)),
2821 terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
2822 )),
2823 expression,
2824 )
2825 .map(|(label, arg)| LabeledArg {
2826 label: label.map(|(l, _)| l),
2827 arg,
2828 })
2829 .parse_next(i)
2830}
2831
2832fn record_ty_field(i: &mut TokenSlice) -> PResult<(Node<Identifier>, Node<Type>)> {
2833 (identifier, colon, opt(whitespace), type_)
2834 .map(|(id, _, _, ty)| (id, ty))
2835 .parse_next(i)
2836}
2837
2838fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> {
2840 let type_ = alt((
2841 (
2843 open_brace,
2844 opt(whitespace),
2845 separated(0.., record_ty_field, comma_sep),
2846 opt(whitespace),
2847 close_brace,
2848 )
2849 .try_map(|(open, _, params, _, close)| {
2850 Ok(Node::new(
2851 Type::Object { properties: params },
2852 open.start,
2853 close.end,
2854 open.module_id,
2855 ))
2856 }),
2857 array_type,
2859 separated(1.., primitive_type, pipe_sep).map(|mut tys: Vec<_>| {
2861 if tys.len() == 1 {
2862 tys.pop().unwrap().map(Type::Primitive)
2863 } else {
2864 let start = tys[0].start;
2865 let module_id = tys[0].module_id;
2866 let end = tys.last().unwrap().end;
2867 Node::new(Type::Union { tys }, start, end, module_id)
2868 }
2869 }),
2870 ))
2871 .parse_next(i)?;
2872
2873 Ok(type_)
2874}
2875
2876fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> {
2877 alt((
2878 (
2880 fun,
2881 opt((
2882 delimited(
2884 open_paren,
2885 opt(alt((
2886 (
2888 type_,
2889 comma,
2890 opt(whitespace),
2891 separated(
2892 1..,
2893 (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2894 comma_sep,
2895 ),
2896 )
2897 .map(|(t, _, _, args)| (Some(t), args)),
2898 separated(
2900 1..,
2901 (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)),
2902 comma_sep,
2903 )
2904 .map(|args| (None, args)),
2905 type_.map(|t| (Some(t), Vec::new())),
2907 ))),
2908 close_paren,
2909 ),
2910 opt((colon, opt(whitespace), type_)),
2912 )),
2913 )
2914 .map(|(t, tys)| {
2915 let mut ft = FunctionType::empty_fn_type();
2916
2917 if let Some((args, ret)) = tys {
2918 if let Some((unnamed, named)) = args {
2919 if let Some(unnamed) = unnamed {
2920 ft.unnamed_arg = Some(Box::new(unnamed));
2921 }
2922 ft.named_args = named;
2923 }
2924 if let Some((_, _, ty)) = ret {
2925 ft.return_type = Some(Box::new(ty));
2926 }
2927 }
2928
2929 Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id)
2930 }),
2931 (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
2933 let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
2934 result.inner =
2935 PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
2936 result
2937 }),
2938 ))
2939 .parse_next(i)
2940}
2941
2942fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
2943 fn opt_whitespace(i: &mut TokenSlice) -> PResult<()> {
2944 ignore_whitespace(i);
2945 Ok(())
2946 }
2947
2948 open_bracket(i)?;
2949 let ty = type_(i)?;
2950 let len = opt((
2951 semi_colon,
2952 opt_whitespace,
2953 any.try_map(|token: Token| match token.token_type {
2954 TokenType::Number => {
2955 let value = token.uint_value().ok_or_else(|| {
2956 CompilationError::fatal(
2957 token.as_source_range(),
2958 format!("Expected unsigned integer literal, found: {}", token.value),
2959 )
2960 })?;
2961
2962 Ok(value as usize)
2963 }
2964 _ => Err(CompilationError::fatal(token.as_source_range(), "invalid array length")),
2965 }),
2966 opt(plus),
2967 ))
2968 .parse_next(i)?;
2969 close_bracket(i)?;
2970
2971 let len = if let Some((tok, _, n, plus)) = len {
2972 if plus.is_some() {
2973 if n != 1 {
2974 return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
2975 tok.as_source_range(),
2976 "Non-empty arrays are specified using `1+`, for a fixed-size array use just an integer",
2977 ))));
2978 } else {
2979 ArrayLen::NonEmpty
2980 }
2981 } else {
2982 ArrayLen::Known(n)
2983 }
2984 } else {
2985 ArrayLen::None
2986 };
2987
2988 Ok(ty.map(|ty| Type::Array { ty: Box::new(ty), len }))
2989}
2990
2991fn uom_for_type(i: &mut TokenSlice) -> PResult<NumericSuffix> {
2992 any.try_map(|t: Token| t.value.parse()).parse_next(i)
2993}
2994
2995fn comment(i: &mut TokenSlice) -> PResult<Node<String>> {
2996 any.verify_map(|token: Token| {
2997 let value = match token.token_type {
2998 TokenType::LineComment => token.value,
2999 TokenType::BlockComment => token.value,
3000 _ => return None,
3001 };
3002 Some(Node::new(value, token.start, token.end, token.module_id))
3003 })
3004 .context(expected("Comment"))
3005 .parse_next(i)
3006}
3007
3008fn comments(i: &mut TokenSlice) -> PResult<Node<Vec<String>>> {
3009 let comments: Vec<Node<String>> = repeat(1.., (comment, opt(whitespace)).map(|(c, _)| c)).parse_next(i)?;
3010 let start = comments[0].start;
3011 let module_id = comments[0].module_id;
3012 let end = comments.last().unwrap().end;
3013 let inner = comments.into_iter().map(|n| n.inner).collect();
3014 Ok(Node::new(inner, start, end, module_id))
3015}
3016
3017struct ParamDescription {
3018 labeled: bool,
3019 arg_name: Token,
3020 type_: std::option::Option<Node<Type>>,
3021 default_value: Option<DefaultParamVal>,
3022 attr: Option<Node<Annotation>>,
3023 comments: Option<Node<Vec<String>>>,
3024}
3025
3026fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
3027 let (_, comments, _, attr, _, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
3028 opt(whitespace),
3029 opt(comments),
3030 opt(whitespace),
3031 opt(outer_annotation),
3032 opt(whitespace),
3033 opt(at_sign),
3034 any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
3035 opt(question_mark),
3036 opt(whitespace),
3037 opt((colon, opt(whitespace), type_).map(|tup| tup.2)),
3038 opt(whitespace),
3039 opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)),
3040 )
3041 .parse_next(i)?;
3042
3043 Ok(ParamDescription {
3044 labeled: found_at_sign.is_none(),
3045 arg_name,
3046 type_,
3047 default_value: match (question_mark.is_some(), default_literal) {
3048 (true, Some(lit)) => Some(DefaultParamVal::Literal(*lit)),
3049 (true, None) => Some(DefaultParamVal::none()),
3050 (false, None) => None,
3051 (false, Some(lit)) => {
3052 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.";
3053 let e = CompilationError::fatal((&lit).into(), msg);
3054 return Err(ErrMode::Backtrack(ContextError::from(e)));
3055 }
3056 },
3057 attr,
3058 comments,
3059 })
3060}
3061
3062fn parameters(i: &mut TokenSlice) -> PResult<Vec<Parameter>> {
3064 let candidates: Vec<_> = separated(0.., parameter, comma_sep)
3066 .context(expected("function parameters"))
3067 .parse_next(i)?;
3068 opt(comma_sep).parse_next(i)?;
3069
3070 let params: Vec<Parameter> = candidates
3072 .into_iter()
3073 .map(
3074 |ParamDescription {
3075 labeled,
3076 arg_name,
3077 type_,
3078 default_value,
3079 attr,
3080 comments,
3081 }| {
3082 let mut identifier = Node::<Identifier>::try_from(arg_name)?;
3083 if let Some(comments) = comments {
3084 identifier.comment_start = comments.start;
3085 identifier.pre_comments = comments.inner;
3086 }
3087 if let Some(attr) = attr {
3088 identifier.outer_attrs.push(attr);
3089 }
3090
3091 Ok(Parameter {
3092 identifier,
3093 type_,
3094 default_value,
3095 labeled,
3096 digest: None,
3097 })
3098 },
3099 )
3100 .collect::<Result<_, _>>()
3101 .map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
3102
3103 if let Some(param) = params.iter().skip(1).find(|param| !param.labeled) {
3105 let source_range = SourceRange::from(param);
3106 return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
3107 source_range,
3108 "Only the first parameter can be declared unlabeled",
3109 ))));
3110 }
3111
3112 if let Err(e) = optional_after_required(¶ms) {
3114 return Err(ErrMode::Cut(ContextError::from(e)));
3115 }
3116 Ok(params)
3117}
3118
3119fn optional_after_required(params: &[Parameter]) -> Result<(), CompilationError> {
3120 let mut found_optional = false;
3121 for p in params {
3122 if p.optional() {
3123 found_optional = true;
3124 }
3125 if !p.optional() && found_optional {
3126 let e = CompilationError::fatal(
3127 (&p.identifier).into(),
3128 "mandatory parameters must be declared before optional parameters",
3129 );
3130 return Err(e);
3131 }
3132 }
3133 Ok(())
3134}
3135
3136fn binding_name(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
3138 identifier
3139 .context(expected("an identifier, which will be the name of some value"))
3140 .parse_next(i)
3141}
3142
3143fn fn_call_pos_or_kw(i: &mut TokenSlice) -> PResult<Expr> {
3145 alt((fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),)).parse_next(i)
3146}
3147
3148fn labelled_fn_call(i: &mut TokenSlice) -> PResult<Expr> {
3149 let expr = fn_call_pos_or_kw.parse_next(i)?;
3150
3151 let label = opt(label).parse_next(i)?;
3152 match label {
3153 Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
3154 None => Ok(expr),
3155 }
3156}
3157
3158fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
3159 let fn_name = name(i)?;
3160 opt(whitespace).parse_next(i)?;
3161 let _ = open_paren.parse_next(i)?;
3162 ignore_whitespace(i);
3163
3164 let early_close = peek(close_paren).parse_next(i);
3166 if early_close.is_ok() {
3167 let cl = close_paren.parse_next(i)?;
3168 let result = Node::new_node(
3169 fn_name.start,
3170 cl.end,
3171 fn_name.module_id,
3172 CallExpressionKw {
3173 callee: fn_name,
3174 unlabeled: Default::default(),
3175 arguments: Default::default(),
3176 digest: None,
3177 non_code_meta: Default::default(),
3178 },
3179 );
3180 return Ok(result);
3181 }
3182
3183 let early_close = peek((expression, opt(whitespace), close_paren)).parse_next(i);
3185 if early_close.is_ok() {
3186 let first_expression = expression.parse_next(i)?;
3187 ignore_whitespace(i);
3188 let end = close_paren.parse_next(i)?.end;
3189 let result = Node::new_node(
3190 fn_name.start,
3191 end,
3192 fn_name.module_id,
3193 CallExpressionKw {
3194 callee: fn_name,
3195 unlabeled: Some(first_expression),
3196 arguments: Default::default(),
3197 digest: None,
3198 non_code_meta: Default::default(),
3199 },
3200 );
3201 return Ok(result);
3202 }
3203
3204 #[derive(Debug)]
3205 #[allow(clippy::large_enum_variant)]
3206 enum ArgPlace {
3207 NonCode(Node<NonCodeNode>),
3208 LabeledArg(LabeledArg),
3209 UnlabeledArg(Expr),
3210 Keyword(Token),
3211 }
3212 let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
3213 let args: Vec<_> = repeat(
3214 0..,
3215 alt((
3216 terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
3217 terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
3218 terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
3219 expression.map(ArgPlace::UnlabeledArg),
3220 )),
3221 )
3222 .parse_next(i)?;
3223 let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().try_fold(
3224 (Vec::new(), BTreeMap::new()),
3225 |(mut args, mut non_code_nodes), (index, e)| {
3226 match e {
3227 ArgPlace::NonCode(x) => {
3228 non_code_nodes.insert(index, vec![x]);
3229 }
3230 ArgPlace::LabeledArg(x) => {
3231 args.push(x);
3232 }
3233 ArgPlace::Keyword(kw) => {
3234 return Err(ErrMode::Cut(
3235 CompilationError::fatal(
3236 SourceRange::from(kw.clone()),
3237 format!(
3238 "`{}` is not the name of an argument (it's a reserved keyword)",
3239 kw.value
3240 ),
3241 )
3242 .into(),
3243 ));
3244 }
3245 ArgPlace::UnlabeledArg(arg) => {
3246 let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
3247 if followed_by_equals {
3248 return Err(ErrMode::Cut(
3249 CompilationError::fatal(
3250 SourceRange::from(arg),
3251 "This argument has a label, but no value. Put some value after the equals sign",
3252 )
3253 .into(),
3254 ));
3255 } else {
3256 args.push(LabeledArg { label: None, arg });
3257 }
3258 }
3259 }
3260 Ok((args, non_code_nodes))
3261 },
3262 )?;
3263 ignore_whitespace(i);
3264 opt(comma_sep).parse_next(i)?;
3265 let end = close_paren.parse_next(i)?.end;
3266
3267 let mut counted_labels = IndexMap::with_capacity(args.len());
3269 for arg in &args {
3270 if let Some(l) = &arg.label {
3271 *counted_labels.entry(&l.inner.name).or_insert(0) += 1;
3272 }
3273 }
3274 if let Some((duplicated, n)) = counted_labels.iter().find(|(_label, n)| n > &&1) {
3275 let msg = format!(
3276 "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."
3277 );
3278 ParseContext::err(CompilationError::err(
3279 SourceRange::new(fn_name.start, end, fn_name.module_id),
3280 msg,
3281 ));
3282 }
3283
3284 let non_code_meta = NonCodeMeta {
3285 non_code_nodes,
3286 ..Default::default()
3287 };
3288 let result = Node::new_node(
3289 fn_name.start,
3290 end,
3291 fn_name.module_id,
3292 CallExpressionKw {
3293 callee: fn_name,
3294 unlabeled: initial_unlabeled_arg,
3295 arguments: args,
3296 digest: None,
3297 non_code_meta,
3298 },
3299 );
3300
3301 let callee_str = result.callee.name.name.to_string();
3302 if let Some(suggestion) = super::deprecation(&callee_str, DeprecationKind::Function) {
3303 ParseContext::warn(
3304 CompilationError::err(
3305 result.as_source_range(),
3306 format!("Calling `{}` is deprecated, prefer using `{}`.", callee_str, suggestion),
3307 )
3308 .with_suggestion(
3309 format!("Replace `{}` with `{}`", callee_str, suggestion),
3310 suggestion,
3311 None,
3312 Tag::Deprecated,
3313 ),
3314 );
3315 }
3316
3317 Ok(result)
3318}
3319
3320#[cfg(test)]
3321mod tests {
3322 use itertools::Itertools;
3323 use pretty_assertions::assert_eq;
3324
3325 use super::*;
3326 use crate::{
3327 parsing::ast::types::{BodyItem, Expr, VariableKind},
3328 KclError, ModuleId,
3329 };
3330
3331 fn assert_reserved(word: &str) {
3332 let code = format!(r#"{} = 0"#, word);
3334 let result = crate::parsing::top_level_parse(code.as_str());
3335 let err = &result.unwrap_errs().next().unwrap();
3336 assert!(
3339 err.message.starts_with("Unexpected token: ")
3340 || err.message.starts_with("= is not")
3341 || err
3342 .message
3343 .starts_with("Cannot assign a variable to a reserved keyword: "),
3344 "Error message is: `{}`",
3345 err.message,
3346 );
3347 }
3348
3349 #[test]
3350 fn reserved_words() {
3351 for word in crate::parsing::token::RESERVED_WORDS.keys().sorted() {
3354 assert_reserved(word);
3355 }
3356 assert_reserved("import");
3357 }
3358
3359 #[test]
3360 fn parse_names() {
3361 for (test, expected_len) in [("someVar", 0), ("::foo", 0), ("foo::bar::baz", 2)] {
3362 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3363 match name.parse(tokens.as_slice()) {
3364 Ok(n) => assert_eq!(n.path.len(), expected_len, "Could not parse name from `{test}`: {n:?}"),
3365 Err(e) => panic!("Could not parse name from `{test}`: {e:?}"),
3366 }
3367 }
3368 }
3369
3370 #[test]
3371 fn weird_program_unclosed_paren() {
3372 let tokens = crate::parsing::token::lex("fn firstPrime(", ModuleId::default()).unwrap();
3373 let tokens = tokens.as_slice();
3374 let last = tokens.last().unwrap().as_source_range();
3375 let err: CompilationError = program.parse(tokens).unwrap_err().into();
3376 assert_eq!(err.source_range, last);
3377 assert_eq!(err.message, "Unexpected end of file. The compiler expected )");
3380 }
3381
3382 #[test]
3383 fn kw_call_as_operand() {
3384 let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
3385 let tokens = tokens.as_slice();
3386 let op = operand.parse(tokens).unwrap();
3387 println!("{op:#?}");
3388 }
3389
3390 #[test]
3391 fn weird_program_just_a_pipe() {
3392 let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();
3393 let err: CompilationError = program.parse(tokens.as_slice()).unwrap_err().into();
3394 assert_eq!(err.source_range, SourceRange::new(0, 1, ModuleId::default()));
3395 assert_eq!(err.message, "Unexpected token: |");
3396 }
3397
3398 #[test]
3399 fn parse_binary_expressions() {
3400 for (i, test_program) in ["1 + 2 + 3"].into_iter().enumerate() {
3401 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3402 let _actual = match binary_expression.parse_next(&mut tokens.as_slice()) {
3403 Ok(x) => x,
3404 Err(e) => panic!("Failed test {i}, could not parse binary expressions from \"{test_program}\": {e:?}"),
3405 };
3406 }
3407 }
3408
3409 #[test]
3410 fn test_vardec_no_keyword() {
3411 let tokens = crate::parsing::token::lex("x = 4", ModuleId::default()).unwrap();
3412 let vardec = declaration(&mut tokens.as_slice()).unwrap();
3413 assert_eq!(vardec.inner.kind, VariableKind::Const);
3414 let vardec = &vardec.declaration;
3415 assert_eq!(vardec.id.name, "x");
3416 let Expr::Literal(init_val) = &vardec.init else {
3417 panic!("weird init value")
3418 };
3419 assert_eq!(init_val.raw, "4");
3420 }
3421
3422 #[test]
3423 fn test_negative_operands() {
3424 let tokens = crate::parsing::token::lex("-leg2", ModuleId::default()).unwrap();
3425 let _s = operand.parse_next(&mut tokens.as_slice()).unwrap();
3426 }
3427
3428 #[test]
3429 fn test_comments_in_function1() {
3430 let test_program = r#"() {
3431 // comment 0
3432 a = 1
3433 // comment 1
3434 b = 2
3435 /// comment 2
3436 return 1
3437 }"#;
3438 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3439 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3440 assert_eq!(expr.params, vec![]);
3441 let comment_start = expr.body.body[0].get_comments();
3442 let comment0 = expr.body.body[1].get_comments();
3443 let comment1 = expr.body.body[2].get_comments();
3444 assert_eq!(comment_start, vec!["// comment 0".to_owned()]);
3445 assert_eq!(comment0, vec!["// comment 1".to_owned()]);
3446 assert_eq!(comment1, vec!["/// comment 2".to_owned()]);
3447 }
3448
3449 #[test]
3450 fn test_comments_in_function2() {
3451 let test_program = r#"() {
3452 yo = { a = { b = { c = '123' } } } /* block
3453comment */
3454}"#;
3455 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3456 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3457 let comment0 = &expr.body.non_code_meta.non_code_nodes.get(&0).unwrap()[0];
3458 assert_eq!(comment0.value(), "block\ncomment");
3459 }
3460
3461 #[test]
3462 fn test_comment_at_start_of_program() {
3463 let test_program = r#"
3464/* comment at start */
3465
3466mySk1 = startSketchOn(XY)
3467 |> startProfile(at = [0, 0])"#;
3468 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3469 let program = program.parse(tokens.as_slice()).unwrap();
3470 let mut starting_comments = program.inner.non_code_meta.start_nodes;
3471 assert_eq!(starting_comments.len(), 2);
3472 let start0 = starting_comments.remove(0);
3473 let start1 = starting_comments.remove(0);
3474 assert_eq!(
3475 start0.value,
3476 NonCodeValue::BlockComment {
3477 value: "comment at start".to_owned(),
3478 style: CommentStyle::Block
3479 }
3480 );
3481 assert_eq!(start1.value, NonCodeValue::NewLine);
3482 }
3483
3484 #[test]
3485 fn test_comment_in_pipe() {
3486 let tokens = crate::parsing::token::lex(r#"x = y() |> /*hi*/ z(%)"#, ModuleId::default()).unwrap();
3487 let mut body = program.parse(tokens.as_slice()).unwrap().inner.body;
3488 let BodyItem::VariableDeclaration(item) = body.remove(0) else {
3489 panic!("expected vardec");
3490 };
3491 let val = item.inner.declaration.inner.init;
3492 let Expr::PipeExpression(pipe) = val else {
3493 panic!("expected pipe");
3494 };
3495 let mut noncode = pipe.inner.non_code_meta;
3496 assert_eq!(noncode.non_code_nodes.len(), 1);
3497 let comment = noncode.non_code_nodes.remove(&0).unwrap().pop().unwrap();
3498 assert_eq!(
3499 comment.value,
3500 NonCodeValue::BlockComment {
3501 value: "hi".to_owned(),
3502 style: CommentStyle::Block
3503 }
3504 );
3505 }
3506
3507 #[test]
3508 fn test_whitespace_in_function() {
3509 let test_program = r#"() {
3510 return sg
3511 return sg
3512 }"#;
3513 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3514 let _expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3515 }
3516
3517 #[test]
3518 fn test_empty_lines_in_function() {
3519 let test_program = "() {
3520
3521 return 2
3522 }";
3523 let module_id = ModuleId::from_usize(1);
3524 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3525 let expr = function_decl.parse_next(&mut tokens.as_slice()).unwrap();
3526 assert_eq!(
3527 expr.body.non_code_meta.start_nodes,
3528 vec![Node::new(
3529 NonCodeNode {
3530 value: NonCodeValue::NewLine,
3531 digest: None
3532 },
3533 4,
3534 22,
3535 module_id,
3536 )]
3537 );
3538 }
3539
3540 #[test]
3541 fn inline_comment_pipe_expression() {
3542 let test_input = r#"a(XY)
3543 |> b(%)
3544 |> c(%) // inline-comment
3545 |> d(%)"#;
3546
3547 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3548 let Node {
3549 inner: PipeExpression {
3550 body, non_code_meta, ..
3551 },
3552 ..
3553 } = pipe_expression.parse_next(&mut tokens.as_slice()).unwrap();
3554 assert_eq!(non_code_meta.non_code_nodes.len(), 1);
3555 assert_eq!(
3556 non_code_meta.non_code_nodes.get(&2).unwrap()[0].value,
3557 NonCodeValue::InlineComment {
3558 value: "inline-comment".to_owned(),
3559 style: CommentStyle::Line
3560 }
3561 );
3562 assert_eq!(body.len(), 4);
3563 }
3564
3565 #[test]
3566 fn many_comments() {
3567 let test_program = r#"// this is a comment
3568 yo = { a = { b = { c = '123' } } } /* block
3569 comment */
3570
3571 key = 'c'
3572 // this is also a comment
3573 return things
3574"#;
3575
3576 let module_id = ModuleId::default();
3577 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3578 let Program {
3579 body, non_code_meta, ..
3580 } = function_body.parse(tokens.as_slice()).unwrap().inner;
3581 assert_eq!(body[0].get_comments(), vec!["// this is a comment".to_owned()],);
3582
3583 assert_eq!(
3584 Some(&vec![
3585 Node::new(
3586 NonCodeNode {
3587 value: NonCodeValue::InlineComment {
3588 value: "block\n comment".to_owned(),
3589 style: CommentStyle::Block
3590 },
3591 digest: None,
3592 },
3593 57,
3594 79,
3595 module_id,
3596 ),
3597 Node::new(
3598 NonCodeNode {
3599 value: NonCodeValue::NewLine,
3600 digest: None,
3601 },
3602 79,
3603 83,
3604 module_id,
3605 )
3606 ]),
3607 non_code_meta.non_code_nodes.get(&0),
3608 );
3609
3610 assert_eq!(body[2].get_comments(), vec!["// this is also a comment".to_owned()],);
3611 }
3612
3613 #[test]
3614 fn inline_block_comments() {
3615 let test_program = r#"yo = 3 /* block
3616 comment */
3617 return 1"#;
3618
3619 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3620 let actual = program.parse(tokens.as_slice()).unwrap();
3621 assert_eq!(actual.non_code_meta.non_code_nodes.len(), 1);
3622 assert_eq!(
3623 actual.non_code_meta.non_code_nodes.get(&0).unwrap()[0].value,
3624 NonCodeValue::InlineComment {
3625 value: "block\n comment".to_owned(),
3626 style: CommentStyle::Block
3627 }
3628 );
3629 }
3630
3631 #[test]
3632 fn test_bracketed_binary_expression() {
3633 let input = "(2 - 3)";
3634 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3635 let actual = match binary_expr_in_parens.parse(tokens.as_slice()) {
3636 Ok(x) => x,
3637 Err(e) => panic!("{e:?}"),
3638 };
3639 assert_eq!(actual.operator, BinaryOperator::Sub);
3640 }
3641
3642 #[test]
3643 fn test_arg() {
3644 for input in [
3645 "( sigmaAllow * width )",
3646 "6 / ( sigmaAllow * width )",
3647 "sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))",
3648 ] {
3649 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3650 let _actual = match expression.parse(tokens.as_slice()) {
3651 Ok(x) => x,
3652 Err(e) => panic!("{e:?}"),
3653 };
3654 }
3655 }
3656
3657 #[test]
3658 fn test_arithmetic() {
3659 let input = "1 * (2 - 3)";
3660 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3661 let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3663 assert_eq!(actual.operator, BinaryOperator::Mul);
3664 let BinaryPart::BinaryExpression(rhs) = actual.inner.right else {
3665 panic!("Expected RHS to be another binary expression");
3666 };
3667 assert_eq!(rhs.operator, BinaryOperator::Sub);
3668 match &rhs.right {
3669 BinaryPart::Literal(lit) => {
3670 assert!(lit.start == 9 && lit.end == 10);
3671 assert!(
3672 lit.value
3673 == LiteralValue::Number {
3674 value: 3.0,
3675 suffix: NumericSuffix::None
3676 }
3677 && &lit.raw == "3"
3678 && lit.digest.is_none()
3679 );
3680 }
3681 _ => panic!(),
3682 }
3683 }
3684
3685 #[test]
3686 fn assign_brackets() {
3687 for (i, test_input) in [
3688 "thickness_squared = (1 + 1)",
3689 "thickness_squared = ( 1 + 1)",
3690 "thickness_squared = (1 + 1 )",
3691 "thickness_squared = ( 1 + 1 )",
3692 ]
3693 .into_iter()
3694 .enumerate()
3695 {
3696 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3697 let actual = match declaration.parse(tokens.as_slice()) {
3698 Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3699 Ok(a) => a,
3700 };
3701 let Expr::BinaryExpression(_expr) = &actual.declaration.inner.init else {
3702 panic!(
3703 "Expected test {i} to be a binary expression but it wasn't, it was {:?}",
3704 actual.declaration
3705 );
3706 };
3707 }
3709 }
3710
3711 #[test]
3712 fn test_function_call() {
3713 for (i, test_input) in ["x = f(1)", "x = f( 1 )"].into_iter().enumerate() {
3714 let tokens = crate::parsing::token::lex(test_input, ModuleId::default()).unwrap();
3715 let _actual = match declaration.parse(tokens.as_slice()) {
3716 Err(e) => panic!("Could not parse test {i}: {e:#?}"),
3717 Ok(a) => a,
3718 };
3719 }
3720 }
3721
3722 #[test]
3723 fn test_nested_arithmetic() {
3724 let input = "1 * ((2 - 3) / 4)";
3725 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
3726 let outer = binary_expression.parse(tokens.as_slice()).unwrap();
3728 assert_eq!(outer.operator, BinaryOperator::Mul);
3729 let BinaryPart::BinaryExpression(middle) = outer.inner.right else {
3730 panic!("Expected RHS to be another binary expression");
3731 };
3732
3733 assert_eq!(middle.operator, BinaryOperator::Div);
3734 let BinaryPart::BinaryExpression(inner) = middle.inner.left else {
3735 panic!("expected nested binary expression");
3736 };
3737 assert_eq!(inner.operator, BinaryOperator::Sub);
3738 }
3739
3740 #[test]
3741 fn binary_expression_ignores_whitespace() {
3742 let tests = ["1 - 2", "1- 2", "1 -2", "1-2"];
3743 for test in tests {
3744 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
3745 let actual = binary_expression.parse(tokens.as_slice()).unwrap();
3746 assert_eq!(actual.operator, BinaryOperator::Sub);
3747 let BinaryPart::Literal(left) = actual.inner.left else {
3748 panic!("should be expression");
3749 };
3750 assert_eq!(
3751 left.value,
3752 LiteralValue::Number {
3753 value: 1.0,
3754 suffix: NumericSuffix::None
3755 }
3756 );
3757 let BinaryPart::Literal(right) = actual.inner.right else {
3758 panic!("should be expression");
3759 };
3760 assert_eq!(
3761 right.value,
3762 LiteralValue::Number {
3763 value: 2.0,
3764 suffix: NumericSuffix::None
3765 }
3766 );
3767 }
3768 }
3769
3770 #[test]
3771 fn some_pipe_expr() {
3772 let test_program = r#"x()
3773 |> y(%) /* this is
3774 a comment
3775 spanning a few lines */
3776 |> z(%)"#;
3777 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3778 let actual = pipe_expression.parse(tokens.as_slice()).unwrap();
3779 let n = actual.non_code_meta.non_code_nodes.len();
3780 assert_eq!(n, 1, "expected one comment in pipe expression but found {n}");
3781 let nc = &actual.non_code_meta.non_code_nodes.get(&1).unwrap()[0];
3782 assert!(nc.value().starts_with("this"));
3783 assert!(nc.value().ends_with("lines"));
3784 }
3785
3786 #[test]
3787 fn comments_in_pipe_expr() {
3788 for (i, test_program) in [
3789 r#"y() |> /*hi*/ z(%)"#,
3790 "1 |>/*hi*/ f(%)",
3791 r#"y() |> /*hi*/ z(%)"#,
3792 "1 /*hi*/ |> f(%)",
3793 "1
3794 // Hi
3795 |> f(%)",
3796 "1
3797 /* Hi
3798 there
3799 */
3800 |> f(%)",
3801 ]
3802 .into_iter()
3803 .enumerate()
3804 {
3805 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3806 let actual = pipe_expression.parse(tokens.as_slice());
3807 assert!(actual.is_ok(), "could not parse test {i}, '{test_program}'");
3808 let actual = actual.unwrap();
3809 let n = actual.non_code_meta.non_code_nodes.len();
3810 assert_eq!(n, 1, "expected one comment in pipe expression but found {n}",)
3811 }
3812 }
3813
3814 #[test]
3815 fn comments() {
3816 let module_id = ModuleId::from_usize(1);
3817 for (i, (test_program, expected)) in [
3818 (
3819 "//hi",
3820 Node::new(
3821 NonCodeNode {
3822 value: NonCodeValue::BlockComment {
3823 value: "hi".to_owned(),
3824 style: CommentStyle::Line,
3825 },
3826 digest: None,
3827 },
3828 0,
3829 4,
3830 module_id,
3831 ),
3832 ),
3833 (
3834 "/*hello*/",
3835 Node::new(
3836 NonCodeNode {
3837 value: NonCodeValue::BlockComment {
3838 value: "hello".to_owned(),
3839 style: CommentStyle::Block,
3840 },
3841 digest: None,
3842 },
3843 0,
3844 9,
3845 module_id,
3846 ),
3847 ),
3848 (
3849 "/* hello */",
3850 Node::new(
3851 NonCodeNode {
3852 value: NonCodeValue::BlockComment {
3853 value: "hello".to_owned(),
3854 style: CommentStyle::Block,
3855 },
3856 digest: None,
3857 },
3858 0,
3859 11,
3860 module_id,
3861 ),
3862 ),
3863 (
3864 "/* \nhello */",
3865 Node::new(
3866 NonCodeNode {
3867 value: NonCodeValue::BlockComment {
3868 value: "hello".to_owned(),
3869 style: CommentStyle::Block,
3870 },
3871 digest: None,
3872 },
3873 0,
3874 12,
3875 module_id,
3876 ),
3877 ),
3878 (
3879 "
3880 /* hello */",
3881 Node::new(
3882 NonCodeNode {
3883 value: NonCodeValue::BlockComment {
3884 value: "hello".to_owned(),
3885 style: CommentStyle::Block,
3886 },
3887 digest: None,
3888 },
3889 0,
3890 29,
3891 module_id,
3892 ),
3893 ),
3894 (
3895 "
3897
3898 /* hello */",
3899 Node::new(
3900 NonCodeNode {
3901 value: NonCodeValue::NewLineBlockComment {
3902 value: "hello".to_owned(),
3903 style: CommentStyle::Block,
3904 },
3905 digest: None,
3906 },
3907 0,
3908 32,
3909 module_id,
3910 ),
3911 ),
3912 (
3913 "
3915
3916 /* hello */",
3917 Node::new(
3918 NonCodeNode {
3919 value: NonCodeValue::NewLineBlockComment {
3920 value: "hello".to_owned(),
3921 style: CommentStyle::Block,
3922 },
3923 digest: None,
3924 },
3925 0,
3926 30,
3927 module_id,
3928 ),
3929 ),
3930 (
3931 r#"/* block
3932 comment */"#,
3933 Node::new(
3934 NonCodeNode {
3935 value: NonCodeValue::BlockComment {
3936 value: "block\n comment".to_owned(),
3937 style: CommentStyle::Block,
3938 },
3939 digest: None,
3940 },
3941 0,
3942 39,
3943 module_id,
3944 ),
3945 ),
3946 ]
3947 .into_iter()
3948 .enumerate()
3949 {
3950 let tokens = crate::parsing::token::lex(test_program, module_id).unwrap();
3951 let actual = non_code_node.parse(tokens.as_slice());
3952 assert!(actual.is_ok(), "could not parse test {i}: {actual:#?}");
3953 let actual = actual.unwrap();
3954 assert_eq!(actual, expected, "failed test {i}");
3955 }
3956 }
3957
3958 #[test]
3959 fn recognize_invalid_params() {
3960 let test_fn = "(let) => { return 1 }";
3961 let module_id = ModuleId::from_usize(2);
3962 let tokens = crate::parsing::token::lex(test_fn, module_id).unwrap();
3963 let err = function_decl.parse(tokens.as_slice()).unwrap_err().into_inner();
3964 let cause = err.cause.unwrap();
3965 assert_eq!(cause.source_range, SourceRange::new(1, 4, ModuleId::from_usize(2)));
3967 assert_eq!(cause.message, "Cannot assign a variable to a reserved keyword: let");
3968 }
3969
3970 #[test]
3971 fn comment_in_string() {
3972 let string_literal = r#""
3973 // a comment
3974 ""#;
3975 let tokens = crate::parsing::token::lex(string_literal, ModuleId::default()).unwrap();
3976 let parsed_literal = literal.parse(tokens.as_slice()).unwrap();
3977 assert_eq!(
3978 parsed_literal.value,
3979 "
3980 // a comment
3981 "
3982 .into()
3983 );
3984 }
3985
3986 #[test]
3987 fn pipes_on_pipes_minimal() {
3988 let test_program = r#"startSketchOn(XY)
3989 |> startProfile(at = [0, 0])
3990 |> line(endAbsolute = [0, -0]) // MoveRelative
3991
3992 "#;
3993 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
3994 let tokens = &mut tokens.as_slice();
3995 let _actual = pipe_expression.parse_next(tokens).unwrap();
3996 assert_eq!(tokens.first().unwrap().token_type, TokenType::Whitespace);
3997 }
3998
3999 #[test]
4000 fn test_pipes_on_pipes() {
4001 let test_program = include_str!("../../e2e/executor/inputs/pipes_on_pipes.kcl");
4002 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4003 let _ = run_parser(tokens.as_slice()).unwrap();
4004 }
4005
4006 #[test]
4007 fn test_cube() {
4008 let test_program = include_str!("../../e2e/executor/inputs/cube.kcl");
4009 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4010 match program.parse(tokens.as_slice()) {
4011 Ok(_) => {}
4012 Err(e) => {
4013 panic!("{e:#?}");
4014 }
4015 }
4016 }
4017
4018 #[test]
4019 fn parse_numeric() {
4020 let test_program = "fn foo(x: number(Length)) {}";
4021 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4022 run_parser(tokens.as_slice()).unwrap();
4023
4024 let test_program = "42_mm";
4025 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4026 assert_eq!(tokens.iter().count(), 1);
4027 run_parser(tokens.as_slice()).unwrap();
4028
4029 let test_program = "42_Length";
4030 let tokens = crate::parsing::token::lex(test_program, ModuleId::default()).unwrap();
4031 assert_eq!(tokens.iter().count(), 2);
4032 assert_eq!(run_parser(tokens.as_slice()).unwrap_errs().count(), 1);
4033 }
4034
4035 #[test]
4036 fn test_parameter_list() {
4037 let tests = [
4038 ("", vec![]),
4039 ("a", vec!["a"]),
4040 ("a, b", vec!["a", "b"]),
4041 ("a,b", vec!["a", "b"]),
4042 ];
4043 for (i, (input, expected)) in tests.into_iter().enumerate() {
4044 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4045 let actual = parameters.parse(tokens.as_slice());
4046 assert!(actual.is_ok(), "could not parse test {i}");
4047 let actual_ids: Vec<_> = actual.unwrap().into_iter().map(|p| p.identifier.inner.name).collect();
4048 assert_eq!(actual_ids, expected);
4049 }
4050 }
4051
4052 #[test]
4053 fn test_user_function() {
4054 let input = "() {
4055 return 2
4056 }";
4057
4058 let tokens = crate::parsing::token::lex(input, ModuleId::default()).unwrap();
4059 let actual = function_decl.parse(tokens.as_slice());
4060 assert!(actual.is_ok(), "could not parse test function");
4061 }
4062
4063 #[test]
4064 fn test_declaration() {
4065 let tests = ["myVar = 5", "myVar=5", "myVar =5", "myVar= 5"];
4066 for test in tests {
4067 let tokens = crate::parsing::token::lex(test, ModuleId::default()).unwrap();
4069 let mut expected_body = crate::parsing::parse_tokens(tokens.clone()).unwrap().inner.body;
4070 assert_eq!(expected_body.len(), 1);
4071 let BodyItem::VariableDeclaration(expected) = expected_body.pop().unwrap() else {
4072 panic!("Expected variable declaration");
4073 };
4074
4075 let actual = declaration.parse(tokens.as_slice()).unwrap();
4077 assert_eq!(expected, actual);
4078
4079 assert_eq!(actual.inner.kind, VariableKind::Const);
4081 assert_eq!(actual.start, 0);
4082 let decl = &actual.declaration;
4083 assert_eq!(decl.id.name, "myVar");
4084 let Expr::Literal(value) = &decl.inner.init else {
4085 panic!("value should be a literal")
4086 };
4087 assert_eq!(value.end, test.len());
4088 assert_eq!(value.raw, "5");
4089 }
4090 }
4091
4092 #[test]
4093 fn test_math_parse() {
4094 let module_id = ModuleId::default();
4095 let actual = crate::parsing::parse_str(r#"5 + "a""#, module_id).unwrap().inner.body;
4096 let expr = Node::boxed(
4097 BinaryExpression {
4098 operator: BinaryOperator::Add,
4099 left: BinaryPart::Literal(Box::new(Node::new(
4100 Literal {
4101 value: LiteralValue::Number {
4102 value: 5.0,
4103 suffix: NumericSuffix::None,
4104 },
4105 raw: "5".to_owned(),
4106 digest: None,
4107 },
4108 0,
4109 1,
4110 module_id,
4111 ))),
4112 right: BinaryPart::Literal(Box::new(Node::new(
4113 Literal {
4114 value: "a".into(),
4115 raw: r#""a""#.to_owned(),
4116 digest: None,
4117 },
4118 4,
4119 7,
4120 module_id,
4121 ))),
4122 digest: None,
4123 },
4124 0,
4125 7,
4126 module_id,
4127 );
4128 let expected = vec![BodyItem::ExpressionStatement(Node::new(
4129 ExpressionStatement {
4130 expression: Expr::BinaryExpression(expr),
4131 digest: None,
4132 },
4133 0,
4134 7,
4135 module_id,
4136 ))];
4137 assert_eq!(expected, actual);
4138 }
4139
4140 #[test]
4141 fn test_abstract_syntax_tree() {
4142 let code = "5 +6";
4143 let module_id = ModuleId::default();
4144 let result = crate::parsing::parse_str(code, module_id).unwrap();
4145 let expected_result = Node::new(
4146 Program {
4147 body: vec![BodyItem::ExpressionStatement(Node::new(
4148 ExpressionStatement {
4149 expression: Expr::BinaryExpression(Node::boxed(
4150 BinaryExpression {
4151 left: BinaryPart::Literal(Box::new(Node::new(
4152 Literal {
4153 value: LiteralValue::Number {
4154 value: 5.0,
4155 suffix: NumericSuffix::None,
4156 },
4157 raw: "5".to_string(),
4158 digest: None,
4159 },
4160 0,
4161 1,
4162 module_id,
4163 ))),
4164 operator: BinaryOperator::Add,
4165 right: BinaryPart::Literal(Box::new(Node::new(
4166 Literal {
4167 value: LiteralValue::Number {
4168 value: 6.0,
4169 suffix: NumericSuffix::None,
4170 },
4171 raw: "6".to_string(),
4172 digest: None,
4173 },
4174 3,
4175 4,
4176 module_id,
4177 ))),
4178 digest: None,
4179 },
4180 0,
4181 4,
4182 module_id,
4183 )),
4184 digest: None,
4185 },
4186 0,
4187 4,
4188 module_id,
4189 ))],
4190 shebang: None,
4191 non_code_meta: NonCodeMeta::default(),
4192 inner_attrs: Vec::new(),
4193 digest: None,
4194 },
4195 0,
4196 4,
4197 module_id,
4198 );
4199
4200 assert_eq!(result, expected_result);
4201 }
4202
4203 #[test]
4204 fn test_empty_file() {
4205 let some_program_string = r#""#;
4206 let result = crate::parsing::top_level_parse(some_program_string);
4207 assert!(result.is_ok());
4208 }
4209
4210 #[track_caller]
4211 fn assert_no_err(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4212 let result = crate::parsing::top_level_parse(p);
4213 let result = result.0.unwrap();
4214 assert!(result.1.iter().all(|e| !e.severity.is_err()), "found: {:#?}", result.1);
4215 (result.0.unwrap(), result.1)
4216 }
4217
4218 #[track_caller]
4219 fn assert_no_fatal(p: &str) -> (Node<Program>, Vec<CompilationError>) {
4220 let result = crate::parsing::top_level_parse(p);
4221 let result = result.0.unwrap();
4222 assert!(
4223 result.1.iter().all(|e| e.severity != Severity::Fatal),
4224 "found: {:#?}",
4225 result.1
4226 );
4227 (result.0.unwrap(), result.1)
4228 }
4229
4230 #[track_caller]
4231 fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
4232 let result = crate::parsing::top_level_parse(p);
4233 let err = result.unwrap_errs().next().unwrap();
4234 assert!(
4235 err.message.starts_with(msg),
4236 "Found `{}`, expected `{msg}`",
4237 err.message
4238 );
4239 let src_actual = [err.source_range.start(), err.source_range.end()];
4240 assert_eq!(
4241 src_expected,
4242 src_actual,
4243 "expected error would highlight `{}` but it actually highlighted `{}`",
4244 &p[src_expected[0]..src_expected[1]],
4245 &p[src_actual[0]..src_actual[1]],
4246 );
4247 }
4248
4249 #[track_caller]
4250 fn assert_err_contains(p: &str, expected: &str) {
4251 let result = crate::parsing::top_level_parse(p);
4252 let err = &result.unwrap_errs().next().unwrap().message;
4253 assert!(err.contains(expected), "actual='{err}'");
4254 }
4255
4256 #[test]
4257 fn test_parse_half_pipe_small() {
4258 assert_err_contains(
4259 "secondExtrude = startSketchOn(XY)
4260 |> startProfile(at = [0,0])
4261 |",
4262 "Unexpected token: |",
4263 );
4264 }
4265
4266 #[test]
4267 fn test_parse_member_expression_double_nested_braces() {
4268 let code = r#"prop = yo["one"][two]"#;
4269 crate::parsing::top_level_parse(code).unwrap();
4270 }
4271
4272 #[test]
4273 fn test_parse_member_expression_binary_expression_period_number_first() {
4274 let code = r#"obj = { a: 1, b: 2 }
4275height = 1 - obj.a"#;
4276 crate::parsing::top_level_parse(code).unwrap();
4277 }
4278
4279 #[test]
4280 fn test_parse_member_expression_allowed_type_in_expression() {
4281 let code = r#"obj = { thing: 1 }
4282startSketchOn(obj.sketch)"#;
4283
4284 crate::parsing::top_level_parse(code).unwrap();
4285 }
4286
4287 #[test]
4288 fn test_parse_member_expression_binary_expression_brace_number_first() {
4289 let code = r#"obj = { a: 1, b: 2 }
4290height = 1 - obj["a"]"#;
4291 crate::parsing::top_level_parse(code).unwrap();
4292 }
4293
4294 #[test]
4295 fn test_parse_member_expression_binary_expression_brace_number_second() {
4296 let code = r#"obj = { a: 1, b: 2 }
4297height = obj["a"] - 1"#;
4298 crate::parsing::top_level_parse(code).unwrap();
4299 }
4300
4301 #[test]
4302 fn test_parse_member_expression_binary_expression_in_array_number_first() {
4303 let code = r#"obj = { a: 1, b: 2 }
4304height = [1 - obj["a"], 0]"#;
4305 crate::parsing::top_level_parse(code).unwrap();
4306 }
4307
4308 #[test]
4309 fn test_parse_member_expression_binary_expression_in_array_number_second() {
4310 let code = r#"obj = { a: 1, b: 2 }
4311height = [obj["a"] - 1, 0]"#;
4312 crate::parsing::top_level_parse(code).unwrap();
4313 }
4314
4315 #[test]
4316 fn test_parse_member_expression_binary_expression_in_array_number_second_missing_space() {
4317 let code = r#"obj = { a: 1, b: 2 }
4318height = [obj["a"] -1, 0]"#;
4319 crate::parsing::top_level_parse(code).unwrap();
4320 }
4321
4322 #[test]
4323 fn test_anon_fn() {
4324 crate::parsing::top_level_parse("foo(num=42, closure=fn(x) { return x + 1 })").unwrap();
4325 }
4326
4327 #[test]
4328 fn test_annotation_fn() {
4329 crate::parsing::top_level_parse(
4330 r#"fn foo() {
4331 @annotated
4332 return 1
4333}"#,
4334 )
4335 .unwrap();
4336 }
4337
4338 #[test]
4339 fn test_annotation_settings() {
4340 crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
4341 }
4342
4343 #[test]
4344 fn test_anon_fn_no_fn() {
4345 assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
4346 }
4347
4348 #[test]
4349 fn test_parse_half_pipe() {
4350 let code = "height = 10
4351
4352firstExtrude = startSketchOn(XY)
4353 |> startProfile(at = [0,0])
4354 |> line(at = [0, 8])
4355 |> line(at = [20, 0])
4356 |> line(at = [0, -8])
4357 |> close()
4358 |> extrude(length=2)
4359
4360secondExtrude = startSketchOn(XY)
4361 |> startProfile(at = [0,0])
4362 |";
4363 assert_err_contains(code, "Unexpected token: |");
4364 }
4365
4366 #[test]
4367 fn test_parse_greater_bang() {
4368 assert_err(">!", "Unexpected token: >", [0, 1]);
4369 }
4370
4371 #[test]
4372 fn test_parse_unlabeled_param_not_allowed() {
4373 assert_err(
4374 "fn f(@x, @y) { return 1 }",
4375 "Only the first parameter can be declared unlabeled",
4376 [9, 11],
4377 );
4378 assert_err(
4379 "fn f(x, @y) { return 1 }",
4380 "Only the first parameter can be declared unlabeled",
4381 [8, 10],
4382 );
4383 }
4384
4385 #[test]
4386 fn test_parse_z_percent_parens() {
4387 assert_err("z%)", "Unexpected token: %", [1, 2]);
4388 }
4389
4390 #[test]
4391 fn test_parse_parens_unicode() {
4392 let result = crate::parsing::top_level_parse("(ޜ");
4393 let KclError::Lexical(details) = result.0.unwrap_err() else {
4394 panic!();
4395 };
4396 assert_eq!(details.message, "found unknown token 'ޜ'");
4399 assert_eq!(details.source_ranges[0].start(), 1);
4400 assert_eq!(details.source_ranges[0].end(), 2);
4401 }
4402
4403 #[test]
4404 fn test_parse_negative_in_array_binary_expression() {
4405 let code = r#"leg1 = 5
4406thickness = 0.56
4407
4408bracket = [-leg2 + thickness, 0]
4409"#;
4410 crate::parsing::top_level_parse(code).unwrap();
4411 }
4412
4413 #[test]
4414 fn test_parse_nested_open_brackets() {
4415 let _ = crate::parsing::top_level_parse(
4416 r#"
4417z(-[["#,
4418 )
4419 .unwrap_errs();
4420 }
4421
4422 #[test]
4423 fn test_parse_weird_new_line_function() {
4424 assert_err(
4425 r#"z
4426(--#"#,
4427 "Unexpected token: (",
4428 [2, 3],
4429 );
4430 }
4431
4432 #[test]
4433 fn test_parse_weird_lots_of_fancy_brackets() {
4434 assert_err(
4435 r#"zz({{{{{{{{)iegAng{{{{{{{##"#,
4436 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
4437 [3, 4],
4438 );
4439 }
4440
4441 #[test]
4442 fn test_parse_weird_close_before_open() {
4443 assert_err_contains(
4444 r#"fn)n
4445e
4446["#,
4447 "expected whitespace, found ')' which is brace",
4448 );
4449 }
4450
4451 #[test]
4452 fn test_parse_weird_close_before_nada() {
4453 assert_err_contains(r#"fn)n-"#, "expected whitespace, found ')' which is brace");
4454 }
4455
4456 #[test]
4457 fn test_parse_weird_lots_of_slashes() {
4458 assert_err_contains(
4459 r#"J///////////o//+///////////P++++*++++++P///////ËŸ
4460++4"#,
4461 "Unexpected token: +",
4462 );
4463 }
4464
4465 #[test]
4466 fn test_optional_param_order() {
4467 for (i, (params, expect_ok)) in [
4468 (
4469 vec![Parameter {
4470 identifier: Node::no_src(Identifier {
4471 name: "a".to_owned(),
4472 digest: None,
4473 }),
4474 type_: None,
4475 default_value: Some(DefaultParamVal::none()),
4476 labeled: true,
4477 digest: None,
4478 }],
4479 true,
4480 ),
4481 (
4482 vec![Parameter {
4483 identifier: Node::no_src(Identifier {
4484 name: "a".to_owned(),
4485 digest: None,
4486 }),
4487 type_: None,
4488 default_value: None,
4489 labeled: true,
4490 digest: None,
4491 }],
4492 true,
4493 ),
4494 (
4495 vec![
4496 Parameter {
4497 identifier: Node::no_src(Identifier {
4498 name: "a".to_owned(),
4499 digest: None,
4500 }),
4501 type_: None,
4502 default_value: None,
4503 labeled: true,
4504 digest: None,
4505 },
4506 Parameter {
4507 identifier: Node::no_src(Identifier {
4508 name: "b".to_owned(),
4509 digest: None,
4510 }),
4511 type_: None,
4512 default_value: Some(DefaultParamVal::none()),
4513 labeled: true,
4514 digest: None,
4515 },
4516 ],
4517 true,
4518 ),
4519 (
4520 vec![
4521 Parameter {
4522 identifier: Node::no_src(Identifier {
4523 name: "a".to_owned(),
4524 digest: None,
4525 }),
4526 type_: None,
4527 default_value: Some(DefaultParamVal::none()),
4528 labeled: true,
4529 digest: None,
4530 },
4531 Parameter {
4532 identifier: Node::no_src(Identifier {
4533 name: "b".to_owned(),
4534 digest: None,
4535 }),
4536 type_: None,
4537 default_value: None,
4538 labeled: true,
4539 digest: None,
4540 },
4541 ],
4542 false,
4543 ),
4544 ]
4545 .into_iter()
4546 .enumerate()
4547 {
4548 let actual = optional_after_required(¶ms);
4549 assert_eq!(actual.is_ok(), expect_ok, "failed test {i}");
4550 }
4551 }
4552
4553 #[test]
4554 fn test_error_keyword_in_variable() {
4555 assert_err(
4556 r#"const let = "thing""#,
4557 "Cannot assign a variable to a reserved keyword: let",
4558 [6, 9],
4559 );
4560 }
4561
4562 #[test]
4563 fn test_error_keyword_in_fn_name() {
4564 assert_err(
4565 r#"fn let = () {}"#,
4566 "Cannot assign a variable to a reserved keyword: let",
4567 [3, 6],
4568 );
4569 }
4570
4571 #[test]
4572 fn test_error_keyword_in_fn_args() {
4573 assert_err(
4574 r#"fn thing = (let) => {
4575 return 1
4576}"#,
4577 "Cannot assign a variable to a reserved keyword: let",
4578 [12, 15],
4579 )
4580 }
4581
4582 #[test]
4583 fn bad_imports() {
4584 assert_err(
4585 r#"import cube from "../cube.kcl""#,
4586 "import path may not start with '..'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4587 [17, 30],
4588 );
4589 assert_err(
4590 r#"import cube from "/cube.kcl""#,
4591 "import path may not start with '/' or '\\'. Cannot traverse to something outside the bounds of your project. If this path is inside your project please find a better way to reference it.",
4592 [17, 28],
4593 );
4594 assert_err(
4595 r#"import cube from "C:\cube.kcl""#,
4596 "import path may only contain alphanumeric characters, `_`, `-`, `.`, `/`, and `\\`.",
4597 [17, 30],
4598 );
4599 assert_err(
4600 r#"import cube from "cube/cube.kcl""#,
4601 "import path to a subdirectory must only refer to main.kcl.",
4602 [17, 32],
4603 );
4604 assert_err(
4605 r#"import * as foo from "dsfs""#,
4606 "as is not the 'from' keyword",
4607 [9, 11],
4608 );
4609 assert_err(
4610 r#"import a from "dsfs" as b"#,
4611 "unsupported import path format",
4612 [14, 20],
4613 );
4614 assert_err(
4615 r#"import * from "dsfs" as b"#,
4616 "unsupported import path format",
4617 [14, 20],
4618 );
4619 assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
4620 assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
4621 assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
4622 assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
4623 assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
4624 assert_err(
4625 r#"import "foo.bar.kcl""#,
4626 "import path is not a valid identifier and must be aliased.",
4627 [7, 20],
4628 );
4629 assert_err(
4630 r#"import "_foo.kcl""#,
4631 "import path is not a valid identifier and must be aliased.",
4632 [7, 17],
4633 );
4634 assert_err(
4635 r#"import "foo-bar.kcl""#,
4636 "import path is not a valid identifier and must be aliased.",
4637 [7, 20],
4638 );
4639 }
4640
4641 #[test]
4642 fn std_fn_decl() {
4643 let code = r#"/// Compute the cosine of a number (in radians).
4644///
4645/// ```
4646/// exampleSketch = startSketchOn(XZ)
4647/// |> startProfile(at = [0, 0])
4648/// |> angledLine(
4649/// angle = 30,
4650/// length = 3 / cos(toRadians(30)),
4651/// )
4652/// |> yLine(endAbsolute = 0)
4653/// |> close(%)
4654///
4655/// example = extrude(exampleSketch, length = 5)
4656/// ```
4657@(impl = std_rust)
4658export fn cos(num: number(rad)): number(_) {}"#;
4659 let _ast = crate::parsing::top_level_parse(code).unwrap();
4660 }
4661
4662 #[test]
4663 fn warn_import() {
4664 let some_program_string = r#"import "foo.bad""#;
4665 let (_, errs) = assert_no_err(some_program_string);
4666 assert_eq!(errs.len(), 1, "{errs:#?}");
4667 }
4668
4669 #[test]
4670 fn warn_late_settings() {
4671 let some_program_string = r#"foo = 42
4672@settings(defaultLengthUnit = mm)
4673"#;
4674 let (_, errs) = assert_no_err(some_program_string);
4675 assert_eq!(errs.len(), 1, "{errs:#?}");
4676 }
4677
4678 #[test]
4679 fn warn_unknown_suffix() {
4680 let some_program_string = r#"foo = 42_?
4681"#;
4682 let (_, errs) = assert_no_err(some_program_string);
4683 assert_eq!(errs.len(), 1, "{errs:#?}");
4684 }
4685
4686 #[test]
4687 fn fn_decl_uom_ty() {
4688 let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
4689 let (_, errs) = assert_no_fatal(some_program_string);
4690 assert!(errs.is_empty(), "Expected no errors, found: {errs:?}");
4691 }
4692
4693 #[test]
4694 fn error_underscore() {
4695 let (_, errs) = assert_no_fatal("_foo(a=_blah, b=_)");
4696 assert_eq!(errs.len(), 3, "found: {errs:#?}");
4697 }
4698
4699 #[test]
4700 fn error_double_and() {
4701 let (_, errs) = assert_no_fatal("foo = true && false");
4702 assert_eq!(errs.len(), 1, "found: {errs:#?}");
4703 assert!(errs[0].message.contains("`&&`") && errs[0].message.contains("`&`") && errs[0].suggestion.is_some());
4704 }
4705
4706 #[test]
4707 fn error_type_ascription() {
4708 let (_, errs) = assert_no_fatal("a + b: number");
4709 assert!(errs.is_empty());
4710 }
4711
4712 #[test]
4713 fn zero_param_function() {
4714 let code = r#"
4715 fn firstPrimeNumber() {
4716 return 2
4717 }
4718 firstPrimeNumber()
4719 "#;
4720 let _ast = crate::parsing::top_level_parse(code).unwrap();
4721 }
4722
4723 #[test]
4724 fn array() {
4725 let program = r#"[1, 2, 3]"#;
4726 let module_id = ModuleId::default();
4727 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4728 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4729 }
4730
4731 #[test]
4732 fn array_linesep_trailing_comma() {
4733 let program = r#"[
4734 1,
4735 2,
4736 3,
4737 ]"#;
4738 let module_id = ModuleId::default();
4739 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4740 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4741 }
4742
4743 #[allow(unused)]
4744 #[test]
4745 fn array_linesep_no_trailing_comma() {
4746 let program = r#"[
4747 1,
4748 2,
4749 3
4750 ]"#;
4751 let module_id = ModuleId::default();
4752 let tokens = crate::parsing::token::lex(program, module_id).unwrap();
4753 let _arr = array_elem_by_elem(&mut tokens.as_slice()).unwrap();
4754 }
4755
4756 #[test]
4757 fn basic_if_else() {
4758 let some_program_string = "if true {
4759 3
4760 } else {
4761 4
4762 }";
4763 let module_id = ModuleId::default();
4764 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4765 let _res = if_expr(&mut tokens.as_slice()).unwrap();
4766 }
4767
4768 #[test]
4769 fn basic_else_if() {
4770 let some_program_string = "else if true {
4771 4
4772 }";
4773 let module_id = ModuleId::default();
4774 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4775 let _res = else_if(&mut tokens.as_slice()).unwrap();
4776 }
4777
4778 #[test]
4779 fn basic_if_else_if() {
4780 let some_program_string = "if true {
4781 3
4782 } else if true {
4783 4
4784 } else {
4785 5
4786 }";
4787 let module_id = ModuleId::default();
4788 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap();
4789 let _res = if_expr(&mut tokens.as_slice()).unwrap();
4790 }
4791
4792 #[test]
4793 fn test_keyword_ok_in_fn_args_return() {
4794 let some_program_string = r#"fn thing(param) {
4795 return true
4796}
4797
4798thing(false)
4799"#;
4800 crate::parsing::top_level_parse(some_program_string).unwrap();
4801 }
4802
4803 #[test]
4804 fn test_error_define_var_as_function() {
4805 assert_err(r#"fn thing = "thing""#, "Unexpected token: \"thing\"", [11, 18]);
4809 }
4810
4811 #[test]
4812 fn random_words_fail() {
4813 let test_program = r#"part001 = startSketchOn(-XZ)
4814 |> startProfile(at = [8.53, 11.8])
4815 asdasd asdasd
4816 |> line(at = [11.12, -14.82])
4817 |> line(at = [-13.27, -6.98])
4818 |> line(at = [-5.09, 12.33])
4819 asdasd
4820"#;
4821 let _ = crate::parsing::top_level_parse(test_program).unwrap_errs();
4822 }
4823
4824 #[test]
4825 fn test_member_expression_sketch() {
4826 let some_program_string = r#"fn cube(pos, scale) {
4827 sg = startSketchOn(XY)
4828 |> startProfile(pos)
4829 |> line(at = [0, scale])
4830 |> line(at = [scale, 0])
4831 |> line(at = [0, -scale])
4832
4833 return sg
4834}
4835
4836b1 = cube(pos=[0,0], scale=10)
4837b2 = cube(pos=[3,3], scale=4)
4838
4839pt1 = b1[0]
4840pt2 = b2[0]
4841"#;
4842 crate::parsing::top_level_parse(some_program_string).unwrap();
4843 }
4844
4845 #[test]
4846 fn test_math_with_stdlib() {
4847 let some_program_string = r#"d2r = pi() / 2
4848let other_thing = 2 * cos(3)"#;
4849 crate::parsing::top_level_parse(some_program_string).unwrap();
4850 }
4851
4852 #[test]
4853 fn test_negative_arguments() {
4854 let some_program_string = r#"fn box(p, h, l, w) {
4855 myBox = startSketchOn(XY)
4856 |> startProfile(p)
4857 |> line(at = [0, l])
4858 |> line(at = [w, 0])
4859 |> line(at = [0, -l])
4860 |> close()
4861 |> extrude(length=h)
4862
4863 return myBox
4864}
4865let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
4866"#;
4867 crate::parsing::top_level_parse(some_program_string).unwrap();
4868 }
4869
4870 #[test]
4871 fn kw_fn() {
4872 for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
4873 let module_id = ModuleId::default();
4874 let tokens = crate::parsing::token::lex(input, module_id).unwrap();
4875 super::program.parse(tokens.as_slice()).unwrap();
4876 }
4877 }
4878
4879 #[test]
4880 fn test_parse_tag_named_std_lib() {
4881 let some_program_string = r#"startSketchOn(XY)
4882 |> startProfile(at = [0, 0])
4883 |> line(%, end = [5, 5], tag = $xLine)
4884"#;
4885 assert_err(
4886 some_program_string,
4887 "Cannot assign a tag to a reserved keyword: xLine",
4888 [86, 92],
4889 );
4890 }
4891
4892 #[test]
4893 fn test_parse_empty_tag_brace() {
4894 let some_program_string = r#"startSketchOn(XY)
4895 |> startProfile(at = [0, 0])
4896 |> line(%, $)
4897 "#;
4898 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4899 }
4900 #[test]
4901 fn test_parse_empty_tag_whitespace() {
4902 let some_program_string = r#"startSketchOn(XY)
4903 |> startProfile(at = [0, 0])
4904 |> line(%, $ ,01)
4905 "#;
4906 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4907 }
4908
4909 #[test]
4910 fn test_parse_empty_tag_comma() {
4911 let some_program_string = r#"startSketchOn(XY)
4912 |> startProfile(at = [0, 0])
4913 |> line(%, $,)
4914 "#;
4915 assert_err(some_program_string, "Tag names must not be empty", [67, 68]);
4916 }
4917 #[test]
4918 fn test_parse_tag_starting_with_digit() {
4919 let some_program_string = r#"
4920 startSketchOn(XY)
4921 |> startProfile(at = [0, 0])
4922 |> line(%, $01)"#;
4923 assert_err(
4924 some_program_string,
4925 "Tag names must not start with a number. Tag starts with `01`",
4926 [72, 74],
4927 );
4928 }
4929 #[test]
4930 fn test_parse_tag_including_digit() {
4931 let some_program_string = r#"
4932 startSketchOn(XY)
4933 |> startProfile(at = [0, 0])
4934 |> line(%, tag = $var01)"#;
4935 assert_no_err(some_program_string);
4936 }
4937
4938 #[test]
4939 fn test_parse_param_bool_default() {
4940 let some_program_string = r#"fn patternTransform(
4941 use_original?: boolean = false,
4942) {}"#;
4943 assert_no_err(some_program_string);
4944 }
4945
4946 #[test]
4947 fn parse_function_types() {
4948 let code = r#"foo = x: fn
4949foo = x: fn(number)
4950fn foo(x: fn(): number): fn { return 0 }
4951fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
4952type fn
4953type foo = fn
4954type foo = fn(a: string, b: { f: fn(): any })
4955type foo = fn(a: string, b: {})
4956type foo = fn(a: string, b: { })
4957type foo = fn([fn])
4958type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
4959 "#;
4960 assert_no_err(code);
4961 }
4962 #[test]
4963 fn test_parse_tag_starting_with_bang() {
4964 let some_program_string = r#"startSketchOn(XY)
4965 |> startProfile(at = [0, 0])
4966 |> line(%, $!var,01)
4967 "#;
4968 assert_err(some_program_string, "Tag names must not start with a bang", [67, 68]);
4969 }
4970 #[test]
4971 fn test_parse_tag_starting_with_dollar() {
4972 let some_program_string = r#"startSketchOn(XY)
4973 |> startProfile(at = [0, 0])
4974 |> line(%, $$,01)
4975 "#;
4976 assert_err(some_program_string, "Tag names must not start with a dollar", [67, 68]);
4977 }
4978 #[test]
4979 fn test_parse_tag_starting_with_fn() {
4980 let some_program_string = r#"startSketchOn(XY)
4981 |> startProfile(at = [0, 0])
4982 |> line(%, $fn,01)
4983 "#;
4984 assert_err(some_program_string, "Tag names must not start with a keyword", [67, 69]);
4985 }
4986 #[test]
4987 fn test_parse_tag_starting_with_a_comment() {
4988 let some_program_string = r#"startSketchOn(XY)
4989 |> startProfile(at = [0, 0])
4990 |> line(%, $//
4991 ,01)
4992 "#;
4993 assert_err(
4994 some_program_string,
4995 "Tag names must not start with a lineComment",
4996 [67, 69],
4997 );
4998 }
4999
5000 #[test]
5001 fn test_parse_tag_with_reserved_in_middle_works() {
5002 let some_program_string = r#"
5003 startSketchOn(XY)
5004 |> startProfile(at = [0, 0])
5005 |> line(end = [5, 5], tag = $sketching)
5006 "#;
5007 assert_no_err(some_program_string);
5008 }
5009
5010 #[test]
5011 fn test_parse_array_missing_closing_bracket() {
5012 let some_program_string = r#"
5013sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45, 119.09)"#;
5014 assert_err(
5015 some_program_string,
5016 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5017 [52, 60],
5018 );
5019 }
5020 #[test]
5021 fn test_parse_array_missing_comma() {
5022 let some_program_string = r#"
5023sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 119.09])"#;
5024 assert_err(
5025 some_program_string,
5026 "Unexpected character encountered. You might be missing a comma in between elements.",
5027 [53, 66],
5028 );
5029 }
5030 #[test]
5031 fn test_parse_array_reserved_word_early_exit() {
5032 let some_program_string = r#"
5035sketch001 = startSketchOn(XZ) |> startProfile(at = [90.45 $struct])"#;
5036 assert_err(
5037 some_program_string,
5038 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5039 [52, 53],
5040 );
5041 }
5042 #[test]
5043 fn test_parse_array_random_brace() {
5044 let some_program_string = r#"
5045sketch001 = startSketchOn(XZ) |> startProfile(at = [}])"#;
5046 assert_err(
5047 some_program_string,
5048 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
5049 [52, 53],
5050 );
5051 }
5052
5053 #[test]
5054 fn test_parse_object_missing_closing_brace() {
5055 let some_program_string = r#"{
5056 foo = bar,"#;
5057
5058 assert_err(
5059 some_program_string,
5060 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5061 [0, 23],
5062 );
5063 }
5064 #[test]
5065 fn test_parse_object_reserved_word_early_exit() {
5066 let some_program_string = r#"{bar = foo struct = man}"#;
5069
5070 assert_err(
5071 some_program_string,
5072 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5073 [0, 1],
5074 );
5075 }
5076 #[test]
5077 fn test_parse_object_missing_comma() {
5078 let some_program_string = r#"{
5079 foo = bar,
5080 bar = foo
5081 bat = man
5082 }"#;
5083
5084 assert_err(
5085 some_program_string,
5086 "Unexpected character encountered. You might be missing a comma in between properties.",
5087 [37, 78],
5088 );
5089 }
5090
5091 #[test]
5092 fn test_parse_object_missing_comma_one_line() {
5093 let some_program_string = r#"{bar = foo bat = man}"#;
5094
5095 assert_err(
5096 some_program_string,
5097 "Unexpected character encountered. You might be missing a comma in between properties.",
5098 [1, 21],
5099 );
5100 }
5101
5102 #[test]
5103 fn test_parse_object_random_bracket() {
5104 let some_program_string = r#"{]}"#;
5105
5106 assert_err(
5107 some_program_string,
5108 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
5109 [0, 1],
5110 );
5111 }
5112
5113 #[test]
5114 fn test_parse_object_shorthand_missing_comma() {
5115 let some_program_string = r#"
5116bar = 1
5117 {
5118 foo = bar,
5119 bar
5120 bat = man
5121 }"#;
5122
5123 assert_err(
5124 some_program_string,
5125 "Unexpected character encountered. You might be missing a comma in between properties.",
5126 [54, 89],
5127 );
5128 }
5129
5130 #[test]
5131 fn test_unary_not_on_keyword_bool() {
5132 let some_program_string = r#"!true"#;
5133 let module_id = ModuleId::default();
5134 let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); let actual = match unary_expression.parse(tokens.as_slice()) {
5136 Ok(x) => x,
5138 Err(e) => panic!("{e:?}"),
5139 };
5140 assert_eq!(actual.operator, UnaryOperator::Not);
5141 crate::parsing::top_level_parse(some_program_string).unwrap(); }
5143
5144 #[test]
5145 fn test_sensible_error_when_missing_rhs_of_kw_arg() {
5146 for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
5147 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5148 let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
5149 let cause = err.inner().cause.as_ref().unwrap();
5150 assert_eq!(
5151 cause.message, "This argument has a label, but no value. Put some value after the equals sign",
5152 "failed test {i}: {program}"
5153 );
5154 assert_eq!(
5155 cause.source_range.start(),
5156 program.find("y").unwrap(),
5157 "failed test {i}: {program}"
5158 );
5159 }
5160 }
5161
5162 #[test]
5163 fn test_sensible_error_when_using_keyword_as_arg_label() {
5164 for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
5165 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5166 let err = match fn_call_kw.parse(tokens.as_slice()) {
5167 Err(e) => e,
5168 Ok(ast) => {
5169 eprintln!("{ast:#?}");
5170 panic!("Expected this to error but it didn't");
5171 }
5172 };
5173 let cause = err.inner().cause.as_ref().unwrap();
5174 assert_eq!(
5175 cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
5176 "failed test {i}: {program}"
5177 );
5178 assert_eq!(
5179 cause.source_range.start(),
5180 program.find("fn").unwrap(),
5181 "failed test {i}: {program}"
5182 );
5183 }
5184 }
5185
5186 #[test]
5187 fn test_sensible_error_when_missing_rhs_of_obj_property() {
5188 for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {
5189 let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
5190 let err = object.parse(tokens.as_slice()).unwrap_err();
5191 let cause = err.inner().cause.as_ref().unwrap();
5192 assert_eq!(
5193 cause.message, "This property has a label, but no value. Put some value after the equals sign",
5194 "failed test {i}: {program}"
5195 );
5196 assert_eq!(
5197 cause.source_range.start(),
5198 program.rfind('=').unwrap(),
5199 "failed test {i}: {program}"
5200 );
5201 }
5202 }
5203
5204 #[test]
5205 fn test_sensible_error_duplicated_args() {
5206 let program = r#"f(arg = 1, normal = 44, arg = 2)"#;
5207 let (_, mut errs) = assert_no_fatal(program);
5208 assert_eq!(errs.len(), 1);
5209 let err = errs.pop().unwrap();
5210 assert_eq!(
5211 err.message,
5212 "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.",
5213 );
5214 }
5215}
5216
5217#[cfg(test)]
5218mod snapshot_math_tests {
5219 use super::*;
5220
5221 macro_rules! snapshot_test {
5225 ($func_name:ident, $test_kcl_program:expr) => {
5226 #[test]
5227 fn $func_name() {
5228 let module_id = crate::ModuleId::default();
5229 let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5230 ParseContext::init();
5231
5232 let actual = match binary_expression.parse(tokens.as_slice()) {
5233 Ok(x) => x,
5234 Err(_e) => panic!("could not parse test"),
5235 };
5236 insta::assert_json_snapshot!(actual);
5237 let _ = ParseContext::take();
5238 }
5239 };
5240 }
5241
5242 snapshot_test!(a, "1 + 2");
5243 snapshot_test!(b, "1+2");
5244 snapshot_test!(c, "1 -2");
5245 snapshot_test!(d, "1 + 2 * 3");
5246 snapshot_test!(e, "1 * ( 2 + 3 )");
5247 snapshot_test!(f, "1 * ( 2 + 3 ) / 4");
5248 snapshot_test!(g, "1 + ( 2 + 3 ) / 4");
5249 snapshot_test!(h, "1 * (( 2 + 3 ) / 4 + 5 )");
5250 snapshot_test!(i, "1 * ((( 2 + 3 )))");
5251 snapshot_test!(j, "distance * p * FOS * 6 / (sigmaAllow * width)");
5252 snapshot_test!(k, "2 + (((3)))");
5253}
5254
5255#[cfg(test)]
5256mod snapshot_tests {
5257 use super::*;
5258
5259 macro_rules! snapshot_test {
5263 ($func_name:ident, $test_kcl_program:expr) => {
5264 #[test]
5265 fn $func_name() {
5266 let module_id = crate::ModuleId::default();
5267 println!("{}", $test_kcl_program);
5268 let tokens = crate::parsing::token::lex($test_kcl_program, module_id).unwrap();
5269 print_tokens(tokens.as_slice());
5270 ParseContext::init();
5271 let actual = match program.parse(tokens.as_slice()) {
5272 Ok(x) => x,
5273 Err(e) => panic!("could not parse test: {e:?}"),
5274 };
5275 let mut settings = insta::Settings::clone_current();
5276 settings.set_sort_maps(true);
5277 settings.bind(|| {
5278 insta::assert_json_snapshot!(actual);
5279 });
5280 let _ = ParseContext::take();
5281 }
5282 };
5283 }
5284
5285 snapshot_test!(
5286 a,
5287 r#"boxSketch = startSketchOn(XY)
5288 |> startProfileAt(at = [0, 0])
5289 |> line(at = [0, 10])
5290 |> tangentialArc(end = [-5, 5])
5291 |> line(at = [5, -15])
5292 |> extrude(length=10)
5293"#
5294 );
5295 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)");
5298 snapshot_test!(d, "myVar = 5 + 6 |> myFunc(45)");
5299 snapshot_test!(e, "x = 1 * (3 - 4)");
5300 snapshot_test!(f, r#"x = 1 // this is an inline comment"#);
5301 snapshot_test!(
5302 g,
5303 r#"fn x() {
5304 return sg
5305 return sg
5306 }"#
5307 );
5308 snapshot_test!(d2, r#"x = -leg2 + thickness"#);
5309 snapshot_test!(
5310 h,
5311 r#"obj = { a = 1, b = 2 }
5312 height = 1 - obj.a"#
5313 );
5314 snapshot_test!(
5315 i,
5316 r#"obj = { a = 1, b = 2 }
5317 height = 1 - obj["a"]"#
5318 );
5319 snapshot_test!(
5320 j,
5321 r#"obj = { a = 1, b = 2 }
5322 height = obj["a"] - 1"#
5323 );
5324 snapshot_test!(
5325 k,
5326 r#"obj = { a = 1, b = 2 }
5327 height = [1 - obj["a"], 0]"#
5328 );
5329 snapshot_test!(
5330 l,
5331 r#"obj = { a = 1, b = 2 }
5332 height = [obj["a"] - 1, 0]"#
5333 );
5334 snapshot_test!(
5335 m,
5336 r#"obj = {a = 1, b = 2 }
5337 height = [obj["a"] -1, 0]"#
5338 );
5339 snapshot_test!(n, "height = 1 - obj.a");
5340 snapshot_test!(o, "six = 1 + 2 + 3");
5341 snapshot_test!(p, "five = 3 * 1 + 2");
5342 snapshot_test!(q, r#"height = [ obj["a"], 0 ]"#);
5343 snapshot_test!(
5344 r,
5345 r#"obj = { a = 1, b = 2 }
5346 height = obj["a"]"#
5347 );
5348 snapshot_test!(s, r#"prop = yo["one"][two]"#);
5349 snapshot_test!(t, r#"pt1 = b1[x]"#);
5350 snapshot_test!(u, "prop = yo.one.two.three.four");
5351 snapshot_test!(v, r#"pt1 = b1[0]"#);
5352 snapshot_test!(w, r#"pt1 = b1['zero']"#);
5353 snapshot_test!(x, r#"pt1 = b1.zero"#);
5354 snapshot_test!(y, r#"sg = startSketchOn(XY) |> startProfile(pos)"#);
5355 snapshot_test!(
5356 z,
5357 "sg = startSketchOn(XY)
5358 |> startProfile(pos) |> line([0, -scale])"
5359 );
5360 snapshot_test!(aa, r#"sg = -scale"#);
5361 snapshot_test!(ab, "line(endAbsolute = [0, -1])");
5362 snapshot_test!(
5363 ad,
5364 r#"
5365 fn firstPrimeNumber() {
5366 return 2
5367 }
5368 firstPrimeNumber()"#
5369 );
5370 snapshot_test!(
5371 ae,
5372 r#"fn thing(param) {
5373 return true
5374 }
5375 thing(false)"#
5376 );
5377 snapshot_test!(
5378 af,
5379 r#"mySketch = startSketchOn(XY)
5380 |> startProfile(at = [0,0])
5381 |> line(endAbsolute = [0, 1], tag = $myPath)
5382 |> line(endAbsolute = [1, 1])
5383 |> line(endAbsolute = [1, 0], tag = $rightPath)
5384 |> close()"#
5385 );
5386 snapshot_test!(
5387 ag,
5388 "mySketch = startSketchOn(XY) |> startProfile(at = [0,0]) |> line(endAbsolute = [1, 1]) |> close()"
5389 );
5390 snapshot_test!(ah, "myBox = startSketchOn(XY) |> startProfile(at = p)");
5391 snapshot_test!(ai, r#"myBox = f(1) |> g(2)"#);
5392 snapshot_test!(
5393 aj,
5394 r#"myBox = startSketchOn(XY) |> startProfile(at = p) |> line(end = [0, l])"#
5395 );
5396 snapshot_test!(ak, "line(endAbsolute = [0, 1])");
5397 snapshot_test!(ap, "mySketch = startSketchOn(XY) |> startProfile(at = [0,0])");
5398 snapshot_test!(aq, "log(number = 5, msg = \"hello\", id=aIdentifier)");
5399 snapshot_test!(ar, r#"5 + "a""#);
5400 snapshot_test!(at, "line([0, l])");
5401 snapshot_test!(au, include_str!("../../e2e/executor/inputs/cylinder.kcl"));
5402 snapshot_test!(av, "fn f(angle?) { return default(maybe=angle, otherwise=360) }");
5403 snapshot_test!(
5404 aw,
5405 "numbers = [
5406 1,
5407 // A,
5408 // B,
5409 3,
5410 ]"
5411 );
5412 snapshot_test!(
5413 ax,
5414 "numbers = [
5415 1,
5416 2,
5417 // A,
5418 // B,
5419 ]"
5420 );
5421 snapshot_test!(
5422 ay,
5423 "let props = {
5424 a: 1,
5425 // b: 2,
5426 c: 3,
5427 }"
5428 );
5429 snapshot_test!(
5430 az,
5431 "props = {
5432 a: 1,
5433 // b: 2,
5434 c: 3
5435 }"
5436 );
5437 snapshot_test!(
5438 bb,
5439 r#"
5440my14 = 4 ^ 2 - 3 ^ 2 * 2
5441"#
5442 );
5443 snapshot_test!(
5444 bc,
5445 r#"x = if true {
5446 3
5447 } else {
5448 4
5449 }"#
5450 );
5451 snapshot_test!(
5452 bd,
5453 r#"x = if true {
5454 3
5455 } else if func(radius) {
5456 4
5457 } else {
5458 5
5459 }"#
5460 );
5461 snapshot_test!(be, "x = 3 == 3");
5462 snapshot_test!(bf, "x = 3 != 3");
5463 snapshot_test!(bg, r#"x = 4"#);
5464 snapshot_test!(bh, "obj = {center = [10, 10], radius =5}");
5465 snapshot_test!(
5466 bi,
5467 r#"x = 3
5468 obj = { x, y = 4}"#
5469 );
5470 snapshot_test!(bj, "true");
5471 snapshot_test!(bk, "truee");
5472 snapshot_test!(bl, "x = !true");
5473 snapshot_test!(bm, "x = true & false");
5474 snapshot_test!(bn, "x = true | false");
5475 snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
5476 snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
5477 snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
5478 snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
5479 snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
5480 snapshot_test!(
5481 kw_function_decl_with_default_and_type,
5482 r#"fn foo(x?: number = 2) { return 1 }"#
5483 );
5484 snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#);
5485 snapshot_test!(
5486 kw_function_call_multiline,
5487 r#"val = f(
5488 arg = x,
5489 foo = x,
5490 bar = x,
5491 )"#
5492 );
5493 snapshot_test!(
5494 kw_function_call_multiline_with_comments,
5495 r#"val = f(
5496 arg = x,
5497 // foo = x,
5498 bar = x,
5499 )"#
5500 );
5501 snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#);
5502 snapshot_test!(
5503 array_ranges,
5504 r#"incl = [1..10]
5505 excl = [0..<10]"#
5506 );
5507}
5508
5509#[allow(unused)]
5510#[cfg(test)]
5511pub(crate) fn print_tokens(tokens: TokenSlice) {
5512 for (i, tok) in tokens.iter().enumerate() {
5513 println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
5514 }
5515}