fused-effects-1.1.1.1: examples/Parser.hs
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE 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.Kind (Type)
import Data.List (intercalate)
import Hedgehog
import qualified Hedgehog.Function as Fn
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Utils
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))) === [c | f c]
, 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, c2) <- forAll ((,) <$> Gen.alphaNum <*> 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))) === [(c1, c2) | f c1, f c2]
]
, 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 :: Type -> Type) k where
Satisfy :: (Char -> Bool) -> Symbol m Char
satisfy :: Has Symbol sig m => (Char -> Bool) -> m Char
satisfy p = send (Satisfy p)
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) => Algebra (Symbol :+: sig) (ParseC m) where
alg hdl sig ctx = case sig of
L (Satisfy p) -> do
input <- ParseC get
case input of
c:cs | p c -> c <$ ctx <$ ParseC (put cs)
_ -> empty
R other -> ParseC (alg (runParseC . hdl) (R other) ctx)
{-# 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