Skip to main content

texlab/syntax/
bibtex.rs

1use rowan::{ast::AstNode, NodeOrToken};
2
3#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
4#[allow(non_camel_case_types)]
5#[repr(u16)]
6pub enum SyntaxKind {
7    WHITESPACE,
8    JUNK,
9    L_DELIM,
10    R_DELIM,
11    L_CURLY,
12    R_CURLY,
13    COMMA,
14    POUND,
15    QUOTE,
16    EQ,
17    TYPE,
18    WORD,
19    NAME,
20    INTEGER,
21    NBSP,
22    ACCENT_NAME,
23    COMMAND_NAME,
24
25    PREAMBLE,
26    STRING,
27    ENTRY,
28    FIELD,
29    VALUE,
30    LITERAL,
31    JOIN,
32    ACCENT,
33    COMMAND,
34    CURLY_GROUP,
35    QUOTE_GROUP,
36    ROOT,
37}
38
39pub use SyntaxKind::*;
40
41impl From<SyntaxKind> for rowan::SyntaxKind {
42    fn from(kind: SyntaxKind) -> Self {
43        Self(kind as u16)
44    }
45}
46
47#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
48pub enum Lang {}
49
50impl rowan::Language for Lang {
51    type Kind = SyntaxKind;
52
53    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
54        assert!(raw.0 <= SyntaxKind::ROOT as u16);
55        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
56    }
57
58    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
59        kind.into()
60    }
61}
62
63pub type SyntaxNode = rowan::SyntaxNode<Lang>;
64
65pub type SyntaxToken = rowan::SyntaxToken<Lang>;
66
67pub type SyntaxElement = rowan::SyntaxElement<Lang>;
68
69macro_rules! ast_node {
70    (name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => {
71        #[derive(Clone)]
72        pub struct $name {
73            node: SyntaxNode,
74        }
75
76        impl AstNode for $name {
77            type Language = Lang;
78
79            fn can_cast(kind: SyntaxKind) -> bool {
80                match kind {
81                    $($kind => true,)+
82                    _ => false,
83                }
84            }
85
86            fn cast(node: SyntaxNode) -> Option<Self>
87            where
88                Self: Sized,
89            {
90                match node.kind() {
91                    $($kind => Some(Self { node}),)+
92                    _ => None,
93                }
94            }
95
96            fn syntax(&self) -> &SyntaxNode {
97                &self.node
98            }
99        }
100
101        $(
102            impl $trait for $name { }
103        )*
104    };
105}
106
107macro_rules! ast_node_enum {
108    (name: $name:ident, variants: [$($variant:ident),+]) => {
109        #[derive(Clone)]
110        pub enum $name {
111            $($variant($variant),)*
112        }
113
114        impl AstNode for $name {
115            type Language = Lang;
116
117            fn can_cast(kind: SyntaxKind) -> bool {
118                false $(|| $variant::can_cast(kind))+
119            }
120
121            fn cast(node: SyntaxNode) -> Option<Self>
122            where
123                Self: Sized,
124            {
125                None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))*
126            }
127
128            fn syntax(&self) -> &SyntaxNode {
129                match self {
130                    $(Self::$variant(node) => node.syntax(),)*
131                }
132            }
133        }
134
135        $(
136            impl From<$variant> for $name {
137                fn from(node: $variant) -> Self {
138                    Self::$variant(node)
139                }
140            }
141        )*
142    };
143}
144
145pub trait HasType: AstNode<Language = Lang> {
146    fn type_token(&self) -> Option<SyntaxToken> {
147        self.syntax()
148            .children_with_tokens()
149            .filter_map(NodeOrToken::into_token)
150            .find(|token| token.kind() == TYPE)
151    }
152}
153
154pub trait HasDelims: AstNode<Language = Lang> {
155    fn left_delim_token(&self) -> Option<SyntaxToken> {
156        self.syntax()
157            .children_with_tokens()
158            .filter_map(NodeOrToken::into_token)
159            .find(|token| token.kind() == L_DELIM)
160    }
161
162    fn right_delim_token(&self) -> Option<SyntaxToken> {
163        self.syntax()
164            .children_with_tokens()
165            .filter_map(NodeOrToken::into_token)
166            .find(|token| token.kind() == R_DELIM)
167    }
168}
169
170pub trait HasName: AstNode<Language = Lang> {
171    fn name_token(&self) -> Option<SyntaxToken> {
172        self.syntax()
173            .children_with_tokens()
174            .filter_map(NodeOrToken::into_token)
175            .find(|token| token.kind() == NAME)
176    }
177}
178
179pub trait HasEq: AstNode<Language = Lang> {
180    fn eq_token(&self) -> Option<SyntaxToken> {
181        self.syntax()
182            .children_with_tokens()
183            .filter_map(NodeOrToken::into_token)
184            .find(|token| token.kind() == NAME)
185    }
186}
187
188pub trait HasComma: AstNode<Language = Lang> {
189    fn comma_token(&self) -> Option<SyntaxToken> {
190        self.syntax()
191            .children_with_tokens()
192            .filter_map(NodeOrToken::into_token)
193            .find(|token| token.kind() == COMMA)
194    }
195}
196
197pub trait HasPound: AstNode<Language = Lang> {
198    fn pound_token(&self) -> Option<SyntaxToken> {
199        self.syntax()
200            .children_with_tokens()
201            .filter_map(NodeOrToken::into_token)
202            .find(|token| token.kind() == POUND)
203    }
204}
205
206pub trait HasInteger: AstNode<Language = Lang> {
207    fn integer_token(&self) -> Option<SyntaxToken> {
208        self.syntax()
209            .children_with_tokens()
210            .filter_map(NodeOrToken::into_token)
211            .find(|token| token.kind() == INTEGER)
212    }
213}
214
215pub trait HasCommandName: AstNode<Language = Lang> {
216    fn command_name_token(&self) -> Option<SyntaxToken> {
217        self.syntax()
218            .children_with_tokens()
219            .filter_map(NodeOrToken::into_token)
220            .find(|token| token.kind() == COMMAND_NAME)
221    }
222}
223
224pub trait HasAccentName: AstNode<Language = Lang> {
225    fn accent_name_token(&self) -> Option<SyntaxToken> {
226        self.syntax()
227            .children_with_tokens()
228            .filter_map(NodeOrToken::into_token)
229            .find(|token| token.kind() == ACCENT_NAME)
230    }
231}
232
233pub trait HasWord: AstNode<Language = Lang> {
234    fn word_token(&self) -> Option<SyntaxToken> {
235        self.syntax()
236            .children_with_tokens()
237            .filter_map(NodeOrToken::into_token)
238            .find(|token| token.kind() == WORD)
239    }
240}
241
242pub trait HasValue: AstNode<Language = Lang> {
243    fn value(&self) -> Option<Value> {
244        self.syntax().children().find_map(Value::cast)
245    }
246}
247
248ast_node!(name: Root, kinds: [ROOT], traits: []);
249
250impl Root {
251    pub fn strings(&self) -> impl Iterator<Item = StringDef> {
252        self.syntax().children().filter_map(StringDef::cast)
253    }
254
255    pub fn entries(&self) -> impl Iterator<Item = Entry> {
256        self.syntax().children().filter_map(Entry::cast)
257    }
258
259    pub fn find_entry(&self, name: &str) -> Option<Entry> {
260        self.entries().find(|entry| {
261            entry
262                .name_token()
263                .map_or(false, |token| token.text() == name)
264        })
265    }
266}
267
268ast_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]);
269
270ast_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]);
271
272ast_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]);
273
274impl Entry {
275    pub fn fields(&self) -> impl Iterator<Item = Field> {
276        self.syntax().children().filter_map(Field::cast)
277    }
278}
279
280ast_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]);
281
282ast_node_enum!(name: Value, variants: [Literal,  CurlyGroup, QuoteGroup, Join, Accent, Command]);
283
284ast_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]);
285
286ast_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []);
287
288ast_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []);
289
290ast_node!(name: Join, kinds: [JOIN], traits: [HasPound]);
291
292impl Join {
293    pub fn left_value(&self) -> Option<Value> {
294        self.syntax().children().find_map(Value::cast)
295    }
296
297    pub fn right_value(&self) -> Option<Value> {
298        self.syntax().children().filter_map(Value::cast).nth(1)
299    }
300}
301
302ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]);
303
304ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]);