It’s not fully deterministic:
Code I’m working on (which doesn’t work) in version 8.1.32-10-g1885b056f:
:- use_module(library(clpfd)).
:- debug(dcg).
say(Where,List1,List2) :- \+ \+ debug(dcg,"~q ~q ~q",[Where,List1,List2]).
% greedily grab an "ab"
abba_char(AB,BA) -->
[a,b], !,
call(say("#1")),
abba_char(ABm,BA), {AB #= ABm+1}.
% greedily grab an "ba"
abba_char(AB,BA) -->
[b,a], !,
call(say("#2")),
abba_char(AB,BAm), {BA #= BAm+1}.
% lookahead 1 char, it's not a "b", push it back!
abba_char(AB,BA), [C] -->
[a,C], { C \== b }, !,
call(say("#3")),
abba_char(AB,BA).
% lookahead 1 char, it's not an "a", push it back!
abba_char(AB,BA), [C] -->
[b,C], { C \== a }, !,
call(say("#4")),
abba_char(AB,BA).
% characters neither "a" nor "b" are disregarded
abba_char(AB,BA) -->
[C], { \+ memberchk(C,[a,b]) }, !,
call(say("#5")),
abba_char(AB,BA).
% a final "a"
abba_char(0,0) -->
[a],!,
call(say("#5")).
% a final "b"
abba_char(0,0) -->
[b],!,
call(say("#6")).
% no character at all, which in this case means the list is now empty
abba_char(0,0) -->
[],
call(say("#done")).
% helper that eats an atom and gives the parsing result
phrase_chars(X,AB,BA) :- atom_chars(X,Cs),phrase(abba_char(AB,BA),Cs).
:- begin_tests(dcg_chars).
test(0,[true(Truly)]) :- phrase_chars('',AB,BA),Truly = ([AB,BA] == [0,0]).
test(1,[true(Truly)]) :- phrase_chars('abbaabbaba',AB,BA),Truly = ([AB,BA] == [2,3]).
test(2,[true(Truly)]) :- phrase_chars('yabybayyyy',AB,BA),Truly = ([AB,BA] == [1,1]).
test(3,[true(Truly)]) :- phrase_chars('yyyyyyyyya',AB,BA),Truly = ([AB,BA] == [0,0]).
test(4,[true(Truly)]) :- phrase_chars('yyyyyyybaa',AB,BA),Truly = ([AB,BA] == [0,1]).
test(5,[true(Truly)]) :- phrase_chars('yyyyyyyba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(6,[true(Truly)]) :- phrase_chars('yaybyayba' ,AB,BA),Truly = ([AB,BA] == [0,1]).
test(7,[true(Truly)]) :- phrase_chars('yaabby' ,AB,BA),Truly = ([AB,BA] == [1,0]).
:- end_tests(dcg_chars).
rt(dcg_chars) :- run_tests(dcg_chars).
You get the error as follows:
Run
phrase_chars('yaabby' ,AB,BA).
then the Prolog Processor goes into an infinite loop. For some reason call//1
resets the lists? It is as yet a mystery to me.
Let it run a second, then call CTRL-C, and then press (a)bort.
In most cases, this ends well, but occasionally, you get the exception above.
The test code is likely irrelevant.