/*  Part of SWI-Prolog

    Author:        Jan Wielemaker, Johan Romme
    E-mail:        J.Wielemaker@cs.vu.nl
    WWW:           http://www.swi-prolog.org
    Copyright (c)  2012-2016, VU University Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

:- module(ifprolog,
	  [ calling_context/1,			% -Module
	    context/2,				% :Goal, +Mapping
	    block/3,				% :Goal, +Tag, :Recovery
	    exit_block/1,			% +Tag
	    cut_block/1,			% +Tag

	    modify_mode/3,			% +PI, -Old, +New
	    debug_mode/3,			% +PI, -Old, +New
	    ifprolog_debug/1,			% :Goal,
	    debug_config/3,			% +Key, +Current, +Value
	    float_format/2,			% -Old, +New
	    program_parameters/1,		% -Argv
	    user_parameters/1,			% -Argv
	    match/2,				% +Mask, +Atom
	    match/3,				% +Mask, +Atom, ?Replacements
	    lower_upper/2,			% ?Lower, ?Upper
	    current_error/1,			% -Stream
	    writeq_atom/2,			% +Term, -Atom
	    write_atom/2,			% +Term, -Atom
	    write_formatted_atom/3,		% -Atom, +Format, +ArgList
	    write_formatted/2,			% +Format, +ArgList
	    write_formatted/3,			% +Stream, +Format, +ArgList
	    atom_part/4,			% +Atom, +Pos, +Len, -Sub
	    atom_prefix/3,			% +Atom, +Len, -Sub
	    atom_suffix/3,			% +Atom, +Len, -Sub
	    atom_split/3,			% +Atom, +Delimiter, ?Subatoms
	    if_concat_atom/2,			% +List, ?Atom
	    if_concat_atom/3,			% +List, +Delimiter, ?Atom
	    getchar/3,				% +Atom, +Pos, -Char
	    parse_atom/6,			% +Atom, +StartPos, ?EndPos,
						% ?Term, ?VarList, ?Error
	    index/3,				% +Atom, +String, -Position
	    list_length/2,			% +List, ?Length
	    load/1,				% :FileName
%	    unload/1,				% +Module
	    file_test/2,			% +File, +Mode
	    filepos/2,				% @Stream, -Line
	    filepos/3,				% @Stream, -Line, -Column
	    getcwd/1,				% -Dir
	    assign_alias/2,			% +Alias, @Stream
	    get_until/3,			% +SearchChar, ?Text, ?EndChar
	    get_until/4,			% @In, +SearchChar, ?Text, ?EndChar
	    for/3,				% +Start, ?Counter, +End
	    prolog_version/1,                   % -Atom
	    proroot/1,				% -Atom
	    system_name/1,			% -Atom
	    localtime/9,			% +Time, ?Year, ?Month,
						% ?Day, ?DoW, ?DoY,
						% ?Hour, ?Min, ?Sec

	    asserta_with_names/2,		% @Term, +VarNames
	    assertz_with_names/2,		% @Term, +VarNames
	    clause_with_names/3,		% ?Head, ?Body, ?VarNames
	    retract_with_names/2,		% ?Clause, ?VarNames
	    predicate_type/2,			% @Predicate, ?Type
	    current_visible/2,			% @Module, @Predicate
	    current_signal/2,			% ?Signal, ?Mode
	    digit/1,				% +Character
	    letter/1,				% +Character

	    current_global/1,			% +Name
	    get_global/2,			% +Name, ?Value
	    set_global/2,			% +Name, ?Value
	    unset_global/1,			% +Name

	    current_default_module/1,		% -Module
	    set_default_module/1,		% +Module

	    op(1150, fx, (meta)),
	    op(1150, fx, (export)),
	    op(100, xfx, @),
	    op(900, xfx, =>),
	    op(900,  fy, not)
	  ]).
:- use_module(library(debug)).
:- use_module(library(arithmetic)).
:- use_module(library(memfile)).
:- use_module(library(apply)).
:- set_prolog_flag(double_quotes, codes).

/** <module> IF/Prolog compatibility package

This library realises emulation of IF/Prolog.  As with all the emulation
layers in the dialect directory, the   emulation has been established on
`as needed' basis from porting programs. This implies that the emulation
is incomplete. Emumated directives, predicates   and libraries are often
not 100% compatible with the IF/Prolog version.

Note that this emulation layer targets primarily IF/Prolog version 5.

Please   help   extending   this   library   and   submit   patches   to
bugs@swi-prolog.org.
*/

:- module_transparent
	calling_context/1.

:- meta_predicate
	context(0, +),
	block(0, +, 0),
	modify_mode(:, -, +),
	debug_mode(:, -, +),
	ifprolog_debug(0),
	load(:),
	asserta_with_names(:, +),
	assertz_with_names(:, +),
	clause_with_names(:, -, -),
	retract_with_names(:, -),
	predicate_type(:, -),
	current_global(:),
	get_global(:, -),
	set_global(:, +),
	unset_global(:).


		 /*******************************
		 *	     EXPANSION		*
		 *******************************/

:- multifile
	user:goal_expansion/2,
	user:term_expansion/2,
	user:file_search_path/2,
	user:prolog_file_type/2,
	ifprolog_goal_expansion/2,
	ifprolog_term_expansion/2.
:- dynamic
	user:goal_expansion/2,
	user:term_expansion/2,
	user:file_search_path/2,
	user:prolog_file_type/2.

