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