Basic DCG example predicates

Some basic DCG example predicates because I am always looking for these examples in my code. Now they are in one place for all to see. :wink:

% one or more - Accumulator returns closed list
%! 'digit+'(-Codes:list(code))// is semidet.
'digit+'([H|T]) -->
    digit(H), !,
    'digit*'(T).

% zero or more - Accumulator returns closed list
%! 'digit*'(-Codes:list(code))// is semidet.
'digit*'([H|T]) -->
    digit(H), !,
    'digit*'(T).
'digit*'([]) --> [].

% one or more - Accumulator returns difference list
%! 'digit+'(-Codes:list(code), -Tail:list(code))// is semidet.
'digit+'([H|T0],T) -->
    digit(H), !,
    'digit*'(T0,T).

% zero or more - Accumulator returns difference list
%! 'digit*'(-Codes:list(code), -Tail:list(code))// is semidet.
'digit*'([H|T0],T) -->
    digit(H), !,
    'digit*'(T0,T).
'digit*'(T,T) --> [].

% Recognizer
%! digit// is semidet.
digit -->
    digit(_).

%! digit(-Code:code)// is semidet.
digit(0'0) --> "0", !.
digit(0'1) --> "1", !.
digit(0'2) --> "2", !.
digit(0'3) --> "3", !.
digit(0'4) --> "4", !.
digit(0'5) --> "5", !.
digit(0'6) --> "6", !.
digit(0'7) --> "7", !.
digit(0'8) --> "8", !.
digit(0'9) --> "9".

% Note other alpha predicates are not created because they are similar to digit predicates.

% zero or more - Accumulator returns difference list
%! 'alpha*'(-Codes:list(code), -Tail:list(code))// is semidet.
'alpha*'([H|T0],T) -->
    alpha(H), !,
    'alpha*'(T0,T).
'alpha*'(T,T) --> [].

%! alpha(-Code:code)// is semidet.
alpha(C) -->
    [C],
    {
        between(0'a,0'z,C), !
    ;
        between(0'A,0'Z,C)
    }.

%! c_identifier_start(-Code:code)// is semidet.
c_identifier_start(C) -->
    alpha(C), !.
c_identifier_start(0'_) -->
    [0'_].

% Accumulator returns difference list
%! c_identifier_rest(-Codes:list(code), -Tail:list(code))// is semidet.
c_identifier_rest([C|T0],T) -->
    (
        c_identifier_start(C), !
    ;
        digit(C)
    ), !,
    c_identifier_rest(T0,T).
c_identifier_rest(T,T) --> [].

%! c_identifier(@Value)// is semidet.
c_identifier(identifier(Identifier)) -->
    c_identifier_start(H),    % Returns a single character code.
    c_identifier_rest(T,[]),  % Ues a character code difference list.
    { atom_codes(Identifier,[H|T]) }.

% -----------------------------------------------------------------------------

:- begin_tests(dcg).

digit_test( success,  "0", 0'0,    [] ).
digit_test( success,  "1", 0'1,    [] ).
digit_test( success,  "2", 0'2,    [] ).
digit_test( success,  "3", 0'3,    [] ).
digit_test( success,  "4", 0'4,    [] ).
digit_test( success,  "5", 0'5,    [] ).
digit_test( success,  "6", 0'6,    [] ).
digit_test( success,  "7", 0'7,    [] ).
digit_test( success,  "8", 0'8,    [] ).
digit_test( success,  "9", 0'9,    [] ).
digit_test( success, "0a", 0'0, [0'a] ).
digit_test( success, "1a", 0'1, [0'a] ).
digit_test( success, "2a", 0'2, [0'a] ).
digit_test( success, "3a", 0'3, [0'a] ).
digit_test( success, "4a", 0'4, [0'a] ).
digit_test( success, "5a", 0'5, [0'a] ).
digit_test( success, "6a", 0'6, [0'a] ).
digit_test( success, "7a", 0'7, [0'a] ).
digit_test( success, "8a", 0'8, [0'a] ).
digit_test( success, "9a", 0'9, [0'a] ).
digit_test( fail, "a" ).
digit_test( fail, "z" ).
digit_test( fail, "A" ).
digit_test( fail, "Z" ).
digit_test( fail, "~" ).
digit_test( fail, "+" ).
digit_test( fail, "@" ).

test(digit_success,[forall(digit_test(success,Input,C,Rest))]) :-
    string_codes(Input,Codes),
    phrase(digit(C),Codes,Rest).

test(digit_fail,[fail,forall(digit_test(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase(digit(_),Codes,[]).

test(digit_recognizer_success,[forall(digit_test(success,Input,_,_))]) :-
    string_codes(Input,Codes),
    phrase(digit,Codes,_).

test(digit_recognizer_fail,[fail,forall(digit_test(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase(digit,Codes,[]).

'digit*_test'( success,     "",            [],    [] ).
'digit*_test'( success,    "0",         [0'0],    [] ).
'digit*_test'( success,    "1",         [0'1],    [] ).
'digit*_test'( success,    "2",         [0'2],    [] ).
'digit*_test'( success,    "3",         [0'3],    [] ).
'digit*_test'( success,    "4",         [0'4],    [] ).
'digit*_test'( success,    "5",         [0'5],    [] ).
'digit*_test'( success,    "6",         [0'6],    [] ).
'digit*_test'( success,    "7",         [0'7],    [] ).
'digit*_test'( success,    "8",         [0'8],    [] ).
'digit*_test'( success,    "9",         [0'9],    [] ).
'digit*_test'( success,   "00",     [0'0,0'0],    [] ).
'digit*_test'( success,   "09",     [0'0,0'9],    [] ).
'digit*_test'( success,   "90",     [0'9,0'0],    [] ).
'digit*_test'( success,   "99",     [0'9,0'9],    [] ).
'digit*_test'( success,  "000", [0'0,0'0,0'0],    [] ).
'digit*_test'( success,  "009", [0'0,0'0,0'9],    [] ).
'digit*_test'( success,  "090", [0'0,0'9,0'0],    [] ).
'digit*_test'( success,  "099", [0'0,0'9,0'9],    [] ).
'digit*_test'( success,  "900", [0'9,0'0,0'0],    [] ).
'digit*_test'( success,  "909", [0'9,0'0,0'9],    [] ).
'digit*_test'( success,  "990", [0'9,0'9,0'0],    [] ).
'digit*_test'( success,  "999", [0'9,0'9,0'9],    [] ).
'digit*_test'( success,    "a",            [], [0'a] ).
'digit*_test'( success,   "0a",         [0'0], [0'a] ).
'digit*_test'( success,   "1a",         [0'1], [0'a] ).
'digit*_test'( success,   "2a",         [0'2], [0'a] ).
'digit*_test'( success,   "3a",         [0'3], [0'a] ).
'digit*_test'( success,   "4a",         [0'4], [0'a] ).
'digit*_test'( success,   "5a",         [0'5], [0'a] ).
'digit*_test'( success,   "6a",         [0'6], [0'a] ).
'digit*_test'( success,   "7a",         [0'7], [0'a] ).
'digit*_test'( success,   "8a",         [0'8], [0'a] ).
'digit*_test'( success,   "9a",         [0'9], [0'a] ).
'digit*_test'( success,  "00a",     [0'0,0'0], [0'a] ).
'digit*_test'( success, "000a", [0'0,0'0,0'0], [0'a] ).
'digit*_test'( fail, "a" ).
'digit*_test'( fail, "z" ).
'digit*_test'( fail, "A" ).
'digit*_test'( fail, "Z" ).
'digit*_test'( fail, "~" ).
'digit*_test'( fail, "+" ).
'digit*_test'( fail, "@" ).

test(digit_close_list_accumulator_success,[forall('digit*_test'(success,Input,Digit_codes,Rest))]) :-
    string_codes(Input,Codes),
    phrase('digit*'(Digit_codes),Codes,Rest).

test(digit_close_list_accumulator_fail,[fail,forall('digit*_test'(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase('digit*'(_),Codes,[]).

test(digit_difference_list_accumulator_success,[forall('digit*_test'(success,Input,Digit_codes,Rest))]) :-
    string_codes(Input,Codes),
    phrase('digit*'(Digit_codes,[]),Codes,Rest).

test(digit_difference_list_accumulator_fail,[fail,forall('digit*_test'(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase('digit*'(_,_),Codes,[]).

'digit+_test'( success,    "0",         [0'0],    [] ).
'digit+_test'( success,    "1",         [0'1],    [] ).
'digit+_test'( success,    "2",         [0'2],    [] ).
'digit+_test'( success,    "3",         [0'3],    [] ).
'digit+_test'( success,    "4",         [0'4],    [] ).
'digit+_test'( success,    "5",         [0'5],    [] ).
'digit+_test'( success,    "6",         [0'6],    [] ).
'digit+_test'( success,    "7",         [0'7],    [] ).
'digit+_test'( success,    "8",         [0'8],    [] ).
'digit+_test'( success,    "9",         [0'9],    [] ).
'digit+_test'( success,   "00",     [0'0,0'0],    [] ).
'digit+_test'( success,   "09",     [0'0,0'9],    [] ).
'digit+_test'( success,   "90",     [0'9,0'0],    [] ).
'digit+_test'( success,   "99",     [0'9,0'9],    [] ).
'digit+_test'( success,  "000", [0'0,0'0,0'0],    [] ).
'digit+_test'( success,  "009", [0'0,0'0,0'9],    [] ).
'digit+_test'( success,  "090", [0'0,0'9,0'0],    [] ).
'digit+_test'( success,  "099", [0'0,0'9,0'9],    [] ).
'digit+_test'( success,  "900", [0'9,0'0,0'0],    [] ).
'digit+_test'( success,  "909", [0'9,0'0,0'9],    [] ).
'digit+_test'( success,  "990", [0'9,0'9,0'0],    [] ).
'digit+_test'( success,  "999", [0'9,0'9,0'9],    [] ).
'digit+_test'( success,   "0a",         [0'0], [0'a] ).
'digit+_test'( success,   "1a",         [0'1], [0'a] ).
'digit+_test'( success,   "2a",         [0'2], [0'a] ).
'digit+_test'( success,   "3a",         [0'3], [0'a] ).
'digit+_test'( success,   "4a",         [0'4], [0'a] ).
'digit+_test'( success,   "5a",         [0'5], [0'a] ).
'digit+_test'( success,   "6a",         [0'6], [0'a] ).
'digit+_test'( success,   "7a",         [0'7], [0'a] ).
'digit+_test'( success,   "8a",         [0'8], [0'a] ).
'digit+_test'( success,   "9a",         [0'9], [0'a] ).
'digit+_test'( success,  "00a",     [0'0,0'0], [0'a] ).
'digit+_test'( success, "000a", [0'0,0'0,0'0], [0'a] ).
'digit+_test'( fail,  "" ).
'digit+_test'( fail, "a" ).
'digit+_test'( fail, "z" ).
'digit+_test'( fail, "A" ).
'digit+_test'( fail, "Z" ).
'digit+_test'( fail, "~" ).
'digit+_test'( fail, "+" ).
'digit+_test'( fail, "@" ).

test(digit_close_list_accumulator_success,[forall('digit+_test'(success,Input,Digit_codes,Rest))]) :-
    string_codes(Input,Codes),
    phrase('digit+'(Digit_codes),Codes,Rest).

test(digit_close_list_accumulator_fail,[fail,forall('digit+_test'(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase('digit+'(_),Codes,[]).

test(digit_difference_list_accumulator_success,[forall('digit+_test'(success,Input,Digit_codes,Rest))]) :-
    string_codes(Input,Codes),
    phrase('digit+'(Digit_codes,[]),Codes,Rest).

test(digit_difference_list_accumulator_fail,[fail,forall('digit+_test'(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase('digit+'(_,_),Codes,[]).

alpha_test( success, "a", 0'a, [] ).
alpha_test( success, "z", 0'z, [] ).
alpha_test( success, "A", 0'A, [] ).
alpha_test( success, "Z", 0'Z, [] ).
alpha_test( fail, "0" ).
alpha_test( fail, "9" ).
alpha_test( fail, "~" ).
alpha_test( fail, "+" ).
alpha_test( fail, "_" ).

test(alpha_success,[forall(alpha_test(success,Input,Alpha_code,Rest))]) :-
    string_codes(Input,Codes),
    phrase(alpha(Alpha_code),Codes,Rest).

test(alpha_fail,[fail,forall(alpha_test(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase(alpha(_),Codes,_).

c_identifier_start_test( success, "a", 0'a, [] ).
c_identifier_start_test( success, "z", 0'z, [] ).
c_identifier_start_test( success, "A", 0'A, [] ).
c_identifier_start_test( success, "Z", 0'Z, [] ).
c_identifier_start_test( success, "_", 0'_, [] ).
c_identifier_start_test( fail, "0" ).
c_identifier_start_test( fail, "9" ).
c_identifier_start_test( fail, "~" ).
c_identifier_start_test( fail, "+" ).

test(c_identifier_start_success,[forall(c_identifier_start_test(success,Input,C_identifier_start_code,Rest))]) :-
    string_codes(Input,Codes),
    phrase(c_identifier_start(C_identifier_start_code),Codes,Rest).

test(c_identifier_start_fail,[fail,forall(c_identifier_start_test(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase(c_identifier_start(_),Codes,_).

c_identifier_rest_test(success,   "",             [],    [] ).
c_identifier_rest_test(success,  "a",          [0'a],    [] ).
c_identifier_rest_test(success,  "z",          [0'z],    [] ).
c_identifier_rest_test(success,  "A",          [0'A],    [] ).
c_identifier_rest_test(success,  "Z",          [0'Z],    [] ).
c_identifier_rest_test(success,  "0",          [0'0],    [] ).
c_identifier_rest_test(success,  "9",          [0'9],    [] ).
c_identifier_rest_test(success,  "_",          [0'_],    [] ).
c_identifier_rest_test(success,  "~",             [], [0'~] ).
c_identifier_rest_test(success,  "+",             [], [0'+] ).
c_identifier_rest_test(success, "aa",      [0'a,0'a],    [] ).
c_identifier_rest_test(success, "a1",      [0'a,0'1],    [] ).
c_identifier_rest_test(success, "a~",          [0'a], [0'~] ).
c_identifier_rest_test(success, "aaa", [0'a,0'a,0'a],    [] ).
c_identifier_rest_test(success, "a1a", [0'a,0'1,0'a],    [] ).
c_identifier_rest_test(success, "a11", [0'a,0'1,0'1],    [] ).
c_identifier_rest_test(success, "aa~",     [0'a,0'a], [0'~] ).
c_identifier_rest_test(success, "aaa~",[0'a,0'a,0'a], [0'~] ).

test(c_identifier_rest_success,[forall(c_identifier_rest_test(success,Input,C_identifier_rest_codes,Rest))]) :-
    string_codes(Input,Codes),
    phrase(c_identifier_rest(C_identifier_rest_codes,[]),Codes,Rest).

c_identifier_test( success,  "a",  identifier(  a),    [] ).
c_identifier_test( success,  "z",  identifier(  z),    [] ).
c_identifier_test( success,  "A",  identifier('A'),    [] ).
c_identifier_test( success,  "Z",  identifier('Z'),    [] ).
c_identifier_test( success,  "_",  identifier('_'),    [] ).
c_identifier_test( success, "aa",  identifier( aa),    [] ).
c_identifier_test( success, "a1",  identifier( a1),    [] ).
c_identifier_test( success, "a~",  identifier(  a), [0'~] ).
c_identifier_test( success, "aaa", identifier(aaa),    [] ).
c_identifier_test( success, "a1a", identifier(a1a),    [] ).
c_identifier_test( success, "a11", identifier(a11),    [] ).
c_identifier_test( success, "aa~", identifier( aa), [0'~] ).
c_identifier_test( success, "aaa~",identifier(aaa), [0'~] ).
c_identifier_test( fail,  "").
c_identifier_test( fail, "0").
c_identifier_test( fail, "9").
c_identifier_test( fail, "~").
c_identifier_test( fail, "+").

test(c_identifier_success,[forall(c_identifier_test(success,Input,Identifier,Rest))]) :-
    string_codes(Input,Codes),
    phrase(c_identifier(Identifier),Codes,Rest).

test(c_identifier_fail,[fail,forall(c_identifier_test(fail,Input))]) :-
    string_codes(Input,Codes),
    phrase(c_identifier(_),Codes,_).

:- end_tests(dcg).

Note: If some of this looks exactly like the code by Wouter Beek in dcg.pl it is because

  1. If you write lots of DCG code you will eventually refactor it down to almost the same code.
  2. I did borrow his use of * meaning zero or more and + meaning one or more in the predicate names, but even the use of * and + are standard.

@jan

When you get a chance, no rush.

Can you check the PlDoc header comments in the above post to make sure I did these right.
Feel free to edit the post if that is faster. I can always look at the edit differences.

Thanks.

The overall syntax is like this. The block’s first line must start with %!

%! <mode declaration>
%  Markdown
%  @key nodes

You can simply see what do did using this command line. That should open a browser on the file’s overview page.

swipl --pldoc myfile.pl
1 Like