smallcheck-0.2.1: examples/imperative/Properties.hs
import Behaviour
import Interpreter
import Compiler
import Machine
import Syntax
import Value
import Test.SmallCheck
------------- <series of expressions and commands> -------------
-- In the abstract syntax variables are just strings,
-- but we do not want to enumerate all lists of characters.
-- Just a couple of distinct names.
newtype VarName = VarName Name
instance Serial VarName where
series = const [VarName [c] | c <- ['a'..'b']]
var :: VarName -> Expr
var (VarName v) = Var v
assign :: VarName -> Expr -> Command
assign (VarName v) e = (v := e)
-- Uses of depth 0 ensure that all occurrences of variables
-- or literals are treated as zero-depth atoms.
-- The rest is completely standard, but for the use of
-- 'var' for Var and 'assign' for Assign.
instance Serial Value where
series = cons0 Wrong
\/ cons1 Log . depth 0
\/ cons1 Num . depth 0
instance Serial Op1 where
series = const [Not, Minus]
instance Serial Op2 where
series = const [And, Or, Eq, Less, LessEq,
Add, Sub, Mul, Div, Mod]
instance Serial Expr where
series = cons1 var . depth 0
\/ cons1 Val . depth 0
\/ cons2 Uno
\/ cons3 Duo
instance Serial Command where
series = cons0 Skip
\/ cons1 Print
\/ cons2 assign
\/ cons2 (:->)
\/ cons3 If
\/ cons2 While
----------------- <Closed Expressions> -------------------
-- If we want a series for a subset of the values in
-- a given type, one way to define it is via a newtype.
-- Here, expressions without variables.
newtype ClosedExpr = Closed Expr deriving Show
instance Serial ClosedExpr where
series = cons1 val . depth 0
\/ cons2 uno
\/ cons3 duo
where
val v = Closed (Val v)
uno op (Closed e) = Closed (Uno op e)
duo op (Closed e1) (Closed e2) = Closed (Duo op e1 e2)
----------------- <Customised Programs> -----------------
-- The space of all commands grows very quickly with depth,
-- and many syntactically legal commands are bound to fail.
-- Here we define a restricted subset of commands in a
-- 'standard form':
-- -- Skip only occurs as an else-alternative
-- -- Print is only applied to simple variables
-- -- Only integer values are assigned to variables.
-- -- If and While conditions are compound comparisons.
newtype StdCommand = Std Command deriving Show
instance Serial StdCommand where
series = cons1 print'
\/ cons2 assign'
\/ cons2 seq'
\/ cons3 if'
\/ cons2 while'
where
print' (VarName v) = Std (Print (Var v))
assign' (VarName v) (I e) = Std (v := e)
seq' (Std c0) (Std c1) = Std (c0 :-> c1)
if' (B e) (Std c0) (SkipOrStd c1) = Std (If e c0 c1)
while' (B e) (Std c) = Std (While e c)
newtype SkipOrStdCommand = SkipOrStd Command
instance Serial SkipOrStdCommand where
series = cons0 skip
\/ cons1 std . depth 0
where
skip = SkipOrStd Skip
std (Std c) = SkipOrStd c
newtype IExpr = I Expr
instance Serial IExpr where
series = cons1 var' . depth 0
\/ cons1 val' . depth 0
\/ cons1 uno'
\/ cons3 duo'
where
var' (VarName v) = I (Var v)
val' i = I (Val (Num i))
uno' (I e) = I (Uno Minus e)
duo' (I2 d) (I e0) (I e1) = I (Duo d e0 e1)
newtype IOp2 = I2 Op2
instance Serial IOp2 where
series = const [I2 op | op <- [Add, Sub, Mul, Div, Mod]]
newtype BExpr = B Expr
instance Serial BExpr where
series = cons1 uno'
\/ cons3 duo'
\/ cons3 cmp'
where
uno' (B e) = B (Uno Not e)
duo' (B2 d) (B e0) (B e1) = B (Duo d e0 e1)
cmp' (C2 c) (I e0) (I e1) = B (Duo c e0 e1)
newtype BOp2 = B2 Op2
instance Serial BOp2 where
series = const [B2 op | op <- [And,Or]]
newtype COp2 = C2 Op2
instance Serial COp2 where
series = const [C2 op | op <- [Eq,Less,LessEq]]
-------- <depth-bounded equivalence of program traces> --------
newtype Approx = Approx Int deriving Show
instance Serial Approx where
series d = [Approx d]
(=~=) :: Eq a => Trace a -> Trace a -> Approx -> Bool
s =~= t = \(Approx d) -> approx d s t
----------------- <congruence properties> ------------------
prop_Congruence :: Command -> Property
prop_Congruence p =
t1 /= Crash || t2 /= Crash ==>
(t1 =~= t2)
where
t1 = obey p
t2 = exec (compile p)
prop_StdCongruence :: StdCommand -> Property
prop_StdCongruence (Std p) =
prop_Congruence p
main :: IO ()
main = do
putStrLn "-- congruence for all programs:"
smallCheck 2 prop_Congruence
putStrLn "-- congruence for standard-form programs:"
smallCheck 2 prop_StdCongruence