222 lines
6.5 KiB
Python
222 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
#######################################################################
|
|
# Name: export.py
|
|
# Purpose: Export support for arpeggio
|
|
# Author: Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
|
|
# Copyright: (c) 2009 Igor R. Dejanović <igor DOT dejanovic AT gmail DOT com>
|
|
# License: MIT License
|
|
#######################################################################
|
|
|
|
from __future__ import unicode_literals
|
|
import io
|
|
from arpeggio import Terminal
|
|
|
|
|
|
class Exporter(object):
|
|
"""
|
|
Base class for all Exporters.
|
|
"""
|
|
|
|
def __init__(self):
|
|
super(Exporter, self).__init__()
|
|
|
|
# Export initialization
|
|
self._render_set = set() # Used in rendering to prevent
|
|
# rendering
|
|
# of the same node multiple times
|
|
|
|
self._adapter_map = {} # Used as a registry of adapters to
|
|
# ensure that the same adapter is
|
|
# returned for the same adaptee object
|
|
|
|
def export(self, obj):
|
|
"""
|
|
Export of an obj to a string.
|
|
"""
|
|
self._outf = io.StringIO()
|
|
self._export(obj)
|
|
content = self._outf.getvalue()
|
|
self._outf.close()
|
|
return content
|
|
|
|
def exportFile(self, obj, file_name):
|
|
"""
|
|
Export of obj to a file.
|
|
"""
|
|
self._outf = io.open(file_name, "w", encoding="utf-8")
|
|
self._export(obj)
|
|
self._outf.close()
|
|
|
|
def _export(self, obj):
|
|
self._outf.write(self._start())
|
|
self._render_node(obj)
|
|
self._outf.write(self._end())
|
|
|
|
def _start(self):
|
|
"""
|
|
Override this to specify the beginning of the graph representation.
|
|
"""
|
|
return ""
|
|
|
|
def _end(self):
|
|
"""
|
|
Override this to specify the end of the graph representation.
|
|
"""
|
|
return ""
|
|
|
|
|
|
class ExportAdapter(object):
|
|
"""
|
|
Base adapter class for the export support.
|
|
Adapter should be defined for every export and graph type.
|
|
|
|
Attributes:
|
|
adaptee: A node to adapt.
|
|
export: An export object used as a context of the export.
|
|
"""
|
|
def __init__(self, node, export):
|
|
self.adaptee = node # adaptee is adapted graph node
|
|
self.export = export
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Support for DOT language
|
|
|
|
|
|
class DOTExportAdapter(ExportAdapter):
|
|
"""
|
|
Base adapter class for the DOT export support.
|
|
"""
|
|
@property
|
|
def id(self):
|
|
"""
|
|
Graph node unique identification.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def desc(self):
|
|
"""
|
|
Graph node textual description.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def neighbours(self):
|
|
"""
|
|
A set of adjacent graph nodes.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class PMDOTExportAdapter(DOTExportAdapter):
|
|
"""
|
|
Adapter for ParsingExpression graph types (parser model).
|
|
"""
|
|
@property
|
|
def id(self):
|
|
return id(self.adaptee)
|
|
|
|
@property
|
|
def desc(self):
|
|
return self.adaptee.desc
|
|
|
|
@property
|
|
def neighbours(self):
|
|
if not hasattr(self, "_neighbours"):
|
|
self._neighbours= []
|
|
|
|
# Registry of adapters used in this export
|
|
adapter_map = self.export._adapter_map
|
|
|
|
for c, n in enumerate(self.adaptee.nodes):
|
|
if isinstance(n, PMDOTExportAdapter):
|
|
# if the neighbour node is already adapted use that adapter
|
|
self._neighbours.append((str(c + 1), n))
|
|
elif id(n) in adapter_map:
|
|
# current node is adaptee -> there is registered adapter
|
|
self._neighbours.append((str(c + 1), adapter_map[id(n)]))
|
|
else:
|
|
# Create new adapter
|
|
adapter = PMDOTExportAdapter(n, self.export)
|
|
self._neighbours.append((str(c + 1), adapter))
|
|
adapter_map[adapter.id] = adapter
|
|
|
|
return self._neighbours
|
|
|
|
|
|
class PTDOTExportAdapter(PMDOTExportAdapter):
|
|
"""
|
|
Adapter for ParseTreeNode graph types.
|
|
"""
|
|
@property
|
|
def neighbours(self):
|
|
if isinstance(self.adaptee, Terminal):
|
|
return []
|
|
else:
|
|
if not hasattr(self, "_neighbours"):
|
|
self._neighbours = []
|
|
for c, n in enumerate(self.adaptee):
|
|
adapter = PTDOTExportAdapter(n, self.export)
|
|
self._neighbours.append((str(c + 1), adapter))
|
|
return self._neighbours
|
|
|
|
|
|
class DOTExporter(Exporter):
|
|
"""
|
|
Export to DOT language (part of GraphViz, see http://www.graphviz.org/)
|
|
"""
|
|
def _render_node(self, node):
|
|
if not node in self._render_set:
|
|
self._render_set.add(node)
|
|
self._outf.write('\n%s [label="%s"];' %
|
|
(node.id, self._dot_label_esc(node.desc)))
|
|
#TODO Comment handling
|
|
# if hasattr(node, "comments") and root.comments:
|
|
# retval += self.node(root.comments)
|
|
# retval += '\n%s->%s [label="comment"]' % \
|
|
#(id(root), id(root.comments))
|
|
for name, n in node.neighbours:
|
|
self._outf.write('\n%s->%s [label="%s"]' %
|
|
(node.id, n.id, name))
|
|
self._outf.write('\n')
|
|
self._render_node(n)
|
|
|
|
def _start(self):
|
|
return "digraph arpeggio_graph {"
|
|
|
|
def _end(self):
|
|
return "\n}"
|
|
|
|
def _dot_label_esc(self, to_esc):
|
|
to_esc = to_esc.replace("\\", "\\\\")
|
|
to_esc = to_esc.replace('\"', '\\"')
|
|
to_esc = to_esc.replace('\n', '\\n')
|
|
return to_esc
|
|
|
|
|
|
class PMDOTExporter(DOTExporter):
|
|
"""
|
|
A convenience DOTExport extension that uses ParserExpressionDOTExportAdapter
|
|
"""
|
|
def export(self, obj):
|
|
return super(PMDOTExporter, self).\
|
|
export(PMDOTExportAdapter(obj, self))
|
|
|
|
def exportFile(self, obj, file_name):
|
|
return super(PMDOTExporter, self).\
|
|
exportFile(PMDOTExportAdapter(obj, self), file_name)
|
|
|
|
|
|
class PTDOTExporter(DOTExporter):
|
|
"""
|
|
A convenience DOTExport extension that uses PTDOTExportAdapter
|
|
"""
|
|
def export(self, obj):
|
|
return super(PTDOTExporter, self).\
|
|
export(PTDOTExportAdapter(obj, self))
|
|
|
|
def exportFile(self, obj, file_name):
|
|
return super(PTDOTExporter, self).\
|
|
exportFile(PTDOTExportAdapter(obj, self), file_name)
|