The debugger allows the user to store some private information in the
backtrace. It allocates a Prolog variable in each break
level and in each invocation. The breakpoint test
private(
Priv)
unifies Priv with the private
information associated with the break level, while the test
goal_private(
GPriv)
unifies GPriv with the
Prolog variable stored in the invocation.
Both variables are initially unbound, and behave as if they were passed around the program being debugged in additional arguments. This implies that any variable assignments done within these variables are undone on backtracking.
In practice, the private
condition gives you access to a Prolog
variable shared by all invocations of a break level. This makes it
possible to remember a term and look at it later, in a possibly
more instantiated form, as shown by the following example.
memory(Term) :- execution_state(private(P)), memberchk(myterm(Term), P). | ?- trace, append([1,2,3,4], [5,6], L). 1 1 Call: append([1,2,3,4],[5,6],_514) ? @ | :- append(_,_,L)^memory(L). 1 1 Call: append([1,2,3,4],[5,6],_514) ? c 2 2 Call: append([2,3,4],[5,6],_2064) ? c 3 3 Call: append([3,4],[5,6],_2422) ? c 4 4 Call: append([4],[5,6],_2780) ? @ | :- memory(L), write(L), nl. [1,2,3|_2780] 4 4 Call: append([4],[5,6],_2780) ?
The predicate memory/1
receives the term to be
remembered in its argument. It gets hold of the private field
associated with the break level in variable P
, and calls
memberchk/2
(see lib-lists), with the term to be
remembered, wrapped in myterm
, as the list element, and the
private field, as the list. Thus the latter, initially unbound
variable, is used as an open-ended list. For example, when
memory/1
is called for the first time, the private field gets
instantiated to [myterm(Term)|_]
. If later you call
memory/1
with an uninstantiated argument, it will
retrieve the term remembered earlier and unify it with the
argument.
The above trace excerpt shows how this utility predicate can be
used to remember an interesting Prolog term. Within invocation
number 1 we call memory/1
with the third, output argument
of append/3
, using the ‘@’ command (see Debug Commands). A few tracing steps later, we retrieve the term
remembered and print it, showing its current instantiation. Being
able to access the instantiation status of some terms of interest
can be very useful in debugging. In library(debugger_examples)
we
describe new debugger commands for naming Prolog variables and
providing name-based access to these variables, based on the above
technique.
We could have avoided the use of memberchk/2
in the example by
simply storing the term to be remembered in the private field
itself (memory(Term) :- execution_state(private(Term)).
). But
this would have made the private field unusable for other purposes. For
example, the finite domain constraint debugger (see lib-fdbg) would stop
working, as it relies on the private fields.
There is only a single private variable of both kinds within the given
scope. Therefore the convention of using an open ended list for storing
information in private fields, as shown in the above example, is very
much recommended. The different users of the private field are
distinguished by the wrapper they use (e.g. myterm/1
above,
fdbg/1
for the constraint debugger, etc.). Future SICStus Prolog
releases may enforce this convention by providing appropriate
breakpoint tests.
We now present an example of using the goal private field. Earlier we have shown a spypoint definition that made a predicate invisible in the sense that its ports are silently passed through and it is automatically skipped over. However, with that earlier solution, execution always continues in trace mode after skipping. We now improve the spypoint definition: the mode in which the Call port was reached is remembered in the goal private field, and the mode action variable is reset to this value at the Exit port.
mode_memory(Mode) :- execution_state(goal_private(GP)), memberchk(mymode(Mode), GP). | ?- spy(foo/2, -[silent,proceed, true(mode_memory(MM)), ( call -> get(mode(MM)), inv(Inv), skip(Inv) ; exit -> mode(MM) ; true )]).
Here, we first define an auxiliary predicate mode_memory/1
,
which uses the open list convention for storing information in the
goal private field, applying the mymode/1
wrapper. We then
create a spypoint for foo/2
, whose action part first sets
the print
and command
action variables. Next, the
mode_memory/1
predicate is called, unifying the mode
memory with the MM
variable. We then branch in the action
part: at Call ports the uninstantiated MM
is
unified with the current mode, and a skip
command is
issued. At Exit ports MM
holds the mode saved at the Call
port, so the mode(MM)
action re-activates this mode. At all
other ports we just silently proceed without changing the debugger
mode.