402 lines
11 KiB
Prolog
402 lines
11 KiB
Prolog
/* Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemaker@vu.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 1985-2012, University of Amsterdam
|
|
VU University Amsterdam
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
As a special exception, if you link this library with other files,
|
|
compiled with a Free Software compiler, to produce an executable, this
|
|
library does not by itself cause the resulting executable to be covered
|
|
by the GNU General Public License. This exception does not however
|
|
invalidate any other reasons why the executable file might be covered by
|
|
the GNU General Public License.
|
|
*/
|
|
|
|
:- module(prolog_debug,
|
|
[ debug/3, % +Topic, +Format, :Args
|
|
debug/1, % +Topic
|
|
nodebug/1, % +Topic
|
|
debugging/1, % ?Topic
|
|
debugging/2, % ?Topic, ?Bool
|
|
list_debug_topics/0,
|
|
debug_message_context/1, % (+|-)What
|
|
|
|
assertion/1 % :Goal
|
|
]).
|
|
%% :- use_module(library(error)).
|
|
:- use_module(library(lists)).
|
|
:- set_prolog_flag(generate_debug_info, false).
|
|
|
|
:- meta_predicate
|
|
assertion(0),
|
|
debug(+,+,:).
|
|
|
|
:- multifile prolog:assertion_failed/2.
|
|
:- dynamic prolog:assertion_failed/2.
|
|
|
|
/*:- use_module(library(prolog_stack)).*/ % We use the autoloader if needed
|
|
|
|
:- if(current_prolog_flag(dialect, yap)).
|
|
|
|
:- use_module(library(hacks), [stack_dump/1]).
|
|
|
|
% this is as good as I can do.
|
|
backtrace(N) :-
|
|
stack_dump(N).
|
|
|
|
:- endif.
|
|
|
|
%:- set_prolog_flag(generate_debug_info, false).
|
|
|
|
:- dynamic
|
|
debugging/3, % Topic, Enabled, To
|
|
debug_context/1.
|
|
|
|
debug_context(thread).
|
|
|
|
/** <module> Print debug messages and test assertions
|
|
@ingroup swi
|
|
|
|
This library is a replacement for format/3 for printing debug messages.
|
|
Messages are assigned a _topic_. By dynamically enabling or disabling
|
|
topics the user can select desired messages. Debug statements are
|
|
removed when the code is compiled for optimization.
|
|
|
|
See manual for details. With XPCE, you can use the call below to start a
|
|
graphical monitoring tool.
|
|
|
|
==
|
|
?- prolog_ide(debug_monitor).
|
|
==
|
|
|
|
Using the predicate assertion/1 you can make assumptions about your
|
|
program explicit, trapping the debugger if the condition does not hold.
|
|
|
|
@author Jan Wielemaker
|
|
*/
|
|
|
|
%% debugging(+Topic) is semidet.
|
|
%% debugging(-Topic) is nondet.
|
|
%% debugging(?Topic, ?Bool) is nondet.
|
|
%
|
|
% Examine debug topics. The form debugging(+Topic) may be used to
|
|
% perform more complex debugging tasks. A typical usage skeleton
|
|
% is:
|
|
%
|
|
% ==
|
|
% ( debugging(mytopic)
|
|
% -> <perform debugging actions>
|
|
% ; true
|
|
% ),
|
|
% ...
|
|
% ==
|
|
%
|
|
% The other two calls are intended to examine existing and enabled
|
|
% debugging tokens and are typically not used in user programs.
|
|
|
|
debugging(Topic) :-
|
|
debugging(Topic, true, _To).
|
|
|
|
debugging(Topic, Bool) :-
|
|
debugging(Topic, Bool, _To).
|
|
|
|
%% debug(+Topic) is det.
|
|
%% nodebug(+Topic) is det.
|
|
%
|
|
% Add/remove a topic from being printed. nodebug(_) removes all
|
|
% topics. Gives a warning if the topic is not defined unless it is
|
|
% used from a directive. The latter allows placing debug topics at
|
|
% the start of a (load-)file without warnings.
|
|
%
|
|
% For debug/1, Topic can be a term Topic > Out, where Out is
|
|
% either a stream or stream-alias or a filename (atom). This
|
|
% redirects debug information on this topic to the given output.
|
|
|
|
debug(Topic) :-
|
|
debug(Topic, true).
|
|
nodebug(Topic) :-
|
|
debug(Topic, false).
|
|
|
|
debug(Spec, Val) :-
|
|
debug_target(Spec, Topic, Out),
|
|
( ( retract(debugging(Topic, Enabled0, To0))
|
|
*-> update_debug(Enabled0, To0, Val, Out, Enabled, To),
|
|
assert(debugging(Topic, Enabled, To)),
|
|
fail
|
|
; ( prolog_load_context(file, _)
|
|
-> true
|
|
; print_message(warning, debug_no_topic(Topic))
|
|
),
|
|
update_debug(false, [], Val, Out, Enabled, To),
|
|
assert(debugging(Topic, Enabled, To))
|
|
)
|
|
-> true
|
|
; true
|
|
).
|
|
|
|
debug_target(Spec, Topic, To) :-
|
|
nonvar(Spec),
|
|
Spec = (Topic > To), !.
|
|
debug_target(Topic, Topic, -).
|
|
|
|
update_debug(_, To0, true, -, true, To) :- !,
|
|
ensure_output(To0, To).
|
|
update_debug(true, To0, true, Out, true, Output) :- !,
|
|
append(To0, [Out], Output).
|
|
update_debug(false, _, true, Out, true, [Out]) :- !.
|
|
update_debug(_, _, false, -, false, []) :- !.
|
|
update_debug(true, [Out], false, Out, false, []) :- !.
|
|
update_debug(true, To0, false, Out, true, Output) :- !,
|
|
delete(To0, Out, Output).
|
|
|
|
ensure_output([], [user_error]) :- !.
|
|
ensure_output(List, List).
|
|
|
|
%% debug_topic(+Topic) is det.
|
|
%
|
|
% Declare a topic for debugging. This can be used to find all
|
|
% topics available for debugging.
|
|
|
|
debug_topic(Topic) :-
|
|
( debugging(Registered, _, _),
|
|
Registered =@= Topic
|
|
-> true
|
|
; assert(debugging(Topic, false, []))
|
|
).
|
|
|
|
%% list_debug_topics is det.
|
|
%
|
|
% List currently known debug topics and their setting.
|
|
|
|
list_debug_topics :-
|
|
format(user_error, '~*t~45|~n', "-"),
|
|
format(user_error, '~w~t ~w~35| ~w~n',
|
|
['Debug Topic', 'Activated', 'To']),
|
|
format(user_error, '~*t~45|~n', "-"),
|
|
( debugging(Topic, Value, To),
|
|
format(user_error, '~w~t ~w~35| ~w~n', [Topic, Value, To]),
|
|
fail
|
|
; true
|
|
).
|
|
|
|
%% debug_message_context(+What) is det.
|
|
%
|
|
% Specify additional context for debug messages. What is one of
|
|
% +Context or -Context, and Context is one of =thread=, =time= or
|
|
% time(Format), where Format is a format specification for
|
|
% format_time/3 (default is =|%T.%3f|=). Initially, debug/3 shows
|
|
% only thread information.
|
|
|
|
debug_message_context(+Topic) :- !,
|
|
valid_topic(Topic, Del, Add),
|
|
retractall(debug_context(Del)),
|
|
assert(debug_context(Add)).
|
|
debug_message_context(-Topic) :- !,
|
|
valid_topic(Topic, Del, _),
|
|
retractall(debug_context(Del)).
|
|
debug_message_context(Term) :-
|
|
type_error(debug_message_context, Term).
|
|
|
|
valid_topic(thread, thread, thread) :- !.
|
|
valid_topic(time, time(_), time('%T.%3f')) :- !.
|
|
valid_topic(time(Format), time(_), time(Format)) :- !.
|
|
valid_topic(X, _, _) :-
|
|
domain_error(debug_message_context, X).
|
|
|
|
|
|
%% debug(+Topic, +Format, :Args) is det.
|
|
%
|
|
% Format a message if debug topic is enabled. Similar to format/3
|
|
% to =user_error=, but only prints if Topic is activated through
|
|
% debug/1. Args is a meta-argument to deal with goal for the
|
|
% @-command. Output is first handed to the hook
|
|
% prolog:debug_print_hook/3. If this fails, Format+Args is
|
|
% translated to text using the message-translation (see
|
|
% print_message/2) for the term debug(Format, Args) and then
|
|
% printed to every matching destination (controlled by debug/1)
|
|
% using print_message_lines/3.
|
|
%
|
|
% The message is preceded by '% ' and terminated with a newline.
|
|
%
|
|
% @see format/3.
|
|
|
|
debug(Topic, Format, Args) :-
|
|
debugging(Topic, true, To), !,
|
|
print_debug(Topic, To, Format, Args).
|
|
debug(_, _, _).
|
|
|
|
|
|
%% prolog:debug_print_hook(+Topic, +Format, +Args) is semidet.
|
|
%
|
|
% Hook called by debug/3. This hook is used by the graphical
|
|
% frontend that can be activated using prolog_ide/1:
|
|
%
|
|
% ==
|
|
% ?- prolog_ide(debug_monitor).
|
|
% ==
|
|
|
|
:- multifile
|
|
prolog:debug_print_hook/3.
|
|
|
|
print_debug(Topic, _To, Format, Args) :-
|
|
prolog:debug_print_hook(Topic, Format, Args), !.
|
|
print_debug(_, [], _, _) :- !.
|
|
print_debug(Topic, To, Format, Args) :-
|
|
phrase('$messages':translate_message(debug(Format, Args)), Lines),
|
|
( member(T, To),
|
|
debug_output(T, Stream),
|
|
print_message_lines(Stream, kind(debug(Topic)), Lines),
|
|
fail
|
|
; true
|
|
).
|
|
|
|
|
|
debug_output(user, user_error) :- !.
|
|
debug_output(Stream, Stream) :-
|
|
is_stream(Stream), !.
|
|
debug_output(File, Stream) :-
|
|
open(File, append, Stream,
|
|
[ close_on_abort(false),
|
|
alias(File),
|
|
buffer(line)
|
|
]).
|
|
|
|
|
|
/*******************************
|
|
* ASSERTION *
|
|
*******************************/
|
|
|
|
%% assertion(:Goal) is det.
|
|
%
|
|
% Acts similar to C assert() macro. It has no effect if Goal
|
|
% succeeds. If Goal fails or throws an exception, the following
|
|
% steps are taken:
|
|
%
|
|
% * call prolog:assertion_failed/2. If prolog:assertion_failed/2
|
|
% fails, then:
|
|
%
|
|
% - If this is an interactive toplevel thread, print a
|
|
% message, the stack-trace, and finally trap the debugger.
|
|
% - Otherwise, throw error(assertion_error(Reason, G),_) where
|
|
% Reason is one of =fail= or the exception raised.
|
|
|
|
assertion(G) :-
|
|
\+ \+ catch(G,
|
|
Error,
|
|
assertion_failed(Error, G)),
|
|
!.
|
|
assertion(G) :-
|
|
assertion_failed(fail, G),
|
|
assertion_failed. % prevent last call optimization.
|
|
|
|
assertion_failed(Reason, G) :-
|
|
prolog:assertion_failed(Reason, G), !.
|
|
assertion_failed(Reason, G) :-
|
|
print_message(error, assertion_failed(Reason, G)),
|
|
backtrace(10),
|
|
( current_prolog_flag(break_level, _) % interactive thread
|
|
-> trace
|
|
; throw(error(assertion_error(Reason, G), _))
|
|
).
|
|
|
|
assertion_failed.
|
|
|
|
%% assume(:Goal) is det.
|
|
%
|
|
% Acts similar to C assert() macro. It has no effect of Goal
|
|
% succeeds. If Goal fails it prints a message, a stack-trace
|
|
% and finally traps the debugger.
|
|
%
|
|
% @deprecated Use assertion/1 in new code.
|
|
|
|
/*******************************
|
|
* EXPANSION *
|
|
*******************************/
|
|
|
|
:- multifile
|
|
system:goal_expansion/2.
|
|
|
|
system:goal_expansion(debug(Topic,_,_), true) :-
|
|
( current_prolog_flag(optimise, true)
|
|
-> true
|
|
; debug_topic(Topic),
|
|
fail
|
|
).
|
|
system:goal_expansion(debugging(Topic), fail) :-
|
|
( current_prolog_flag(optimise, true)
|
|
-> true
|
|
; debug_topic(Topic),
|
|
fail
|
|
).
|
|
system:goal_expansion(assertion(_), Goal) :-
|
|
current_prolog_flag(optimise, true),
|
|
Goal = true.
|
|
system:goal_expansion(assume(_), Goal) :-
|
|
print_message(informational,
|
|
compatibility(renamed(assume/1, assertion/1))),
|
|
current_prolog_flag(optimise, true),
|
|
Goal = true.
|
|
|
|
|
|
/*******************************
|
|
* MESSAGES *
|
|
*******************************/
|
|
|
|
:- multifile
|
|
prolog:message/3.
|
|
|
|
prolog:message(assertion_failed(_, G)) -->
|
|
[ 'Assertion failed: ~q'-[G] ].
|
|
prolog:message(debug(Fmt, Args)) -->
|
|
show_thread_context,
|
|
show_time_context,
|
|
[ Fmt-Args ].
|
|
prolog:message(debug_no_topic(Topic)) -->
|
|
[ '~q: no matching debug topic (yet)'-[Topic] ].
|
|
|
|
show_thread_context -->
|
|
{ debug_context(thread),
|
|
thread_self(Me) ,
|
|
Me \== main
|
|
},
|
|
[ '[Thread ~w] '-[Me] ].
|
|
show_thread_context -->
|
|
[].
|
|
|
|
show_time_context -->
|
|
{ debug_context(time(Format)),
|
|
get_time(Now),
|
|
format_time(string(S), Format, Now)
|
|
},
|
|
[ '[~w] '-[S] ].
|
|
show_time_context -->
|
|
[].
|
|
|
|
/*******************************
|
|
* HOOKS *
|
|
*******************************/
|
|
|
|
%% prolog:assertion_failed(+Reason, +Goal) is semidet.
|
|
%
|
|
% This hook is called if the Goal of assertion/1 fails. Reason is
|
|
% unified with either =fail= if Goal simply failed or an exception
|
|
% call otherwise. If this hook fails, the default behaviour is
|
|
% activated. If the hooks throws an exception it will be
|
|
% propagated into the caller of assertion/1.
|