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