aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2026-10-16 18:16:36 +0800
committerJSDurand <mmemmew@gmail.com>2026-10-16 18:16:36 +0800
commit918235659f7b0d80bfa305875ab7cc988cc51761 (patch)
treeee657d85a6954a9d4327cc0d938e605cbb8637d1
parentfba9a90cfc6aca2a26cce72f55fb261789139781 (diff)
partial commit, before trying the new guide by claudeattempt1
-rw-r--r--chain/src/atom/default.rs170
-rw-r--r--chain/src/atom/mod.rs18
-rw-r--r--chain/src/default.rs936
-rw-r--r--chain/src/item/default/mod.rs17
-rw-r--r--chain/src/lib.rs72
-rw-r--r--tests/oracle.rs579
6 files changed, 1234 insertions, 558 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;
diff --git a/chain/src/default.rs b/chain/src/default.rs
index db42ef8..7ab2636 100644
--- a/chain/src/default.rs
+++ b/chain/src/default.rs
@@ -1,3 +1,4 @@
+#![allow(unused)]
//! This file provides a default implementation of the
//! [`Chain`][crate::Chain] trait.
//!
@@ -130,7 +131,7 @@ impl Default for DerIterIndex {
}
/// A complex type used for storing values of edges with two layers.
-type SecondTypeValue = (PaVi, bool, Vec<(Roi, usize)>);
+type SecondTypeValue = (PaVi, bool, usize, Vec<(Roi, usize)>);
/// An iterator of TwoLayers.
#[derive(Debug, Default)]
@@ -159,13 +160,14 @@ impl DerIter {
label: usize,
forest_source: PaVi,
accepting: bool,
+ frame_nt: usize,
edges: Vec<(Roi, usize)>,
) {
- if let Some((_, _, vec)) = self.seconds.get_mut(&label) {
+ if let Some((_, _, _, vec)) = self.seconds.get_mut(&label) {
vec.extend(edges);
} else {
self.seconds
- .insert(label, (forest_source, accepting, edges));
+ .insert(label, (forest_source, accepting, frame_nt, edges));
self.second_array.push(label);
}
@@ -180,10 +182,18 @@ impl Iterator for DerIter {
match self.index {
DerIterIndex::Map(index) => {
if let Some(key) = self.second_array.get(index) {
- if let Some((forest_source, accepting, edges)) = self.seconds.remove(key) {
+ if let Some((forest_source, accepting, frame_nt, edges)) =
+ self.seconds.remove(key)
+ {
self.index = DerIterIndex::Map(index + 1);
- Some(TwoLayers::Two(*key, forest_source, accepting, edges))
+ Some(TwoLayers::Two(
+ *key,
+ forest_source,
+ accepting,
+ frame_nt,
+ edges,
+ ))
} else {
// this should not happen
dbg!("a key does not exist in the hashmap: something is wrong when taking derivatives");
@@ -222,6 +232,7 @@ pub struct DefaultChain {
forest: DefaultForest<ForestLabel<GrammarLabel>>,
accepting_vec: Vec<bool>,
accepting_sources: Vec<PaVi>,
+ recording: bool,
}
// TODO: Implement saving the chain on disk, so that we do not have to
@@ -389,43 +400,27 @@ impl LabelExtGraph<Edge> for DefaultChain {
#[cfg(debug_assertions)]
assert_eq!(new, accepting_len);
- let mut updated = false;
+ // The node is accepting if and only if some accepting
+ // label has an accepting target. The result must not
+ // depend on the order in which the labels are visited.
- for (label, child_iter) in self.graph.labels_of(new)? {
- let old_accepting = {
- let mut result = false;
- for child in child_iter {
- if *self
- .accepting_vec
- .get(child)
- .ok_or(GError::IndexOutOfBounds(child, accepting_len))?
- {
- result = true;
- break;
- }
- }
-
- result
- };
-
- if !old_accepting {
- self.accepting_vec.push(false);
- updated = true;
-
- break;
+ let mut accepting = false;
+ 'label_loop: for (label, child_iter) in self.graph.labels_of(new)? {
+ if !label.is_accepting() {
+ continue;
}
-
- if label.is_accepting() {
- self.accepting_vec.push(true);
- updated = true;
-
- break;
+ for child in child_iter {
+ if *self
+ .accepting_vec
+ .get(child)
+ .ok_or(GError::IndexOutOfBounds(child, accepting_len))?
+ {
+ accepting = true;
+ break 'label_loop;
+ }
}
}
-
- if !updated {
- self.accepting_vec.push(false);
- }
+ self.accepting_vec.push(accepting);
}
Ok(new)
@@ -452,7 +447,8 @@ impl Chain for DefaultChain {
builder.add_edge(
first,
root,
- Edge::new(empty_state, Default::default(), initial_nullable),
+ Edge::new(empty_state, Default::default(), initial_nullable)
+ .with_frame_nt(START_NONTERMINAL),
)?;
let graph = builder.build();
@@ -471,6 +467,8 @@ impl Chain for DefaultChain {
let accepting_sources = Vec::new();
+ let recording = false;
+
Ok(Self {
graph,
atom,
@@ -479,6 +477,7 @@ impl Chain for DefaultChain {
forest,
accepting_vec,
accepting_sources,
+ recording,
})
}
@@ -540,15 +539,8 @@ impl Chain for DefaultChain {
///
/// The format is as follows:
///
- /// `(graph, atom, forest, accepting_vec, forest_source, new_source)`
- type GeneratorInput<'a> = (
- &'a DLGraph<Edge>,
- &'a DefaultAtom,
- &'a DefaultForest<ForestLabel<GrammarLabel>>,
- &'a [bool],
- PaVi,
- PaVi,
- );
+ /// `(graph, atom, accepting_vec, current_node)`
+ type GeneratorInput<'a> = (&'a DLGraph<Edge>, &'a DefaultAtom, &'a [bool], usize);
/// A helper function to generate edges to join.
///
@@ -562,13 +554,16 @@ impl Chain for DefaultChain {
/// to save some allocations.
fn generate_edges(
input: GeneratorInput<'_>,
+ base_edge: &Edge,
mut child_iter: impl ExactSizeIterator<Item = usize> + Clone,
atom_child_iter: impl Iterator<Item = usize> + Clone,
mut output: impl AsMut<Vec<(Roi, usize)>>,
) -> Result<bool, Error> {
let mut accepting = false;
- let (graph, atom, _forest, accepting_vec, pavi, true_pavi) = input;
+ let (graph, atom, accepting_vec, _current) = input;
+
+ let frame_nt = base_edge.frame_nt();
// First check the values from iterators are all valid.
let graph_len = graph.nodes_len();
@@ -616,17 +611,6 @@ impl Chain for DefaultChain {
})
.sum::<usize>();
- // for atom_child in atom_child_iter.clone() {
- // let atom_child_accepting = atom.is_accepting(atom_child).unwrap();
-
- // num += child_iter.len();
-
- // if atom_child_accepting {
- // accepting = true;
- // num += child_iter_total_degree;
- // }
- // }
-
let num = num;
let output = output.as_mut();
@@ -636,10 +620,12 @@ impl Chain for DefaultChain {
// now push into output
for atom_child in atom_child_iter {
- let atom_child_accepting = atom.is_accepting(atom_child).unwrap();
+ let atom_child_accepting = atom.is_accepting_in(frame_nt, atom_child).unwrap();
let atom_child_empty_node = atom.is_empty_node(atom_child).unwrap();
- let roi = Edge::new(atom_child, pavi, atom_child_accepting).into();
+ let roi = Edge::new(atom_child, PaVi::default(), atom_child_accepting)
+ .with_frame_nt(frame_nt)
+ .into();
if atom_child_empty_node {
output.extend(child_iter.clone().map(|child| (child.into(), child)));
@@ -650,15 +636,8 @@ impl Chain for DefaultChain {
if atom_child_accepting {
for child in child_iter.clone() {
for (child_label, child_child) in graph.labels_of(child).unwrap() {
- let mut new_label = *child_label;
-
- new_label.set_true_source(true_pavi);
- // new_label.set_forest_source(pavi);
-
output.extend(
- std::iter::repeat(new_label.into())
- .take(child_child.len())
- .zip(child_child),
+ child_child.map(|child_child| ((*child_label).into(), child_child)),
);
}
}
@@ -670,22 +649,18 @@ impl Chain for DefaultChain {
Ok(accepting)
}
- if !no_item
- && self.forest.nodes_len() > 2
- && matches!(self.forest.degree(1), Ok(d) if d > 0)
- {
- // dbg!(self.forest.vertex_label(1)?, self.atom.empty());
- if let Ok(Some(label)) = self.forest.vertex_label(1) {
- if label.label().label().rule().map(|n| n << 1) == Some(self.atom.empty()) {
- self.forest.remove_node(1)?;
- }
+ self.recording = !no_item;
+
+ if !no_item {
+ if pos == 0 {
+ // The start of the frame of the initial edge.
+
+ // TODO
}
}
let mut der_iter = DerIter::default();
- let mut accepting_pavi: HashSet<PaVi> = HashSet::new();
-
for (label, child_iter) in self.graph.labels_of(self.current)? {
for (atom_label, atom_child_iter) in self.atom.labels_of(label.label())? {
if atom_label.is_left_p() {
@@ -694,55 +669,35 @@ impl Chain for DefaultChain {
continue;
}
- let atom_moved = atom_label.get_moved();
+ // We do not move into the rules of non-terminals that
+ // are not left corners of the frame.
+ let frame_nt = label.frame_nt();
+ let atom_children: Vec<usize> = atom_child_iter
+ .filter(|child| self.atom.allowed_in_frame(frame_nt, *child))
+ .collect();
+
+ if atom_children.is_empty() {
+ continue;
+ }
+
+ let atom_child_iter = atom_children.iter().copied();
+
+ let generator_input = (
+ &self.graph,
+ &self.atom,
+ self.accepting_vec.as_slice(),
+ self.current,
+ );
match *atom_label.get_value() {
Some(Ter(ter)) if ter == t => {
- let new_pavi = if !no_item {
- // prepare forest fragment
-
- // if ter == 0 {
- // println!("{ter}, {t}, {atom_moved}, {atom_label}");
- // println!(
- // "hey: {:?}",
- // self.atom.labels_of(0).unwrap().collect::<Vec<_>>()
- // );
- // panic!("test");
- // }
-
- let fragment =
- generate_fragment([atom_moved.into(), Ter(ter).into()], pos)?;
-
- self.forest.insert_item(
- *label,
- t,
- fragment,
- atom_child_iter.clone(),
- &self.atom,
- )?
- } else {
- PaVi::default()
- };
-
- let generator_input = (
- &self.graph,
- &self.atom,
- &self.forest,
- self.accepting_vec.as_slice(),
- new_pavi,
- new_pavi,
- );
-
- let accepting = generate_edges(
+ generate_edges(
generator_input,
+ label,
child_iter.clone(),
atom_child_iter.clone(),
&mut der_iter.singles,
)?;
-
- if accepting {
- accepting_pavi.insert(new_pavi);
- }
}
Some(Non(non)) => {
if !self
@@ -753,20 +708,6 @@ impl Chain for DefaultChain {
continue;
}
- #[allow(clippy::single_match)]
- match label.forest_source() {
- PaVi::Parent(node, _, _) => match self.forest.vertex_label(node) {
- Ok(Some(label)) => match label.label().end() {
- Some(end) if end != pos => {
- continue;
- }
- _ => {}
- },
- _ => {}
- },
- _ => {}
- }
-
let virtual_node = self
.atom
.atom(non, t)
@@ -778,88 +719,34 @@ impl Chain for DefaultChain {
.is_accepting(virtual_node)
.map_err(index_out_of_bounds_conversion)?;
- // if pos == 9 {
- // dbg!(label, atom_label, self.current, &self.history);
- // }
-
- let first_segment_pavi: PaVi;
- let virtual_pavi: PaVi;
-
- if !no_item {
- let first_fragment =
- generate_fragment([atom_moved.into(), Non(non).into()], pos)?;
-
- first_segment_pavi = self.forest.insert_item(
- *label,
- t,
- first_fragment,
- atom_child_iter.clone(),
- &self.atom,
- )?;
-
- let virtual_fragment =
- DefaultForest::new_leaf(GrammarLabel::new(Ter(t), pos));
-
- // NOTE: We only need the PaVi from the
- // first segment, so we pass an empty
- // iterator, in which case the passed
- // label is only used for the PaVi.
- virtual_pavi = self.forest.insert_item(
- Edge::new(0, first_segment_pavi, accepting),
- t,
- virtual_fragment,
- std::iter::empty(),
- &self.atom,
- )?;
- } else {
- first_segment_pavi = PaVi::default();
- virtual_pavi = PaVi::default();
- }
+ // // The frame of the callee starts here.
+ // let fresh: Starts = if no_item {
+ // Starts::new()
+ // } else {
+ // Starts::from([pos])
+ // };
let mut new_edges = Vec::new();
- let generator_input = (
- &self.graph,
- &self.atom,
- &self.forest,
- self.accepting_vec.as_slice(),
- first_segment_pavi,
- virtual_pavi,
- );
-
- let virtual_accepting = generate_edges(
+ generate_edges(
generator_input,
+ label,
child_iter.clone(),
atom_child_iter.clone(),
&mut new_edges,
)?;
- if virtual_accepting {
- accepting_pavi.insert(first_segment_pavi);
- }
-
if accepting {
- accepting_pavi.insert(virtual_pavi);
-
- for (roi, target) in new_edges.iter() {
- match roi {
- Roi::Re(edge) => {
- let mut edge = *edge;
-
- edge.set_true_source(virtual_pavi);
-
- der_iter.singles.push((Roi::Re(edge), *target));
- }
- Roi::Im(_) => der_iter.singles.push((*roi, *target)),
- }
- }
+ der_iter.singles.extend(new_edges.iter().cloned());
}
if !self.atom.is_empty_node(virtual_node).unwrap() {
der_iter.add_second_layer(
virtual_node,
- virtual_pavi,
+ PaVi::default(),
accepting,
+ non,
+ // fresh.clone(),
new_edges,
);
@@ -872,9 +759,11 @@ impl Chain for DefaultChain {
if self.atom.is_empty_node(atom_child).unwrap() {
der_iter.singles.extend(child_iter.clone().map(|child| {
(
- Edge::new(virtual_node, virtual_pavi, accepting)
+ Edge::new(virtual_node, PaVi::default(), accepting)
+ .with_frame_nt(non)
.into(),
child,
+ // fresh.clone(),
)
}));
}
@@ -901,12 +790,18 @@ impl Chain for DefaultChain {
for (child_label, child_child_iter) in
self.graph.labels_of(child).unwrap()
{
- let mut child_label = *child_label;
-
- child_label.set_true_source(virtual_pavi);
-
der_iter.singles.extend(child_child_iter.map(
- |child_child| (child_label.into(), child_child),
+ |child_child| {
+ (
+ (*child_label).into(),
+ child_child,
+ // self.starts.get(
+ // child,
+ // child_label,
+ // child_child,
+ // ),
+ )
+ },
));
}
}
@@ -922,9 +817,6 @@ impl Chain for DefaultChain {
}
}
- self.accepting_sources.clear();
- self.accepting_sources.extend(accepting_pavi);
-
Ok(der_iter)
}
@@ -1124,390 +1016,390 @@ impl Chain for DefaultChain {
}
}
-#[cfg(test)]
-mod test_der_iter {
- use super::*;
+// #[cfg(test)]
+// mod test_der_iter {
+// use super::*;
+
+// #[test]
+// fn test() -> Result<(), Box<dyn std::error::Error>> {
+// let mut der_iter = DerIter::default();
+
+// let parent = Default::default();
+
+// der_iter
+// .singles
+// .push((Edge::new(0, parent, true).into(), 0));
+
+// der_iter
+// .singles
+// .push((Edge::new(1, parent, true).into(), 0));
+
+// der_iter
+// .singles
+// .push((Edge::new(2, parent, true).into(), 0));
+
+// der_iter.add_second_layer(
+// 3,
+// parent,
+// true,
+// vec![(Edge::new(4, parent, true).into(), 1)],
+// );
+
+// der_iter.add_second_layer(
+// 6,
+// parent,
+// true,
+// vec![(Edge::new(5, parent, true).into(), 1)],
+// );
+
+// // add an entry with a repeated label
+// der_iter.add_second_layer(
+// 3,
+// parent,
+// true,
+// vec![(Edge::new(7, parent, true).into(), 2)],
+// );
+
+// assert_eq!(
+// der_iter.next(),
+// Some(TwoLayers::Two(
+// 3,
+// parent,
+// true,
+// vec![
+// (Edge::new(4, parent, true).into(), 1),
+// (Edge::new(7, parent, true).into(), 2)
+// ]
+// ))
+// );
+
+// assert_eq!(
+// der_iter.next(),
+// Some(TwoLayers::Two(
+// 6,
+// parent,
+// true,
+// vec![(Edge::new(5, parent, true).into(), 1)]
+// ))
+// );
+
+// assert_eq!(
+// der_iter.next(),
+// Some(TwoLayers::One(Edge::new(0, parent, true).into(), 0))
+// );
+
+// assert_eq!(
+// der_iter.next(),
+// Some(TwoLayers::One(Edge::new(1, parent, true).into(), 0))
+// );
+
+// assert_eq!(
+// der_iter.next(),
+// Some(TwoLayers::One(Edge::new(2, parent, true).into(), 0))
+// );
+
+// assert_eq!(der_iter.next(), None);
+// assert_eq!(der_iter.next(), None);
+
+// Ok(())
+// }
+// }
+
+// #[cfg(test)]
+// mod test_chain {
+// use super::*;
+// use grammar::test_grammar_helper::*;
+
+// #[test]
+// fn base_test() -> Result<(), Box<dyn std::error::Error>> {
+// let mut no_item = false;
+
+// for arg in std::env::args() {
+// if arg == "no_item" {
+// no_item = true;
+// break;
+// }
+// }
+
+// let grammar = new_notes_grammar()?;
+
+// let atom = DefaultAtom::from_grammar(grammar)?;
+
+// let mut chain = DefaultChain::unit(atom)?;
+// chain.chain(3, 00, no_item)?;
+// chain.chain(1, 01, no_item)?;
+// chain.chain(2, 02, no_item)?;
+// chain.chain(2, 03, no_item)?;
+// chain.chain(2, 04, no_item)?;
+// chain.chain(0, 05, no_item)?;
+// chain.chain(5, 06, no_item)?;
+// chain.chain(1, 07, no_item)?;
+// chain.chain(6, 08, no_item)?;
+// chain.chain(6, 09, no_item)?;
+// chain.chain(6, 10, no_item)?;
+// chain.chain(0, 11, no_item)?;
+// chain.chain(0, 12, no_item)?;
+
+// if !no_item {
+// let output = chain.end_of_input(13, 0)?;
+// chain.forest.print_viz("forest.gv")?;
+
+// output.print_viz("extracted forest.gv")?;
+
+// chain.graph.print_viz("chain.gv")?;
+// chain.atom.print_nfa("nfa.gv")?;
+// item::default::print_labels(&chain.atom, &chain.forest)?;
+// }
+
+// for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
+// dbg!(label);
+// }
+
+// assert!(matches!(chain.epsilon(), Ok(true)));
+
+// #[cfg(feature = "test-print-viz")]
+// {
+// chain.graph.print_viz("chain.gv")?;
+// chain.atom.print_nfa("nfa.gv")?;
- #[test]
- fn test() -> Result<(), Box<dyn std::error::Error>> {
- let mut der_iter = DerIter::default();
+// chain.forest.print_viz("forest.gv")?;
- let parent = Default::default();
-
- der_iter
- .singles
- .push((Edge::new(0, parent, true).into(), 0));
-
- der_iter
- .singles
- .push((Edge::new(1, parent, true).into(), 0));
-
- der_iter
- .singles
- .push((Edge::new(2, parent, true).into(), 0));
-
- der_iter.add_second_layer(
- 3,
- parent,
- true,
- vec![(Edge::new(4, parent, true).into(), 1)],
- );
-
- der_iter.add_second_layer(
- 6,
- parent,
- true,
- vec![(Edge::new(5, parent, true).into(), 1)],
- );
-
- // add an entry with a repeated label
- der_iter.add_second_layer(
- 3,
- parent,
- true,
- vec![(Edge::new(7, parent, true).into(), 2)],
- );
-
- assert_eq!(
- der_iter.next(),
- Some(TwoLayers::Two(
- 3,
- parent,
- true,
- vec![
- (Edge::new(4, parent, true).into(), 1),
- (Edge::new(7, parent, true).into(), 2)
- ]
- ))
- );
-
- assert_eq!(
- der_iter.next(),
- Some(TwoLayers::Two(
- 6,
- parent,
- true,
- vec![(Edge::new(5, parent, true).into(), 1)]
- ))
- );
+// item::default::print_labels(&chain.atom, &chain.forest)?;
+// }
- assert_eq!(
- der_iter.next(),
- Some(TwoLayers::One(Edge::new(0, parent, true).into(), 0))
- );
+// Ok(())
+// }
- assert_eq!(
- der_iter.next(),
- Some(TwoLayers::One(Edge::new(1, parent, true).into(), 0))
- );
+// #[test]
+// fn test_assumption() -> Result<(), Box<dyn std::error::Error>> {
+// use grammar::Grammar;
- assert_eq!(
- der_iter.next(),
- Some(TwoLayers::One(Edge::new(2, parent, true).into(), 0))
- );
+// let grammar_str = std::fs::read_to_string(
+// "/Users/durand/Desktop/Centre/A propos de programmes/Rust/rep/grammar/abnf grammars/test.abnf",
+// )
+// .unwrap();
- assert_eq!(der_iter.next(), None);
- assert_eq!(der_iter.next(), None);
+// let grammar: Grammar = grammar_str.parse()?;
- Ok(())
- }
-}
+// let atom = DefaultAtom::from_grammar(grammar)?;
-#[cfg(test)]
-mod test_chain {
- use super::*;
- use grammar::test_grammar_helper::*;
+// let mut chain = DefaultChain::unit(atom)?;
- #[test]
- fn base_test() -> Result<(), Box<dyn std::error::Error>> {
- let mut no_item = false;
+// let no_item = false;
- for arg in std::env::args() {
- if arg == "no_item" {
- no_item = true;
- break;
- }
- }
-
- let grammar = new_notes_grammar()?;
-
- let atom = DefaultAtom::from_grammar(grammar)?;
-
- let mut chain = DefaultChain::unit(atom)?;
- chain.chain(3, 00, no_item)?;
- chain.chain(1, 01, no_item)?;
- chain.chain(2, 02, no_item)?;
- chain.chain(2, 03, no_item)?;
- chain.chain(2, 04, no_item)?;
- chain.chain(0, 05, no_item)?;
- chain.chain(5, 06, no_item)?;
- chain.chain(1, 07, no_item)?;
- chain.chain(6, 08, no_item)?;
- chain.chain(6, 09, no_item)?;
- chain.chain(6, 10, no_item)?;
- chain.chain(0, 11, no_item)?;
- chain.chain(0, 12, no_item)?;
-
- if !no_item {
- let output = chain.end_of_input(13, 0)?;
- chain.forest.print_viz("forest.gv")?;
-
- output.print_viz("extracted forest.gv")?;
-
- chain.graph.print_viz("chain.gv")?;
- chain.atom.print_nfa("nfa.gv")?;
- item::default::print_labels(&chain.atom, &chain.forest)?;
- }
-
- for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
- dbg!(label);
- }
-
- assert!(matches!(chain.epsilon(), Ok(true)));
-
- #[cfg(feature = "test-print-viz")]
- {
- chain.graph.print_viz("chain.gv")?;
- chain.atom.print_nfa("nfa.gv")?;
-
- chain.forest.print_viz("forest.gv")?;
-
- item::default::print_labels(&chain.atom, &chain.forest)?;
- }
-
- Ok(())
- }
-
- #[test]
- fn test_assumption() -> Result<(), Box<dyn std::error::Error>> {
- use grammar::Grammar;
-
- let grammar_str = std::fs::read_to_string(
- "/Users/durand/Desktop/Centre/A propos de programmes/Rust/rep/grammar/abnf grammars/test.abnf",
- )
- .unwrap();
+// let input: &[usize] = &[
+// 3, 0, 2, 1, 5, 0, 6, 1, 3, 0, 2,
+// 1, // 5, 0, 6, 1, 3, 0, 2, 1, 5, 0, 6, 1, 4, 0, 2, 1,
+// ];
- let grammar: Grammar = grammar_str.parse()?;
+// let input_len = input.len();
- let atom = DefaultAtom::from_grammar(grammar)?;
+// // let to_print = std::fs::metadata("output/").is_ok();
+// let to_print = false;
- let mut chain = DefaultChain::unit(atom)?;
+// for (pos, t) in input.iter().copied().enumerate().take(input_len) {
+// chain.chain(t, pos, no_item)?;
+// if to_print {
+// chain.print_current(&format!("chain {pos}.gv"))?;
+// chain.forest().print_viz(&format!("forest {pos}.gv"))?;
+// }
+// dbg!(pos, t);
+// }
- let no_item = false;
+// chain.print_viz("chain.gv")?;
+// chain.atom.print_viz("nfa.gv")?;
- let input: &[usize] = &[
- 3, 0, 2, 1, 5, 0, 6, 1, 3, 0, 2,
- 1, // 5, 0, 6, 1, 3, 0, 2, 1, 5, 0, 6, 1, 4, 0, 2, 1,
- ];
+// // let _ = chain.forest.print_viz("forest before extraction.gv");
- let input_len = input.len();
-
- // let to_print = std::fs::metadata("output/").is_ok();
- let to_print = false;
-
- for (pos, t) in input.iter().copied().enumerate().take(input_len) {
- chain.chain(t, pos, no_item)?;
- if to_print {
- chain.print_current(&format!("chain {pos}.gv"))?;
- chain.forest().print_viz(&format!("forest {pos}.gv"))?;
- }
- dbg!(pos, t);
- }
+// let extracted = chain.end_of_input(input_len, input[input_len - 1])?;
- chain.print_viz("chain.gv")?;
- chain.atom.print_viz("nfa.gv")?;
+// let _ = extracted.print_viz("extracted forest.gv");
- // let _ = chain.forest.print_viz("forest before extraction.gv");
+// // item::default::print_labels(&chain.atom, &chain.forest)?;
- let extracted = chain.end_of_input(input_len, input[input_len - 1])?;
+// Ok(())
+// }
- let _ = extracted.print_viz("extracted forest.gv");
+// #[test]
+// fn test_ambiguity() -> Result<(), Box<dyn std::error::Error>> {
+// if std::fs::metadata("debug.log").is_ok() {
+// std::fs::remove_file("debug.log")?;
+// }
- // item::default::print_labels(&chain.atom, &chain.forest)?;
+// let mut no_item = false;
- Ok(())
- }
-
- #[test]
- fn test_ambiguity() -> Result<(), Box<dyn std::error::Error>> {
- if std::fs::metadata("debug.log").is_ok() {
- std::fs::remove_file("debug.log")?;
- }
-
- let mut no_item = false;
-
- for arg in std::env::args() {
- if arg == "no_item" {
- no_item = true;
- break;
- }
- }
+// for arg in std::env::args() {
+// if arg == "no_item" {
+// no_item = true;
+// break;
+// }
+// }
- let grammar = new_paren_grammar()?;
+// let grammar = new_paren_grammar()?;
- let atom = DefaultAtom::from_grammar(grammar)?;
+// let atom = DefaultAtom::from_grammar(grammar)?;
- let mut chain = DefaultChain::unit(atom)?;
- chain.chain(0, 0, no_item)?;
+// let mut chain = DefaultChain::unit(atom)?;
+// chain.chain(0, 0, no_item)?;
- if !no_item {
- chain.forest.print_viz("forest0.gv")?;
- }
+// if !no_item {
+// chain.forest.print_viz("forest0.gv")?;
+// }
- chain.chain(2, 1, no_item)?;
+// chain.chain(2, 1, no_item)?;
- if !no_item {
- chain.forest.print_viz("forest1.gv")?;
- }
+// if !no_item {
+// chain.forest.print_viz("forest1.gv")?;
+// }
- chain.chain(2, 2, no_item)?;
+// chain.chain(2, 2, no_item)?;
- if !no_item {
- chain.forest.print_viz("forest2.gv")?;
- }
+// if !no_item {
+// chain.forest.print_viz("forest2.gv")?;
+// }
- chain.chain(2, 3, no_item)?;
+// chain.chain(2, 3, no_item)?;
- if !no_item {
- chain.forest.print_viz("forest3.gv")?;
- }
+// if !no_item {
+// chain.forest.print_viz("forest3.gv")?;
+// }
- chain.chain(1, 4, no_item)?;
+// chain.chain(1, 4, no_item)?;
- if !no_item {
- let _output = chain.end_of_input(5, 1)?;
- chain.forest.print_viz("forest.gv")?;
- // chain.forest.print_closed_viz("closed.gv")?;
- }
+// if !no_item {
+// let _output = chain.end_of_input(5, 1)?;
+// chain.forest.print_viz("forest.gv")?;
+// // chain.forest.print_closed_viz("closed.gv")?;
+// }
- chain.graph.print_viz("chain.gv")?;
- chain.atom.print_nfa("nfa.gv")?;
- item::default::print_labels(&chain.atom, &chain.forest)?;
+// chain.graph.print_viz("chain.gv")?;
+// chain.atom.print_nfa("nfa.gv")?;
+// item::default::print_labels(&chain.atom, &chain.forest)?;
- for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
- dbg!(label);
- }
+// for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
+// dbg!(label);
+// }
- dbg!(chain.current(), chain.history());
+// dbg!(chain.current(), chain.history());
- #[cfg(feature = "test-print-viz")]
- {
- chain.graph.print_viz("chain.gv")?;
- chain.atom.print_nfa("nfa.gv")?;
+// #[cfg(feature = "test-print-viz")]
+// {
+// chain.graph.print_viz("chain.gv")?;
+// chain.atom.print_nfa("nfa.gv")?;
- chain.forest.print_viz("forest.gv")?;
+// chain.forest.print_viz("forest.gv")?;
- chain.forest.print_closed_viz("closed.gv")?;
+// chain.forest.print_closed_viz("closed.gv")?;
- item::default::print_labels(&chain.atom, &chain.forest)?;
- }
+// item::default::print_labels(&chain.atom, &chain.forest)?;
+// }
- assert!(matches!(chain.epsilon(), Ok(true)));
+// assert!(matches!(chain.epsilon(), Ok(true)));
- Ok(())
- }
+// Ok(())
+// }
- #[test]
- fn test_speed() -> Result<(), Box<dyn std::error::Error>> {
- let mut no_item = false;
+// #[test]
+// fn test_speed() -> Result<(), Box<dyn std::error::Error>> {
+// let mut no_item = false;
- for arg in std::env::args() {
- if arg == "no_item" {
- no_item = true;
- break;
- }
- }
+// for arg in std::env::args() {
+// if arg == "no_item" {
+// no_item = true;
+// break;
+// }
+// }
- let grammar = new_notes_grammar_no_regexp()?;
+// let grammar = new_notes_grammar_no_regexp()?;
- println!("grammar: {grammar}");
+// println!("grammar: {grammar}");
- let atom = DefaultAtom::from_grammar(grammar)?;
+// let atom = DefaultAtom::from_grammar(grammar)?;
- let mut chain = DefaultChain::unit(atom)?;
+// let mut chain = DefaultChain::unit(atom)?;
- let input_template = vec![3, 1, 2, 2, 2, 0, 5, 1, 6, 6, 6, 0, 0];
+// let input_template = vec![3, 1, 2, 2, 2, 0, 5, 1, 6, 6, 6, 0, 0];
- let repeat_times = {
- let mut result = 1;
+// let repeat_times = {
+// let mut result = 1;
- for arg in std::env::args() {
- let parse_as_digit: Result<usize, _> = arg.parse();
+// for arg in std::env::args() {
+// let parse_as_digit: Result<usize, _> = arg.parse();
- // just use the first number in the arguments
- if let Ok(parse_result) = parse_as_digit {
- result = parse_result;
+// // just use the first number in the arguments
+// if let Ok(parse_result) = parse_as_digit {
+// result = parse_result;
- break;
- }
- }
+// break;
+// }
+// }
- result
- };
+// result
+// };
- println!("repeating {repeat_times} times");
+// println!("repeating {repeat_times} times");
- let input = input_template.repeat(repeat_times);
+// let input = input_template.repeat(repeat_times);
- let start = std::time::Instant::now();
+// let start = std::time::Instant::now();
- for (index, t) in input.iter().copied().enumerate() {
- chain.chain(t, index, no_item)?;
- }
+// for (index, t) in input.iter().copied().enumerate() {
+// chain.chain(t, index, no_item)?;
+// }
- let elapsed = start.elapsed();
+// let elapsed = start.elapsed();
- assert!(matches!(chain.epsilon(), Ok(true)));
+// assert!(matches!(chain.epsilon(), Ok(true)));
- dbg!(elapsed);
- dbg!(chain.current());
+// dbg!(elapsed);
+// dbg!(chain.current());
- assert_eq!(input.len(), chain.history().len());
+// assert_eq!(input.len(), chain.history().len());
- let history_file_name = "output/history";
- if std::fs::metadata(history_file_name).is_ok() {
- std::fs::remove_file(history_file_name)?;
- }
+// let history_file_name = "output/history";
+// if std::fs::metadata(history_file_name).is_ok() {
+// std::fs::remove_file(history_file_name)?;
+// }
- let mut history_file = std::fs::OpenOptions::new()
- .create(true)
- .write(true)
- .open(history_file_name)?;
+// let mut history_file = std::fs::OpenOptions::new()
+// .create(true)
+// .write(true)
+// .open(history_file_name)?;
- use std::fmt::Write;
- use std::io::Write as IOWrite;
+// use std::fmt::Write;
+// use std::io::Write as IOWrite;
- let mut log_string = String::new();
+// let mut log_string = String::new();
- writeln!(&mut log_string, "index: terminal, history")?;
+// writeln!(&mut log_string, "index: terminal, history")?;
- for (index, t) in input.iter().copied().enumerate().take(input.len() - 1) {
- writeln!(
- &mut log_string,
- "{index}: {t}, {}",
- chain.history().get(index).unwrap()
- )?;
- }
+// for (index, t) in input.iter().copied().enumerate().take(input.len() - 1) {
+// writeln!(
+// &mut log_string,
+// "{index}: {t}, {}",
+// chain.history().get(index).unwrap()
+// )?;
+// }
- println!("Successfully logged to output/history");
+// println!("Successfully logged to output/history");
- history_file.write_all(log_string.as_bytes())?;
+// history_file.write_all(log_string.as_bytes())?;
- for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
- dbg!(label);
- }
+// for label in chain.labels_of(chain.current())?.map(|(label, _)| label) {
+// dbg!(label);
+// }
- #[cfg(feature = "test-print-viz")]
- {
- chain.graph.print_viz("chain.gv")?;
- chain.atom.print_nfa("nfa.gv")?;
- item::default::print_labels(&chain.atom, &chain.forest)?;
+// #[cfg(feature = "test-print-viz")]
+// {
+// chain.graph.print_viz("chain.gv")?;
+// chain.atom.print_nfa("nfa.gv")?;
+// item::default::print_labels(&chain.atom, &chain.forest)?;
- chain.forest.print_viz("forest.gv")?;
+// chain.forest.print_viz("forest.gv")?;
- chain.forest.print_closed_viz("closed.gv")?;
- }
+// chain.forest.print_closed_viz("closed.gv")?;
+// }
- Ok(())
- }
-}
+// Ok(())
+// }
+// }
diff --git a/chain/src/item/default/mod.rs b/chain/src/item/default/mod.rs
index eef7def..040079f 100644
--- a/chain/src/item/default/mod.rs
+++ b/chain/src/item/default/mod.rs
@@ -98,9 +98,10 @@ impl<T: GraphLabel> AsRef<DefaultForest<T>> for DefaultForest<T> {
}
impl<T: GraphLabel> ParentsGraph for DefaultForest<T> {
- type Iter<'a>= <PLGraph<T> as ParentsGraph>::Iter<'a>
+ type Iter<'a>
+ = <PLGraph<T> as ParentsGraph>::Iter<'a>
where
- Self:'a;
+ Self: 'a;
#[inline]
fn parents_of(&self, node_id: usize) -> Result<<Self as ParentsGraph>::Iter<'_>, GError> {
@@ -123,21 +124,25 @@ impl<T: GraphLabel> ParentsGraph for DefaultForest<T> {
}
impl<T: GraphLabel> LabelGraph<T> for DefaultForest<T> {
- type Iter<'a> = std::iter::Empty<usize>
+ type Iter<'a>
+ = std::iter::Empty<usize>
where
Self: 'a;
- type TupleIter<'a> = std::iter::Empty<(T, usize)>
+ type TupleIter<'a>
+ = std::iter::Empty<(T, usize)>
where
Self: 'a,
T: 'a;
- type LabelIter<'a> = std::iter::Empty<(&'a T, <Self as LabelGraph<T>>::Iter<'a>)>
+ type LabelIter<'a>
+ = std::iter::Empty<(&'a T, <Self as LabelGraph<T>>::Iter<'a>)>
where
Self: 'a,
T: 'a;
- type EdgeLabelIter<'a> = std::iter::Empty<T>
+ type EdgeLabelIter<'a>
+ = std::iter::Empty<T>
where
Self: 'a,
T: 'a;
diff --git a/chain/src/lib.rs b/chain/src/lib.rs
index 10143dd..24598a8 100644
--- a/chain/src/lib.rs
+++ b/chain/src/lib.rs
@@ -32,17 +32,21 @@ pub struct Edge {
true_source: PaVi,
/// Whether or not this edge is "accepting".
accepting: bool,
+ /// The non-terminal that starts this edge.
+ frame_nt: usize,
}
impl Edge {
/// Construct a new edge.
pub fn new(label: usize, forest_source: PaVi, accepting: bool) -> Self {
let true_source = forest_source;
+ let frame_nt = 0usize;
Self {
label,
forest_source,
accepting,
true_source,
+ frame_nt,
}
}
@@ -61,6 +65,24 @@ impl Edge {
self.forest_source
}
+ /// Return the frame non-terminal of the edge.
+ pub fn frame_nt(&self) -> usize {
+ self.frame_nt
+ }
+
+ /// Set the frame non-terminal.
+ pub fn set_frame_nt(&mut self, nt: usize) {
+ self.frame_nt = nt;
+ }
+
+ /// Build a new edge with a given frame non-terminal.
+ pub fn with_frame_nt(self, nt: usize) -> Self {
+ Self {
+ frame_nt: nt,
+ ..self
+ }
+ }
+
/// Set the associated forest edge of the edge of the edge.
pub fn set_forest_source(&mut self, source: PaVi) {
self.forest_source = source;
@@ -161,7 +183,7 @@ pub enum TwoLayers {
/// the source of the associated forest edge of the second layer,
/// and the third is a list of edges, which are the common first
/// layers.
- Two(usize, PaVi, bool, Vec<(Roi, usize)>),
+ Two(usize, PaVi, bool, usize, Vec<(Roi, usize)>),
}
/// The type of a collapsed iterator.
@@ -203,12 +225,22 @@ where
if let Some(two_layer) = self.iter.next() {
match two_layer {
TwoLayers::One(edge, to) => Some(Ok((edge, to))),
- TwoLayers::Two(label, forest_source, accepting, edges) => {
- let new_index = match self.chain.extend(
- edges
- .into_iter()
- .filter_map(|(roi, target)| roi.real_part().map(|edge| (edge, target))),
- ) {
+ TwoLayers::Two(label, forest_source, accepting, frame_nt, edges) => {
+ let imaginary: Vec<(Roi, usize)> = edges
+ .iter()
+ .filter(|(roi, _)| roi.imaginary_part().is_some())
+ .map(|(roi, target)| (*roi, *target))
+ .collect();
+
+ let real: Vec<(Edge, usize)> = edges
+ .into_iter()
+ .filter_map(|(roi, target)| roi.real_part().map(|edge| (edge, target)))
+ .collect();
+
+ let new_index = match self
+ .chain
+ .extend(real.iter().map(|(edge, target)| (*edge, *target)))
+ {
Ok(new) => new,
Err(error) => {
// Prevent further iterations.
@@ -218,8 +250,15 @@ where
}
};
+ if let Err(err) = self.chain.update_epsilon(new_index, imaginary.into_iter()) {
+ self.stop = true;
+ return Some(Err(err));
+ };
+
Some(Ok((
- Edge::new(label, forest_source, accepting).into(),
+ Edge::new(label, forest_source, accepting)
+ .with_frame_nt(frame_nt)
+ .into(),
new_index,
)))
}
@@ -302,15 +341,16 @@ pub trait Chain: LabelExtGraph<Edge> {
let edges = self.union(der_iter)?;
- let new_index = self.extend(
- edges
- .iter()
- .filter_map(|(roi, target)| roi.real_part().map(|edge| (edge, *target))),
- )?;
+ let real: Vec<(Edge, usize)> = edges
+ .iter()
+ .filter_map(|(roi, target)| roi.real_part().map(|edge| (edge, *target)))
+ .collect();
- if self.is_empty_node(new_index)? {
- self.update_epsilon(new_index, edges.into_iter())?;
- }
+ let new_index = self.extend(real.iter().map(|(edge, target)| (*edge, *target)))?;
+
+ // Imaginary edges can make the node accepting even when the
+ // node has real edges.
+ self.update_epsilon(new_index, edges.into_iter())?;
self.update_history(new_index);
diff --git a/tests/oracle.rs b/tests/oracle.rs
index cab790f..1390511 100644
--- a/tests/oracle.rs
+++ b/tests/oracle.rs
@@ -1800,4 +1800,583 @@ mod parser_side {
failures[0]
);
}
+
+ // ------------------------------------------------------------
+ // Recognition gate (plan step 1), written by Claude Code.
+ //
+ // The recognizer (run with `no_item = true`, so no forest is built)
+ // must accept an input if and only if the input is derivable.
+ // Derivability is computed exactly, without a height bound, by a
+ // fixpoint over the facts `(X, i, j)`.
+ // ------------------------------------------------------------
+
+ /// Exact derivability of `tokens` from the start symbol.
+ fn exact_derivable(grammar: &Grammar, tokens: &[String]) -> bool {
+ let n = tokens.len();
+ let mut facts: std::collections::BTreeSet<(String, usize, usize)> = Default::default();
+
+ loop {
+ let before = facts.len();
+
+ for (nt, alternatives) in &grammar.rules {
+ for alt in alternatives {
+ for i in 0..=n {
+ // Positions reachable after matching a prefix of `alt`.
+ let mut ends = std::collections::BTreeSet::from([i]);
+
+ for symbol in alt {
+ let mut next = std::collections::BTreeSet::new();
+
+ for &k in &ends {
+ if grammar.is_nonterminal(symbol) {
+ for l in k..=n {
+ if facts.contains(&(symbol.clone(), k, l)) {
+ next.insert(l);
+ }
+ }
+ } else if k < n && tokens[k] == *symbol {
+ next.insert(k + 1);
+ }
+ }
+
+ ends = next;
+ }
+
+ for j in ends {
+ facts.insert((nt.clone(), i, j));
+ }
+ }
+ }
+ }
+
+ if facts.len() == before {
+ break;
+ }
+ }
+
+ facts.contains(&(grammar.start.clone(), 0, n))
+ }
+
+ /// Run only the recognizer, with the same timeout and panic guard as
+ /// `run_parser`. An error from `chain` counts as a rejection.
+ fn recognizes(setup: &Setup, input: &[String]) -> Result<bool, String> {
+ init();
+
+ let (setup, input) = (setup.clone(), input.to_vec());
+ let (sender, receiver) = std::sync::mpsc::channel();
+
+ std::thread::spawn(move || {
+ let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+ let atom = DefaultAtom::from_grammar(parser_grammar(&setup))
+ .map_err(|e| format!("from_grammar: {e:?}"))?;
+ let mut chain = DefaultChain::unit(atom).map_err(|e| format!("unit: {e:?}"))?;
+
+ for (pos, token) in input.iter().enumerate() {
+ let id = setup.names.ters.iter().position(|t| t == token).unwrap();
+
+ if chain.chain(id, pos, true).is_err() {
+ return Ok(false);
+ }
+ }
+
+ chain.epsilon().map_err(|e| format!("epsilon: {e:?}"))
+ }));
+
+ let _ = sender.send(match outcome {
+ Ok(result) => result,
+ Err(_) => Err("parser panicked".to_string()),
+ });
+ });
+
+ receiver
+ .recv_timeout(std::time::Duration::from_secs(20))
+ .unwrap_or_else(|_| Err("TIMEOUT".to_string()))
+ }
+
+ /// Compare the recognizer with exact derivability on one input.
+ fn check_recognition(grammar_text: &str, input: &str) -> Result<(), String> {
+ let input = tokens(input);
+ let setup = Setup::new(grammar_text, &input);
+ let expected = exact_derivable(&setup.grammar, &input);
+
+ match recognizes(&setup, &input) {
+ Ok(got) if got == expected => Ok(()),
+ Ok(got) => Err(format!(
+ "grammar:\n{}\ninput: {:?}\nrecognizer says {got}, exact derivability is {expected}",
+ grammar_text.trim(),
+ input.join(" ")
+ )),
+ Err(e) => Err(format!(
+ "grammar:\n{}\ninput: {:?}\n{e}",
+ grammar_text.trim(),
+ input.join(" ")
+ )),
+ }
+ }
+
+ /// Every input of length 1 to `max_len` over `alphabet`.
+ fn all_inputs(alphabet: &[&str], max_len: usize) -> Vec<String> {
+ let mut result = Vec::new();
+ let mut layer: Vec<Vec<&str>> = vec![Vec::new()];
+
+ for _ in 0..max_len {
+ layer = layer
+ .iter()
+ .flat_map(|prefix| {
+ alphabet.iter().map(move |t| {
+ let mut v = prefix.clone();
+ v.push(t);
+ v
+ })
+ })
+ .collect();
+
+ result.extend(layer.iter().map(|v| v.join(" ")));
+ }
+
+ result
+ }
+
+ #[test]
+ fn gate_recognition_hand_cases() {
+ let cases: &[(&str, &[&str], usize)] = &[
+ ("S -> a a", &["a"], 4),
+ ("S -> a S | a", &["a"], 5),
+ ("S -> S a | a", &["a"], 5),
+ ("S -> S S | a", &["a"], 5),
+ ("S -> S S | S | a", &["a"], 4),
+ ("S -> S S | a | eps", &["a"], 4),
+ ("S -> S | a", &["a"], 3),
+ ("S -> A | a\nA -> S", &["a"], 3),
+ ("S -> A A\nA -> a | eps", &["a"], 4),
+ ("S -> a S | eps", &["a"], 4),
+ ("S -> a A b\nA -> eps | A c", &["a", "b", "c"], 5),
+ ("S -> A S a | a\nA -> eps", &["a"], 5),
+ ("S -> B a\nB -> a B | eps", &["a"], 5),
+ ("S -> b | a\nA -> A S A | eps", &["a", "b"], 5),
+ ("S -> X d\nX -> Y c\nY -> a", &["a", "c", "d"], 4),
+ ("S -> Y\nY -> A | B\nA -> x\nB -> x", &["x"], 3),
+ ("S -> A\nA -> B C\nB -> x | eps\nC -> y | eps", &["x", "y"], 4),
+ ("S -> A b | d\nA -> S c", &["b", "c", "d"], 5),
+ ("S -> x S | S a | a", &["x", "a"], 5),
+ ("S -> a S b | eps", &["a", "b"], 6),
+ ("E -> E + T | T\nT -> T * n | n", &["n", "+", "*"], 5),
+ ("E -> E + E | n", &["n", "+"], 5),
+ ];
+
+ let mut failures = Vec::new();
+
+ for (grammar, alphabet, max_len) in cases {
+ for input in all_inputs(alphabet, *max_len) {
+ if let Err(report) = check_recognition(grammar, &input) {
+ failures.push(report);
+ }
+ }
+ }
+
+ assert!(
+ failures.is_empty(),
+ "\n{} mismatches; first:\n{}\n",
+ failures.len(),
+ failures.join("\n--\n")
+ );
+ }
+
+ #[test]
+ fn gate_recognition_random_grammars() {
+ let seed: u64 = std::env::var("REP_SEED")
+ .ok()
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0x5EED);
+ let cases: usize = std::env::var("REP_CASES")
+ .ok()
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(200);
+
+ eprintln!("recognition gate: seed = {seed}, cases = {cases}");
+
+ let mut rng = Rng(seed | 1);
+ let mut failures = Vec::new();
+
+ for _ in 0..cases {
+ let grammar_text = random_grammar(&mut rng);
+ let len = 1 + rng.below(6);
+ let input: Vec<&str> = (0..len).map(|_| ["a", "b"][rng.below(2)]).collect();
+
+ if let Err(report) = check_recognition(&grammar_text, &input.join(" ")) {
+ failures.push(report);
+ }
+ }
+
+ assert!(
+ failures.is_empty(),
+ "\n{} of {cases} mismatches (seed {seed}); first:\n{}\n",
+ failures.len(),
+ failures[0]
+ );
+ }
+
+ /// Print the chain after every token (recognizer only), for
+ /// `DIAG_GRAMMAR` and `DIAG_INPUT`. Written by Claude Code.
+ #[test]
+ #[ignore]
+ fn probe_chain() {
+ init();
+ let g = std::env::var("DIAG_GRAMMAR").unwrap_or("S -> A A\nA -> a | eps".into());
+ let i = std::env::var("DIAG_INPUT").unwrap_or("a a a".into());
+ let toks = tokens(&i);
+ let setup = Setup::new(&g, &toks);
+ let atom = DefaultAtom::from_grammar(parser_grammar(&setup)).unwrap();
+ if std::env::var("DIAG_ATOM").is_ok() {
+ use chain::atom::Atom;
+ for node in 0..atom.nodes_len() {
+ let name = atom.rule_pos_string(node / 2).map(|n| format!("{n} ({})", node % 2)).unwrap_or_default();
+ for (label, targets) in atom.labels_of(node).unwrap() {
+ println!("ATOM {node} [{name}] --{label}--> {:?} (acc={})", targets.collect::<Vec<_>>(), atom.is_accepting(node).unwrap());
+ }
+ }
+ }
+ let mut chain = DefaultChain::unit(atom).unwrap();
+
+ for (pos, token) in toks.iter().enumerate() {
+ let id = setup.names.ters.iter().position(|t| t == token).unwrap();
+ let result = chain.chain(id, pos, true);
+ println!("after token {pos} ({token}): {result:?}; current = {}, accepting = {:?}",
+ chain.current(), chain.epsilon());
+
+ for (label, targets) in chain.labels_of(chain.current()).unwrap() {
+ let name = chain.atom().rule_pos_string(label.label()).unwrap_or_else(|_| format!("#{}", label.label()));
+ println!(" {name} [state {}] frame={} acc={} -> {:?}",
+ label.label(), label.frame_nt(), label.is_accepting(), targets.collect::<Vec<_>>());
+ }
+ }
+ }
+
+ // ------------------------------------------------------------
+ // Facts gate (plan step 10), written by Claude Code.
+ //
+ // While the parser reads the input it records facts `(X, i, j)`.
+ // The recording alone is checked here, without the forest: after
+ // an eager closure of the recorded facts,
+ //
+ // - no fact is missing: every nonterminal node of every oracle
+ // tree (of height at most `h`) with a non-empty span is a fact;
+ // - the input is derivable from the facts (plus the empty spans,
+ // which come from nullability) if and only if it is derivable.
+ // ------------------------------------------------------------
+
+ /// The facts `(X, i, j)` with `i < j` of a tree in the notation of
+ /// the oracle.
+ fn tree_facts(tree: &str, nons: &[String]) -> BTreeSet<(usize, usize, usize)> {
+ let padded = tree.replace('(', " ( ").replace(')', " ) ");
+ let words: Vec<&str> = padded.split_whitespace().collect();
+
+ let mut result = BTreeSet::new();
+ let mut stack: Vec<(usize, usize)> = Vec::new();
+ let mut at = 0usize;
+ let mut k = 0usize;
+
+ while k < words.len() {
+ match words[k] {
+ "(" => {
+ let name = words[k + 1];
+ stack.push((nons.iter().position(|n| n == name).unwrap(), at));
+ k += 1;
+ }
+ ")" => {
+ let (nt, start) = stack.pop().unwrap();
+
+ if at > start {
+ result.insert((nt, start, at));
+ }
+ }
+ "eps" => {}
+ _ => at += 1,
+ }
+
+ k += 1;
+ }
+
+ result
+ }
+
+ /// Derivability when a nonterminal may derive a non-empty span
+ /// only if that is a recorded fact.
+ fn derivable_from_facts(
+ grammar: &Grammar,
+ nons: &[String],
+ tokens: &[String],
+ facts: &BTreeSet<(usize, usize, usize)>,
+ ) -> bool {
+ let n = tokens.len();
+ let id = |name: &str| nons.iter().position(|x| x == name).unwrap();
+ let mut known: BTreeSet<(usize, usize, usize)> = BTreeSet::new();
+
+ loop {
+ let before = known.len();
+
+ for (nt, alternatives) in &grammar.rules {
+ for alt in alternatives {
+ for i in 0..=n {
+ let mut ends = BTreeSet::from([i]);
+
+ for symbol in alt {
+ let mut next = BTreeSet::new();
+
+ for &k in &ends {
+ if grammar.is_nonterminal(symbol) {
+ for l in k..=n {
+ if known.contains(&(id(symbol), k, l)) {
+ next.insert(l);
+ }
+ }
+ } else if k < n && tokens[k] == *symbol {
+ next.insert(k + 1);
+ }
+ }
+
+ ends = next;
+ }
+
+ for j in ends {
+ if i == j || facts.contains(&(id(nt), i, j)) {
+ known.insert((id(nt), i, j));
+ }
+ }
+ }
+ }
+ }
+
+ if known.len() == before {
+ break;
+ }
+ }
+
+ known.contains(&(0, 0, n))
+ }
+
+ /// Run the parser with the forest requested and return the chain.
+ fn run_chain(setup: &Setup, input: &[String]) -> Result<DefaultChain, String> {
+ init();
+
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+ let atom = DefaultAtom::from_grammar(parser_grammar(setup))
+ .map_err(|e| format!("from_grammar: {e:?}"))?;
+ let mut chain = DefaultChain::unit(atom).map_err(|e| format!("unit: {e:?}"))?;
+
+ for (pos, token) in input.iter().enumerate() {
+ let id = setup.names.ters.iter().position(|t| t == token).unwrap();
+
+ chain
+ .chain(id, pos, false)
+ .map_err(|e| format!("chain() failed at token {pos}: {e:?}"))?;
+ }
+
+ Ok(chain)
+ }))
+ .unwrap_or_else(|_| Err("parser panicked".to_string()))
+ }
+
+ fn check_facts(grammar_text: &str, input: &str, h: usize) -> Result<(), String> {
+ let input = tokens(input);
+ let setup = Setup::new(grammar_text, &input);
+ let header = format!("grammar:\n{}\ninput: {:?}", grammar_text.trim(), input.join(" "));
+
+ let chain = run_chain(&setup, &input).map_err(|e| format!("{header}\n{e}"))?;
+
+ let facts = chain
+ .facts()
+ .eager_closure(chain.tokens(), chain.atom())
+ .map_err(|e| format!("{header}\neager closure: {e:?}"))?;
+
+ let mut problems = Vec::new();
+
+ let mut wanted = BTreeSet::new();
+
+ for tree in oracle_trees(&setup.grammar, &input, h) {
+ wanted.extend(tree_facts(&tree, &setup.names.nons));
+ }
+
+ let name = |f: &(usize, usize, usize)| format!("{}[{},{}]", setup.names.nons[f.0], f.1, f.2);
+
+ let missing: Vec<String> = wanted.difference(&facts).map(name).collect();
+
+ if !missing.is_empty() {
+ problems.push(format!("missing facts: {missing:?}"));
+ }
+
+ let exact = exact_derivable(&setup.grammar, &input);
+ let from_facts = derivable_from_facts(&setup.grammar, &setup.names.nons, &input, &facts);
+
+ if exact != from_facts {
+ problems.push(format!("derivable from facts is {from_facts}, exact derivability is {exact}"));
+ }
+
+ if problems.is_empty() {
+ Ok(())
+ } else {
+ Err(format!("{header}\n{}", problems.join("\n")))
+ }
+ }
+
+ #[test]
+ fn gate_facts_hand_cases() {
+ let cases: &[(&str, &str, usize)] = &[
+ ("S -> a a", "a a", 3),
+ ("S -> a S | a", "a a a a", 6),
+ ("S -> A b\nA -> a", "a b", 3),
+ ("S -> A B\nA -> a\nB -> b", "a b", 3),
+ ("S -> S S | a", "a a a a a a", 6),
+ ("E -> E + E | n", "n + n + n", 5),
+ ("S -> A A\nA -> a | eps", "a", 4),
+ ("S -> a S | eps", "a a", 5),
+ ("S -> a A b\nA -> eps | A c", "a c c b", 5),
+ ("S -> S a | a", "a a a a", 5),
+ ("S -> A S a | a\nA -> eps", "a a a", 5),
+ ("S -> S | a", "a", 4),
+ ("S -> A | a\nA -> S", "a", 5),
+ ("S -> S S | S | a", "a a", 4),
+ ("S -> S S | a | eps", "a a", 4),
+ ("S -> S S S | a", "a a a a a a a a a", 5),
+ ("S -> S S | S a | a", "a a a a a a a", 5),
+ ("S -> A b | d\nA -> S c", "d c b c b", 6),
+ ("S -> x S | S a | a", "x x a a a", 6),
+ ("E -> E + T | T\nT -> T * n | n", "n + n * n + n", 6),
+ ("S -> Y c\nY -> A | B\nA -> x\nB -> x", "x c", 5),
+ ("S -> Y\nY -> A | B\nA -> x\nB -> x", "x", 5),
+ ("S -> Y c | Y\nY -> A d | B\nA -> B\nB -> x", "x d c", 6),
+ ("S -> A c | B c\nA -> x\nB -> x", "x c", 4),
+ ("S -> A\nA -> B C\nB -> x | eps\nC -> y | eps", "x", 5),
+ ("S -> A B c\nA -> x | eps\nB -> A | eps", "x c", 5),
+ ];
+
+ let mut failures = Vec::new();
+
+ for (grammar, input, h) in cases {
+ if let Err(report) = check_facts(grammar, input, *h) {
+ failures.push(report);
+ }
+ }
+
+ assert!(
+ failures.is_empty(),
+ "\n{} failing cases; first:\n{}\n",
+ failures.len(),
+ failures[0]
+ );
+ }
+
+ #[test]
+ fn gate_facts_random_grammars() {
+ let seed: u64 = std::env::var("REP_SEED")
+ .ok()
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0x5EED);
+ let cases: usize = std::env::var("REP_CASES")
+ .ok()
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(150);
+
+ eprintln!("facts gate: seed = {seed}, cases = {cases}");
+
+ let mut rng = Rng(seed | 1);
+ let mut failures = Vec::new();
+
+ for _ in 0..cases {
+ let grammar_text = random_grammar(&mut rng);
+ let len = 1 + rng.below(6);
+ let input: Vec<&str> = (0..len).map(|_| ["a", "b"][rng.below(2)]).collect();
+
+ if let Err(report) = check_facts(&grammar_text, &input.join(" "), 4) {
+ failures.push(report);
+ }
+ }
+
+ assert!(
+ failures.is_empty(),
+ "\n{} of {cases} failing (seed {seed}); first:\n{}\n",
+ failures.len(),
+ failures[0]
+ );
+ }
+
+ // ------------------------------------------------------------
+ // Sizes (plan section 4.6): the forest must be polynomial, and
+ // for the linear grammars below, linear in the input.
+ // ------------------------------------------------------------
+
+ fn forest_size(grammar_text: &str, input: &str) -> (usize, usize) {
+ let input = tokens(input);
+ let setup = Setup::new(grammar_text, &input);
+ let start = std::time::Instant::now();
+ let forest = run_parser(&setup, &input).expect("the parser fails");
+ let _ = start.elapsed();
+
+ (forest.nodes_len(), forest.edges().count())
+ }
+
+ #[test]
+ fn size_linear_grammars_have_linear_forests() {
+ for (grammar, token) in [
+ ("S -> a S | eps", "a"),
+ ("S -> S a | a", "a"),
+ ("S -> a S b | eps", "a b"),
+ ] {
+ let make = |n: usize| {
+ if grammar.contains('b') {
+ format!("{} {}", vec!["a"; n].join(" "), vec!["b"; n].join(" "))
+ } else {
+ vec![token; n].join(" ")
+ }
+ };
+
+ let (small, _) = forest_size(grammar, &make(20));
+ let (large, _) = forest_size(grammar, &make(40));
+
+ assert!(
+ large <= 2 * small + 8,
+ "{grammar}: {small} nodes at 20 and {large} nodes at 40 are not linear"
+ );
+ }
+ }
+
+ #[test]
+ fn size_ambiguous_grammar_is_cubic_at_most() {
+ let (small, _) = forest_size("S -> S S | a", &vec!["a"; 10].join(" "));
+ let (large, _) = forest_size("S -> S S | a", &vec!["a"; 20].join(" "));
+
+ // O(n^2) nodes with O(n) alternatives each; doubling the input
+ // multiplies the nodes by about 4 to 8.
+ assert!(large <= 10 * small, "{small} nodes at 10 and {large} nodes at 20");
+ }
+
+ /// Recognition of long inputs (plan step 6): no quadratic behaviour
+ /// for left and right recursion and for the expression grammar.
+ #[test]
+ fn recognition_time_on_long_inputs() {
+ init();
+
+ for (grammar, input) in [
+ ("S -> a S | a", vec!["a"; 400].join(" ")),
+ ("S -> S a | a", vec!["a"; 400].join(" ")),
+ ("E -> E + T | T\nT -> T * n | n", vec!["n"; 200].join(" + ")),
+ ] {
+ let toks = tokens(&input);
+ let setup = Setup::new(grammar, &toks);
+ let atom = DefaultAtom::from_grammar(parser_grammar(&setup)).unwrap();
+ let mut chain = DefaultChain::unit(atom).unwrap();
+ let start = std::time::Instant::now();
+
+ for (pos, token) in toks.iter().enumerate() {
+ let id = setup.names.ters.iter().position(|t| t == token).unwrap();
+ chain.chain(id, pos, true).unwrap();
+ }
+
+ let elapsed = start.elapsed();
+
+ assert!(chain.epsilon().unwrap());
+ eprintln!("{} tokens, {grammar:?}: {elapsed:?}", toks.len());
+ assert!(elapsed < std::time::Duration::from_secs(10), "{grammar}: {elapsed:?}");
+ }
+ }
}