trex/token.rs
1//! The typed-token contract shared by the lexer and the engine.
2//!
3//! Every trex atom (`\N`, `\W`, `\Q`, `\I`, ...) names a
4//! [`TokenKind`]. The lexer produces a flat `Vec<Token>` over the
5//! input; the derivative engine consumes that slice. Bracket
6//! pairing is recorded on the token itself ([`Token::mate`]) so
7//! that matching a balanced, nestable group is a constant-time
8//! jump rather than a recursive descent at match time.
9//!
10//! This module defines only the data contract. The lexer that fills it
11//! ([`crate::lexer`]) and the engine that reads it ([`crate::engine`])
12//! live in their own modules.
13
14use std::num::NonZeroU32;
15
16/// The three bracket pairs the lexer balances.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum BracketKind {
19 /// `(` and `)`.
20 Paren,
21 /// `[` and `]`.
22 Square,
23 /// `{` and `}`.
24 Brace,
25}
26
27/// The typed class of a single token.
28///
29/// Each variant is the target of one surface atom. Promoting a
30/// span to one of these classes is the lexer's job; once classed,
31/// a whole number or a whole quoted string is one atom, which is
32/// what lets a trex pattern stay on one line.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum TokenKind {
35 /// Integer or floating-point literal. Surface atom: `\N`.
36 Number,
37 /// Word or identifier run. Surface atom: `\W`.
38 Word,
39 /// Quoted string, including its delimiters, with escapes
40 /// resolved by the lexer. A string closes on its own line, or a
41 /// backslash carries it past the newline; a quote its line does not
42 /// close is punctuation. Surface atom: `\Q`.
43 Quoted,
44 /// IPv4 or IPv6 address. Surface atom: `\I`.
45 Ip,
46 /// URL. Surface atom: `\U`.
47 Url,
48 /// Email address. Surface atom: `\E`.
49 Email,
50 /// Date or timestamp. Surface atom: `\T`.
51 Timestamp,
52 /// A semantic-version string, `MAJOR.MINOR.PATCH` with an optional
53 /// `-prerelease` and/or `+build` suffix. Surface atom: `\V`.
54 Version,
55 /// A UUID / GUID: `8-4-4-4-12` hex digits. Surface atom: `\{uuid}`.
56 Uuid,
57 /// A MAC / EUI-48 hardware address: six `:`- or `-`-separated hex
58 /// pairs. Surface atom: `\A`.
59 Mac,
60 /// A hex color literal, `#rgb` or `#rrggbb`. Surface atom: `\H`.
61 HexColor,
62 /// A CIDR block: an IPv4 address with a `/prefix`. Surface atom: `\C`.
63 Cidr,
64 /// A percentage, `N%` or `N.N%`. Surface atom: `\%`.
65 Percent,
66 /// A byte size with a unit, `10MB` / `1.5GiB` / `512KB`. Surface atom: `\Z`.
67 ByteSize,
68 /// A money amount, `$1,234.56` / `$5`. Surface atom: `\$`.
69 Money,
70 /// A hash digest: a run of exactly 32 / 40 / 64 hex characters with at
71 /// least one hex letter (md5 / sha1 / sha256). Surface atom: `\D`.
72 HashDigest,
73 /// A hex run: 32 or more hex characters with at least one hex letter, at
74 /// a length no hash digest has (a key, a dump, a digest of another
75 /// size). Surface atom: `\{hex}`.
76 Hex,
77 /// A duration: one or more `number + time-unit` segments
78 /// (`1500ms`, `2.5s`, `3h20m`). Surface atom: `\R`.
79 Duration,
80 /// A filesystem path (`/usr/bin/x`, `./rel`, `../up`, `~/home`,
81 /// `C:\dir\file`, `\\server\share`). Surface atom: `\L`.
82 Path,
83 /// A JSON Web Token: three base64url segments separated by dots
84 /// (`header.payload.signature`). Surface atom: `\{jwt}`.
85 Jwt,
86 /// A payment-card number, 13-19 digits, contiguous or grouped the way an
87 /// issuer prints them (`4-4-4-4`, `4-6-5`, `4-6-4` or `4-4-4-4-3`, with
88 /// one separator throughout, a space or a hyphen), that passes the Luhn
89 /// check. Surface atom: `\{creditcard}`.
90 CreditCard,
91 /// A base64 / base64url blob: a `[A-Za-z0-9+/]` run of length a multiple of
92 /// four and at least 16, with charset diversity (so a plain word is not one)
93 /// and optional `=` padding. Surface atom: `\{base64}`.
94 Base64,
95 /// A geographic coordinate in decimal degrees, `lat,long` with both in range
96 /// (lat -90..90, long -180..180), each carrying four fractional digits, and
97 /// the pair on its own rather than inside a longer comma-separated run.
98 /// Surface atom: `\{geo}`.
99 Geo,
100 /// A telephone number: international, a `+` then an ITU-T E.164 country
101 /// calling code and 7-15 digits in all; or North American written
102 /// nationally, `NPA-NXX-XXXX` hyphenated with an optional `1-` prefix.
103 /// Surface atom: `\{phone}`.
104 Phone,
105 /// A physical quantity: a number, signed where nothing alphanumeric
106 /// precedes the sign, then a unit symbol of [`crate::quantity`]'s table,
107 /// attached or one space apart (`5kg`, `-40°C`, `3.2 GHz`, `40 %`).
108 /// Surface atom: `\{quantity}`; `\{qty}` is the class of every kind a
109 /// quantity predicate reads.
110 Quantity,
111 /// Run of insignificant whitespace. Surface atom: `\S`.
112 /// Skipped between atoms in token-mode unless matched
113 /// explicitly.
114 Whitespace,
115 /// A single punctuation token. Surface atom: `\P`.
116 Punct,
117 /// An opening bracket of the given kind. Its [`Token::mate`]
118 /// points at the matching close.
119 Open(BracketKind),
120 /// A closing bracket of the given kind. Its [`Token::mate`]
121 /// points back at the matching open.
122 Close(BracketKind),
123 /// A span matching a user-declared shape, identified by its id in the
124 /// [`crate::custom::ShapeSet`] that lexed it. Surface atom: `\{name}`,
125 /// the name that shape was declared under.
126 Custom(u8),
127 /// A token the lexer did not assign a more specific class.
128 Other,
129}
130
131impl TokenKind {
132 /// Whether this kind is skipped between atoms in token-mode.
133 #[must_use]
134 pub fn is_insignificant(self) -> bool {
135 matches!(self, TokenKind::Whitespace)
136 }
137
138 /// The bracket a kind code stands for and whether it opens, or `None` for
139 /// a code that is no bracket.
140 ///
141 /// Beside [`TokenKind::code`] so both directions of the encoding read from
142 /// the one table, and derived from it rather than written out a second
143 /// time. A reader holding codes and not kinds - the significant stream
144 /// stores codes - needs this to match a close against the open it wants.
145 #[must_use]
146 pub fn bracket_of_code(code: u32) -> Option<(bool, BracketKind)> {
147 for bk in [BracketKind::Paren, BracketKind::Square, BracketKind::Brace] {
148 if TokenKind::Open(bk).code() == code {
149 return Some((true, bk));
150 }
151 if TokenKind::Close(bk).code() == code {
152 return Some((false, bk));
153 }
154 }
155 None
156 }
157
158 /// A small, total, distinct integer code for this kind, used to
159 /// carry the token-kind stream and the atom-kind table to a backend
160 /// that cannot hold the Rust enum (the GPU kernel). The bracket kind
161 /// is folded into the code so `Open(Paren)` and `Open(Square)` stay
162 /// distinct.
163 #[must_use]
164 pub fn code(self) -> u32 {
165 match self {
166 TokenKind::Number => 0,
167 TokenKind::Word => 1,
168 TokenKind::Quoted => 2,
169 TokenKind::Ip => 3,
170 TokenKind::Url => 4,
171 TokenKind::Email => 5,
172 TokenKind::Timestamp => 6,
173 TokenKind::Whitespace => 7,
174 TokenKind::Punct => 8,
175 TokenKind::Other => 9,
176 TokenKind::Open(BracketKind::Paren) => 10,
177 TokenKind::Open(BracketKind::Square) => 11,
178 TokenKind::Open(BracketKind::Brace) => 12,
179 TokenKind::Close(BracketKind::Paren) => 13,
180 TokenKind::Close(BracketKind::Square) => 14,
181 TokenKind::Close(BracketKind::Brace) => 15,
182 // Codes for the richer typed kinds continue past the bracket
183 // block. The GPU kernel compares these as opaque integers
184 // (`kernels/scan.cu`), so a new code needs no kernel change.
185 TokenKind::Version => 16,
186 TokenKind::Uuid => 17,
187 TokenKind::Mac => 18,
188 TokenKind::HexColor => 19,
189 TokenKind::Cidr => 20,
190 TokenKind::Percent => 21,
191 TokenKind::ByteSize => 22,
192 TokenKind::Money => 23,
193 TokenKind::HashDigest => 24,
194 TokenKind::Duration => 25,
195 TokenKind::Path => 26,
196 TokenKind::Jwt => 27,
197 TokenKind::CreditCard => 28,
198 TokenKind::Base64 => 29,
199 TokenKind::Geo => 30,
200 TokenKind::Phone => 31,
201 TokenKind::Quantity => 32,
202 TokenKind::Hex => 33,
203 // Custom codes start past the built-in block. `shape_class`
204 // shifts a code left by 16, so the range stays inside a u32.
205 TokenKind::Custom(id) => 34 + u32::from(id),
206 }
207 }
208
209 /// The kind a code names: the inverse of [`Self::code`] on every kind.
210 ///
211 /// # Panics
212 /// On a code no kind has, which only a stream written by something other
213 /// than [`Self::code`] could carry.
214 #[must_use]
215 pub fn from_code(code: u32) -> TokenKind {
216 match code {
217 0 => TokenKind::Number,
218 1 => TokenKind::Word,
219 2 => TokenKind::Quoted,
220 3 => TokenKind::Ip,
221 4 => TokenKind::Url,
222 5 => TokenKind::Email,
223 6 => TokenKind::Timestamp,
224 7 => TokenKind::Whitespace,
225 8 => TokenKind::Punct,
226 9 => TokenKind::Other,
227 10 => TokenKind::Open(BracketKind::Paren),
228 11 => TokenKind::Open(BracketKind::Square),
229 12 => TokenKind::Open(BracketKind::Brace),
230 13 => TokenKind::Close(BracketKind::Paren),
231 14 => TokenKind::Close(BracketKind::Square),
232 15 => TokenKind::Close(BracketKind::Brace),
233 16 => TokenKind::Version,
234 17 => TokenKind::Uuid,
235 18 => TokenKind::Mac,
236 19 => TokenKind::HexColor,
237 20 => TokenKind::Cidr,
238 21 => TokenKind::Percent,
239 22 => TokenKind::ByteSize,
240 23 => TokenKind::Money,
241 24 => TokenKind::HashDigest,
242 25 => TokenKind::Duration,
243 26 => TokenKind::Path,
244 27 => TokenKind::Jwt,
245 28 => TokenKind::CreditCard,
246 29 => TokenKind::Base64,
247 30 => TokenKind::Geo,
248 31 => TokenKind::Phone,
249 32 => TokenKind::Quantity,
250 33 => TokenKind::Hex,
251 34..=289 => TokenKind::Custom((code - 34) as u8),
252 _ => panic!("{code} is no token kind's code"),
253 }
254 }
255}
256
257/// One lexed token: a typed class plus the half-open byte span
258/// `[start, end)` it covers in the input.
259///
260/// The bracket-pairing result rides on the token: for an
261/// [`TokenKind::Open`] / [`TokenKind::Close`] token [`Token::mate`] is the
262/// index of the partner token in the stream, and `None` for every other
263/// kind.
264///
265/// A whole input's tokens are one flat vector that every consumer streams,
266/// so the struct's width is bandwidth. The mate is stored as the partner's
267/// index plus one in a [`NonZeroU32`], which puts the `None` in the zero
268/// niche and costs four bytes rather than the sixteen an `Option<usize>`
269/// takes.
270impl TokenKind {
271 /// The kind's name, as `\{name}` writes it in a pattern.
272 #[must_use]
273 pub fn name(self) -> &'static str {
274 match self {
275 TokenKind::Number => "number",
276 TokenKind::Word => "word",
277 TokenKind::Quoted => "quoted",
278 TokenKind::Ip => "ip",
279 TokenKind::Url => "url",
280 TokenKind::Email => "email",
281 TokenKind::Timestamp => "timestamp",
282 TokenKind::Version => "version",
283 TokenKind::Uuid => "uuid",
284 TokenKind::Mac => "mac",
285 TokenKind::HexColor => "hexcolor",
286 TokenKind::Cidr => "cidr",
287 TokenKind::Percent => "percent",
288 TokenKind::ByteSize => "bytesize",
289 TokenKind::Money => "money",
290 TokenKind::HashDigest => "hash",
291 TokenKind::Hex => "hex",
292 TokenKind::Duration => "duration",
293 TokenKind::Path => "path",
294 TokenKind::Jwt => "jwt",
295 TokenKind::CreditCard => "creditcard",
296 TokenKind::Base64 => "base64",
297 TokenKind::Geo => "geo",
298 TokenKind::Phone => "phone",
299 TokenKind::Quantity => "quantity",
300 TokenKind::Whitespace => "whitespace",
301 TokenKind::Punct => "punct",
302 TokenKind::Open(_) => "open",
303 TokenKind::Close(_) => "close",
304 TokenKind::Custom(_) => "custom",
305 TokenKind::Other => "other",
306 }
307 }
308
309 /// The kind written under `name`, the inverse of [`Self::name`] for the
310 /// kinds a name reaches.
311 ///
312 /// A bracket is named by its side rather than its shape, since that is
313 /// what [`Self::name`] reports; a declared kind has no name here, because
314 /// the name belongs to the declaration rather than to the enum.
315 #[must_use]
316 pub fn named(name: &str) -> Option<TokenKind> {
317 [
318 TokenKind::Number,
319 TokenKind::Word,
320 TokenKind::Quoted,
321 TokenKind::Ip,
322 TokenKind::Url,
323 TokenKind::Email,
324 TokenKind::Timestamp,
325 TokenKind::Version,
326 TokenKind::Uuid,
327 TokenKind::Mac,
328 TokenKind::HexColor,
329 TokenKind::Cidr,
330 TokenKind::Percent,
331 TokenKind::ByteSize,
332 TokenKind::Money,
333 TokenKind::HashDigest,
334 TokenKind::Hex,
335 TokenKind::Duration,
336 TokenKind::Path,
337 TokenKind::Jwt,
338 TokenKind::CreditCard,
339 TokenKind::Base64,
340 TokenKind::Geo,
341 TokenKind::Phone,
342 TokenKind::Quantity,
343 TokenKind::Whitespace,
344 TokenKind::Punct,
345 TokenKind::Open(BracketKind::Paren),
346 TokenKind::Close(BracketKind::Paren),
347 TokenKind::Other,
348 ]
349 .into_iter()
350 .find(|k| k.name() == name)
351 }
352
353 /// The single letter that names the kind as `\X` in a pattern, for the
354 /// kinds that have one.
355 #[must_use]
356 pub fn escape(self) -> Option<char> {
357 Some(match self {
358 TokenKind::Number => 'N',
359 TokenKind::Word => 'W',
360 TokenKind::Quoted => 'Q',
361 TokenKind::Ip => 'I',
362 TokenKind::Url => 'U',
363 TokenKind::Email => 'E',
364 TokenKind::Timestamp => 'T',
365 TokenKind::Punct => 'P',
366 TokenKind::Whitespace => 'S',
367 TokenKind::Version => 'V',
368 TokenKind::HexColor => 'H',
369 TokenKind::Cidr => 'C',
370 TokenKind::ByteSize => 'Z',
371 TokenKind::Percent => '%',
372 TokenKind::Money => '$',
373 TokenKind::HashDigest => 'D',
374 TokenKind::Duration => 'R',
375 TokenKind::Path => 'L',
376 TokenKind::Uuid
377 | TokenKind::Mac
378 | TokenKind::Hex
379 | TokenKind::Jwt
380 | TokenKind::CreditCard
381 | TokenKind::Base64
382 | TokenKind::Geo
383 | TokenKind::Phone
384 | TokenKind::Quantity
385 | TokenKind::Open(_)
386 | TokenKind::Close(_)
387 | TokenKind::Custom(_)
388 | TokenKind::Other => return None,
389 })
390 }
391}
392
393#[derive(Clone, Copy, Debug, PartialEq, Eq)]
394pub struct Token {
395 /// The token's typed class.
396 pub kind: TokenKind,
397 /// Inclusive start byte offset into the input.
398 pub start: u32,
399 /// Exclusive end byte offset into the input.
400 pub end: u32,
401 /// The mate's index plus one, or `None`. Read it through
402 /// [`Token::mate`] and write it through [`Token::set_mate`], which hold
403 /// the encoding in one place; the field is named for what it stores so
404 /// that reading it as an index does not compile.
405 pub mate_plus_one: Option<NonZeroU32>,
406}
407
408impl Token {
409 /// Construct a token with no bracket mate.
410 ///
411 /// # Panics
412 /// When an offset does not fit the stored width, which needs an input
413 /// over four gigabytes. Truncating instead would point a token at the
414 /// wrong bytes.
415 #[must_use]
416 #[inline]
417 pub fn new(kind: TokenKind, start: usize, end: usize) -> Self {
418 Self {
419 kind,
420 start: u32::try_from(start).expect("a byte offset within the stored width"),
421 end: u32::try_from(end).expect("a byte offset within the stored width"),
422 mate_plus_one: None,
423 }
424 }
425
426 /// The token's byte span, for indexing the input it was lexed from.
427 #[must_use]
428 #[inline]
429 pub fn span(&self) -> std::ops::Range<usize> {
430 self.start as usize..self.end as usize
431 }
432
433 /// The start offset as a `usize`, for arithmetic and indexing against
434 /// everything else, which counts bytes in the machine's width. The field
435 /// is the storage and this is the view of it; the two never disagree,
436 /// and a site that needs the wider one fails to compile rather than
437 /// silently taking the narrower.
438 #[must_use]
439 #[inline]
440 pub fn start(&self) -> usize {
441 self.start as usize
442 }
443
444 /// The end offset as a `usize`. See [`Token::start`].
445 #[must_use]
446 #[inline]
447 pub fn end(&self) -> usize {
448 self.end as usize
449 }
450
451 /// Move the span by `by` bytes, for a token lexed from a chunk and
452 /// rebased onto the whole input.
453 ///
454 /// # Panics
455 /// When the shifted offset does not fit the stored width.
456 #[inline]
457 pub fn shift(&mut self, by: usize) {
458 let shifted = |v: u32| -> u32 {
459 u32::try_from(v as usize + by).expect("a byte offset within the stored width")
460 };
461 self.start = shifted(self.start);
462 self.end = shifted(self.end);
463 }
464
465 /// The index of this token's matching bracket, or `None` when it has
466 /// none.
467 #[must_use]
468 #[inline]
469 pub fn mate(&self) -> Option<usize> {
470 self.mate_plus_one.map(|m| m.get() as usize - 1)
471 }
472
473 /// Record this token's matching bracket, or clear it.
474 ///
475 /// # Panics
476 /// When the index does not fit the stored width. A stream that long
477 /// would need more than four billion tokens, which no input this lexer
478 /// can hold produces, and silently storing a wrapped index would pair
479 /// the wrong brackets.
480 #[inline]
481 pub fn set_mate(&mut self, index: Option<usize>) {
482 self.mate_plus_one = index.map(|i| {
483 let plus_one = u32::try_from(i + 1).expect("a token index within the stored width");
484 NonZeroU32::new(plus_one).expect("one more than an index is never zero")
485 });
486 }
487
488 /// The byte length of the token's span.
489 #[must_use]
490 pub fn len(&self) -> usize {
491 (self.end - self.start) as usize
492 }
493
494 /// Whether the token's span is empty.
495 #[must_use]
496 pub fn is_empty(&self) -> bool {
497 self.start == self.end
498 }
499
500 /// Whether this token participates in token-mode matching
501 /// (everything except insignificant whitespace).
502 #[must_use]
503 pub fn is_significant(&self) -> bool {
504 !self.kind.is_insignificant()
505 }
506}
507
508/// The class key of a token: a generic token-to-string mapping over the typed-token kinds, used by
509/// the compression-based structure layer and the field axes. Numbers / strings / literals collapse
510/// to a tag, short words stay literal, long words collapse to `#id`, brackets and punctuation stay
511/// themselves. A substrate primitive over the typed tokens, independent of any domain.
512#[must_use]
513pub fn code_class(t: &Token, code: &[u8]) -> String {
514 let text = || String::from_utf8_lossy(&code[t.span()]).into_owned();
515 match t.kind {
516 TokenKind::Punct => text(),
517 TokenKind::Number => "#num".into(),
518 TokenKind::Quoted => "#str".into(),
519 TokenKind::Ip
520 | TokenKind::Url
521 | TokenKind::Email
522 | TokenKind::Timestamp
523 | TokenKind::Version
524 | TokenKind::Uuid
525 | TokenKind::Mac
526 | TokenKind::HexColor
527 | TokenKind::Cidr
528 | TokenKind::Percent
529 | TokenKind::ByteSize
530 | TokenKind::Money
531 | TokenKind::HashDigest
532 | TokenKind::Hex
533 | TokenKind::Duration
534 | TokenKind::Path
535 | TokenKind::Jwt
536 | TokenKind::CreditCard
537 | TokenKind::Base64
538 | TokenKind::Geo
539 | TokenKind::Phone
540 | TokenKind::Quantity => "#lit".into(),
541 TokenKind::Word => {
542 let w = text();
543 if w.len() <= 5 { w } else { "#id".into() }
544 }
545 TokenKind::Open(BracketKind::Paren) => "(".into(),
546 TokenKind::Open(BracketKind::Square) => "[".into(),
547 TokenKind::Open(BracketKind::Brace) => "{".into(),
548 TokenKind::Close(BracketKind::Paren) => ")".into(),
549 TokenKind::Close(BracketKind::Square) => "]".into(),
550 TokenKind::Close(BracketKind::Brace) => "}".into(),
551 TokenKind::Custom(id) => format!("#shape{id}"),
552 TokenKind::Other | TokenKind::Whitespace => "#other".into(),
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn span_length_matches_offsets() {
562 let t = Token::new(TokenKind::Number, 4, 7);
563 assert_eq!(t.len(), 3);
564 assert!(!t.is_empty());
565 assert!(t.is_significant());
566 }
567
568 #[test]
569 fn whitespace_is_insignificant() {
570 let t = Token::new(TokenKind::Whitespace, 0, 1);
571 assert!(!t.is_significant());
572 assert!(t.kind.is_insignificant());
573 }
574
575 #[test]
576 fn bracket_mate_defaults_none_then_sets() {
577 let mut t = Token::new(TokenKind::Open(BracketKind::Paren), 0, 1);
578 assert_eq!(t.mate(), None);
579 t.set_mate(Some(9));
580 assert_eq!(t.mate(), Some(9));
581 // Index zero is a mate like any other; the stored value is what
582 // carries the plus one.
583 t.set_mate(Some(0));
584 assert_eq!(t.mate(), Some(0));
585 assert_eq!(t.mate_plus_one.map(NonZeroU32::get), Some(1));
586 t.set_mate(None);
587 assert_eq!(t.mate(), None);
588 }
589
590 #[test]
591 fn a_token_is_no_wider_than_the_layout_it_was_shrunk_to() {
592 // Two offsets and a mate at four bytes each, a kind at two: fourteen
593 // of content at an alignment of four. The option costs the zero
594 // niche rather than a word, which is what the plus-one encoding gains.
595 assert_eq!(size_of::<Option<NonZeroU32>>(), 4);
596 assert_eq!(size_of::<Token>(), 16);
597 }
598
599 /// Every bracket kind survives the trip out to a code and back, and no
600 /// other kind answers as a bracket - which is what lets a reader holding
601 /// codes match a close against its open.
602 #[test]
603 fn a_bracket_kind_code_reads_back_as_the_bracket_it_came_from() {
604 for bk in [BracketKind::Paren, BracketKind::Square, BracketKind::Brace] {
605 assert_eq!(
606 TokenKind::bracket_of_code(TokenKind::Open(bk).code()),
607 Some((true, bk)),
608 "an open {bk:?} reads back"
609 );
610 assert_eq!(
611 TokenKind::bracket_of_code(TokenKind::Close(bk).code()),
612 Some((false, bk)),
613 "a close {bk:?} reads back"
614 );
615 }
616 for kind in [
617 TokenKind::Number,
618 TokenKind::Word,
619 TokenKind::Quoted,
620 TokenKind::Whitespace,
621 TokenKind::Punct,
622 TokenKind::Other,
623 TokenKind::Timestamp,
624 ] {
625 assert_eq!(
626 TokenKind::bracket_of_code(kind.code()),
627 None,
628 "{kind:?} is no bracket"
629 );
630 }
631 }
632}