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 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 let lhs = self.lower_or_missing(exprs.first(), range);
637 let rhs = self.lower_or_missing(exprs.get(1), range);
638 Expr::Bin { op, lhs, rhs }
639 }
640 K::UnaryExpr => {
641 let op = un_op(node).unwrap_or(UnOp::Pos);
642 let operand = self.lower_first_expr(node);
643 Expr::Unary { op, operand }
644 }
645 K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
646 K::TernaryExpr => {
647 let exprs = cst::child_exprs(node);
648 let then_branch = self.lower_or_missing(exprs.first(), range);
649 let cond = self.lower_or_missing(exprs.get(1), range);
650 let else_branch = self.lower_or_missing(exprs.get(2), range);
651 Expr::Ternary {
652 cond,
653 then_branch,
654 else_branch,
655 }
656 }
657 K::CallExpr => {
658 if let Some(path) = get_node_call_path(node) {
660 Expr::GetNode {
661 path: Some(path),
662 unique: false,
663 }
664 } else {
665 let callee = self.lower_first_expr(node);
666 let args = cst::first_child(node, |k| k == K::ArgList)
667 .map(|al| self.lower_exprs(&al))
668 .unwrap_or_default();
669 Expr::Call { callee, args }
670 }
671 }
672 K::IndexExpr => {
673 let exprs = cst::child_exprs(node);
674 let base = self.lower_or_missing(exprs.first(), range);
675 let index = self.lower_or_missing(exprs.get(1), range);
676 Expr::Index { base, index }
677 }
678 K::FieldExpr => {
679 let receiver = self.lower_first_expr(node);
680 let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
681 Expr::Field {
682 receiver,
683 name,
684 name_range,
685 }
686 }
687 K::IsExpr => {
688 let operand = self.lower_first_expr(node);
689 Expr::Is {
690 operand,
691 ty: type_ref_ptr(node),
692 negated: cst::has_token(node, K::NotKw),
693 }
694 }
695 K::CastExpr => {
696 let operand = self.lower_first_expr(node);
697 Expr::Cast {
698 operand,
699 ty: type_ref_ptr(node),
700 }
701 }
702 K::InExpr => {
703 let exprs = cst::child_exprs(node);
704 let lhs = self.lower_or_missing(exprs.first(), range);
705 let rhs = self.lower_or_missing(exprs.get(1), range);
706 Expr::In {
707 lhs,
708 rhs,
709 negated: cst::has_token(node, K::NotKw),
710 }
711 }
712 K::ArrayLit => Expr::Array(self.lower_exprs(node)),
713 K::DictLit => {
714 let entries = cst::children_of(node, K::DictEntry)
715 .iter()
716 .map(|e| {
717 let kv = cst::child_exprs(e);
718 let key = self.lower_or_missing(kv.first(), cst::text_range_of(e));
719 let value = kv.get(1).map(|v| self.lower_expr(v));
720 (key, value)
721 })
722 .collect();
723 Expr::Dict(entries)
724 }
725 K::LambdaExpr => {
726 let params = cst::first_child(node, |k| k == K::ParamList)
727 .map(|pl| self.lower_params(&pl))
728 .unwrap_or_default();
729 let body = cst::first_child(node, |k| k == K::Block)
730 .map(|b| self.lower_block(&b))
731 .unwrap_or_default();
732 Expr::Lambda { params, body }
733 }
734 K::PreloadExpr => {
735 let arg_node = cst::first_child(node, |k| k == K::ArgList)
736 .and_then(|al| cst::first_child_expr(&al));
737 let path = arg_node
740 .as_ref()
741 .filter(|n| n.kind() == K::Literal)
742 .and_then(|n| cst::child_token_text(n, K::String))
743 .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
744 let arg = arg_node.map(|e| self.lower_expr(&e));
745 Expr::Preload { arg, path }
746 }
747 K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
748 path: node_path_text(node),
749 unique: node.kind() == K::UniqueNodeExpr,
750 },
751 _ => Expr::Missing,
752 };
753 self.alloc_expr(expr, range)
754 }
755
756 fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
757 let range = cst::text_range_of(node);
758 let expr = match cst::first_token(node) {
759 Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
760 Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
761 Some(t) => Expr::Name(SmolStr::new(t.text())),
762 None => Expr::Missing,
763 };
764 self.alloc_expr(expr, range)
765 }
766
767 fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
768 match node {
769 Some(n) => self.lower_expr(n),
770 None => self.missing(fallback),
771 }
772 }
773
774 fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
775 cst::child_exprs(node)
776 .iter()
777 .map(|c| self.lower_expr(c))
778 .collect()
779 }
780
781 fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
782 cst::children_of(param_list, SyntaxKind::Param)
783 .iter()
784 .filter_map(|p| {
785 let name_tok = ast::Param::cast(p.clone())?.name()?;
786 let name_node = name_tok.syntax();
787 Some(ParamBinding {
788 name: SmolStr::new(name_tok.text()?),
789 type_ref: type_ref_ptr(p),
790 default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
791 name_range: cst::text_range_of(name_node),
792 })
793 })
794 .collect()
795 }
796
797 fn lower_block(&mut self, block: &GdNode) -> Block {
798 block
799 .children()
800 .filter_map(|c| self.lower_stmt(c))
801 .collect()
802 }
803
804 fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
805 use SyntaxKind as K;
806 let range = cst::text_range_of(node);
807 let stmt = match node.kind() {
808 K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
809 K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
810 K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
811 K::IfStmt => self.lower_if(node),
812 K::WhileStmt => Stmt::While {
813 cond: self.lower_first_expr(node),
814 body: self.lower_child_block(node),
815 },
816 K::ForStmt => Stmt::For(self.lower_for(node)),
817 K::MatchStmt => self.lower_match(node),
818 K::BreakStmt => Stmt::Break,
819 K::ContinueStmt => Stmt::Continue,
820 K::PassStmt | K::BreakpointStmt => Stmt::Pass,
821 K::AssertStmt => Stmt::Assert(
822 cst::first_child(node, |k| k == K::ArgList)
823 .and_then(|al| cst::first_child_expr(&al))
824 .map(|e| self.lower_expr(&e)),
825 ),
826 _ => return None,
828 };
829 Some(self.alloc_stmt(stmt, range))
830 }
831
832 fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
833 let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
834 let name = name_node
835 .as_ref()
836 .and_then(|n| ast::Name::cast(n.clone()))
837 .and_then(|n| n.text())
838 .map(SmolStr::new)
839 .unwrap_or_default();
840 LocalVar {
841 name,
842 type_ref: type_ref_ptr(node),
843 init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
844 is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
845 is_const: node.kind() == SyntaxKind::ConstDecl,
846 name_range: name_node
847 .as_ref()
848 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
849 }
850 }
851
852 fn lower_if(&mut self, node: &GdNode) -> Stmt {
853 let cond = self.lower_first_expr(node);
854 let then_branch = self.lower_child_block(node);
855 let elifs = cst::children_of(node, SyntaxKind::ElifClause)
856 .iter()
857 .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
858 .collect();
859 let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
860 .map(|c| self.lower_child_block(&c));
861 Stmt::If {
862 cond,
863 then_branch,
864 elifs,
865 else_branch,
866 }
867 }
868
869 fn lower_for(&mut self, node: &GdNode) -> ForLoop {
870 let name = cst::first_child(node, |k| k == SyntaxKind::Name);
871 let var = name
872 .as_ref()
873 .and_then(|n| ast::Name::cast(n.clone()))
874 .and_then(|n| n.text())
875 .map(SmolStr::new)
876 .unwrap_or_default();
877 ForLoop {
878 var,
879 var_type: type_ref_ptr(node),
880 var_range: name
881 .as_ref()
882 .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
883 iter: self.lower_first_expr(node),
884 body: self.lower_child_block(node),
885 }
886 }
887
888 fn lower_match(&mut self, node: &GdNode) -> Stmt {
889 let scrutinee = self.lower_first_expr(node);
890 let arms = cst::children_of(node, SyntaxKind::MatchArm)
891 .iter()
892 .map(|arm| {
893 let binds = cst::children_of(arm, SyntaxKind::PatternBind)
894 .iter()
895 .filter_map(|b| {
896 let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
897 let name = ast::Name::cast(name_node.clone())?
898 .text()
899 .map(SmolStr::new)?;
900 Some(MatchBind {
901 name,
902 range: cst::text_range_of(&name_node),
903 })
904 })
905 .collect();
906 let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
907 .and_then(|g| cst::first_child_expr(&g))
908 .map(|e| self.lower_expr(&e));
909 let body = self.lower_child_block(arm);
910 MatchArm {
911 binds,
912 guard,
913 body,
914 range: cst::text_range_of(arm),
915 is_catch_all: arm_is_unconditional_catch_all(arm),
916 }
917 })
918 .collect();
919 Stmt::Match { scrutinee, arms }
920 }
921
922 fn lower_child_block(&mut self, node: &GdNode) -> Block {
924 cst::first_child(node, |k| k == SyntaxKind::Block)
925 .map(|b| self.lower_block(&b))
926 .unwrap_or_default()
927 }
928}
929
930fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
932 cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
933}
934
935fn literal_kind(node: &GdNode) -> Literal {
937 use SyntaxKind as K;
938 match cst::first_token(node).map(|t| t.kind()) {
939 Some(K::Int) => Literal::Int,
940 Some(K::Float) => Literal::Float,
941 Some(K::String) => Literal::Str,
942 Some(K::StringName) => Literal::StringName,
943 Some(K::NodePath) => Literal::NodePath,
944 Some(K::True | K::False) => Literal::Bool,
945 Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
946 _ => Literal::Null,
947 }
948}
949
950fn bin_op(node: &GdNode) -> Option<BinOp> {
952 node.children_with_tokens()
953 .filter_map(cstree::util::NodeOrToken::into_token)
954 .find_map(|t| BinOp::from_token(t.kind()))
955}
956
957fn un_op(node: &GdNode) -> Option<UnOp> {
959 node.children_with_tokens()
960 .filter_map(cstree::util::NodeOrToken::into_token)
961 .find_map(|t| UnOp::from_token(t.kind()))
962}
963
964fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
966 let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
967 let tok = cst::first_token(&nameref)?;
968 Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
969}
970
971fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
975 let callee = cst::first_child_expr(node)?;
976 let is_get_node = match callee.kind() {
981 SyntaxKind::NameRef => {
982 cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
983 }
984 SyntaxKind::FieldExpr => {
985 is_self_receiver(&callee)
986 && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
987 }
988 _ => false,
989 };
990 if !is_get_node {
991 return None;
992 }
993 let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
994 .and_then(|al| cst::first_child_expr(&al))?;
995 if arg.kind() != SyntaxKind::Literal {
996 return None; }
998 let s = cst::child_token_text(&arg, SyntaxKind::String)?;
999 Some(SmolStr::new(s.trim_matches(['"', '\''])))
1000}
1001
1002fn is_get_node_name(name: &str) -> bool {
1003 matches!(name, "get_node" | "get_node_or_null")
1004}
1005
1006fn is_self_receiver(field_expr: &GdNode) -> bool {
1008 cst::first_child_expr(field_expr).is_some_and(|recv| {
1009 recv.kind() == SyntaxKind::NameRef
1010 && recv
1011 .children_with_tokens()
1012 .filter_map(cstree::util::NodeOrToken::into_token)
1013 .any(|t| t.kind() == SyntaxKind::SelfKw)
1014 })
1015}
1016
1017fn node_path_text(node: &GdNode) -> Option<SmolStr> {
1020 if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
1021 return Some(SmolStr::new(s.trim_matches(['"', '\''])));
1022 }
1023 let segs: Vec<String> = node
1024 .children_with_tokens()
1025 .filter_map(cstree::util::NodeOrToken::into_token)
1026 .filter(|t| t.kind() == SyntaxKind::Ident)
1027 .map(|t| t.text().to_owned())
1028 .collect();
1029 (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use gdscript_syntax::parse;
1036
1037 fn func_body(src: &str) -> Body {
1038 let root = parse(src).syntax_node();
1039 let func = gdscript_syntax::ast::descendants(&root)
1040 .into_iter()
1041 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1042 .expect("a FuncDecl");
1043 body_of_func(&func)
1044 }
1045
1046 #[test]
1047 fn lowers_params_and_return() {
1048 let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
1049 assert_eq!(body.params.len(), 2);
1050 assert_eq!(body.params[0].name, "a");
1051 assert!(body.params[0].type_ref.is_some());
1052 assert!(body.params[1].default.is_some());
1053 assert_eq!(body.block.len(), 1);
1054 let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
1055 panic!("expected return")
1056 };
1057 assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
1058 }
1059
1060 #[test]
1061 fn lowers_local_var_and_field_and_call() {
1062 let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1063 let Stmt::Var(v) = body.stmt(body.block[0]) else {
1065 panic!("expected var")
1066 };
1067 assert_eq!(v.name, "n");
1068 assert!(v.is_inferred && v.init.is_some());
1069 let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1071 panic!("expected expr stmt")
1072 };
1073 let Expr::Call { callee, .. } = body.expr(*e) else {
1074 panic!("expected call")
1075 };
1076 assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1077 }
1078
1079 #[test]
1080 fn lowers_if_with_is_narrowing() {
1081 let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1082 let Stmt::If {
1083 cond,
1084 then_branch,
1085 else_branch,
1086 ..
1087 } = body.stmt(body.block[0])
1088 else {
1089 panic!("expected if")
1090 };
1091 assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1092 assert_eq!(then_branch.len(), 1);
1093 assert!(else_branch.is_some());
1094 }
1095
1096 #[test]
1097 fn source_map_finds_tightest_expr() {
1098 let body = func_body("func f(a, b):\n\treturn a + b\n");
1100 let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1101 let id = body
1102 .source_map
1103 .expr_at_offset(b_offset)
1104 .expect("an expr at b");
1105 assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1106 }
1107
1108 #[test]
1109 fn initializer_body_has_tail() {
1110 let root = parse("var x = 1 + 2\n").syntax_node();
1111 let var = gdscript_syntax::ast::descendants(&root)
1112 .into_iter()
1113 .find(|n| n.kind() == SyntaxKind::VarDecl)
1114 .unwrap();
1115 let init = crate::cst::first_child_expr(&var).unwrap();
1116 let body = body_of_expr(&init);
1117 assert!(body.tail.is_some());
1118 assert!(matches!(
1119 body.expr(body.tail.unwrap()),
1120 Expr::Bin { op: BinOp::Add, .. }
1121 ));
1122 }
1123}