n65 0.5.0 → 1.0.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +125 -0
- data/Gemfile +3 -1
- data/README.md +3 -20
- data/Rakefile +15 -1
- data/bin/n65 +2 -0
- data/data/opcodes.yaml +39 -39
- data/everdrive_transfer/everdrive.rb +175 -0
- data/examples/pulse_chord.asm +1 -1
- data/examples/scales.asm +182 -0
- data/lib/n65/directives/ascii.rb +4 -19
- data/lib/n65/directives/bytes.rb +20 -35
- data/lib/n65/directives/dw.rb +22 -36
- data/lib/n65/directives/enter_scope.rb +14 -30
- data/lib/n65/directives/exit_scope.rb +7 -17
- data/lib/n65/directives/inc.rb +14 -30
- data/lib/n65/directives/incbin.rb +6 -24
- data/lib/n65/directives/ines_header.rb +66 -27
- data/lib/n65/directives/label.rb +8 -21
- data/lib/n65/directives/org.rb +9 -19
- data/lib/n65/directives/segment.rb +5 -19
- data/lib/n65/directives/space.rb +6 -18
- data/lib/n65/front_end.rb +36 -39
- data/lib/n65/instruction.rb +123 -159
- data/lib/n65/instruction_base.rb +6 -18
- data/lib/n65/memory_space.rb +51 -71
- data/lib/n65/opcodes.rb +3 -5
- data/lib/n65/parser.rb +20 -38
- data/lib/n65/regexes.rb +20 -21
- data/lib/n65/symbol_table.rb +58 -89
- data/lib/n65/version.rb +3 -1
- data/lib/n65.rb +120 -121
- data/n65.gemspec +17 -12
- data/nes_lib/nes.sym +2 -2
- data/spec/.rubocop.yml +4 -0
- data/spec/assembler_spec.rb +84 -0
- data/spec/lib/n65/memory_space_spec.rb +147 -0
- data/spec/lib/n65/symbol_table_spec.rb +291 -0
- data/utils/opcode_table_to_yaml.rb +65 -67
- data.tar.gz.sig +0 -0
- metadata +84 -41
- metadata.gz.sig +0 -0
- data/examples/music_driver.asm +0 -202
- data/test/test_memory_space.rb +0 -82
- data/test/test_symbol_table.rb +0 -238
- data/utils/midi/Makefile +0 -3
- 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 +0 -93
- data/utils/midi/include/file.h +0 -57
- data/utils/midi/include/helpers.h +0 -14
- data/utils/midi/include/track.h +0 -45
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +0 -204
- data/utils/midi/source/convert.cpp +0 -16
- data/utils/midi/source/event.cpp +0 -96
- data/utils/midi/source/file.cpp +0 -37
- data/utils/midi/source/helpers.cpp +0 -46
- data/utils/midi/source/track.cpp +0 -37
data/lib/n65/parser.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module N65
|
|
3
|
-
|
|
4
4
|
require_relative 'instruction'
|
|
5
5
|
require_relative 'directives/ines_header'
|
|
6
6
|
require_relative 'directives/org'
|
|
@@ -15,71 +15,53 @@ module N65
|
|
|
15
15
|
require_relative 'directives/exit_scope'
|
|
16
16
|
require_relative 'directives/space'
|
|
17
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
|
|
18
|
+
# This class determines what sort of line of code we
|
|
19
|
+
# are dealing with, parses one line, and returns an
|
|
20
|
+
# object deriving from InstructionBase
|
|
24
21
|
class Parser
|
|
25
|
-
|
|
26
|
-
#### Custom Exceptions
|
|
27
22
|
class CannotParse < StandardError; end
|
|
28
23
|
|
|
24
|
+
DIRECTIVES = [INESHeader, Org, Segment, IncBin, Inc, DW, Bytes, ASCII, EnterScope, ExitScope, Space].freeze
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
####
|
|
33
|
-
## Parses a line of program source into an object
|
|
34
|
-
## deriving from base class InstructionBase
|
|
26
|
+
# Parses a line of program source into an object
|
|
27
|
+
# deriving from base class InstructionBase
|
|
35
28
|
def self.parse(line)
|
|
36
29
|
sanitized = sanitize_line(line)
|
|
37
30
|
return nil if sanitized.empty?
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
# First check to see if we have a label.
|
|
40
33
|
label = Label.parse(sanitized)
|
|
41
|
-
unless label.nil?
|
|
42
|
-
return label
|
|
43
|
-
end
|
|
34
|
+
return label unless label.nil?
|
|
44
35
|
|
|
45
|
-
|
|
36
|
+
# Now check if we have a directive
|
|
46
37
|
directive = parse_directive(sanitized)
|
|
47
|
-
unless directive.nil?
|
|
48
|
-
return directive
|
|
49
|
-
end
|
|
38
|
+
return directive unless directive.nil?
|
|
50
39
|
|
|
51
|
-
|
|
40
|
+
# Now, surely it is an asm instruction?
|
|
52
41
|
instruction = Instruction.parse(sanitized)
|
|
53
|
-
unless instruction.nil?
|
|
54
|
-
return instruction
|
|
55
|
-
end
|
|
42
|
+
return instruction unless instruction.nil?
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
# Guess not, we have no idea
|
|
45
|
+
raise(CannotParse, sanitized)
|
|
59
46
|
end
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
####
|
|
64
|
-
## Sanitize one line of program source
|
|
48
|
+
# Sanitize one line of program source
|
|
65
49
|
def self.sanitize_line(line)
|
|
66
|
-
code = line.split(';').first ||
|
|
50
|
+
code = line.split(';').first || ''
|
|
67
51
|
code.strip.chomp
|
|
68
52
|
end
|
|
53
|
+
private_class_method :sanitize_line
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
####
|
|
72
55
|
## Try to Parse a directive
|
|
73
56
|
def self.parse_directive(line)
|
|
74
57
|
if line.start_with?('.')
|
|
75
|
-
|
|
58
|
+
DIRECTIVES.each do |directive|
|
|
76
59
|
object = directive.parse(line)
|
|
77
60
|
return object unless object.nil?
|
|
78
61
|
end
|
|
79
62
|
end
|
|
80
63
|
nil
|
|
81
64
|
end
|
|
82
|
-
|
|
65
|
+
private_class_method :parse_directive
|
|
83
66
|
end
|
|
84
|
-
|
|
85
67
|
end
|
data/lib/n65/regexes.rb
CHANGED
|
@@ -1,33 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module N65
|
|
4
|
-
|
|
5
|
-
####
|
|
6
|
-
## All the regexes used to parse in one module
|
|
4
|
+
# All the regexes used to parse in one module
|
|
7
5
|
module Regexes
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
# rubocop:disable Naming/ConstantName
|
|
7
|
+
# Mnemonics
|
|
8
|
+
Mnemonic = '([A-Za-z]{3})'
|
|
9
|
+
Branches = '(BPL|BMI|BVC|BVS|BCC|BCS|BNE|BEQ|bpl|bmi|bvc|bvs|bcc|bcs|bne|beq)'
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
Hex8
|
|
14
|
-
Hex16
|
|
11
|
+
# Numeric Literals
|
|
12
|
+
Hex8 = '\$([A-Fa-f0-9]{1,2})'
|
|
13
|
+
Hex16 = '\$([A-Fa-f0-9]{3,4})'
|
|
15
14
|
|
|
16
|
-
Bin8
|
|
17
|
-
Bin16
|
|
15
|
+
Bin8 = '%([01]{1,8})'
|
|
16
|
+
Bin16 = '%([01]{9,16})'
|
|
18
17
|
|
|
19
|
-
Num8
|
|
20
|
-
Num16
|
|
18
|
+
Num8 = Regexp.union(Regexp.new(Hex8), Regexp.new(Bin8)).to_s
|
|
19
|
+
Num16 = Regexp.union(Regexp.new(Hex16), Regexp.new(Bin16)).to_s
|
|
21
20
|
|
|
22
|
-
Immediate
|
|
21
|
+
Immediate = "\##{Num8}"
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
Sym
|
|
23
|
+
# Symbols, must begin with a letter, and supports dot syntax
|
|
24
|
+
Sym = '([a-zA-Z][a-zA-Z\d_\.]*(?:[\+\-\*\/]\d+)*)'
|
|
26
25
|
|
|
26
|
+
# The X or Y register
|
|
27
|
+
XReg = '[Xx]'
|
|
28
|
+
YReg = '[Yy]'
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
XReg = '[Xx]'
|
|
30
|
-
YReg = '[Yy]'
|
|
30
|
+
# rubocop:enable Naming/ConstantName
|
|
31
31
|
end
|
|
32
|
-
|
|
33
32
|
end
|
data/lib/n65/symbol_table.rb
CHANGED
|
@@ -1,131 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
|
|
2
3
|
module N65
|
|
3
|
-
|
|
4
4
|
class SymbolTable
|
|
5
5
|
attr_accessor :scope_stack
|
|
6
6
|
|
|
7
|
-
##### Custom Exceptions
|
|
8
7
|
class InvalidScope < StandardError; end
|
|
9
8
|
class UndefinedSymbol < StandardError; end
|
|
10
9
|
class CantExitScope < StandardError; end
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
####
|
|
14
|
-
## Initialize a symbol table that begins in global scope
|
|
11
|
+
# Initialize a symbol table that begins in global scope
|
|
15
12
|
def initialize
|
|
16
13
|
@symbols = {
|
|
17
|
-
:
|
|
14
|
+
global: {}
|
|
18
15
|
}
|
|
19
16
|
@anonymous_scope_number = 0
|
|
20
17
|
@scope_stack = [:global]
|
|
18
|
+
@subroutine_cycles = {}
|
|
21
19
|
end
|
|
22
20
|
|
|
21
|
+
# Add a running cycle count to current top level scopes (ie subroutines)
|
|
22
|
+
def add_cycles(cycles)
|
|
23
|
+
cycles ||= 0
|
|
24
|
+
top_level_subroutine = @scope_stack[1]
|
|
25
|
+
return if top_level_subroutine.nil?
|
|
26
|
+
|
|
27
|
+
@subroutine_cycles[top_level_subroutine] ||= 0
|
|
28
|
+
@subroutine_cycles[top_level_subroutine] += cycles
|
|
29
|
+
end
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## and switch into that scope
|
|
31
|
+
# Define a new scope, which can be anonymous or named
|
|
32
|
+
# and switch into that scope
|
|
27
33
|
def enter_scope(name = nil)
|
|
28
|
-
name = generate_name if name.nil?
|
|
34
|
+
name = generate_name if name.nil?
|
|
29
35
|
name = name.to_sym
|
|
30
36
|
scope = current_scope
|
|
31
|
-
if scope.
|
|
32
|
-
|
|
33
|
-
fail(InvalidScope, "Scope: #{name} already exists")
|
|
34
|
-
end
|
|
37
|
+
raise(InvalidScope, "Scope: #{name} already exists") if scope.key?(name)
|
|
38
|
+
|
|
35
39
|
scope[name] = {}
|
|
36
40
|
@scope_stack.push(name)
|
|
37
41
|
end
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
####
|
|
41
|
-
## Exit the current scope
|
|
43
|
+
# Exit the current scope
|
|
42
44
|
def exit_scope
|
|
43
|
-
if @scope_stack.size == 1
|
|
44
|
-
|
|
45
|
-
end
|
|
45
|
+
raise(CantExitScope, 'You cannot exit global scope') if @scope_stack.size == 1
|
|
46
|
+
|
|
46
47
|
@scope_stack.pop
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
####
|
|
51
|
-
## Define a symbol in the current scope
|
|
50
|
+
# Define a symbol in the current scope
|
|
52
51
|
def define_symbol(symbol, value)
|
|
53
52
|
scope = current_scope
|
|
54
53
|
scope[symbol.to_sym] = value
|
|
55
54
|
end
|
|
56
55
|
|
|
56
|
+
# Separate arithmetic from symbol
|
|
57
|
+
def find_arithmetic(name)
|
|
58
|
+
last_name = name.split('.').last
|
|
59
|
+
md = last_name.match(%r{([+\-*/])(\d+)$})
|
|
60
|
+
f = ->(v) { v }
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
|
+
unless md.nil?
|
|
63
|
+
full_match, operator, argument = md.to_a
|
|
64
|
+
name = name.gsub(full_match, '')
|
|
65
|
+
f = ->(value) { value.send(operator.to_sym, argument.to_i) }
|
|
83
66
|
end
|
|
84
67
|
|
|
85
|
-
|
|
86
|
-
fail(UndefinedSymbol, name)
|
|
87
|
-
end
|
|
88
|
-
value
|
|
68
|
+
[name, f]
|
|
89
69
|
end
|
|
90
|
-
=end
|
|
91
|
-
|
|
92
70
|
|
|
93
|
-
|
|
94
|
-
##
|
|
71
|
+
# Resolve a symbol to its value
|
|
95
72
|
def resolve_symbol(name)
|
|
73
|
+
name, arithmetic = find_arithmetic(name)
|
|
74
|
+
|
|
96
75
|
method = name.include?('.') ? :resolve_symbol_dot_syntax : :resolve_symbol_scoped
|
|
97
|
-
value =
|
|
76
|
+
value = send(method, name)
|
|
77
|
+
value = arithmetic.call(value)
|
|
78
|
+
raise(UndefinedSymbol, name) if value.nil?
|
|
98
79
|
|
|
99
|
-
fail(UndefinedSymbol, name) if value.nil?
|
|
100
80
|
value
|
|
101
81
|
end
|
|
102
82
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
## Resolve symbol by working backwards through each
|
|
106
|
-
## containing scope. Similarly named scopes shadow outer scopes
|
|
83
|
+
# Resolve symbol by working backwards through each
|
|
84
|
+
# containing scope. Similarly named scopes shadow outer scopes
|
|
107
85
|
def resolve_symbol_scoped(name)
|
|
108
86
|
root = "-#{name}".to_sym
|
|
109
87
|
stack = @scope_stack.dup
|
|
110
88
|
loop do
|
|
111
89
|
scope = retreive_scope(stack)
|
|
112
90
|
|
|
113
|
-
|
|
91
|
+
# We see if there is a key either under this name, or root
|
|
114
92
|
v = scope[name.to_sym] || scope[root]
|
|
115
|
-
v = v.
|
|
93
|
+
v = v.is_a?(Hash) ? v[root] : v
|
|
116
94
|
|
|
117
95
|
return v unless v.nil?
|
|
118
96
|
|
|
119
|
-
|
|
97
|
+
# Pop the stack so we can decend to the parent scope, if any
|
|
120
98
|
stack.pop
|
|
121
99
|
return nil if stack.empty?
|
|
122
100
|
end
|
|
123
101
|
end
|
|
124
102
|
|
|
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
|
|
103
|
+
# Dot syntax means to check an absolute path to the symbol
|
|
104
|
+
# :global is ignored if it is provided as part of the path
|
|
129
105
|
def resolve_symbol_dot_syntax(name)
|
|
130
106
|
path_ary = name.split('.').map(&:to_sym)
|
|
131
107
|
symbol = path_ary.pop
|
|
@@ -134,33 +110,32 @@ module N65
|
|
|
134
110
|
|
|
135
111
|
scope = retreive_scope(path_ary)
|
|
136
112
|
|
|
137
|
-
|
|
113
|
+
# We see if there is a key either under this name, or root
|
|
138
114
|
v = scope[symbol]
|
|
139
|
-
v.
|
|
115
|
+
v.is_a?(Hash) ? v[root] : v
|
|
140
116
|
end
|
|
141
117
|
|
|
142
|
-
|
|
143
|
-
####
|
|
144
|
-
## Export the symbol table as YAML
|
|
118
|
+
# Export the symbol table as YAML
|
|
145
119
|
def export_to_yaml
|
|
146
120
|
@symbols.to_yaml.gsub(/(\d+)$/) do |match|
|
|
147
121
|
integer = match.to_i
|
|
148
|
-
|
|
122
|
+
format('0x%.4X', integer)
|
|
149
123
|
end
|
|
150
124
|
end
|
|
151
125
|
|
|
126
|
+
# Export a cycle count for top level subroutines
|
|
127
|
+
def export_cycle_count_yaml
|
|
128
|
+
@subroutine_cycles.to_yaml
|
|
129
|
+
end
|
|
152
130
|
|
|
153
131
|
private
|
|
154
132
|
|
|
155
|
-
|
|
156
|
-
## A bit more clearly states to get the current scope
|
|
133
|
+
# A bit more clearly states to get the current scope
|
|
157
134
|
def current_scope
|
|
158
135
|
retreive_scope
|
|
159
136
|
end
|
|
160
137
|
|
|
161
|
-
|
|
162
|
-
####
|
|
163
|
-
## Retrieve a reference to a scope, current scope by default
|
|
138
|
+
# Retrieve a reference to a scope, current scope by default
|
|
164
139
|
def retreive_scope(path_ary = @scope_stack)
|
|
165
140
|
path_ary = path_ary.dup
|
|
166
141
|
path_ary.unshift(:global) unless path_ary.first == :global
|
|
@@ -171,28 +146,22 @@ module N65
|
|
|
171
146
|
if new_scope.nil?
|
|
172
147
|
path_string = generate_scope_path(path_ary)
|
|
173
148
|
message = "Resolving scope: #{path_string} failed at #{path_component}"
|
|
174
|
-
|
|
149
|
+
raise(InvalidScope, message) if new_scope.nil?
|
|
175
150
|
end
|
|
176
151
|
|
|
177
152
|
new_scope
|
|
178
153
|
end
|
|
179
154
|
end
|
|
180
155
|
|
|
181
|
-
|
|
182
|
-
####
|
|
183
|
-
## Generate a scope path from an array
|
|
156
|
+
# Generate a scope path from an array
|
|
184
157
|
def generate_scope_path(path_ary)
|
|
185
158
|
path_ary.join('.')
|
|
186
159
|
end
|
|
187
160
|
|
|
188
|
-
|
|
189
|
-
####
|
|
190
|
-
## Generate an anonymous scope name
|
|
161
|
+
# Generate an anonymous scope name
|
|
191
162
|
def generate_name
|
|
192
163
|
@anonymous_scope_number += 1
|
|
193
164
|
"anonymous_#{@anonymous_scope_number}"
|
|
194
165
|
end
|
|
195
|
-
|
|
196
166
|
end
|
|
197
|
-
|
|
198
167
|
end
|
data/lib/n65/version.rb
CHANGED