Search code examples
prologdcg

How to write this DCG more elegantly?


Playing around with DCGs and stubled upon the following problem:

I want to parse as well as produce an exact amount of spaces (" "). My trivial approach of simply doing this:

trivial_nat_space(0) --> [].
trivial_nat_space(N) -->
    { N > 0, N is M+1 },
    " ",
    trivial_nat_space(M).

failed terribly, because of insufficient instantiation of N and M depending on whether i do

?- String="   ", my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])

or

?- Anz_Spaces=3,my_string_codes(String,Codes), phrase(trivial_nat_space(Anz_Spaces), Codes, [])

where (for convenience)

my_string_codes(S,C) :-
    when((ground(S);ground(C)), string_codes(S,C)).

searching for a nice solution to the problem I made a version that depends on self defined nats:

z.
s(z).
s(s(O)) :-
  s(O).

nat_num(S,C) :-
    when((ground(S);ground(C)),nat_num_(S,C)).
nat_num_(z,0) :- !.
nat_num_(s(N),X) :-
    nonvar(X),
    !,
    X > 0,
    Y is X-1,
    nat_num_(N,Y).
nat_num_(s(N),X) :-
    var(X),
    nat_num_(N,Y),
    X is Y+1.

n_space(z) --> [].
n_space(s(N)) -->
    " ",
    n_space(N).

which I would like to avoid because the additional encoding of the natural number is kind of already present in the builtin numbers.

and this:

nat_space(0) --> [].
nat_space(N) -->
    { var(N) },
    " ",
    nat_space(M),
    { N is M+1 }.
nat_space(M) -->
    { nonvar(M), M>0 },
    " ",
    { N is M-1 },
    nat_space(N).

which does work fine:

?- Anz_Spaces=3,my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
Anz_Spaces = 3,
String = "   ",
Codes = [32, 32, 32].

?- String="   ",my_string_codes(String,Codes), once(phrase(nat_space(Anz_Spaces), Codes, [])).
String = "   ",
Codes = [32, 32, 32],
Anz_Spaces = 3.

However the encoding of nat_spaces is (in my opinion) far from nice: it depends on meta-predicates to enforce a specific execution sequence, and (more seriously): if the parser were more complex than just " ", the logic would have to be defined in a seperate DCG predicate/rule resulting in the logic for a single parser/generator to be split into two definitions (the separated one and the one enforcing the correct execution sequence).

Is this the canonical/standard way of solving problems like this or is there a more general, elegant solution that I am missing right now?

Additional Info:

I am using SWI-Prolog version 8.3.9 for x86_64-linux with :- [library(dcg/basics)] and no additional arguments when starting the runtime. Nor do I set any settings in the file with the definitions.


Solution

  • For your specific example, you can use CLP(fd) to be able to use the DCG in both ways:

    trivial_nat_space(0) --> [].
    trivial_nat_space(N) -->
        { N #> 0, M #= N-1 },
        " ",
        trivial_nat_space(M).
    

    In the following sample runs I will use backticks (`) to use coded strings:

    ?- phrase(trivial_nat_space(Anz_Spaces), `   `, []).
    Anz_Spaces = 3 ;
    false.
    
    ?- phrase(trivial_nat_space(3), Spaces, []).
    Spaces = [32, 32, 32] ;
    false.
    
    ?- phrase(trivial_nat_space(N), Spaces, []).
    N = 0,
    Spaces = [] ;
    N = 1,
    Spaces = [32] ;
    N = 2,
    Spaces = [32, 32] ;
    N = 3,
    Spaces = [32, 32, 32] 
     ...