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}