-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLoxArbitrary.hs
225 lines (195 loc) · 7.37 KB
/
LoxArbitrary.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
module LoxArbitrary where
import Control.Applicative
import Control.Monad (guard)
import Data.Char qualified as Char
import Data.List qualified as List
import Data.Map (Map, (!?))
import Data.Map qualified as Map
import Data.Maybe (fromMaybe, isJust, isNothing)
import LoxStepper
import LoxSyntax
import ParserLib (Parser)
import ParserLib qualified as P
import State (State)
import State qualified as S
import Test.HUnit (Assertion, Counts, Test (..), assert, runTestTT, (~:), (~?=))
import Test.QuickCheck (Arbitrary (..), Gen)
import Test.QuickCheck qualified as QC
import Text.PrettyPrint (Doc, (<+>))
import Text.PrettyPrint qualified as PP
import Text.Read (readMaybe)
-- hi
quickCheckN :: (QC.Testable prop) => Int -> prop -> IO ()
quickCheckN n = QC.quickCheckWith $ QC.stdArgs {QC.maxSuccess = n, QC.maxSize = 100}
genId :: Gen Id
genId = arbitrary
-- Generator for Maybe Id
genMaybeId :: Gen (Maybe Id)
genMaybeId =
QC.frequency
[ (1, pure Nothing),
(2, Just <$> genId)
]
-- Generator for Table
genTable :: Gen Table
genTable = Map.fromList <$> QC.listOf ((,) <$> arbitrary <*> arbitrary)
-- Generator for Environment
genEnvironment :: Gen Environment
genEnvironment = Env <$> genMemory <*> genMaybeId
where
genMemory :: Gen (Map Name Table)
genMemory = Map.fromList <$> QC.listOf ((,) <$> arbitrary <*> genTable)
-- Generator for Environments
genEnvironments :: Gen Environments
genEnvironments = Map.fromList <$> QC.listOf ((,) <$> genId <*> genEnvironment)
genMaybe :: Gen a -> Gen (Maybe a)
genMaybe ga = QC.chooseInteger (0, 10) >>= \p -> if p < 5 then ga >>= \x -> return (Just x) else return Nothing
genStack :: Int -> Gen Stack
genStack 0 = Stk <$> arbitrary <*> pure Nothing
genStack n = Stk <$> arbitrary <*> genMaybe stk
where
stk = genStack (n - 1)
genStore :: Int -> Gen Store
genStore n = St <$> arbitrary <*> genEnvironments <*> genStack n
instance Arbitrary Store where
arbitrary = QC.sized genStore
shrink _ = []
-- Syntax generators --
-- Generate a small set of names for generated tests. These names are guaranteed to not include reserved words
genName :: Gen Name
genName = QC.elements ["_", "_G", "x", "X", "y", "x0", "X0", "xy", "XY", "_x"]
-- Generate a string literal, being careful about the characters that it may contain
genStringLit :: Gen String
genStringLit = escape <$> QC.listOf (QC.elements stringLitChars)
where
-- escape special characters appearing in the string,
escape :: String -> String
escape = foldr Char.showLitChar ""
-- generate strings containing printable characters or spaces, but not including '\"'
stringLitChars :: [Char]
stringLitChars = filter (\c -> c /= '\"' && (Char.isSpace c || Char.isPrint c)) ['\NUL' .. '~']
genString :: Gen String
genString = QC.vectorOf 5 $ QC.elements "abcdefg"
-- | access the first statement in a block, if one exists
first :: Block -> [Statement]
first (Block []) = []
first (Block (x : _)) = [x]
genLValue :: Int -> Gen LValue
genLValue 0 = LName <$> genString
genLValue n =
QC.frequency
[ (1, LName <$> genString),
(1, LArrayIndex <$> genLValue n' <*> genExp n')
]
where
n' = n `div` 2
genExp :: Int -> Gen Expression
genExp 0 = QC.oneof [Var <$> genString, Val <$> arbitrary]
genExp n =
QC.frequency
[ (1, Var <$> genString),
(1, Val <$> arbitrary),
(1, ArrayIndex <$> genExp n' <*> genExp n'),
(1, ArrayCons <$> QC.vectorOf 3 (genExp n')),
(n, Op1 <$> arbitrary <*> genExp n'),
(n, Op2 <$> genExp n' <*> arbitrary <*> genExp n'),
(0, FunctionCall <$> genExp n' <*> QC.vectorOf 3 (genExp n'))
]
where
n' = n `div` 2
genStatement :: Int -> Gen Statement
genStatement n | n <= 1 = QC.oneof [Assign <$> genLValue 0 <*> genExp 0, return Empty]
genStatement n =
let name = genName
in QC.frequency
[ (1, Assign <$> genLValue n' <*> genExp n'),
(1, VarDecl <$> genName <*> genExp n'),
(1, return Empty),
(n, If <$> genExp n' <*> genBlock n' <*> genBlock n'),
-- generate loops half as frequently as if statements
(n', For <$> (VarDecl <$> name <*> genExp n') <*> genExp n' <*> (Assign <$> (LName <$> name) <*> genExp n') <*> genBlock n'),
(n', While <$> genExp n' <*> genBlock n'),
(n', FunctionCallStatement <$> (Var <$> genName) <*> QC.vectorOf 3 (genExp n')),
(n', FunctionDef <$> genName <*> QC.vectorOf 3 genName <*> genBlock n'), -- TODO: make sure it always have return at the end
(n', Return <$> genExp n')
]
where
n' = n `div` 2
genBlock :: Int -> Gen Block
genBlock n = Block <$> genStmts n
where
genStmts 0 = pure []
genStmts n =
QC.frequency
[ (1, return []),
(n, (:) <$> genStatement n' <*> genStmts n')
]
where
n' = n `div` 2
instance Arbitrary LValue where
arbitrary = QC.sized genLValue
shrink (LName n) = []
shrink (LArrayIndex e n) = [LArrayIndex e n' | n' <- shrink n]
instance Arbitrary Uop where
arbitrary = QC.arbitraryBoundedEnum
instance Arbitrary Bop where
arbitrary = QC.arbitraryBoundedEnum
shrinkStringLit :: String -> [String]
shrinkStringLit s = filter (/= '\"') <$> shrink s
instance Arbitrary Value where
arbitrary =
QC.oneof
[ IntVal <$> arbitrary,
BoolVal <$> arbitrary,
pure NilVal,
StringVal <$> genStringLit,
FunctionValIncomplete <$> QC.vectorOf 3 genName <*> genBlock 3
-- Don't generate ArrayVal since it isnt used in the interpreter
]
shrink (IntVal n) = IntVal <$> shrink n
shrink (BoolVal b) = BoolVal <$> shrink b
shrink NilVal = []
shrink (StringVal s) = StringVal <$> shrinkStringLit s
shrink (FunctionValIncomplete ns b) = [FunctionValIncomplete ns b' | b' <- shrink b]
shrink _ = undefined
instance Arbitrary Expression where
arbitrary = QC.sized genExp
shrink (Op1 o e) = e : [Op1 o e' | e' <- shrink e]
shrink (Op2 e1 o e2) =
[Op2 e1' o e2 | e1' <- shrink e1]
++ [Op2 e1 o e2' | e2' <- shrink e2]
++ [e1, e2]
shrink (ArrayIndex e1 e2) = [ArrayIndex e1' e2 | e1' <- shrink e1] ++ [ArrayIndex e1 e2' | e2' <- shrink e2]
shrink (ArrayCons es) = [ArrayCons es' | es' <- shrink es]
shrink (FunctionCall e es) = [FunctionCall e' es | e' <- shrink e] ++ [FunctionCall e es' | es' <- shrink es]
shrink _ = []
instance Arbitrary Statement where
arbitrary = QC.sized genStatement
shrink (Assign v e) =
[Assign v' e | v' <- shrink v]
++ [Assign v e' | e' <- shrink e]
shrink (If e b1 b2) =
first b1
++ first b2
++ [If e' b1 b2 | e' <- shrink e]
++ [If e b1' b2 | b1' <- shrink b1]
++ [If e b1 b2' | b2' <- shrink b2]
shrink (While e b) =
first b
++ [While e' b | e' <- shrink e]
++ [While e b' | b' <- shrink b]
shrink Empty = []
shrink (VarDecl n e) = [VarDecl n e' | e' <- shrink e]
shrink (For s1 e s2 b) =
first b
++ [For s1' e s2 b | s1' <- shrink s1]
++ [For s1 e' s2 b | e' <- shrink e]
++ [For s1 e s2' b | s2' <- shrink s2]
++ [For s1 e s2 b' | b' <- shrink b]
shrink (FunctionCallStatement e es) = [FunctionCallStatement e' es | e' <- shrink e] ++ [FunctionCallStatement e es' | es' <- shrink es]
shrink (FunctionDef n ns b) = first b ++ [FunctionDef n ns b' | b' <- shrink b]
shrink (Return e) = [Return e' | e' <- shrink e]
shrink _ = []
instance Arbitrary Block where
arbitrary = QC.sized genBlock
shrink (Block ss) = [Block ss' | ss' <- shrink ss]