1:- module( pack_errors, [
    2                pack_errors/0,              % 
    3                caught/3,                   % +Goal, +Error, +Opts 
    4                ground/2, ground_binary/2,  % +Term, -Groundness
    5                defined/2, defined/3,       % +Pid,  +From[, +Opts]
    6                throw/2,                    % +Error,+Opts
    7                type/2,                     % +Type, +Term
    8                type/3,                     % +Type, +Term, +Opts
    9                of_same_length/1,           % +Lists
   10                of_same_length/2,           % +List1, +List2; +Lists, +Opts
   11                of_same_length/3,           % +List1, +List2, +Opts
   12                pack_errors_version/2       % +Version, +Date
   13                        ] ).   14
   15:- use_module(library(lists)).            % append/3,...
   16:- use_module(library(error)).            % is_of_type/2.
   17:- use_module(library(debug)).            % debug/3.

Contextual error handling for packs

This is a stoics.infrastructure pack that

  1. implements the mid layer for handling Prolog errors
  2. provides a simple, uniform way for displaying originating pack/module and predicate
  3. includes useful pre-canned errors
  4. incorporates error related predicates
  5. decouples the type of printing from the execution behaviour and controls both aspects through simple options

Version 0.3 introduced type errors via type/3 on top of must_be/2.
Version 2.0 has been re-written to be Options centric, fully decoupled and introduced of_same_length/3.

The pack manage mid-level error handling in a uniform way so other packs can use SWI's infracture in a simple way. The user only needs to define the print messages (if the pre-canned ones are not suitable) and then throw the appropriate terms during execution.

Two simple ways to identifying originating caller are provided by allowing options in either the message, or via using an new version of throw, throw/2.

In addition the library includes a number or pre-canned messages and has evolved to provide some error related predicates.

Throwing pack errors

Any term recognised as the first argument of the defined message/3 can be made to spit
a token identifying the originating pack/module and predicate. The main intuition is that this is the
the predicate responsible for the error. You can do this by either wrapping the message or by using
pack_error's own version of throw, throw/2.

Wrapping is via pack_error/2 where the first argument is the message and second is a list of options.

?- throw( pack_error(lengths_mismatch(a,b,1,2),[]) ).
ERROR: Lists for a and b have mismatching lengths: 1 and 2 respectively

?- throw( pack_error(lengths_mismatch(a,b,1,2),[foo:bar/1]) ).
ERROR: foo:bar/1: Lists for a and b have mismatching lengths: 1 and 2 respectively

You can also use throw/2, which is defined in the pack, without wrapping the Message,

?- throw( lengths_mismatch(a,b,1,2), [foo:bar/1] ).
ERROR: foo:bar/1: Lists for a and b have mismatching lengths: 1 and 2 respectively

In both cases, you can drop the list if it contains a single element, thus

?- throw( lengths_mismatch(a,b,1,2), foo:bar/1 ).
ERROR: foo:bar/1: Lists for a and b have mismatching lengths: 1 and 2 respectively

Note that in the latter case (throw/2) the options can also contain terms controling the execution of throw/2.

Options in both cases provide the context:

Pname / Arity
predicate for decoration
Mod:Pname/Arity
prefixed predicate and decoration
pack(Pack)
pack of the originating predicate
pred(Pname/Arity)
alternative have for identifying the predicate

The library is loosely designed around the principle that most packs will define a homonym module. If both Pack and Mod are given and are the same only one is printed, however if they differ, they will both be shown. The order of identification is that of going throough the list above from top to bottom. The first one matching will identify the predicate and stop looking, so alternatives will be ignored.

Prepacked errors

Argument errors.
Poss is a list of (argument) positions and Args a list of arguments.

Printing of Arg(s) itself can be surpressed with prolog_flag(pack_errors_arg,false)- useful for long data.

Other errors

Examples

?- throw( pack_error(arg_ground(3,name), os:os_ext/3) ).
ERROR: os:os_ext/3: Ground argument expected at position: 3,  (found: name)

?- throw( pack_error(arg_ground(3,name(_)), os:os_ext/3) ).
ERROR: os:os_ext/3: Ground argument expected at position: 3,  (found: name(_4210))

?- set_prolog_flag(pack_errors_arg,true).   % this is the default, so no change in behaviour:

?- throw( pack_error(arg_ground(3,name(_)), os:os_ext/3) ).
ERROR: os:os_ext/3: Ground argument expected at position: 3,  (found: name(_4210))

?- set_prolog_flag(pack_errors_arg,false).

?- throw( pack_error(arg_ground(3,name(_)), os:os_ext/3) ).
ERROR: os:os_ext/3: Ground argument expected at position: 3

?- set_prolog_flag(pack_errors_arg,true).

?- throw( pack_error(arg_enumerate(3,[a,b,c],d), [pack(os),pred(os_pred/3)]) ).
ERROR: os:os_pred/3: Term at position: 3, is not one of: [a,b,c], (found: d)

% use throw/2 as it makes code clearer:
?- throw( arg_enumerate(3,[a,b,c],d), os:os_pred/3 ).
ERROR: os:os_pred/3: Term at position: 3, is not one of: [a,b,c], (found: d)