:- dynamic
	in_module_interface/1.

user:goal_expansion(In, Out) :-
	prolog_load_context(dialect, ifprolog),
	ifprolog_goal_expansion(In, Out).

user:term_expansion(In, Out) :-
	prolog_load_context(dialect, ifprolog),
	ifprolog_term_expansion(In, Out).

%%	ifprolog_goal_expansion(+In, +Out)
%
%	goal_expansion  rules  to   emulate    IF/Prolog   behaviour  in
%	SWI-Prolog. The expansions  below   maintain  optimization  from
%	compilation.   Defining   them   as   predicates   would   loose
%	compilation.

%%	context(:Goal, Handler)
%
%	Is  mapped  to  catch(Goal,  Error,    Recover)  is  Handler  is
%	=|error(_,_) => Recover|=. Other cases are   not  covered by the
%	emulation.

%%	asserta(Head,Body) is det.
%%	assertz(Head,Body) is det.
%%	retract(Head,Body) is det.
%
%	Mapped to asserta((Head:-Body)),  etc.  Note   that  this  masks
%	SWI-Prolog's asserta/2, etc.

ifprolog_goal_expansion(Module:Goal, Expanded) :-
	Module == system, nonvar(Goal), !,
	expand_goal(Goal, ExpandedGoal),
	head_pi(ExpandedGoal, PI),
	(   current_predicate(ifprolog:PI),
	    \+ predicate_property(ExpandedGoal, imported_from(_))
	->  Expanded = ifprolog:ExpandedGoal
	;   Expanded = ExpandedGoal
	).
ifprolog_goal_expansion(Goal, Expanded) :-
	if_goal_expansion(Goal, Expanded).

if_goal_expansion(context(Goal, [Error => Recover]),
		  catch(Goal, Error, Recover)) :-
	assertion(Error = error(_,_)).
if_goal_expansion(assertz(Head,Body),
		  assertz((Head:-Body))).
if_goal_expansion(asserta(Head,Body),
		  asserta((Head:-Body))).
if_goal_expansion(retract(Head,Body),
		  retract((Head:-Body))).
if_goal_expansion(Call@Module, call((Module:Goal)@Module)) :-
	nonvar(Call),
	Call = call(Goal).
if_goal_expansion(concat_atom(L,A), if_concat_atom(L,A)).
if_goal_expansion(concat_atom(L,D,A), if_concat_atom(L,D,A)).


head_pi(M:Head, M:PI) :- !,
	head_pi(Head, PI).
head_pi(Head, Name/Arity) :-
	functor(Head, Name, Arity).


%%	ifprolog_term_expansion(+In, +Out)
%
%	term_expansion  rules  to   emulate    IF/Prolog   behaviour  in
%	SWI-Prolog.

%%	meta(+ListOfPI)
%
%	Mapped  to  module_transparent/1.  Not  sure   whether  this  is
%	correct. It surely is not very elegant   to  map to a deprecated
%	feature.  Luckily,  although  the  module_transparent/1  API  is
%	deprecated, the underlying functionality is   still  core of the
%	module system.
%
%	Note that if :- meta  appears   inside  a  module interface, the
%	predicate is also exported.

%%	export(+ListOfPI) is det.
%%	discontiguous(+ListOfPI) is det.
%
%	Mapped to comma-lists

%%	module(+Name).
%%	begin_module(+Name).
%%	end_module(+Name).
%
%	These are emulated correctly,  provided   module/1  is the first
%	term of the file and the  implementation   is  part  of the same
%	file. Begin/end are ignored.

ifprolog_term_expansion((:- meta([])), []).
ifprolog_term_expansion((:- meta(List)),
			[ (:- module_transparent(Spec))
			| Export
			]) :-
	pi_list_to_pi_term(List, Spec),
	(   in_module_interface(_)
	->  Export = [(:- export(Spec))]
	;   Export = []
	).

ifprolog_term_expansion((:- export([])), []).
ifprolog_term_expansion((:- export(List)),
			(:- export(Spec))) :-
	is_list(List),
	pi_list_to_pi_term(List, Spec).

ifprolog_term_expansion((:- private(_)), []).

ifprolog_term_expansion((:- discontiguous([])), []).
ifprolog_term_expansion((:- discontiguous(List)),
			(:- discontiguous(Spec))) :-
	is_list(List),
	pi_list_to_pi_term(List, Spec).

ifprolog_term_expansion((:- multifile([])), []).
ifprolog_term_expansion((:- multifile(List)),
			(:- multifile(Spec))) :-
	is_list(List),
	pi_list_to_pi_term(List, Spec).

ifprolog_term_expansion((:- module(Name)),
			(:- module(Name, []))) :-
	asserta(in_module_interface(Name)).
ifprolog_term_expansion((:- begin_module(Name)), []) :-
	prolog_load_context(module, Loading),
	assertion(Name == Loading),
	retract(in_module_interface(Name)).
ifprolog_term_expansion((:- end_module(_)), []).
ifprolog_term_expansion((:- end_module), []).
ifprolog_term_expansion((:- nonotify), []).	% TBD: set verbosity


ifprolog_term_expansion((:- import(Module)),
			(:- use_module(File))) :-
	(   module_property(Module, file(File))
	->  true
	;   existence_error(module, Module)
	).
