fused-effects-0.5.0.0: examples/Parser.hs
{-# LANGUAGE DeriveAnyClass, DeriveGeneric, DeriveTraversable, DerivingStrategies, ExistentialQuantification, FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, StandaloneDeriving, TypeOperators, UndecidableInstances #-}
module Parser
( spec
) where
import Control.Effect.Carrier
import Control.Effect.Cut
import Control.Effect.NonDet
import Control.Effect.State
import Control.Monad (replicateM)
import Data.Char
import Data.List (intercalate)
import GHC.Generics (Generic1)
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
spec :: Spec
spec = describe "parser" $ do
describe "parse" $ do
prop "returns pure values at the end of input" $
\ a -> run (runNonDet (parse "" (pure a))) == [a :: Integer]
prop "fails if input remains" $
\ c cs a -> run (runNonDet (parse (c:cs) (pure (a :: Integer)))) == []
describe "satisfy" $ do
prop "matches with a predicate" $
\ c f -> run (runNonDet (parse [c] (satisfy (applyFun f)))) == if applyFun f c then [c] else []
prop "fails at end of input" $
\ f -> run (runNonDet (parse "" (satisfy (applyFun f)))) == []
prop "fails if input remains" $
\ c1 c2 f -> run (runNonDet (parse [c1, c2] (satisfy (applyFun f)))) == []
prop "consumes input" $
\ c1 c2 f -> run (runNonDet (parse [c1, c2] ((,) <$> satisfy (applyFun f) <*> satisfy (applyFun f)))) == if applyFun f c1 && applyFun f c2 then [(c1, c2)] else []
describe "factor" $ do
prop "matches positive integers" $
\ a -> run (runNonDet (runCut (parse (show (abs a)) factor))) == [abs a]
prop "matches parenthesized expressions" . forAll (sized arbNested) $
\ as -> run (runNonDet (runCut (parse ('(' : intercalate "+" (intercalate "*" . map (show . abs) . (1:) <$> [0]:as) ++ ")") factor))) == [sum (map (product . map abs) as)]
describe "term" $ do
prop "matches factors" $
\ a -> run (runNonDet (runCut (parse (show (abs a)) term))) == [abs a]
prop "matches multiplication" $
\ as -> run (runNonDet (runCut (parse (intercalate "*" (show . abs <$> 1:as)) term))) == [product (map abs as)]
describe "expr" $ do
prop "matches factors" $
\ a -> run (runNonDet (runCut (parse (show (abs a)) expr))) == [abs a]
prop "matches multiplication" $
\ as -> run (runNonDet (runCut (parse (intercalate "*" (show . abs <$> 1:as)) expr))) == [product (map abs as)]
prop "matches addition" $
\ as -> run (runNonDet (runCut (parse (intercalate "+" (show . abs <$> 0:as)) expr))) == [sum (map abs as)]
prop "respects order of operations" . forAll (sized arbNested) $
\ as -> run (runNonDet (runCut (parse (intercalate "+" (intercalate "*" . map (show . abs) . (1:) <$> [0]:as)) expr))) == [sum (map (product . map abs) as)]
where arbNested :: Arbitrary a => Int -> Gen [[a]]
arbNested 0 = pure []
arbNested n = do
Positive m <- arbitrary
let n' = n `div` (m + 1)
replicateM m (vector n')
data Symbol m k = Satisfy (Char -> Bool) (Char -> m k)
deriving stock (Functor, Generic1)
deriving anyclass (HFunctor, Effect)
satisfy :: (Carrier sig m, Member Symbol sig) => (Char -> Bool) -> m Char
satisfy p = send (Satisfy p pure)
char :: (Carrier sig m, Member Symbol sig) => Char -> m Char
char = satisfy . (==)
digit :: (Carrier sig m, Member Symbol sig) => m Char
digit = satisfy isDigit
parens :: (Carrier sig m, Member Symbol sig) => 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 newtype (Alternative, Applicative, Functor, Monad)
instance (Alternative m, Carrier sig m, Effect sig) => Carrier (Symbol :+: sig) (ParseC m) where
eff (L (Satisfy p k)) = do
input <- ParseC get
case input of
c:cs | p c -> ParseC (put cs) *> k c
_ -> empty
eff (R other) = ParseC (eff (R (handleCoercible other)))
{-# INLINE eff #-}
expr :: (Alternative m, Carrier sig m, Member Cut sig, Member Symbol sig) => m Int
expr = do
i <- term
call ((i +) <$ char '+' <* cut <*> expr
<|> pure i)
term :: (Alternative m, Carrier sig m, Member Cut sig, Member Symbol sig) => m Int
term = do
i <- factor
call ((i *) <$ char '*' <* cut <*> term
<|> pure i)
factor :: (Alternative m, Carrier sig m, Member Cut sig, Member Symbol sig) => m Int
factor
= read <$> some digit
<|> parens expr