c64asm 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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