megaparsec/Text/Megaparsec/Expr.hs

149 lines
5.3 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- |
-- Module : Text.Megaparsec.Expr
-- Copyright : © 2015 Megaparsec contributors
-- © 2007 Paolo Martini
-- © 19992001 Daan Leijen
-- License : BSD3
--
-- Maintainer : Mark Karpov <markkarpov@opmbx.org>
-- Stability : experimental
-- Portability : non-portable
--
-- A helper module to parse \"expressions\".
-- Builds a parser given a table of operators and associativities.
module Text.Megaparsec.Expr
( Assoc (..)
, Operator (..)
, OperatorTable
, buildExpressionParser )
where
import Control.Applicative ((<|>))
import Data.List (foldl')
import Text.Megaparsec.Combinator
import Text.Megaparsec.Prim
-- | This data type specifies the associativity of operators: left, right
-- or none.
data Assoc
= AssocNone
| AssocLeft
| AssocRight
-- | This data type specifies operators that work on values of type @a@.
-- An operator is either binary infix or unary prefix or postfix. A binary
-- operator has also an associated associativity.
data Operator s u m a
= Infix (ParsecT s u m (a -> a -> a)) Assoc
| Prefix (ParsecT s u m (a -> a))
| Postfix (ParsecT s u m (a -> a))
-- | An @OperatorTable s u m a@ is a list of @Operator s u m a@
-- lists. The list is ordered in descending precedence. All operators in one
-- list have the same precedence (but may have a different associativity).
type OperatorTable s u m a = [[Operator s u m a]]
-- | @buildExpressionParser table term@ builds an expression parser for
-- terms @term@ with operators from @table@, taking the associativity and
-- precedence specified in @table@ into account. Prefix and postfix
-- operators of the same precedence can only occur once (i.e. @--2@ is not
-- allowed if @-@ is prefix negate). Prefix and postfix operators of the
-- same precedence associate to the left (i.e. if @++@ is postfix increment,
-- than @-2++@ equals @-1@, not @-3@).
--
-- The @buildExpressionParser@ takes care of all the complexity involved in
-- building expression parser. Here is an example of an expression parser
-- that handles prefix signs, postfix increment and basic arithmetic.
--
-- > expr = buildExpressionParser table term <?> "expression"
-- >
-- > term = parens expr <|> natural <?> "simple expression"
-- >
-- > table = [ [ prefix "-" negate
-- > , prefix "+" id ]
-- > , [ postfix "++" (+1) ]
-- > , [ binary "*" (*) AssocLeft
-- > , binary "/" div AssocLeft ]
-- > , [ binary "+" (+) AssocLeft
-- > , binary "-" (-) AssocLeft ] ]
-- >
-- > binary name fun assoc = Infix (reservedOp name >> return fun) assoc
-- > prefix name fun = Prefix (reservedOp name >> return fun)
-- > postfix name fun = Postfix (reservedOp name >> return fun)
buildExpressionParser :: Stream s m t => OperatorTable s u m a ->
ParsecT s u m a -> ParsecT s u m a
buildExpressionParser ops simpleExpr = foldl' makeParser simpleExpr ops
makeParser :: (Foldable t, Stream s m t1) =>
ParsecT s u m b -> t (Operator s u m b) -> ParsecT s u m b
makeParser term ops =
termP >>= \x -> rasP x <|> lasP x <|> nasP x <|> return x <?> "operator"
where (ras, las, nas, prefix, postfix) = foldr splitOp ([],[],[],[],[]) ops
rasOp = choice ras
lasOp = choice las
nasOp = choice nas
prefixOp = choice prefix <?> ""
postfixOp = choice postfix <?> ""
ambigious assoc op =
try $ op >> fail ("ambiguous use of a " ++ assoc
++ " associative operator")
ambigiousRight = ambigious "right" rasOp
ambigiousLeft = ambigious "left" lasOp
ambigiousNon = ambigious "non" nasOp
termP = do
pre <- prefixP
x <- term
post <- postfixP
return $ post (pre x)
postfixP = postfixOp <|> return id
prefixP = prefixOp <|> return id
rasP x = do { f <- rasOp; y <- termP >>= rasP1; return (f x y)}
<|> ambigiousLeft
<|> ambigiousNon
rasP1 x = rasP x <|> return x
lasP x = do { f <- lasOp; y <- termP; lasP1 (f x y) }
<|> ambigiousRight
<|> ambigiousNon
lasP1 x = lasP x <|> return x
nasP x = do
f <- nasOp
y <- termP
ambigiousRight <|> ambigiousLeft <|> ambigiousNon <|> return (f x y)
splitOp :: Operator s u m a ->
( [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a)]
, [ParsecT s u m (a -> a)] ) ->
( [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a -> a)]
, [ParsecT s u m (a -> a)]
, [ParsecT s u m (a -> a)] )
splitOp (Infix op assoc) (ras, las, nas, prefix, postfix) =
case assoc of
AssocNone -> (ras, las, op:nas, prefix, postfix)
AssocLeft -> (ras, op:las, nas, prefix, postfix)
AssocRight -> (op:ras, las, nas, prefix, postfix)
splitOp (Prefix op) (ras, las, nas, prefix, postfix) =
(ras, las, nas, op:prefix, postfix)
splitOp (Postfix op) (ras, las, nas, prefix, postfix) =
(ras, las, nas, prefix, op:postfix)