/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Written 2018-2025 by Markus Triska (triska@metalevel.at) I place this code in the public domain. Use it in any way you want. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(error, [must_be/2, can_be/2, instantiation_error/0, domain_error/2, type_error/2, representation_error/1, resource_error/1, instantiation_error/1, domain_error/3, type_error/3, call_with_error_context/2 ]). :- meta_predicate(check_(1, ?, ?)). %% must_be(Type, Term) % % This predicate is intended for type-checks of built-in predicates. % % It asserts that Term is: % % 1) instantiated *and* % 2) instantiated to an instance of the given Type. % % It corresponds to usage mode +Term. % % Currently, the following types are supported: % % - atom % - boolean % - character % - chars % - in_character % - integer % - list % - octet_character % - octet_chars % - pair % - term must_be(Type, Term) :- must_be_(type, Type), must_be_(Type, Term). must_be_(Type, _) :- var(Type), instantiation_error(must_be/2). must_be_(var, Term) :- ( var(Term) -> true ; throw(error(uninstantiation_error(Term), must_be/2)) ). must_be_(integer, Term) :- check_(integer, integer, Term). must_be_(not_less_than_zero, N) :- must_be(integer, N), ( N >= 0 -> true ; domain_error(not_less_than_zero, N, must_be/2) ). must_be_(atom, Term) :- check_(atom, atom, Term). must_be_(character, T) :- check_(error:character, character, T). must_be_(in_character, T) :- check_(error:in_character, in_character, T). must_be_(chars, Ls) :- can_be(chars, Ls), % prioritize type errors over instantiation errors must_be(list, Ls), ( '$is_partial_string'(Ls) -> % The expected case (success) uses a very fast test. % We cannot use partial_string/1 from library(iso_ext), % because that library itself imports library(error). true ; all_characters(Ls) ). must_be_(octet_character, C) :- must_be(character, C), ( octet_character(C) -> true ; domain_error(octet_character, C, must_be/2) ). must_be_(octet_chars, Cs) :- must_be(chars, Cs), ( '$first_non_octet'(Cs, C) -> domain_error(octet_character, C, must_be/2) ; true ). must_be_(list, Term) :- check_(error:ilist, list, Term). must_be_(type, Term) :- check_(error:type, type, Term). must_be_(boolean, Term) :- check_(error:boolean, boolean, Term). must_be_(pair, Term) :- check_(error:pair, pair, Term). must_be_(term, Term) :- ( acyclic_term(Term) -> ( ground(Term) -> true ; instantiation_error(must_be/2) ) ; type_error(term, Term, must_be/2) ). % We cannot use maplist(must_be(character), Cs), because library(lists) % uses library(error), so importing it would create a cyclic dependency. all_characters([]). all_characters([C|Cs]) :- must_be(character, C), all_characters(Cs). check_(Pred, Type, Term) :- ( var(Term) -> instantiation_error(must_be/2) ; call(Pred, Term) -> true ; type_error(Type, Term, must_be/2) ). pair(_-_). boolean(B) :- ( B == true ; B == false ). character(C) :- atom(C), atom_length(C, 1). octet_character(C) :- char_code(C, Code), 0 =< Code, Code =< 0xff. in_character(C) :- ( character(C) ; C == end_of_file ). ilist(Ls) :- '$skip_max_list'(_, _, Ls, Rs), ( var(Rs) -> instantiation_error(must_be/2) ; Rs == [] ). type(type). type(integer). type(atom). type(character). type(in_character). type(octet_character). type(octet_chars). type(chars). type(list). type(var). type(boolean). type(term). type(not_less_than_zero). type(pair). %% can_be(Type, Term) % % This predicate is intended for type-checks of built-in predicates. % % It asserts that there is a substitution which, if applied to Term, % makes it an instance of Type. % % It corresponds to usage mode ?Term. % % It supports the same types as must_be/2. can_be(Type, Term) :- must_be(type, Type), ( var(Term) -> true ; can_(Type, Term) -> true ; type_error(Type, Term, can_be/2) ). can_(integer, Term) :- integer(Term). can_(not_less_than_zero, N) :- ( integer(N) -> ( N >= 0 -> true ; domain_error(not_less_than_zero, N, can_be/2) ) ; type_error(integer, N, can_be/2) ). can_(atom, Term) :- atom(Term). can_(character, T) :- character(T). can_(in_character, T) :- in_character(T). can_(chars, Ls) :- ( '$is_partial_string'(Ls) -> true ; can_be(list, Ls), can_be_chars(Ls) ). can_(octet_character, C) :- ( octet_character(C) -> true ; domain_error(octet_character, C, can_be/2) ). can_(octet_chars, Cs) :- can_be(chars, Cs), ( '$skip_max_list'(_, _, Cs, []), % temporarily turn Cs into a list '$first_non_octet'(Cs, C) -> domain_error(octet_character, C, can_be/2) ; true ). can_(list, Term) :- list_or_partial_list(Term). can_(boolean, Term) :- boolean(Term). can_(pair, Term) :- pair(Term). can_(term, Term) :- ( acyclic_term(Term) -> true ; type_error(term, Term, can_be/2) ). can_be_chars(Var) :- var(Var), !. can_be_chars([]). can_be_chars([X|Xs]) :- can_be(character, X), can_be_chars(Xs). list_or_partial_list(Ls) :- '$skip_max_list'(_, _, Ls, Rs), ( var(Rs) -> true ; Rs == [] ). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Shorthands for throwing ISO errors. The variants without context promote the use of call_with_error_context/2. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ instantiation_error :- throw(error(instantiation_error, [])). domain_error(Type, Term) :- throw(error(domain_error(Type, Term), [])). type_error(Type, Term) :- throw(error(type_error(Type, Term), [])). representation_error(Flag) :- throw(error(representation_error(Flag), [])). resource_error(Resource) :- throw(error(resource_error(Resource), [])). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The variants *with* context would not have been needed if call_with_error_context/2 had been found earlier. In the future, we may be able to remove them. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ instantiation_error(Context) :- throw(error(instantiation_error, Context)). domain_error(Type, Term, Context) :- throw(error(domain_error(Type, Term), Context)). type_error(Type, Term, Context) :- throw(error(type_error(Type, Term), Context)). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - call_with_error_context/2 See https://github.com/mthom/scryer-prolog/discussions/2839 . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ %% call_with_error_context(+Goal, +Pair) % % Call _Goal_ with error context _Pair_. % % Examples of error contexts: `predicate-PI`, `file-Filename` etc. :- meta_predicate(call_with_error_context(0,+)). call_with_error_context(G_0, Pair) :- must_be(pair, Pair), catch(G_0, error(E,Pairs), throw(error(E,[Pair|Pairs]))).