use rowan::{NodeOrToken, TextRange};
use super::{AstNode, child, children};
use crate::bib::syntax::{SyntaxKind, SyntaxNode};
macro_rules! ast_node {
($(#[$meta:meta])* $name:ident, $kind:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name {
syntax: SyntaxNode,
}
impl AstNode for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then_some(Self { syntax })
}
fn syntax(&self) -> &SyntaxNode {
&self.syntax
}
}
};
}
ast_node!(
Entry, ENTRY
);
ast_node!(
StringEntry, STRING_ENTRY
);
ast_node!(
Field, FIELD
);
ast_node!(
Value, VALUE
);
ast_node!(
EntryType, ENTRY_TYPE
);
ast_node!(
Key, KEY
);
ast_node!(
FieldName, FIELD_NAME
);
pub(crate) fn joined_words(node: &SyntaxNode) -> String {
let mut text = String::new();
for element in node.children_with_tokens() {
if let NodeOrToken::Token(token) = element
&& matches!(token.kind(), SyntaxKind::WORD | SyntaxKind::NUMBER)
{
text.push_str(token.text());
}
}
text
}
fn nonempty_words(node: &SyntaxNode) -> Option<String> {
let text = joined_words(node);
(!text.is_empty()).then_some(text)
}
impl EntryType {
pub fn text(&self) -> Option<String> {
nonempty_words(&self.syntax)
}
}
impl Key {
pub fn text(&self) -> Option<String> {
nonempty_words(&self.syntax)
}
}
impl FieldName {
pub fn text(&self) -> Option<String> {
nonempty_words(&self.syntax)
}
}
impl Entry {
pub fn entry_type(&self) -> Option<String> {
child::<EntryType>(&self.syntax)?.text()
}
pub fn cite_key(&self) -> Option<(String, TextRange)> {
let key = child::<Key>(&self.syntax)?;
key.text().map(|text| (text, key.syntax.text_range()))
}
pub fn fields(&self) -> impl Iterator<Item = Field> {
children::<Field>(&self.syntax)
}
}
impl StringEntry {
pub fn def_name(&self) -> Option<(String, TextRange)> {
let name = child::<Field>(&self.syntax)?.name_node()?;
name.text().map(|text| (text, name.syntax.text_range()))
}
}
impl Field {
pub fn name_node(&self) -> Option<FieldName> {
child::<FieldName>(&self.syntax)
}
pub fn name(&self) -> Option<String> {
self.name_node()?.text()
}
pub fn value(&self) -> Option<Value> {
child::<Value>(&self.syntax)
}
}
impl Value {
pub fn macro_uses(&self) -> impl Iterator<Item = (String, TextRange)> {
macro_uses_of(&self.syntax)
}
}
pub(crate) fn macro_uses_of(node: &SyntaxNode) -> impl Iterator<Item = (String, TextRange)> {
node.children()
.filter(|n| n.kind() == SyntaxKind::LITERAL)
.filter_map(|literal| {
let token = literal
.children_with_tokens()
.filter_map(|e| e.into_token())
.find(|t| matches!(t.kind(), SyntaxKind::WORD | SyntaxKind::NUMBER))?;
(token.kind() == SyntaxKind::WORD)
.then(|| (token.text().to_string(), literal.text_range()))
})
}