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