use cstree::util::NodeOrToken;
use crate::SyntaxKind;
use crate::syntax_kind::{GdNode, GdToken};
pub trait AstNode: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(node: GdNode) -> Option<Self>;
fn syntax(&self) -> &GdNode;
}
macro_rules! ast_node {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone)]
pub struct $name(GdNode);
impl AstNode for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$name
}
fn cast(node: GdNode) -> Option<Self> {
if node.kind() == SyntaxKind::$name {
Some(Self(node))
} else {
None
}
}
fn syntax(&self) -> &GdNode {
&self.0
}
}
};
}
ast_node!(
SourceFile
);
ast_node!(ClassNameDecl);
ast_node!(ExtendsClause);
ast_node!(Annotation);
ast_node!(FuncDecl);
ast_node!(VarDecl);
ast_node!(ConstDecl);
ast_node!(EnumDecl);
ast_node!(EnumVariant);
ast_node!(SignalDecl);
ast_node!(InnerClassDecl);
ast_node!(ClassBody);
ast_node!(ParamList);
ast_node!(Param);
ast_node!(Block);
ast_node!(TypeRef);
ast_node!(
Name
);
fn child<N: AstNode>(node: &GdNode) -> Option<N> {
node.children().find_map(|c| N::cast(c.clone()))
}
fn children<N: AstNode>(node: &GdNode) -> impl Iterator<Item = N> + '_ {
node.children().filter_map(|c| N::cast(c.clone()))
}
fn has_token(node: &GdNode, kind: SyntaxKind) -> bool {
node.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.any(|t| t.kind() == kind)
}
fn token_text(node: &GdNode, kind: SyntaxKind) -> Option<String> {
node.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|t| t.kind() == kind)
.map(|t| t.text().to_owned())
}
fn name_token_text(node: &GdNode) -> Option<String> {
node.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|t| {
matches!(
t.kind(),
SyntaxKind::Ident | SyntaxKind::MatchKw | SyntaxKind::WhenKw
)
})
.map(|t| t.text().to_owned())
}
impl Name {
#[must_use]
pub fn text(&self) -> Option<String> {
name_token_text(&self.0)
}
}
impl SourceFile {
pub fn decls(&self) -> impl Iterator<Item = Decl> + '_ {
self.0.children().filter_map(|c| Decl::cast(c.clone()))
}
}
impl FuncDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
#[must_use]
pub fn param_list(&self) -> Option<ParamList> {
child(&self.0)
}
#[must_use]
pub fn body(&self) -> Option<Block> {
child(&self.0)
}
#[must_use]
pub fn return_type(&self) -> Option<TypeRef> {
child(&self.0)
}
#[must_use]
pub fn is_static(&self) -> bool {
has_token(&self.0, SyntaxKind::StaticKw)
}
}
impl ParamList {
pub fn params(&self) -> impl Iterator<Item = Param> + '_ {
children(&self.0)
}
}
impl Param {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
#[must_use]
pub fn type_ref(&self) -> Option<TypeRef> {
child(&self.0)
}
}
impl VarDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
#[must_use]
pub fn type_ref(&self) -> Option<TypeRef> {
child(&self.0)
}
#[must_use]
pub fn is_static(&self) -> bool {
has_token(&self.0, SyntaxKind::StaticKw)
}
}
impl ConstDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
}
impl EnumDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
pub fn variants(&self) -> impl Iterator<Item = EnumVariant> + '_ {
children(&self.0)
}
}
impl EnumVariant {
#[must_use]
pub fn text(&self) -> Option<String> {
name_token_text(&self.0)
}
}
impl SignalDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
#[must_use]
pub fn param_list(&self) -> Option<ParamList> {
child(&self.0)
}
}
impl ClassNameDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
}
impl InnerClassDecl {
#[must_use]
pub fn name(&self) -> Option<Name> {
child(&self.0)
}
#[must_use]
pub fn body(&self) -> Option<ClassBody> {
child(&self.0)
}
}
impl ClassBody {
pub fn decls(&self) -> impl Iterator<Item = Decl> + '_ {
self.0.children().filter_map(|c| Decl::cast(c.clone()))
}
}
impl Annotation {
#[must_use]
pub fn name(&self) -> Option<String> {
token_text(&self.0, SyntaxKind::Ident)
}
}
impl TypeRef {
#[must_use]
pub fn text(&self) -> Option<String> {
self.0
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::VoidKw))
.map(|t| t.text().to_owned())
}
}
#[derive(Debug, Clone)]
pub enum Decl {
ClassName(ClassNameDecl),
Func(FuncDecl),
Var(VarDecl),
Const(ConstDecl),
Enum(EnumDecl),
Signal(SignalDecl),
Class(InnerClassDecl),
}
impl Decl {
#[must_use]
pub fn cast(node: GdNode) -> Option<Self> {
match node.kind() {
SyntaxKind::ClassNameDecl => ClassNameDecl::cast(node).map(Self::ClassName),
SyntaxKind::FuncDecl => FuncDecl::cast(node).map(Self::Func),
SyntaxKind::VarDecl => VarDecl::cast(node).map(Self::Var),
SyntaxKind::ConstDecl => ConstDecl::cast(node).map(Self::Const),
SyntaxKind::EnumDecl => EnumDecl::cast(node).map(Self::Enum),
SyntaxKind::SignalDecl => SignalDecl::cast(node).map(Self::Signal),
SyntaxKind::InnerClassDecl => InnerClassDecl::cast(node).map(Self::Class),
_ => None,
}
}
#[must_use]
pub fn syntax(&self) -> &GdNode {
match self {
Self::ClassName(d) => d.syntax(),
Self::Func(d) => d.syntax(),
Self::Var(d) => d.syntax(),
Self::Const(d) => d.syntax(),
Self::Enum(d) => d.syntax(),
Self::Signal(d) => d.syntax(),
Self::Class(d) => d.syntax(),
}
}
#[must_use]
pub fn name(&self) -> Option<String> {
let name = match self {
Self::ClassName(d) => d.name(),
Self::Func(d) => d.name(),
Self::Var(d) => d.name(),
Self::Const(d) => d.name(),
Self::Enum(d) => d.name(),
Self::Signal(d) => d.name(),
Self::Class(d) => d.name(),
};
name.and_then(|n| n.text())
}
}
#[must_use]
pub fn descendants(root: &GdNode) -> Vec<GdNode> {
let mut out = vec![root.clone()];
for child in root.children() {
out.extend(descendants(child));
}
out
}
#[must_use]
pub fn token_at(root: &GdNode, offset: text_size::TextSize) -> Option<GdToken> {
root.token_at_offset(offset).right_biased()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse;
#[test]
fn func_accessors() {
let parse = parse("static func add(a: int, b := 1) -> int:\n\treturn a + b\n");
let file = SourceFile::cast(parse.syntax_node()).unwrap();
let func = file
.decls()
.find_map(|d| match d {
Decl::Func(f) => Some(f),
_ => None,
})
.unwrap();
assert!(func.is_static());
assert_eq!(func.name().and_then(|n| n.text()).as_deref(), Some("add"));
assert_eq!(
func.return_type().and_then(|t| t.text()).as_deref(),
Some("int")
);
let params: Vec<_> = func
.param_list()
.unwrap()
.params()
.filter_map(|p| p.name().and_then(|n| n.text()))
.collect();
assert_eq!(params, vec!["a", "b"]);
assert!(func.body().is_some());
}
#[test]
fn declarations_are_enumerated() {
let parse = parse(
"class_name Foo\nconst K = 1\nvar x: int\nsignal s\nenum E { A, B }\nfunc f():\n\tpass\nclass Inner:\n\tvar y = 2\n",
);
let file = SourceFile::cast(parse.syntax_node()).unwrap();
let names: Vec<_> = file.decls().map(|d| d.name().unwrap_or_default()).collect();
assert_eq!(names, vec!["Foo", "K", "x", "s", "E", "f", "Inner"]);
}
#[test]
fn enum_variants_and_inner_class_members() {
let parse =
parse("enum E { A, B = 5, C }\nclass Inner:\n\tvar a = 1\n\tfunc m():\n\t\tpass\n");
let file = SourceFile::cast(parse.syntax_node()).unwrap();
let en = file
.decls()
.find_map(|d| match d {
Decl::Enum(e) => Some(e),
_ => None,
})
.unwrap();
let variants: Vec<_> = en.variants().filter_map(|v| v.text()).collect();
assert_eq!(variants, vec!["A", "B", "C"]);
let inner = file
.decls()
.find_map(|d| match d {
Decl::Class(c) => Some(c),
_ => None,
})
.unwrap();
let member_names: Vec<_> = inner
.body()
.unwrap()
.decls()
.map(|d| d.name().unwrap_or_default())
.collect();
assert_eq!(member_names, vec!["a", "m"]);
}
#[test]
fn token_at_offset_finds_identifier() {
let src = "var hello = 1\n";
let parse = parse(src);
let node = parse.syntax_node();
let tok = node
.token_at_offset(text_size::TextSize::new(5))
.right_biased()
.unwrap();
assert_eq!(tok.kind(), SyntaxKind::Ident);
assert_eq!(tok.text(), "hello");
}
}