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