1 | % (c) 2009-2024 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
2 | % Heinrich Heine Universitaet Duesseldorf | |
3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
4 | ||
5 | /* | |
6 | * This module allows to register predicates as "debugging predicates". | |
7 | * When such a predicate is marked by the prefix operator ~~, | |
8 | * during term expansion, the term is replaced by: | |
9 | * - if remove_debug_calls is true: | |
10 | * one of the term's arguments, namely the one that is marked | |
11 | * by a star (*) during registration | |
12 | * - if remove_debug_calls is false: | |
13 | * only the prefix operator ~~ is removed, leaving the rest | |
14 | * of the term unchanged. | |
15 | * | |
16 | * Example: | |
17 | * | |
18 | * :- register_debugging_call(log(-,*)). | |
19 | * log(Info,X) :- | |
20 | * print(Info),print(' enter: '),print(X),nl, | |
21 | * call(X), | |
22 | * print(Info),print(' leave: '),print(X),nl. | |
23 | * | |
24 | * :- enable_debugging_calls. | |
25 | * p1 :- ~~log(p1, member(X,[a])). | |
26 | * :- disable_debugging_calls. | |
27 | * p2 :- ~~log(p2, member(X,[a])). | |
28 | * | |
29 | * a call of p1 prints: | |
30 | * p1 enter: member(_123,[a]) | |
31 | * p1 leave: member(a,[a]) | |
32 | * while a call to p2 prints nothing | |
33 | * | |
34 | */ | |
35 | ||
36 | :- module(debugging_calls,[register_debugging_call/1, | |
37 | register_debugging_calls/1, | |
38 | enable_debugging_calls/0, | |
39 | disable_debugging_calls/0, | |
40 | remove_debugging_calls/4 % called by term expander | |
41 | ]). | |
42 | ||
43 | :- use_module(library(lists)). | |
44 | :- use_module(library(codesio)). | |
45 | ||
46 | :- use_module(module_information,[module_info/2]). | |
47 | ||
48 | :- module_info(group,infrastructure). | |
49 | :- module_info(description,'This module provides functionality to specify meta-calls that (like a must-not-fail call) that are short-circuited when compiling production code.'). | |
50 | ||
51 | :- set_prolog_flag(double_quotes, codes). | |
52 | ||
53 | :- op(300, fy, ~~). | |
54 | ||
55 | :- dynamic remove_debug_calls/0. | |
56 | % remove_debug_calls. | |
57 | ||
58 | :- dynamic is_debug_call/3. | |
59 | ||
60 | safe_atom_codes(A,C) :- | |
61 | catch(atom_codes(A,C), | |
62 | error(representation_error(max_atom_length),_), | |
63 | (print(exception(max_atom_length)),nl,A='???')). | |
64 | ||
65 | %avl_term(X) :- var(X),!,fail. | |
66 | %avl_term(avl_set(_)). | |
67 | %avl_term((A,B)):- (avl_term(A) -> true ; avl_term(B)). | |
68 | %avl_term([H|_]) :- avl_term(H). | |
69 | ||
70 | get_current_position(Line,Col,Module,File) :- | |
71 | prolog_load_context(module, Module), | |
72 | prolog_load_context(file, File), | |
73 | prolog_load_context(term_position,TPos), % gives something like $stream_position(18031,18031,340,21) | |
74 | !, | |
75 | (TPos= '$stream_position'(_,_,Line,Col) -> true ; Line='?', Col='?'). | |
76 | get_current_position('?','?',Module,'?') :- | |
77 | prolog_load_context(module, Module),!. | |
78 | get_current_position('?','?','?','?'). | |
79 | ||
80 | safe_functor(Var,F,N) :- var(Var),!, F='$VAR', N=0. | |
81 | safe_functor(X,F,N) :- functor(X,F,N). | |
82 | ||
83 | % built-ins to observe before their execution where we observe one argument | |
84 | observe_built_in(assert(P),assert,P). | |
85 | observe_built_in(asserta(P),asserta,P). | |
86 | observe_built_in(assertz(P),assertz,P). | |
87 | observe_built_in(ground(P),ground,P). | |
88 | observe_built_in(copy_term(P,_),copy_term,P). | |
89 | observe_built_in(copy_term(P,_,_),copy_term,P). | |
90 | observe_built_in(term_variables(P,_),term_variables,P). | |
91 | observe_built_in(term_hash(P,_),term_hash,P). | |
92 | observe_built_in(term_hash(P,_,_),term_hash,P). | |
93 | observe_built_in(number_vars(P,_,_),term_variables,P). | |
94 | observe_built_in(acyclic_term(P),acyclic_term,P). | |
95 | ||
96 | % operators with two arguments which must both be large and which compare the arguments | |
97 | observe_built_in2('/=='(P,Q),'/==',P,Q) :- \+ atom(Q), \+ atom(P). | |
98 | observe_built_in2('/='(P,Q),'/=',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined). | |
99 | observe_built_in2('=='(P,Q),'==',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined). | |
100 | observe_built_in2('@<'(P,Q),'@<',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined). | |
101 | observe_built_in2('@>'(P,Q),'@>',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined). | |
102 | observe_built_in2(dif(P,Q),dif,P,Q) :- \+ atom(Q), \+ atom(P). | |
103 | observe_built_in2(unify_with_occurs_check(P,Q),unify_with_occurs_check,P,Q) :- \+ atom(Q), \+ atom(P). | |
104 | observe_built_in2(subsumes_term(P,Q),subsumes_term,P,Q) :- \+ atom(Q), \+ atom(P). | |
105 | ||
106 | ||
107 | % built-ins to observe after their execution | |
108 | observe_built_in_post(frozen(_,P),frozen,P). | |
109 | observe_built_in_post(retract(P),retract,P). | |
110 | ||
111 | ||
112 | ignore_module(aggregate). | |
113 | ignore_module(assoc). | |
114 | ignore_module(avl). | |
115 | ignore_module(a_star). | |
116 | ignore_module(binomialheap). | |
117 | ignore_module(builtins). | |
118 | ignore_module(chr). % prevent inserting observation code into some library modules | |
119 | ignore_module(chr_compiler_utility). | |
120 | ignore_module(chr_hashtable_store). | |
121 | ignore_module(chr_runtime). | |
122 | ignore_module(chr_translate). | |
123 | ignore_module(clpfd). | |
124 | ignore_module(fastrw). | |
125 | ignore_module(guard_entailment). | |
126 | ignore_module(heaps). | |
127 | ignore_module(hpattvars). | |
128 | ignore_module(hprolog). | |
129 | ignore_module(listmap). | |
130 | ignore_module(lists). | |
131 | ignore_module(mutdict). | |
132 | ignore_module(pairlist). | |
133 | ignore_module(plunit). | |
134 | ignore_module(random). | |
135 | ignore_module(samsort). | |
136 | ignore_module(system). | |
137 | ignore_module(terms). | |
138 | ignore_module(xml). | |
139 | ||
140 | ignore_module(chr_integer_inequality). | |
141 | ||
142 | ||
143 | ||
144 | :- load_files(library(system), [when(compile_time), imports([environ/2])]). | |
145 | ||
146 | remove_debugging_calls(Layout,Term,Layout,Term) :- var(Term),!. | |
147 | % comment in to see nl (newline term_expansion) info with :- set_prolog_flag(source_info,on). | |
148 | % and be sure to enable user:term_expansion in debugging_calls_te.pl | |
149 | %remove_debugging_calls(Layout,nl,Layout,format(' line:~w col:~w in ~w (~w)~n',[L,C,CM,CF])) :- !, get_current_position(L,C,CM,CF). | |
150 | :- if(environ(prob_debug_flag,true)). | |
151 | ||
152 | ||
153 | remove_debugging_calls(_,_Call,_,_) :- | |
154 | prolog_load_context(module, CurModule), | |
155 | ignore_module(CurModule),!, | |
156 | %get_current_position(L,C,CM,CF),format(' ignoring >> ~w at line:~w col:~w in ~w (~w)~n',[CurModule,L,C,CM,CF]), | |
157 | fail. | |
158 | remove_debugging_calls(Layout,Call,Layout, | |
159 | (debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze), | |
160 | (Sze>100 -> (Sze>10000 -> LRG=' ***LARGE***' ; LRG=''), | |
161 | format(' ~w(~w/~w - sze:~w~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,LRG,L,C,CM,CF]) ; true), | |
162 | Call) ) :- | |
163 | observe_built_in(Call,BI,P), !, get_current_position(L,C,CM,CF). | |
164 | remove_debugging_calls(Layout,Call,Layout, | |
165 | (debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze), | |
166 | debugging_calls:safe_functor(Q,F2,N2),terms:term_size(Q,Sze2), | |
167 | (Sze>100,Sze2>100, | |
168 | (F,N)=(F2,N2) % otherwise the terms are obviously different and the calls cannot be expensive | |
169 | -> format(' ~w(~w/~w - ~w <-> ~w/~w - ~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,F2,N2,Sze2,L,C,CM,CF]) | |
170 | ; true), Call) ) :- | |
171 | observe_built_in2(Call,BI,P,Q), !, get_current_position(L,C,CM,CF). | |
172 | remove_debugging_calls(Layout,Call,Layout, | |
173 | (Call,debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze), | |
174 | (Sze>100 -> format(' ~w(~w/~w - ~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,L,C,CM,CF]) ; true)) ) :- | |
175 | observe_built_in_post(Call,BI,P), !, get_current_position(L,C,CM,CF). | |
176 | remove_debugging_calls(Layout,time_out(P,TO,Res),Layout, | |
177 | (print(time_out(P)),nl,functor(P,F,N), | |
178 | format(' time_out(~w/~w,~w) line:~w col:~w in ~w (~w)~n', [F,N,TO,L,C,CM,CF]), | |
179 | time_out(P,TO,Res))) :- !, | |
180 | get_current_position(L,C,CM,CF). | |
181 | % comment in lines below to see retracts; be sure to enable user:term_expansion in debugging_calls_te.pl | |
182 | %remove_debugging_calls(Layout,retractall(P),Layout, | |
183 | % (retractall(P),functor(P,F,N),format(' retractall(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]))) :- !, get_current_position(L,C,CM,CF). % for some reason retractall is sometimes expensive | |
184 | % a few other calls to observe | |
185 | %remove_debugging_calls(Layout,arg(Nr,T,A),Layout,(format(' line:~w col:~w in ~w (~w)~n',[L,C,CM,CF]),arg(Nr,T,A))) :- !, get_current_position(L,C,CM,CF). | |
186 | %remove_debugging_calls(Layout,call_cleanup(P,P2),Layout,(functor(P,F,N),format(' call_cleanup(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]),call_cleanup(P,P2))) :- !, get_current_position(L,C,CM,CF). | |
187 | %remove_debugging_calls(Layout,on_exception(Exc,P,P2),Layout,(functor(P,F,N),format(' on_exception(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]),on_exception(Exc,P,P2))) :- !, get_current_position(L,C,CM,CF). | |
188 | % comment in next clause to see nesting of call_cleanup calls: | |
189 | %remove_debugging_calls(Layout,call_cleanup(P,P2),Layout, | |
190 | % (debugging_calls:get_functor(P,F,N),debugging_calls:inc_indent,write(enter),FCall, | |
191 | % call_cleanup(P,(P2,debugging_calls:dec_indent,write(exit),FCall)))) :- !, | |
192 | % FCall = format('_call_cleanup(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]), get_current_position(L,C,CM,CF). | |
193 | :- endif. | |
194 | remove_debugging_calls(LayoutIn,when(Cond,Body),LayoutOut,ResTerm) :- !, | |
195 | ResTerm = when(Cond,NewBody), % do not expand inside condition | |
196 | (LayoutIn = [] | |
197 | -> LayoutBody = [], | |
198 | LayoutOut = [] | |
199 | ; LayoutIn = [LayoutWhen,LayoutCond,LayoutBody], | |
200 | LayoutOut = [LayoutWhen,LayoutCond,LayoutBodyOut]), | |
201 | remove_debugging_calls(LayoutBody,Body,LayoutBodyOut,NewBody). | |
202 | %remove_debugging_calls(Layout,ground(Term),Layout,Term2) :- !, Term2 = (nonvar(Term),(debugging_calls:avl_term(Term) -> print(ground_check(Term)),nl, trace ; true),ground(Term)). %% comment in to observe ground checks | |
203 | remove_debugging_calls(LayoutIn,Term,Layout2,Term2) :- | |
204 | functor(Term, ~~, 1), | |
205 | arg(1,Term,Term1), | |
206 | nonvar(Term1),!,functor(Term1,Functor,Arity), | |
207 | ( LayoutIn = [] -> Layout1 = [] | |
208 | ; LayoutIn = [_,Layout1]), | |
209 | ( is_debug_call(Functor,Arity,CallPos) -> | |
210 | ( remove_debug_calls -> | |
211 | safe_arg(CallPos,Term1,TermI), | |
212 | ( Layout1 = [] -> LayoutI = [] | |
213 | ; Layout1 = [_|ArgLayout] -> | |
214 | nth1(CallPos,ArgLayout,LayoutI)), | |
215 | remove_debugging_calls(LayoutI,TermI,Layout2,Term2) | |
216 | ; | |
217 | remove_debugging_calls(Layout1,Term1,Layout2,Term2)) | |
218 | ; | |
219 | write_to_codes(Functor/Arity,M1), | |
220 | append(["encountered unregistered ",M1, | |
221 | " tagged for debugging removal"],M3), | |
222 | safe_atom_codes(Msg1,M3), | |
223 | print_message(error,Msg1), | |
224 | Term2 = Term1 | |
225 | ). | |
226 | %remove_debugging_calls(LayoutIn,on_exception(E,Call,Code),LayoutOut,Term2) :- !, | |
227 | % LayoutOut = LayoutIn, | |
228 | % print(on_exception(E,Call,Code)),nl, | |
229 | % Term2 = on_exception(E,Call,(format('~n**EXCEPTION = ~w~n~n',[E]),Code)). | |
230 | %remove_debugging_calls(LayoutIn,time_out(Call,TO,Res),LayoutOut,Term2) :- !, | |
231 | % LayoutOut = LayoutIn, | |
232 | % print(time_out(Call,TO,Res)),nl, | |
233 | % Term2 = ((Call = _:C -> true ; Call=C), | |
234 | % functor(C,F,N),format('**TIMEOUT CALL ~w/~w = ~w ms~n',[F,N,TO]), | |
235 | % time_out(Call,TO,Res), | |
236 | % (Res=time_out -> format('**TIMEOUT OCCURED ~w/~w after ~w ms~n',[F,N,TO]) ; true)). | |
237 | remove_debugging_calls(LayoutIn,Term1,LayoutOut,Term2) :- | |
238 | !,functor(Term1,Functor,Arity), | |
239 | functor(Term2,Functor,Arity), | |
240 | ( LayoutIn = [] -> | |
241 | LayoutOut = [], | |
242 | remove_debugging_calls_args_nl(1,Arity,Term1,Term2) | |
243 | ; LayoutIn = [Pos|Layout1] -> | |
244 | LayoutOut = [Pos|Layout2], | |
245 | remove_debugging_calls_args(Layout1,Term1,Layout2,Term2,1) | |
246 | ; | |
247 | LayoutIn = LayoutOut, | |
248 | Term1 = Term2). | |
249 | ||
250 | ||
251 | remove_debugging_calls_args([],_,[],_,_). | |
252 | remove_debugging_calls_args([L1|Layout1],Term1,[L2|Layout2],Term2,N) :- | |
253 | safe_arg(N,Term1,In), safe_arg(N,Term2,Out), | |
254 | remove_debugging_calls(L1,In,L2,Out), | |
255 | N2 is N+1, | |
256 | remove_debugging_calls_args(Layout1,Term1,Layout2,Term2,N2). | |
257 | ||
258 | safe_arg(N,Term,Arg) :- compound(Term), arg(N,Term,Arg). | |
259 | ||
260 | remove_debugging_calls_args_nl(N,Max,_,_) :- N > Max,!. | |
261 | remove_debugging_calls_args_nl(N,Max,Term1,Term2) :- | |
262 | N =< Max, | |
263 | safe_arg(N,Term1,In), safe_arg(N,Term2,Out), | |
264 | remove_debugging_calls([],In,_,Out), | |
265 | N2 is N+1, | |
266 | remove_debugging_calls_args_nl(N2,Max,Term1,Term2). | |
267 | ||
268 | register_debugging_calls([]). | |
269 | register_debugging_calls([Call|Calls]) :- | |
270 | register_debugging_call(Call), | |
271 | register_debugging_calls(Calls). | |
272 | ||
273 | register_debugging_call(Call) :- | |
274 | functor(Call,Functor,Arity), | |
275 | Call =.. [_|Args], | |
276 | ? | ( nth1(Pos,Args,*) -> |
277 | ? | ( nth1(NPos,Args,*), NPos \= Pos -> |
278 | register_error_msg(Call) | |
279 | ; | |
280 | retractall(is_debug_call(Functor,Arity,_)), | |
281 | assertz(is_debug_call(Functor,Arity,Pos))) | |
282 | ; | |
283 | register_error_msg(Call)). | |
284 | register_error_msg(Call) :- | |
285 | write_to_codes(Call,M), | |
286 | append("register_debug_call has wrong argument: ", M, Codes), | |
287 | safe_atom_codes(Msg,Codes), | |
288 | print_message(error,Msg). | |
289 | ||
290 | ||
291 | enable_debugging_calls :- | |
292 | retractall(remove_debug_calls). | |
293 | disable_debugging_calls :- | |
294 | (remove_debug_calls -> true; assertz(remove_debug_calls)). | |
295 | ||
296 | :- public inc_indent/0. | |
297 | inc_indent :- (bb_get(debugging_indent_level,L) -> true ; L=0), | |
298 | L1 is L+1, | |
299 | bb_put(debugging_indent_level,L1), | |
300 | indent(L). | |
301 | ||
302 | indent(X) :- X<1,!. | |
303 | indent(X) :- write('>'), X1 is X-1, indent(X1). | |
304 | ||
305 | :- public dec_indent/0. | |
306 | dec_indent :- | |
307 | bb_get(debugging_indent_level,L), L>0,!, | |
308 | L1 is L-1, | |
309 | bb_put(debugging_indent_level,L1), | |
310 | indent(L1). | |
311 | dec_indent :- format('~n**** UNMATCHED decrease_indent~n',[]). | |
312 | ||
313 | :- public get_functor/3. | |
314 | get_functor(Var,F,N) :- var(Var), !, F='$VAR',N=0. | |
315 | get_functor(Mod:E,Mod:F,N) :- !, get_functor(E,F,N). | |
316 | get_functor(catch(E,_,_),catch:F,N) :- !, get_functor(E,F,N). | |
317 | get_functor(if(E,_,_),if:F,N) :- !, get_functor(E,F,N). | |
318 | get_functor(call(E),catch:F,N) :- !, get_functor(E,F,N). | |
319 | get_functor(E,F,N) :- (E=true -> trace ; true), functor(E,F,N). | |
320 | ||
321 | :- if(current_prolog_flag(dialect, sicstus)). | |
322 | /* | |
323 | * This code uses the term_expansion/6 hook, wich | |
324 | * is a multifile-predicate. Due to a bug in SICStus Prolog, | |
325 | * it is not possible to mix uncompiled and compiled code | |
326 | * for such a predicate. | |
327 | * So we always compile the term_expansion hook | |
328 | */ | |
329 | :- current_prolog_flag(compiling,Pre), | |
330 | set_prolog_flag(compiling,compactcode), | |
331 | compile(debugging_calls_te), | |
332 | set_prolog_flag(compiling,Pre). | |
333 | :- else. | |
334 | :- load_files(debugging_calls_te). | |
335 | :- endif. |