Skip to main content

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}