n65 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +340 -0
  5. data/README.md +126 -0
  6. data/Rakefile +2 -0
  7. data/bin/n65 +11 -0
  8. data/data/opcodes.yaml +1030 -0
  9. data/examples/beep.asm +24 -0
  10. data/examples/mario2.asm +260 -0
  11. data/examples/mario2.char +0 -0
  12. data/examples/music_driver.asm +202 -0
  13. data/examples/noise.asm +93 -0
  14. data/examples/pulse_chord.asm +213 -0
  15. data/images/assembler_demo.png +0 -0
  16. data/lib/n65.rb +243 -0
  17. data/lib/n65/directives/ascii.rb +42 -0
  18. data/lib/n65/directives/bytes.rb +102 -0
  19. data/lib/n65/directives/dw.rb +86 -0
  20. data/lib/n65/directives/enter_scope.rb +55 -0
  21. data/lib/n65/directives/exit_scope.rb +35 -0
  22. data/lib/n65/directives/inc.rb +67 -0
  23. data/lib/n65/directives/incbin.rb +51 -0
  24. data/lib/n65/directives/ines_header.rb +53 -0
  25. data/lib/n65/directives/label.rb +46 -0
  26. data/lib/n65/directives/org.rb +47 -0
  27. data/lib/n65/directives/segment.rb +45 -0
  28. data/lib/n65/directives/space.rb +46 -0
  29. data/lib/n65/front_end.rb +90 -0
  30. data/lib/n65/instruction.rb +308 -0
  31. data/lib/n65/instruction_base.rb +29 -0
  32. data/lib/n65/memory_space.rb +150 -0
  33. data/lib/n65/opcodes.rb +9 -0
  34. data/lib/n65/parser.rb +85 -0
  35. data/lib/n65/regexes.rb +33 -0
  36. data/lib/n65/symbol_table.rb +198 -0
  37. data/lib/n65/version.rb +3 -0
  38. data/n65.gemspec +23 -0
  39. data/nes_lib/nes.sym +105 -0
  40. data/test/test_memory_space.rb +82 -0
  41. data/test/test_symbol_table.rb +238 -0
  42. data/utils/midi/Makefile +3 -0
  43. data/utils/midi/c_scale.mid +0 -0
  44. data/utils/midi/convert +0 -0
  45. data/utils/midi/guitar.mid +0 -0
  46. data/utils/midi/include/event.h +93 -0
  47. data/utils/midi/include/file.h +57 -0
  48. data/utils/midi/include/helpers.h +14 -0
  49. data/utils/midi/include/track.h +45 -0
  50. data/utils/midi/lil_melody.mid +0 -0
  51. data/utils/midi/mi_feabhra.mid +0 -0
  52. data/utils/midi/midi_to_nes.rb +204 -0
  53. data/utils/midi/source/convert.cpp +16 -0
  54. data/utils/midi/source/event.cpp +96 -0
  55. data/utils/midi/source/file.cpp +37 -0
  56. data/utils/midi/source/helpers.cpp +46 -0
  57. data/utils/midi/source/track.cpp +37 -0
  58. data/utils/opcode_table_to_yaml.rb +91 -0
  59. 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