Next: Preparing a program for debugging, Previous: GNU Emacs interface, Up: Debugging [Contents][Index]
The Mercury debugger is based on a modified version of the box model on which the four-port debuggers of most Prolog systems are based. Such debuggers abstract the execution of a program into a sequence, also called a trace, of execution events of various kinds. The four kinds of events supported by most Prolog systems (their ports) are
A call event occurs just after a procedure has been called, and control has just reached the start of the body of the procedure.
An exit event occurs when a procedure call has succeeded, and control is about to return to its caller.
A redo event occurs when all computations to the right of a procedure call have failed, and control is about to return to this call to try to find alternative solutions.
A fail event occurs when a procedure call has run out of alternatives, and control is about to return to the rightmost computation to its left that still has possibly successful alternatives left.
Mercury also supports these four kinds of events, but not all events can occur for every procedure call. Which events can occur for a procedure call, and in what order, depend on the determinism of the procedure. The possible event sequences for procedures of the various determinisms are as follows.
a call event, zero or more repeats of (exit event, redo event), and a fail event
a call event, one or more repeats of (exit event, redo event), and a fail event
a call event, and either an exit event or a fail event
a call event and an exit event
a call event and a fail event
a call event
In addition to these four event types, Mercury supports exception events. An exception event occurs when an exception has been thrown inside a procedure, and control is about to propagate this exception to the caller. An exception event can replace the final exit or fail event in the event sequences above or, in the case of erroneous procedures, can come after the call event.
Besides the event types call, exit, redo, fail and exception, which describe the interface of a call, Mercury also supports several types of events that report on what is happening internal to a call. Each of these internal event types has an associated parameter called a path. The internal event types are:
A cond event occurs when execution reaches the start of the condition of an if-then-else. The path associated with the event specifies which if-then-else this is.
A then event occurs when execution reaches the start of the then part of an if-then-else. The path associated with the event specifies which if-then-else this is.
An else event occurs when execution reaches the start of the else part of an if-then-else. The path associated with the event specifies which if-then-else this is.
A disj event occurs when execution reaches the start of a disjunct in a disjunction. The path associated with the event specifies which disjunct of which disjunction this is.
A switch event occurs when execution reaches the start of one arm of a switch (a disjunction in which each disjunct unifies a bound variable with different function symbol). The path associated with the event specifies which arm of which switch this is.
A neg_enter event occurs when execution reaches the start of a negated goal. The path associated with the event specifies which negation goal this is.
A neg_fail event occurs when a goal inside a negation succeeds, which means that its negation fails. The path associated with the event specifies which negation goal this is.
A neg_success event occurs when a goal inside a negation fails, which means that its negation succeeds. The path associated with the event specifies which negation goal this is.
A goal path is a sequence of path components separated by semicolons. Each path component is one of the following:
cnum
The num’th conjunct of a conjunction.
dnum
The num’th disjunct of a disjunction.
snum
The num’th arm of a switch.
?
The condition of an if-then-else.
t
The then part of an if-then-else.
e
The else part of an if-then-else.
~
The goal inside a negation.
q!
The goal inside an existential quantification or other scope that changes the determinism of the goal.
q
The goal inside an existential quantification or other scope that doesn’t change the determinism of the goal.
A goal path describes the position of a goal inside the body of a procedure definition. For example, if the procedure body is a disjunction in which each disjunct is a conjunction, then the goal path ‘d2;c3;’ denotes the third conjunct within the second disjunct. If the third conjunct within the second disjunct is an atomic goal such as a call or a unification, then this will be the only goal with whose path has ‘d2;c3;’ as a prefix. If it is a compound goal, then its components will all have paths that have ‘d2;c3;’ as a prefix, e.g. if it is an if-then-else, then its three components will have the paths ‘d2;c3;?;’, ‘d2;c3;t;’ and ‘d2;c3;e;’.
Goal paths refer to the internal form of the procedure definition. When debugging is enabled (and the option ‘--trace-optimized’ is not given), the compiler will try to keep this form as close as possible to the source form of the procedure, in order to make event paths as useful as possible to the programmer. Due to the compiler’s flattening of terms, and its introduction of extra unifications to implement calls in implied modes, the number of conjuncts in a conjunction will frequently differ between the source and internal form of a procedure. This is rarely a problem, however, as long as you know about it. Mode reordering can be a bit more of a problem, but it can be avoided by writing single-mode predicates and functions so that producers come before consumers. The compiler transformation that potentially causes the most trouble in the interpretation of goal paths is the conversion of disjunctions into switches. In most cases, a disjunction is transformed into a single switch, and it is usually easy to guess, just from the events within a switch arm, just which disjunct the switch arm corresponds to. Some cases are more complex; for example, it is possible for a single disjunction to be transformed into several switches, possibly with other, smaller disjunctions inside them. In such cases, making sense of goal paths may require a look at the internal form of the procedure. You can ask the compiler to generate a file with the internal forms of the procedures in a given module by including the options ‘-dfinal -Dpaths’ on the command line when compiling that module.
Next: Preparing a program for debugging, Previous: GNU Emacs interface, Up: Debugging [Contents][Index]