1:- module(onepointfour_basics_safe_format, 2 [ 3 safe_format/3 % safe_format(+Msg,+Args,-ResultString) 4 ]). 5 6:- use_module(library(yall)). 7:- use_module(library(apply)). 8:- use_module(library(apply_macros)).
format/3 and format/2 are demanding in what they require as arguments. They throw an exception if there is a mismatch between the placeholders count and the count of elements in Args, or if an element in Args is a mismatch for a placeholder in Msg. This can lead to exceptions at inopportune times, especially if the Msg or Args arguments are built dynamically and code lacks sufficient coverage.
Use this predicate to make format/3 generate ResultString (always an SWI-Prolog string) from Msg and Args. If an exception is thrown from the call to format/3, it is caught and a replacement message is generated in ResultString.
?- safe_format("Hello ~d",[7889],Result). Result = "Hello 7889". ?- safe_format("Hello ~d",[hello],Result). Result = "Exception in format/3 with format string <Hello ~d> and args <hello>". ?- safe_format("Open the ~s.",["pod bay doors","HAL"],Result). Result = "Exception in format/3 with format string <Open the ~s.> and args <\"pod bay doors\">,<\"HAL\">".
57safe_format(Msg,Args,Result) :- 58 (is_list(Args) -> ListyArgs = Args ; ListyArgs = [Args]), 59 catch( 60 format(string(Result),Msg,ListyArgs), % this "should" work 61 _, 62 safe_format_exception_handler(Msg,ListyArgs,Result)). % but if not, we go here 63 64% We end up here if format/3 doesn't like its arguments; finagle something! 65% While still catching any exception thrown by the building of the replacement 66% string. 67 68safe_format_exception_handler(Msg,ListyArgs,Result) :- 69 catch( 70 build_replacement_string(Msg,ListyArgs,Result), 71 _, 72 (Result = "Exception inside of exception handler of safe_format/3")). 73 74build_replacement_string(Msg,ListyArgs,Result) :- 75 maplist( 76 format_whatever, 77 ListyArgs, % input 78 FormattedArgs), % ouput is a list of string 79 atomics_to_string(FormattedArgs,",",ArgsString), 80 atomics_to_string([ 81 "Exception in format/3 with format string <", 82 Msg, 83 "> and args ", 84 ArgsString],Result). 85 86format_whatever(Element,Formatted) :- 87 format(string(Formatted),"<~p>",[Element]). % "~p" == "print term for debugging purposes"