Quintus Prolog Manual
This chapter explains the basic concepts and terminology used by the Quintus Prolog debugger. Following chapters describe the two debuggers available in the Quintus Prolog system: the X Windows-based source linked debugger and the standard debugger.
The main features of the debugging package are:
E-1-1: The Procedure Box Control Flow Model
During debugging, the debugger stops at various points in the execution of a goal, allowing you see what arguments the goal is called with, what arguments it returns with, and whether it succeeds or fails. As in other programming languages, key points of interest are procedure entry and return, but in Prolog there is the additional complexity of backtracking. One of the major confusions that novice Prolog programmers have to face is the question of what actually happens when a goal fails and the system suddenly begins backtracking. The procedure box model of Prolog execution views program control flow in terms of movement from clause to clause in the procedures of the program. This model provides a basis for the debugging mechanism and enables the user to view program behavior in a consistent way.
Let us look at a representative Prolog procedure:
Call Exit
descendant(X, Y) :- offspring(X, Y).
descendant(X, Z) :-
offspring(X, Y), descendant(Y, Z).
Fail Redo
The first clause states that Y is a descendant of X if Y is an offspring of X, and the second clause states that Z is a descendant of X if Y is an offspring of X and if Z is a descendant of Y. In the diagram a box has been drawn around the whole procedure; labeled arrows indicate the control flow into and out of this box. There are four such arrows which we shall describe in turn.
Note that the box we have drawn around the procedure should really be seen as an invocation box, rather than a procedure box. Often, there are many different Calls and Exits to a particular procedure in the control flow, but these are for different invocations. Each invocation box is given a unique integer identifier to prevent confusion.
E-1-2: Understanding Prolog Execution Using The Debugger
The Quintus Prolog debugger extends the procedure box control flow model to add extra information about the details of the execution of a goal, allowing you to better understand how your code behaves, and its efficiency.
Prolog incorporates a backtracking mechanism as a basic feature, which allows Prolog programs to efficiently search for multiple solutions to a problem. A goal is determinate if it has only one solution (or none). Often, this is what is desired. When a goal is not determinate, Prolog must keep around information to allow it to backtrack to look for alternate solutions. This extra information is called a choice point.
When a goal is intended to be nondeterminate, it may be expected to leave a choice point. However, when a goal that is expected to be determinate leaves a choice point, this may indicate an error in the program. In this case, the goal might succeed with the correct answer, but on backtracking produce wrong answers or not terminate. At the very least, an unnecessary choice point means that memory is being wasted. Quintus Prolog detects many kinds of determinate goals, and either does not allocate a choice point at all, or deallocates it as soon as possible, saving time and memory (see section B-5-1). Sometimes, however, you must help Prolog out by putting a cut (see section B-5-1-1) in your program, or by using an if-then-else (see section G-2-7) construct. The Quintus Prolog debugger helps you find such cases.
Quintus Prolog indexes on the first argument of a predicate. This means that if the first arguments in the clauses of a predicate are not variables, and the first argument in a call to that predicate is non-variable, then Prolog will go directly to the clause that matches, without even considering those that don't. Note that in order for this indexing to be very efficient, it only looks at the principal functor of a compound term. This means that if the first argument of one clause is a(a) and the first argument of the next clause is a(b), indexing will not be able to distinguish these clauses, so both will need to be tried.
Actually, indexing is more complicated than this. Any clause whose head has a variable as first argument will match any call, so indexing cannot be applied to this clause. Therefore, a predicate can be divided into alternating groups of adjacent indexable and nonindexable clauses. When the first argument of a call is non-variable, Quintus Prolog will skip over any clauses that don't match that argument, within a group of adjacent indexable clauses. Quintus Prolog will then try every clause in a group of adjacent nonindexable clauses, and then again skip nonmatching clause in an indexable group, and so on.
Even more important than time saved by indexing is its effect on determinacy. In effect, indexing makes it possible to ignore clauses following the clause being tried, as well as clauses preceding it. If it is possible to ignore all the clauses following the clause being tried, then Prolog will not create a choice point, or if a choice point has already been allocated for the call, Prolog will de-allocate it. Careful use of indexing can save a great deal time and memory running your program.
The Quintus Prolog debugger helps you understand these efficiency concerns, and also Quintus Prolog's exception handling, by extending the box model with three extra ports. These ports are described below.
Here's a more complete picture of the invocation box, including the extended ports.
Call Exit
descendant(X, Y) :-
Head offspring(X, Y).
Done
Fail descendant(X, Z) :-
Head offspring(X, Y),
descendant(Y, Z).
Exception Redo
E-1-3: Traveling Between Ports
The Quintus Prolog debugger provides a rich set of commands to move between ports (that is, execute your program in a controlled way).
E-1-3-1: Basic Traveling Commands
Spypoints allow you to indicate where to stop on a per-predicate or even per-goal basis. For example, you might find that you want to run until some particular predicate is called. In this case, you would set a spypoint on that predicate, using spy/1. Such spypoints are turned off with nospy/1.
It may be desirable to stop when you get to a particular call from one predicate to another. This can be done with the built-in predicate add_spypoint/1. These spypoints can be removed with remove_spypoint/1.
To examine spypoints, use current_spypoint/1. Spypoints are also included in the output of debugging/0. Finally, spypoints can be removed all at once with nospyall/0.
The debuggers also have commands for setting spypoints, which are easier to use than these predicates.
E-1-3-3: Traveling Commands Sensitive to Spypoints
The basic traveling commands listed above all ignore spypoints; the following commands take advantage of spypoints.
E-1-3-4: Commands That Change The Flow Of Control
The debugger also has these commands that alter the flow of control of your program.
E-1-4-1: Trace Mode, Debug Mode, And Zip Mode
The debugger has three modes of operation: trace mode, debug mode, and zip mode. While trace mode is in force, every time you type a goal at the top level, the debugger starts single-stepping (creeping) immediately. In contrast, while debug mode is in force, the debugger does nothing until a call is made to a procedure or goal on which you have placed a spypoint. That is, it starts by leaping. Similarly, when in zip mode, the debugger begins by zipping.
The debugger mode (trace, debug, or zip) determines the first procedure call which will be traced after a goal is typed at top level. There is no other difference among the three modes. Whenever the debugger prompts you for input, you have a number of options, including those of creeping (single-stepping) leaping (jumping to the next spypoint), and zipping (jumping to the next spypoint without keeping debugging information).
The debugger mode can be set by using
The purpose of leashing is to allow you to speed up single-stepping (creeping) through a program by telling the debugger that it does not always need to wait for user interaction at every port.
The leashing mode only applies to procedures that do not have spypoints on them, and it determines which ports of such procedures are leashed. By default, all ports are leashed. On arrival at a leashed port, the debugger will stop to allow you to look at the execution state and decide what to do next. At unleashed ports, the goal is displayed but program execution does not stop to allow user interaction.
At any time, there is a leashing mode in force which determines at which of the seven ports of a procedure box (Call, Exit, Redo, Fail, Done, Head, and Exception) the debugger will stop and wait for a command. By default, the debugger will stop at every port, but sometimes you may wish to reduce the number of times you have to issue commands. For example, it is often convenient only to have to interact at the Call, Redo, and Exception ports.
To set the leashing mode, that is, to specify ports for leashing, call leash/1. The leashing mode can also be set from the options menu of the source linked debugger (section E-2).
NOTE: Spypoints are not affected by leashing; the debugger will always stop at every port for a procedure or call on which there is a spypoint.
To allow users to produce code that cannot be debugged by others (for security reasons), Quintus Prolog 3.0 supports the concept of a locked predicate. A locked predicate will be treated by the debugger as opaque: users will not be able to creep into it. The debugger will behave in much the same way for locked predicates as it does for built-ins.
Predicates may be locked by specifying the -h switch to qpc when compiling a file. See the documentation for qpc, section H-1-2.
The built-in predicate prolog_flag/3 or unknown/2 can be used to determine or set Prolog's behavior when it comes across an undefined predicate. By default, unknown procedures raise an exception.
Procedures which are known to be dynamic fail when there are no clauses for them.
When an undefined predicate is called and the undefined predicate behavior is set to 'error' rather than 'fail', unknown_predicate_handler/3 is called in module 'user'. By defining this predicate, you can (selectively) trap calls to undefined predicates in a program.
E-1-4-5: Current Debugging State
Information about the current debugging state includes the following:
This information can be displayed by calling debugging/0.
add_spypoint/1 notrace/0 current_spypoint/1 remove_spypoint/1 debug/0 spy/1 debugging/0 trace/0 leash/1 unknown/2 nodebug/0 unknown_predicate_handler/3 nospy/1 nospyall/0
contact:
product support
sales information