1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7#[repr(u16)]
8#[expect(non_camel_case_types)]
9pub enum SyntaxKind {
10 WHITESPACE = 0,
13 NEWLINE,
15 LINE_COMMENT,
17 BLOCK_COMMENT,
19
20 KW_INCLUDE,
22 KW_EXTERNAL,
23 KW_VAR,
24 KW_CONST,
25 KW_LIST,
26 KW_TEMP,
27 KW_RETURN,
28 KW_REF,
29 KW_TRUE,
30 KW_FALSE,
31 KW_NOT,
32 KW_AND,
33 KW_OR,
34 KW_MOD,
35 KW_HAS,
36 KW_HASNT,
37 KW_ELSE,
38 KW_FUNCTION,
39 KW_STOPPING,
40 KW_CYCLE,
41 KW_SHUFFLE,
42 KW_ONCE,
43 KW_DONE,
44 KW_END,
45 KW_TODO,
46
47 EQ,
50 PLUS_EQ,
52 MINUS_EQ,
54 EQ_EQ,
56 BANG_EQ,
58 LT,
60 GT,
62 LT_EQ,
64 GT_EQ,
66 AMP,
68 AMP_AMP,
70 PLUS,
72 MINUS,
74 STAR,
76 SLASH,
78 PERCENT,
80 CARET,
82 BANG,
84 QUESTION,
86 BANG_QUESTION,
88 DOLLAR,
90 L_PAREN,
92 R_PAREN,
94 L_BRACE,
96 R_BRACE,
98 L_BRACKET,
100 R_BRACKET,
102 PIPE,
104 COMMA,
106 DOT,
108 COLON,
110 HASH,
112 TILDE,
114 BACKSLASH,
116
117 GLUE,
120 DIVERT,
122 THREAD,
124 TUNNEL_ONWARDS,
126
127 INTEGER,
130 FLOAT,
132 QUOTE,
134 STRING_TEXT,
136 STRING_ESCAPE,
138 IDENT,
140 ERROR_TOKEN,
142 EOF,
144
145 SOURCE_FILE,
147 INCLUDE_STMT,
148 FILE_PATH,
149 EXTERNAL_DECL,
150 KNOT_DEF,
151 KNOT_HEADER,
152 KNOT_BODY,
153 KNOT_PARAMS,
154 KNOT_PARAM_DECL,
155 STITCH_DEF,
156 STITCH_HEADER,
157 STITCH_BODY,
158 EMPTY_LINE,
159 AUTHOR_WARNING,
160 LOGIC_LINE,
161 CONTENT_LINE,
162 TAG_LINE,
163 STRAY_CLOSING_BRACE,
164 RETURN_STMT,
165 TEMP_DECL,
166 ASSIGNMENT,
167 MIXED_CONTENT,
168 TEXT,
169 ESCAPE,
170 GLUE_NODE,
171 CHOICE,
172 CHOICE_BULLETS,
173 LABEL,
174 CHOICE_CONDITION,
175 CHOICE_START_CONTENT,
176 CHOICE_BRACKET_CONTENT,
177 CHOICE_INNER_CONTENT,
178 GATHER,
179 GATHER_DASHES,
180 TAGS,
181 TAG,
182 INLINE_LOGIC,
183 MULTILINE_BLOCK,
184 SEQUENCE_WITH_ANNOTATION,
185 SEQUENCE_SYMBOL_ANNOTATION,
186 SEQUENCE_WORD_ANNOTATION,
187 INLINE_BRANCHES_SEQ,
188 MULTILINE_BRANCHES_SEQ,
189 MULTILINE_BRANCH_SEQ,
190 BRANCH_CONTENT,
191 CONDITIONAL_WITH_EXPR,
192 BRANCHLESS_COND_BODY,
193 ELSE_BRANCH,
194 INLINE_BRANCHES_COND,
195 MULTILINE_BRANCHES_COND,
196 MULTILINE_CONDITIONAL,
197 MULTILINE_BRANCH_COND,
198 MULTILINE_BRANCH_BODY,
199 IMPLICIT_SEQUENCE,
200 INNER_EXPRESSION,
201 PREFIX_EXPR,
202 POSTFIX_EXPR,
203 INFIX_EXPR,
204 PAREN_EXPR,
205 FUNCTION_CALL,
206 ARG_LIST,
207 DIVERT_TARGET_EXPR,
208 LIST_EXPR,
209 DIVERT_NODE,
210 SIMPLE_DIVERT,
211 DIVERT_TARGET_WITH_ARGS,
212 THREAD_START,
213 TUNNEL_ONWARDS_NODE,
214 TUNNEL_CALL_NODE,
215 IDENTIFIER,
216 PATH,
217 VAR_DECL,
218 CONST_DECL,
219 LIST_DECL,
220 LIST_DEF,
221 LIST_MEMBER,
222 LIST_MEMBER_ON,
223 LIST_MEMBER_OFF,
224 FUNCTION_PARAM_LIST,
225 INTEGER_LIT,
226 FLOAT_LIT,
227 STRING_LIT,
228 BOOLEAN_LIT,
229 ERROR,
230
231 #[doc(hidden)]
233 __LAST,
234}
235
236impl SyntaxKind {
237 #[must_use]
239 pub fn is_token(self) -> bool {
240 matches!(
241 self,
242 Self::WHITESPACE
243 | Self::NEWLINE
244 | Self::LINE_COMMENT
245 | Self::BLOCK_COMMENT
246 | Self::KW_INCLUDE
247 | Self::KW_EXTERNAL
248 | Self::KW_VAR
249 | Self::KW_CONST
250 | Self::KW_LIST
251 | Self::KW_TEMP
252 | Self::KW_RETURN
253 | Self::KW_REF
254 | Self::KW_TRUE
255 | Self::KW_FALSE
256 | Self::KW_NOT
257 | Self::KW_AND
258 | Self::KW_OR
259 | Self::KW_MOD
260 | Self::KW_HAS
261 | Self::KW_HASNT
262 | Self::KW_ELSE
263 | Self::KW_FUNCTION
264 | Self::KW_STOPPING
265 | Self::KW_CYCLE
266 | Self::KW_SHUFFLE
267 | Self::KW_ONCE
268 | Self::KW_DONE
269 | Self::KW_END
270 | Self::KW_TODO
271 | Self::EQ
272 | Self::PLUS_EQ
273 | Self::MINUS_EQ
274 | Self::EQ_EQ
275 | Self::BANG_EQ
276 | Self::LT
277 | Self::GT
278 | Self::LT_EQ
279 | Self::GT_EQ
280 | Self::AMP
281 | Self::AMP_AMP
282 | Self::PLUS
283 | Self::MINUS
284 | Self::STAR
285 | Self::SLASH
286 | Self::PERCENT
287 | Self::CARET
288 | Self::BANG
289 | Self::QUESTION
290 | Self::BANG_QUESTION
291 | Self::DOLLAR
292 | Self::L_PAREN
293 | Self::R_PAREN
294 | Self::L_BRACE
295 | Self::R_BRACE
296 | Self::L_BRACKET
297 | Self::R_BRACKET
298 | Self::PIPE
299 | Self::COMMA
300 | Self::DOT
301 | Self::COLON
302 | Self::HASH
303 | Self::TILDE
304 | Self::BACKSLASH
305 | Self::GLUE
306 | Self::DIVERT
307 | Self::THREAD
308 | Self::TUNNEL_ONWARDS
309 | Self::INTEGER
310 | Self::FLOAT
311 | Self::QUOTE
312 | Self::STRING_TEXT
313 | Self::STRING_ESCAPE
314 | Self::IDENT
315 | Self::ERROR_TOKEN
316 | Self::EOF
317 )
318 }
319
320 #[must_use]
322 pub fn is_node(self) -> bool {
323 !self.is_token() && self != Self::__LAST
324 }
325
326 #[must_use]
329 pub fn is_trivia(self) -> bool {
330 matches!(
331 self,
332 Self::WHITESPACE | Self::LINE_COMMENT | Self::BLOCK_COMMENT
333 )
334 }
335
336 #[must_use]
338 pub fn is_keyword(self) -> bool {
339 matches!(
340 self,
341 Self::KW_INCLUDE
342 | Self::KW_EXTERNAL
343 | Self::KW_VAR
344 | Self::KW_CONST
345 | Self::KW_LIST
346 | Self::KW_TEMP
347 | Self::KW_RETURN
348 | Self::KW_REF
349 | Self::KW_TRUE
350 | Self::KW_FALSE
351 | Self::KW_NOT
352 | Self::KW_AND
353 | Self::KW_OR
354 | Self::KW_MOD
355 | Self::KW_HAS
356 | Self::KW_HASNT
357 | Self::KW_ELSE
358 | Self::KW_FUNCTION
359 | Self::KW_STOPPING
360 | Self::KW_CYCLE
361 | Self::KW_SHUFFLE
362 | Self::KW_ONCE
363 | Self::KW_DONE
364 | Self::KW_END
365 | Self::KW_TODO
366 )
367 }
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
372pub enum InkLanguage {}
373
374impl rowan::Language for InkLanguage {
375 type Kind = SyntaxKind;
376
377 fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
378 assert!(raw.0 < SyntaxKind::__LAST as u16);
379 #[expect(unsafe_code, reason = "repr(u16) transmute with bounds check")]
382 unsafe {
383 std::mem::transmute::<u16, SyntaxKind>(raw.0)
384 }
385 }
386
387 fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
388 rowan::SyntaxKind(kind as u16)
389 }
390}
391
392pub type SyntaxNode = rowan::SyntaxNode<InkLanguage>;
394pub type SyntaxToken = rowan::SyntaxToken<InkLanguage>;
396pub type SyntaxElement = rowan::SyntaxElement<InkLanguage>;
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use rowan::Language;
403
404 #[test]
405 fn roundtrip_through_rowan() {
406 let mut i = 0u16;
408 loop {
409 if i == SyntaxKind::__LAST as u16 {
410 break;
411 }
412 let raw = rowan::SyntaxKind(i);
413 let kind = InkLanguage::kind_from_raw(raw);
414 let back = InkLanguage::kind_to_raw(kind);
415 assert_eq!(raw, back, "roundtrip failed for discriminant {i}");
416 i += 1;
417 }
418 }
419
420 #[test]
421 fn token_node_partition() {
422 let mut i = 0u16;
424 loop {
425 if i == SyntaxKind::__LAST as u16 {
426 break;
427 }
428 let kind = InkLanguage::kind_from_raw(rowan::SyntaxKind(i));
429 assert!(
430 kind.is_token() ^ kind.is_node(),
431 "{kind:?} is neither token nor node (or both)"
432 );
433 i += 1;
434 }
435 }
436
437 #[test]
438 fn trivia_is_subset_of_tokens() {
439 let mut i = 0u16;
440 loop {
441 if i == SyntaxKind::__LAST as u16 {
442 break;
443 }
444 let kind = InkLanguage::kind_from_raw(rowan::SyntaxKind(i));
445 if kind.is_trivia() {
446 assert!(kind.is_token(), "{kind:?} is trivia but not a token");
447 }
448 i += 1;
449 }
450 }
451
452 #[test]
453 fn newline_is_not_trivia() {
454 assert!(!SyntaxKind::NEWLINE.is_trivia());
455 assert!(SyntaxKind::NEWLINE.is_token());
456 }
457
458 #[test]
459 fn keywords_are_tokens() {
460 let keywords = [
461 SyntaxKind::KW_INCLUDE,
462 SyntaxKind::KW_EXTERNAL,
463 SyntaxKind::KW_VAR,
464 SyntaxKind::KW_CONST,
465 SyntaxKind::KW_LIST,
466 SyntaxKind::KW_TEMP,
467 SyntaxKind::KW_RETURN,
468 SyntaxKind::KW_REF,
469 SyntaxKind::KW_TRUE,
470 SyntaxKind::KW_FALSE,
471 SyntaxKind::KW_NOT,
472 SyntaxKind::KW_AND,
473 SyntaxKind::KW_OR,
474 SyntaxKind::KW_MOD,
475 SyntaxKind::KW_HAS,
476 SyntaxKind::KW_HASNT,
477 SyntaxKind::KW_ELSE,
478 SyntaxKind::KW_FUNCTION,
479 SyntaxKind::KW_STOPPING,
480 SyntaxKind::KW_CYCLE,
481 SyntaxKind::KW_SHUFFLE,
482 SyntaxKind::KW_ONCE,
483 SyntaxKind::KW_DONE,
484 SyntaxKind::KW_END,
485 SyntaxKind::KW_TODO,
486 ];
487 for kw in keywords {
488 assert!(kw.is_token(), "{kw:?} should be a token");
489 assert!(kw.is_keyword(), "{kw:?} should be a keyword");
490 }
491 }
492
493 #[test]
494 fn non_keywords_are_not_keywords() {
495 assert!(!SyntaxKind::IDENT.is_keyword());
496 assert!(!SyntaxKind::PLUS.is_keyword());
497 assert!(!SyntaxKind::SOURCE_FILE.is_keyword());
498 }
499}