Skip to main content

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    /// `pub` — the native visibility marker (issue #1582, RULED
52    /// 2026-08-03, `docs/decision-log.md` "Native visibility marker: a
53    /// `pub` keyword"). Optionally precedes `flow`/`fn`/`var`/`const`/
54    /// `struct`/`extern`/`flags` (never `import`/`use`/`module`, which
55    /// have no [`crate::VisibilityMark`] slot to carry, and never a
56    /// knot/stitch — those are ink-dialect grammar, untouched here).
57    /// Produces the same `VisibilityMark::Public` the brink dialect's
58    /// `#@public` tag directive already does; absent, a declaration stays
59    /// `Private` (already ratified 2026-07-23, unchanged by this token).
60    /// Hard-reserved everywhere, like every other keyword in this section
61    /// (Finding #1) — a prose line whose first word is literally "pub"
62    /// still falls through to `body_line`'s generic `TEXT` fallback
63    /// exactly like an unmatched `flow`/`var`/… does today, since the
64    /// declaration-head lookahead (`parser/decl.rs::at_pub_decl`) only
65    /// commits when a legal declaration shape follows.
66    KW_PUB,
67    /// `flow` — story-time container (Coloring axis, charter §3).
68    KW_FLOW,
69    /// `fn` — expression-time container (Coloring axis, charter §3).
70    KW_FN,
71    KW_VAR,
72    KW_CONST,
73    /// `let` — a code-ground statement-position binding (B0.8 Wave A,
74    /// `docs/decision-log.md` 2026-07-23 "Code-ground sitting"). Distinct
75    /// from `var`/`const` (declaration-layer keywords, B0.5): `let`
76    /// introduces a `LET_STMT` inside a `STMT_BLOCK`, terminated by `;`
77    /// like every other code-ground statement — `var`/`const` keep their
78    /// existing terminator-free declaration shape (`parser/decl.rs`'s
79    /// `var_decl`/`const_decl` doc comments).
80    KW_LET,
81    /// `flags` — renamed LIST (charter §11).
82    KW_FLAGS,
83    KW_STRUCT,
84    KW_EXTERN,
85    KW_IMPORT,
86    KW_USE,
87    KW_MODULE,
88    /// `return` — leave this container; also the tunnel-return respelling's
89    /// first half (`return -> x`, charter §11).
90    KW_RETURN,
91    /// `ref` — ref-argument marker (kept from ink).
92    KW_REF,
93    /// `if` — word-annotated brace family member (charter §6) AND (Finding
94    /// #1) reserved as a code-ground keyword everywhere.
95    KW_IF,
96    /// `match` — word-annotated brace family member (charter §6).
97    KW_MATCH,
98    /// `else` — conditional else-arm AND a choice point's fallback branch
99    /// (charter §11: "a choice point's fallback is its else-branch").
100    KW_ELSE,
101    /// `while` — code-ground loop statement (B0.8 Wave B,
102    /// `docs/decision-log.md` 2026-07-23 "Code-ground sitting"). Hard-
103    /// reserved everywhere, mirroring `if`/`match`/`else` (Finding #1) —
104    /// unlike the brink-dialect's `~ { … }` T1b grammar, where `while` is a
105    /// *contextual* soft keyword (`brink-syntax/src/parser/logic.rs`), the
106    /// native surface reserves its `RustScript`-shaped statement keywords
107    /// globally.
108    KW_WHILE,
109    /// `for` — code-ground loop statement (B0.8 Wave B). Hard-reserved,
110    /// see [`Self::KW_WHILE`]'s doc.
111    KW_FOR,
112    /// `in` — the `for name in expr { … }` loop-head separator (B0.8 Wave
113    /// B). Hard-reserved, see [`Self::KW_WHILE`]'s doc.
114    KW_IN,
115    /// `until` — the code-ground condition-park statement (B0.8 Wave B,
116    /// decision-log 2026-07-23 item 4): `until <pure-bool-expr>;` parks the
117    /// flow until the condition becomes true (reactive), then resumes —
118    /// the runtime's existing `FlowSleep` reactive-wake mechanism. Native
119    /// **retires** `await` entirely (its future-resolution mental model is
120    /// wrong for a condition-park); `until` is the only spelling. Lowers to
121    /// the exact same `AwaitStmt` HIR node the brink-dialect's `~ await
122    /// cond` produces — a spelling change, not a new construct (NF-2
123    /// fence). Hard-reserved, see [`Self::KW_WHILE`]'s doc.
124    KW_UNTIL,
125    /// `break` — code-ground loop-exit statement (B0.8 Wave B tail, issue
126    /// #1322, `docs/decision-log.md` 2026-07-23 "Code-ground sitting").
127    /// Hard-reserved, see [`Self::KW_WHILE`]'s doc. No content-ground
128    /// counterpart — `break` only has meaning inside a `while`/`for` body.
129    KW_BREAK,
130    /// `continue` — code-ground loop-skip statement (B0.8 Wave B tail,
131    /// issue #1322). Hard-reserved, see [`Self::KW_WHILE`]'s doc. No
132    /// content-ground counterpart, same as [`Self::KW_BREAK`].
133    KW_CONTINUE,
134    /// `as` — import/use aliasing (`use a::b as c`).
135    KW_AS,
136    /// `or` — B1 `or`-coalescing (`docs/stdlib-spec.md` §1.6a, issue
137    /// #1460): `x or default`. A distinct keyword from `||` (boolean
138    /// disjunction, still two adjacent `PIPE` tokens — see
139    /// `ast::InfixExpr::is_double_pipe`).
140    KW_OR,
141    KW_TRUE,
142    KW_FALSE,
143    /// `END` — divert target sentinel (kept verbatim, charter §11).
144    KW_END,
145    /// `DONE` — divert target sentinel (kept verbatim, charter §11).
146    KW_DONE,
147
148    // ── Punctuation / operator tokens ────────────────────────────
149    /// `=`
150    EQ,
151    /// `+=`
152    PLUS_EQ,
153    /// `-=`
154    MINUS_EQ,
155    /// `*=`
156    STAR_EQ,
157    /// `/=`
158    SLASH_EQ,
159    /// `==`
160    EQ_EQ,
161    /// `!=`
162    BANG_EQ,
163    /// `<`
164    LT,
165    /// `>`
166    GT,
167    /// `<=`
168    LT_EQ,
169    /// `>=`
170    GT_EQ,
171    /// `&`
172    AMP,
173    /// `&&`
174    AMP_AMP,
175    /// `+`
176    PLUS,
177    /// `-`. Also the entry-marker sigil (charter §6) and the choice-list
178    /// once-bullet-adjacent dash; the parser, not the lexer, decides which
179    /// role a given `-` plays from structural position.
180    MINUS,
181    /// `*`
182    STAR,
183    /// `/`
184    SLASH,
185    /// `%`
186    PERCENT,
187    /// `^`
188    CARET,
189    /// `!`
190    BANG,
191    /// `?`. Also half of the `{?` choice-point opener — the parser
192    /// recognizes the *adjacent* `L_BRACE QUESTION` pair, no compound token
193    /// (mirrors how `{if`/`{match`/`{~` are recognized: `{` is always plain
194    /// `L_BRACE`, disambiguation is a parser lookahead, not a lexer job).
195    QUESTION,
196    /// `(`
197    L_PAREN,
198    /// `)`
199    R_PAREN,
200    /// `{`
201    L_BRACE,
202    /// `}`
203    R_BRACE,
204    /// `[`
205    L_BRACKET,
206    /// `]`
207    R_BRACKET,
208    /// `|`. Two adjacent `PIPE`s are NOT compounded into a logical-or token
209    /// (mirrors brink-syntax precedent for `||`/`++`/`--`) — the parser
210    /// disambiguates `a || b` (logical or) from `|x|` (lambda params) by
211    /// expression-vs-lambda-head position, not lexical shape.
212    PIPE,
213    /// `,`
214    COMMA,
215    /// `.` — the intra-module separator (containers, fields, variants,
216    /// UFCS; charter §13.2).
217    DOT,
218    /// `:`
219    COLON,
220    /// `;` — `use`'s optional statement terminator (charter §13.2's
221    /// literal example: `use story::market::{barter, haggle};`, Finding #6:
222    /// no *declaration* requires one), **and** the code-ground statement
223    /// terminator (B0.8 Wave A, `docs/decision-log.md` 2026-07-23
224    /// "Code-ground sitting"): `LET_STMT`/`ASSIGN_STMT`/`EXPR_STMT` inside a
225    /// `STMT_BLOCK` each require a trailing `;` — the one thing that
226    /// distinguishes a statement from the block's unterminated tail
227    /// expression (blocks-as-values).
228    SEMICOLON,
229    /// `::` — the module-wall separator (charter §13.2). Lexed as one
230    /// compound token so a bare `:` (used in inline `{if cond: …}` bodies)
231    /// never gets swallowed by a stray adjacent colon.
232    COLON_COLON,
233    /// `#` — tag opener (charter §11: tags kept).
234    HASH,
235    /// `~`. Also an alternation-family opener (`{~ }` shuffle, charter §6).
236    TILDE,
237    /// `\`
238    BACKSLASH,
239    /// `@`. Always its own token (never `ERROR_TOKEN`), whatever role the
240    /// parser gives it from structural position:
241    /// - directly against an identifier at body-item position it opens a
242    ///   [`Self::CUE`]/[`Self::COMPACT_CUE`] — the ruled block-cue
243    ///   spelling (`docs/prose-dialect-spec.md` §8b.9, issue #1715);
244    /// - anywhere else — detached (`@ 5pm`), or reached mid-line — it
245    ///   folds into plain `TEXT`, so prose containing a bare `@`
246    ///   round-trips losslessly and errorlessly
247    ///   (`docs/directive-annotations-spec.md` §5b: "a lone `@` in prose
248    ///   stays plain text").
249    ///
250    /// The `@[` annotation opener is a separate compound token
251    /// ([`Self::AT_L_BRACKET`]), so the two `@` channels never compete.
252    AT,
253
254    // ── Compound tokens ──────────────────────────────────────────
255    /// `@[` — annotation-line opener (charter §11 / NS-A2 lineage,
256    /// `docs/directive-annotations-spec.md` §5b). Only the *adjacent* pair
257    /// opens an annotation line.
258    AT_L_BRACKET,
259    /// `<>` — glue (kept, charter §11).
260    GLUE,
261    /// `->` — divert (kept verbatim, charter §11).
262    DIVERT,
263    /// `<-` — splice, valid only inside a choice point (charter §5).
264    THREAD,
265    /// `=>` — match-arm separator.
266    FAT_ARROW,
267
268    // ── Content tokens ───────────────────────────────────────────
269    /// Integer literal (digits only; no leading sign — unary `-` is a
270    /// separate `PREFIX_EXPR`).
271    INTEGER,
272    /// Float literal (`digits.digits`).
273    FLOAT,
274    /// `"` (opening or closing quote).
275    QUOTE,
276    /// Run of non-special characters inside a string literal.
277    STRING_TEXT,
278    /// Escape sequence inside a string (`\n`, `\t`, `\\`, `\"`).
279    STRING_ESCAPE,
280    /// Identifier: ASCII `[A-Za-z_][A-Za-z0-9_]*` (Finding #2: native
281    /// identifiers are ASCII-only in this skeleton — the charter's S4
282    /// casing partition (`snake_case`/`UpperCamel`) is an ASCII-shaped rule
283    /// and the ink Unicode identifier range table is ink-specific baggage
284    /// with no native ruling to inherit it from; widening later is
285    /// additive, not breaking).
286    IDENT,
287    /// Any byte/char the lexer could not classify — unterminated block
288    /// comments' unreachable tail (folded into `BLOCK_COMMENT` itself, not
289    /// this), and raw prose bytes at declaration scope. Does NOT include a
290    /// lone `@`, which lexes as its own `AT` token (see `AT`'s doc
291    /// comment) — not this.
292    ERROR_TOKEN,
293    /// End of file (synthetic).
294    EOF,
295
296    // ── Node kinds — doc comments (B0.6b) ────────────────────────
297    /// A contiguous run of [`Self::DOC_COMMENT_OUTER`] tokens (the leading
298    /// child of the declaration node it documents) or
299    /// [`Self::DOC_COMMENT_INNER`] tokens (the leading child of the
300    /// enclosing knot/flow/file body it documents) — one node shape for
301    /// both variants; `ast::DocComment::is_inner` tells them apart by
302    /// inspecting which token kind the node's children carry
303    /// (`docs/native-surface-charter.md`'s doc-comment section).
304    DOC_COMMENT,
305
306    // ── Node kinds — top level & declarations ───────────────────
307    SOURCE_FILE,
308    /// `flow name(params) { … }` / nested `flow` = stitch (charter §4).
309    FLOW_DECL,
310    /// `fn name(params) { … }`.
311    FN_DECL,
312    /// Shared param-list shape for `FLOW_DECL`/`FN_DECL`.
313    PARAM_LIST,
314    /// One parameter: `ref`? `IDENT` (`:` type)? (NG-A, #1487). Also the
315    /// node lambda parameters use under `LAMBDA_PARAMS` — they used to be
316    /// bare `IDENT` tokens directly there, but now each gets its own
317    /// `PARAM` so a `: type` annotation attaches to the right one (`ref`
318    /// is still not accepted on a lambda parameter).
319    PARAM,
320    /// `var name = expr`.
321    VAR_DECL,
322    /// `const name = expr`.
323    CONST_DECL,
324    /// `flags Name = (member), member, …` (charter §11).
325    FLAGS_DECL,
326    FLAGS_MEMBER_LIST,
327    /// One flag member; a parenthesized member is the default-on entry.
328    FLAGS_MEMBER,
329    /// `struct Name { field: type, … }` (charter §13.1's sibling; concrete
330    /// grammar shape here, no field-type semantics checked).
331    STRUCT_DECL,
332    STRUCT_FIELD,
333    /// `extern name(params)`.
334    EXTERN_DECL,
335    /// `use path::{a, b as c};` (Rust `use` lifted verbatim, charter
336    /// §13.2).
337    USE_DECL,
338    /// One `use` tree: a path optionally followed by `{ … }` (nested
339    /// group), `as alias`, or bare.
340    USE_TREE,
341    USE_TREE_LIST,
342    /// `import name;` (Finding #3: the charter doesn't separately spell an
343    /// `import` grammar distinct from `use` — b0-sequencing's token-set
344    /// bullet lists `import` as its own decl keyword alongside `use`
345    /// regardless, so this skeleton gives it the minimal reasonable shape:
346    /// a single bare path statement, distinct node from `USE_DECL`. Real
347    /// semantics (whole-module import vs name-import) are B0.6's call).
348    IMPORT_DECL,
349    /// `module name { … }` — a nested module block (charter §13.2: files
350    /// hold declared `module` blocks nesting within them).
351    MODULE_DECL,
352
353    // ── Node kinds — bodies & content ───────────────────────────
354    /// A brace-delimited body: `{ BodyItem* }`. Universal body delimiter
355    /// (charter §4) for flow/fn/module bodies and nested block content.
356    BLOCK,
357    /// A single line of prose content, generic text interspersed with
358    /// interpolation/glue, terminated by `NEWLINE` or EOF.
359    CONTENT_LINE,
360    /// `~ stmt` — the content-ground line-escape into code (charter §8.2,
361    /// RULED 2026-07-23, `docs/decision-log.md` "Native interleaving &
362    /// body-dialect spelling": ink's logic line, kept — issue #1991).
363    /// Wraps a single [`Self::LET_STMT`]/[`Self::ASSIGN_STMT`]/
364    /// [`Self::EXPR_STMT`]/[`Self::UNTIL_STMT`]/[`Self::STMT_BLOCK`] child,
365    /// every node kind reused **unmodified** from the code-ground statement
366    /// layer (`parser/stmt.rs`) in a different position — the four
367    /// line-shaped children (`LET_STMT`/`ASSIGN_STMT`/`EXPR_STMT`/
368    /// `UNTIL_STMT`) parse WITHOUT the code-ground `;` terminator, this
369    /// escape being one content-ground line, terminated by `NEWLINE`/EOF
370    /// exactly like [`Self::CONTENT_LINE`] itself; the `STMT_BLOCK` child
371    /// (a `~{ … }` multi-statement logic block, issue #1972) is
372    /// self-delimiting via its own matching `}` instead. Mirrors
373    /// [`Self::RETURN_STMT`]'s doc precedent (one node shape safely serving
374    /// two grammars). Parsed by `parser/stmt.rs::logic_line`, dispatched
375    /// from `block::body_line`'s (and `family::colon_body_line`'s) `TILDE`
376    /// arm.
377    LOGIC_LINE,
378    /// `> text` — the code-ground line-escape into prose (charter §8.2,
379    /// RULED 2026-07-23, `docs/decision-log.md` "Native interleaving &
380    /// body-dialect spelling": the mirror image of [`Self::LOGIC_LINE`] at
381    /// the opposite ground — issue #1992). Wraps a single
382    /// [`Self::CONTENT_LINE`] child, reused **unmodified** from the
383    /// content-ground line layer (`parser/content.rs::content_line`) in a
384    /// different position: same grammar, same terminator discipline
385    /// (`NEWLINE`/EOF, never a bare `R_BRACE`, which — as for `CONTENT_LINE`
386    /// itself — closes the enclosing body rather than the escape). Mirrors
387    /// [`Self::LOGIC_LINE`]'s own one-node-two-grammars precedent, just with
388    /// the wrapped/wrapper roles swapped: there the escape wraps a
389    /// code-ground node inside a content-ground dispatch; here it wraps a
390    /// content-ground node inside a code-ground dispatch. Parsed by
391    /// `parser/stmt.rs::prose_line`, dispatched from `stmt::statement()`'s
392    /// `GT` arm — reachable everywhere a code-ground `STMT_BLOCK` statement
393    /// is parsed (a `fn`'s default body, a `flow`'s `~{ }` override, and
394    /// every nested `if`/`while`/`for` body, which all share that one
395    /// dispatch loop).
396    PROSE_LINE,
397    /// A run of literal text inside a `CONTENT_LINE` (no escapes, no
398    /// interpolation — those break the run).
399    TEXT,
400    /// `{expr}` — bare-brace interpolation, and nothing else, ever (charter
401    /// §6).
402    INTERPOLATION,
403    /// `<>` glue, in content position.
404    GLUE_NODE,
405    /// `# tag text` — a tag line (charter §11: tags kept).
406    TAG_LINE,
407    /// One `#`-prefixed tag inside a `TAG_LINE` or a `CONTENT_LINE`'s
408    /// trailing-tags tail. Also the trailing-tag shape a `FLOW_DECL`
409    /// header line and a [`Self::SCENE_HEADING`] carry (§8b.4 —
410    /// container-level per-flow tags).
411    TAG,
412
413    // ── Node kinds — prose block elements (docs/prose-dialect-spec.md ──
414    // ── §8b/§8d, RULED 2026-07-25 across sittings 4–5) ────────────────
415    //
416    // The built-in screenplay preset's *grammar*. Recognition is
417    // line-shape-static (spec §3.1) and lives in `parser/element.rs`;
418    // attachment and the preset's data schema are deliberately elsewhere
419    // (issues #1717/#1720), and dispatch to an annotated handler is
420    // `hir::lower_native::element` (issue #1838) — this enum only names
421    // the shapes the parser can see. `LYRICS` is absent on purpose: the
422    // lyrics element was **dropped** (§8b.1) because Fountain's `~`
423    // force-marker collides with the logic-line escape.
424    /// A header-scoped stitch: a [`Self::SCENE_HEADING`] plus the
425    /// [`Self::SCENE_BODY`] it scopes (§8b.2, RULED). **Amends charter
426    /// §4's "braces are the universal body delimiter"** for preset
427    /// heading-elements in prose-ground only — a scene runs to the next
428    /// heading or the enclosing close, restoring ink's own header-scoped
429    /// stitch. Heading-stitches are **flat siblings**: scenes never nest,
430    /// as on a real page, and deeper nesting keeps the general
431    /// `flow x { … }` spelling, which stays first-class in prose-ground.
432    SCENE_STITCH,
433    /// The heading line itself: `INT. MARKET SQUARE - NIGHT [market]
434    /// #tense #act1`. Line order is fixed (§8b.3): pattern, `[slug]`,
435    /// tags. Two rejected slug spellings, recorded so they are not
436    /// revisited: `#x#` (clashes with the tag lexer) and `{x}` (lexes as
437    /// interpolation — headings get **no** carve-out).
438    SCENE_HEADING,
439    /// The heading's title run — everything before the optional
440    /// `[slug]`/tags. The title is the display name (§3.3) and, with no
441    /// explicit slug, the address is inferred from it.
442    SCENE_TITLE,
443    /// `[market]` — the explicit address slug on a heading (§8b.3).
444    SCENE_SLUG,
445    /// The header-scoped body a [`Self::SCENE_HEADING`] opens: every item
446    /// up to the next heading, the enclosing `}`, or EOF. Braceless by
447    /// construction — that is the whole point of §8b.2 — so it is a
448    /// distinct node kind from [`Self::BLOCK`], whose contract is "a
449    /// brace-delimited body".
450    SCENE_BODY,
451    /// `@VENDOR` — a block character cue (attached-forward, §3.6). Its
452    /// trailing tags are the ruled home for cue extensions (§8d.4:
453    /// `@VENDOR #(v.o.)` — no parsed `ext` capture, no new payload
454    /// machinery).
455    CUE,
456    /// The name run inside a [`Self::CUE`]/[`Self::COMPACT_CUE`], after
457    /// the `@` sigil and before `:`/tags/end of line.
458    CUE_NAME,
459    /// `@KID: Says who?` — the compact cue (§8b.9, the Yarn cross): cue
460    /// plus a single fused dialogue line, declared as a **second pattern
461    /// beside** the block cue rather than a rewrite of it. Holds a
462    /// [`Self::CUE_NAME`] and the fused [`Self::CONTENT_LINE`].
463    COMPACT_CUE,
464    /// `(hushed)` — a parenthetical delivery line (attached-forward,
465    /// §3.6). Recognized only inside a live cue chain (after a cue, a
466    /// parenthetical, or that cue's dialogue), so the G-1 `(label)`
467    /// content-line spelling is untouched everywhere else.
468    PARENTHETICAL,
469    /// `!name rest of the line…` — the self-announcing `!name`
470    /// annotation-element dispatch sigil (§3.5b, issue #2004). The `!` and
471    /// the name must be **adjacent**, mirroring [`Self::CUE`]'s `@NAME`
472    /// discipline (`element::at_bang_dispatch`) — a bare `!` not
473    /// immediately followed by an identifier stays ordinary prose. Holds a
474    /// [`Self::DISPATCH_NAME`] and the remainder as a fused
475    /// [`Self::CONTENT_LINE`] (the same technique [`Self::COMPACT_CUE`]
476    /// uses for its dialogue line) — whether a handler by that name
477    /// actually exists, and whether its `args = "…"` pattern matches the
478    /// remainder, is `hir::lower_native::element::try_dispatch`'s
479    /// question, not the parser's.
480    BANG_DISPATCH,
481    /// The name run inside a [`Self::BANG_DISPATCH`], after the `!` sigil
482    /// and before the remainder.
483    DISPATCH_NAME,
484
485    // ── Node kinds — inline markup (docs/prose-dialect-spec.md §4, ──────
486    // ── RULED 2026-07-25, issue #1716) ───────────────────────────────────
487    //
488    // XML-shaped spans (§4.1): `<name attr="v">content</name>`, self-
489    // closing allowed (`<pause/>`, `<sfx name="bell"/>` — the point-marker
490    // use case, §8b.11). Recognition is "blunt lexing" at the *parser*
491    // level over already-existing tokens — no new lexer tokens: `LT`
492    // immediately (no trivia) followed by `IDENT` opens a span
493    // (`markup::at_span_open`), `LT SLASH IDENT` immediately closes one
494    // (`markup::at_span_close`); `GLUE` (`<>`) and `THREAD` (`<-`) are
495    // already distinct compound tokens at the lexer, so a bare `<` never
496    // competes with either. Freeform by default (§4.2): an unknown tag
497    // name is not a parse-time concern at all — manifest validation is a
498    // separate, later compiler pass over the same tree, exactly the
499    // externals-manifest pattern. `<center>` (§8d.3) is ordinary markup;
500    // nothing here special-cases it. A tag name may also contain `-` as an
501    // internal separator only (`<fade-in>`; RULED 2026-08-01, issue #1996)
502    // — `markup::tag_name_len` widens just this position's name shape, not
503    // `IDENT` lexing itself.
504    /// One inline span: the open tag (name + attrs), its content (when not
505    /// self-closing — recursively any content-item shape, including a
506    /// nested `SPAN`), and the matching close tag. Self-closing spans (no
507    /// content, no close tag) are the point-marker shape (§8b.11).
508    SPAN,
509    /// The tag name at a [`Self::SPAN`]'s open tag — one `IDENT`, or an
510    /// `IDENT (MINUS IDENT)*` chain for a hyphenated name (`<fade-in>`,
511    /// issue #1996). Wrapped (rather than a bare token) so lowering can
512    /// find *this* name unambiguously among the attr names and the close
513    /// tag's own (unwrapped) name tokens that also live under `SPAN`.
514    SPAN_NAME,
515    /// One `name="value"` attribute inside a [`Self::SPAN`]'s open tag.
516    SPAN_ATTR,
517    /// An attribute's quoted value. Deliberately **not** [`Self::STRING_LIT`]
518    /// — attribute values are static text only (§4.1's worked examples are
519    /// all static: `<sfx name="bell"/>`, `<item id="lantern">`); nothing in
520    /// the ruling asks for `{expr}` interpolation inside an attribute, and
521    /// reusing `STRING_LIT` would silently admit it. Uses the same
522    /// `STRING_TEXT`/`STRING_ESCAPE` token pair as `STRING_LIT`, just
523    /// without the `INTERPOLATION` child arm.
524    SPAN_ATTR_VALUE,
525    /// One escape sequence: `BACKSLASH` plus the one escaped token — `\<`
526    /// `\{` `\#` `\\`, and *only* those four (§8d.6: "the escape set is
527    /// final... do not extend it"). A `BACKSLASH` before anything else is a
528    /// parse error, not this node — see `markup::escape`.
529    ESCAPE,
530
531    // ── Node kinds — choice points (charter §5) ─────────────────
532    /// `{? … }` — an explicit choice point.
533    CHOICE_POINT,
534    /// One `*`/`+` choice line inside a `CHOICE_POINT`.
535    CHOICE,
536    /// `*` (once) or `+` (sticky) bullet token wrapper.
537    CHOICE_BULLET,
538    /// `(name)` — a choice label (kept, charter §11).
539    LABEL,
540    /// `{if cond}` — a choice guard.
541    CHOICE_GUARD,
542    /// The `text[bracket]inner` display-split anatomy of a choice line
543    /// (kept as-is, charter §5).
544    CHOICE_START_CONTENT,
545    CHOICE_BRACKET_CONTENT,
546    CHOICE_INNER_CONTENT,
547    /// A choice's braced nested-content body (charter §5: "choice bodies
548    /// take braces when they have nested content").
549    CHOICE_BODY,
550    /// `else { … }` — a choice point's fallback branch (charter §11).
551    ELSE_BRANCH,
552    /// `<- flow(args)` — a splice inside a choice point (charter §5).
553    SPLICE,
554
555    // ── Node kinds — the annotated-brace family (charter §6) ────
556    /// `{if cond { … } else { … }}` / `{if cond: … else: …}` (Finding #4:
557    /// this skeleton accepts BOTH an inline colon-body form and a braced
558    /// multiline-arm form for `if`/`match` rather than the entry-marker-`-`
559    /// form charter §6 documents for the *alternation* family — the
560    /// charter itself flags entry-marker anatomy as "under-understood even
561    /// by the implementer," and nothing in the charter says `-` arms apply
562    /// to `if`/`match` specifically, so branches use the brace delimiter
563    /// charter §4 already declares universal, and dashes are reserved for
564    /// alternation blocks below. Flagged for the Track-B queue to confirm
565    /// or correct.) The colon-body form's `else:` boundary is recognized
566    /// whether it starts its own physical line or trails other content on
567    /// the SAME line (#1254 Gap 1, fixed #1261 — `family::colon_body_line`).
568    /// A flat `else if <cond> { … }`/`else if <cond>: …` chain (ruled
569    /// 2026-07-22, #1258, implemented #1261) lowers to the identical shape
570    /// an explicit nested `{if …}` would.
571    CONDITIONAL_BLOCK,
572    IF_ARM,
573    MATCH_ARM,
574    /// A `match` arm's pattern (kept intentionally shallow — a bare
575    /// expression grammar reused, not a real pattern language; exhaustive
576    /// pattern matching is out of B0.5's scope).
577    MATCH_PATTERN,
578
579    /// `{~ … }` shuffle / `{& … }` cycle / `{! … }` once / `{| … }`
580    /// stopping-sequence — one node shape, `ALTERNATION_MARKER` child
581    /// records which. A `{` led by any of these four marker chars is
582    /// ALWAYS claimed by this family ahead of bare `{expr}` interpolation
583    /// (ruled 2026-07-22, "alternation markers win," #1258/#1261 —
584    /// `family::at_alternation`'s doc comment has the full rationale and
585    /// the parens escape hatch); a body with zero branches (`{~}`, `{&\n}`)
586    /// is a parse error (brink-syntax parity), not silently accepted.
587    ALTERNATION_BLOCK,
588    /// The `~`/`&`/`!`/`|` token that opened an `ALTERNATION_BLOCK`.
589    ALTERNATION_MARKER,
590    /// One `-`-prefixed entry/arm inside a multiline `ALTERNATION_BLOCK`
591    /// (charter §6). Runs until the next `-` or the closing `}`.
592    ENTRY,
593
594    /// `{? … }`'s sibling annotation-position dispatch already lives under
595    /// `CHOICE_POINT` above; this marker exists only so the family's
596    /// dispatch site has one name to log against in doc comments — not a
597    /// real node, never emitted. (Kept out of `is_node`/`is_token` via the
598    /// `__LAST` sentinel below being the true boundary; this variant is
599    /// unused and reserved as a documentation anchor only.)
600    // (intentionally no variant here — CHOICE_POINT already covers it)
601
602    // ── Node kinds — annotations (charter §11, `@[…]`) ──────────
603    /// `@[name(args)]` (directive-annotations-spec.md §5b's paren-clause
604    /// grammar, e.g. `@[effects(pure, silent, reads(gold, hp))]`).
605    ANNOTATION_LINE,
606    /// The parenthesized, comma-separated argument list of an annotation
607    /// or nested paren-clause.
608    ANNOTATION_ARGS,
609    /// One argument: a bare `IDENT`, or `IDENT(ANNOTATION_ARGS)` (the
610    /// nested paren-clause form, e.g. `reads(gold, hp)` nested inside
611    /// `effects(…)`).
612    ANNOTATION_ARG,
613
614    // ── Node kinds — diverts, tunnels, return (charter §11) ─────
615    /// `-> target` — kept verbatim.
616    DIVERT_STMT,
617    /// `-> place ->` — a tunnel call (kept, charter §11): divert, target,
618    /// divert, with nothing else before the line ends.
619    TUNNEL_CALL,
620    /// A divert target: `END` / `DONE` / a `PATH`.
621    DIVERT_TARGET,
622    /// `return` / `return <expr>` — leave this container, optionally with a
623    /// value (content-ground, `parser/divert.rs::return_stmt`; the value
624    /// expression is optional — issue #1973 added it, previously always
625    /// bare). **Also** reused, unmodified, as the code-ground `return e?;`
626    /// value-return statement (B0.8 Wave B tail, issue #1322,
627    /// `docs/decision-log.md` 2026-07-23 "Code-ground sitting" item 1) —
628    /// `parser/stmt.rs::return_stmt` parses an optional value expression
629    /// and a `;` terminator instead. The two grammars never overlap
630    /// (dispatched from different parent contexts — content-ground
631    /// `BLOCK`/`family.rs` vs. code-ground `STMT_BLOCK`/`stmt.rs`), so one
632    /// node shape serves both, mirroring the brink-dialect's own
633    /// `RETURN_STMT` (`brink-syntax`), which likewise serves both its bare
634    /// container-exit and its valued `~ { … }`-block-return uses.
635    /// `ast::ReturnStmt::value()` is a plain "first child expr, if any"
636    /// accessor — `Some`/`None` for both grammars now (content-ground: a
637    /// bare `return` is still `None`, and `return -> x` is a distinct
638    /// `RETURN_REDIRECT` node below, never this one's value; code-ground:
639    /// the initializer was already optional).
640    RETURN_STMT,
641    /// `return -> x` — the tunnel-return respelling (charter §11):
642    /// `RETURN_STMT` immediately followed by a divert to `x`. Content-
643    /// ground only — code-ground `return` has no redirect counterpart (a
644    /// content-ground/tunnel concept with no code-ground meaning).
645    RETURN_REDIRECT,
646
647    // ── Node kinds — paths (charter §13.2) ───────────────────────
648    /// A dotted/`::`-separated name path. `::` crosses module walls, `.`
649    /// walks everything inside.
650    PATH,
651    PATH_SEGMENT,
652
653    // ── Node kinds — a minimal expression grammar ────────────────
654    // Shared by interpolation content, annotation args, choice guards,
655    // divert targets, and conditional/match heads. B0.8 Wave A (below) adds
656    // the statement layer (`let`/assignment/expression-statements/blocks-
657    // as-values) over this skeleton; B0.8 Wave B (further below) adds
658    // `if`/`while`/`for`/`until` control flow, and Wave B tail (issue
659    // #1322) adds `return`/`break`/`continue`/compound-assign, as more
660    // statement kinds (`docs/b0-sequencing.md` §B0.8, `docs/decision-log.md`
661    // 2026-07-23 "Code-ground sitting"). UFCS *resolution* (field-access-
662    // wins vs. free-fn desugar) is not a grammar concern at all: the call
663    // shape parses and structurally lowers as-is, and the type-directed
664    // verdict is `brink-analyzer::ufcs`' job (issue #1482, B3a) — see
665    // `brink_ir::hir::lower_native::expr`'s module doc.
666    INTEGER_LIT,
667    FLOAT_LIT,
668    STRING_LIT,
669    BOOLEAN_LIT,
670    PATH_EXPR,
671    PAREN_EXPR,
672    PREFIX_EXPR,
673    INFIX_EXPR,
674    CALL_EXPR,
675    ARG_LIST,
676    /// `|x, y| expr` — lambda pipes. Tokenized and structurally parsed in
677    /// B0.5; **lowered** since issue #1685 (`hir::lower_native::lambda` →
678    /// `hir::Expr::Lambda`), per the 2026-07-19 ruling.
679    LAMBDA_EXPR,
680    /// Holds one `PARAM` child per lambda parameter (NG-A, #1487) — each
681    /// parameter used to be a bare `IDENT` token directly under this node;
682    /// promoting them to `PARAM` lets `: type` attach to the right one.
683    LAMBDA_PARAMS,
684    /// `[expr, expr, …]` — the array/sequence literal (NG-D, issue #1490,
685    /// RULED 2026-07-27: "the everyday collection literal deserves the
686    /// lightest spelling"). The B5-symmetric `Array { … }` construction-
687    /// registry entry was weighed and rejected in the same ruling —
688    /// `L_BRACKET`/`R_BRACKET` were already lexed and idle in expression
689    /// position, so this is a new atom, not a `CONSTRUCT_LITERAL` registry
690    /// entry. Holds its element expressions as direct children (same shape
691    /// as [`Self::ARG_LIST`] — no per-element wrapper node, unlike
692    /// [`Self::CONSTRUCT_ENTRY`], since an array element is never a pair).
693    /// Distinct from the type-annotation grammar's `<T>` generic-argument
694    /// syntax (NG-D's sibling ruling, issue #1552): `[ ]` is for *values*,
695    /// `< >` is for *type arguments* — `parser/types.rs` never touches
696    /// `L_BRACKET`, so the two spellings cannot collide.
697    ARRAY_LITERAL,
698
699    // ── Node kinds — the construction initializer (B5, issue #1464, ─────
700    // ── #1103 RULED 2026-07-23, `docs/stdlib-spec.md` §9.6) ─────────────
701    /// `TypeName { … }` — the one construction-initializer grammar
702    /// (`docs/decision-log.md` 2026-07-23 "Collection/construction
703    /// initializer"). The brace *tokens* are fixed surface grammar this
704    /// parser produces; **meaning is protocol dispatch**, resolved one
705    /// layer up by the `construct` registry
706    /// (`brink_ir::hir::construct::ConstructTarget`) against the leading
707    /// [`Self::PATH`], never by this grammar. So `Map { "a": 1 }`,
708    /// `Flags { Red, Blue }`, `Weighted { 3: "gold" }` and a struct's
709    /// `Point { x: 1, y: 2 }` are all one node shape here.
710    CONSTRUCT_LITERAL,
711    /// One entry of a [`Self::CONSTRUCT_LITERAL`], in whichever of the
712    /// three ruled forms the source used: the **element** form (a single
713    /// child expression — `Flags { Red }`), or the **pair**/**field** form
714    /// (two child expressions around a `COLON` — `Map { k: v }`,
715    /// `Point { x: 1 }`). Pair and field are one shape by construction:
716    /// they differ only in what the target type makes of the left-hand
717    /// expression, which is dispatch, not grammar.
718    CONSTRUCT_ENTRY,
719
720    // ── Node kinds — the code-ground statement layer (B0.8 Wave A, ──────
721    // ── `docs/decision-log.md` 2026-07-23 "Code-ground sitting") ────────
722    // RustScript-shaped statements over the expression skeleton above.
723    // Parser only — no HIR lowering yet (that's Wave B, alongside `if`/
724    // `while`/`for`/`until`). `parser/stmt.rs` is the dispatcher.
725    /// `{ stmt* tail? }` — the code-ground body shape. Blocks-as-values
726    /// ruled: an unterminated trailing expression, if present, is the
727    /// block's *tail* — a bare (unwrapped) expression child, the last thing
728    /// before `R_BRACE`. Reached as an expression atom (`expr::atom`'s
729    /// `L_BRACE` case) — a statement-block is itself an expression
730    /// (`let x = { … };` is valid), distinct from the content-ground
731    /// [`Self::BLOCK`] `flow`/`fn`/`module` bodies still use (that seam is
732    /// Wave B's call, not this wave's).
733    STMT_BLOCK,
734    /// `let name = expr;` (initializer optional). Distinct from
735    /// [`Self::VAR_DECL`]/[`Self::CONST_DECL`] — those are declaration-layer
736    /// keywords (B0.5, terminator-free); `let` is code-ground, inside a
737    /// [`Self::STMT_BLOCK`], and always `;`-terminated.
738    LET_STMT,
739    /// `x = expr;` / `x.field = expr;` — a read-modify-write place path
740    /// (charter's RMW-paths ruling). The place is a dotted [`Self::PATH`]
741    /// (no `::` — an assignable place is always local).
742    ASSIGN_STMT,
743    /// `expr;` — a bare expression statement, `;`-terminated. The one
744    /// thing distinguishing this from a [`Self::STMT_BLOCK`]'s unterminated
745    /// tail expression.
746    EXPR_STMT,
747
748    // ── Node kinds — the code-ground statement tail (B0.8 Wave B tail, ──
749    // ── issue #1322, `docs/decision-log.md` 2026-07-23 "Code-ground ────
750    // ── sitting") ────────────────────────────────────────────────────
751    // `break`/`continue` have no content-ground counterpart (loops are a
752    // code-ground-only concept); `return`'s valued form reuses
753    // `Self::RETURN_STMT` (see that variant's doc) rather than adding a
754    // node here. Lowers to the *existing* `~ { … }` T1b closed statement
755    // set (`BlockStmt::{Return,Break,Continue}` in `brink-ir`) — the NF-2
756    // fence, no new HIR nodes.
757    /// `break;` — loop-exit statement, `;`-terminated like every other
758    /// code-ground statement. Legal only inside a `while`/`for` body — an
759    /// out-of-loop `break` is `brink-analyzer`'s job to reject (E057), not
760    /// this grammar's.
761    BREAK_STMT,
762    /// `continue;` — loop-skip statement, `;`-terminated. See
763    /// [`Self::BREAK_STMT`]'s doc for the same in-loop caveat.
764    CONTINUE_STMT,
765
766    // ── Node kinds — the code-ground control-flow layer (B0.8 Wave B, ───
767    // ── `docs/decision-log.md` 2026-07-23 "Code-ground sitting") ────────
768    // Rides Wave A's `STMT_BLOCK` for every body (`parser/control_flow.rs`
769    // reuses `parser/stmt.rs::stmt_block` verbatim — no second block
770    // shape). Lowers to the *existing* `~ { … }` T1b closed statement set
771    // (`IfStmt`/`WhileStmt`/`ForStmt`/`AwaitStmt` in `brink-ir`) — the NF-2
772    // fence, no new HIR nodes.
773    /// `if cond { … } (else if cond { … } | else { … })?`. No case for a
774    /// bare `{` opener here — that's [`Self::CONDITIONAL_BLOCK`]'s
775    /// annotated-brace family, a different (content-ground) construct this
776    /// one does not replace.
777    IF_STMT,
778    /// The `else` arm of an [`Self::IF_STMT`]: either another [`Self::IF_STMT`]
779    /// (an `else if` chain) or a plain [`Self::STMT_BLOCK`].
780    ELSE_CLAUSE,
781    /// `while cond { … }`. Always a plain loop on the native surface — no
782    /// `while await cond { … }` persistent-await form (that's the
783    /// brink-dialect T1b grammar's own concern; native retired `await`
784    /// entirely in favor of [`Self::UNTIL_STMT`], decision-log item 4).
785    WHILE_STMT,
786    /// `for name in expr { … }` — single-binding iteration (charter's
787    /// existing `ForStmt` HIR shape; no destructuring).
788    FOR_STMT,
789    /// `until <pure-bool-expr>;` — the condition-park statement
790    /// (decision-log 2026-07-23 item 4): native's sole flow-suspension
791    /// spelling, replacing `await`. Lowers to the same `AwaitStmt` HIR node
792    /// the brink-dialect's `~ await cond` produces.
793    UNTIL_STMT,
794
795    // ── Node kinds — the type-annotation grammar (NG-A/B/C, issues ──────
796    // ── #1487/#1488/#1489; `docs/decision-log.md` 2026-07-26 "NG-C ─────
797    // ── ruled: `: type` returns everywhere") ────────────────────────────
798    // One `: type` spelling in every position: `fn f(g: Guest): float`,
799    // `flow f(): Quest`, `let x: int = 1;`, `var hp: int = 10`,
800    // `|g: Guest|: bool { … }`. Structurally mirrors the brink dialect's
801    // own TM-2 grammar (`brink-syntax/src/parser/types.rs`) so both
802    // frontends lower to the same `brink_ir::hir::TypeExpr` shape.
803    /// `: type_expr` — the annotation clause itself (the `:` token plus
804    /// exactly one [`Self::TYPE_EXPR`] child).
805    TYPE_ANNOTATION,
806    /// A type expression: wraps exactly one of [`Self::TYPE_NAME`],
807    /// [`Self::TYPE_GENERIC`], or [`Self::TYPE_FN`].
808    TYPE_EXPR,
809    /// A bare nominal type name — `int`, `string`, a struct name. The
810    /// grammar accepts any `IDENT`; recognizing the fixed set is a semantic
811    /// check (`brink-analyzer`), never this parser's concern.
812    TYPE_NAME,
813    /// `name<arg, …>` — `List<L>`, `Map<K, V>`, or any unrecognized
814    /// generic head.
815    TYPE_GENERIC,
816    /// `fn(type, …): type` — a function type. Parses here; the checker
817    /// decides what it means.
818    TYPE_FN,
819
820    // ── Node kind — the `as` binding (B1b, issue #1475, ruled ──────────
821    // ── `docs/decision-log.md` 2026-07-26 "The `as` binding") ──────────
822    /// `as NAME` — the condition-position Option binding, in BOTH of the
823    /// language's condition positions: the statement forms
824    /// ([`Self::IF_STMT`], [`Self::WHILE_STMT`]) and the template form
825    /// ([`Self::CONDITIONAL_BLOCK`]'s `{if …: … else: …}`). One construct,
826    /// one node kind — the ruling explicitly refused a second binding
827    /// grammar. Always the LAST child node of the construct it binds in,
828    /// following the head expression, so every existing "first child node
829    /// that isn't a body/arm" condition accessor keeps working.
830    ///
831    /// Also parsed and lowered inside a [`Self::CHOICE_GUARD`] (issue
832    /// #1508): the guard's binding captures at presentation time, riding
833    /// the same `OptionBind` opcode + frame-slot machinery this construct
834    /// already uses in the other two positions — no separate wire-level
835    /// capture was needed once traced end to end (the choice's
836    /// thread-fork snapshot, which already restores tunnel/function
837    /// temps across a pick, generalizes to the guard's bound slot for
838    /// free). `E146` is retired now that this lowers for real.
839    AS_BINDING,
840
841    /// A parse-error wrapper node — swallows one unexpected token so error
842    /// recovery always makes forward progress.
843    ERROR,
844
845    // Not a real kind — used only for `rowan::Language::kind_to_raw` bounds.
846    #[doc(hidden)]
847    __LAST,
848}
849
850impl SyntaxKind {
851    /// Returns `true` for tokens produced by the lexer (leaf nodes in the CST).
852    #[must_use]
853    pub fn is_token(self) -> bool {
854        matches!(
855            self,
856            Self::WHITESPACE
857                | Self::NEWLINE
858                | Self::LINE_COMMENT
859                | Self::BLOCK_COMMENT
860                | Self::DOC_COMMENT_OUTER
861                | Self::DOC_COMMENT_INNER
862                | Self::KW_PUB
863                | Self::KW_FLOW
864                | Self::KW_FN
865                | Self::KW_VAR
866                | Self::KW_CONST
867                | Self::KW_LET
868                | Self::KW_FLAGS
869                | Self::KW_STRUCT
870                | Self::KW_EXTERN
871                | Self::KW_IMPORT
872                | Self::KW_USE
873                | Self::KW_MODULE
874                | Self::KW_RETURN
875                | Self::KW_REF
876                | Self::KW_IF
877                | Self::KW_MATCH
878                | Self::KW_ELSE
879                | Self::KW_WHILE
880                | Self::KW_FOR
881                | Self::KW_IN
882                | Self::KW_UNTIL
883                | Self::KW_BREAK
884                | Self::KW_CONTINUE
885                | Self::KW_AS
886                | Self::KW_OR
887                | Self::KW_TRUE
888                | Self::KW_FALSE
889                | Self::KW_END
890                | Self::KW_DONE
891                | Self::EQ
892                | Self::PLUS_EQ
893                | Self::MINUS_EQ
894                | Self::STAR_EQ
895                | Self::SLASH_EQ
896                | Self::EQ_EQ
897                | Self::BANG_EQ
898                | Self::LT
899                | Self::GT
900                | Self::LT_EQ
901                | Self::GT_EQ
902                | Self::AMP
903                | Self::AMP_AMP
904                | Self::PLUS
905                | Self::MINUS
906                | Self::STAR
907                | Self::SLASH
908                | Self::PERCENT
909                | Self::CARET
910                | Self::BANG
911                | Self::QUESTION
912                | Self::L_PAREN
913                | Self::R_PAREN
914                | Self::L_BRACE
915                | Self::R_BRACE
916                | Self::L_BRACKET
917                | Self::R_BRACKET
918                | Self::PIPE
919                | Self::COMMA
920                | Self::DOT
921                | Self::COLON
922                | Self::SEMICOLON
923                | Self::COLON_COLON
924                | Self::HASH
925                | Self::TILDE
926                | Self::BACKSLASH
927                | Self::AT
928                | Self::AT_L_BRACKET
929                | Self::GLUE
930                | Self::DIVERT
931                | Self::THREAD
932                | Self::FAT_ARROW
933                | Self::INTEGER
934                | Self::FLOAT
935                | Self::QUOTE
936                | Self::STRING_TEXT
937                | Self::STRING_ESCAPE
938                | Self::IDENT
939                | Self::ERROR_TOKEN
940                | Self::EOF
941        )
942    }
943
944    /// Returns `true` for composite nodes built by the parser.
945    #[must_use]
946    pub fn is_node(self) -> bool {
947        !self.is_token() && self != Self::__LAST
948    }
949
950    /// Returns `true` for trivia — tokens the parser may skip over.
951    /// `NEWLINE` is **not** trivia; it terminates lines and delimits blocks.
952    #[must_use]
953    pub fn is_trivia(self) -> bool {
954        matches!(
955            self,
956            Self::WHITESPACE | Self::LINE_COMMENT | Self::BLOCK_COMMENT
957        )
958    }
959
960    /// Returns `true` for keyword tokens.
961    #[must_use]
962    pub fn is_keyword(self) -> bool {
963        matches!(
964            self,
965            Self::KW_PUB
966                | Self::KW_FLOW
967                | Self::KW_FN
968                | Self::KW_VAR
969                | Self::KW_CONST
970                | Self::KW_LET
971                | Self::KW_FLAGS
972                | Self::KW_STRUCT
973                | Self::KW_EXTERN
974                | Self::KW_IMPORT
975                | Self::KW_USE
976                | Self::KW_MODULE
977                | Self::KW_RETURN
978                | Self::KW_REF
979                | Self::KW_IF
980                | Self::KW_MATCH
981                | Self::KW_ELSE
982                | Self::KW_WHILE
983                | Self::KW_FOR
984                | Self::KW_IN
985                | Self::KW_UNTIL
986                | Self::KW_BREAK
987                | Self::KW_CONTINUE
988                | Self::KW_AS
989                | Self::KW_OR
990                | Self::KW_TRUE
991                | Self::KW_FALSE
992                | Self::KW_END
993                | Self::KW_DONE
994        )
995    }
996}
997
998/// Rowan language tag for the native `.brink` grammar.
999#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1000pub enum NativeLanguage {}
1001
1002impl rowan::Language for NativeLanguage {
1003    type Kind = SyntaxKind;
1004
1005    fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
1006        assert!(raw.0 < SyntaxKind::__LAST as u16);
1007        // SAFETY: `SyntaxKind` is `#[repr(u16)]` with contiguous discriminants,
1008        // and we just checked bounds.
1009        #[expect(unsafe_code, reason = "repr(u16) transmute with bounds check")]
1010        unsafe {
1011            std::mem::transmute::<u16, SyntaxKind>(raw.0)
1012        }
1013    }
1014
1015    fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
1016        rowan::SyntaxKind(kind as u16)
1017    }
1018}
1019
1020/// A rowan `SyntaxNode` parameterized by [`NativeLanguage`].
1021pub type SyntaxNode = rowan::SyntaxNode<NativeLanguage>;
1022/// A rowan `SyntaxToken` parameterized by [`NativeLanguage`].
1023pub type SyntaxToken = rowan::SyntaxToken<NativeLanguage>;
1024/// A rowan `SyntaxElement` parameterized by [`NativeLanguage`].
1025pub type SyntaxElement = rowan::SyntaxElement<NativeLanguage>;
1026
1027#[cfg(test)]
1028mod tests {
1029    use super::*;
1030    use rowan::Language;
1031
1032    #[test]
1033    fn roundtrip_through_rowan() {
1034        let mut i = 0u16;
1035        loop {
1036            if i == SyntaxKind::__LAST as u16 {
1037                break;
1038            }
1039            let raw = rowan::SyntaxKind(i);
1040            let kind = NativeLanguage::kind_from_raw(raw);
1041            let back = NativeLanguage::kind_to_raw(kind);
1042            assert_eq!(raw, back, "roundtrip failed for discriminant {i}");
1043            i += 1;
1044        }
1045    }
1046
1047    #[test]
1048    fn token_node_partition() {
1049        let mut i = 0u16;
1050        loop {
1051            if i == SyntaxKind::__LAST as u16 {
1052                break;
1053            }
1054            let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
1055            assert!(
1056                kind.is_token() ^ kind.is_node(),
1057                "{kind:?} is neither token nor node (or both)"
1058            );
1059            i += 1;
1060        }
1061    }
1062
1063    #[test]
1064    fn trivia_is_subset_of_tokens() {
1065        let mut i = 0u16;
1066        loop {
1067            if i == SyntaxKind::__LAST as u16 {
1068                break;
1069            }
1070            let kind = NativeLanguage::kind_from_raw(rowan::SyntaxKind(i));
1071            if kind.is_trivia() {
1072                assert!(kind.is_token(), "{kind:?} is trivia but not a token");
1073            }
1074            i += 1;
1075        }
1076    }
1077
1078    #[test]
1079    fn newline_is_not_trivia() {
1080        assert!(!SyntaxKind::NEWLINE.is_trivia());
1081        assert!(SyntaxKind::NEWLINE.is_token());
1082    }
1083
1084    #[test]
1085    fn keywords_are_tokens() {
1086        let keywords = [
1087            SyntaxKind::KW_PUB,
1088            SyntaxKind::KW_FLOW,
1089            SyntaxKind::KW_FN,
1090            SyntaxKind::KW_VAR,
1091            SyntaxKind::KW_CONST,
1092            SyntaxKind::KW_LET,
1093            SyntaxKind::KW_FLAGS,
1094            SyntaxKind::KW_STRUCT,
1095            SyntaxKind::KW_EXTERN,
1096            SyntaxKind::KW_IMPORT,
1097            SyntaxKind::KW_USE,
1098            SyntaxKind::KW_MODULE,
1099            SyntaxKind::KW_RETURN,
1100            SyntaxKind::KW_REF,
1101            SyntaxKind::KW_IF,
1102            SyntaxKind::KW_MATCH,
1103            SyntaxKind::KW_ELSE,
1104            SyntaxKind::KW_WHILE,
1105            SyntaxKind::KW_FOR,
1106            SyntaxKind::KW_IN,
1107            SyntaxKind::KW_UNTIL,
1108            SyntaxKind::KW_BREAK,
1109            SyntaxKind::KW_CONTINUE,
1110            SyntaxKind::KW_AS,
1111            SyntaxKind::KW_OR,
1112            SyntaxKind::KW_TRUE,
1113            SyntaxKind::KW_FALSE,
1114            SyntaxKind::KW_END,
1115            SyntaxKind::KW_DONE,
1116        ];
1117        for kw in keywords {
1118            assert!(kw.is_token(), "{kw:?} should be a token");
1119            assert!(kw.is_keyword(), "{kw:?} should be a keyword");
1120        }
1121    }
1122
1123    #[test]
1124    fn non_keywords_are_not_keywords() {
1125        assert!(!SyntaxKind::IDENT.is_keyword());
1126        assert!(!SyntaxKind::PLUS.is_keyword());
1127        assert!(!SyntaxKind::SOURCE_FILE.is_keyword());
1128    }
1129}