n65 0.5.0
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 +23 -0
- data/Gemfile +4 -0
- data/LICENSE +340 -0
- data/README.md +126 -0
- data/Rakefile +2 -0
- data/bin/n65 +11 -0
- data/data/opcodes.yaml +1030 -0
- data/examples/beep.asm +24 -0
- data/examples/mario2.asm +260 -0
- data/examples/mario2.char +0 -0
- data/examples/music_driver.asm +202 -0
- data/examples/noise.asm +93 -0
- data/examples/pulse_chord.asm +213 -0
- data/images/assembler_demo.png +0 -0
- data/lib/n65.rb +243 -0
- data/lib/n65/directives/ascii.rb +42 -0
- data/lib/n65/directives/bytes.rb +102 -0
- data/lib/n65/directives/dw.rb +86 -0
- data/lib/n65/directives/enter_scope.rb +55 -0
- data/lib/n65/directives/exit_scope.rb +35 -0
- data/lib/n65/directives/inc.rb +67 -0
- data/lib/n65/directives/incbin.rb +51 -0
- data/lib/n65/directives/ines_header.rb +53 -0
- data/lib/n65/directives/label.rb +46 -0
- data/lib/n65/directives/org.rb +47 -0
- data/lib/n65/directives/segment.rb +45 -0
- data/lib/n65/directives/space.rb +46 -0
- data/lib/n65/front_end.rb +90 -0
- data/lib/n65/instruction.rb +308 -0
- data/lib/n65/instruction_base.rb +29 -0
- data/lib/n65/memory_space.rb +150 -0
- data/lib/n65/opcodes.rb +9 -0
- data/lib/n65/parser.rb +85 -0
- data/lib/n65/regexes.rb +33 -0
- data/lib/n65/symbol_table.rb +198 -0
- data/lib/n65/version.rb +3 -0
- data/n65.gemspec +23 -0
- data/nes_lib/nes.sym +105 -0
- data/test/test_memory_space.rb +82 -0
- data/test/test_symbol_table.rb +238 -0
- data/utils/midi/Makefile +3 -0
- data/utils/midi/c_scale.mid +0 -0
- data/utils/midi/convert +0 -0
- data/utils/midi/guitar.mid +0 -0
- data/utils/midi/include/event.h +93 -0
- data/utils/midi/include/file.h +57 -0
- data/utils/midi/include/helpers.h +14 -0
- data/utils/midi/include/track.h +45 -0
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +204 -0
- data/utils/midi/source/convert.cpp +16 -0
- data/utils/midi/source/event.cpp +96 -0
- data/utils/midi/source/file.cpp +37 -0
- data/utils/midi/source/helpers.cpp +46 -0
- data/utils/midi/source/track.cpp +37 -0
- data/utils/opcode_table_to_yaml.rb +91 -0
- metadata +133 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module N65
|
3
|
+
|
4
|
+
####
|
5
|
+
## This class represents a label, and will create
|
6
|
+
## an entry in the symbol table associated with
|
7
|
+
## the address it appears at.
|
8
|
+
class Label
|
9
|
+
|
10
|
+
####
|
11
|
+
## Try to parse as a label
|
12
|
+
def self.parse(line)
|
13
|
+
match_data = line.match(/^([a-zA-Z][a-zA-Z0-9_]+):$/)
|
14
|
+
unless match_data.nil?
|
15
|
+
label = match_data[1].to_sym
|
16
|
+
return self.new(label)
|
17
|
+
end
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
####
|
23
|
+
## Create a new label object
|
24
|
+
def initialize(symbol)
|
25
|
+
@symbol = symbol
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
####
|
30
|
+
## Create an entry in the symbol table for this label
|
31
|
+
def exec(assembler)
|
32
|
+
program_counter = assembler.program_counter
|
33
|
+
assembler.symbol_table.define_symbol(@symbol, program_counter)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
####
|
38
|
+
## Display
|
39
|
+
def to_s
|
40
|
+
"#{@symbol}:"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
####
|
6
|
+
## This is an .org directive
|
7
|
+
class Org < InstructionBase
|
8
|
+
attr_reader :address
|
9
|
+
|
10
|
+
####
|
11
|
+
## Try to parse an .org statement
|
12
|
+
def self.parse(line)
|
13
|
+
match_data = line.match(/^\.org\s+\$([0-9A-Fa-f]{4})$/)
|
14
|
+
return nil if match_data.nil?
|
15
|
+
address = match_data[1].to_i(16)
|
16
|
+
Org.new(address)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
####
|
21
|
+
## Initialized with address to switch to
|
22
|
+
def initialize(address)
|
23
|
+
@address = address
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
####
|
28
|
+
## Exec this directive on the assembler
|
29
|
+
def exec(assembler)
|
30
|
+
assembler.program_counter = address
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
####
|
35
|
+
## Display
|
36
|
+
def to_s
|
37
|
+
if @address <= 0xff
|
38
|
+
".org $%2.X" % @address
|
39
|
+
else
|
40
|
+
".org $%4.X" % @address
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
module N65
|
3
|
+
|
4
|
+
|
5
|
+
####
|
6
|
+
## This directive instruction can include a binary file
|
7
|
+
class Segment < InstructionBase
|
8
|
+
|
9
|
+
####
|
10
|
+
## Try to parse a dw directive
|
11
|
+
def self.parse(line)
|
12
|
+
match_data = line.match(/^.segment (prog|char) (\d+)$/i)
|
13
|
+
unless match_data.nil?
|
14
|
+
_, segment, bank = match_data.to_a
|
15
|
+
return Segment.new(segment, bank.to_i)
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
####
|
22
|
+
## Initialize with filename
|
23
|
+
def initialize(segment, bank)
|
24
|
+
@bank = bank
|
25
|
+
@segment = segment
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
####
|
30
|
+
## Execute the segment and bank change on the assembler
|
31
|
+
def exec(assembler)
|
32
|
+
assembler.current_segment = @segment
|
33
|
+
assembler.current_bank = @bank
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
####
|
38
|
+
## Display
|
39
|
+
def to_s
|
40
|
+
".segment #{@segment} #{@bank}"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive gives a symbolic name for memory and creates space for a variable in RAM
|
8
|
+
class Space < InstructionBase
|
9
|
+
|
10
|
+
####
|
11
|
+
## Try to parse a .space directive
|
12
|
+
def self.parse(line)
|
13
|
+
match_data = line.match(/^.space\s+([a-zA-Z]?[a-zA-Z0-9_]+?)\s+([0-9]+)$/)
|
14
|
+
return nil if match_data.nil?
|
15
|
+
_, name, size = match_data.to_a
|
16
|
+
|
17
|
+
Space.new(name, size.to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
####
|
22
|
+
## Initialize some memory space with a name
|
23
|
+
def initialize(name, size)
|
24
|
+
@name = name
|
25
|
+
@size = size
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
####
|
30
|
+
## .space creates a symbol at the current PC, and then advances PC by size
|
31
|
+
def exec(assembler)
|
32
|
+
program_counter = assembler.program_counter
|
33
|
+
assembler.symbol_table.define_symbol(@name, program_counter)
|
34
|
+
assembler.program_counter += @size
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
####
|
39
|
+
## Display
|
40
|
+
def to_s
|
41
|
+
".space #{@name} #{@size}"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require_relative '../n65'
|
3
|
+
|
4
|
+
module N65
|
5
|
+
|
6
|
+
####
|
7
|
+
## This class handles the front end aspects,
|
8
|
+
## parsing the commandline options and running the assembler
|
9
|
+
class FrontEnd
|
10
|
+
|
11
|
+
####
|
12
|
+
## Initialize with ARGV commandline
|
13
|
+
def initialize(argv)
|
14
|
+
@options = {:output_file => nil}
|
15
|
+
@argv = argv.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
####
|
20
|
+
## Run the assembler
|
21
|
+
def run
|
22
|
+
## First use the option parser
|
23
|
+
parser = create_option_parser
|
24
|
+
parser.parse!(@argv)
|
25
|
+
|
26
|
+
## Whatever is leftover in argv the input files
|
27
|
+
if @argv.size.zero?
|
28
|
+
STDERR.puts("No input files")
|
29
|
+
exit(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
## Only can assemble one file at once for now
|
33
|
+
if @argv.size != 1
|
34
|
+
STDERR.puts "Can only assemble one input file at once :("
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
input_file = @argv.shift
|
39
|
+
|
40
|
+
## Make sure the input file exists
|
41
|
+
unless File.exists?(input_file)
|
42
|
+
STDERR.puts "Input file #{input_file} does not exist"
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
## Maybe they didn't provide an output file name, so we'll guess
|
47
|
+
if @options[:output_file].nil?
|
48
|
+
ext = File.extname(input_file)
|
49
|
+
@options[:output_file] = input_file.gsub(ext, '') + '.nes'
|
50
|
+
end
|
51
|
+
|
52
|
+
if @options.values.any?(&:nil?)
|
53
|
+
STDERR.puts "Missing options try --help"
|
54
|
+
exit(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
#begin
|
58
|
+
N65::Assembler.from_file(input_file, @options[:output_file])
|
59
|
+
#rescue StandardError => error
|
60
|
+
#STDERR.puts("Assemble Failed!")
|
61
|
+
#STDERR.puts(error.class)
|
62
|
+
#if error.message
|
63
|
+
#STDERR.puts(error.message)
|
64
|
+
#end
|
65
|
+
#exit(1)
|
66
|
+
#end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
####
|
72
|
+
## Create a commandline option parser
|
73
|
+
def create_option_parser
|
74
|
+
OptionParser.new do |opts|
|
75
|
+
opts.banner = "Usage: #{$0} [options] <input_file.asm>"
|
76
|
+
|
77
|
+
opts.on('-o', '--outfile filename', 'outfile') do |output_file|
|
78
|
+
@options[:output_file] = output_file;
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on('-h', '--help', 'Displays Help') do
|
82
|
+
puts opts
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require_relative 'opcodes'
|
2
|
+
require_relative 'regexes'
|
3
|
+
|
4
|
+
module N65
|
5
|
+
|
6
|
+
####
|
7
|
+
## Represents a single 6502 Instruction
|
8
|
+
class Instruction
|
9
|
+
attr_reader :op, :arg, :mode, :hex, :description, :length, :cycle, :boundry_add, :flags, :address
|
10
|
+
|
11
|
+
## Custom Exceptions
|
12
|
+
class InvalidInstruction < StandardError; end
|
13
|
+
class UnresolvedSymbols < StandardError; end
|
14
|
+
class InvalidAddressingMode < StandardError; end
|
15
|
+
class AddressOutOfRange < StandardError; end
|
16
|
+
class ArgumentTooLarge < StandardError; end
|
17
|
+
|
18
|
+
## Include Regexes
|
19
|
+
include Regexes
|
20
|
+
|
21
|
+
AddressingModes = {
|
22
|
+
:relative => {
|
23
|
+
:example => 'B** my_label',
|
24
|
+
:display => '%s $%.4X',
|
25
|
+
:regex => /$^/i, # Will never match this one
|
26
|
+
:regex_label => /^#{Branches}\s+#{Sym}$/
|
27
|
+
},
|
28
|
+
|
29
|
+
:immediate => {
|
30
|
+
:example => 'AAA #$FF',
|
31
|
+
:display => '%s #$%.2X',
|
32
|
+
:regex => /^#{Mnemonic}\s+#{Immediate}$/,
|
33
|
+
:regex_label => /^#{Mnemonic}\s+#(<|>)#{Sym}$/
|
34
|
+
},
|
35
|
+
|
36
|
+
:implied => {
|
37
|
+
:example => 'AAA',
|
38
|
+
:display => '%s',
|
39
|
+
:regex => /^#{Mnemonic}$/
|
40
|
+
},
|
41
|
+
|
42
|
+
:zero_page => {
|
43
|
+
:example => 'AAA $FF',
|
44
|
+
:display => '%s $%.2X',
|
45
|
+
:regex => /^#{Mnemonic}\s+#{Num8}$/,
|
46
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}\s+zp$/
|
47
|
+
},
|
48
|
+
|
49
|
+
:zero_page_x => {
|
50
|
+
:example => 'AAA $FF, X',
|
51
|
+
:display => '%s $%.2X, X',
|
52
|
+
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{XReg}$/,
|
53
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}\s+zp$/
|
54
|
+
},
|
55
|
+
|
56
|
+
:zero_page_y => {
|
57
|
+
:example => 'AAA $FF, Y',
|
58
|
+
:display => '%s $%.2X, Y',
|
59
|
+
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
60
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg} zp$/
|
61
|
+
},
|
62
|
+
|
63
|
+
:absolute => {
|
64
|
+
:example => 'AAA $FFFF',
|
65
|
+
:display => '%s $%.4X',
|
66
|
+
:regex => /^#{Mnemonic}\s+#{Num16}$/,
|
67
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}$/
|
68
|
+
},
|
69
|
+
|
70
|
+
:absolute_x => {
|
71
|
+
:example => 'AAA $FFFF, X',
|
72
|
+
:display => '%s $%.4X, X',
|
73
|
+
:regex => /^#{Mnemonic}\s+#{Num16}\s?,\s?#{XReg}$/,
|
74
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/
|
75
|
+
},
|
76
|
+
|
77
|
+
:absolute_y => {
|
78
|
+
:example => 'AAA $FFFF, Y',
|
79
|
+
:display => '%s $%.4X, Y',
|
80
|
+
:regex => /^#{Mnemonic}\s+#{Num16}\s?,\s?#{YReg}$/,
|
81
|
+
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/
|
82
|
+
},
|
83
|
+
|
84
|
+
:indirect => {
|
85
|
+
:example => 'AAA ($FFFF)',
|
86
|
+
:display => '%s ($%.4X)',
|
87
|
+
:regex => /^#{Mnemonic}\s+\(#{Num16}\)$/,
|
88
|
+
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)$/
|
89
|
+
},
|
90
|
+
|
91
|
+
:indirect_x => {
|
92
|
+
:example => 'AAA ($FF, X)',
|
93
|
+
:display => '%s ($%.2X, X)',
|
94
|
+
:regex => /^#{Mnemonic}\s+\(#{Num8}\s?,\s?#{XReg}\)$/,
|
95
|
+
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/
|
96
|
+
},
|
97
|
+
|
98
|
+
:indirect_y => {
|
99
|
+
:example => 'AAA ($FF), Y)',
|
100
|
+
:display => '%s ($%.2X), Y',
|
101
|
+
:regex => /^#{Mnemonic}\s+\(#{Num8}\)\s?,\s?#{YReg}$/,
|
102
|
+
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
####
|
107
|
+
## Parse one line of assembly, returns nil if the line
|
108
|
+
## is ultimately empty of asm instructions
|
109
|
+
## Raises SyntaxError if the line is malformed in some way
|
110
|
+
def self.parse(line)
|
111
|
+
|
112
|
+
## Try to parse this line in each addressing mode
|
113
|
+
AddressingModes.each do |mode, parse_info|
|
114
|
+
|
115
|
+
## We have regexes that match each addressing mode
|
116
|
+
match_data = parse_info[:regex].match(line)
|
117
|
+
|
118
|
+
unless match_data.nil?
|
119
|
+
## We must have a straight instruction without symbols, construct
|
120
|
+
## an Instruction from the match_data, and return it
|
121
|
+
_, op, arg_hex, arg_bin = match_data.to_a
|
122
|
+
|
123
|
+
## Until I think of something better, it seems that the union regex
|
124
|
+
## puts a hexidecimal argument in one capture, and a binary in the next
|
125
|
+
## This is annoying, but still not as annoying as using Treetop to parse
|
126
|
+
if arg_hex != nil
|
127
|
+
return Instruction.new(op, arg_hex.to_i(16), mode)
|
128
|
+
elsif arg_bin != nil
|
129
|
+
return Instruction.new(op, arg_bin.to_i(2), mode)
|
130
|
+
else
|
131
|
+
return Instruction.new(op, nil, mode)
|
132
|
+
end
|
133
|
+
|
134
|
+
else
|
135
|
+
## Can this addressing mode even use labels?
|
136
|
+
unless parse_info[:regex_label].nil?
|
137
|
+
|
138
|
+
## See if it does in fact have a symbolic argument
|
139
|
+
match_data = parse_info[:regex_label].match(line)
|
140
|
+
|
141
|
+
unless match_data.nil?
|
142
|
+
## We have found an assembly instruction containing a symbolic
|
143
|
+
## argument. We can resolve this symbol later by looking at the
|
144
|
+
## symbol table in the #exec method
|
145
|
+
match_array = match_data.to_a
|
146
|
+
|
147
|
+
## If we have a 4 element array, this means we matched something
|
148
|
+
## like LDA #<label, which is a legal immediate one byte value
|
149
|
+
## by taking the msb. We need to make that distinction in the
|
150
|
+
## Instruction, by passing an extra argument
|
151
|
+
if match_array.size == 4
|
152
|
+
_, op, byte_selector, arg = match_array
|
153
|
+
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
154
|
+
else
|
155
|
+
_, op, arg = match_array
|
156
|
+
return Instruction.new(op, arg, mode)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
## We just don't recognize this line of asm, it must be a Syntax Error
|
164
|
+
fail(SyntaxError, line)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
####
|
169
|
+
## Create an instruction. Having the instruction op a downcased symbol is nice
|
170
|
+
## because that can later be used to index into our opcodes hash in OpCodes
|
171
|
+
## OpCodes contains the definitions of each OpCode
|
172
|
+
def initialize(op, arg, mode, byte_selector = nil)
|
173
|
+
|
174
|
+
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
175
|
+
@byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym
|
176
|
+
fail(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
|
177
|
+
|
178
|
+
@op = op.downcase.to_sym
|
179
|
+
definition = OpCodes[@op]
|
180
|
+
fail(InvalidInstruction, op) if definition.nil?
|
181
|
+
|
182
|
+
@arg = arg
|
183
|
+
|
184
|
+
## Be sure the mode is an actually supported mode.
|
185
|
+
@mode = mode.to_sym
|
186
|
+
fail(InvalidAddressingMode, mode) unless AddressingModes.has_key?(@mode)
|
187
|
+
|
188
|
+
if definition[@mode].nil?
|
189
|
+
fail(InvalidInstruction, "#{op} cannot be used in #{mode} mode")
|
190
|
+
end
|
191
|
+
|
192
|
+
@description, @flags = definition.values_at(:description, :flags)
|
193
|
+
@hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add)
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
####
|
198
|
+
## Return if this instruction is a zero page instruction
|
199
|
+
def zero_page_instruction?
|
200
|
+
[:zero_page, :zero_page_x, :zero_page_y].include?(@mode)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
####
|
205
|
+
## Execute writes the emitted bytes to virtual memory, and updates PC
|
206
|
+
## If there is a symbolic argument, we can try to resolve it now, or
|
207
|
+
## promise to resolve it later.
|
208
|
+
def exec(assembler)
|
209
|
+
|
210
|
+
promise = assembler.with_saved_state do |saved_assembler|
|
211
|
+
@arg = saved_assembler.symbol_table.resolve_symbol(@arg)
|
212
|
+
|
213
|
+
## If the instruction uses a byte selector, we need to apply that.
|
214
|
+
@arg = apply_byte_selector(@byte_selector, @arg)
|
215
|
+
|
216
|
+
## If the instruction is relative we need to work out how far away it is
|
217
|
+
@arg = @arg - saved_assembler.program_counter - 2 if @mode == :relative
|
218
|
+
|
219
|
+
saved_assembler.write_memory(emit_bytes)
|
220
|
+
end
|
221
|
+
|
222
|
+
case @arg
|
223
|
+
when Fixnum, NilClass
|
224
|
+
assembler.write_memory(emit_bytes)
|
225
|
+
when String
|
226
|
+
begin
|
227
|
+
## This is a bug, I don't believe it will ever get here.
|
228
|
+
## I think it always resolves every symbol later.
|
229
|
+
promise.call
|
230
|
+
rescue SymbolTable::UndefinedSymbol
|
231
|
+
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
232
|
+
## I still have to write a placeholder instruction of the right
|
233
|
+
## length. The promise will come back and resolve the address.
|
234
|
+
assembler.write_memory(placeholder)
|
235
|
+
return promise
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
####
|
242
|
+
## Apply a byte selector to an argument
|
243
|
+
def apply_byte_selector(byte_selector, value)
|
244
|
+
return value if byte_selector.nil?
|
245
|
+
case byte_selector
|
246
|
+
when :>
|
247
|
+
high_byte(value)
|
248
|
+
when :<
|
249
|
+
low_byte(value)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
####
|
255
|
+
## Emit bytes from asm structure
|
256
|
+
def emit_bytes
|
257
|
+
case @length
|
258
|
+
when 1
|
259
|
+
[@hex]
|
260
|
+
when 2
|
261
|
+
if zero_page_instruction? && @arg < 0 || @arg > 0xff
|
262
|
+
fail(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed")
|
263
|
+
end
|
264
|
+
[@hex, @arg]
|
265
|
+
when 3
|
266
|
+
[@hex] + break_16(@arg)
|
267
|
+
else
|
268
|
+
fail("Can't handle instructions > 3 bytes")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
####
|
274
|
+
## Pretty Print
|
275
|
+
def to_s
|
276
|
+
#display = AddressingModes[@mode][:display]
|
277
|
+
#if @arg.kind_of?(String)
|
278
|
+
#sprintf("#{display} (#{@mode}, #{@arg})", @op, 0x0)
|
279
|
+
#else
|
280
|
+
#sprintf("#{display} (#{@mode})", @op, @arg)
|
281
|
+
#end
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
private
|
286
|
+
####
|
287
|
+
## Break an integer into two 8-bit parts
|
288
|
+
def break_16(integer)
|
289
|
+
[integer & 0x00FF, (integer & 0xFF00) >> 8]
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
####
|
294
|
+
## Take the high byte of a 16-bit integer
|
295
|
+
def high_byte(word)
|
296
|
+
(word & 0xFF00) >> 8
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
####
|
301
|
+
## Take the low byte of a 16-bit integer
|
302
|
+
def low_byte(word)
|
303
|
+
word & 0xFF
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|