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}