基本上是把程式呼叫的路線,
用Graphviz畫成圖,
而Graphviz基本上也是一種DSL語言,
為了特定領域所創作的語言。
所以本需求,基本上還是把Cymbol(C語言的子集)語言轉成Graphviz語言。
int main() { fact(); a(); }
float fact(int n) {
print(n);
if ( n==0 ) then return 1;
return n * fact(n-1);
}
void a() { int x = b(); if false then {c(); d();} }
void b() { c(); }
void c() { b(); }
void d() { }
void e() { }
轉換成
Graphviz語言
digraph G {
ranksep=.25;
edge [arrowsize=.5]
node [shape=circle, fontname="ArialNarrow",
fontsize=12, fixedsize=true, height=.45];
main; fact; a; b; c; d; e;
main -> fact;
main -> a;
fact -> print;
fact -> fact;
a -> b;
a -> c;
a -> d;
b -> c;
c -> b;
}
這兩種語言的差異有山大,
而且用途也不一樣,
可以有初步的想法,就是文法中可以很清楚的把函數的型定義清楚,在ENTER函數時,把函數名稱放進一個資料結構裏,
可以得到所有的函數,而函數中呼叫函數,觸發的時機,找到相對應的事件裏,拉上關係。
以本例而言,是用生成的CODE裏的,
static class FunctionListener extends CymbolBaseListener {
Graph graph = new Graph();
String currentFunctionName = null;
public void enterFunctionDecl(CymbolParser.FunctionDeclContext ctx) {
currentFunctionName = ctx.ID().getText();
graph.nodes.add(currentFunctionName);
}
public void exitCall(CymbolParser.CallContext ctx) {
String funcName = ctx.ID().getText();
// map current function to the callee
graph.edge(currentFunctionName, funcName);
}
}
巧妙的利用enterFunctionDecl,這時抓到所有函數的名稱,放到資料結構裏,達成第一個目的。
接著在exitCall裏,找出呼叫的關係。
當專案的程式高達數萬行時,一些源碼方析的工具,就相形重要了。
而呼叫圖(CALL GRAPH)是其中一個重要的分析手法。