% cleanup.yap
% Copyright (C) 2002 by Christian Thaeter
%
% public interface:
%
% :- fragile name/arity.
%       declares the predicate denoted by name/arity as fragile predicate.
%       Whenever such a fragile predicate is used in a query it will be
%       called through call_cleanup/1.
%
% call_cleanup(Goal).
% call_cleanup(Goal,CleanUpGoal).
%       Goal will be called in a cleanup-context, where any registered
%       CleanUpGoal inside of that context will be called when Goal is left,
%       either by a fail, cut or exeption.
%       It is possible to nest cleanup contexts.
%
% on_cleanup(CleanUpGoal).
%       registers CleanUpGoal to the current cleanup context.
%       CleanUpGoal's are executed in reverse order of their registration.
%       throws an exception if called outside of any cleanup-context.
%
% cleanup_all.
%       calls all pending CleanUpGoals and resets the cleanup-system to an initial state. 
%       should only be used as one of the last calls in the main program.
%
% hidden predicates:
% most private predicates could also be used in special cases, such as manually setting up cleanup-contexts.
% Read the Source.

:- module( cleanup, [
		call_cleanup/2,
		call_cleanup/1,
		on_cleanup/1,
		cleanup_all/0
	]).


:- multifile user:goal_expansion/3.

:- user_defined_directive(fragile(G), cleanup:cleanup_expansion(G)).
:- op(1150, fx,fragile).

:- meta_predicate
	call_cleanup(:,:),
	call_cleanup(:),
	on_cleanup(:),
	on_cleanup(?,:),
	on_cleanupz(:),
	on_cleanupz(?,:).


:- initialization(init_cleanup).
init_cleanup :-
	bb_put(expansion_toggle,1),
	\+ bb_get(cleanup_level,_),
	bb_put(cleanup_level,0).
	% TODO: would be nice to register cleanup_all into the
	% toplevel to be called after each query is finished
init_cleanup.

% call goal G  with a cleanup CL in a cleanup context
call_cleanup(G,CL) :-
	needs_cleanup(L),
	on_cleanup(L,CL),
	(
		catch(G,X,(do_cleanup(L),throw(X)))
	;
		do_cleanup(L)
	).


% call a goal G in a cleanup context
call_cleanup(G) :-
	needs_cleanup(L),
	(
		catch(G,X,(do_cleanup(L),throw(X)))
	;
		do_cleanup(L)
	).


% begin cleanup level
needs_cleanup(CL) :-
	bb_get(cleanup_level,L),
	CL is L + 1,
	bb_put(cleanup_level,CL).


cleanup_context(CL) :-
	bb_get(cleanup_level,CL).


% leave cleanup level, call all registred cleanup predicates within
do_cleanup(CL) :-
	CN is CL - 1,
	bb_put(cleanup_level,CN),
	next_cleanup(CL).

next_cleanup(CL) :-
	!,recorded(cleanup:handle,(L,G),R),
	CL =< L,
	erase(R),
	(call(G);true),
	next_cleanup(CL).

% clean up all remaining stuff / reinitialize cleanup-module
cleanup_all :-
	do_cleanup(1).
cleanup_all.

% register a cleanup predicate (normal reverse-order cleanup)
on_cleanup(G) :-
	bb_get(cleanup_level,L),
	on_cleanup(L,G).

on_cleanup(L,G) :-
	L =< 0,
	throw(error(instantiation_error,no_cleanup_context(G))).
on_cleanup(L,G) :-
	callable(G),
	recorda(cleanup:handle,(L,G),_).


% register a cleanup predicate (reverse-reverse-order cleanup)
on_cleanupz(G) :-
	bb_get(cleanup_level,L),
	on_cleanupz(L,G).

on_cleanupz(L,G) :-
	L =< 0,
	throw(no_cleanup_context(G)).
on_cleanupz(L,G) :-
	callable(G),
	recordz(cleanup:handle,(L,G),_).

% helpers
cleanup_expansion(X) :-
	var(X),!,throw(error(instantiation_error,fragile(X))).
cleanup_expansion((H,T)) :- !,cleanup_expansion(H),cleanup_expansion(T).
cleanup_expansion([H,T]) :- !, cleanup_expansion(H),
	( T = [] -> true ; cleanup_expansion(T) ).
cleanup_expansion(M:G/A) :-
 	atom(G),integer(A),!,
	compose_var_goal(G/A,GG),
        \+ user:goal_expansion(GG,M,call_cleanup(M:GG)),
	assert((   user:goal_expansion(GG,M,NG)
	       :-  bb_get(expansion_toggle,1)
	       ->  bb_put(expansion_toggle,0),
		   NG=call_cleanup(M:GG)
	       ;   bb_put(expansion_toggle,1),
		   NG=M:GG )).
cleanup_expansion(G/A) :-
       !,prolog_flag(typein_module,M),cleanup_expansion(M:G/A).
cleanup_expansion(X) :-
	!,throw(error(instantiation_error,fragile(X))).

compose_var_goal(G/A,NG) :-
	arity_to_vars(A,L), NG =.. [G|L].

arity_to_vars(N,L) :-
	arity_to_vars(N,[],L).
arity_to_vars(N,L1,L2) :-
	N > 0,
	NN is N-1,
	LT = [L|L1],
	arity_to_vars(NN,LT,L2).
arity_to_vars(0,L,L).