Skip to main content

antlr4_runtime/
lexer.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3use std::cell::{RefCell, RefMut};
4use std::collections::{BTreeSet, HashMap, VecDeque};
5use std::hash::BuildHasherDefault;
6use std::mem::size_of;
7use std::ops::Range;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use crate::atn::LexerAtn;
12use crate::char_stream::{CharStream, TextInterval};
13use crate::int_stream::EOF;
14use crate::prediction::{
15    ContextArena, ContextId, EMPTY_CONTEXT, PredictionFxHasher, PredictionWorkspace,
16};
17use crate::recognizer::{Recognizer, RecognizerData};
18use crate::semir::MemberEnv;
19use crate::token::{
20    DEFAULT_CHANNEL, INVALID_TOKEN_TYPE, TokenId, TokenSink, TokenSourceError, TokenSpec,
21    TokenStoreError,
22};
23
24#[allow(clippy::disallowed_types)]
25type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
26
27pub const SKIP: i32 = -3;
28pub const MORE: i32 = -2;
29pub const DEFAULT_MODE: i32 = 0;
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub struct LexerMode(pub i32);
33
34/// Grammar-specific lexer action reached on the accepted ATN path.
35///
36/// ANTLR serializes embedded lexer actions as `(rule_index, action_index)`
37/// pairs. The runtime also records the input position where the action was
38/// reached so generated code can evaluate templates such as `Text()` at the
39/// same point as a generated ANTLR lexer, not only at the token end.
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub struct LexerCustomAction {
42    rule_index: i32,
43    action_index: i32,
44    position: usize,
45}
46
47impl LexerCustomAction {
48    /// Creates a custom lexer action event from serialized ATN metadata.
49    pub const fn new(rule_index: i32, action_index: i32, position: usize) -> Self {
50        Self {
51            rule_index,
52            action_index,
53            position,
54        }
55    }
56
57    /// Lexer rule index that owns the embedded action.
58    pub const fn rule_index(self) -> i32 {
59        self.rule_index
60    }
61
62    /// Per-rule action index assigned by ANTLR serialization.
63    pub const fn action_index(self) -> i32 {
64        self.action_index
65    }
66
67    /// Character-stream position at which the action transition was reached.
68    pub const fn position(self) -> usize {
69        self.position
70    }
71}
72
73/// Grammar-specific lexer predicate reached while exploring an ATN path.
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75pub struct LexerPredicate {
76    rule_index: usize,
77    pred_index: usize,
78    position: usize,
79}
80
81impl LexerPredicate {
82    /// Creates a lexer predicate event from serialized ATN metadata.
83    pub const fn new(rule_index: usize, pred_index: usize, position: usize) -> Self {
84        Self {
85            rule_index,
86            pred_index,
87            position,
88        }
89    }
90
91    /// Lexer rule index that owns the predicate transition.
92    pub const fn rule_index(self) -> usize {
93        self.rule_index
94    }
95
96    /// Per-rule predicate index assigned by ANTLR serialization.
97    pub const fn pred_index(self) -> usize {
98        self.pred_index
99    }
100
101    /// Character-stream position at which the predicate is evaluated.
102    pub const fn position(self) -> usize {
103        self.position
104    }
105}
106
107/// Lexer reference held by [`LexerSemCtx`]. A semantic *predicate* is evaluated
108/// speculatively and gets a shared borrow; a *custom action* runs on the
109/// committed path and gets a mutable borrow so a hook can change lexer state
110/// and pending token emission, matching the closure-based `custom_action` API.
111#[derive(Debug)]
112enum LexerRef<'a, I>
113where
114    I: CharStream,
115{
116    Shared(&'a BaseLexer<I>),
117    Mut(&'a mut BaseLexer<I>),
118}
119
120impl<I> LexerRef<'_, I>
121where
122    I: CharStream,
123{
124    const fn get(&self) -> &BaseLexer<I> {
125        match self {
126            LexerRef::Shared(lexer) => lexer,
127            LexerRef::Mut(lexer) => lexer,
128        }
129    }
130}
131
132/// Runtime view passed to lexer semantic hooks.
133#[derive(Debug)]
134pub struct LexerSemCtx<'a, I>
135where
136    I: CharStream,
137{
138    lexer: LexerRef<'a, I>,
139    rule_index: usize,
140    coordinate_index: usize,
141    position: usize,
142}
143
144impl<'a, I> LexerSemCtx<'a, I>
145where
146    I: CharStream,
147{
148    pub(crate) const fn new(
149        lexer: &'a BaseLexer<I>,
150        rule_index: usize,
151        coordinate_index: usize,
152        position: usize,
153    ) -> Self {
154        Self {
155            lexer: LexerRef::Shared(lexer),
156            rule_index,
157            coordinate_index,
158            position,
159        }
160    }
161
162    /// Builds a context with a mutable lexer borrow, for a custom-action hook
163    /// that may change lexer and pending-token state.
164    pub(crate) const fn new_mut(
165        lexer: &'a mut BaseLexer<I>,
166        rule_index: usize,
167        coordinate_index: usize,
168        position: usize,
169    ) -> Self {
170        Self {
171            lexer: LexerRef::Mut(lexer),
172            rule_index,
173            coordinate_index,
174            position,
175        }
176    }
177
178    /// Lexer rule index that owns the predicate/action coordinate.
179    #[must_use]
180    pub const fn rule_index(&self) -> usize {
181        self.rule_index
182    }
183
184    /// Predicate/action index inside the owning lexer rule.
185    #[must_use]
186    pub const fn coordinate_index(&self) -> usize {
187        self.coordinate_index
188    }
189
190    /// Absolute input position where the predicate/action transition fired.
191    #[must_use]
192    pub const fn position(&self) -> usize {
193        self.position
194    }
195
196    /// Lexer mode at this coordinate.
197    #[must_use]
198    pub fn mode(&self) -> i32 {
199        self.lexer.get().mode()
200    }
201
202    /// Current source column.
203    #[must_use]
204    pub const fn column(&self) -> usize {
205        self.lexer.get().column()
206    }
207
208    /// Source column at [`Self::position`].
209    #[must_use]
210    pub fn position_column(&self) -> usize {
211        self.lexer.get().column_at(self.position)
212    }
213
214    /// Column captured at the current token start.
215    #[must_use]
216    pub const fn token_start_column(&self) -> usize {
217        self.lexer.get().token_start_column()
218    }
219
220    /// Text matched from token start to this coordinate.
221    #[must_use]
222    pub fn text_so_far(&self) -> String {
223        self.lexer.get().token_text_until(self.position)
224    }
225
226    /// Character at a one-based lookahead/lookbehind offset.
227    ///
228    /// Predicates read relative to their speculative ATN coordinate. Actions
229    /// read relative to the committed input cursor, including characters
230    /// consumed by an earlier action.
231    pub fn la(&mut self, offset: isize) -> i32 {
232        match &mut self.lexer {
233            LexerRef::Shared(lexer) => lexer.lookahead_at(self.position, offset),
234            LexerRef::Mut(lexer) => lexer.input_mut().la(offset),
235        }
236    }
237
238    /// Absolute source index where the current token begins.
239    #[must_use]
240    pub const fn token_start(&self) -> usize {
241        self.lexer.get().token_start()
242    }
243
244    /// Pending type of the token being matched.
245    #[must_use]
246    pub const fn token_type(&self) -> i32 {
247        self.lexer.get().token_type()
248    }
249
250    /// Pending channel of the token being matched.
251    #[must_use]
252    pub const fn channel(&self) -> i32 {
253        self.lexer.get().channel()
254    }
255
256    /// Sets the pending emitted token type. Action context only; see
257    /// [`Self::set_mode`] for the return value.
258    pub const fn set_type(&mut self, token_type: i32) -> bool {
259        match &mut self.lexer {
260            LexerRef::Mut(lexer) => {
261                lexer.set_type(token_type);
262                true
263            }
264            LexerRef::Shared(_) => false,
265        }
266    }
267
268    /// Sets the pending emitted token channel. Action context only; see
269    /// [`Self::set_mode`] for the return value.
270    pub const fn set_channel(&mut self, channel: i32) -> bool {
271        match &mut self.lexer {
272            LexerRef::Mut(lexer) => {
273                lexer.set_channel(channel);
274                true
275            }
276            LexerRef::Shared(_) => false,
277        }
278    }
279
280    /// Consumes one input character and updates source position tracking.
281    /// Action context only; returns whether the operation was available.
282    pub fn consume(&mut self) -> bool {
283        match &mut self.lexer {
284            LexerRef::Mut(lexer) => {
285                lexer.consume_char();
286                true
287            }
288            LexerRef::Shared(_) => false,
289        }
290    }
291
292    /// Marks the current match as skipped. Action context only.
293    pub const fn skip(&mut self) -> bool {
294        self.set_type(SKIP)
295    }
296
297    /// Extends the current token with another lexer-rule match. Action context
298    /// only.
299    pub const fn more(&mut self) -> bool {
300        self.set_type(MORE)
301    }
302
303    /// Repositions the committed accept cursor. Action context only.
304    pub fn reset_accept_position(&mut self, index: usize) -> bool {
305        match &mut self.lexer {
306            LexerRef::Mut(lexer) => {
307                lexer.reset_accept_position(index);
308                true
309            }
310            LexerRef::Shared(_) => false,
311        }
312    }
313
314    /// Moves the current token start forward within the committed match.
315    ///
316    /// This is used after queueing a prefix token so automatic emission covers
317    /// only the remaining suffix. Returns `false` for predicate contexts or an
318    /// index outside the current token span.
319    pub fn set_token_start(&mut self, index: usize) -> bool {
320        match &mut self.lexer {
321            LexerRef::Mut(lexer) => lexer.set_token_start(index),
322            LexerRef::Shared(_) => false,
323        }
324    }
325
326    /// Queues an additional token on the current channel.
327    ///
328    /// The queued token spans the current token start through `stop`
329    /// (inclusive) and is returned before the match's automatically emitted
330    /// token. Action context only.
331    pub fn enqueue_token(&mut self, token_type: i32, stop: usize) -> bool {
332        let channel = self.channel();
333        self.enqueue_token_with_channel(token_type, channel, stop)
334    }
335
336    /// Queues an additional token on an explicit channel. See
337    /// [`Self::enqueue_token`].
338    pub fn enqueue_token_with_channel(
339        &mut self,
340        token_type: i32,
341        channel: i32,
342        stop: usize,
343    ) -> bool {
344        match &mut self.lexer {
345            LexerRef::Mut(lexer) => {
346                lexer.enqueue_token(token_type, channel, stop, None);
347                true
348            }
349            LexerRef::Shared(_) => false,
350        }
351    }
352
353    /// Sets the current lexer mode. Available only from a custom-action hook
354    /// (the mutable-borrow context); a no-op with a warning path for the
355    /// speculative predicate context, where mutating lexer state is invalid.
356    ///
357    /// Returns `true` if the mutation was applied (action context), `false` if
358    /// it was ignored (predicate context).
359    pub fn set_mode(&mut self, mode: i32) -> bool {
360        match &mut self.lexer {
361            LexerRef::Mut(lexer) => {
362                lexer.set_mode(mode);
363                true
364            }
365            LexerRef::Shared(_) => false,
366        }
367    }
368
369    /// Pushes the current mode and switches to `mode`. Action context only; see
370    /// [`Self::set_mode`] for the return value.
371    pub fn push_mode(&mut self, mode: i32) -> bool {
372        match &mut self.lexer {
373            LexerRef::Mut(lexer) => {
374                lexer.push_mode(mode);
375                true
376            }
377            LexerRef::Shared(_) => false,
378        }
379    }
380
381    /// Pops the mode stack, restoring the previous mode. Action context only;
382    /// returns the popped mode (`None` if the stack was empty or this is a
383    /// predicate context).
384    pub fn pop_mode(&mut self) -> Option<i32> {
385        match &mut self.lexer {
386            LexerRef::Mut(lexer) => lexer.pop_mode(),
387            LexerRef::Shared(_) => None,
388        }
389    }
390
391    /// Reads a grammar-declared integer member slot; `None` when never written.
392    #[must_use]
393    pub fn member_int(&self, member: usize) -> Option<i64> {
394        self.lexer.get().members().scalar(member)
395    }
396
397    /// Reads the top of a grammar-declared stack member slot; `None` when the
398    /// stack is empty or was never pushed.
399    #[must_use]
400    pub fn member_stack_top(&self, member: usize) -> Option<i64> {
401        self.lexer.get().members().stack_top(member)
402    }
403
404    /// Depth of a grammar-declared stack member slot.
405    #[must_use]
406    pub fn member_stack_len(&self, member: usize) -> usize {
407        self.lexer.get().members().stack_len(member)
408    }
409
410    /// Writes a grammar-declared integer member slot. Action context only; see
411    /// [`Self::set_mode`] for the return value.
412    pub fn set_member_int(&mut self, member: usize, value: i64) -> bool {
413        self.with_members_mut(|members| members.set_scalar(member, value))
414            .is_some()
415    }
416
417    /// Adds to a grammar-declared integer member slot, returning the new value.
418    /// `None` in a predicate context, where mutation is invalid.
419    pub fn add_member_int(&mut self, member: usize, delta: i64) -> Option<i64> {
420        self.with_members_mut(|members| members.add_scalar(member, delta))
421    }
422
423    /// Pushes onto a grammar-declared stack member slot. Action context only;
424    /// see [`Self::set_mode`] for the return value.
425    pub fn push_member(&mut self, member: usize, value: i64) -> bool {
426        self.with_members_mut(|members| members.push_stack(member, value))
427            .is_some()
428    }
429
430    /// Pops a grammar-declared stack member slot, returning the removed value.
431    /// `None` when the stack is empty or this is a predicate context.
432    pub fn pop_member(&mut self, member: usize) -> Option<i64> {
433        self.with_members_mut(|members| members.pop_stack(member))
434            .flatten()
435    }
436
437    /// Runs `apply` against mutable member state, or returns `None` in a
438    /// predicate context where mutating lexer state is invalid.
439    fn with_members_mut<T>(&mut self, apply: impl FnOnce(&mut MemberEnv) -> T) -> Option<T> {
440        match &mut self.lexer {
441            LexerRef::Mut(lexer) => Some(apply(lexer.members_mut())),
442            LexerRef::Shared(_) => None,
443        }
444    }
445}
446
447/// Lexer predicate coordinate lowered into [`crate::semir::SemIr`].
448#[derive(Clone, Copy, Debug, Eq, PartialEq)]
449pub struct LexerSemanticPredicate {
450    /// Serialized lexer rule index that owns this predicate.
451    pub rule_index: usize,
452    /// Predicate index inside the owning rule.
453    pub pred_index: usize,
454    /// Root expression in the associated [`LexerSemantics::ir`] arena.
455    pub expr: crate::semir::ExprId,
456}
457
458/// Lexer action coordinate lowered into [`crate::semir::SemIr`].
459#[derive(Clone, Copy, Debug, Eq, PartialEq)]
460pub struct LexerSemanticAction {
461    /// Serialized lexer rule index that owns this action.
462    pub rule_index: usize,
463    /// Action index inside the owning rule.
464    pub action_index: usize,
465    /// Root statement in the associated [`LexerSemantics::ir`] arena.
466    pub stmt: crate::semir::StmtId,
467}
468
469/// Data-driven lexer semantic tables emitted by generated lexers.
470///
471/// The lexer analog of [`crate::ParserSemantics`]. Grammars whose
472/// `@lexer::members` state and inline actions/predicates the generator could
473/// lower need no hand-written hooks at all (issue #206).
474#[derive(Clone, Debug, Default, Eq, PartialEq)]
475pub struct LexerSemantics {
476    pub ir: crate::semir::SemIr,
477    pub predicates: Vec<LexerSemanticPredicate>,
478    pub actions: Vec<LexerSemanticAction>,
479}
480
481impl LexerSemantics {
482    /// Evaluates a lowered predicate coordinate, or `None` when this table has
483    /// no entry for it (the caller then falls back to hooks / policy).
484    pub fn eval_predicate<I>(&self, lexer: &BaseLexer<I>, predicate: LexerPredicate) -> Option<bool>
485    where
486        I: CharStream,
487    {
488        let entry = self.predicates.iter().find(|entry| {
489            entry.rule_index == predicate.rule_index() && entry.pred_index == predicate.pred_index()
490        })?;
491        let mut ctx = LexerSemIrCtx::new(lexer, predicate);
492        Some(crate::semir::eval_pred(&self.ir, entry.expr, &mut ctx))
493    }
494
495    /// Executes a lowered action coordinate, reporting whether this table
496    /// owned it.
497    pub fn exec_action<I>(&self, lexer: &mut BaseLexer<I>, action: LexerCustomAction) -> bool
498    where
499        I: CharStream,
500    {
501        let Ok(rule_index) = usize::try_from(action.rule_index()) else {
502            return false;
503        };
504        let Ok(action_index) = usize::try_from(action.action_index()) else {
505            return false;
506        };
507        let Some(entry) = self
508            .actions
509            .iter()
510            .find(|entry| entry.rule_index == rule_index && entry.action_index == action_index)
511        else {
512            return false;
513        };
514        let stmt = entry.stmt;
515        let mut ctx = LexerSemIrCtx::new_mut(lexer, action);
516        crate::semir::exec_stmt(&self.ir, stmt, &mut ctx);
517        true
518    }
519}
520
521/// `SemIR` evaluation adapter over a lexer, for grammar predicates and actions
522/// that the generator lowered into IR instead of a hook (issue #206).
523///
524/// Predicates get a shared borrow and evaluate at their speculative ATN
525/// coordinate, so lookahead and text-so-far are relative to `position`.
526/// Actions get a mutable borrow and run on the committed path, where mutating
527/// member state matches what a generated ANTLR lexer does in its own fields.
528#[derive(Debug)]
529pub struct LexerSemIrCtx<'a, I>
530where
531    I: CharStream,
532{
533    ctx: LexerSemCtx<'a, I>,
534}
535
536impl<'a, I> LexerSemIrCtx<'a, I>
537where
538    I: CharStream,
539{
540    /// Builds a predicate-evaluation adapter at a speculative ATN coordinate.
541    pub(crate) const fn new(lexer: &'a BaseLexer<I>, predicate: LexerPredicate) -> Self {
542        Self {
543            ctx: LexerSemCtx::new(
544                lexer,
545                predicate.rule_index(),
546                predicate.pred_index(),
547                predicate.position(),
548            ),
549        }
550    }
551
552    /// Builds an action-execution adapter on the committed path.
553    pub(crate) fn new_mut(lexer: &'a mut BaseLexer<I>, action: LexerCustomAction) -> Self {
554        let rule_index = usize::try_from(action.rule_index()).unwrap_or_default();
555        let action_index = usize::try_from(action.action_index()).unwrap_or_default();
556        Self {
557            ctx: LexerSemCtx::new_mut(lexer, rule_index, action_index, action.position()),
558        }
559    }
560
561    /// The underlying hook context, for callers that also dispatch hooks.
562    pub const fn ctx_mut(&mut self) -> &mut LexerSemCtx<'a, I> {
563        &mut self.ctx
564    }
565}
566
567impl<I> crate::semir::PredContext for LexerSemIrCtx<'_, I>
568where
569    I: CharStream,
570{
571    type TokenText<'a>
572        = String
573    where
574        Self: 'a;
575
576    fn la(&mut self, offset: isize) -> i64 {
577        i64::from(self.ctx.la(offset))
578    }
579
580    /// A lexer has no lookahead *token*; text predicates use
581    /// [`crate::semir::PExpr::TokenTextSoFar`] instead.
582    fn token_text(&mut self, _offset: isize) -> Option<Self::TokenText<'_>> {
583        None
584    }
585
586    fn token_index_adjacent(&mut self) -> bool {
587        false
588    }
589
590    fn ctx_rule_text(&self, _rule_index: usize) -> Option<String> {
591        None
592    }
593
594    fn member(&self, member: usize) -> Option<i64> {
595        Some(self.ctx.member_int(member).unwrap_or_default())
596    }
597
598    fn member_top(&self, member: usize) -> Option<i64> {
599        self.ctx.member_stack_top(member)
600    }
601
602    fn member_len(&self, member: usize) -> usize {
603        self.ctx.member_stack_len(member)
604    }
605
606    fn local_arg(&self) -> Option<i64> {
607        None
608    }
609
610    fn column(&self) -> Option<i64> {
611        Some(i64::try_from(self.ctx.position_column()).unwrap_or(i64::MAX))
612    }
613
614    fn token_start_column(&self) -> Option<i64> {
615        Some(i64::try_from(self.ctx.token_start_column()).unwrap_or(i64::MAX))
616    }
617
618    fn token_text_so_far(&self) -> Option<String> {
619        Some(self.ctx.text_so_far())
620    }
621
622    /// Hooks are dispatched by the caller, which owns the `SemanticHooks`
623    /// object; an unrouted hook node declines rather than guessing.
624    fn hook(&mut self, _hook: crate::semir::HookId) -> bool {
625        false
626    }
627}
628
629impl<I> crate::semir::ActContext for LexerSemIrCtx<'_, I>
630where
631    I: CharStream,
632{
633    fn set_member(&mut self, member: usize, value: i64) {
634        self.ctx.set_member_int(member, value);
635    }
636
637    fn push_member(&mut self, member: usize, value: i64) {
638        self.ctx.push_member(member, value);
639    }
640
641    fn pop_member(&mut self, member: usize) -> Option<i64> {
642        self.ctx.pop_member(member)
643    }
644
645    /// A lexer rule has no return fields; ignore rather than inventing state.
646    fn set_return(&mut self, _name: &str, _value: i64) {}
647
648    fn action_hook(&mut self, _hook: crate::semir::HookId) {}
649}
650
651/// Mutable lexer state exposed at lifecycle boundaries that have no ATN
652/// semantic coordinate.
653///
654/// The context is used before a token request starts matching, after an
655/// accepted path has applied its actions but before emission, and while a
656/// lexer is reset for reuse. [`Self::accept_position`] is present only at the
657/// post-accept boundary.
658#[derive(Debug)]
659pub struct LexerLifecycleCtx<'a, I>
660where
661    I: CharStream,
662{
663    lexer: &'a mut BaseLexer<I>,
664    accept_position: Option<usize>,
665}
666
667impl<'a, I> LexerLifecycleCtx<'a, I>
668where
669    I: CharStream,
670{
671    pub(crate) const fn new(lexer: &'a mut BaseLexer<I>, accept_position: Option<usize>) -> Self {
672        Self {
673            lexer,
674            accept_position,
675        }
676    }
677
678    /// Original input boundary selected by the accepted ATN path.
679    ///
680    /// A post-accept hook may move the committed cursor away from this
681    /// boundary with [`Self::reset_accept_position`].
682    #[must_use]
683    pub const fn accept_position(&self) -> Option<usize> {
684        self.accept_position
685    }
686
687    /// Current committed input position.
688    #[must_use]
689    pub fn input_position(&self) -> usize {
690        self.lexer.input().index()
691    }
692
693    /// Current lexer mode.
694    #[must_use]
695    pub const fn mode(&self) -> i32 {
696        self.lexer.mode
697    }
698
699    /// Current source line.
700    #[must_use]
701    pub const fn line(&self) -> usize {
702        self.lexer.line()
703    }
704
705    /// Current source column.
706    #[must_use]
707    pub const fn column(&self) -> usize {
708        self.lexer.column()
709    }
710
711    /// Absolute source index where the current token begins.
712    #[must_use]
713    pub const fn token_start(&self) -> usize {
714        self.lexer.token_start()
715    }
716
717    /// Source line captured at the current token start.
718    #[must_use]
719    pub const fn token_start_line(&self) -> usize {
720        self.lexer.token_start_line()
721    }
722
723    /// Source column captured at the current token start.
724    #[must_use]
725    pub const fn token_start_column(&self) -> usize {
726        self.lexer.token_start_column()
727    }
728
729    /// Pending type of the token being matched.
730    #[must_use]
731    pub const fn token_type(&self) -> i32 {
732        self.lexer.token_type()
733    }
734
735    /// Pending channel of the token being matched.
736    #[must_use]
737    pub const fn channel(&self) -> i32 {
738        self.lexer.channel()
739    }
740
741    /// Number of tokens waiting to be returned before another ATN match.
742    #[must_use]
743    pub fn pending_token_count(&self) -> usize {
744        self.lexer.pending_tokens.len()
745    }
746
747    /// Text from the current token start through the committed input cursor.
748    #[must_use]
749    pub fn token_text(&self) -> String {
750        self.lexer.token_text()
751    }
752
753    /// Text selected by the original accepted ATN path.
754    ///
755    /// Returns `None` outside the post-accept callback.
756    #[must_use]
757    pub fn accepted_text(&self) -> Option<String> {
758        self.accept_position
759            .map(|position| self.lexer.token_text_until(position))
760    }
761
762    /// Character at a one-based lookahead/lookbehind offset from the
763    /// committed input cursor.
764    pub fn la(&mut self, offset: isize) -> i32 {
765        self.lexer.la(offset)
766    }
767
768    /// Consumes one input character and updates source position tracking.
769    pub fn consume(&mut self) {
770        self.lexer.consume_char();
771    }
772
773    /// Overrides the pending emitted token type.
774    pub const fn set_type(&mut self, token_type: i32) {
775        self.lexer.set_type(token_type);
776    }
777
778    /// Overrides the pending emitted token channel.
779    pub const fn set_channel(&mut self, channel: i32) {
780        self.lexer.set_channel(channel);
781    }
782
783    /// Marks the current match as skipped.
784    pub const fn skip(&mut self) {
785        self.lexer.skip();
786    }
787
788    /// Extends the current token with another lexer-rule match.
789    pub const fn more(&mut self) {
790        self.lexer.more();
791    }
792
793    /// Repositions the committed accept cursor.
794    pub fn reset_accept_position(&mut self, index: usize) {
795        self.lexer.reset_accept_position(index);
796    }
797
798    /// Moves the current token start forward within the committed match.
799    pub fn set_token_start(&mut self, index: usize) -> bool {
800        self.lexer.set_token_start(index)
801    }
802
803    /// Queues an additional token on the current channel.
804    pub fn enqueue_token(&mut self, token_type: i32, stop: usize) {
805        self.enqueue_token_with_channel(token_type, self.channel(), stop);
806    }
807
808    /// Queues an additional token on an explicit channel.
809    pub fn enqueue_token_with_channel(&mut self, token_type: i32, channel: i32, stop: usize) {
810        self.lexer.enqueue_token(token_type, channel, stop, None);
811    }
812
813    /// Sets the current lexer mode.
814    pub fn set_mode(&mut self, mode: i32) {
815        self.lexer.set_mode(mode);
816    }
817
818    /// Pushes the current mode and switches to `mode`.
819    pub fn push_mode(&mut self, mode: i32) {
820        self.lexer.push_mode(mode);
821    }
822
823    /// Pops the mode stack, restoring the previous mode.
824    pub fn pop_mode(&mut self) -> Option<i32> {
825        self.lexer.pop_mode()
826    }
827}
828
829pub trait Lexer: Recognizer {
830    fn mode(&self) -> i32;
831    fn set_mode(&mut self, mode: i32);
832    fn push_mode(&mut self, mode: i32);
833    fn pop_mode(&mut self) -> Option<i32>;
834}
835
836#[derive(Clone, Debug)]
837pub struct BaseLexer<I> {
838    input: I,
839    data: RecognizerData,
840    has_source_text: bool,
841    mode: i32,
842    mode_stack: Vec<i32>,
843    token_type: i32,
844    channel: i32,
845    token_start: usize,
846    token_start_line: usize,
847    token_start_column: usize,
848    line: usize,
849    column: usize,
850    hit_eof: bool,
851    force_interpreted: bool,
852    errors: RefCell<Vec<TokenSourceError>>,
853    semantic_error_coordinates: RefCell<BTreeSet<(u8, usize, usize, usize)>>,
854    pending_tokens: VecDeque<TokenSpec>,
855    /// Grammar-declared `@lexer::members` state (issue #206).
856    ///
857    /// Unlike the parser's member environment, this is not path-local and needs
858    /// no speculative snapshot: lexer actions run only after an accept position
859    /// is committed (see `atn::lexer`'s custom-action dispatch), which is where
860    /// a generated ANTLR lexer mutates its own fields too. Predicates read it
861    /// through a shared borrow, and predicate-bearing DFA states are always
862    /// re-simulated rather than cached, so a read never observes a stale accept.
863    members: MemberEnv,
864    /// Declared initial scalar values, so `reset()` restores the state a fresh
865    /// lexer had rather than an all-zero one. Empty for grammars whose members
866    /// have no initializer (or none at all).
867    member_inits: Vec<(usize, i64)>,
868    dfa_cache: Rc<RefCell<LexerDfaCache>>,
869}
870
871/// Learned lexer DFA: the input-independent state/transition tables built up
872/// by ATN simulation.
873///
874/// Semantic-predicate-dependent states are stored flagged and every consumer
875/// re-simulates them instead of trusting their cached data, so the cache can
876/// be shared across lexer instances (and inputs) for the same ATN — see
877/// [`BaseLexer::with_shared_dfa`].
878#[derive(Debug, Default)]
879struct LexerDfaCache {
880    prediction: LexerPredictionStore,
881    state_numbers: FxHashMap<LexerDfaKey, usize>,
882    accept_predictions: FxHashMap<usize, i32>,
883    /// `showDFA` edge trace. Lives with the tables it describes, so a lexer
884    /// on a shared cache reports the accumulated DFA — matching the reference
885    /// runtimes, whose static shared DFA is what `showDFA` prints.
886    edges: BTreeSet<LexerDfaEdge>,
887    /// Dense by DFA state number (states are numbered contiguously from 0).
888    cached_states: Vec<Option<Rc<LexerDfaCachedState>>>,
889    /// Per-source-state edge rows for symbols in `0..DENSE_EDGE_SYMBOLS`,
890    /// allocated lazily on the first cached transition out of a state. The
891    /// per-character lookup is then one bounds check and an array index —
892    /// the same scheme as Go's `edges[t-MinDFAEdge]`.
893    dense_edges: Vec<Option<Box<DenseEdgeRow>>>,
894    /// Transitions on symbols outside the dense range (supplementary planes).
895    sparse_edges: FxHashMap<(usize, i32), LexerDfaCachedTransition>,
896    mode_starts: FxHashMap<i32, usize>,
897}
898
899/// Storage measurements for one learned lexer DFA cache.
900#[non_exhaustive]
901#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
902pub struct LexerDfaStats {
903    pub states: usize,
904    pub cached_states: usize,
905    pub transitions: usize,
906    pub max_configs_per_state: usize,
907    pub contexts: usize,
908    pub action_trace_sequences: usize,
909    pub action_traces: usize,
910    /// Retained element storage for action payloads in learned cache entries.
911    pub action_trace_bytes: usize,
912}
913
914/// Canonical caller contexts paired with the learned lexer DFA that stores
915/// their IDs.
916#[derive(Debug, Default)]
917pub(crate) struct LexerPredictionStore {
918    pub(crate) contexts: LexerContextArena,
919    pub(crate) workspace: PredictionWorkspace,
920}
921
922#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
923pub(crate) struct LexerActionTrace {
924    pub(crate) action_index: usize,
925    pub(crate) position: usize,
926    /// Owning lexer rule, used to suppress actions from nested token-rule calls.
927    pub(crate) rule_index: usize,
928}
929
930/// Thin clone-on-write handle; `Arc<[T]>` would widen every action-free config
931/// and cannot append in place when the payload is uniquely owned.
932#[allow(clippy::rc_buffer)]
933#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
934pub(crate) struct LexerActionTraceList(Option<Arc<Vec<LexerActionTrace>>>);
935
936impl LexerActionTraceList {
937    pub(crate) fn from_vec(traces: Vec<LexerActionTrace>) -> Self {
938        if traces.is_empty() {
939            Self::default()
940        } else {
941            Self(Some(Arc::new(traces)))
942        }
943    }
944
945    pub(crate) fn as_slice(&self) -> &[LexerActionTrace] {
946        self.0.as_deref().map_or(&[], Vec::as_slice)
947    }
948
949    pub(crate) fn to_vec(&self) -> Vec<LexerActionTrace> {
950        self.as_slice().to_vec()
951    }
952
953    pub(crate) fn make_mut(&mut self) -> &mut Vec<LexerActionTrace> {
954        Arc::make_mut(self.0.get_or_insert_with(|| Arc::new(Vec::new())))
955    }
956
957    pub(crate) fn retain(&mut self, mut keep: impl FnMut(&LexerActionTrace) -> bool) {
958        let Some(traces) = self.0.as_mut() else {
959            return;
960        };
961        Arc::make_mut(traces).retain(|trace| keep(trace));
962        if traces.is_empty() {
963            self.0 = None;
964        }
965    }
966
967    pub(crate) fn len(&self) -> usize {
968        self.as_slice().len()
969    }
970}
971
972/// Store-local identity for one ordered lexer caller-context node.
973#[repr(transparent)]
974#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
975pub(crate) struct LexerContextId(u32);
976
977pub(crate) const EMPTY_LEXER_CONTEXT: LexerContextId = LexerContextId(0);
978
979/// One node in an ordered graph of lexer caller stacks.
980///
981/// `Union` preserves ATN traversal priority. The paired unordered prediction
982/// context detects when a later union adds no stack paths, which keeps cyclic
983/// lexer closures finite without flattening their priority order.
984#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
985pub(crate) enum LexerContextNode {
986    Empty,
987    Singleton {
988        parent: LexerContextId,
989        return_state: usize,
990    },
991    Union {
992        left: LexerContextId,
993        right: LexerContextId,
994    },
995}
996
997#[derive(Clone, Copy, Debug)]
998struct LexerContextRecord {
999    node: LexerContextNode,
1000    path_set: ContextId,
1001}
1002
1003/// Canonical ordered caller-context DAG for one learned or compiled lexer DFA.
1004#[derive(Debug)]
1005pub(crate) struct LexerContextArena {
1006    records: Vec<LexerContextRecord>,
1007    ids: FxHashMap<LexerContextNode, LexerContextId>,
1008    path_sets: ContextArena,
1009}
1010
1011impl LexerContextArena {
1012    pub(crate) fn new() -> Self {
1013        let mut ids = FxHashMap::default();
1014        ids.insert(LexerContextNode::Empty, EMPTY_LEXER_CONTEXT);
1015        Self {
1016            records: vec![LexerContextRecord {
1017                node: LexerContextNode::Empty,
1018                path_set: EMPTY_CONTEXT,
1019            }],
1020            ids,
1021            path_sets: ContextArena::new(),
1022        }
1023    }
1024
1025    pub(crate) fn singleton(
1026        &mut self,
1027        parent: LexerContextId,
1028        return_state: usize,
1029    ) -> LexerContextId {
1030        self.assert_valid(parent);
1031        let node = LexerContextNode::Singleton {
1032            parent,
1033            return_state,
1034        };
1035        if let Some(&context) = self.ids.get(&node) {
1036            return context;
1037        }
1038        let path_set = self
1039            .path_sets
1040            .singleton(self.record(parent).path_set, return_state);
1041        self.intern(node, path_set)
1042    }
1043
1044    pub(crate) fn merge(
1045        &mut self,
1046        left: LexerContextId,
1047        right: LexerContextId,
1048        workspace: &mut PredictionWorkspace,
1049    ) -> LexerContextId {
1050        self.assert_valid(left);
1051        self.assert_valid(right);
1052        if left == right {
1053            return left;
1054        }
1055        let left_set = self.record(left).path_set;
1056        let right_set = self.record(right).path_set;
1057        let path_set = self.path_sets.merge(left_set, right_set, false, workspace);
1058        if path_set == left_set {
1059            return left;
1060        }
1061        let node = LexerContextNode::Union { left, right };
1062        if let Some(&context) = self.ids.get(&node) {
1063            return context;
1064        }
1065        self.intern(node, path_set)
1066    }
1067
1068    pub(crate) fn node(&self, context: LexerContextId) -> LexerContextNode {
1069        self.record(context).node
1070    }
1071
1072    pub(crate) fn has_empty_path(&self, context: LexerContextId) -> bool {
1073        self.path_sets.has_empty_path(self.record(context).path_set)
1074    }
1075
1076    pub(crate) const fn len(&self) -> usize {
1077        self.records.len()
1078    }
1079
1080    fn intern(&mut self, node: LexerContextNode, path_set: ContextId) -> LexerContextId {
1081        let context = LexerContextId(
1082            u32::try_from(self.records.len()).expect("lexer context arena must fit in u32"),
1083        );
1084        self.records.push(LexerContextRecord { node, path_set });
1085        self.ids.insert(node, context);
1086        context
1087    }
1088
1089    fn record(&self, context: LexerContextId) -> &LexerContextRecord {
1090        self.assert_valid(context);
1091        &self.records[usize::try_from(context.0).expect("u32 lexer context ID fits in usize")]
1092    }
1093
1094    fn assert_valid(&self, context: LexerContextId) {
1095        assert!(
1096            usize::try_from(context.0).is_ok_and(|index| index < self.records.len()),
1097            "lexer context ID does not belong to this store"
1098        );
1099    }
1100}
1101
1102impl Default for LexerContextArena {
1103    fn default() -> Self {
1104        Self::new()
1105    }
1106}
1107
1108/// Dense-row width: ASCII, matching the reference runtimes' DFA edge arrays.
1109const DENSE_EDGE_SYMBOLS: usize = 128;
1110
1111type DenseEdgeRow = [LexerDfaCachedTransition; DENSE_EDGE_SYMBOLS];
1112
1113/// Sentinel for an empty dense-row slot; no real transition targets it
1114/// because DFA state numbers are assigned contiguously from 0.
1115const EMPTY_DENSE_EDGE: LexerDfaCachedTransition = LexerDfaCachedTransition {
1116    target_state: usize::MAX,
1117    position_delta: 0,
1118};
1119
1120thread_local! {
1121    /// Learned lexer DFAs shared across lexer instances, keyed by a generated
1122    /// lexer's static ATN identity (mirrors the parser's shared decision DFAs).
1123    static SHARED_LEXER_DFA_CACHES: RefCell<HashMap<usize, Rc<RefCell<LexerDfaCache>>>> =
1124        RefCell::new(HashMap::new());
1125}
1126
1127/// Position-independent lexer ATN config sequence used for observed DFA traces.
1128///
1129/// Configuration order is part of the identity because it carries serialized
1130/// ATN priority through non-greedy decisions.
1131#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1132pub(crate) struct LexerDfaKey {
1133    configs: Vec<LexerDfaConfigKey>,
1134}
1135
1136impl LexerDfaKey {
1137    pub(crate) const fn new(configs: Vec<LexerDfaConfigKey>) -> Self {
1138        Self { configs }
1139    }
1140}
1141
1142/// One lexer ATN config identity with the absolute input position removed.
1143#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1144pub(crate) struct LexerDfaConfigKey {
1145    pub(crate) state: usize,
1146    pub(crate) alt_rule_index: Option<usize>,
1147    pub(crate) consumed_eof: bool,
1148    pub(crate) passed_non_greedy: bool,
1149    pub(crate) context: LexerContextId,
1150    pub(crate) actions: Vec<LexerDfaActionKey>,
1151}
1152
1153#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1154pub(crate) struct LexerDfaActionKey {
1155    pub(crate) action_index: usize,
1156    pub(crate) position_delta: usize,
1157    pub(crate) rule_index: usize,
1158}
1159
1160impl LexerDfaConfigKey {
1161    pub(crate) const fn new(
1162        state: usize,
1163        alt_rule_index: Option<usize>,
1164        consumed_eof: bool,
1165        passed_non_greedy: bool,
1166        context: LexerContextId,
1167        actions: Vec<LexerDfaActionKey>,
1168    ) -> Self {
1169        Self {
1170            state,
1171            alt_rule_index,
1172            consumed_eof,
1173            passed_non_greedy,
1174            context,
1175            actions,
1176        }
1177    }
1178}
1179
1180#[derive(Clone, Copy, Debug)]
1181pub(crate) struct LexerDfaCachedTransition {
1182    pub(crate) target_state: usize,
1183    pub(crate) position_delta: usize,
1184}
1185
1186#[derive(Clone, Debug)]
1187pub(crate) struct LexerDfaCachedAccept {
1188    pub(crate) position_delta: usize,
1189    pub(crate) rule_index: usize,
1190    pub(crate) consumed_eof: bool,
1191    pub(crate) actions: Vec<LexerDfaActionKey>,
1192}
1193
1194#[derive(Clone, Debug)]
1195pub(crate) struct LexerDfaCachedState {
1196    pub(crate) has_semantic_context: bool,
1197    pub(crate) configs: Vec<LexerDfaConfigKey>,
1198    pub(crate) accept: Option<LexerDfaCachedAccept>,
1199}
1200
1201/// One printable lexer DFA edge keyed so repeated matches keep deterministic
1202/// output order.
1203#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
1204struct LexerDfaEdge {
1205    from: usize,
1206    symbol: i32,
1207    to: usize,
1208}
1209
1210impl<I> BaseLexer<I>
1211where
1212    I: CharStream,
1213{
1214    pub fn new(input: I, data: RecognizerData) -> Self {
1215        let has_source_text = input.source_text().is_some();
1216        Self {
1217            input,
1218            data,
1219            has_source_text,
1220            mode: DEFAULT_MODE,
1221            mode_stack: Vec::new(),
1222            token_type: INVALID_TOKEN_TYPE,
1223            channel: DEFAULT_CHANNEL,
1224            token_start: 0,
1225            token_start_line: 1,
1226            token_start_column: 0,
1227            line: 1,
1228            column: 0,
1229            hit_eof: false,
1230            force_interpreted: false,
1231            errors: RefCell::new(Vec::new()),
1232            semantic_error_coordinates: RefCell::new(BTreeSet::new()),
1233            pending_tokens: VecDeque::new(),
1234            members: MemberEnv::new(),
1235            member_inits: Vec::new(),
1236            dfa_cache: Rc::new(RefCell::new(LexerDfaCache::default())),
1237        }
1238    }
1239
1240    /// Seeds grammar-declared initial member values (issue #206).
1241    ///
1242    /// Generated lexers call this at construction for a grammar whose
1243    /// `@lexer::members` declares an initializer (`private bool verbatium =
1244    /// true;`). The values are retained so every [`Self::reset`] and
1245    /// [`Self::set_input_stream`] restores them instead of zeroing the slots.
1246    #[must_use]
1247    pub fn with_initial_members(mut self, initial: impl IntoIterator<Item = (usize, i64)>) -> Self {
1248        self.member_inits = initial.into_iter().collect();
1249        self.members = MemberEnv::with_initial_scalars(self.member_inits.iter().copied());
1250        self
1251    }
1252
1253    /// Resets runtime-owned lexer state so this instance can consume its input
1254    /// again from the beginning.
1255    ///
1256    /// Learned DFA tables and configuration such as forced interpretation are
1257    /// retained. Token-production state, diagnostics, pending tokens, modes,
1258    /// grammar-declared member state, and source position are cleared.
1259    pub fn reset(&mut self) {
1260        self.input.seek(0);
1261        self.mode = DEFAULT_MODE;
1262        self.mode_stack.clear();
1263        self.token_type = INVALID_TOKEN_TYPE;
1264        self.channel = DEFAULT_CHANNEL;
1265        self.token_start = 0;
1266        self.token_start_line = 1;
1267        self.token_start_column = 0;
1268        self.line = 1;
1269        self.column = 0;
1270        self.hit_eof = false;
1271        self.errors.get_mut().clear();
1272        self.semantic_error_coordinates.get_mut().clear();
1273        self.pending_tokens.clear();
1274        // A retained interpolation depth or mode-nesting stack would silently
1275        // mis-lex the next input, so member state is per-input, not per-lexer.
1276        // It resets to the grammar's *declared* initial values, not to zero —
1277        // a `bool enabled = true` member must be true again for the next input.
1278        self.members
1279            .reset_to_initial(self.member_inits.iter().copied());
1280    }
1281
1282    /// Replaces the character stream and fully resets lexer state for reuse.
1283    ///
1284    /// Learned DFA tables and configuration such as forced interpretation are
1285    /// retained. The new stream is always rewound to its beginning.
1286    pub fn set_input_stream(&mut self, input: I) {
1287        self.input = input;
1288        self.has_source_text = self.input.source_text().is_some();
1289        self.reset();
1290    }
1291
1292    /// Switches this lexer to the thread-shared learned DFA for `atn`.
1293    ///
1294    /// Generated lexers create a fresh instance per parse; without sharing,
1295    /// every instance relearns the same DFA through ATN simulation. The shared
1296    /// cache is keyed by the generated lexer's `&'static LexerAtn` identity and
1297    /// holds only input-independent data, so it stays valid across inputs.
1298    /// The `showDFA` edge trace lives in the cache too, so it reports the
1299    /// accumulated DFA — the same view the reference runtimes print from
1300    /// their static shared DFA.
1301    #[must_use]
1302    pub fn with_shared_dfa(mut self, atn: &'static LexerAtn) -> Self {
1303        let ptr: *const LexerAtn = atn;
1304        let key = ptr as usize;
1305        self.dfa_cache = SHARED_LEXER_DFA_CACHES
1306            .with(|caches| Rc::clone(caches.borrow_mut().entry(key).or_insert_with(Rc::default)));
1307        self
1308    }
1309
1310    /// Clears the learned lexer DFA shared by recognizers for this grammar.
1311    ///
1312    /// Ahead-of-time compiled DFA tables are immutable generated data and are
1313    /// unaffected. Any path that falls back to ATN interpretation relearns its
1314    /// dynamic DFA from an empty cache after this call.
1315    pub fn clear_dfa(&self) {
1316        let mut cache = self.dfa_cache.borrow_mut();
1317        // In-flight predicate evaluation may clear the DFA while its configs
1318        // still hold store-local context IDs.
1319        let prediction = std::mem::take(&mut cache.prediction);
1320        *cache = LexerDfaCache {
1321            prediction,
1322            ..LexerDfaCache::default()
1323        };
1324    }
1325
1326    pub const fn input(&self) -> &I {
1327        &self.input
1328    }
1329
1330    pub const fn input_mut(&mut self) -> &mut I {
1331        &mut self.input
1332    }
1333
1334    /// Captures the input index and source position for the token currently
1335    /// being matched.
1336    pub fn begin_token(&mut self) {
1337        self.semantic_error_coordinates.get_mut().clear();
1338        self.token_type = INVALID_TOKEN_TYPE;
1339        self.channel = DEFAULT_CHANNEL;
1340        self.token_start = self.input.index();
1341        self.token_start_line = self.line;
1342        self.token_start_column = self.column;
1343    }
1344
1345    /// Returns the absolute character index where the current token began.
1346    pub const fn token_start(&self) -> usize {
1347        self.token_start
1348    }
1349
1350    /// Returns the source line captured at the start of the current token.
1351    pub const fn token_start_line(&self) -> usize {
1352        self.token_start_line
1353    }
1354
1355    /// Returns the source column captured at the start of the current token.
1356    pub const fn token_start_column(&self) -> usize {
1357        self.token_start_column
1358    }
1359
1360    /// Returns the pending type of the token being matched.
1361    pub const fn token_type(&self) -> i32 {
1362        self.token_type
1363    }
1364
1365    /// Overrides the pending type of the token being matched.
1366    pub const fn set_type(&mut self, token_type: i32) {
1367        self.token_type = token_type;
1368    }
1369
1370    /// Returns the pending channel of the token being matched.
1371    pub const fn channel(&self) -> i32 {
1372        self.channel
1373    }
1374
1375    /// Overrides the pending channel of the token being matched.
1376    pub const fn set_channel(&mut self, channel: i32) {
1377        self.channel = channel;
1378    }
1379
1380    /// Marks the current match as skipped.
1381    pub const fn skip(&mut self) {
1382        self.set_type(SKIP);
1383    }
1384
1385    /// Extends the current token with another lexer-rule match.
1386    pub const fn more(&mut self) {
1387        self.set_type(MORE);
1388    }
1389
1390    /// Reads a character at a one-based lookahead/lookbehind offset from the
1391    /// committed input cursor without moving it.
1392    pub fn la(&mut self, offset: isize) -> i32 {
1393        self.input.la(offset)
1394    }
1395
1396    fn lookahead_at(&self, position: usize, offset: isize) -> i32 {
1397        if offset == 0 {
1398            return 0;
1399        }
1400        let absolute = if offset > 0 {
1401            position.checked_add((offset - 1).cast_unsigned())
1402        } else {
1403            offset
1404                .checked_neg()
1405                .and_then(|distance| usize::try_from(distance).ok())
1406                .and_then(|distance| position.checked_sub(distance))
1407        };
1408        let Some(index) = absolute.filter(|index| *index < self.input.size()) else {
1409            return EOF;
1410        };
1411        if let Some(symbol) = self.input.symbol_at(index) {
1412            return symbol;
1413        }
1414        self.input
1415            .text(TextInterval::new(index, index))
1416            .chars()
1417            .next()
1418            .map_or(EOF, |ch| u32::from(ch).cast_signed())
1419    }
1420
1421    /// Consumes one character from the input stream and updates lexer line and
1422    /// column counters.
1423    ///
1424    /// The input stream is indexed by Unicode scalar values. Newline handling
1425    /// follows ANTLR's default convention of incrementing the line and resetting
1426    /// the column after `\n`.
1427    pub fn consume_char(&mut self) {
1428        let la = self.input.la(1);
1429        if la == EOF {
1430            return;
1431        }
1432        self.input.consume();
1433        if char::from_u32(la.cast_unsigned()) == Some('\n') {
1434            self.line += 1;
1435            self.column = 0;
1436        } else {
1437            self.column += 1;
1438        }
1439    }
1440
1441    /// Commits a predicted input span while keeping the current line and column
1442    /// as the coordinates at `start`.
1443    pub(crate) fn commit_position(&mut self, start: usize, target: usize) {
1444        self.reposition_from(start, self.line, self.column, target);
1445    }
1446
1447    fn reposition_from(&mut self, start: usize, line: usize, column: usize, target: usize) {
1448        let start = start.min(self.input.size());
1449        let target = target.max(start).min(self.input.size());
1450        if let Some(summary) = self.input.position_summary(start, target) {
1451            self.input.seek(target);
1452            (self.line, self.column) = summary.apply(line, column);
1453            #[cfg(feature = "perf-counters")]
1454            crate::perf::record_lexer_bulk_commit(target - start);
1455            return;
1456        }
1457
1458        self.input.seek(start);
1459        self.line = line;
1460        self.column = column;
1461        #[cfg(feature = "perf-counters")]
1462        let before = self.input.index();
1463        while self.input.index() < target && self.input.la(1) != EOF {
1464            self.consume_char();
1465        }
1466        #[cfg(feature = "perf-counters")]
1467        crate::perf::record_lexer_scalar_replay(self.input.index().saturating_sub(before));
1468    }
1469
1470    /// Rewinds or advances the input cursor to a token accept boundary.
1471    ///
1472    /// Some generated lexers intentionally accept a longer path to disambiguate
1473    /// a token, then emit only the prefix and leave the suffix for the next
1474    /// token. Recomputing line/column from `token_start` keeps the visible lexer
1475    /// position consistent after moving the cursor backwards.
1476    pub fn reset_accept_position(&mut self, index: usize) {
1477        let target = index.max(self.token_start);
1478        self.reposition_from(
1479            self.token_start,
1480            self.token_start_line,
1481            self.token_start_column,
1482            target,
1483        );
1484    }
1485
1486    /// Moves the current token start forward within the consumed input span.
1487    ///
1488    /// Source line and column are advanced with the start, so a subsequently
1489    /// emitted suffix token carries the same coordinates it would have had if
1490    /// lexed independently.
1491    pub fn set_token_start(&mut self, index: usize) -> bool {
1492        if index < self.token_start || index > self.input.index() {
1493            return false;
1494        }
1495        let (line, column) = self.position_at(index);
1496        self.token_start = index;
1497        self.token_start_line = line;
1498        self.token_start_column = column;
1499        true
1500    }
1501
1502    /// Builds a token spanning from the current token start to the character
1503    /// before the input cursor.
1504    ///
1505    /// When generated or interpreted lexer code does not supply explicit text,
1506    /// the base lexer captures the matched source interval so downstream token
1507    /// streams and parse trees can render token text without retaining a source
1508    /// pair object.
1509    pub fn emit(
1510        &self,
1511        sink: &mut TokenSink<'_>,
1512        token_type: i32,
1513        channel: i32,
1514        text: Option<String>,
1515    ) -> Result<TokenId, TokenStoreError> {
1516        let stop = self.input.index().checked_sub(1).unwrap_or(usize::MAX);
1517        self.emit_with_stop(sink, token_type, channel, stop, text)
1518    }
1519
1520    /// Builds a token with an explicit stop index.
1521    ///
1522    /// EOF-matching lexer rules do not consume a Unicode scalar value, so their
1523    /// stop index can be one before the current input index. The caller passes
1524    /// `usize::MAX` to represent ANTLR's `-1` stop index at empty input.
1525    pub fn emit_with_stop(
1526        &self,
1527        sink: &mut TokenSink<'_>,
1528        token_type: i32,
1529        channel: i32,
1530        stop: usize,
1531        text: Option<String>,
1532    ) -> Result<TokenId, TokenStoreError> {
1533        sink.push(self.token_spec_with_stop(token_type, channel, stop, text))
1534    }
1535
1536    fn token_spec_with_stop(
1537        &self,
1538        token_type: i32,
1539        channel: i32,
1540        stop: usize,
1541        text: Option<String>,
1542    ) -> TokenSpec {
1543        let text = text.or_else(|| {
1544            if stop == usize::MAX {
1545                Some("<EOF>".to_owned())
1546            } else {
1547                None
1548            }
1549        });
1550        let source_interval = if self.has_source_text
1551            && text.is_none()
1552            && stop != usize::MAX
1553            && self.token_start <= stop
1554        {
1555            self.input
1556                .byte_interval(TextInterval::new(self.token_start, stop))
1557        } else {
1558            None
1559        };
1560        let text = text.or_else(|| {
1561            source_interval
1562                .is_none()
1563                .then(|| self.input.text(TextInterval::new(self.token_start, stop)))
1564        });
1565        let (start_byte, stop_byte) = source_interval
1566            .or_else(|| self.token_byte_span(stop))
1567            .unwrap_or((usize::MAX, usize::MAX));
1568        TokenSpec {
1569            token_type,
1570            channel,
1571            start: self.token_start,
1572            stop,
1573            start_byte,
1574            stop_byte,
1575            line: self.token_start_line,
1576            column: self.token_start_column,
1577            text,
1578            source_backed: source_interval.is_some(),
1579        }
1580    }
1581
1582    /// Queues an additional token to be returned before the current match's
1583    /// automatic token.
1584    ///
1585    /// The token spans the current token start through `stop` (inclusive).
1586    /// `text = None` keeps the token source-backed when the input supports it.
1587    pub fn enqueue_token(
1588        &mut self,
1589        token_type: i32,
1590        channel: i32,
1591        stop: usize,
1592        text: Option<String>,
1593    ) {
1594        let token = self.token_spec_with_stop(token_type, channel, stop, text);
1595        self.pending_tokens.push_back(token);
1596    }
1597
1598    pub(crate) fn emit_pending_token(
1599        &mut self,
1600        sink: &mut TokenSink<'_>,
1601    ) -> Result<Option<TokenId>, TokenStoreError> {
1602        self.pending_tokens
1603            .pop_front()
1604            .map(|token| sink.push(token))
1605            .transpose()
1606    }
1607
1608    pub(crate) fn emit_or_enqueue_with_stop(
1609        &mut self,
1610        sink: &mut TokenSink<'_>,
1611        stop: usize,
1612        text: Option<String>,
1613    ) -> Result<TokenId, TokenStoreError> {
1614        let token = self.token_spec_with_stop(self.token_type, self.channel, stop, text);
1615        self.emit_or_enqueue(sink, token)
1616    }
1617
1618    fn emit_or_enqueue(
1619        &mut self,
1620        sink: &mut TokenSink<'_>,
1621        token: TokenSpec,
1622    ) -> Result<TokenId, TokenStoreError> {
1623        if self.pending_tokens.is_empty() {
1624            return sink.push(token);
1625        }
1626        self.pending_tokens.push_back(token);
1627        self.emit_pending_token(sink)?
1628            .ok_or_else(|| unreachable!("the pending-token queue was just populated"))
1629    }
1630
1631    /// Returns the current token text from the token start through the input
1632    /// cursor.
1633    pub fn token_text(&self) -> String {
1634        self.token_text_until(self.input.index())
1635    }
1636
1637    /// Returns the current token text from the token start through
1638    /// `stop_exclusive`.
1639    ///
1640    /// Lexer custom actions can occur before the accepted token is complete.
1641    /// The action event records the position where the transition fired, and
1642    /// generated action code uses this helper to render ANTLR's `Text()`
1643    /// template at that exact point.
1644    pub fn token_text_until(&self, stop_exclusive: usize) -> String {
1645        if stop_exclusive <= self.token_start {
1646            return String::new();
1647        }
1648        self.input
1649            .text(TextInterval::new(self.token_start, stop_exclusive - 1))
1650    }
1651
1652    /// Computes the zero-based source column at an absolute input position
1653    /// reached during prediction of the current token.
1654    pub fn column_at(&self, position: usize) -> usize {
1655        self.position_at(position).1
1656    }
1657
1658    /// Grammar-declared `@lexer::members` state (issue #206).
1659    #[must_use]
1660    pub const fn members(&self) -> &MemberEnv {
1661        &self.members
1662    }
1663
1664    /// Mutable grammar-declared member state, for committed-path actions.
1665    pub const fn members_mut(&mut self) -> &mut MemberEnv {
1666        &mut self.members
1667    }
1668
1669    fn position_at(&self, position: usize) -> (usize, usize) {
1670        let mut line = self.token_start_line;
1671        let mut column = self.token_start_column;
1672        if position <= self.token_start {
1673            return (line, column);
1674        }
1675        if let Some(summary) = self.input.position_summary(self.token_start, position) {
1676            return summary.apply(line, column);
1677        }
1678        for ch in self
1679            .input
1680            .text(TextInterval::new(self.token_start, position - 1))
1681            .chars()
1682        {
1683            if ch == '\n' {
1684                line += 1;
1685                column = 0;
1686            } else {
1687                column += 1;
1688            }
1689        }
1690        (line, column)
1691    }
1692
1693    /// Builds the synthetic EOF token at the current input cursor.
1694    pub fn eof_token(&self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError> {
1695        sink.push(self.eof_token_spec())
1696    }
1697
1698    pub(crate) fn emit_eof_or_pending(
1699        &mut self,
1700        sink: &mut TokenSink<'_>,
1701    ) -> Result<TokenId, TokenStoreError> {
1702        let token = self.eof_token_spec();
1703        self.emit_or_enqueue(sink, token)
1704    }
1705
1706    fn eof_token_spec(&self) -> TokenSpec {
1707        let byte_offset = self.eof_byte_offset().unwrap_or(usize::MAX);
1708        TokenSpec::eof(self.input.index(), byte_offset, self.line, self.column)
1709    }
1710
1711    fn eof_byte_offset(&self) -> Option<usize> {
1712        self.byte_offset_at(self.input.index())
1713    }
1714
1715    fn token_byte_span(&self, stop: usize) -> Option<(usize, usize)> {
1716        if stop != usize::MAX && self.token_start <= stop {
1717            let (start_byte, stop_byte) = self
1718                .input
1719                .byte_interval(TextInterval::new(self.token_start, stop))?;
1720            return Some((start_byte, stop_byte));
1721        }
1722        let byte_offset = self.byte_offset_at(self.token_start)?;
1723        Some((byte_offset, byte_offset))
1724    }
1725
1726    fn byte_offset_at(&self, index: usize) -> Option<usize> {
1727        let byte_offset = if index == 0 {
1728            0
1729        } else {
1730            let previous = TextInterval::new(index - 1, index - 1);
1731            self.input.byte_interval(previous)?.1
1732        };
1733        Some(byte_offset)
1734    }
1735
1736    fn byte_span_for_scalar_range(&self, span: Range<usize>) -> Option<Range<usize>> {
1737        if span.start > span.end {
1738            return None;
1739        }
1740        if span.is_empty() {
1741            let offset = self.byte_offset_at(span.start)?;
1742            return Some(offset..offset);
1743        }
1744        let (start, end) = self
1745            .input
1746            .byte_interval(TextInterval::new(span.start, span.end - 1))?;
1747        Some(start..end)
1748    }
1749}
1750
1751impl<I> Recognizer for BaseLexer<I>
1752where
1753    I: CharStream,
1754{
1755    fn data(&self) -> &RecognizerData {
1756        &self.data
1757    }
1758
1759    fn data_mut(&mut self) -> &mut RecognizerData {
1760        &mut self.data
1761    }
1762}
1763
1764impl<I> Lexer for BaseLexer<I>
1765where
1766    I: CharStream,
1767{
1768    fn mode(&self) -> i32 {
1769        self.mode
1770    }
1771
1772    fn set_mode(&mut self, mode: i32) {
1773        self.mode = mode;
1774    }
1775
1776    fn push_mode(&mut self, mode: i32) {
1777        self.mode_stack.push(self.mode);
1778        self.mode = mode;
1779    }
1780
1781    fn pop_mode(&mut self) -> Option<i32> {
1782        let mode = self.mode_stack.pop()?;
1783        self.mode = mode;
1784        Some(mode)
1785    }
1786}
1787
1788impl<I> BaseLexer<I>
1789where
1790    I: CharStream,
1791{
1792    pub const fn line(&self) -> usize {
1793        self.line
1794    }
1795
1796    pub const fn column(&self) -> usize {
1797        self.column
1798    }
1799
1800    pub fn source_name(&self) -> &str {
1801        self.input.source_name()
1802    }
1803
1804    pub fn source_text(&self) -> Option<Rc<str>> {
1805        self.input.source_text()
1806    }
1807
1808    pub const fn hit_eof(&self) -> bool {
1809        self.hit_eof
1810    }
1811
1812    pub const fn set_hit_eof(&mut self, hit_eof: bool) {
1813        self.hit_eof = hit_eof;
1814    }
1815
1816    /// Routes every token through ATN interpretation even when the generated
1817    /// lexer carries an ahead-of-time compiled DFA.
1818    ///
1819    /// Interpretation is what learns the replayable DFA that
1820    /// [`Self::lexer_dfa_string`] reports, so harnesses asserting on the
1821    /// observed-DFA trace (ANTLR's `showDFA` descriptors) enable this before
1822    /// lexing.
1823    pub const fn set_force_interpreted(&mut self, force_interpreted: bool) {
1824        self.force_interpreted = force_interpreted;
1825    }
1826
1827    /// Whether compiled-DFA entry points must fall back to interpretation.
1828    pub const fn force_interpreted(&self) -> bool {
1829        self.force_interpreted
1830    }
1831
1832    /// Buffers a lexer diagnostic for the current token span until the token
1833    /// stream consumer can emit it in parser-compatible order.
1834    ///
1835    /// `line` and `column` should identify the current token start. Use
1836    /// [`Self::record_error_for_scalar_span`] when the diagnostic covers a
1837    /// different input range.
1838    pub fn record_error(&self, line: usize, column: usize, message: impl Into<String>) {
1839        let scalar_span = self.token_start..self.input.index().max(self.token_start);
1840        self.record_error_for_scalar_span(line, column, message, scalar_span);
1841    }
1842
1843    /// Buffers a lexer diagnostic for an explicit half-open Unicode-scalar span.
1844    ///
1845    /// The span is converted through [`CharStream::byte_interval`]. Streams
1846    /// without an exact UTF-8 byte mapping leave the diagnostic byte span
1847    /// unknown.
1848    pub fn record_error_for_scalar_span(
1849        &self,
1850        line: usize,
1851        column: usize,
1852        message: impl Into<String>,
1853        scalar_span: Range<usize>,
1854    ) {
1855        let mut error = TokenSourceError::new(line, column, message);
1856        error.span = self.byte_span_for_scalar_range(scalar_span);
1857        self.errors.borrow_mut().push(error);
1858    }
1859
1860    /// Records one fail-loud semantic-hook miss per coordinate and token start.
1861    pub fn record_semantic_error(&self, action: bool, rule_index: usize, coordinate_index: usize) {
1862        let kind = u8::from(action);
1863        if !self.semantic_error_coordinates.borrow_mut().insert((
1864            kind,
1865            rule_index,
1866            coordinate_index,
1867            self.token_start,
1868        )) {
1869            return;
1870        }
1871        let label = if action { "action" } else { "predicate" };
1872        self.record_error(
1873            self.token_start_line,
1874            self.token_start_column,
1875            format!("unhandled lexer semantic {label}: rule={rule_index} index={coordinate_index}"),
1876        );
1877    }
1878
1879    /// Returns and clears lexer diagnostics produced while fetching tokens.
1880    pub fn drain_errors(&mut self) -> Vec<TokenSourceError> {
1881        std::mem::take(self.errors.get_mut())
1882    }
1883
1884    /// Borrows the canonical caller-context store paired with this lexer's
1885    /// learned DFA.
1886    pub(crate) fn lexer_prediction_store(&self) -> RefMut<'_, LexerPredictionStore> {
1887        RefMut::map(self.dfa_cache.borrow_mut(), |cache| &mut cache.prediction)
1888    }
1889
1890    /// Starts a fresh token prediction while retaining bounded scratch
1891    /// allocations for subsequent matches.
1892    pub(crate) fn reset_lexer_prediction_workspace(&self) {
1893        self.dfa_cache.borrow_mut().prediction.workspace.reset();
1894    }
1895
1896    /// Returns learned lexer-DFA shape and optional action-payload storage.
1897    pub fn lexer_dfa_stats(&self) -> LexerDfaStats {
1898        let cache = self.dfa_cache.borrow();
1899        let cached_states = cache.cached_states.iter().flatten().count();
1900        let transitions = cache
1901            .dense_edges
1902            .iter()
1903            .flatten()
1904            .map(|row| {
1905                row.iter()
1906                    .filter(|transition| transition.target_state != usize::MAX)
1907                    .count()
1908            })
1909            .sum::<usize>()
1910            + cache.sparse_edges.len();
1911        let max_configs_per_state = cache
1912            .cached_states
1913            .iter()
1914            .flatten()
1915            .map(|state| state.configs.len())
1916            .max()
1917            .unwrap_or(0);
1918        let mut action_trace_sequences = 0;
1919        let mut action_traces = 0;
1920        let mut action_trace_bytes = 0;
1921        let mut account_actions = |actions: &Vec<LexerDfaActionKey>| {
1922            if actions.is_empty() {
1923                return;
1924            }
1925            action_trace_sequences += 1;
1926            action_traces += actions.len();
1927            action_trace_bytes += actions.capacity() * size_of::<LexerDfaActionKey>();
1928        };
1929        for key in cache.state_numbers.keys() {
1930            for config in &key.configs {
1931                account_actions(&config.actions);
1932            }
1933        }
1934        for state in cache.cached_states.iter().flatten() {
1935            for config in &state.configs {
1936                account_actions(&config.actions);
1937            }
1938            if let Some(accept) = state.accept.as_ref() {
1939                account_actions(&accept.actions);
1940            }
1941        }
1942        LexerDfaStats {
1943            states: cache.state_numbers.len(),
1944            cached_states,
1945            transitions,
1946            max_configs_per_state,
1947            contexts: cache.prediction.contexts.len(),
1948            action_trace_sequences,
1949            action_traces,
1950            action_trace_bytes,
1951        }
1952    }
1953
1954    #[cfg(test)]
1955    pub(crate) fn lexer_dfa_cache_shape(&self) -> (usize, usize, usize, usize) {
1956        let stats = self.lexer_dfa_stats();
1957        (
1958            stats.cached_states,
1959            stats.transitions,
1960            stats.max_configs_per_state,
1961            stats.contexts,
1962        )
1963    }
1964
1965    /// Returns the stable state number for a normalized lexer DFA config set,
1966    /// creating one if this input path has not reached it before.
1967    pub(crate) fn lexer_dfa_state(
1968        &self,
1969        key: LexerDfaKey,
1970        accept_prediction: Option<i32>,
1971    ) -> usize {
1972        let mut cache = self.dfa_cache.borrow_mut();
1973        let next = cache.state_numbers.len();
1974        let state = *cache.state_numbers.entry(key).or_insert(next);
1975        if let Some(prediction) = accept_prediction {
1976            cache.accept_predictions.insert(state, prediction);
1977        }
1978        state
1979    }
1980
1981    /// Records a visible lexer DFA edge unless it was already observed.
1982    pub fn record_lexer_dfa_edge(&self, from: usize, symbol: i32, to: usize) {
1983        self.dfa_cache
1984            .borrow_mut()
1985            .edges
1986            .insert(LexerDfaEdge { from, symbol, to });
1987    }
1988
1989    pub(crate) fn cached_lexer_dfa_transition(
1990        &self,
1991        state: usize,
1992        symbol: i32,
1993    ) -> Option<LexerDfaCachedTransition> {
1994        let cache = self.dfa_cache.borrow();
1995        if let Ok(sym) = usize::try_from(symbol)
1996            && sym < DENSE_EDGE_SYMBOLS
1997        {
1998            let transition = cache.dense_edges.get(state)?.as_ref()?[sym];
1999            return (transition.target_state != usize::MAX).then_some(transition);
2000        }
2001        cache.sparse_edges.get(&(state, symbol)).copied()
2002    }
2003
2004    pub(crate) fn cache_lexer_dfa_transition(
2005        &self,
2006        state: usize,
2007        symbol: i32,
2008        transition: LexerDfaCachedTransition,
2009    ) {
2010        let mut cache = self.dfa_cache.borrow_mut();
2011        if let Ok(sym) = usize::try_from(symbol)
2012            && sym < DENSE_EDGE_SYMBOLS
2013        {
2014            if cache.dense_edges.len() <= state {
2015                cache.dense_edges.resize_with(state + 1, || None);
2016            }
2017            let row = cache.dense_edges[state]
2018                .get_or_insert_with(|| Box::new([EMPTY_DENSE_EDGE; DENSE_EDGE_SYMBOLS]));
2019            // First write wins, matching the previous map `entry().or_insert`.
2020            if row[sym].target_state == usize::MAX {
2021                row[sym] = transition;
2022            }
2023            return;
2024        }
2025        cache
2026            .sparse_edges
2027            .entry((state, symbol))
2028            .or_insert(transition);
2029    }
2030
2031    pub(crate) fn cached_lexer_dfa_state(&self, state: usize) -> Option<Rc<LexerDfaCachedState>> {
2032        self.dfa_cache
2033            .borrow()
2034            .cached_states
2035            .get(state)
2036            .cloned()
2037            .flatten()
2038    }
2039
2040    pub(crate) fn cache_lexer_dfa_state(&self, state: usize, cached_state: LexerDfaCachedState) {
2041        let mut cache = self.dfa_cache.borrow_mut();
2042        if cache.cached_states.len() <= state {
2043            cache.cached_states.resize_with(state + 1, || None);
2044        }
2045        cache.cached_states[state].get_or_insert_with(|| Rc::new(cached_state));
2046    }
2047
2048    pub(crate) fn cached_lexer_mode_start(&self, mode: i32) -> Option<usize> {
2049        self.dfa_cache.borrow().mode_starts.get(&mode).copied()
2050    }
2051
2052    pub(crate) fn cache_lexer_mode_start(&self, mode: i32, state: usize) {
2053        self.dfa_cache
2054            .borrow_mut()
2055            .mode_starts
2056            .entry(mode)
2057            .or_insert(state);
2058    }
2059
2060    /// Serializes the observed default-mode lexer DFA in ANTLR's text shape.
2061    pub fn lexer_dfa_string(&self) -> String {
2062        let mut out = String::new();
2063        let cache = self.dfa_cache.borrow();
2064        for edge in &cache.edges {
2065            let Some(label) = lexer_dfa_edge_label(edge.symbol) else {
2066                continue;
2067            };
2068            out.push_str(&self.lexer_dfa_state_string(edge.from));
2069            out.push('-');
2070            out.push_str(&label);
2071            out.push_str("->");
2072            out.push_str(&self.lexer_dfa_state_string(edge.to));
2073            out.push('\n');
2074        }
2075        out
2076    }
2077
2078    fn lexer_dfa_state_string(&self, state: usize) -> String {
2079        self.dfa_cache
2080            .borrow()
2081            .accept_predictions
2082            .get(&state)
2083            .map_or_else(
2084                || format!("s{state}"),
2085                |prediction| format!(":s{state}=>{prediction}"),
2086            )
2087    }
2088}
2089
2090fn lexer_dfa_edge_label(symbol: i32) -> Option<String> {
2091    char::from_u32(symbol.cast_unsigned()).map(|ch| format!("'{ch}'"))
2092}
2093
2094#[cfg(test)]
2095#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
2096mod tests {
2097    use super::*;
2098    use crate::char_stream::InputStream;
2099    use crate::int_stream::IntStream;
2100    use crate::recognizer::RecognizerData;
2101    use crate::token::{DEFAULT_CHANNEL, Token, TokenStore};
2102    use crate::vocabulary::Vocabulary;
2103
2104    #[test]
2105    fn action_trace_lists_clone_on_write_without_aliasing() {
2106        let first = LexerActionTrace {
2107            action_index: 1,
2108            position: 2,
2109            rule_index: 3,
2110        };
2111        let second = LexerActionTrace {
2112            action_index: 4,
2113            position: 5,
2114            rule_index: 6,
2115        };
2116        let mut original = LexerActionTraceList::default();
2117        original.make_mut().push(first);
2118        let mut fork = original.clone();
2119        fork.make_mut().push(second);
2120
2121        assert_eq!(original.as_slice(), [first]);
2122        assert_eq!(fork.as_slice(), [first, second]);
2123        fork.retain(|_| false);
2124        assert!(fork.as_slice().is_empty());
2125
2126        fn assert_send<T: Send>() {}
2127        assert_send::<LexerActionTraceList>();
2128    }
2129
2130    #[test]
2131    fn action_trace_lists_do_not_retain_growing_prefixes() {
2132        let mut actions = LexerActionTraceList::default();
2133        for position in 0..1024 {
2134            let source = actions.clone();
2135            actions.make_mut().push(LexerActionTrace {
2136                action_index: 0,
2137                position,
2138                rule_index: 0,
2139            });
2140            drop(source);
2141            assert_eq!(
2142                Arc::strong_count(actions.0.as_ref().expect("non-empty action list")),
2143                1
2144            );
2145        }
2146        assert_eq!(actions.len(), 1024);
2147    }
2148
2149    #[derive(Clone, Debug)]
2150    struct UnsharedInput {
2151        input: InputStream,
2152        maps_bytes: bool,
2153    }
2154
2155    impl UnsharedInput {
2156        fn mapped(input: InputStream) -> Self {
2157            Self {
2158                input,
2159                maps_bytes: true,
2160            }
2161        }
2162
2163        fn scalar_only(input: InputStream) -> Self {
2164            Self {
2165                input,
2166                maps_bytes: false,
2167            }
2168        }
2169    }
2170
2171    impl IntStream for UnsharedInput {
2172        fn consume(&mut self) {
2173            self.input.consume();
2174        }
2175
2176        fn la(&mut self, offset: isize) -> i32 {
2177            self.input.la(offset)
2178        }
2179
2180        fn index(&self) -> usize {
2181            self.input.index()
2182        }
2183
2184        fn seek(&mut self, index: usize) {
2185            self.input.seek(index);
2186        }
2187
2188        fn size(&self) -> usize {
2189            self.input.size()
2190        }
2191
2192        fn source_name(&self) -> &str {
2193            self.input.source_name()
2194        }
2195    }
2196
2197    impl CharStream for UnsharedInput {
2198        fn text(&self, interval: TextInterval) -> String {
2199            self.input.text(interval)
2200        }
2201
2202        fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
2203            if self.maps_bytes {
2204                self.input.byte_interval(interval)
2205            } else {
2206                None
2207            }
2208        }
2209    }
2210
2211    #[test]
2212    fn eof_token_uses_utf8_byte_offset_after_non_ascii_input() {
2213        let data = RecognizerData::new(
2214            "T",
2215            Vocabulary::new(
2216                std::iter::empty::<Option<&str>>(),
2217                std::iter::empty::<Option<&str>>(),
2218                std::iter::empty::<Option<&str>>(),
2219            ),
2220        );
2221        let mut lexer = BaseLexer::new(InputStream::new("β"), data);
2222        lexer.consume_char();
2223
2224        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2225        let mut sink = TokenSink::new(&mut store);
2226        let id = lexer.eof_token(&mut sink).expect("test token should fit");
2227        let token = sink.view(id).expect("emitted token should exist");
2228
2229        // byte_span is the field this test exists to pin and is absent from TokenView's Debug, so
2230        // snapshot the explicit (start, stop, text, byte_span) record rather than the token.
2231        insta::assert_compact_debug_snapshot!(
2232            (token.start(), token.stop(), token.text(), token.byte_span()),
2233            @r#"(1, 0, Some("<EOF>"), Some(2..2))"#
2234        );
2235    }
2236
2237    #[test]
2238    fn eof_token_has_no_byte_span_without_byte_mapping() {
2239        let data = RecognizerData::new(
2240            "T",
2241            Vocabulary::new(
2242                std::iter::empty::<Option<&str>>(),
2243                std::iter::empty::<Option<&str>>(),
2244                std::iter::empty::<Option<&str>>(),
2245            ),
2246        );
2247        let mut lexer = BaseLexer::new(UnsharedInput::scalar_only(InputStream::new("β")), data);
2248        lexer.consume_char();
2249
2250        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2251        let mut sink = TokenSink::new(&mut store);
2252        let id = lexer.eof_token(&mut sink).expect("test token should fit");
2253        let token = sink.view(id).expect("emitted token should exist");
2254
2255        insta::assert_compact_debug_snapshot!(
2256            (token.start(), token.stop(), token.text(), token.byte_span()),
2257            @r#"(1, 0, Some("<EOF>"), None)"#
2258        );
2259    }
2260
2261    #[test]
2262    fn eof_rule_token_uses_utf8_byte_offset_after_non_ascii_input() {
2263        let data = RecognizerData::new(
2264            "T",
2265            Vocabulary::new(
2266                std::iter::empty::<Option<&str>>(),
2267                std::iter::empty::<Option<&str>>(),
2268                std::iter::empty::<Option<&str>>(),
2269            ),
2270        );
2271        let mut lexer = BaseLexer::new(InputStream::new("β"), data);
2272        lexer.consume_char();
2273        lexer.begin_token();
2274
2275        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2276        let mut sink = TokenSink::new(&mut store);
2277        let id = lexer
2278            .emit_with_stop(&mut sink, 1, DEFAULT_CHANNEL, 0, Some("<EOF>".to_owned()))
2279            .expect("test token should fit");
2280        let token = sink.view(id).expect("emitted token should exist");
2281
2282        // byte_span is the field this test exists to pin and is absent from TokenView's Debug, so
2283        // snapshot the explicit (start, stop, text, byte_span) record rather than the token.
2284        insta::assert_compact_debug_snapshot!(
2285            (token.start(), token.stop(), token.text(), token.byte_span()),
2286            @r#"(1, 0, Some("<EOF>"), Some(2..2))"#
2287        );
2288    }
2289
2290    #[test]
2291    fn emit_implicit_text_uses_utf8_byte_span_for_non_ascii_input() {
2292        let data = RecognizerData::new(
2293            "T",
2294            Vocabulary::new(
2295                std::iter::empty::<Option<&str>>(),
2296                std::iter::empty::<Option<&str>>(),
2297                std::iter::empty::<Option<&str>>(),
2298            ),
2299        );
2300        let mut lexer = BaseLexer::new(InputStream::new("β"), data);
2301        lexer.begin_token();
2302        lexer.consume_char();
2303
2304        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2305        let mut sink = TokenSink::new(&mut store);
2306        let id = lexer
2307            .emit(&mut sink, 1, DEFAULT_CHANNEL, None)
2308            .expect("test token should fit");
2309        let token = sink.view(id).expect("emitted token should exist");
2310
2311        // byte_span is the field this test exists to pin and is absent from TokenView's Debug, so
2312        // snapshot the explicit (start, stop, text, byte_span) record rather than the token.
2313        insta::assert_compact_debug_snapshot!(
2314            (token.start(), token.stop(), token.text(), token.byte_span()),
2315            @r#"(0, 0, Some("β"), Some(0..2))"#
2316        );
2317    }
2318
2319    #[test]
2320    fn emit_falls_back_to_explicit_text_without_shareable_source() {
2321        let data = RecognizerData::new(
2322            "T",
2323            Vocabulary::new(
2324                std::iter::empty::<Option<&str>>(),
2325                std::iter::empty::<Option<&str>>(),
2326                std::iter::empty::<Option<&str>>(),
2327            ),
2328        );
2329        let mut lexer = BaseLexer::new(UnsharedInput::mapped(InputStream::new("β")), data);
2330        lexer.begin_token();
2331        lexer.consume_char();
2332
2333        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
2334        let mut sink = TokenSink::new(&mut store);
2335        let id = lexer
2336            .emit(&mut sink, 1, DEFAULT_CHANNEL, None)
2337            .expect("unshared input should emit explicit token text");
2338        let token = sink.view(id).expect("emitted token should exist");
2339
2340        assert_eq!(token.text(), Some("β"));
2341        assert_eq!(token.byte_span(), Some(0..2));
2342    }
2343
2344    #[test]
2345    fn position_commits_and_rewinds_preserve_line_and_column() {
2346        let data = RecognizerData::new(
2347            "T",
2348            Vocabulary::new(
2349                std::iter::empty::<Option<&str>>(),
2350                std::iter::empty::<Option<&str>>(),
2351                std::iter::empty::<Option<&str>>(),
2352            ),
2353        );
2354        let mut lexer = BaseLexer::new(InputStream::new("ab\nγd"), data);
2355        lexer.begin_token();
2356
2357        lexer.commit_position(0, 5);
2358        assert_eq!(lexer.input().index(), 5);
2359        assert_eq!((lexer.line(), lexer.column()), (2, 2));
2360        assert_eq!(lexer.column_at(2), 2);
2361        assert_eq!(lexer.column_at(4), 1);
2362
2363        lexer.reset_accept_position(3);
2364        assert_eq!(lexer.input().index(), 3);
2365        assert_eq!((lexer.line(), lexer.column()), (2, 0));
2366    }
2367
2368    #[test]
2369    fn custom_stream_position_commit_replays_without_fast_path_methods() {
2370        let data = RecognizerData::new(
2371            "T",
2372            Vocabulary::new(
2373                std::iter::empty::<Option<&str>>(),
2374                std::iter::empty::<Option<&str>>(),
2375                std::iter::empty::<Option<&str>>(),
2376            ),
2377        );
2378        let mut lexer = BaseLexer::new(UnsharedInput::mapped(InputStream::new("a\nb")), data);
2379        lexer.begin_token();
2380
2381        lexer.commit_position(0, 3);
2382        assert_eq!(lexer.input().index(), 3);
2383        assert_eq!((lexer.line(), lexer.column()), (2, 1));
2384    }
2385
2386    #[test]
2387    fn semantic_hook_errors_are_deduplicated_per_token_coordinate() {
2388        let data = RecognizerData::new(
2389            "T",
2390            Vocabulary::new(
2391                std::iter::empty::<Option<&str>>(),
2392                std::iter::empty::<Option<&str>>(),
2393                std::iter::empty::<Option<&str>>(),
2394            ),
2395        );
2396        let mut lexer = BaseLexer::new(InputStream::new("a"), data);
2397        lexer.begin_token();
2398        lexer.record_semantic_error(false, 3, 7);
2399        lexer.record_semantic_error(false, 3, 7);
2400
2401        let errors = lexer.drain_errors();
2402        insta::assert_compact_debug_snapshot!(errors, @r#"[TokenSourceError { line: 1, column: 0, span: Some(0..0), message: "unhandled lexer semantic predicate: rule=3 index=7" }]"#);
2403
2404        lexer.begin_token();
2405        lexer.record_semantic_error(false, 3, 7);
2406        assert_eq!(
2407            lexer.drain_errors().len(),
2408            1,
2409            "deduplication resets at every token boundary, even after rewinding"
2410        );
2411    }
2412
2413    #[test]
2414    fn set_input_stream_replaces_input_and_resets_transient_state() {
2415        let data = RecognizerData::new(
2416            "T",
2417            Vocabulary::new(
2418                std::iter::empty::<Option<&str>>(),
2419                std::iter::empty::<Option<&str>>(),
2420                std::iter::empty::<Option<&str>>(),
2421            ),
2422        );
2423        let mut lexer = BaseLexer::new(InputStream::new("old"), data);
2424        lexer.consume_char();
2425        lexer.set_mode(7);
2426        lexer.push_mode(9);
2427        lexer.set_type(3);
2428        lexer.record_error(1, 0, "stale");
2429
2430        lexer.set_input_stream(InputStream::with_source_name("new", "replacement"));
2431
2432        assert_eq!(lexer.input().index(), 0);
2433        assert_eq!(lexer.input().size(), 3);
2434        assert_eq!(lexer.source_name(), "replacement");
2435        assert_eq!(lexer.source_text().as_deref(), Some("new"));
2436        assert_eq!(lexer.mode(), DEFAULT_MODE);
2437        assert_eq!(lexer.token_type(), INVALID_TOKEN_TYPE);
2438        assert_eq!((lexer.line(), lexer.column()), (1, 0));
2439        assert!(!lexer.hit_eof());
2440        assert!(lexer.drain_errors().is_empty());
2441        assert!(lexer.pop_mode().is_none());
2442    }
2443
2444    #[test]
2445    fn clear_dfa_invalidates_all_lexers_sharing_the_cache() {
2446        let atn = Box::leak(Box::new(LexerAtn::new(1)));
2447        let data = || {
2448            RecognizerData::new(
2449                "T",
2450                Vocabulary::new(
2451                    std::iter::empty::<Option<&str>>(),
2452                    std::iter::empty::<Option<&str>>(),
2453                    std::iter::empty::<Option<&str>>(),
2454                ),
2455            )
2456        };
2457        let first = BaseLexer::new(InputStream::new("a"), data()).with_shared_dfa(atn);
2458        let second = BaseLexer::new(InputStream::new("a"), data()).with_shared_dfa(atn);
2459        let state = first.lexer_dfa_state(LexerDfaKey::new(Vec::new()), Some(1));
2460        first.record_lexer_dfa_edge(state, i32::from(b'a'), state);
2461
2462        assert!(!second.lexer_dfa_string().is_empty());
2463        first.clear_dfa();
2464        assert!(first.lexer_dfa_string().is_empty());
2465        assert!(second.lexer_dfa_string().is_empty());
2466    }
2467
2468    /// Builds the `@lexer::members` state the C# interpolation lexer declares
2469    /// (issue #206): a scalar depth counter, a scalar `verbatium` flag, and two
2470    /// stacks. Slot numbering mirrors what the generator assigns.
2471    mod member_slots {
2472        pub(super) const INTERPOLATED_STRING_LEVEL: usize = 0;
2473        pub(super) const VERBATIUM: usize = 1;
2474        pub(super) const INTERPOLATED_VERBATIUMS: usize = 0;
2475        pub(super) const CURLY_LEVELS: usize = 1;
2476    }
2477
2478    fn member_state_lexer() -> BaseLexer<InputStream> {
2479        let data = RecognizerData::new(
2480            "CSharpLexer",
2481            Vocabulary::new(
2482                std::iter::empty::<Option<&str>>(),
2483                std::iter::empty::<Option<&str>>(),
2484                std::iter::empty::<Option<&str>>(),
2485            ),
2486        );
2487        BaseLexer::new(InputStream::new("$\"{x}\""), data)
2488    }
2489
2490    /// Replays the C# lexer's interpolation bookkeeping across nested strings
2491    /// and asserts the `verbatium` flag each `{ !verbatium }?` guard would see.
2492    ///
2493    /// `DOUBLE_QUOTE_INSIDE` restores the flag with
2494    /// `Count > 0 ? Peek() : false`, which is `MemberTop` falling back to Null.
2495    #[test]
2496    fn lexer_stack_members_track_nested_interpolation_state() {
2497        use member_slots::{INTERPOLATED_STRING_LEVEL, INTERPOLATED_VERBATIUMS};
2498
2499        let mut lexer = member_state_lexer();
2500        let members = lexer.members_mut();
2501
2502        // INTERPOLATED_REGULAR_STRING_START: `$"` — verbatium = false.
2503        members.add_scalar(INTERPOLATED_STRING_LEVEL, 1);
2504        members.push_stack(INTERPOLATED_VERBATIUMS, 0);
2505        assert_eq!(members.stack_top(INTERPOLATED_VERBATIUMS), Some(0));
2506        assert_eq!(members.scalar(INTERPOLATED_STRING_LEVEL), Some(1));
2507
2508        // Nested INTERPOLATED_VERBATIUM_STRING_START: `$@"` — verbatium = true.
2509        members.add_scalar(INTERPOLATED_STRING_LEVEL, 1);
2510        members.push_stack(INTERPOLATED_VERBATIUMS, 1);
2511        assert_eq!(members.stack_top(INTERPOLATED_VERBATIUMS), Some(1));
2512        assert_eq!(members.stack_len(INTERPOLATED_VERBATIUMS), 2);
2513
2514        // DOUBLE_QUOTE_INSIDE closes the verbatim string; the enclosing
2515        // regular string's `false` must come back.
2516        members.add_scalar(INTERPOLATED_STRING_LEVEL, -1);
2517        assert_eq!(members.pop_stack(INTERPOLATED_VERBATIUMS), Some(1));
2518        assert_eq!(members.stack_top(INTERPOLATED_VERBATIUMS), Some(0));
2519
2520        // Closing the outer string empties the stack: `Count > 0` is false, so
2521        // the grammar falls back to `false` — Null, which is falsy.
2522        members.add_scalar(INTERPOLATED_STRING_LEVEL, -1);
2523        assert_eq!(members.pop_stack(INTERPOLATED_VERBATIUMS), Some(0));
2524        assert_eq!(members.stack_top(INTERPOLATED_VERBATIUMS), None);
2525        assert_eq!(members.scalar(INTERPOLATED_STRING_LEVEL), Some(0));
2526    }
2527
2528    /// `reset()` must clear member state: a retained interpolation depth would
2529    /// silently mis-lex the next input on a reused lexer.
2530    #[test]
2531    fn lexer_reset_clears_member_state() {
2532        use member_slots::{CURLY_LEVELS, INTERPOLATED_STRING_LEVEL};
2533
2534        let mut lexer = member_state_lexer();
2535        lexer.members_mut().add_scalar(INTERPOLATED_STRING_LEVEL, 2);
2536        lexer.members_mut().push_stack(CURLY_LEVELS, 1);
2537        assert!(!lexer.members().is_empty());
2538
2539        lexer.reset();
2540
2541        assert!(lexer.members().is_empty(), "reset must clear member state");
2542        assert_eq!(lexer.members().scalar(INTERPOLATED_STRING_LEVEL), None);
2543        assert_eq!(lexer.members().stack_top(CURLY_LEVELS), None);
2544    }
2545
2546    /// A grammar-declared initializer (`private bool verbatium = true;`) must
2547    /// survive construction *and* every reset. Zeroing the slot instead would
2548    /// make a predicate reading it reject input the source grammar accepts,
2549    /// while the manifest still reported the coordinate as translated.
2550    #[test]
2551    fn lexer_declared_initial_members_survive_construction_and_reset() {
2552        use member_slots::{CURLY_LEVELS, INTERPOLATED_STRING_LEVEL, VERBATIUM};
2553
2554        let mut lexer = member_state_lexer().with_initial_members([(VERBATIUM, 1)]);
2555        assert_eq!(lexer.members().scalar(VERBATIUM), Some(1));
2556
2557        // Mutate away from the declared value, and dirty a stack too.
2558        lexer.members_mut().set_scalar(VERBATIUM, 0);
2559        lexer.members_mut().add_scalar(INTERPOLATED_STRING_LEVEL, 3);
2560        lexer.members_mut().push_stack(CURLY_LEVELS, 1);
2561
2562        lexer.reset();
2563
2564        // The declared initializer is restored, not zeroed...
2565        assert_eq!(lexer.members().scalar(VERBATIUM), Some(1));
2566        // ...while undeclared slots and all stacks go back to empty.
2567        assert_eq!(lexer.members().scalar(INTERPOLATED_STRING_LEVEL), None);
2568        assert_eq!(lexer.members().stack_len(CURLY_LEVELS), 0);
2569    }
2570
2571    /// A predicate context must not mutate lexer state: predicates run
2572    /// speculatively on paths that may be abandoned. The mutators report
2573    /// `false`/`None` there instead of silently applying.
2574    #[test]
2575    fn lexer_predicate_context_cannot_mutate_member_state() {
2576        use member_slots::{INTERPOLATED_VERBATIUMS, VERBATIUM};
2577
2578        let mut lexer = member_state_lexer();
2579        lexer.members_mut().set_scalar(VERBATIUM, 1);
2580        lexer.members_mut().push_stack(INTERPOLATED_VERBATIUMS, 1);
2581
2582        let mut ctx = LexerSemCtx::new(&lexer, 0, 0, 0);
2583        // Reads work in a predicate context.
2584        assert_eq!(ctx.member_int(VERBATIUM), Some(1));
2585        assert_eq!(ctx.member_stack_top(INTERPOLATED_VERBATIUMS), Some(1));
2586        assert_eq!(ctx.member_stack_len(INTERPOLATED_VERBATIUMS), 1);
2587        // Writes are refused.
2588        assert!(!ctx.set_member_int(VERBATIUM, 0));
2589        assert!(!ctx.push_member(INTERPOLATED_VERBATIUMS, 0));
2590        assert_eq!(ctx.pop_member(INTERPOLATED_VERBATIUMS), None);
2591        assert_eq!(ctx.add_member_int(VERBATIUM, 5), None);
2592
2593        assert_eq!(lexer.members().scalar(VERBATIUM), Some(1));
2594        assert_eq!(lexer.members().stack_len(INTERPOLATED_VERBATIUMS), 1);
2595    }
2596
2597    /// End-to-end through `LexerSemantics`: the `{ !verbatium }?` and
2598    /// `{ verbatium }?` guard pair lowered as pure `SemIR`, evaluated against
2599    /// state that a lowered action wrote — no hooks anywhere.
2600    #[test]
2601    fn lexer_semantics_evaluates_stack_guards_written_by_lowered_actions() {
2602        use crate::semir::{AStmt, PExpr, SemIr};
2603        use member_slots::INTERPOLATED_VERBATIUMS;
2604
2605        let mut ir = SemIr::new();
2606        // `{ verbatium }?` reading the interpolation stack's top.
2607        let top = ir.expr(PExpr::MemberTop(INTERPOLATED_VERBATIUMS));
2608        // `{ !verbatium }?`
2609        let not_top = ir.expr(PExpr::Not(top));
2610        // `interpolatedVerbatiums.Push(true)` / `.Pop()`
2611        let yes = ir.expr(PExpr::Bool(true));
2612        let push_verbatim = ir.stmt(AStmt::PushMember(INTERPOLATED_VERBATIUMS, yes));
2613        let pop = ir.stmt(AStmt::PopMember(INTERPOLATED_VERBATIUMS));
2614
2615        let semantics = LexerSemantics {
2616            ir,
2617            predicates: vec![
2618                LexerSemanticPredicate {
2619                    rule_index: 1,
2620                    pred_index: 0,
2621                    expr: top,
2622                },
2623                LexerSemanticPredicate {
2624                    rule_index: 2,
2625                    pred_index: 0,
2626                    expr: not_top,
2627                },
2628            ],
2629            actions: vec![
2630                LexerSemanticAction {
2631                    rule_index: 0,
2632                    action_index: 0,
2633                    stmt: push_verbatim,
2634                },
2635                LexerSemanticAction {
2636                    rule_index: 3,
2637                    action_index: 0,
2638                    stmt: pop,
2639                },
2640            ],
2641        };
2642
2643        let verbatim_guard = LexerPredicate::new(1, 0, 0);
2644        let regular_guard = LexerPredicate::new(2, 0, 0);
2645        let mut lexer = member_state_lexer();
2646
2647        // Before any push the stack is empty: the verbatim guard fails and the
2648        // regular guard passes, matching `Count > 0 ? Peek() : false`.
2649        assert_eq!(
2650            semantics.eval_predicate(&lexer, verbatim_guard),
2651            Some(false)
2652        );
2653        assert_eq!(semantics.eval_predicate(&lexer, regular_guard), Some(true));
2654
2655        // The lowered `$@"` action pushes `true`; the guards must flip.
2656        assert!(semantics.exec_action(&mut lexer, LexerCustomAction::new(0, 0, 0)));
2657        assert_eq!(semantics.eval_predicate(&lexer, verbatim_guard), Some(true));
2658        assert_eq!(semantics.eval_predicate(&lexer, regular_guard), Some(false));
2659
2660        // The lowered closing-quote action pops it back.
2661        assert!(semantics.exec_action(&mut lexer, LexerCustomAction::new(3, 0, 0)));
2662        assert_eq!(
2663            semantics.eval_predicate(&lexer, verbatim_guard),
2664            Some(false)
2665        );
2666        assert_eq!(semantics.eval_predicate(&lexer, regular_guard), Some(true));
2667
2668        // A coordinate this table does not own is declined, so the caller can
2669        // still fall back to hooks or the unknown policy.
2670        assert_eq!(
2671            semantics.eval_predicate(&lexer, LexerPredicate::new(9, 9, 0)),
2672            None
2673        );
2674        assert!(!semantics.exec_action(&mut lexer, LexerCustomAction::new(9, 9, 0)));
2675    }
2676}