914 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Prolog
		
	
	
	
	
	
			
		
		
	
	
			914 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Prolog
		
	
	
	
	
	
/*  Part of SWI-Prolog
 | 
						|
 | 
						|
    Author:        Jan Wielemaker
 | 
						|
    E-mail:        J.Wielemaker@cs.vu.nl
 | 
						|
    WWW:           http://www.swi-prolog.org
 | 
						|
    Copyright (C): 2011, 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 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(predicate_options,
 | 
						|
	  [ predicate_options/3,		% +PI, +Arg, +Options
 | 
						|
	    assert_predicate_options/4,		% +PI, +Arg, +Options, ?New
 | 
						|
 | 
						|
	    current_option_arg/2,		% ?PI, ?Arg
 | 
						|
	    current_predicate_option/3,		% ?PI, ?Arg, ?Option
 | 
						|
	    check_predicate_option/3,		% +PI, +Arg, +Option
 | 
						|
						% Create declarations
 | 
						|
	    current_predicate_options/3,	% ?PI, ?Arg, ?Options
 | 
						|
	    retractall_predicate_options/0,
 | 
						|
	    derived_predicate_options/3,	% :PI, ?Arg, ?Options
 | 
						|
	    derived_predicate_options/1,	% +Module
 | 
						|
						% Checking
 | 
						|
	    check_predicate_options/0,
 | 
						|
	    derive_predicate_options/0,
 | 
						|
	    check_predicate_options/1		% :PredicateIndicator
 | 
						|
	  ]).
 | 
						|
:- use_module(library(lists)).
 | 
						|
:- use_module(library(pairs)).
 | 
						|
:- use_module(library(error)).
 | 
						|
:- use_module(library(lists)).
 | 
						|
:- use_module(library(debug)).
 | 
						|
:- use_module(library(prolog_clause)).
 | 
						|
 | 
						|
:- meta_predicate
 | 
						|
	predicate_options(:, +, +),
 | 
						|
	assert_predicate_options(:, +, +, ?),
 | 
						|
	current_predicate_option(:, ?, ?),
 | 
						|
	check_predicate_option(:, ?, ?),
 | 
						|
	current_predicate_options(:, ?, ?),
 | 
						|
	current_option_arg(:, ?),
 | 
						|
	pred_option(:,-),
 | 
						|
	derived_predicate_options(:,?,?),
 | 
						|
	check_predicate_options(:).
 | 
						|
 | 
						|
/** <module> Access and analyse predicate options
 | 
						|
@ingroup swi
 | 
						|
 | 
						|
This  module  provides  the  developers   interface  for  the  directive
 | 
						|
predicate_options/3. This directive allows  us   to  specify  that e.g.,
 | 
						|
open/4 processes options using the 4th  argument and supports the option
 | 
						|
=type= using the values =text= and  =binary=. Declaring options that are
 | 
						|
processed allows for more reliable  handling   of  predicate options and
 | 
						|
simplifies porting applications. This  library   provides  the following
 | 
						|
functionality:
 | 
						|
 | 
						|
  * Query supported options through current_predicate_option/3
 | 
						|
    or current_predicate_options/3.  This is intended to support
 | 
						|
    conditional compilation and an IDE.
 | 
						|
  * Derive additional declarations through dataflow analysis using
 | 
						|
    derive_predicate_options/0.
 | 
						|
  * Perform a compile-time analysis of the entire loaded program using
 | 
						|
    check_predicate_options/0.
 | 
						|
 | 
						|
Below, we describe some use-cases.
 | 
						|
 | 
						|
  $ Quick check of a program :
 | 
						|
  This scenario is useful as an occasional check or to assess problems
 | 
						|
  with option-handling for porting an application to SWI-Prolog.  It
 | 
						|
  consists of three steps: loading the program (1 and 2), deriving
 | 
						|
  option handling for application predicates (3) and running the
 | 
						|
  checker (4).
 | 
						|
 | 
						|
    ==
 | 
						|
    1 ?- [load].
 | 
						|
    2 ?- autoload.
 | 
						|
    3 ?- derive_predicate_options.
 | 
						|
    4 ?- check_predicate_options.
 | 
						|
    ==
 | 
						|
 | 
						|
  $ Add declaations to your program :
 | 
						|
  Adding declarations about option processes improves the quality of
 | 
						|
  the checking.  The analysis of derive_predicate_options/0 may miss
 | 
						|
  options and does not derive the types for options that are processed
 | 
						|
  in Prolog code.  The process is similar to the above.  In steps 4 and
 | 
						|
  further, the inferred declarations are listed, inspected and added to
 | 
						|
  the source-code of the module.
 | 
						|
 | 
						|
    ==
 | 
						|
    1 ?- [load].
 | 
						|
    2 ?- autoload.
 | 
						|
    3 ?- derive_predicate_options.
 | 
						|
    4 ?- derived_predicate_options(module_1).
 | 
						|
    5 ?- derived_predicate_options(module_2).
 | 
						|
    6 ?- ...
 | 
						|
    ==
 | 
						|
 | 
						|
  $ Declare option processing requirements :
 | 
						|
  If an application requires that open/4 needs to support lock(write),
 | 
						|
  it may do so using the derective below.  This directive raises an
 | 
						|
  exception when loaded on a Prolog implementation that does not support
 | 
						|
  this option.
 | 
						|
 | 
						|
    ==
 | 
						|
    :- current_predicate_option(open/4, 4, lock(write)).
 | 
						|
    ==
 | 
						|
 | 
						|
@see library(option) for accessing options in Prolog code.
 | 
						|
*/
 | 
						|
 | 
						|
