1

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?

Peter
  • 2,919
  • 1
  • 16
  • 35
  • Without knowing megaparsec's implementation or rationale, I would venture that this is sort of how unary operators are supposed to work, no? I've never seen anybody write `+-5` and expect the result to be "negative five". – Fyodor Soikin Jan 06 '21 at 20:24
  • 1
    @FyodorSoikin: Why not? E.g. in C I can write `!!2` which will evaluate to `1`. – Peter Jan 06 '21 at 20:25
  • @FyodorSoikin I just tried this in five languages (Ruby, Python, JavaScript, Java and Haskell). In four out of five, the result of evaluating `+-5` is indeed "negative five". In Haskell it's a syntax error because there is no unary `+` operator (so just `+5` would be an error too). If it were, I would expect the result to be negative five in Haskell as well. – sepp2k Jan 06 '21 at 20:31
  • Your definition for `valueTerm` looks off - did you mis-transcribe it? I'd expect it to end with `Integer <$> integerLiteral`, not `Integer <|> integerLiteral`. – amalloy Jan 06 '21 at 20:45
  • @amalloy: Ah yes, that's a typo, `integerLiteral` is also missing. I removed some stuff to make the example smaller. – Peter Jan 06 '21 at 20:45

1 Answers1

4

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")
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71