Skip to main content

praxis_syntax/
kind.rs

1//! The single vocabulary of Praxis syntax: tokens, trivia, and tree nodes.
2//!
3//! `SyntaxKind` is one enumeration that carries every leaf token the lexer
4//! emits (literals, keywords, operators, trivia) *and* every interior node the
5//! parser produces. This is the rowan idiom (ADR-003): one `#[repr(u16)]` enum
6//! backs a strongly-typed lossless tree, and the [`PraxisLanguage`](crate::PraxisLanguage)
7//! implementation carries it through `rowan`'s generic node types.
8//!
9//! Adding a construct is therefore two edits: a token kind (if it is a new
10//! leaf) or a node kind, plus the parser code that emits it. The kinds are kept
11//! exhaustive here so the lexer and parser never need to invent identifiers at
12//! runtime — illegal kinds are unrepresentable.
13
14// `is_token`/`is_node`/keyword tables are exercised by the unit tests below;
15// the large match arms are exhaustive by construction.
16
17#![allow(dead_code)] // the kind space is exhaustive; not every kind has a consumer.
18
19/// Every lexical token, piece of trivia, and tree node in Praxis.
20///
21/// The ordering inside the enum is grouping-only (comments delimit the
22/// sections) and carries no semantic meaning. The discriminants are stable
23/// `u16` values because rowan stores them as raw integers in the green tree.
24///
25/// Naming convention: keywords carry a `KW_` prefix, punctuation a prefix
26/// matching its role (`L_`/`R_` for matching pairs), and tree nodes an `_EXPR`/
27/// `_STMT`/`_ITEM` suffix. The screaming-snake names make lexical kinds visually
28/// distinct from the CamelCase AST wrappers in `praxis-ast`, which is why we
29/// relax the usual camel-case lint for this one enum.
30#[repr(u16)]
31#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
32#[allow(non_camel_case_types)]
33pub enum SyntaxKind {
34    // ---- Trivia (kept in the lossless tree, ignored for parsing) ----
35    /// A run of spaces, tabs, and newlines outside a comment.
36    Whitespace,
37    /// A `//` line comment (not including the trailing newline).
38    LineComment,
39    /// A nestable `/* ... */` block comment, including delimiters.
40    BlockComment,
41
42    // ---- Identifiers and literals ----
43    /// An identifier that is *not* a keyword.
44    Ident,
45    /// An integer literal, e.g. `42`.
46    IntLit,
47    /// A floating-point literal, e.g. `3.14`, `1e10`, `.5`, `2.` (§4.12).
48    /// A bare `.` is `DOT`; a float literal needs a digit on at least one side
49    /// of the dot, or an exponent. A `.` immediately followed by another `.` is
50    /// a range (`..` / `..=`), never part of a float.
51    FloatLit,
52    /// A double-quoted text literal with **no interpolation holes**, e.g.
53    /// `"hello"` — the whole literal, quotes included.
54    ///
55    /// A literal that holds a `{` is not this kind: it is an
56    /// [`InterpOpen`](Self::InterpOpen) / [`InterpMiddle`](Self::InterpMiddle) /
57    /// [`InterpClose`](Self::InterpClose) run with the holes' ordinary tokens
58    /// between the fragments (§8.1, ADR-147). **An unterminated literal is this
59    /// kind either way**, holes or not: the lexer only splits a literal it has
60    /// already proved closes on its line, so `T004` reports the whole run as one
61    /// token.
62    TextLit,
63    /// The first fragment of an interpolated text literal: the opening `"`, the
64    /// literal text before the first hole, and the `{` that opens it — e.g.
65    /// `"Part 2: {` (§8.1, ADR-147).
66    ///
67    /// The delimiters are *inside* the token, one byte at each end, so the three
68    /// fragment kinds decode identically (`&text[1..len-1]` through
69    /// [`praxis_syntax::literal::decode_text_body`]) and the token stream still
70    /// tiles the source (ADR-003).
71    ///
72    /// The fragments are separate tokens rather than one opaque literal because
73    /// a name inside a hole has to be a **token at its own range** in the
74    /// lossless tree: that is the only way `praxis-hir`'s capture analysis,
75    /// which looks token ranges up in the resolver's map, sees it. A closure
76    /// body of `"{outer}"` would otherwise capture nothing and read a slot
77    /// nothing filled (ADR-147 decision 1).
78    ///
79    /// [`praxis_syntax::literal::decode_text_body`]: crate::literal::decode_text_body
80    InterpOpen,
81    /// A fragment between two holes: the `}` closing one, the literal text, and
82    /// the `{` opening the next — e.g. `} and {`. Empty text is ordinary
83    /// (`"{a}{b}"` has the two-byte fragment `}{`).
84    InterpMiddle,
85    /// The last fragment: the `}` closing the final hole, the trailing literal
86    /// text, and the closing `"` — e.g. `}!"`.
87    InterpClose,
88    /// A single-quoted character literal, e.g. `'#'` (ADR-141).
89    ///
90    /// Exactly **one** Unicode scalar, and the lexer is where that is decided:
91    /// `''` and `'ab'` are `T007`, not a silently truncated `Char`. Its escapes
92    /// are the text literal's — `\n \r \t \0 \\ \"` — plus `\'`, and there are
93    /// no `\x`/`\u{…}` forms, because two escape tables for one language is the
94    /// drift `praxis_syntax::literal`'s module doc was written to forbid.
95    ///
96    /// **This kind means the literal closed.** An unterminated run is still
97    /// pushed as a `CharLit` (losslessness, ADR-003) after a `T006`, so a
98    /// consumer must ask [`praxis_syntax::literal::decode_char_literal`] rather
99    /// than assume; there is no second kind here the way there is for a
100    /// template, because a `'` cannot open a sublanguage nobody scanned.
101    CharLit,
102    /// A backtick-delimited parser template, e.g. `` `{x:int}` ``. The whole
103    /// template is one token; its interior is re-scanned by the input-parser
104    /// lexer (§7).
105    ///
106    /// **This kind means the template closed.** A run that did not is
107    /// [`SyntaxKind::UnterminatedBacktickTemplate`], so a `BacktickTemplate`'s
108    /// text is a complete template *by construction* and no consumer has to
109    /// re-derive that (ADR-094).
110    BacktickTemplate,
111    /// A backtick run that did not close before its line ended (ADR-094).
112    ///
113    /// Two kinds rather than one predicate, because "is this token terminated"
114    /// must not be re-derived by each consumer. A template ends at its line, so
115    /// the common unterminated token *is* `` `{int` ``: a hand-rolled
116    /// `strip_prefix('`').and_then(strip_suffix('`'))` succeeds on it, the
117    /// interior scanner is handed `{int`, and `I030` comes back describing an
118    /// interior nobody wrote — the fabricated-interior class that
119    /// `an_unterminated_template_does_not_also_report_a_fabricated_interior`
120    /// exists to forbid.
121    ///
122    /// So the state is made unrepresentable instead: the lexer decides once,
123    /// and a consumer that receives this kind knows there is nothing to scan.
124    /// It also means such a token can be typed with a fresh variable rather than
125    /// drawing `Y023` ("write `read` before it") — advice that cannot close a
126    /// template.
127    UnterminatedBacktickTemplate,
128
129    // ---- Keywords (§4) ----
130    KW_VAR,      // `var`
131    KW_FN,       // `fn`
132    KW_IF,       // `if`
133    KW_ELSE,     // `else`
134    KW_WHILE,    // `while`
135    KW_FOR,      // `for`
136    KW_IN,       // `in` (for-loop iterator separator, §4.11)
137    KW_LOOP,     // `loop`
138    KW_MATCH,    // `match`
139    KW_RETURN,   // `return`
140    KW_BREAK,    // `break`
141    KW_CONTINUE, // `continue`
142    KW_READ,     // `read`
143    KW_STRUCT,   // `struct`
144    KW_ENUM,     // `enum`
145    KW_TRUE,     // `true`
146    KW_FALSE,    // `false`
147
148    // ---- Punctuation and operators ----
149    /// `(`
150    L_PAREN,
151    /// `)`
152    R_PAREN,
153    /// `{`
154    L_BRACE,
155    /// `}`
156    R_BRACE,
157    /// `[`
158    L_BRACK,
159    /// `]`
160    R_BRACK,
161    /// `,`
162    COMMA,
163    /// `.`
164    DOT,
165    /// `..`
166    DOT2,
167    /// `..=`
168    DOT2EQ,
169    /// `:`
170    COLON,
171    /// `;`
172    SEMICOLON,
173    /// `->`
174    THIN_ARROW,
175    /// `=>`
176    FAT_ARROW,
177    /// `#`
178    HASH,
179    /// `|`
180    PIPE,
181    /// `||`
182    PIPE2,
183    /// `&`
184    AMP,
185    /// `&&` — logical and. The lexer's max-munch keeps it one token, as it does
186    /// `||`, so a bare `AMP` is never part of one.
187    AMP2,
188    /// `_` — a lone underscore (placeholder/punning site).
189    UNDERSCORE,
190
191    // Arithmetic operators.
192    /// `+`
193    PLUS,
194    /// `-`
195    MINUS,
196    /// `*`
197    STAR,
198    /// `/`
199    SLASH,
200    /// `%`
201    PERCENT,
202
203    // Compound-assignment operators.
204    /// `+=`
205    PLUS_EQ,
206    /// `-=`
207    MINUS_EQ,
208    /// `*=`
209    STAR_EQ,
210    /// `/=`
211    SLASH_EQ,
212    /// `%=`
213    PERCENT_EQ,
214
215    // Comparison operators.
216    /// `==`
217    EQ2,
218    /// `!=`
219    NEQ,
220    /// `<`
221    LT,
222    /// `>`
223    GT,
224    /// `<=`
225    LTEQ,
226    /// `>=`
227    GTEQ,
228
229    /// `=` (assignment / binding).
230    EQ,
231    /// `!` (logical not).
232    BANG,
233    /// `?` (reserved for later use).
234    QUESTION,
235
236    // ---- Sentinel ----
237    /// End of input. Emitted as the final token so the parser can treat EOF
238    /// uniformly.
239    EOF,
240    /// A byte the lexer does not recognize. The lexer also emits a real
241    /// diagnostic (`T003`) for it rather than silently dropping it.
242    ERROR,
243
244    // ---- Tree nodes (produced by the parser) ----
245    /// The root node of a parsed file.
246    SOURCE_FILE,
247    /// A `var name = expr` binding — the language's one binding form (ADR-125).
248    VAR_STMT,
249    /// A bare expression used as a statement.
250    EXPR_STMT,
251    /// A reassignment statement: `name = expr` or `name += expr` etc. (§4.2).
252    ASSIGN_STMT,
253    /// A reassignment through a place expression: `m[key] = expr`,
254    /// `counts[key] += 1` (§6.2).
255    ///
256    /// Its own kind rather than an `ASSIGN_STMT` with an expression target: an
257    /// `ASSIGN_STMT`'s target is a *token* and its single expression child is the
258    /// value, so a target that is itself an expression cannot be told from the
259    /// value. The target here is the first expression child and the value the
260    /// second.
261    PLACE_ASSIGN_STMT,
262    /// The two-token `min=` / `max=` operator of an updating store (§6.2): an
263    /// `Ident` spelling `min` or `max`, immediately followed by `=`.
264    ///
265    /// A node rather than a token because `min` **is** an identifier — the lexer
266    /// cannot claim it without taking `min` away from every program that names
267    /// the prelude helper — so the operator is decided contextually, at the one
268    /// position where an identifier cannot otherwise appear. Wrapping the pair
269    /// keeps the `=` from being a direct child of the statement, where a walk
270    /// looking for the assignment operator would read the update as a plain
271    /// store.
272    UPDATE_OP,
273    /// The two-token `:bp` marker a statement may end with (§9.8): a `COLON`
274    /// immediately followed by an `Ident` spelling `bp`.
275    ///
276    /// A node rather than a token for [`UPDATE_OP`](Self::UPDATE_OP)'s reason,
277    /// and the same reason it is decided by *position* instead of by the lexer:
278    /// `bp` is an identifier everywhere else, and a lexer rule claiming `:bp`
279    /// would take `bp` away from every program that annotates a binding with a
280    /// type whose name begins that way. The one place an identifier cannot
281    /// otherwise follow a `:` is the end of a statement, which is exactly where
282    /// this is admitted. Wrapping the pair keeps the `:` from being a direct
283    /// child of the statement, where a walk looking for a type annotation would
284    /// find it.
285    BREAKPOINT,
286    /// A top-level or nested `fn` declaration.
287    FN_ITEM,
288    /// A `struct Name { field: Type, … }` declaration (§4.5).
289    STRUCT_ITEM,
290    /// An `enum Name { Variant, Variant(Type), … }` declaration (§4.6).
291    ENUM_ITEM,
292    /// One variant of an enum: `Name` or `Name(Type, …)`.
293    ENUM_VARIANT,
294    /// The `{ field: Type, … }` body of a struct declaration.
295    FIELD_LIST,
296    /// A single `name: Type` field of a struct.
297    FIELD,
298    /// A `Name { field: expr, … }` record-literal expression (§4.5).
299    RECORD_LIT_EXPR,
300    /// A `receiver.0` tuple-element expression (§4.4).
301    ///
302    /// Its own kind rather than a `FIELD_EXPR` holding an `IntLit`: an element is
303    /// selected by **position** and the index must be a literal, where a field is
304    /// selected by name — two different operations that lower to two different
305    /// runtime calls.
306    TUPLE_INDEX_EXPR,
307    /// The `[Type, …]` type-argument list of a constructor call (§3.3):
308    /// the brackets in `Counter[(Int, Int)]()`.
309    ///
310    /// Its own kind rather than an `INDEX_EXPR` holding types: the brackets in
311    /// `Counter[(Int, Int)]()` and in `m[key]` are the same two characters and
312    /// two different operations, and only the *name* in front tells them apart
313    /// (`Int` is a legal expression too, so the contents cannot).
314    TYPE_ARG_LIST,
315    /// A `receiver[index]` subscript expression (§4.7/§6.2/§6.4).
316    ///
317    /// The index list is an `ARG_LIST`, because §6.4's `grid[x, y]` makes a
318    /// subscript variadic: the arity is part of what selects the operation, the
319    /// same way a method call's is.
320    INDEX_EXPR,
321    /// A `receiver.field` field-access expression (§4.5).
322    FIELD_EXPR,
323    /// A `match scrutinee { pattern => expr, … }` expression (§4.6/§4.11).
324    MATCH_EXPR,
325    /// A closure expression `|params| expr` (§4.10). Bare `PIPE` claims the
326    /// `|` (lexer max-munch keeps `||` as logical-or `PIPE2`).
327    CLOSURE_EXPR,
328    /// One `pattern => expr` arm of a match expression.
329    MATCH_ARM,
330    /// A pattern (§4.6): wildcard `_`, literal, variable bind, enum variant,
331    /// or tuple/record destructuring.
332    PATTERN,
333    /// One `name` or `name: pattern` field of a record pattern (§4.5).
334    ///
335    /// Its own kind rather than the [`FIELD`](Self::FIELD) a struct declaration
336    /// and a record literal share: those hold a type and an expression, and this
337    /// holds a *pattern*. A punned `P { x }` and an explicit `P { x: p }` are
338    /// then one node shape — the name is always the token, the sub-pattern is
339    /// always the optional child — so pairing a field with its pattern never has
340    /// to count identifiers.
341    PATTERN_FIELD,
342    /// A single `name: Type` parameter.
343    PARAM,
344    /// The `(...)` parameter list.
345    PARAM_LIST,
346    /// A `{ ... }` block expression.
347    BLOCK_EXPR,
348    /// An `if cond { ... } else { ... }` expression.
349    IF_EXPR,
350    /// The `else` arm (block or `else if`).
351    ELSE_BRANCH,
352    /// A `while cond { ... }` expression.
353    WHILE_EXPR,
354    /// A `for pat in iter { ... }` expression (§4.11).
355    FOR_EXPR,
356    /// A `loop { ... }` expression (§4.11).
357    LOOP_EXPR,
358    /// A `break [expr]` expression (§4.11).
359    BREAK_EXPR,
360    /// A `continue` expression (§4.11).
361    CONTINUE_EXPR,
362    /// A `return [expr]` expression (§4.11).
363    RETURN_EXPR,
364    /// A `callee(args)` call expression (covers `out(...)`).
365    CALL_EXPR,
366    /// A `receiver.method(args)` method-call expression (§16.2).
367    METHOD_CALL_EXPR,
368    /// The `(arg, arg, ...)` argument list of a call.
369    ARG_LIST,
370    /// A path: an identifier or a dotted name.
371    PATH_EXPR,
372    /// A literal value
373    /// (`IntLit`/`FloatLit`/`TextLit`/`CharLit`/`true`/`false`/backtick
374    /// template).
375    LITERAL,
376    /// An interpolated text literal: `"a{x}b"` (§8.1, ADR-147).
377    ///
378    /// Its children alternate — [`InterpOpen`](Self::InterpOpen), an expression,
379    /// then zero or more [`InterpMiddle`](Self::InterpMiddle)/expression pairs,
380    /// then [`InterpClose`](Self::InterpClose) — and the expressions are
381    /// ordinary expression subtrees, not a sublanguage.
382    ///
383    /// Its own kind rather than a [`LITERAL`](Self::LITERAL) with children,
384    /// because it is not one: a `LITERAL` is a leaf whose value the lowerer
385    /// reads off a token, and every walk in the workspace that finds names,
386    /// resolves them, renames them or captures them has to descend into a hole.
387    /// Giving `LITERAL` children would have made "does this node contain a name"
388    /// a question with two answers.
389    INTERP_EXPR,
390    /// A reference to a name (identifier used as a value).
391    NAME_REF,
392    /// A binary operator expression, e.g. `a + b`.
393    BIN_EXPR,
394    /// A range expression: `a..b` (half-open) or `a..=b` (inclusive) — §4.11,
395    /// ADR-059. Its own node kind rather than a [`BIN_EXPR`](Self::BIN_EXPR):
396    /// a range is not an operator applied to two numbers, it is a *collection*
397    /// built from two bounds, and every consumer that asks "what binary
398    /// operator is this" would otherwise have to answer "none of them".
399    RANGE_EXPR,
400    /// A unary operator expression, e.g. `-x`.
401    UNARY_EXPR,
402    /// A parenthesized expression `( expr )`.
403    PAREN_EXPR,
404    /// A tuple expression `( e1, e2, … )` with two or more elements. A
405    /// single parenthesized value is [`PAREN_EXPR`](Self::PAREN_EXPR), not this.
406    TUPLE_EXPR,
407    /// A list expression `[ e1, e2, … ]` — a `Vec` literal (§6.1).
408    ///
409    /// Its own kind rather than an [`INDEX_EXPR`](Self::INDEX_EXPR) with no
410    /// receiver: the brackets in `[1, 2]` and in `m[k]` are the same two
411    /// characters and two different operations, and what tells them apart is
412    /// **position** — a subscript continues an expression, a list begins one.
413    /// That is the rule [`TYPE_ARG_LIST`](Self::TYPE_ARG_LIST) is decided by, and
414    /// the rule that decides the `(` too.
415    LIST_EXPR,
416    /// A type written in source: a scalar or grouped type name (`Int`, `Text`, …),
417    /// with or without a bracketed type-argument list (§4.4). Tuple and
418    /// function types carry their own kinds.
419    TYPE_REF,
420    /// A tuple type `(T, U, …)`. A parenthesized single type `(T)` is just `T`,
421    /// so this always carries two or more elements.
422    TUPLE_TYPE,
423    /// A function type `(P0, P1, …) -> R`.
424    FN_TYPE,
425    /// A parse-error placeholder node wrapping tokens the parser could not
426    /// place. Recovery (§15.2) emits these so the tree stays well-formed.
427    PARSE_ERROR,
428    // ---- Input-parser expression nodes (§7) ----
429    /// `read parser_expression` — a prefix expression applying a parser to the
430    /// whole process-input buffer (§7.1).
431    READ_EXPR,
432    /// `parse(text, parser_expression)` — apply a parser to an existing `Text`
433    /// value (§7.1).
434    PARSE_EXPR,
435    /// A parser expression (§7 EBNF): an atomic, a template, or a constructor
436    /// call. The body of `read` and the second arg of `parse`.
437    PARSER_EXPR,
438    /// An atomic parser name: `int`, `char`, `word`, etc. (§7.4).
439    PARSER_ATOM,
440    /// A backtick template `` `{x:int},{y:int}` `` inside a parser expression
441    /// (§7.2). Its children are the scanned template parts.
442    PARSER_TEMPLATE,
443    /// A `{name:parser}` or `{parser}` capture inside a template (§7.3).
444    PARSER_CAPTURE,
445    /// A constructor call `lines(P)`, `csv(P)`, `sep(sep, P)`, etc. (§7.5).
446    PARSER_CALL,
447    /// The `(arg, arg, ...)` argument list of a parser constructor call.
448    PARSER_ARG_LIST,
449    /// A named argument inside a parser constructor call (§7.5):
450    /// `name: parser_expr`, e.g. `rules: lines(int)` in heterogeneous
451    /// `sections`, or `skip: whitespace` in `chars`. Holds the name ident, the
452    /// `:`, and the parser-expr value.
453    PARSER_NAMED_ARG,
454    /// The **literal** value of a keyword argument inside a parser constructor
455    /// call: the `0` of `grid(char, ragged, fill: 0)` or the `"-"` of
456    /// `fill: "-"` (§7.5).
457    ///
458    /// Its own kind because a keyword argument's value is not a parser
459    /// expression and cannot be parsed as one: handing it to `parse_parser_expr`
460    /// reports `P001 expected a parser expression` and leaves a `PARSE_ERROR`
461    /// with no literal for the HIR bridge to read, so §7.5's own documented
462    /// spelling would build a ragged grid padded with `""` instead of `0`.
463    PARSER_KEYWORD_VALUE,
464}
465
466impl SyntaxKind {
467    /// Whether this kind is trivia: whitespace or a comment. Trivia is kept in
468    /// the lossless tree (§13.1) but skipped for parsing decisions.
469    #[must_use]
470    pub fn is_trivia(self) -> bool {
471        matches!(
472            self,
473            Self::Whitespace | Self::LineComment | Self::BlockComment
474        )
475    }
476
477    /// Whether this kind is a keyword token.
478    ///
479    /// Derived from [`SyntaxKind::keyword_text`] rather than maintained as a
480    /// second list, so a kind cannot be a keyword in one table and not in the
481    /// other.
482    #[must_use]
483    pub fn is_keyword(self) -> bool {
484        self.keyword_text().is_some()
485    }
486
487    /// Every keyword's source spelling, in discriminant order.
488    ///
489    /// **Swept, not listed.** The whole kind space is walked and filtered by
490    /// [`is_keyword`](Self::is_keyword), so a keyword added to
491    /// [`keyword_text`](Self::keyword_text) joins this by construction.
492    ///
493    /// The TextMate grammar is tested against this: the editor's keyword
494    /// pattern is a copy of the lexer's table that no compiler checks, and the
495    /// failure — a word quietly stopping being coloured — is one nobody files.
496    #[must_use]
497    pub fn all_keyword_texts() -> Vec<&'static str> {
498        (0..=Self::LAST)
499            .map(Self::from_raw_u16)
500            .filter_map(Self::keyword_text)
501            .collect()
502    }
503
504    /// Whether this kind is one of the three shapes a written type annotation
505    /// can take: a name (with or without bracketed arguments), a tuple, or a
506    /// function type.
507    ///
508    /// The set lives here, once, because everything that looks at an annotation
509    /// needs the same answer: `praxis_ast::TypeRef::cast` accepts exactly these
510    /// kinds, and type resolution recurses through exactly these children. A
511    /// site that spelled the list out for itself and listed only `TYPE_REF`
512    /// would silently drop every direct tuple and function annotation.
513    #[must_use]
514    pub fn is_type_node(self) -> bool {
515        matches!(self, Self::TYPE_REF | Self::TUPLE_TYPE | Self::FN_TYPE)
516    }
517
518    /// Whether this kind is a token the parser wraps in a
519    /// [`LITERAL`](Self::LITERAL) node: the four scalar literals, `true`/`false`,
520    /// and both backtick-template kinds.
521    ///
522    /// Here for [`is_type_node`](Self::is_type_node)'s reason: the parser writes
523    /// this set when it builds the node and `praxis_ast::Literal::token` reads
524    /// it back. Two copies drift, and a reader missing a kind answers `None` for
525    /// a `LITERAL` the parser really did build, dropping every HIR pass into its
526    /// "no token at all" branch.
527    ///
528    /// **Both template kinds are in.** A template in value position has no
529    /// meaning — §7.1 enters the parser sublanguage at `read`/`parse` and nowhere
530    /// else — and is reported as `Y023`, but it is reported *about the token*,
531    /// and an accessor that cannot see the token cannot report on it. The
532    /// unterminated one draws no `Y023`, since that advice cannot close a
533    /// template (ADR-094); it types as a fresh variable, which is exactly what
534    /// the missing-token branch happened to produce.
535    ///
536    /// `true`/`false` are literals too, and take the same parse arm: an arm of
537    /// their own that did not eat leading trivia first would make `true` span
538    /// `" true"` where `1` spans `"1"`.
539    #[must_use]
540    pub fn is_literal_token(self) -> bool {
541        matches!(
542            self,
543            Self::IntLit
544                | Self::FloatLit
545                | Self::TextLit
546                | Self::CharLit
547                | Self::BacktickTemplate
548                | Self::UnterminatedBacktickTemplate
549                | Self::KW_TRUE
550                | Self::KW_FALSE
551        )
552    }
553
554    /// Whether this kind is a literal a **pattern** may test against (§4.6): an
555    /// integer, text, a character, `true` or `false`.
556    ///
557    /// Strictly narrower than [`is_literal_token`](Self::is_literal_token), and
558    /// the difference is that a pattern tests a *constant*. There is no float
559    /// pattern (§4.6), and a backtick template is not a constant either — nor is
560    /// an interpolated literal, which the parser refuses in pattern position
561    /// outright (ADR-147): `match s { "{x}" => … }` would otherwise leave a
562    /// pattern whose only direct `Ident` is the hole's `x`, read as a variable
563    /// bind, and swallow every value.
564    ///
565    /// `CharLit` is in the set (ADR-141). A caller's copy of this list that
566    /// omitted it would stop a `match` arm list after `'#' => …`, dropping every
567    /// arm below it from the tree with no diagnostic at all.
568    #[must_use]
569    pub fn is_pattern_literal(self) -> bool {
570        matches!(
571            self,
572            Self::IntLit | Self::TextLit | Self::CharLit | Self::KW_TRUE | Self::KW_FALSE
573        )
574    }
575
576    /// The largest discriminant. Sound because the enum declares no explicit
577    /// discriminants, so its values are consecutive from zero — which
578    /// [`SyntaxKind::from_raw_u16`] relies on and
579    /// `every_raw_value_in_range_round_trips` checks.
580    const LAST: u16 = SyntaxKind::PARSER_KEYWORD_VALUE as u16;
581
582    /// Total conversion from a raw `u16`. Out-of-range values become
583    /// [`SyntaxKind::ERROR`] — the safe rowan `Language` boundary must never
584    /// construct an invalid enum discriminant, whatever the input.
585    #[must_use]
586    pub const fn from_raw_u16(raw: u16) -> SyntaxKind {
587        if raw > Self::LAST {
588            return SyntaxKind::ERROR;
589        }
590        // SAFETY: `SyntaxKind` is `#[repr(u16)]` with no explicit
591        // discriminants, so 0..=LAST are exactly its valid values, and `raw` is
592        // checked to be in that range.
593        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw) }
594    }
595
596    /// Whether this kind is a leaf token (emitted by the lexer), as opposed to
597    /// trivia or an interior tree node.
598    #[must_use]
599    pub fn is_token(self) -> bool {
600        !self.is_trivia() && !self.is_node()
601    }
602
603    /// Whether this kind is an interior tree node (produced by the parser).
604    #[must_use]
605    pub fn is_node(self) -> bool {
606        self >= Self::SOURCE_FILE
607    }
608
609    /// Look up the keyword kind for an identifier's text, or `None` if it is a
610    /// plain identifier. Used by the lexer to split keywords out of the ident
611    /// run via a single table.
612    #[must_use]
613    pub fn from_keyword(text: &str) -> Option<SyntaxKind> {
614        Some(match text {
615            "var" => Self::KW_VAR,
616            "fn" => Self::KW_FN,
617            "if" => Self::KW_IF,
618            "else" => Self::KW_ELSE,
619            "while" => Self::KW_WHILE,
620            "for" => Self::KW_FOR,
621            "in" => Self::KW_IN,
622            "loop" => Self::KW_LOOP,
623            "match" => Self::KW_MATCH,
624            "return" => Self::KW_RETURN,
625            "break" => Self::KW_BREAK,
626            "continue" => Self::KW_CONTINUE,
627            "read" => Self::KW_READ,
628            "struct" => Self::KW_STRUCT,
629            "enum" => Self::KW_ENUM,
630            "true" => Self::KW_TRUE,
631            "false" => Self::KW_FALSE,
632            _ => return None,
633        })
634    }
635
636    /// The source spelling of a keyword, or `None` for non-keywords. The
637    /// inverse of [`from_keyword`]; handy for diagnostics and completion, which
638    /// have to spell a keyword back out.
639    #[must_use]
640    pub fn keyword_text(self) -> Option<&'static str> {
641        Some(match self {
642            Self::KW_VAR => "var",
643            Self::KW_FN => "fn",
644            Self::KW_IF => "if",
645            Self::KW_ELSE => "else",
646            Self::KW_WHILE => "while",
647            Self::KW_FOR => "for",
648            Self::KW_IN => "in",
649            Self::KW_LOOP => "loop",
650            Self::KW_MATCH => "match",
651            Self::KW_RETURN => "return",
652            Self::KW_BREAK => "break",
653            Self::KW_CONTINUE => "continue",
654            Self::KW_READ => "read",
655            Self::KW_STRUCT => "struct",
656            Self::KW_ENUM => "enum",
657            Self::KW_TRUE => "true",
658            Self::KW_FALSE => "false",
659            _ => return None,
660        })
661    }
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667
668    #[test]
669    fn trivia_classification() {
670        assert!(SyntaxKind::Whitespace.is_trivia());
671        assert!(SyntaxKind::LineComment.is_trivia());
672        assert!(SyntaxKind::BlockComment.is_trivia());
673        assert!(!SyntaxKind::Ident.is_trivia());
674        assert!(!SyntaxKind::KW_IF.is_trivia());
675        assert!(!SyntaxKind::PLUS.is_trivia());
676    }
677
678    #[test]
679    fn token_vs_node_partition() {
680        // Tokens and trivia are not nodes; everything from SOURCE_FILE up is.
681        assert!(SyntaxKind::Ident.is_token());
682        assert!(SyntaxKind::IntLit.is_token());
683        // A newly inserted token kind lands *before* `SOURCE_FILE` or it is
684        // silently reclassified: `is_node` is `self >= SOURCE_FILE`, so the
685        // partition is decided by declaration order and nothing else says so.
686        assert!(SyntaxKind::CharLit.is_token());
687        assert!(!SyntaxKind::CharLit.is_node());
688        assert!(SyntaxKind::KW_VAR.is_token());
689        assert!(SyntaxKind::PLUS.is_token());
690        assert!(SyntaxKind::EOF.is_token());
691        assert!(!SyntaxKind::Whitespace.is_token()); // trivia, not a token
692        assert!(!SyntaxKind::VAR_STMT.is_token()); // node
693        assert!(SyntaxKind::SOURCE_FILE.is_node());
694        assert!(SyntaxKind::PARSE_ERROR.is_node());
695        assert!(!SyntaxKind::Ident.is_node());
696        assert!(!SyntaxKind::EOF.is_node());
697    }
698
699    #[test]
700    fn every_pattern_literal_is_a_literal_token() {
701        // Swept, not listed: the pattern set is a strict subset of the literal
702        // set, so a kind added to one and forgotten in the other is caught here
703        // rather than by a `Literal::token` that quietly answers `None`.
704        for raw in 0..=SyntaxKind::LAST {
705            let kind = SyntaxKind::from_raw_u16(raw);
706            assert!(
707                !kind.is_pattern_literal() || kind.is_literal_token(),
708                "{kind:?} tests as a pattern literal but is not a literal token"
709            );
710        }
711        // The three the pattern grammar leaves out, each for the doc's reason.
712        assert!(!SyntaxKind::FloatLit.is_pattern_literal()); // §4.6: no float pattern
713        assert!(!SyntaxKind::BacktickTemplate.is_pattern_literal());
714        assert!(!SyntaxKind::UnterminatedBacktickTemplate.is_pattern_literal());
715        // …and the two that must be in their sets.
716        assert!(SyntaxKind::CharLit.is_pattern_literal()); // ADR-141
717        assert!(SyntaxKind::UnterminatedBacktickTemplate.is_literal_token()); // ADR-094
718        assert!(!SyntaxKind::Ident.is_literal_token());
719        assert!(!SyntaxKind::UNDERSCORE.is_pattern_literal());
720    }
721
722    #[test]
723    fn keyword_round_trip() {
724        // Every keyword round-trips through from_keyword/keyword_text.
725        let all = [
726            SyntaxKind::KW_VAR,
727            SyntaxKind::KW_FN,
728            SyntaxKind::KW_IF,
729            SyntaxKind::KW_ELSE,
730            SyntaxKind::KW_WHILE,
731            SyntaxKind::KW_FOR,
732            SyntaxKind::KW_LOOP,
733            SyntaxKind::KW_MATCH,
734            SyntaxKind::KW_RETURN,
735            SyntaxKind::KW_BREAK,
736            SyntaxKind::KW_CONTINUE,
737            SyntaxKind::KW_READ,
738            SyntaxKind::KW_STRUCT,
739            SyntaxKind::KW_ENUM,
740            SyntaxKind::KW_TRUE,
741            SyntaxKind::KW_FALSE,
742        ];
743        for kw in all {
744            assert!(kw.is_keyword());
745            let text = kw.keyword_text().expect("keyword has text");
746            assert_eq!(SyntaxKind::from_keyword(text), Some(kw), "{text}");
747        }
748    }
749
750    #[test]
751    fn regression_in_is_classified_consistently_with_the_keyword_table() {
752        let kind = SyntaxKind::from_keyword("in").expect("`in` is a keyword");
753        assert_eq!(kind, SyntaxKind::KW_IN);
754        assert_eq!(kind.keyword_text(), Some("in"));
755        assert!(
756            kind.is_keyword(),
757            "every kind produced by from_keyword must satisfy is_keyword"
758        );
759    }
760
761    #[test]
762    fn non_keywords_do_not_classify_as_keywords() {
763        assert_eq!(SyntaxKind::from_keyword("out"), None); // builtin, not keyword
764        assert_eq!(SyntaxKind::from_keyword("x"), None);
765        assert_eq!(SyntaxKind::from_keyword("Int"), None); // type name is an ident
766        assert!(!SyntaxKind::Ident.is_keyword());
767        assert!(!SyntaxKind::PLUS.is_keyword());
768    }
769}