:- multifile option_decl/3, pred_option/3.
 | 
						|
:- dynamic   dyn_option_decl/3.
 | 
						|
 | 
						|
%%	predicate_options(:PI, +Arg, +Options) is det.
 | 
						|
%
 | 
						|
%	Declare that the predicate PI processes options on Arg.  Options
 | 
						|
%	is a list of options processed.  Each element is one of:
 | 
						|
%
 | 
						|
%	  * Option(ModeAndType)
 | 
						|
%	  PI processes Option. The option-value must comply to
 | 
						|
%	  ModeAndType.  Mode is one of + or - and Type is a type as
 | 
						|
%	  accepted by must_be/2.
 | 
						|
%
 | 
						|
%	  * pass_to(:PI,Arg)
 | 
						|
%	  The option-list is passed to the indicated predicate.
 | 
						|
%
 | 
						|
%	Below is an example that   processes  the option header(boolean)
 | 
						|
%	and passes all options to open/4:
 | 
						|
%
 | 
						|
%	  ==
 | 
						|
%	  :- predicate_options(write_xml_file/3, 3,
 | 
						|
%			       [ header(boolean),
 | 
						|
%			         pass_to(open/4, 4)
 | 
						|
%			       ]).
 | 
						|
%
 | 
						|
%	  write_xml_file(File, XMLTerm, Options) :-
 | 
						|
%	      open(File, write, Out, Options),
 | 
						|
%	      (   option(header(true), Option, true)
 | 
						|
%	      ->  write_xml_header(Out)
 | 
						|
%	      ;   true
 | 
						|
%	      ),
 | 
						|
%	      ...
 | 
						|
%	  ==
 | 
						|
%
 | 
						|
%	This predicate may  only  be  used   as  a  _directive_  and  is
 | 
						|
%	processed  by  expand_term/2.  Option  processing    can  be  be
 | 
						|
%	specified at runtime using  assert_predicate_options/3, which is
 | 
						|
%	intended to support program analysis.
 | 
						|
 | 
						|
predicate_options(PI, Arg, Options) :-
 | 
						|
	throw(error(context_error(nodirective,
 | 
						|
				  predicate_options(PI, Arg, Options)), _)).
 | 
						|
 | 
						|
 | 
						|
%%	assert_predicate_options(:PI, +Arg, +Options, ?New) is semidet.
 | 
						|
%
 | 
						|
%	As predicate_options(:PI, +Arg, +Options).  New   is  a  boolean
 | 
						|
%	indicating whether the declarations  have   changed.  If  new is
 | 
						|
%	provided and =false=, the predicate   becomes  semidet and fails
 | 
						|
%	without modifications if modifications are required.
 | 
						|
 | 
						|
assert_predicate_options(PI, Arg, Options, New) :-
 | 
						|
	canonical_pi(PI, M:Name/Arity),
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	(   dyn_option_decl(Head, M, Arg)
 | 
						|
	->  true
 | 
						|
	;   New = true,
 | 
						|
	    assertz(dyn_option_decl(Head, M, Arg))
 | 
						|
	),
 | 
						|
	phrase('$predopts':option_clauses(Options, Head, M, Arg),
 | 
						|
	       OptionClauses),
 | 
						|
	forall(member(Clause, OptionClauses),
 | 
						|
	       assert_option_clause(Clause, New)),
 | 
						|
	(   var(New)
 | 
						|
	->  New = false
 | 
						|
	;   true
 | 
						|
	).
 | 
						|
 | 
						|
assert_option_clause(Clause, New) :-
 | 
						|
	rename_clause(Clause, NewClause,
 | 
						|
		      '$pred_option'(A,B,C,D), '$dyn_pred_option'(A,B,C,D)),
 | 
						|
	clause_head(NewClause, NewHead),
 | 
						|
	(   clause(NewHead, _)
 | 
						|
	->  true
 | 
						|
	;   New = true,
 | 
						|
	    assertz(NewClause)
 | 
						|
	).
 | 
						|
 | 
						|
clause_head(M:(Head:-_Body), M:Head) :- !.
 | 
						|
clause_head((M:Head :-_Body), M:Head) :- !.
 | 
						|
clause_head(Head, Head).
 | 
						|
 | 
						|
rename_clause(M:Clause, M:NewClause, Head, NewHead) :- !,
 | 
						|
	rename_clause(Clause, NewClause, Head, NewHead).
 | 
						|
rename_clause((Head :- Body), (NewHead :- Body), Head, NewHead) :- !.
 | 
						|
rename_clause(Head, NewHead, Head, NewHead) :- !.
 | 
						|
rename_clause(Head, Head, _, _).
 | 
						|
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	  QUERY OPTIONS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
%%	current_option_arg(:PI, ?Arg) is nondet.
 | 
						|
%
 | 
						|
%	True when Arg of PI processes   predicate options. Which options
 | 
						|
%	are processed can be accessed using current_predicate_option/3.
 | 
						|
 | 
						|
current_option_arg(Module:Name/Arity, Arg) :-
 | 
						|
	current_option_arg(Module:Name/Arity, Arg, _DefM).
 | 
						|
 | 
						|
current_option_arg(Module:Name/Arity, Arg, DefM) :-
 | 
						|
	atom(Name), integer(Arity), !,
 | 
						|
	resolve_module(Module:Name/Arity, DefM:Name/Arity),
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	(   option_decl(Head, DefM, Arg)
 | 
						|
	;   dyn_option_decl(Head, DefM, Arg)
 | 
						|
	).
 | 
						|
