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(Option<i64>),
35 Float,
37 Bool(bool),
39 Str,
41 StringName,
43 NodePath,
45 Null,
47 MathConst,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum BinOp {
54 Add,
56 Sub,
58 Mul,
60 Div,
62 Mod,
64 Pow,
66 Eq,
68 Ne,
70 Lt,
72 Gt,
74 Le,
76 Ge,
78 And,
80 Or,
82 BitAnd,
84 BitOr,
86 BitXor,
88 Shl,
90 Shr,
92 Assign,
94}
95
96impl BinOp {
97 #[must_use]
100 pub fn from_token(kind: SyntaxKind) -> Option<Self> {
101 use SyntaxKind as K;
102 Some(match kind {
103 K::Plus => Self::Add,
104 K::Minus => Self::Sub,
105 K::Star => Self::Mul,
106 K::Slash => Self::Div,
107 K::Percent => Self::Mod,
108 K::StarStar => Self::Pow,
109 K::EqEq => Self::Eq,
110 K::Neq => Self::Ne,
111 K::Lt => Self::Lt,
112 K::Gt => Self::Gt,
113 K::Le => Self::Le,
114 K::Ge => Self::Ge,
115 K::AndKw | K::AmpAmp => Self::And,
116 K::OrKw | K::PipePipe => Self::Or,
117 K::Amp => Self::BitAnd,
118 K::Pipe => Self::BitOr,
119 K::Caret => Self::BitXor,
120 K::Shl => Self::Shl,
121 K::Shr => Self::Shr,
122 K::Eq
123 | K::PlusEq
124 | K::MinusEq
125 | K::StarEq
126 | K::SlashEq
127 | K::PercentEq
128 | K::StarStarEq
129 | K::AmpEq
130 | K::PipeEq
131 | K::CaretEq
132 | K::ShlEq
133 | K::ShrEq => Self::Assign,
134 _ => return None,
135 })
136 }
137
138 #[must_use]
140 pub fn is_arithmetic(self) -> bool {
141 matches!(
142 self,
143 Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod | Self::Pow
144 )
145 }
146
147 #[must_use]
149 pub fn is_boolean(self) -> bool {
150 matches!(
151 self,
152 Self::Eq | Self::Ne | Self::Lt | Self::Gt | Self::Le | Self::Ge | Self::And | Self::Or
153 )
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum UnOp {
160 Neg,
162 Pos,
164 Not,
166 BitNot,
168}
169
170impl UnOp {
171 #[must_use]
173 pub fn from_token(kind: SyntaxKind) -> Option<Self> {
174 Some(match kind {
175 SyntaxKind::Minus => Self::Neg,
176 SyntaxKind::Plus => Self::Pos,
177 SyntaxKind::NotKw | SyntaxKind::Bang => Self::Not,
178 SyntaxKind::Tilde => Self::BitNot,
179 _ => return None,
180 })
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum Expr {
187 Missing,
189 Literal(Literal),
191 Name(SmolStr),
193 SelfExpr,
195 Super,
197 Bin {
199 op: BinOp,
201 lhs: ExprId,
203 rhs: ExprId,
205 },
206 Unary {
208 op: UnOp,
210 operand: ExprId,
212 },
213 Ternary {
215 cond: ExprId,
217 then_branch: ExprId,
219 else_branch: ExprId,
221 },
222 Call {
224 callee: ExprId,
226 args: Vec<ExprId>,
228 },
229 Field {
231 receiver: ExprId,
233 name: SmolStr,
235 name_range: TextRange,
237 },
238 Index {
240 base: ExprId,
242 index: ExprId,
244 },
245 Is {
247 operand: ExprId,
249 ty: Option<AstPtr>,
251 negated: bool,
253 },
254 Cast {
256 operand: ExprId,
258 ty: Option<AstPtr>,
260 },
261 In {
263 lhs: ExprId,
265 rhs: ExprId,
267 negated: bool,
269 },
270 Await(ExprId),
272 Array(Vec<ExprId>),
274 Dict(Vec<(ExprId, Option<ExprId>)>),
276 Lambda {
278 params: Vec<ParamBinding>,
280 body: Block,
282 },
283 Preload {
288 arg: Option<ExprId>,
290 path: Option<SmolStr>,
292 },
293 GetNode {
297 path: Option<SmolStr>,
299 unique: bool,
301 },
302 Paren(ExprId),
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct ParamBinding {
309 pub name: SmolStr,
311 pub type_ref: Option<AstPtr>,
313 pub default: Option<ExprId>,
315 pub name_range: TextRange,
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct LocalVar {
322 pub name: SmolStr,
324 pub type_ref: Option<AstPtr>,
326 pub init: Option<ExprId>,
328 pub is_inferred: bool,
330 pub is_const: bool,
332 pub name_range: TextRange,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct ForLoop {
339 pub var: SmolStr,
341 pub var_type: Option<AstPtr>,
343 pub var_range: TextRange,
345 pub iter: ExprId,
347 pub body: Block,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
353pub struct MatchBind {
354 pub name: SmolStr,
356 pub range: TextRange,
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct MatchArm {
363 pub binds: Vec<MatchBind>,
365 pub guard: Option<ExprId>,
367 pub body: Block,
369 pub range: TextRange,
371 pub is_catch_all: bool,
374}
375
376fn arm_is_unconditional_catch_all(arm: &GdNode) -> bool {
382 use SyntaxKind as K;
383 if cst::first_child(arm, |k| k == K::PatternGuard).is_some() {
384 return false;
385 }
386 let patterns: Vec<&GdNode> = arm
387 .children()
388 .filter(|c| {
389 matches!(
390 c.kind(),
391 K::PatternBind
392 | K::PatternLiteral
393 | K::PatternWildcard
394 | K::PatternArray
395 | K::PatternDict
396 | K::PatternRest
397 )
398 })
399 .collect();
400 let [only] = patterns.as_slice() else {
401 return false;
402 };
403 match only.kind() {
404 K::PatternBind | K::PatternWildcard => true,
405 K::PatternLiteral => cst::first_child_expr(only)
408 .and_then(|e| cst::first_token(&e))
409 .is_some_and(|t| t.text() == "_"),
410 _ => false,
411 }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
416pub enum Stmt {
417 Expr(ExprId),
419 Var(LocalVar),
421 Return(Option<ExprId>),
423 If {
425 cond: ExprId,
427 then_branch: Block,
429 elifs: Vec<(ExprId, Block)>,
431 else_branch: Option<Block>,
433 },
434 While {
436 cond: ExprId,
438 body: Block,
440 },
441 For(ForLoop),
443 Match {
445 scrutinee: ExprId,
447 arms: Vec<MatchArm>,
449 },
450 Break,
452 Continue,
454 Pass,
456 Assert(Option<ExprId>),
458}
459
460#[derive(Debug, Clone, Default, PartialEq, Eq)]
463pub struct BodySourceMap {
464 expr_ranges: Vec<TextRange>,
465 stmt_ranges: Vec<TextRange>,
466}
467
468impl BodySourceMap {
469 #[must_use]
471 pub fn expr_range(&self, id: ExprId) -> TextRange {
472 self.expr_ranges[id.0 as usize]
473 }
474
475 #[must_use]
477 pub fn stmt_range(&self, id: StmtId) -> TextRange {
478 self.stmt_ranges[id.0 as usize]
479 }
480
481 #[must_use]
483 pub fn expr_at_offset(&self, offset: u32) -> Option<ExprId> {
484 self.expr_ranges
485 .iter()
486 .enumerate()
487 .filter(|(_, r)| r.start <= offset && offset < r.end)
488 .min_by_key(|(_, r)| r.end - r.start)
489 .map(|(i, _)| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
490 }
491
492 #[must_use]
495 pub fn expr_for_range(&self, range: TextRange) -> Option<ExprId> {
496 self.expr_ranges
497 .iter()
498 .position(|r| *r == range)
499 .map(|i| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
500 }
501}
502
503#[derive(Debug, Clone, Default, PartialEq, Eq)]
505pub struct Body {
506 pub exprs: Vec<Expr>,
508 pub stmts: Vec<Stmt>,
510 pub params: Vec<ParamBinding>,
512 pub block: Block,
514 pub tail: Option<ExprId>,
516 pub source_map: BodySourceMap,
518}
519
520impl Body {
521 #[must_use]
523 pub fn expr(&self, id: ExprId) -> &Expr {
524 &self.exprs[id.0 as usize]
525 }
526
527 #[must_use]
529 pub fn stmt(&self, id: StmtId) -> &Stmt {
530 &self.stmts[id.0 as usize]
531 }
532}
533
534#[must_use]
536pub fn body_of_func(func: &GdNode) -> Body {
537 let mut low = Lowerer::default();
538 let decl = ast::FuncDecl::cast(func.clone());
539 let params = decl
540 .as_ref()
541 .and_then(ast::FuncDecl::param_list)
542 .map(|pl| low.lower_params(pl.syntax()))
543 .unwrap_or_default();
544 let block = decl
545 .as_ref()
546 .and_then(ast::FuncDecl::body)
547 .map(|b| low.lower_block(b.syntax()))
548 .unwrap_or_default();
549 low.finish(params, block, None)
550}
551
552#[must_use]
554pub fn body_of_expr(expr: &GdNode) -> Body {
555 let mut low = Lowerer::default();
556 let tail = low.lower_expr(expr);
557 low.finish(Vec::new(), Vec::new(), Some(tail))
558}
559
560#[must_use]
564pub fn body_of_decl_stmt(decl: &GdNode) -> Body {
565 let mut low = Lowerer::default();
566 let block = low.lower_stmt(decl).into_iter().collect();
567 low.finish(Vec::new(), block, None)
568}
569
570#[must_use]
572pub fn body(root: &GdNode, ptr: AstPtr) -> Option<Body> {
573 let node = ptr.to_node(root)?;
574 Some(body_of_func(&node))
575}
576
577#[derive(Default)]
578struct Lowerer {
579 exprs: Vec<Expr>,
580 stmts: Vec<Stmt>,
581 expr_ranges: Vec<TextRange>,
582 stmt_ranges: Vec<TextRange>,
583}
584
585impl Lowerer {
586 fn finish(self, params: Vec<ParamBinding>, block: Block, tail: Option<ExprId>) -> Body {
587 Body {
588 exprs: self.exprs,
589 stmts: self.stmts,
590 params,
591 block,
592 tail,
593 source_map: BodySourceMap {
594 expr_ranges: self.expr_ranges,
595 stmt_ranges: self.stmt_ranges,
596 },
597 }
598 }
599
600 fn alloc_expr(&mut self, expr: Expr, range: TextRange) -> ExprId {
601 let id = ExprId(u32::try_from(self.exprs.len()).unwrap_or(u32::MAX));
602 self.exprs.push(expr);
603 self.expr_ranges.push(range);
604 id
605 }
606
607 fn alloc_stmt(&mut self, stmt: Stmt, range: TextRange) -> StmtId {
608 let id = StmtId(u32::try_from(self.stmts.len()).unwrap_or(u32::MAX));
609 self.stmts.push(stmt);
610 self.stmt_ranges.push(range);
611 id
612 }
613
614 fn missing(&mut self, range: TextRange) -> ExprId {
615 self.alloc_expr(Expr::Missing, range)
616 }
617
618 fn lower_first_expr(&mut self, node: &GdNode) -> ExprId {
620 match cst::first_child_expr(node) {
621 Some(c) => self.lower_expr(&c),
622 None => self.missing(cst::text_range_of(node)),
623 }
624 }
625
626 #[allow(clippy::too_many_lines)]
627 fn lower_expr(&mut self, node: &GdNode) -> ExprId {
628 use SyntaxKind as K;
629 let range = cst::text_range_of(node);
630 let expr = match node.kind() {
631 K::Literal => Expr::Literal(literal_kind(node)),
632 K::NameRef => return self.lower_name_ref(node),
633 K::ParenExpr => Expr::Paren(self.lower_first_expr(node)),
634 K::BinExpr => {
635 let exprs = cst::child_exprs(node);
636 let op = bin_op(node).unwrap_or(BinOp::Add);
637 if op == BinOp::Assign
643 && let Some(under) = compound_assign_op(node)
644 {
645 let lhs = self.lower_or_missing(exprs.first(), range);
646 let lhs_read = self.lower_or_missing(exprs.first(), range);
647 let rhs = self.lower_or_missing(exprs.get(1), range);
648 let value = self.alloc_expr(
649 Expr::Bin {
650 op: under,
651 lhs: lhs_read,
652 rhs,
653 },
654 range,
655 );
656 Expr::Bin {
657 op: BinOp::Assign,
658 lhs,
659 rhs: value,
660 }
661 } else {
662 let lhs = self.lower_or_missing(exprs.first(), range);
663 let rhs = self.lower_or_missing(exprs.get(1), range);
664 Expr::Bin { op, lhs, rhs }
665 }
666 }
667 K::UnaryExpr => {
668 let op = un_op(node).unwrap_or(UnOp::Pos);
669 let operand = self.lower_first_expr(node);
670 Expr::Unary { op, operand }
671 }
672 K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
673 K::TernaryExpr => {
674 let exprs = cst::child_exprs(node);
675 let then_branch = self.lower_or_missing(exprs.first(), range);
676 let cond = self.lower_or_missing(exprs.get(1), range);
677 let else_branch = self.lower_or_missing(exprs.get(2), range);
678 Expr::Ternary {
679 cond,
680 then_branch,
681 else_branch,
682 }
683 }
684 K::CallExpr => {
685 if let Some(path) = get_node_call_path(node) {
687 Expr::GetNode {
688 path: Some(path),
689 unique: false,
690 }
691 } else {
692 let callee = self.lower_first_expr(node);
693 let args = cst::first_child(node, |k| k == K::ArgList)
694 .map(|al| self.lower_exprs(&al))
695 .unwrap_or_default();
696 Expr::Call { callee, args }
697 }
698 }
699 K::IndexExpr => {
700 let exprs = cst::child_exprs(node);
701 let base = self.lower_or_missing(exprs.first(), range);
702 let index = self.lower_or_missing(exprs.get(1), range);
703 Expr::Index { base, index }
704 }
705 K::FieldExpr => {
706 let receiver = self.lower_first_expr(node);
707 let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
708 Expr::Field {
709 receiver,
710 name,
711 name_range,
712 }
713 }
714 K::IsExpr => {
715 let operand = self.lower_first_expr(node);
716 Expr::Is {
717 operand,
718 ty: type_ref_ptr(node),
719 negated: cst::has_token(node, K::NotKw),
720 }
721 }
722 K::CastExpr => {
723 let operand = self.lower_first_expr(node);
724 Expr::Cast {
725 operand,
726 ty: type_ref_ptr(node),
727 }
728 }
729 K::InExpr => {
730 let exprs = cst::child_exprs(node);
731 let lhs = self.lower_or_missing(exprs.first(), range);
732 let rhs = self.lower_or_missing(exprs.get(1), range);
733 Expr::In {
734 lhs,
735 rhs,
736 negated: cst::has_token(node, K::NotKw),
737 }
738 }
739 K::ArrayLit => Expr::Array(self.lower_exprs(node)),
740 K::DictLit => {
741 let entries = cst::children_of(node, K::DictEntry)
742 .iter()
743 .map(|e| {
744 let kv = cst::child_exprs(e);
745 let key = if cst::has_token(e, K::Eq) {
752 let range = kv
753 .first()
754 .map_or_else(|| cst::text_range_of(e), cst::text_range_of);
755 self.alloc_expr(Expr::Literal(Literal::Str), range)
756 } else {
757 self.lower_or_missing(kv.first(), cst::text_range_of(e))
758 };
759 let value = kv.get(1).map(|v| self.lower_expr(v));
760 (key, value)
761 })
762 .collect();
763 Expr::Dict(entries)
764 }
765 K::LambdaExpr => {
766 let params = cst::first_child(node, |k| k == K::ParamList)
767 .map(|pl| self.lower_params(&pl))
768 .unwrap_or_default();
769 let body = cst::first_child(node, |k| k == K::Block)
770 .map(|b| self.lower_block(&b))
771 .unwrap_or_default();
772 Expr::Lambda { params, body }
773 }
774 K::PreloadExpr => {
775 let arg_node = cst::first_child(node, |k| k == K::ArgList)
776 .and_then(|al| cst::first_child_expr(&al));
777 let path = arg_node
780 .as_ref()
781 .filter(|n| n.kind() == K::Literal)
782 .and_then(|n| cst::child_token_text(n, K::String))
783 .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
784 let arg = arg_node.map(|e| self.lower_expr(&e));
785 Expr::Preload { arg, path }
786 }
787 K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
788 path: node_path_text(node),
789 unique: node.kind() == K::UniqueNodeExpr,
790 },
791 _ => Expr::Missing,
792 };
793 self.alloc_expr(expr, range)
794 }
795
796 fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
797 let range = cst::text_range_of(node);
798 let expr = match cst::first_token(node) {
799 Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
800 Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
801 Some(t) => Expr::Name(SmolStr::new(t.text())),
802 None => Expr::Missing,
803 };
804 self.alloc_expr(expr, range)
805 }
806
807 fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
808 match node {
809 Some(n) => self.lower_expr(n),
810 None => self.missing(fallback),
811 }
812 }
813
814 fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
815 cst::child_exprs(node)
816 .iter()
817 .map(|c| self.lower_expr(c))
818 .collect()
819 }
820
821 fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
822 cst::children_of(param_list, SyntaxKind::Param)
823 .iter()
824 .filter_map(|p| {
825 let name_tok = ast::Param::cast(p.clone())?.name()?;
826 let name_node = name_tok.syntax();
827 Some(ParamBinding {
828 name: SmolStr::new(name_tok.text()?),
829 type_ref: type_ref_ptr(p),
830 default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
831 name_range: cst::text_range_of(name_node),
832 })
833 })
834 .collect()
835 }
836
837 fn lower_block(&mut self, block: &GdNode) -> Block {
838 let mut out = Block::default();
839 self.lower_block_into(block, &mut out);
840 out
841 }
842
843 fn lower_block_into(&mut self, block: &GdNode, out: &mut Block) {
849 for c in block.children() {
850 if c.kind() == SyntaxKind::Block {
851 self.lower_block_into(c, out);
852 } else if let Some(s) = self.lower_stmt(c) {
853 out.push(s);
854 }
855 }
856 }
857
858 fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
859 use SyntaxKind as K;
860 let range = cst::text_range_of(node);
861 let stmt = match node.kind() {
862 K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
863 K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
864 K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
865 K::IfStmt => self.lower_if(node),
866 K::WhileStmt => Stmt::While {
867 cond: self.lower_first_expr(node),
868 body: self.lower_child_block(node),
869 },
870 K::ForStmt => Stmt::For(self.lower_for(node)),
871 K::MatchStmt => self.lower_match(node),
872 K::BreakStmt => Stmt::Break,
873 K::ContinueStmt => Stmt::Continue,
874 K::PassStmt | K::BreakpointStmt => Stmt::Pass,
875 K::AssertStmt => Stmt::Assert(
876 cst::first_child(node, |k| k == K::ArgList)
877 .and_then(|al| cst::first_child_expr(&al))
878 .map(|e| self.lower_expr(&e)),
879 ),
880 _ => return None,
882 };
883 Some(self.alloc_stmt(stmt, range))
884 }
885
886 fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
887 let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
888 let name = name_node
889 .as_ref()
890 .and_then(|n| ast::Name::cast(n.clone()))
891 .and_then(|n| n.text())
892 .map(SmolStr::new)
893 .unwrap_or_default();
894 LocalVar {
895 name,
896 type_ref: type_ref_ptr(node),
897 init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
898 is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
899 is_const: node.kind() == SyntaxKind::ConstDecl,
900 name_range: name_node
901 .as_ref()
902 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
903 }
904 }
905
906 fn lower_if(&mut self, node: &GdNode) -> Stmt {
907 let cond = self.lower_first_expr(node);
908 let then_branch = self.lower_child_block(node);
909 let elifs = cst::children_of(node, SyntaxKind::ElifClause)
910 .iter()
911 .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
912 .collect();
913 let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
914 .map(|c| self.lower_child_block(&c));
915 Stmt::If {
916 cond,
917 then_branch,
918 elifs,
919 else_branch,
920 }
921 }
922
923 fn lower_for(&mut self, node: &GdNode) -> ForLoop {
924 let name = cst::first_child(node, |k| k == SyntaxKind::Name);
925 let var = name
926 .as_ref()
927 .and_then(|n| ast::Name::cast(n.clone()))
928 .and_then(|n| n.text())
929 .map(SmolStr::new)
930 .unwrap_or_default();
931 ForLoop {
932 var,
933 var_type: type_ref_ptr(node),
934 var_range: name
935 .as_ref()
936 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
937 iter: self.lower_first_expr(node),
938 body: self.lower_child_block(node),
939 }
940 }
941
942 fn lower_match(&mut self, node: &GdNode) -> Stmt {
943 let scrutinee = self.lower_first_expr(node);
944 let arms = cst::children_of(node, SyntaxKind::MatchArm)
945 .iter()
946 .map(|arm| {
947 let binds = cst::children_of(arm, SyntaxKind::PatternBind)
948 .iter()
949 .filter_map(|b| {
950 let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
951 let name = ast::Name::cast(name_node.clone())?
952 .text()
953 .map(SmolStr::new)?;
954 Some(MatchBind {
955 name,
956 range: cst::text_range_of(&name_node),
957 })
958 })
959 .collect();
960 let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
961 .and_then(|g| cst::first_child_expr(&g))
962 .map(|e| self.lower_expr(&e));
963 let body = self.lower_child_block(arm);
964 MatchArm {
965 binds,
966 guard,
967 body,
968 range: cst::text_range_of(arm),
969 is_catch_all: arm_is_unconditional_catch_all(arm),
970 }
971 })
972 .collect();
973 Stmt::Match { scrutinee, arms }
974 }
975
976 fn lower_child_block(&mut self, node: &GdNode) -> Block {
978 cst::first_child(node, |k| k == SyntaxKind::Block)
979 .map(|b| self.lower_block(&b))
980 .unwrap_or_default()
981 }
982}
983
984fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
986 cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
987}
988
989fn literal_kind(node: &GdNode) -> Literal {
991 use SyntaxKind as K;
992 match cst::first_token(node).map(|t| t.kind()) {
993 Some(K::Int) => {
994 Literal::Int(cst::first_token(node).and_then(|t| parse_int_literal(t.text())))
995 }
996 Some(K::Float) => Literal::Float,
997 Some(K::String) => Literal::Str,
998 Some(K::StringName) => Literal::StringName,
999 Some(K::NodePath) => Literal::NodePath,
1000 Some(K::True) => Literal::Bool(true),
1001 Some(K::False) => Literal::Bool(false),
1002 Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
1003 _ => Literal::Null,
1004 }
1005}
1006
1007fn parse_int_literal(text: &str) -> Option<i64> {
1010 let t = text.replace('_', "");
1011 if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
1012 i64::from_str_radix(hex, 16).ok()
1013 } else if let Some(bin) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) {
1014 i64::from_str_radix(bin, 2).ok()
1015 } else {
1016 t.parse().ok()
1017 }
1018}
1019
1020fn bin_op(node: &GdNode) -> Option<BinOp> {
1022 node.children_with_tokens()
1023 .filter_map(cstree::util::NodeOrToken::into_token)
1024 .find_map(|t| BinOp::from_token(t.kind()))
1025}
1026
1027fn compound_assign_op(node: &GdNode) -> Option<BinOp> {
1030 use SyntaxKind as K;
1031 node.children_with_tokens()
1032 .filter_map(cstree::util::NodeOrToken::into_token)
1033 .find_map(|t| {
1034 Some(match t.kind() {
1035 K::PlusEq => BinOp::Add,
1036 K::MinusEq => BinOp::Sub,
1037 K::StarEq => BinOp::Mul,
1038 K::SlashEq => BinOp::Div,
1039 K::PercentEq => BinOp::Mod,
1040 K::StarStarEq => BinOp::Pow,
1041 K::AmpEq => BinOp::BitAnd,
1042 K::PipeEq => BinOp::BitOr,
1043 K::CaretEq => BinOp::BitXor,
1044 K::ShlEq => BinOp::Shl,
1045 K::ShrEq => BinOp::Shr,
1046 _ => return None,
1047 })
1048 })
1049}
1050
1051fn un_op(node: &GdNode) -> Option<UnOp> {
1053 node.children_with_tokens()
1054 .filter_map(cstree::util::NodeOrToken::into_token)
1055 .find_map(|t| UnOp::from_token(t.kind()))
1056}
1057
1058fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
1060 let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
1061 let tok = cst::first_token(&nameref)?;
1062 Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
1063}
1064
1065fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
1069 let callee = cst::first_child_expr(node)?;
1070 let is_get_node = match callee.kind() {
1075 SyntaxKind::NameRef => {
1076 cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
1077 }
1078 SyntaxKind::FieldExpr => {
1079 is_self_receiver(&callee)
1080 && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
1081 }
1082 _ => false,
1083 };
1084 if !is_get_node {
1085 return None;
1086 }
1087 let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
1088 .and_then(|al| cst::first_child_expr(&al))?;
1089 if arg.kind() != SyntaxKind::Literal {
1090 return None; }
1092 let s = cst::child_token_text(&arg, SyntaxKind::String)?;
1093 Some(SmolStr::new(s.trim_matches(['"', '\''])))
1094}
1095
1096fn is_get_node_name(name: &str) -> bool {
1097 matches!(name, "get_node" | "get_node_or_null")
1098}
1099
1100fn is_self_receiver(field_expr: &GdNode) -> bool {
1102 cst::first_child_expr(field_expr).is_some_and(|recv| {
1103 recv.kind() == SyntaxKind::NameRef
1104 && recv
1105 .children_with_tokens()
1106 .filter_map(cstree::util::NodeOrToken::into_token)
1107 .any(|t| t.kind() == SyntaxKind::SelfKw)
1108 })
1109}
1110
1111fn node_path_text(node: &GdNode) -> Option<SmolStr> {
1114 if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
1115 return Some(SmolStr::new(s.trim_matches(['"', '\''])));
1116 }
1117 let segs: Vec<String> = node
1118 .children_with_tokens()
1119 .filter_map(cstree::util::NodeOrToken::into_token)
1120 .filter(|t| t.kind() == SyntaxKind::Ident)
1121 .map(|t| t.text().to_owned())
1122 .collect();
1123 (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129 use gdscript_syntax::parse;
1130
1131 fn func_body(src: &str) -> Body {
1132 let root = parse(src).syntax_node();
1133 let func = gdscript_syntax::ast::descendants(&root)
1134 .into_iter()
1135 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1136 .expect("a FuncDecl");
1137 body_of_func(&func)
1138 }
1139
1140 #[test]
1141 fn lowers_params_and_return() {
1142 let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
1143 assert_eq!(body.params.len(), 2);
1144 assert_eq!(body.params[0].name, "a");
1145 assert!(body.params[0].type_ref.is_some());
1146 assert!(body.params[1].default.is_some());
1147 assert_eq!(body.block.len(), 1);
1148 let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
1149 panic!("expected return")
1150 };
1151 assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
1152 }
1153
1154 #[test]
1155 fn lowers_local_var_and_field_and_call() {
1156 let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1157 let Stmt::Var(v) = body.stmt(body.block[0]) else {
1159 panic!("expected var")
1160 };
1161 assert_eq!(v.name, "n");
1162 assert!(v.is_inferred && v.init.is_some());
1163 let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1165 panic!("expected expr stmt")
1166 };
1167 let Expr::Call { callee, .. } = body.expr(*e) else {
1168 panic!("expected call")
1169 };
1170 assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1171 }
1172
1173 #[test]
1174 fn lowers_if_with_is_narrowing() {
1175 let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1176 let Stmt::If {
1177 cond,
1178 then_branch,
1179 else_branch,
1180 ..
1181 } = body.stmt(body.block[0])
1182 else {
1183 panic!("expected if")
1184 };
1185 assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1186 assert_eq!(then_branch.len(), 1);
1187 assert!(else_branch.is_some());
1188 }
1189
1190 #[test]
1191 fn source_map_finds_tightest_expr() {
1192 let body = func_body("func f(a, b):\n\treturn a + b\n");
1194 let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1195 let id = body
1196 .source_map
1197 .expr_at_offset(b_offset)
1198 .expect("an expr at b");
1199 assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1200 }
1201
1202 #[test]
1203 fn initializer_body_has_tail() {
1204 let root = parse("var x = 1 + 2\n").syntax_node();
1205 let var = gdscript_syntax::ast::descendants(&root)
1206 .into_iter()
1207 .find(|n| n.kind() == SyntaxKind::VarDecl)
1208 .unwrap();
1209 let init = crate::cst::first_child_expr(&var).unwrap();
1210 let body = body_of_expr(&init);
1211 assert!(body.tail.is_some());
1212 assert!(matches!(
1213 body.expr(body.tail.unwrap()),
1214 Expr::Bin { op: BinOp::Add, .. }
1215 ));
1216 }
1217}