gruesome 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,399 @@
1
+ # The Z-Machine processor unit will execute a stream of Instructions
2
+
3
+ require_relative 'instruction'
4
+ require_relative 'opcode'
5
+ require_relative 'header'
6
+ require_relative 'zscii'
7
+ require_relative 'abbreviation_table'
8
+ require_relative 'dictionary'
9
+ require_relative 'object_table'
10
+
11
+ module Gruesome
12
+ module Z
13
+ class Processor
14
+ def initialize(memory)
15
+ @memory = memory
16
+ @header = Header.new(@memory.contents)
17
+ @abbreviation_table = AbbreviationTable.new(@memory)
18
+ @object_table = ObjectTable.new(@memory)
19
+ @dictionary = Dictionary.new(@memory)
20
+ end
21
+
22
+ def routine_call(address, arguments, result_variable = nil)
23
+ if address == 0
24
+ # special case, do not call, simply return false
25
+ if result_variable != nil
26
+ @memory.writev(result_variable, 0)
27
+ end
28
+ else
29
+ return_addr = @memory.program_counter
30
+ @memory.program_counter = address
31
+
32
+ # read routine
33
+ num_locals = @memory.force_readb(@memory.program_counter)
34
+ @memory.program_counter += 1
35
+
36
+ # create environment
37
+ @memory.push_routine(return_addr, num_locals, result_variable)
38
+
39
+ if @header.version <= 4
40
+ # read initial values when version 1-4
41
+ (1..num_locals).each do |i|
42
+ @memory.writev(i, @memory.force_readw(@memory.program_counter))
43
+ @memory.program_counter += 2
44
+ end
45
+ else
46
+ # reset local vars to 0
47
+ (1..num_locals).each do |i|
48
+ @memory.writev(i, 0)
49
+ end
50
+ end
51
+
52
+ # copy arguments over into locals
53
+ idx = 1
54
+ arguments.each do |i|
55
+ @memory.writev(idx, i)
56
+ idx += 1
57
+ end
58
+ end
59
+ end
60
+
61
+ def routine_return(result)
62
+ frame = @memory.pop_routine
63
+
64
+ if frame[:destination] != nil
65
+ @memory.writev(frame[:destination], result)
66
+ end
67
+ @memory.program_counter = frame[:return_address]
68
+ end
69
+
70
+ def branch(branch_to, branch_on, result)
71
+ if (result == branch_on)
72
+ if branch_to == 0
73
+ routine_return(0)
74
+ elsif branch_to == 1
75
+ routine_return(1)
76
+ else
77
+ @memory.program_counter = branch_to
78
+ # @memory.program_counter -= 2
79
+ end
80
+ end
81
+ end
82
+
83
+ def execute(instruction)
84
+ # Pull values from the variables if needed by the instruction
85
+ # there are some exceptions for variable-by-reference instructions
86
+ operands = instruction.operands.each_with_index.map do |operand, idx|
87
+ if idx == 0 and Opcode.is_variable_by_reference?(instruction.opcode, @header.version)
88
+ operand
89
+ elsif instruction.types[idx] == OperandType::VARIABLE
90
+ @memory.readv(operand)
91
+ else
92
+ operand
93
+ end
94
+ end
95
+
96
+ case instruction.opcode
97
+ when Opcode::ART_SHIFT
98
+ places = unsigned_to_signed(operands[1])
99
+ if places < 0
100
+ @memory.writev(instruction.destination, unsigned_to_signed(operands[0]) >> places.abs)
101
+ else
102
+ @memory.writev(instruction.destination, operands[0] << places)
103
+ end
104
+ when Opcode::CALL, Opcode::CALL_1N
105
+ routine_call(@memory.packed_address_to_byte_address(operands[0]), operands[1..-1], instruction.destination)
106
+ when Opcode::CLEAR_ATTR
107
+ @object_table.object_clear_attribute(operands[0], operands[1])
108
+ when Opcode::DEC
109
+ @memory.writev(operands[0], unsigned_to_signed(@memory.readv(operands[0])) - 1)
110
+ when Opcode::INC
111
+ @memory.writev(operands[0], unsigned_to_signed(@memory.readv(operands[0])) + 1)
112
+ when Opcode::INC_CHK
113
+ new_value = unsigned_to_signed(@memory.readv(operands[0])) + 1
114
+ @memory.writev(operands[0], new_value)
115
+ result = new_value > unsigned_to_signed(operands[1])
116
+ branch(instruction.branch_to, instruction.branch_on, result)
117
+ when Opcode::DEC_CHK
118
+ new_value = unsigned_to_signed(@memory.readv(operands[0])) - 1
119
+ @memory.writev(operands[0], new_value)
120
+ result = new_value < unsigned_to_signed(operands[1])
121
+ branch(instruction.branch_to, instruction.branch_on, result)
122
+ when Opcode::INSERT_OBJ
123
+ @object_table.object_insert_object(operands[1], operands[0])
124
+ when Opcode::GET_CHILD
125
+ child = @object_table.object_get_child(operands[0])
126
+ @memory.writev(instruction.destination, child)
127
+ result = child != 0
128
+ branch(instruction.branch_to, instruction.branch_on, result)
129
+ when Opcode::GET_PARENT
130
+ parent = @object_table.object_get_parent(operands[0])
131
+ @memory.writev(instruction.destination, parent)
132
+ when Opcode::GET_PROP
133
+ prop = @object_table.object_get_property_word(operands[0], operands[1])
134
+ @memory.writev(instruction.destination, prop)
135
+ when Opcode::GET_PROP_ADDR
136
+ prop = @object_table.object_get_property_addr(operands[0], operands[1])
137
+ @memory.writev(instruction.destination, prop)
138
+ when Opcode::GET_PROP_LEN
139
+ if operands[0] == 0
140
+ result = 0
141
+ else
142
+ result = @object_table.object_get_property_data_size_from_address(operands[0])
143
+ end
144
+ @memory.writev(instruction.destination, result)
145
+ when Opcode::GET_SIBLING
146
+ sibling = @object_table.object_get_sibling(operands[0])
147
+ @memory.writev(instruction.destination, sibling)
148
+ result = sibling != 0
149
+ branch(instruction.branch_to, instruction.branch_on, result)
150
+ when Opcode::JUMP, Opcode::PIRACY
151
+ @memory.program_counter += unsigned_to_signed(operands[0])
152
+ @memory.program_counter -= 2
153
+ when Opcode::JE
154
+ result = operands[1..-1].inject(false) { |result, element|
155
+ result | (operands[0] == element)
156
+ }
157
+ branch(instruction.branch_to, instruction.branch_on, result)
158
+ when Opcode::JG
159
+ result = operands[1..-1].inject(false) { |result, element|
160
+ result | (unsigned_to_signed(operands[0]) > unsigned_to_signed(element))
161
+ }
162
+ branch(instruction.branch_to, instruction.branch_on, result)
163
+ when Opcode::JIN
164
+ result = @object_table.object_get_parent(operands[0]) == operands[1]
165
+ branch(instruction.branch_to, instruction.branch_on, result)
166
+ when Opcode::JL
167
+ result = operands[1..-1].inject(false) { |result, element|
168
+ result | (unsigned_to_signed(operands[0]) < unsigned_to_signed(element))
169
+ }
170
+ branch(instruction.branch_to, instruction.branch_on, result)
171
+ when Opcode::JZ
172
+ result = operands[0] == 0
173
+ branch(instruction.branch_to, instruction.branch_on, result)
174
+ when Opcode::LOAD
175
+ if operands[0] != instruction.destination
176
+ @memory.writev(instruction.destination, @memory.readv(operands[0]))
177
+
178
+ # make sure to re-push the value since LOAD does not
179
+ # change the stack but uses the value directly
180
+ if operands[0] == 0
181
+ @memory.writev(0, @memory.readv(instruction.destination))
182
+ end
183
+ end
184
+ when Opcode::LOADB
185
+ @memory.writev(instruction.destination, @memory.readb(operands[0] + unsigned_to_signed(operands[1])))
186
+ when Opcode::LOADW
187
+ @memory.writev(instruction.destination, @memory.readw(operands[0] + unsigned_to_signed(operands[1])*2))
188
+ when Opcode::LOG_SHIFT
189
+ places = unsigned_to_signed(operands[1])
190
+ if places < 0
191
+ @memory.writev(instruction.destination, operands[0] >> places.abs)
192
+ else
193
+ @memory.writev(instruction.destination, operands[0] << places)
194
+ end
195
+ when Opcode::NOP
196
+ when Opcode::NEW_LINE
197
+ puts
198
+ when Opcode::POP
199
+ # get rid of the first item on stack
200
+ @memory.readv(0)
201
+ when Opcode::PRINT
202
+ print operands[0]
203
+ when Opcode::PRINT_ADDR
204
+ print ZSCII.translate(0, @header.version, @memory.force_readzstr(operands[0])[1], @abbreviation_table)
205
+ when Opcode::PRINT_CHAR
206
+ print ZSCII.translate_Zchar(operands[0])
207
+ when Opcode::PRINT_NUM
208
+ print unsigned_to_signed(operands[0]).to_s
209
+ when Opcode::PRINT_OBJ
210
+ print @object_table.object_short_text(operands[0])
211
+ when Opcode::PRINT_PADDR
212
+ str_addr = @memory.packed_address_to_byte_address(operands[0])
213
+ print ZSCII.translate(0, @header.version, @memory.force_readzstr(str_addr)[1], @abbreviation_table)
214
+ when Opcode::PRINT_RET
215
+ puts operands[0]
216
+ routine_return(1)
217
+ when Opcode::PULL
218
+ if @header.version == 6
219
+ # TODO: Version 6 PULL instruction
220
+ #
221
+ # stack to pull from is given as operand[0]
222
+ # instruction.destination is the destination resister
223
+ else
224
+ # pop value from stack
225
+ @memory.writev(operands[0], @memory.readv(0))
226
+ end
227
+ when Opcode::PUSH
228
+ # add value to stack
229
+ @memory.writev(0, operands[0])
230
+ when Opcode::PUT_PROP
231
+ object_id = operands[0]
232
+ prop_id = operands[1]
233
+ prop_value = operands[2]
234
+
235
+ @object_table.object_set_property_word(object_id, prop_id, prop_value)
236
+ when Opcode::RANDOM
237
+ value = unsigned_to_signed(operands[0])
238
+ if value < 0
239
+ # seed with value
240
+ srand(value)
241
+ result = 0
242
+ elsif value == 0
243
+ # seed with timestamp
244
+ srand()
245
+ result = 0
246
+ else
247
+ # pull random number from between 1 and value
248
+ result = rand(value-1) + 1
249
+ end
250
+ @memory.writev(instruction.destination, result)
251
+ when Opcode::REMOVE_OBJ
252
+ puts "remove_obj"
253
+ @object_table.object_remove_object(operands[0])
254
+ when Opcode::RET
255
+ routine_return(operands[0])
256
+ when Opcode::RET_POPPED
257
+ routine_return(@memory.readv(0))
258
+ when Opcode::RTRUE
259
+ routine_return(1)
260
+ when Opcode::RFALSE
261
+ routine_return(0)
262
+ when Opcode::SET_ATTR
263
+ @object_table.object_set_attribute(operands[0], operands[1])
264
+ when Opcode::SREAD
265
+ # read the maximum number of bytes in the text-buffer
266
+ max_bytes = @memory.force_readb(operands[0])
267
+
268
+ # address of the next byte in the text-buffer
269
+ addr = operands[0] + 1
270
+
271
+ # read in a line of input from stdin
272
+ line = $stdin.readline[0..-2]
273
+
274
+ # truncate line to fit the max characters given by text-buffer
275
+ if line.length > max_bytes
276
+ line = line[0..max_bytes]
277
+ end
278
+
279
+ # tokenize
280
+ lexed = @dictionary.tokenize(line)
281
+
282
+ # parse
283
+ parsed = @dictionary.parse(lexed)
284
+
285
+ # encode the line into a ZChar stream
286
+ str = ZSCII.encode_to_zchars(line, @header.version)
287
+
288
+ # write the text to the text-buffer
289
+ num_bytes = 1
290
+ first_position = 1
291
+ if @header.version >= 5
292
+ num_bytes += 1
293
+ @memory.force_writeb(addr, line.length)
294
+ first_position = 2
295
+ addr += 1
296
+ end
297
+
298
+ str.each do |zchr|
299
+ num_bytes += 1
300
+ if num_bytes > max_bytes
301
+ break
302
+ end
303
+ @memory.force_writeb(addr, zchr)
304
+ addr += 1
305
+ end
306
+
307
+ if @header.version < 5
308
+ # terminator
309
+ if num_bytes <= max_bytes
310
+ @memory.force_writeb(addr, 0)
311
+ end
312
+ end
313
+
314
+ # write the parse-buffer
315
+ max_bytes = @memory.force_readb(operands[1])
316
+ max_bytes = 2 + max_bytes * 4
317
+ addr = operands[1] + 1
318
+ num_bytes = 1
319
+
320
+ if num_bytes <= max_bytes
321
+ @memory.force_writeb(addr, lexed.length)
322
+ addr += 1
323
+ end
324
+
325
+ parsed.each do |token, index|
326
+ num_bytes += 4
327
+ if num_bytes > max_bytes
328
+ break
329
+ end
330
+ @memory.force_writew(addr, token[:address])
331
+ addr += 2
332
+ @memory.force_writeb(addr, token[:size])
333
+ addr += 1
334
+ @memory.force_writeb(addr, token[:position]+first_position)
335
+ addr += 1
336
+ end
337
+ when Opcode::TEST
338
+ result = (operands[0] & operands[1]) == operands[1]
339
+ branch(instruction.branch_to, instruction.branch_on, result)
340
+ when Opcode::TEST_ATTR
341
+ result = @object_table.object_has_attribute?(operands[0], operands[1])
342
+ branch(instruction.branch_to, instruction.branch_on, result)
343
+ when Opcode::ADD
344
+ @memory.writev(instruction.destination,
345
+ unsigned_to_signed(operands[0]) + unsigned_to_signed(operands[1]))
346
+ when Opcode::SUB
347
+ @memory.writev(instruction.destination,
348
+ unsigned_to_signed(operands[0]) - unsigned_to_signed(operands[1]))
349
+ when Opcode::MUL
350
+ @memory.writev(instruction.destination,
351
+ unsigned_to_signed(operands[0]) * unsigned_to_signed(operands[1]))
352
+ when Opcode::DIV
353
+ result = unsigned_to_signed(operands[0]).to_f / unsigned_to_signed(operands[1]).to_f
354
+ if result < 0
355
+ result = -(result.abs.floor)
356
+ else
357
+ result = result.floor
358
+ end
359
+ @memory.writev(instruction.destination, result.to_i)
360
+ when Opcode::MOD
361
+ a = unsigned_to_signed(operands[0])
362
+ b = unsigned_to_signed(operands[1])
363
+ result = a.abs % b.abs
364
+ if a < 0
365
+ result = -result
366
+ end
367
+ @memory.writev(instruction.destination, result.to_i)
368
+ when Opcode::NOT
369
+ @memory.writev(instruction.destination, ~(operands[0]))
370
+ when Opcode::OR
371
+ @memory.writev(instruction.destination, operands[0] | operands[1])
372
+ when Opcode::AND
373
+ @memory.writev(instruction.destination, operands[0] & operands[1])
374
+ when Opcode::STORE
375
+ if operands[0] == 0
376
+ @memory.readv(operands[0])
377
+ end
378
+ @memory.writev(operands[0], operands[1])
379
+ when Opcode::STOREB
380
+ @memory.writeb(operands[0] + unsigned_to_signed(operands[1]), operands[2])
381
+ when Opcode::STOREW
382
+ @memory.writew(operands[0] + unsigned_to_signed(operands[1])*2, operands[2])
383
+ else
384
+ raise "opcode " + Opcode.name(instruction.opcode, @header.version) + " not implemented"
385
+ end
386
+ end
387
+
388
+ def unsigned_to_signed(op)
389
+ sign = op & 0x8000
390
+ if sign != 0
391
+ -(65536 - op)
392
+ else
393
+ op
394
+ end
395
+ end
396
+ private :unsigned_to_signed
397
+ end
398
+ end
399
+ end