current_option_arg(M:Name/Arity, Arg, M) :-
 | 
						|
	(   option_decl(Head, M, Arg)
 | 
						|
	;   dyn_option_decl(Head, M, Arg)
 | 
						|
	),
 | 
						|
	functor(Head, Name, Arity).
 | 
						|
 | 
						|
%%	current_predicate_option(:PI, ?Arg, ?Option) is nondet.
 | 
						|
%
 | 
						|
%	True when Arg of PI processes Option. For example, the following
 | 
						|
%	is true:
 | 
						|
%
 | 
						|
%	  ==
 | 
						|
%	  ?- current_predicate_option(open/4, 4, type(text)).
 | 
						|
%	  true.
 | 
						|
%	  ==
 | 
						|
%
 | 
						|
%	This predicate is intended to   support  conditional compilation
 | 
						|
%	using      if/1      ...      endif/0.        The      predicate
 | 
						|
%	current_predicate_options/3 can be  used  to   access  the  full
 | 
						|
%	capabilities of a predicate.
 | 
						|
 | 
						|
current_predicate_option(Module:PI, Arg, Option) :-
 | 
						|
	current_option_arg(Module:PI, Arg, DefM),
 | 
						|
	PI = Name/Arity,
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	catch(pred_option(DefM:Head, Option),
 | 
						|
	      error(type_error(_,_),_),
 | 
						|
	      fail).
 | 
						|
 | 
						|
%%	check_predicate_option(:PI, +Arg, +Option) is det.
 | 
						|
%
 | 
						|
%	Similar to current_predicate_option/3, but   intended to support
 | 
						|
%	runtime checking.
 | 
						|
%
 | 
						|
%	@error	existence_error(option, OptionName) if the option is not
 | 
						|
%		supported by PI.
 | 
						|
%	@error	type_error(Type, Value) if the option is supported but
 | 
						|
%		the value does not match the option type. See must_be/2.
 | 
						|
 | 
						|
check_predicate_option(Module:PI, Arg, Option) :-
 | 
						|
	define_predicate(Module:PI),
 | 
						|
	current_option_arg(Module:PI, Arg, DefM),
 | 
						|
	PI = Name/Arity,
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	(   pred_option(DefM:Head, Option)
 | 
						|
	->  true
 | 
						|
	;   existence_error(option, Option)
 | 
						|
	).
 | 
						|
 | 
						|
 | 
						|
pred_option(M:Head, Option) :-
 | 
						|
	pred_option(M:Head, Option, []).
 | 
						|
 | 
						|
pred_option(M:Head, Option, Seen) :-
 | 
						|
	(   has_static_option_decl(M),
 | 
						|
	    M:'$pred_option'(Head, _, Option, Seen)
 | 
						|
	;   has_dynamic_option_decl(M),
 | 
						|
	    M:'$dyn_pred_option'(Head, _, Option, Seen)
 | 
						|
	).
 | 
						|
 | 
						|
has_static_option_decl(M) :-
 | 
						|
	'$c_current_predicate'(_, M:'$pred_option'(_,_,_,_)).
 | 
						|
has_dynamic_option_decl(M) :-
 | 
						|
	'$c_current_predicate'(_, M:'$dyn_pred_option'(_,_,_,_)).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *     TYPE&MODE CONSTRAINTS	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
:- public
 | 
						|
	system:predicate_option_mode/2,
 | 
						|
	system:predicate_option_type/2.
 | 
						|
 | 
						|
add_attr(Var, Value) :-
 | 
						|
	(   get_attr(Var, predicate_options, Old)
 | 
						|
	->  put_attr(Var, predicate_options, [Value|Old])
 | 
						|
	;   put_attr(Var, predicate_options, [Value])
 | 
						|
	).
 | 
						|
 | 
						|
system:predicate_option_type(Type, Arg) :-
 | 
						|
	var(Arg), !,
 | 
						|
	add_attr(Arg, option_type(Type)).
 | 
						|
system:predicate_option_type(Type, Arg) :-
 | 
						|
	must_be(Type, Arg).
 | 
						|
 | 
						|
system:predicate_option_mode(Mode, Arg) :-
 | 
						|
	var(Arg), !,
 | 
						|
	add_attr(Arg, option_mode(Mode)).
 | 
						|
system:predicate_option_mode(Mode, Arg) :-
 | 
						|
	check_mode(Mode, Arg).
 | 
						|
 | 
						|
check_mode(input, Arg) :-
 | 
						|
	(   nonvar(Arg)
 | 
						|
	->  true
 | 
						|
	;   instantiation_error(Arg)
 | 
						|
	).
 | 
						|
check_mode(output, Arg) :-
 | 
						|
	(   var(Arg)
 | 
						|
	->  true
 | 
						|
	;   instantiation_error(Arg)	% TBD: Uninstantiated
 | 
						|
	).
 | 
						|
 | 
						|
attr_unify_hook([], _).
 | 
						|
attr_unify_hook([H|T], Var) :-
 | 
						|
	option_hook(H, Var),
 | 
						|
	attr_unify_hook(T, Var).
 | 
						|
 | 
						|
option_hook(option_type(Type), Value) :-
 | 
						|
	is_of_type(Type, Value).
 | 
						|
option_hook(option_mode(Mode), Value) :-
 | 
						|
	check_mode(Mode, Value).
 | 
						|
 | 
						|
 | 
						|
