lib/rley/parser/parse_forest_builder.rb in rley-0.3.01 vs lib/rley/parser/parse_forest_builder.rb in rley-0.3.04

- old
+ new

@@ -1,5 +1,10 @@ +require_relative '../syntax/terminal' +require_relative '../syntax/non_terminal' +require_relative '../gfg/end_vertex' +require_relative '../gfg/item_vertex' +require_relative '../gfg/start_vertex' require_relative '../sppf/epsilon_node' require_relative '../sppf/non_terminal_node' require_relative '../sppf/alternative_node' require_relative '../sppf/parse_forest' @@ -7,19 +12,22 @@ module Parser # This module is used as a namespace # Builder GoF pattern. Builder pattern builds a complex object # (say, a parse forest) from simpler objects (terminal and non-terminal # nodes) and using a step by step approach. class ParseForestBuilder - # The sequence of input tokens + # The sequence of input tokens attr_reader(:tokens) # Link to forest object attr_reader(:forest) # Link to current path attr_reader(:curr_path) + # The last parse entry visited + attr_reader(:last_visitee) + # A hash with pairs of the form: visited parse entry => forest node attr_reader(:entry2node) # A hash with pairs of the form: parent end entry => path to alternative node # This is needed for synchronizing backtracking @@ -32,75 +40,56 @@ @entry2path_to_alt = {} end def receive_event(anEvent, anEntry, anIndex) # puts "Event: #{anEvent} #{anEntry} #{anIndex}" - case anEntry.vertex - when GFG::StartVertex - process_start_entry(anEvent, anEntry, anIndex) - - when GFG::ItemVertex - process_item_entry(anEvent, anEntry, anIndex) - - when GFG::EndVertex - process_end_entry(anEvent, anEntry, anIndex) - else - fail NotImplementedError + if anEntry.dotted_entry? + process_item_entry(anEvent, anEntry, anIndex) + elsif anEntry.start_entry? + process_start_entry(anEvent, anEntry, anIndex) + elsif anEntry.end_entry? + process_end_entry(anEvent, anEntry, anIndex) + else + fail NotImplementedError end + + @last_visitee = anEntry end # Return the current_parent node def curr_parent() return self.curr_path.last end private def process_start_entry(anEvent, anEntry, anIndex) - self.curr_path.pop while curr_parent.kind_of?(SPPF::AlternativeNode) self.curr_path.pop end def process_end_entry(anEvent, anEntry, anIndex) case anEvent when :visit - if curr_path.empty? - # Build parse forest with root node derived from the - # accepting parse entry. - @forest = create_forest(anEntry, anIndex) - else - # if current_parent node matches the lhs non-terminal of anEntry - # set its origin to the origin of its first child (if not yet assigned) - curr_parent.range.assign(low: anEntry.origin) - @entry2node[anEntry] = self.curr_parent - if anEntry.antecedents.size > 1 - # Store current path for later backtracking - # puts "Store backtrack context #{anEntry}" - # puts "path [#{curr_path.join(', ')}]" - self.entry2path_to_alt[anEntry] = curr_path.dup - curr_parent.refinement = :or + # create a node with the non-terminal + # with same right extent as curr_entry_set_index + # add the new node as first child of current_parent + # append the new node to the curr_path + range = { low: anEntry.origin, high: anIndex } + non_terminal = anEntry.vertex.non_terminal + create_non_terminal_node(anEntry, range, non_terminal) + @forest = create_forest(curr_parent) unless @last_visitee - create_alternative_node(anEntry.antecedents.first) - end - end - when :backtrack # Restore path @curr_path = self.entry2path_to_alt[anEntry].dup # puts "Restore path #{curr_path.join(', ')}]" antecedent_index = curr_parent.subnodes.size # puts "Current parent #{curr_parent.to_string(0)}" # puts "Antecedent index #{antecedent_index}" - create_alternative_node(anEntry.antecedents[antecedent_index]) - when :revisit - # Remove most recent entry in path - @curr_path.pop - # Remove also its reference in parent - curr_parent.subnodes.pop - + when :revisit # Retrieve the already existing node corresponding to re-visited entry popular = @entry2node[anEntry] # Share with parent curr_parent.add_subnode(popular) @@ -108,110 +97,106 @@ else fail NotImplementedError end end -=begin - if it is a dotted item entry (pattern is: X => α . β): - if there is at least one symbol before the dot - if that symbol is a non-terminal: - if that symbol is a terminal # else - create a token node, - with same origin as token, - with same right extent = origin + 1 - add the new node as first child of current_parent - set curr_entry_set_index to curr_entry_set_index - 1 - if it is a dotted item entry with a beginning dot: # else - if current_parent node matches the lhs non-terminal of anEntry - set its origin to the origin of its first child (if not yet assigned) - remove this node from the path -=end def process_item_entry(anEvent, anEntry, anIndex) + if anEntry.exit_entry? + # Previous entry was an end entry (X. pattern) + # Does the previous entry have multiple antecedent? + if last_visitee.end_entry? && last_visitee.antecedents.size > 1 + # Store current path for later backtracking + # puts "Store backtrack context #{last_visitee}" + # puts "path [#{curr_path.join(', ')}]" + self.entry2path_to_alt[last_visitee] = curr_path.dup + curr_parent.refinement = :or + + create_alternative_node(anEntry) + end + end + # Retrieve the grammar symbol before the dot (if any) prev_symbol = anEntry.prev_symbol case prev_symbol when Syntax::Terminal - # create a token node, - # with same origin as token, - # with same right extent = origin + 1 - # add the new node as first child of current_parent + # Add node without changing current path create_token_node(anEntry, anIndex) - when Syntax::NonTerminal - # create a node with the non-terminal before the dot, - # with same right extent as curr_entry_set_index - # add the new node as first child of current_parent - # append the new node to the curr_path - range = { high: anIndex } - create_non_terminal_node(anEntry, range, prev_symbol) + # Do nothing when NilClass # Dot at the beginning of production if anEntry.vertex.dotted_item.production.empty? - # Empty rhs => create an epsilon node + # Empty rhs => create an epsilon node ... + # ... without changing current path create_epsilon_node(anEntry, anIndex) end + self.curr_path.pop if curr_parent.kind_of?(SPPF::AlternativeNode) end end - + # Create an empty parse forest - def create_forest(anEntry, anIndex) - full_range = { low: 0, high: anIndex } - root_node = create_non_terminal_node(anEntry, full_range) - return Rley::SPPF::ParseForest.new(root_node) + def create_forest(aRootNode) + return Rley::SPPF::ParseForest.new(aRootNode) end # Factory method. Build and return an SPPF non-terminal node. def create_non_terminal_node(anEntry, aRange, nonTSymb = nil) non_terminal = nonTSymb.nil? ? anEntry.vertex.non_terminal : nonTSymb new_node = Rley::SPPF::NonTerminalNode.new(non_terminal, aRange) entry2node[anEntry] = new_node + # puts "FOREST ADD #{curr_parent.key if curr_parent}/#{new_node.key}" add_subnode(new_node) return new_node end # Add an alternative node to the forest def create_alternative_node(anEntry) alternative = Rley::SPPF::AlternativeNode.new(anEntry.vertex, curr_parent.range) add_subnode(alternative) + # puts "FOREST ADD #{alternative.key}" return alternative end + # create a token node, + # with same origin as token, + # with same right extent = origin + 1 + # add the new node as first child of current_parent def create_token_node(anEntry, anIndex) token_position = anIndex - 1 curr_token = tokens[token_position] new_node = SPPF::TokenNode.new(curr_token, token_position) candidate = add_node_to_forest(new_node) - entry2node[anEntry] = candidate + entry2node[anEntry] = candidate - return candidate + return candidate end def create_epsilon_node(anEntry, anIndex) new_node = SPPF::EpsilonNode.new(anIndex) candidate = add_node_to_forest(new_node) - entry2node[anEntry] = candidate + entry2node[anEntry] = candidate return candidate end - + # Add the given node if not yet present in parse forest def add_node_to_forest(aNode) key_node = aNode.key if forest.include?(key_node) new_node = forest.key2node[key_node] else new_node = aNode forest.key2node[key_node] = new_node # puts "FOREST ADD #{key_node}" end - add_subnode(new_node, false) + add_subnode(new_node, false) return new_node end \ No newline at end of file