Node:Programming Breakpoints, Previous:Hooks Related to Breakpoints, Up:Advanced Debugging
We will show two examples using the advanced features of the debugger.
The first example defines a hide_exit(Pred)
predicate, which will
hide the Exit port for Pred
(i.e. it will silently proceed),
provided the current goal was already ground at the Call port, and
nothing was traced inside the given invocation. The
hide_exit(Pred)
creates two spypoints for predicate Pred
:
:- meta_predicate hide_exit(:). hide_exit(Pred) :- add_breakpoint([pred(Pred),call]- true(save_groundness), _), add_breakpoint([pred(Pred),exit,true(hide_exit)]-hide, _).
The first spypoint is applicable at the Call port, and it calls
save_groundness
to check if the given invocation was ground, and
if so, it stores a term hide_exit(ground)
in the
goal_private
attribute of the invocation.
save_groundness :- execution_state([goal(_:G),goal_private(Priv)]), ground(G), !, memberchk(hide_exit(ground), Priv). save_groundness.
The second spypoint created by hide_exit/1
is applicable at the
Exit port and it checks whether the hide_exit/0
condition is true. If so, it issues a hide
action, which is a
breakpoint macro expanding to [silent,proceed]
.
hide_exit :- execution_state([inv(I),max_inv(I),goal_private(Priv)]), memberchk(hide_exit(Ground), Priv), Ground == ground.
Here, hide_exit
encapsulates the tests that the invocation number
is the same as the last invocation number used (max_inv
), and
that the goal_private
attribute of the invocation is identical to
ground
. The first test ensures that nothing was traced inside the
current invocation.
If we load the above code, as well as the small example below, then the
following interaction can take place. Note that the hide_exit
is
called with the _:_
argument, resulting in generic spypoints
being created.
| ?- [user]. | cnt(0) :- !. | cnt(N) :- | N > 0, N1 is N-1, cnt(N1). | % consulted user in module user, 0 msec 424 bytes yes | ?- hide_exit(_:_), trace, cnt(1). % The debugger will first zip -- showing spypoints (zip) % Generic spypoint added, BID=1 % Generic spypoint added, BID=2 % The debugger will first creep -- showing everything (trace) # 1 1 Call: cnt(1) ? c # 2 2 Call: 1>0 ? c # 3 2 Call: _2019 is 1-1 ? c 3 2 Exit: 0 is 1-1 ? c # 4 2 Call: cnt(0) ? c 1 1 Exit: cnt(1) ? c yes % trace | ?-
Invocation 1 is ground, its Exit port is not hidden, because further goals were traced inside it. On the other hand, Exit ports of ground invocations 2 and 4 are hidden.
Our second example defines a predicate call_backtrace(Goal,
BTrace)
, which will execute Goal
and build a backtrace showing
the successful invocations executed during the solution of Goal
.
The advantages of such a special backtrace over the one incorporated in the debugger are the following:
The call_backtrace/2
predicate is based on the advice facility. It
uses the variable accessible via the private(_)
condition to
store a mutable holding the backtrace (see Meta Logic). Outside the
call_backtrace
predicate the mutable will have the value
off
.
The example is a module-file, so that internal invocations can be
identified by the module-name. We load the lists
library, because
memberchk/2
will be used in the handling of the private field.
:- module(backtrace, [call_backtrace/2]). :- use_module(library(lists)). :- meta_predicate call_backtrace(:, ?). call_backtrace(Goal, BTrace) :- Spec = [goal(M:G),advice,port(call)] -[true(backtrace:store_goal(M,G)),command(flit)], ( current_breakpoint(Spec, _, on, _) -> B = [] ; add_breakpoint(Spec, B) ), call_cleanup(call_backtrace1(Goal, BTrace), remove_breakpoints(B)).
call_backtrace(Goal, BTrace)
is a meta-predicate, which first
sets up an appropriate advice-point for building the backtrace. The
advice-point will be activated at each Call port, will call the
store_goal/2
predicate with arguments containing the module and the
goal in question. Note that the advice-point will not build a procedure
box (cf. the flit
command in the action part).
The advice-point will be added just once: any further (recursive) calls to
call_backtrace/2
will notice the existence of the breakpoint and
will skip the add_breakpoint/2
call. Note that
the breakpoint spec Spec
is in
normalized form (e.g. the first two conditions are goal
and
advice
), so that Spec
is good both for checking the presence
of a breakpoint with the given spec in
current_breakpoint/4
, and for creating a new breakpoint in
add_breakpoint/2
.
Having ensured the appropriate advice-point exists,
call_backtrace/2
calls call_backtrace1/2
with a cleanup
operation which removes the breakpoint added.
:- meta_predicate call_backtrace1(:, ?). call_backtrace1(Goal, BTrace) :- execution_state(private(Priv)), memberchk(backtrace_mutable(Mut), Priv), ( is_mutable(Mut) -> get_mutable(Old, Mut), update_mutable([], Mut) ; create_mutable([], Mut), Old = off ), call(Goal), get_mutable(BTrace, Mut), update_mutable(Old, Mut).
The predicate call_backtrace1/2
retrieves the private field of
the execution state and uses it to store a mutable, wrapped in
backtrace_mutable
. When first called within a top-level the
mutable is created with the value []
. In later calls the mutable
is re-initialized to []
. Having set up the mutable, Goal
is called. In the course of the execution of the Goal
the
debugger will accumulate the backtrace in the mutable. Finally, the
mutable is read, its value is returned in BTrace
, and it is
restored to its old value (or off
).
store_goal(M, G) :- M \== backtrace, G \= call(_), execution_state(private(Priv)), memberchk(backtrace_mutable(Mut), Priv), is_mutable(Mut), get_mutable(BTrace, Mut), BTrace \== off, !, update_mutable([M:G|BTrace], Mut). store_goal(_, _).
store_goal/2
is the predicate called by the advice-point, with
the module and the goal as arguments. We first ensure that calls from
within the backtrace
module and those of call/1
get
ignored. Next, the module qualified goal term is prepended to the
mutable value retrieved from the private field, provided the mutable
exists and its value is not off
.
Below is an example run, using a small program:
| ?- [user]. | cnt(N):- N =< 0, !. | cnt(N) :- N > 0, N1 is N-1, cnt(N1). | {consulted user in module user, 0 msec 224 bytes} yes | ?- call_backtrace(cnt(1), B). % Generic advice point added, BID=1 % Generic advice point, BID=1, removed (last) B = [user:(0=<0),user:cnt(0),user:(0 is 1-1),user:(1>0),user:cnt(1)] ? yes | ?-
Note that the backtrace produced by call_backtrace/2
can not
contain any information regarding failed branches. For example, the
very first invocation within the above execution, 1 =< 0
, is
first put on the backtrace at its Call port, but this is immediately
undone because the goal fails. If you would like to build a backtrace
that preserves failed branches, you have to use side-effects,
e.g. dynamic predicates.
Further examples of complex breakpoint handling are contained in
library(debugger_examples)
.