I used Text.Megaparsec.Expr to write a minimal (56 lines) 2-operator recursive parser. It's on github[1]. It outputs a binary tree of the following form:

    data AExpr = Var String | Pair AExpr AExpr deriving (Show)

The operator table supplied to makeExprParser defines two operators, # and ##. ## binds after #, but both of them refer to the same function, the Pair constructor of the AExpr data type:

    aOperators :: [[Operator Parser AExpr]]
    aOperators =
      [ [ InfixN # symbol "#" *> pure (Pair) ]
      , [ InfixN # symbol "##" *> pure (Pair) ]
      ]

The # operator works in isolation:

    > parseMaybe aExpr "a # b"
    Just (Pair (Var "a") (Var "b"))

Parentheses work with the # operator:

    > parseMaybe aExpr "(a # b) # (c # d)"
    Just (Pair (Pair (Var "a") (Var "b")) -- whitespace added by hand
               (Pair (Var "c") (Var "d")))

And the # and ## operators work together as intended:

    > parseMaybe aExpr "a # b ## c # d"
    Just (Pair (Pair (Var "a") (Var "b")) -- whitespace added by hand
               (Pair (Var "c") (Var "d")))

But the ## operator in isolation does not parse!

    > parseMaybe aExpr "a ## b"
    Nothing

[1] https://github.com/JeffreyBenjaminBrown/digraphs-with-text/blob/master/howto/megaparsec/experim.hs


--
Jeff Brown | Jeffrey Benjamin Brown
Website   |   Facebook   |   LinkedIn(I often miss messages here)   |   Github