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