use cstree::util::NodeOrToken;
use gdscript_base::TextRange;
use gdscript_syntax::{GdNode, GdToken, SyntaxKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AstPtr {
pub kind: SyntaxKind,
pub range: TextRange,
}
impl AstPtr {
#[must_use]
pub fn of(node: &GdNode) -> Self {
Self {
kind: node.kind(),
range: text_range_of(node),
}
}
#[must_use]
pub fn to_node(self, root: &GdNode) -> Option<GdNode> {
find_node(root, self)
}
}
fn find_node(node: &GdNode, ptr: AstPtr) -> Option<GdNode> {
let range = text_range_of(node);
if node.kind() == ptr.kind && range == ptr.range {
return Some(node.clone());
}
if range.start <= ptr.range.start && range.end >= ptr.range.end {
for child in node.children() {
if let Some(found) = find_node(child, ptr) {
return Some(found);
}
}
}
None
}
#[must_use]
pub fn text_range_of(node: &GdNode) -> TextRange {
let r = node.text_range();
TextRange::new(u32::from(r.start()), u32::from(r.end()))
}
#[must_use]
pub fn has_token(node: &GdNode, kind: SyntaxKind) -> bool {
node.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.any(|t| t.kind() == kind)
}
#[must_use]
pub fn child_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())
}
#[must_use]
pub fn is_expr_kind(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::BinExpr
| SyntaxKind::UnaryExpr
| SyntaxKind::TernaryExpr
| SyntaxKind::CastExpr
| SyntaxKind::IsExpr
| SyntaxKind::InExpr
| SyntaxKind::CallExpr
| SyntaxKind::IndexExpr
| SyntaxKind::FieldExpr
| SyntaxKind::AwaitExpr
| SyntaxKind::Literal
| SyntaxKind::NameRef
| SyntaxKind::ArrayLit
| SyntaxKind::DictLit
| SyntaxKind::LambdaExpr
| SyntaxKind::ParenExpr
| SyntaxKind::PreloadExpr
| SyntaxKind::GetNodeExpr
| SyntaxKind::UniqueNodeExpr
)
}
pub fn first_child(node: &GdNode, pred: impl Fn(SyntaxKind) -> bool) -> Option<GdNode> {
node.children()
.find_map(|c| pred(c.kind()).then(|| c.clone()))
}
#[must_use]
pub fn first_child_expr(node: &GdNode) -> Option<GdNode> {
first_child(node, is_expr_kind)
}
#[must_use]
pub fn child_exprs(node: &GdNode) -> Vec<GdNode> {
node.children()
.filter(|c| is_expr_kind(c.kind()))
.cloned()
.collect()
}
#[must_use]
pub fn children_of(node: &GdNode, kind: SyntaxKind) -> Vec<GdNode> {
node.children()
.filter(|c| c.kind() == kind)
.cloned()
.collect()
}
#[must_use]
pub fn extends_head_token(node: &GdNode) -> Option<GdToken> {
let mut after_extends = false;
for t in node
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
{
if t.kind() == SyntaxKind::ExtendsKw {
after_extends = true;
} else if after_extends && t.kind() == SyntaxKind::Ident {
return Some(t.clone());
}
}
None
}
#[must_use]
pub fn first_token(node: &GdNode) -> Option<GdToken> {
node.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|t| !t.kind().is_trivia() && !t.kind().is_synthetic_layout())
.cloned()
}
#[must_use]
pub fn token_range(token: &GdToken) -> TextRange {
let r = token.text_range();
TextRange::new(u32::from(r.start()), u32::from(r.end()))
}