n65 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +340 -0
  5. data/README.md +126 -0
  6. data/Rakefile +2 -0
  7. data/bin/n65 +11 -0
  8. data/data/opcodes.yaml +1030 -0
  9. data/examples/beep.asm +24 -0
  10. data/examples/mario2.asm +260 -0
  11. data/examples/mario2.char +0 -0
  12. data/examples/music_driver.asm +202 -0
  13. data/examples/noise.asm +93 -0
  14. data/examples/pulse_chord.asm +213 -0
  15. data/images/assembler_demo.png +0 -0
  16. data/lib/n65.rb +243 -0
  17. data/lib/n65/directives/ascii.rb +42 -0
  18. data/lib/n65/directives/bytes.rb +102 -0
  19. data/lib/n65/directives/dw.rb +86 -0
  20. data/lib/n65/directives/enter_scope.rb +55 -0
  21. data/lib/n65/directives/exit_scope.rb +35 -0
  22. data/lib/n65/directives/inc.rb +67 -0
  23. data/lib/n65/directives/incbin.rb +51 -0
  24. data/lib/n65/directives/ines_header.rb +53 -0
  25. data/lib/n65/directives/label.rb +46 -0
  26. data/lib/n65/directives/org.rb +47 -0
  27. data/lib/n65/directives/segment.rb +45 -0
  28. data/lib/n65/directives/space.rb +46 -0
  29. data/lib/n65/front_end.rb +90 -0
  30. data/lib/n65/instruction.rb +308 -0
  31. data/lib/n65/instruction_base.rb +29 -0
  32. data/lib/n65/memory_space.rb +150 -0
  33. data/lib/n65/opcodes.rb +9 -0
  34. data/lib/n65/parser.rb +85 -0
  35. data/lib/n65/regexes.rb +33 -0
  36. data/lib/n65/symbol_table.rb +198 -0
  37. data/lib/n65/version.rb +3 -0
  38. data/n65.gemspec +23 -0
  39. data/nes_lib/nes.sym +105 -0
  40. data/test/test_memory_space.rb +82 -0
  41. data/test/test_symbol_table.rb +238 -0
  42. data/utils/midi/Makefile +3 -0
  43. data/utils/midi/c_scale.mid +0 -0
  44. data/utils/midi/convert +0 -0
  45. data/utils/midi/guitar.mid +0 -0
  46. data/utils/midi/include/event.h +93 -0
  47. data/utils/midi/include/file.h +57 -0
  48. data/utils/midi/include/helpers.h +14 -0
  49. data/utils/midi/include/track.h +45 -0
  50. data/utils/midi/lil_melody.mid +0 -0
  51. data/utils/midi/mi_feabhra.mid +0 -0
  52. data/utils/midi/midi_to_nes.rb +204 -0
  53. data/utils/midi/source/convert.cpp +16 -0
  54. data/utils/midi/source/event.cpp +96 -0
  55. data/utils/midi/source/file.cpp +37 -0
  56. data/utils/midi/source/helpers.cpp +46 -0
  57. data/utils/midi/source/track.cpp +37 -0
  58. data/utils/opcode_table_to_yaml.rb +91 -0
  59. 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
+
@@ -0,0 +1,9 @@
1
+ require 'yaml'
2
+
3
+ module N65
4
+
5
+ ## Load OpCode definitions into this module
6
+ MyDirectory = File.expand_path(File.dirname(__FILE__))
7
+ OpCodes = YAML.load_file("#{MyDirectory}/../../data/opcodes.yaml")
8
+
9
+ end
@@ -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
@@ -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