Skip to main content

antlr4_runtime/atn/
bypass.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3//! Rule-bypass alternatives for parse-tree pattern matching.
4//!
5//! ANTLR's parse-tree pattern matcher needs to interpret a hybrid token stream
6//! in which an *imaginary* token can stand in for an entire parser rule (the
7//! `<expr>` tag in a pattern like `x = <expr>;`). Upstream implements this by
8//! re-deserializing the grammar ATN with `generateRuleBypassTransitions`
9//! enabled (`ATNDeserializer`): each rule gains a **bypass alternative** so the
10//! ordinary parser interpreter can match one imaginary token as if it were the
11//! whole rule. See `ATNDeserializer.java` (the `isGenerateRuleBypassTransitions`
12//! block) for the reference algorithm this mirrors.
13//!
14//! This runtime stores parser ATNs as a packed, immutable word stream rather
15//! than a mutable object graph, so the transform reads the source ATN through
16//! its borrowing views into an out-adjacency model, applies the rewrite there,
17//! and emits a fresh [`ParserAtn`] through [`ParserAtnBuilder`]. The existing
18//! ATN interpreter then runs over the result unchanged — no hot-path edits.
19//!
20//! ### Why `max_token_type` is left unchanged
21//!
22//! Upstream assigns each rule the imaginary token type `maxTokenType + i + 1`
23//! but never raises `maxTokenType` itself. We keep the same invariant on
24//! purpose: an [`Atom`](ParserTransitionSpec::Atom) transition matches by exact
25//! label equality (no range check), so the bypass edge matches its imaginary
26//! type fine, while grammar wildcards and `~x` negated sets — which the runtime
27//! bounds by `min..=max_token_type` — can never accidentally match an imaginary
28//! token that lives *above* the unchanged maximum.
29
30use std::collections::BTreeSet;
31
32use super::AtnStateKind;
33use super::parser_atn::{
34    ParserAtn, ParserAtnBuilder, ParserAtnError, ParserIntervalSetId, ParserTransitionData,
35    ParserTransitionSpec,
36};
37
38impl ParserAtn {
39    /// Builds a copy of this parser ATN with rule-bypass alternatives added.
40    ///
41    /// Every rule gains an imaginary token type (`max_token_type + rule + 1`)
42    /// and a bypass block so the ATN interpreter can match that single
43    /// imaginary token in place of the whole rule. `max_token_type` is
44    /// unchanged (see the module docs). The returned ATN is otherwise a faithful
45    /// copy: state kinds, transitions, interval sets, decisions, and
46    /// rule/precedence metadata are all preserved.
47    ///
48    /// # Errors
49    ///
50    /// Returns [`ParserAtnError`] if the state/transition/token counts overflow
51    /// the packed compact-index range, if a left-recursive rule's precedence
52    /// prefix cannot be identified, or if the re-emitted stream fails
53    /// validation.
54    pub fn with_bypass_alternatives(&self) -> Result<Self, ParserAtnError> {
55        BypassBuilder::new(self)?.build()
56    }
57
58    /// The imaginary token type reserved for a rule's bypass alternative:
59    /// `max_token_type + rule_index + 1`.
60    ///
61    /// This is the single source of the formula shared by
62    /// [`Self::with_bypass_alternatives`] (which labels the bypass `Atom` edge
63    /// with it) and the pattern matcher (which stamps rule-tag tokens with it),
64    /// so the two can never disagree about a tag's token type.
65    ///
66    /// # Errors
67    ///
68    /// Returns [`ParserAtnError::Overflow`] when the type would exceed `i32`.
69    pub fn bypass_token_type(&self, rule_index: usize) -> Result<i32, ParserAtnError> {
70        imaginary_token_type(self.max_token_type(), rule_index)
71    }
72}
73
74/// Shared formula for a rule's imaginary bypass token type.
75fn imaginary_token_type(max_token_type: i32, rule: usize) -> Result<i32, ParserAtnError> {
76    let overflow = || ParserAtnError::Overflow {
77        field: "bypass imaginary token type",
78        value: rule,
79    };
80    let rule = i32::try_from(rule).map_err(|_| overflow())?;
81    max_token_type
82        .checked_add(rule)
83        .and_then(|value| value.checked_add(1))
84        .ok_or_else(overflow)
85}
86
87/// Mutable working copy of a parser ATN used to apply the bypass rewrite.
88struct BypassBuilder {
89    max_token_type: i32,
90    /// Per-state kind, parallel to state number.
91    kinds: Vec<AtnStateKind>,
92    /// Per-state grammar rule index (`None` for rule-agnostic states).
93    rule_indices: Vec<Option<usize>>,
94    /// Block-start end state, if any.
95    end_states: Vec<Option<usize>>,
96    /// Loop-end loop-back state, if any.
97    loop_back_states: Vec<Option<usize>>,
98    /// Non-greedy decision flag, preserved verbatim.
99    non_greedy: Vec<bool>,
100    /// Left-recursive rule flag, preserved on rule-start states.
101    left_recursive: Vec<bool>,
102    /// Out-adjacency: `out[source]` holds that state's transitions in order.
103    out: Vec<Vec<ParserTransitionSpec>>,
104    /// Interval sets copied verbatim; identities stay valid because order is
105    /// preserved.
106    interval_sets: Vec<Vec<(i32, i32)>>,
107    decisions: Vec<usize>,
108    rule_starts: Vec<usize>,
109    rule_stops: Vec<usize>,
110}
111
112impl BypassBuilder {
113    /// Reads the packed source ATN into the mutable model.
114    fn new(atn: &ParserAtn) -> Result<Self, ParserAtnError> {
115        let state_count = atn.state_count();
116        let mut kinds = Vec::with_capacity(state_count);
117        let mut rule_indices = Vec::with_capacity(state_count);
118        let mut end_states = Vec::with_capacity(state_count);
119        let mut loop_back_states = Vec::with_capacity(state_count);
120        let mut non_greedy = Vec::with_capacity(state_count);
121        let mut left_recursive = Vec::with_capacity(state_count);
122        let mut out = Vec::with_capacity(state_count);
123
124        for number in 0..state_count {
125            let state = atn
126                .state(number)
127                .expect("state index below state_count is in bounds");
128            kinds.push(state.kind());
129            rule_indices.push(state.rule_index());
130            end_states.push(state.end_state());
131            loop_back_states.push(state.loop_back_state());
132            non_greedy.push(state.non_greedy());
133            left_recursive.push(state.left_recursive_rule());
134            out.push(
135                state
136                    .transitions()
137                    .iter()
138                    .map(|transition| data_to_spec(transition.data()))
139                    .collect::<Result<Vec<_>, _>>()?,
140            );
141        }
142
143        let interval_sets = (0..atn.set_count())
144            .map(|index| {
145                atn.token_set(index)
146                    .expect("set index below set_count is in bounds")
147                    .ranges()
148                    .collect::<Vec<_>>()
149            })
150            .collect();
151
152        let decisions = atn.decision_to_state().into_iter().collect();
153        let rule_starts = atn.rule_to_start_state().into_iter().collect();
154        let rule_stops = atn.rule_to_stop_state().into_iter().collect();
155
156        Ok(Self {
157            max_token_type: atn.max_token_type(),
158            kinds,
159            rule_indices,
160            end_states,
161            loop_back_states,
162            non_greedy,
163            left_recursive,
164            out,
165            interval_sets,
166            decisions,
167            rule_starts,
168            rule_stops,
169        })
170    }
171
172    /// Applies the rewrite and emits the packed result.
173    fn build(mut self) -> Result<ParserAtn, ParserAtnError> {
174        let rule_count = self.rule_starts.len();
175        let original_state_count = self.kinds.len();
176
177        // Reserve the three new states per rule up front so their numbers are
178        // known before any edge is rewired. Layout: for rule `i`,
179        // `bypass_start = base`, `bypass_stop = base + 1`, `match = base + 2`.
180        let new_state_base = original_state_count;
181        for rule in 0..rule_count {
182            let bypass_start = new_state_base + rule * 3;
183            let bypass_stop = bypass_start + 1;
184            let match_state = bypass_start + 2;
185            // Bypass start is a decision block start whose end is the stop.
186            self.push_state(
187                AtnStateKind::BlockStart,
188                Some(rule),
189                Some(bypass_stop),
190                None,
191            );
192            self.push_state(AtnStateKind::BlockEnd, Some(rule), None, None);
193            self.push_state(AtnStateKind::Basic, None, None, None);
194            debug_assert_eq!(self.out.len(), match_state + 1);
195        }
196
197        // Retarget/move plan per rule, computed against the *pre-move* graph so
198        // the left-recursive exclude transition is identified correctly. End
199        // states are captured here and reused below — recomputing them after
200        // the move/retarget passes would scan a mutated graph.
201        let mut end_states: Vec<usize> = Vec::with_capacity(rule_count);
202        let mut bypass_stop_for_end: Vec<Option<usize>> = vec![None; self.kinds.len()];
203        let mut excluded: BTreeSet<(usize, usize)> = BTreeSet::new();
204        for rule in 0..rule_count {
205            let bypass_stop = new_state_base + rule * 3 + 1;
206            let (end_state, exclude) = self.rule_end_state(rule)?;
207            end_states.push(end_state);
208            bypass_stop_for_end[end_state] = Some(bypass_stop);
209            if let Some(exclude) = exclude {
210                excluded.insert(exclude);
211            }
212        }
213
214        // Move each rule-start's transitions onto its bypass-start block.
215        for rule in 0..rule_count {
216            let rule_start = self.rule_starts[rule];
217            let bypass_start = new_state_base + rule * 3;
218            let moved = std::mem::take(&mut self.out[rule_start]);
219            self.out[bypass_start] = moved;
220        }
221
222        // Retarget every edge that targeted a rule's end state onto that rule's
223        // bypass stop, skipping the left-recursive exclude edge(s).
224        for (source, transitions) in self.out.iter_mut().enumerate() {
225            for (index, spec) in transitions.iter_mut().enumerate() {
226                if excluded.contains(&(source, index)) {
227                    continue;
228                }
229                if let Some(bypass_stop) = bypass_stop_for_end[spec.target()] {
230                    *spec = spec.with_target(bypass_stop);
231                }
232            }
233        }
234
235        // Add the bypass edges last so the `bypass_stop -> end_state` link
236        // (which targets an end state) is never caught by the retarget pass.
237        for (rule, &end_state) in end_states.iter().enumerate() {
238            let rule_start = self.rule_starts[rule];
239            let bypass_start = new_state_base + rule * 3;
240            let bypass_stop = bypass_start + 1;
241            let match_state = bypass_start + 2;
242            let imaginary = self.imaginary_token_type(rule)?;
243
244            self.out[rule_start].push(ParserTransitionSpec::Epsilon {
245                target: bypass_start,
246            });
247            self.out[bypass_start].push(ParserTransitionSpec::Epsilon {
248                target: match_state,
249            });
250            self.out[bypass_stop].push(ParserTransitionSpec::Epsilon { target: end_state });
251            self.out[match_state].push(ParserTransitionSpec::Atom {
252                target: bypass_stop,
253                label: imaginary,
254            });
255            self.decisions.push(bypass_start);
256        }
257
258        self.emit()
259    }
260
261    /// Appends one new state to the model, keeping every parallel array aligned.
262    fn push_state(
263        &mut self,
264        kind: AtnStateKind,
265        rule_index: Option<usize>,
266        end_state: Option<usize>,
267        loop_back_state: Option<usize>,
268    ) {
269        self.kinds.push(kind);
270        self.rule_indices.push(rule_index);
271        self.end_states.push(end_state);
272        self.loop_back_states.push(loop_back_state);
273        self.non_greedy.push(false);
274        self.left_recursive.push(false);
275        self.out.push(Vec::new());
276    }
277
278    /// Returns `(end_state, exclude_edge)` for a rule.
279    ///
280    /// For an ordinary rule the end state is the rule stop and there is no
281    /// excluded edge. For a left-recursive rule the end state is the
282    /// `StarLoopEntry` that begins the precedence-climbing loop, and the excluded
283    /// edge is the loop-back edge into it (which must keep pointing at the entry
284    /// rather than being diverted to the bypass block). Mirrors the
285    /// `isLeftRecursiveRule` branch in `ATNDeserializer`.
286    fn rule_end_state(
287        &self,
288        rule: usize,
289    ) -> Result<(usize, Option<(usize, usize)>), ParserAtnError> {
290        let rule_start = self.rule_starts[rule];
291        if !self.left_recursive[rule_start] {
292            return Ok((self.rule_stops[rule], None));
293        }
294
295        // Find the StarLoopEntry whose last edge reaches a LoopEnd that
296        // epsilon-transitions to the rule stop: that entry is the precedence
297        // prefix boundary ANTLR wraps.
298        for state in 0..self.kinds.len() {
299            if self.rule_indices[state] != Some(rule)
300                || self.kinds[state] != AtnStateKind::StarLoopEntry
301            {
302                continue;
303            }
304            let Some(last) = self.out[state].last() else {
305                continue;
306            };
307            let loop_end = last.target();
308            if self.kinds.get(loop_end).copied() != Some(AtnStateKind::LoopEnd) {
309                continue;
310            }
311            // Upstream additionally requires the loop end to be epsilon-only
312            // before trusting its first edge (`maybeLoopEndState
313            // .epsilonOnlyTransitions && ... instanceof RuleStopState`).
314            let epsilon_only = self.out[loop_end]
315                .iter()
316                .all(|edge| matches!(edge, ParserTransitionSpec::Epsilon { .. }));
317            let reaches_stop = self.out[loop_end]
318                .first()
319                .is_some_and(|edge| self.kinds.get(edge.target()) == Some(&AtnStateKind::RuleStop));
320            if !epsilon_only || !reaches_stop {
321                continue;
322            }
323            let Some(loop_back) = self.loop_back_states[loop_end] else {
324                continue;
325            };
326            // The loop-back state's first edge is the excluded loop-back into
327            // the entry; verify the structure before trusting it.
328            if self.out[loop_back]
329                .first()
330                .is_some_and(|edge| edge.target() == state)
331            {
332                return Ok((state, Some((loop_back, 0))));
333            }
334        }
335
336        Err(ParserAtnError::InvalidData(format!(
337            "could not identify precedence prefix boundary for left-recursive rule {rule}"
338        )))
339    }
340
341    /// Imaginary token type reserved for a rule's bypass alternative.
342    fn imaginary_token_type(&self, rule: usize) -> Result<i32, ParserAtnError> {
343        imaginary_token_type(self.max_token_type, rule)
344    }
345
346    /// Emits the mutable model as a validated packed [`ParserAtn`].
347    fn emit(self) -> Result<ParserAtn, ParserAtnError> {
348        let mut builder = ParserAtnBuilder::new(self.max_token_type);
349
350        for (index, &kind) in self.kinds.iter().enumerate() {
351            builder.add_state(kind, self.rule_indices[index])?;
352        }
353        for (index, end_state) in self.end_states.iter().enumerate() {
354            if let Some(end_state) = end_state {
355                builder.set_end_state(index, *end_state)?;
356            }
357        }
358        for (index, loop_back) in self.loop_back_states.iter().enumerate() {
359            if let Some(loop_back) = loop_back {
360                builder.set_loop_back_state(index, *loop_back)?;
361            }
362        }
363        for (index, &flag) in self.non_greedy.iter().enumerate() {
364            if flag {
365                builder.set_non_greedy(index)?;
366            }
367        }
368        for (index, &flag) in self.left_recursive.iter().enumerate() {
369            if flag {
370                builder.set_left_recursive_rule(index)?;
371            }
372        }
373        for ranges in &self.interval_sets {
374            builder.add_interval_set(ranges.iter().copied())?;
375        }
376        for (source, transitions) in self.out.iter().enumerate() {
377            for spec in transitions {
378                builder.add_transition(source, *spec)?;
379            }
380        }
381        for &state in &self.decisions {
382            builder.add_decision_state(state)?;
383        }
384        builder.set_rule_to_start_state(self.rule_starts)?;
385        builder.set_rule_to_stop_state(self.rule_stops)?;
386
387        builder.finish()
388    }
389}
390
391/// Converts a borrowing transition view into a builder spec, translating the
392/// borrowed interval set back into its stable identity.
393fn data_to_spec(data: ParserTransitionData<'_>) -> Result<ParserTransitionSpec, ParserAtnError> {
394    Ok(match data {
395        ParserTransitionData::Epsilon { target } => ParserTransitionSpec::Epsilon { target },
396        ParserTransitionData::Atom { target, label } => {
397            ParserTransitionSpec::Atom { target, label }
398        }
399        ParserTransitionData::Range {
400            target,
401            start,
402            stop,
403        } => ParserTransitionSpec::Range {
404            target,
405            start,
406            stop,
407        },
408        ParserTransitionData::Set { target, set } => ParserTransitionSpec::Set {
409            target,
410            set: ParserIntervalSetId::try_from(set.index())?,
411        },
412        ParserTransitionData::NotSet { target, set } => ParserTransitionSpec::NotSet {
413            target,
414            set: ParserIntervalSetId::try_from(set.index())?,
415        },
416        ParserTransitionData::Wildcard { target } => ParserTransitionSpec::Wildcard { target },
417        ParserTransitionData::Rule {
418            target,
419            rule_index,
420            follow_state,
421            precedence,
422        } => ParserTransitionSpec::Rule {
423            target,
424            rule_index,
425            follow_state,
426            precedence,
427        },
428        ParserTransitionData::Predicate {
429            target,
430            rule_index,
431            pred_index,
432            context_dependent,
433        } => ParserTransitionSpec::Predicate {
434            target,
435            rule_index,
436            pred_index,
437            context_dependent,
438        },
439        ParserTransitionData::Action {
440            target,
441            rule_index,
442            action_index,
443            context_dependent,
444        } => ParserTransitionSpec::Action {
445            target,
446            rule_index,
447            action_index,
448            context_dependent,
449        },
450        ParserTransitionData::Precedence { target, precedence } => {
451            ParserTransitionSpec::Precedence { target, precedence }
452        }
453    })
454}
455
456#[cfg(test)]
457#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
458mod tests {
459    use super::*;
460    use crate::atn::parser_atn::{ParserTransition, ParserTransitionKind};
461    use crate::token::{Token, TokenId, TokenSink, TokenSource, TokenSpec, TokenStoreError};
462    use crate::{BaseParser, CommonTokenStream, NodeKind, RecognizerData, Vocabulary};
463
464    /// A token source over a fixed list of specs, ending in EOF — the runtime
465    /// analog of ANTLR's `ListTokenSource` used to feed a hybrid pattern stream.
466    #[derive(Debug)]
467    struct ListSource {
468        specs: Vec<TokenSpec>,
469        index: usize,
470    }
471
472    impl TokenSource for ListSource {
473        fn next_token(&mut self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError> {
474            let spec = self
475                .specs
476                .get(self.index)
477                .cloned()
478                .unwrap_or_else(|| TokenSpec::eof(self.index, self.index, 1, self.index));
479            self.index += 1;
480            sink.push(spec)
481        }
482
483        fn line(&self) -> usize {
484            1
485        }
486
487        fn column(&self) -> usize {
488            self.index
489        }
490
491        fn source_name(&self) -> &'static str {
492            "bypass-test"
493        }
494    }
495
496    /// Two-rule grammar `a : X b ; b : Y ;` (tokens `X=1`, `Y=2`).
497    ///
498    /// Deliberately plain (no loops, no left recursion) so the bypass rewrite's
499    /// state growth and edge rewiring are easy to reason about.
500    fn two_rule_atn() -> ParserAtn {
501        let mut atn = ParserAtnBuilder::new(2);
502        for (number, kind, rule) in [
503            (0, AtnStateKind::RuleStart, 0),
504            (1, AtnStateKind::Basic, 0),
505            (2, AtnStateKind::Basic, 0),
506            (3, AtnStateKind::RuleStop, 0),
507            (4, AtnStateKind::RuleStart, 1),
508            (5, AtnStateKind::Basic, 1),
509            (6, AtnStateKind::RuleStop, 1),
510        ] {
511            assert_eq!(
512                atn.add_state(kind, Some(rule)).expect("state").index(),
513                number
514            );
515        }
516        atn.set_rule_to_start_state(vec![0, 4]).expect("starts");
517        atn.set_rule_to_stop_state(vec![3, 6]).expect("stops");
518        // a : X b ;
519        atn.add_transition(
520            0,
521            ParserTransitionSpec::Atom {
522                target: 1,
523                label: 1,
524            },
525        )
526        .expect("edge");
527        atn.add_transition(
528            1,
529            ParserTransitionSpec::Rule {
530                target: 4,
531                rule_index: 1,
532                follow_state: 2,
533                precedence: 0,
534            },
535        )
536        .expect("edge");
537        atn.add_transition(2, ParserTransitionSpec::Epsilon { target: 3 })
538            .expect("edge");
539        // Synthetic rule-return edge already present in a packed ATN.
540        atn.add_transition(6, ParserTransitionSpec::Epsilon { target: 2 })
541            .expect("edge");
542        // b : Y ;
543        atn.add_transition(
544            4,
545            ParserTransitionSpec::Atom {
546                target: 5,
547                label: 2,
548            },
549        )
550        .expect("edge");
551        atn.add_transition(5, ParserTransitionSpec::Epsilon { target: 6 })
552            .expect("edge");
553        atn.finish().expect("valid base ATN")
554    }
555
556    /// One left-recursive rule shaped like ANTLR's transformed `e : e '+' e | X`:
557    /// `RuleStart(LR) -> Basic(prefix) -> StarLoopEntry -> {StarBlockStart(body),
558    /// LoopEnd -> RuleStop}`, with the loop body returning through a
559    /// `StarLoopBack` whose sole edge re-enters the entry.
560    fn left_recursive_atn() -> ParserAtn {
561        let mut atn = ParserAtnBuilder::new(2);
562        for (number, kind, rule) in [
563            (0, AtnStateKind::RuleStart, 0),
564            (1, AtnStateKind::Basic, 0), // primary/prefix matcher
565            (2, AtnStateKind::StarLoopEntry, 0),
566            (3, AtnStateKind::Basic, 0), // loop body: '+' e
567            (4, AtnStateKind::StarLoopBack, 0),
568            (5, AtnStateKind::LoopEnd, 0),
569            (6, AtnStateKind::RuleStop, 0),
570        ] {
571            assert_eq!(
572                atn.add_state(kind, Some(rule)).expect("state").index(),
573                number
574            );
575        }
576        atn.set_left_recursive_rule(0).expect("LR flag");
577        atn.set_rule_to_start_state(vec![0]).expect("starts");
578        atn.set_rule_to_stop_state(vec![6]).expect("stops");
579        atn.set_loop_back_state(5, 4).expect("loop back");
580        atn.add_decision_state(2).expect("decision");
581        atn.add_transition(
582            0,
583            ParserTransitionSpec::Atom {
584                target: 1,
585                label: 1,
586            },
587        )
588        .expect("edge");
589        atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
590            .expect("edge");
591        atn.add_transition(2, ParserTransitionSpec::Epsilon { target: 3 })
592            .expect("edge");
593        atn.add_transition(2, ParserTransitionSpec::Epsilon { target: 5 })
594            .expect("edge");
595        atn.add_transition(
596            3,
597            ParserTransitionSpec::Atom {
598                target: 4,
599                label: 2,
600            },
601        )
602        .expect("edge");
603        // The loop-back edge that must stay pointed at the StarLoopEntry.
604        atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 2 })
605            .expect("edge");
606        atn.add_transition(5, ParserTransitionSpec::Epsilon { target: 6 })
607            .expect("edge");
608        atn.finish().expect("valid left-recursive ATN")
609    }
610
611    /// Pins the left-recursive branch of `rule_end_state`: the bypass block
612    /// wraps the precedence prefix (ending at the `StarLoopEntry`, not the rule
613    /// stop) and the loop-back edge is excluded from retargeting.
614    #[test]
615    fn bypass_wraps_left_recursive_prefix_and_preserves_loop_back() {
616        let base = left_recursive_atn();
617        let bypass = base.with_bypass_alternatives().expect("bypass ATN");
618
619        let star_loop_entry = 2;
620        let star_loop_back = 4;
621        let bypass_start = base.state_count();
622        let bypass_stop = bypass_start + 1;
623
624        // The prefix edge into the StarLoopEntry is retargeted to bypass stop…
625        let prefix_targets: Vec<_> = bypass
626            .state(1)
627            .expect("prefix state")
628            .transitions()
629            .iter()
630            .map(ParserTransition::target)
631            .collect();
632        assert_eq!(prefix_targets, vec![bypass_stop]);
633        // …while the loop-back edge still re-enters the StarLoopEntry.
634        let loop_back_targets: Vec<_> = bypass
635            .state(star_loop_back)
636            .expect("loop-back state")
637            .transitions()
638            .iter()
639            .map(ParserTransition::target)
640            .collect();
641        assert_eq!(loop_back_targets, vec![star_loop_entry]);
642        // The bypass stop rejoins the graph at the StarLoopEntry (the
643        // left-recursive "end state"), not at the rule stop.
644        let stop_targets: Vec<_> = bypass
645            .state(bypass_stop)
646            .expect("bypass stop")
647            .transitions()
648            .iter()
649            .map(ParserTransition::target)
650            .collect();
651        assert_eq!(stop_targets, vec![star_loop_entry]);
652    }
653
654    /// A rule flagged left-recursive whose precedence prefix boundary cannot be
655    /// identified must fail loudly instead of producing a broken bypass ATN.
656    #[test]
657    fn bypass_reports_unrecognizable_left_recursive_structure() {
658        let mut atn = ParserAtnBuilder::new(1);
659        for (number, kind) in [
660            (0, AtnStateKind::RuleStart),
661            (1, AtnStateKind::Basic),
662            (2, AtnStateKind::RuleStop),
663        ] {
664            assert_eq!(atn.add_state(kind, Some(0)).expect("state").index(), number);
665        }
666        // Flag the rule left-recursive without any StarLoopEntry structure.
667        atn.set_left_recursive_rule(0).expect("LR flag");
668        atn.set_rule_to_start_state(vec![0]).expect("starts");
669        atn.set_rule_to_stop_state(vec![2]).expect("stops");
670        atn.add_transition(
671            0,
672            ParserTransitionSpec::Atom {
673                target: 1,
674                label: 1,
675            },
676        )
677        .expect("edge");
678        atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
679            .expect("edge");
680        let base = atn.finish().expect("valid base ATN");
681
682        let error = base
683            .with_bypass_alternatives()
684            .expect_err("missing precedence prefix must be reported");
685        assert!(
686            error.to_string().contains("left-recursive rule 0"),
687            "unexpected error: {error}"
688        );
689    }
690
691    #[test]
692    fn bypass_grows_three_states_per_rule_and_keeps_max_token_type() {
693        let base = two_rule_atn();
694        let bypass = base.with_bypass_alternatives().expect("bypass ATN");
695
696        // Three new states per rule, appended after the originals.
697        assert_eq!(
698            bypass.state_count(),
699            base.state_count() + 3 * base.rule_count()
700        );
701        // Imaginary token types live above the *unchanged* maximum.
702        assert_eq!(bypass.max_token_type(), base.max_token_type());
703    }
704
705    #[test]
706    fn bypass_adds_one_imaginary_atom_per_rule() {
707        let base = two_rule_atn();
708        let bypass = base.with_bypass_alternatives().expect("bypass ATN");
709
710        // Rule i's imaginary token type is max_token_type + i + 1.
711        let mut imaginary_atoms = Vec::new();
712        for state in 0..bypass.state_count() {
713            for transition in bypass.state(state).expect("state").transitions() {
714                if transition.kind() == ParserTransitionKind::Atom {
715                    if let ParserTransitionData::Atom { label, .. } = transition.data() {
716                        if label > base.max_token_type() {
717                            imaginary_atoms.push(label);
718                        }
719                    }
720                }
721            }
722        }
723        imaginary_atoms.sort_unstable();
724        assert_eq!(imaginary_atoms, vec![3, 4]); // max(2) + {1, 2}
725    }
726
727    #[test]
728    fn bypass_preserves_rule_start_and_stop_tables() {
729        let base = two_rule_atn();
730        let bypass = base.with_bypass_alternatives().expect("bypass ATN");
731
732        let base_starts: Vec<_> = base.rule_to_start_state().into_iter().collect();
733        let bypass_starts: Vec<_> = bypass.rule_to_start_state().into_iter().collect();
734        assert_eq!(base_starts, bypass_starts);
735
736        let base_stops: Vec<_> = base.rule_to_stop_state().into_iter().collect();
737        let bypass_stops: Vec<_> = bypass.rule_to_stop_state().into_iter().collect();
738        assert_eq!(base_stops, bypass_stops);
739    }
740
741    #[test]
742    fn rule_start_gains_epsilon_into_bypass_block() {
743        let base = two_rule_atn();
744        let bypass = base.with_bypass_alternatives().expect("bypass ATN");
745
746        // Rule 0's start state (0) should now epsilon into its bypass-start
747        // block (the first appended state), added as the last outgoing edge.
748        let bypass_start_for_rule_0 = base.state_count();
749        let rule_start = bypass.state(0).expect("rule start");
750        let epsilon_targets: Vec<_> = rule_start
751            .transitions()
752            .iter()
753            .filter(|t| t.kind() == ParserTransitionKind::Epsilon)
754            .map(ParserTransition::target)
755            .collect();
756        assert!(
757            epsilon_targets.contains(&bypass_start_for_rule_0),
758            "rule start must epsilon into its bypass block; got {epsilon_targets:?}"
759        );
760    }
761
762    fn two_rule_recognizer_data() -> RecognizerData {
763        RecognizerData::new(
764            "Bypass.g4",
765            Vocabulary::new(
766                [None, Some("'x'"), Some("'y'")],
767                [None, Some("X"), Some("Y")],
768                [None::<&str>, None],
769            ),
770        )
771        .with_rule_names(["a", "b"])
772    }
773
774    /// The load-bearing end-to-end proof: the *unchanged* ATN interpreter, run
775    /// over the bypass ATN, matches a single imaginary token in place of a whole
776    /// rule and renders it as a one-terminal rule subtree — exactly the shape
777    /// ANTLR's `getRuleTagToken` detects.
778    #[test]
779    fn interpreter_matches_imaginary_token_as_whole_rule() {
780        let bypass = two_rule_atn()
781            .with_bypass_alternatives()
782            .expect("bypass ATN");
783        // Rule 1 ("b")'s imaginary token type = max_token_type(2) + 1 + 1 = 4.
784        let imaginary_b = 4;
785        let source = ListSource {
786            specs: vec![
787                TokenSpec::explicit(1, "x"),             // real X token
788                TokenSpec::explicit(imaginary_b, "<b>"), // imaginary rule-b tag
789            ],
790            index: 0,
791        };
792        let mut parser =
793            BaseParser::new(CommonTokenStream::new(source), two_rule_recognizer_data());
794
795        let tree = parser
796            .parse_atn_rule(&bypass, 0)
797            .expect("bypass interpret of `a : X b` with imaginary b");
798
799        let root = parser.node(tree).as_rule().expect("root is rule a");
800        assert_eq!(root.rule_index(), 0);
801        let children: Vec<_> = root.node().children().collect();
802        assert_eq!(children.len(), 2, "a has children [X, b]");
803
804        // First child: the real X terminal.
805        let x = children[0].as_terminal().expect("first child terminal X");
806        assert_eq!(x.symbol().token_type(), 1);
807
808        // Second child: rule b rendered as a single-terminal subtree whose lone
809        // leaf carries the imaginary token type. This is the `(b <b>)` shape.
810        let b = children[1].as_rule().expect("second child rule b");
811        assert_eq!(b.rule_index(), 1);
812        let b_children: Vec<_> = b.node().children().collect();
813        assert_eq!(b_children.len(), 1, "bypassed rule b has exactly one child");
814        assert_eq!(b_children[0].kind(), NodeKind::Terminal);
815        assert_eq!(
816            b_children[0]
817                .as_terminal()
818                .expect("b's lone child is a terminal")
819                .symbol()
820                .token_type(),
821            imaginary_b,
822            "the lone child carries the imaginary bypass token type"
823        );
824    }
825
826    /// The bypass ATN must still parse ordinary input identically: feeding the
827    /// real tokens `X Y` reconstructs `(a X (b Y))` with no imaginary tokens.
828    #[test]
829    fn bypass_atn_still_parses_ordinary_input() {
830        let bypass = two_rule_atn()
831            .with_bypass_alternatives()
832            .expect("bypass ATN");
833        let source = ListSource {
834            specs: vec![TokenSpec::explicit(1, "x"), TokenSpec::explicit(2, "y")],
835            index: 0,
836        };
837        let mut parser =
838            BaseParser::new(CommonTokenStream::new(source), two_rule_recognizer_data());
839
840        let tree = parser
841            .parse_atn_rule(&bypass, 0)
842            .expect("bypass interpret of ordinary `X Y`");
843
844        let root = parser.node(tree).as_rule().expect("root is rule a");
845        let children: Vec<_> = root.node().children().collect();
846        assert_eq!(children.len(), 2);
847        assert_eq!(
848            children[0]
849                .as_terminal()
850                .expect("X terminal")
851                .symbol()
852                .token_type(),
853            1
854        );
855        let b = children[1].as_rule().expect("rule b");
856        let y = b
857            .node()
858            .children()
859            .next()
860            .expect("b child")
861            .as_terminal()
862            .expect("Y terminal");
863        assert_eq!(y.symbol().token_type(), 2);
864        assert_eq!(parser.number_of_syntax_errors(), 0);
865    }
866}