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,42 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive to include bytes
|
8
|
+
class ASCII < InstructionBase
|
9
|
+
|
10
|
+
|
11
|
+
####
|
12
|
+
## Try to parse an incbin directive
|
13
|
+
def self.parse(line)
|
14
|
+
match_data = line.match(/^\.ascii\s+"([^"]+)"$/)
|
15
|
+
return nil if match_data.nil?
|
16
|
+
ASCII.new(match_data[1])
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
####
|
21
|
+
## Initialize with filename
|
22
|
+
def initialize(string)
|
23
|
+
@string = string
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
####
|
28
|
+
## Execute on the assembler
|
29
|
+
def exec(assembler)
|
30
|
+
assembler.write_memory(@string.bytes)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
####
|
35
|
+
## Display
|
36
|
+
def to_s
|
37
|
+
".ascii \"#{@string}\""
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
require_relative '../regexes.rb'
|
3
|
+
|
4
|
+
module N65
|
5
|
+
|
6
|
+
|
7
|
+
####
|
8
|
+
## This directive to include bytes
|
9
|
+
class Bytes < InstructionBase
|
10
|
+
|
11
|
+
#### Custom Exceptions
|
12
|
+
class InvalidByteValue < StandardError; end
|
13
|
+
|
14
|
+
|
15
|
+
####
|
16
|
+
## Try to parse an incbin directive
|
17
|
+
def self.parse(line)
|
18
|
+
match_data = line.match(/^\.bytes\s+(.+)$/)
|
19
|
+
return nil if match_data.nil?
|
20
|
+
|
21
|
+
bytes_array = match_data[1].split(',').map do |byte_string|
|
22
|
+
|
23
|
+
## Does byte_string represent a numeric literal, or is it a symbol?
|
24
|
+
## In numeric captures $2 is always binary, $1 is always hex
|
25
|
+
|
26
|
+
case byte_string.strip
|
27
|
+
when Regexp.new("^#{Regexes::Num8}$")
|
28
|
+
$2.nil? ? $1.to_i(16) : $2.to_i(2)
|
29
|
+
|
30
|
+
when Regexp.new("^#{Regexes::Num16}$")
|
31
|
+
value = $2.nil? ? $1.to_i(16) : $2.to_i(2)
|
32
|
+
|
33
|
+
## Break value up into two bytes
|
34
|
+
high = (0xff00 & value) >> 8
|
35
|
+
low = (0x00ff & value)
|
36
|
+
[low, high]
|
37
|
+
when Regexp.new("^#{Regexes::Sym}$")
|
38
|
+
$1
|
39
|
+
else
|
40
|
+
fail(InvalidByteValue, byte_string)
|
41
|
+
end
|
42
|
+
end.flatten
|
43
|
+
|
44
|
+
Bytes.new(bytes_array)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
####
|
49
|
+
## Initialize with filename
|
50
|
+
def initialize(bytes_array)
|
51
|
+
@bytes_array = bytes_array
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
####
|
56
|
+
## Execute on the assembler
|
57
|
+
def exec(assembler)
|
58
|
+
|
59
|
+
promise = assembler.with_saved_state do |saved_assembler|
|
60
|
+
@bytes_array.map! do |byte|
|
61
|
+
case byte
|
62
|
+
when Fixnum
|
63
|
+
byte
|
64
|
+
when String
|
65
|
+
value = saved_assembler.symbol_table.resolve_symbol(byte)
|
66
|
+
else
|
67
|
+
fail(InvalidByteValue, byte)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
saved_assembler.write_memory(@bytes_array)
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
promise.call
|
75
|
+
rescue SymbolTable::UndefinedSymbol
|
76
|
+
## Write the bytes but assume a zero page address for all symbols
|
77
|
+
## And just write 0xDE for a placeholder
|
78
|
+
placeholder_bytes = @bytes_array.map do |byte|
|
79
|
+
case bytes
|
80
|
+
when Fixnum
|
81
|
+
byte
|
82
|
+
when String
|
83
|
+
0xDE
|
84
|
+
else
|
85
|
+
fail(InvalidByteValue, byte)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
assembler.write_memory(placeholder_bytes)
|
89
|
+
return promise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
####
|
95
|
+
## Display, I don't want to write all these out
|
96
|
+
def to_s
|
97
|
+
".bytes (#{@bytes_array.length})"
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive instruction can include a binary file
|
8
|
+
class DW < InstructionBase
|
9
|
+
|
10
|
+
####
|
11
|
+
## Try to parse a dw directive
|
12
|
+
def self.parse(line)
|
13
|
+
|
14
|
+
## Maybe it is a straight up bit of hex
|
15
|
+
match_data = line.match(/^\.dw\s+\$([0-9A-F]{1,4})$/)
|
16
|
+
unless match_data.nil?
|
17
|
+
word = match_data[1].to_i(16)
|
18
|
+
return DW.new(word)
|
19
|
+
end
|
20
|
+
|
21
|
+
## Or maybe it points to a symbol
|
22
|
+
match_data = line.match(/^\.dw\s+([A-Za-z_][A-Za-z0-9_\.]+)/)
|
23
|
+
unless match_data.nil?
|
24
|
+
symbol = match_data[1]
|
25
|
+
return DW.new(symbol)
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
####
|
32
|
+
## Initialize with filename
|
33
|
+
def initialize(value)
|
34
|
+
@value = value
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
####
|
39
|
+
## Execute on the assembler, now in this case value may
|
40
|
+
## be a symbol that needs to be resolved, if so we return
|
41
|
+
## a lambda which can be executed later, with the promise
|
42
|
+
## that that symbol will have then be defined
|
43
|
+
## This is a little complicated, I admit.
|
44
|
+
def exec(assembler)
|
45
|
+
|
46
|
+
promise = assembler.with_saved_state do |saved_assembler|
|
47
|
+
value = saved_assembler.symbol_table.resolve_symbol(@value)
|
48
|
+
bytes = [value & 0xFFFF].pack('S').bytes
|
49
|
+
saved_assembler.write_memory(bytes)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
## Try to execute it now, or setup the promise to return
|
54
|
+
case @value
|
55
|
+
when Fixnum
|
56
|
+
bytes = [@value & 0xFFFF].pack('S').bytes
|
57
|
+
assembler.write_memory(bytes)
|
58
|
+
when String
|
59
|
+
begin
|
60
|
+
promise.call
|
61
|
+
rescue SymbolTable::UndefinedSymbol
|
62
|
+
## Must still advance PC before returning promise, so we'll write
|
63
|
+
## a place holder value of 0xDEAD
|
64
|
+
assembler.write_memory([0xDE, 0xAD])
|
65
|
+
return promise
|
66
|
+
end
|
67
|
+
else
|
68
|
+
fail("Uknown argument in .dw directive")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
####
|
74
|
+
## Display
|
75
|
+
def to_s
|
76
|
+
case @value
|
77
|
+
when String
|
78
|
+
".dw #{@value}"
|
79
|
+
when Fixnum
|
80
|
+
".dw $%4.X" % @value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive to include bytes
|
8
|
+
class EnterScope < InstructionBase
|
9
|
+
|
10
|
+
####
|
11
|
+
## Try to parse an incbin directive
|
12
|
+
def self.parse(line)
|
13
|
+
## Anonymous scope
|
14
|
+
match_data = line.match(/^\.scope$/)
|
15
|
+
unless match_data.nil?
|
16
|
+
return EnterScope.new
|
17
|
+
end
|
18
|
+
|
19
|
+
## Named scope
|
20
|
+
match_data = line.match(/^\.scope\s+([a-zA-Z][a-zA-Z0-9_]+)$/)
|
21
|
+
return nil if match_data.nil?
|
22
|
+
EnterScope.new(match_data[1])
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
####
|
27
|
+
## Initialize with filename
|
28
|
+
def initialize(name = nil)
|
29
|
+
@name = name
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
####
|
34
|
+
## Execute on the assembler, also create a symbol referring to
|
35
|
+
## the current pc which contains a hyphen, and is impossible for
|
36
|
+
## the user to create. This makes a scope simultaneously act as
|
37
|
+
## a label to the current PC. If someone tries to use a scope
|
38
|
+
## name as a label, it can return the address when the scope opened.
|
39
|
+
def exec(assembler)
|
40
|
+
assembler.symbol_table.enter_scope(@name)
|
41
|
+
unless @name.nil?
|
42
|
+
assembler.symbol_table.define_symbol("-#{@name}", assembler.program_counter)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
####
|
48
|
+
## Display
|
49
|
+
def to_s
|
50
|
+
".scope #{@name}"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive to include bytes
|
8
|
+
class ExitScope < InstructionBase
|
9
|
+
|
10
|
+
|
11
|
+
####
|
12
|
+
## Try to parse an incbin directive
|
13
|
+
def self.parse(line)
|
14
|
+
match_data = line.match(/^\.$/)
|
15
|
+
return nil if match_data.nil?
|
16
|
+
ExitScope.new
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
####
|
21
|
+
## Execute on the assembler
|
22
|
+
def exec(assembler)
|
23
|
+
assembler.symbol_table.exit_scope
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
####
|
28
|
+
## Display
|
29
|
+
def to_s
|
30
|
+
"."
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive instruction can include another asm file
|
8
|
+
class Inc < InstructionBase
|
9
|
+
|
10
|
+
#### System include directory
|
11
|
+
SystemInclude = File.dirname(__FILE__) + "/../../../nes_lib"
|
12
|
+
|
13
|
+
#### Custom Exceptions
|
14
|
+
class FileNotFound < StandardError; end
|
15
|
+
|
16
|
+
|
17
|
+
####
|
18
|
+
## Try to parse an incbin directive
|
19
|
+
def self.parse(line)
|
20
|
+
## Do We have a system directory include?
|
21
|
+
match_data = line.match(/^\.inc <([^>]+)>$/)
|
22
|
+
unless match_data.nil?
|
23
|
+
filename = File.join(SystemInclude, match_data[1])
|
24
|
+
return Inc.new(filename)
|
25
|
+
end
|
26
|
+
|
27
|
+
## Do We have a project relative directory include?
|
28
|
+
match_data = line.match(/^\.inc "([^"]+)"$/)
|
29
|
+
unless match_data.nil?
|
30
|
+
filename = File.join(Dir.pwd, match_data[1])
|
31
|
+
return Inc.new(filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
## Nope, not an inc directive
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
####
|
40
|
+
## Initialize with filename
|
41
|
+
def initialize(filename)
|
42
|
+
@filename = filename
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
####
|
47
|
+
## Execute on the assembler
|
48
|
+
def exec(assembler)
|
49
|
+
unless File.exists?(@filename)
|
50
|
+
fail(FileNotFound, ".inc can't find #{@filename}")
|
51
|
+
end
|
52
|
+
File.read(@filename).split(/\n/).each do |line|
|
53
|
+
assembler.assemble_one_line(line)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
####
|
59
|
+
## Display
|
60
|
+
def to_s
|
61
|
+
".inc \"#{@filename}\""
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../instruction_base'
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
|
6
|
+
####
|
7
|
+
## This directive instruction can include a binary file
|
8
|
+
class IncBin < InstructionBase
|
9
|
+
|
10
|
+
#### Custom Exceptions
|
11
|
+
class FileNotFound < StandardError; end
|
12
|
+
|
13
|
+
|
14
|
+
####
|
15
|
+
## Try to parse an incbin directive
|
16
|
+
def self.parse(line)
|
17
|
+
match_data = line.match(/^\.incbin "([^"]+)"$/)
|
18
|
+
return nil if match_data.nil?
|
19
|
+
filename = match_data[1]
|
20
|
+
IncBin.new(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
####
|
25
|
+
## Initialize with filename
|
26
|
+
def initialize(filename)
|
27
|
+
@filename = filename
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
####
|
32
|
+
## Execute on the assembler
|
33
|
+
def exec(assembler)
|
34
|
+
unless File.exists?(@filename)
|
35
|
+
fail(FileNotFound, ".incbin can't find #{@filename}")
|
36
|
+
end
|
37
|
+
data = File.read(@filename).unpack('C*')
|
38
|
+
assembler.write_memory(data)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
####
|
43
|
+
## Display
|
44
|
+
def to_s
|
45
|
+
".incbin \"#{@filename}\""
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative '../instruction_base'
|
3
|
+
|
4
|
+
module N65
|
5
|
+
|
6
|
+
|
7
|
+
####
|
8
|
+
## This directive instruction can setup an ines header
|
9
|
+
class INESHeader < InstructionBase
|
10
|
+
attr_reader :prog, :char, :mapper, :mirror
|
11
|
+
|
12
|
+
|
13
|
+
####
|
14
|
+
## Implementation of the parser for this directive
|
15
|
+
def self.parse(line)
|
16
|
+
match_data = line.match(/^\.ines (.+)$/)
|
17
|
+
return nil if match_data.nil?
|
18
|
+
|
19
|
+
header = JSON.parse(match_data[1])
|
20
|
+
INESHeader.new(header['prog'], header['char'], header['mapper'], header['mirror'])
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
####
|
25
|
+
## Construct a header
|
26
|
+
def initialize(prog, char, mapper, mirror)
|
27
|
+
@prog, @char, @mapper, @mirror = prog, char, mapper, mirror
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
####
|
32
|
+
## Exec function the assembler will call
|
33
|
+
def exec(assembler)
|
34
|
+
assembler.set_ines_header(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
####
|
39
|
+
## Emit the header bytes, this is not exactly right, but it works for now.
|
40
|
+
def emit_bytes
|
41
|
+
[0x4E, 0x45, 0x53, 0x1a, @prog, @char, @mapper, @mirror, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
####
|
46
|
+
## Display
|
47
|
+
def to_s
|
48
|
+
".ines {\"prog\": #{@prog}, \"char\": #{@char}, \"mapper\": #{@mapper}, \"mirror\": #{@mirror}}"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|