View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2003-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(swi_option,
   38          [ option/2,                   % +Term, +List
   39            option/3,                   % +Term, +List, +Default
   40            select_option/3,            % +Term, +Options, -RestOpts
   41            select_option/4,            % +Term, +Options, -RestOpts, +Default
   42            merge_options/3,            % +New, +Old, -Merged
   43            meta_options/3,             % :IsMeta, :OptionsIn, -OptionsOut
   44            dict_options/2              % ?Dict, ?Options
   45          ]).   46:- autoload(library(lists), [selectchk/3]).   47:- autoload(library(error), [must_be/2]).   48:- set_prolog_flag(generate_debug_info, false).   49
   50:- meta_predicate
   51    meta_options(1, :, -).

Option list processing

The library(option) provides some utilities for processing option lists. Option lists are commonly used as an alternative for many arguments. Examples of built-in predicates are open/4 and write_term/3. Naming the arguments results in more readable code, and the list nature makes it easy to extend the list of options accepted by a predicate. Option lists come in two styles, both of which are handled by this library.

Name(Value)
This is the preferred style.
Name = Value
This is often used, but deprecated.

Processing options inside time-critical code (loops) can cause serious overhead. One possibility is to define a record using library(record) and initialise this using make_<record>/2. In addition to providing good performance, this also provides type-checking and central declaration of defaults.

:- record atts(width:integer=100, shape:oneof([box,circle])=box).

process(Data, Options) :-
        make_atts(Options, Attributes),
        action(Data, Attributes).

action(Data, Attributes) :-
        atts_shape(Attributes, Shape),
        ...

Options typically have exactly one argument. The library does support options with 0 or more than one argument with the following restrictions:

See also
- library(record)
- Option processing capabilities may be declared using the directive predicate_options/3. */
To be done
- We should consider putting many options in an assoc or record with appropriate preprocessing to achieve better performance.
 option(?Option, +OptionList, +Default) is semidet
Get an Option from OptionList. OptionList can use the Name=Value as well as the Name(Value) convention.
Arguments:
Option- Term of the form Name(?Value).
  110option(Opt, Options, Default) :-
  111    is_dict(Options),
  112    !,
  113    functor(Opt, Name, 1),
  114    (   get_dict(Name, Options, Val)
  115    ->  true
  116    ;   Val = Default
  117    ),
  118    arg(1, Opt, Val).
  119option(Opt, Options, Default) :-        % make option processing stead-fast
  120    functor(Opt, Name, Arity),
  121    functor(GenOpt, Name, Arity),
  122    (   get_option(GenOpt, Options)
  123    ->  Opt = GenOpt
  124    ;   arg(1, Opt, Default)
  125    ).
 option(?Option, +OptionList) is semidet
Get an Option from OptionList. OptionList can use the Name=Value as well as the Name(Value) convention. Fails silently if the option does not appear in OptionList.
Arguments:
Option- Term of the form Name(?Value).
  136option(Opt, Options) :-                 % make option processing stead-fast
  137    is_dict(Options),
  138    !,
  139    functor(Opt, Name, 1),
  140    get_dict(Name, Options, Val),
  141    arg(1, Opt, Val).
  142option(Opt, Options) :-                 % make option processing stead-fast
  143    functor(Opt, Name, Arity),
  144    functor(GenOpt, Name, Arity),
  145    get_option(GenOpt, Options),
  146    !,
  147    Opt = GenOpt.
  148
  149get_option(Opt, Options) :-
  150    memberchk(Opt, Options),
  151    !.
  152get_option(Opt, Options) :-
  153    functor(Opt, OptName, 1),
  154    arg(1, Opt, OptVal),
  155    memberchk(OptName=OptVal, Options),
  156    !.
 select_option(?Option, +Options, -RestOptions) is semidet
Get and remove Option from an option list. As option/2, removing the matching option from Options and unifying the remaining options with RestOptions.
  165select_option(Opt, Options0, Options) :-
  166    is_dict(Options0),
  167    !,
  168    functor(Opt, Name, 1),
  169    get_dict(Name, Options0, Val),
  170    arg(1, Opt, Val),
  171    del_dict(Name, Options0, Val, Options).
  172select_option(Opt, Options0, Options) :-        % stead-fast
  173    functor(Opt, Name, Arity),
  174    functor(GenOpt, Name, Arity),
  175    get_option(GenOpt, Options0, Options),
  176    Opt = GenOpt.
  177
  178get_option(Opt, Options0, Options) :-
  179    selectchk(Opt, Options0, Options),
  180    !.
  181get_option(Opt, Options0, Options) :-
  182    functor(Opt, OptName, 1),
  183    arg(1, Opt, OptVal),
  184    selectchk(OptName=OptVal, Options0, Options).
 select_option(?Option, +Options, -RestOptions, +Default) is det
Get and remove Option with default value. As select_option/3, but if Option is not in Options, its value is unified with Default and RestOptions with Options.
  192select_option(Option, Options, RestOptions, Default) :-
  193    is_dict(Options),
  194    !,
  195    functor(Option, Name, 1),
  196    (   del_dict(Name, Options, Val, RestOptions)
  197    ->  true
  198    ;   Val = Default,
  199        RestOptions = Options
  200    ),
  201    arg(1, Option, Val).
  202select_option(Option, Options, RestOptions, Default) :-
  203    functor(Option, Name, Arity),
  204    functor(GenOpt, Name, Arity),
  205    (   get_option(GenOpt, Options, RestOptions)
  206    ->  Option = GenOpt
  207    ;   RestOptions = Options,
  208        arg(1, Option, Default)
  209    ).
 merge_options(+New, +Old, -Merged) is det
