array-forth-0.2.0.4: test/Language/ArrayForth/Test.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Main where
import Control.Applicative ((<$>), (<*>))
import Data.Bits (complement, xor, (.&.))
import Language.ArrayForth.Interpreter
import Language.ArrayForth.NativeProgram
import Language.ArrayForth.Opcode
import Language.ArrayForth.Parse ()
import Language.ArrayForth.Program
import Language.ArrayForth.Stack
import Language.ArrayForth.State hiding (State (..))
import qualified Language.ArrayForth.State as S
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
import Test.Framework.TH
import Test.HUnit
import Test.QuickCheck (forAll, (==>))
import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
import Test.QuickCheck.Gen (Gen, elements, listOf,
oneof)
instance Arbitrary F18Word where arbitrary = fromInteger <$> arbitrary
wordBits bits = (((2 ^ bits) - 1) .&.) <$> arbitrary
instance Arbitrary Stack where arbitrary = foldl push empty <$> arbitrary
instance Arbitrary Opcode where arbitrary = elements opcodes
straight, jumps, fast, slow, inSlot3 :: Gen Opcode
straight = elements $ filter (not . isJump) opcodes
jumps = elements $ filter isJump opcodes
fast = elements $ filter (\ e -> opcodeTime e == 1.5) opcodes
slow = elements $ filter (\ e -> opcodeTime e == 5) opcodes
inSlot3 = elements $ filter slot3 opcodes
instance Arbitrary Instrs where arbitrary = oneof [instrs, jump3, jump2, jump1, constant]
instrs, jump3, jump2, jump1, constant :: Gen Instrs
instrs = Instrs <$> straight <*> straight <*> straight <*> inSlot3
jump3 = Jump3 <$> straight <*> straight <*> jumps <*> wordBits 3
jump2 = Jump2 <$> straight <*> jumps <*> wordBits 8
jump1 = Jump1 <$> jumps <*> wordBits 10
constant = Constant <$> arbitrary
instance Arbitrary Instruction where
arbitrary = oneof [opcode, number, unused]
opcode, number, unused :: Gen Instruction
opcode = Opcode <$> straight
jump = Jump <$> jumps <*> (Concrete <$> arbitrary)
number = Number <$> arbitrary
unused = return Unused
straightlineProgram :: Gen Program
straightlineProgram = listOf $ oneof [Opcode <$> straight, number, unused]
main = $(defaultMainGenerator)
run = runNativeProgram startState . read
-- Instruction utilities tests:
prop_bits word = word == (toBits $ fromBits word)
prop_opcode word = word < 0x20 ==> word == (fromOpcode $ toOpcode word)
prop_pushPop word stack = word == snd (pop $ push stack word)
prop_pop stack = stack == foldl1 (.) (replicate 8 $ fst . pop) stack
prop_runningTimeConstant program = forAll constant $ \ c ->
runningTime (program ++ [c]) == runningTime program
prop_showReadProgram :: Program -> Bool
prop_showReadProgram program = program == read (show program)
prop_showReadNative :: NativeProgram -> Bool
prop_showReadNative program = program == read (show program)
-- Returns whether the given instruction word has jump addresses for
-- all the jumps and has no jumps without addresses.
isValid :: Instrs -> Bool
isValid (Instrs a b c d) = all (not . isJump) [a, b, c] && slot3 d
isValid (Jump3 a b c addr) = all (not . isJump) [a, b] && isJump c
isValid (Jump2 a b addr) = not (isJump a) && isJump b
isValid (Jump1 a addr) = isJump a
isValid Constant{} = True
-- For now, we do not really support jumps in the Program type.
prop_validNative = forAll straightlineProgram $ \ p -> all isValid $ toNative p
case_runningTime = do let time = runningTime . read
11.0 @=? time ". . . . @p . . . 10"
0 @=? time ". . . ."
20 @=? time "@p @p @p @p 1 2 3 4"
-- Testing the utility functions for actually synthesizing programs:
case_toNative = do read "@p . @p . 2 10 or . . ." @=?
toNative [Number 2, Opcode Nop, Number 10, Opcode Or]
read "@p . @p . 2 10 + . . ." @=?
toNative [Number 2, Opcode Nop, Number 10, Opcode Plus]
read "jump 5 . + @p @p 1 2 @p . . . 3" @=?
toNative (read ":foo jump :bar + 1 2 3 :bar")
case_fromNative = do [Opcode Nop, Opcode Nop, Opcode Nop, Opcode Nop] @=?
fromNative (read ". . . .")
[Opcode Nop, Number 1, Number 2, Opcode Nop] @=?
fromNative (read ". @p @p . 1 2")
-- Interpreter tests (ported from Racket):
unchanged = assertBool "Something changed!" . all (== 0)
a,b,p,r,s,t :: (?res :: S.State) => F18Word
a = S.a ?res
b = S.b ?res
p = S.p ?res
r = S.r ?res
s = S.s ?res
t = S.t ?res
memory :: (?res :: S.State) => Memory
memory = S.memory ?res
dataStack :: (?res :: S.State) => Stack
dataStack = S.dataStack ?res
case_1 = do let ?res = run "@p @p . + 2 3"
3 @=? p
5 @=? t
unchanged [a, b, r, s]
case_2 = do let ?res = run "@p - . . 0"
2 @=? p
(- 1) @=? t
unchanged [a, b, r, s]
case_3 = do let ?res = run "@p b! @p . 4 42 !b @p . ."
5 @=? p
42 @=? t
4 @=? b
42 @=? memory ! 4
unchanged [a, r, s]
case_4 = do let ?res = run "- dup dup dup dup dup dup dup"
2 @=? p
(- 1) @=? t
(- 1) @=? s
fill empty [0, 0, -1, -1, -1, -1, -1, -1] @=? dataStack
unchanged [a, b, r]
case_5 = do let ?res = run "dup or a! @p 123 !+ @p ! . 456 dup or a! . @+ 2* @+ . 2/ + ! ."
2 @=? a
123 @=? memory ! 0
456 @=? memory ! 1
474 @=? memory ! 2
7 @=? p
unchanged [b, r, s, t]
case_ret = do let ?res = run "call 2 . . . . ; . . ."
1 @=? p
unchanged [a, b, r, s, t]
case_jump = do let ?res = run "jump 42"
42 @=? p
unchanged [a, b, r, s, t]
case_call = do let ?res = run "call 10"
1 @=? r
10 @=? p
unchanged [a, b, s, t]
case_unext = do let ?res = run ". . unext ."
1 @=? p
unchanged [a, b, r, s, t]
case_unext' = do let ?res = run "@p push . . 41 @+ . . unext"
3 @=? p
42 @=? a
unchanged [b, r, s, t]
case_if = do let ?res = run "if 42"
1 @=? p
unchanged [a, b, r, s, t]
case_if' = do let ?res = run "@p if 42 10"
42 @=? p
unchanged [a, b, r, s]
case_minusIf = do let ?res = run "-if 42"
42 @=? p
unchanged [a, b, r, s, t]
case_minusIf' = do let ?res = run "- -if 42"
1 @=? p
unchanged [a, b, r, s]
case_fetchP = do let ?res = run "@p . . . 42"
2 @=? p
42 @=? t
unchanged [a, b, r, s]
case_fetchPlus = do let ?res = run "@+ . . ."
1 @=? a
memory ! 0 @=? t
unchanged [b, r, s]
case_fetchB = do let ?res = run "@b . . ."
memory ! 0 @=? t
unchanged [b, r, s]
case_fetch = do let ?res = run "@ . . ."
memory ! 0 @=? t
unchanged [b, r, s]
case_storeP = do let ?res = run "@p !p . . 42"
3 @=? p
42 @=? memory ! (p - 1)
unchanged [a, b, r, s]
case_storePlus = do let ?res = run "@p !+ . . 42"
1 @=? a
2 @=? p
42 @=? memory ! 0
unchanged [b, r, s]
case_storePlus' = do let ?res = run "@p @p a! . 42 10 !+ . . ."
11 @=? a
4 @=? p
42 @=? memory ! 10
unchanged [b, r, s]
case_storePlus'' = do let ?res = run "dup or a! @p 123 !+ @p ! . 456"
1 @=? a
123 @=? memory ! 0
456 @=? memory ! 1
unchanged [b, r, s, t]
case_storeB = do let ?res = run "@p !b . . 42"
2 @=? p
42 @=? memory ! 0
unchanged [a, b, r, s]
case_storeB' = do let ?res = run "@p @p b! . 42 10 !b . . ."
4 @=? p
42 @=? memory ! 10
10 @=? b
unchanged [a, r, s]
case_store = do let ?res = run "@p ! . . 42"
2 @=? p
42 @=? memory ! 0
unchanged [a, b, r, s]
case_store' = do let ?res = run "@p @p a! . 42 10 ! . . ."
10 @=? a
4 @=? p
42 @=? memory ! 10
unchanged [b, r, s]
case_store'' = do let ?res = run "dup or a! @p 123 ! . . ."
0 @=? a
123 @=? memory ! 0
unchanged [b, r, s, t]
case_multiplyStepEven = do let ?res = run "@p @p @p . 10 0 10 a! +* . ."
5 @=? a
10 @=? s
0 @=? t
unchanged [b, r]
case_multiplyStepOdd = do let ?res = run "@p @p @p . 10 0 11 a! +* . ."
5 @=? a
10 @=? s
5 @=? t
unchanged [b, r]
case_multiplyStep = do let ?res = run "@p @p @p . 262143 0 1 a! +* . ."
0x20000 @=? a
0x3ffff @=? s
0x3ffff @=? t
unchanged [b, r]
case_multiply = do let ?res = run "@p @p @p . 10 0 11 a! +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* ."
110 @=? a
10 @=? s
0 @=? t
unchanged [b, r]
case_multiply' = do let ?res = run "@p @p @p . 262143 0 1 a! +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* ."
0x3ffff @=? a
0x3ffff @=? s
0x3ffff @=? t
unchanged [b, r]
case_multiply'' = do let ?res = run "@p @p @p . 262143 0 262143 a! +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* ."
1 @=? a
0x3ffff @=? s
0x3ffff @=? t
unchanged [b, r]
case_multiply''' = do let ?res = run "@p @p @p . 1 0 262143 a! +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* ."
0x3ffff @=? a
1 @=? s
0 @=? t
unchanged [b, r]
case_multiply'''' = do let ?res = run "@p @p @p . 261612 0 7276 a! +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* ."
0xef90 @=? a
0x3fdec @=? s
0x3fff1 @=? t
unchanged [b, r]
case_times2 = do let ?res = run "@p 2* . . 2"
4 @=? t
2 @=? p
unchanged [a, b, r, s]
case_div2 = do let ?res = run "@p 2/ . . 4"
2 @=? t
2 @=? p
unchanged [a, b, r, s]
case_not = do let ?res = run "- . . ."
(- 1) @=? t
1 @=? p
unchanged [a, b, r, s]
case_not' = do let ?res = run "@p - . . 42"
complement 42 @=? t
2 @=? p
unchanged [a, b, r, s]
case_plus = do let ?res = run "@p @p . + 12 30"
42 @=? t
3 @=? p
unchanged [a, b, r, s]
case_and = do let ?res = run "@p @p and . 12 30"
12 .&. 30 @=? t
3 @=? p
unchanged [a, b, r, s]
case_or = do let ?res = run "@p @p or . 12 30"
12 `xor` 30 @=? t
3 @=? p
unchanged [a, b, r, s]
case_drop = do let ?res = run "@p @p drop . 1 2"
1 @=? t
3 @=? p
unchanged [a, b, r, s]
case_dup = do let ?res = run "@p dup . . 42"
42 @=? t
42 @=? s
2 @=? p
unchanged [a, b, r]
case_dup' = do let ?res = run "@p dup or . 42"
0 @=? t
2 @=? p
unchanged [a, b, r, s]
case_pop = do let ?res = run "call 2 0 pop . . ."
1 @=? t
unchanged [a, b, r, s]
case_over = do let ?res = run "@p @p over . 1 2"
1 @=? t
2 @=? s
3 @=? p
unchanged [a, b, r]
case_a = do let ?res = run "@p a! a . 42"
42 @=? a
42 @=? t
2 @=? p
unchanged [b, r, s]
case_nop = do let ?res = step $ run ". . . ."
1 @=? p
unchanged [a, b, r, s, t]
case_push = do let ?res = run "@p push . . 42"
42 @=? r
2 @=? p
unchanged [a, b, s, t]
case_setB = do let ?res = run "@p b! . . 42"
42 @=? b
2 @=? p
unchanged [a, r, s, t]
case_setA = do let ?res = run "@p a! . . 42"
42 @=? a
2 @=? p
unchanged [b, r, s, t]