?- throw( arg_enumerate(3,[a,b,c],d), [pack(os),os_pred/3] ).
ERROR: os:os_pred/3: Term at position: 3, is not one of: [a,b,c], (found: d)

?- throw( lengths_mismatch(a,b,1,2), [pack(foo)] ).
ERROR: foo:_Unk: Lists for a and b have mismatching lengths: 1 and 2 respectively

?- throw( lengths_mismatch(a,b,1,2), pred(bar/1) ).
ERROR: _Unk:bar/1: Lists for a and b have mismatching lengths: 1 and 2 respectively

?- throw( cast(abc('file.csv'),atom), os:os_term/2 ).
ERROR: os:os_term/2: Cannot cast: abc(file.csv), to type: atom

Examples from other packs:

?- map_list_options( plus_one, In, [2,3,4,5], [add_options(maybe),on_fail(skip)] ).
ERROR: false:map_list_options/4 @ option(add_options): Object of type: boolean, expected but found term: maybe

Defining new pack errors

Example file (available at pack('pack_errors/examples/fold_data_errors.pl')):

:- multifile( pack_errors:message/3 ).

pack_errors:message( fold_data_insufficient(Dlen,N) ) -->
    ['Insufficient length of data (~d) as ~d folds are required'-[Dlen,N]].
pack_errors:message( fold_data_residual(Dlen) ) -->
    ['Residual data of length: ~d while splitting folds'-[Dlen]].

Load and try with

?- [pack('pack_errors/examples/fold_data_errors')].

?- throw( pack_error(fold_data_insufficient(10,20),true) ).
ERROR: Insufficient length of data (10) as 20 folds are required

?- throw( fold_data_insufficient(10,20), mlu:ten_fold/3 ).
ERROR: mlu:ten_fold/3: Insufficient length of data (10) as 20 folds are required

Pack info

The library reacts to debug(pack_errors) spitting informational message along the execution of library predicates.

Pack predicates:

Pack defined errors selection: (see pack('pack_errors/prolog/pack_errors.pl') for a full list)

author
- nicos angelopoulos
version
- 0.1 2016/01/30
- 0.2 2016/02/24
- 0.3 2017/03/06
- 2.0 2018/10/01
- 2.1 2019/4/22
- 2.2 2022/12/29
See also
- http://stoics.org.uk/~nicos/sware/pack_errors

*/

license
- MIT
  218:- multifile prolog:message//1.  219
  220caught_defaults( [on_exit(error),on_true(true)] ).
 caught(+Goal, +Error, +Opts)
Catches all errors and failure of Goal. The idea is that all non-successful executions are handled identical by the call. If Goal errors, the primary thrown ball is caught and discarded. If Goal errors or fails, behaviour depends on option value OnExit (see Opts below).

Opts

ball(Ball)
instantiates the original exception Ball caught from calling Goal. (So that parts of it can be included in Error.)
on_exit(OnExit=error)
what to do on failed and errored executions
true
succeeds and repors nothing
fail
reports nothing but call itself fails
error
throws the error (any unrecognised value defaults to error)
on_true(OnTrue=true)
call OnTrue iff Goal was successful (and no handling was done)
?- caught( fail, my_exception(on_data), true ).
ERROR: Unhandled exception: pack_error(my_exception(on_data),[on_exit(error),message(error)])

?- caught( fail, my_exception(on_data), on_exit(true) ).
false
% it fails because the message writing fails, which is probably best

?- caught( false,  os_exists_not(abc), [] ).
ERROR: OS entity: abc, does not exist

?- caught( false,  os_exists_not(abc), on_exit(error) ).
ERROR: OS entity: abc, does not exist

?- caught( false,  os_exists_not(abc), on_exit(fail) ).
ERROR: OS entity: abc, does not exist
false.

?- caught( false,  os_exists_not(abc), on_exit(true) ).
ERROR: OS entity: abc, does not exist
true.
See also
- throw/2

*/

  272caught( Goal, Error, Args ) :-
  273    pack_errors_options_append( caught, Args, Opts ),
  274    caught_opts( Goal, Error, Opts ).
  275
  276caught_opts( Goal, Error, Opts ) :-
  277    catch( Goal, Ball, pack_errors:caught_error(Goal,Ball,Error,Opts) ),
  278    memberchk( OnTrue, Opts ),
  279    call( OnTrue ),
  280    !.
  281caught_opts( _Goal, Error, Args ) :-
  282    pack_errors_options_append( caught, Args, Opts ),
  283    caught_opt_throw( OnThrow, Opts ),
  284    throw( Error, on_exit(OnThrow) ).
  285
  286caught_error( _Goal, Ball, Error, Opts ) :-
  287    memberchk( ball(Ball), Opts ),
  288    !,
  289    caught_opt_throw( OnThrow, Opts ),
  290    throw( Error, on_exit(OnThrow) ).
  291caught_error( _Goal, _Ball, Error, Opts ) :-
  292    caught_opt_throw( OnThrow, Opts ),
  293    throw( Error, on_exit(OnThrow) ).
  294
  295caught_opt_throw( OnThrow, Opts ) :-
  296    memberchk( on_exit(Rep), Opts ),
  297    caught_opt_report_throw( Rep, OnThrow ).
  298
  299caught_opt_report_throw( true, true ) :-
  300    !.
  301caught_opt_report_throw( Rep, Rep ).
 ground(+Term, -Groundness)
 ground_binary(+Term, -Groundness)