ifprolog_term_expansion((:- import(Module, ImportList)),
			(:- use_module(File, ImportList))) :-
	(   module_property(Module, file(File))
	->  true
	;   existence_error(module, Module)
	).

%%	pi_list_to_pi_term(+List, -CommaList) is det.

pi_list_to_pi_term([PI], PI) :- !.
pi_list_to_pi_term([H|T], (H,CommaList)) :-
	pi_list_to_pi_term(T, CommaList).

                 /*******************************
                 *          LIBRARY SETUP       *
                 *******************************/

%%      push_ifprolog_library
%
%       Pushes searching for dialect/ifprolog in   front of every library
%       directory that contains such as sub-directory.

push_ifprolog_library :-
        (   absolute_file_name(library(dialect/ifprolog), Dir,
                               [ file_type(directory),
                                 access(read),
                                 solutions(all),
                                 file_errors(fail)
                               ]),
            asserta((user:file_search_path(library, Dir) :-
                    prolog_load_context(dialect, ifprolog))),
            fail
        ;   true
        ).

%%	push_ifprolog_file_extension
%
%	Looks for .pro files before looking for .pl files if the current
%	dialect is =pro=. If the dialect is   not active, the .pro files
%	are found as last resort.

push_ifprolog_file_extension :-
	asserta((user:prolog_file_type(pro, prolog) :-
		prolog_load_context(dialect, ifprolog))).

user:prolog_file_type(pro, prolog) :-
	\+ prolog_load_context(dialect, ifprolog).

:- push_ifprolog_library,
   push_ifprolog_file_extension.


		 /*******************************
		 *	    PREDICATES		*
		 *******************************/

%%	calling_context(-Context)
%
%	Mapped to context_module/1.

calling_context(Context) :-
	context_module(Context).

%%	context(:Goal, +Mapping)
%
%	IF/Prolog context/2 construct. This is  the true predicate. This
%	is normally mapped by goal-expansion.
%
%	@bug	Does not deal with IF/Prolog signal mapping

context(M:Goal, Mapping) :-
	member(Error => Action, Mapping),
	nonvar(Error),
	Error = error(_,_), !,
	catch(M:Goal, Error, Action).
context(M:Goal, _Mapping) :-
	M:Goal.

%%	block(:Goal, +Tag, :Recovery).
%%	exit_block(+Tag).
%%	cut_block(+Tag) is semidet.
%
%	The control construct block/3 runs Goal in a block labelled Tag.
%	If Goal calls exit_block/1 using a   matching Tag, the execution
%	of Goal is abandoned  using   exception  handling  and execution
%	continues by running Recovery.  Goal   can  call cut_block/1. If
%	there is a block with matching   Tag,  all choice points created
%	since the block was started are destroyed.
%
%	@bug	The block control structure is implemented on top of
%		catch/3 and throw/1.  If catch/3 is used inside Goal,
%		the user must ensure that either (1) the protected
%		goal does not call exit_block/1 or cut_block/1 or (2)
%		the _Catcher_ of the catch/3 call does *not* unify with
%		a term block(_,_).

block(Goal, Tag, Recovery) :-
	prolog_current_choice(Choice),
	catch(Goal, block(Tag, Choice), Recovery).

exit_block(Tag) :-
	throw(block(Tag, _)).

cut_block(Tag) :-
	prolog_current_frame(Frame),
	findall(Choice,			% use findall/3 to avoid binding
		prolog_frame_attribute(
		    Frame, parent_goal,
		    system:catch(_, block(Tag, Choice), _)),
		[Choice]),
	nonvar(Choice),
	prolog_cut_to(Choice).

%%	modify_mode(+PI, -OldMode, +NewMode) is det.
%
%	Switch between static and  dynamic   code.  Fully supported, but
%	notably changing static to dynamic code   is  not allowed if the
%	predicate has clauses.

modify_mode(PI, OldMode, NewMode) :-
	pi_head(PI, Head),
	old_mode(Head, OldMode),
	set_mode(PI, OldMode, NewMode).

old_mode(Head, Mode) :-
	(   predicate_property(Head, dynamic)
	->  Mode = on
	;   Mode = off
	).

set_mode(_, Old, Old) :- !.
set_mode(PI, _, on) :- !,
	dynamic(PI).
set_mode(PI, _, off) :-
	compile_predicates([PI]).

pi_head(M:PI, M:Head) :- !,
	pi_head(PI, Head).
pi_head(Name/Arity, Term) :-
	functor(Term, Name, Arity).

%%	debug_mode(:PI, -Old, +New)
%
%	Old is not unified.  Only  New  ==   off  is  mapped  to disable
%	debugging of a predicate.

debug_mode(PI, _, off) :- !,
	'$hide'(PI).
debug_mode(_, _, on).

%%	ifprolog_debug(:Goal)
%
%	Map IF/Prolog debug(Goal)@Module. This should  run Goal in debug
%	mode. We rarely needs this type of measures in SWI-Prolog.

ifprolog_debug(Goal) :-
	Goal.

%%	debug_config(+Key, -Current, +Value)
%
%	Ignored.  Prints a message.

debug_config(Key,Current,Value) :-
	print_message(informational, ignored(debug_config(Key,Current,Value))).

%%	float_format(-Old, +New)
%
%	Ignored. Prints a message. Cannot   be emulated. Printing floats
%	with a specified precision can only be done using format/2.

float_format(Old, New) :-
	print_message(informational, ignored(float_format(Old, New))).

