37
38:- module(prolog_breakpoints,
39 [ set_breakpoint/4, 40 set_breakpoint/5, 41 set_breakpoint_condition/2, 42 delete_breakpoint/1, 43 breakpoint_property/2 44 ]). 45:- use_module(library(debug), [debug/3]). 46:- autoload(library(error), [existence_error/2]). 47:- autoload(library(lists), [nth1/3, member/2]). 48:- autoload(library(prolog_clause), [clause_info/4, clause_name/2]). 49:- autoload(library(apply), [maplist/2]). 50:- autoload(library(broadcast), [broadcast/1]). 51
52
63
85
86set_breakpoint(File, Line, Char, Id) :-
87 set_breakpoint(File, File, Line, Char, Id).
88set_breakpoint(Owner, File, Line, Char, Id) :-
89 debug(break, 'break_at(~q, ~d, ~d).', [File, Line, Char]),
90 '$clause_from_source'(Owner, File, Line, ClauseRefs),
91 member(ClauseRef, ClauseRefs),
92 clause_info(ClauseRef, InfoFile, TermPos, _NameOffset),
93 ( InfoFile == File
94 -> '$break_pc'(ClauseRef, PC, NextPC),
95 debug(break, 'Clause ~p, PC=~p NextPC=~p', [ClauseRef, PC, NextPC]),
96 '$clause_term_position'(ClauseRef, NextPC, List),
97 debug(break, 'Location = ~w', [List]),
98 range(List, TermPos, SubPos),
99 arg(1, SubPos, A),
100 arg(2, SubPos, Z),
101 debug(break, 'Term from ~w-~w', [A, Z]),
102 Z >= Char, !,
103 Len is Z - A,
104 b_setval('$breakpoint', file_location(File, Line, A, Len))
105 ; print_message(warning, breakpoint(no_source(ClauseRef, File, Line))),
106 '$break_pc'(ClauseRef, PC, _), !,
107 nb_delete('$breakpoint')
108 ),
109 debug(break, 'Break at clause ~w, PC=~w', [ClauseRef, PC]),
110 '$break_at'(ClauseRef, PC, true),
111 nb_delete('$breakpoint'),
112 known_breakpoint(ClauseRef, PC, _Location, Id).
113
114range(_, Pos, _), var(Pos) =>
115 fail.
116range(List, parentheses_term_position(_,_,Pos), SubPos) =>
117 range(List, Pos, SubPos).
118range([], Pos, SubPos) =>
119 SubPos = Pos.
120range([H|T], term_position(_, _, _, _, PosL), SubPos) =>
121 nth1(H, PosL, Pos),
122 range(T, Pos, SubPos).
123range(exit, Pos, SubPos) =>
124 arg(2, Pos, End),
125 Dot is End,
126 EndDot is Dot+1,
127 SubPos = Dot-EndDot.
128
129:- dynamic
130 known_breakpoint/4, 131 breakpoint_condition/4, 132 break_id/1. 133
134next_break_id(Id) :-
135 retract(break_id(Id0)),
136 !,
137 Id is Id0+1,
138 asserta(break_id(Id)).
139next_break_id(1) :-
140 asserta(break_id(1)).
141
149
150delete_breakpoint(Id) :-
151 integer(Id),
152 known_breakpoint(ClauseRef, PC, _Location, Id),
153 !,
154 '$break_at'(ClauseRef, PC, false).
155delete_breakpoint(Id) :-
156 existence_error(breakpoint, Id).
157
173
174breakpoint_property(Id, file(File)) :-
175 known_breakpoint(ClauseRef,_,_,Id),
176 clause_property(ClauseRef, file(File)).
177breakpoint_property(Id, line_count(Line)) :-
178 known_breakpoint(_,_,Location,Id),
179 location_line(Location, Line).
180breakpoint_property(Id, character_range(Start, Len)) :-
181 known_breakpoint(ClauseRef,PC,Location,Id),
182 ( Location = file_location(_File, _Line, Start, Len)
183 -> true
184 ; break_location(ClauseRef, PC, _File, SubPos),
185 compound(SubPos),
186 arg(1, SubPos, Start),
187 arg(2, Start, End),
188 nonvar(Start), nonvar(End),
189 Len is End+1-Start
190 ).
191breakpoint_property(Id, clause(Reference)) :-
192 known_breakpoint(Reference,_,_,Id).
193breakpoint_property(Id, condition(Cond)) :-
194 known_breakpoint(_,_,_,Id),
195 breakpoint_condition(Id, Cond, _CondTerm, _VarOffsets).
196
197location_line(file_location(_File, Line, _Start, _Len), Line).
198location_line(file_character_range(File, Start, _Len), Line) :-
199 file_line(File, Start, Line).
200location_line(file_line(_File, Line), Line).
201
202
207
208file_line(File, Start, Line) :-
209 setup_call_cleanup(
210 prolog_clause:try_open_source(File, In),
211 stream_line(In, Start, 1, Line),
212 close(In)).
213
214stream_line(In, _, Line0, Line) :-
215 at_end_of_stream(In),
216 !,
217 Line = Line0.
218stream_line(In, Index, Line0, Line) :-
219 skip(In, 0'\n),
220 character_count(In, At),
221 ( At > Index
222 -> Line = Line0
223 ; Line1 is Line0+1,
224 stream_line(In, Index, Line1, Line)
225 ).
226
227
228 231
232:- initialization
233 prolog_unlisten(break, onbreak),
234 prolog_listen(break, onbreak). 235
236onbreak(exist, ClauseRef, PC) :-
237 known_breakpoint(ClauseRef, PC, _Location, Id),
238 !,
239 break_message(breakpoint(exist, Id)).
240onbreak(true, ClauseRef, PC) :-
241 !,
242 debug(break, 'Trap in Clause ~p, PC ~d', [ClauseRef, PC]),
243 with_mutex('$break', next_break_id(Id)),
244 ( nb_current('$breakpoint', Location)
245 -> true
246 ; break_location(ClauseRef, PC, File, A-Z)
247 -> Len is Z+1-A,
248 Location = file_character_range(File, A, Len)
249 ; clause_property(ClauseRef, file(File)),
250 clause_property(ClauseRef, line_count(Line))
251 -> Location = file_line(File, Line)
252 ; Location = unknown
253 ),
254 asserta(known_breakpoint(ClauseRef, PC, Location, Id)),
255 break_message(breakpoint(set, Id)).
256onbreak(false, ClauseRef, PC) :-
257 debug(break, 'Remove breakpoint from ~p, PC ~d', [ClauseRef, PC]),
258 delete_breakpoint(ClauseRef, PC).
259onbreak(retract, ClauseRef, PC) :-
260 debug(break, 'Remove breakpoint from ~p, PC ~d (due to retract)',
261 [ClauseRef, PC]),
262 delete_breakpoint(ClauseRef, PC).
263
264delete_breakpoint(ClauseRef, PC) :-
265 clause(known_breakpoint(ClauseRef, PC, _Location, Id), true, Ref),
266 retractall(breakpoint_condition(Id, _, _, _)),
267 call_cleanup(break_message(breakpoint(delete, Id)), erase(Ref)).
268
269break_message(Message) :-
270 broadcast(prolog(Message)),
271 print_message(informational, Message).
272
281
282break_location(ClauseRef, PC, File, SubPos) :-
283 clause_info(ClauseRef, File, TermPos, _NameOffset),
284 '$fetch_vm'(ClauseRef, PC, NPC, _VMI),
285 '$clause_term_position'(ClauseRef, NPC, List),
286 debug(break, 'ClausePos = ~w', [List]),
287 range(List, TermPos, SubPos),
288 debug(break, 'Subgoal at: ~p', [SubPos]).
289
290
291 294
295:- multifile
296 prolog:message/3. 297
298prolog:message(breakpoint(no_source(ClauseRef, _File, Line))) -->
299 [ 'Failed to find line ~d in body of clause ~p. Breaking at start of body.'-
300 [Line, ClauseRef]
301 ].
302prolog:message(breakpoint_condition_error(Id, Error)) -->
303 [ 'Exception while evaluating breakpoint ~p condition:'-[Id], nl,
304 prolog:translate_message(Error)
305 ].
306prolog:message(breakpoint(SetClear, Id)) -->
307 setclear(SetClear),
308 breakpoint(Id).
309
310setclear(set) -->
311 ['Breakpoint '].
312setclear(exist) -->
313 ['Existing breakpoint '].
314setclear(delete) -->
315 ['Deleted breakpoint '].
316
317breakpoint(Id) -->
318 breakpoint_name(Id),
319 ( { breakpoint_property(Id, file(File)),
320 breakpoint_property(Id, line_count(Line))
321 }
322 -> [ ' at ', url(File:Line) ]
323 ; []
324 ).
325
326breakpoint_name(Id) -->
327 { breakpoint_property(Id, clause(ClauseRef)) },
328 ( { clause_property(ClauseRef, erased) }
329 -> ['~w'-[Id]]
330 ; { clause_name(ClauseRef, Name) },
331 ['~w in ~w'-[Id, Name]]
332 ).
333
334
335 338
353
354set_breakpoint_condition(Id, Cond) :-
355 known_breakpoint(ClauseRef, _PC, _Location, Id),
356 !,
357 term_string(CondGoal, Cond, [variable_names(Bindings)]),
358 clause_info(ClauseRef, _InfoFile, _TermPos, NameOffset),
359 clause_property(ClauseRef, module(Module)),
360 names_offsets(Bindings, NameOffset, FrameOffsetsCondVars),
361 retractall(breakpoint_condition(Id, _, _, _)),
362 asserta(breakpoint_condition(Id, Cond, Module:CondGoal, FrameOffsetsCondVars)).
363set_breakpoint_condition(Id, _Cond) :-
364 existence_error(breakpoint, Id).
365
366:- multifile prolog:break_hook/7. 367
368prolog:break_hook(Clause, PC, Frame, _Choice, _Goal, true, Action) :-
369 known_breakpoint(Clause, PC, _, Id),
370 ( breakpoint_condition(Id, _CondString, CondGoal, FrameOffsetsCondVars)
371 -> check_breakpoint_condition(Id, Frame, CondGoal,
372 FrameOffsetsCondVars, Action)
373 ; Action = trace
374 ).
375
376check_breakpoint_condition(Id, Frame, CondGoal, FrameOffsetsCondVars, Action) :-
377 maplist(unify_with_frame_variable(Frame), FrameOffsetsCondVars),
378 ( catch(CondGoal,
379 Error,
380 print_message(warning,
381 breakpoint_condition_error(Id, Error)))
382 -> Action = trace
383 ; Action = continue
384 ).
385
386unify_with_frame_variable(Frame, Offset-Var) :-
387 prolog_frame_attribute(Frame, argument(Offset), Var).
388
389
390names_offsets([Name=Var|T], NameOffset, OffsetsVars) :-
391 ( arg(Offset, NameOffset, Name)
392 -> OffsetsVars = [Offset-Var|R],
393 names_offsets(T, NameOffset, R)
394 ; names_offsets(T, NameOffset, OffsetsVars)
395 ).
396names_offsets([], _, [])