haskellmegaparsec

Chaining unary operators with megaparsec


I have written a parser using megaparsec for a very simple language consisting of integer literals and two unary operators "a" and "b":

data ValueExpr = OpA ValueExpr  
               | OpB ValueExpr
               | Integer Integer

valueExpr :: Parser ValueExpr
valueExpr = makeExprParser valueTerm valueOperatorTable

valueTerm :: Parser ValueExpr
valueTerm = parenthesised valueExpr
          <|> Integer <$> integerLiteral

integerLiteral :: Parser Integer
integerLiteral = -- omitted

valueOperatorTable :: [[Operator Parser ValueExpr]]
valueOperatorTable = [[unaryOp "a" AOp,
                       unaryOp "b" BOp]]

parenthesised :: Parser a -> Parser a
parenthesised = between (char '(') (char ')')

unaryOp :: Text -> (a -> a) -> Operator Parser a
unaryOp name f = Prefix (f <$ symbol name)

binaryOp :: Text -> (a -> a -> a) -> Operator Parser a
binaryOp name f = InfixL (f <$ symbol name)

However, it seems that this doesn't allow me to "chain" unary operators, i.e. when trying to parse "ab1", I'm met with "unexpected 'b'". Why is that?


Solution

  • This is briefly mentioned in the documentation for makeExprParser:

    Unary operators of the same precedence can only occur once (i.e., --2 is not allowed if - is prefix negate). If you need to parse several prefix or postfix operators in a row, ... you can use this approach:

    manyUnaryOp = foldr1 (.) <$> some singleUnaryOp
    

    This is not done by default because in some cases allowing repeating prefix or postfix operators is not desirable.

    In your specific example, something like the following ought to work:

    valueOperatorTable :: [[Operator Parser ValueExpr]]
    valueOperatorTable = [[Prefix unaryOps]]
    
    unaryOps :: Parser (ValueExpr -> ValueExpr)
    unaryOps = foldr1 (.) <$> some (OpA <$ symbol "a" <|> OpB <$ symbol "b")