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