Class: N65::Assembler
- Inherits:
-
Object
- Object
- N65::Assembler
- Defined in:
- lib/n65.rb
Defined Under Namespace
Classes: AddressOutOfRange, FileNotFound, INESHeaderAlreadySet, InvalidSegment, WriteOutOfBounds
Instance Attribute Summary collapse
-
#current_bank ⇒ Object
Returns the value of attribute current_bank.
-
#current_segment ⇒ Object
Returns the value of attribute current_segment.
-
#program_counter ⇒ Object
Returns the value of attribute program_counter.
-
#promises ⇒ Object
readonly
Returns the value of attribute promises.
-
#symbol_table ⇒ Object
readonly
Returns the value of attribute symbol_table.
-
#virtual_memory ⇒ Object
readonly
Returns the value of attribute virtual_memory.
Class Method Summary collapse
-
.from_file(infile, outfile) ⇒ Object
Assemble from an asm file to a nes ROM.
Instance Method Summary collapse
-
#assemble_one_line(line) ⇒ Object
This is the main assemble method, it parses one line into an object which when given a reference to this assembler, controls the assembler itself through public methods, executing assembler directives, and emitting bytes into our virtual memory spaces.
-
#emit_binary_rom ⇒ Object
Emit a binary ROM.
-
#fulfill_promises ⇒ Object
This will empty out our promise queue and try to fullfil operations that required an undefined symbol when first encountered.
-
#get_current_state ⇒ Object
Return an object that contains the assembler’s current state.
-
#initialize ⇒ Assembler
constructor
Initialize with a bank 1 of prog space for starters.
-
#set_current_state(struct) ⇒ Object
Set the current state from an OpenStruct.
-
#set_ines_header(ines_header) ⇒ Object
Set the iNES header.
-
#with_saved_state(&block) ⇒ Object
This rewinds the state of the assembler, so a promise can be executed with a previous state, for example if we can’t resolve a symbol right now, and want to try during the second pass.
-
#write_memory(bytes, pc = @program_counter, segment = @current_segment, bank = @current_bank) ⇒ Object
Write to memory space.
Constructor Details
#initialize ⇒ Assembler
Initialize with a bank 1 of prog space for starters
63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/n65.rb', line 63 def initialize @ines_header = nil @program_counter = 0x0 @current_segment = :prog @current_bank = 0x0 @symbol_table = SymbolTable.new @promises = [] @virtual_memory = { :prog => [MemorySpace.create_prog_rom], :char => [] } end |
Instance Attribute Details
#current_bank ⇒ Object
Returns the value of attribute current_bank.
9 10 11 |
# File 'lib/n65.rb', line 9 def current_bank @current_bank end |
#current_segment ⇒ Object
Returns the value of attribute current_segment.
9 10 11 |
# File 'lib/n65.rb', line 9 def current_segment @current_segment end |
#program_counter ⇒ Object
Returns the value of attribute program_counter.
9 10 11 |
# File 'lib/n65.rb', line 9 def program_counter @program_counter end |
#promises ⇒ Object (readonly)
Returns the value of attribute promises.
9 10 11 |
# File 'lib/n65.rb', line 9 def promises @promises end |
#symbol_table ⇒ Object (readonly)
Returns the value of attribute symbol_table.
9 10 11 |
# File 'lib/n65.rb', line 9 def symbol_table @symbol_table end |
#virtual_memory ⇒ Object (readonly)
Returns the value of attribute virtual_memory.
9 10 11 |
# File 'lib/n65.rb', line 9 def virtual_memory @virtual_memory end |
Class Method Details
.from_file(infile, outfile) ⇒ Object
Assemble from an asm file to a nes ROM
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/n65.rb', line 21 def self.from_file(infile, outfile) fail(FileNotFound, infile) unless File.exists?(infile) assembler = self.new program = File.read(infile) puts "Building #{infile}" ## Process each line in the file program.split(/\n/).each_with_index do |line, line_number| begin assembler.assemble_one_line(line) rescue StandardError => e STDERR.puts("\n\n#{e.class}\n#{line}\n#{e}\nOn line #{line_number}") exit(1) end print '.' end puts ## Second pass to resolve any missing symbols. print "Second pass, resolving symbols..." assembler.fulfill_promises puts " Done." ## Let's not export the symbol table to a file anymore ## Will add an option for this later. #print "Writing symbol table to #{outfile}.yaml..." #File.open("#{outfile}.yaml", 'w') do |fp| #fp.write(assembler.symbol_table.export_to_yaml) #end #puts "Done." ## For right now, let's just emit the first prog bank File.open(outfile, 'w') do |fp| fp.write(assembler.emit_binary_rom) end puts "All Done :)" end |
Instance Method Details
#assemble_one_line(line) ⇒ Object
This is the main assemble method, it parses one line into an object
which when given a reference to this assembler, controls the assembler
itself through public methods, executing assembler directives, and
emitting bytes into our virtual memory spaces. Empty lines or lines
with only comments parse to nil, and we just ignore them.
100 101 102 103 104 105 106 107 108 109 |
# File 'lib/n65.rb', line 100 def assemble_one_line(line) parsed_object = Parser.parse(line) unless parsed_object.nil? exec_result = parsed_object.exec(self) ## If we have returned a promise save it for the second pass @promises << exec_result if exec_result.kind_of?(Proc) end end |
#emit_binary_rom ⇒ Object
Emit a binary ROM
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/n65.rb', line 190 def emit_binary_rom progs = @virtual_memory[:prog] chars = @virtual_memory[:char] puts "iNES Header" puts "+ #{progs.size} PROG ROM bank#{progs.size != 1 ? 's' : ''}" puts "+ #{chars.size} CHAR ROM bank#{chars.size != 1 ? 's' : ''}" rom_size = 0x10 rom_size += MemorySpace::BankSizes[:prog] * progs.size rom_size += MemorySpace::BankSizes[:char] * chars.size puts "= Output ROM will be #{rom_size} bytes" rom = MemorySpace.new(rom_size, :rom) offset = 0x0 offset += rom.write(0x0, @ines_header.emit_bytes) progs.each do |prog| offset += rom.write(offset, prog.read(0x8000, MemorySpace::BankSizes[:prog])) end chars.each do |char| offset += rom.write(offset, char.read(0x0, MemorySpace::BankSizes[:char])) end rom.emit_bytes.pack('C*') end |
#fulfill_promises ⇒ Object
This will empty out our promise queue and try to fullfil operations
that required an undefined symbol when first encountered.
115 116 117 118 119 |
# File 'lib/n65.rb', line 115 def fulfill_promises while promise = @promises.pop promise.call end end |
#get_current_state ⇒ Object
Return an object that contains the assembler’s current state
79 80 81 82 83 |
# File 'lib/n65.rb', line 79 def get_current_state saved_program_counter, saved_segment, saved_bank = @program_counter, @current_segment, @current_bank saved_scope = symbol_table.scope_stack.dup OpenStruct.new(program_counter: saved_program_counter, segment: saved_segment, bank: saved_bank, scope: saved_scope) end |
#set_current_state(struct) ⇒ Object
Set the current state from an OpenStruct
88 89 90 91 |
# File 'lib/n65.rb', line 88 def set_current_state(struct) @program_counter, @current_segment, @current_bank = struct.program_counter, struct.segment, struct.bank symbol_table.scope_stack = struct.scope.dup end |
#set_ines_header(ines_header) ⇒ Object
Set the iNES header
152 153 154 155 |
# File 'lib/n65.rb', line 152 def set_ines_header(ines_header) fail(INESHeaderAlreadySet) unless @ines_header.nil? @ines_header = ines_header end |
#with_saved_state(&block) ⇒ Object
This rewinds the state of the assembler, so a promise can be
executed with a previous state, for example if we can't resolve
a symbol right now, and want to try during the second pass
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/n65.rb', line 126 def with_saved_state(&block) ## Save the current state of the assembler old_state = get_current_state lambda do ## Set the assembler state back to the old state and run the block like that set_current_state(old_state) block.call(self) end end |
#write_memory(bytes, pc = @program_counter, segment = @current_segment, bank = @current_bank) ⇒ Object
Write to memory space. Typically, we are going to want to write
to the location of the current PC, current segment, and current bank.
Bounds check is inside MemorySpace#write
143 144 145 146 147 |
# File 'lib/n65.rb', line 143 def write_memory(bytes, pc = @program_counter, segment = @current_segment, bank = @current_bank) memory_space = get_virtual_memory_space(segment, bank) memory_space.write(pc, bytes) @program_counter += bytes.size end |