Example that shows how to use call_dcg/3 to implement state/context?

To me the question is a bit confusing but understandably why it is confusing is because you are just learning about DCGs. As such I will answer this by taking various parts of the question out and and answer them but not in the order asked.

I don’t think I have ever used call_dcg/3 (source) except once to check it out to see how it works, I essentially use just phrase/3 (source) instead. The reason being that in the documentation it notes,

This predicate (call_dcg/3) was introduced after type checking was added to phrase/3.

In looking at the source one notices that phrase/3 executes call_dcg/3 but phrase/3 also has some type checking. I have never found the type checking of phrase/3 to cause me problems. Also most example you find will use phrase/2 which is just

phrase(RuleSet, Input) :-
    phrase(RuleSet, Input, []).

so essentially, phrase/2 is phrase/3 with the third parameter being set to [].

So the example below will use phrase/3.


Use phrase/2 or phrase/3.


I don’t know what you mean by sophisticated, so I will give what I consider a plain and simple example.

For the example I will use digits//1 (source) from library DCG basics - (predicates)

Notice that the signature of digits//1 has two / and not one. This is because digits//1 is to not be though of as a Prolog predicate with 1 argument but a clause with 1 visible argument. In reality it is also digits/3, a predicate with 3 arguments. To see this use listing/1, e.g.

?- listing(digits/3).
dcg_basics:digits([H|T], A, B) :-
    digit(H, A, C),
    !,
    D=C,
    digits(T, D, B).
dcg_basics:digits([], A, A).

true.

compare that with the source code

digits([H|T]) -->
	digit(H), !,
	digits(T).
digits([]) -->
	[].

listing/1 with digits//1 also works.

?- listing(digits//1).
dcg_basics:digits([H|T], A, B) :-
    digit(H, A, C),
    !,
    D=C,
    digits(T, D, B).
dcg_basics:digits([], A, A).

true.

If you change the A and B to S0 and S
and C to S1
and D to S2
you get

dcg_basics:digits([H|T], S0, S) :-
    digit(H, S0, S1),
    !,
    S2=S1,
    digits(T, S2, S).
dcg_basics:digits([], S0, S0).

which is the S0 and S from call_dcg/3. So S0 and S are hidden state variables you are looking to pass around.

For the actual example I will use Prolog unit tests so that you will and others will have the basic template for doing this and so that you can add more test easily without having to write the basic code over and over.

:- use_module(library(dcg/basics)).

:- begin_tests(digits).

digits_test_case("1",[49],[]).
digits_test_case("1",[0'1],[]).

test(001, [forall(digits_test_case(Input,Expected_digits,Expected_rest))]) :-
    string_codes(Input,Codes),
    DCG = digits(Digits),
    phrase(DCG,Codes,Rest),
    assertion( Rest == Expected_rest ),
    assertion( Digits == Expected_digits ).

:- end_tests(digits).

To use this consult the code as normal, then run_tests.

?- consult("C:/Users/Eric/Documents/Projects/Prolog/swi-discourse_004.pl").
true.

?- run_tests.
% PL-Unit: digits .. done
% All 2 tests passed
true.

This shows that the code was successfully loaded and that the two test case ran successfully.

The two test case are identical, but the expected result, while being the same, is written with two different ways of portraying the ASCII character 1. The first is as the character code as a decimal, 49 and the second is as a special Prolog form that uses the human readable glyph for the number one, 1 but so that it is converted to the character code during complication, the 0' prefix tells the compiler to covert this to a character code.

To add more test cases just edit the code and save

digits_test_case("9",[0'9],[]).
digits_test_case("123",[0'1,0'2,0'3],[]).

then run make.

?- make.
% c:/users/eric/documents/projects/prolog/swi-discourse_004 compiled 0.00 sec, 1 clauses
% PL-Unit: digits .... done
% All 4 tests passed
true.

The code loaded, compiled, ran 4 tests, and all 4 tests passed.


When using DCGs, some people like to process the data as characters codes and some people like to process the data as atoms. It took me a while to get this. The code in the library dcg/basics processes the data as character codes so be fore warned.

You should also be aware of set_prolog_flag/2 and the option doublequotes. See: The string type and its double quoted syntax. I used it in this earlier example


Since digits//1 is really digits/3 you can also do this

digits(Digits,S0,S).

If you add this test case to the example which uses digits/3 instead

test(002, [forall(digits_test_case(Input,Expected_digits,Expected_rest))]) :-
    string_codes(Input,Codes),
    digits(Digits,Codes,Rest),
    assertion( Rest == Expected_rest ),
    assertion( Digits == Expected_digits ).

then you get

?- make.
% c:/users/eric/documents/projects/prolog/swi-discourse_004 compiled 0.00 sec, 2 clauses
% PL-Unit: digits ........ done
% All 8 tests passed
true.

So the 4 test of test case 01 worked and the same 4 test with test 02 also worked the same.

2 Likes