Instantiates groundness of Term to Type. In ground_binary/2 Groundness partial and false are collapsed to false.

Groundness

true
Term is ground
false
Term is variable
partial
Term is partially instantiated
?- ground( abc, Abc ), ground( de(F), Def ), ground( GHI, Ghi ).
Abc = true,
Def = partial,
Ghi = false.

?- ground_binary( abc, Abc ), ground_binary( de(F), Def ), ground_binary( GHI, Ghi ).
Abc = true,
Def = Ghi, Ghi = false.

*/

  330ground( Term, Type ) :-
  331    var( Term ),
  332    !,
  333    Type = false.
  334ground( Term, Type ) :-
  335    ground( Term ),
  336    !,
  337    Type = true.
  338ground( _Term, Type ) :-
  339    Type = partial.
  340    
  341ground_binary( Term, Type ) :-
  342    ground( Term ), 
  343    !,
  344    Type = true.
  345ground_binary( _Term, false ).
  346
  347throw_defaults([err(error)]).
  348
  349% fixme: use _known and throw error else
  350throw_err_opt_vals( error, error, error ).
  351throw_err_opt_vals( test, quiet, false ).
  352throw_err_opt_vals( exists, warning, false ).
 throw(+Error, +Opts)
An optionised version of throw/1. The Error is not always thrown (eg OnThrow==false, see Opts below).
This version of throw() decouples type of message printing and execution behaviour.<br>

As of version 0.3 this should be the adviced entry point for message writing and ball throwing for stoics packs.

Opts

err(Err=error)
convenenience option that sets both Level and OnExit, if they are absent
error
Level = error and OnExit = error
test
Level=quiet and OnExit = false
exists
Level=warning and OnExit = false
on_exit(OnExit=error)
defines execution behaviour on exiting the printing of the error. One of [true,false,error]. if not given the default depends on Err,
true
succeed
false
fails
error
errors
Pid
predicate indicator (foo:bar/1 or bar/2)
pack(Pack)
originator pack/module
pred(Pid)
originator predicate
option(Opt)
name of originator option
message(Level=error)
passed to print_message/2 (first argument), but also accepts quiet (as silent still prints things...)
?-
    throw( cast(abc('file.csv'),atom) ).

ERROR: Unhandled exception: cast(abc(file.csv),atom)

?-
    throw( pack_error(cast(abc('file.csv'),atom),true) ).

ERROR: Cannot cast: abc(file.csv), to type: atom

?-
    Opt = os:os_exists/2,
    throw(pack_error(cast(abc('file.csv'),atom),Opt)), writeln(later).

ERROR: os:os_exists/2: Cannot cast: abc(file.csv), to type: atom

?-
    throw(cast(abc('file.csv'),atom), os:os_exists/2), writeln(later).

ERROR: os:os_exists/2: Cannot cast: abc(file.csv), to type: atom

?-
    throw(cast(abc('file.csv'),atom), err(test)), writeln(later).

false.

?-
    _Opts = [message(quiet),on_exit(true)],
    throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

later
true.

?-
    _Opts = [message(warning),on_exit(true)],
    throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

Warning: Cannot cast: abc(file.csv), to type: atom
later
true.

?-
    _Opts = [message(informational),on_exit(true)],
    throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

% Cannot cast: abc(file.csv), to type: atom
later
true.

?-
   _Opts = [message(warning),on_exit(false)],
   throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

Warning: Cannot cast: abc(file.csv), to type: atom
false.

?-
    throw(cast(abc('file.csv'),atom), err(exists)), writeln(later).

Warning: Cannot cast: abc(file.csv), to type: atom
false.

?-
    throw(cast(abc('file.csv'),atom), on_exit(true)), writeln(later).

ERROR: Cannot cast: abc(file.csv), to type: atom
later
true.

?-
    throw(cast(abc('file.csv'),atom), on_exit(false)), writeln(later).

ERROR: Cannot cast: abc(file.csv), to type: atom
false.

?-
    throw(cast(abc('file.csv'),atom), on_exit(error)), writeln(later).

ERROR: Cannot cast: abc(file.csv), to type: atom

?-
    throw(cast(abc('file.csv'),atom), message(warning)), writeln(later).

Warning: Cannot cast: abc(file.csv), to type: atom

?-
    throw(cast(abc('file.csv'),atom), message(informational)), writeln(later).

% Cannot cast: abc(file.csv), to type: atom
later
true.

?-
    _Opts = [message(informational),on_exit(false)],
    throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

% Cannot cast: abc(file.csv), to type: atom
false.

?-
    _Opts = [message(informational),on_exit(error)],
    throw(cast(abc('file.csv'),atom), _Opts), writeln(later).

