36
37:- module(prolog_breakpoints,
38 [ set_breakpoint/4, 39 set_breakpoint/5, 40 delete_breakpoint/1, 41 breakpoint_property/2 42 ]). 43:- autoload(library(debug), [debug/3]). 44:- autoload(library(error), [existence_error/2]). 45:- autoload(library(lists), [nth1/3, member/2]). 46:- autoload(library(prolog_clause), [clause_info/4, clause_name/2]). 47
48
62
84
85set_breakpoint(File, Line, Char, Id) :-
86 set_breakpoint(File, File, Line, Char, Id).
87set_breakpoint(Owner, File, Line, Char, Id) :-
88 debug(break, 'break_at(~q, ~d, ~d).', [File, Line, Char]),
89 '$clause_from_source'(Owner, File, Line, ClauseRefs),
90 member(ClauseRef, ClauseRefs),
91 clause_info(ClauseRef, InfoFile, TermPos, _NameOffset),
92 ( InfoFile == File
93 -> '$break_pc'(ClauseRef, PC, NextPC),
94 debug(break, 'Clause ~p, PC=~p NextPC=~p', [ClauseRef, PC, NextPC]),
95 '$clause_term_position'(ClauseRef, NextPC, List),
96 debug(break, 'Location = ~w', [List]),
97 range(List, TermPos, A, Z),
98 debug(break, 'Term from ~w-~w', [A, Z]),
99 Z >= Char, !,
100 Len is Z - A,
101 b_setval('$breakpoint', file_location(File, Line, A, Len))
102 ; print_message(warning, breakpoint(no_source(ClauseRef, File, Line))),
103 '$break_pc'(ClauseRef, PC, _), !,
104 nb_delete('$breakpoint')
105 ),
106 debug(break, 'Break at clause ~w, PC=~w', [ClauseRef, PC]),
107 '$break_at'(ClauseRef, PC, true),
108 nb_delete('$breakpoint'),
109 known_breakpoint(ClauseRef, PC, _Location, Id).
110
111range(_, Pos, _, _) :-
112 var(Pos), !, fail.
113range([], Pos, A, Z) :-
114 arg(1, Pos, A),
115 arg(2, Pos, Z).
116range([H|T], term_position(_, _, _, _, PosL), A, Z) :-
117 nth1(H, PosL, Pos),
118 range(T, Pos, A, Z).
119
120:- dynamic
121 known_breakpoint/4, 122 break_id/1. 123
124next_break_id(Id) :-
125 retract(break_id(Id0)),
126 !,
127 Id is Id0+1,
128 asserta(break_id(Id)).
129next_break_id(1) :-
130 asserta(break_id(1)).
131
139
140delete_breakpoint(Id) :-
141 integer(Id),
142 known_breakpoint(ClauseRef, PC, _Location, Id),
143 !,
144 '$break_at'(ClauseRef, PC, false).
145delete_breakpoint(Id) :-
146 existence_error(breakpoint, Id).
147
163
164breakpoint_property(Id, file(File)) :-
165 known_breakpoint(ClauseRef,_,_,Id),
166 clause_property(ClauseRef, file(File)).
167breakpoint_property(Id, line_count(Line)) :-
168 known_breakpoint(_,_,Location,Id),
169 location_line(Location, Line).
170breakpoint_property(Id, character_range(Start, Len)) :-
171 known_breakpoint(ClauseRef,PC,Location,Id),
172 ( Location = file_location(_File, _Line, Start, Len)
173 -> true
174 ; break_location(ClauseRef, PC, _File, Start-End),
175 Len is End+1-Start
176 ).
177breakpoint_property(Id, clause(Reference)) :-
178 known_breakpoint(Reference,_,_,Id).
179
180location_line(file_location(_File, Line, _Start, _Len), Line).
181location_line(file_character_range(File, Start, _Len), Line) :-
182 file_line(File, Start, Line).
183location_line(file_line(_File, Line), Line).
184
185
190
191file_line(File, Start, Line) :-
192 setup_call_cleanup(
193 prolog_clause:try_open_source(File, In),
194 stream_line(In, Start, 1, Line),
195 close(In)).
196
197stream_line(In, _, Line0, Line) :-
198 at_end_of_stream(In),
199 !,
200 Line = Line0.
201stream_line(In, Index, Line0, Line) :-
202 skip(In, 0'\n),
203 character_count(In, At),
204 ( At > Index
205 -> Line = Line0
206 ; Line1 is Line0+1,
207 stream_line(In, Index, Line1, Line)
208 ).
209
210
211 214
215:- initialization
216 prolog_unlisten(break, onbreak),
217 prolog_listen(break, onbreak). 218
219onbreak(exist, ClauseRef, PC) :-
220 known_breakpoint(ClauseRef, PC, _Location, Id),
221 !,
222 break_message(breakpoint(exist, Id)).
223onbreak(true, ClauseRef, PC) :-
224 !,
225 debug(break, 'Trap in Clause ~p, PC ~d', [ClauseRef, PC]),
226 with_mutex('$break', next_break_id(Id)),
227 ( nb_current('$breakpoint', Location)
228 -> true
229 ; break_location(ClauseRef, PC, File, A-Z)
230 -> Len is Z+1-A,
231 Location = file_character_range(File, A, Len)
232 ; clause_property(ClauseRef, file(File)),
233 clause_property(ClauseRef, line_count(Line))
234 -> Location = file_line(File, Line)
235 ; Location = unknown
236 ),
237 asserta(known_breakpoint(ClauseRef, PC, Location, Id)),
238 break_message(breakpoint(set, Id)).
239onbreak(false, ClauseRef, PC) :-
240 debug(break, 'Remove breakpoint from ~p, PC ~d', [ClauseRef, PC]),
241 clause(known_breakpoint(ClauseRef, PC, _Location, Id), true, Ref),
242 call_cleanup(break_message(breakpoint(delete, Id)), erase(Ref)).
243onbreak(gc, ClauseRef, PC) :-
244 debug(break, 'Remove breakpoint from ~p, PC ~d (due to CGC)',
245 [ClauseRef, PC]),
246 retractall(known_breakpoint(ClauseRef, PC, _Location, _Id)).
247
248break_message(Message) :-
249 print_message(informational, Message).
250
258
259break_location(ClauseRef, PC, File, A-Z) :-
260 clause_info(ClauseRef, File, TermPos, _NameOffset),
261 '$fetch_vm'(ClauseRef, PC, NPC, _VMI),
262 '$clause_term_position'(ClauseRef, NPC, List),
263 debug(break, 'ClausePos = ~w', [List]),
264 range(List, TermPos, A, Z),
265 debug(break, 'Range: ~d .. ~d', [A, Z]).
266
267
268 271
272:- multifile
273 prolog:message/3. 274
275prolog:message(breakpoint(no_source(ClauseRef, _File, Line))) -->
276 [ 'Failed to find line ~d in body of clause ~p. Breaking at start of body.'-
277 [Line, ClauseRef]
278 ].
279prolog:message(breakpoint(SetClear, Id)) -->
280 setclear(SetClear),
281 breakpoint(Id).
282
283setclear(set) -->
284 ['Breakpoint '].
285setclear(exist) -->
286 ['Existing breakpoint '].
287setclear(delete) -->
288 ['Deleted breakpoint '].
289
290breakpoint(Id) -->
291 breakpoint_name(Id),
292 ( { breakpoint_property(Id, file(File)),
293 file_base_name(File, Base),
294 breakpoint_property(Id, line_count(Line))
295 }
296 -> [ ' at ~w:~d'-[Base, Line] ]
297 ; []
298 ).
299
300breakpoint_name(Id) -->
301 { breakpoint_property(Id, clause(ClauseRef)) },
302 ( { clause_property(ClauseRef, erased) }
303 -> ['~w'-[Id]]
304 ; { clause_name(ClauseRef, Name) },
305 ['~w in ~w'-[Id, Name]]
306 )