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