% Cannot cast: abc(file.csv), to type: atom
author
- nicos angelopoulos
version
- 0.2 2017/3/6
- 0.3 2018/1/5 added tracer options: pack, pred & pack_format
- 0.4 2018/9/30 severe re-write, see docs

*/

  513throw( Error, Args ) :-
  514    pack_errors_options_append( throw, Args, Opts ),
  515    memberchk( err(Err), Opts ),
  516    throw_err_opt_vals( Err, LvlDef, OnXDef ),
  517    ( memberchk(message(Lvl),Opts)-> true; Lvl = LvlDef ),
  518    ( memberchk(on_exit(OnExit),Opts) -> true ; OnExit = OnXDef ),
  519    throw_on_valid( OnExit ),
  520    throw_level( Lvl, Error, OnExit, Opts ).
  521
  522throw_on_valid( OnThrow ) :-
  523    throw_on_known( OnThrow ),
  524    !.
  525throw_on_valid( OnThrow ) :-
  526    % fixme: render it !
  527    throw( unknown_option_value(throw/2,on_exit(OnThrow)) ).
  528
  529throw_on_known(error).
  530throw_on_known(true).
  531throw_on_known(fail).
  532throw_on_known(false).
  533
  534% fixme: ask in forum with silent in print_message/2 prints things ...
  535%
  536throw_level( quiet, _BallMark, OnExit, _Opts ) :-
  537    !,
  538    throw_level_exit( OnExit ).
  539throw_level( Lvl, BallMark, OnExit, Opts ) :-
  540    ( BallMark =.. [pack_error,Barg] ->
  541        Ball =.. [pack_error,Barg,Opts]
  542        ;
  543        ( BallMark =.. [pack_error,_Barg,_Opts] ->
  544            Ball = BallMark
  545            ;
  546            Ball = pack_error(BallMark,Opts)
  547        )
  548    ),
  549    debug( pack_errors, 'Leveled ball: ~w', Ball ),
  550    throw_level_on_exit( OnExit, Lvl, Ball ).
  551
  552throw_level_on_exit( error, error, Ball ) :-
  553    !,
  554    throw( Ball ).
  555throw_level_on_exit( OnExit, Lvl, Ball ) :-
  556    debug( pack_errors, 'Explicit layout at level: ~w', [Lvl] ),
  557    prolog:message( Ball, Mess, [] ), % i thinks [] is correct
  558	print_message_lines( current_output, kind(Lvl), Mess ),
  559    throw_level_exit( OnExit ).
  560
  561throw_level_exit( error ) :-
  562    !,
  563    throw( true ).
  564    % abort.
  565throw_level_exit( Goal ) :-
  566    call( Goal ),
  567    !.
  568
  569type_defaults( [error(true),pack(false),pred(false),arg(false)] ).
 type(+Type, @Term)
 type(+Type, @Term, +Opts)
type/2 is a superset of must_be, in that it adds Type = @(Callable), (equiv: Type = call(Callable)), which will succeed iff call( Callable, Term ) succeeds. It also enhances must_be/2 by adding options. In the case of a call-wrapped type, the call to type/3 will succeed iff call(Callable,Term) succeeds.

Opts (unlisted is ok)

error(Err=true)
when false, call fails instead of throwing error
arg(Err=true)
some argument position of Term. (false is reserved and prints no info.)
pack(Pack=false)
when given, the error contains info on the pack throwing the error (false is reserved and prints no info)
pred(Pred=false)
when given, the error contains info on the predicate throwing the error (false is reserved and prints no info)
?- type( boolean, maybe ).
ERROR: Object of type: boolean, expected but found term: maybe

?- type( boolean, maybe, error(false) ).
false.

?- type( boolean, maybe, pack(sure) ).
ERROR: pack(sure): Object of type: boolean, expected but found term: maybe

?- type( boolean, maybe, [pack(sure),pred(lost/2)] ).
ERROR: sure:lost/2: Object of type: boolean, expected but found term: maybe

?- type( boolean, maybe, [pack(sure),pred(lost/2+3)] ).
ERROR: sure:lost/2+3: Object of type: boolean, expected but found term: maybe

?- type( boolean, maybe, [pack(sure),pred(1+lost/2)] ).
ERROR: sure:1+lost/2: Object of type: boolean, expected but found term: maybe

?- type( boolean, maybe, [pack(sure),pred(lost(arg1)/2)] ).
ERROR: sure:lost(arg1)/2: Object of type: boolean, expected but found term: maybe