attribute_goals(Var) -->
 | 
						|
	{ get_attr(Var, predicate_options, Attrs) },
 | 
						|
	option_goals(Attrs, Var).
 | 
						|
 | 
						|
option_goals([], _) --> [].
 | 
						|
option_goals([H|T], Var) -->
 | 
						|
	option_goal(H, Var),
 | 
						|
	option_goals(T, Var).
 | 
						|
 | 
						|
option_goal(option_type(Type), Var) --> [predicate_option_type(Type, Var)].
 | 
						|
option_goal(option_mode(Mode), Var) --> [predicate_option_mode(Mode, Var)].
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	OUTPUT DECLARATIONS	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
%%	current_predicate_options(:PI, ?Arg, ?Options) is nondet.
 | 
						|
%
 | 
						|
%	True when Options is the current   active option declaration for
 | 
						|
%	PI  on  Arg.   See   predicate_options/3    for   the   argument
 | 
						|
%	descriptions. If PI  is  ground  and   refers  to  an  undefined
 | 
						|
%	predicate, the autoloader is used to  obtain a definition of the
 | 
						|
%	predicate.
 | 
						|
 | 
						|
current_predicate_options(PI, Arg, Options) :-
 | 
						|
	define_predicate(PI),
 | 
						|
	setof(Arg-Option,
 | 
						|
	      current_predicate_option_decl(PI, Arg, Option),
 | 
						|
	      Options0),
 | 
						|
	group_pairs_by_key(Options0, Grouped),
 | 
						|
	member(Arg-Options, Grouped).
 | 
						|
 | 
						|
current_predicate_option_decl(PI, Arg, Option) :-
 | 
						|
	current_predicate_option(PI, Arg, Option0),
 | 
						|
	Option0 =.. [Name|Values],
 | 
						|
	maplist(mode_and_type, Values, Types),
 | 
						|
	Option =.. [Name|Types].
 | 
						|
 | 
						|
mode_and_type(Value, ModeAndType) :-
 | 
						|
	copy_term(Value,_,Goals),
 | 
						|
	(   memberchk(predicate_option_mode(output, _), Goals)
 | 
						|
	->  ModeAndType = -(Type)
 | 
						|
	;   ModeAndType = Type
 | 
						|
	),
 | 
						|
	(   memberchk(predicate_option_type(Type, _), Goals)
 | 
						|
	->  true
 | 
						|
	;   Type = any
 | 
						|
	).
 | 
						|
 | 
						|
define_predicate(PI) :-
 | 
						|
	ground(PI), !,
 | 
						|
	PI = M:Name/Arity,
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	once(predicate_property(M:Head, _)).
 | 
						|
define_predicate(_).
 | 
						|
 | 
						|
%%	derived_predicate_options(:PI, ?Arg, ?Options) is nondet.
 | 
						|
%
 | 
						|
%	True  when  Options  is  the  current  _derived_  active  option
 | 
						|
%	declaration for PI on Arg.
 | 
						|
 | 
						|
derived_predicate_options(PI, Arg, Options) :-
 | 
						|
	define_predicate(PI),
 | 
						|
	setof(Arg-Option,
 | 
						|
	      derived_predicate_option(PI, Arg, Option),
 | 
						|
	      Options0),
 | 
						|
	group_pairs_by_key(Options0, Grouped),
 | 
						|
	member(Arg-Options1, Grouped),
 | 
						|
	PI = M:_,
 | 
						|
	phrase(expand_pass_to_options(Options1, M), Options2),
 | 
						|
	sort(Options2, Options).
 | 
						|
 | 
						|
derived_predicate_option(PI, Arg, Decl) :-
 | 
						|
	current_option_arg(PI, Arg, DefM),
 | 
						|
	PI = _:Name/Arity,
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	has_dynamic_option_decl(DefM),
 | 
						|
	(   has_static_option_decl(DefM),
 | 
						|
	    DefM:'$pred_option'(Head, Decl, _, [])
 | 
						|
	;   DefM:'$dyn_pred_option'(Head, Decl, _, [])
 | 
						|
	).
 | 
						|
 | 
						|
%%	expand_pass_to_options(+OptionsIn, +Module, -OptionsOut)// is det.
 | 
						|
%
 | 
						|
%	Expand the options of pass_to(PI,Arg) if PI  does not refer to a
 | 
						|
%	public predicate.
 | 
						|
 | 
						|
expand_pass_to_options([], _) --> [].
 | 
						|
expand_pass_to_options([H|T], M) -->
 | 
						|
	expand_pass_to(H, M),
 | 
						|
	expand_pass_to_options(T, M).
 | 
						|
 | 
						|
expand_pass_to(pass_to(PI, Arg), Module) -->
 | 
						|
	{ strip_module(Module:PI, M, Name/Arity),
 | 
						|
	  functor(Head, Name, Arity),
 | 
						|
	  \+ (   predicate_property(M:Head, exported)
 | 
						|
	     ;   predicate_property(M:Head, public)
 | 
						|
	     ;   M == system
 | 
						|
	     ), !,
 | 
						|
	  current_predicate_options(M:Name/Arity, Arg, Options)
 | 
						|
	},
 | 
						|
	list(Options).
 | 
						|
expand_pass_to(Option, _) -->
 | 
						|
	[Option].
 | 
						|
 | 
						|
list([]) --> [].
 | 
						|
list([H|T]) --> [H], list(T).
 | 
						|
 | 
						|
%%	derived_predicate_options(+Module) is det.
 | 
						|
