c64asm 0.4.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.yardopts +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +126 -0
- data/Rakefile +6 -0
- data/c64asm.gemspec +19 -0
- data/examples/04.png +0 -0
- data/examples/04.prg +0 -0
- data/examples/hello_world.png +0 -0
- data/examples/hello_world.rb +30 -0
- data/lib/c64asm/asm.rb +751 -0
- data/lib/c64asm/basic.rb +132 -0
- data/lib/c64asm/data.rb +221 -0
- data/lib/c64asm/version.rb +7 -0
- data/lib/c64asm.rb +5 -0
- data/tools/make-data.rb +80 -0
- metadata +64 -0
data/lib/c64asm/asm.rb
ADDED
@@ -0,0 +1,751 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# See LICENSE.txt for licensing information.
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'c64asm/data'
|
6
|
+
require 'c64asm/basic'
|
7
|
+
|
8
|
+
# Fixnum monkey-patches
|
9
|
+
class Fixnum
|
10
|
+
# Return the least significant byte
|
11
|
+
def ls_byte; self & 255; end
|
12
|
+
|
13
|
+
# Return the most significant byte
|
14
|
+
def ms_byte; self >> 8; end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Our namespace
|
18
|
+
module C64Asm
|
19
|
+
attr_accessor :logger
|
20
|
+
|
21
|
+
# Default logging is verbose and to STDERR
|
22
|
+
@logger = Logger.new(STDERR)
|
23
|
+
@logger.level = Logger::DEBUG
|
24
|
+
@logger.formatter = lambda do |s, d, p, m|
|
25
|
+
"#{d.strftime('%H:%M:%S.%L')} #{s.to_s.ljust(7)} -- #{m}\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Log a message if we have a logger present
|
29
|
+
def self.log(level, msg)
|
30
|
+
@logger.send(level, msg) if @logger
|
31
|
+
end
|
32
|
+
|
33
|
+
# General C64Asm exception class
|
34
|
+
class Error < Exception; end
|
35
|
+
|
36
|
+
# Operand error
|
37
|
+
class OperandError < Error; end
|
38
|
+
|
39
|
+
# Operand, the most important building block
|
40
|
+
class Operand
|
41
|
+
attr_reader :op, :mode, :arg, :label
|
42
|
+
|
43
|
+
# Setup and validate an operand
|
44
|
+
def initialize(op, arg = false, mod = false)
|
45
|
+
raise OperandError, 'No such operand' unless OP_CODES.has_key? op
|
46
|
+
|
47
|
+
@op = op
|
48
|
+
@mode = false
|
49
|
+
@arg = arg
|
50
|
+
@mod = mod
|
51
|
+
@label = false
|
52
|
+
@ready = false
|
53
|
+
|
54
|
+
opcode = OP_CODES[op]
|
55
|
+
|
56
|
+
# mode resolution
|
57
|
+
if opcode.has_key? :n
|
58
|
+
# all immediates
|
59
|
+
@mode = :n
|
60
|
+
@ready = true
|
61
|
+
elsif not arg and opcode.has_key? :e
|
62
|
+
@mode = :e
|
63
|
+
@ready = true
|
64
|
+
elsif opcode.keys.length == 1
|
65
|
+
# branching and jsr
|
66
|
+
@mode = opcode.keys.first
|
67
|
+
elsif arg and arg.instance_of? Fixnum
|
68
|
+
# for the rest, let's try figure out mode by checking argument
|
69
|
+
# we treat addressing modes as of higher priority, eg. :z over :d
|
70
|
+
if arg >= 0 and arg <= 255
|
71
|
+
if opcode.has_key? :z
|
72
|
+
@mode = :z
|
73
|
+
elsif opcode.has_key? :d
|
74
|
+
@mode = :d
|
75
|
+
else
|
76
|
+
raise OperandError, 'No mode handling byte'
|
77
|
+
end
|
78
|
+
elsif arg >= 256 and arg <= 65535
|
79
|
+
if opcode.has_key? :a
|
80
|
+
@mode = :a
|
81
|
+
else
|
82
|
+
raise OperandError, 'Argument out of range'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# future reference handling, aka labels
|
88
|
+
if arg and arg.instance_of? Symbol
|
89
|
+
# labels can point only to absolute addresses
|
90
|
+
unless (has_a = opcode.has_key? :a) or opcode.has_key? :r
|
91
|
+
raise OperandError, 'Used with label but no :a or :r modes'
|
92
|
+
end
|
93
|
+
|
94
|
+
@mode = has_a ? :a : :r
|
95
|
+
@label = arg
|
96
|
+
end
|
97
|
+
|
98
|
+
# argument checking
|
99
|
+
if @mode and not @label
|
100
|
+
raise OperandError, 'Invalid argument' unless validate
|
101
|
+
|
102
|
+
@ready = true
|
103
|
+
end
|
104
|
+
|
105
|
+
# modifier checking
|
106
|
+
check_mod if mod
|
107
|
+
end
|
108
|
+
|
109
|
+
# create addressing mode methods
|
110
|
+
ADDR_MODES.keys.each do |mode|
|
111
|
+
class_eval "def #{mode}(arg, mod = nil); check_mode(:#{mode}, arg, mod); end"
|
112
|
+
end
|
113
|
+
|
114
|
+
# Do we have all data in raw form
|
115
|
+
def ready?; @ready; end
|
116
|
+
|
117
|
+
# Resolve addresses, if needed
|
118
|
+
def resolve(arg)
|
119
|
+
return true unless @label
|
120
|
+
|
121
|
+
@mod ? @arg = arg.send(*@mod) : @arg = arg
|
122
|
+
raise OperandError, 'Invalid argument' unless validate
|
123
|
+
|
124
|
+
@ready = true
|
125
|
+
end
|
126
|
+
|
127
|
+
# Turn the operand into source code string
|
128
|
+
def to_source
|
129
|
+
source = @op.to_s
|
130
|
+
|
131
|
+
if @label
|
132
|
+
if @mod
|
133
|
+
case @mod.first
|
134
|
+
when :+
|
135
|
+
label = @label.to_s + ('%+d' % @mod[1])
|
136
|
+
when :ls_byte
|
137
|
+
label = '<' + @label.to_s
|
138
|
+
when :ms_byte
|
139
|
+
label = '>' + @label.to_s
|
140
|
+
else
|
141
|
+
label = @label.to_s + @mod.join
|
142
|
+
end
|
143
|
+
else
|
144
|
+
label = @label.to_s
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
unless @mode == :n or @mode == :e
|
149
|
+
if @label
|
150
|
+
source += ADDR_MODES[@mode][:src] % label
|
151
|
+
else
|
152
|
+
if @mode == :r
|
153
|
+
source += ' *%+d' % @arg.to_s
|
154
|
+
else
|
155
|
+
source += ADDR_MODES[@mode][:src] % ('$' + @arg.to_s(16))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
source
|
161
|
+
end
|
162
|
+
|
163
|
+
# Return the length of the additional operand in machine code bytes, or false
|
164
|
+
def length; @mode ? 1 + ADDR_MODES[@mode][:len] : false; end
|
165
|
+
|
166
|
+
# Return pretty string representation of the operand
|
167
|
+
def to_s; "<Operand: #{to_source}>"; end
|
168
|
+
|
169
|
+
# Turn the operand into a byte array
|
170
|
+
# Won't work if we haven't got all the necessary data yet.
|
171
|
+
def to_a
|
172
|
+
return [] unless @ready
|
173
|
+
|
174
|
+
bytes = [OP_CODES[@op][@mode][:byte]]
|
175
|
+
|
176
|
+
if @arg and @arg > 255
|
177
|
+
bytes += [@arg.ls_byte, @arg.ms_byte]
|
178
|
+
elsif @arg
|
179
|
+
bytes += [@arg]
|
180
|
+
else
|
181
|
+
bytes
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Turn the operand into a byte string
|
186
|
+
# Won't work if we haven't got all the necessary data yet.
|
187
|
+
def to_binary
|
188
|
+
return '' unless @ready
|
189
|
+
|
190
|
+
@mode == :r ? to_a.pack('Cc') : to_a.pack('C*')
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
# Validate addressing mode
|
195
|
+
def check_mode(mode, arg, mod)
|
196
|
+
raise OperandError, 'Operand was ready' if @ready
|
197
|
+
raise OperandError, 'No such mode' unless OP_CODES[@op].has_key? mode
|
198
|
+
|
199
|
+
@mode = mode
|
200
|
+
@arg = arg
|
201
|
+
@mod = mod
|
202
|
+
|
203
|
+
case arg
|
204
|
+
when Fixnum
|
205
|
+
raise OperandError, 'Invalid argument' unless validate
|
206
|
+
@ready = true
|
207
|
+
when Symbol
|
208
|
+
modec = @mode.to_s[0]
|
209
|
+
if @mod
|
210
|
+
raise OperandError, 'Label used with wrong mode' unless (modec == 'a') or (modec == 'd')
|
211
|
+
else
|
212
|
+
raise OperandError, 'Label used with wrong mode' unless modec == 'a'
|
213
|
+
end
|
214
|
+
@label = arg
|
215
|
+
else
|
216
|
+
raise OperandError, 'Invalid argument type'
|
217
|
+
end
|
218
|
+
|
219
|
+
check_mod if mod
|
220
|
+
|
221
|
+
self
|
222
|
+
end
|
223
|
+
|
224
|
+
# Validate modifier
|
225
|
+
def check_mod
|
226
|
+
raise OperandError, 'Modifier used with non-label argument' unless @label
|
227
|
+
|
228
|
+
if @mod.instance_of? Fixnum
|
229
|
+
@mod = [:+, @mod]
|
230
|
+
elsif @mod.instance_of? Array and @mod.length == 2 and [:/, :*, :<<, :>>, :& , :|].member? @mod.first
|
231
|
+
raise OperandError, 'Arithmetic argument has to be a fixnum' unless @mod[1].instance_of? Fixnum
|
232
|
+
elsif [:<, :>].member? @mod
|
233
|
+
# this two modifiers make sense only for :d addressing mode
|
234
|
+
if not @mode or (@mode and @mode != :d)
|
235
|
+
unless OP_CODES[@op].has_key? :d
|
236
|
+
raise OperandError, 'Byte modifier used with non-direct addressing mode'
|
237
|
+
end
|
238
|
+
|
239
|
+
@mode = :d
|
240
|
+
end
|
241
|
+
|
242
|
+
@mod = [@mod == :< ? :ls_byte : :ms_byte]
|
243
|
+
else
|
244
|
+
raise OperandError, 'Unknown modifier'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Low-level validation
|
249
|
+
def validate
|
250
|
+
if (@mode == :n and @arg) or \
|
251
|
+
(@mode == :r and not (@arg >= -128 and @arg <= 127)) or \
|
252
|
+
([:a, :ax, :ay, :ar].member? @mode and not (@arg >= 0 and @arg <= 65535)) or \
|
253
|
+
([:d, :z, :zx, :zy, :zxr, :zyr].member? @mode and not (@arg >= 0 and @arg <= 255))
|
254
|
+
false
|
255
|
+
else
|
256
|
+
true
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Nop error
|
262
|
+
class NopError < Error; end
|
263
|
+
|
264
|
+
# Nops don't translate to machine code
|
265
|
+
# Things like labels, align statements etc.
|
266
|
+
class Nop
|
267
|
+
# No need to resolve anything here
|
268
|
+
def ready?; true; end
|
269
|
+
|
270
|
+
# Not a label
|
271
|
+
def label; false; end
|
272
|
+
|
273
|
+
# We don't generate machine code
|
274
|
+
def to_a; []; end
|
275
|
+
|
276
|
+
# We don't generate machine code
|
277
|
+
def to_binary; ''; end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Label error
|
281
|
+
class LabelError < NopError; end
|
282
|
+
|
283
|
+
# Label references an address
|
284
|
+
# Which might as well be not know at the time of declaration.
|
285
|
+
class Label < Nop
|
286
|
+
attr_reader :name
|
287
|
+
|
288
|
+
# Create new label nop
|
289
|
+
def initialize(name)
|
290
|
+
raise LabelError, 'Label name must be a symbol' unless name.instance_of? Symbol
|
291
|
+
|
292
|
+
@name = name
|
293
|
+
end
|
294
|
+
|
295
|
+
# Return source code representation
|
296
|
+
def to_source; @name.to_s; end
|
297
|
+
|
298
|
+
# Return pretty string representation
|
299
|
+
def to_s; "<Label: #{@name}>"; end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Alignment error
|
303
|
+
class AlignError < NopError; end
|
304
|
+
|
305
|
+
# Align sets the linker current address
|
306
|
+
class Align < Nop
|
307
|
+
attr_reader :addr
|
308
|
+
|
309
|
+
# Create new alignment nop
|
310
|
+
def initialize(addr)
|
311
|
+
unless addr.instance_of? Fixnum and (addr >= 0 and addr <= 65535)
|
312
|
+
raise AlignError, 'Alignment address has to be in range $0 - $ffff'
|
313
|
+
end
|
314
|
+
|
315
|
+
@addr = addr
|
316
|
+
end
|
317
|
+
|
318
|
+
# Return source code representation
|
319
|
+
def to_source; "* = $#{@addr.to_s(16)}"; end
|
320
|
+
|
321
|
+
# Return pretty string representation
|
322
|
+
def to_s; "<Align: $#{@addr.to_s(16)}"; end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Data error
|
326
|
+
class DataError < NopError; end
|
327
|
+
|
328
|
+
# Data is a bunch of bytes
|
329
|
+
class Data < Nop
|
330
|
+
attr_reader :data, :mode, :length
|
331
|
+
|
332
|
+
# Create new data nop
|
333
|
+
# Handles a couple of input modes.
|
334
|
+
def initialize(data, mode = :default)
|
335
|
+
case data
|
336
|
+
when String
|
337
|
+
raise DataError, 'Unimplemented mode' unless [:default, :screen].member? mode
|
338
|
+
@length = data.length
|
339
|
+
when Array
|
340
|
+
raise DataError, 'Unimplemented mode' unless [:default, :word].member? mode
|
341
|
+
@length = mode == :word ? 2 * data.length : data.length
|
342
|
+
end
|
343
|
+
|
344
|
+
@data = data
|
345
|
+
@mode = mode
|
346
|
+
|
347
|
+
validate
|
348
|
+
end
|
349
|
+
|
350
|
+
# Return pretty string representation
|
351
|
+
def to_s
|
352
|
+
string = '<Data: '
|
353
|
+
|
354
|
+
case @data
|
355
|
+
when String
|
356
|
+
string += '"' + (@data.length > 16 ? @data.slice(0, 16) + '...' : @data) + '"'
|
357
|
+
when Array
|
358
|
+
slice = @data.length > 8 ? @data.slice(0, 8) : @data
|
359
|
+
string += slice.collect{|e| '$' + e.to_s(16)}.join(',')
|
360
|
+
string += '...' if slice.length != @data.length
|
361
|
+
end
|
362
|
+
|
363
|
+
if @mode != :default
|
364
|
+
string += " (#{mode})"
|
365
|
+
end
|
366
|
+
|
367
|
+
string += '>'
|
368
|
+
end
|
369
|
+
|
370
|
+
# Return source code representation
|
371
|
+
def to_source
|
372
|
+
case @data
|
373
|
+
when String
|
374
|
+
case @mode
|
375
|
+
when :default
|
376
|
+
".text \"#{@data}\""
|
377
|
+
when :screen
|
378
|
+
".screen \"#{@data}\""
|
379
|
+
end
|
380
|
+
when Array
|
381
|
+
case @mode
|
382
|
+
when :default
|
383
|
+
".byte #{@data.collect{|e| '$' + e.to_s(16)}.join(',')}"
|
384
|
+
when :word
|
385
|
+
".word #{@data.collect{|e| '$' + e.to_s(16)}.join(',')}"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Turn data into a byte string
|
391
|
+
def to_binary
|
392
|
+
case @data
|
393
|
+
when String
|
394
|
+
case @mode
|
395
|
+
when :default
|
396
|
+
@data.each_codepoint.to_a.collect{|p| PETSCII[p]}.pack('C*')
|
397
|
+
when :screen
|
398
|
+
@data.upcase.each_codepoint.to_a.collect{|p| CHAR_MAP[p]}.pack('C*')
|
399
|
+
end
|
400
|
+
when Array
|
401
|
+
case @mode
|
402
|
+
when :default
|
403
|
+
@data.pack('C*')
|
404
|
+
when :word
|
405
|
+
@data.collect{|e| [e.ls_byte, e.ms_byte]}.flatten.pack('C*')
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
private
|
411
|
+
# Validate data
|
412
|
+
def validate
|
413
|
+
case @data
|
414
|
+
when String
|
415
|
+
case @mode
|
416
|
+
when :default
|
417
|
+
@data.each_codepoint{|p| raise DataError, 'Invalid data' unless PETSCII.has_key? p}
|
418
|
+
when :screen
|
419
|
+
@data.upcase.each_codepoint{|p| raise DataError, 'Invalid data' unless CHAR_MAP.has_key? p}
|
420
|
+
end
|
421
|
+
when Array
|
422
|
+
case @mode
|
423
|
+
when :default
|
424
|
+
@data.each{|e| raise DataError, 'Invalid data' unless (e >= 0 and e <= 255)}
|
425
|
+
when :word
|
426
|
+
@data.each{|e| raise DataError, 'Invalid data' unless (e >= 0 and e <= 65535)}
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# Block error
|
433
|
+
class BlockError < Error; end
|
434
|
+
|
435
|
+
# Block is a raw machine code block
|
436
|
+
# Implements the non-magic of linking, aka symbol resolution.
|
437
|
+
class Block < Array
|
438
|
+
attr_reader :labels, :linked
|
439
|
+
|
440
|
+
# Create new block
|
441
|
+
def initialize
|
442
|
+
@labels = {}
|
443
|
+
@linked = false
|
444
|
+
@chunks = {}
|
445
|
+
end
|
446
|
+
|
447
|
+
# Link resolves symbols and relative jumps given the origin
|
448
|
+
def link(origin = 0x1000)
|
449
|
+
raise BlockError, 'Invalid origin' unless (origin >= 0 and origin <= 65535)
|
450
|
+
|
451
|
+
# override origin if first non-Block element is an Align object
|
452
|
+
felem = first
|
453
|
+
while felem.class == Block
|
454
|
+
felem = felem.first
|
455
|
+
end
|
456
|
+
if felem.instance_of? Align
|
457
|
+
origin = felem.addr
|
458
|
+
end
|
459
|
+
|
460
|
+
endaddr = linker_pass(origin, :one)
|
461
|
+
linker_pass(origin, :two)
|
462
|
+
|
463
|
+
@linked
|
464
|
+
end
|
465
|
+
|
466
|
+
# Return source code representation
|
467
|
+
def to_source
|
468
|
+
(['.block'] + collect{|e| e.to_source} + ['.bend']).flatten
|
469
|
+
end
|
470
|
+
|
471
|
+
# Return pretty string representation
|
472
|
+
def to_s; "<Block #{length}>"; end
|
473
|
+
|
474
|
+
# Turn block into byte string
|
475
|
+
def to_binary
|
476
|
+
link unless @linked
|
477
|
+
[@linked[0].ls_byte, @linked[0].ms_byte].pack('CC') + binary_pass
|
478
|
+
end
|
479
|
+
|
480
|
+
# Return verbose representation
|
481
|
+
def dump
|
482
|
+
link unless @linked
|
483
|
+
lines = dump_pass
|
484
|
+
lines.shift
|
485
|
+
lines
|
486
|
+
end
|
487
|
+
|
488
|
+
# Write block to a given file in the given format
|
489
|
+
# This will probably overwrite the target file without warning.
|
490
|
+
def write!(fname, mode = 'w', what = :prg)
|
491
|
+
File.open(fname, mode) do |fd|
|
492
|
+
case what
|
493
|
+
when :prg
|
494
|
+
fd.write(to_binary)
|
495
|
+
when :src
|
496
|
+
fd.write(to_source.join("\n"))
|
497
|
+
when :dump
|
498
|
+
fd.write(dump.join("\n"))
|
499
|
+
else
|
500
|
+
raise BlockError, 'Unknown generation mode'
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# Internal method, treat as private
|
506
|
+
def dump_pass
|
507
|
+
addr = @linked.first
|
508
|
+
lines = []
|
509
|
+
|
510
|
+
lines.push('$%0.4x ' % addr + " \t.block")
|
511
|
+
each do |e|
|
512
|
+
line = ('$%0.4x ' % addr).upcase
|
513
|
+
block = false
|
514
|
+
|
515
|
+
case e
|
516
|
+
when Operand
|
517
|
+
if e.mode == :r
|
518
|
+
bytes = e.to_a.pack('Cc').unpack('C*')
|
519
|
+
else
|
520
|
+
bytes = e.to_a
|
521
|
+
end
|
522
|
+
line += bytes.to_a.collect{|e| '%0.2X' % e}.join(' ')
|
523
|
+
line += ' ' * (9 - (3 * e.length))
|
524
|
+
line += " \t#{e.to_source}"
|
525
|
+
addr += e.length
|
526
|
+
when Align
|
527
|
+
line += " \t#{e.to_source}"
|
528
|
+
addr = e.addr
|
529
|
+
when Label
|
530
|
+
line += " #{e.to_source}"
|
531
|
+
when Data
|
532
|
+
line += ".. .. .. \t#{e.to_source}"
|
533
|
+
addr += e.length
|
534
|
+
when Block
|
535
|
+
addr, lines_passed = e.dump_pass
|
536
|
+
lines += lines_passed
|
537
|
+
block = true
|
538
|
+
end
|
539
|
+
|
540
|
+
lines.push(line) unless block
|
541
|
+
end
|
542
|
+
lines.push('$%0.4x ' % addr + " \t.bend")
|
543
|
+
|
544
|
+
[addr, lines]
|
545
|
+
end
|
546
|
+
|
547
|
+
# Internal method, treat as private
|
548
|
+
def binary_pass
|
549
|
+
binary = ''
|
550
|
+
each do |e|
|
551
|
+
case e
|
552
|
+
when Align
|
553
|
+
binary += ([0] * (e.addr - @chunks[e.addr])).pack('C*')
|
554
|
+
when Label
|
555
|
+
true
|
556
|
+
when Data
|
557
|
+
binary += e.to_binary
|
558
|
+
when Operand
|
559
|
+
binary += e.to_binary
|
560
|
+
when Block
|
561
|
+
binary += e.binary_pass
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
binary
|
566
|
+
end
|
567
|
+
|
568
|
+
# Internal method, treat as private
|
569
|
+
def linker_pass(addr, pass)
|
570
|
+
@labels = {} if pass == :one
|
571
|
+
origin = addr
|
572
|
+
|
573
|
+
each do |e|
|
574
|
+
case e
|
575
|
+
when Align
|
576
|
+
raise BlockError, "Invalid alignment from $#{addr.to_s(16)} to $#{e.addr.to_s(16)}" if e.addr < addr
|
577
|
+
|
578
|
+
@chunks[e.addr] = addr if pass == :one
|
579
|
+
addr = e.addr
|
580
|
+
when Label
|
581
|
+
if pass == :one
|
582
|
+
if @labels.has_key? e.name
|
583
|
+
C64Asm.log :warn, "Redefinition of label #{e.name} from $#{@labels[e.name].to_s(16)} to $#{addr.to_s(16)}"
|
584
|
+
end
|
585
|
+
|
586
|
+
@labels[e.name] = addr
|
587
|
+
end
|
588
|
+
when Data
|
589
|
+
addr += e.length
|
590
|
+
when Operand
|
591
|
+
if pass == :two
|
592
|
+
unless e.ready?
|
593
|
+
if e.label == :*
|
594
|
+
arg = addr
|
595
|
+
elsif @labels.has_key? e.label
|
596
|
+
arg = @labels[e.label]
|
597
|
+
else
|
598
|
+
C64Asm.log :error, "Can't resolve label for #{e.to_s}"
|
599
|
+
raise BlockError
|
600
|
+
end
|
601
|
+
|
602
|
+
if e.mode == :r
|
603
|
+
arg = arg - addr -2
|
604
|
+
end
|
605
|
+
|
606
|
+
e.resolve(arg)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
addr += e.length
|
611
|
+
when Block
|
612
|
+
addr = e.linker_pass(addr, pass)
|
613
|
+
else
|
614
|
+
C64Asm.log :error, "Invalid element #{e.to_s} in Block"
|
615
|
+
raise BlockError
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
@linked = [origin, addr] if pass == :one
|
620
|
+
addr
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
# Macro error
|
625
|
+
class MacroError < Error; end
|
626
|
+
|
627
|
+
# Macro is the top-level building block
|
628
|
+
class Macro
|
629
|
+
attr_reader :variables
|
630
|
+
|
631
|
+
# Create new macro
|
632
|
+
# You can supply a hash of default variables that will be available within the block.
|
633
|
+
def initialize(vars = {}, &blk)
|
634
|
+
@variables = vars
|
635
|
+
@procs = []
|
636
|
+
|
637
|
+
@procs.push(blk) if blk
|
638
|
+
end
|
639
|
+
|
640
|
+
# Add more code to a block
|
641
|
+
def add_code(&blk); @procs.push(blk); end
|
642
|
+
|
643
|
+
# Return pretty string representation
|
644
|
+
def to_s; "<Macro #{@procs.length} #{@variables.to_s}>"; end
|
645
|
+
|
646
|
+
# Return a block from the macro given variables
|
647
|
+
def call(vars = {})
|
648
|
+
return Block.new if @procs.empty?
|
649
|
+
|
650
|
+
@code = Block.new
|
651
|
+
@labels = []
|
652
|
+
|
653
|
+
# check for extraneous variables
|
654
|
+
extra = vars.keys - @variables.keys
|
655
|
+
raise MacroError, "Extraneous variables #{extra.join(', ')}" unless extra.empty?
|
656
|
+
|
657
|
+
# merge variable hash
|
658
|
+
@vars = @variables.merge(vars)
|
659
|
+
|
660
|
+
@procs.each{|b| instance_eval(&b)}
|
661
|
+
@code
|
662
|
+
end
|
663
|
+
|
664
|
+
private
|
665
|
+
# Add alignment nop
|
666
|
+
def align(addr)
|
667
|
+
begin
|
668
|
+
@code.push(Align.new(addr))
|
669
|
+
rescue AlignError => e
|
670
|
+
parse_error "Align instruction error: #{e.to_s}"
|
671
|
+
end
|
672
|
+
addr
|
673
|
+
end
|
674
|
+
|
675
|
+
# Add label nop
|
676
|
+
def label(name)
|
677
|
+
parse_warn "Redefinition of label #{name}" if @labels.member? name
|
678
|
+
begin
|
679
|
+
@code.push(Label.new(name))
|
680
|
+
rescue LabelError => e
|
681
|
+
parse_error "Label instruction error: #{e.to_s}"
|
682
|
+
end
|
683
|
+
@labels.push(name)
|
684
|
+
name
|
685
|
+
end
|
686
|
+
|
687
|
+
# Add data nop
|
688
|
+
def data(arg, mode = :default)
|
689
|
+
begin
|
690
|
+
data = Data.new(arg, mode)
|
691
|
+
@code.push(data)
|
692
|
+
rescue DataError => e
|
693
|
+
parse_error "Data instruction error: #{e.to_s}"
|
694
|
+
end
|
695
|
+
data.length
|
696
|
+
end
|
697
|
+
|
698
|
+
# Add block nop
|
699
|
+
def block(stuff)
|
700
|
+
parse_error 'Block not an instance of Block' unless stuff.instance_of? Block
|
701
|
+
@code.push(stuff)
|
702
|
+
stuff.length
|
703
|
+
end
|
704
|
+
|
705
|
+
# Add more code
|
706
|
+
def insert(stuff)
|
707
|
+
parse_error 'Block not an instance of Block' unless stuff.instance_of? Block
|
708
|
+
@code += stuff
|
709
|
+
stuff.length
|
710
|
+
end
|
711
|
+
|
712
|
+
# The DSL happens here
|
713
|
+
def method_missing(name, *args, &blk)
|
714
|
+
name = :and if name == :ane
|
715
|
+
if OP_CODES.has_key? name
|
716
|
+
begin
|
717
|
+
if args.length == 0
|
718
|
+
op = Operand.new(name)
|
719
|
+
else
|
720
|
+
arg = args.first
|
721
|
+
mod = args[1] or false
|
722
|
+
op = Operand.new(name, arg, mod)
|
723
|
+
end
|
724
|
+
rescue OperandError => e
|
725
|
+
parse_error "Operand error: #{e.to_s}"
|
726
|
+
end
|
727
|
+
@code.push(op)
|
728
|
+
op
|
729
|
+
elsif @vars.has_key? name
|
730
|
+
@vars[name]
|
731
|
+
else
|
732
|
+
parse_error "Method :#{name} not found"
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
# General logging helper
|
737
|
+
def say(level, msg)
|
738
|
+
from = caller[2].match(/.*?\:\d+/)[0]
|
739
|
+
C64Asm.log level, "(#{from}) #{msg}"
|
740
|
+
end
|
741
|
+
|
742
|
+
# Parse error logging helper
|
743
|
+
def parse_error(msg)
|
744
|
+
say :error, msg
|
745
|
+
raise MacroError
|
746
|
+
end
|
747
|
+
|
748
|
+
# Parse error logging helper
|
749
|
+
def parse_warn(msg); say :warn, msg; end
|
750
|
+
end
|
751
|
+
end
|