aboutsummaryrefslogtreecommitdiff
path: root/chain/src/atom
diff options
context:
space:
mode:
Diffstat (limited to 'chain/src/atom')
-rw-r--r--chain/src/atom/default.rs170
-rw-r--r--chain/src/atom/mod.rs18
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;