*/

  620type( Type, Term ) :-
  621    type( Type, Term, [] ).
  622
  623type( Type, _Term, _Args ) :-
  624    \+ ground( Type ),
  625    !,
  626    % throw( pack_error(pack_error,type/3,arg_ground(1,Type)) ).
  627    throw( arg_ground(1,Type), pack_error:type/3 ).
  628type( Type, Term, Args ) :-
  629    pack_errors_options_append( type, Args, Opts ),
  630    type_optioned( Type, Term, Opts ).
  631
  632type_optioned( @(GoalPrv), Term, _Opts ) :-
  633    ( GoalPrv = _:_ -> Goal = GoalPrv; Goal = user:GoalPrv ),
  634    call( Goal, Term ),
  635    !.
  636type_optioned( call(GoalPrv), Term, _Opts ) :-
  637    ( GoalPrv = _:_ -> Goal = GoalPrv; Goal = user:GoalPrv ),
  638    call( Goal, Term ),
  639    !.
  640type_optioned( Type, Term, _Opts ) :-
  641    Type \= call(_),
  642    is_of_type( Type, Term ),
  643    !.
  644type_optioned( Type, Term, Opts ) :-
  645    memberchk( error(Error), Opts ),
  646    type_error( Error, Type, Term, Opts ).
  647
  648type_error( false, _Type, _Term, _Opts ) :- !, fail.
  649type_error( true, Type, Term, Opts ) :-
  650    % memberchk( pack(Pack), Opts ),
  651    % memberchk( pred(Pred), Opts ),
  652    memberchk( arg(Pos), Opts ),
  653    type_error_position( Pos, Type, Term, Opts ).
  654
  655% type_error_position( false, Pack, Pred, Type, Term ) :-
  656type_error_position( false, Type, Term, Opts ) :-
  657    !,
  658    throw( pack_error(type_error(Type,Term)), Opts ). 
  659type_error_position( Pos, Type, Term, Opts ) :-
  660    throw( pack_error(type_error(Pos,Type,Term)), Opts ). 
  661
  662pack_errors_options_append( Pname, ArgS, Opts ) :-
  663    ( is_list(ArgS) -> Args = ArgS ; Args = [ArgS] ),
  664    atom_concat( Pname, '_defaults', Dname ),
  665    Dcall =.. [Dname,Defs],
  666    call( Dcall ),
  667    append( Args, Defs, Opts ),
  668    !.
  669
  670defined_defaults( [load(false)] ).
 defined(+Pid, +From, +Opts)
Throws an error if Pid is not defined in current context.
From is the source from where Pid was supposed to be loaded.
This predicate can act independently (particularly with load(true))
or be combined with pack(lib)'s lib(suggests(Pack)) to, on-demand,
pinpoint to which library is missing and what
predicate within that pack is the deal breaker.

Note that pack(lib) also provides

lib(suggests(Pid,Load))

which is an alternative and more automatic way to achieve demand driven loading via hot-swapping.

:- lib(suggests(Pack))

silently fails if Pack is not present. This is intendent for dependendencies that do not impact major parts for the importing pack. Thus allow common use without grabbing all dependencies that may not be needed for a particular user.

Opts are passed to throw/2, except for: load(Load=false)

?- defined( abc/0, pack(b_real) ).
ERROR: Predicate: abc/0 is not defined (source apparently available at: pack(b_real); not asked to load)

?- defined( abc/0, false ).
ERROR: Predicate: abc/0 is not defined

?- defined( abc/0, false, pack(sourcey) ).
ERROR: sourcey:$unknown/0: Predicate: abc/0 is not defined

?- defined( abc/0, pack(b_real), [pack(sourcey),pred(foo/1;2)] ).
ERROR: sourcey:foo/1;2: Predicate: abc/0 is not defined (source apparently available at: pack(b_real); not asked to load)

?- defined( b_real/0, pack(b_real), [as_pack_err(true),load(library(b_real))] ).
true.

The above only succeeds if b_real is an install library and defines b_real/0.

From or Load can have the special form: lib(CodeLib). This assumes pack(lib) is installed and lib/1 will be used to load the requested CodeLib.

?- defined( b_real/0, lib(b_real), load(true) ).

Will again, only succeed if b_real is installed and defines b_real/0. In this occasion library(lib) should be also installed.

author
- nicos angelopoulos
version
- 0.1 2018/1/5
See also
- throw/2
- lib/1 (lib(suggests/1)) can work with this predicate
- lib/1 (lib(suggests/2)) as an alternative

*/

  730defined( Pid, From ) :-
  731    defined( Pid, From, [] ).
  732defined( Pid, _From, _Opts ) :-
  733    current_predicate( Pid ),
  734    !. % fixme: need version where From and Into are checked ? 
  735       %        here we don't check as From and Into are assumed as tracers no enforcables
  736defined( Pid, From, ArgS ) :-
  737    \+ var(ArgS),                   % fixme: error
  738    defined_defaults( Defs ),
  739    ( is_list(ArgS) -> Args = ArgS; Args = [ArgS] ),
  740    append( Args, Defs, Opts ),
  741    memberchk( load(Load), Opts ),
  742    defined_if_load( Load, Pid, From, Args ).
  743
  744defined_if_load( false, Pid, From, Opts ) :-
  745    throw( expected_from(false,Pid,From), Opts ).
  746defined_if_load( true, Pid, From, Opts ) :-
  747    !,
  748    defined_load( From, Pid, From, Opts ).
  749defined_if_load( Other, Pid, From, Opts ) :-
  750    defined_load( Other, Pid, From, Opts ).
  751
  752defined_load( lib(This), Pid, From, Args ) :-
  753    !,
  754    lib:lib(This),
  755    defined_loaded( Pid, From, Args ).
  756defined_load( LoadThis, Pid, From, Args ) :-
  757    % fixme: check is not loaded ?
  758    user:ensure_loaded( LoadThis ),
  759    defined_loaded( Pid, From, Args ).
  760
  761defined_loaded( Pid, _From, _Opts ) :-
  762    current_predicate( user:Pid ),
  763    !. % fixme: need version where From and Into are checked ? 
  764defined_loaded( Pid, From, Opts ) :-
  765    throw( expected_from(true,Pid,From), Opts ).
 pack_errors
