brink_syntax_native/syntax_kind.rs
1/// All syntactic constructs in the native `.brink` grammar.
2///
3/// This is a **peer** enum to `brink-syntax`'s ink-shaped `SyntaxKind` — its
4/// own discriminant space, sharing no numbering with the ink frontend (NF-1
5/// ruling, 2026-07-19: a new crate, not a co-located module, because the
6/// only reason to share `SyntaxKind` space was `AstPtr` interop, which the
7/// HIR admission contract's opaque `Provenance` already removed). Tokens
8/// (lexer output) and nodes (parser output) share one flat enum so `rowan`
9/// can store them in a single `u16` discriminant — see [`is_token`] /
10/// [`is_node`].
11///
12/// Scope: B0.5 (`docs/b0-sequencing.md` §B0.5) — the token set and
13/// error-resilient CST for the *ruled* native surface subset (NF-2:
14/// writer-sufficient, not the full charter). No HIR lowering happens in
15/// this crate; that is B0.6/B0.7/B0.8.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(u16)]
18#[expect(non_camel_case_types)]
19pub enum SyntaxKind {
20 // ── Trivia tokens ─────────────────────────────────────────────
21 /// Spaces and tabs (NOT newlines). A leading UTF-8 BOM is folded in here
22 /// too (lossless-roundtrip requirement — see lexer tests).
23 WHITESPACE = 0,
24 /// `\n` or `\r\n`.
25 NEWLINE,
26 /// `// ...` through end-of-line. Also `////+` (four or more slashes) —
27 /// Rust precedent: only *exactly* three slashes is a doc comment (see
28 /// [`Self::DOC_COMMENT_OUTER`]).
29 LINE_COMMENT,
30 /// `/* ... */` (may span lines; unterminated block comments run to EOF —
31 /// recorded as a parse error, never a panic).
32 BLOCK_COMMENT,
33 /// `/// ...` through end-of-line — exactly three slashes (a fourth
34 /// keeps it a plain [`Self::LINE_COMMENT`]). B0.6b
35 /// (`docs/decision-log.md` 2026-07-20): first-class on the native
36 /// surface — **not** trivia (see [`Self::is_trivia`]), since the parser
37 /// dispatches on this token to decide whether a contiguous run attaches
38 /// as a `DOC_COMMENT` CST node to the declaration it immediately
39 /// precedes (`parser::doc_comment`).
40 DOC_COMMENT_OUTER,
41 /// `//! ...` through end-of-line — the inner form (B0.6b, Rust `//!`
42 /// precedent, ink had no equivalent). A contiguous run at the very
43 /// start of a knot/flow/file body documents the *enclosing* container
44 /// rather than a following declaration. Also not trivia.
45 DOC_COMMENT_INNER,
46
47 // ── Keyword tokens — hard-reserved (Finding #1: if/match/else/as are
48 // reserved everywhere, Rust-style, not contextual like ink's T1b
49 // keywords — the charter doesn't rule this either way; RustScript's own
50 // north star reserves them globally, so this is the coherent default) ──
51 /// `flow` — story-time container (Coloring axis, charter §3).
52 KW_FLOW,
53 /// `fn` — expression-time container (Coloring axis, charter §3).
54 KW_FN,
55 KW_VAR,
56 KW_CONST,
57 /// `flags` — renamed LIST (charter §11).
58 KW_FLAGS,
59 KW_STRUCT,
60 KW_EXTERN,
61 KW_IMPORT,
62 KW_USE,
63 KW_MODULE,
64 /// `return` — leave this container; also the tunnel-return respelling's
65 /// first half (`return -> x`, charter §11).
66 KW_RETURN,
67 /// `ref` — ref-argument marker (kept from ink).
68 KW_REF,
69 /// `if` — word-annotated brace family member (charter §6) AND (Finding
70 /// #1) reserved as a code-ground keyword everywhere.
71 KW_IF,
72 /// `match` — word-annotated brace family member (charter §6).
73 KW_MATCH,
74 /// `else` — conditional else-arm AND a choice point's fallback branch
75 /// (charter §11: "a choice point's fallback is its else-branch").
76 KW_ELSE,
77 /// `as` — import/use aliasing (`use a::b as c`).
78 KW_AS,
79 KW_TRUE,
80 KW_FALSE,
81 /// `END` — divert target sentinel (kept verbatim, charter §11).
82 KW_END,
83 /// `DONE` — divert target sentinel (kept verbatim, charter §11).
84 KW_DONE,
85
86 // ── Punctuation / operator tokens ────────────────────────────
87 /// `=`
88 EQ,
89 /// `+=`
90 PLUS_EQ,
91 /// `-=`
92 MINUS_EQ,
93 /// `*=`
94 STAR_EQ,
95 /// `/=`
96 SLASH_EQ,
97 /// `==`
98 EQ_EQ,
99 /// `!=`
100 BANG_EQ,
101 /// `<`
102 LT,
103 /// `>`
104 GT,
105 /// `<=`
106 LT_EQ,
107 /// `>=`
108 GT_EQ,
109 /// `&`
110 AMP,
111 /// `&&`
112 AMP_AMP,
113 /// `+`
114 PLUS,
115 /// `-`. Also the entry-marker sigil (charter §6) and the choice-list
116 /// once-bullet-adjacent dash; the parser, not the lexer, decides which
117 /// role a given `-` plays from structural position.
118 MINUS,
119 /// `*`
120 STAR,
121 /// `/`
122 SLASH,
123 /// `%`
124 PERCENT,
125 /// `^`
126 CARET,
127 /// `!`
128 BANG,
129 /// `?`. Also half of the `{?` choice-point opener — the parser
130 /// recognizes the *adjacent* `L_BRACE QUESTION` pair, no compound token
131 /// (mirrors how `{if`/`{match`/`{~` are recognized: `{` is always plain
132 /// `L_BRACE`, disambiguation is a parser lookahead, not a lexer job).
133 QUESTION,
134 /// `(`
135 L_PAREN,
136 /// `)`
137 R_PAREN,
138 /// `{`
139 L_BRACE,
140 /// `}`
141 R_BRACE,
142 /// `[`
143 L_BRACKET,
144 /// `]`
145 R_BRACKET,
146 /// `|`. Two adjacent `PIPE`s are NOT compounded into a logical-or token
147 /// (mirrors brink-syntax precedent for `||`/`++`/`--`) — the parser
148 /// disambiguates `a || b` (logical or) from `|x|` (lambda params) by
149 /// expression-vs-lambda-head position, not lexical shape.
150 PIPE,
151 /// `,`
152 COMMA,
153 /// `.` — the intra-module separator (containers, fields, variants,
154 /// UFCS; charter §13.2).
155 DOT,
156 /// `:`
157 COLON,
158 /// `;` — recognized only as `use`'s optional statement terminator
159 /// (charter §13.2's literal example: `use story::market::{barter,
160 /// haggle};`). Not a general statement separator anywhere else in this
161 /// skeleton (Finding #6: no other declaration requires one).
162 SEMICOLON,
163 /// `::` — the module-wall separator (charter §13.2). Lexed as one
164 /// compound token so a bare `:` (used in inline `{if cond: …}` bodies)
165 /// never gets swallowed by a stray adjacent colon.
166 COLON_COLON,
167 /// `#` — tag opener (charter §11: tags kept).
168 HASH,
169 /// `~`. Also an alternation-family opener (`{~ }` shuffle, charter §6).
170 TILDE,
171 /// `\`
172 BACKSLASH,
173 /// `@`. A lone `@` outside the `@[` pair is not punctuation in this
174 /// grammar (mirrors the ink `AT_L_BRACKET` precedent) and is emitted as
175 /// `ERROR_TOKEN` so prose containing a bare `@` still round-trips
176 /// losslessly instead of being silently absorbed.
177 AT,
178
179 // ── Compound tokens ──────────────────────────────────────────
180 /// `@[` — annotation-line opener (charter §11 / NS-A2 lineage,
181 /// `docs/directive-annotations-spec.md` §5b). Only the *adjacent* pair
182 /// opens an annotation line.
183 AT_L_BRACKET,
184 /// `<>` — glue (kept, charter §11).
185 GLUE,
186 /// `->` — divert (kept verbatim, charter §11).
187 DIVERT,
188 /// `<-` — splice, valid only inside a choice point (charter §5).
189 THREAD,
190 /// `=>` — match-arm separator.
191 FAT_ARROW,
192
193 // ── Content tokens ───────────────────────────────────────────
194 /// Integer literal (digits only; no leading sign — unary `-` is a
195 /// separate `PREFIX_EXPR`).
196 INTEGER,
197 /// Float literal (`digits.digits`).
198 FLOAT,
199 /// `"` (opening or closing quote).
200 QUOTE,
201 /// Run of non-special characters inside a string literal.
202 STRING_TEXT,
203 /// Escape sequence inside a string (`\n`, `\t`, `\\`, `\"`).
204 STRING_ESCAPE,
205 /// Identifier: ASCII `[A-Za-z_][A-Za-z0-9_]*` (Finding #2: native
206 /// identifiers are ASCII-only in this skeleton — the charter's S4
207 /// casing partition (`snake_case`/`UpperCamel`) is an ASCII-shaped rule
208 /// and the ink Unicode identifier range table is ink-specific baggage
209 /// with no native ruling to inherit it from; widening later is
210 /// additive, not breaking).
211 IDENT,
212 /// Any byte/char the lexer could not classify — including a lone `@`,
213 /// unterminated block comments' unreachable tail (folded into
214 /// `BLOCK_COMMENT` itself, not this), and raw prose bytes at
215 /// declaration scope.
216 ERROR_TOKEN,
217 /// End of file (synthetic).
218 EOF,
219
220 // ── Node kinds — doc comments (B0.6b) ────────────────────────
221 /// A contiguous run of [`Self::DOC_COMMENT_OUTER`] tokens (the leading
222 /// child of the declaration node it documents) or
223 /// [`Self::DOC_COMMENT_INNER`] tokens (the leading child of the
224 /// enclosing knot/flow/file body it documents) — one node shape for
225 /// both variants; `ast::DocComment::is_inner` tells them apart by
226 /// inspecting which token kind the node's children carry
227 /// (`docs/native-surface-charter.md`'s doc-comment section).
228 DOC_COMMENT,
229
230 // ── Node kinds — top level & declarations ───────────────────
231 SOURCE_FILE,
232 /// `flow name(params) { … }` / nested `flow` = stitch (charter §4).
233 FLOW_DECL,
234 /// `fn name(params) { … }`.
235 FN_DECL,
236 /// Shared param-list shape for `FLOW_DECL`/`FN_DECL`.
237 PARAM_LIST,
238 /// One parameter: `ref`? `IDENT`.
239 PARAM,
240 /// `var name = expr`.
241 VAR_DECL,
242 /// `const name = expr`.
243 CONST_DECL,
244 /// `flags Name = (member), member, …` (charter §11).
245 FLAGS_DECL,
246 FLAGS_MEMBER_LIST,
247 /// One flag member; a parenthesized member is the default-on entry.
248 FLAGS_MEMBER,
249 /// `struct Name { field: type, … }` (charter §13.1's sibling; concrete
250 /// grammar shape here, no field-type semantics checked).
251 STRUCT_DECL,
252 STRUCT_FIELD,
253 /// `extern name(params)`.
254 EXTERN_DECL,
255 /// `use path::{a, b as c};` (Rust `use` lifted verbatim, charter
256 /// §13.2).
257 USE_DECL,
258 /// One `use` tree: a path optionally followed by `{ … }` (nested
259 /// group), `as alias`, or bare.
260 USE_TREE,
261 USE_TREE_LIST,
262 /// `import name;` (Finding #3: the charter doesn't separately spell an
263 /// `import` grammar distinct from `use` — b0-sequencing's token-set
264 /// bullet lists `import` as its own decl keyword alongside `use`
265 /// regardless, so this skeleton gives it the minimal reasonable shape:
266 /// a single bare path statement, distinct node from `USE_DECL`. Real
267 /// semantics (whole-module import vs name-import) are B0.6's call).
268 IMPORT_DECL,
269 /// `module name { … }` — a nested module block (charter §13.2: files
270 /// hold declared `module` blocks nesting within them).
271 MODULE_DECL,
272
273 // ── Node kinds — bodies & content ───────────────────────────
274 /// A brace-delimited body: `{ BodyItem* }`. Universal body delimiter
275 /// (charter §4) for flow/fn/module bodies and nested block content.
276 BLOCK,
277 /// A single line of prose content, generic text interspersed with
278 /// interpolation/glue, terminated by `NEWLINE` or EOF.
279 CONTENT_LINE,
280 /// A run of literal text inside a `CONTENT_LINE` (no escapes, no
281 /// interpolation — those break the run).
282 TEXT,
283 /// `{expr}` — bare-brace interpolation, and nothing else, ever (charter
284 /// §6).
285 INTERPOLATION,
286 /// `<>` glue, in content position.
287 GLUE_NODE,
288 /// `# tag text` — a tag line (charter §11: tags kept).
289 TAG_LINE,
290 /// One `#`-prefixed tag inside a `TAG_LINE` or a `CONTENT_LINE`'s
291 /// trailing-tags tail.
292 TAG,
293
294 // ── Node kinds — choice points (charter §5) ─────────────────
295 /// `{? … }` — an explicit choice point.
296 CHOICE_POINT,
297 /// One `*`/`+` choice line inside a `CHOICE_POINT`.
298 CHOICE,
299 /// `*` (once) or `+` (sticky) bullet token wrapper.
300 CHOICE_BULLET,
301 /// `(name)` — a choice label (kept, charter §11).
302 LABEL,
303 /// `{if cond}` — a choice guard.
304 CHOICE_GUARD,
305 /// The `text[bracket]inner` display-split anatomy of a choice line
306 /// (kept as-is, charter §5).
307 CHOICE_START_CONTENT,
308 CHOICE_BRACKET_CONTENT,
309 CHOICE_INNER_CONTENT,
310 /// A choice's braced nested-content body (charter §5: "choice bodies
311 /// take braces when they have nested content").
312 CHOICE_BODY,
313 /// `else { … }` — a choice point's fallback branch (charter §11).
314 ELSE_BRANCH,
315 /// `<- flow(args)` — a splice inside a choice point (charter §5).
316 SPLICE,
317
318 // ── Node kinds — the annotated-brace family (charter §6) ────
319 /// `{if cond { … } else { … }}` / `{if cond: … else: …}` (Finding #4:
320 /// this skeleton accepts BOTH an inline colon-body form and a braced
321 /// multiline-arm form for `if`/`match` rather than the entry-marker-`-`
322 /// form charter §6 documents for the *alternation* family — the
323 /// charter itself flags entry-marker anatomy as "under-understood even
324 /// by the implementer," and nothing in the charter says `-` arms apply
325 /// to `if`/`match` specifically, so branches use the brace delimiter
326 /// charter §4 already declares universal, and dashes are reserved for
327 /// alternation blocks below. Flagged for the Track-B queue to confirm
328 /// or correct.)
329 CONDITIONAL_BLOCK,
330 IF_ARM,
331 MATCH_ARM,
332 /// A `match` arm's pattern (kept intentionally shallow — a bare
333 /// expression grammar reused, not a real pattern language; exhaustive
334 /// pattern matching is out of B0.5's scope).
335 MATCH_PATTERN,
336
337 /// `{~ … }` shuffle / `{& … }` cycle / `{! … }` once / `{| … }`
338 /// stopping-sequence — one node shape, `ALTERNATION_MARKER` child
339 /// records which.
340 ALTERNATION_BLOCK,
341 /// The `~`/`&`/`!`/`|` token that opened an `ALTERNATION_BLOCK`.
342 ALTERNATION_MARKER,
343 /// One `-`-prefixed entry/arm inside a multiline `ALTERNATION_BLOCK`
344 /// (charter §6). Runs until the next `-` or the closing `}`.
345 ENTRY,
346
347 /// `{? … }`'s sibling annotation-position dispatch already lives under
348 /// `CHOICE_POINT` above; this marker exists only so the family's
349 /// dispatch site has one name to log against in doc comments — not a
350 /// real node, never emitted. (Kept out of `is_node`/`is_token` via the
351 /// `__LAST` sentinel below being the true boundary; this variant is
352 /// unused and reserved as a documentation anchor only.)
353 // (intentionally no variant here — CHOICE_POINT already covers it)
354
355 // ── Node kinds — annotations (charter §11, `@[…]`) ──────────
356 /// `@[name(args)]` (directive-annotations-spec.md §5b's paren-clause
357 /// grammar, e.g. `@[effects(pure, silent, reads(gold, hp))]`).
358 ANNOTATION_LINE,
359 /// The parenthesized, comma-separated argument list of an annotation
360 /// or nested paren-clause.
361 ANNOTATION_ARGS,
362 /// One argument: a bare `IDENT`, or `IDENT(ANNOTATION_ARGS)` (the
363 /// nested paren-clause form, e.g. `reads(gold, hp)` nested inside
364 /// `effects(…)`).
365 ANNOTATION_ARG,
366
367 // ── Node kinds — diverts, tunnels, return (charter §11) ─────
368 /// `-> target` — kept verbatim.
369 DIVERT_STMT,
370 /// `-> place ->` — a tunnel call (kept, charter §11): divert, target,
371 /// divert, with nothing else before the line ends.
372 TUNNEL_CALL,
373 /// A divert target: `END` / `DONE` / a `PATH`.
374 DIVERT_TARGET,
375 /// `return` — leave this container.
376 RETURN_STMT,
377 /// `return -> x` — the tunnel-return respelling (charter §11):
378 /// `RETURN_STMT` immediately followed by a divert to `x`.
379 RETURN_REDIRECT,
380
381 // ── Node kinds — paths (charter §13.2) ───────────────────────
382 /// A dotted/`::`-separated name path. `::` crosses module walls, `.`
383 /// walks everything inside.
384 PATH,
385 PATH_SEGMENT,
386
387 // ── Node kinds — a minimal expression grammar ────────────────
388 // Shared by interpolation content, annotation args, choice guards,
389 // divert targets, and conditional/match heads. Real code-dialect
390 // statement grammar (let/assign/if-stmt/while/for/UFCS-calls/etc.) is
391 // explicitly B0.8 (`docs/b0-sequencing.md` §B0.8) — this is the
392 // expression *skeleton* B0.5 needs to give the constructs above a real
393 // (not just balanced-token) internal shape.
394 INTEGER_LIT,
395 FLOAT_LIT,
396 STRING_LIT,
397 BOOLEAN_LIT,
398 PATH_EXPR,
399 PAREN_EXPR,
400 PREFIX_EXPR,
401 INFIX_EXPR,
402 CALL_EXPR,
403 ARG_LIST,
404 /// `|x, y| expr` — lambda pipes, tokenized and structurally parsed;
405 /// lowering is explicitly deferred (charter §7/§8: "B0.5 tokenizes
406 /// pipes; B0.8 does not lower them").
407 LAMBDA_EXPR,
408 LAMBDA_PARAMS,
409
410 /// A parse-error wrapper node — swallows one unexpected token so error
411 /// recovery always makes forward progress.
412 ERROR,
413
414 // Not a real kind — used only for `rowan::Language::kind_to_raw` bounds.
415 #[doc(hidden)]
416 __LAST,
417}
418
419impl SyntaxKind {
420 /// Returns `true` for tokens produced by the lexer (leaf nodes in the CST).
421 #[must_use]
422 pub fn is_token(self) -> bool {
423 matches!(
424 self,
425 Self::WHITESPACE
426 | Self::NEWLINE
427 | Self::LINE_COMMENT
428 | Self::BLOCK_COMMENT
429 | Self::DOC_COMMENT_OUTER
430 | Self::DOC_COMMENT_INNER
431 | Self::KW_FLOW
432 | Self::KW_FN
433 | Self::KW_VAR
434 | Self::KW_CONST
435 | Self::KW_FLAGS
436 | Self::KW_STRUCT
437 | Self::KW_EXTERN
438 | Self::KW_IMPORT
439 | Self::KW_USE
440 | Self::KW_MODULE
441 | Self::KW_RETURN
442 | Self::KW_REF
443 | Self::KW_IF
444 | Self::KW_MATCH
445 | Self::KW_ELSE
446 | Self::KW_AS
447 | Self::KW_TRUE
448 | Self::KW_FALSE
449 | Self::KW_END
450 | Self::KW_DONE
451 | Self::EQ
452 | Self::PLUS_EQ
453 | Self::MINUS_EQ
454 | Self::STAR_EQ
455 | Self::SLASH_EQ
456 | Self::EQ_EQ
457 | Self::BANG_EQ
458 | Self::LT
459 | Self::GT
460 | Self::LT_EQ
461 | Self::GT_EQ
462 | Self::AMP
463 | Self::AMP_AMP
464 | Self::PLUS
465 | Self::MINUS
466 | Self::STAR
467 | Self::SLASH
468 | Self::PERCENT
469 | Self::CARET
470 | Self::BANG
471 | Self::QUESTION
472 | Self::L_PAREN
473 | Self::R_PAREN
474 | Self::L_BRACE
475 | Self::R_BRACE
476 | Self::L_BRACKET
477 | Self::R_BRACKET
478 | Self::PIPE
479 | Self::COMMA
480 | Self::DOT
481 | Self::COLON
482 | Self::SEMICOLON
483 | Self::COLON_COLON
484 | Self::HASH
485 | Self::TILDE
486 | Self::BACKSLASH
487 | Self::AT
488 | Self::AT_L_BRACKET
489 | Self::GLUE
490 | Self::DIVERT
491 | Self::THREAD
492 | Self::FAT_ARROW
493 | Self::INTEGER
494 | Self::FLOAT
495 | Self::QUOTE
496 | Self::STRING_TEXT
497 | Self::STRING_ESCAPE
498 | Self::IDENT
499 | Self::ERROR_TOKEN
500 | Self::EOF
501 )
502 }
503
504 /// Returns `true` for composite nodes built by the parser.
505 #[must_use]
506 pub fn is_node(self) -> bool {
507 !self.is_token() && self != Self::__LAST
508 }
509
510 /// Returns `true` for trivia — tokens the parser may skip over.
511 /// `NEWLINE` is **not** trivia; it terminates lines and delimits blocks.
512 #[must_use]
513 pub fn is_trivia(self) -> bool {
514 matches!(
515 self,
516 Self::WHITESPACE | Self::LINE_COMMENT | Self::BLOCK_COMMENT
517 )
518 }
519
520 /// Returns `true` for keyword tokens.
521 #[must_use]
522 pub fn is_keyword(self) -> bool {
523 matches!(
524 self,
525 Self::KW_FLOW
526 | Self::KW_FN
527 | Self::KW_VAR
528 | Self::KW_CONST
529 | Self::KW_FLAGS
530 | Self::KW_STRUCT
531 | Self::KW_EXTERN
532 | Self::KW_IMPORT
533 | Self::KW_USE
534 | Self::KW_MODULE
535 | Self::KW_RETURN
536 | Self::KW_REF
537 | Self::KW_IF
538 | Self::KW_MATCH
539 | Self::KW_ELSE
540 | Self::KW_AS
541 | Self::KW_TRUE
542 | Self::KW_FALSE
543 | Self::KW_END
544 | Self::KW_DONE
545 )
546 }
547}
548
549/// Rowan language tag for the native `.brink` grammar.
550#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
551pub enum NativeLanguage {}
552
553impl rowan::Language for NativeLanguage {
554 type Kind = SyntaxKind;
555
556 fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
557 assert!(raw.0 < SyntaxKind::__LAST as u16);
558 // SAFETY: `SyntaxKind` is `#[repr(u16)]` with contiguous discriminants,
559 // and we just checked bounds.
560 #[expect(unsafe_code, reason = "repr(u16) transmute with bounds check")]
561 unsafe {
562 std::mem::transmute::<u16, SyntaxKind>(raw.0)
563 }
564 }
565
566 fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
567 rowan::SyntaxKind(kind as u16)
568 }
569}
570
571/// A rowan `SyntaxNode` parameterized by [`NativeLanguage`].
572pub type SyntaxNode = rowan::SyntaxNode<NativeLanguage>;
573/// A rowan `SyntaxToken` parameterized by [`NativeLanguage`].
574pub type SyntaxToken = rowan::SyntaxToken<NativeLanguage>;
575/// A rowan `SyntaxElement` parameterized by [`NativeLanguage`].
576pub type SyntaxElement = rowan::SyntaxElement<NativeLanguage>;
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581 use rowan::Language;
582
583 #[test]
584 fn roundtrip_through_rowan() {
585 let mut i = 0u16;
586 loop {
587 if i == SyntaxKind::__LAST as u16 {
588 break;
589 }
590 let raw = rowan::SyntaxKind(i);
591 let kind = NativeLanguage::kind_from_raw(raw);
592 let back = NativeLanguage::kind_to_raw(kind);
593 assert_eq!(raw, back, "roundtrip failed for discriminant {i}");
594 i += 1;
595 }
596 }
597
598 #[test]
599 fn token_node_partition() {
600 let mut i = 0u16;
601 loop {
602 if i == SyntaxKind::__LAST as u16 {
603 break;
604 }
605 let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
606 assert!(
607 kind.is_token() ^ kind.is_node(),
608 "{kind:?} is neither token nor node (or both)"
609 );
610 i += 1;
611 }
612 }
613
614 #[test]
615 fn trivia_is_subset_of_tokens() {
616 let mut i = 0u16;
617 loop {
618 if i == SyntaxKind::__LAST as u16 {
619 break;
620 }
621 let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
622 if kind.is_trivia() {
623 assert!(kind.is_token(), "{kind:?} is trivia but not a token");
624 }
625 i += 1;
626 }
627 }
628
629 #[test]
630 fn newline_is_not_trivia() {
631 assert!(!SyntaxKind::NEWLINE.is_trivia());
632 assert!(SyntaxKind::NEWLINE.is_token());
633 }
634
635 #[test]
636 fn keywords_are_tokens() {
637 let keywords = [
638 SyntaxKind::KW_FLOW,
639 SyntaxKind::KW_FN,
640 SyntaxKind::KW_VAR,
641 SyntaxKind::KW_CONST,
642 SyntaxKind::KW_FLAGS,
643 SyntaxKind::KW_STRUCT,
644 SyntaxKind::KW_EXTERN,
645 SyntaxKind::KW_IMPORT,
646 SyntaxKind::KW_USE,
647 SyntaxKind::KW_MODULE,
648 SyntaxKind::KW_RETURN,
649 SyntaxKind::KW_REF,
650 SyntaxKind::KW_IF,
651 SyntaxKind::KW_MATCH,
652 SyntaxKind::KW_ELSE,
653 SyntaxKind::KW_AS,
654 SyntaxKind::KW_TRUE,
655 SyntaxKind::KW_FALSE,
656 SyntaxKind::KW_END,
657 SyntaxKind::KW_DONE,
658 ];
659 for kw in keywords {
660 assert!(kw.is_token(), "{kw:?} should be a token");
661 assert!(kw.is_keyword(), "{kw:?} should be a keyword");
662 }
663 }
664
665 #[test]
666 fn non_keywords_are_not_keywords() {
667 assert!(!SyntaxKind::IDENT.is_keyword());
668 assert!(!SyntaxKind::PLUS.is_keyword());
669 assert!(!SyntaxKind::SOURCE_FILE.is_keyword());
670 }
671}