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