use rowan::{ast::AstNode, NodeOrToken};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
#[allow(non_camel_case_types)]
#[repr(u16)]
pub enum SyntaxKind {
WHITESPACE,
JUNK,
L_DELIM,
R_DELIM,
L_CURLY,
R_CURLY,
COMMA,
POUND,
QUOTE,
EQ,
TYPE,
WORD,
NAME,
INTEGER,
NBSP,
ACCENT_NAME,
COMMAND_NAME,
PREAMBLE,
STRING,
ENTRY,
FIELD,
VALUE,
LITERAL,
JOIN,
ACCENT,
COMMAND,
CURLY_GROUP,
QUOTE_GROUP,
ROOT,
}
pub use SyntaxKind::*;
impl From<SyntaxKind> for rowan::SyntaxKind {
fn from(kind: SyntaxKind) -> Self {
Self(kind as u16)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub enum Lang {}
impl rowan::Language for Lang {
type Kind = SyntaxKind;
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
assert!(raw.0 <= SyntaxKind::ROOT as u16);
unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
}
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
kind.into()
}
}
pub type SyntaxNode = rowan::SyntaxNode<Lang>;
pub type SyntaxToken = rowan::SyntaxToken<Lang>;
pub type SyntaxElement = rowan::SyntaxElement<Lang>;
macro_rules! ast_node {
(name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => {
#[derive(Clone)]
pub struct $name {
node: SyntaxNode,
}
impl AstNode for $name {
type Language = Lang;
fn can_cast(kind: SyntaxKind) -> bool {
match kind {
$($kind => true,)+
_ => false,
}
}
fn cast(node: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match node.kind() {
$($kind => Some(Self { node}),)+
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.node
}
}
$(
impl $trait for $name { }
)*
};
}
macro_rules! ast_node_enum {
(name: $name:ident, variants: [$($variant:ident),+]) => {
#[derive(Clone)]
pub enum $name {
$($variant($variant),)*
}
impl AstNode for $name {
type Language = Lang;
fn can_cast(kind: SyntaxKind) -> bool {
false $(|| $variant::can_cast(kind))+
}
fn cast(node: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))*
}
fn syntax(&self) -> &SyntaxNode {
match self {
$(Self::$variant(node) => node.syntax(),)*
}
}
}
$(
impl From<$variant> for $name {
fn from(node: $variant) -> Self {
Self::$variant(node)
}
}
)*
};
}
pub trait HasType: AstNode<Language = Lang> {
fn type_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == TYPE)
}
}
pub trait HasDelims: AstNode<Language = Lang> {
fn left_delim_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == L_DELIM)
}
fn right_delim_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == R_DELIM)
}
}
pub trait HasName: AstNode<Language = Lang> {
fn name_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == NAME)
}
}
pub trait HasEq: AstNode<Language = Lang> {
fn eq_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == NAME)
}
}
pub trait HasComma: AstNode<Language = Lang> {
fn comma_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == COMMA)
}
}
pub trait HasPound: AstNode<Language = Lang> {
fn pound_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == POUND)
}
}
pub trait HasInteger: AstNode<Language = Lang> {
fn integer_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == INTEGER)
}
}
pub trait HasCommandName: AstNode<Language = Lang> {
fn command_name_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == COMMAND_NAME)
}
}
pub trait HasAccentName: AstNode<Language = Lang> {
fn accent_name_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == ACCENT_NAME)
}
}
pub trait HasWord: AstNode<Language = Lang> {
fn word_token(&self) -> Option<SyntaxToken> {
self.syntax()
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.find(|token| token.kind() == WORD)
}
}
pub trait HasValue: AstNode<Language = Lang> {
fn value(&self) -> Option<Value> {
self.syntax().children().find_map(Value::cast)
}
}
ast_node!(name: Root, kinds: [ROOT], traits: []);
impl Root {
pub fn strings(&self) -> impl Iterator<Item = StringDef> {
self.syntax().children().filter_map(StringDef::cast)
}
pub fn entries(&self) -> impl Iterator<Item = Entry> {
self.syntax().children().filter_map(Entry::cast)
}
pub fn find_entry(&self, name: &str) -> Option<Entry> {
self.entries().find(|entry| {
entry
.name_token()
.map_or(false, |token| token.text() == name)
})
}
}
ast_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]);
ast_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]);
ast_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]);
impl Entry {
pub fn fields(&self) -> impl Iterator<Item = Field> {
self.syntax().children().filter_map(Field::cast)
}
}
ast_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]);
ast_node_enum!(name: Value, variants: [Literal, CurlyGroup, QuoteGroup, Join, Accent, Command]);
ast_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]);
ast_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []);
ast_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []);
ast_node!(name: Join, kinds: [JOIN], traits: [HasPound]);
impl Join {
pub fn left_value(&self) -> Option<Value> {
self.syntax().children().find_map(Value::cast)
}
pub fn right_value(&self) -> Option<Value> {
self.syntax().children().filter_map(Value::cast).nth(1)
}
}
ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]);
ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]);