283 lines
7.1 KiB
Python
283 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
import itertools
|
|
|
|
import pygraphviz as pgv # pylint: disable=E0401
|
|
|
|
from .walkers import NodeWalker
|
|
|
|
__all__ = ['draw']
|
|
|
|
|
|
def draw(filename, grammar):
|
|
traverser = GraphvizWalker()
|
|
traverser.walk(grammar)
|
|
traverser.draw(filename)
|
|
|
|
|
|
class GraphvizWalker(NodeWalker):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.top_graph = pgv.AGraph(
|
|
directed=True, rankdir='LR', packMode='clust', splines='true',
|
|
)
|
|
self.stack = [self.top_graph]
|
|
self.node_count = 0
|
|
|
|
@property
|
|
def graph(self):
|
|
return self.stack[-1]
|
|
|
|
def draw(self, filename):
|
|
self.graph.layout(prog='dot')
|
|
# WARNING: neato generated graphics hang my GPU
|
|
# self.graph.layout(prog='neato')
|
|
self.graph.draw(filename)
|
|
|
|
def push_graph(self, name=None, **attr):
|
|
if name is None:
|
|
self.node_count += 1
|
|
name = 'g%d' % self.node_count
|
|
self.stack.append(self.graph.add_subgraph(name, **attr))
|
|
return self.graph
|
|
|
|
def pop_graph(self):
|
|
self.stack.pop()
|
|
|
|
def node(self, name, id=None, **attr):
|
|
if id is None:
|
|
self.node_count += 1
|
|
id = 'n%d' % self.node_count
|
|
else:
|
|
try:
|
|
return self.graph.get_node(id)
|
|
except KeyError:
|
|
pass
|
|
self.graph.add_node(id, **attr)
|
|
n = self.graph.get_node(id)
|
|
n.attr['label'] = name
|
|
# n.attr['shape'] = 'circle'
|
|
return n
|
|
|
|
def tnode(self, name, **attr):
|
|
return self.node(name, **attr)
|
|
|
|
def dot(self):
|
|
n = self.node('')
|
|
n.attr['shape'] = 'point'
|
|
n.attr['size'] = 0.0000000001
|
|
n.attr['label'] = ''
|
|
return n
|
|
|
|
def start_node(self):
|
|
return self.dot()
|
|
|
|
def ref_node(self, name):
|
|
n = self.node(name)
|
|
n.attr['shape'] = 'box'
|
|
return n
|
|
|
|
def rule_node(self, name, **attr):
|
|
n = self.node(name, **attr)
|
|
n.attr['shape'] = 'parallelogram'
|
|
return n
|
|
|
|
def end_node(self):
|
|
n = self.node('')
|
|
n.attr['shape'] = 'point'
|
|
n.attr['width'] = 0.1
|
|
return n
|
|
|
|
def edge(self, s, e, **attr):
|
|
self.graph.add_edge(s, e, **attr)
|
|
edge = self.graph.get_edge(s, e)
|
|
# edge.attr['arrowhead'] = 'normal'
|
|
edge.attr['arrowhead'] = 'none'
|
|
return edge
|
|
|
|
def redge(self, s, e):
|
|
edge = self.edge(s, e)
|
|
edge.attr['dir'] = 'back'
|
|
return edge
|
|
|
|
def zedge(self, s, e):
|
|
return self.edge(s, e, len=0.000001)
|
|
|
|
def nedge(self, s, e):
|
|
return self.edge(s, e, style='invisible', dir='none')
|
|
|
|
def path(self, p):
|
|
self.graph.add_path(p)
|
|
|
|
def subgraph(self, name, bunch):
|
|
self.top_graph.add_subgraph(name)
|
|
|
|
def concat(self, *args):
|
|
return list(itertools.chain(*args))
|
|
|
|
def _walk_decorator(self, d):
|
|
return self.walk(d.exp)
|
|
|
|
def walk_default(self, node):
|
|
raise NotImplementedError('No walking for ', type(node).__name__)
|
|
|
|
def walk__decorator(self, d):
|
|
return self.walk(d.exp)
|
|
|
|
def walk__grammar(self, g):
|
|
self.push_graph(g.name + '0')
|
|
try:
|
|
vrules = [self.walk(r) for r in reversed(g.rules)]
|
|
finally:
|
|
self.pop_graph()
|
|
self.push_graph(g.name + '1')
|
|
try:
|
|
pass
|
|
# link all rule starting nodes with invisible edges
|
|
# starts = [self.node(r.name, id=r.name) for r in g.rules]
|
|
# for n1, n2 in zip(starts, starts[1:]):
|
|
# self.nedge(n1, n2)
|
|
finally:
|
|
self.pop_graph()
|
|
s, t = vrules[0][0], vrules[-1][1]
|
|
return (s, t)
|
|
|
|
def walk__rule(self, r):
|
|
self.push_graph(r.name)
|
|
try:
|
|
i, e = self.walk(r.exp)
|
|
s = self.rule_node(r.name, id=r.name)
|
|
self.edge(s, i)
|
|
t = self.end_node()
|
|
self.edge(e, t)
|
|
return (s, t)
|
|
finally:
|
|
self.pop_graph()
|
|
|
|
def walk__based_rule(self, r):
|
|
return self.walk__rule(r)
|
|
|
|
def walk__rule_ref(self, rr):
|
|
n = self.ref_node(rr.name)
|
|
return (n, n)
|
|
|
|
def walk__special(self, s):
|
|
n = self.node(s.special)
|
|
return (n, n)
|
|
|
|
def walk__override(self, o):
|
|
return self._walk_decorator(o)
|
|
|
|
def walk__named(self, n):
|
|
return self._walk_decorator(n)
|
|
|
|
def walk__named_list(self, n):
|
|
return self._walk_decorator(n)
|
|
|
|
def walk__cut(self, c):
|
|
# c = self.node('>>')
|
|
# return (c, c)
|
|
return None
|
|
|
|
def walk__optional(self, o):
|
|
i, e = self._walk_decorator(o)
|
|
ni = self.dot()
|
|
ne = self.dot()
|
|
self.zedge(ni, i)
|
|
self.edge(ni, ne)
|
|
self.zedge(e, ne)
|
|
return (ni, ne)
|
|
|
|
def walk__closure(self, r):
|
|
self.push_graph(rankdir='TB')
|
|
try:
|
|
i, e = self._walk_decorator(r)
|
|
ni = self.dot()
|
|
self.edge(ni, i)
|
|
self.edge(e, ni)
|
|
return (ni, ni)
|
|
finally:
|
|
self.pop_graph()
|
|
|
|
def walk__positive_closure(self, r):
|
|
i, e = self._walk_decorator(r)
|
|
if i == e:
|
|
self.redge(e, i)
|
|
else:
|
|
self.edge(e, i)
|
|
return (i, e)
|
|
|
|
def walk__join(self, r):
|
|
i, e = self._walk_decorator(r)
|
|
n = self.tnode(r.sep)
|
|
self.edge(i, n)
|
|
self.edge(n, e)
|
|
return (i, e)
|
|
|
|
def walk__group(self, g):
|
|
return self._walk_decorator(g)
|
|
|
|
def walk__choice(self, c):
|
|
vopt = [self.walk(o) for o in c.options]
|
|
vopt = [o for o in vopt if o is not None]
|
|
ni = self.dot()
|
|
ne = self.dot()
|
|
for i, e in vopt:
|
|
self.edge(ni, i)
|
|
self.edge(e, ne)
|
|
return (ni, ne)
|
|
|
|
def walk__sequence(self, s):
|
|
vseq = [self.walk(x) for x in s.sequence]
|
|
vseq = [x for x in vseq if x is not None]
|
|
i, _ = vseq[0]
|
|
_, e = vseq[-1]
|
|
if i != e:
|
|
bunch = zip(
|
|
[a for _x, a in vseq[:-1]],
|
|
[b for b, _y in vseq[1:]],
|
|
strict=False,
|
|
)
|
|
for n, n1 in bunch:
|
|
self.edge(n, n1)
|
|
return (i, e)
|
|
|
|
def walk__lookahead(self, la):
|
|
_, e = self._walk_decorator(la)
|
|
n = self.node('&')
|
|
self.edge(n, e)
|
|
return (n, e)
|
|
|
|
def walk__negative_lookahead(self, la):
|
|
_, e = self._walk_decorator(la)
|
|
n = self.node('!')
|
|
self.edge(n, e)
|
|
return (n, e)
|
|
|
|
def walk__rule_include(self, la):
|
|
_, e = self._walk_decorator(la)
|
|
n = self.node('>')
|
|
self.edge(n, e)
|
|
return (n, e)
|
|
|
|
def walk__pattern(self, p):
|
|
n = self.tnode(p.pattern)
|
|
return (n, n)
|
|
|
|
def walk__token(self, t):
|
|
n = self.tnode(t.token)
|
|
return (n, n)
|
|
|
|
def walk__void(self, v):
|
|
n = self.dot()
|
|
return (n, n)
|
|
|
|
def walk__constant(self, t):
|
|
n = self.tnode(f'`{t.ast}`')
|
|
return (n, n)
|
|
|
|
def walk__eof(self, v):
|
|
# n = self.node('$')
|
|
# return (n, n)
|
|
return None
|