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}