Skip to main content

rusty_alto/
materialize.rs

1//! Algorithms that explore implicit automata and materialize intersections.
2
3use crate::{
4    Arity, BottomUpTa, CondensedTa, CondensedTopDownTa, Explicit, ExplicitBuilder, FxHashSet,
5    Interner, KeySet, Memo, ParseControl, SetTrie, StateId, Symbol, SymbolSet,
6    control::ParseCancelled, run::cartesian_product,
7};
8use fixedbitset::FixedBitSet;
9use smallvec::SmallVec;
10use std::collections::VecDeque;
11use std::hash::Hash;
12
13type FxHashMap<K, V> = hashbrown::HashMap<K, V, rustc_hash::FxBuildHasher>;
14
15#[cfg(feature = "stats")]
16macro_rules! stat_inc {
17    ($stats:expr, $field:ident) => {
18        $stats.$field += 1;
19    };
20}
21
22#[cfg(not(feature = "stats"))]
23macro_rules! stat_inc {
24    ($stats:expr, $field:ident) => {};
25}
26
27/// Explore a finite automaton and return an equivalent explicit fragment.
28///
29/// `materialize` starts from all nullary symbols in `alphabet`, repeatedly
30/// queries transitions over already discovered states, and freezes every
31/// queried rule into an [`Explicit`] automaton. The returned [`Interner`] maps
32/// the explicit [`StateId`] values back to the original state type.
33///
34/// The caller must provide a finite alphabet as `(symbol, arity)` pairs. The
35/// construction terminates when the reachable state space is finite. If the
36/// implicit automaton can keep producing fresh states forever, this function
37/// will also keep exploring.
38///
39/// Arity 0, 1, and 2 are handled directly. Higher arities are supported but can
40/// be expensive because the number of state tuples grows exponentially.
41pub fn materialize<A: BottomUpTa>(
42    a: &A,
43    alphabet: &[(Symbol, Arity)],
44) -> (Explicit, Interner<A::State>) {
45    let memo = Memo::new(a);
46    let mut known = Vec::<StateId>::new();
47    let mut known_bits = FixedBitSet::new();
48    let mut worklist = Vec::<StateId>::new();
49
50    for &(symbol, arity) in alphabet {
51        if arity == 0 {
52            collect_step(
53                &memo,
54                symbol,
55                &[],
56                &mut known,
57                &mut known_bits,
58                &mut worklist,
59            );
60        }
61    }
62
63    while let Some(popped) = worklist.pop() {
64        let snapshot = known.clone();
65        for &(symbol, arity) in alphabet {
66            match arity {
67                0 => {}
68                1 => collect_step(
69                    &memo,
70                    symbol,
71                    &[popped],
72                    &mut known,
73                    &mut known_bits,
74                    &mut worklist,
75                ),
76                2 => {
77                    for &other in &snapshot {
78                        collect_step(
79                            &memo,
80                            symbol,
81                            &[popped, other],
82                            &mut known,
83                            &mut known_bits,
84                            &mut worklist,
85                        );
86                        if other != popped {
87                            collect_step(
88                                &memo,
89                                symbol,
90                                &[other, popped],
91                                &mut known,
92                                &mut known_bits,
93                                &mut worklist,
94                            );
95                        }
96                    }
97                }
98                n => {
99                    let pools = vec![snapshot.as_slice(); n as usize];
100                    cartesian_product(&pools, |tuple| {
101                        if tuple.contains(&popped) {
102                            collect_step(
103                                &memo,
104                                symbol,
105                                tuple,
106                                &mut known,
107                                &mut known_bits,
108                                &mut worklist,
109                            );
110                        }
111                    });
112                }
113            }
114        }
115    }
116
117    memo.into_explicit()
118}
119
120fn collect_step<A: BottomUpTa>(
121    memo: &Memo<&A>,
122    symbol: Symbol,
123    children: &[StateId],
124    known: &mut Vec<StateId>,
125    known_bits: &mut FixedBitSet,
126    worklist: &mut Vec<StateId>,
127) {
128    memo.step(symbol, children, &mut |q| {
129        if !known_bits.contains(q.index()) {
130            known_bits.grow(q.index() + 1);
131            known_bits.set(q.index(), true);
132            known.push(q);
133            worklist.push(q);
134        }
135    });
136}
137
138/// Counters collected by [`materialize_indexed_condensed_intersection`].
139#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
140pub struct IndexedCondensedIntersectionStats {
141    /// Number of product states in the materialized intersection.
142    pub output_states: usize,
143    /// Number of product rules in the materialized intersection.
144    pub output_rules: usize,
145    /// Number of right-side condensed nullary rule shapes visited.
146    pub right_nullary_rules: usize,
147    /// Number of right-side indexed condensed queries issued.
148    pub right_indexed_queries: usize,
149    /// Number of product states popped from the work queue.
150    #[cfg(feature = "stats")]
151    pub queue_pops: usize,
152    /// Number of left-rule child occurrences considered from reached product states.
153    #[cfg(feature = "stats")]
154    pub left_occurrences_considered: usize,
155    /// Number of right condensed rules scanned while joining with left occurrences.
156    #[cfg(feature = "stats")]
157    pub right_rules_scanned: usize,
158    /// Number of candidate pairs that matched on symbol and arity.
159    #[cfg(feature = "stats")]
160    pub symbol_arity_matches: usize,
161    /// Number of candidate pairs whose child product states already existed.
162    #[cfg(feature = "stats")]
163    pub child_tuple_matches: usize,
164}
165
166impl IndexedCondensedIntersectionStats {
167    /// Total number of right-side nullary shapes plus indexed queries.
168    pub fn right_queries(&self) -> usize {
169        self.right_nullary_rules + self.right_indexed_queries
170    }
171}
172
173#[derive(Clone)]
174pub(crate) struct OwnedRule {
175    pub(crate) symbol: Symbol,
176    pub(crate) children: SmallVec<[StateId; 2]>,
177    pub(crate) result: StateId,
178    pub(crate) weight: f64,
179}
180
181#[derive(Clone)]
182pub(crate) struct OwnedCondensedRule<S> {
183    pub(crate) children: SmallVec<[S; 2]>,
184    pub(crate) symbols: SymbolSet,
185    pub(crate) result: S,
186}
187
188#[derive(Clone, Debug, Default)]
189pub(crate) struct ProductStateMap {
190    by_right: Vec<FxHashMap<StateId, StateId>>,
191}
192
193impl ProductStateMap {
194    fn new() -> Self {
195        Self::default()
196    }
197
198    pub(crate) fn get(&self, left: StateId, right: StateId) -> Option<StateId> {
199        self.by_right
200            .get(right.index())
201            .and_then(|partners| partners.get(&left).copied())
202    }
203
204    pub(crate) fn insert(&mut self, left: StateId, right: StateId, product: StateId) {
205        if self.by_right.len() <= right.index() {
206            self.by_right
207                .resize_with(right.index() + 1, FxHashMap::default);
208        }
209        self.by_right[right.index()].insert(left, product);
210    }
211}
212
213#[derive(Debug, Default)]
214pub(crate) struct TrustedRuleTracker {
215    #[cfg(debug_assertions)]
216    seen: FxHashSet<(Symbol, SmallVec<[StateId; 2]>, StateId)>,
217}
218
219impl TrustedRuleTracker {
220    pub(crate) fn add_rule(
221        &mut self,
222        builder: &mut ExplicitBuilder,
223        symbol: Symbol,
224        children: SmallVec<[StateId; 2]>,
225        parent: StateId,
226        weight: f64,
227    ) {
228        #[cfg(debug_assertions)]
229        {
230            let key = (symbol, children.clone(), parent);
231            debug_assert!(
232                self.seen.insert(key),
233                "trusted materializer generated a duplicate transition"
234            );
235        }
236
237        builder.add_weighted_rule_inline(symbol, children, parent, weight);
238    }
239}
240
241#[derive(Clone, Copy)]
242enum StateSetView<'a> {
243    One(StateId),
244    Many(&'a FxHashSet<StateId>),
245}
246
247impl KeySet<StateId> for StateSetView<'_> {
248    fn len(&self) -> usize {
249        match self {
250            StateSetView::One(_) => 1,
251            StateSetView::Many(states) => states.len(),
252        }
253    }
254
255    fn contains(&self, key: &StateId) -> bool {
256        match self {
257            StateSetView::One(state) => state == key,
258            StateSetView::Many(states) => states.contains(key),
259        }
260    }
261
262    fn for_each(&self, out: &mut dyn FnMut(&StateId)) {
263        match self {
264            StateSetView::One(state) => out(state),
265            StateSetView::Many(states) => {
266                for state in *states {
267                    out(state);
268                }
269            }
270        }
271    }
272}
273
274#[derive(Default)]
275pub(crate) struct LeftIndex {
276    nullary_by_symbol: FxHashMap<Symbol, Vec<usize>>,
277    pub(crate) by_state: FxHashMap<StateId, Vec<(Symbol, usize, usize)>>,
278    by_children: SetTrie<StateId, FxHashMap<Symbol, Vec<usize>>>,
279    by_rotated_children: Vec<SetTrie<StateId, FxHashMap<Symbol, Vec<usize>>>>,
280}
281
282impl LeftIndex {
283    pub(crate) fn build(rules: &[OwnedRule]) -> Self {
284        Self::build_filtered(rules, |_, _| true)
285    }
286
287    pub(crate) fn build_filtered(
288        rules: &[OwnedRule],
289        mut include: impl FnMut(usize, &OwnedRule) -> bool,
290    ) -> Self {
291        let mut index = Self::default();
292        for (rule_idx, rule) in rules.iter().enumerate() {
293            if !include(rule_idx, rule) {
294                continue;
295            }
296            index
297                .by_children
298                .get_or_insert_with(&rule.children, FxHashMap::default)
299                .entry(rule.symbol)
300                .or_default()
301                .push(rule_idx);
302            if rule.children.is_empty() {
303                index
304                    .nullary_by_symbol
305                    .entry(rule.symbol)
306                    .or_default()
307                    .push(rule_idx);
308            }
309            for (position, &child) in rule.children.iter().enumerate() {
310                index
311                    .by_state
312                    .entry(child)
313                    .or_default()
314                    .push((rule.symbol, position, rule_idx));
315                if index.by_rotated_children.len() <= position {
316                    index
317                        .by_rotated_children
318                        .resize_with(position + 1, SetTrie::default);
319                }
320                let mut rotated_children = SmallVec::<[StateId; 4]>::new();
321                rotated_children.push(child);
322                rotated_children.extend(rule.children[..position].iter().copied());
323                rotated_children.extend(rule.children[position + 1..].iter().copied());
324                index.by_rotated_children[position]
325                    .get_or_insert_with(&rotated_children, FxHashMap::default)
326                    .entry(rule.symbol)
327                    .or_default()
328                    .push(rule_idx);
329            }
330        }
331        index
332    }
333
334    pub(crate) fn rule_indexes_for_sets_into<S>(
335        &self,
336        symbols: &SymbolSet,
337        child_sets: &[S],
338        out: &mut Vec<usize>,
339    ) where
340        S: KeySet<StateId>,
341    {
342        out.clear();
343        self.by_children
344            .for_each_value_for_key_sets(child_sets, |rules_by_symbol| {
345                if symbols.len() < rules_by_symbol.len() {
346                    for symbol in symbols.iter() {
347                        if let Some(rule_indexes) = rules_by_symbol.get(&symbol) {
348                            out.extend(rule_indexes.iter().copied());
349                        }
350                    }
351                } else {
352                    for (&symbol, rule_indexes) in rules_by_symbol {
353                        if symbols.contains(symbol) {
354                            out.extend(rule_indexes.iter().copied());
355                        }
356                    }
357                }
358            });
359    }
360
361    fn extend_symbol_matches(
362        symbols: &SymbolSet,
363        rules_by_symbol: &FxHashMap<Symbol, Vec<usize>>,
364        out: &mut Vec<usize>,
365    ) {
366        if symbols.len() < rules_by_symbol.len() {
367            for symbol in symbols.iter() {
368                if let Some(rule_indexes) = rules_by_symbol.get(&symbol) {
369                    out.extend(rule_indexes.iter().copied());
370                }
371            }
372        } else {
373            for (&symbol, rule_indexes) in rules_by_symbol {
374                if symbols.contains(symbol) {
375                    out.extend(rule_indexes.iter().copied());
376                }
377            }
378        }
379    }
380
381    #[allow(dead_code)]
382    pub(crate) fn rule_indexes_for_rotated_sets_into<S>(
383        &self,
384        trigger_position: usize,
385        symbols: &SymbolSet,
386        rotated_child_sets: &[S],
387        out: &mut Vec<usize>,
388    ) where
389        S: KeySet<StateId>,
390    {
391        out.clear();
392        let Some(trie) = self.by_rotated_children.get(trigger_position) else {
393            return;
394        };
395        trie.for_each_value_for_key_sets(rotated_child_sets, |rules_by_symbol| {
396            Self::extend_symbol_matches(symbols, rules_by_symbol, out);
397        });
398    }
399
400    pub(crate) fn rule_indexes_for_rotated_trigger_sets_into<S>(
401        &self,
402        trigger_position: usize,
403        trigger_left: StateId,
404        symbols: &SymbolSet,
405        sibling_sets: &[S],
406        out: &mut Vec<usize>,
407    ) where
408        S: KeySet<StateId>,
409    {
410        out.clear();
411        let Some(trie) = self.by_rotated_children.get(trigger_position) else {
412            return;
413        };
414        trie.for_each_value_for_prefix_and_key_sets(
415            &[trigger_left],
416            sibling_sets,
417            |rules_by_symbol| {
418                Self::extend_symbol_matches(symbols, rules_by_symbol, out);
419            },
420        );
421    }
422}
423
424/// A matched nullary edge from `for_each_nullary_edge`.
425pub(crate) struct NullaryEdge {
426    pub(crate) rule_index: usize,
427    pub(crate) parent_left: StateId,
428    pub(crate) parent_right: StateId,
429    pub(crate) symbol: Symbol,
430    pub(crate) weight: f64,
431}
432
433/// A candidate non-nullary edge from `for_each_candidate_edge`.
434pub(crate) struct CandidateEdge {
435    pub(crate) parent_left: StateId,
436    pub(crate) parent_right: StateId,
437    pub(crate) children: SmallVec<[StateId; 2]>,
438    pub(crate) weight: f64,
439    pub(crate) symbol: Symbol,
440    pub(crate) trigger_position: usize,
441}
442
443/// Intern all right-side nullary results and match them against left nullary
444/// rules. Calls `on_edge` for every matched (left_rule, right_result) pair.
445/// Increments `stats.right_nullary_rules` once per right condensed nullary
446/// shape.
447pub(crate) trait StateInterner<T> {
448    fn intern(&mut self, state: T) -> StateId;
449}
450
451impl<T> StateInterner<T> for Interner<T>
452where
453    T: Clone + Eq + Hash,
454{
455    fn intern(&mut self, state: T) -> StateId {
456        Interner::intern(self, state)
457    }
458}
459
460pub(crate) fn for_each_nullary_edge<R, I>(
461    left_rules: &[OwnedRule],
462    left_index: &LeftIndex,
463    right: &R,
464    right_interner: &mut I,
465    stats: &mut IndexedCondensedIntersectionStats,
466    control: Option<&ParseControl>,
467    on_edge: &mut dyn FnMut(NullaryEdge),
468) where
469    R: CondensedTa,
470    R::State: Clone + Eq + Hash,
471    I: StateInterner<R::State>,
472{
473    right.condensed_nullary_rules(&mut |symbols, right_result| {
474        if control.is_some_and(ParseControl::is_cancelled) {
475            return;
476        }
477        stats.right_nullary_rules += 1;
478        let right_result = right_interner.intern(right_result);
479        for symbol in symbols.iter() {
480            let Some(left_rule_indexes) = left_index.nullary_by_symbol.get(&symbol) else {
481                continue;
482            };
483            for &left_rule_idx in left_rule_indexes {
484                if control.is_some_and(ParseControl::is_cancelled) {
485                    return;
486                }
487                let left_rule = &left_rules[left_rule_idx];
488                on_edge(NullaryEdge {
489                    rule_index: left_rule_idx,
490                    parent_left: left_rule.result,
491                    parent_right: right_result,
492                    symbol,
493                    weight: left_rule.weight,
494                });
495            }
496        }
497    });
498}
499
500/// For a single trigger product state, assemble candidate non-nullary edges.
501/// Queries right condensed rules indexed by `(trigger_position, trigger_right)`,
502/// caches results, and calls `on_edge` for every candidate where all sibling
503/// children already have product IDs. Does not check `current_product_is_latest`
504/// or intern the parent — those are caller responsibilities.
505///
506/// Increments `stats.right_indexed_queries` on cache misses.
507#[allow(clippy::too_many_arguments)]
508pub(crate) fn for_each_candidate_edge<R: CondensedTa>(
509    trigger_left: StateId,
510    trigger_right: StateId,
511    trigger_product: StateId,
512    left_rules: &[OwnedRule],
513    left_index: &LeftIndex,
514    product_ids: &ProductStateMap,
515    right: &R,
516    right_interner: &mut Interner<R::State>,
517    right_by_child_cache: &mut FxHashMap<(usize, StateId), Vec<OwnedCondensedRule<StateId>>>,
518    stats: &mut IndexedCondensedIntersectionStats,
519    control: Option<&ParseControl>,
520    on_edge: &mut dyn FnMut(CandidateEdge),
521) where
522    R::State: Clone + Eq + Hash,
523{
524    let Some(left_occurrences) = left_index.by_state.get(&trigger_left) else {
525        return;
526    };
527
528    for &(symbol, position, left_rule_idx) in left_occurrences {
529        if control.is_some_and(ParseControl::is_cancelled) {
530            return;
531        }
532        stat_inc!(stats, left_occurrences_considered);
533        let left_rule = &left_rules[left_rule_idx];
534        let cache_key = (position, trigger_right);
535        let right_rules = right_by_child_cache.entry(cache_key).or_insert_with(|| {
536            stats.right_indexed_queries += 1;
537            let raw_state = right_interner.resolve(trigger_right).clone();
538            let mut collected = Vec::new();
539            right.condensed_rules_by_child(
540                position,
541                &raw_state,
542                &mut |children, symbols, result| {
543                    if control.is_some_and(ParseControl::is_cancelled) {
544                        return;
545                    }
546                    collected.push(OwnedCondensedRule {
547                        children: children
548                            .iter()
549                            .cloned()
550                            .map(|child| right_interner.intern(child))
551                            .collect(),
552                        symbols: symbols.clone(),
553                        result: right_interner.intern(result),
554                    });
555                },
556            );
557            collected
558        });
559
560        for right_rule in right_rules {
561            if control.is_some_and(ParseControl::is_cancelled) {
562                return;
563            }
564            stat_inc!(stats, right_rules_scanned);
565            if !right_rule.symbols.contains(symbol)
566                || right_rule.children.len() != left_rule.children.len()
567            {
568                continue;
569            }
570            stat_inc!(stats, symbol_arity_matches);
571
572            let mut children = SmallVec::<[StateId; 2]>::new();
573            let mut ok = true;
574            for (child_position, (&left_child, &right_child)) in left_rule
575                .children
576                .iter()
577                .zip(&right_rule.children)
578                .enumerate()
579            {
580                if child_position == position
581                    && left_child == trigger_left
582                    && right_child == trigger_right
583                {
584                    children.push(trigger_product);
585                } else if let Some(child) = product_ids.get(left_child, right_child) {
586                    children.push(child);
587                } else {
588                    ok = false;
589                    break;
590                }
591            }
592            if !ok {
593                continue;
594            }
595            stat_inc!(stats, child_tuple_matches);
596
597            on_edge(CandidateEdge {
598                parent_left: left_rule.result,
599                parent_right: right_rule.result,
600                children,
601                weight: left_rule.weight,
602                symbol,
603                trigger_position: position,
604            });
605        }
606    }
607}
608
609/// Materialize the intersection of an explicit grammar automaton with a
610/// condensed right automaton using indexed condensed right-side queries.
611///
612/// The right automaton is not eagerly enumerated. Nullary rules are driven from
613/// right condensed nullary shapes, and non-nullary rules are queried by
614/// `(child_position, right_child_state)` as product states become reachable.
615/// The returned interner maps the right component of product states back to
616/// right automaton states; output state IDs are dense product-state IDs.
617pub fn materialize_indexed_condensed_intersection<R>(
618    left: &Explicit,
619    right: &R,
620) -> (
621    Explicit,
622    Interner<R::State>,
623    IndexedCondensedIntersectionStats,
624)
625where
626    R: CondensedTa,
627    R::State: Clone + Eq + Hash,
628{
629    let (explicit, right_interner, _pairs, stats) =
630        materialize_indexed_condensed_intersection_with_pairs(left, right);
631    (explicit, right_interner, stats)
632}
633
634/// Like [`materialize_indexed_condensed_intersection`], but also returns the
635/// `product_pairs` mapping: `pairs[s.index()] = (left_state, right_state)` for
636/// every output product state `s`, where `right_state` resolves through the
637/// returned interner. Used by analysis tooling (e.g. the F-heuristic probe) that
638/// needs to recover the `(grammar state, right state)` identity of each fine
639/// product state.
640#[allow(clippy::type_complexity)]
641pub fn materialize_indexed_condensed_intersection_with_pairs<R>(
642    left: &Explicit,
643    right: &R,
644) -> (
645    Explicit,
646    Interner<R::State>,
647    Vec<(StateId, StateId)>,
648    IndexedCondensedIntersectionStats,
649)
650where
651    R: CondensedTa,
652    R::State: Clone + Eq + Hash,
653{
654    materialize_indexed_condensed_intersection_with_pairs_controlled(
655        left,
656        right,
657        &ParseControl::new(),
658    )
659    .expect("a fresh parse control cannot be cancelled")
660}
661
662#[allow(clippy::type_complexity)]
663pub(crate) fn materialize_indexed_condensed_intersection_with_pairs_controlled<R>(
664    left: &Explicit,
665    right: &R,
666    control: &ParseControl,
667) -> Result<
668    (
669        Explicit,
670        Interner<R::State>,
671        Vec<(StateId, StateId)>,
672        IndexedCondensedIntersectionStats,
673    ),
674    ParseCancelled,
675>
676where
677    R: CondensedTa,
678    R::State: Clone + Eq + Hash,
679{
680    control.check()?;
681    let left_rules: Vec<_> = left
682        .rules()
683        .map(|rule| OwnedRule {
684            symbol: rule.symbol,
685            children: rule.children.iter().copied().collect(),
686            result: rule.result,
687            weight: rule.weight,
688        })
689        .collect();
690    let left_index = LeftIndex::build(&left_rules);
691
692    let mut right_interner = Interner::new();
693    let mut product_ids = ProductStateMap::new();
694    let mut product_pairs = Vec::<(StateId, StateId)>::new();
695    let mut queue = VecDeque::<(StateId, StateId, StateId)>::new();
696    let mut builder = ExplicitBuilder::new();
697    let mut rule_tracker = TrustedRuleTracker::default();
698    let mut right_by_child_cache =
699        FxHashMap::<(usize, StateId), Vec<OwnedCondensedRule<StateId>>>::default();
700    let mut stats = IndexedCondensedIntersectionStats::default();
701
702    // Collect nullary edges first, then apply them (avoids borrow conflict on
703    // right_interner which is both mutated by the helper and read in get_or_create_product_id).
704    let mut nullary_edges = Vec::<NullaryEdge>::new();
705    for_each_nullary_edge(
706        &left_rules,
707        &left_index,
708        right,
709        &mut right_interner,
710        &mut stats,
711        Some(control),
712        &mut |edge| nullary_edges.push(edge),
713    );
714    control.check()?;
715    for edge in nullary_edges {
716        control.check()?;
717        let (parent, is_new) = get_or_create_product_id(
718            edge.parent_left,
719            edge.parent_right,
720            left,
721            right,
722            &mut product_ids,
723            &mut product_pairs,
724            &right_interner,
725            &mut builder,
726        );
727        if is_new {
728            queue.push_back((edge.parent_left, edge.parent_right, parent));
729        }
730        rule_tracker.add_rule(
731            &mut builder,
732            edge.symbol,
733            SmallVec::new(),
734            parent,
735            edge.weight,
736        );
737    }
738
739    while let Some((left_state, right_state, current_product)) = queue.pop_front() {
740        control.check()?;
741        stat_inc!(stats, queue_pops);
742
743        // Collect candidate edges first, then apply them (avoids borrow conflicts on
744        // product_ids and right_interner which are both read/mutated by the helper and
745        // the driver closure).
746        let mut candidate_edges = Vec::<CandidateEdge>::new();
747        for_each_candidate_edge(
748            left_state,
749            right_state,
750            current_product,
751            &left_rules,
752            &left_index,
753            &product_ids,
754            right,
755            &mut right_interner,
756            &mut right_by_child_cache,
757            &mut stats,
758            Some(control),
759            &mut |edge| candidate_edges.push(edge),
760        );
761        control.check()?;
762        for edge in candidate_edges {
763            control.check()?;
764            if !current_product_is_latest(&edge.children, edge.trigger_position, current_product) {
765                continue;
766            }
767
768            let (parent, is_new) = get_or_create_product_id(
769                edge.parent_left,
770                edge.parent_right,
771                left,
772                right,
773                &mut product_ids,
774                &mut product_pairs,
775                &right_interner,
776                &mut builder,
777            );
778            if is_new {
779                queue.push_back((edge.parent_left, edge.parent_right, parent));
780            }
781            rule_tracker.add_rule(
782                &mut builder,
783                edge.symbol,
784                edge.children,
785                parent,
786                edge.weight,
787            );
788        }
789    }
790
791    stats.output_states = product_pairs.len();
792    let explicit = builder.build_trusted();
793    stats.output_rules = explicit.rules().count();
794    Ok((explicit, right_interner, product_pairs, stats))
795}
796
797/// Materialize the intersection using parent-indexed condensed right queries.
798///
799/// This mirrors Alto's condensed CKY-style intersection: traverse reachable
800/// states of the right automaton from its top-down initial states, recursively
801/// compute left partner states for right children, then join right condensed
802/// rules against left grammar rules whose children are in those partner sets.
803pub fn materialize_topdown_condensed_intersection<R>(
804    left: &Explicit,
805    right: &R,
806) -> (
807    Explicit,
808    Interner<R::State>,
809    IndexedCondensedIntersectionStats,
810)
811where
812    R: CondensedTopDownTa,
813    R::State: Clone + Eq + Hash,
814{
815    let (explicit, right_interner, _pairs, stats) =
816        materialize_topdown_condensed_intersection_with_pairs(left, right);
817    (explicit, right_interner, stats)
818}
819
820/// Like [`materialize_topdown_condensed_intersection`], but also returns the
821/// product-state identities in output-state order.
822#[allow(clippy::type_complexity)]
823pub fn materialize_topdown_condensed_intersection_with_pairs<R>(
824    left: &Explicit,
825    right: &R,
826) -> (
827    Explicit,
828    Interner<R::State>,
829    Vec<(StateId, StateId)>,
830    IndexedCondensedIntersectionStats,
831)
832where
833    R: CondensedTopDownTa,
834    R::State: Clone + Eq + Hash,
835{
836    materialize_topdown_condensed_intersection_with_pairs_controlled(
837        left,
838        right,
839        &ParseControl::new(),
840    )
841    .expect("a fresh parse control cannot be cancelled")
842}
843
844#[allow(clippy::type_complexity)]
845pub(crate) fn materialize_topdown_condensed_intersection_with_pairs_controlled<R>(
846    left: &Explicit,
847    right: &R,
848    control: &ParseControl,
849) -> Result<
850    (
851        Explicit,
852        Interner<R::State>,
853        Vec<(StateId, StateId)>,
854        IndexedCondensedIntersectionStats,
855    ),
856    ParseCancelled,
857>
858where
859    R: CondensedTopDownTa,
860    R::State: Clone + Eq + Hash,
861{
862    control.check()?;
863    let left_rules: Vec<_> = left
864        .rules()
865        .map(|rule| OwnedRule {
866            symbol: rule.symbol,
867            children: rule.children.iter().copied().collect(),
868            result: rule.result,
869            weight: rule.weight,
870        })
871        .collect();
872    let left_index = LeftIndex::build(&left_rules);
873
874    let mut ctx = TopDownIntersection {
875        left,
876        right,
877        left_rules: &left_rules,
878        left_index: &left_index,
879        right_interner: Interner::new(),
880        product_ids: ProductStateMap::new(),
881        product_pairs: Vec::new(),
882        builder: ExplicitBuilder::new(),
883        rule_tracker: TrustedRuleTracker::default(),
884        partners: Vec::new(),
885        visited: FixedBitSet::new(),
886        matches_scratch: Vec::new(),
887        stats: IndexedCondensedIntersectionStats::default(),
888        control,
889    };
890
891    right.condensed_initial_states(&mut |q| {
892        if control.is_cancelled() {
893            return;
894        }
895        let q = ctx.intern_right(q);
896        ctx.visit(q);
897    });
898    control.check()?;
899
900    ctx.stats.output_states = ctx.product_pairs.len();
901    let explicit = ctx.builder.build_trusted();
902    ctx.stats.output_rules = explicit.rules().count();
903    Ok((explicit, ctx.right_interner, ctx.product_pairs, ctx.stats))
904}
905
906struct TopDownIntersection<'a, R>
907where
908    R: CondensedTopDownTa,
909    R::State: Clone + Eq + Hash,
910{
911    left: &'a Explicit,
912    right: &'a R,
913    left_rules: &'a [OwnedRule],
914    left_index: &'a LeftIndex,
915    right_interner: Interner<R::State>,
916    product_ids: ProductStateMap,
917    product_pairs: Vec<(StateId, StateId)>,
918    builder: ExplicitBuilder,
919    rule_tracker: TrustedRuleTracker,
920    partners: Vec<FxHashSet<StateId>>,
921    visited: FixedBitSet,
922    matches_scratch: Vec<usize>,
923    stats: IndexedCondensedIntersectionStats,
924    control: &'a ParseControl,
925}
926
927impl<R> TopDownIntersection<'_, R>
928where
929    R: CondensedTopDownTa,
930    R::State: Clone + Eq + Hash,
931{
932    fn intern_right(&mut self, state: R::State) -> StateId {
933        let id = self.right_interner.intern(state);
934        if self.partners.len() <= id.index() {
935            self.partners
936                .resize_with(id.index() + 1, FxHashSet::default);
937        }
938        if self.visited.len() <= id.index() {
939            self.visited.grow(id.index() + 1);
940        }
941        id
942    }
943
944    fn visit(&mut self, q: StateId) {
945        if self.control.is_cancelled() {
946            return;
947        }
948        if self.visited.contains(q.index()) {
949            return;
950        }
951        self.visited.set(q.index(), true);
952        self.stats.right_indexed_queries += 1;
953
954        let raw_parent = self.right_interner.resolve(q).clone();
955        let mut normal_rules = SmallVec::<[OwnedCondensedRule<StateId>; 4]>::new();
956        let mut loop_rules = SmallVec::<[OwnedCondensedRule<StateId>; 4]>::new();
957
958        self.right
959            .condensed_rules_by_parent(&raw_parent, &mut |symbols, children| {
960                if self.control.is_cancelled() {
961                    return;
962                }
963                let children: SmallVec<[StateId; 2]> = children
964                    .iter()
965                    .cloned()
966                    .map(|child| self.intern_right(child))
967                    .collect();
968                let rule = OwnedCondensedRule {
969                    children,
970                    symbols: symbols.clone(),
971                    result: q,
972                };
973                if rule.children.contains(&q) {
974                    loop_rules.push(rule);
975                } else {
976                    normal_rules.push(rule);
977                }
978            });
979
980        for right_rule in normal_rules {
981            if self.control.is_cancelled() {
982                return;
983            }
984            for &child in &right_rule.children {
985                self.visit(child);
986            }
987            self.process_normal_rule(&right_rule);
988        }
989
990        for right_rule in &loop_rules {
991            if self.control.is_cancelled() {
992                return;
993            }
994            for &child in &right_rule.children {
995                if child != q {
996                    self.visit(child);
997                }
998            }
999        }
1000        self.process_loop_rules(q, &loop_rules);
1001    }
1002
1003    fn process_normal_rule(&mut self, right_rule: &OwnedCondensedRule<StateId>) {
1004        if !right_rule
1005            .children
1006            .iter()
1007            .all(|&child| self.has_partners(child))
1008        {
1009            return;
1010        }
1011
1012        let child_sets = right_rule
1013            .children
1014            .iter()
1015            .map(|&child| &self.partners[child.index()])
1016            .collect::<SmallVec<[&FxHashSet<StateId>; 4]>>();
1017        self.left_index.rule_indexes_for_sets_into(
1018            &right_rule.symbols,
1019            &child_sets,
1020            &mut self.matches_scratch,
1021        );
1022        drop(child_sets);
1023        let matches = std::mem::take(&mut self.matches_scratch);
1024        for &rule_idx in &matches {
1025            if self.control.is_cancelled() {
1026                return;
1027            }
1028            let left_rule = &self.left_rules[rule_idx];
1029            self.collect_pair(left_rule, right_rule);
1030        }
1031        self.matches_scratch = matches;
1032    }
1033
1034    fn process_loop_rules(&mut self, q: StateId, loop_rules: &[OwnedCondensedRule<StateId>]) {
1035        if !self.has_partners(q) {
1036            return;
1037        }
1038
1039        for right_rule in loop_rules {
1040            if self.control.is_cancelled() {
1041                return;
1042            }
1043            for loop_position in right_rule
1044                .children
1045                .iter()
1046                .enumerate()
1047                .filter_map(|(position, &child)| (child == q).then_some(position))
1048            {
1049                let mut agenda = self.partners[q.index()]
1050                    .iter()
1051                    .copied()
1052                    .collect::<VecDeque<_>>();
1053                let mut seen = self.partners[q.index()].clone();
1054
1055                while let Some(left_at_loop) = agenda.pop_front() {
1056                    if self.control.is_cancelled() {
1057                        return;
1058                    }
1059                    let mut child_sets = SmallVec::<[StateSetView<'_>; 4]>::new();
1060                    let mut missing = false;
1061                    for (position, &right_child) in right_rule.children.iter().enumerate() {
1062                        if position == loop_position {
1063                            child_sets.push(StateSetView::One(left_at_loop));
1064                        } else if self.has_partners(right_child) {
1065                            child_sets
1066                                .push(StateSetView::Many(&self.partners[right_child.index()]));
1067                        } else {
1068                            missing = true;
1069                            break;
1070                        }
1071                    }
1072                    if missing {
1073                        continue;
1074                    }
1075
1076                    self.left_index.rule_indexes_for_sets_into(
1077                        &right_rule.symbols,
1078                        &child_sets,
1079                        &mut self.matches_scratch,
1080                    );
1081                    drop(child_sets);
1082                    let matches = std::mem::take(&mut self.matches_scratch);
1083
1084                    let mut newly_added = SmallVec::<[StateId; 4]>::new();
1085                    for &rule_idx in &matches {
1086                        if self.control.is_cancelled() {
1087                            return;
1088                        }
1089                        let left_rule = &self.left_rules[rule_idx];
1090                        if self.collect_pair(left_rule, right_rule) && seen.insert(left_rule.result)
1091                        {
1092                            newly_added.push(left_rule.result);
1093                        }
1094                    }
1095                    for state in newly_added {
1096                        agenda.push_back(state);
1097                    }
1098                    self.matches_scratch = matches;
1099                }
1100            }
1101        }
1102    }
1103
1104    fn has_partners(&self, right_state: StateId) -> bool {
1105        self.partners
1106            .get(right_state.index())
1107            .is_some_and(|partners| !partners.is_empty())
1108    }
1109
1110    fn collect_pair(
1111        &mut self,
1112        left_rule: &OwnedRule,
1113        right_rule: &OwnedCondensedRule<StateId>,
1114    ) -> bool {
1115        let mut children = SmallVec::<[StateId; 2]>::new();
1116        for (&left_child, &right_child) in left_rule.children.iter().zip(&right_rule.children) {
1117            let Some(child_pair) = self.product_ids.get(left_child, right_child) else {
1118                return false;
1119            };
1120            children.push(child_pair);
1121        }
1122
1123        let (parent, is_new_product) = get_or_create_product_id(
1124            left_rule.result,
1125            right_rule.result,
1126            self.left,
1127            self.right,
1128            &mut self.product_ids,
1129            &mut self.product_pairs,
1130            &self.right_interner,
1131            &mut self.builder,
1132        );
1133        if is_new_product {
1134            self.partners[right_rule.result.index()].insert(left_rule.result);
1135        }
1136
1137        self.rule_tracker.add_rule(
1138            &mut self.builder,
1139            left_rule.symbol,
1140            children,
1141            parent,
1142            left_rule.weight,
1143        );
1144        is_new_product
1145    }
1146}
1147
1148fn current_product_is_latest(
1149    children: &SmallVec<[StateId; 2]>,
1150    current_position: usize,
1151    current_product: StateId,
1152) -> bool {
1153    let mut latest = StateId(0);
1154    let mut first_latest_position = 0;
1155    for (position, &child) in children.iter().enumerate() {
1156        if position == 0 || child.0 > latest.0 {
1157            latest = child;
1158            first_latest_position = position;
1159        }
1160    }
1161    latest == current_product && first_latest_position == current_position
1162}
1163
1164#[allow(clippy::too_many_arguments)]
1165pub(crate) fn get_or_create_product_id<R>(
1166    left_state: StateId,
1167    right_state: StateId,
1168    left: &Explicit,
1169    right: &R,
1170    ids: &mut ProductStateMap,
1171    pairs: &mut Vec<(StateId, StateId)>,
1172    right_interner: &Interner<R::State>,
1173    builder: &mut ExplicitBuilder,
1174) -> (StateId, bool)
1175where
1176    R: BottomUpTa,
1177{
1178    if let Some(id) = ids.get(left_state, right_state) {
1179        return (id, false);
1180    }
1181    let id = builder.new_state();
1182    ids.insert(left_state, right_state, id);
1183    pairs.push((left_state, right_state));
1184    if left.is_accepting(&left_state) && right.is_accepting(right_interner.resolve(right_state)) {
1185        builder.add_accepting(id);
1186    }
1187    (id, true)
1188}
1189
1190pub(crate) fn get_or_create_product_id_direct(
1191    left_state: StateId,
1192    right_state: StateId,
1193    ids: &mut ProductStateMap,
1194    pairs: &mut Vec<(StateId, StateId)>,
1195) -> (StateId, bool) {
1196    if let Some(id) = ids.get(left_state, right_state) {
1197        return (id, false);
1198    }
1199
1200    let id = StateId(pairs.len() as u32);
1201    ids.insert(left_state, right_state, id);
1202    pairs.push((left_state, right_state));
1203    (id, true)
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208    use super::*;
1209    use crate::{BottomUpTa, ExplicitBuilder};
1210
1211    #[test]
1212    fn product_state_map_is_right_major_and_sparse() {
1213        let mut map = ProductStateMap::new();
1214        let left = StateId(7);
1215        let right = StateId(3);
1216        let product = StateId(11);
1217
1218        assert_eq!(map.get(left, right), None);
1219        map.insert(left, right, product);
1220        assert_eq!(map.get(left, right), Some(product));
1221        assert_eq!(map.get(StateId(8), right), None);
1222        assert_eq!(map.get(left, StateId(4)), None);
1223    }
1224
1225    #[test]
1226    fn state_set_view_matches_set_trie_traversal() {
1227        let mut trie = SetTrie::new();
1228        trie.get_or_insert_with(&[StateId(1), StateId(2)], Vec::new)
1229            .push("hit");
1230        trie.get_or_insert_with(&[StateId(3), StateId(2)], Vec::new)
1231            .push("miss");
1232
1233        let second = FxHashSet::from_iter([StateId(2)]);
1234        let key_sets = [StateSetView::One(StateId(1)), StateSetView::Many(&second)];
1235        let mut values = Vec::new();
1236        trie.for_each_value_for_key_sets(&key_sets, |found| {
1237            values.extend(found.iter().copied());
1238        });
1239
1240        assert_eq!(values, vec!["hit"]);
1241    }
1242
1243    #[test]
1244    fn left_index_reuses_output_buffer_for_set_queries() {
1245        let symbol = Symbol(1);
1246        let other_symbol = Symbol(2);
1247        let q0 = StateId(0);
1248        let q1 = StateId(1);
1249        let rules = vec![
1250            OwnedRule {
1251                symbol,
1252                children: SmallVec::from_slice(&[q0, q1]),
1253                result: StateId(2),
1254                weight: 1.0,
1255            },
1256            OwnedRule {
1257                symbol: other_symbol,
1258                children: SmallVec::from_slice(&[q0, q1]),
1259                result: StateId(3),
1260                weight: 1.0,
1261            },
1262        ];
1263        let index = LeftIndex::build(&rules);
1264        let mut symbols = SymbolSet::default();
1265        symbols.insert(symbol);
1266        let first = FxHashSet::from_iter([q0]);
1267        let second = FxHashSet::from_iter([q1]);
1268        let key_sets = [&first, &second];
1269        let mut out = vec![usize::MAX];
1270
1271        index.rule_indexes_for_sets_into(&symbols, &key_sets, &mut out);
1272        assert_eq!(out, vec![0]);
1273
1274        symbols.insert(other_symbol);
1275        index.rule_indexes_for_sets_into(&symbols, &key_sets, &mut out);
1276        out.sort_unstable();
1277        assert_eq!(out, vec![0, 1]);
1278    }
1279
1280    #[test]
1281    fn materializes_explicit_identity_fragment() {
1282        let a = Symbol(0);
1283        let f = Symbol(1);
1284        let mut b = ExplicitBuilder::new();
1285        let leaf = b.new_state();
1286        let root = b.new_state();
1287        b.add_rule(a, vec![], leaf);
1288        b.add_rule(f, vec![leaf, leaf], root);
1289        b.add_accepting(root);
1290        let explicit = b.build();
1291
1292        let (mat, _interner) = materialize(&explicit, &[(a, 0), (f, 2)]);
1293        let mut leaves = Vec::new();
1294        mat.step(a, &[], &mut |q| leaves.push(q));
1295        let mut roots = Vec::new();
1296        mat.step(f, &[leaves[0], leaves[0]], &mut |q| roots.push(q));
1297        assert_eq!(roots.len(), 1);
1298        assert!(mat.is_accepting(&roots[0]));
1299    }
1300
1301    #[test]
1302    fn topdown_condensed_intersection_matches_indexed_on_explicit_pair() {
1303        let a = Symbol(0);
1304        let f = Symbol(1);
1305
1306        let mut left_builder = ExplicitBuilder::new();
1307        let left_leaf = left_builder.new_state();
1308        let left_root = left_builder.new_state();
1309        left_builder.add_rule(a, vec![], left_leaf);
1310        left_builder.add_rule(f, vec![left_leaf, left_leaf], left_root);
1311        left_builder.add_accepting(left_root);
1312        let left = left_builder.build();
1313
1314        let mut right_builder = ExplicitBuilder::new();
1315        let right_leaf = right_builder.new_state();
1316        let right_root = right_builder.new_state();
1317        right_builder.add_rule(a, vec![], right_leaf);
1318        right_builder.add_rule(f, vec![right_leaf, right_leaf], right_root);
1319        right_builder.add_accepting(right_root);
1320        let right = right_builder.build();
1321
1322        let (topdown, _, topdown_stats) = materialize_topdown_condensed_intersection(&left, &right);
1323        let (indexed, _, indexed_stats) = materialize_indexed_condensed_intersection(&left, &right);
1324
1325        assert_eq!(topdown_stats.output_states, indexed_stats.output_states);
1326        assert_eq!(topdown_stats.output_rules, indexed_stats.output_rules);
1327        assert_eq!(topdown.rules().count(), indexed.rules().count());
1328        assert!(!topdown.is_empty());
1329    }
1330}