Previous | Table of Contents | Next |
6.3.8.2. Some Other Debugger Commands
The fine-grained method used previously of tracing through an execution sequence is evocatively known as creeping, but Enter is not the only thing we can press at a debugger prompt. In fact, there is a whole set of commands we can use. Each command is issued by typing a single letter followed by pressing Enter.
The debug command s, for skip, can be used at a call or redo port to skip an entire computation. This leaves us at the exit or fail port at the end of its invocation without tracing what has happened in between. A useful technique is to look at the information that the debugger gives us and use it to decide whether to skip over the call. The debugger always tells us the predicate name and the arguments to the call, so if we know or can guess the outcome of the call, we may choose to skip over it. Some other simple debugging commands are
6.3.8.3. Spy-Points
If we know that a problem exists only once we reach a certain predicate, it is tedious to creep through a large number of levels until we reach the problem predicate. In the four-port debugger, we can set breakpoints, or spy-points, on given predicates. We do this by issuing the top-level query spy p, where p is the name of the predicate we want a spy-point on. When we have done this, the debug command l, for leap, skips over (that is, executes without displaying any debugging information or prompts) all computation up to the next spy-point.
In addition to the trace mode used previously is a debug mode. When the interpreter is in debug mode (which is entered with debug and exited with nodebug), a query is executed as if the interpreter were in trace mode, except that there is an automatic l command given to the debugger. Thus no information is presented about the computation until the first spy-point is reached. This can be a useful timesaver.
Spy-points and leaping are useful but are tricky to use effectively. If we set spy-points at predicates at too low a level, leaping still gives us too much information; if we set them at predicates at too high a level, we may skip over the area where the problem occurs, perhaps failing back to the top-level interpreter without finding the problem we were looking for. A spy-point is most useful on a predicate that we suspect may be buggy or one that is called just before a known problem arises.
While at the debugging prompt, we can issue the command + to set a spy-point on-the-fly at the current predicate or to remove a previously set spy-point. Both commands take effect immediately for the whole debugging session. An experienced Prolog programmer can use leaping, creeping, skipping, and on-the-fly spy-point setting to identify a problem relatively quickly.
Interactive debugging is available mainly in interpreters. Compiled Prolog code is sometimes able to use the four-port debugger but often can be debugged only with calls to the write predicate. Thus, a Prolog system with both an interpreter (and debugger) and a compiler, or with a compiler allowing debugging support, is the most useful configuration.
One of the most useful features of modern Prolog systems is Definite Clause Grammars, or DCGs. DCGs allow us to write grammars in a natural style almost like traditional context-free grammars, but in such a way that the grammar is integrated with the rest of the Prolog program.
Grammar processing was, in fact, one of the original applications for which Colmerauer built Prolog. The idea of DCGs, which greatly simplify grammar processing, was first proposed by Fernando Pereira and David H. D. Warren (1980) and has now become a standard feature of Prolog.
6.3.9.1. Grammars in Conventional Prolog
Consider the following Prolog predicates:
beginning_is_sentence(List, Rest) :- beginning_is_noun_phrase(List, Rest1), beginning_is_verb_phrase(Rest1, Rest). beginning_is_noun_phrase([evelyn|Rest], Rest). beginning_is_noun_phrase([chris|Rest], Rest). beginning_is_verb_phrase([sings|Rest], Rest). beginning_is_verb_phrase([jogs|Rest], Rest). beginning_is_verb_phrase([loves|Rest1], Rest) :- beginning_is_noun_phrase(Rest1, Rest).
These predicates can be seen as parsing the beginning of a list of constants as being a sentence, noun phrase, or verb phrase. beginning_is_noun_phrase takes two lists (List, Rest) and succeeds only if the beginning of List is a noun phrase we recognize (here, we recognize only two names as noun phrases) and the rest of List is Rest. The other predicates operate similarly. We can see whether the list [evelyn, loves, chris] is a sentence by posing the query
beginning_is_sentence([evelyn, loves, chris], []).
This requires that the rest of the list after the sentence is the empty listthat is, that the entire list constitutes a complete sentence. This query succeeds, but for instance, the queries
beginning_is_sentence([evelyn, loves], []). beginning_is_sentence([evelyn, sings, chris], []). beginning_is_sentence([evelyn, loves, chris, madly], []).
all fail. The query beginning_is_sentence([evelyn, loves, chris, madly], Rest) returns the substitution Rest = [madly], although it is not as usual to give a non-empty list as the last parameter.
This technique can be used to write any grammar in Prolog. However, there are some straightforward and tedious aspects of the technique. In the predicate bodies, the second argument of one call is always the first argument to the next call, and the second argument of the last call is always the second argument in the head. The important information in the predicates is the sequence of predicate calls, and those are somewhat obscured by the rest of the definitions.
Previous | Table of Contents | Next |