{-# LANGUAGE GADTs #-}
module Problem2 where
import Control.Monad
import Test.QuickCheck
data Parser tok a where
Zero :: Parser tok ()
One :: Parser tok ()
Check :: (tok -> Bool) -> Parser tok tok
Satisfy :: ([tok] -> Bool) -> Parser tok [tok]
Push :: tok -> Parser tok a -> Parser tok a
Plus :: Parser tok a -> Parser tok b -> Parser tok (Either a b)
Times :: Parser tok a -> Parser tok b -> Parser tok (a, b)
Star :: Parser tok a -> Parser tok [a]
parse :: MonadPlus m => Parser tok a -> [tok] -> m a
-- |Zero| always fails.
parse Zero ts = mzero
-- |One| matches only the empty string.
parse One [] = return ()
parse One _ = mzero
-- |Check p| matches a string with exactly one token |t| such that |p t| holds.
parse (Check p) [t] = if p t then return t else mzero
parse (Check p) _ = mzero
-- |Satisfy p| matches any string such that |p ts| holds.
parse (Satisfy p) xs = if p xs then return xs else mzero
-- |Push t p| matches a string |ts| when |p| matches |(t:ts)|.
parse (Push t p) ts = parse p (t:ts)
-- |Plus p q| matches when either |p| or |q| does.
parse (Plus p q) ts = liftM Left (parse p ts) `mplus`
liftM Right (parse q ts)
----------------------------------------------------------------
-- Solution to 3a)
-- |Times p q| matches the concatenation of |p| and |q|.
parse (Times p q) ts = parseTimes p q ts
-- |Star p| matches zero or more copies of |p|.
parse (Star p) ts = parseStar p ts
parseStar :: MonadPlus m => Parser tok a -> [tok] -> m [a]
parseStar p [] = return []
parseStar p (t:ts) = do
(v,vs) <- parse (Times p (Star p)) (t:ts)
return (v:vs)
parseTimes :: MonadPlus m =>
Parser tok a -> Parser tok b -> [tok] -> m (a, b)
parseTimes p q [] = liftM2 (,) (parse p []) (parse q [])
parseTimes p q (t:ts) =
parse (Times (Push t p) q) ts `mplus`
liftM2 (,) (parse p []) (parse q (t:ts))
----------------------------------------------------------------
-- Solution to 3b)
newtype P m tok a = P {runP :: [tok] -> m a}
zero :: MonadPlus m => P m tok ()
one :: MonadPlus m => P m tok ()
check :: MonadPlus m => (tok -> Bool) -> P m tok tok
satisfy :: MonadPlus m => ([tok] -> Bool) -> P m tok [tok]
plus :: MonadPlus m => P m tok a -> P m tok b -> P m tok (Either a b)
zero = P (const mzero)
one = P (\xs -> if null xs then return () else mzero)
check p = P (\xs -> let n = length xs; x = head xs
in if n==1 && p x then return x else mzero)
satisfy p = P (\xs -> if p xs then return xs else mzero)
plus (P p) (P q) = P (\xs -> liftM Left (p xs) `mplus` liftM Right (q xs))
----------------------------------------------------------------
-- Below is some testing code and some variant solutions copied from
-- students (not guaranteed to work).
-- Examples: (not part of the exam question)
-- Looping test:
test :: Maybe [()]
test = parse (Star One) "I really must get to the bottom of this..."
token x = Check (x ==)
string xs = Satisfy (xs ==)
p = Times (token 'a') (token 'b')
p1 = Times (Star (token 'a')) (Star (token 'b'))
p2 = Star p1
blocks :: (Eq tok) => Parser tok [[tok]]
blocks = Star (Satisfy allEqual)
where allEqual xs = and (zipWith (==) xs (drop 1 xs))
evenOdd = Plus (Star (Times (Check even) (Check odd)))
(Star (Times (Check odd) (Check even)))
----------------
-- a very rudimentary test suite:
test1 xs = parse p xs == if xs == "ab" then Just ('a','b') else Nothing
test2 xs = if null rest
then label "Just" $ Just (as, bs) == parse p1 xs
else label "triv" $ Nothing == parse p1 xs
where (as, bs') = (takeWhile ('a'==) xs, dropWhile ('a'==) xs)
(bs, rest)= (takeWhile ('b'==) xs, dropWhile ('b'==) xs)
-- This test depends on the choice / order of matches
test3 = parse p2 "aaabbbbaabbbbbbbaaabbabab" ==
Just [("aaa","bbbb"),("aa","bbbbbbb"),("aaa","bb"),("a","b"),("a","b")]
-- This test depends on the choice / order of matches
test4 = parse blocks "aaaabbbbbbbbcccccddd" ==
Just ["aaaa","bbbbbbbb","ccccc","ddd"]
test5 = parse evenOdd [0..9] ==
Just (Left [(0,1),(2,3),(4,5),(6,7),(8,9)])
test6 = parse evenOdd [1..10] ==
Just (Right [(1,2),(3,4),(5,6),(7,8),(9,10)])
main = do quickCheck test1
quickCheck test2
print $ test3 && test4 && test5 && test6
mconcat :: MonadPlus m => [m a] -> m a
mconcat = foldr mplus mzero
parseTimes2 :: MonadPlus m =>
Parser tok a -> Parser tok b -> [tok] -> m (a, b)
parseTimes2 p q ts = mconcat as
where as = [ liftM2 (,) (parse p pre) (parse q suf)
| (pre,suf) <- preAndSuf ts
]
preAndSuf :: [a] -> [([a],[a])]
preAndSuf xs = [ splitAt n xs | n <- [0..length xs] ]
parseTimes3 :: MonadPlus m =>
Parser tok a -> Parser tok b -> [tok] -> m (a, b)
parseTimes3 p q ts = mconcat [ do a <- parse p (take i ts)
b <- parse q (drop i ts)
return (a, b)
| i <- [0..length ts] ]
parseStar2 :: MonadPlus m => Parser tok a -> [tok] -> m [a]
parseStar2 p ts =
(if null ts then return [] else mzero) `mplus`
liftM (uncurry (:)) (parse (Times p (Star p)) ts)
parseStar3 :: MonadPlus m => Parser tok a -> [tok] -> m [a]
parseStar3 p [] = return []
parseStar3 p ts = mconcat [ do a <- parse p (take i ts)
as <- parse (Star p) (drop i ts)
return (a:as)
| i <- [1..length ts] ]
test7 xs = parseTimes3 p q xs == (parseTimes p q xs :: Maybe (Char, Char))
where p = Check ('o'<)
q = Check ('o'>=)
test8 xs = parseStar p xs == (parseStar3 p xs :: Maybe String)
where p = Check ('o'<)