gruesome 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in grue.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ <pre>
2
+ ▄▄▄▄▄ ▄▄▄▄▄
3
+ ▀████▄ ▄████▀
4
+ ▀████▄ ▄████▀
5
+ ▄▄▄▄▄▄ ██████▄ ▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄ ▄████▀ ▄▄▄ ▄▄▄ ▄▄▄▄▄▄
6
+ ▄████████▄ ██▓▀████▄ ██▓ ███ ▄███████ ▄███████▄ ▄████▀██▄ ████▄████ ▄███████
7
+ ██▓ ███ ██▓ ▀███ ██▓ ███ ██▓ ██▓ ███ ███▀ ███ █████████ ██▓
8
+ ██▓ ██▓ ███ ██▓ ███ ██▓ ██▓ ██▓ ███ ███▀█▀███ ██▓
9
+ ██▓ █████ ████████ ███ ███ ████████ ▀██████▄ ██▓ ███ ██▓ ███ ████████
10
+ ██▓ ▀▀███ ▀██▓▀▀▀█▄▄ ███ ███ ███▀▀▀▀▀ ▀▀▀▀███ ██▓ ███ ██▓ ███ ███▀▀▀▀▀
11
+ ██▓ ███ ██▓ ███ ██▓ ███ ██▓ ██▓ ███ ██▓ ███ ███ ███ ██▓
12
+ ▀████████▀ ██▓ ███ ▀███████▀ ▀███████ ▀███████▀ ▀███████▀ ██▓ ███ ▀███████
13
+ ▀▀▀▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀▀▀ ▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀▀▀▀
14
+ </pre>
15
+
16
+ # Gruesome
17
+
18
+ The Ruby Z-Code Emulator and IF Manager (Currently in progress). It current can
19
+ play version 3 games such as the ZORK trilogy.
20
+
21
+ ## Currently implemented
22
+
23
+ * Majority of version 3 Z-Machine (Enough to play most games)
24
+
25
+ ## Next
26
+
27
+ * Versions 4+ (Goal: Run Photopia)
28
+
29
+ ## Overall Goals
30
+
31
+ There are several goals:
32
+
33
+ * Emulate the Z-Machine accurately for all versions (text-only first)
34
+ * Provide detailed information to IF designers and programmers
35
+ * Make it easy to pull down and play new and old IF and text adventure games
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ # rake spec
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ spec.pattern = 'spec/*/*_spec.rb'
6
+ end
7
+
8
+ # rake doc
9
+ RSpec::Core::RakeTask.new(:doc) do |spec|
10
+ spec.pattern = 'spec/*/*_spec.rb'
11
+ spec.rspec_opts = ['--format documentation']
12
+ end
13
+
14
+ require 'bundler'
15
+ Bundler::GemHelper.install_tasks
data/bin/gruesome ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.dirname(__FILE__) + '/../lib'
4
+
5
+ require 'gruesome'
6
+ require 'gruesome/cli'
7
+
8
+ Gruesome::CLI.run
9
+
data/gruesome.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gruesome/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gruesome"
7
+ s.version = Grue::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dave Wilkinson"]
10
+ s.email = ["wilkie05@gmail.com"]
11
+ s.homepage = "http://github.com/wilkie/gruesome"
12
+ s.summary = %q{An Interactive Fiction client that can play/read interactive stories}
13
+ s.description = %q{Reads and executes various interactive fiction technologies and helps easily download new stories from other sources on the Internet.}
14
+
15
+ s.rubyforge_project = "gruesome"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "rspec"
23
+
24
+ s.add_dependency "bit-struct"
25
+ end
@@ -0,0 +1,70 @@
1
+ require 'optparse'
2
+
3
+ require_relative '../gruesome'
4
+ require_relative 'machine'
5
+ require_relative 'logo'
6
+
7
+ module Gruesome
8
+ class CLI
9
+ BANNER = <<-USAGE
10
+ Usage:
11
+ gruesome play STORY_FILE
12
+
13
+ Description:
14
+ The 'play' command will start a session of the story given as STORY_FILE
15
+
16
+ Example:
17
+ gruesome play zork1.z3
18
+
19
+ USAGE
20
+
21
+ class << self
22
+ def parse_options
23
+ @opts = OptionParser.new do |opts|
24
+ opts.banner = BANNER.gsub(/^\t{2}/, '')
25
+
26
+ opts.separator ''
27
+ opts.separator 'Options:'
28
+
29
+ opts.on('-h', '--help', 'Display this help') do
30
+ puts opts
31
+ exit
32
+ end
33
+ end
34
+
35
+ @opts.parse!
36
+ end
37
+
38
+ def CLI.run
39
+ begin
40
+ parse_options
41
+ rescue OptionParser::InvalidOption => e
42
+ warn e
43
+ exit -1
44
+ end
45
+
46
+ def fail
47
+ puts @opts
48
+ exit -1
49
+ end
50
+
51
+ if ARGV.empty?
52
+ fail
53
+ end
54
+
55
+ case ARGV.first
56
+ when 'play'
57
+ fail unless ARGV[1]
58
+
59
+ Gruesome::Logo.print
60
+
61
+ puts
62
+ puts "--------------------------------------------------------------------------------"
63
+ puts
64
+
65
+ Gruesome::Machine.new(ARGV[1]).execute
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ #
3
+ # DO NOT REMOVE ABOVE COMMENT! IT IS MAGIC!
4
+
5
+ module Gruesome
6
+ module Logo
7
+ # I'm just making sure this works regardless of the unicode format this
8
+ # source file is saved as...
9
+ LOGO = <<ENDLOGO
10
+ ▄▄▄▄▄ ▄▄▄▄▄
11
+ ▀████▄ ▄████▀
12
+ ▀████▄ ▄████▀
13
+ ▄▄▄▄▄▄ ██████▄ ▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄████▀ ▄▄▄ ▄▄▄ ▄▄▄▄▄▄
14
+ ▄████████▄ ██▓▀████▄ ██▓ ███ ▄███████ ▄████████▄ ▄████▀██▄ ████▄████ ▄███████
15
+ ██▓ ███ ██▓ ▀███ ██▓ ███ ██▓ ██▓ ███ ███▀ ███ █████████ ██▓
16
+ ██▓ ██▓ ███ ██▓ ███ ██▓ ██▓ ██▓ ███ ███▀█▀███ ██▓
17
+ ██▓ █████ ████████ ███ ███ ████████ ▀███████▄ ██▓ ███ ██▓ ███ ████████
18
+ ██▓ ▀▀███ ▀██▓▀▀▀█▄▄ ███ ███ ███▀▀▀▀▀ ▀▀▀▀▀███ ██▓ ███ ██▓ ███ ███▀▀▀▀▀
19
+ ██▓ ███ ██▓ ███ ██▓ ███ ██▓ ██▓ ███ ██▓ ███ ███ ███ ██▓
20
+ ▀████████▀ ██▓ ███ ▀███████▀ ▀███████ ▀████████▀ ▀███████▀ ██▓ ███ ▀███████
21
+ ▀▀▀▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀▀▀ ▀▀▀▀▀▀ ▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀▀▀▀
22
+ ENDLOGO
23
+
24
+ def Logo.print
25
+ puts LOGO
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'z/machine'
2
+
3
+ module Gruesome
4
+ class Machine
5
+ def initialize(story_file)
6
+ # Later, detect the type
7
+ #
8
+ # For now, assume Z-Machine
9
+
10
+ @machine = Z::Machine.new(story_file)
11
+ end
12
+
13
+ def execute
14
+ @machine.execute
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Grue
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ # The Z-Machine, in order to conserve precious space, often encoded in various
2
+ # strings a codeword that would inject the word given at a particular place in
3
+ # the abbreviation table.
4
+
5
+ require_relative 'memory'
6
+ require_relative 'header'
7
+ require_relative 'zscii'
8
+
9
+ module Gruesome
10
+ module Z
11
+ class AbbreviationTable
12
+ def initialize(memory)
13
+ @memory = memory
14
+ @header = Header.new(@memory.contents)
15
+ end
16
+
17
+ def lookup(alphabet, index, translation_alphabet)
18
+ abbrev_index = (32 * (alphabet)) + index
19
+ addr = @header.abbrev_tbl_addr + abbrev_index*2
20
+
21
+ # this will yield a word address, which we multiply by 2
22
+ # to get into a byte address
23
+ str_addr = @memory.force_readw(addr)
24
+ str_addr *= 2
25
+
26
+ ZSCII.translate(translation_alphabet, @header.version, @memory.force_readzstr(str_addr)[1], nil)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,292 @@
1
+ # The Z-Machine has a RISC-like instruction set with 1 byte opcodes with
2
+ # the exception of several extension opcodes of 2 bytes.
3
+ #
4
+ # The operands are variable length, however, mostly for size constraint reasons.
5
+ #
6
+ # The format:
7
+ #
8
+ # OPCODE :: 1 or 2 bytes
9
+ # OPCODE_TYPE (opt) :: 1 to 2 bytes, divided into 2-bit fields
10
+ # OPERANDS :: 0 to 8 given, each 1 or 2 bytes, or an unlimited length string
11
+
12
+ require_relative 'opcode'
13
+ require_relative 'operand_type'
14
+ require_relative 'opcode_class'
15
+ require_relative 'instruction'
16
+ require_relative 'zscii'
17
+
18
+ module Gruesome
19
+ module Z
20
+
21
+ # This is the instruction decoder
22
+ class Decoder
23
+ def initialize(memory)
24
+ @memory = memory
25
+ @instruction_cache = {}
26
+ @header = Header.new(@memory.contents)
27
+ @abbreviation_table = AbbreviationTable.new(@memory)
28
+
29
+ # For versions 1 and 2, there is a permanent alphabet
30
+ @alphabet = 0
31
+ end
32
+
33
+ def fetch
34
+ pc = @memory.program_counter
35
+ orig_pc = pc
36
+
37
+ # Determine type and form of the operand
38
+ # along with the number of operands
39
+
40
+ # read first byte to get opcode
41
+ opcode = @memory.force_readb(pc)
42
+ pc = pc + 1
43
+
44
+ # opcode form is top 2 bits
45
+ opcode_form = (opcode >> 6) & 3
46
+ operand_count = 0
47
+ operand_types = Array.new(8) { OperandType::OMITTED }
48
+ operand_values = []
49
+
50
+ # SHORT
51
+ if opcode_form == 2
52
+ # operand count is determined by bits 4 and 5
53
+ if ((opcode >> 4) & 3) == 3
54
+ operand_count = 0
55
+ opcode_class = OpcodeClass::OP0
56
+ else
57
+ operand_count = 1
58
+ opcode_class = OpcodeClass::OP1
59
+ end
60
+
61
+ operand_types[0] = (opcode >> 4) & 3
62
+
63
+ # opcode is given as bottom 4 bits
64
+ opcode = opcode & 0b1111
65
+
66
+ # VARIABLE
67
+ elsif opcode_form == 3
68
+ if (opcode & 0b100000) == 0
69
+ # when bit 5 is clear, there are two operands
70
+ operand_count = 2
71
+ opcode_class = OpcodeClass::OP2
72
+ else
73
+ # otherwise, there are VAR number
74
+ operand_count = 8
75
+ opcode_class = OpcodeClass::VAR
76
+ end
77
+
78
+ # opcode is given as bottom 5 bits
79
+ opcode = opcode & 0b11111
80
+
81
+ # EXTENDED
82
+ elsif opcode == 190 # extended form
83
+ opcode_class = OpcodeClass::EXT
84
+
85
+ # VAR number
86
+ operand_count = 8
87
+
88
+ # opcode is given as the next byte
89
+ opcode = @memory.force_readb(pc)
90
+ pc = pc + 1
91
+
92
+ # LONG
93
+ else
94
+
95
+ # there are always 2 operands
96
+ operand_count = 2
97
+ opcode_class = OpcodeClass::OP2
98
+
99
+ # bit 6 of opcode is type of operand 1
100
+ type = opcode & 0b1000000
101
+ if type == 0 # 0 means small constant
102
+ type = OperandType::SMALL
103
+ else # 1 means variable
104
+ type = OperandType::VARIABLE
105
+ end
106
+ operand_types[0] = type
107
+
108
+ # bit 5 of opcode is type of operand 2
109
+ type = opcode & 0b100000
110
+ if type == 0 # 0 means small constant
111
+ type = OperandType::SMALL
112
+ else # 1 means variable
113
+ type = OperandType::VARIABLE
114
+ end
115
+ operand_types[1] = type
116
+
117
+ # opcode is given as bottom 5 bits
118
+ opcode = opcode & 0b11111
119
+ end
120
+
121
+ # We need the opcode and opcode_class to be combined
122
+ opcode = (opcode << 3) | opcode_class
123
+
124
+ # convert some moved opcodes
125
+ if (@header.version <= 4)
126
+ if opcode == Opcode::CALL_1N
127
+ opcode = Opcode::NOT
128
+ end
129
+ end
130
+
131
+ # handle VAR operands
132
+ if opcode_form == 3 or opcode_class == OpcodeClass::VAR or opcode_class == OpcodeClass::EXT
133
+ # each type for the operands is given by reading
134
+ # the next 1 or 2 bytes.
135
+ #
136
+ # This byte contains 4 type descriptions where
137
+ # the most significant 2 bits are the 0th type
138
+ # and the least 2 are the 3rd type
139
+ #
140
+ # If a type is deemed omitted, every subsequent
141
+ # type must also be omitted
142
+
143
+ byte = @memory.force_readb(pc)
144
+ pc = pc + 1
145
+
146
+ operand_types[0] = (byte >> 6) & 3
147
+ operand_types[1] = (byte >> 4) & 3
148
+ operand_types[2] = (byte >> 2) & 3
149
+ operand_types[3] = byte & 3
150
+
151
+ # Get the number of operands
152
+ idx = -1
153
+ first_omitted = -1
154
+ operand_count = operand_types.inject(0) do |result, element|
155
+ idx = idx + 1
156
+ if element == OperandType::OMITTED
157
+ first_omitted = idx
158
+ result
159
+ elsif first_omitted == -1
160
+ result + 1
161
+ else
162
+ # Error, OMITTED was found, but another type
163
+ # was defined as not omitted
164
+ # We will ignore
165
+ result
166
+ end
167
+ end
168
+
169
+ if opcode == Opcode::CALL_VS2 or opcode == Opcode::CALL_VN2
170
+ # Certain opcodes can have up to 8 operands!
171
+ # These are given by a second byte
172
+ byte = @memory.force_readb(pc)
173
+ pc = pc + 1
174
+
175
+ operand_types[4] = (byte >> 6) & 3
176
+ operand_types[5] = (byte >> 4) & 3
177
+ operand_types[6] = (byte >> 2) & 3
178
+ operand_types[7] = byte & 3
179
+
180
+ # update operand_count once more
181
+ operand_count = operand_types.inject(operand_count) do |result, element|
182
+ idx = idx + 1
183
+ if element == OperandType::OMITTED
184
+ first_omitted = idx
185
+ result
186
+ elsif first_omitted == -1
187
+ result + 1
188
+ else
189
+ # Error, OMITTED was found, but another type
190
+ # was defined as not omitted
191
+ # We will ignore
192
+ result
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ # Retrieve the operand values
199
+ operand_types = operand_types.slice(0, operand_count)
200
+ operand_types.each do |i|
201
+ if i == OperandType::SMALL or i == OperandType::VARIABLE
202
+ operand_values << @memory.force_readb(pc)
203
+ pc = pc + 1
204
+ elsif i == OperandType::LARGE
205
+ operand_values << @memory.force_readw(pc)
206
+ pc = pc + 2
207
+ end
208
+ end
209
+
210
+ # If the opcode stores, we need to pull the next byte to get the
211
+ # destination of the result
212
+ destination = nil
213
+ if Opcode.is_store?(opcode, @header.version)
214
+ destination = @memory.force_readb(pc)
215
+ pc = pc + 1
216
+ end
217
+
218
+ # The opcode may indicate that it's argument is a string literal
219
+ if Opcode.has_string?(opcode, @header.version)
220
+ str = ""
221
+ # Now we read in 2-byte words until the most significant bit is set
222
+ # We unencode them from the ZSCII encoding
223
+
224
+ continue = true
225
+
226
+ alphabet = 0
227
+ if (@header.version < 3)
228
+ alphabet = @alphabet
229
+ end
230
+
231
+ result = @memory.force_readzstr(pc)
232
+ pc = pc + result[0]
233
+ chrs = result[1]
234
+
235
+ # convert the string from ZSCII to UTF8
236
+ operand_types << OperandType::STRING
237
+ operand_values << ZSCII.translate(@alphabet, @header.version, chrs, @abbreviation_table)
238
+
239
+ # determine shift locks
240
+ if (@header.version < 3)
241
+ @alphabet = ZSCII.eval_alphabet(@alphabet, @header.version, chrs, @abbreviation_table)
242
+ end
243
+ end
244
+
245
+ # If the opcode is a branch, we need to pull the offset info
246
+ branch_destination = nil
247
+ branch_condition = false
248
+ if Opcode.is_branch?(opcode, @header.version)
249
+ branch_offset = @memory.force_readb(pc)
250
+ pc = pc + 1
251
+
252
+ # if bit 7 is set, the branch occurs on a true condition
253
+ # false otherwise
254
+ if (branch_offset & 0b10000000) != 0
255
+ branch_condition = true
256
+ end
257
+
258
+ # if bit 6 is clear, the branch offset is 14 bits (6 from first byte
259
+ # and the 8 from the next byte) This is _signed_
260
+ if (branch_offset & 0b01000000) == 0
261
+ branch_offset = branch_offset & 0b111111
262
+ negative = (branch_offset & 0b100000) > 0
263
+ branch_offset = branch_offset << 8
264
+ branch_offset = branch_offset | @memory.force_readb(pc)
265
+ if (negative)
266
+ branch_offset = -(16384 - branch_offset)
267
+ end
268
+ pc = pc + 1
269
+ else # otherwise, the offset is simply the remaining 6 bits _unsigned_
270
+ branch_offset = branch_offset & 0b111111
271
+ end
272
+
273
+ # calculate actual destination from the offset
274
+ if branch_offset == 0 or branch_offset == 1
275
+ # a return
276
+ branch_destination = branch_offset
277
+ else
278
+ branch_destination = pc + branch_offset - 2
279
+ end
280
+ end
281
+
282
+ # Create an Instruction class to hold this metadata
283
+ inst = Instruction.new(opcode, operand_types, operand_values, destination, branch_destination, branch_condition, pc - orig_pc)
284
+
285
+ # Store in the instruction cache
286
+ @instruction_cache[orig_pc] = inst
287
+
288
+ inst
289
+ end
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,156 @@
1
+ # The Z-Machine has a dictionary which it uses to parse input and
2
+ # perform lexical analysis.
3
+
4
+ # The header is located at the address given in the Header at
5
+ # address 0x08
6
+
7
+ require_relative 'memory'
8
+ require_relative 'header'
9
+ require_relative 'zscii'
10
+
11
+ module Gruesome
12
+ module Z
13
+ class Dictionary
14
+ def initialize(memory)
15
+ @memory = memory
16
+ @header = Header.new(@memory.contents)
17
+
18
+ @abbreviation_table = AbbreviationTable.new(@memory)
19
+
20
+ @lookup_cache = {}
21
+ end
22
+
23
+ def address
24
+ @header.dictionary_addr
25
+ end
26
+
27
+ def address_of_entries
28
+ addr = self.address
29
+ num_input_codes = @memory.force_readb(addr)
30
+ addr += 1
31
+ addr += num_input_codes
32
+ addr += 3
33
+
34
+ addr
35
+ end
36
+
37
+ def entry_length
38
+ addr = self.address_of_entries - 3
39
+ return @memory.force_readb(addr)
40
+ end
41
+
42
+ def number_of_words
43
+ addr = self.address_of_entries - 2
44
+ return @memory.force_readw(addr)
45
+ end
46
+
47
+ def word_address(index)
48
+ addr = address_of_entries
49
+ addr += entry_length * index
50
+
51
+ return addr
52
+ end
53
+
54
+ def word(index)
55
+ addr = word_address(index)
56
+
57
+ codes = nil
58
+
59
+ if @header.version <= 3
60
+ codes = @memory.force_readzstr(addr, 4)[1]
61
+ else
62
+ codes = @memory.force_readzstr(addr, 6)[1]
63
+ end
64
+
65
+ str = ZSCII.translate(0, @header.version, codes, @abbreviation_table)
66
+ @lookup_cache[str] = index
67
+ return str
68
+ end
69
+
70
+ def lookup_word(token)
71
+ if token.length > 6 and @header.version <= 3
72
+ token = token[0..5]
73
+ elsif token.length > 9
74
+ token = token[0..8]
75
+ end
76
+ cached_index = @lookup_cache[token]
77
+ if cached_index != nil
78
+ if self.word(cached_index) == token
79
+ return cached_index
80
+ end
81
+ end
82
+
83
+ number_of_words.times do |i|
84
+ if word(i) == token
85
+ cached_index = i
86
+ break
87
+ end
88
+ end
89
+
90
+ cached_index
91
+ end
92
+
93
+ def word_separators
94
+ addr = self.address
95
+
96
+ # the number of word separators
97
+ num_input_codes = @memory.force_readb(addr)
98
+ addr += 1
99
+
100
+ codes = []
101
+ num_input_codes.times do
102
+ codes << @memory.force_readb(addr)
103
+ addr += 1
104
+ end
105
+
106
+ codes = codes.map do |i|
107
+ ZSCII.translate_Zchar(i)
108
+ end
109
+
110
+ return codes
111
+ end
112
+
113
+ def tokenize(line)
114
+ orig_line = line
115
+
116
+ # I. ensure that word separators are surrounded by spaces
117
+ self.word_separators.each do |sep|
118
+ line = line.gsub(sep, " " + sep + " ")
119
+ end
120
+
121
+ # II. break up the line into words delimited by spaces
122
+ tokens = line.split(' ')
123
+
124
+ line = orig_line
125
+ last_pos = 0
126
+ ret = tokens.map do |token|
127
+ index = line.index(token)
128
+ line = line[index+token.size..-1]
129
+
130
+ index = index + last_pos
131
+ last_pos = index + token.size
132
+
133
+ index
134
+ { :position => index, :string => token }
135
+ end
136
+
137
+ ret
138
+ end
139
+
140
+ def parse(tokens)
141
+ ret = []
142
+ tokens.each do |token|
143
+ index = lookup_word(token[:string])
144
+ if index != nil
145
+ addr = word_address(index)
146
+ else
147
+ addr = 0
148
+ end
149
+ ret << { :size => token[:string].size, :address => addr, :position => token[:position] }
150
+ end
151
+
152
+ ret
153
+ end
154
+ end
155
+ end
156
+ end