Search code examples
prologdcgclpfd

Prolog+clpfd: simple binary number parser with value


I'm currently trying to understand DCGs in prolog.

Consider this example.

digit(0) --> "0".
digit(1) --> "1".

binaryNumber(Val) --> digit(Val).

binaryNumber(Next*2 + Cur) -->
    %CurVal #= Cur + Next*2,
    binaryNumber(Next),
    digit(Cur).

That produces:

207 ?- binaryNumber(X, Y, []).
X = 0,
Y = [48] ;
X = 1,
Y = [49] ;
X = 0*2+0,
Y = [48, 48] ;
X = 0*2+1,
Y = [48, 49] ;
X = 1*2+0,
Y = [49, 48] ;
X = 1*2+1,
Y = [49, 49] ;
X = (0*2+0)*2+0,

Which is nice.

However, if I want to "convert" string to value:

:- use_module(library(clpfd)).

digit(0) --> "0".
digit(1) --> "1".

binaryNumber(Val) --> digit(Val).

binaryNumber(CurVal) -->
    CurVal #= Cur + Next*2,
    binaryNumber(Next),
    digit(Cur).

I get:

209 ?- binaryNumber(X, Y, []).
X = 0,
Y = [48] ;
X = 1,
Y = [49] ;
ERROR: binaryNumber/3: Undefined procedure: (#=)/4
ERROR:   However, there are definitions for:
ERROR:         (#=)/2
   Exception: (7) #=(_G4807345, _G4807428+_G4807431*2, _G4807346, _G4807475) ? 

...

Two questions:

  1. Why does binaryNumber want #= to have "arity" of 4?
  2. How do I fix this?

Solution

  • You're very close!

    Commonly, a foo//n isn't implemented "directly", but by translating a grammar foo//n to a corresponding Prolog predicate foo//(n+2). This translation is done by term_expansion/2, a mechanism analogous to macros in other languages. Usually, you don't have to mind it at all.


    For more on read: (1) this DCG primer, and (2) the question "Is there a way or an algorithm to convert DCG into normal definite clauses in Prolog?" and the answers to that question.


    Coming back to the subject, I see two issues in your use:

    1. If used within grammar rules, "ordinary" Prolog goals must be encapsulated with curly braces {}/1, so they are skipped in aforementioned "grammar to predicate" translation step. In your code, you don't want to use (#=)//2 (a.k.a. (#=)/4), you want (#=)/2!

    2. It is good practise, not to use foo/(n+2) goals directly. Use phrase/2 or phrase/3 for that!

    So let's edit the corresponding code snippet:

    binaryNumber(Next*10 + Cur) -->
        { CurVal #= Cur + Next*2 },
        binaryNumber(Next),
        digit(Cur).
    

    Now let's query!

    ?- phrase(binaryNumber(X),Ts).
    X = 0, Ts = [48]    ;
    X = 1, Ts = [49]    ;
    X = 0, Ts = [48,48] ;
    X = 1, Ts = [48,49] ;
    X = 2, Ts = [49,48] ;
    X = 3, Ts = [49,49] ...