Skip to main content

antlr4_runtime/
lexer.rs

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