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