Search code examples
prologdcg

Giving one of two valid parses "right of way" in a DCG


I've just learned about DCGs and have been really enjoying my early explorations. It's astonishing how much you can get out of a very brief specification. I've bumped into a bit of a wall with what I imagine is a fairly common problem, and was hoping for some expert advice. (Or even novice advice, considering I'm a complete beginner.)

Here's a toy version of my grammar. I've made it pie-focused in honor of 3/14:

sent(sent(VP, NP)) --> vp(VP), np(NP).
vp(vp(V)) --> v(V).
vp(vp(V, Qty)) --> v(V), qty(Qty).
np(np(Noun, Qty)) --> qty(Qty), n(Noun).
np(np(Noun)) --> n(Noun).

qty(qty(3)) --> ["3"].
v(v(eat)) --> ["eat"].
n(n(pie)) --> ["pies"].

In this grammar, we're worried about quantities, and it matters which the quantity is assigned to, the noun or the verb. So a sentence like "Eat 3 pies" has two valid parses, as seen here:

?- sent(X, ["eat", "3", "pies"], []).
X = sent(vp(v(eat)), np(n(pie), qty(3))) ;
X = sent(vp(v(eat), qty(3)), np(n(pie))) ;

In a situation like this, I always want the verb to be "awarded" the quantity. My first move, then, would be to simply remove the rule that allows for a quantity to precede a noun. But in another sentence like this one...

Bake 4 pies for 3 friends

... I would like friends to have a qty(3) with it.

Is there a technique for resolving these ambiguities?

Thank you for your time!


Solution

  • There are many things you can do.

    The easiest is probably to rule out the case np/2 for the first NP.

    sent(sent(VP,NP)) --> vp(VP), np(NP), { NP \= np(_,_) }.
    sent(sent(VP,NP,PP)) --> vp(VP), np(NP), pp(PP), { NP \= np(_,_) }.
    
    vp(vp(V)) --> v(V).
    vp(vp(V,Qty)) --> v(V), qty(Qty).
    
    pp(pp(Prep,NP)) --> prep(Prep), np(NP).
    
    np(np(Noun,Qty)) --> qty(Qty), n(Noun).
    np(np(Noun)) --> n(Noun).
    
    qty(qty(3)) --> ["3"].
    qty(qty(4)) --> ["4"].
    
    prep(for) --> ["for"].
    
    v(v(bake)) --> ["bake"].
    v(v(eat)) --> ["eat"].
    
    n(n(pie)) --> ["pies"].
    n(n(friend)) --> ["friends"].
    

    This produces:

    | ?- sent(X,["eat","3","pies"],[]).
    X = sent(vp(v(eat),qty(3)),np(n(pie))) ? ;
    no
    | ?- sent(X,["bake","4","pies","for","3","friends"],[]).
    X = sent(vp(v(bake),qty(4)),np(n(pie)),pp(for,np(n(friend),qty(3)))) ? ;
    no
    

    But it seems hard to justify this design linguistically. Consider "having eaten yesterday 3 pies", "eat for dinner 3 pies", "eat 1 sausage, 2 carrots, and 3 pies" etc.