fused-effects-1.0.0.0: examples/Parser.hs
{-# LANGUAGE DeriveGeneric, DeriveTraversable, ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, StandaloneDeriving, TypeOperators, UndecidableInstances #-}
module Parser
( example
) where
import Control.Algebra
import Control.Carrier.Cut.Church
import Control.Carrier.NonDet.Church
import Control.Carrier.State.Strict
import Control.Monad (replicateM)
import Data.Char
import Data.List (intercalate)
import GHC.Generics (Generic1)
import Hedgehog
import qualified Hedgehog.Function as Fn
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Test.Tasty
import Test.Tasty.Hedgehog
example :: TestTree
example = testGroup "parser"
[ testGroup "parse"
[ testProperty "returns pure values at the end of input" . property $ do
a <- forAll genFactor
run (runNonDetA (parse "" (pure a))) === [a]
, testProperty "fails if input remains" . property $ do
c <- forAll Gen.alphaNum
cs <- forAll (Gen.string (Range.linear 0 10) Gen.alphaNum)
a <- forAll genFactor
run (runNonDetA (parse (c:cs) (pure a))) === []
]
, testGroup "satisfy"
[ testProperty "matches with a predicate" . property $ do
c <- forAll Gen.alphaNum
f <- (. ord) <$> Fn.forAllFn predicate
run (runNonDetA (parse [c] (satisfy f))) === if f c then [c] else []
, testProperty "fails at end of input" . property $ do
f <- (. ord) <$> Fn.forAllFn predicate
run (runNonDetA (parse "" (satisfy f))) === []
, testProperty "fails if input remains" . property $ do
c1 <- forAll Gen.alphaNum
c2 <- forAll Gen.alphaNum
f <- (. ord) <$> Fn.forAllFn predicate
run (runNonDetA (parse [c1, c2] (satisfy f))) === []
, testProperty "consumes input" . property $ do
c1 <- forAll Gen.alphaNum
c2 <- forAll Gen.alphaNum
f <- (. ord) <$> Fn.forAllFn predicate
run (runNonDetA (parse [c1, c2] ((,) <$> satisfy f <*> satisfy f))) === if f c1 && f c2 then [(c1, c2)] else []
]
, testGroup "factor"
[ testProperty "matches positive integers" . property $ do
a <- forAll genFactor
run (runCutA (parse (show (abs a)) factor)) === [abs a]
, testProperty "matches parenthesized expressions" . property $ do
as <- forAll (Gen.sized (arbNested genFactor))
run (runCutA (parse ('(' : intercalate "+" (intercalate "*" . map (show . abs) . (1:) <$> [0]:as) ++ ")") factor)) === [sum (map (product . map abs) as)]
]
, testGroup "term"
[ testProperty "matches factors" . property $ do
a <- forAll genFactor
run (runCutA (parse (show (abs a)) term)) === [abs a]
, testProperty "matches multiplication" . property $ do
as <- forAll genFactors
run (runCutA (parse (intercalate "*" (show . abs <$> 1:as)) term)) === [product (map abs as)]
]
, testGroup "expr"
[ testProperty "matches factors" . property $ do
a <- forAll genFactor
run (runCutA (parse (show (abs a)) expr)) === [abs a]
, testProperty "matches multiplication" . property $ do
as <- forAll genFactors
run (runCutA (parse (intercalate "*" (show . abs <$> 1:as)) expr)) === [product (map abs as)]
, testProperty "matches addition" . property $ do
as <- forAll genFactors
run (runCutA (parse (intercalate "+" (show . abs <$> 0:as)) expr)) === [sum (map abs as)]
, testProperty "respects order of operations" . property $ do
as <- forAll (Gen.sized (arbNested (Gen.integral (Range.linear 0 100))))
run (runCutA (parse (intercalate "+" (intercalate "*" . map (show . abs) . (1:) <$> [0]:as)) expr)) === [sum (map (product . map abs) as)]
]
]
where arbNested :: Gen a -> Range.Size -> Gen [[a]]
arbNested _ 0 = pure []
arbNested g n = do
m <- Gen.integral (Range.linear 0 10)
let n' = n `div` (m + 1)
replicateM (Range.unSize m) (Gen.list (Range.singleton (Range.unSize n')) g)
predicate = Fn.fn Gen.bool
genFactor = Gen.integral (Range.linear 0 100)
genFactors = Gen.list (Range.linear 0 10) genFactor
data Symbol m k = Satisfy (Char -> Bool) (Char -> m k)
deriving (Functor, Generic1)
instance HFunctor Symbol
instance Effect Symbol
satisfy :: Has Symbol sig m => (Char -> Bool) -> m Char
satisfy p = send (Satisfy p pure)
char :: Has Symbol sig m => Char -> m Char
char = satisfy . (==)
digit :: Has Symbol sig m => m Char
digit = satisfy isDigit
parens :: Has Symbol sig m => m a -> m a
parens m = char '(' *> m <* char ')'
parse :: (Alternative m, Monad m) => String -> ParseC m a -> m a
parse input = (>>= exhaustive) . runState input . runParseC
where exhaustive ("", a) = pure a
exhaustive _ = empty
newtype ParseC m a = ParseC { runParseC :: StateC String m a }
deriving (Alternative, Applicative, Functor, Monad)
instance (Alternative m, Algebra sig m, Effect sig) => Algebra (Symbol :+: sig) (ParseC m) where
alg (L (Satisfy p k)) = do
input <- ParseC get
case input of
c:cs | p c -> ParseC (put cs) *> k c
_ -> empty
alg (R other) = ParseC (alg (R (handleCoercible other)))
{-# INLINE alg #-}
expr :: (Alternative m, Has Cut sig m, Has Symbol sig m) => m Int
expr = do
i <- term
call ((i +) <$ char '+' <* cut <*> expr
<|> pure i)
term :: (Alternative m, Has Cut sig m, Has Symbol sig m) => m Int
term = do
i <- factor
call ((i *) <$ char '*' <* cut <*> term
<|> pure i)
factor :: (Alternative m, Has Cut sig m, Has Symbol sig m) => m Int
factor
= read <$> some digit
<|> parens expr