%%	program_parameters(-List:atom)
%
%	All command-line argument, including the executable,

program_parameters(Argv) :-
	current_prolog_flag(os_argv, Argv).

%%	user_parameters(-List:atom)
%
%	Parameters after =|--|=.

user_parameters(Argv) :-
	current_prolog_flag(argv, Argv).

%%	match(+Mask, +Atom) is semidet.
%
%	Same as once(match(Mask, Atom, _Replacements)).

match(Mask, Atom) :-
	match(Mask, Atom, _), !.

%%	match(+Mask, +Atom, ?Replacements) is nondet.
%
%	Pattern matching. This emulation  should   be  complete.  Can be
%	optimized using caching of  the   pattern-analysis  or doing the
%	analysis at compile-time.

match(Mask, Atom, Replacements) :-
	atom_codes(Mask, MaskCodes),
	atom_codes(Atom, Codes),
	phrase(match_pattern(Pattern), MaskCodes), !,
	pattern_goal(Pattern, Codes, Replacements, Goal),
	Goal.

pattern_goal([], [], [], true).
pattern_goal([string(String)|T], Codes, Replacements, Goal) :- !,
	append(String, Rest, Codes),
	pattern_goal(T, Rest, Replacements, Goal).
pattern_goal([star|T], Codes, [Atom|Replacements], Goal) :-
	append(Replacement, Rest, Codes),
	Goal = (atom_codes(Atom, Replacement),Goal2),
	pattern_goal(T, Rest, Replacements, Goal2).
pattern_goal([set(S)|T], [C|Rest], [Atom|Replacements], Goal) :-
	memberchk(C, S), !,
	Goal = (char_code(Atom, C),Goal2),
	pattern_goal(T, Rest, Replacements, Goal2).
pattern_goal([any|T], [C|Rest], [Atom|Replacements], Goal) :-
	Goal = (char_code(Atom, C),Goal2),
	pattern_goal(T, Rest, Replacements, Goal2).

match_pattern([set(S)|T]) -->
	"[",
	match_set(S), !,
	match_pattern(T).
match_pattern([string(List)|T]) -->
	non_special(List),
	{ List \== [] }, !,
	match_pattern(T).
match_pattern([star|T]) -->
	"*", !,
	match_pattern(T).
match_pattern([any|T]) -->
	"?", !,
	match_pattern(T).
match_pattern([]) --> [].

match_set([]) --> "]", !.
match_set(L) -->
	[C0], "-", [C1],
	{ C1 \= 0'],
	  C0 =< C1,
	  numlist(C0, C1, Range),
	  append(Range, T, L)
	},
	match_set(T).
match_set([C|L]) -->
	[C],
	match_set(L).

non_special([H|T]) -->
	[H],
	{ \+ special(H) }, !,
	non_special(T).
non_special([]) --> [].