%
 | 
						|
%	Derive predicate option declarations for   the  given module and
 | 
						|
%	print them to the current output.
 | 
						|
 | 
						|
derived_predicate_options(Module) :-
 | 
						|
	var(Module), !,
 | 
						|
	forall(current_module(Module),
 | 
						|
	       derived_predicate_options(Module)).
 | 
						|
derived_predicate_options(Module) :-
 | 
						|
	findall(predicate_options(Module:PI, Arg, Options),
 | 
						|
		( derived_predicate_options(Module:PI, Arg, Options),
 | 
						|
		  PI = Name/Arity,
 | 
						|
		  functor(Head, Name, Arity),
 | 
						|
		  (   predicate_property(Module:Head, exported)
 | 
						|
		  ->  true
 | 
						|
		  ;   predicate_property(Module:Head, public)
 | 
						|
		  )
 | 
						|
		),
 | 
						|
		Decls0),
 | 
						|
	maplist(qualify_decl(Module), Decls0, Decls1),
 | 
						|
	sort(Decls1, Decls),
 | 
						|
	(   Decls \== []
 | 
						|
	->  format('~N~n~n% Predicate option declarations for module ~q~n~n',
 | 
						|
		   [Module]),
 | 
						|
	    forall(member(Decl, Decls),
 | 
						|
		   portray_clause((:-Decl)))
 | 
						|
	;   true
 | 
						|
	).
 | 
						|
 | 
						|
qualify_decl(M,
 | 
						|
	     predicate_options(PI0, Arg, Options0),
 | 
						|
	     predicate_options(PI1, Arg, Options1)) :-
 | 
						|
	qualify(PI0, M, PI1),
 | 
						|
	maplist(qualify_option(M), Options0, Options1).
 | 
						|
 | 
						|
qualify_option(M, pass_to(PI0, Arg), pass_to(PI1, Arg)) :- !,
 | 
						|
	qualify(PI0, M, PI1).
 | 
						|
qualify_option(_, Opt, Opt).
 | 
						|
 | 
						|
qualify(M:Term, M, Term) :- !.
 | 
						|
qualify(QTerm, _, QTerm).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      CLEANUP		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
%%	retractall_predicate_options is det.
 | 
						|
%
 | 
						|
%	Remove all dynamically (derived) predicate options.
 | 
						|
 | 
						|
retractall_predicate_options :-
 | 
						|
	forall(retract(dyn_option_decl(_,M,_)),
 | 
						|
	       abolish(M:'$dyn_pred_option'/4)).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *     COMPILE-TIME CHECKER	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
 | 
						|
:- thread_local
 | 
						|
	new_decl/1.
 | 
						|
 | 
						|
%%	check_predicate_options is det.
 | 
						|
%
 | 
						|
%	Analyse loaded program for  errornous   options.  This predicate
 | 
						|
%	decompiles  the  current  program  and  searches  for  calls  to
 | 
						|
%	predicates that process  options.  For   each  option  list,  it
 | 
						|
%	validates  whether  the  provided  options   are  supported  and
 | 
						|
%	validates the argument type.  This   predicate  performs partial
 | 
						|
%	dataflow analysis to track option-lists inside a clause.
 | 
						|
%
 | 
						|
%	@see	derive_predicate_options/0 can be used to derive
 | 
						|
%		declarations for predicates that pass options. This
 | 
						|
%		predicate should normally be called before
 | 
						|
%		check_predicate_options/0.
 | 
						|
 | 
						|
check_predicate_options :-
 | 
						|
	forall(current_module(Module),
 | 
						|
	       check_predicate_options_module(Module)).
 | 
						|
 | 
						|
%%	derive_predicate_options is det.
 | 
						|
%
 | 
						|
%	Derive  new  predicate  option    declarations.  This  predicate
 | 
						|
%	analyses the loaded program to find clauses that process options
 | 
						|
%	using one of  the  predicates   from  library(option)  or passes
 | 
						|
%	options to other predicates that are   known to process options.
 | 
						|
%	The process is repeated until no new declarations are retrieved.
 | 
						|
%
 | 
						|
%	@see autoload/0 may be used to complete the loaded program.
 | 
						|
 | 
						|
derive_predicate_options :-
 | 
						|
	derive_predicate_options(NewDecls),
 | 
						|
	(   NewDecls == []
 | 
						|
	->  true
 | 
						|
	;   print_message(informational, check_options(new(NewDecls))),
 | 
						|
	    new_decls(NewDecls),
 | 
						|
	    derive_predicate_options
 | 
						|
	).
 | 
						|
 | 
						|
new_decls([]).
 | 
						|
new_decls([predicate_options(PI, A, O)|T]) :-
 | 
						|
	assert_predicate_options(PI, A, O, _),
 | 
						|
	new_decls(T).
 | 
						|
 | 
						|
 | 
						|
derive_predicate_options(NewDecls) :-
 | 
						|
	call_cleanup(
 | 
						|
	    ( forall(
 | 
						|
		  current_module(Module),
 | 
						|
		  forall(
 | 
						|
		      ( predicate_in_module(Module, PI),
 | 
						|
			PI = Name/Arity,
 | 
						|
			functor(Head, Name, Arity),
 | 
						|
			catch(Module:clause(Head, Body, Ref), _, fail)
 | 
						|
		      ),
 | 
						|
		      check_clause((Head:-Body), Module, Ref, decl))),
 | 
						|
	      (	  setof(Decl, retract(new_decl(Decl)), NewDecls)
 | 
						|
	      ->  true
 | 
						|
	      ;	  NewDecls = []
 | 
						|
	      )
 | 
						|
	    ),
 | 
						|
	    retractall(new_decl(_))).
 | 
						|
 | 
						|
 | 
						|
