The duties of a foreign predicate are minimized in that the
interpreter automatically performs some housekeeping. The
housekeeping includes rolling back the variable creations and
instantiations made by the foreign predicate. The housekeeping
varies on whether the foreign predicate is deterministic or not.
It is based on two primitive operations that allow manipulating
the variable bindings and variable creations performed by a
predicate. Variable bindings are typically established during
unification whereas variable creations are needed for temporary
The binding and creation of variables can be viewed as filling a
log. It is possible to put a mark on the log via the method
markFrame(). The log is filled by methods such as unifyTerm(),
createVars(), etc... Via the method releaseFrame() the log can be
reset to the given mark. As a result all variable bindings and
variable creations from the top back to the mark are rolled back.
The log does not only support a single mark. It is possible to
create multiple marks and if needed one can rollback over multiple
The log is only a simplified view of what is provided by the
system. For example the body variable optimization will remove
unused variable bindings and variable creations between the last
mark and the top. It will thus reduce the size of the log without
following the two primitive operations markFrame() and
releaseFrame(). Operations for the body variable optimization are
currently not exposed in the Jekejeke Prolog API.
The system now keeps one important mark always on the current. It
is the tail recursion barrier. This mark indicates the variable
bindings and variable creations up to the last choice point.
Characteristic of a deterministic predicate is that it will not
create a new choice point and it will therefore never move the
tail recursion barrier. A deterministic predicate will either fail
or succeeds, but it will never be retried because of its lack of a
choice point. The system provides some automatic housekeeping for
When a deterministic predicate succeeds the system will automatically call the next goal in the continuation goal list. When a deterministic predicate fails the system will automatically retry the last choice point. When a choice point is tried it will use the tail recursion barrier to roll back the log, provided it is interested in variable bindings or the variable count.
Both failed and successful deterministic predicates might fill
the log. That a failed predicates might fill the log is a feature
that saves some operations. The failed predicate might have
allocated some place holders and attempted unification. The failed
predicate does not need to establish its own mark and perform a
rollback on its own. It can simple delegate this task to the
housekeeping of the system.
The tail recursion barrier is also involved in non-deterministic
predicates that create choice points. In this respect there is no
difference between the first call of a deterministic or
non-deterministic predicate. Both deterministic and
non-deterministic predicates are called with a tail recursion
barrier in place. The predicate can then do some work and call the
method markFrame(). The predicate might also then do some further
work. If the work did not lead to some result it might call
If the predicate is satisfied with the result and if it desires a
choice point it might then turn the mark into a new barrier. For
this purpose it has to call the method swapBarrier(). The
predicate will then indicate that it wants a choice point via the
method setRetry(). The predicate will also have called setData()
so as to tell the system the desired barrier mark. The
housekeeping of the system is such that it will create the choice
point and call the next goal in the continuation goal list.
Back tracking is usually caused by a deterministic or
non-deterministic predicate that fails after a sequence of
deterministic predicates that succeeded. The system will first do
the normal book keeping for a failed predicate. This consists in
rolling back the log to the now new barrier and retrying the
choice point. The choice point for the predicate will then invoke
the predicate with getFirst() returning false. The return of false
by this method informs the predicate that the invocation is not a
call but a redo.
It is also possible for the predicate to attach some additional information besides a barrier mark to the choice point. The sole possibility to do this is also the method setData(). The easiest approach is to define a class for the desired data structure and add enough instance fields to the class. Upon redo the additional information and the barrier mark can then be retrieved via the method getData(). Upon redo a predicate has first to undo its move of the barrier via the method swapBarrier(). The predicate can then also call again releaseBind() before doing some further work.
Since the barrier handling is very tedious the interpreter does default barrier handling for non-deterministic predicates. Since intervention is impossible the default handling can be switched off by the method setSpecial(). There can be situations where no barrier handling at all is necessary. This is for example the case for certain patterns of doing a call-in by the predicate since the call-in API does already barrier handling. Other situations might require more custom control.
There is also a housekeeping defined for local cuts, normal cuts
and execution errors. All three events have in common that they do
not simply restore a particular mark. Local cuts rollback all
choice points inside the current rule. Normal cuts rollback all
choice points skipping cut transparent rules. Execution errors
rollback all choice points in the Prolog processor invocation.
Currently to be able to handle execution errors new Prolog
processor invocations are created, inside which the execution
error can happen and then returning to the previous Prolog
processor invocation to perform the execution error handling.