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]);