This is a documentation predicate, providing an anchor for documentation pointers.

*/

  772pack_errors :-
  773    write( 'Contextual error handling for packs' ), nl.
 of_same_length(+Lists)
 of_same_length(+List1, +List2)
of_same_length(+Lists, +Opts)
 of_same_length(+List1, +List2, +Opts)
Generic sanity predicate, checking that two or more lists are of the same length.

In order to disambiguate between the two versions of the arity 2, in that scenario options should be a term of the form opts(OptsL).

Opts are passed to throw/2, the only local one is:

?- of_same_length( [a,b,c], [1,2,3] ).
true.

?- of_same_length( [[a,b,c],[1,2,3]] ).
true.

?- of_same_length( [1,2,3], [a,b], token1(first) ).
ERROR: Lists for first and 2 have mismatching lengths: 3 and 2 respectively
author
- nicos angelopoulos
version
- 0.1 2018/09/24

*/

  818of_same_length( [List1|Lists] ) :-
  819    of_same_length_1( Lists, List1, [] ).
  820
  821of_same_length( [List1|Lists], opts(Opts) ) :-
  822    !,
  823    of_same_length_1( Lists, List1, Opts ).
  824of_same_length( List1, List2 ) :-
  825    of_same_length_1( [List2], List1, [] ).
  826of_same_length( List1, List2, Opts ) :-
  827    of_same_length_1( [List2], List1, Opts ).
  828
  829of_same_length_1( Lists, List1, Args ) :-
  830    length( List1, Lng1 ),
  831    \+ var(Args),
  832    ( is_list(Args) -> append(Args,[action(throw)],Opts) ; Opts = [Args,action(throw)] ),
  833    memberchk( action(Act), Opts ),
  834    of_same_length_1( Lists, 2, Lng1, List1, Act, Opts ).
  835
  836% currently List1 is not used, but it could be passed to 
  837% of_same_length_mismatch for reporting of clashing lists...
  838of_same_length_1( [], _I, _Lng, _List1, _Act, _Opts ).
  839of_same_length_1( [HList|T], I, Lng1, List1, Act, Opts ) :-
  840     length( HList, HLng ),
  841     ( HLng =:= Lng1 ->
  842          true
  843          ;
  844          % throw( not_of_equal_length(HLng,Lng) )
  845          ( memberchk(token1(Tkn1),Opts) -> true; Tkn1 = 1 ),
  846          ( memberchk(token2(Tkn2),Opts) -> true; Tkn2 = I ),
  847          of_same_length_mismatch( Act, Lng1, HLng, Tkn1, Tkn2, Opts )
  848     ),
  849     J is I + 1,
  850     of_same_length_1( T, J, Lng1, List1, Act, Opts ).
  851
  852of_same_length_mismatch( error, Lng1, Lng2, Tkn1, Tkn2, Opts ) :-
  853    throw( lengths_mismatch(Tkn1,Tkn2,Lng1,Lng2), Opts ).
  854of_same_length_mismatch( fail, _Lng1, _Lng2, _Tkn1, _Tkn2, _Opts ) :-
  855    fail.
  856of_same_length_mismatch( false, _Lng1, _Lng2, _Tkn1, _Tkn2, _Opts ) :-
  857    fail.
  858% throw_lists ? which will also include the offender & base lists ?
  859of_same_length_mismatch( throw, Lng1, Lng2, Tkn1, Tkn2, _Opts ) :-
  860    throw( of_same_length(Lng1,Lng2,Tkn1,Tkn2) ).
  861of_same_length_mismatch( warning, Lng1, Lng2, Tkn1, Tkn2, _Opts ) :-
  862    % % Format = 'Lists at:~w and ~w, have differing lengths: ~d and ~d',
  863    % % message_report( Format, [Tkn1,Tkn2,Lng1,Lng2], informational ).
  864    throw( lengths_mismatch(Tkn1,Tkn2,Lng1,Lng2), message(warning) ).
  865    % message( lengths_mismatch(Tkn1,Tkn2,Lng1,Lng2), List, [] ),
  866	% print_message_lines(current_output, kind(warning), List ).
  867% of_same_length_mismatch( warning, Lng1, Lng2, Tkn1, Tkn2, _Opts ) :-
  868
  869of_same_length_mismatch( error, Lng1, Lng2, Tkn1, Tkn2, _Opts ) :-
  870    Format = 'Lists at:~p, have differing lengths, ~d and ~d',
  871    message_report( Format, [Tkn1,Tkn2,Lng1,Lng2], error ),
  872    fail.
  873of_same_length_mismatch( warning(Tkn), Lng1, Lng2, _L1, _L2, _Opts ) :-
  874    Format = 'Lists at:~p, have differing lengths, ~d and ~d',
  875    message_report( Format, [Tkn,Lng1,Lng2], informational ).
  876of_same_length_mismatch( warn_lists(Tkn), Lng1, Lng2, L1, L2, _Opts ) :-
  877    Format = 'Lists at:~p, have differing lengths, ~d and ~d. The lists are as follows',
  878    Args   = [Tkn,Lng1,Lng2],
  879    message_report( Format, Args, informational ),
  880    Format1 = 'Length mismatch list1:~p',
  881    message_report( Format1, [L1], debug(_) ),
  882    Format2 = 'Length mismatch List2:~p',
  883    message_report( Format2, [L2], debug(_) ).
  884
  885pack_message_options_augment( Opts, Apts ) :-
  886    nth1( N, Opts, Mod:Pred, Rest ),
  887    \+ (nth1(N1,Opts,_Name/_Arity), N1<N),
  888    !,
  889    pack_message_options_trail( Rest, Trail, Rems ),
  890    Apts = [pred(Mod:Pred),trail(Trail)|Rems]. 
  891pack_message_options_augment( Opts, Apts ) :-
  892    nth1( _N1, Opts, Name/Arity, Rest ),
  893    !,
  894    pack_message_options_trail( Rest, Trail, Rems ),
  895    Apts = [pred(Name/Arity),trail(Trail)|Rems].
  896    %  fixme: just stick a mod infront of Name/Arity
  897pack_message_options_augment( Apts, Apts ).
  898
  899pack_message_options_trail( [], [], [] ).
  900pack_message_options_trail( [Opt|Opts], Trail, Rems ) :-
  901    ( (Opt=_Mod:_Name1/_Arity1; Opt=_Name2/_Arity2) ->
  902        Trail = [Opt|TellTail],
  903        Rems = Tems
  904        ;
  905        Trail = TellTail,
  906        Rems = [Opt|Tems]
  907    ),
  908    pack_message_options_trail( Opts, TellTail, Tems ).
  909
  910/* fixme: delete
  911pack_message_options_augment( Opts, Apts ) :-
  912    nth1( N, Opts, Name/Arity, Rest ),
  913    pack_message_options_trail( Rest, Trail, Rems ),
  914    ( select(Mod:Pred,Opts,Rem) ->
  915        Apts = [pred(Mod:Pred)|Rem]
  916        ;
  917        ( select(Name/Arity,Opts,Rem) ->
  918            Apts = [pred(Name/Arity)|Rem]
  919            ;
  920            Apts = Opts
  921        )
  922    ).
  923*/
 pack_errors_version(-Version, -Date)
