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