Earley-0.13.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
import qualified Arbitrary
tests :: TestTree
tests = testGroup "Expr"
[ QC.testProperty "Left-recursive: parse . pretty = id" $
\e -> [e] === parseLeftExpr (prettyLeftExpr 0 e)
, QC.testProperty "Left-recursive: parse . pretty = id (generator)" $ do
(e, s) <- Arbitrary.arbitrary $ generator leftExpr tokens
return
$ [e] === parseLeftExpr (prettyLeftExpr 0 e)
.&&. [e] === parseLeftExpr (unwords s)
, QC.testProperty "Right-recursive: parse . pretty = id" $
\e -> [e] === parseRightExpr (prettyRightExpr 0 e)
, QC.testProperty "Right-recursive: parse . pretty = id (generator)" $ do
(e, s) <- Arbitrary.arbitrary $ generator rightExpr tokens
return
$ [e] === parseRightExpr (prettyRightExpr 0 e)
.&&. [e] === parseRightExpr (unwords s)
, QC.testProperty "Ambiguous: parse . pretty ≈ id" $
\e -> e `elem` parseAmbiguousExpr (prettyLeftExpr 0 e)
.&&. e `elem` parseAmbiguousExpr (prettyRightExpr 0 e)
.&&. [e] == parseAmbiguousExpr (prettyAmbiguousExpr e)
, QC.testProperty "Ambiguous: parse . pretty ≈ id (generator)" $ do
(e, s) <- Arbitrary.arbitrary $ generator ambiguousExpr tokens
return $ e `elem` parseAmbiguousExpr (prettyLeftExpr 0 e)
.&&. e `elem` parseAmbiguousExpr (prettyRightExpr 0 e)
.&&. [e] == parseAmbiguousExpr (prettyAmbiguousExpr e)
.&&. e `elem` parseAmbiguousExpr (unwords s)
]
tokens :: [String]
tokens = pure <$> "abcxyz+*()"
parseLeftExpr :: String -> [Expr]
parseLeftExpr input = fst (fullParses (parser leftExpr) (lexExpr input))
parseRightExpr :: String -> [Expr]
parseRightExpr input = fst (fullParses (parser rightExpr) (lexExpr input))
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` 3)
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 ]
leftExpr :: Grammar r (Prod r String String Expr)
leftExpr = 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
rightExpr :: Grammar r (Prod r String String Expr)
rightExpr = mdo
x1 <- rule $ Add <$> x2 <* namedToken "+" <*> x1
<|> x2
<?> "sum"
x2 <- rule $ Mul <$> x3 <* namedToken "*" <*> x2
<|> 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
prettyLeftExpr :: Int -> Expr -> String
prettyLeftExpr _ (Var s) = s
prettyLeftExpr d (Add a b) = prettyParens (d > 0) $ prettyLeftExpr 0 a ++ " + " ++ prettyLeftExpr 1 b
prettyLeftExpr d (Mul a b) = prettyParens (d > 1) $ prettyLeftExpr 1 a ++ " * " ++ prettyLeftExpr 2 b
prettyRightExpr :: Int -> Expr -> String
prettyRightExpr _ (Var s) = s
prettyRightExpr d (Add a b) = prettyParens (d > 0) $ prettyRightExpr 1 a ++ " + " ++ prettyRightExpr 0 b
prettyRightExpr d (Mul a b) = prettyParens (d > 1) $ prettyRightExpr 2 a ++ " * " ++ prettyRightExpr 1 b
prettyAmbiguousExpr :: Expr -> String
prettyAmbiguousExpr (Var s) = s
prettyAmbiguousExpr (Add a b) = prettyParens True $ prettyAmbiguousExpr a ++ " + " ++ prettyAmbiguousExpr b
prettyAmbiguousExpr (Mul a b) = prettyParens True $ prettyAmbiguousExpr a ++ " * " ++ prettyAmbiguousExpr 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)