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).