use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
pub trait AstToken {
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized;
fn cast(syntax: SyntaxToken) -> Option<Self>
where
Self: Sized;
fn syntax(&self) -> &SyntaxToken;
fn text(&self) -> &str {
self.syntax().text()
}
}
pub fn child_token<T: AstToken>(parent: &SyntaxNode) -> Option<T> {
parent
.children_with_tokens()
.filter_map(|el| el.into_token())
.find_map(T::cast)
}
macro_rules! ast_token {
($(#[$meta:meta])* $name:ident, |$kind:ident| $can_cast:expr) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxToken);
impl AstToken for $name {
fn can_cast($kind: SyntaxKind) -> bool {
$can_cast
}
fn cast(syntax: SyntaxToken) -> Option<Self> {
Self::can_cast(syntax.kind()).then_some(Self(syntax))
}
fn syntax(&self) -> &SyntaxToken {
&self.0
}
}
};
}
ast_token!(
Ident,
|kind| kind == SyntaxKind::IDENT
);
ast_token!(
Operator,
|kind| kind.is_operator()
);
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
fn first_token(src: &str, kind: SyntaxKind) -> SyntaxToken {
parse(src)
.cst
.descendants_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == kind)
.expect("token present")
}
#[test]
fn ident_casts_only_idents() {
let ident = first_token("foo + 1\n", SyntaxKind::IDENT);
assert_eq!(Ident::cast(ident).unwrap().text(), "foo");
let plus = first_token("foo + 1\n", SyntaxKind::PLUS);
assert!(Ident::cast(plus).is_none());
}
#[test]
fn operator_casts_the_is_operator_set() {
let plus = first_token("a + b\n", SyntaxKind::PLUS);
assert_eq!(Operator::cast(plus).unwrap().text(), "+");
let ident = first_token("a + b\n", SyntaxKind::IDENT);
assert!(Operator::cast(ident).is_none());
}
#[test]
fn syntax_round_trips_to_the_raw_token() {
let raw = first_token("x\n", SyntaxKind::IDENT);
let typed = Ident::cast(raw.clone()).unwrap();
assert_eq!(typed.syntax(), &raw);
}
}