diff options
| -rw-r--r-- | chain/src/atom/default.rs | 170 | ||||
| -rw-r--r-- | chain/src/atom/mod.rs | 18 | ||||
| -rw-r--r-- | chain/src/default.rs | 936 | ||||
| -rw-r--r-- | chain/src/item/default/mod.rs | 17 | ||||
| -rw-r--r-- | chain/src/lib.rs | 72 | ||||
| -rw-r--r-- | tests/oracle.rs | 579 |
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:?}"); + } + } } |
