diff options
| author | JSDurand <mmemmew@gmail.com> | 2026-10-16 18:16:36 +0800 |
|---|---|---|
| committer | JSDurand <mmemmew@gmail.com> | 2026-10-16 18:16:36 +0800 |
| commit | 918235659f7b0d80bfa305875ab7cc988cc51761 (patch) | |
| tree | ee657d85a6954a9d4327cc0d938e605cbb8637d1 /chain/src/atom | |
| parent | fba9a90cfc6aca2a26cce72f55fb261789139781 (diff) | |
partial commit, before trying the new guide by claudeattempt1
Diffstat (limited to 'chain/src/atom')
| -rw-r--r-- | chain/src/atom/default.rs | 170 | ||||
| -rw-r--r-- | chain/src/atom/mod.rs | 18 |
2 files changed, 174 insertions, 14 deletions
diff --git a/chain/src/atom/default.rs b/chain/src/atom/default.rs index 7e50868..1870aa1 100644 --- a/chain/src/atom/default.rs +++ b/chain/src/atom/default.rs @@ -82,6 +82,19 @@ pub struct DefaultAtom { virtual_nodes: VirtualMap, virtual_traces: VirtualTraceMap, virtual_frags: VirtualFragMap, + // /// `left_corners[x][y]` is true if and only if the non-terminal + // /// `y` is in the reflexive-transitive closure of left corners of + // /// the non-terminal `x`: `y` can start a derivation from `x`, + // /// after only nullable symbols. + // left_corners: Vec<Vec<bool>>, + /// `left_corners[x][y]` is true if and only if the non-terminal + /// `y` is in the reflexive-transitive closure of left corners of + /// the non-terminal `x`: `y` can start a derivation from `x`, + /// after only nullable symbols. + left_corners: Vec<Vec<bool>>, + /// `acc_by_nt[x][node]` is true if and only if `node` reaches the + /// end of the non-terminal `x` by nullable edges. + acc_by_nt: Vec<Vec<bool>>, } impl DefaultAtom { @@ -209,21 +222,25 @@ impl Display for DefaultAtom { // LabelGraph, in order to implement Nfa. impl LabelGraph<LabelType<TNT>> for DefaultAtom { - type Iter<'b> = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::Iter<'b> + type Iter<'b> + = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::Iter<'b> where Self: 'b; - type TupleIter<'b> = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::TupleIter<'b> + type TupleIter<'b> + = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::TupleIter<'b> where Self: 'b, DOption<TNT>: 'b; - type LabelIter<'b> = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::LabelIter<'b> + type LabelIter<'b> + = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::LabelIter<'b> where Self: 'b, DOption<TNT>: 'b; - type EdgeLabelIter<'a> = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::EdgeLabelIter<'a> + type EdgeLabelIter<'a> + = <DefaultNFA<LabelType<TNT>> as LabelGraph<LabelType<TNT>>>::EdgeLabelIter<'a> where Self: 'a, DOption<TNT>: 'a; @@ -402,6 +419,99 @@ impl DefaultAtom { } // Perform nulling and remove_epsilon at the same time. + // Acceptance is relative to the frame: a state may reach the + // end of one non-terminal through nullable edges without + // reaching the end of another. So for every non-terminal `x` + // compute the states that reach the end of `x` by nullable + // edges, in the same way as `accepting_vec`. + let acc_by_nt: Vec<Vec<bool>> = (0..grammar.non_num()) + .map(|x| -> Result<Vec<bool>, GrammarError> { + let mut result: Vec<bool> = vec![false; nfa_len]; + + *result.get_mut(accumulators[x] + 1).unwrap() = true; + + let mut updated = true; + + while updated { + updated = false; + + for node in nfa.nodes() { + if result[node] { + continue; + } + + 'label_loop: for (label, target_iter) in nfa + .labels_of(node) + .map_err(|_| GrammarError::IndexOutOfBounds(node, nfa_len))? + { + if label_is_nullable(*label) { + for target in target_iter { + if result[target] { + result[node] = true; + updated = true; + + break 'label_loop; + } + } + } + } + } + } + + Ok(result) + }) + .collect::<Result<_, _>>()?; + + // Left corners must be computed before taking the null + // closure, whose edges would hide the structure we are + // following. + let left_corners: Vec<Vec<bool>> = { + let rule_positions = grammar.total() * 2; + let nt_num = grammar.non_num(); + + let mut result = vec![vec![false; nt_num]; nt_num]; + + for x in 0..nt_num { + let mut seen = vec![false; nfa_len]; + let mut stack = vec![*accumulators.get(x).unwrap()]; + + result[x][x] = true; + + while let Some(node) = stack.pop() { + if std::mem::replace(&mut seen[node], true) { + continue; + } + + if node < rule_positions { + result[x][grammar.get_rule_num(node / 2)?] = true; + } + + // The edges out of the end of a rule lead to the + // rules which have that non-terminal as a left + // corner, not to the left corners of `x`. + if node % 2 == 1 && accumulators_set.contains(&(node - 1)) { + continue; + } + + for (label, target_iter) in nfa + .labels_of(node) + .map_err(|_| GrammarError::IndexOutOfBounds(node, nfa_len))? + { + if let Some(TNT::Non(y)) = *label.get_value() { + result[x][y] = true; + stack.push(*accumulators.get(y).unwrap()); + } + + if label_is_nullable(*label) { + stack.extend(target_iter); + } + } + } + } + + result + }; + nfa.closure( label_is_nullable, true, @@ -548,15 +658,13 @@ impl DefaultAtom { .or_default() .insert(rule, frag); - // Those left-linearly expanded arrows should - // not affect the nullability of the virtual - // nodes. - if !label.is_left_p() { - accepting = accepting - || *accepting_vec.get(child).ok_or( - GrammarError::IndexOutOfBounds(child, accepting_vec.len()), - )?; - } + // The virtual node accepts if some child + // reaches the end of `nt`. + accepting = accepting + || *acc_by_nt + .get(nt) + .and_then(|row| row.get(child)) + .ok_or(GrammarError::IndexOutOfBounds(child, nfa_len))?; if let Some((_, old_accepting)) = terminals_map.get_mut(&t) { *old_accepting = *old_accepting || accepting; @@ -638,6 +746,8 @@ impl DefaultAtom { accepting_vec, virtual_traces, virtual_frags, + acc_by_nt, + left_corners, }) } @@ -732,7 +842,8 @@ impl Atom for DefaultAtom { )) } - type Iter<'a> = Copied<Iter<'a, usize>> + type Iter<'a> + = Copied<Iter<'a, usize>> where Self: 'a; @@ -747,6 +858,37 @@ impl Atom for DefaultAtom { fn accepting_len(&self) -> usize { self.accepting_vec.len() } + + fn is_accepting_in(&self, frame_nt: usize, node_id: usize) -> Result<bool, GrammarError> { + if node_id < self.grammar.total() * 2 { + self.acc_by_nt + .get(frame_nt) + .and_then(|row| row.get(node_id)) + .copied() + .ok_or(GrammarError::IndexOutOfBounds( + node_id, + self.accepting_vec.len(), + )) + } else { + self.is_accepting(node_id) + } + } + + fn allowed_in_frame(&self, frame_nt: usize, node_id: usize) -> bool { + if node_id >= self.grammar.total() * 2 { + return true; + } + + match self.grammar.get_rule_num(node_id / 2) { + Ok(owner) => self + .left_corners + .get(frame_nt) + .and_then(|row| row.get(owner)) + .copied() + .unwrap_or(true), + _ => true, + } + } } #[cfg(test)] diff --git a/chain/src/atom/mod.rs b/chain/src/atom/mod.rs index 4b3bfce..1fdbbd1 100644 --- a/chain/src/atom/mod.rs +++ b/chain/src/atom/mod.rs @@ -35,6 +35,24 @@ pub trait Atom: Nfa<LabelType<TNT>> + Deref<Target = Grammar> { /// Return the bound for `is_accepting`. fn accepting_len(&self) -> usize; + + /// Tell whether a node is accepting inside a frame of the + /// non-terminal `frame_nt`, that is to say, whether reaching the + /// node completes `frame_nt`. + /// + /// For a rule position this is not the same as `is_accepting`, + /// since the states are shared by all non-terminals. For other + /// nodes, virtual nodes included, it is the same. + fn is_accepting_in(&self, frame_nt: usize, node_id: usize) -> Result<bool, GrammarError>; + + /// Tell whether, inside a frame of the non-terminal `frame_nt`, + /// we are allowed to move into the node `node_id`. + /// + /// The states of the automaton are shared by all non-terminals, + /// so a rule position is allowed only if the non-terminal owning + /// it is a left corner of `frame_nt`. Other nodes are always + /// allowed. + fn allowed_in_frame(&self, frame_nt: usize, node_id: usize) -> bool; } pub mod default; |
