% Meta-interpreter to internalize backtracking and unification
% Author: Frank Pfenning
:- op(200, xfy, '\\').  % for queues in infix notation

% meta-interpreter succeeds either way and prints
% either "success" or "failure", allows multiple solutions
%
% declare example predicates dynamic so they can be matched with clause/2

% solve(G, Gamma, S, F, N, J)
% Gamma |- G / S / F, N is next free variable, J = success or failure
% Gamma must already be residuated
solve(top, _, top, _, _, success).
solve(top, Gamma, top, F, N, J) :-  % fail from success for more solns
	solve(bot, Gamma, top, F, N, J).
solve(top, Gamma, and(G, S), F, N, J) :-
	solve(G, Gamma, S, F, N, J).
solve(and(G1, G2), Gamma, S, F, N, J) :-
	solve(G1, Gamma, and(G2, S), F, N, J).
solve(bot, _, _, bot, _, failure).
solve(bot, Gamma, _, or(and(G2, S), F), N, J) :-
	solve(G2, Gamma, S, F, N, J).
solve(or(G1, G2), Gamma, S, F, N, J) :-
	solve(G1, Gamma, S, or(and(G2, S), F), N, J).
solve(equal(T1, T2), Gamma, S, F, N, J) :-
	unify(T1, T2, Theta) ->
	( apply_prop(S, Theta, STheta),
	  solve(top, Gamma, STheta, F, N, J) )
      ;
	solve(bot, Gamma, S, F, N, J).
solve(exists(X, G), Gamma, S, F, N, J) :-
	apply_prop(G, [for(var(N),X)], G1),
	N1 is N+1,
	solve(G1, Gamma, S, F, N1, J).
solve(app(Pred, Ts), Gamma, S, F, N, J) :-
	length(Ts, Arity),
	memberchk(prog(Pred/Arity, implies(G, app(Pred, Args))), Gamma),
	zip_subst(Ts, Args, Theta),
	apply_prop(G, Theta, GTheta),
	solve(GTheta, Gamma, S, F, N, J).

% N = next free variable
% Gamma |- G, N is next free variable, J = success of failure
solve(G, Gamma, N, J) :- solve(G, Gamma, top, bot, N, J).

% prove(G, Gamma, S, F1\F2, N, J)
% Gamma |- G / S / FQ, N is next free variable, J = success or failure
% Gamma must already be residuated
% F2 is always a free variable which is the tail of F1 (as a failure cont)
prove(top, _, top, _, _, success).
prove(top, Gamma, top, FQ, N, J) :-  % fail from success for more solns
	prove(bot, Gamma, top, FQ, N, J).
prove(top, Gamma, and(G, S), FQ, N, J) :-
	prove(G, Gamma, S, FQ, N, J).
prove(and(G1, G2), Gamma, S, FQ, N, J) :-
	prove(G1, Gamma, and(G2, S), FQ, N, J).
prove(bot, _, _, bot \ bot, _, J) :- !, J = failure.  % commit to avoid next clause
prove(bot, Gamma, _, or(and(G2, S), F1) \ F2, N, J) :-
	prove(G2, Gamma, S, F1 \ F2, N, J).
prove(or(G1, G2), Gamma, S, F1 \ or(and(G2, S), F2), N, J) :-
	prove(G1, Gamma, S, F1 \ F2, N, J).
prove(equal(T1, T2), Gamma, S, FQ, N, J) :-
	unify(T1, T2, Theta) ->
	( apply_prop(S, Theta, STheta),
	  prove(top, Gamma, STheta, FQ, N, J) )
      ;
	prove(bot, Gamma, S, FQ, N, J).
prove(exists(X, G), Gamma, S, FQ, N, J) :-
	apply_prop(G, [for(var(N),X)], G1),
	N1 is N+1,
	prove(G1, Gamma, S, FQ, N1, J).
prove(app(Pred, Ts), Gamma, S, FQ, N, J) :-
	length(Ts, Arity),
	memberchk(prog(Pred/Arity, implies(G, app(Pred, Args))), Gamma),
	zip_subst(Ts, Args, Theta),
	apply_prop(G, Theta, GTheta),
	% push GTheta onto queue of failure continuations for fairness
	prove(or(bot,GTheta), Gamma, S, FQ, N, J).

% N = next free variable
% Gamma |- G, N is next free variable, J = success of failure
prove(G, Gamma, N, J) :- prove(G, Gamma, top, F \ F, N, J).

% zip_subst([T1,...,Tn], [X1,...,Xn], [T1/X1,...,Tn/Xn])
zip_subst([], [], []).
zip_subst([T|Ts], [var(X)|Xs], [for(T, X)| Theta]) :-
	zip_subst(Ts, Xs, Theta).

% transform program to residuated form
% resid_prog([Pred1/Arity1,...], [implies(G1, app(Pred1, Args1)), ...]).
% normal program does not quantify over argument variables
resid_prog([Pred/Arity|Preds],
	   [prog(Pred/Arity, implies(G, app(Pred, Args)))|Gamma]) :-
	reify(Pred/Arity, D),
	arg_vars(0, Arity, Args),
	resid(D, app(Pred, Args), G),
	resid_prog(Preds, Gamma).
resid_prog([], []).

% resid(D, P, G) if D |- P > G
resid(forall(bd(X), D), P, exists(bd(X), G)) :-
	resid(D, P, G).
resid(and(D1, D2), P, or(G1, G2)) :-
	resid(D1, P, G1),
	resid(D2, P, G2).
resid(top, _, bot).
resid(implies(G2, D1), P, and(G1, G2)) :-
	resid(D1, P, G1).
resid(app(Pred, Ss), P, equal(app(Pred, Ss), P)).

% arg_vars(N,Arity) = [var(arg(N)),...,var(arg(Arity-1))]
arg_vars(Arity, Arity, []).
arg_vars(N, Arity, [var(arg(N))|Args]) :-
	N < Arity,
	N1 is N+1,
	arg_vars(N1, Arity, Args).

% apply_prop(A, Theta, A Theta)
apply_prop(top, _, top).
apply_prop(and(A, B), Theta, and(ATheta, BTheta)) :-
	apply_prop(A, Theta, ATheta),
	apply_prop(B, Theta, BTheta).
apply_prop(bot, _, bot).
apply_prop(or(A, B),  Theta, or(ATheta, BTheta)) :-
	apply_prop(A, Theta, ATheta),
	apply_prop(B, Theta, BTheta).
apply_prop(implies(A, B), Theta, implies(ATheta, BTheta)) :-
	apply_prop(A, Theta, ATheta),
	apply_prop(B, Theta, BTheta).
apply_prop(equal(T, S), Theta, equal(TTheta, STheta)) :-
	apply(T, Theta, TTheta),
	apply(S, Theta, STheta).
apply_prop(forall(bd(X), A), Theta, forall(bd(X), ATheta)) :-
	% bd(X) notin dom(Theta) or cod(Theta)!
	apply_prop(A, Theta, ATheta).
apply_prop(exists(bd(X), A), Theta, exists(bd(X), ATheta)) :-
	% bd(X) notin dom(Theta) or cod(Theta)!
	apply_prop(A, Theta, ATheta).
apply_prop(app(Pred, Ts), Theta, app(Pred, TsTheta)) :-
	applys(Ts, Theta, TsTheta).
