packages feed

smallcheck-0.2.1: examples/regular/Regular.hs

module Regular where

import Data.Char (isAlpha)
import Data.List (intersperse)
import Control.Monad (liftM)
import Test.SmallCheck

-- A data type of regular expressions.

data RE = Emp
        | Lam
        | Sym Char
	| Alt [RE]
        | Cat [RE]
	| Rep RE
	deriving Eq

isEmp, isLam, isSym, isCat, isAlt, isRep :: RE -> Bool
isEmp Emp     = True
isEmp _       = False
isLam Lam     = True
isLam _       = False
isSym (Sym _) = True
isSym _       = False
isAlt (Alt _) = True
isAlt _       = False
isCat (Cat _) = True
isCat _       = False
isRep (Rep _) = True
isRep _       = False

-- Syms may be used to represent terminals or variables.
-- Using cat and alt instead of Cat and Alt ensures that:
-- (1) Cat and Alt arguments are multi-item lists;
-- (2) items in Cat arguments are not Cats;
-- (3) items in Alt arguments are not Alts.

cat :: [RE] -> RE
cat []  = Lam
cat [x] = x
cat xs  = Cat (concatMap catList xs)
  where
  catList (Cat ys) = ys
  catList z        = [z]

alt :: [RE] -> RE
alt []  = Emp
alt [x] = x
alt xs  = Alt (concatMap altList xs)
  where
  altList (Alt ys) = ys
  altList z        = [z]

instance Read RE where
  readsPrec _ s  = [rest s [[[]]]]

rest :: String -> [[[RE]]] -> (RE,String)
rest ""      (    a:as) = if null as then (a2re a,"")
                          else wrong
rest ('+':s) ((c:a):as) = if null c then wrong
			  else rest s (([]:c:a):as)
rest ('*':s) ((c:a):as) = case c of
                          []     -> wrong
                          (x:xs) -> rest s (((Rep x:xs):a):as)
rest ('0':s) ((c:a):as) = rest s (((Emp:c):a):as)
rest ('1':s) ((c:a):as) = rest s (((Lam:c):a):as)
rest ('(':s) as         = rest s ([[]]:as)
rest (')':s) (a:as)     = case as of
                          [] -> wrong
			  ((c:a'):as') -> rest s (((a2re a:c):a'):as')
rest (' ':s) as         = rest s as
rest (v  :s) ((c:a):as) = if isAlpha v then rest s (((Sym v:c):a):as)
                          else if null as then (a2re (c:a),v:s)
			  else wrong

a2re :: [[RE]] -> RE
a2re = alt . reverse . map (cat . reverse)

wrong = error "unreadable RE"

instance Show RE where
  show Emp      = "0"
  show Lam      = "1"
  show (Sym c)  = [c]
  show (Alt xs) = concat (intersperse "+" (map show xs))
  show (Cat xs) = concatMap (showBrackIf isAlt) xs
  show (Rep x)  = showBrackIf (\x -> isCat x || isAlt x) x ++ "*"

showBrackIf p x = ['(' | q] ++ show x ++ [')' | q] where q = p x

instance Serial RE where
  series = cons0 Emp
        \/ cons0 Lam
        \/ cons1 Sym . depth 0
        \/ cons1 alt
        \/ cons1 cat
        \/ cons1 Rep

prop_readShow :: RE -> IO Bool
prop_readShow re = do
  writeFile "tmp" (show re)
  re' <- liftM read (readFile "tmp")
  return (re'==re)

main = do
  rule
  putStrLn "Testing property involving IO.  Always returns True?"
  putStrLn "do\n\
           \  writeFile \"tmp\" (show re)\n\
           \  re' <- liftM read (readFile \"tmp\")\n\
           \  return (re'==re)"
  rule
  smallCheck 4 prop_readShow
  where
  rule = putStrLn
           "----------------------------------------------------"