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.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +28 -0
  4. data/.gitignore +1 -1
  5. data/.rubocop.yml +125 -0
  6. data/Gemfile +3 -1
  7. data/README.md +3 -20
  8. data/Rakefile +15 -1
  9. data/bin/n65 +2 -0
  10. data/data/opcodes.yaml +39 -39
  11. data/everdrive_transfer/everdrive.rb +175 -0
  12. data/examples/pulse_chord.asm +1 -1
  13. data/examples/scales.asm +182 -0
  14. data/lib/n65/directives/ascii.rb +4 -19
  15. data/lib/n65/directives/bytes.rb +20 -35
  16. data/lib/n65/directives/dw.rb +22 -36
  17. data/lib/n65/directives/enter_scope.rb +14 -30
  18. data/lib/n65/directives/exit_scope.rb +7 -17
  19. data/lib/n65/directives/inc.rb +14 -30
  20. data/lib/n65/directives/incbin.rb +6 -24
  21. data/lib/n65/directives/ines_header.rb +66 -27
  22. data/lib/n65/directives/label.rb +8 -21
  23. data/lib/n65/directives/org.rb +9 -19
  24. data/lib/n65/directives/segment.rb +5 -19
  25. data/lib/n65/directives/space.rb +6 -18
  26. data/lib/n65/front_end.rb +36 -39
  27. data/lib/n65/instruction.rb +123 -159
  28. data/lib/n65/instruction_base.rb +6 -18
  29. data/lib/n65/memory_space.rb +51 -71
  30. data/lib/n65/opcodes.rb +3 -5
  31. data/lib/n65/parser.rb +20 -38
  32. data/lib/n65/regexes.rb +20 -21
  33. data/lib/n65/symbol_table.rb +58 -89
  34. data/lib/n65/version.rb +3 -1
  35. data/lib/n65.rb +120 -121
  36. data/n65.gemspec +17 -12
  37. data/nes_lib/nes.sym +2 -2
  38. data/spec/.rubocop.yml +4 -0
  39. data/spec/assembler_spec.rb +84 -0
  40. data/spec/lib/n65/memory_space_spec.rb +147 -0
  41. data/spec/lib/n65/symbol_table_spec.rb +291 -0
  42. data/utils/opcode_table_to_yaml.rb +65 -67
  43. data.tar.gz.sig +0 -0
  44. metadata +84 -41
  45. metadata.gz.sig +0 -0
  46. data/examples/music_driver.asm +0 -202
  47. data/test/test_memory_space.rb +0 -82
  48. data/test/test_symbol_table.rb +0 -238
  49. data/utils/midi/Makefile +0 -3
  50. data/utils/midi/c_scale.mid +0 -0
  51. data/utils/midi/convert +0 -0
  52. data/utils/midi/guitar.mid +0 -0
  53. data/utils/midi/include/event.h +0 -93
  54. data/utils/midi/include/file.h +0 -57
  55. data/utils/midi/include/helpers.h +0 -14
  56. data/utils/midi/include/track.h +0 -45
  57. data/utils/midi/lil_melody.mid +0 -0
  58. data/utils/midi/mi_feabhra.mid +0 -0
  59. data/utils/midi/midi_to_nes.rb +0 -204
  60. data/utils/midi/source/convert.cpp +0 -16
  61. data/utils/midi/source/event.cpp +0 -96
  62. data/utils/midi/source/file.cpp +0 -37
  63. data/utils/midi/source/helpers.cpp +0 -46
  64. 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
- 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
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
- ## First check to see if we have a label.
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
- ## Now check if we have a directive
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
- ## Now, surely it is an asm instruction?
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
- ## Guess not, we have no idea
58
- fail(CannotParse, sanitized)
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
- Directives.each do |directive|
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
- ## 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)'
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
- ## Numeric Literals
13
- Hex8 = '\$([A-Fa-f0-9]{1,2})'
14
- Hex16 = '\$([A-Fa-f0-9]{3,4})'
11
+ # Numeric Literals
12
+ Hex8 = '\$([A-Fa-f0-9]{1,2})'
13
+ Hex16 = '\$([A-Fa-f0-9]{3,4})'
15
14
 
16
- Bin8 = '%([01]{1,8})'
17
- Bin16 = '%([01]{9,16})'
15
+ Bin8 = '%([01]{1,8})'
16
+ Bin16 = '%([01]{9,16})'
18
17
 
19
- Num8 = Regexp.union(Regexp.new(Hex8), Regexp.new(Bin8)).to_s
20
- Num16 = Regexp.union(Regexp.new(Hex16),Regexp.new(Bin16)).to_s
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 = "\##{Num8}"
21
+ Immediate = "\##{Num8}"
23
22
 
24
- ## Symbols, must begin with a letter, and supports dot syntax
25
- Sym = '([a-zA-Z][a-zA-Z\d_\.]*)'
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
- ## The X or Y register
29
- XReg = '[Xx]'
30
- YReg = '[Yy]'
30
+ # rubocop:enable Naming/ConstantName
31
31
  end
32
-
33
32
  end
@@ -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
- :global => {}
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
- ## Define a new scope, which can be anonymous or named
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.has_key?(name)
32
- #path_string = generate_scope_path(path_ary)
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
- fail(CantExitScope, "You cannot exit global scope")
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
- =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
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
- if value.nil?
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 = self.send(method, name)
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
- ## We see if there is a key either under this name, or root
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.kind_of?(Hash) ? v[root] : v
93
+ v = v.is_a?(Hash) ? v[root] : v
116
94
 
117
95
  return v unless v.nil?
118
96
 
119
- ## Pop the stack so we can decend to the parent scope, if any
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
- ## We see if there is a key either under this name, or root
113
+ # We see if there is a key either under this name, or root
138
114
  v = scope[symbol]
139
- v.kind_of?(Hash) ? v[root] : 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
- sprintf("0x%.4X", integer)
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
- fail(InvalidScope, message) if new_scope.nil?
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module N65
2
- VERSION = "0.5.0"
4
+ VERSION ||= '1.0.0'
3
5
  end