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