gruesome 0.0.1

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.
@@ -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