check_predicate_options_module(Module) :-
 | 
						|
	forall(predicate_in_module(Module, PI),
 | 
						|
	       check_predicate_options(Module:PI)).
 | 
						|
 | 
						|
predicate_in_module(Module, PI) :-
 | 
						|
	current_predicate(Module:PI),
 | 
						|
	PI = Name/Arity,
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	\+ predicate_property(Module:Head, imported_from(_)).
 | 
						|
 | 
						|
%%	check_predicate_options(:PredicateIndicator) is det.
 | 
						|
%
 | 
						|
%	Verify calls to predicates that have   options in all clauses of
 | 
						|
%	the predicate indicated by PredicateIndicator.
 | 
						|
 | 
						|
check_predicate_options(Module:Name/Arity) :-
 | 
						|
	debug(predicate_options, 'Checking ~q', [Module:Name/Arity]),
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	forall(catch(Module:clause(Head, Body, Ref), _, fail),
 | 
						|
	       check_clause((Head:-Body), Module, Ref, check)).
 | 
						|
 | 
						|
%%	check_clause(+Clause, +Module, +Ref, +Action) is det.
 | 
						|
%
 | 
						|
%	Action is one of
 | 
						|
%
 | 
						|
%	  * decl
 | 
						|
%	  Create additional declarations
 | 
						|
%	  * check
 | 
						|
%	  Produce error messages
 | 
						|
 | 
						|
check_clause((Head:-Body), M, ClauseRef, Action) :- !,
 | 
						|
	catch(check_body(Body, M, _, Action), E, true),
 | 
						|
	(   var(E)
 | 
						|
	->  option_decl(M:Head, Action)
 | 
						|
	;   (   clause_info(ClauseRef, File, TermPos, _NameOffset),
 | 
						|
	        TermPos = term_position(_,_,_,_,[_,BodyPos]),
 | 
						|
	        catch(check_body(Body, M, BodyPos, Action),
 | 
						|
		      error(Formal, ArgPos), true),
 | 
						|
		compound(ArgPos),
 | 
						|
		arg(1, ArgPos, CharCount),
 | 
						|
		integer(CharCount)
 | 
						|
	    ->	Location = file_char_count(File, CharCount)
 | 
						|
	    ;   Location = clause(ClauseRef),
 | 
						|
		E = error(Formal, _)
 | 
						|
	    ),
 | 
						|
	    print_message(error, predicate_option_error(Formal, Location))
 | 
						|
	).
 | 
						|
 | 
						|
 | 
						|
%%	check_body(+Body, +Module, +TermPos, +Action)
 | 
						|
 | 
						|
check_body(Var, _, _, _) :-
 | 
						|
	var(Var), !.
 | 
						|
check_body(M:G, _, term_position(_,_,_,_,[_,Pos]), Action) :- !,
 | 
						|
	check_body(G, M, Pos, Action).
 | 
						|
check_body((A,B), M, term_position(_,_,_,_,[PA,PB]), Action) :- !,
 | 
						|
	check_body(A, M, PA, Action),
 | 
						|
	check_body(B, M, PB, Action).
 | 
						|
check_body(A=B, _, _, _) :-		% partial evaluation
 | 
						|
	unify_with_occurs_check(A,B), !.
 | 
						|
check_body(Goal, M, term_position(_,_,_,_,ArgPosList), Action) :-
 | 
						|
	callable(Goal),
 | 
						|
	functor(Goal, Name, Arity),
 | 
						|
	(   '$get_predicate_attribute'(M:Goal, imported, DefM)
 | 
						|
	->  true
 | 
						|
	;   DefM = M
 | 
						|
	),
 | 
						|
	(   eval_option_pred(DefM:Goal)
 | 
						|
	->  true
 | 
						|
	;   current_option_arg(DefM:Name/Arity, OptArg), !,
 | 
						|
	    arg(OptArg, Goal, Options),
 | 
						|
	    nth1(OptArg, ArgPosList, ArgPos),
 | 
						|
	    check_options(DefM:Name/Arity, OptArg, Options, ArgPos, Action)
 | 
						|
	).
 | 
						|
check_body(Goal, M, _, Action) :-
 | 
						|
	prolog:called_by(Goal, Called), !,
 | 
						|
	check_called_by(Called, M, Action).
 | 
						|
check_body(Meta, M, term_position(_,_,_,_,ArgPosList), Action) :-
 | 
						|
	'$get_predicate_attribute'(M:Meta, meta_predicate, Head), !,
 | 
						|
	check_meta_args(1, Head, Meta, M, ArgPosList, Action).
 | 
						|
check_body(_, _, _, _).
 | 
						|
 | 
						|
check_meta_args(I, Head, Meta, M, [ArgPos|ArgPosList], Action) :-
 | 
						|
	arg(I, Head, AS), !,
 | 
						|
	(   AS == 0
 | 
						|
	->  arg(I, Meta, MA),
 | 
						|
	    check_body(MA, M, ArgPos, Action)
 | 
						|
	;   true
 | 
						|
	),
 | 
						|
	succ(I, I2),
 | 
						|
	check_meta_args(I2, Head, Meta, M, ArgPosList, Action).
 | 
						|
check_meta_args(_,_,_,_, _, _).
 | 
						|
 | 
						|
