We will show two examples using the advanced features of the debugger.
The first example defines a
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) goal creates two
spypoints for predicate
:- 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
save_groundness to check if the given invocation was
ground, and if so, then it stores a term
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
condition is true. If so, then it issues a
hide action, which is a
breakpoint macro expanding to
hide_exit :- execution_state([inv(I),max_inv(I),goal_private(Priv)]), memberchk(hide_exit(Ground), Priv), Ground == ground.
hide_exit encapsulates the tests that the invocation number
be the same as the last invocation number used (
goal_private attribute of the invocation be identical to
ground. The first test ensures that nothing was traced inside the
If we load the above code, as well as the small example below, then
the following interaction, discussed below, can take place. Note that the
hide_exit predicate is called with the
argument, resulting in generic spypoints being created.
| ?- consult(user). | cnt(0) :- !. | cnt(N) :- N > 0, N1 is N-1, cnt(N1). | ^D % consulted user in module user, 0 msec 424 bytes | ?- 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 % 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
BTrace), which will execute
Goal and build a backtrace
showing the successful invocations executed during the solution of
The advantages of such a special backtrace over the one incorporated in the debugger are the following:
call_backtrace/2 predicate is based on the advice
facility. It uses the variable accessible via the
private(_) condition to store a mutable (see ref-lte-mut) holding the
backtrace. Outside the
predicate the mutable will have the value
The example is a module-file, so that internal invocations can be
identified by the module name. We load the
memberchk/2 will be used in the handling of the
:- module(backtrace, [call_backtrace/2]). :- use_module(library(lists)). :- meta_predicate call_backtrace(0, ?). call_backtrace(Goal, BTrace) :- Spec = [advice,call] -[true((goal(M:G),store_goal(M,G))),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 and 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)
call_backtrace/2 will notice the existence of the
breakpoint and will skip the
Having ensured the appropriate advice-point exists,
call_backtrace1/2 with a cleanup
operation that removes the breakpoint added, if any.
:- meta_predicate call_backtrace1(0, ?). call_backtrace1(Goal, BTrace) :- execution_state(private(Priv)), memberchk(backtrace_mutable(Mut), Priv), ( mutable(Mut) -> get_mutable(Old, Mut), update_mutable(, Mut) ; create_mutable(, Mut), Old = off ), call(Goal), get_mutable(BTrace, Mut), update_mutable(Old, Mut).
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
Goal is called. In the course of the execution of
Goal the debugger will accumulate the backtrace in the
mutable. Finally, the mutable is read, its value is returned
BTrace, and it is restored to its old value (or
store_goal(M, G) :- M \== backtrace, G \= call(_), execution_state(private(Priv)), memberchk(backtrace_mutable(Mut), Priv), 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
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
Below is an example run, using a small program:
| ?- consult(user). | cnt(N):- N =< 0, !. | cnt(N) :- N > 0, N1 is N-1, cnt(N1). | ^D % consulted user in module user, 0 msec 424 bytes | ?- 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)] | ?-
Note that the backtrace produced by
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, then you have to use
side-effects, e.g. dynamic predicates.
Further examples of complex breakpoint handling are contained in
This concludes the tutorial introduction of the advanced debugger features.