Earley-0.11.0.1: tests/Expr.hs
{-# LANGUAGE RecursiveDo #-}
module Expr where
import Control.Applicative
import Data.Char
import Test.Tasty
import Test.Tasty.QuickCheck as QC
import Text.Earley
tests :: TestTree
tests = testGroup "Expr"
[ QC.testProperty "Expr: parse . pretty = id" $
\e -> [e] === parseExpr (prettyExpr 0 e)
, QC.testProperty "Ambiguous Expr: parse . pretty ≈ id" $
\e -> e `elem` parseAmbiguousExpr (prettyExpr 0 e)
]
parseExpr :: String -> [Expr]
parseExpr input = fst (fullParses (parser expr) (lexExpr input)) -- We need to annotate types for point-free version
parseAmbiguousExpr :: String -> [Expr]
parseAmbiguousExpr input = fst (fullParses (parser ambiguousExpr) (lexExpr input))
data Expr
= Add Expr Expr
| Mul Expr Expr
| Var String
deriving (Eq, Ord, Show)
instance Arbitrary Expr where
arbitrary = sized arbExpr
where arbIdent = Var <$> elements ["a", "b", "c", "x", "y", "z"]
arbExpr n | n > 0 = oneof [ arbIdent
, Add <$> arbExpr1 <*> arbExpr1
, Mul <$> arbExpr1 <*> arbExpr1
]
where arbExpr1 = arbExpr (n `div` 2)
arbExpr _ = arbIdent
shrink (Var _) = []
shrink (Add a b) = a : b : [ Add a' b | a' <- shrink a ] ++ [ Add a b' | b' <- shrink b ]
shrink (Mul a b) = a : b : [ Mul a' b | a' <- shrink a ] ++ [ Mul a b' | b' <- shrink b ]
expr :: Grammar r (Prod r String String Expr)
expr = mdo
x1 <- rule $ Add <$> x1 <* namedToken "+" <*> x2
<|> x2
<?> "sum"
x2 <- rule $ Mul <$> x2 <* namedToken "*" <*> x3
<|> x3
<?> "product"
x3 <- rule $ Var <$> (satisfy ident <?> "identifier")
<|> namedToken "(" *> x1 <* namedToken ")"
return x1
where
ident (x:_) = isAlpha x
ident _ = False
ambiguousExpr :: Grammar r (Prod r String String Expr)
ambiguousExpr = mdo
x1 <- rule $ Add <$> x1 <* namedToken "+" <*> x1
<|> x2
<?> "sum"
x2 <- rule $ Mul <$> x2 <* namedToken "*" <*> x2
<|> x3
<?> "product"
x3 <- rule $ Var <$> (satisfy ident <?> "identifier")
<|> namedToken "(" *> x1 <* namedToken ")"
return x1
where
ident (x:_) = isAlpha x
ident _ = False
prettyParens :: Bool -> String -> String
prettyParens True s = "(" ++ s ++ ")"
prettyParens False s = s
prettyExpr :: Int -> Expr -> String
prettyExpr _ (Var s) = s
prettyExpr d (Add a b) = prettyParens (d > 0) $ prettyExpr 0 a ++ " + " ++ prettyExpr 1 b
prettyExpr d (Mul a b) = prettyParens (d > 1) $ prettyExpr 1 a ++ " * " ++ prettyExpr 2 b
-- @words@ like lexer, but consider parentheses as separate tokens
lexExpr :: String -> [String]
lexExpr "" = []
lexExpr ('(' : s) = "(" : lexExpr s
lexExpr (')' : s) = ")" : lexExpr s
lexExpr (c : s)
| isSpace c = lexExpr s
| otherwise = let (tok, rest) = span p (c : s)
in tok : lexExpr rest
where p x = not (x == '(' || x == ')' || isSpace x)