Mercurial > hg > callgraph
comparison prolog/callgraph.pl @ 0:b12b733d1dd0
Initial check-in.
author | samer |
---|---|
date | Sun, 23 Mar 2014 16:05:36 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:b12b733d1dd0 |
---|---|
1 :- module(callgraph, | |
2 [ module_dotpdf/2 | |
3 , module_dot/2 | |
4 % , assert_module_graph/1 | |
5 % , current_ugraph/1 | |
6 % , current_dot/3 | |
7 % , module_graphml/1 | |
8 ]). | |
9 | |
10 /** <module> Visualisation of inter-predicate call graphs | |
11 | |
12 ---++ Usage | |
13 This module allows you to produce a call graph of a module, where | |
14 nodes (boxes) represent predicates in the module and an edge between | |
15 two predicates indicates that one (the source end) calls the other | |
16 (the pointy end). | |
17 | |
18 By default, node styles are used to indicate whether or not the predicate is | |
19 exported (bold outline), dynamic (italic label, filled), multifile (box with diagonals | |
20 in the corners). | |
21 For dynamic predicates, dashed edges are used to represent operations which | |
22 mutate (assert to or retract from) the predicate. | |
23 | |
24 Items in the recorded database are also represented, as they consitute mutable | |
25 state much like dynamic predicates. Recorded items are labelled as Key:Functor/Arity | |
26 and drawn in a filled octagonal box. Dashed edges represents writing to the | |
27 recorded database and ordinary solid edges represent reading. | |
28 | |
29 Basic method of usage is: | |
30 1. Load the module you want to graph. | |
31 2. Load this module. | |
32 3. Generate and write the module call graph to a PDF document: | |
33 == | |
34 ?- module_dotpdf(ModuleName,[]). | |
35 == | |
36 | |
37 See module_dot/2 for options that affect graph content and style, and | |
38 module_dotpdf/2 for options that affect rendering. | |
39 | |
40 ---++ Implementation notes | |
41 | |
42 NB. This is very preliminary. The intereface is not necessarily stable. | |
43 The dot DCG implementation is a bit embarassing but it does the job for now. | |
44 | |
45 Three parts to this: | |
46 1. Using prolog_walk_code/1 to compile a database of inter-predicate calls | |
47 into dynamic predicates calls/2, mutates/2, reads/2 and writes/2. | |
48 2. Possible transformations of graph (eg pruning). | |
49 3. Collect information about desired modules/predicates into a Dot graph | |
50 structure as defined by dotdct.pl | |
51 */ | |
52 | |
53 | |
54 :- use_module('library/dcgu'). | |
55 :- use_module('library/dot'). | |
56 % :- use_module(library(graphml_ugraph)). | |
57 | |
58 % ------------ Building the call graph in the Prolog database ----------------------- | |
59 | |
60 :- dynamic calls/2. | |
61 :- dynamic mutates/2. | |
62 :- dynamic reads/2, writes/2. | |
63 | |
64 %% assert_module_graph(+ModuleName) is det. | |
65 % Analyses module ModuleName (using prolog_walk_code/1) asserting information about the | |
66 % call graph to a set of private dynamic predicates. | |
67 assert_module_graph(Mod) :- | |
68 retract_call_graph, | |
69 prolog_walk_code([ trace_reference(_), module(Mod), on_trace(assert_edge(Mod)), source(false) ]), | |
70 predicate_property(calls(_,_), number_of_clauses(N)), | |
71 format('Got ~D edges~n', [N]). | |
72 | |
73 retract_call_graph :- | |
74 retractall(calls(_,_)), | |
75 retractall(mutates(_,_)), | |
76 retractall(reads(_,_)), | |
77 retractall(writes(_,_)). | |
78 | |
79 % Irreflexive version of calls. | |
80 calls_ir(Caller,Callee) :- | |
81 calls(Caller,Callee), | |
82 Caller\=Callee. | |
83 | |
84 %% mutator(+Goal,-Pred) is semidet. | |
85 % True when Goal changes predicate Pred. | |
86 mutator(assert(H:-_),H) :- !. | |
87 mutator(assertz(H:-_),H) :- !. | |
88 mutator(asserta(H:-_),H) :- !. | |
89 mutator(assert(H),H) :- !. | |
90 mutator(assertz(H),H). | |
91 mutator(asserta(H),H). | |
92 mutator(retract(H),H). | |
93 mutator(retractall(H),H). | |
94 caller(retract(H), H). | |
95 | |
96 reader(recorded(Key,Term,_), Node) :- goal_pred(Key:Term,Node). | |
97 reader(recorded(Key,Term), Node) :- goal_pred(Key:Term,Node). | |
98 writer(recorda(Key,Term,_), Node) :- goal_pred(Key:Term,Node). | |
99 writer(recorda(Key,Term), Node) :- goal_pred(Key:Term,Node). | |
100 writer(recordz(Key,Term,_), Node) :- goal_pred(Key:Term,Node). | |
101 writer(recordz(Key,Term), Node) :- goal_pred(Key:Term,Node). | |
102 writer(record(Key,Term,_), Node) :- goal_pred(Key:Term,Node). | |
103 writer(record(Key,Term), Node) :- goal_pred(Key:Term,Node). | |
104 | |
105 assert_edge(M, M:Head, M:Caller, _Where) :- | |
106 writer(Head,Node), !, | |
107 goal_pred(M:Caller,CallerFA), | |
108 assert_fact(writes(CallerFA,Node)). | |
109 | |
110 assert_edge(M, M:Head, M:Caller, _Where) :- | |
111 reader(Head,Node), !, | |
112 goal_pred(M:Caller,CallerFA), | |
113 assert_fact(reads(CallerFA,Node)). | |
114 | |
115 assert_edge(M, M:Head, M:Modifier, _Where) :- | |
116 mutator(Head,Modified), !, | |
117 goal_pred(M:Modified,ModifiedFA), | |
118 goal_pred(M:Modifier,ModifierFA), | |
119 assert_fact(mutates(ModifierFA,ModifiedFA)), | |
120 ( caller(Head,Modified) | |
121 -> assert_fact(calls(ModifierFA,ModifiedFA)) | |
122 ). | |
123 | |
124 % matches calls within module M. | |
125 assert_edge(M, M:Callee, M:Caller, _Where) :- | |
126 \+predicate_property(M:Callee, built_in), | |
127 \+predicate_property(M:Callee, imported_from(_)), !, | |
128 goal_pred(M:Callee,CalleeFA), | |
129 goal_pred(M:Caller,CallerFA), | |
130 assert_fact(calls(CallerFA,CalleeFA)). | |
131 | |
132 % do nothing silently for other calls | |
133 assert_edge(_,_,_,_). | |
134 | |
135 % asserts fact if not already asserted. | |
136 assert_fact(Head) :- call(Head) -> true; assertz(Head). | |
137 | |
138 goal_pred(M:H,M:F/A) :- nonvar(M), (nonvar(H);ground(F/A)), functor(H,F,A). | |
139 | |
140 | |
141 % ----------------------- GraphML output ---------------------- | |
142 % Leaving this out for the time being. | |
143 | |
144 % module_graphml(Mod) :- | |
145 % assert_module_graph(Mod), | |
146 % current_ugraph(Graph), | |
147 % retract_call_graph, | |
148 % format(atom(File),'~w.graphml',[Mod]), | |
149 % graphml_write_ugraph(File, nomap, [], Graph). | |
150 | |
151 % nomap(id,node(N),A) :- term_to_atom(N,A), writeln(nomap(id,node(N),A)). | |
152 % % nomap(id,edge(_,_),''). | |
153 % % [key(node, color, string), key(edge,color,string)], | |
154 % % cmap(color, node(_), green). | |
155 % % cmap(color, edge(_), red). | |
156 | |
157 | |
158 % %% current_ugraph(-Graph:graphml) is det. | |
159 % % Returns the current call graph as a GraphML document structure. | |
160 % current_ugraph(Graph) :- | |
161 % findall(Pred, (calls_ir(Mod:Pred,_);calls_ir(_,Mod:Pred)), Preds), | |
162 % sort(Preds,Preds1), | |
163 % setof(Caller-Callees, (member(Caller,Preds1), callees(Mod,Caller,Callees)), Graph). | |
164 | |
165 % callees(Mod,Caller,Callees) :- setof(Callee, calls_ir(Mod:Caller,Mod:Callee),Callees), !. | |
166 % callees(_,[]). | |
167 | |
168 | |
169 | |
170 % ----------------------- Dot output ---------------------- | |
171 | |
172 | |
173 %% module_dot(+ModuleName,Opts) is det. | |
174 % Writes a call graph for named module as a dot file named "[ModuleName].dot". | |
175 % This predicate also accepts various options (some of the these types | |
176 % refer to the GraphViz attribute types): | |
177 % * prune(Prune:bool) / false | |
178 % If true, then graph subtrees are removed using prune_subtrees/0. | |
179 % * recursive(bool) / false | |
180 % If true, then looping edges are used to decorate predicates that call themselves | |
181 % directly. Otherwise, such direct recursion is hidden. | |
182 % * hide_list(list(pred_ind)) / [] | |
183 % A list of predicate (name/arity) to hide from the graph. | |
184 % * arrowhead(arrow_type) / vee | |
185 % Dot arrowhead name as an atom, for all call edges. | |
186 % * export_style(node_style) / bold | |
187 % Dot node style for exported predicates. | |
188 % * dynamic_shape(node_shape) / box | |
189 % Dot node shape for dynamic predicates. | |
190 % * dynamic_style(node_style) / filled | |
191 % Dot node style for dynamic predicates. | |
192 % * multifile_shape(node_shape) / box | |
193 % Dot node shape for multifile predicates. | |
194 % * multifile_style(node_style) / diagonals | |
195 % Dot node style for multifile predicates. | |
196 % * recorded_shape(node_shape) / octagon | |
197 % Dot node shape for recorded facts. | |
198 % * recorded_style(node_style) / filled | |
199 % Dot node style for recorded facts. | |
200 % * mutate_style(line_style) / dashed | |
201 % Dot line style for edges representing mutation of a dynamic predicate. | |
202 % * read_style(line_style) / solid | |
203 % Dot line style for edges representing readed of a recorded fact. | |
204 % * write_style(line_style) / dashed | |
205 % Dot line style for edges representing writing of a recored fact. | |
206 % * font(S:string) | |
207 % Font family (as a list of codes) for all labels. How these names are | |
208 % interpreted depends on your host operating system. On Mac OS X, I find | |
209 % I am able to use any font available in the "Font Book" application with | |
210 % the name written exactly (including spaces) as in the "Font" column. | |
211 % Default is "Times". | |
212 % | |
213 % Types for Dot attributes: | |
214 % see http://graphviz.org/Documentation.php for more details on | |
215 % * arrow_type: http://graphviz.org/content/attrs#karrowType | |
216 % * node_shape: http://graphviz.org/content/node-shapes | |
217 % * node_style: http://graphviz.org/content/attrs#kstyle | |
218 % | |
219 % == | |
220 % line_style ---> solid ; dashed ; dotted ; bold. | |
221 % arrow_type ---> normal ; vee ; empty ; box ; none ; dot ; ... . | |
222 % node_shape ---> box ; ellipse ; circle ; diamond ; trapezium ; parallelogram | |
223 % ; house ; square ; pentagon ; hexagon ; septagon ; octagon ; ... . | |
224 % node_style ---> solid ; dashed ; dotted ; bold ; rounded | |
225 % ; diagonals ; filled ; striped ; wedged. | |
226 % == | |
227 module_dot(Mod,Opts) :- | |
228 assert_module_graph(Mod), | |
229 (option(prune(true),Opts) -> prune_subtrees; true), | |
230 current_dot(Mod,Opts,Graph), | |
231 retract_call_graph, | |
232 format(atom(File),'~w.dot',[Mod]), | |
233 graph_dot(Graph,File). | |
234 | |
235 %% module_dotpdf(+Mod,Opts) is det. | |
236 % Writes a call graph for module Mod as a PDF file named "[Mod].pdf". | |
237 % As well as the options accepted by module_dot/2, this predicate also accepts: | |
238 % * method(Method:graphviz_method) / unflatten | |
239 % Determines which GraphViz programs are used to render the graph. The type | |
240 % graphviz_method is defined as: | |
241 % == | |
242 % graphviz_method ---> dot ; neato; fdp ; sfdp ; circo ; twopi | |
243 % ; unflatten(list(unflatten_opt)) | |
244 % ; unflatten. | |
245 % unflatten_opt ---> l(N:natural) % -l<N> | |
246 % ; fl(N:natural) % -f -l<N> | |
247 % ; c(natural). % -c<N> | |
248 % == | |
249 % The unflatten methods filter the graph through unflatten before passing | |
250 % on to dot. | |
251 module_dotpdf(Mod,Opts) :- | |
252 assert_module_graph(Mod), | |
253 option(method(Method),Opts,unflatten), | |
254 (option(prune(true),Opts) -> prune_subtrees; true), | |
255 current_dot(Mod,Opts,Graph), | |
256 retract_call_graph, | |
257 dotrun(Method,pdf,Graph,Mod). | |
258 | |
259 | |
260 %% current_dot(+Mod,+Opts,-DotGraph) is det. | |
261 % Returns the currently asserted graph as a dot graph structure, | |
262 % using the given options and restricting the graph to module Mod. | |
263 % The options are documented under module_dot/2. | |
264 current_dot(Mod,Opts,digraph(Mod,Graph)) :- | |
265 predopt(Opts,recorded,DBNodeAttr,[]), | |
266 setof(with_opts(node(Pred),Attrs), node_decl(Opts,Mod,Pred,Attrs), Decls), | |
267 esetof(with_opts(node(N),DBNodeAttr), db_node(N), DBNodes), | |
268 writeln(DBNodes), | |
269 module_graph(Mod,Opts,Decls,DBNodes,Graph,[]). | |
270 | |
271 node_decl(Opts,Mod,Pred,Attrs) :- | |
272 declarable_node(Opts,Mod,Pred), | |
273 pred_attr(Opts,Mod:Pred,Attrs). | |
274 | |
275 read_edge(Mod,_Opts,Pred,DBTerm) :- reads(Mod:Pred, DBTerm). | |
276 write_edge(Mod,_Opts,Pred,DBTerm) :- writes(Mod:Pred, DBTerm). | |
277 | |
278 declarable_node(Opts,M,Pred) :- | |
279 option(hide_list(HideList),Opts,[]), | |
280 ( predicate_property(M:Head, dynamic) | |
281 ; predicate_property(M:Head, exported) | |
282 ; predicate_property(M:Head, multifile) | |
283 ), | |
284 \+predicate_property(M:Head, built_in), | |
285 \+predicate_property(M:Head, imported_from(_)), | |
286 goal_pred(M:Head,M:Pred), | |
287 \+member(Pred, ['$mode'/2,'$pldoc'/4, '$pldoc_link'/2]), | |
288 \+member(Pred,HideList). | |
289 | |
290 visible_call(Mod,Opts,Caller,Callee) :- | |
291 option(hide_list(L),Opts,[]), | |
292 option(recursive(T),Opts,false), | |
293 calls(Mod:Caller,Mod:Callee), | |
294 (T=false -> Caller\=Callee; true), | |
295 \+member(Caller,L), | |
296 \+member(Callee,L). | |
297 | |
298 visible_mutation(Mod,Opts,P1,P2) :- | |
299 option(hide_list(L),Opts,[]), | |
300 mutates(Mod:P1,Mod:P2), | |
301 \+member(P1,L), | |
302 \+member(P2,L). | |
303 | |
304 module_graph(Mod,Opts,Decls,DBNodes) --> | |
305 { edgeopt(Opts,mutates,MAttr,[]), | |
306 edgeopt(Opts,reads,RAttr,[]), | |
307 edgeopt(Opts,writes,WAttr,[]) | |
308 }, | |
309 seqmap(global_opts(Opts),[graph,node,edge]), % global attributes | |
310 list(Decls), list(DBNodes), | |
311 findall(arrow(Caller,Callee), visible_call(Mod,Opts,Caller,Callee)), | |
312 findall(with_opts(arrow(Mutator,Mutatee),MAttr), visible_mutation(Mod,Opts,Mutator,Mutatee)), | |
313 findall(with_opts(arrow(Pred,DBTerm),RAttr), read_edge(Mod,Opts,Pred,DBTerm)), | |
314 findall(with_opts(arrow(Pred,DBTerm),WAttr), write_edge(Mod,Opts,Pred,DBTerm)). | |
315 | |
316 esetof(A,B,C) :- setof(A,B,C) *-> true; C=[]. | |
317 | |
318 list([]) --> []. | |
319 list([X|XS]) --> [X], list(XS). | |
320 | |
321 db_node(N) :- reads(_,N); writes(_,N). | |
322 | |
323 global_opts(_,graph) --> []. | |
324 global_opts(O,node) --> {font(normal,O,F)}, [node_opts([ shape=at(box), fontname=qq(F) ])]. | |
325 global_opts(O,edge) --> {option(arrowhead(AH),O,vee)}, [edge_opts([ arrowhead=at(AH) ])]. | |
326 | |
327 predopt(O,exported) --> | |
328 {option(export_style(S),O,bold)}, | |
329 {font(bold,O,F)}, | |
330 [ style = qq(at(S)), fontname=qq(F) ]. | |
331 predopt(O,dynamic) --> | |
332 {option(dynamic_shape(S),O,box)}, | |
333 {option(dynamic_style(St),O,filled)}, | |
334 {font(italic,O,F)}, | |
335 [ shape = at(S), fontname=qq(F), style = qq(at(St)) ]. | |
336 predopt(O,multifile) --> | |
337 {option(multifile_shape(S),O,box)}, | |
338 {option(multifile_style(St),O,diagonals)}, | |
339 [ shape = at(S), style = qq(at(St)) ]. | |
340 predopt(O,recorded) --> | |
341 {option(recorded_shape(S),O,octagon)}, | |
342 {option(recorded_style(St),O,filled)}, | |
343 [ shape = at(S), style = qq(at(St)) ]. | |
344 | |
345 edgeopt(O,mutates) --> {option(mutate_style(S),O,dashed)}, [ style = qq(at(S)) ]. | |
346 edgeopt(O,writes) --> {option(write_style(S),O,dashed)}, [ style = qq(at(S)) ]. | |
347 edgeopt(O,reads) --> {option(read_style(S),O,solid)}, [ style = qq(at(S)) ]. | |
348 | |
349 pred_attr(O,Pred,Attrs1) :- | |
350 goal_pred(Goal,Pred), | |
351 phrase( ( if( predicate_property(Goal,dynamic), predopt(O,dynamic)), | |
352 if( predicate_property(Goal,multifile), predopt(O,multifile)), | |
353 if( predicate_property(Goal,exported), predopt(O,exported))), | |
354 Attrs, []), | |
355 Attrs = [_|_], | |
356 compile_attrs(Attrs,[],Attrs1). | |
357 | |
358 compile_attrs([],A,A). | |
359 compile_attrs([style=S|AX],AttrsSoFar,FinalAttrs) :- !, | |
360 ( select(style=OS,AttrsSoFar,A1) | |
361 -> combine_styles(S,OS,NS), A2=[style=NS|A1] | |
362 ; A2=[style=S|AttrsSoFar] | |
363 ), | |
364 compile_attrs(AX,A2,FinalAttrs). | |
365 compile_attrs([A|AX],A0,A2) :- compile_attrs(AX,[A|A0],A2). | |
366 | |
367 combine_styles(qq(S1),qq(S2),qq((S1,",",S2))). | |
368 | |
369 % compile_attrs1([],A,[]). | |
370 % compile_attrs1([A|AX],A0,[A|A1]) :- compile_attrs1(AX,[A|A0],A1). | |
371 | |
372 font_family(O) --> {option(font(FF),O,"Times")}, seqmap(out,FF). | |
373 font(normal,O,F) :- phrase(font_family(O),F,[]). | |
374 font(italic,O,F) :- phrase((font_family(O)," Italic"),F,[]). | |
375 font(bold,O,F) :- phrase((font_family(O)," Bold"),F,[]). | |
376 | |
377 do_until(P) :- | |
378 call(P,Flag), | |
379 ( Flag=true -> true | |
380 ; do_until(P) | |
381 ). | |
382 | |
383 prunable(Node) :- | |
384 setof( Parent, calls(Parent,Node), [_]), % node has exactly one caller | |
385 \+calls(Node,_), % no children | |
386 \+mutates(Node,_), % doesn't affect dynamic preds | |
387 goal_pred(G,Node), | |
388 \+predicate_property(G,dynamic), | |
389 \+predicate_property(G,multifile), | |
390 \+predicate_property(G,exported). | |
391 | |
392 %% prune_subtrees is det. | |
393 % Operates on the currently asserted graph (see assert_module_graph/1). It searches | |
394 % for any part of the call graph which is a pure tree, and removes all the nodes below | |
395 % the root. Thus, any 'leaf' predicate which is only ever called by one 'parent' is | |
396 % removed. This is step is repeated until there are no more leaf predicates. The idea | |
397 % is that the child tree can be considered 'private' to its parent. | |
398 prune_subtrees :- do_until(prune_subtrees). | |
399 | |
400 prune_subtrees(false) :- | |
401 bagof(Node, prunable(Node), Nodes), !, | |
402 forall(member(N,Nodes), (writeln(pruning:N), retractall(calls(_,N)))). | |
403 | |
404 prune_subtrees(true). |