Skip to main content

tabnas/
lexer.rs

1// Copyright (c) 2013-2026 Richard Rodger, MIT License
2
3use crate::error::TabnasError;
4use crate::options::{LexCheck, LexCheckResult, MatchTokenMatcher, Options};
5use crate::token::{
6    Point, Token, TIN_BD, TIN_CM, TIN_LN, TIN_NR, TIN_SP, TIN_ST, TIN_TX, TIN_VL, TIN_ZZ,
7};
8use crate::value::Value;
9use regex::Regex;
10use std::panic::{catch_unwind, AssertUnwindSafe};
11use std::sync::Arc;
12
13pub struct Lexer<'a> {
14    src: &'a str,
15    chars: Vec<char>,
16    byte_indices: Vec<usize>,
17    char_len: usize,
18    idx: usize,
19    ri: usize,
20    ci: usize,
21    options: Arc<Options>,
22    ignore_tins: Vec<crate::Tin>,
23    char_sets: crate::text::CharSets,
24    err: Option<TabnasError>,
25    end_reached: bool,
26    /// `number.exclude`, compiled once per grammar and shared by the
27    /// parser that owns it; see [`compile_number_exclude`].
28    exclude_regex: Option<Arc<Regex>>,
29    want: Option<Vec<crate::Tin>>,
30    standalone: Option<(crate::Rule, crate::Context)>,
31}
32
33#[derive(Clone)]
34pub(crate) struct LexerState {
35    idx: usize,
36    ri: usize,
37    ci: usize,
38    err: Option<TabnasError>,
39    end_reached: bool,
40}
41
42/// Opaque snapshot returned by [`Lexer::relex_for_rule`]. Pass it to
43/// [`Lexer::unrelex`] if the caller later rejects the committed recut.
44#[derive(Clone)]
45pub struct RelexCheckpoint {
46    state: LexerState,
47    replay: std::collections::VecDeque<Token>,
48}
49
50enum CheckFlow {
51    Continue,
52    Skip,
53    Token(Box<Token>),
54}
55
56/// The result the lexer passes through its own internals.
57///
58/// `TabnasError` is 664 bytes, so `LexResult<Token>` is 664 bytes
59/// too: three times the token it carries, and moved on the SUCCESS path
60/// through every frame of the lexer chain. Boxing the error inside the
61/// engine makes those results the size of a token again, and costs an
62/// allocation only when there is an error to report, which is the path that
63/// is already building a 664-byte diagnostic.
64///
65/// The public entry points still hand back an unboxed `TabnasError`, so no
66/// caller of this crate sees the box.
67type LexResult<T> = Result<T, Box<TabnasError>>;
68
69/// The compiled form of `number.exclude`, or `None` when there is no
70/// pattern or it does not compile (an invalid pattern excludes nothing,
71/// as it always has).
72///
73/// Compiling a regex costs about 700K instructions, which is more than
74/// a small document costs to parse, so the parser compiles it once per
75/// grammar and hands every lexer the same automaton through the `Arc`.
76/// Cloning a `Regex` would not do: it shares the automaton but builds a
77/// fresh scratch-cache pool, and the first search from each clone fills
78/// it. Go and TypeScript compile the pattern once, at configuration time.
79pub(crate) fn compile_number_exclude(options: &Options) -> Option<Arc<Regex>> {
80    let pattern = options.number.exclude.as_deref()?;
81    Regex::new(pattern).ok().map(Arc::new)
82}
83
84impl<'a> Lexer<'a> {
85    pub fn new(src: &'a str, mut options: Options) -> Self {
86        if let Err(error) = options.validate_comment_definitions() {
87            panic!("invalid options: {error}");
88        }
89        // A lexer built directly may be handed options nobody has
90        // ordered yet. The parser's own lexer comes through
91        // `with_shared`, whose options were ordered when they were
92        // prepared, and whose exclude pattern was compiled then too.
93        options.sort_for_lexing();
94        let exclude_regex = compile_number_exclude(&options);
95        Self::with_shared(src, Arc::new(options), exclude_regex)
96    }
97
98    /// Lex against options the parser already owns and has ordered, with
99    /// the `number.exclude` pattern it compiled from them.
100    pub(crate) fn with_shared(
101        src: &'a str,
102        options: Arc<Options>,
103        exclude_regex: Option<Arc<Regex>>,
104    ) -> Self {
105        let mut chars = Vec::new();
106        let mut byte_indices = Vec::new();
107        for (b_idx, c) in src.char_indices() {
108            chars.push(c);
109            byte_indices.push(b_idx);
110        }
111        let char_len = chars.len();
112
113        Lexer {
114            src,
115            chars,
116            byte_indices,
117            char_len,
118            idx: 0,
119            ri: 1,
120            ci: 1,
121            ignore_tins: options.ignore_tins(),
122            char_sets: options.char_sets(),
123            options,
124            err: None,
125            end_reached: false,
126            exclude_regex,
127            want: None,
128            standalone: None,
129        }
130    }
131
132    fn current_point(&self) -> Point {
133        Point {
134            len: self.src.len(),
135            site: crate::Site {
136                si: self.byte_position(),
137                pos: self.idx,
138                ri: self.ri,
139                ci: self.ci,
140            },
141        }
142    }
143
144    /// The source from char index `start` up to `end`, clipped to the end
145    /// of the source: the span of a bad token, cut as TypeScript's
146    /// `lex.bad(why, pstart, pend)` cuts it (`ts/src/lexer.ts`).
147    ///
148    /// Only the END is clipped. The caller must supply `start <= end`
149    /// and `start <= self.char_len`, or the indexing panics. Every call
150    /// site below passes `esc_point.site.pos - 1`, which additionally
151    /// needs `1 <= pos`; that holds because `esc_point` is captured
152    /// after the escape character has been consumed. These three are
153    /// the preconditions `rs/verus/lexer_span.rs` ASSUMES: it proves the
154    /// span arithmetic is in bounds given them, and does not model the
155    /// call sites, so nothing machine-checks that they hold here. A
156    /// refactor that captured the point before the advance would still
157    /// pass `rs/verus/run.sh`.
158    fn source_span(&self, start: usize, end: usize) -> String {
159        self.chars[start..end.min(self.char_len)].iter().collect()
160    }
161
162    fn state(&self) -> LexerState {
163        LexerState {
164            idx: self.idx,
165            ri: self.ri,
166            ci: self.ci,
167            err: self.err.clone(),
168            end_reached: self.end_reached,
169        }
170    }
171
172    /// Full immutable source supplied to this lexer.
173    pub fn source(&self) -> &str {
174        self.src
175    }
176
177    /// Source remaining at the live cursor.
178    pub fn remaining(&self) -> &str {
179        &self.src[self.byte_position()..]
180    }
181
182    /// Return at most `max_chars` Unicode scalar values from the live cursor.
183    /// This is the Rust counterpart of the public `lex.fwd`/`Lex.Fwd` helper.
184    pub fn forward(&self, max_chars: usize) -> &str {
185        let remaining = self.remaining();
186        let end = remaining
187            .char_indices()
188            .nth(max_chars)
189            .map_or(remaining.len(), |(index, _)| index);
190        &remaining[..end]
191    }
192
193    /// Snapshot the live cursor for token construction.
194    pub fn point(&self) -> Point {
195        self.current_point()
196    }
197
198    /// Advance by Unicode scalar values. Returns false without moving when
199    /// the requested count extends beyond end-of-source.
200    pub fn advance_chars(&mut self, count: usize) -> bool {
201        if self.idx.saturating_add(count) > self.char_len {
202            return false;
203        }
204        for _ in 0..count {
205            self.advance();
206        }
207        true
208    }
209
210    /// Construct a token from a point captured before cursor advancement.
211    pub fn token(
212        &self,
213        name: impl AsRef<str>,
214        tin: crate::Tin,
215        value: Value,
216        source: impl Into<crate::TokenText>,
217        point: Point,
218    ) -> Token {
219        Token::new(name, tin, value, source, point)
220    }
221
222    /// Resolve or allocate a token identity in this lexer's configuration.
223    pub fn token_tin(&mut self, name: impl Into<String>) -> crate::Tin {
224        Arc::make_mut(&mut self.options).register_token(name)
225    }
226
227    /// Resolve a token identity back to its configured name.
228    pub fn token_name(&self, tin: crate::Tin) -> String {
229        self.options.token_name(tin)
230    }
231
232    /// Whether the options this lexer runs under enable `alt`: not when
233    /// `rule.exclude` names one of its groups, nor when `rule.include`
234    /// lists groups and it declares none of them. The parser skips such an
235    /// alternate, and TypeScript removes it from the rule spec
236    /// (`filterRules`) before anything reads the spec, so a custom matcher
237    /// that reads a rule's alternates, to tell a key position from a value
238    /// one, asks this to see the same alternates TypeScript does.
239    pub fn alt_enabled(&self, alt: &crate::AltSpec) -> bool {
240        crate::parser::groups_enabled(alt, &self.options)
241    }
242
243    /// Construct a bad token at the current cursor.
244    ///
245    /// A custom matcher that returns it reports a lexer fault, which the
246    /// parser handles as it handles its own lexer's: a rule's fetch raises
247    /// it at once with `why` as the code, at this position, or, under
248    /// recovery, records it, coalescing a run of them into one error, and
249    /// moves the cursor past its source; relexing leaves it for an
250    /// alternate to re-cut. The cursor need not be advanced here.
251    pub fn bad(&self, why: impl Into<String>) -> Token {
252        let point = self.current_point();
253        let source = self
254            .peek()
255            .map_or_else(String::new, |character| character.to_string());
256        let mut token = Token::new("#BD", TIN_BD, Value::Undefined, source, point);
257        token.err = crate::TokenCode::from(why.into());
258        token.why = token.err.clone();
259        token
260    }
261
262    /// Construct a bad token whose displayed source is a scalar-indexed span.
263    /// As in TypeScript, the diagnostic point remains the live cursor.
264    /// Returned from a matcher it is handled as [`Lexer::bad`] describes;
265    /// recovery steps past the span, counted from that cursor.
266    pub fn bad_span(&self, why: impl Into<String>, start: usize, end: usize) -> Token {
267        let point = self.current_point();
268        let source = if start <= end && end <= self.char_len {
269            let start_byte = self
270                .byte_indices
271                .get(start)
272                .copied()
273                .unwrap_or(self.src.len());
274            let end_byte = self
275                .byte_indices
276                .get(end)
277                .copied()
278                .unwrap_or(self.src.len());
279            self.src[start_byte..end_byte].to_string()
280        } else {
281            self.peek()
282                .map_or_else(String::new, |character| character.to_string())
283        };
284        let mut token = Token::new("#BD", TIN_BD, Value::Undefined, source, point);
285        token.err = crate::TokenCode::from(why.into());
286        token.why = token.err.clone();
287        token
288    }
289
290    fn byte_position(&self) -> usize {
291        self.byte_indices
292            .get(self.idx)
293            .copied()
294            .unwrap_or(self.src.len())
295    }
296
297    fn advance(&mut self) -> Option<char> {
298        if self.idx < self.char_len {
299            let c = self.chars[self.idx];
300            self.idx += 1;
301            if self.char_sets.row.contains(c) {
302                self.ri += 1;
303                self.ci = 1;
304            } else {
305                self.ci += 1;
306            }
307            Some(c)
308        } else {
309            None
310        }
311    }
312
313    /// Step over one character of a string body, counting a column and
314    /// never a row. `advance` counts a row for any character in
315    /// `line.rowChars`, which is right between tokens and wrong inside a
316    /// string: TypeScript's `buildStringBodySpec` (ts/src/lexer.ts) makes
317    /// a row character plain body there unless the string is multi-line
318    /// and the character is in `line.chars` as well, which is
319    /// [`Lexer::advance_string_line`].
320    fn advance_body(&mut self) -> Option<char> {
321        let c = self.peek()?;
322        self.idx += 1;
323        self.ci += 1;
324        Some(c)
325    }
326
327    /// Step over a line character inside a multi-line string: the column
328    /// resets, and a character in `line.rowChars` counts a row, as
329    /// TypeScript's string-body classes LINE and LINE+ROW do
330    /// (`STRING_BODY_TABLE` in ts/src/lexer.ts) and as Go's
331    /// `BuildStringBodySpec` ports them.
332    fn advance_string_line(&mut self) -> Option<char> {
333        let c = self.peek()?;
334        self.idx += 1;
335        if self.char_sets.row.contains(c) {
336            self.ri += 1;
337        }
338        self.ci = 1;
339        Some(c)
340    }
341
342    fn peek(&self) -> Option<char> {
343        if self.idx < self.char_len {
344            Some(self.chars[self.idx])
345        } else {
346            None
347        }
348    }
349
350    fn peek_at(&self, offset: usize) -> Option<char> {
351        let i = self.idx + offset;
352        if i < self.char_len {
353            Some(self.chars[i])
354        } else {
355            None
356        }
357    }
358
359    fn wants(&self, tin: crate::Tin) -> bool {
360        self.want
361            .as_ref()
362            .is_none_or(|wanted| wanted.contains(&tin))
363    }
364
365    fn run_check(&mut self, check: Option<LexCheck>, point: Point) -> CheckFlow {
366        let Some(check) = check else {
367            return CheckFlow::Continue;
368        };
369        let remaining = &self.src[self.byte_position()..];
370        let result = check
371            .run_imperative(self)
372            .or_else(|| check.run(remaining))
373            .unwrap_or(LexCheckResult::Continue);
374        match result {
375            LexCheckResult::Continue => CheckFlow::Continue,
376            LexCheckResult::Skip => CheckFlow::Skip,
377            LexCheckResult::NativeToken(token) => CheckFlow::Token(token),
378            LexCheckResult::Token(token)
379                if !token.source.is_empty() && remaining.starts_with(&token.source) =>
380            {
381                let tin = if token.tin < 0 {
382                    self.options.token(&token.name).unwrap_or(token.tin)
383                } else {
384                    token.tin
385                };
386                if tin < 0 {
387                    return CheckFlow::Skip;
388                }
389                for _ in token.source.chars() {
390                    self.advance();
391                }
392                CheckFlow::Token(Box::new(Token::new(
393                    token.name,
394                    tin,
395                    token.value,
396                    token.source,
397                    point,
398                )))
399            }
400            LexCheckResult::Token(_) => CheckFlow::Skip,
401        }
402    }
403
404    /// Give any plugin matcher whose order is below `before` its turn.
405    ///
406    /// Nine sites in the lexer call this per token, once at each stage a
407    /// matcher is allowed to intervene. A grammar with no custom matcher,
408    /// which is most of them, was paying nine index lookups per token to be
409    /// told nine times that there is nothing to run. The guard is inline so
410    /// those sites skip the call itself; the walk stays out of line.
411    #[inline]
412    fn run_custom_matchers(
413        &mut self,
414        index: &mut usize,
415        before: f64,
416        point: Point,
417        plugin: &mut Option<(&mut crate::Rule, &mut crate::Context)>,
418    ) -> Option<Token> {
419        if *index >= self.options.lex.matchers.len() {
420            return None;
421        }
422        self.run_remaining_custom_matchers(index, before, point, plugin)
423    }
424
425    #[inline(never)]
426    fn run_remaining_custom_matchers(
427        &mut self,
428        index: &mut usize,
429        before: f64,
430        point: Point,
431        plugin: &mut Option<(&mut crate::Rule, &mut crate::Context)>,
432    ) -> Option<Token> {
433        while let Some(matcher) = self
434            .options
435            .lex
436            .matchers
437            .get_index(*index)
438            .map(|(_, matcher)| matcher)
439            .filter(|matcher| matcher.order < before)
440            .cloned()
441        {
442            *index += 1;
443            let remaining = &self.src[self.byte_position()..];
444            let saved = self.state();
445            let token = if let Some(callback) = matcher.imperative.as_ref() {
446                let Some((rule, context)) = plugin.as_mut() else {
447                    continue;
448                };
449                callback(self, rule, context)
450            } else {
451                matcher
452                    .matcher
453                    .as_ref()
454                    .and_then(|callback| callback(remaining))
455                    .filter(|token| {
456                        !token.source.is_empty() && remaining.starts_with(&token.source)
457                    })
458                    .map(|token| {
459                        Token::new(token.name, token.tin, token.value, token.source, point)
460                    })
461            };
462            let Some(mut token) = token else {
463                if self.want.is_some() {
464                    self.restore(saved);
465                }
466                continue;
467            };
468
469            // TypeScript and Go run opaque custom matchers speculatively for
470            // a negotiated cut. An unwanted result rolls back locally so a
471            // later matcher can still satisfy the request.
472            let tin = if token.tin < 0 {
473                self.options.token(&token.name).unwrap_or(token.tin)
474            } else {
475                token.tin
476            };
477            if tin < 0 || !self.wants(tin) {
478                self.restore(saved);
479                continue;
480            }
481            if matcher.imperative.is_none() {
482                for _ in token.src.chars() {
483                    self.advance();
484                }
485            }
486            token.tin = tin;
487            return Some(token);
488        }
489        None
490    }
491
492    fn is_text_delimiter_here(&self) -> bool {
493        self.is_text_delimiter_at(self.idx)
494    }
495
496    fn is_text_delimiter_at(&self, index: usize) -> bool {
497        let Some(ch) = self.chars.get(index).copied() else {
498            return true;
499        };
500        let remaining = &self.src[self.byte_indices[index]..];
501        (self.options.space.lex && self.char_sets.space.contains(ch))
502            || (self.options.fixed.lex
503                && self
504                    .options
505                    .fixed
506                    .tokens
507                    .values()
508                    .any(|token| !token.source.is_empty() && remaining.starts_with(&token.source)))
509            || (self.options.line.lex
510                && (self.char_sets.line_ends.contains(ch) || matches!(ch, '\u{2028}' | '\u{2029}')))
511            || (self.options.comment.lex
512                && self.options.comment.definitions.values().any(|definition| {
513                    definition.lex
514                        && !definition.start.is_empty()
515                        && remaining.starts_with(&definition.start)
516                }))
517            || self
518                .options
519                .ender
520                .iter()
521                .any(|ender| !ender.is_empty() && remaining.starts_with(ender))
522    }
523
524    /// Fetches the next non-IGNORE token (skipping spaces, lines, comments).
525    pub fn next_token(&mut self) -> Result<Token, TabnasError> {
526        let point = self.current_point();
527        let result = match catch_unwind(AssertUnwindSafe(|| {
528            if let Some(ref error) = self.err {
529                return Err(Box::new(error.clone()));
530            }
531
532            loop {
533                let token = self.next_raw(None)?;
534                if !self.ignore_tins.contains(&token.tin) {
535                    return Ok(token);
536                }
537            }
538        })) {
539            Ok(result) => result,
540            Err(payload) => self.record_panic(payload, "Lexer::next_token", point),
541        };
542        // The box is internal to the engine; a caller gets the error itself.
543        result.map_err(|error| *error)
544    }
545
546    /// Fetch the next token without discarding whitespace, line, or comment tokens.
547    pub fn next_raw_token(&mut self) -> Result<Token, TabnasError> {
548        let point = self.current_point();
549        let result = match catch_unwind(AssertUnwindSafe(|| self.next_raw(None))) {
550            Ok(result) => result,
551            Err(payload) => self.record_panic(payload, "Lexer::next_raw_token", point),
552        };
553        result.map_err(|error| *error)
554    }
555
556    /// Fetch one token for an imperative parser callback, preserving ignored
557    /// space/line/comment tokens just like TypeScript's public `lex.next`.
558    /// Replayed tokens produced by `Context::rewind` are served first.
559    pub fn next_raw_for_rule(
560        &mut self,
561        rule: &mut crate::Rule,
562        context: &mut crate::Context,
563    ) -> LexResult<Token> {
564        if let Some(token) = context.next_replay() {
565            Ok(token)
566        } else {
567            self.next_raw_with(None, Some((rule, context)))
568        }
569    }
570
571    /// Fetch the next non-ignored token for an imperative parser callback.
572    pub fn next_for_rule(
573        &mut self,
574        rule: &mut crate::Rule,
575        context: &mut crate::Context,
576    ) -> LexResult<Token> {
577        loop {
578            let token = self.next_raw_for_rule(rule, context)?;
579            if !self.ignore_tins.contains(&token.tin) {
580                return Ok(token);
581            }
582        }
583    }
584
585    /// Public negotiated-relex entry point for native parser callbacks.
586    /// A successful recut commits the lexer cursor and returns an opaque undo
587    /// checkpoint; a failed recut restores all lexer state before returning.
588    pub fn relex_for_rule(
589        &mut self,
590        from: &Token,
591        wanted: &[crate::Tin],
592        rule: &mut crate::Rule,
593        context: &mut crate::Context,
594    ) -> Option<(Token, RelexCheckpoint)> {
595        self.relex(from, wanted, rule, context)
596    }
597
598    /// Undo a committed [`Lexer::relex_for_rule`] operation, including the
599    /// pending tokens hidden while the replacement cut was negotiated.
600    pub fn unrelex(&mut self, checkpoint: RelexCheckpoint, context: &mut crate::Context) {
601        self.restore(checkpoint.state);
602        context.restore_replay(checkpoint.replay);
603    }
604
605    fn record_panic(
606        &mut self,
607        payload: Box<dyn std::any::Any + Send>,
608        api: &str,
609        point: Point,
610    ) -> LexResult<Token> {
611        let error = TabnasError::from_panic(
612            payload,
613            api,
614            self.src,
615            point.site.pos,
616            point.site.ri,
617            point.site.ci,
618            &self.options,
619        );
620        self.err = Some(error.clone());
621        Err(Box::new(error))
622    }
623
624    /// Fetch a raw token while restricting non-eager custom token matchers to
625    /// the exact tins accepted at the parser slot being filled. Builtin and
626    /// fixed-token matchers are unaffected by this gate.
627    pub(crate) fn next_rule_token(
628        &mut self,
629        expected_match_tins: &[crate::Tin],
630        rule: &mut crate::Rule,
631        context: &mut crate::Context,
632    ) -> LexResult<Token> {
633        self.next_raw_with(Some(expected_match_tins), Some((rule, context)))
634    }
635
636    /// Step past a bad token the parser has absorbed or skipped, as
637    /// TypeScript's `advanceLexPast` does (ts/src/rules.ts): a bad token
638    /// does not advance the cursor by itself, so recovery moves it to the
639    /// end of the token's span, never backwards, and, for a fault raised
640    /// inside a compound construct, on past the next row character so
641    /// lexing resumes on a fresh row. The lexer's own faults latch until
642    /// this clears them.
643    pub(crate) fn skip_bad(&mut self, token: &Token, to_line_end: bool) {
644        let span = token.src.chars().count().max(1);
645        let mut target = self.idx.max(token.site.pos.saturating_add(span));
646        if to_line_end {
647            let mut end = target;
648            while end < self.char_len && !self.char_sets.row.contains(self.chars[end]) {
649                end += 1;
650            }
651            target = target.max(self.char_len.min(end + 1));
652        }
653        while self.idx < target && self.idx < self.char_len {
654            self.advance();
655        }
656        self.err = None;
657        if self.idx < self.char_len {
658            self.end_reached = false;
659        }
660    }
661
662    /// Re-cut an already buffered source span, constrained to the token
663    /// identities requested by one alternate. On success the cursor remains
664    /// after the new cut; the returned state can restore the original cut if
665    /// that alternate later fails.
666    pub(crate) fn relex(
667        &mut self,
668        from: &Token,
669        wanted: &[crate::Tin],
670        rule: &mut crate::Rule,
671        context: &mut crate::Context,
672    ) -> Option<(Token, RelexCheckpoint)> {
673        if from.src.is_empty() || from.site.pos > self.char_len || wanted.is_empty() {
674            return None;
675        }
676        // The standing error, if any, is moved into the checkpoint rather
677        // than copied: the cut below clears it anyway, and a copy is the
678        // length of the source (`full_source`) for every cut attempted.
679        let err = self.err.take();
680        let saved = LexerState {
681            err,
682            ..self.state()
683        };
684        // TypeScript temporarily replaces the lexer's pending-token queue
685        // with an empty queue for a negotiated cut. Rust keeps that queue on
686        // Context, so hide it explicitly and preserve it in the checkpoint.
687        let replay = context.take_replay();
688        self.idx = from.site.pos;
689        self.ri = from.site.ri;
690        self.ci = from.site.ci;
691        self.err = None;
692        self.end_reached = false;
693        self.want = Some(wanted.to_vec());
694        // Straight to the matchers, past `next_raw_with`: an error here only
695        // rejects the cut, and the restore below puts back the lexer's own,
696        // so the source it would attach, and the copy it would keep, are
697        // never seen. With them, every rejected cut cost the length of the
698        // source, and a flat stylesheet parsed in quadratic time.
699        let recut = self.next_raw_inner(None, Some((rule, context))).ok();
700        self.want = None;
701        match recut.filter(|token| wanted.contains(&token.tin)) {
702            Some(mut token) => {
703                token.ignored = from.ignored.clone();
704                Some((
705                    token,
706                    RelexCheckpoint {
707                        state: saved,
708                        replay,
709                    },
710                ))
711            }
712            None => {
713                self.restore(saved);
714                // Discard any speculative replay generated by an imperative
715                // matcher and restore the queue that preceded the attempt.
716                context.restore_replay(replay);
717                None
718            }
719        }
720    }
721
722    pub(crate) fn restore(&mut self, state: LexerState) {
723        self.idx = state.idx;
724        self.ri = state.ri;
725        self.ci = state.ci;
726        self.err = state.err;
727        self.end_reached = state.end_reached;
728        self.want = None;
729    }
730
731    fn next_raw(&mut self, expected_match_tins: Option<&[crate::Tin]>) -> LexResult<Token> {
732        // Only a lexer being driven directly needs these, and building
733        // them costs a whole `Options` clone. A parse reaches the lexer
734        // through `next_rule_token`, which brings the real rule and
735        // context with it, so it never wants them at all.
736        let (mut rule, mut context) = match self.standalone.take() {
737            Some(pair) => pair,
738            None => (
739                crate::Rule::new("#NORULE", Value::Undefined),
740                crate::Context::new(
741                    self.options.rewind.history,
742                    self.src,
743                    Value::Undefined,
744                    Arc::clone(&self.options),
745                    crate::InstanceInfo::default(),
746                ),
747            ),
748        };
749        let result = self.next_raw_with(expected_match_tins, Some((&mut rule, &mut context)));
750        self.standalone = Some((rule, context));
751        result
752    }
753
754    fn modify_text_value(
755        &mut self,
756        mut value: Value,
757        plugin: &mut Option<(&mut crate::Rule, &mut crate::Context)>,
758    ) -> Value {
759        if self.options.text.modify.is_empty() {
760            return value;
761        }
762        let modifiers = self.options.text.modify.clone();
763        let options = self.options.clone();
764        let Some((rule, context)) = plugin.as_mut() else {
765            panic!("imperative text modifier requires an active lexer context");
766        };
767        for modifier in modifiers {
768            value = modifier.run(value, self, rule, context, &options);
769        }
770        value
771    }
772
773    fn next_raw_with(
774        &mut self,
775        expected_match_tins: Option<&[crate::Tin]>,
776        plugin: Option<(&mut crate::Rule, &mut crate::Context)>,
777    ) -> LexResult<Token> {
778        let result = self.next_raw_inner(expected_match_tins, plugin);
779        match result {
780            Ok(token) => Ok(token),
781            Err(mut error) => {
782                // The matchers build their errors without the source, and it
783                // is attached here, where an error leaves the lexer: a
784                // negotiated cut (`relex`) rejects many candidates, each an
785                // error nobody sees, and a copy of the whole source apiece
786                // made that quadratic.
787                error.full_source = self.src.to_string();
788                error.apply_options(&self.options);
789                self.err = Some((*error).clone());
790                Err(error)
791            }
792        }
793    }
794
795    fn next_raw_inner(
796        &mut self,
797        expected_match_tins: Option<&[crate::Tin]>,
798        mut plugin: Option<(&mut crate::Rule, &mut crate::Context)>,
799    ) -> LexResult<Token> {
800        if self.end_reached {
801            return Ok(Token::new(
802                "#ZZ",
803                TIN_ZZ,
804                Value::Undefined,
805                "",
806                self.current_point(),
807            ));
808        }
809
810        if self.idx >= self.char_len {
811            self.end_reached = true;
812            return Ok(Token::new(
813                "#ZZ",
814                TIN_ZZ,
815                Value::Undefined,
816                "",
817                self.current_point(),
818            ));
819        }
820
821        let pnt = self.current_point();
822        let c = self.peek().unwrap();
823        let mut custom_index = 0;
824
825        if let Some(token) =
826            self.run_custom_matchers(&mut custom_index, 1_000_000.0, pnt, &mut plugin)
827        {
828            return Ok(token);
829        }
830
831        // User-declared match tokens occupy the 1e6 matcher priority band.
832        let match_skipped = if self.options.match_lex
833            && (!self.options.match_values.is_empty()
834                || self
835                    .options
836                    .match_tokens
837                    .values()
838                    .any(|matcher| self.wants(matcher.tin)))
839        {
840            match self.run_check(self.options.match_check.clone(), pnt) {
841                CheckFlow::Continue => false,
842                CheckFlow::Skip => true,
843                CheckFlow::Token(token) => return Ok(*token),
844            }
845        } else {
846            false
847        };
848        let remaining = &self.src[self.byte_position()..];
849        let custom_value = (self.options.match_lex && !match_skipped && self.want.is_none())
850            .then(|| {
851                self.options
852                    .match_values
853                    .values()
854                    .find_map(|matcher| match &matcher.matcher {
855                        MatchTokenMatcher::Callback(callback) => callback(remaining)
856                            .filter(|result| {
857                                !result.source.is_empty() && remaining.starts_with(&result.source)
858                            })
859                            .map(|result| (result.source, result.value)),
860                        MatchTokenMatcher::Regex(regex) => {
861                            let captures = regex.captures(remaining)?;
862                            let found = captures
863                                .get(0)
864                                .filter(|found| found.start() == 0 && !found.as_str().is_empty())?;
865                            let source = found.as_str().to_string();
866                            let value = matcher.transform.as_ref().map_or_else(
867                                || {
868                                    matcher
869                                        .val
870                                        .clone()
871                                        .unwrap_or_else(|| Value::String(source.clone()))
872                                },
873                                |transform| {
874                                    let groups = captures
875                                        .iter()
876                                        .map(|capture| {
877                                            capture.map_or_else(String::new, |value| {
878                                                value.as_str().into()
879                                            })
880                                        })
881                                        .collect::<Vec<_>>();
882                                    transform(&groups)
883                                },
884                            );
885                            Some((source, value))
886                        }
887                    })
888            })
889            .flatten();
890        if let Some((source, value)) = custom_value {
891            for _ in source.chars() {
892                self.advance();
893            }
894            return Ok(Token::new("#VL", TIN_VL, value, source, pnt));
895        }
896
897        let remaining = &self.src[self.byte_position()..];
898        // With no custom matcher there is nothing for the band to do: both
899        // passes walk an empty table and yield nothing, and `fix_len` is
900        // read only by that walk. Most grammars register none, and every
901        // token fetch of theirs paid the eager pass's scan of the fixed
902        // table (one closure call per fixed literal) to arrive at the
903        // `None` this guard now hands over directly. TS `makeMatchMatcher`
904        // returns null on an empty table (ts/src/lexer.ts) and the band is
905        // never installed; Go reaches the same place by defaulting
906        // `MatchLex` off unless `Options.Match` is set. Rust defaults
907        // `match_lex` true as TS does, so the guard is the parity.
908        let custom = (self.options.match_lex
909            && !match_skipped
910            && !self.options.match_tokens.is_empty())
911        .then(|| {
912            // Two passes, position-expected before eager, as go/lexer.go
913            // matchMatch and ts/src/lexer.ts makeMatchMatcher both make.
914            // One tin-ordered pass in which eagerness merely bypassed the
915            // slot gate let an eager matcher EARLIER in tin order win over
916            // an expected one later: with `p = %x31-39` beside
917            // `d = %x30-39`, the `2` of `12` lexed as the narrower class
918            // the `*d` loop never asked for. Eagerness is for firing where
919            // the slot's list is narrower than the grammar, never for
920            // outbidding what the slot names.
921            //
922            // Under a want the alternate's own tin list is the sharper
923            // gate, so one filtered pass is the whole search. With no
924            // expected list at all (a standalone lexer, no rule) nothing
925            // constrains the caller and every matcher is eligible in the
926            // first pass.
927            // The longest FIXED literal this slot expects that matches
928            // here, or 0. Only the eager pass consults it: there, a
929            // literal the slot names beats an eager-only matcher that
930            // cuts no further than it does. Without this, a character
931            // class that CONTAINS a literal the grammar also uses
932            // swallows it wherever the class is eager (`num = "0" /
933            // posdigit *digit` beside `digit = %x30-39` rejected
934            // `0.0.0`). LENGTH decides, not mere existence, so a keyword
935            // literal cannot truncate a longer word: ties go to the
936            // literal, and an eager matcher that cuts further still
937            // wins. TS and Go do the same, in makeMatchMatcher and
938            // matchMatch.
939            //
940            // Computed once per fetch and only when a regex matcher in the
941            // eager pass has something to weigh against it, as TS
942            // `expectedFixedLen` does (`fixLen = -1` until asked). The
943            // scan is the whole fixed table against the slot's list; an
944            // expected matcher that wins in pass 0, or a fetch under a
945            // want, never needs it. Nothing the scan reads changes
946            // between the two passes, so lazy equals eager.
947            let mut fix_len: Option<usize> = None;
948            let compute_fix_len = || {
949                if self.want.is_none() && self.options.fixed.lex {
950                    expected_match_tins.map_or(0, |expected| {
951                        self.options
952                            .fixed
953                            .tokens
954                            .values()
955                            .filter(|token| {
956                                !token.source.is_empty()
957                                    && expected.contains(&token.tin)
958                                    && remaining.starts_with(&token.source)
959                            })
960                            .map(|token| token.source.len())
961                            .max()
962                            .unwrap_or(0)
963                    })
964                } else {
965                    0
966                }
967            };
968            let passes = if self.want.is_some() { 1 } else { 2 };
969            (0..passes).find_map(|pass| {
970                self.options.match_tokens.values().find_map(|matcher| {
971                    if !self.wants(matcher.tin) {
972                        return None;
973                    }
974                    if self.want.is_none() {
975                        let expected = expected_match_tins
976                            .is_none_or(|expected| expected.contains(&matcher.tin));
977                        if pass == 0 {
978                            if !expected {
979                                return None;
980                            }
981                        } else if expected || !matcher.eager {
982                            return None;
983                        }
984                    }
985                    let result = match &matcher.matcher {
986                        MatchTokenMatcher::Regex(regex) => regex
987                            .find(remaining)
988                            .filter(|found| found.start() == 0)
989                            // The eager pass yields to an expected
990                            // literal it cannot out-cut; the fixed
991                            // matcher (2e6) runs next and takes it. See
992                            // `fix_len` above.
993                            .filter(|found| {
994                                pass == 0 || {
995                                    let fix_len = *fix_len.get_or_insert_with(compute_fix_len);
996                                    fix_len == 0 || found.len() > fix_len
997                                }
998                            })
999                            .map(|found| {
1000                                let source = found.as_str().to_string();
1001                                (source.clone(), Value::String(source))
1002                            }),
1003                        MatchTokenMatcher::Callback(callback) => callback(remaining)
1004                            .filter(|result| {
1005                                !result.source.is_empty() && remaining.starts_with(&result.source)
1006                            })
1007                            .map(|result| (result.source, result.value)),
1008                    };
1009                    result.map(|(source, value)| (matcher.name.clone(), matcher.tin, source, value))
1010                })
1011            })
1012        });
1013        if let Some(Some((name, tin, matched, value))) = custom {
1014            for _ in matched.chars() {
1015                self.advance();
1016            }
1017            return Ok(Token::new(name, tin, value, matched, pnt));
1018        }
1019
1020        if let Some(token) =
1021            self.run_custom_matchers(&mut custom_index, 2_000_000.0, pnt, &mut plugin)
1022        {
1023            return Ok(token);
1024        }
1025
1026        // Fixed literals occupy the 2e6 band and use longest-match wins.
1027        let fixed_skipped = if self.options.fixed.lex {
1028            match self.run_check(self.options.fixed.check.clone(), pnt) {
1029                CheckFlow::Continue => false,
1030                CheckFlow::Skip => true,
1031                CheckFlow::Token(token) => return Ok(*token),
1032            }
1033        } else {
1034            false
1035        };
1036        let remaining = &self.src[self.byte_position()..];
1037        // The winner is carried out of the table as its position, not as a
1038        // copy of its text. `Token::new` takes the name and the source text
1039        // by reference and stores both inline, so the only owned copy the
1040        // token needs is the one inside `Value::String`. Naming the match
1041        // as three owned values cost three `String` allocations per fixed
1042        // token, two of them freed again before the token was built.
1043        // The first byte decides almost every entry. Asking `wants` and then
1044        // `starts_with` of each fixed token in turn ran a tin lookup and a
1045        // `memcmp` per token in the grammar per token in the input, and a
1046        // grammar with fifty fixed tokens pays fifty of each to reject
1047        // forty-nine. One byte answers the same question, and an empty
1048        // source is kept out by its own check, which only entries that
1049        // already matched the byte ever reach.
1050        let first_byte = remaining.as_bytes().first().copied();
1051        let fixed = (self.options.fixed.lex && !fixed_skipped)
1052            .then(|| {
1053                self.options
1054                    .fixed
1055                    .tokens
1056                    .values()
1057                    .enumerate()
1058                    .filter(|(_, token)| {
1059                        token.source.as_bytes().first().copied() == first_byte
1060                            && !token.source.is_empty()
1061                            && self.wants(token.tin)
1062                            && remaining.starts_with(&token.source)
1063                    })
1064                    .max_by_key(|(_, token)| token.source.len())
1065                    .map(|(index, token)| (index, token.source.chars().count()))
1066            })
1067            .flatten();
1068        if let Some((index, source_chars)) = fixed {
1069            for _ in 0..source_chars {
1070                self.advance();
1071            }
1072            let (_, token) = self
1073                .options
1074                .fixed
1075                .tokens
1076                .get_index(index)
1077                .expect("index came from this table, which nothing writes to mid-parse");
1078            return Ok(Token::new(
1079                &token.name,
1080                token.tin,
1081                Value::String(token.source.clone()),
1082                token.source.as_str(),
1083                pnt,
1084            ));
1085        }
1086
1087        if let Some(token) =
1088            self.run_custom_matchers(&mut custom_index, 3_000_000.0, pnt, &mut plugin)
1089        {
1090            return Ok(token);
1091        }
1092
1093        // 1. Whitespace
1094        let space_skipped = if self.options.space.lex && self.wants(TIN_SP) {
1095            match self.run_check(self.options.space.check.clone(), pnt) {
1096                CheckFlow::Continue => false,
1097                CheckFlow::Skip => true,
1098                CheckFlow::Token(token) => return Ok(*token),
1099            }
1100        } else {
1101            false
1102        };
1103        if self.options.space.lex
1104            && !space_skipped
1105            && self.wants(TIN_SP)
1106            && self.char_sets.space.contains(c)
1107        {
1108            let mut src = String::new();
1109            while let Some(ch) = self.peek() {
1110                if self.char_sets.space.contains(ch) {
1111                    src.push(ch);
1112                    self.advance();
1113                } else {
1114                    break;
1115                }
1116            }
1117            return Ok(Token::new(
1118                "#SP",
1119                TIN_SP,
1120                Value::String(src.clone()),
1121                src,
1122                pnt,
1123            ));
1124        }
1125
1126        if let Some(token) =
1127            self.run_custom_matchers(&mut custom_index, 4_000_000.0, pnt, &mut plugin)
1128        {
1129            return Ok(token);
1130        }
1131
1132        // 2. Line ending
1133        let line_skipped = if self.options.line.lex && self.wants(TIN_LN) {
1134            match self.run_check(self.options.line.check.clone(), pnt) {
1135                CheckFlow::Continue => false,
1136                CheckFlow::Skip => true,
1137                CheckFlow::Token(token) => return Ok(*token),
1138            }
1139        } else {
1140            false
1141        };
1142        if self.options.line.lex
1143            && !line_skipped
1144            && self.wants(TIN_LN)
1145            && (self.char_sets.line_ends.contains(c))
1146        {
1147            let mut src = String::new();
1148            let mut seen = std::collections::HashSet::new();
1149            while let Some(ch) = self.peek() {
1150                if !self.char_sets.line_ends.contains(ch) {
1151                    break;
1152                }
1153                if self.options.line.single && !seen.insert(ch) {
1154                    break;
1155                }
1156                src.push(self.advance().expect("peeked character must advance"));
1157            }
1158            self.ci = 1;
1159            return Ok(Token::new(
1160                "#LN",
1161                TIN_LN,
1162                Value::String(src.clone()),
1163                src,
1164                pnt,
1165            ));
1166        }
1167
1168        if self.options.line.lex
1169            && !line_skipped
1170            && self.wants(TIN_LN)
1171            && (c == '\u{2028}' || c == '\u{2029}')
1172        {
1173            let bad_char = self.advance().expect("peeked character must advance");
1174            let err = TabnasError::new(
1175                "unexpected",
1176                bad_char.to_string(),
1177                "",
1178                pnt.site.pos,
1179                pnt.site.ri,
1180                pnt.site.ci,
1181            );
1182            self.err = Some(err.clone());
1183            return Err(Box::new(err));
1184        }
1185
1186        if let Some(token) =
1187            self.run_custom_matchers(&mut custom_index, 5_000_000.0, pnt, &mut plugin)
1188        {
1189            return Ok(token);
1190        }
1191
1192        // 3. Quoted strings. These precede comments in the canonical matcher
1193        // order, so an overlapping quote/comment opener is a string unless
1194        // string matching explicitly abandons the malformed candidate.
1195        let string_skipped = if self.options.string.lex && self.wants(TIN_ST) {
1196            match self.run_check(self.options.string.check.clone(), pnt) {
1197                CheckFlow::Continue => false,
1198                CheckFlow::Skip => true,
1199                CheckFlow::Token(token) => return Ok(*token),
1200            }
1201        } else {
1202            false
1203        };
1204        if self.options.string.lex
1205            && !string_skipped
1206            && self.wants(TIN_ST)
1207            && self.char_sets.string.contains(c)
1208        {
1209            let start = (self.idx, self.ri, self.ci);
1210            match self.match_string(c, pnt) {
1211                result @ Ok(_) => return result,
1212                Err(error) if !self.options.string.abandon => return Err(error),
1213                Err(_) => {
1214                    (self.idx, self.ri, self.ci) = start;
1215                    self.err = None;
1216                }
1217            }
1218        }
1219
1220        if let Some(token) =
1221            self.run_custom_matchers(&mut custom_index, 6_000_000.0, pnt, &mut plugin)
1222        {
1223            return Ok(token);
1224        }
1225
1226        // 4. Comments (longest opening marker wins; ties sort by name).
1227        let comment_skipped = if self.options.comment.lex && self.wants(TIN_CM) {
1228            match self.run_check(self.options.comment.check.clone(), pnt) {
1229                CheckFlow::Continue => false,
1230                CheckFlow::Skip => true,
1231                CheckFlow::Token(token) => return Ok(*token),
1232            }
1233        } else {
1234            false
1235        };
1236        if self.options.comment.lex && !comment_skipped && self.wants(TIN_CM) {
1237            if let Some(token) = self.match_comment(pnt)? {
1238                return Ok(token);
1239            }
1240        }
1241
1242        if let Some(token) =
1243            self.run_custom_matchers(&mut custom_index, 7_000_000.0, pnt, &mut plugin)
1244        {
1245            return Ok(token);
1246        }
1247
1248        // 5. Numbers
1249        let number_skipped = if self.options.number.lex && self.wants(TIN_NR) {
1250            match self.run_check(self.options.number.check.clone(), pnt) {
1251                CheckFlow::Continue => false,
1252                CheckFlow::Skip => true,
1253                CheckFlow::Token(token) => return Ok(*token),
1254            }
1255        } else {
1256            false
1257        };
1258        if self.options.number.lex
1259            && !number_skipped
1260            && self.wants(TIN_NR)
1261            && (c == '-' || c == '+' || c == '.' || c.is_ascii_digit())
1262        {
1263            if let Some(tkn) = self.match_number(pnt)? {
1264                return Ok(tkn);
1265            }
1266        }
1267
1268        if let Some(token) =
1269            self.run_custom_matchers(&mut custom_index, 8_000_000.0, pnt, &mut plugin)
1270        {
1271            return Ok(token);
1272        }
1273
1274        // 6. Text and named/regex values share the same delimited run.
1275        // Negotiated lexing gates this combined family by its primary token
1276        // identity (#TX), matching the TypeScript and Go dispatchers. Once
1277        // entered, an exact or regexp value definition may still produce
1278        // #VL; the caller rejects and rolls that cut back when #VL was not
1279        // requested.
1280        let text_matcher_wanted = self.wants(TIN_TX);
1281        let value_lex = self.options.value.lex && text_matcher_wanted;
1282        let text_lex = self.options.text.lex && text_matcher_wanted;
1283        let text_skipped = if text_lex || value_lex {
1284            match self.run_check(self.options.text.check.clone(), pnt) {
1285                CheckFlow::Continue => false,
1286                CheckFlow::Skip => true,
1287                CheckFlow::Token(token) => return Ok(*token),
1288            }
1289        } else {
1290            false
1291        };
1292        if (text_lex || value_lex) && !text_skipped && !self.is_text_delimiter_here() {
1293            let start = (self.idx, self.ri, self.ci);
1294            // Only a `value` definition declaring `consume` looks at the
1295            // rest of the document, and the JSON grammar has none -- but
1296            // this ran for every text token, copying the whole tail of the
1297            // input each time. `self.src` is borrowed from the caller for
1298            // `'a` and is never reassigned, so reading the reference out
1299            // before the scan below gives a slice that does not borrow
1300            // `self` and survives the `&mut self` the scan needs.
1301            let source: &'a str = self.src;
1302            let remaining = &source[self.byte_position()..];
1303            let mut src = String::new();
1304            while let Some(ch) = self.peek() {
1305                if self.is_text_delimiter_here() {
1306                    break;
1307                }
1308                src.push(ch);
1309                self.advance();
1310            }
1311
1312            let mut output = None;
1313            if value_lex {
1314                if let Some(definition) = self
1315                    .options
1316                    .value
1317                    .definitions
1318                    .get(&src)
1319                    .filter(|definition| definition.matcher.is_none())
1320                    .cloned()
1321                {
1322                    output = Some(Token::new(
1323                        "#VL",
1324                        TIN_VL,
1325                        definition
1326                            .val
1327                            .clone()
1328                            .unwrap_or_else(|| Value::String(src.clone())),
1329                        src.clone(),
1330                        pnt,
1331                    ));
1332                }
1333
1334                if output.is_none() {
1335                    let mut definitions: Vec<_> = self
1336                        .options
1337                        .value
1338                        .definitions
1339                        .iter()
1340                        .filter(|(_, definition)| definition.matcher.is_some())
1341                        .map(|(name, definition)| (name.clone(), definition.clone()))
1342                        .collect();
1343                    definitions.sort_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
1344                    for (_, definition) in definitions {
1345                        let regex = definition.matcher.as_ref().expect("filtered matcher");
1346                        let target: &str = if definition.consume { remaining } else { &src };
1347                        let Some(captures) = regex.captures(target) else {
1348                            continue;
1349                        };
1350                        let Some(found) = captures.get(0).filter(|found| found.start() == 0) else {
1351                            continue;
1352                        };
1353                        if !definition.consume && found.end() != target.len() {
1354                            continue;
1355                        }
1356                        let matched = found.as_str().to_string();
1357                        let value = definition.transform.as_ref().map_or_else(
1358                            || {
1359                                definition
1360                                    .val
1361                                    .clone()
1362                                    .unwrap_or_else(|| Value::String(matched.clone()))
1363                            },
1364                            |transform| {
1365                                let groups = captures
1366                                    .iter()
1367                                    .map(|capture| {
1368                                        capture
1369                                            .map_or_else(String::new, |value| value.as_str().into())
1370                                    })
1371                                    .collect::<Vec<_>>();
1372                                transform(&groups)
1373                            },
1374                        );
1375                        if definition.consume {
1376                            (self.idx, self.ri, self.ci) = start;
1377                            for _ in matched.chars() {
1378                                self.advance();
1379                            }
1380                        }
1381                        output = Some(Token::new("#VL", TIN_VL, value, matched, pnt));
1382                        break;
1383                    }
1384                }
1385            }
1386
1387            if output.is_none() && (!text_lex || text_skipped) {
1388                (self.idx, self.ri, self.ci) = start;
1389            } else if output.is_none() {
1390                output = Some(Token::new(
1391                    "#TX",
1392                    TIN_TX,
1393                    Value::String(src.clone()),
1394                    src,
1395                    pnt,
1396                ));
1397            }
1398
1399            if let Some(mut token) = output {
1400                let value = std::mem::replace(&mut token.val, Value::Undefined);
1401                token.val = self.modify_text_value(value, &mut plugin);
1402                return Ok(token);
1403            }
1404        }
1405
1406        if let Some(token) =
1407            self.run_custom_matchers(&mut custom_index, f64::INFINITY, pnt, &mut plugin)
1408        {
1409            return Ok(token);
1410        }
1411
1412        // 7. Unclaimed character -> Error: unexpected
1413        let bad_char = self.advance().unwrap();
1414        let err = TabnasError::new(
1415            "unexpected",
1416            bad_char.to_string(),
1417            "",
1418            pnt.site.pos,
1419            pnt.site.ri,
1420            pnt.site.ci,
1421        );
1422        self.err = Some(err.clone());
1423        Err(Box::new(err))
1424    }
1425
1426    fn match_comment(&mut self, pnt: Point) -> LexResult<Option<Token>> {
1427        let remaining = &self.src[self.byte_position()..];
1428        let mut definitions: Vec<_> = self
1429            .options
1430            .comment
1431            .definitions
1432            .iter()
1433            .filter(|(_, definition)| {
1434                // Same first-byte test as the fixed-token scan above.
1435                definition.start.as_bytes().first().copied()
1436                    == remaining.as_bytes().first().copied()
1437                    && !definition.start.is_empty()
1438                    && definition.lex
1439                    && remaining.starts_with(&definition.start)
1440            })
1441            .collect();
1442        definitions.sort_by(|(name_a, a), (name_b, b)| {
1443            b.start
1444                .len()
1445                .cmp(&a.start.len())
1446                .then_with(|| name_a.cmp(name_b))
1447        });
1448        let Some((_, definition)) = definitions.first() else {
1449            return Ok(None);
1450        };
1451        let definition = (*definition).clone();
1452        let mut src = String::new();
1453        for _ in definition.start.chars() {
1454            src.push(self.advance().expect("comment marker must advance"));
1455        }
1456
1457        let mut terminated_by_suffix = false;
1458        let mut closed = definition.line;
1459        loop {
1460            let remainder = &self.src[self.byte_position()..];
1461            let suffix = definition
1462                .suffixes
1463                .iter()
1464                .filter(|suffix| !suffix.is_empty() && remainder.starts_with(*suffix))
1465                .max_by_key(|suffix| suffix.len())
1466                .cloned();
1467            let suffix = suffix.or_else(|| {
1468                let matcher = definition.suffix_matcher.as_ref()?;
1469                let effect = matcher.run(remainder);
1470                if effect.is_some() {
1471                    return effect;
1472                }
1473                let saved = self.state();
1474                let wanted = self.want.clone();
1475                let token = matcher.run_imperative(self);
1476                self.restore(saved);
1477                self.want = wanted;
1478                token.map(|token| token.src.to_string())
1479            });
1480            let remainder = &self.src[self.byte_position()..];
1481            let suffix =
1482                suffix.filter(|suffix| !suffix.is_empty() && remainder.starts_with(suffix));
1483            if let Some(suffix) = suffix {
1484                for _ in suffix.chars() {
1485                    src.push(self.advance().expect("comment suffix must advance"));
1486                }
1487                terminated_by_suffix = true;
1488                closed = true;
1489                break;
1490            }
1491            if !definition.line
1492                && !definition.end.is_empty()
1493                && remainder.starts_with(&definition.end)
1494            {
1495                for _ in definition.end.chars() {
1496                    src.push(self.advance().expect("comment end must advance"));
1497                }
1498                closed = true;
1499                break;
1500            }
1501            let Some(ch) = self.peek() else {
1502                break;
1503            };
1504            if definition.line && (self.char_sets.line_ends.contains(ch)) {
1505                break;
1506            }
1507            src.push(self.advance().expect("comment body must advance"));
1508        }
1509
1510        if !closed {
1511            let err = TabnasError::new(
1512                "unterminated_comment",
1513                src,
1514                "",
1515                pnt.site.pos,
1516                pnt.site.ri,
1517                pnt.site.ci,
1518            );
1519            self.err = Some(err.clone());
1520            return Err(Box::new(err));
1521        }
1522
1523        if definition.eat_line && !terminated_by_suffix {
1524            while let Some(ch) = self.peek() {
1525                if !self.char_sets.line_ends.contains(ch) {
1526                    break;
1527                }
1528                src.push(self.advance().expect("comment line tail must advance"));
1529            }
1530        }
1531
1532        Ok(Some(Token::new(
1533            "#CM",
1534            TIN_CM,
1535            Value::String(src.clone()),
1536            src,
1537            pnt,
1538        )))
1539    }
1540
1541    fn match_number(&mut self, pnt: Point) -> LexResult<Option<Token>> {
1542        let start_idx = self.idx;
1543        let mut src = String::new();
1544
1545        // Optional sign.
1546        if matches!(self.peek(), Some('-' | '+')) {
1547            src.push(self.advance().unwrap());
1548        }
1549
1550        // Base-prefixed integers are complete at the final valid digit.
1551        if self.peek() == Some('0') {
1552            if let Some(prefix) = self.peek_at(1) {
1553                let radix = match prefix {
1554                    'x' | 'X' if self.options.number.hex => Some(16),
1555                    'o' | 'O' if self.options.number.oct => Some(8),
1556                    'b' | 'B' if self.options.number.bin => Some(2),
1557                    _ => None,
1558                };
1559                if let Some(radix) = radix {
1560                    src.push(self.advance().expect("peeked zero"));
1561                    src.push(self.advance().expect("peeked base prefix"));
1562                    let mut saw_digit = false;
1563                    while let Some(ch) = self.peek() {
1564                        if ch.is_digit(radix) {
1565                            saw_digit = true;
1566                            src.push(self.advance().expect("peeked base digit"));
1567                        } else if self
1568                            .options
1569                            .number
1570                            .sep
1571                            .as_ref()
1572                            .is_some_and(|separator| separator.contains(ch))
1573                        {
1574                            src.push(self.advance().expect("peeked base digit"));
1575                        } else {
1576                            break;
1577                        }
1578                    }
1579                    if saw_digit && self.is_text_delimiter_here() {
1580                        if self
1581                            .exclude_regex
1582                            .as_ref()
1583                            .is_some_and(|regex| regex.is_match(&src))
1584                        {
1585                            self.reset_number(start_idx, pnt);
1586                            return Ok(None);
1587                        }
1588                        if self.options.value.lex {
1589                            if let Some(definition) = self
1590                                .options
1591                                .value
1592                                .definitions
1593                                .get(&src)
1594                                .filter(|definition| definition.matcher.is_none())
1595                            {
1596                                return Ok(Some(Token::new(
1597                                    "#VL",
1598                                    TIN_VL,
1599                                    definition
1600                                        .val
1601                                        .clone()
1602                                        .unwrap_or_else(|| Value::String(src.clone())),
1603                                    src,
1604                                    pnt,
1605                                )));
1606                            }
1607                        }
1608                        // The digits of the literal, prefix, sign and any
1609                        // separators removed, folded as they are read. The
1610                        // fold keeps a bounded head, a digit count and a
1611                        // sticky bit, so a literal of any length costs the
1612                        // same handful of bytes: buffering the digits
1613                        // instead would let one long token multiply the
1614                        // memory the source already holds.
1615                        let mut fold = DigitFold::new(radix.trailing_zeros());
1616                        for ch in src.chars().skip_while(|ch| matches!(ch, '-' | '+')).skip(2) {
1617                            if self
1618                                .options
1619                                .number
1620                                .sep
1621                                .as_ref()
1622                                .is_some_and(|separator| separator.contains(ch))
1623                            {
1624                                continue;
1625                            }
1626                            fold.push(ch.to_digit(radix).expect("validated base digit"));
1627                        }
1628                        let mut value = fold.finish();
1629                        if src.starts_with('-') {
1630                            value = -value;
1631                        }
1632                        return Ok(Some(Token::new(
1633                            "#NR",
1634                            TIN_NR,
1635                            Value::Number(value),
1636                            src,
1637                            pnt,
1638                        )));
1639                    }
1640                    self.reset_number(start_idx, pnt);
1641                    return Ok(None);
1642                }
1643            }
1644        }
1645
1646        let Some(ch) = self.peek() else {
1647            self.reset_number(start_idx, pnt);
1648            return Ok(None);
1649        };
1650        if ch == '.' {
1651            if !self.peek_at(1).is_some_and(|next| next.is_ascii_digit()) {
1652                self.reset_number(start_idx, pnt);
1653                return Ok(None);
1654            }
1655            src.push(self.advance().expect("peeked leading decimal point"));
1656        } else if !ch.is_ascii_digit() {
1657            self.reset_number(start_idx, pnt);
1658            return Ok(None);
1659        }
1660
1661        let (has_digits, edge_separator) = self.scan_number_digits(&mut src);
1662        if !has_digits || edge_separator {
1663            self.reset_number(start_idx, pnt);
1664            return Ok(None);
1665        }
1666
1667        // The canonical regexp admits a trailing decimal point and an
1668        // exponent after it (`2.e3`), but declines `0.a` as one text run.
1669        if self.peek() == Some('.') {
1670            let next = self.peek_at(1);
1671            let exponent_after_dot = matches!(next, Some('e' | 'E'))
1672                && match self.peek_at(2) {
1673                    Some('+' | '-') => self.peek_at(3).is_some_and(|ch| ch.is_ascii_digit()),
1674                    Some(ch) => ch.is_ascii_digit(),
1675                    None => false,
1676                };
1677            if next.is_some_and(|ch| ch.is_ascii_digit()) {
1678                src.push(self.advance().expect("peeked decimal point"));
1679                let (_, edge_separator) = self.scan_number_digits(&mut src);
1680                if edge_separator {
1681                    self.reset_number(start_idx, pnt);
1682                    return Ok(None);
1683                }
1684            } else if next.is_some()
1685                && !self.is_text_delimiter_at(self.idx + 1)
1686                && next != Some('.')
1687                && !exponent_after_dot
1688            {
1689                self.reset_number(start_idx, pnt);
1690                return Ok(None);
1691            } else {
1692                src.push(self.advance().expect("peeked trailing decimal point"));
1693            }
1694        }
1695
1696        if matches!(self.peek(), Some('e' | 'E')) {
1697            let exponent_start = self.idx;
1698            let source_len = src.len();
1699            src.push(self.advance().expect("peeked exponent marker"));
1700            if matches!(self.peek(), Some('+' | '-')) {
1701                src.push(self.advance().expect("peeked exponent sign"));
1702            }
1703            let (has_exponent_digits, edge_separator) = self.scan_number_digits(&mut src);
1704            if edge_separator {
1705                self.reset_number(start_idx, pnt);
1706                return Ok(None);
1707            }
1708            if !has_exponent_digits {
1709                self.idx = exponent_start;
1710                src.truncate(source_len);
1711            }
1712        }
1713
1714        if !self.is_text_delimiter_here() {
1715            self.reset_number(start_idx, pnt);
1716            return Ok(None);
1717        }
1718
1719        // Check exclusion regex (e.g. ^00+)
1720        if let Some(ref re) = self.exclude_regex {
1721            if re.is_match(&src) {
1722                // Number is excluded, backtrack
1723                self.reset_number(start_idx, pnt);
1724                return Ok(None);
1725            }
1726        }
1727
1728        if self.options.value.lex {
1729            if let Some(definition) = self
1730                .options
1731                .value
1732                .definitions
1733                .get(&src)
1734                .filter(|definition| definition.matcher.is_none())
1735            {
1736                return Ok(Some(Token::new(
1737                    "#VL",
1738                    TIN_VL,
1739                    definition
1740                        .val
1741                        .clone()
1742                        .unwrap_or_else(|| Value::String(src.clone())),
1743                    src,
1744                    pnt,
1745                )));
1746            }
1747        }
1748
1749        // Parse float
1750        let parse_src = self.options.number.sep.as_ref().map_or_else(
1751            || src.clone(),
1752            |separator| src.chars().filter(|ch| !separator.contains(*ch)).collect(),
1753        );
1754        match parse_src.parse::<f64>() {
1755            Ok(num) => Ok(Some(Token::new(
1756                "#NR",
1757                TIN_NR,
1758                Value::Number(num),
1759                src,
1760                pnt,
1761            ))),
1762            Err(_) => {
1763                self.reset_number(start_idx, pnt);
1764                Ok(None)
1765            }
1766        }
1767    }
1768
1769    fn reset_number(&mut self, start_idx: usize, pnt: Point) {
1770        self.idx = start_idx;
1771        self.ri = pnt.site.ri;
1772        self.ci = pnt.site.ci;
1773    }
1774
1775    /// Consume a decimal digit/separator run. Separators are legal only
1776    /// between digits; a leading or trailing separator makes the whole run
1777    /// fall through to text, matching the TypeScript regexp and Go scanner.
1778    fn scan_number_digits(&mut self, src: &mut String) -> (bool, bool) {
1779        // The run is measured before any of it is consumed. Advancing
1780        // as it goes would hold `&mut self` across a read of
1781        // `self.options.number.sep`, and the way that used to be settled
1782        // was to clone the separator — an allocation and a free for
1783        // every number in the input, for a value that cannot change
1784        // while one number is being scanned.
1785        let run_start = self.idx;
1786        let mut saw_digit = false;
1787        let mut last_was_separator = false;
1788        let mut end = run_start;
1789        {
1790            let separator = self.options.number.sep.as_deref();
1791            while let Some(ch) = self.chars.get(end).copied() {
1792                if ch.is_ascii_digit() {
1793                    saw_digit = true;
1794                    last_was_separator = false;
1795                } else if separator.is_some_and(|separator| separator.contains(ch)) {
1796                    last_was_separator = true;
1797                } else {
1798                    break;
1799                }
1800                end += 1;
1801            }
1802        }
1803        while self.idx < end {
1804            src.push(self.advance().expect("scanned number character"));
1805        }
1806        let starts_with_separator = self.idx > run_start
1807            && self.options.number.sep.as_deref().is_some_and(|separator| {
1808                self.chars[run_start..self.idx]
1809                    .first()
1810                    .is_some_and(|ch| separator.contains(*ch))
1811            });
1812        (saw_digit, starts_with_separator || last_was_separator)
1813    }
1814
1815    fn match_string(&mut self, quote: char, pnt: Point) -> LexResult<Token> {
1816        let quote_char = self.advance().unwrap();
1817        let mut out_str = String::new();
1818        let mut raw_src = String::new();
1819        raw_src.push(quote_char);
1820
1821        let mut pending_high_surrogate: Option<u16> = None;
1822
1823        // The body classes of TypeScript's `buildStringBodySpec`
1824        // (ts/src/lexer.ts), which Go's `BuildStringBodySpec` ports: a
1825        // line character is LINE or LINE+ROW only inside a multi-line
1826        // string, where it resets the column and, in `line.rowChars`,
1827        // counts a row; anywhere else in a body it is plain content,
1828        // counted as a column, unless it is a control character, which
1829        // stops the body as `unprintable`. This loop used to count a row
1830        // for any row character it stepped over, string body or not, and
1831        // to refuse any line character inside a single-line string: with
1832        // json5's U+2028 and U+2029 as row characters, `y` in
1833        // `"a<U+2028>b" y` sat on row 2 here and on row 1 in TypeScript
1834        // and Go, and with the two in `line.chars` as well the string was
1835        // `unprintable` (tabnas/parser#263).
1836        let multi_line = self.options.string.multi_chars.contains(quote);
1837
1838        while let Some(c) = self.peek() {
1839            if c == quote {
1840                raw_src.push(self.advance().unwrap());
1841                // Rust strings cannot represent a lone UTF-16 surrogate, so
1842                // preserve the Go-port behavior and fold it to U+FFFD.
1843                self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
1844                return Ok(Token::new(
1845                    "#ST",
1846                    TIN_ST,
1847                    Value::String(out_str),
1848                    raw_src,
1849                    pnt,
1850                ));
1851            }
1852
1853            if let Some(replacement) = self.options.string.replace.get(&c).cloned() {
1854                raw_src.push(self.advance().expect("peeked character must advance"));
1855                self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
1856                out_str.push_str(&replacement);
1857                continue;
1858            }
1859
1860            if multi_line && self.char_sets.line.contains(c) {
1861                raw_src.push(
1862                    self.advance_string_line()
1863                        .expect("peeked character must advance"),
1864                );
1865                out_str.push(c);
1866                continue;
1867            }
1868
1869            // A control character stops the body: a line character
1870            // always, since a single-line string cannot hold one, and any
1871            // other unless `string.allowControl` admits it. Sited ON the
1872            // character, as TypeScript does (`pnt.sI = sI; pnt.cI = cI`
1873            // before its `bad()` call, ts/src/lexer.ts). `pnt` is the
1874            // opening quote, and reporting that put every embedded
1875            // newline at the start of its string.
1876            if (c as u32) < 32
1877                && (self.char_sets.line.contains(c) || !self.options.string.allow_control)
1878            {
1879                let site = self.current_point().site;
1880                let err =
1881                    TabnasError::new("unprintable", c.to_string(), "", site.pos, site.ri, site.ci);
1882                self.err = Some(err.clone());
1883                return Err(Box::new(err));
1884            }
1885
1886            if c == self.options.string.escape_char {
1887                raw_src.push(self.advance().unwrap());
1888                let esc_point = self.current_point();
1889                if let Some(esc) = self.advance() {
1890                    raw_src.push(esc);
1891                    if let Some(replacement) = self.options.string.escape.get(&esc).cloned() {
1892                        self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
1893                        out_str.push_str(&replacement);
1894                        continue;
1895                    }
1896                    match esc {
1897                        'u' => {
1898                            // Unicode escape: \uXXXX or \u{X...}. An
1899                            // invalid one is reported on the backslash
1900                            // with the span TypeScript cuts: six source
1901                            // characters for the fixed-width form, four
1902                            // for `\x`, and through the closing brace
1903                            // (or to the end of the source) for the
1904                            // braced form -- clipped, never padded, so a
1905                            // truncated escape at end of input reports
1906                            // exactly the characters that are there.
1907                            if self.peek() == Some('{') && !self.options.string.escape_strict {
1908                                raw_src.push(self.advance().unwrap()); // '{'
1909                                let mut hex = String::new();
1910                                let mut closed = false;
1911                                while let Some(h) = self.peek() {
1912                                    if h == '}' {
1913                                        raw_src.push(self.advance().unwrap());
1914                                        closed = true;
1915                                        break;
1916                                    }
1917                                    raw_src.push(self.advance().unwrap());
1918                                    hex.push(h);
1919                                }
1920
1921                                if !closed
1922                                    || hex.is_empty()
1923                                    || hex.len() > 6
1924                                    || !hex.chars().all(|ch| ch.is_ascii_hexdigit())
1925                                {
1926                                    let err = TabnasError::new(
1927                                        "invalid_unicode",
1928                                        self.source_span(esc_point.site.pos - 1, self.idx),
1929                                        "",
1930                                        esc_point.site.pos - 1,
1931                                        esc_point.site.ri,
1932                                        esc_point.site.ci - 1,
1933                                    );
1934                                    self.err = Some(err.clone());
1935                                    return Err(Box::new(err));
1936                                }
1937
1938                                let cp = match u32::from_str_radix(&hex, 16) {
1939                                    Ok(val) if val <= 0x10FFFF => val,
1940                                    _ => {
1941                                        let err = TabnasError::new(
1942                                            "invalid_unicode",
1943                                            self.source_span(esc_point.site.pos - 1, self.idx),
1944                                            "",
1945                                            esc_point.site.pos - 1,
1946                                            esc_point.site.ri,
1947                                            esc_point.site.ci - 1,
1948                                        );
1949                                        self.err = Some(err.clone());
1950                                        return Err(Box::new(err));
1951                                    }
1952                                };
1953
1954                                self.emit_unicode_escape(
1955                                    cp,
1956                                    &mut pending_high_surrogate,
1957                                    &mut out_str,
1958                                );
1959                            } else {
1960                                // Exactly 4 hex digits: \uXXXX
1961                                let mut hex = String::new();
1962                                for _ in 0..4 {
1963                                    if let Some(h) = self.peek() {
1964                                        if h.is_ascii_hexdigit() {
1965                                            raw_src.push(self.advance().unwrap());
1966                                            hex.push(h);
1967                                        } else {
1968                                            break;
1969                                        }
1970                                    } else {
1971                                        break;
1972                                    }
1973                                }
1974
1975                                if hex.len() != 4 {
1976                                    let err = TabnasError::new(
1977                                        "invalid_unicode",
1978                                        self.source_span(
1979                                            esc_point.site.pos - 1,
1980                                            esc_point.site.pos + 5,
1981                                        ),
1982                                        "",
1983                                        esc_point.site.pos - 1,
1984                                        esc_point.site.ri,
1985                                        esc_point.site.ci - 1,
1986                                    );
1987                                    self.err = Some(err.clone());
1988                                    return Err(Box::new(err));
1989                                }
1990
1991                                let cp = u16::from_str_radix(&hex, 16).map_err(|_| {
1992                                    let err = TabnasError::new(
1993                                        "invalid_unicode",
1994                                        self.source_span(
1995                                            esc_point.site.pos - 1,
1996                                            esc_point.site.pos + 5,
1997                                        ),
1998                                        "",
1999                                        esc_point.site.pos - 1,
2000                                        esc_point.site.ri,
2001                                        esc_point.site.ci - 1,
2002                                    );
2003                                    self.err = Some(err.clone());
2004                                    err
2005                                })?;
2006
2007                                self.emit_unicode_escape(
2008                                    u32::from(cp),
2009                                    &mut pending_high_surrogate,
2010                                    &mut out_str,
2011                                );
2012                            }
2013                        }
2014                        'x' if !self.options.string.escape_strict => {
2015                            let mut hex = String::new();
2016                            for _ in 0..2 {
2017                                if let Some(h) = self.peek() {
2018                                    if h.is_ascii_hexdigit() {
2019                                        raw_src.push(
2020                                            self.advance().expect("peeked character must advance"),
2021                                        );
2022                                        hex.push(h);
2023                                    }
2024                                }
2025                            }
2026                            if hex.len() != 2 {
2027                                let err = TabnasError::new(
2028                                    "invalid_ascii",
2029                                    self.source_span(
2030                                        esc_point.site.pos - 1,
2031                                        esc_point.site.pos + 3,
2032                                    ),
2033                                    "",
2034                                    esc_point.site.pos - 1,
2035                                    esc_point.site.ri,
2036                                    esc_point.site.ci - 1,
2037                                );
2038                                self.err = Some(err.clone());
2039                                return Err(Box::new(err));
2040                            }
2041                            let byte = u8::from_str_radix(&hex, 16).expect("validated ASCII hex");
2042                            self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
2043                            out_str.push(char::from(byte));
2044                        }
2045                        other => {
2046                            if !self.options.string.allow_unknown {
2047                                // Sited on the escape CHARACTER with a
2048                                // one-character span, as TypeScript
2049                                // (`pnt.sI = sI; pnt.cI = cI; lex.bad(
2050                                // S.unexpected, sI, sI + 1)`) and Go do;
2051                                // the other escape errors sit on the
2052                                // backslash and span the construct.
2053                                let err = TabnasError::new(
2054                                    "unexpected",
2055                                    other.to_string(),
2056                                    "",
2057                                    esc_point.site.pos,
2058                                    esc_point.site.ri,
2059                                    esc_point.site.ci,
2060                                );
2061                                self.err = Some(err.clone());
2062                                return Err(Box::new(err));
2063                            }
2064                            self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
2065                            out_str.push(other);
2066                        }
2067                    }
2068                } else {
2069                    let err = TabnasError::new(
2070                        "unterminated_string",
2071                        raw_src,
2072                        "",
2073                        pnt.site.pos,
2074                        pnt.site.ri,
2075                        pnt.site.ci,
2076                    );
2077                    self.err = Some(err.clone());
2078                    return Err(Box::new(err));
2079                }
2080            } else {
2081                self.flush_surrogate(&mut pending_high_surrogate, &mut out_str);
2082                raw_src.push(self.advance_body().expect("peeked character must advance"));
2083                out_str.push(c);
2084            }
2085        }
2086
2087        let err = TabnasError::new(
2088            "unterminated_string",
2089            raw_src,
2090            "",
2091            pnt.site.pos,
2092            pnt.site.ri,
2093            pnt.site.ci,
2094        );
2095        self.err = Some(err.clone());
2096        Err(Box::new(err))
2097    }
2098
2099    fn flush_surrogate(&self, pending: &mut Option<u16>, out: &mut String) {
2100        if pending.take().is_some() {
2101            out.push('\u{FFFD}');
2102        }
2103    }
2104
2105    /// Emit one decoded Unicode escape while pairing UTF-16 surrogate code
2106    /// units across both `\\uXXXX` and `\\u{...}` spellings.
2107    fn emit_unicode_escape(&self, cp: u32, pending: &mut Option<u16>, out: &mut String) {
2108        if (0xD800..=0xDBFF).contains(&cp) {
2109            self.flush_surrogate(pending, out);
2110            *pending = Some(cp as u16);
2111        } else if (0xDC00..=0xDFFF).contains(&cp) {
2112            if let Some(high) = pending.take() {
2113                let scalar = 0x10000 + (((u32::from(high)) - 0xD800) << 10) + (cp - 0xDC00);
2114                out.push(char::from_u32(scalar).expect("paired surrogates form a Unicode scalar"));
2115            } else {
2116                out.push('\u{FFFD}');
2117            }
2118        } else {
2119            self.flush_surrogate(pending, out);
2120            out.push(char::from_u32(cp).expect("validated escape is a Unicode scalar"));
2121        }
2122    }
2123}
2124
2125// ---------------------------------------------------------------------------
2126// Base-prefixed integer literals.
2127//
2128// A `0x`, `0o` or `0b` literal is read as an EXACT integer and rounded to
2129// a double ONCE. The obvious fold -- `value = value * radix + digit` in
2130// `f64` -- rounds at every digit, and past the 53-bit exact integer range
2131// those roundings accumulate: `0Xa6f2f78f4f9bf44` came out as
2132// `43a4de5ef1e9f37e` where canonical TypeScript and the Go port both
2133// answer `43a4de5ef1e9f37f`, one unit in the last place low. That is
2134// silently altered data, not a formatting difference.
2135//
2136// TypeScript coerces the literal with unary `+`, whose StringNumericValue
2137// is the exact mathematical value of the digits rounded once, half to
2138// even; Go reads it through `big.Int` and `big.Float.Float64()`, which is
2139// the same rule. These reproduce it. Only the VALUE is affected: which
2140// literals are accepted, and the token they become, are settled by
2141// `match_number` before any of this runs.
2142//
2143// The decimal path needs none of it -- `str::parse::<f64>` is correctly
2144// rounded for a digit string of any length.
2145// ---------------------------------------------------------------------------
2146
2147/// `2^k` for a non-negative `k`, exactly, saturating to infinity above the
2148/// double range. A repeated multiply would round on the way up.
2149fn pow2(k: i64) -> f64 {
2150    debug_assert!(k >= 0, "only non-negative exponents arise here");
2151    if k > 1023 {
2152        f64::INFINITY
2153    } else {
2154        f64::from_bits(((k + 1023) as u64) << 52)
2155    }
2156}
2157
2158/// Folds the digits of a base-prefixed literal into the NEAREST double,
2159/// rounding half to even, without holding the digits.
2160///
2161/// `bits` is the width of one digit, so the base is a power of two: 1 for
2162/// binary, 3 for octal, 4 for hexadecimal. Those are the only bases
2163/// `match_number` reads, which is what lets a `u128` head plus a sticky
2164/// bit stand in for arbitrary-precision arithmetic.
2165///
2166/// Only three things about a literal can change the answer: the top
2167/// `128 / bits` significant digits, how many digits follow them, and
2168/// whether any of those is non-zero. This keeps exactly those, so the
2169/// space it costs does not grow with the literal, however long an
2170/// untrusted document makes one.
2171struct DigitFold {
2172    bits: u32,
2173    /// The significant digits packed so far, at most `head_len` of them.
2174    head: u128,
2175    /// How many significant digits have been pushed, head and tail alike.
2176    len: usize,
2177    /// Whether any digit past the head was non-zero.
2178    sticky: bool,
2179    /// Whether a non-zero digit has been seen. Leading zeros carry no
2180    /// value, and dropping them is what makes the head wider than the 54
2181    /// significant bits the rounding needs.
2182    started: bool,
2183}
2184
2185impl DigitFold {
2186    fn new(bits: u32) -> Self {
2187        debug_assert!(
2188            (1..=4).contains(&bits),
2189            "only the power-of-two bases the lexer reads"
2190        );
2191        DigitFold {
2192            bits,
2193            head: 0,
2194            len: 0,
2195            sticky: false,
2196            started: false,
2197        }
2198    }
2199
2200    /// A `u128` holds exactly this many digits of the base.
2201    fn head_len(&self) -> usize {
2202        (128 / self.bits) as usize
2203    }
2204
2205    fn push(&mut self, digit: u32) {
2206        if !self.started {
2207            if 0 == digit {
2208                return;
2209            }
2210            self.started = true;
2211        }
2212        if self.len < self.head_len() {
2213            self.head = (self.head << self.bits) | u128::from(digit);
2214        } else if 0 != digit {
2215            self.sticky = true;
2216        }
2217        self.len += 1;
2218    }
2219
2220    fn finish(&self) -> f64 {
2221        if !self.started {
2222            return 0.0;
2223        }
2224        let head_len = self.head_len();
2225        if self.len <= head_len {
2226            // A `u128` to `f64` cast rounds to nearest, ties to even, which
2227            // is the rule the canonical runtime follows.
2228            return self.head as f64;
2229        }
2230
2231        // Longer than a u128: the head holds the top `head_len` digits and
2232        // `sticky` remembers whether anything below them was set. Those two
2233        // are all the rounding can depend on. The leading digit is
2234        // non-zero, so the head is at least 121 bits wide in every base
2235        // here and `shift` is comfortably positive.
2236        let dropped = i64::from(self.bits) * (self.len - head_len) as i64;
2237        let shift = 128 - self.head.leading_zeros() - 53;
2238
2239        let mut mantissa = (self.head >> shift) as u64;
2240        let half = (self.head >> (shift - 1)) & 1 == 1;
2241        let sticky = self.head & ((1u128 << (shift - 1)) - 1) != 0 || self.sticky;
2242        if half && (sticky || mantissa & 1 == 1) {
2243            // At most 2^53, which is still an exact double.
2244            mantissa += 1;
2245        }
2246        // The mantissa carries at most 53 significant bits, so the scaling
2247        // is exact inside the double range and overflows to infinity
2248        // outside it.
2249        mantissa as f64 * pow2(dropped + i64::from(shift))
2250    }
2251}