Search code examples
parsingcompiler-constructiongrammaryacchappy

How to define association on rule without tokens?


I have the following minimized grammar

Exp : let var '=' Exp in Exp end                     { App (Fn $2 $6) $4 }
    | Exp Exp                                        { App $1 $2 }
    | Exp OpCode Exp                                 { Op $1 Add $3 }
    | '(' Exp ')'                                    { $2 }
    | num                                            { Num $1 }
    | var                                            { Ident $1 }
    | '\\' var '.' Exp                               { Fn $2 $4 }

The Exp Exp rule is used to apply a function in a value. But if I have something like myFunc 1 2 it defaults to precendence myFunc (1 2), which is not what I want. I want (myFunc 1) 2, for currying.

But how can I define the association if I don't have a non-terminal symbol? Trying to do %left Exp don't seems to help.


Solution

  • You can't really apply precedence or associativity unless you have a terminal to shift, because precedence and associativity rules are used to resolve shift/reduce conflicts. You don't necessarily need a terminal in the reduction, so you could use a fake terminal and write:

    Exp: Exp Exp %prec CURRY
    

    But that won't help you because there is no terminal to compare precedence with. Precedence relations are always a comparison between the precedence of the lookahead symbol (a terminal) and possible reductions (by default, the precedence of a reduction is based on the rightmost terminal in the rule, but as above you can explicitly specify).

    Since you can't do it the short-hand way, you need to fallback to the old-fashioned style where you write an unambiguous grammar with explicit precedence rules:

    Curry: Term
         | Curry Term
    

    (That's left-associative, by the away. If func 1 2 is parsed as ((func 1) 2), then application is associating to the left.)

    Assuming that infix binds tighter than application, you'd then have:

    Term: Value
        | Term Opcode Value
    
    Value: '(' Exp ')'
         | num
         | var
    
    Exp: Curry
    

    (You'll have to figure out how to integrate lambdas into that. It depends on how you expect them to group, but hopefully the model above is clear.)