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,29 @@
|
|
1
|
+
|
2
|
+
module N65
|
3
|
+
|
4
|
+
class InstructionBase
|
5
|
+
|
6
|
+
|
7
|
+
#####
|
8
|
+
## Sort of a "pure virtual" class method, not really tho.
|
9
|
+
def self.parse(line)
|
10
|
+
fail(NotImplementedError, "#{self.class.name} must implement self.parse")
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
####
|
15
|
+
## Does this instruction have unresolved symbols?
|
16
|
+
def unresolved_symbols?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
####
|
22
|
+
## Another method subclasses will be expected to implement
|
23
|
+
def exec(assembler)
|
24
|
+
fail(NotImplementedError, "#{self.class.name} must implement exec")
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
module N65
|
3
|
+
|
4
|
+
####
|
5
|
+
## Let's use this to simulate a virtual address space
|
6
|
+
## Either a 16kb prog rom or 8kb char rom space.
|
7
|
+
## It can also be used to create arbitrary sized spaces
|
8
|
+
## for example to build the final binary ROM in.
|
9
|
+
class MemorySpace
|
10
|
+
|
11
|
+
#### Custom exceptions
|
12
|
+
class AccessOutsideProgRom < StandardError; end
|
13
|
+
class AccessOutsideCharRom < StandardError; end
|
14
|
+
class AccessOutOfBounds < StandardError; end
|
15
|
+
|
16
|
+
|
17
|
+
#### Some constants, the size of PROG and CHAR ROM
|
18
|
+
BankSizes = {
|
19
|
+
:ines => 0x10, # 16b
|
20
|
+
:prog => 0x4000, # 16kb
|
21
|
+
:char => 0x2000, # 8kb
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
####
|
26
|
+
## Create a new PROG ROM
|
27
|
+
def self.create_prog_rom
|
28
|
+
self.create_bank(:prog)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
####
|
33
|
+
## Create a new CHAR ROM
|
34
|
+
def self.create_char_rom
|
35
|
+
self.create_bank(:char)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
####
|
40
|
+
## Create a new bank
|
41
|
+
def self.create_bank(type)
|
42
|
+
self.new(BankSizes[type], type)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
####
|
47
|
+
## Create a completely zeroed memory space
|
48
|
+
def initialize(size, type)
|
49
|
+
@type = type
|
50
|
+
@memory = Array.new(size, 0x0)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
####
|
55
|
+
## Normalized read from memory
|
56
|
+
def read(address, count)
|
57
|
+
from_normalized = normalize_address(address)
|
58
|
+
to_normalized = normalize_address(address + (count - 1))
|
59
|
+
ensure_addresses_in_bounds!([from_normalized, to_normalized])
|
60
|
+
|
61
|
+
@memory[from_normalized..to_normalized]
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
####
|
66
|
+
## Normalized write to memory
|
67
|
+
def write(address, bytes)
|
68
|
+
from_normalized = normalize_address(address)
|
69
|
+
to_normalized = normalize_address(address + bytes.size - 1)
|
70
|
+
ensure_addresses_in_bounds!([from_normalized, to_normalized])
|
71
|
+
|
72
|
+
bytes.each_with_index do |byte, index|
|
73
|
+
@memory[from_normalized + index] = byte
|
74
|
+
end
|
75
|
+
bytes.size
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
####
|
80
|
+
## Return the memory as an array of bytes to write to disk
|
81
|
+
def emit_bytes
|
82
|
+
@memory
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
####
|
89
|
+
## Are the given addresses in bounds? If not blow up.
|
90
|
+
def ensure_addresses_in_bounds!(addresses)
|
91
|
+
addresses.each do |address|
|
92
|
+
unless address >= 0 && address < @memory.size
|
93
|
+
fail(AccessOutOfBounds, sprintf("Address $%.4X is out of bounds in this #{@type} bank"))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
####
|
101
|
+
## Since prog rom can be loaded at either 0x8000 or 0xC000
|
102
|
+
## We should normalize the addresses to fit properly into
|
103
|
+
## these banks, basically it acts like it is mirroring addresses
|
104
|
+
## in those segments. Char rom doesn't need this. This will also
|
105
|
+
## fail if you are accessing outside of the address space.
|
106
|
+
def normalize_address(address)
|
107
|
+
case @type
|
108
|
+
when :prog
|
109
|
+
if address_inside_prog_rom1?(address)
|
110
|
+
return address - 0x8000
|
111
|
+
end
|
112
|
+
if address_inside_prog_rom2?(address)
|
113
|
+
return address - 0xC000
|
114
|
+
end
|
115
|
+
fail(AccessOutsideProgRom, sprintf("Address $%.4X is outside PROG ROM", address))
|
116
|
+
when :char
|
117
|
+
unless address_inside_char_rom?(address)
|
118
|
+
fail(AccessOutsideCharRom, sprintf("Address $%.4X is outside CHAR ROM", address))
|
119
|
+
end
|
120
|
+
address
|
121
|
+
else
|
122
|
+
address
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
####
|
128
|
+
## Is this address inside the prog rom 1 area?
|
129
|
+
def address_inside_prog_rom1?(address)
|
130
|
+
address >= 0x8000 && address < 0xC000
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
####
|
135
|
+
## Is this address inside the prog rom 2 area?
|
136
|
+
def address_inside_prog_rom2?(address)
|
137
|
+
address >= 0xC000 && address <= 0xffff
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
####
|
142
|
+
## Is this address inside the char rom area?
|
143
|
+
def address_inside_char_rom?(address)
|
144
|
+
address >= 0x0000 && address <= 0x1fff
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
data/lib/n65/opcodes.rb
ADDED
data/lib/n65/parser.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
module N65
|
3
|
+
|
4
|
+
require_relative 'instruction'
|
5
|
+
require_relative 'directives/ines_header'
|
6
|
+
require_relative 'directives/org'
|
7
|
+
require_relative 'directives/segment'
|
8
|
+
require_relative 'directives/incbin'
|
9
|
+
require_relative 'directives/inc'
|
10
|
+
require_relative 'directives/dw'
|
11
|
+
require_relative 'directives/bytes'
|
12
|
+
require_relative 'directives/ascii'
|
13
|
+
require_relative 'directives/label'
|
14
|
+
require_relative 'directives/enter_scope'
|
15
|
+
require_relative 'directives/exit_scope'
|
16
|
+
require_relative 'directives/space'
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
####
|
21
|
+
## This class determines what sort of line of code we
|
22
|
+
## are dealing with, parses one line, and returns an
|
23
|
+
## object deriving from InstructionBase
|
24
|
+
class Parser
|
25
|
+
|
26
|
+
#### Custom Exceptions
|
27
|
+
class CannotParse < StandardError; end
|
28
|
+
|
29
|
+
|
30
|
+
Directives = [INESHeader, Org, Segment, IncBin, Inc, DW, Bytes, ASCII, EnterScope, ExitScope, Space]
|
31
|
+
|
32
|
+
####
|
33
|
+
## Parses a line of program source into an object
|
34
|
+
## deriving from base class InstructionBase
|
35
|
+
def self.parse(line)
|
36
|
+
sanitized = sanitize_line(line)
|
37
|
+
return nil if sanitized.empty?
|
38
|
+
|
39
|
+
## First check to see if we have a label.
|
40
|
+
label = Label.parse(sanitized)
|
41
|
+
unless label.nil?
|
42
|
+
return label
|
43
|
+
end
|
44
|
+
|
45
|
+
## Now check if we have a directive
|
46
|
+
directive = parse_directive(sanitized)
|
47
|
+
unless directive.nil?
|
48
|
+
return directive
|
49
|
+
end
|
50
|
+
|
51
|
+
## Now, surely it is an asm instruction?
|
52
|
+
instruction = Instruction.parse(sanitized)
|
53
|
+
unless instruction.nil?
|
54
|
+
return instruction
|
55
|
+
end
|
56
|
+
|
57
|
+
## Guess not, we have no idea
|
58
|
+
fail(CannotParse, sanitized)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
####
|
64
|
+
## Sanitize one line of program source
|
65
|
+
def self.sanitize_line(line)
|
66
|
+
code = line.split(';').first || ""
|
67
|
+
code.strip.chomp
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
####
|
72
|
+
## Try to Parse a directive
|
73
|
+
def self.parse_directive(line)
|
74
|
+
if line.start_with?('.')
|
75
|
+
Directives.each do |directive|
|
76
|
+
object = directive.parse(line)
|
77
|
+
return object unless object.nil?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/lib/n65/regexes.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module N65
|
4
|
+
|
5
|
+
####
|
6
|
+
## All the regexes used to parse in one module
|
7
|
+
module Regexes
|
8
|
+
## Mnemonics
|
9
|
+
Mnemonic = '([A-Za-z]{3})'
|
10
|
+
Branches = '(BPL|BMI|BVC|BVS|BCC|BCS|BNE|BEQ|bpl|bmi|bvc|bvs|bcc|bcs|bne|beq)'
|
11
|
+
|
12
|
+
## Numeric Literals
|
13
|
+
Hex8 = '\$([A-Fa-f0-9]{1,2})'
|
14
|
+
Hex16 = '\$([A-Fa-f0-9]{3,4})'
|
15
|
+
|
16
|
+
Bin8 = '%([01]{1,8})'
|
17
|
+
Bin16 = '%([01]{9,16})'
|
18
|
+
|
19
|
+
Num8 = Regexp.union(Regexp.new(Hex8), Regexp.new(Bin8)).to_s
|
20
|
+
Num16 = Regexp.union(Regexp.new(Hex16),Regexp.new(Bin16)).to_s
|
21
|
+
|
22
|
+
Immediate = "\##{Num8}"
|
23
|
+
|
24
|
+
## Symbols, must begin with a letter, and supports dot syntax
|
25
|
+
Sym = '([a-zA-Z][a-zA-Z\d_\.]*)'
|
26
|
+
|
27
|
+
|
28
|
+
## The X or Y register
|
29
|
+
XReg = '[Xx]'
|
30
|
+
YReg = '[Yy]'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
|
2
|
+
module N65
|
3
|
+
|
4
|
+
class SymbolTable
|
5
|
+
attr_accessor :scope_stack
|
6
|
+
|
7
|
+
##### Custom Exceptions
|
8
|
+
class InvalidScope < StandardError; end
|
9
|
+
class UndefinedSymbol < StandardError; end
|
10
|
+
class CantExitScope < StandardError; end
|
11
|
+
|
12
|
+
|
13
|
+
####
|
14
|
+
## Initialize a symbol table that begins in global scope
|
15
|
+
def initialize
|
16
|
+
@symbols = {
|
17
|
+
:global => {}
|
18
|
+
}
|
19
|
+
@anonymous_scope_number = 0
|
20
|
+
@scope_stack = [:global]
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
####
|
25
|
+
## Define a new scope, which can be anonymous or named
|
26
|
+
## and switch into that scope
|
27
|
+
def enter_scope(name = nil)
|
28
|
+
name = generate_name if name.nil?
|
29
|
+
name = name.to_sym
|
30
|
+
scope = current_scope
|
31
|
+
if scope.has_key?(name)
|
32
|
+
#path_string = generate_scope_path(path_ary)
|
33
|
+
fail(InvalidScope, "Scope: #{name} already exists")
|
34
|
+
end
|
35
|
+
scope[name] = {}
|
36
|
+
@scope_stack.push(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
####
|
41
|
+
## Exit the current scope
|
42
|
+
def exit_scope
|
43
|
+
if @scope_stack.size == 1
|
44
|
+
fail(CantExitScope, "You cannot exit global scope")
|
45
|
+
end
|
46
|
+
@scope_stack.pop
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
####
|
51
|
+
## Define a symbol in the current scope
|
52
|
+
def define_symbol(symbol, value)
|
53
|
+
scope = current_scope
|
54
|
+
scope[symbol.to_sym] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
=begin
|
59
|
+
####
|
60
|
+
## Resolve symbol to a value, for example:
|
61
|
+
## scope1.scope2.variable
|
62
|
+
## It is not nessessary to specify the root scope :global
|
63
|
+
## You can just address anything by name in the current scope
|
64
|
+
## To go backwards in scope you need to write the full path
|
65
|
+
## like global.sprite.x or whatever
|
66
|
+
def resolve_symbol_old(name)
|
67
|
+
|
68
|
+
value = if name.include?('.')
|
69
|
+
path_ary = name.split('.').map(&:to_sym)
|
70
|
+
symbol = path_ary.pop
|
71
|
+
path_ary.shift if path_ary.first == :global
|
72
|
+
scope = retreive_scope(path_ary)
|
73
|
+
## We also try to look up the address associated with the scope
|
74
|
+
root = "-#{symbol}".to_sym
|
75
|
+
v = scope[symbol]
|
76
|
+
v.kind_of?(Hash) ? v[root] : v
|
77
|
+
else
|
78
|
+
root = "-#{name}".to_sym
|
79
|
+
scope = current_scope
|
80
|
+
## We also try to look up the address associated with the scope
|
81
|
+
v = scope[name.to_sym] || scope[root]
|
82
|
+
v.kind_of?(Hash) ? v[root] : v
|
83
|
+
end
|
84
|
+
|
85
|
+
if value.nil?
|
86
|
+
fail(UndefinedSymbol, name)
|
87
|
+
end
|
88
|
+
value
|
89
|
+
end
|
90
|
+
=end
|
91
|
+
|
92
|
+
|
93
|
+
####
|
94
|
+
##
|
95
|
+
def resolve_symbol(name)
|
96
|
+
method = name.include?('.') ? :resolve_symbol_dot_syntax : :resolve_symbol_scoped
|
97
|
+
value = self.send(method, name)
|
98
|
+
|
99
|
+
fail(UndefinedSymbol, name) if value.nil?
|
100
|
+
value
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
####
|
105
|
+
## Resolve symbol by working backwards through each
|
106
|
+
## containing scope. Similarly named scopes shadow outer scopes
|
107
|
+
def resolve_symbol_scoped(name)
|
108
|
+
root = "-#{name}".to_sym
|
109
|
+
stack = @scope_stack.dup
|
110
|
+
loop do
|
111
|
+
scope = retreive_scope(stack)
|
112
|
+
|
113
|
+
## We see if there is a key either under this name, or root
|
114
|
+
v = scope[name.to_sym] || scope[root]
|
115
|
+
v = v.kind_of?(Hash) ? v[root] : v
|
116
|
+
|
117
|
+
return v unless v.nil?
|
118
|
+
|
119
|
+
## Pop the stack so we can decend to the parent scope, if any
|
120
|
+
stack.pop
|
121
|
+
return nil if stack.empty?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
####
|
127
|
+
## Dot syntax means to check an absolute path to the symbol
|
128
|
+
## :global is ignored if it is provided as part of the path
|
129
|
+
def resolve_symbol_dot_syntax(name)
|
130
|
+
path_ary = name.split('.').map(&:to_sym)
|
131
|
+
symbol = path_ary.pop
|
132
|
+
root = "-#{symbol}".to_sym
|
133
|
+
path_ary.shift if path_ary.first == :global
|
134
|
+
|
135
|
+
scope = retreive_scope(path_ary)
|
136
|
+
|
137
|
+
## We see if there is a key either under this name, or root
|
138
|
+
v = scope[symbol]
|
139
|
+
v.kind_of?(Hash) ? v[root] : v
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
####
|
144
|
+
## Export the symbol table as YAML
|
145
|
+
def export_to_yaml
|
146
|
+
@symbols.to_yaml.gsub(/(\d+)$/) do |match|
|
147
|
+
integer = match.to_i
|
148
|
+
sprintf("0x%.4X", integer)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
####
|
156
|
+
## A bit more clearly states to get the current scope
|
157
|
+
def current_scope
|
158
|
+
retreive_scope
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
####
|
163
|
+
## Retrieve a reference to a scope, current scope by default
|
164
|
+
def retreive_scope(path_ary = @scope_stack)
|
165
|
+
path_ary = path_ary.dup
|
166
|
+
path_ary.unshift(:global) unless path_ary.first == :global
|
167
|
+
|
168
|
+
path_ary.inject(@symbols) do |scope, path_component|
|
169
|
+
new_scope = scope[path_component.to_sym]
|
170
|
+
|
171
|
+
if new_scope.nil?
|
172
|
+
path_string = generate_scope_path(path_ary)
|
173
|
+
message = "Resolving scope: #{path_string} failed at #{path_component}"
|
174
|
+
fail(InvalidScope, message) if new_scope.nil?
|
175
|
+
end
|
176
|
+
|
177
|
+
new_scope
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
####
|
183
|
+
## Generate a scope path from an array
|
184
|
+
def generate_scope_path(path_ary)
|
185
|
+
path_ary.join('.')
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
####
|
190
|
+
## Generate an anonymous scope name
|
191
|
+
def generate_name
|
192
|
+
@anonymous_scope_number += 1
|
193
|
+
"anonymous_#{@anonymous_scope_number}"
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|