use super::syntax_kind::SyntaxKind;
use super::{SyntaxNode, SyntaxToken};
#[inline]
fn is_name_token(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::IDENT
| SyntaxKind::START_KW
| SyntaxKind::END_KW
| SyntaxKind::DONE_KW
| SyntaxKind::THIS_KW
| SyntaxKind::MEMBER_KW
| SyntaxKind::FRAME_KW )
}
#[inline]
fn strip_unrestricted_name(text: &str) -> String {
if text.starts_with('\'') && text.ends_with('\'') && text.len() > 1 {
text[1..text.len() - 1].to_string()
} else {
text.to_string()
}
}
#[inline]
fn has_token(node: &SyntaxNode, kind: SyntaxKind) -> bool {
node.children_with_tokens()
.filter_map(|e| e.into_token())
.any(|t| t.kind() == kind)
}
#[inline]
fn find_name_token(node: &SyntaxNode) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(|e| e.into_token())
.find(|t| is_name_token(t.kind()))
}
macro_rules! has_token_method {
($name:ident, $kind:ident) => {
#[doc = concat!("Check if this node has the `", stringify!($kind), "` token.")]
pub fn $name(&self) -> bool {
has_token(&self.0, SyntaxKind::$kind)
}
};
($name:ident, $kind:ident, $example:literal) => {
#[doc = concat!("Check if this node has the `", stringify!($kind), "` token (e.g., `", $example, "`).")]
pub fn $name(&self) -> bool {
has_token(&self.0, SyntaxKind::$kind)
}
};
}
macro_rules! first_child_method {
($name:ident, $type:ident) => {
#[doc = concat!("Get the first `", stringify!($type), "` child of this node.")]
pub fn $name(&self) -> Option<$type> {
self.0.children().find_map($type::cast)
}
};
}
macro_rules! children_method {
($name:ident, $type:ident) => {
#[doc = concat!("Get all `", stringify!($type), "` children of this node.")]
pub fn $name(&self) -> impl Iterator<Item = $type> + '_ {
self.0.children().filter_map($type::cast)
}
};
}
macro_rules! children_vec_method {
($name:ident, $type:ident) => {
#[doc = concat!("Get all `", stringify!($type), "` children of this node as a Vec.")]
pub fn $name(&self) -> Vec<$type> {
self.0.children().filter_map($type::cast).collect()
}
};
}
macro_rules! descendants_method {
($name:ident, $type:ident) => {
#[doc = concat!("Get all `", stringify!($type), "` descendants of this node.")]
pub fn $name(&self) -> impl Iterator<Item = $type> + '_ {
self.0.descendants().filter_map($type::cast)
}
};
($name:ident, $type:ident, $doc:literal) => {
#[doc = $doc]
pub fn $name(&self) -> impl Iterator<Item = $type> + '_ {
self.0.descendants().filter_map($type::cast)
}
};
}
macro_rules! child_after_keyword_method {
($name:ident, $type:ident, $keyword:ident, $doc:literal) => {
#[doc = $doc]
pub fn $name(&self) -> Option<$type> {
let mut seen_keyword = false;
for child in self.0.children_with_tokens() {
match child {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::$keyword => {
seen_keyword = true;
}
rowan::NodeOrToken::Node(n) if seen_keyword => {
if let Some(result) = $type::cast(n) {
return Some(result);
}
}
_ => {}
}
}
None
}
};
}
macro_rules! body_members_method {
() => {
pub fn members(&self) -> impl Iterator<Item = NamespaceMember> + '_ {
self.body()
.into_iter()
.flat_map(|body| body.members().collect::<Vec<_>>())
}
};
}
macro_rules! find_token_kind_method {
($name:ident, [$($kind:ident),+ $(,)?], $doc:literal) => {
#[doc = $doc]
pub fn $name(&self) -> Option<SyntaxKind> {
self.0
.children_with_tokens()
.filter_map(|e| e.into_token())
.find(|t| matches!(t.kind(), $(SyntaxKind::$kind)|+))
.map(|t| t.kind())
}
};
}
macro_rules! source_target_pair {
($source:ident, $target:ident, $iter_method:ident, $type:ident) => {
#[doc = concat!("Get the first `", stringify!($type), "` (source).")]
pub fn $source(&self) -> Option<$type> {
self.$iter_method().next()
}
#[doc = concat!("Get the second `", stringify!($type), "` (target).")]
pub fn $target(&self) -> Option<$type> {
self.$iter_method().nth(1)
}
};
}
macro_rules! token_to_enum_method {
($name:ident, $enum_type:ident, [$($token:ident => $variant:ident),+ $(,)?]) => {
pub fn $name(&self) -> Option<$enum_type> {
for token in self.0.children_with_tokens().filter_map(|e| e.into_token()) {
match token.kind() {
$(SyntaxKind::$token => return Some($enum_type::$variant),)+
_ => {}
}
}
None
}
};
}
macro_rules! prefix_metadata_method {
() => {
pub fn prefix_metadata(&self) -> Vec<PrefixMetadata> {
collect_prefix_metadata(&self.0)
}
};
}
fn collect_prefix_metadata(node: &SyntaxNode) -> Vec<PrefixMetadata> {
let mut result = Vec::new();
let mut current = node.prev_sibling();
while let Some(sibling) = current {
if sibling.kind() == SyntaxKind::PREFIX_METADATA {
if let Some(pm) = PrefixMetadata::cast(sibling.clone()) {
result.push(pm);
}
current = sibling.prev_sibling();
} else {
break;
}
}
result.reverse();
result
}
fn split_at_keyword<T: AstNode>(node: &SyntaxNode, keyword: SyntaxKind) -> (Vec<T>, Vec<T>) {
let mut before = Vec::new();
let mut after = Vec::new();
let mut found_keyword = false;
for elem in node.children_with_tokens() {
if let Some(token) = elem.as_token() {
if token.kind() == keyword {
found_keyword = true;
}
} else if let Some(child) = elem.as_node() {
if let Some(item) = T::cast(child.clone()) {
if found_keyword {
after.push(item);
} else {
before.push(item);
}
}
}
}
(before, after)
}
pub trait AstNode: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(node: SyntaxNode) -> Option<Self>;
fn syntax(&self) -> &SyntaxNode;
fn descendants<T: AstNode>(&self) -> impl Iterator<Item = T> {
self.syntax().descendants().filter_map(T::cast)
}
fn doc_comment(&self) -> Option<String> {
extract_doc_comment(self.syntax())
}
}
pub fn extract_doc_comment(node: &SyntaxNode) -> Option<String> {
let mut comments = Vec::new();
let mut current = node.prev_sibling_or_token();
while let Some(node_or_token) = current {
match node_or_token {
rowan::NodeOrToken::Token(ref t) => {
match t.kind() {
SyntaxKind::WHITESPACE => {
current = t.prev_sibling_or_token();
}
SyntaxKind::BLOCK_COMMENT => {
let text = t.text();
let content = text
.strip_prefix("/*")
.and_then(|s| s.strip_suffix("*/"))
.map(clean_doc_comment)
.unwrap_or_default();
if !content.is_empty() {
comments.push(content);
}
break;
}
SyntaxKind::LINE_COMMENT => {
let text = t.text();
let content = text.strip_prefix("//").unwrap_or(text).trim();
if !content.is_empty() {
comments.push(content.to_string());
}
current = t.prev_sibling_or_token();
}
_ => break, }
}
rowan::NodeOrToken::Node(ref n) => {
if n.kind() == SyntaxKind::COMMENT_ELEMENT {
for child in n.children_with_tokens() {
if let rowan::NodeOrToken::Token(t) = child {
if t.kind() == SyntaxKind::BLOCK_COMMENT {
let text = t.text();
let content = text
.strip_prefix("/*")
.and_then(|s| s.strip_suffix("*/"))
.map(clean_doc_comment)
.unwrap_or_default();
if !content.is_empty() {
comments.push(content);
}
break;
}
}
}
break;
} else {
break;
}
}
}
}
if comments.is_empty() {
return None;
}
comments.reverse();
Some(comments.join("\n"))
}
fn clean_doc_comment(s: &str) -> String {
s.lines()
.map(|line| {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix('*') {
rest.trim_start().to_string()
} else {
trimmed.to_string()
}
})
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join("\n")
}
pub trait AstToken: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(token: SyntaxToken) -> Option<Self>;
fn syntax(&self) -> &SyntaxToken;
fn text(&self) -> &str {
self.syntax().text()
}
}
macro_rules! ast_node {
($name:ident, $kind:ident) => {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(pub(crate) SyntaxNode);
impl AstNode for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(node: SyntaxNode) -> Option<Self> {
if Self::can_cast(node.kind()) {
Some(Self(node))
} else {
None
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
};
}
mod elements;
mod expressions;
mod namespace;
mod relationships;
pub use self::elements::*;
pub use self::expressions::*;
pub use self::namespace::*;
pub use self::relationships::*;
#[cfg(test)]
mod tests;