%%	check_called_by(+CalledBy, +M, +Action) is det.
 | 
						|
%
 | 
						|
%	Handle results from prolog:called_by/2.
 | 
						|
 | 
						|
check_called_by([], _, _).
 | 
						|
check_called_by([H|T], M, Action) :-
 | 
						|
	(   H = G+N
 | 
						|
	->  (   extend(G, N, G2)
 | 
						|
	    ->	check_body(G2, M, _, Action)
 | 
						|
	    ;	true
 | 
						|
	    )
 | 
						|
	;   check_body(H, M, _, Action)
 | 
						|
	),
 | 
						|
	check_called_by(T, M, Action).
 | 
						|
 | 
						|
extend(Goal, N, GoalEx) :-
 | 
						|
	callable(Goal),
 | 
						|
	Goal =.. List,
 | 
						|
	length(Extra, N),
 | 
						|
	append(List, Extra, ListEx),
 | 
						|
	GoalEx =.. ListEx.
 | 
						|
 | 
						|
 | 
						|
%%	check_options(:Predicate, +OptionArg, +Options, +ArgPos, +Action)
 | 
						|
%
 | 
						|
%	Verify the list Options,  that  is   passed  into  Predicate  on
 | 
						|
%	argument OptionArg. ArgPos is a   term-position  term describing
 | 
						|
%	the location of the Options list. If  Options is a partial list,
 | 
						|
%	the tail is annotated with pass_to(PI, OptArg).
 | 
						|
 | 
						|
check_options(PI, OptArg, QOptions, ArgPos, Action) :-
 | 
						|
	debug(predicate_options, '\tChecking call to ~q', [PI]),
 | 
						|
	remove_qualifier(QOptions, Options),
 | 
						|
	must_be(list_or_partial_list, Options),
 | 
						|
	check_option_list(Options, PI, OptArg, Options, ArgPos, Action).
 | 
						|
 | 
						|
remove_qualifier(X, X) :-
 | 
						|
	var(X), !.
 | 
						|
remove_qualifier(_:X, X) :- !.
 | 
						|
remove_qualifier(X, X).
 | 
						|
 | 
						|
check_option_list(Var,  PI, OptArg, _, _, _) :-
 | 
						|
	var(Var), !,
 | 
						|
	annotate(Var, pass_to(PI, OptArg)).
 | 
						|
check_option_list([], _, _, _, _, _).
 | 
						|
check_option_list([H|T], PI, OptArg, Options, ArgPos, Action) :-
 | 
						|
	check_option(PI, OptArg, H, ArgPos, Action),
 | 
						|
	check_option_list(T, PI, OptArg, Options, ArgPos, Action).
 | 
						|
 | 
						|
check_option(_, _, _, _, decl) :- !.
 | 
						|
check_option(PI, OptArg, Opt, ArgPos, _) :-
 | 
						|
	catch(check_predicate_option(PI, OptArg, Opt), E, true), !,
 | 
						|
	(   var(E)
 | 
						|
	->  true
 | 
						|
	;   E = error(Formal,_),
 | 
						|
	    throw(error(Formal,ArgPos))
 | 
						|
	).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	    ANNOTATIONS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
%%	annotate(+Var, +Term) is det.
 | 
						|
%
 | 
						|
%	Use constraints to accumulate annotations   about  variables. If
 | 
						|
%	two annotated variables are unified, the attributes are joined.
 | 
						|
 | 
						|
annotate(Var, Term) :-
 | 
						|
	(   get_attr(Var, predopts_analysis, Old)
 | 
						|
	->  put_attr(Var, predopts_analysis, [Term|Old])
 | 
						|
	;   var(Var)
 | 
						|
	->  put_attr(Var, predopts_analysis, [Term])
 | 
						|
	;   true
 | 
						|
	).
 | 
						|
 | 
						|
annotations(Var, Annotations) :-
 | 
						|
	get_attr(Var, predopts_analysis, Annotations).
 | 
						|
 | 
						|
predopts_analysis:attr_unify_hook(Opts, Value) :-
 | 
						|
	get_attr(Value, predopts_analysis, Others), !,
 | 
						|
	append(Opts, Others, All),
 | 
						|
	put_attr(Value, predopts_analysis, All).
 | 
						|
predopts_analysis:attr_unify_hook(_, _).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	   PARTIAL EVAL		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
eval_option_pred(swi_option:option(Opt, Options)) :-
 | 
						|
	processes(Opt, Spec),
 | 
						|
	annotate(Options, Spec).
 | 
						|
eval_option_pred(swi_option:option(Opt, Options, _Default)) :-
 | 
						|
	processes(Opt, Spec),
 | 
						|
	annotate(Options, Spec).
 | 
						|
eval_option_pred(swi_option:select_option(Opt, Options, Rest)) :-
 | 
						|
	ignore(unify_with_occurs_check(Rest, Options)),
 | 
						|
	processes(Opt, Spec),
 | 
						|
	annotate(Options, Spec).
 | 
						|
eval_option_pred(swi_option:select_option(Opt, Options, Rest, _Default)) :-
 | 
						|
	ignore(unify_with_occurs_check(Rest, Options)),
 | 
						|
	processes(Opt, Spec),
 | 
						|
	annotate(Options, Spec).
 | 
						|
eval_option_pred(swi_option:meta_options(_Cond, QOptionsIn, QOptionsOut)) :-
 | 
						|
	remove_qualifier(QOptionsIn, OptionsIn),
 | 
						|
	remove_qualifier(QOptionsOut, OptionsOut),
 | 
						|
	ignore(unify_with_occurs_check(OptionsIn, OptionsOut)).
 | 
						|
 | 
						|