Merge two option lists. Merged is a sorted list of options using the canonical format Name(Value) holding all options from New and Old, after removing conflicting options from Old.

Multi-values options (e.g., proxy(Host, Port)) are allowed, where both option-name and arity define the identity of the option.

  222merge_options([], Old, Merged) :-
  223    !,
  224    canonicalise_options(Old, Merged).
  225merge_options(New, [], Merged) :-
  226    !,
  227    canonicalise_options(New, Merged).
  228merge_options(New, Old, Merged) :-
  229    canonicalise_options(New, NCanonical),
  230    canonicalise_options(Old, OCanonical),
  231    sort(NCanonical, NSorted),
  232    sort(OCanonical, OSorted),
  233    ord_merge(NSorted, OSorted, Merged).
  234
  235ord_merge([], L, L) :- !.
  236ord_merge(L, [], L) :- !.
  237ord_merge([NO|TN], [OO|TO], Merged) :-
  238    sort_key(NO, NKey),
  239    sort_key(OO, OKey),
  240    compare(Diff, NKey, OKey),
  241    ord_merge(Diff, NO, NKey, OO, OKey, TN, TO, Merged).
  242
  243ord_merge(=, NO, _, _, _, TN, TO, [NO|T]) :-
  244    ord_merge(TN, TO, T).
  245ord_merge(<, NO, _, OO, OKey, TN, TO, [NO|T]) :-
  246    (   TN = [H|TN2]
  247    ->  sort_key(H, NKey),
  248        compare(Diff, NKey, OKey),
  249        ord_merge(Diff, H, NKey, OO, OKey, TN2, TO, T)
  250    ;   T = [OO|TO]
  251    ).
  252ord_merge(>, NO, NKey, OO, _, TN, TO, [OO|T]) :-
  253    (   TO = [H|TO2]
  254    ->  sort_key(H, OKey),
  255        compare(Diff, NKey, OKey),
  256        ord_merge(Diff, NO, NKey, H, OKey, TN, TO2, T)
  257    ;   T = [NO|TN]
  258    ).
  259
  260sort_key(Option, Name-Arity) :-
  261    functor(Option, Name, Arity).
 canonicalise_options(+OptionsIn, -OptionsOut) is det
Rewrite option list from possible Name=Value to Name(Value)
  267canonicalise_options(Dict, Out) :-
  268    is_dict(Dict),
  269    !,
  270    dict_pairs(Dict, _, Pairs),
  271    canonicalise_options2(Pairs, Out).
  272canonicalise_options(In, Out) :-
  273    memberchk(_=_, In),            % speedup a bit if already ok.
  274    !,
  275    canonicalise_options2(In, Out).
  276canonicalise_options(Options, Options).
  277
  278canonicalise_options2([], []).
  279canonicalise_options2([H0|T0], [H|T]) :-
  280    canonicalise_option(H0, H),
  281    canonicalise_options2(T0, T).
  282
  283canonicalise_option(Name=Value, H) :-
  284    !,
  285    H =.. [Name,Value].
  286canonicalise_option(Name-Value, H) :-
  287    !,
  288    H =.. [Name,Value].
  289canonicalise_option(H, H).
 meta_options(+IsMeta, :Options0, -Options) is det
Perform meta-expansion on options that are module-sensitive. Whether an option name is module-sensitive is determined by calling call(IsMeta, Name). Here is an example:
        meta_options(is_meta, OptionsIn, Options),
        ...

is_meta(callback).

Meta-options must have exactly one argument. This argument will be qualified.

To be done
- Should be integrated with declarations from predicate_options/3.
  311meta_options(IsMeta, Context:Options0, Options) :-
  312    is_dict(Options0),
  313    !,
  314    dict_pairs(Options0, Class, Pairs0),
  315    meta_options(Pairs0, IsMeta, Context, Pairs),
  316    dict_pairs(Options, Class, Pairs).
  317meta_options(IsMeta, Context:Options0, Options) :-
  318    must_be(list, Options0),
  319    meta_options(Options0, IsMeta, Context, Options).
  320
  321meta_options([], _, _, []).
  322meta_options([H0|T0], IM, Context, [H|T]) :-
  323    meta_option(H0, IM, Context, H),
  324    meta_options(T0, IM, Context, T).
  325
  326meta_option(Name=V0, IM, Context, Name=(M:V)) :-
  327    call(IM, Name),
  328    !,
  329    strip_module(Context:V0, M, V).
  330meta_option(Name-V0, IM, Context, Name-(M:V)) :-
  331    call(IM, Name),
  332    !,
  333    strip_module(Context:V0, M, V).
  334meta_option(O0, IM, Context, O) :-
  335    compound(O0),
  336    O0 =.. [Name,V0],
  337    call(IM, Name),
  338    !,
  339    strip_module(Context:V0, M, V),
  340    O =.. [Name,M:V].
  341meta_option(O, _, _, O).
 dict_options(?Dict, ?Options) is det
Convert between an option list and a dictionary. One of the arguments must be instantiated. If the option list is created, it is created in canonical form, i.e., using Option(Value) with the Options sorted in the standard order of terms. Note that the conversion is not always possible due to different constraints and conversion may thus lead to (type) errors.

Also note that most system predicates and predicates using this library for processing the option argument can both work with classical Prolog options and dicts objects.

  363dict_options(Dict, Options) :-
  364    nonvar(Dict),
  365    !,
  366    dict_pairs(Dict, _, Pairs),
  367    canonicalise_options2(Pairs, Options).
  368dict_options(Dict, Options) :-
  369    dict_create(Dict, _, Options)