1use gdscript_base::TextRange;
12use gdscript_syntax::ast::{self, AstNode};
13use gdscript_syntax::{GdNode, SyntaxKind};
14use smol_str::SmolStr;
15
16use crate::cst::{self, AstPtr};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct ExprId(pub u32);
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct StmtId(pub u32);
25
26pub type Block = Vec<StmtId>;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Literal {
32 Int,
34 Float,
36 Bool(bool),
38 Str,
40 StringName,
42 NodePath,
44 Null,
46 MathConst,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum BinOp {
53 Add,
55 Sub,
57 Mul,
59 Div,
61 Mod,
63 Pow,
65 Eq,
67 Ne,
69 Lt,
71 Gt,
73 Le,
75 Ge,
77 And,
79 Or,
81 BitAnd,
83 BitOr,
85 BitXor,
87 Shl,
89 Shr,
91 Assign,
93}
94
95impl BinOp {
96 #[must_use]
99 pub fn from_token(kind: SyntaxKind) -> Option<Self> {
100 use SyntaxKind as K;
101 Some(match kind {
102 K::Plus => Self::Add,
103 K::Minus => Self::Sub,
104 K::Star => Self::Mul,
105 K::Slash => Self::Div,
106 K::Percent => Self::Mod,
107 K::StarStar => Self::Pow,
108 K::EqEq => Self::Eq,
109 K::Neq => Self::Ne,
110 K::Lt => Self::Lt,
111 K::Gt => Self::Gt,
112 K::Le => Self::Le,
113 K::Ge => Self::Ge,
114 K::AndKw | K::AmpAmp => Self::And,
115 K::OrKw | K::PipePipe => Self::Or,
116 K::Amp => Self::BitAnd,
117 K::Pipe => Self::BitOr,
118 K::Caret => Self::BitXor,
119 K::Shl => Self::Shl,
120 K::Shr => Self::Shr,
121 K::Eq
122 | K::PlusEq
123 | K::MinusEq
124 | K::StarEq
125 | K::SlashEq
126 | K::PercentEq
127 | K::StarStarEq
128 | K::AmpEq
129 | K::PipeEq
130 | K::CaretEq
131 | K::ShlEq
132 | K::ShrEq => Self::Assign,
133 _ => return None,
134 })
135 }
136
137 #[must_use]
139 pub fn is_arithmetic(self) -> bool {
140 matches!(
141 self,
142 Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod | Self::Pow
143 )
144 }
145
146 #[must_use]
148 pub fn is_boolean(self) -> bool {
149 matches!(
150 self,
151 Self::Eq | Self::Ne | Self::Lt | Self::Gt | Self::Le | Self::Ge | Self::And | Self::Or
152 )
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum UnOp {
159 Neg,
161 Pos,
163 Not,
165 BitNot,
167}
168
169impl UnOp {
170 #[must_use]
172 pub fn from_token(kind: SyntaxKind) -> Option<Self> {
173 Some(match kind {
174 SyntaxKind::Minus => Self::Neg,
175 SyntaxKind::Plus => Self::Pos,
176 SyntaxKind::NotKw | SyntaxKind::Bang => Self::Not,
177 SyntaxKind::Tilde => Self::BitNot,
178 _ => return None,
179 })
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum Expr {
186 Missing,
188 Literal(Literal),
190 Name(SmolStr),
192 SelfExpr,
194 Super,
196 Bin {
198 op: BinOp,
200 lhs: ExprId,
202 rhs: ExprId,
204 },
205 Unary {
207 op: UnOp,
209 operand: ExprId,
211 },
212 Ternary {
214 cond: ExprId,
216 then_branch: ExprId,
218 else_branch: ExprId,
220 },
221 Call {
223 callee: ExprId,
225 args: Vec<ExprId>,
227 },
228 Field {
230 receiver: ExprId,
232 name: SmolStr,
234 name_range: TextRange,
236 },
237 Index {
239 base: ExprId,
241 index: ExprId,
243 },
244 Is {
246 operand: ExprId,
248 ty: Option<AstPtr>,
250 negated: bool,
252 },
253 Cast {
255 operand: ExprId,
257 ty: Option<AstPtr>,
259 },
260 In {
262 lhs: ExprId,
264 rhs: ExprId,
266 negated: bool,
268 },
269 Await(ExprId),
271 Array(Vec<ExprId>),
273 Dict(Vec<(ExprId, Option<ExprId>)>),
275 Lambda {
277 params: Vec<ParamBinding>,
279 body: Block,
281 },
282 Preload {
287 arg: Option<ExprId>,
289 path: Option<SmolStr>,
291 },
292 GetNode {
296 path: Option<SmolStr>,
298 unique: bool,
300 },
301 Paren(ExprId),
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct ParamBinding {
308 pub name: SmolStr,
310 pub type_ref: Option<AstPtr>,
312 pub default: Option<ExprId>,
314 pub name_range: TextRange,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct LocalVar {
321 pub name: SmolStr,
323 pub type_ref: Option<AstPtr>,
325 pub init: Option<ExprId>,
327 pub is_inferred: bool,
329 pub is_const: bool,
331 pub name_range: TextRange,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct ForLoop {
338 pub var: SmolStr,
340 pub var_type: Option<AstPtr>,
342 pub var_range: TextRange,
344 pub iter: ExprId,
346 pub body: Block,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct MatchBind {
353 pub name: SmolStr,
355 pub range: TextRange,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct MatchArm {
362 pub binds: Vec<MatchBind>,
364 pub guard: Option<ExprId>,
366 pub body: Block,
368 pub range: TextRange,
370 pub is_catch_all: bool,
373}
374
375fn arm_is_unconditional_catch_all(arm: &GdNode) -> bool {
381 use SyntaxKind as K;
382 if cst::first_child(arm, |k| k == K::PatternGuard).is_some() {
383 return false;
384 }
385 let patterns: Vec<&GdNode> = arm
386 .children()
387 .filter(|c| {
388 matches!(
389 c.kind(),
390 K::PatternBind
391 | K::PatternLiteral
392 | K::PatternWildcard
393 | K::PatternArray
394 | K::PatternDict
395 | K::PatternRest
396 )
397 })
398 .collect();
399 let [only] = patterns.as_slice() else {
400 return false;
401 };
402 match only.kind() {
403 K::PatternBind | K::PatternWildcard => true,
404 K::PatternLiteral => cst::first_child_expr(only)
407 .and_then(|e| cst::first_token(&e))
408 .is_some_and(|t| t.text() == "_"),
409 _ => false,
410 }
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415pub enum Stmt {
416 Expr(ExprId),
418 Var(LocalVar),
420 Return(Option<ExprId>),
422 If {
424 cond: ExprId,
426 then_branch: Block,
428 elifs: Vec<(ExprId, Block)>,
430 else_branch: Option<Block>,
432 },
433 While {
435 cond: ExprId,
437 body: Block,
439 },
440 For(ForLoop),
442 Match {
444 scrutinee: ExprId,
446 arms: Vec<MatchArm>,
448 },
449 Break,
451 Continue,
453 Pass,
455 Assert(Option<ExprId>),
457}
458
459#[derive(Debug, Clone, Default, PartialEq, Eq)]
462pub struct BodySourceMap {
463 expr_ranges: Vec<TextRange>,
464 stmt_ranges: Vec<TextRange>,
465}
466
467impl BodySourceMap {
468 #[must_use]
470 pub fn expr_range(&self, id: ExprId) -> TextRange {
471 self.expr_ranges[id.0 as usize]
472 }
473
474 #[must_use]
476 pub fn stmt_range(&self, id: StmtId) -> TextRange {
477 self.stmt_ranges[id.0 as usize]
478 }
479
480 #[must_use]
482 pub fn expr_at_offset(&self, offset: u32) -> Option<ExprId> {
483 self.expr_ranges
484 .iter()
485 .enumerate()
486 .filter(|(_, r)| r.start <= offset && offset < r.end)
487 .min_by_key(|(_, r)| r.end - r.start)
488 .map(|(i, _)| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
489 }
490
491 #[must_use]
494 pub fn expr_for_range(&self, range: TextRange) -> Option<ExprId> {
495 self.expr_ranges
496 .iter()
497 .position(|r| *r == range)
498 .map(|i| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
499 }
500}
501
502#[derive(Debug, Clone, Default, PartialEq, Eq)]
504pub struct Body {
505 pub exprs: Vec<Expr>,
507 pub stmts: Vec<Stmt>,
509 pub params: Vec<ParamBinding>,
511 pub block: Block,
513 pub tail: Option<ExprId>,
515 pub source_map: BodySourceMap,
517}
518
519impl Body {
520 #[must_use]
522 pub fn expr(&self, id: ExprId) -> &Expr {
523 &self.exprs[id.0 as usize]
524 }
525
526 #[must_use]
528 pub fn stmt(&self, id: StmtId) -> &Stmt {
529 &self.stmts[id.0 as usize]
530 }
531}
532
533#[must_use]
535pub fn body_of_func(func: &GdNode) -> Body {
536 let mut low = Lowerer::default();
537 let decl = ast::FuncDecl::cast(func.clone());
538 let params = decl
539 .as_ref()
540 .and_then(ast::FuncDecl::param_list)
541 .map(|pl| low.lower_params(pl.syntax()))
542 .unwrap_or_default();
543 let block = decl
544 .as_ref()
545 .and_then(ast::FuncDecl::body)
546 .map(|b| low.lower_block(b.syntax()))
547 .unwrap_or_default();
548 low.finish(params, block, None)
549}
550
551#[must_use]
553pub fn body_of_expr(expr: &GdNode) -> Body {
554 let mut low = Lowerer::default();
555 let tail = low.lower_expr(expr);
556 low.finish(Vec::new(), Vec::new(), Some(tail))
557}
558
559#[must_use]
563pub fn body_of_decl_stmt(decl: &GdNode) -> Body {
564 let mut low = Lowerer::default();
565 let block = low.lower_stmt(decl).into_iter().collect();
566 low.finish(Vec::new(), block, None)
567}
568
569#[must_use]
571pub fn body(root: &GdNode, ptr: AstPtr) -> Option<Body> {
572 let node = ptr.to_node(root)?;
573 Some(body_of_func(&node))
574}
575
576#[derive(Default)]
577struct Lowerer {
578 exprs: Vec<Expr>,
579 stmts: Vec<Stmt>,
580 expr_ranges: Vec<TextRange>,
581 stmt_ranges: Vec<TextRange>,
582}
583
584impl Lowerer {
585 fn finish(self, params: Vec<ParamBinding>, block: Block, tail: Option<ExprId>) -> Body {
586 Body {
587 exprs: self.exprs,
588 stmts: self.stmts,
589 params,
590 block,
591 tail,
592 source_map: BodySourceMap {
593 expr_ranges: self.expr_ranges,
594 stmt_ranges: self.stmt_ranges,
595 },
596 }
597 }
598
599 fn alloc_expr(&mut self, expr: Expr, range: TextRange) -> ExprId {
600 let id = ExprId(u32::try_from(self.exprs.len()).unwrap_or(u32::MAX));
601 self.exprs.push(expr);
602 self.expr_ranges.push(range);
603 id
604 }
605
606 fn alloc_stmt(&mut self, stmt: Stmt, range: TextRange) -> StmtId {
607 let id = StmtId(u32::try_from(self.stmts.len()).unwrap_or(u32::MAX));
608 self.stmts.push(stmt);
609 self.stmt_ranges.push(range);
610 id
611 }
612
613 fn missing(&mut self, range: TextRange) -> ExprId {
614 self.alloc_expr(Expr::Missing, range)
615 }
616
617 fn lower_first_expr(&mut self, node: &GdNode) -> ExprId {
619 match cst::first_child_expr(node) {
620 Some(c) => self.lower_expr(&c),
621 None => self.missing(cst::text_range_of(node)),
622 }
623 }
624
625 #[allow(clippy::too_many_lines)]
626 fn lower_expr(&mut self, node: &GdNode) -> ExprId {
627 use SyntaxKind as K;
628 let range = cst::text_range_of(node);
629 let expr = match node.kind() {
630 K::Literal => Expr::Literal(literal_kind(node)),
631 K::NameRef => return self.lower_name_ref(node),
632 K::ParenExpr => Expr::Paren(self.lower_first_expr(node)),
633 K::BinExpr => {
634 let exprs = cst::child_exprs(node);
635 let op = bin_op(node).unwrap_or(BinOp::Add);
636 if op == BinOp::Assign
642 && let Some(under) = compound_assign_op(node)
643 {
644 let lhs = self.lower_or_missing(exprs.first(), range);
645 let lhs_read = self.lower_or_missing(exprs.first(), range);
646 let rhs = self.lower_or_missing(exprs.get(1), range);
647 let value = self.alloc_expr(
648 Expr::Bin {
649 op: under,
650 lhs: lhs_read,
651 rhs,
652 },
653 range,
654 );
655 Expr::Bin {
656 op: BinOp::Assign,
657 lhs,
658 rhs: value,
659 }
660 } else {
661 let lhs = self.lower_or_missing(exprs.first(), range);
662 let rhs = self.lower_or_missing(exprs.get(1), range);
663 Expr::Bin { op, lhs, rhs }
664 }
665 }
666 K::UnaryExpr => {
667 let op = un_op(node).unwrap_or(UnOp::Pos);
668 let operand = self.lower_first_expr(node);
669 Expr::Unary { op, operand }
670 }
671 K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
672 K::TernaryExpr => {
673 let exprs = cst::child_exprs(node);
674 let then_branch = self.lower_or_missing(exprs.first(), range);
675 let cond = self.lower_or_missing(exprs.get(1), range);
676 let else_branch = self.lower_or_missing(exprs.get(2), range);
677 Expr::Ternary {
678 cond,
679 then_branch,
680 else_branch,
681 }
682 }
683 K::CallExpr => {
684 if let Some(path) = get_node_call_path(node) {
686 Expr::GetNode {
687 path: Some(path),
688 unique: false,
689 }
690 } else {
691 let callee = self.lower_first_expr(node);
692 let args = cst::first_child(node, |k| k == K::ArgList)
693 .map(|al| self.lower_exprs(&al))
694 .unwrap_or_default();
695 Expr::Call { callee, args }
696 }
697 }
698 K::IndexExpr => {
699 let exprs = cst::child_exprs(node);
700 let base = self.lower_or_missing(exprs.first(), range);
701 let index = self.lower_or_missing(exprs.get(1), range);
702 Expr::Index { base, index }
703 }
704 K::FieldExpr => {
705 let receiver = self.lower_first_expr(node);
706 let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
707 Expr::Field {
708 receiver,
709 name,
710 name_range,
711 }
712 }
713 K::IsExpr => {
714 let operand = self.lower_first_expr(node);
715 Expr::Is {
716 operand,
717 ty: type_ref_ptr(node),
718 negated: cst::has_token(node, K::NotKw),
719 }
720 }
721 K::CastExpr => {
722 let operand = self.lower_first_expr(node);
723 Expr::Cast {
724 operand,
725 ty: type_ref_ptr(node),
726 }
727 }
728 K::InExpr => {
729 let exprs = cst::child_exprs(node);
730 let lhs = self.lower_or_missing(exprs.first(), range);
731 let rhs = self.lower_or_missing(exprs.get(1), range);
732 Expr::In {
733 lhs,
734 rhs,
735 negated: cst::has_token(node, K::NotKw),
736 }
737 }
738 K::ArrayLit => Expr::Array(self.lower_exprs(node)),
739 K::DictLit => {
740 let entries = cst::children_of(node, K::DictEntry)
741 .iter()
742 .map(|e| {
743 let kv = cst::child_exprs(e);
744 let key = self.lower_or_missing(kv.first(), cst::text_range_of(e));
745 let value = kv.get(1).map(|v| self.lower_expr(v));
746 (key, value)
747 })
748 .collect();
749 Expr::Dict(entries)
750 }
751 K::LambdaExpr => {
752 let params = cst::first_child(node, |k| k == K::ParamList)
753 .map(|pl| self.lower_params(&pl))
754 .unwrap_or_default();
755 let body = cst::first_child(node, |k| k == K::Block)
756 .map(|b| self.lower_block(&b))
757 .unwrap_or_default();
758 Expr::Lambda { params, body }
759 }
760 K::PreloadExpr => {
761 let arg_node = cst::first_child(node, |k| k == K::ArgList)
762 .and_then(|al| cst::first_child_expr(&al));
763 let path = arg_node
766 .as_ref()
767 .filter(|n| n.kind() == K::Literal)
768 .and_then(|n| cst::child_token_text(n, K::String))
769 .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
770 let arg = arg_node.map(|e| self.lower_expr(&e));
771 Expr::Preload { arg, path }
772 }
773 K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
774 path: node_path_text(node),
775 unique: node.kind() == K::UniqueNodeExpr,
776 },
777 _ => Expr::Missing,
778 };
779 self.alloc_expr(expr, range)
780 }
781
782 fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
783 let range = cst::text_range_of(node);
784 let expr = match cst::first_token(node) {
785 Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
786 Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
787 Some(t) => Expr::Name(SmolStr::new(t.text())),
788 None => Expr::Missing,
789 };
790 self.alloc_expr(expr, range)
791 }
792
793 fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
794 match node {
795 Some(n) => self.lower_expr(n),
796 None => self.missing(fallback),
797 }
798 }
799
800 fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
801 cst::child_exprs(node)
802 .iter()
803 .map(|c| self.lower_expr(c))
804 .collect()
805 }
806
807 fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
808 cst::children_of(param_list, SyntaxKind::Param)
809 .iter()
810 .filter_map(|p| {
811 let name_tok = ast::Param::cast(p.clone())?.name()?;
812 let name_node = name_tok.syntax();
813 Some(ParamBinding {
814 name: SmolStr::new(name_tok.text()?),
815 type_ref: type_ref_ptr(p),
816 default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
817 name_range: cst::text_range_of(name_node),
818 })
819 })
820 .collect()
821 }
822
823 fn lower_block(&mut self, block: &GdNode) -> Block {
824 block
825 .children()
826 .filter_map(|c| self.lower_stmt(c))
827 .collect()
828 }
829
830 fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
831 use SyntaxKind as K;
832 let range = cst::text_range_of(node);
833 let stmt = match node.kind() {
834 K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
835 K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
836 K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
837 K::IfStmt => self.lower_if(node),
838 K::WhileStmt => Stmt::While {
839 cond: self.lower_first_expr(node),
840 body: self.lower_child_block(node),
841 },
842 K::ForStmt => Stmt::For(self.lower_for(node)),
843 K::MatchStmt => self.lower_match(node),
844 K::BreakStmt => Stmt::Break,
845 K::ContinueStmt => Stmt::Continue,
846 K::PassStmt | K::BreakpointStmt => Stmt::Pass,
847 K::AssertStmt => Stmt::Assert(
848 cst::first_child(node, |k| k == K::ArgList)
849 .and_then(|al| cst::first_child_expr(&al))
850 .map(|e| self.lower_expr(&e)),
851 ),
852 _ => return None,
854 };
855 Some(self.alloc_stmt(stmt, range))
856 }
857
858 fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
859 let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
860 let name = name_node
861 .as_ref()
862 .and_then(|n| ast::Name::cast(n.clone()))
863 .and_then(|n| n.text())
864 .map(SmolStr::new)
865 .unwrap_or_default();
866 LocalVar {
867 name,
868 type_ref: type_ref_ptr(node),
869 init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
870 is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
871 is_const: node.kind() == SyntaxKind::ConstDecl,
872 name_range: name_node
873 .as_ref()
874 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
875 }
876 }
877
878 fn lower_if(&mut self, node: &GdNode) -> Stmt {
879 let cond = self.lower_first_expr(node);
880 let then_branch = self.lower_child_block(node);
881 let elifs = cst::children_of(node, SyntaxKind::ElifClause)
882 .iter()
883 .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
884 .collect();
885 let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
886 .map(|c| self.lower_child_block(&c));
887 Stmt::If {
888 cond,
889 then_branch,
890 elifs,
891 else_branch,
892 }
893 }
894
895 fn lower_for(&mut self, node: &GdNode) -> ForLoop {
896 let name = cst::first_child(node, |k| k == SyntaxKind::Name);
897 let var = name
898 .as_ref()
899 .and_then(|n| ast::Name::cast(n.clone()))
900 .and_then(|n| n.text())
901 .map(SmolStr::new)
902 .unwrap_or_default();
903 ForLoop {
904 var,
905 var_type: type_ref_ptr(node),
906 var_range: name
907 .as_ref()
908 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
909 iter: self.lower_first_expr(node),
910 body: self.lower_child_block(node),
911 }
912 }
913
914 fn lower_match(&mut self, node: &GdNode) -> Stmt {
915 let scrutinee = self.lower_first_expr(node);
916 let arms = cst::children_of(node, SyntaxKind::MatchArm)
917 .iter()
918 .map(|arm| {
919 let binds = cst::children_of(arm, SyntaxKind::PatternBind)
920 .iter()
921 .filter_map(|b| {
922 let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
923 let name = ast::Name::cast(name_node.clone())?
924 .text()
925 .map(SmolStr::new)?;
926 Some(MatchBind {
927 name,
928 range: cst::text_range_of(&name_node),
929 })
930 })
931 .collect();
932 let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
933 .and_then(|g| cst::first_child_expr(&g))
934 .map(|e| self.lower_expr(&e));
935 let body = self.lower_child_block(arm);
936 MatchArm {
937 binds,
938 guard,
939 body,
940 range: cst::text_range_of(arm),
941 is_catch_all: arm_is_unconditional_catch_all(arm),
942 }
943 })
944 .collect();
945 Stmt::Match { scrutinee, arms }
946 }
947
948 fn lower_child_block(&mut self, node: &GdNode) -> Block {
950 cst::first_child(node, |k| k == SyntaxKind::Block)
951 .map(|b| self.lower_block(&b))
952 .unwrap_or_default()
953 }
954}
955
956fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
958 cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
959}
960
961fn literal_kind(node: &GdNode) -> Literal {
963 use SyntaxKind as K;
964 match cst::first_token(node).map(|t| t.kind()) {
965 Some(K::Int) => Literal::Int,
966 Some(K::Float) => Literal::Float,
967 Some(K::String) => Literal::Str,
968 Some(K::StringName) => Literal::StringName,
969 Some(K::NodePath) => Literal::NodePath,
970 Some(K::True) => Literal::Bool(true),
971 Some(K::False) => Literal::Bool(false),
972 Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
973 _ => Literal::Null,
974 }
975}
976
977fn bin_op(node: &GdNode) -> Option<BinOp> {
979 node.children_with_tokens()
980 .filter_map(cstree::util::NodeOrToken::into_token)
981 .find_map(|t| BinOp::from_token(t.kind()))
982}
983
984fn compound_assign_op(node: &GdNode) -> Option<BinOp> {
987 use SyntaxKind as K;
988 node.children_with_tokens()
989 .filter_map(cstree::util::NodeOrToken::into_token)
990 .find_map(|t| {
991 Some(match t.kind() {
992 K::PlusEq => BinOp::Add,
993 K::MinusEq => BinOp::Sub,
994 K::StarEq => BinOp::Mul,
995 K::SlashEq => BinOp::Div,
996 K::PercentEq => BinOp::Mod,
997 K::StarStarEq => BinOp::Pow,
998 K::AmpEq => BinOp::BitAnd,
999 K::PipeEq => BinOp::BitOr,
1000 K::CaretEq => BinOp::BitXor,
1001 K::ShlEq => BinOp::Shl,
1002 K::ShrEq => BinOp::Shr,
1003 _ => return None,
1004 })
1005 })
1006}
1007
1008fn un_op(node: &GdNode) -> Option<UnOp> {
1010 node.children_with_tokens()
1011 .filter_map(cstree::util::NodeOrToken::into_token)
1012 .find_map(|t| UnOp::from_token(t.kind()))
1013}
1014
1015fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
1017 let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
1018 let tok = cst::first_token(&nameref)?;
1019 Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
1020}
1021
1022fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
1026 let callee = cst::first_child_expr(node)?;
1027 let is_get_node = match callee.kind() {
1032 SyntaxKind::NameRef => {
1033 cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
1034 }
1035 SyntaxKind::FieldExpr => {
1036 is_self_receiver(&callee)
1037 && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
1038 }
1039 _ => false,
1040 };
1041 if !is_get_node {
1042 return None;
1043 }
1044 let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
1045 .and_then(|al| cst::first_child_expr(&al))?;
1046 if arg.kind() != SyntaxKind::Literal {
1047 return None; }
1049 let s = cst::child_token_text(&arg, SyntaxKind::String)?;
1050 Some(SmolStr::new(s.trim_matches(['"', '\''])))
1051}
1052
1053fn is_get_node_name(name: &str) -> bool {
1054 matches!(name, "get_node" | "get_node_or_null")
1055}
1056
1057fn is_self_receiver(field_expr: &GdNode) -> bool {
1059 cst::first_child_expr(field_expr).is_some_and(|recv| {
1060 recv.kind() == SyntaxKind::NameRef
1061 && recv
1062 .children_with_tokens()
1063 .filter_map(cstree::util::NodeOrToken::into_token)
1064 .any(|t| t.kind() == SyntaxKind::SelfKw)
1065 })
1066}
1067
1068fn node_path_text(node: &GdNode) -> Option<SmolStr> {
1071 if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
1072 return Some(SmolStr::new(s.trim_matches(['"', '\''])));
1073 }
1074 let segs: Vec<String> = node
1075 .children_with_tokens()
1076 .filter_map(cstree::util::NodeOrToken::into_token)
1077 .filter(|t| t.kind() == SyntaxKind::Ident)
1078 .map(|t| t.text().to_owned())
1079 .collect();
1080 (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
1081}
1082
1083#[cfg(test)]
1084mod tests {
1085 use super::*;
1086 use gdscript_syntax::parse;
1087
1088 fn func_body(src: &str) -> Body {
1089 let root = parse(src).syntax_node();
1090 let func = gdscript_syntax::ast::descendants(&root)
1091 .into_iter()
1092 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1093 .expect("a FuncDecl");
1094 body_of_func(&func)
1095 }
1096
1097 #[test]
1098 fn lowers_params_and_return() {
1099 let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
1100 assert_eq!(body.params.len(), 2);
1101 assert_eq!(body.params[0].name, "a");
1102 assert!(body.params[0].type_ref.is_some());
1103 assert!(body.params[1].default.is_some());
1104 assert_eq!(body.block.len(), 1);
1105 let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
1106 panic!("expected return")
1107 };
1108 assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
1109 }
1110
1111 #[test]
1112 fn lowers_local_var_and_field_and_call() {
1113 let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1114 let Stmt::Var(v) = body.stmt(body.block[0]) else {
1116 panic!("expected var")
1117 };
1118 assert_eq!(v.name, "n");
1119 assert!(v.is_inferred && v.init.is_some());
1120 let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1122 panic!("expected expr stmt")
1123 };
1124 let Expr::Call { callee, .. } = body.expr(*e) else {
1125 panic!("expected call")
1126 };
1127 assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1128 }
1129
1130 #[test]
1131 fn lowers_if_with_is_narrowing() {
1132 let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1133 let Stmt::If {
1134 cond,
1135 then_branch,
1136 else_branch,
1137 ..
1138 } = body.stmt(body.block[0])
1139 else {
1140 panic!("expected if")
1141 };
1142 assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1143 assert_eq!(then_branch.len(), 1);
1144 assert!(else_branch.is_some());
1145 }
1146
1147 #[test]
1148 fn source_map_finds_tightest_expr() {
1149 let body = func_body("func f(a, b):\n\treturn a + b\n");
1151 let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1152 let id = body
1153 .source_map
1154 .expr_at_offset(b_offset)
1155 .expect("an expr at b");
1156 assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1157 }
1158
1159 #[test]
1160 fn initializer_body_has_tail() {
1161 let root = parse("var x = 1 + 2\n").syntax_node();
1162 let var = gdscript_syntax::ast::descendants(&root)
1163 .into_iter()
1164 .find(|n| n.kind() == SyntaxKind::VarDecl)
1165 .unwrap();
1166 let init = crate::cst::first_child_expr(&var).unwrap();
1167 let body = body_of_expr(&init);
1168 assert!(body.tail.is_some());
1169 assert!(matches!(
1170 body.expr(body.tail.unwrap()),
1171 Expr::Bin { op: BinOp::Add, .. }
1172 ));
1173 }
1174}