Intoduction
We recently started porting formerly Jekejeke Prolog to the 100% Prolog Novacore from Dogelog Player. The current state is that formerly Jekejeke Prolog is now 50% Prolog, and we are continuing the transformation.
Formerly Jekejeke Prolog did have a natively written Interactive Debugger. Dogelog Player does not yet have an Interactive Debugger. So we started an experiment of writing a non-native Interactive Debugger.
Vanilla Interpreter
The Jekejeke Prolog debugger was compilation based to gain speed. It used clauses that had a fork, one side of the fork was normal compiled code, and the other side was instrumented compiled code. The instrumentation were calls to built-ins.
For Dogelog Player we less emphasize speed and see some benefit in a meta interpreter based approach, which would be independent of the existing compilation scheme. Such a debugger is for example found in Prolog Café by Banbara & Tamura (2005).
solve(V) :- var(V), throw(error(instantiation_error,_)).
solve(true) :- !.
solve(call(A)) :- !, solve(A).
solve((A,B)) :- !, solve(A), solve(B).
solve((A;B)) :- !, (solve(A); solve(B)).
solve(H) :- defined(H), !, rule(H, B), solve(B).
solve(H) :- H.
The Prolog programming language is very well suited to write meta interpreters since it is homoiconic, one can easily switch between code and data. The above shows a so called Vanilla interpreter that can execute built-ins as well.
Cut Handling
The above interpreter cannot be implemented in ISO core standard. The predicate defined/1 is some sort of predicate property predicate which would require the ISO module standard. Further rule/2 is a clause/2 variant, that needs to also work for static predicates.
To realize cut handling in the meta interpreter we rely on more non-standard predicates that are gladly available in Dogelog Player. Prolog Cafe uses '$get_current_B'(B) and '$cut'(B) with reference to the WAM. Since its inception Dogelog Player has '$MARK'(B) and '$CUT'(B).
solve(A) :- '$MARK'(M), solve(A, M).
solve(V, _) :- var(V), throw(error(instantiation_error,_)).
solve(true, _) :- !.
solve(!, M) :- !, '$CUT'(M).
solve(call(A), _) :- !, solve(A).
solve((A,B), M) :- !, solve(A, M), solve(B, M).
solve((A;B), M) :- !, (solve(A, M); solve(B, M)).
solve(H, _) :- defined(H), !, '$MARK'(M), rule(H, B), solve(B, M).
solve(H, _) :- H.
The above extended vanilla interpreter models well the cut transparency of the conjunction (',')/2 and disjunction (;)/2, as well as the fact that cuts do not permeate through call/1. The vanilla interpreter can be furter extended to support if-then-else as well and the local cut inside (->)/2.
4-Ports Non-Determinism
Logging the execution of a Prolog text via the 4 Port Model described by Lawrence Byrd in 1980 is still popular. Our Book of the Dead for the Model is based on formerly Jekejeke Prolog, developed up to 2015, which is currently subject to a pathological examination.
The Vanilla interpreter can be turned into a tracer by adding port prompts. We placed the prompts around the meta interpretation of the clauses of a defined predicate. One will first see only two calls, entering and exiting the clauses of a defined predicate:
trace(H, _) :- defined(H), !,
in(H),
'$MARK'(M), rule(H, B), trace(B, M),
out(H).
An element of backtracking is that Prolog code is kind of executed in reverse order. While a sequence in normal execution is execution from left to right, during backtracking jumps from right to left happen. We use backtacking to double the shown ports, here an example from in/1:
in(H) :-
prompt('CALL', H).
in(H) :-
prompt('FAIL', H), fail.
This quick result is indeed an Interactive Debugger:
2-Ports Determinism
Modern Prolog systems like SWI-Prolog, and our passed away Prolog system formerly Jekejeke Prolog do detect determinism. Determinism in the form that the predicate didn't generate some choice points, leads to an annoying prompt of a REDO and FAIL pair for every such Byrd box.
We can use '$MARK'/1 itself to detect such determinism, and then cut away the REDO and FAIL pair. The corresponding Prolog code inside the meta interpreter reads as follows. The debugging model now reduces from 4-Ports CALL, EXIT, REDO, FAIL to 2-Ports CALL, EXIT:
trace(H, _) :- defined(H), !,
in(H),
'$MARK'(M), rule(H, B), trace(B, M), '$MARK'(J),
out(H),
(M == J -> !; true).
The result is less sore to the eye:
The presented meta interpreter has a few downsides, which might stress the unterlying Prolog system. For example the tail out/1 in the non-smart version destroys tail recursion optimization, in case the Prolog system attempts that. It gets even worse with the determinism check.
On the other hand the determinism check has also some benefit, in that a debugged predicate will leave no choice points, where the non-debugged predicate also left no choice points. This is why the answer substitution display in the fac/2 example could stop prompting for a semicolon ;.
We will mostlikely ship this Interactive Debugger with the next release 1.3.4 of Dogelog Player. Commands such as l(eap), s(kip) and t(race) are already implement. An immediate priority wont be source level debugging, but rather hiding the details of the system and libraries while debugging.
Conclusions
Our pathological examination of formerly Jekejeke Prolog lead to an Interactive Debugger that can handle cuts, but that is also smart enough to switch from 4-Port model to 2-Port model. We will mostlikely ship it, despite some loose ends like hiding system and library details.
Prolog Cafe: A Prolog-to-Java source-to-source translator system
https://gerrit.googlesource.com/prolog-cafe/
Dogelog Player goes Multi Threading
https://medium.com/@janburse_2989/6720695470fe