special(0'*).
special(0'?).
special(0'[).

%%	lower_upper(+Lower, -Upper) is det.
%%	lower_upper(-Lower, +Upper) is det.
%
%	Multi-moded combination of upcase_atom/2 and downcase_atom/2.


lower_upper(Lower, Upper) :-
	nonvar(Lower), !,
	upcase_atom(Lower, Upper).
lower_upper(Lower, Upper) :-
	downcase_atom(Upper, Lower).

%%	load(File)
%
%	Mapped to consult.  I think that the compatible version should
%	only load .qlf (compiled) code.

load(File) :-
	consult(File).

%%	unload(+Module) is det.
%
%	Unload the named module.
%
%	@bug: What to do with modules that are not associated to a
%	file?

unload(Module) :-
	module_property(Module, file(File)), !,
	unload_file(File).
unload(_Module) :-
	assertion(fail).

%%	file_test(+File, +Mode)
%
%	Mapped to access_file/2 (which understand more modes). Note that
%	this predicate is defined in the   module  =system= to allow for
%	direct calling.

file_test(File, Mode) :-
	access_file(File, Mode).

%%	filepos(@Stream, -Line)
%
%	from  the  IF/Prolog  documentation    The  predicate  filepos/2
%	determines the current line  position   of  the  specified input
%	stream and unifies the  result  with   Line.  The  current  line
%	position is the number of line processed + 1

filepos(Stream, Line) :-
	line_count(Stream, L),
	Line is L + 1.


%%	getcwd(-Dir)
%
%	The predicate getcwd/1 unifies Dir with the full pathname of the
%	current working directory.

getcwd(Dir) :-
	working_directory(Dir, Dir).

%%	filepos(@Stream, -Line, -Column)
%
%	from  the  IF/Prolog  documentation    The  predicate  filepos/2
%	determines the current line  position   of  the  specified input
%	stream and unifies the  result  with   Line.  The  current  line
%	position is the number of line processed + 1

filepos(Stream, Line, Column) :-
	line_count(Stream, L),
	line_position(Stream, C),
	Line is L + 1,
	Column is C + 1.

%%	assign_alias(+Alias, @Stream) is det.
%

assign_alias(Alias, Stream) :-
	set_stream(Stream, alias(Alias)).

%%	writeq_atom(+Term, -Atom)
%
%	Use writeq/1 to write Term to Atom.

writeq_atom(Term, Atom) :-
	with_output_to(atom(Atom), writeq(Term)).

%%	write_atom(+Term, -Atom)
%
%	Use write/1 to write Term to Atom.

write_atom(Term, Atom) :-
	with_output_to(atom(Atom), write(Term)).

%%	current_error(-Stream)
%
%	Doesn't exist in SWI-Prolog, but =user_error= is always an alias
%	to the current error stream.

current_error(user_error).


		 /*******************************
		 *	  FORMATTED WRITE	*
		 *******************************/

%%	write_formatted_atom(-Atom, +Format, +ArgList) is det.
%%	write_formatted(+Format, +ArgList) is det.
%%	write_formatted(@Stream, +Format, +ArgList) is det.
%
%	Emulation of IF/Prolog formatted write.   The  emulation is very
%	incomplete. Notable asks for dealing with aligned fields, etc.
%
%	@bug	Not all format characters are processed
%	@bug    Incomplete processing of modifiers, fieldwidth and precision
%	@tbd	This should become goal-expansion based to process
%		format specifiers at compile-time.

write_formatted_atom(Atom, Format, ArgList) :-
	with_output_to(atom(Atom), write_formatted(Format, ArgList)).

write_formatted(Format, ArgList) :-
	write_formatted(current_output, Format, ArgList).

write_formatted(Out, Format, ArgList) :-
	atom_codes(Format, Codes),
	phrase(format_string(FormatCodes), Codes), !,
	string_codes(FormatString, FormatCodes),
	format(Out, FormatString, ArgList).

format_string([]) --> [].
format_string(Fmt) -->
	"%", format_modifiers(Flags, FieldLen, Precision), [IFC], !,
	{   map_format([IFC], Flags, FieldLen, Precision, Repl)
	->  append(Repl, T, Fmt)
	;   print_message(warning, ifprolog_format(IFC)),
	    %backtrace(20),
	    T = Fmt
	},
	format_string(T).
format_string([H|T]) -->
	[H],
	format_string(T).

map_format(Format, [], default, default, Mapped) :- !,
	map_format(Format, Mapped).
map_format(Format, Flags, Width, Precision, Mapped) :-
	integer(Width), !,			% left/right aligned in Width
	map_format(Format, Field),
	format_precision(Precision, Field, PrecField),
	fill_code(Flags, [Fill]),
	(   memberchk(-, Flags)			% left aligned
	->  format(codes(Mapped), '~~|~s~~`~ct~~~d+', [PrecField, Fill, Width])
	;   format(codes(Mapped), '~~|~~`~ct~s~~~d+', [Fill, PrecField, Width])
	).
map_format(Format, Flags, _, _, Mapped) :-
	memberchk(#, Flags),
	can_format(Format, Mapped), !.
map_format(Format, _, _, Precision, Mapped) :-
	map_format(Format, Field),
	format_precision(Precision, Field, Mapped).

can_format("o", "0~8r").
can_format("x", "0x~16r").
can_format("X", "0x~16R").
can_format("w", "~k").

map_format("t", "~w").
map_format("q", "~q").
map_format("s", "~a").
map_format("f", "~f").
map_format("e", "~e").
map_format("E", "~E").
map_format("g", "~G").
map_format("d", "~d").
map_format("x", "~16r").
map_format("o", "~8r").
map_format("X", "~16R").
map_format("O", "~8R").
map_format("c", "~c").
map_format("%", "%").

have_precision("d").
have_precision("D").
have_precision("e").
have_precision("E").
have_precision("f").
have_precision("g").
have_precision("G").

format_precision(N, [0'~|C], [0'~|Field]) :-
    integer(N),
    have_precision(C),
    !,
    format(codes(Field), '~d~s', [N, C]).
format_precision(_, Field, Field).

fill_code(Flags, "0") :- memberchk(0, Flags), !.
fill_code(_,     " ").

%%	format_modifiers(-Flags, -FieldLength, -Precision) is det.
%
%	Read the IF/Prolog format modifiers. We currently do not process
%	any of the modifiers! Some code seems to be using e.g. %07lx. We
%	assume this is the same as -07x (assuming l=left).

format_modifiers(Flags, FieldLength, Precision) -->
	format_flags(Flags0),
	digits(FieldLengthDigits),
	{   FieldLengthDigits == []
	->  FieldLength = default
	;   number_codes(FieldLength, FieldLengthDigits)
	},
	(   "."
	->  digits(PrecisionDigits),
	    { number_codes(Precision, PrecisionDigits) }
	;   { Precision = default }
	),
	opt_alignment(Flags0, Flags).

format_flags([H|T]) -->
	format_flag(H), !,
	format_flags(T).
format_flags([]) --> [].

format_flag(+) --> "+".		% Always prefix number with a sign
format_flag(-) --> "-".		% Left-justify
format_flag(space) --> " ".	% Space before positive numbers
format_flag(#) --> "#".		% Canonical output
format_flag(0) --> "0".		% Use leading 0 for integers

digits([D0|T]) -->
	digit(D0), !,
	digits(T).
digits([]) --> [].

digit(D) --> [D], {between(0'0, 0'9, D)}.

opt_alignment(L, [-|L]) --> "l", !.
opt_alignment(L, L) --> [].


%%	get_until(+SearchChar, -Text, -EndChar) is det.
%%	get_until(@Stream, +SearchChar, -Text, -EndChar) is det.
%
%	Read input from Stream  until   SearchChar.  Unify  EndChar with
%	either SearchChar or the atom =end_of_file=.

get_until(SearchChar, Text, EndChar) :-
	get_until(current_input, SearchChar, Text, EndChar).

get_until(In, SearchChar, Text, EndChar) :-
	get_char(In, C0),
	get_until(C0, In, SearchChar, Codes, EndChar),
	atom_chars(Text, Codes).

get_until(C0, _, C0, [], C0) :- !.
get_until(end_of_file, _, _,  [], end_of_file) :- !.
get_until(C0, In, Search, [C0|T], End) :-
	get_char(In, C1),
	get_until(C1, In, Search, T, End).


		 /*******************************
		 *	      PARSE		*
		 *******************************/

%%	atom_part(+Atom, +Pos, +Len, -Sub) is det.
%
%	True when Sub is part  of   the  atom [Pos,Pos+Len). Unifies Sub
%	with '' if Pos or Len is out of range!?

atom_part(_, Pos, _, Sub) :-
	Pos < 1, !,
	Sub = ''.
atom_part(_, _, Len, Sub) :-
	Len < 1, !,
	Sub = ''.
atom_part(Atom, Pos, _, Sub) :-
	atom_length(Atom, Len),
	Pos > Len, !,
	Sub = ''.
atom_part(Atom, Pos, Len, Sub) :-
	Pos >= 1,
	Pos0 is Pos - 1,
	atom_length(Atom, ALen),
	Len0 is min(Len, ALen-Pos0),
	sub_atom(Atom, Pos0, Len0, _, Sub).

%%	atom_prefix(+Atom, +Len, -Sub) is det.
%
%	Unifies Sub with the atom formed by  the first Len characters in
%	atom.
%
%	 - If Len < 1, Sub is unified with the null atom ''.
%	 - If Len > length of Atom, Sub is unified with Atom.

atom_prefix(_, Len, Sub) :-
	Len < 1, !,
	Sub = ''.
atom_prefix(Atom, Len, Sub) :-
	atom_length(Atom, AtomLen),
	Len > AtomLen, !,
	Sub = Atom.
atom_prefix(Atom, Len, Sub) :-
	sub_atom(Atom, 0, Len, _, Sub).

%%	atom_suffix(+Atom, +Len, -Sub) is det.
%
%	Unifies Sub with the atom formed by   the last Len characters in
%	atom.
%
%	  - If Len < 1, Sub is unified with the null atom ''.
%	  - If Len > length of Atom, Sub is unified with Atom.

atom_suffix(_, Len, Sub) :-
	Len < 1, !,
	Sub = ''.
atom_suffix(Atom, Len, Sub) :-
	atom_length(Atom, AtomLen),
	Len > AtomLen, !,
	Sub = Atom.
atom_suffix(Atom, Len, Sub) :-
	atom_length(Atom, AtomLen),
	Pos is AtomLen - Len,
	sub_atom(Atom, Pos, Len, _, Sub).

%%	atom_split( +Atom, +Delimiter, ?Subatoms )
%
%	Split Atom over Delimiter and unify the parts with Subatoms.

atom_split(Atom, Delimiter, Subatoms)  :-
	atomic_list_concat(Subatoms, Delimiter, Atom).

%%	if_concat_atom(+List, +Delimiter, -Atom) is det.
%
%	True when Atom is the concatenation of   the lexical form of all
%	elements from List, using Delimiter to delimit the elements.
%
%	The behavior of this  ifprolog   predicate  is  different w.r.t.
%	SWI-Prolog in two respect: it supports   arbitrary terms in List
%	rather than only atomic and it does _not_ work in mode -,+,+.

if_concat_atom(List, Delimiter, Atom) :-
	maplist(write_term_to_atom, List, AtomList),
	atomic_list_concat(AtomList, Delimiter, Atom).

write_term_to_atom(Term, Atom) :-
	(   atomic(Term)
	->  Atom = Term
	;   with_output_to(string(Atom), write(Term))
	).

%%	if_concat_atom(+List, -Atom) is det.
%
%	True when Atom is the concatenation of   the lexical form of all
%	elements  from  List.  Same  as  if_concat_atom/3  using  ''  as
%	delimiter.

if_concat_atom(List, Atom) :-
	maplist(write_term_to_atom, List, AtomList),
	atomic_list_concat(AtomList, Atom).

%%	getchar(+Atom, +Pos, -Char)
%
%	Unifies Char with the Position-th character in Atom
%	If Pos < 1 or Pos > length of Atom, then fail.

getchar(_, Pos, _) :-
	Pos < 1, !,
	fail.
getchar(Atom, Pos, _) :-
	atom_length(Atom, Len),
	Pos > Len, !,
	fail.
getchar(Atom, Pos, Char) :-
	P is Pos - 1,
	sub_atom(Atom, P, 1, _, Char).


%%	parse_atom(+Atom, +StartPos, ?EndPos, ?Term, ?VarList, ?Error)
%
%	Read from an atom.
%
%	@param StartPos is 1-based position to start reading
%	@param Error is the 1-based position of a syntax error or 0 if
%	       there is no error.

parse_atom(Atom, StartPos, EndPos, Term, VarList, Error) :-
	setup_call_cleanup(
	    ( atom_to_memory_file(Atom, MemF),
	      open_memory_file(MemF, read, In)
	    ),
	    ( StartPos0 is StartPos-1,
	      seek(In, StartPos0, bof, _),
	      catch(read_term(In, Term, [variable_names(VarList)]), E, true),
	      parse_atom_error(E, Error),
	      character_count(In, EndPos0),
	      EndPos is EndPos0+1
	    ),
	    ( close(In),
	      free_memory_file(MemF)
	    )).

parse_atom_error(Var, Pos) :-
	var(Var), !, Pos = 0.
parse_atom_error(error(_, stream(_Stream, _, _, Pos)), Pos1) :-
	Pos1 is Pos+1.


%%	index(+Atom, +String, -Position) is semidet.
%
%	True when Position is the first   occurrence  of String in Atom.
%	Position is 1-based.

index(Atom, String, Position) :-
	sub_string(Atom, Pos0, _, _, String), !,
        Position is Pos0 + 1.

%%	list_length(+List, ?Length) is det.
%
%	Deterministic version of length/2. Current implementation simply
%	calls length/2.

list_length(List, Length) :-
	length(List, Length).


		 /*******************************
		 *	      MISC		*
		 *******************************/

%%	for(+Start, ?Count, +End) is nondet.
%
%	Similar to between/3, but can count down if Start > End.

for(Start, Count, End) :-
	Start =< End, !,
	between(Start, End, Count).
for(Start, Count, End) :-
	nonvar(Count), !,
	between(End, Start, Count).
for(Start, Count, End) :-
	Range is Start-End,
	between(0, Range, X),
	Count is Start-X.

%%	prolog_version(-Version)
%
%	Return IF/Prolog simulated version string

prolog_version(Version) :-
	current_prolog_flag(version_data, swi(Major, Minor, Patch, _)),
	atomic_list_concat([Major, Minor, Patch], '.', Version).

%%	proroot(-Path)
%
%	True when Path is  the  installation   location  of  the  Prolog
%	system.

proroot(Path) :-
	current_prolog_flag(home, Path).

%%	system_name(-SystemName)
%
%	True when SystemName identifies the  operating system. Note that
%	this returns the SWI-Prolog =arch= flag,   and not the IF/Prolog
%	identifiers.

system_name(SystemName) :-
	current_prolog_flag(arch, SystemName).

%%	localtime(+Time, ?Year, ?Month, ?Day, ?DoW, ?DoY, ?Hour, ?Min, ?Sec)
%
%	Break system time into its components.  Deefines components:
%
%	  | Year    | Year number    | 4 digits        |
%	  | Month   | Month number   | 1..12           |
%	  | Day	    | Day of month   | 1..31           |
%	  | DoW	    | Day of week    | 1..7 (Mon-Sun)  |
%	  | DoY	    | Day in year    | 1..366          |
%	  | Hour    | Hours	     | 0..23           |
%	  | Min	    | Minutes	     | 0..59           |
%	  | Sec	    | Seconds	     | 0..59           |
%
%	Note that in IF/Prolog  V4,  Year  is   0..99,  while  it  is  a
%	four-digit number in IF/Prolog V5.  We emulate IF/Prolog V5.

localtime(TimeExpr, Year, Month, Day, DoW, DoY, Hour, Min, Sec) :-
	arithmetic_expression_value(TimeExpr, Time),
        stamp_date_time(Time, date(Year, Month, Day,
				   Hour, Min, SecFloat,
				   _Off, _TZ, _DST), local),
        Sec is floor(SecFloat),
	Date = date(Year,Month,Day),
	day_of_the_year(Date, DoY),
        day_of_the_week(Date, DoW).


%%	current_global(+Name) is semidet.
%%	get_global(+Name, ?Value) is det.
%%	set_global(+Name, ?Value) is det.
%%	unset_global(+Name) is det.
%
%	IF/Prolog  global  variables,  mapped    to   SWI-Prolog's  nb_*
%	predicates.

current_global(Name) :-
	gvar_name(Name, GName),
	nb_current(GName, _).

get_global(Name, Value) :-
	gvar_name(Name, GName),
	nb_getval(GName, Value).

set_global(Name, Value) :-
	gvar_name(Name, GName),
	nb_setval(GName, Value).

unset_global(Name) :-
	gvar_name(Name, GName),
	nb_delete(GName).

gvar_name(Module:Name, GName) :-
	atomic_list_concat([Module, :, Name], GName).


%%	current_default_module(-Module) is det.
%
%	Name of the toplevel typein module.

current_default_module(Module) :-
	'$current_typein_module'(Module).

%%	set_default_module(+Module) is det.
%
%	Set the default toplevel module.

set_default_module(Module) :-
	module(Module).


		 /*******************************
		 *	      DATABASE		*
		 *******************************/

:- dynamic
	names/2.

%%	asserta_with_names(@Clause, +VarNames) is det.
%%	assertz_with_names(@Clause, +VarNames) is det.
%%	clause_with_names(?Head, ?Body, -VarNames) is det.
%%	retract_with_names(?Clause, -VarNames) is det.
%
%	Predicates that manage  the  database   while  keeping  track of
%	variable names.

asserta_with_names(M:Clause, VarNames) :-
	term_varnames(Clause, VarNames, VarTerm),
	system:asserta(M:Clause, Ref),
	asserta(names(Ref, VarTerm)).
assertz_with_names(M:Clause, VarNames) :-
	term_varnames(Clause, VarNames, VarTerm),
	system:assertz(M:Clause, Ref),
	asserta(names(Ref, VarTerm)).

term_varnames(Term, VarNames, VarTerm) :-
	findall(Vars,
		( term_variables(Term, Vars),
		  bind_names(VarNames)
		),
		[ VarList ]),
	VarTerm =.. [ v | VarList ].

bind_names([]).
bind_names([Name=Var|T]) :-
	Name=Var,
	bind_names(T).


clause_with_names(M:Head, Body, VarNames) :-
	clause(M:Head, Body, Ref),
	(   names(Ref, VarTerm)
	->  term_variables((Head:-Body), Vars),
	    VarTerm =.. [v|NameList],
	    make_bindings(NameList, Vars, VarNames)
	;   VarNames = []
	).

retract_with_names(M:Term, VarNames) :-
	clause(M:Term, Ref),
	erase(Ref),
	(   retract(names(Ref, VarTerm))
	->  term_variables((Term), Vars),
	    VarTerm =.. [v|NameList],
	    make_bindings(NameList, Vars, VarNames)
	;   VarNames = []
	).

make_bindings([], [], []).
make_bindings([Name|NT], [Var|VT], [Name=Var|BT]) :-
	make_bindings(NT, VT, BT).


%%	predicate_type(:PI, -Type) is det.
%
%	True when Type describes the type  of   PI.  Note that the value
%	=linear= seems to mean you can use clause/2 on it, which is true
%	for any SWI-Prolog predicate that is  defined. Therefore, we use
%	it for any predicate that is defined.

predicate_type(M:Name/Arity, Type) :-
	functor(Head, Name, Arity),
	Pred = M:Head,
	(   (   predicate_property(Pred, built_in)
	    ;	predicate_property(Pred, foreign)
	    )
	->  Type = builtin
	;   predicate_property(Pred, imported_from(_))
	->  Type = imported
	;   predicate_property(Pred, dynamic)
	->  Type = linear
	;   control(Head)
	->  Type = control
	;   Name == call
	->  Type = control
	;   current_predicate(M:Name/Arity)
	->  Type = linear
	;   Type = undefined
	).

control((_,_)).
control((_;_)).
control((_->_)).
control((_*->_)).
control((!)).

%%	current_visible(@Module, @PredicateIndicator).
%
%	FIXME check with documentation

current_visible(Module, Name/Arity) :-
	atom(Name), integer(Arity), !,
	functor(Head, Name, Arity),
	predicate_property(Module:Head, visible).
current_visible(Module, Name/Arity) :-
	predicate_property(Module:Head, visible),
	functor(Head, Name, Arity).

%%	current_signal(?Signal, ?Mode) is nondet.
%
%	True when Mode is the current   mode  for handling Signal. Modes
%	are =on=, =off=,  =default=,  =ignore=.   Signals  are  =abort=,
%	=alarm=, =interrupt=, =pipe=, =quit=,   =termination=,  =user_1=
%	and =user_2=.
%
%	@tbd	Implement

current_signal(_,_) :- fail.


%%	digit(+A).
%
%	Is the character A a digit [0-9]
digit(A) :-
	char_type(A, digit).

%%	letter(+A).
%
%	Is the character A a letter [A-Za-z]
letter(A) :-
	char_type(A, alpha).

		 /*******************************
		 *	    ARITHMETIC		*
		 *******************************/

:- arithmetic_function(system:time/0).
:- arithmetic_function(system:trunc/1).
:- arithmetic_function(system:ln/1).
:- arithmetic_function(system:minint/0).
:- arithmetic_function(system:maxint/0).
:- arithmetic_function(system:dbsize/0).
:- arithmetic_function(system:dbused/0).
:- arithmetic_function(system:ssize/0).
:- arithmetic_function(system:gused/0).
:- arithmetic_function(system:lused/0).
:- arithmetic_function(system:tused/0).

system:time(Time) :-
	get_time(GetTime),
	Time is round(GetTime).  % Time in seconds since 1970-01-01 00:00:00 UTC
system:trunc(Val, Trunc) :-
	Trunc is truncate(Val).
system:ln(Val, Log) :-
	Log is log(Val).
system:minint(MinInt) :-
	MinInt is -1<<31.
system:maxint(MaxInt) :-
	MaxInt is 1<<31 - 1.
system:dbsize(0).
system:dbused(0).
system:ssize(Size) :-
	statistics(globallimit, Size).
system:gused(Size) :-
	statistics(globalused, Size).
system:lused(Size) :-
	statistics(localused, Size).
system:tused(Size) :-
	statistics(trailused, Size).


		 /*******************************
		 *	       MESSAGES		*
		 *******************************/

prolog:message(ifprolog_format(IFC)) -->
	[ 'Unknown specifier for write_formatted/3: ~c'-[IFC] ].


		 /*******************************
		 *	  COLOUR SUPPORT	*
		 *******************************/

:- multifile
	prolog_colour:style/2,
	prolog_colour:goal_colours/2.

prolog_colour:goal_colours(meta(_),
			   ifprolog-[predicates]).
prolog_colour:goal_colours(private(_),
			   ifprolog-[predicates]).
prolog_colour:goal_colours(import(Module,_),
			   ifprolog-[module(Module),predicates]).
prolog_colour:goal_colours(begin_module(Module),
			   ifprolog-[module(Module)]).
prolog_colour:goal_colours(end_module(Module),
			   ifprolog-[module(Module)]).
prolog_colour:goal_colours(end_module,
			   ifprolog-[]).
prolog_colour:goal_colours(nonotify,
			   ifprolog-[]).

prolog_colour:style(goal(ifprolog,_), [ colour(blue), background(lightcyan) ]).
