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