processes(Opt, Spec) :-
 | 
						|
	compound(Opt),
 | 
						|
	functor(Opt, OptName, 1),
 | 
						|
	Spec =.. [OptName,any].
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	  NEW DECLARTIONS	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
%%	option_decl(:Head, +Action) is det.
 | 
						|
%
 | 
						|
%	Add new declarations based on attributes   left  by the analysis
 | 
						|
%	pass. We do not add declarations   for system modules or modules
 | 
						|
%	that already contain static declarations.
 | 
						|
%
 | 
						|
%	@tbd	Should we add a mode to include generating declarations
 | 
						|
%		for system modules and modules with static declarations?
 | 
						|
 | 
						|
option_decl(_, check) :- !.
 | 
						|
option_decl(M:_, _) :-
 | 
						|
	system_module(M), !.
 | 
						|
option_decl(M:_, _) :-
 | 
						|
	has_static_option_decl(M), !.
 | 
						|
option_decl(M:Head, _) :-
 | 
						|
	arg(AP, Head, QA),
 | 
						|
	remove_qualifier(QA, A),
 | 
						|
	annotations(A, Annotations0),
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	PI = M:Name/Arity,
 | 
						|
	delete(Annotations0, pass_to(PI,AP), Annotations),
 | 
						|
	Annotations \== [],
 | 
						|
	Decl = predicate_options(PI, AP, Annotations),
 | 
						|
	(   new_decl(Decl)
 | 
						|
	->  true
 | 
						|
	;   assert_predicate_options(M:Name/Arity, AP, Annotations, false)
 | 
						|
	->  true
 | 
						|
	;   assertz(new_decl(Decl)),
 | 
						|
	    debug(predicate_options(decl), '~q', [Decl])
 | 
						|
	),
 | 
						|
	fail.
 | 
						|
option_decl(_, _).
 | 
						|
 | 
						|
system_module(system) :- !.
 | 
						|
system_module(Module) :-
 | 
						|
	sub_atom(Module, 0, _, _, $).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       MISC		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
canonical_pi(M:Name//Arity, M:Name/PArity) :-
 | 
						|
	integer(Arity),
 | 
						|
	PArity is Arity+2.
 | 
						|
canonical_pi(PI, PI).
 | 
						|
 | 
						|
%%	resolve_module(:PI, -DefPI) is det.
 | 
						|
%
 | 
						|
%	Find the real predicate  indicator   pointing  to the definition
 | 
						|
%	module of PI. This is similar to using predicate_property/3 with
 | 
						|
%	the       property       imported_from,         but        using
 | 
						|
%	'$get_predicate_attribute'/3    avoids    auto-importing     the
 | 
						|
%	predicate.
 | 
						|
 | 
						|
resolve_module(Module:Name/Arity, DefM:Name/Arity) :-
 | 
						|
	functor(Head, Name, Arity),
 | 
						|
	(   '$get_predicate_attribute'(Module:Head, imported, M)
 | 
						|
	->  DefM = M
 | 
						|
	;   DefM = Module
 | 
						|
	).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      MESSAGES		*
 | 
						|
		 *******************************/
 | 
						|
:- multifile
 | 
						|
	prolog:message//1.
 | 
						|
 | 
						|
prolog:message(predicate_option_error(Formal, Location)) -->
 | 
						|
	error_location(Location),
 | 
						|
	'$messages':term_message(Formal). % TBD: clean interface
 | 
						|
prolog:message(check_options(new(Decls))) -->
 | 
						|
	[ 'Inferred declarations:'-[], nl ],
 | 
						|
	new_decls(Decls).
 | 
						|
 | 
						|
error_location(file_char_count(File, CharPos)) -->
 | 
						|
	{ filepos_line(File, CharPos, Line, LinePos) },
 | 
						|
	[ '~w:~d:~d: '-[File, Line, LinePos] ].
 | 
						|
error_location(clause(ClauseRef)) -->
 | 
						|
	{ clause_property(ClauseRef, file(File)),
 | 
						|
	  clause_property(ClauseRef, line_count(Line))
 | 
						|
	}, !,
 | 
						|
	[ '~w:~d: '-[File, Line] ].
 | 
						|
error_location(clause(ClauseRef)) -->
 | 
						|
	[ 'Clause ~q: '-[ClauseRef] ].
 | 
						|
 | 
						|
filepos_line(File, CharPos, Line, LinePos) :-
 | 
						|
	setup_call_cleanup(
 | 
						|
	    ( open(File, read, In),
 | 
						|
	      open_null_stream(Out)
 | 
						|
	    ),
 | 
						|
	    ( Skip is CharPos-1,
 | 
						|
	      copy_stream_data(In, Out, Skip),
 | 
						|
	      stream_property(In, position(Pos)),
 | 
						|
	      stream_position_data(line_count, Pos, Line),
 | 
						|
	      stream_position_data(line_position, Pos, LinePos)
 | 
						|
	    ),
 | 
						|
	    ( close(Out),
 | 
						|
	      close(In)
 | 
						|
	    )).
 | 
						|
 | 
						|
new_decls([]) --> [].
 | 
						|
new_decls([H|T]) -->
 | 
						|
	[ '    :- ~q'-[H], nl ],
 | 
						|
	new_decls(T).
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	SYSTEM DECLARATIONS	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
:- use_module(library(dialect/swi/syspred_options)).
 |