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