/************************************************************************* * * * YAP Prolog * * * * Yap Prolog was developed at NCCUP - Universidade do Porto * * * * Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997 * * * ************************************************************************** * * * File: debug.pl * * Last rev: * * mods: * * comments: YAP debugger * * * *************************************************************************/ :- system_module( '$_debug', [debug/0, debugging/0, leash/1, nodebug/0, (nospy)/1, nospyall/0, notrace/0, (spy)/1, trace/0], ['$do_spy'/4, '$init_debugger'/0, '$skipeol'/1]). :- use_system_module( '$_boot', ['$find_goal_definition'/4, '$system_catch'/4]). :- use_system_module( '$_errors', ['$Error'/1, '$do_error'/2]). :- use_system_module( '$_init', ['$system_module'/1]). :- use_system_module( '$_modules', ['$meta_expansion'/6]). :- use_system_module( '$_preds', ['$clause'/4]). /*----------------------------------------------------------------------------- Debugging / creating spy points -----------------------------------------------------------------------------*/ /** @defgroup Deb_Preds Debugging Predicates @ingroup builtins @{ The following predicates are available to control the debugging of programs: + debug Switches the debugger on. + debugging Outputs status information about the debugger which includes the leash mode and the existing spy-points, when the debugger is on. + nodebug Switches the debugger off. */ :- op(900,fx,[spy,nospy]). '$init_debugger' :- '$nb_getval'('$trace', _, fail), !. '$init_debugger' :- '$debugger_input', nb_setval('$trace',off), nb_setval('$if_skip_mode',no_skip), nb_setval('$spy_glist',[]), nb_setval('$spy_gn',1), nb_setval('$debug_run',off), nb_setval('$debug_jump',false). % First part : setting and reseting spy points % $suspy does most of the work '$suspy'(V,S,M) :- var(V) , !, '$do_error'(instantiation_error,M:spy(V,S)). '$suspy'((M:S),P,_) :- !, '$suspy'(S,P,M). '$suspy'([],_,_) :- !. '$suspy'([F|L],S,M) :- !, ( '$suspy'(F,S,M) ; '$suspy'(L,S,M) ). '$suspy'(F/N,S,M) :- !, functor(T,F,N), '$do_suspy'(S, F, N, T, M). '$suspy'(A,S,M) :- atom(A), !, '$suspy_predicates_by_name'(A,S,M). '$suspy'(P,spy,M) :- !, '$do_error'(domain_error(predicate_spec,P),spy(M:P)). '$suspy'(P,nospy,M) :- '$do_error'(domain_error(predicate_spec,P),nospy(M:P)). '$suspy_predicates_by_name'(A,S,M) :- % just check one such predicate exists ( current_predicate(A,M:_) -> M = EM, A = NA ; recorded('$import','$import'(EM,M,GA,_,A,_),_), functor(GA,NA,_) ), !, '$do_suspy_predicates_by_name'(NA,S,EM). '$suspy_predicates_by_name'(A,spy,M) :- !, print_message(warning,no_match(spy(M:A))). '$suspy_predicates_by_name'(A,nospy,M) :- print_message(warning,no_match(nospy(M:A))). '$do_suspy_predicates_by_name'(A,S,M) :- current_predicate(A,M:T), functor(T,A,N), '$do_suspy'(S, A, N, T, M). '$do_suspy_predicates_by_name'(A, S, M) :- recorded('$import','$import'(EM,M,_,T,A,N),_), '$do_suspy'(S, A, N, T, EM). % % protect against evil arguments. % '$do_suspy'(S, F, N, T, M) :- recorded('$import','$import'(EM,M,T0,_,F,N),_), !, functor(T0, F0, N0), '$do_suspy'(S, F0, N0, T, EM). '$do_suspy'(S, F, N, T, M) :- '$undefined'(T,M), !, ( S = spy -> print_message(warning,no_match(spy(M:F/N))) ; print_message(warning,no_match(nospy(M:F/N))) ). '$do_suspy'(S, F, N, T, M) :- '$system_predicate'(T,M), '$predicate_flags'(T,M,F,F), F /\ 0x118dd080 =\= 0, ( S = spy -> '$do_error'(permission_error(access,private_procedure,T),spy(M:F/N)) ; '$do_error'(permission_error(access,private_procedure,T),nospy(M:F/N)) ). '$do_suspy'(S, F, N, T, M) :- '$undefined'(T,M), !, ( S = spy -> print_message(warning,no_match(spy(M:F/N))) ; print_message(warning,no_match(nospy(M:F/N))) ). '$do_suspy'(S,F,N,T,M) :- '$suspy2'(S,F,N,T,M). '$suspy2'(spy,F,N,T,M) :- recorded('$spy','$spy'(T,M),_), !, print_message(informational,breakp(bp(debugger,plain,M:T,M:F/N,N),add,already)). '$suspy2'(spy,F,N,T,M) :- !, recorda('$spy','$spy'(T,M),_), '$set_spy'(T,M), print_message(informational,breakp(bp(debugger,plain,M:T,M:F/N,N),add,ok)). '$suspy2'(nospy,F,N,T,M) :- recorded('$spy','$spy'(T,M),R), !, erase(R), '$rm_spy'(T,M), print_message(informational,breakp(bp(debugger,plain,M:T,M:F/N,N),remove,last)). '$suspy2'(nospy,F,N,_,M) :- print_message(informational,breakp(no,breakpoint_for,M:F/N)). '$pred_being_spied'(G, M) :- recorded('$spy','$spy'(G,M),_), !. /** @pred spy( + _P_ ). Sets spy-points on all the predicates represented by _P_. _P_ can either be a single specification or a list of specifications. Each one must be of the form _Name/Arity_ or _Name_. In the last case all predicates with the name _Name_ will be spied. As in C-Prolog, system predicates and predicates written in C, cannot be spied. */ spy Spec :- '$init_debugger', prolog:debug_action_hook(spy(Spec)), !. spy L :- '$current_module'(M), '$suspy'(L, spy, M), fail. spy _ :- debug. /** @pred nospy( + _P_ ) Removes spy-points from all predicates specified by _P_. The possible forms for _P_ are the same as in `spy P`. */ nospy Spec :- '$init_debugger', prolog:debug_action_hook(nospy(Spec)), !. nospy L :- '$current_module'(M), '$suspy'(L, nospy, M), fail. nospy _. /** @pred nospyall Removes all existing spy-points. */ nospyall :- '$init_debugger', prolog:debug_action_hook(nospyall), !. nospyall :- recorded('$spy','$spy'(T,M),_), functor(T,F,N), '$suspy'(F/N,nospy,M), fail. nospyall. % debug mode -> debug flag = 1 debug :- '$init_debugger', ( nb_getval('$spy_gn',_) -> true ; nb_setval('$spy_gn',1) ), '$start_debugging'(on), print_message(informational,debug(debug)). '$start_debugging'(Mode) :- (Mode == on -> set_prolog_flag(debug, true) ; set_prolog_flag(debug, false) ), nb_setval('$debug_run',off), nb_setval('$debug_jump',false). nodebug :- '$init_debugger', set_prolog_flag(debug, false), nb_setval('$trace',off), print_message(informational,debug(off)). % % remove any debugging info after an abort. % /** @pred trace Switches on the debugger and enters tracing mode. */ trace :- '$init_debugger', '$nb_getval'('$trace', on, fail), !. trace :- nb_setval('$trace',on), '$start_debugging'(on), print_message(informational,debug(trace)), '$creep'. /** @pred notrace Ends tracing and exits the debugger. This is the same as nodebug/0. */ notrace :- '$init_debugger', nodebug. /*----------------------------------------------------------------------------- leash -----------------------------------------------------------------------------*/ /** @pred leash(+ _M_) Sets leashing mode to _M_. The mode can be specified as: + `full` prompt on Call, Exit, Redo and Fail + `tight` prompt on Call, Redo and Fail + `half` prompt on Call and Redo + `loose` prompt on Call + `off` never prompt + `none` never prompt, same as `off` The initial leashing mode is `full`. The user may also specify directly the debugger ports where he wants to be prompted. If the argument for leash is a number _N_, each of lower four bits of the number is used to control prompting at one the ports of the box model. The debugger will prompt according to the following conditions: + if `N/\ 1 =\= 0` prompt on fail + if `N/\ 2 =\= 0` prompt on redo + if `N/\ 4 =\= 0` prompt on exit + if `N/\ 8 =\= 0` prompt on call Therefore, `leash(15)` is equivalent to `leash(full)` and `leash(0)` is equivalent to `leash(off)`. Another way of using `leash` is to give it a list with the names of the ports where the debugger should stop. For example, `leash([call,exit,redo,fail])` is the same as `leash(full)` or `leash(15)` and `leash([fail])` might be used instead of `leash(1)`. @} */ leash(X) :- var(X), '$do_error'(instantiation_error,leash(X)). leash(X) :- '$init_debugger', '$leashcode'(X,Code), set_value('$leash',Code), '$show_leash'(informational,Code), !. leash(X) :- '$do_error'(type_error(leash_mode,X),leash(X)). '$show_leash'(Msg,0) :- print_message(Msg,leash([])). '$show_leash'(Msg,Code) :- '$check_leash_bit'(Code,0x8,L3,call,LF), '$check_leash_bit'(Code,0x4,L2,exit,L3), '$check_leash_bit'(Code,0x2,L1,redo,L2), '$check_leash_bit'(Code,0x1,[],fail,L1), print_message(Msg,leash(LF)). '$check_leash_bit'(Code,Bit,L0,_,L0) :- Bit /\ Code =:= 0, !. '$check_leash_bit'(_,_,L0,Name,[Name|L0]). '$leashcode'(full,0xf) :- !. '$leashcode'(on,0xf) :- !. '$leashcode'(half,0xb) :- !. '$leashcode'(loose,0x8) :- !. '$leashcode'(off,0x0) :- !. '$leashcode'(none,0x0) :- !. %'$leashcode'([L|M],Code) :- !, '$leashcode_list'([L|M],Code). '$leashcode'([L|M],Code) :- !, '$list2Code'([L|M],Code). '$leashcode'(N,N) :- integer(N), N >= 0, N =< 0xf. '$list2Code'(V,_) :- var(V), !, '$do_error'(instantiation_error,leash(V)). '$list2Code'([],0) :- !. '$list2Code'([V|L],_) :- var(V), !, '$do_error'(instantiation_error,leash([V|L])). '$list2Code'([call|L],N) :- '$list2Code'(L,N1), N is 0x8 + N1. '$list2Code'([exit|L],N) :- '$list2Code'(L,N1), N is 0x4 + N1. '$list2Code'([redo|L],N) :- '$list2Code'(L,N1), N is 0x2 + N1. '$list2Code'([fail|L],N) :- '$list2Code'(L,N1), N is 0x1 + N1. /*----------------------------------------------------------------------------- debugging -----------------------------------------------------------------------------*/ debugging :- '$init_debugger', prolog:debug_action_hook(nospyall), !. debugging :- ( current_prolog_flag(debug, true) -> print_message(help,debug(debug)) ; print_message(help,debug(off)) ), findall(M:(N/A),(recorded('$spy','$spy'(T,M),_),functor(T,N,A)),L), print_message(help,breakpoints(L)), get_value('$leash',Leash), '$show_leash'(help,Leash). /* @} */ /** @defgroup Deb_Interaction Interacting with the debugger @ingroup YAPProgramming Debugging with YAP is similar to debugging with C-Prolog. Both systems include a procedural debugger, based on Byrd's four port model. In this model, execution is seen at the procedure level: each activation of a procedure is seen as a box with control flowing into and out of that box. In the four port model control is caught at four key points: before entering the procedure, after exiting the procedure (meaning successful evaluation of all queries activated by the procedure), after backtracking but before trying new alternative to the procedure and after failing the procedure. Each one of these points is named a port: ~~~~~ *--------------------------------------* Call | | Exit ---------> + descendant(X,Y) :- offspring(X,Y). + ---------> | | | descendant(X,Z) :- | <--------- + offspring(X,Y), descendant(Y,Z). + <--------- Fail | | Redo *--------------------------------------* ~~~~~ + `Call` The call port is activated before initial invocation of procedure. Afterwards, execution will try to match the goal with the head of existing clauses for the procedure. + `Exit` This port is activated if the procedure succeeds. Control will now leave the procedure and return to its ancestor. + `Redo` If the goal, or goals, activated after the call port fail then backtracking will eventually return control to this procedure through the redo port. + `Fail` If all clauses for this predicate fail, then the invocation fails, and control will try to redo the ancestor of this invocation. To start debugging, the user will either call `trace` or spy the relevant procedures, entering debug mode, and start execution of the program. When finding the first spy-point, YAP's debugger will take control and show a message of the form: ~~~~~ * (1) call: quicksort([1,2,3],_38) ? ~~~~~ The debugger message will be shown while creeping, or at spy-points, and it includes four or five fields: + The first three characters are used to point out special states of the debugger. If the port is exit and the first character is '?', the current call is non-deterministic, that is, it still has alternatives to be tried. If the second character is a `\*`, execution is at a spy-point. If the third character is a `>`, execution has returned either from a skip, a fail or a redo command. + The second field is the activation number, and uniquely identifies the activation. The number will start from 1 and will be incremented for each activation found by the debugger. + In the third field, the debugger shows the active port. + The fourth field is the goal. The goal is written by `write_term/3` on the standard error stream, using the options given by debugger_print_options. If the active port is leashed, the debugger will prompt the user with a `?`, and wait for a command. A debugger command is just a character, followed by a return. By default, only the call and redo entries are leashed, but the leash/1 predicate can be used in order to make the debugger stop where needed. There are several commands available, but the user only needs to remember the help command, which is `h`. This command shows all the available options, which are: + `c` - creep this command makes YAP continue execution and stop at the next leashed port. + `return` - creep the same as c + `l` - leap YAP will execute until it meets a port for a spied predicate; this mode keeps all computation history for debugging purposes, so it is more expensive than standard execution. Use k or z for fast execution. + `k` - quasi-leap similar to leap but faster since the computation history is not kept; useful when leap becomes too slow. + `z` - zip same as k + `s` - skip YAP will continue execution without showing any messages until returning to the current activation. Spy-points will be ignored in this mode. Note that this command keeps all debugging history, use t for fast execution. This command is meaningless, and therefore illegal, in the fail and exit ports. + `t` - fast-skip similar to skip but faster since computation history is not kept; useful if skip becomes slow. + `f [ _GoalId_]` - fail If given no argument, forces YAP to fail the goal, skipping the fail port and backtracking to the parent. If f receives a goal number as the argument, the command fails all the way to the goal. If goal _GoalId_ has completed execution, YAP fails until meeting the first active ancestor. + `r` [ _GoalId_] - retry This command forces YAP to jump back call to the port. Note that any side effects of the goal cannot be undone. This command is not available at the call port. If f receives a goal number as the argument, the command retries goal _GoalId_ instead. If goal _GoalId_ has completed execution, YAP fails until meeting the first active ancestor. + `a` - abort execution will be aborted, and the interpreter will return to the top-level. YAP disactivates debug mode, but spypoints are not removed. + `n` - nodebug stop debugging and continue execution. The command will not clear active §spy-points. + `e` - exit leave YAP. + `h` - help show the debugger commands. + `!` Query execute a query. YAP will not show the result of the query. + `b` - break break active execution and launch a break level. This is the same as `!break`. + `+` - spy this goal start spying the active goal. The same as `! spy G` where _G_ is the active goal. + `-` - nospy this goal stop spying the active goal. The same as `! nospy G` where _G_ is the active goal. + `p` - print shows the active goal using print/1 + `d` - display shows the active goal using display/1 + `