"""
Helpers around language grammar.
This module requires `antlr4 <https://pypi.python.org/pypi/antlr4-python3-runtime/>`_.
:githublink:`%|py|6`
"""
from antlr4 import ParseTreeListener
[docs]class TreeGraphListener(ParseTreeListener):
"""
This class is an attempt to run through the tree
and to convert into a graph.
.. exref::
:title: Draw a grammar graph for a small code
::
from pyensae.languages import get_parser_lexer, parse_code, get_tree_graph
from pyensae.graph_helper import run_dot
code = '''
namespace hello
{
public static class world
{
public static double function(double x, doubly y)
{
return x+y ;
}
}
}
'''
clparser, cllexer = get_parser_lexer("C#")
parser = parse_code(code, clparser, cllexer)
tree = parser.compilation_unit()
st = get_tree_graph(tree, parser)
dot = st.to_dot()
with open(name, "w") as f:
f.write(dot)
img = os.path.join(temp, "graph.png")
run_dot(name, img)
:githublink:`%|py|46`
"""
[docs] def __init__(self, parser, verbose=False, fLOG=None):
"""
constructor
:param parser: parser used to parse the code
:param verbose: display information along the path
:param fLOG: logging function
:githublink:`%|py|55`
"""
ParseTreeListener.__init__(self)
self.parser = parser
self.vertices = {}
self.edges = {}
self.verbose = verbose
self.fLOG = fLOG
@property
def Vertices(self):
"""
return vertices
:githublink:`%|py|67`
"""
return self.vertices
@property
def Edges(self):
"""
return edges
:githublink:`%|py|74`
"""
return self.edges
[docs] def _get_key_node(self, node):
line, col = node.symbol.line, node.symbol.column
return line, col, "#END#"
[docs] def _get_key_context(self, ctx):
line, col = ctx.start.line, ctx.start.column
class_name = ctx.__class__.__name__
if class_name.endswith("Context"):
class_name = class_name[0].lower() + class_name[1:-7]
return line, col, class_name
[docs] def visitTerminal(self, node):
"""
event
:githublink:`%|py|91`
"""
key = self._get_key_node(node)
val = (node.getText(), node, node.parentCtx)
self.vertices[key] = val
kc = self._get_key_context(node.parentCtx)
self.edges[kc, key] = 1
[docs] def visitErrorNode(self, node):
"""
event
:githublink:`%|py|101`
"""
key = self._get_key_node(node)
val = ("#ERROR#", node, node.parentCtx)
self.vertices[key] = val
kc = self._get_key_context(node.parentCtx)
self.edges[kc, key] = 1
[docs] def enterEveryRule(self, ctx):
"""
event
:githublink:`%|py|111`
"""
key = self._get_key_context(ctx)
self.vertices[key] = (key[-1], ctx, ctx.parentCtx)
if ctx.parentCtx is not None:
kc = self._get_key_context(ctx.parentCtx)
self.edges[kc, key] = 1
if self.verbose: # pragma: no cover
self.fLOG("+", type(ctx), ctx)
self.fLOG("+", ctx.__dict__.keys())
self.fLOG(" +", type(ctx.start), ctx.start)
self.fLOG(" +", ctx.start.__dict__.keys())
text = ctx.getText()
print(" text:", text)
if "node" in ctx.__dict__:
self.fLOG(" +", type(ctx.node), ctx.node)
self.fLOG(" +", ctx.node.__dict__.keys())
[docs] def exitEveryRule(self, ctx):
"""
event
:githublink:`%|py|132`
"""
if self.verbose: # pragma: no cover
self.fLOG("-", type(ctx), ctx)
[docs] def to_networkx(self):
"""
convert the graph into networkx
:return: `networkx Graph <https://networkx.github.io/documentation/latest/tutorial/tutorial.html?highlight=graph>`_
:githublink:`%|py|141`
"""
import networkx # pylint: disable=C0415
verti = self.Vertices
edges = self.Edges
G = networkx.Graph()
for v in verti:
G.add_node(v)
for k in edges:
G.add_edge(k[0], k[1])
return G
[docs] def draw(self, ax=None):
"""
draw the graph with networkx on matplotlib
:param matplotlib: axis
:githublink:`%|py|158`
"""
if ax is None:
import matplotlib.pyplot as plt # pylint: disable=C0415
fig, ax = plt.subplots()
G = self.to_networkx()
import networkx # pylint: disable=C0415
pos = networkx.spring_layout(G) # positions for all nodes
verti = self.Vertices
labels = {p: verti[p][0] for p in pos}
networkx.draw_networkx_nodes(G, pos, ax=ax)
networkx.draw_networkx_edges(G, pos, ax=ax)
networkx.draw_networkx_labels(G, pos, labels, font_size=16)
[docs] def to_dot(self):
"""
export the graph to DOT format
:return: string
:githublink:`%|py|179`
"""
verti = self.Vertices
edges = self.Edges
ids = {}
for k in verti:
ids[k] = len(ids)
rows = ["digraph {"]
for k, v in verti.items():
rows.append('%d [label="%s"];' % (
ids[k], v[0].replace("\\", "\\\\").replace('"', '\\"')))
for k in edges:
a, b = ids[k[0]], ids[k[1]]
rows.append("%d -> %d ;" % (a, b))
rows.append("}")
return "\n".join(rows)