Current version and release date for the library.
V = 2:2:0,
D = date(2022, 12, 29).
author
- nicos angelopoulos
version
- 2:2 2022/12/29

*/

  938pack_errors_version( 2:2:0, date(2022,12,29) ).
  939
  940prolog:message(unhandled_exception(true)) --> [].
  941prolog:message(unhandled_exception(pack_error(Message))) -->
  942     { debug( pack_errors, 'Unhandled pack_error/1 ~w', [Message] ) },
  943     % pack_errors:message(Message,[]).
  944     pack_message(Message,[]).
  945prolog:message(unhandled_exception(pack_error(Message,Opts))) -->
  946     { debug( pack_errors, 'Unhandled pack_error/2 c ~w, ~w', [Message,Opts] ) },
  947     pack_message(Message,Opts).
  948
  949prolog:message(pack_error(Message)) -->
  950     { debug( pack_errors, 'Pack_error/1: ~w', [Message] ) },
  951     pack_message(Message, []).
  952prolog:message(pack_error(Message,Opts)) -->
  953     { debug( pack_errors, 'Pack_error/2: ~w, ~w', [Message,Opts] ) },
  954     pack_message(Message, Opts).
  955
  956pack_message( Mess, OptsPrv ) -->
  957    % fixme: check for var(OptsPrv) ?
  958    {( is_list(OptsPrv) -> OptsPrv = Opts; Opts = [OptsPrv] )},
  959    {pack_message_options_augment(Opts,Apts)},
  960    message_pack( Apts ),
  961    pack_errors:message( Mess ),
  962    pack_message_trail( Apts ).
  963
  964pack_message_trail( Apts ) -->
  965    { memberchk(trail(Trail),Apts),
  966      Trail \== [],
  967      !
  968    },
  969    [ '\nERROR: Trail: ~w' - [Trail] ].
  970pack_message_trail( _Apts ) --> {true}.
  971
  972message_pack( Opts )  -->
  973    { debug( pack_errors, 'message_pack options: ~w', [Opts] ) },
  974    { (memberchk(pred(PredPrv),Opts)->true; PredPrv='_Unk'),
  975      (memberchk(pack(Pack),Opts)->
  976            ( PredPrv = PredMod:PredFct ->
  977                ( PredMod = Pack -> 
  978                    Pred = PredFct
  979                    ;
  980                    Pred = PredPrv   % both Pack and Mod will be displayed
  981                ) 
  982                ;
  983                Pred = PredPrv
  984            )
  985            ; 
  986            ( PredPrv = Pack:Pred ->
  987                true
  988                ;
  989                PredPrv = Pred,
  990                Pack='_Unk'
  991            )
  992      ),
  993      debug( pack_errors, 'pack:predicate identified as: ~w:~w', [Pack,Pred] ),
  994      \+ (Pack=='_Unk', Pred=='_Unk'),
  995      !
  996    },
  997    {( memberchk(option(OptNm),Opts) -> atomic_list_concat([' @ option(',OptNm,')'],OptTkn); OptTkn = '' )},
  998    ['~w:~w~w: '-[Pack,Pred,OptTkn] ].
  999message_pack( _ )  --> [].
 1000
 1001:- multifile( pack_errors:message/3 ). 1002
 1003message( true ) --> [].
 1004
 1005message( arg_enumerate(Pos,Vals,_Arg) ) --> 
 1006    { current_prolog_flag(pack_errors_arg,false) },
 1007    ['Term at position: ~d, is not one of: ~w'-[Pos,Vals]].
 1008message( arg_enumerate(Pos,Vals,Arg) ) --> 
 1009    ['Term at position: ~d, is not one of: ~w, (found: ~w)'-[Pos,Vals,Arg]].
 1010message( arg_ground(Pos,_Arg) ) -->
 1011    { current_prolog_flag(pack_errors_arg,false) },
 1012    ['Ground argument expected at position: ~d'-[Pos]].
 1013message( arg_ground(Pos,Arg) ) -->
 1014    ['Ground argument expected at position: ~d,  (found: ~w)'-[Pos,Arg]].
 1015message( args_ground(Pos,_Arg) ) -->
 1016    { current_prolog_flag(pack_errors_arg,false) },
 1017    ['Ground arguments expected at position: ~d'-[Pos]].
 1018message( args_ground(Pos,Arg) ) -->
 1019    ['Ground argument expected at position: ~d,  (found: ~w)'-[Pos,Arg]].
 1020% message( arg_ground_at_either(Pos1,Pos2,_Arg1,_Arg2) ) -->  % Pos1 & Pos2 can be lists of positions
 1021message( arg_ground_in_one_of(Poss,_Args) ) -->                     
 1022    { current_prolog_flag(pack_errors_arg,false) },
 1023    ['Ground argument expected in one of the positions: ~w'-[Poss]].
 1024message( arg_ground_in_one_of(Poss,Args) ) --> 
 1025    ['Ground argument expected in one of the positions : ~w, but found: ~w'-[Poss,Args]].
 1026message( arg_ground_pattern(Poss,_Args) ) -->
 1027    { current_prolog_flag(pack_errors_arg,false) },
 1028    ['Ground arguments expected in some of the positions: ~w'-[Poss]].
 1029message( arg_ground_pattern(Poss,Args) ) -->
 1030    ['Ground arguments expected in some of the positions: ~w, but found:~w'-[Poss,Args]].
 1031
 1032message( lengths_mismatch(Tkn1,Tkn2,Len1,Len2) ) -->
 1033    ['Lists for ~w and ~w have mismatching lengths: ~d and ~d respectively'-[Tkn1,Tkn2,Len1,Len2]].
 1034message( lengths_mismatch(Tkn1,Tkn2,Op,Len1,Len2) ) -->
 1035    ['Terms idied by: ~w and ~w, have mismatching lengths: ~d and ~d respectively (~w expected)'-[Tkn1,Tkn2,Len1,Len2,Op]].
 1036message( cast(Term,From,To) ) -->
 1037    ['Cannot cast: ~w, from type: ~w to type: ~w'-[Term,From,To]].
 1038message( cast(Term,To) ) -->
 1039    ['Cannot cast: ~w, to type: ~w'-[Term,To]].
 1040message( type_error(false,Type,Term) ) -->
 1041    ['Object of type: ~w, expected but found term: ~w'-[Type,Term]].
 1042message( type_error(Pos,Type,Term) ) -->
 1043    ['Object of type: ~w, expected at position:~w but found: ~w'-[Type,Pos,Term]].
 1044message( type_error(Type,Term) ) -->
 1045    ['Object of type: ~w, expected but found term: ~w'-[Type,Term]].
 1046message( wrong_token(Tkn,Cat) ) -->  % was: unknown_token/2
 1047    ['Token: ~w, is not a recognisable: ~w'-[Tkn,Cat]].
 1048message( expected_from(_,Pid,false) ) -->
 1049    ['Predicate: ~w is not defined'-[Pid]].
 1050message( expected_from(false,Pid,From) ) -->
 1051    ['Predicate: ~w is not defined (source apparently available at: ~w; not asked to load)'-[Pid,From]].
 1052message( expected_from(true,Pid,From) ) -->
 1053    ['Predicate: ~w is not defined (source apparently available at: ~w; which was loaded).'-[Pid,From]].
 1054message( input_file_missing(Os) ) -->
 1055    ['Input file: ~w, is missing.'-[Os]].
 1056message( true ) -->
 1057    []