rubylabs 0.5.5 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/marslab.rb +1147 -0
- data/lib/randomlab.rb +436 -201
- data/lib/rubylabs.rb +15 -14
- data/test/mars_test.rb +519 -0
- data/test/random_test.rb +75 -19
- metadata +4 -4
- data/lib/temps.rb +0 -41
- data/test/temps_test.rb +0 -24
data/lib/rubylabs.rb
CHANGED
@@ -18,6 +18,7 @@ autoload :IterationLab, "iterationlab.rb"
|
|
18
18
|
autoload :RecursionLab, "recursionlab.rb"
|
19
19
|
autoload :HashLab, "hashlab.rb"
|
20
20
|
autoload :BitLab, "bitlab.rb"
|
21
|
+
autoload :MARSLab, "marslab.rb"
|
21
22
|
autoload :RandomLab, "randomlab.rb"
|
22
23
|
autoload :EncryptionLab, "encryptionlab.rb"
|
23
24
|
autoload :ELIZALab, "elizalab.rb"
|
@@ -64,20 +65,20 @@ Log base 2.
|
|
64
65
|
a < b ? a : b
|
65
66
|
end
|
66
67
|
|
67
|
-
=begin rdoc
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
=end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
68
|
+
# =begin rdoc
|
69
|
+
# Return a copy of object x with the elements in a new, scrambled order. The
|
70
|
+
# parameter x can be any object that has an index operator (e.g. strings or
|
71
|
+
# arrays).
|
72
|
+
# =end
|
73
|
+
#
|
74
|
+
# def permutation(x)
|
75
|
+
# res = x.clone
|
76
|
+
# for i in 0..res.length-2
|
77
|
+
# r = rand(res.length-i) + i
|
78
|
+
# res[i], res[r] = res[r], res[i]
|
79
|
+
# end
|
80
|
+
# return res
|
81
|
+
# end
|
81
82
|
|
82
83
|
=begin rdoc
|
83
84
|
|
data/test/mars_test.rb
ADDED
@@ -0,0 +1,519 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
This file has tests of the code written for the CoreWar project: an
|
6
|
+
assembler for "Redcode" programs, and a "MARS" virtual machine emulator.
|
7
|
+
|
8
|
+
The should probably be a few more tests of individual components, but the
|
9
|
+
tests here just make sure the whole system operates as expected. For example,
|
10
|
+
the instruction tests make arrays of strings containing source instructions,
|
11
|
+
pass them to the assembler, and execute the resulting instructions. The
|
12
|
+
tests don't look too closely at the result, e.g. to make sure all the symbols
|
13
|
+
are in the symbol table, or each instruction has the right components -- the
|
14
|
+
test just makes sure the instructions do what they are expected to do.
|
15
|
+
|
16
|
+
Tests in this file:
|
17
|
+
* an assembler test looks for syntax errors on ill-formed statements
|
18
|
+
* another assembler test looks at the code for a small, valid program
|
19
|
+
* an address mode test makes sure MOV instructions copy a piece of data
|
20
|
+
to the expected location using each of the four addressing modes
|
21
|
+
* individual instruction tests for each machine instruction
|
22
|
+
|
23
|
+
NOTE: to test that instructions fetched from memory have the expected value
|
24
|
+
the assertions compare the result of instr.to_s to a string. Things will
|
25
|
+
break horribly if the to_s method changes the format....
|
26
|
+
|
27
|
+
NOTE: Be careful when making test programs -- there needs to be a label or
|
28
|
+
a space before each opcode, e.g. [" DAT #0, <1", " DAT #0, #1"]
|
29
|
+
|
30
|
+
=end
|
31
|
+
|
32
|
+
include MARSLab
|
33
|
+
|
34
|
+
class TestMARS < Test::Unit::TestCase
|
35
|
+
|
36
|
+
# Assembling these instructions should lead to one error per line, and
|
37
|
+
# no code generated.
|
38
|
+
|
39
|
+
def test_01_syntax_errors
|
40
|
+
source = [
|
41
|
+
"@label mov #0, #1 ; label needs to be alphanumeric",
|
42
|
+
"l@bel mov #0, #1 ; label needs to be alphanumeric",
|
43
|
+
"mov #0, #1 ; unlabeled line must start with space",
|
44
|
+
"label @bogus #0, #1 ; opcode with non-alphanumeric",
|
45
|
+
"label b@gus #0, #1 ; opcode with non-alphanumeric",
|
46
|
+
"label muv #0, #1 ; unknown opcode",
|
47
|
+
" mov #b@gus, #1 ; messed up operand",
|
48
|
+
" mov #0 #1 ; missing comma",
|
49
|
+
" EQU 3 ; missing label on pseudo-op",
|
50
|
+
"x EQU 3x ; arg not an integer",
|
51
|
+
" END foo ; undefined label",
|
52
|
+
]
|
53
|
+
|
54
|
+
name, code, symbols, errors = MARS.assemble(source)
|
55
|
+
|
56
|
+
assert_equal source.length, errors.length # every line generates an error
|
57
|
+
assert_equal 0, code.length # should be no instructions generated
|
58
|
+
end
|
59
|
+
|
60
|
+
# A program from the ICWS archives -- has a wide variety of instructions, each
|
61
|
+
# addressing mode, and a pseudo-op. Should produce no errors, and one machine
|
62
|
+
# instruction for each operation (inclduing DAT).
|
63
|
+
|
64
|
+
def test_02_assembly
|
65
|
+
source = [
|
66
|
+
";redcode",
|
67
|
+
";name Mice",
|
68
|
+
";author Chip Wendell",
|
69
|
+
";strategy 1st place in ICWST'86",
|
70
|
+
";strategy replicator",
|
71
|
+
";assert 1",
|
72
|
+
"PTR DAT #0",
|
73
|
+
"START MOV #12 ,PTR",
|
74
|
+
"LOOP MOV @PTR ,<COPY",
|
75
|
+
" DJN LOOP ,PTR",
|
76
|
+
" SPL @COPY ,0",
|
77
|
+
" ADD #653 ,COPY",
|
78
|
+
" JMZ START ,PTR",
|
79
|
+
"COPY DAT #833",
|
80
|
+
" END START",
|
81
|
+
]
|
82
|
+
|
83
|
+
name, code, symbols, errors = MARS.assemble(source)
|
84
|
+
|
85
|
+
assert_equal "Mice", name
|
86
|
+
assert_equal 0, errors.length
|
87
|
+
assert_equal 8, code.length
|
88
|
+
assert_equal 0, symbols["PTR"]
|
89
|
+
assert_equal 1, symbols["START"]
|
90
|
+
assert_equal 2, symbols["LOOP"]
|
91
|
+
assert_equal 7, symbols["COPY"]
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# Assemble and run a synthetic program that has 4 MOV instructions. Each
|
96
|
+
# instruction uses a different addressing mode to find the data source, and
|
97
|
+
# each copies its data to the same location.
|
98
|
+
|
99
|
+
def test_03_modes
|
100
|
+
source = [
|
101
|
+
"immed mov #1, target ; moves #1",
|
102
|
+
"direct mov dsrc, target ; moves #2",
|
103
|
+
"indir mov @ptr, target ; moves #3",
|
104
|
+
"auto mov <ptr2, target ; moves #4, decrements ptr2",
|
105
|
+
"target dat #0, #0 ; place to store data",
|
106
|
+
"dsrc dat #2, #2 ; source for direct mode",
|
107
|
+
"ptr dat #0, isrc ; a pointer to the indirect source",
|
108
|
+
"isrc dat #3, #3 ; source for indirect mode",
|
109
|
+
"ptr2 dat #0, dummy ; this pointer should be decremented",
|
110
|
+
" dat #4, #4 ; source for auto-decrement",
|
111
|
+
"dummy dat #0, #0 ; not used",
|
112
|
+
]
|
113
|
+
|
114
|
+
s = source.find { |line| line =~ /^target/ }
|
115
|
+
target = source.index(s)
|
116
|
+
|
117
|
+
s = source.find { |line| line =~ /^ptr2/ }
|
118
|
+
ptr2 = source.index(s)
|
119
|
+
|
120
|
+
mem, pc = make_machine(source)
|
121
|
+
|
122
|
+
# value before first MOV:
|
123
|
+
assert_equal "DAT #0 #0", mem.fetch(target).to_s
|
124
|
+
|
125
|
+
# value after MOV with immediate mode -- note only B field is copied
|
126
|
+
instr = mem.fetch(pc.next)
|
127
|
+
instr.execute(pc, mem)
|
128
|
+
assert_equal "DAT #0 #1", mem.fetch(target).to_s
|
129
|
+
|
130
|
+
# value after MOV with direct mode -- note both A and B are moved
|
131
|
+
instr = mem.fetch(pc.next)
|
132
|
+
instr.execute(pc, mem)
|
133
|
+
assert_equal "DAT #2 #2", mem.fetch(target).to_s
|
134
|
+
|
135
|
+
# value after MOV with indirect mode -- both A and B moved
|
136
|
+
instr = mem.fetch(pc.next)
|
137
|
+
instr.execute(pc, mem)
|
138
|
+
assert_equal "DAT #3 #3", mem.fetch(target).to_s
|
139
|
+
|
140
|
+
# value of the pointer before the auto-decrement:
|
141
|
+
assert_equal "DAT #0 2", mem.fetch(ptr2).to_s
|
142
|
+
|
143
|
+
# value after MOV with auto-decrement mode -- both A and B moved
|
144
|
+
instr = mem.fetch(pc.next)
|
145
|
+
instr.execute(pc, mem)
|
146
|
+
assert_equal "DAT #4 #4", mem.fetch(target).to_s
|
147
|
+
|
148
|
+
# value of the pointer after the auto-decrement:
|
149
|
+
assert_equal "DAT #0 1", mem.fetch(ptr2).to_s
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
# A DAT instruction is effectively a halt instruction. Note that the
|
154
|
+
# addressing modes are dereferenced, so the auto-decrement mode should
|
155
|
+
# cause the pointer to decrement.
|
156
|
+
|
157
|
+
def test_04_DAT
|
158
|
+
mem, pc = make_machine([" DAT #0, <1", " DAT #0, #1"])
|
159
|
+
|
160
|
+
# pointer before executing the DAT:
|
161
|
+
assert_equal "DAT #0 #1", mem.fetch(1).to_s
|
162
|
+
|
163
|
+
instr = mem.fetch(pc.next)
|
164
|
+
assert_equal :halt, instr.execute(pc, mem)
|
165
|
+
|
166
|
+
# pointer after executing the DAT -- value changed, mode did not
|
167
|
+
assert_equal "DAT #0 #0", mem.fetch(1).to_s
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
# Simple MOV test just executes the instruction in the IMP program.
|
172
|
+
# A second test verifies a runtime error if the B field is immediate.
|
173
|
+
|
174
|
+
def test_05_MOV
|
175
|
+
mem, pc = make_machine([" MOV 0, 1"], 2)
|
176
|
+
instr = mem.fetch(pc.next)
|
177
|
+
assert_equal :continue, instr.execute(pc, mem)
|
178
|
+
assert_equal mem.fetch(0).to_s, mem.fetch(1).to_s
|
179
|
+
|
180
|
+
mem, pc = make_machine([" MOV 0, #1"])
|
181
|
+
instr = mem.fetch(pc.next)
|
182
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
183
|
+
end
|
184
|
+
|
185
|
+
# ADD tests
|
186
|
+
# "ADD #A, dest" adds the constant A to the B field of dest
|
187
|
+
# "ADD src, dest" adds the A and B fields to src to the A and B of dest
|
188
|
+
# "ADD src, #B" is a runtime error (B can't be immediate)
|
189
|
+
|
190
|
+
def test_06_ADD
|
191
|
+
source = [
|
192
|
+
" ADD #3, sum",
|
193
|
+
" ADD x, y",
|
194
|
+
" ADD x, #0",
|
195
|
+
"sum DAT #1, #2",
|
196
|
+
"x DAT #3, #4",
|
197
|
+
"y DAT #5, #6",
|
198
|
+
]
|
199
|
+
|
200
|
+
mem, pc = make_machine(source)
|
201
|
+
|
202
|
+
instr = mem.fetch(pc.next)
|
203
|
+
assert_equal :continue, instr.execute(pc, mem)
|
204
|
+
assert_equal "DAT #1 #5", mem.fetch(3).to_s
|
205
|
+
|
206
|
+
instr = mem.fetch(pc.next)
|
207
|
+
assert_equal :continue, instr.execute(pc, mem)
|
208
|
+
assert_equal "DAT #8 #10", mem.fetch(5).to_s
|
209
|
+
|
210
|
+
instr = mem.fetch(pc.next)
|
211
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
# SUB tests -- same as ADD, but subtracting A from B
|
216
|
+
|
217
|
+
def test_07_SUB
|
218
|
+
source = [
|
219
|
+
" SUB #3, sum",
|
220
|
+
" SUB x, y",
|
221
|
+
" SUB x, #0",
|
222
|
+
"sum DAT #1, #10",
|
223
|
+
"x DAT #3, #4",
|
224
|
+
"y DAT #5, #6",
|
225
|
+
]
|
226
|
+
|
227
|
+
mem, pc = make_machine(source)
|
228
|
+
|
229
|
+
instr = mem.fetch(pc.next)
|
230
|
+
assert_equal :continue, instr.execute(pc, mem)
|
231
|
+
assert_equal "DAT #1 #7", mem.fetch(3).to_s
|
232
|
+
|
233
|
+
instr = mem.fetch(pc.next)
|
234
|
+
assert_equal :continue, instr.execute(pc, mem)
|
235
|
+
assert_equal "DAT #2 #2", mem.fetch(5).to_s
|
236
|
+
|
237
|
+
instr = mem.fetch(pc.next)
|
238
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
# JMP tests
|
243
|
+
# JMP x should set the program counter to x
|
244
|
+
# JMP #x is an error -- the operand can't be immediate
|
245
|
+
|
246
|
+
def test_08_JMP
|
247
|
+
source = [
|
248
|
+
" JMP x",
|
249
|
+
" JMP #0",
|
250
|
+
"x DAT #0, #0",
|
251
|
+
]
|
252
|
+
|
253
|
+
mem, pc = make_machine(source)
|
254
|
+
|
255
|
+
instr = mem.fetch(pc.next)
|
256
|
+
assert_equal :continue, instr.execute(pc, mem)
|
257
|
+
assert_equal 2, pc.next
|
258
|
+
|
259
|
+
instr = mem.fetch(1)
|
260
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
# JMZ tests
|
265
|
+
# JMZ x y should set the program counter to x if y is 0
|
266
|
+
# JMP #x y is an error -- the operand can't be immediate
|
267
|
+
|
268
|
+
def test_09_JMZ
|
269
|
+
source = [
|
270
|
+
" JMZ x, #1",
|
271
|
+
" JMZ x, #0",
|
272
|
+
" JMZ #0, #0",
|
273
|
+
"x DAT #0, #0",
|
274
|
+
]
|
275
|
+
|
276
|
+
mem, pc = make_machine(source)
|
277
|
+
|
278
|
+
instr = mem.fetch(pc.next)
|
279
|
+
assert_equal :continue, instr.execute(pc, mem)
|
280
|
+
loc = pc.next
|
281
|
+
assert_equal 1, loc # branch not taken
|
282
|
+
|
283
|
+
instr = mem.fetch(loc)
|
284
|
+
assert_equal :continue, instr.execute(pc, mem)
|
285
|
+
loc = pc.next
|
286
|
+
assert_equal 3, loc # branch taken
|
287
|
+
|
288
|
+
instr = mem.fetch(2)
|
289
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
# JMN tests -- same as JMZ, but branch on non-zero
|
294
|
+
|
295
|
+
def test_10_JMN
|
296
|
+
source = [
|
297
|
+
" JMN x, #0",
|
298
|
+
" JMN x, #1",
|
299
|
+
" JMN #0, #0",
|
300
|
+
"x DAT #0, #0",
|
301
|
+
]
|
302
|
+
|
303
|
+
mem, pc = make_machine(source)
|
304
|
+
|
305
|
+
instr = mem.fetch(pc.next)
|
306
|
+
assert_equal :continue, instr.execute(pc, mem)
|
307
|
+
loc = pc.next
|
308
|
+
assert_equal 1, loc # branch not taken
|
309
|
+
|
310
|
+
instr = mem.fetch(loc)
|
311
|
+
assert_equal :continue, instr.execute(pc, mem)
|
312
|
+
loc = pc.next
|
313
|
+
assert_equal 3, loc # branch taken
|
314
|
+
|
315
|
+
instr = mem.fetch(2)
|
316
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
# DJN combines auto-decrement processing with a branch instruction. As with
|
321
|
+
# other branches, immediate mode is not allowed for the A operand.
|
322
|
+
|
323
|
+
def test_11_DJN
|
324
|
+
source = [
|
325
|
+
" DJN y, x", # first won't branch
|
326
|
+
" DJN y, x", # ...but second one will
|
327
|
+
" DJN #0, #0",
|
328
|
+
"x DAT #0, #1",
|
329
|
+
"y MOV 0, 1",
|
330
|
+
]
|
331
|
+
|
332
|
+
mem, pc = make_machine(source)
|
333
|
+
|
334
|
+
instr = mem.fetch(pc.next)
|
335
|
+
assert_equal :continue, instr.execute(pc, mem)
|
336
|
+
loc = pc.next
|
337
|
+
assert_equal 1, loc # branch not taken
|
338
|
+
|
339
|
+
instr = mem.fetch(loc)
|
340
|
+
assert_equal :continue, instr.execute(pc, mem)
|
341
|
+
loc = pc.next
|
342
|
+
assert_equal 4, loc # branch taken
|
343
|
+
|
344
|
+
instr = mem.fetch(2)
|
345
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
# CMP fetches the two operands and compares them; if they're the same
|
350
|
+
# the next instruction is skipped.
|
351
|
+
|
352
|
+
def test_12_CMP
|
353
|
+
source = [
|
354
|
+
" CMP #1, x", # don't skip
|
355
|
+
" CMP #0, x", # skip
|
356
|
+
" DAT #0, #0",
|
357
|
+
" CMP x, y", # don't skip
|
358
|
+
" CMP x, x", # skip
|
359
|
+
" DAT #0, #0",
|
360
|
+
" CMP x, #0", # illegal
|
361
|
+
"x DAT #0, #0",
|
362
|
+
"y DAT #0, #1",
|
363
|
+
]
|
364
|
+
|
365
|
+
mem, pc = make_machine(source)
|
366
|
+
|
367
|
+
instr = mem.fetch(pc.next)
|
368
|
+
assert_equal :continue, instr.execute(pc, mem)
|
369
|
+
loc = pc.next
|
370
|
+
assert_equal 1, loc # branch not taken
|
371
|
+
|
372
|
+
instr = mem.fetch(loc)
|
373
|
+
assert_equal :continue, instr.execute(pc, mem)
|
374
|
+
loc = pc.next
|
375
|
+
assert_equal 3, loc # branch taken
|
376
|
+
|
377
|
+
instr = mem.fetch(loc)
|
378
|
+
assert_equal :continue, instr.execute(pc, mem)
|
379
|
+
loc = pc.next
|
380
|
+
assert_equal 4, loc # branch not taken
|
381
|
+
|
382
|
+
instr = mem.fetch(loc)
|
383
|
+
assert_equal :continue, instr.execute(pc, mem)
|
384
|
+
loc = pc.next
|
385
|
+
assert_equal 6, loc # branch taken
|
386
|
+
|
387
|
+
instr = mem.fetch(loc)
|
388
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
# In our implementation SLT only works for immediate A operands. Skip the
|
393
|
+
# next instruction if A is less than the A-field of the dereferenced B operand.
|
394
|
+
|
395
|
+
def test_13_SLT
|
396
|
+
source = [
|
397
|
+
" SLT #3, x", # don't skip
|
398
|
+
" SLT #1, x", # skip
|
399
|
+
" SLT #0, #0", # illegal B operand
|
400
|
+
" SLT #0, z", # don't skip when operands equal
|
401
|
+
"x DAT #2", # expect to stop here
|
402
|
+
"z DAT #0",
|
403
|
+
]
|
404
|
+
|
405
|
+
mem, pc = make_machine(source)
|
406
|
+
|
407
|
+
instr = mem.fetch(pc.next)
|
408
|
+
assert_equal :continue, instr.execute(pc, mem)
|
409
|
+
loc = pc.next
|
410
|
+
assert_equal 1, loc # branch not taken
|
411
|
+
|
412
|
+
instr = mem.fetch(loc)
|
413
|
+
assert_equal :continue, instr.execute(pc, mem)
|
414
|
+
loc = pc.next
|
415
|
+
assert_equal 3, loc # branch taken
|
416
|
+
|
417
|
+
instr = mem.fetch(loc)
|
418
|
+
assert_equal :continue, instr.execute(pc, mem)
|
419
|
+
loc = pc.next
|
420
|
+
assert_equal 4, loc # branch not taken
|
421
|
+
|
422
|
+
instr = mem.fetch(2)
|
423
|
+
assert_raise(MARSRuntimeException) { instr.execute(pc, mem) }
|
424
|
+
|
425
|
+
end
|
426
|
+
|
427
|
+
# SPL forks a new thread that starts execution at the address specified by A.
|
428
|
+
# The pc object maintains a list of locations, with one location per thread.
|
429
|
+
# After the split in this test, the location list should have 2 items, one the
|
430
|
+
# continuation of the original thread and the other the first instruction in
|
431
|
+
# the new thread.
|
432
|
+
|
433
|
+
def test_14_SPL
|
434
|
+
source = [
|
435
|
+
" SPL x",
|
436
|
+
" DAT #0, #0", # space filler
|
437
|
+
" DAT #0, #0",
|
438
|
+
"x MOV 0, 1",
|
439
|
+
]
|
440
|
+
|
441
|
+
mem, pc = make_machine(source)
|
442
|
+
|
443
|
+
instr = mem.fetch(pc.next)
|
444
|
+
assert_equal :continue, instr.execute(pc, mem)
|
445
|
+
assert_equal [1,3], pc.addrs
|
446
|
+
|
447
|
+
end
|
448
|
+
|
449
|
+
# The program counter object should have a history of memory references for each
|
450
|
+
# thread. pc.history[i] is the history vector for thread i
|
451
|
+
|
452
|
+
def test_15_threads
|
453
|
+
source = [
|
454
|
+
" MOV x, y", # should log source and target references
|
455
|
+
" SPL z", # start new thread
|
456
|
+
" ADD x, y", # first thread logs two memory references
|
457
|
+
" DAT #0", # first thread halts
|
458
|
+
"z SUB x, y", # second thread logs two memory references
|
459
|
+
" DAT #0", # second thread halts
|
460
|
+
"x DAT #1",
|
461
|
+
"y DAT #0",
|
462
|
+
]
|
463
|
+
|
464
|
+
mem, pc = make_machine(source)
|
465
|
+
assert_equal 1, pc.history.length
|
466
|
+
assert_equal [], pc.history[0]
|
467
|
+
|
468
|
+
mem.fetch(pc.next).execute(pc, mem)
|
469
|
+
assert_equal [0,6,7], pc.history[0] # log has instr and both operands
|
470
|
+
|
471
|
+
mem.fetch(pc.next).execute(pc, mem)
|
472
|
+
assert_equal 2, pc.history.length # SPL instruction makes new thread and history vector
|
473
|
+
assert_equal [0,6,7,1], pc.history[0]
|
474
|
+
|
475
|
+
mem.fetch(pc.next).execute(pc, mem)
|
476
|
+
assert_equal [0,6,7,1,2,6,7], pc.history[0] # staying with thread 0, add also logs both operands
|
477
|
+
|
478
|
+
mem.fetch(pc.next).execute(pc, mem)
|
479
|
+
assert_equal [4,6,7], pc.history[1] # next instr comes from thread 1, sub logs both operands
|
480
|
+
|
481
|
+
assert_equal :halt, mem.fetch(pc.next).execute(pc, mem) # back to thread 0, DAT halts the thread
|
482
|
+
assert_equal [0,6,7,1,2,6,7,3], pc.history[0] # DAT is logged
|
483
|
+
assert_equal 1, pc.kill_thread # zap this thread, expect other one still there
|
484
|
+
assert_equal [4,6,7], pc.history[0] # other thread is now thread 0
|
485
|
+
|
486
|
+
assert_equal :halt, mem.fetch(pc.next).execute(pc, mem) # it also executes a DAT
|
487
|
+
assert_equal [4,6,7,5], pc.history[0]
|
488
|
+
assert_equal 0, pc.kill_thread
|
489
|
+
|
490
|
+
assert_equal "DAT #0 #1", mem.fetch(7).to_s # y set to 1, inc to 2 by thread 0, set back to 1 by thread 1
|
491
|
+
end
|
492
|
+
|
493
|
+
# The Warrior class constructor takes a file name or a symbol corresponding to one
|
494
|
+
# of the built-in test programs. The program is assembled, and all the relevant
|
495
|
+
# information saved with the object.
|
496
|
+
|
497
|
+
def test_16_warrior_class
|
498
|
+
w = Warrior.new(:imp)
|
499
|
+
assert_equal "Imp", w.name
|
500
|
+
assert_equal [ Word.new("MOV","0","1") ], w.code
|
501
|
+
assert_equal 0, w.symbols[:start]
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
|
506
|
+
# Set up and initialize a mini-machine that has the assembled instructions
|
507
|
+
# in array a loaded into memory, and a program counter set up to fetch
|
508
|
+
# the first instruction.
|
509
|
+
|
510
|
+
def make_machine(a, n = a.length)
|
511
|
+
mem = Memory.new(n)
|
512
|
+
name, code, symbols, errors = MARS.assemble(a)
|
513
|
+
code.each_with_index do |x, i|
|
514
|
+
mem.store(i, x)
|
515
|
+
end
|
516
|
+
pc = PC.new(name, 0)
|
517
|
+
return mem, pc
|
518
|
+
end
|
519
|
+
|
data/test/random_test.rb
CHANGED
@@ -4,37 +4,86 @@ require 'test_helper'
|
|
4
4
|
include RandomLab
|
5
5
|
|
6
6
|
class TestRandoms < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# The prng_sequence method makes a sequence of numbers using a linear congruential
|
9
|
+
# method with the specified a, c, and m values
|
10
|
+
|
11
|
+
def test_01_prng_sequence
|
12
|
+
sched8 = prng_sequence(1, 8, 12)
|
13
|
+
assert_equal 12, sched8.length
|
14
|
+
assert_equal 3, sched8.uniq.length
|
15
|
+
|
16
|
+
sched7 = prng_sequence(1, 7, 12)
|
17
|
+
assert_equal 12, sched7.length
|
18
|
+
assert_equal 12, sched7.uniq.length
|
19
|
+
|
20
|
+
seq1 = prng_sequence(3, 337, 1000)
|
21
|
+
assert_equal [0, 337, 348, 381], seq1[0..3]
|
22
|
+
assert_equal 100, seq1.uniq.length
|
23
|
+
|
24
|
+
seq2 = prng_sequence(81, 337, 1000)
|
25
|
+
assert_equal [0, 337, 634, 691], seq2[0..3]
|
26
|
+
assert_equal 1000, seq2.uniq.length
|
27
|
+
end
|
28
|
+
|
29
|
+
# A PRNG object generates the same sequence of values as the prng_sequence method,
|
30
|
+
# but makes them on demand. Test the sequence, test resetting the seed, test again.
|
31
|
+
# Also tests the random(n,m) method, which maps a sequence value to n..m
|
32
|
+
|
33
|
+
def test_02_PRNG
|
34
|
+
p = PRNG.new(81, 337, 1000)
|
35
|
+
assert_equal 0, p.state
|
36
|
+
assert_equal 337, p.advance
|
37
|
+
assert_equal 634, p.advance
|
38
|
+
|
39
|
+
p.seed(0)
|
40
|
+
assert_equal 0, p.state
|
41
|
+
assert_equal 337, p.advance
|
42
|
+
assert_equal 634, p.advance
|
43
|
+
|
44
|
+
p.seed(0)
|
45
|
+
assert_equal 2, p.random(1,6) # (337 % 6) + 1
|
46
|
+
assert_equal 5, p.random(1,6) # (634 % 6) + 1
|
47
|
+
end
|
7
48
|
|
8
49
|
# For the Card tests, a call to the Card constructor with an integer argument
|
9
50
|
# makes a specified card, starting with 0 = A ♠ and ending with 51 = 2 ♣.
|
51
|
+
|
52
|
+
def test_03_cards
|
53
|
+
c1 = Card.new(0)
|
54
|
+
assert_equal :ace, c1.rank
|
55
|
+
assert_equal :spades, c1.suit
|
56
|
+
|
57
|
+
c2 = Card.new(51)
|
58
|
+
assert_equal :two, c2.rank
|
59
|
+
assert_equal :clubs, c2.suit
|
60
|
+
end
|
10
61
|
|
11
62
|
# Make a full deck, make sure every card is present. Then shuffle the deck,
|
12
63
|
# check again. Sort the shuffled deck, make sure all cards are there and in
|
13
64
|
# order.
|
14
65
|
|
15
|
-
def
|
16
|
-
|
17
|
-
check_all_cards(
|
18
|
-
|
19
|
-
check_all_cards(
|
20
|
-
|
21
|
-
|
22
|
-
assert_equal deck1, deck3
|
66
|
+
def test_04_deck
|
67
|
+
d = new_deck
|
68
|
+
check_all_cards(d)
|
69
|
+
permute(d)
|
70
|
+
check_all_cards(d)
|
71
|
+
d.sort!
|
72
|
+
assert_equal new_deck, d
|
23
73
|
end
|
24
74
|
|
25
75
|
# Make one of each kind of poker hand.
|
26
76
|
|
27
|
-
def
|
28
|
-
|
29
|
-
assert_equal
|
30
|
-
assert_equal
|
31
|
-
assert_equal
|
32
|
-
assert_equal
|
33
|
-
assert_equal
|
34
|
-
assert_equal
|
35
|
-
assert_equal
|
36
|
-
assert_equal
|
37
|
-
assert_equal poker_hand(deal([0,14,29,44,45])), :nada
|
77
|
+
def test_05_poker_ranks
|
78
|
+
assert_equal poker_rank(deal([0,1,2,3,4])), :straight_flush
|
79
|
+
assert_equal poker_rank(deal([0,13,26,39,1])), :four_of_a_kind
|
80
|
+
assert_equal poker_rank(deal([0,13,26,1,14])), :full_house
|
81
|
+
assert_equal poker_rank(deal([0,2,4,6,8])), :flush
|
82
|
+
assert_equal poker_rank(deal([0,14,2,3,4])), :straight
|
83
|
+
assert_equal poker_rank(deal([0,13,26,1,15])), :three_of_a_kind
|
84
|
+
assert_equal poker_rank(deal([0,13,1,14,15])), :two_pair
|
85
|
+
assert_equal poker_rank(deal([0,13,1,2,3])), :pair
|
86
|
+
assert_equal poker_rank(deal([0,14,29,44,45])), :high_card
|
38
87
|
end
|
39
88
|
|
40
89
|
|
@@ -43,6 +92,9 @@ class TestRandoms < Test::Unit::TestCase
|
|
43
92
|
and initialized with a count of 0.
|
44
93
|
|
45
94
|
+check_all_cards+ makes sure there are 13 of each suit and 4 of each rank.
|
95
|
+
|
96
|
+
+deal(a)+ maps all ints in +a+ into the corresponding Card (used to create
|
97
|
+
a known hand for testing poker ranks)
|
46
98
|
=end
|
47
99
|
|
48
100
|
def bins(a)
|
@@ -60,5 +112,9 @@ class TestRandoms < Test::Unit::TestCase
|
|
60
112
|
rb.each { |x,n| assert_equal n, 4 }
|
61
113
|
sb.each { |x,n| assert_equal n, 13 }
|
62
114
|
end
|
115
|
+
|
116
|
+
def deal(a)
|
117
|
+
return a.map { |x| Card.new(x) }
|
118
|
+
end
|
63
119
|
|
64
120
|
end
|