Up Next

15.1  The Box Model

The ECLiPSe debugger is based on a port model which is an extension of the classical Box Model commonly used in Prolog debugging.

A procedure invocation (or goal) is represented by a box with entry and exit ports. Each time a procedure is invoked, a box is created and given a unique invocation number. The invocations of subgoals of this procedure are seen as boxes inside this procedure box.

Figure 15.1: The box model

Tracing the flow of the execution consists in tracing the crossing of the execution flow through any of the port of the box.

The five basic ports of the box model of ECLiPSe are the CALL, EXIT, REDO, FAIL and NEXT ports, the suspension facilities are traced through the DELAY and RESUME ports, and the exceptional exit is indicated by LEAVE.

When a procedure is invoked, the flow of the execution enters the procedure box by its CALL port and enters the first clause box which could (since not all clauses are tried, some of them being sure to fail, i.e., indexing is shown) unify with the goal. It may happen that a procedure is called with arguments that make it sure to fail (because of indexing). In such cases, the flow does not enter any clause box.

For each CALL port a new procedure box is created and is given:

The displayed variable instantiations are the ones at call time, i.e., before the head unification of any clause.

When a clause of a predicate succeeds (i.e., unification succeeded and all procedures called by the clause succeeded), the flow gets out of the box by the EXIT port of both boxes (only the EXIT port of the procedure box is traced).

When a procedure exits non-deterministically (and there are still other clauses to try on that procedure or one of its children goals has alternatives which could be resatisfied), the EXIT port is traced with an asterisk (*EXIT). When the last possibly matching clause of a procedure is exited, the exit is traced without asterisk. This means that this procedure box will never be retried as there is no other untried alternative.

The instantiations shown in the EXIT port are the ones at exit time, they result from the (successful) execution of the procedure.

When a clause of a procedure fails (because head unification failed or because a sub-goal failed), the flow of the execution exits the clause box and leaves the procedure box via the FAIL port. Note that the debugger cannot display any argument information at FAIL ports (an ellipsis ... is displayed instead for each argument).
If a clause fails and there is another possibly matching clause to try, then that one is tried for unification. The flow of the execution from the failure of one clause to the head unification of a following clause is traced as a NEXT port. The displayed variable instantiations are the same as those of the corresponding CALL or REDO port.
This is similar to the NEXT port, but indicates that the next branch of a disjunction (;/2) it tried after the previous branch failed. The predicate that gets displayed with the port is the predicate which contains the disjunction (the immediate ancestor).
When a procedure box is exited trough an *EXIT port, the box can be retried later to get a new solution. This will happen when a later goal fails. The backtracking will cause failing of all procedures that do not have any alternative, then the execution flow will enter a procedure box that an contains alternative through a REDO port.

Two situations may occur: either the last tried clause has called a procedure that has left a choice point (it has exited through an *EXIT port). In that case the nested procedure box is re-entered though another REDO-port.

Otherwise, if the last clause tried does not contain any nondeterministically exited boxes, but there are other untried clauses in the procedure box, the next possibly matching clause will be tried.

The last REDO port in such a sequence is the one which contains the actual alternative that is tried. The variable instantiations for all REDO ports in such a sequence are the ones corresponding to the call time of the last one.

This port allows to trace the execution of exceptions. Exceptions are either raised implicitly by built-in predicates (in which case the built-in itself exits via the LEAVE port), or explicitly through a call to throw/1 (or exit_block/1). All ancestors of the predicate that raised the exception will subsequently exit via a LEAVE port, until a catch/3 (or block/3) is found, whose second argument matches the exception. This invocation of catch/3 then passes a NEXT port (at which point the exception has been caught), and then execution continues via a normal call of the recovery goal (the third argument of the catch/3).

As with the FAIL port, no argument values are displayed in the LEAVE port.

The displayed goal becomes suspended. This is a singleton port, it does not enter or leave a box. However, a new invocation number is assigned to the delayed goal, and this number will be used in the matching RESUME port. The DELAY port is caused by one of the built-in predicates suspend/3, suspend/4, make_suspension/3 or a delay clause. The port is displayed just after the delayed goal has been created.
When a waking condition causes the resuming of a delayed goal, the procedure box is entered through its RESUME port. The box then behaves as if it had been entered through its CALL port. The invocation number is the same as in its previous DELAY port. which makes it easy to identify corresponding delay and resume events. However the depth level of the RESUME corresponds to the waking situation. It is traced like a subgoal of the goal which has caused the waking.

In the rest of this chapter the user interface to the debugger is described, including the commands available in the debugger itself as well as built-in predicates which influence it. Some of the debugger commands are explained using an excerpt of a debugger session. In these examples, the user input is always underlined (it is in fact not always output as typed) to distinguish it from the computer output.

15.1.1  Breakpoints

Breakpoints can be set on specific calls to a predicate, i.e., on a specific body goal in the source, so that the debugger will stop only at a CALL port only when that specific body goal is executed. A breakpoint is specify by giving the source file and the line number where the body goal is.

For example, if the following predicate is in a file called newtop, with the following line numbers:

  243  check_words([],[]).
  244  check_words([Word|Words],[RevWord|RevWords]) :-
  245     check_words(Words,RevWords).

The breakpoint for the body goal check_words(Words, RevWords) would be newtop:245. Note that the file name must be sufficiently specified for ECLiPSe to find the file from your current working directory.

For a call that has a breakpoint set, the execution will stop when the call is made, i.e., at the CALL port for that specific body goal.

Up Next