:- module( cleanup, [ call_cleanup/2, call_cleanup/1, on_cleanup/1, cleanup_all/0, op(1150, fx,fragile) ]). %% @defgroup Cleanup Call Cleanup % @ingroup YAPLibrary % @{ % % call_cleanup/1 and call_cleanup/2 allow predicates to register % code for execution after the call is finished. Predicates can be % declared to be fragile to ensure that call_cleanup is called % for any Goal which needs it. This library is loaded with the % `use_module(library(cleanup))` command. % % 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. :- multifile user:goal_expansion/3. :- user_defined_directive(fragile(G), cleanup:cleanup_expansion(G)). :- 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 /** @pred 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. There are some private predicates which could be used in special cases, such as manually setting up cleanup-contexts and registering CleanUpGoals for other than the current cleanup-context. Read the Source Luke. */ cleanup_all :- do_cleanup(1). cleanup_all. % register a cleanup predicate (normal reverse-order cleanup) /** @pred on_cleanup(+ _CleanUpGoal_) Any Predicate might registers a _CleanUpGoal_. The _CleanUpGoal_ is put onto the current cleanup context. All such CleanUpGoals are executed in reverse order of their registration when the surrounding cleanup-context ends. This call will throw an exception if a predicate tries to register a _CleanUpGoal_ outside of any cleanup-context. */ 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). /** @} */