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,
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}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
372pub enum Stmt {
373 Expr(ExprId),
375 Var(LocalVar),
377 Return(Option<ExprId>),
379 If {
381 cond: ExprId,
383 then_branch: Block,
385 elifs: Vec<(ExprId, Block)>,
387 else_branch: Option<Block>,
389 },
390 While {
392 cond: ExprId,
394 body: Block,
396 },
397 For(ForLoop),
399 Match {
401 scrutinee: ExprId,
403 arms: Vec<MatchArm>,
405 },
406 Break,
408 Continue,
410 Pass,
412 Assert(Option<ExprId>),
414}
415
416#[derive(Debug, Clone, Default, PartialEq, Eq)]
419pub struct BodySourceMap {
420 expr_ranges: Vec<TextRange>,
421 stmt_ranges: Vec<TextRange>,
422}
423
424impl BodySourceMap {
425 #[must_use]
427 pub fn expr_range(&self, id: ExprId) -> TextRange {
428 self.expr_ranges[id.0 as usize]
429 }
430
431 #[must_use]
433 pub fn stmt_range(&self, id: StmtId) -> TextRange {
434 self.stmt_ranges[id.0 as usize]
435 }
436
437 #[must_use]
439 pub fn expr_at_offset(&self, offset: u32) -> Option<ExprId> {
440 self.expr_ranges
441 .iter()
442 .enumerate()
443 .filter(|(_, r)| r.start <= offset && offset < r.end)
444 .min_by_key(|(_, r)| r.end - r.start)
445 .map(|(i, _)| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
446 }
447
448 #[must_use]
451 pub fn expr_for_range(&self, range: TextRange) -> Option<ExprId> {
452 self.expr_ranges
453 .iter()
454 .position(|r| *r == range)
455 .map(|i| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
456 }
457}
458
459#[derive(Debug, Clone, Default, PartialEq, Eq)]
461pub struct Body {
462 pub exprs: Vec<Expr>,
464 pub stmts: Vec<Stmt>,
466 pub params: Vec<ParamBinding>,
468 pub block: Block,
470 pub tail: Option<ExprId>,
472 pub source_map: BodySourceMap,
474}
475
476impl Body {
477 #[must_use]
479 pub fn expr(&self, id: ExprId) -> &Expr {
480 &self.exprs[id.0 as usize]
481 }
482
483 #[must_use]
485 pub fn stmt(&self, id: StmtId) -> &Stmt {
486 &self.stmts[id.0 as usize]
487 }
488}
489
490#[must_use]
492pub fn body_of_func(func: &GdNode) -> Body {
493 let mut low = Lowerer::default();
494 let decl = ast::FuncDecl::cast(func.clone());
495 let params = decl
496 .as_ref()
497 .and_then(ast::FuncDecl::param_list)
498 .map(|pl| low.lower_params(pl.syntax()))
499 .unwrap_or_default();
500 let block = decl
501 .as_ref()
502 .and_then(ast::FuncDecl::body)
503 .map(|b| low.lower_block(b.syntax()))
504 .unwrap_or_default();
505 low.finish(params, block, None)
506}
507
508#[must_use]
510pub fn body_of_expr(expr: &GdNode) -> Body {
511 let mut low = Lowerer::default();
512 let tail = low.lower_expr(expr);
513 low.finish(Vec::new(), Vec::new(), Some(tail))
514}
515
516#[must_use]
520pub fn body_of_decl_stmt(decl: &GdNode) -> Body {
521 let mut low = Lowerer::default();
522 let block = low.lower_stmt(decl).into_iter().collect();
523 low.finish(Vec::new(), block, None)
524}
525
526#[must_use]
528pub fn body(root: &GdNode, ptr: AstPtr) -> Option<Body> {
529 let node = ptr.to_node(root)?;
530 Some(body_of_func(&node))
531}
532
533#[derive(Default)]
534struct Lowerer {
535 exprs: Vec<Expr>,
536 stmts: Vec<Stmt>,
537 expr_ranges: Vec<TextRange>,
538 stmt_ranges: Vec<TextRange>,
539}
540
541impl Lowerer {
542 fn finish(self, params: Vec<ParamBinding>, block: Block, tail: Option<ExprId>) -> Body {
543 Body {
544 exprs: self.exprs,
545 stmts: self.stmts,
546 params,
547 block,
548 tail,
549 source_map: BodySourceMap {
550 expr_ranges: self.expr_ranges,
551 stmt_ranges: self.stmt_ranges,
552 },
553 }
554 }
555
556 fn alloc_expr(&mut self, expr: Expr, range: TextRange) -> ExprId {
557 let id = ExprId(u32::try_from(self.exprs.len()).unwrap_or(u32::MAX));
558 self.exprs.push(expr);
559 self.expr_ranges.push(range);
560 id
561 }
562
563 fn alloc_stmt(&mut self, stmt: Stmt, range: TextRange) -> StmtId {
564 let id = StmtId(u32::try_from(self.stmts.len()).unwrap_or(u32::MAX));
565 self.stmts.push(stmt);
566 self.stmt_ranges.push(range);
567 id
568 }
569
570 fn missing(&mut self, range: TextRange) -> ExprId {
571 self.alloc_expr(Expr::Missing, range)
572 }
573
574 fn lower_first_expr(&mut self, node: &GdNode) -> ExprId {
576 match cst::first_child_expr(node) {
577 Some(c) => self.lower_expr(&c),
578 None => self.missing(cst::text_range_of(node)),
579 }
580 }
581
582 #[allow(clippy::too_many_lines)]
583 fn lower_expr(&mut self, node: &GdNode) -> ExprId {
584 use SyntaxKind as K;
585 let range = cst::text_range_of(node);
586 let expr = match node.kind() {
587 K::Literal => Expr::Literal(literal_kind(node)),
588 K::NameRef => return self.lower_name_ref(node),
589 K::ParenExpr => Expr::Paren(self.lower_first_expr(node)),
590 K::BinExpr => {
591 let exprs = cst::child_exprs(node);
592 let op = bin_op(node).unwrap_or(BinOp::Add);
593 let lhs = self.lower_or_missing(exprs.first(), range);
594 let rhs = self.lower_or_missing(exprs.get(1), range);
595 Expr::Bin { op, lhs, rhs }
596 }
597 K::UnaryExpr => {
598 let op = un_op(node).unwrap_or(UnOp::Pos);
599 let operand = self.lower_first_expr(node);
600 Expr::Unary { op, operand }
601 }
602 K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
603 K::TernaryExpr => {
604 let exprs = cst::child_exprs(node);
605 let then_branch = self.lower_or_missing(exprs.first(), range);
606 let cond = self.lower_or_missing(exprs.get(1), range);
607 let else_branch = self.lower_or_missing(exprs.get(2), range);
608 Expr::Ternary {
609 cond,
610 then_branch,
611 else_branch,
612 }
613 }
614 K::CallExpr => {
615 if let Some(path) = get_node_call_path(node) {
617 Expr::GetNode {
618 path: Some(path),
619 unique: false,
620 }
621 } else {
622 let callee = self.lower_first_expr(node);
623 let args = cst::first_child(node, |k| k == K::ArgList)
624 .map(|al| self.lower_exprs(&al))
625 .unwrap_or_default();
626 Expr::Call { callee, args }
627 }
628 }
629 K::IndexExpr => {
630 let exprs = cst::child_exprs(node);
631 let base = self.lower_or_missing(exprs.first(), range);
632 let index = self.lower_or_missing(exprs.get(1), range);
633 Expr::Index { base, index }
634 }
635 K::FieldExpr => {
636 let receiver = self.lower_first_expr(node);
637 let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
638 Expr::Field {
639 receiver,
640 name,
641 name_range,
642 }
643 }
644 K::IsExpr => {
645 let operand = self.lower_first_expr(node);
646 Expr::Is {
647 operand,
648 ty: type_ref_ptr(node),
649 negated: cst::has_token(node, K::NotKw),
650 }
651 }
652 K::CastExpr => {
653 let operand = self.lower_first_expr(node);
654 Expr::Cast {
655 operand,
656 ty: type_ref_ptr(node),
657 }
658 }
659 K::InExpr => {
660 let exprs = cst::child_exprs(node);
661 let lhs = self.lower_or_missing(exprs.first(), range);
662 let rhs = self.lower_or_missing(exprs.get(1), range);
663 Expr::In {
664 lhs,
665 rhs,
666 negated: cst::has_token(node, K::NotKw),
667 }
668 }
669 K::ArrayLit => Expr::Array(self.lower_exprs(node)),
670 K::DictLit => {
671 let entries = cst::children_of(node, K::DictEntry)
672 .iter()
673 .map(|e| {
674 let kv = cst::child_exprs(e);
675 let key = self.lower_or_missing(kv.first(), cst::text_range_of(e));
676 let value = kv.get(1).map(|v| self.lower_expr(v));
677 (key, value)
678 })
679 .collect();
680 Expr::Dict(entries)
681 }
682 K::LambdaExpr => {
683 let params = cst::first_child(node, |k| k == K::ParamList)
684 .map(|pl| self.lower_params(&pl))
685 .unwrap_or_default();
686 let body = cst::first_child(node, |k| k == K::Block)
687 .map(|b| self.lower_block(&b))
688 .unwrap_or_default();
689 Expr::Lambda { params, body }
690 }
691 K::PreloadExpr => {
692 let arg_node = cst::first_child(node, |k| k == K::ArgList)
693 .and_then(|al| cst::first_child_expr(&al));
694 let path = arg_node
697 .as_ref()
698 .filter(|n| n.kind() == K::Literal)
699 .and_then(|n| cst::child_token_text(n, K::String))
700 .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
701 let arg = arg_node.map(|e| self.lower_expr(&e));
702 Expr::Preload { arg, path }
703 }
704 K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
705 path: node_path_text(node),
706 unique: node.kind() == K::UniqueNodeExpr,
707 },
708 _ => Expr::Missing,
709 };
710 self.alloc_expr(expr, range)
711 }
712
713 fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
714 let range = cst::text_range_of(node);
715 let expr = match cst::first_token(node) {
716 Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
717 Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
718 Some(t) => Expr::Name(SmolStr::new(t.text())),
719 None => Expr::Missing,
720 };
721 self.alloc_expr(expr, range)
722 }
723
724 fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
725 match node {
726 Some(n) => self.lower_expr(n),
727 None => self.missing(fallback),
728 }
729 }
730
731 fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
732 cst::child_exprs(node)
733 .iter()
734 .map(|c| self.lower_expr(c))
735 .collect()
736 }
737
738 fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
739 cst::children_of(param_list, SyntaxKind::Param)
740 .iter()
741 .filter_map(|p| {
742 let name_tok = ast::Param::cast(p.clone())?.name()?;
743 let name_node = name_tok.syntax();
744 Some(ParamBinding {
745 name: SmolStr::new(name_tok.text()?),
746 type_ref: type_ref_ptr(p),
747 default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
748 name_range: cst::text_range_of(name_node),
749 })
750 })
751 .collect()
752 }
753
754 fn lower_block(&mut self, block: &GdNode) -> Block {
755 block
756 .children()
757 .filter_map(|c| self.lower_stmt(c))
758 .collect()
759 }
760
761 fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
762 use SyntaxKind as K;
763 let range = cst::text_range_of(node);
764 let stmt = match node.kind() {
765 K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
766 K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
767 K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
768 K::IfStmt => self.lower_if(node),
769 K::WhileStmt => Stmt::While {
770 cond: self.lower_first_expr(node),
771 body: self.lower_child_block(node),
772 },
773 K::ForStmt => Stmt::For(self.lower_for(node)),
774 K::MatchStmt => self.lower_match(node),
775 K::BreakStmt => Stmt::Break,
776 K::ContinueStmt => Stmt::Continue,
777 K::PassStmt | K::BreakpointStmt => Stmt::Pass,
778 K::AssertStmt => Stmt::Assert(
779 cst::first_child(node, |k| k == K::ArgList)
780 .and_then(|al| cst::first_child_expr(&al))
781 .map(|e| self.lower_expr(&e)),
782 ),
783 _ => return None,
785 };
786 Some(self.alloc_stmt(stmt, range))
787 }
788
789 fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
790 let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
791 let name = name_node
792 .as_ref()
793 .and_then(|n| ast::Name::cast(n.clone()))
794 .and_then(|n| n.text())
795 .map(SmolStr::new)
796 .unwrap_or_default();
797 LocalVar {
798 name,
799 type_ref: type_ref_ptr(node),
800 init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
801 is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
802 is_const: node.kind() == SyntaxKind::ConstDecl,
803 name_range: name_node
804 .as_ref()
805 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
806 }
807 }
808
809 fn lower_if(&mut self, node: &GdNode) -> Stmt {
810 let cond = self.lower_first_expr(node);
811 let then_branch = self.lower_child_block(node);
812 let elifs = cst::children_of(node, SyntaxKind::ElifClause)
813 .iter()
814 .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
815 .collect();
816 let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
817 .map(|c| self.lower_child_block(&c));
818 Stmt::If {
819 cond,
820 then_branch,
821 elifs,
822 else_branch,
823 }
824 }
825
826 fn lower_for(&mut self, node: &GdNode) -> ForLoop {
827 let name = cst::first_child(node, |k| k == SyntaxKind::Name);
828 let var = name
829 .as_ref()
830 .and_then(|n| ast::Name::cast(n.clone()))
831 .and_then(|n| n.text())
832 .map(SmolStr::new)
833 .unwrap_or_default();
834 ForLoop {
835 var,
836 var_type: type_ref_ptr(node),
837 var_range: name
838 .as_ref()
839 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
840 iter: self.lower_first_expr(node),
841 body: self.lower_child_block(node),
842 }
843 }
844
845 fn lower_match(&mut self, node: &GdNode) -> Stmt {
846 let scrutinee = self.lower_first_expr(node);
847 let arms = cst::children_of(node, SyntaxKind::MatchArm)
848 .iter()
849 .map(|arm| {
850 let binds = cst::children_of(arm, SyntaxKind::PatternBind)
851 .iter()
852 .filter_map(|b| {
853 let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
854 let name = ast::Name::cast(name_node.clone())?
855 .text()
856 .map(SmolStr::new)?;
857 Some(MatchBind {
858 name,
859 range: cst::text_range_of(&name_node),
860 })
861 })
862 .collect();
863 let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
864 .and_then(|g| cst::first_child_expr(&g))
865 .map(|e| self.lower_expr(&e));
866 let body = self.lower_child_block(arm);
867 MatchArm { binds, guard, body }
868 })
869 .collect();
870 Stmt::Match { scrutinee, arms }
871 }
872
873 fn lower_child_block(&mut self, node: &GdNode) -> Block {
875 cst::first_child(node, |k| k == SyntaxKind::Block)
876 .map(|b| self.lower_block(&b))
877 .unwrap_or_default()
878 }
879}
880
881fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
883 cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
884}
885
886fn literal_kind(node: &GdNode) -> Literal {
888 use SyntaxKind as K;
889 match cst::first_token(node).map(|t| t.kind()) {
890 Some(K::Int) => Literal::Int,
891 Some(K::Float) => Literal::Float,
892 Some(K::String) => Literal::Str,
893 Some(K::StringName) => Literal::StringName,
894 Some(K::NodePath) => Literal::NodePath,
895 Some(K::True | K::False) => Literal::Bool,
896 Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
897 _ => Literal::Null,
898 }
899}
900
901fn bin_op(node: &GdNode) -> Option<BinOp> {
903 node.children_with_tokens()
904 .filter_map(cstree::util::NodeOrToken::into_token)
905 .find_map(|t| BinOp::from_token(t.kind()))
906}
907
908fn un_op(node: &GdNode) -> Option<UnOp> {
910 node.children_with_tokens()
911 .filter_map(cstree::util::NodeOrToken::into_token)
912 .find_map(|t| UnOp::from_token(t.kind()))
913}
914
915fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
917 let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
918 let tok = cst::first_token(&nameref)?;
919 Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
920}
921
922fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
926 let callee = cst::first_child_expr(node)?;
927 let is_get_node = match callee.kind() {
932 SyntaxKind::NameRef => {
933 cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
934 }
935 SyntaxKind::FieldExpr => {
936 is_self_receiver(&callee)
937 && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
938 }
939 _ => false,
940 };
941 if !is_get_node {
942 return None;
943 }
944 let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
945 .and_then(|al| cst::first_child_expr(&al))?;
946 if arg.kind() != SyntaxKind::Literal {
947 return None; }
949 let s = cst::child_token_text(&arg, SyntaxKind::String)?;
950 Some(SmolStr::new(s.trim_matches(['"', '\''])))
951}
952
953fn is_get_node_name(name: &str) -> bool {
954 matches!(name, "get_node" | "get_node_or_null")
955}
956
957fn is_self_receiver(field_expr: &GdNode) -> bool {
959 cst::first_child_expr(field_expr).is_some_and(|recv| {
960 recv.kind() == SyntaxKind::NameRef
961 && recv
962 .children_with_tokens()
963 .filter_map(cstree::util::NodeOrToken::into_token)
964 .any(|t| t.kind() == SyntaxKind::SelfKw)
965 })
966}
967
968fn node_path_text(node: &GdNode) -> Option<SmolStr> {
971 if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
972 return Some(SmolStr::new(s.trim_matches(['"', '\''])));
973 }
974 let segs: Vec<String> = node
975 .children_with_tokens()
976 .filter_map(cstree::util::NodeOrToken::into_token)
977 .filter(|t| t.kind() == SyntaxKind::Ident)
978 .map(|t| t.text().to_owned())
979 .collect();
980 (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
981}
982
983#[cfg(test)]
984mod tests {
985 use super::*;
986 use gdscript_syntax::parse;
987
988 fn func_body(src: &str) -> Body {
989 let root = parse(src).syntax_node();
990 let func = gdscript_syntax::ast::descendants(&root)
991 .into_iter()
992 .find(|n| n.kind() == SyntaxKind::FuncDecl)
993 .expect("a FuncDecl");
994 body_of_func(&func)
995 }
996
997 #[test]
998 fn lowers_params_and_return() {
999 let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
1000 assert_eq!(body.params.len(), 2);
1001 assert_eq!(body.params[0].name, "a");
1002 assert!(body.params[0].type_ref.is_some());
1003 assert!(body.params[1].default.is_some());
1004 assert_eq!(body.block.len(), 1);
1005 let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
1006 panic!("expected return")
1007 };
1008 assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
1009 }
1010
1011 #[test]
1012 fn lowers_local_var_and_field_and_call() {
1013 let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1014 let Stmt::Var(v) = body.stmt(body.block[0]) else {
1016 panic!("expected var")
1017 };
1018 assert_eq!(v.name, "n");
1019 assert!(v.is_inferred && v.init.is_some());
1020 let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1022 panic!("expected expr stmt")
1023 };
1024 let Expr::Call { callee, .. } = body.expr(*e) else {
1025 panic!("expected call")
1026 };
1027 assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1028 }
1029
1030 #[test]
1031 fn lowers_if_with_is_narrowing() {
1032 let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1033 let Stmt::If {
1034 cond,
1035 then_branch,
1036 else_branch,
1037 ..
1038 } = body.stmt(body.block[0])
1039 else {
1040 panic!("expected if")
1041 };
1042 assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1043 assert_eq!(then_branch.len(), 1);
1044 assert!(else_branch.is_some());
1045 }
1046
1047 #[test]
1048 fn source_map_finds_tightest_expr() {
1049 let body = func_body("func f(a, b):\n\treturn a + b\n");
1051 let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1052 let id = body
1053 .source_map
1054 .expr_at_offset(b_offset)
1055 .expect("an expr at b");
1056 assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1057 }
1058
1059 #[test]
1060 fn initializer_body_has_tail() {
1061 let root = parse("var x = 1 + 2\n").syntax_node();
1062 let var = gdscript_syntax::ast::descendants(&root)
1063 .into_iter()
1064 .find(|n| n.kind() == SyntaxKind::VarDecl)
1065 .unwrap();
1066 let init = crate::cst::first_child_expr(&var).unwrap();
1067 let body = body_of_expr(&init);
1068 assert!(body.tail.is_some());
1069 assert!(matches!(
1070 body.expr(body.tail.unwrap()),
1071 Expr::Bin { op: BinOp::Add, .. }
1072 ));
1073 }
1074}