From cea56cbb9a459157bb8e7791bcb304e0c3a3a773 Mon Sep 17 00:00:00 2001 From: Igor Dejanovic Date: Fri, 13 Oct 2023 15:26:51 +0200 Subject: [PATCH] feat: WIP rework GLR reducer --- rustemo-compiler/src/generator/mod.rs | 2 +- rustemo/src/glr/parser.rs | 294 +++++++++++++------------- tests/build.rs | 2 +- tests/src/output_dir/output_dir.rs | 2 +- 4 files changed, 155 insertions(+), 145 deletions(-) diff --git a/rustemo-compiler/src/generator/mod.rs b/rustemo-compiler/src/generator/mod.rs index 16317472..bb1a0fc3 100644 --- a/rustemo-compiler/src/generator/mod.rs +++ b/rustemo-compiler/src/generator/mod.rs @@ -331,7 +331,7 @@ impl<'g, 's> ParserGenerator<'g, 's> { .collect(); ast.push(parse_quote! { #[allow(clippy::enum_variant_names)] - #[derive(Clone, Copy)] + #[derive(Clone, Copy, PartialEq)] pub enum ProdKind { #(#prodkind_variants),* } diff --git a/rustemo/src/glr/parser.rs b/rustemo/src/glr/parser.rs index 0f28152e..c06b2c82 100644 --- a/rustemo/src/glr/parser.rs +++ b/rustemo/src/glr/parser.rs @@ -142,7 +142,7 @@ where S: State + Ord + Debug, D: ParserDefinition, TK: Copy + Default + PartialEq + Ord + Debug + 'i, - P: Copy + Debug + Into, + P: Copy + Debug + Into + PartialEq, { pub fn new( definition: &'static D, @@ -410,111 +410,46 @@ where ); for path in self.find_reduction_paths(gss, &reduction) { log!(" {} {path}", "Reducing over path:".green()); - let root_head = gss.head(path.root_head); - let start_head = gss.head(match reduction.start { + let start_head = match reduction.start { ReductionStart::Edge(e) => gss.start(e), ReductionStart::Node(n) => n, - }); - let start_position_before = start_head.position_before; - let start_location_pos_before = start_head.location_pos_before; + }; let token_kind_ahead = - start_head.token_ahead().as_ref().unwrap().kind; - let root_state = root_head.state(); + gss.head(start_head).token_ahead().as_ref().unwrap().kind; + let root_state = gss.head(path.root_head).state(); let next_state = self.definition.goto(root_state, production.into()); - let solution = Rc::new(SPPFTree::NonTerm { - prod: production, - data: TreeData { - range: Range { - start: root_head.position_ahead, - end: start_position_before, - }, - location: Location { - start: root_head.location_pos_ahead, - end: Some(start_location_pos_before), - }, - layout: root_head.layout_ahead(), - }, - children: path.parents, - }); - - if let Some(head) = subfrontier.get(&next_state) { - log!( - " {}", - format!( - "Head {} with the same state already exists.", - head.index() - ) - .green() - ); - log!(" {}", - format!( - "Register new solution for {} -> {}.", - head.index(), - path.root_head.index() - ).green() - ); - if let Some(edge) = - gss.add_solution(*head, path.root_head, solution) + // Get all non-error actions + let actions = self + .definition + .actions(next_state, token_kind_ahead) + .iter() + .take_while(|a| !matches!(a, Action::Error)) + .collect::>(); + + if actions.is_empty() { + log!(" No actions for new state {:?} and lookahead {:?}. Skipping.", + next_state, token_kind_ahead); + } else { + // Find a head with the same state or create new if it doesn't exist + let mut head_created = false; + let head = if let Some(head) = subfrontier.get(&next_state) { log!( - " {} {} -> {}.", - "Created edge".green(), - head.index(), - path.root_head.index() + " {}", + format!( + "Head {} with the same state already exists.", + head.index() + ) + .green() ); - // Parent link was created -> register all non-empty - // reductions - for &action in self - .definition - .actions(next_state, token_kind_ahead) - .iter() - .take_while(|a| !matches!(a, Action::Error)) - { - match action { - Action::Reduce(prod, length) if length > 0 => { - log!( - " {} {} -> {}: {:?}, len {}", - "Register new reduction".green(), - head.index(), - path.root_head.index(), - prod, - length - ); - //log!(" {} {:?}, len = {}", "Register reduction for production:".green(), prod, length); - pending_reductions.push_back(Reduction { - start: ReductionStart::Edge(edge), - production: prod, - length, - }) - } - _ => (), - } - } + *head } else { - log!(" {}", - format!( - "Edge {} -> {} already exists. Not created.", - head.index(), - path.root_head.index()).green() - ); - } - } else { - // No head with this state. We shall create one only if - // there is at least one action for the head. If not, we - // shall just report that. - let actions = self - .definition - .actions(next_state, token_kind_ahead) - .iter() - .take_while(|a| !matches!(a, Action::Error)) - .collect::>(); - // No head with this state. Create one, create edge and - // register shifts and reductions. - if !actions.is_empty() { - let new_head = start_head.with_tok_state( - start_head.token_ahead().cloned().unwrap(), + // Create new head + let shead = gss.head(start_head); + let new_head = shead.with_tok_state( + shead.token_ahead().cloned().unwrap(), next_state, ); #[cfg(debug_assertions)] @@ -527,73 +462,143 @@ where new_head_idx.index(), new_head_str ); - let edge = gss.add_solution( - new_head_idx, - path.root_head, - solution, + head_created = true; + new_head_idx + }; + + // Find an edge between the head and the root_head or create new + // if it doesn't exist + let mut edge_created = false; + let edge = if let Some(edge) = + gss.edge_between(head, path.root_head) + { + log!( + " {}", + format!( + "Edge {} -> {} already exists. Not created.", + head.index(), + path.root_head.index() + ) + .green() ); + edge + } else { + // Create new edge log!( - " {} {} -> {}.", + " {} {} -> {}.", "Created edge".green(), - new_head_idx.index(), + head.index(), path.root_head.index() ); - + edge_created = true; + gss.add_parent( + head, + path.root_head, + Rc::new(Parent::new(path.root_head, head, vec![])), + ) + }; + + // Register new solution and actions + if head_created + || edge_created + // Don't register if we already have this production + // reduced between these heads + || !gss.parent(edge).possibilities.borrow().iter().any( + |t| match **t { + SPPFTree::Term { .. } => false, + SPPFTree::NonTerm { prod, .. } => { + prod == production + } + }, + ) + { log!( - " {} {}.", - "Preparing shifts/reduces for head".green(), - new_head_idx.index() + " {}", + format!( + "Register new solution for {} -> {}.", + head.index(), + path.root_head.index() + ) + .green() ); + let root_head = gss.head(path.root_head); + let start_head = gss.head(start_head); + let solution = Rc::new(SPPFTree::NonTerm { + prod: production, + data: TreeData { + range: Range { + start: root_head.position_ahead, + end: start_head.position_before, + }, + location: Location { + start: root_head.location_pos_ahead, + end: Some(start_head.location_pos_before), + }, + layout: root_head.layout_ahead(), + }, + children: path.parents, + }); + gss.parent(edge) + .possibilities + .borrow_mut() + .push(solution); + + // Register actions for &action in actions { match action { Action::Reduce(production, length) => { - log!( - " {}: {:?}, len {}", - "Register new reduction".green(), - production, - length - ); - pending_reductions.push_back(Reduction { - start: if length > 0 { - ReductionStart::Edge( - edge.expect( - "Edge must be created in add_solution!")) + if length > 0 || head_created { + log!( + " {}: {:?}, len {}", + "Register new reduction".green(), + production, + length + ); + pending_reductions.push_back( + Reduction { + start: if length > 0 { + ReductionStart::Edge(edge) } else { - ReductionStart::Node(new_head_idx) + ReductionStart::Node(head) }, - production, - length, - }); + production, + length, + }, + ); + } } Action::Shift(s) => { - log!( - " {}", - format!( - "Adding head {} to pending shifts.", - new_head_idx.index() - ) - .green() - ); - pending_shifts.push((new_head_idx, s)) + if head_created { + log!( + " {}", + format!( + "Adding head {} to pending shifts.", + head.index() + ) + .green() + ); + pending_shifts.push((head, s)); + } } Action::Accept => { - log!( - " {}", - format!( - "Accepting head {}.", - new_head_idx.index() - ) - .red() - ); - accepted_heads.push(new_head_idx) + if head_created { + log!( + " {}", + format!( + "Accepting head {}.", + head.index() + ) + .red() + ); + accepted_heads.push(head) + } } Action::Error => panic!("Cannot happen!"), } } - } else { - log!(" No actions for reduced head. Not creating."); } + } } } @@ -815,7 +820,7 @@ where I: Input + ?Sized + Debug, L: Lexer<'i, GssHead<'i, I, S, TK>, S, TK, Input = I>, S: State + Debug + Ord, - P: Copy + Debug + Into, + P: Copy + Debug + Into + PartialEq, TK: Copy + Debug + Ord + Default + 'i, D: ParserDefinition, { @@ -900,6 +905,11 @@ where } let forest = self.create_forest(gss, accepted_heads); + log!( + "\n{}. {}", + "Finished".red(), + format!("{} solutions found.", forest.solutions()).green() + ); Ok(forest) } diff --git a/tests/build.rs b/tests/build.rs index b2c8d76a..c07b80f2 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -86,7 +86,7 @@ fn main() { ), ( "glr/special/right_nullable", - Box::new(|s| s.parser_algo(ParserAlgo::GLR).print_table(true)), + Box::new(|s| s.parser_algo(ParserAlgo::GLR)), ), ]; diff --git a/tests/src/output_dir/output_dir.rs b/tests/src/output_dir/output_dir.rs index 3af958ab..6031d246 100644 --- a/tests/src/output_dir/output_dir.rs +++ b/tests/src/output_dir/output_dir.rs @@ -41,7 +41,7 @@ impl From for usize { } } #[allow(clippy::enum_variant_names)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum ProdKind { AP1, B1P1,