1use std::cell::Cell;
21use std::sync::Arc;
22
23use cstree::Syntax;
24use cstree::build::GreenNodeBuilder;
25use cstree::green::GreenNode;
26use cstree::interning::TokenInterner;
27use cstree::syntax::ResolvedNode;
28use text_size::{TextRange, TextSize};
29
30use crate::SyntaxKind;
31use crate::lexer::{RawToken, tokenize};
32use crate::prepass::run as run_prepass;
33
34mod grammar;
35
36#[derive(Debug, Clone)]
39pub struct Parse {
40 green: GreenNode,
41 interner: Arc<TokenInterner>,
42 errors: Vec<SyntaxError>,
43}
44
45impl Parse {
46 #[must_use]
49 pub fn syntax_node(&self) -> ResolvedNode<SyntaxKind> {
50 ResolvedNode::new_root_with_resolver(self.green.clone(), Arc::clone(&self.interner))
51 }
52
53 #[must_use]
55 pub fn errors(&self) -> &[SyntaxError] {
56 &self.errors
57 }
58
59 #[must_use]
61 pub fn green(&self) -> &GreenNode {
62 &self.green
63 }
64
65 #[must_use]
68 pub fn debug_tree(&self) -> String {
69 cstree::syntax::SyntaxNode::<SyntaxKind>::new_root(self.green.clone())
70 .debug(&self.interner, true)
71 }
72}
73
74impl PartialEq for Parse {
81 fn eq(&self, other: &Self) -> bool {
82 self.green == other.green && self.errors == other.errors
83 }
84}
85impl Eq for Parse {}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct SyntaxError {
90 pub range: TextRange,
92 pub message: String,
94}
95
96#[must_use]
98pub fn parse(text: &str) -> Parse {
99 let raw = tokenize(text);
100 let (tokens, indent_diags) = run_prepass(&raw, text);
101
102 let mut p = Parser::new(text, &tokens);
103 p.source_file();
104 let Parser {
105 events, mut errors, ..
106 } = p;
107
108 errors.extend(indent_diags.into_iter().map(|d| SyntaxError {
109 range: d.range,
110 message: d.message,
111 }));
112
113 let (green, interner) = build_tree(&events, &tokens, text);
114 Parse {
115 green,
116 interner,
117 errors,
118 }
119}
120
121#[derive(Debug, Clone, Copy)]
124enum Event {
125 Open { kind: SyntaxKind },
126 Close,
127 Advance,
128}
129
130struct Marker {
132 pos: usize,
133}
134
135#[derive(Clone, Copy)]
138struct MarkClosed {
139 pos: usize,
140}
141
142const FUEL: u32 = 256;
146
147struct Parser<'s> {
148 src: &'s str,
149 tokens: &'s [RawToken],
150 nontrivia: Vec<usize>,
152 pos: usize,
154 fuel: Cell<u32>,
155 events: Vec<Event>,
156 errors: Vec<SyntaxError>,
157}
158
159impl<'s> Parser<'s> {
160 fn new(src: &'s str, tokens: &'s [RawToken]) -> Self {
161 let nontrivia = tokens
162 .iter()
163 .enumerate()
164 .filter(|(_, t)| !t.kind.is_trivia())
165 .map(|(i, _)| i)
166 .collect();
167 Self {
168 src,
169 tokens,
170 nontrivia,
171 pos: 0,
172 fuel: Cell::new(FUEL),
173 events: Vec::new(),
174 errors: Vec::new(),
175 }
176 }
177
178 fn nth(&self, n: usize) -> SyntaxKind {
180 assert!(self.fuel.get() > 0, "parser stuck at position {}", self.pos);
181 self.fuel.set(self.fuel.get() - 1);
182 self.nontrivia
183 .get(self.pos + n)
184 .map_or(SyntaxKind::Eof, |&i| self.tokens[i].kind)
185 }
186
187 fn at(&self, kind: SyntaxKind) -> bool {
188 self.nth(0) == kind
189 }
190
191 fn match_begins_statement(&self) -> bool {
200 use SyntaxKind as K;
201 let kind_at = |off: usize| {
202 self.nontrivia
203 .get(self.pos + off)
204 .map_or(K::Eof, |&i| self.tokens[i].kind)
205 };
206 match kind_at(1) {
207 K::Dot
209 | K::Eq
210 | K::ColonEq
211 | K::PlusEq
212 | K::MinusEq
213 | K::StarEq
214 | K::SlashEq
215 | K::StarStarEq
216 | K::PercentEq
217 | K::AmpEq
218 | K::PipeEq
219 | K::CaretEq
220 | K::ShlEq
221 | K::ShrEq => false,
222 K::LParen | K::LBrack => {
223 let mut depth = 0usize;
225 let mut off = 1;
226 loop {
227 match kind_at(off) {
228 K::Eof => return true, K::LParen | K::LBrack | K::LBrace => depth += 1,
230 K::RParen | K::RBrack | K::RBrace => {
231 depth -= 1;
232 if depth == 0 {
233 return kind_at(off + 1) == K::Colon;
236 }
237 }
238 _ => {}
239 }
240 off += 1;
241 }
242 }
243 _ => true,
244 }
245 }
246
247 fn at_any(&self, kinds: &[SyntaxKind]) -> bool {
248 kinds.contains(&self.nth(0))
249 }
250
251 fn eof(&self) -> bool {
252 self.pos >= self.nontrivia.len()
253 }
254
255 fn cur_range(&self) -> TextRange {
257 self.nontrivia.get(self.pos).map_or_else(
258 || TextRange::empty(TextSize::of(self.src)),
259 |&i| self.tokens[i].range,
260 )
261 }
262
263 fn cur_text(&self) -> &str {
266 self.nontrivia
267 .get(self.pos)
268 .map_or("", |&i| &self.src[self.tokens[i].range])
269 }
270
271 fn advance(&mut self) {
272 if self.eof() {
276 return;
277 }
278 self.fuel.set(FUEL);
279 self.events.push(Event::Advance);
280 self.pos += 1;
281 }
282
283 fn open(&mut self) -> Marker {
284 let m = Marker {
285 pos: self.events.len(),
286 };
287 self.events.push(Event::Open {
288 kind: SyntaxKind::Tombstone,
289 });
290 m
291 }
292
293 #[allow(clippy::needless_pass_by_value)]
296 fn close(&mut self, m: Marker, kind: SyntaxKind) -> MarkClosed {
297 self.events[m.pos] = Event::Open { kind };
298 self.events.push(Event::Close);
299 MarkClosed { pos: m.pos }
300 }
301
302 fn open_before(&mut self, m: MarkClosed) -> Marker {
305 self.events.insert(
306 m.pos,
307 Event::Open {
308 kind: SyntaxKind::Tombstone,
309 },
310 );
311 Marker { pos: m.pos }
312 }
313
314 fn eat(&mut self, kind: SyntaxKind) -> bool {
315 if self.at(kind) {
316 self.advance();
317 true
318 } else {
319 false
320 }
321 }
322
323 fn expect(&mut self, kind: SyntaxKind) {
327 if self.eat(kind) {
328 return;
329 }
330 self.error(format!("Expected {}.", kind.display_name()));
331 }
332
333 fn expect_after(&mut self, kind: SyntaxKind, context: &str) {
336 if self.eat(kind) {
337 return;
338 }
339 self.error(format!("Expected {} after {context}.", kind.display_name()));
340 }
341
342 fn expect_closing(&mut self, kind: SyntaxKind, context: &str) {
345 if self.eat(kind) {
346 return;
347 }
348 self.error(format!(
349 "Expected closing {} after {context}.",
350 kind.display_name()
351 ));
352 }
353
354 fn error(&mut self, message: String) {
356 self.errors.push(SyntaxError {
357 range: self.cur_range(),
358 message,
359 });
360 }
361
362 fn advance_with_error(&mut self, message: &str) -> MarkClosed {
366 let m = self.open();
367 self.error(message.to_owned());
368 if !self.eof() {
369 self.advance();
370 }
371 self.close(m, SyntaxKind::ErrorNode)
372 }
373}
374
375fn build_tree(events: &[Event], tokens: &[RawToken], src: &str) -> (GreenNode, Arc<TokenInterner>) {
379 let mut builder: GreenNodeBuilder<'static, 'static, SyntaxKind> = GreenNodeBuilder::new();
380 let mut tok = 0usize;
381 let mut depth: u32 = 0;
382
383 for event in events {
384 match *event {
385 Event::Open { kind } => {
386 if kind == SyntaxKind::Tombstone {
387 continue; }
389 depth += 1;
390 builder.start_node(kind);
391 }
392 Event::Close => {
393 depth -= 1;
394 if depth == 0 {
395 while tok < tokens.len() {
398 emit(&mut builder, tokens[tok], src);
399 tok += 1;
400 }
401 }
402 builder.finish_node();
403 }
404 Event::Advance => {
405 while tok < tokens.len() && tokens[tok].kind.is_trivia() {
406 emit(&mut builder, tokens[tok], src);
407 tok += 1;
408 }
409 if tok < tokens.len() {
410 emit(&mut builder, tokens[tok], src);
411 tok += 1;
412 }
413 }
414 }
415 }
416
417 let (green, cache) = builder.finish();
418 let interner = cache
419 .expect("a builder created with `new()` owns its cache")
420 .into_interner()
421 .expect("the cache owns its interner");
422 (green, Arc::new(interner))
423}
424
425fn emit(builder: &mut GreenNodeBuilder<'static, 'static, SyntaxKind>, t: RawToken, src: &str) {
429 if <SyntaxKind as Syntax>::static_text(t.kind).is_some() {
430 builder.static_token(t.kind);
431 } else {
432 builder.token(t.kind, &src[t.range]);
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 fn round_trips(src: &str) {
441 let parse = parse(src);
442 assert_eq!(
443 parse.syntax_node().to_string(),
444 src,
445 "round-trip mismatch for {src:?}",
446 );
447 }
448
449 #[test]
450 fn round_trips_a_function() {
451 round_trips("func f():\n\tpass\n");
452 }
453
454 #[test]
455 fn round_trips_inline_function() {
456 round_trips("func square(a): return a\n");
457 }
458
459 #[test]
460 fn round_trips_with_trivia() {
461 round_trips("## doc\nfunc _ready() -> void:\n\tpass\n\n# trailing comment\n");
462 }
463
464 #[test]
465 fn round_trips_multiple_functions() {
466 round_trips("func a():\n\tpass\nfunc b():\n\tpass\n");
467 }
468
469 #[test]
470 fn round_trips_empty_and_blank() {
471 round_trips("");
472 round_trips("\n\n");
473 round_trips("# only a comment\n");
474 }
475
476 #[test]
477 fn produces_expected_top_level_shape() {
478 let parse = parse("func f():\n\tpass\n");
479 let root = parse.syntax_node();
480 assert_eq!(root.kind(), SyntaxKind::SourceFile);
481 let func = root
482 .children()
483 .find(|n| n.kind() == SyntaxKind::FuncDecl)
484 .expect("a FuncDecl child");
485 assert!(func.children().any(|n| n.kind() == SyntaxKind::Block));
486 }
487
488 fn contains_node(node: &ResolvedNode<SyntaxKind>, kind: SyntaxKind) -> bool {
489 node.kind() == kind || node.children().any(|c| contains_node(c, kind))
490 }
491
492 #[test]
493 fn syntax_error_messages_match_godot() {
494 let cases: &[(&str, &str)] = &[
498 (
500 "func t() -> void:\n\tif true\n\t\tpass\n",
501 "Expected \":\" after \"if\" condition.",
502 ),
503 (
505 "func t(b: bool) -> void:\n\tif b:\n\t\tpass\n\telif b\n\t\tpass\n",
506 "Expected \":\" after \"elif\" condition.",
507 ),
508 (
510 "func t(b: bool) -> void:\n\tif b:\n\t\tpass\n\telse\n\t\tpass\n",
511 "Expected \":\" after \"else\".",
512 ),
513 (
515 "func t() -> void:\n\twhile false\n\t\tpass\n",
516 "Expected \":\" after \"while\" condition.",
517 ),
518 (
520 "func t() -> void:\n\tfor i in 3\n\t\tpass\n",
521 "Expected \":\" after \"for\" condition.",
522 ),
523 (
525 "func t() -> void:\n\tfor i 10:\n\t\tpass\n",
526 "Expected \"in\" or \":\" after \"for\" variable name.",
527 ),
528 (
530 "func t(v: int) -> void:\n\tmatch v\n\t\t1:\n\t\t\tpass\n",
531 "Expected \":\" after \"match\" expression.",
532 ),
533 (
535 "func t(v: int) -> void:\n\tmatch v:\n\t\t1\n\t\t\tpass\n",
536 "Expected \":\" or \"when\" after \"match\" patterns.",
537 ),
538 (
540 "class Inner\n\tpass\n",
541 "Expected \":\" after class declaration.",
542 ),
543 (
545 "func t(a: int -> void:\n\tpass\n",
546 "Expected closing \")\" after function parameters.",
547 ),
548 (
550 "func t() -> void:\n\tvar toggle = func(broken: pass\n",
551 "Expected closing \")\" after lambda parameters.",
552 ),
553 (
555 "func t() -> void:\n\tprint(1, 2\n",
556 "Expected closing \")\" after call arguments.",
557 ),
558 (
560 "func t() -> void:\n\tvar x = (1 + 2\n\tprint(x)\n",
561 "Expected closing \")\" after grouping expression.",
562 ),
563 (
565 "@\nfunc t() -> void:\n\tpass\n",
566 "Expected annotation identifier after \"@\".",
567 ),
568 (
570 "signal\n\nfunc t() -> void:\n\tpass\n",
571 "Expected signal name after \"signal\".",
572 ),
573 ("func t() -> void\n\tpass\n", "Expected \":\"."),
577 (
579 "5\n\nfunc t() -> void:\n\tpass\n",
580 "Unexpected \"Literal\" in class body.",
581 ),
582 ];
583 for (src, expected) in cases {
584 let errors = parse(src).errors().to_vec();
585 assert!(
586 errors.iter().any(|e| e.message == *expected),
587 "source {src:?}\nexpected {expected:?}\ngot {errors:#?}"
588 );
589 }
590 let errors = parse("func t() -> void:\n\tvar x = [1, 2\n")
592 .errors()
593 .to_vec();
594 assert!(
595 errors.iter().any(|e| e.message == "Expected \"]\"."),
596 "got {errors:#?}"
597 );
598 }
599
600 #[test]
601 fn property_accessors_parse_cleanly() {
602 let indented = "var x: int:\n\tget:\n\t\treturn 1\n\tset(v):\n\t\tx = v\n";
603 round_trips(indented);
604 assert!(
605 parse(indented).errors().is_empty(),
606 "valid get/set accessors must not error: {:?}",
607 parse(indented).errors()
608 );
609 let inline = "var y: int : get = _get_y, set = _set_y\n";
610 round_trips(inline);
611 assert!(
612 parse(inline).errors().is_empty(),
613 "{:?}",
614 parse(inline).errors()
615 );
616 }
617
618 #[test]
619 fn a_non_get_set_accessor_keyword_is_a_parse_error() {
620 let src = "var x: int:\n\tfoo:\n\t\treturn 1\n";
623 assert!(
624 !parse(src).errors().is_empty(),
625 "a non-get/set accessor must be a parse error"
626 );
627 }
628
629 fn has_match_stmt(src: &str) -> bool {
630 contains_node(&parse(src).syntax_node(), SyntaxKind::MatchStmt)
631 }
632
633 #[test]
634 fn a_real_match_statement_still_parses() {
635 let src = "func f():\n\tmatch x:\n\t\t1:\n\t\t\tpass\n";
636 round_trips(src);
637 assert!(has_match_stmt(src));
638 }
639
640 #[test]
641 fn a_parenthesised_match_subject_is_still_a_statement() {
642 let src = "func f():\n\tmatch (x):\n\t\t_:\n\t\t\tpass\n";
643 round_trips(src);
644 assert!(has_match_stmt(src), "`match (x):` is a match statement");
645 }
646
647 #[test]
648 fn match_used_as_an_identifier_is_an_expression_not_a_statement() {
649 for src in [
652 "func f():\n\tmatch.foo()\n",
653 "func f():\n\tmatch = 5\n",
654 "func f():\n\tmatch += 1\n",
655 "func f():\n\tmatch(x)\n",
656 "func f():\n\tmatch[0] = 1\n",
657 ] {
658 round_trips(src);
659 assert!(
660 !has_match_stmt(src),
661 "should be an expression, not a match statement: {src:?}"
662 );
663 }
664 }
665
666 fn node_sexpr(node: &ResolvedNode<SyntaxKind>) -> String {
669 let mut s = format!("({:?}", node.kind());
670 for child in node.children() {
671 s.push(' ');
672 s.push_str(&node_sexpr(child));
673 }
674 s.push(')');
675 s
676 }
677
678 fn structure(src: &str) -> String {
679 node_sexpr(&parse(src).syntax_node())
680 }
681
682 #[test]
683 fn precedence_factor_binds_tighter_than_add() {
684 assert_eq!(
686 structure("var x = 1 + 2 * 3\n"),
687 "(SourceFile (VarDecl (Name) (BinExpr (Literal) (BinExpr (Literal) (Literal)))))"
688 );
689 assert_eq!(
691 structure("var x = 1 * 2 + 3\n"),
692 "(SourceFile (VarDecl (Name) (BinExpr (BinExpr (Literal) (Literal)) (Literal))))"
693 );
694 }
695
696 #[test]
697 fn power_is_left_associative() {
698 assert_eq!(
700 structure("var x = 2 ** 3 ** 4\n"),
701 "(SourceFile (VarDecl (Name) (BinExpr (BinExpr (Literal) (Literal)) (Literal))))"
702 );
703 }
704
705 #[test]
706 fn unary_minus_then_power() {
707 assert_eq!(
709 structure("var x = -2 ** 2\n"),
710 "(SourceFile (VarDecl (Name) (UnaryExpr (BinExpr (Literal) (Literal)))))"
711 );
712 }
713
714 #[test]
715 fn ternary_is_right_associative() {
716 assert_eq!(
717 structure("var x = a if c else b\n"),
718 "(SourceFile (VarDecl (Name) (TernaryExpr (NameRef) (NameRef) (NameRef))))"
719 );
720 }
721
722 #[test]
723 fn postfix_chain_call_field_index() {
724 assert_eq!(
726 structure("var x = a.b().c[0]\n"),
727 "(SourceFile (VarDecl (Name) (IndexExpr (FieldExpr (CallExpr (FieldExpr (NameRef) \
728 (NameRef)) (ArgList)) (NameRef)) (Literal))))"
729 );
730 }
731
732 #[test]
733 fn leading_utf8_bom_is_trivia_not_an_error() {
734 let src = "\u{feff}class_name Foo\nextends Node\n";
737 let parse = parse(src);
738 assert_eq!(
739 parse.syntax_node().to_string(),
740 src,
741 "BOM file must round-trip byte-for-byte"
742 );
743 assert!(
744 parse.errors().is_empty(),
745 "BOM-prefixed file should parse clean: {:?}",
746 parse.errors()
747 );
748 assert!(
750 structure(src).starts_with("(SourceFile (ClassNameDecl"),
751 "{}",
752 structure(src)
753 );
754 }
755
756 #[test]
757 fn multiline_lambda_does_not_absorb_following_paren_line() {
758 let src = "func f():\n\tvar cb := func():\n\t\treturn 1\n\t(self).process()\n";
763 let st = structure(src);
764 assert!(
765 st.contains("(VarDecl (Name) (LambdaExpr"),
766 "lambda should be the var initializer, standalone: {st}"
767 );
768 assert!(
769 !st.contains("CallExpr (LambdaExpr"),
770 "the following `(` line must not be absorbed as a call on the lambda: {st}"
771 );
772 assert!(
774 st.contains("(ExprStmt (CallExpr (FieldExpr (ParenExpr"),
775 "the `(self).process()` line should be its own statement: {st}"
776 );
777 round_trips(src);
778 }
779
780 #[test]
781 fn inline_lambda_still_chains_postfix() {
782 let src = "var x = (func(): return 1).call()\n";
785 let st = structure(src);
786 assert!(
787 st.contains("CallExpr (FieldExpr (ParenExpr (LambdaExpr"),
788 "inline lambda should still accept a postfix chain: {st}"
789 );
790 round_trips(src);
791 }
792
793 #[test]
794 fn statement_level_annotation_in_a_body_parses_clean() {
795 let src = "func f():\n\t@warning_ignore(\"integer_division\")\n\tvar x := 1 / 2\n";
799 let parse = parse(src);
800 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
801 round_trips(src);
802 }
803
804 #[test]
805 fn multiline_lambda_arg_with_dedented_closer_parses_clean() {
806 let src = "func f():\n\tobj.call(func():\n\t\t\tbody()\n\t\t)\n";
810 let parse = parse(src);
811 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
812 round_trips(src);
813 }
814
815 #[test]
816 fn multiline_lambda_body_ending_at_a_comma_parses_clean() {
817 let src = "func f():\n\tobj.call(\n\t\tfunc(v):\n\t\t\tuse(v), 0.0, 1.0)\n";
821 let parse = parse(src);
822 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
823 round_trips(src);
824 }
825
826 const CORPUS: &str = r#"@tool
829class_name Player extends CharacterBody2D
830## A documented player controller.
831
832const SPEED := 300.0
833@export var health: int = 100
834@export_range(0, 100) var armor := 0
835static var instances: Array[Player] = []
836
837enum State { IDLE, RUNNING, JUMPING = 10 }
838
839signal died(reason: String)
840
841var _vel: Vector2 = Vector2.ZERO:
842 get:
843 return _vel
844 set(value):
845 _vel = value
846
847class Inner extends RefCounted:
848 var x = 1
849 func helper() -> int:
850 return x * 2
851
852func _ready() -> void:
853 var node := $Sprite2D
854 var unique = %HealthBar
855 add_child(preload("res://thing.tscn").instantiate())
856 for i in range(0, 10):
857 if i % 2 == 0 and i > 0:
858 print(i, " even")
859 elif i == 5:
860 continue
861 else:
862 pass
863 while health > 0:
864 health -= 1
865 match State.IDLE:
866 State.IDLE, State.RUNNING:
867 pass
868 [var first, ..]:
869 print(first)
870 {"key": var v} when v > 0:
871 print(v)
872 _:
873 breakpoint
874 var cb := func(a: int, b := 2) -> int: return a + b
875 var ok = node is Node2D
876 var cast = node as Sprite2D
877 assert(health >= 0, "negative health")
878 died.emit("test")
879"#;
880
881 #[test]
882 fn corpus_round_trips_byte_for_byte() {
883 round_trips(CORPUS);
884 }
885
886 #[test]
887 fn corpus_parses_without_unexpected_errors() {
888 let parse = parse(CORPUS);
890 assert!(
891 parse.errors().is_empty(),
892 "unexpected parse errors:\n{:#?}",
893 parse.errors()
894 );
895 }
896
897 #[test]
898 fn inline_if_elif_else_clauses_attach() {
899 let src = "func f():\n\tif a: x = 1\n\telif b: x = 2\n\telse: x = 3\n";
904 let parse = parse(src);
905 assert_eq!(parse.syntax_node().to_string(), src, "lossless");
906 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
907 let root = parse.syntax_node();
908 let if_stmt = root
909 .descendants()
910 .find(|n| n.kind() == SyntaxKind::IfStmt)
911 .expect("an IfStmt node");
912 assert!(
913 if_stmt
914 .descendants()
915 .any(|n| n.kind() == SyntaxKind::ElifClause),
916 "elif clause attached to the if"
917 );
918 assert!(
919 if_stmt
920 .descendants()
921 .any(|n| n.kind() == SyntaxKind::ElseClause),
922 "else clause attached to the if"
923 );
924 }
925
926 #[test]
927 fn soft_keyword_names_parse() {
928 let src = "static func match(when: bool) -> int:\n\tvar r = RUIRouteMatcher.match(when)\n\treturn when\n";
932 let parse = parse(src);
933 assert_eq!(parse.syntax_node().to_string(), src, "lossless");
934 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
935 }
936
937 #[test]
938 fn multiline_lambda_with_trailing_call_paren() {
939 let src = "func f():\n\tt.connect(func():\n\t\tif ok:\n\t\t\tp.free())\n";
942 let parse = parse(src);
943 assert_eq!(parse.syntax_node().to_string(), src, "lossless");
944 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
945 }
946
947 #[test]
948 fn multiline_lambda_in_call_argument_parses() {
949 let src = "func f():\n\tcb(func(a, b):\n\t\treturn a + b\n\t)\n";
951 let parse = parse(src);
952 assert_eq!(parse.syntax_node().to_string(), src, "lossless");
953 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
954 let root = parse.syntax_node();
955 let lambda = root
956 .descendants()
957 .find(|n| n.kind() == SyntaxKind::LambdaExpr)
958 .expect("a LambdaExpr node");
959 let block = lambda
960 .children()
961 .find(|n| n.kind() == SyntaxKind::Block)
962 .expect("the lambda body Block");
963 assert!(
964 block
965 .descendants()
966 .any(|n| n.kind() == SyntaxKind::ReturnStmt),
967 "the lambda body contains the return statement"
968 );
969 }
970
971 #[test]
972 fn single_line_lambda_in_call_argument_parses() {
973 let src = "var m = arr.map(func(x): x * 2)\n";
975 let parse = parse(src);
976 assert_eq!(parse.syntax_node().to_string(), src, "lossless");
977 assert!(parse.errors().is_empty(), "no errors: {:?}", parse.errors());
978 assert!(
979 parse
980 .syntax_node()
981 .descendants()
982 .any(|n| n.kind() == SyntaxKind::LambdaExpr),
983 "a LambdaExpr node"
984 );
985 }
986
987 #[test]
988 fn broken_code_recovers_and_round_trips() {
989 let src = "func ok():\n\tpass\nfunc bad(:\n\tpass\nfunc also_ok():\n\tpass\n";
992 let parse = parse(src);
993 assert_eq!(
994 parse.syntax_node().to_string(),
995 src,
996 "recovery must stay lossless"
997 );
998 assert!(!parse.errors().is_empty(), "expected a syntax error");
999 let funcs = parse
1001 .syntax_node()
1002 .children()
1003 .filter(|n| n.kind() == SyntaxKind::FuncDecl)
1004 .count();
1005 assert!(
1006 funcs >= 2,
1007 "siblings should survive a broken declaration, got {funcs}"
1008 );
1009 }
1010
1011 #[test]
1012 fn over_indented_body_line_does_not_cascade_to_class_level() {
1013 let src =
1020 "func render():\n\tvar a = 1\n\t\tvar bad = 2\n\tvar b = 2\n\tfor i in 3:\n\t\tpass\n";
1021 let parse = parse(src);
1022 assert_eq!(
1023 parse.syntax_node().to_string(),
1024 src,
1025 "recovery stays lossless"
1026 );
1027 assert_eq!(
1028 parse.errors().len(),
1029 1,
1030 "exactly one diagnostic, not a cascade: {:?}",
1031 parse.errors()
1032 );
1033 let root = parse.syntax_node();
1035 let top_funcs = root
1036 .children()
1037 .filter(|n| n.kind() == SyntaxKind::FuncDecl)
1038 .count();
1039 assert_eq!(top_funcs, 1, "the whole body stays in one function");
1040 assert!(
1041 !root.children().any(|n| n.kind() == SyntaxKind::VarDecl),
1042 "no body statement leaked to class level: {}",
1043 parse.debug_tree()
1044 );
1045 assert!(
1047 root.descendants().any(|n| n.kind() == SyntaxKind::ForStmt),
1048 "the trailing `for` is recovered as a body statement"
1049 );
1050 }
1051
1052 #[test]
1053 fn golden_small_class() {
1054 let parse = parse("class_name Foo\nvar x := 1\n");
1055 expect_test::expect_file!["../test_data/golden/small_class.cst"]
1056 .assert_eq(&parse.debug_tree());
1057 }
1058}