| @@ -7,6 +7,7 @@ Full reference and more details is here: | |||
| http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ | |||
| """ | |||
| from random import randint | |||
| from ..tree import Tree | |||
| from ..exceptions import ParseError | |||
| from ..lexer import Token | |||
| @@ -15,6 +16,7 @@ from ..grammar import NonTerminal, Terminal | |||
| from .earley_common import Column, Derivation | |||
| from collections import deque | |||
| from importlib import import_module | |||
| class ForestNode(object): | |||
| pass | |||
| @@ -61,7 +63,13 @@ class SymbolNode(ForestNode): | |||
| return hash((self.s, self.start.i, self.end.i)) | |||
| def __repr__(self): | |||
| symbol = self.s.name if isinstance(self.s, (NonTerminal, Terminal)) else self.s[0].origin.name | |||
| if self.is_intermediate: | |||
| rule = self.s[0] | |||
| ptr = self.s[1] | |||
| names = [ "{}*".format(expansion.name) if index == ptr else expansion.name for index, expansion in enumerate(rule.expansion) ] | |||
| symbol = "{} ::= {}".format(rule.origin.name, ' '.join(names)) | |||
| else: | |||
| symbol = self.s.name | |||
| return "(%s, %d, %d, %d)" % (symbol, self.start.i, self.end.i, self.priority if self.priority is not None else 0) | |||
| class PackedNode(ForestNode): | |||
| @@ -105,8 +113,14 @@ class PackedNode(ForestNode): | |||
| return self._hash | |||
| def __repr__(self): | |||
| symbol = self.s.name if isinstance(self.s, (NonTerminal, Terminal)) else self.s[0].origin.name | |||
| return "{%s, %d, %s, %s, %s}" % (symbol, self.start.i, self.left, self.right, self.priority if self.priority is not None else 0) | |||
| if isinstance(self.s, tuple): | |||
| rule = self.s[0] | |||
| ptr = self.s[1] | |||
| names = [ "{}*".format(expansion.name) if index == ptr else expansion.name for index, expansion in enumerate(rule.expansion) ] | |||
| symbol = "{} ::= {}".format(rule.origin.name, ' '.join(names)) | |||
| else: | |||
| symbol = self.s.name | |||
| return "{%s, %d, %d}" % (symbol, self.start.i, self.priority if self.priority is not None else 0) | |||
| class ForestVisitor(object): | |||
| """ | |||
| @@ -351,3 +365,78 @@ class ForestToAmbiguousTreeVisitor(ForestVisitor): | |||
| self.output_stack[-1].children.append(result) | |||
| else: | |||
| self.result = result | |||
| class ForestToPyDotVisitor(ForestVisitor): | |||
| """ | |||
| A Forest visitor which writes the SPPF to a PNG. | |||
| The SPPF can get really large, really quickly because | |||
| of the amount of meta-data it stores, so this is probably | |||
| only useful for trivial trees and learning how the SPPF | |||
| is structured. | |||
| """ | |||
| def __init__(self, rankdir="TB"): | |||
| self.pydot = import_module('pydot') | |||
| self.graph = self.pydot.Dot(graph_type='digraph', rankdir=rankdir) | |||
| def go(self, root, filename): | |||
| super(ForestToPyDotVisitor, self).go(root) | |||
| self.graph.write_png(filename) | |||
| def visit_token_node(self, node): | |||
| graph_node_id = str(id(node)) | |||
| graph_node_label = "\"{}\"".format(node.value.replace('"', '\\"')) | |||
| graph_node_color = 0x808080 | |||
| graph_node_style = "filled" | |||
| graph_node_shape = "polygon" | |||
| graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label) | |||
| self.graph.add_node(graph_node) | |||
| def visit_packed_node_in(self, node): | |||
| graph_node_id = str(id(node)) | |||
| graph_node_label = repr(node) | |||
| graph_node_color = 0x808080 | |||
| graph_node_style = "filled" | |||
| graph_node_shape = "diamond" | |||
| graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label) | |||
| self.graph.add_node(graph_node) | |||
| return iter([node.left, node.right]) | |||
| def visit_packed_node_out(self, node): | |||
| graph_node_id = str(id(node)) | |||
| graph_node = self.graph.get_node(graph_node_id)[0] | |||
| for child in [node.left, node.right]: | |||
| if child is not None: | |||
| child_graph_node_id = str(id(child)) | |||
| child_graph_node = self.graph.get_node(child_graph_node_id)[0] | |||
| self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node)) | |||
| else: | |||
| #### Try and be above the Python object ID range; probably impl. specific, but maybe this is okay. | |||
| child_graph_node_id = str(randint(100000000000000000000000000000,123456789012345678901234567890)) | |||
| child_graph_node_style = "invis" | |||
| child_graph_node = self.pydot.Node(child_graph_node_id, style=child_graph_node_style, label="None") | |||
| child_edge_style = "invis" | |||
| self.graph.add_node(child_graph_node) | |||
| self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node, style=child_edge_style)) | |||
| def visit_symbol_node_in(self, node): | |||
| graph_node_id = str(id(node)) | |||
| graph_node_label = repr(node) | |||
| graph_node_color = 0x808080 | |||
| graph_node_style = "filled" | |||
| if node.is_intermediate: | |||
| graph_node_shape = "ellipse" | |||
| else: | |||
| graph_node_shape = "rectangle" | |||
| graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label) | |||
| self.graph.add_node(graph_node) | |||
| return iter(node.children) | |||
| def visit_symbol_node_out(self, node): | |||
| graph_node_id = str(id(node)) | |||
| graph_node = self.graph.get_node(graph_node_id)[0] | |||
| for child in node.children: | |||
| child_graph_node_id = str(id(child)) | |||
| child_graph_node = self.graph.get_node(child_graph_node_id)[0] | |||
| self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node)) | |||