public String toDOT() {
StringBuilder buf = new StringBuilder();
buf.append("digraph G {\n");
buf.append(" ranksep=.25;\n");
buf.append(" edge [arrowsize=.5]\n");
buf.append(" node [shape=circle, fontname=\"ArialNarrow\",\n");
buf.append(" fontsize=12, fixedsize=true, height=.45];\n");
buf.append(" ");
for (String node : nodes) { // print all nodes first
buf.append(node);
buf.append("; ");
}
buf.append("\n");
for (String src : edges.keySet()) {
for (String trg : edges.get(src)) {
buf.append(" ");
buf.append(src);
buf.append(" -> ");
buf.append(trg);
buf.append(";\n");
}
}
buf.append("}\n");
return buf.toString();
}
现在我们要做的是使用监听器填满这些数据结构,监听器需要两个字段用于记录。
123
static class FunctionListener extends CymbolBaseListener {
Graph graph = new Graph();
String currentFunctionName = null;
然后应用只需要监听两个事件。首先,在语法分析器发现函数声明时记录当前的函数名。
1234
public void enterFunctionDecl(CymbolParser.FunctionDeclContext ctx) {
currentFunctionName = ctx.ID().getText();
graph.nodes.add(currentFunctionName);
}
其次,当语法分析器侦测到函数调用时,应用需要记录从当前函数到被调用函数的一条边。
12345
public void exitCall(CymbolParser.CallContext ctx) {
String funcName = ctx.ID().getText();
// map current function to the callee
graph.edge(currentFunctionName, funcName);
}
注意,函数调用不能隐藏在嵌套代码块或诸如a()这样的声明中。
1
void a() { int x = b(); if false then {c(); d();} }
无论什么时候,只要树遍历器发现函数调用就触发监听器方法exitCall()。
通过语法分析树和类FunctionListener,我们可以启动带有监听器的一个遍历器去产生输出。
1234
ParseTreeWalker walker = new ParseTreeWalker();
FunctionListener collector = new FunctionListener();
walker.walk(collector, tree);
System.out.println(collector.graph.toString())
在转储DOT字符串前,该代码会打印出函数和边的列表。
123
antlr Cymbol.g
compile *.java
run CallGraph t.cymbol