mirror of https://github.com/safiire/n65.git
configuring rubocop and cleaning up the first few files
This commit is contained in:
parent
40dd755c55
commit
fdc3317c12
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
Layout/LineLength:
|
||||||
|
Max: 120
|
||||||
|
|
||||||
|
Naming/MethodParameterName:
|
||||||
|
MinArgNameLength: 1
|
4
Gemfile
4
Gemfile
|
@ -1,4 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
# Specify your gem's dependencies in n65.gemspec
|
# Specify gem dependencies in n65.gemspec
|
||||||
gemspec
|
gemspec
|
||||||
|
|
7
Rakefile
7
Rakefile
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'bundler/gem_tasks'
|
require 'bundler/gem_tasks'
|
||||||
require 'rake/testtask'
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
@ -5,8 +7,7 @@ Rake::TestTask.new do |t|
|
||||||
t.pattern = 'test/test*.rb'
|
t.pattern = 'test/test*.rb'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
## Check the syntax of all ruby files
|
## Check the syntax of all ruby files
|
||||||
task :syntax do |t|
|
task :syntax do
|
||||||
sh "find . -name *.rb -type f -exec ruby -c {} \\; -exec echo {} \\;"
|
sh 'find . -name *.rb -type f -exec ruby -c {} \; -exec echo {} \;'
|
||||||
end
|
end
|
||||||
|
|
2
bin/n65
2
bin/n65
|
@ -1,4 +1,6 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## 6502 Assembler for the NES's 2A03
|
## 6502 Assembler for the NES's 2A03
|
||||||
##
|
##
|
||||||
|
|
135
lib/n65.rb
135
lib/n65.rb
|
@ -8,25 +8,22 @@ module N65
|
||||||
class Assembler
|
class Assembler
|
||||||
attr_reader :program_counter, :current_segment, :current_bank, :symbol_table, :virtual_memory, :promises
|
attr_reader :program_counter, :current_segment, :current_bank, :symbol_table, :virtual_memory, :promises
|
||||||
|
|
||||||
##### Custom exceptions
|
|
||||||
class AddressOutOfRange < StandardError; end
|
class AddressOutOfRange < StandardError; end
|
||||||
class InvalidSegment < StandardError; end
|
class InvalidSegment < StandardError; end
|
||||||
class WriteOutOfBounds < StandardError; end
|
class WriteOutOfBounds < StandardError; end
|
||||||
class INESHeaderAlreadySet < StandardError; end
|
class INESHeaderAlreadySet < StandardError; end
|
||||||
class FileNotFound < StandardError; end
|
class FileNotFound < StandardError; end
|
||||||
|
|
||||||
|
# Assemble from an asm file to a nes ROM
|
||||||
####
|
|
||||||
## Assemble from an asm file to a nes ROM
|
|
||||||
def self.from_file(infile, options)
|
def self.from_file(infile, options)
|
||||||
fail(FileNotFound, infile) unless File.exists?(infile)
|
raise(FileNotFound, infile) unless File.exists?(infile)
|
||||||
|
|
||||||
assembler = self.new
|
assembler = self.new
|
||||||
program = File.read(infile)
|
program = File.read(infile)
|
||||||
output_file = options[:output_file]
|
output_file = options[:output_file]
|
||||||
|
|
||||||
puts "Building #{infile}" unless options[:quiet]
|
puts "Building #{infile}" unless options[:quiet]
|
||||||
## Process each line in the file
|
# Process each line in the file
|
||||||
program.split(/\n/).each_with_index do |line, line_number|
|
program.split(/\n/).each_with_index do |line, line_number|
|
||||||
begin
|
begin
|
||||||
assembler.assemble_one_line(line)
|
assembler.assemble_one_line(line)
|
||||||
|
@ -38,12 +35,12 @@ module N65
|
||||||
end
|
end
|
||||||
puts unless options[:quiet]
|
puts unless options[:quiet]
|
||||||
|
|
||||||
## Second pass to resolve any missing symbols.
|
# Second pass to resolve any missing symbols.
|
||||||
print "Second pass, resolving symbols..." unless options[:quiet]
|
print "Second pass, resolving symbols..." unless options[:quiet]
|
||||||
assembler.fulfill_promises
|
assembler.fulfill_promises
|
||||||
puts " Done." unless options[:quiet]
|
puts " Done." unless options[:quiet]
|
||||||
|
|
||||||
## Optionally write out a symbol map
|
# Optionally write out a symbol map
|
||||||
if options[:write_symbol_table]
|
if options[:write_symbol_table]
|
||||||
print "Writing symbol table to #{output_file}.yaml..." unless options[:quiet]
|
print "Writing symbol table to #{output_file}.yaml..." unless options[:quiet]
|
||||||
File.open("#{output_file}.yaml", 'w') do |fp|
|
File.open("#{output_file}.yaml", 'w') do |fp|
|
||||||
|
@ -52,7 +49,7 @@ module N65
|
||||||
puts "Done." unless options[:quiet]
|
puts "Done." unless options[:quiet]
|
||||||
end
|
end
|
||||||
|
|
||||||
## Optionally write out cycle count for subroutines
|
# Optionally write out cycle count for subroutines
|
||||||
if options[:cycle_count]
|
if options[:cycle_count]
|
||||||
print "Writing subroutine cycle counts to #{output_file}.cycles.yaml..." unless options[:quiet]
|
print "Writing subroutine cycle counts to #{output_file}.cycles.yaml..." unless options[:quiet]
|
||||||
File.open("#{output_file}.cycles.yaml", 'w') do |fp|
|
File.open("#{output_file}.cycles.yaml", 'w') do |fp|
|
||||||
|
@ -61,7 +58,7 @@ module N65
|
||||||
puts "Done." unless options[:quiet]
|
puts "Done." unless options[:quiet]
|
||||||
end
|
end
|
||||||
|
|
||||||
## Emit the complete binary ROM
|
# Emit the complete binary ROM
|
||||||
rom = assembler.emit_binary_rom
|
rom = assembler.emit_binary_rom
|
||||||
File.open(output_file, 'w') do |fp|
|
File.open(output_file, 'w') do |fp|
|
||||||
fp.write(rom)
|
fp.write(rom)
|
||||||
|
@ -75,9 +72,7 @@ module N65
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Initialize with a bank 1 of prog space for starters
|
||||||
####
|
|
||||||
## Initialize with a bank 1 of prog space for starters
|
|
||||||
def initialize
|
def initialize
|
||||||
@ines_header = nil
|
@ines_header = nil
|
||||||
@program_counter = 0x0
|
@program_counter = 0x0
|
||||||
|
@ -91,118 +86,97 @@ module N65
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Return an object that contains the assembler's current state
|
||||||
####
|
|
||||||
## Return an object that contains the assembler's current state
|
|
||||||
def get_current_state
|
def get_current_state
|
||||||
saved_program_counter, saved_segment, saved_bank = @program_counter, @current_segment, @current_bank
|
saved_program_counter, saved_segment, saved_bank = @program_counter, @current_segment, @current_bank
|
||||||
saved_scope = symbol_table.scope_stack.dup
|
saved_scope = symbol_table.scope_stack.dup
|
||||||
OpenStruct.new(program_counter: saved_program_counter, segment: saved_segment, bank: saved_bank, scope: saved_scope)
|
OpenStruct.new(program_counter: saved_program_counter, segment: saved_segment, bank: saved_bank, scope: saved_scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the current state from an OpenStruct
|
||||||
####
|
|
||||||
## Set the current state from an OpenStruct
|
|
||||||
def set_current_state(struct)
|
def set_current_state(struct)
|
||||||
@program_counter, @current_segment, @current_bank = struct.program_counter, struct.segment, struct.bank
|
@program_counter, @current_segment, @current_bank = struct.program_counter, struct.segment, struct.bank
|
||||||
symbol_table.scope_stack = struct.scope.dup
|
symbol_table.scope_stack = struct.scope.dup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This is the main assemble method, it parses one line into an object
|
||||||
####
|
# which when given a reference to this assembler, controls the assembler
|
||||||
## This is the main assemble method, it parses one line into an object
|
# itself through public methods, executing assembler directives, and
|
||||||
## which when given a reference to this assembler, controls the assembler
|
# emitting bytes into our virtual memory spaces. Empty lines or lines
|
||||||
## itself through public methods, executing assembler directives, and
|
# with only comments parse to nil, and we just ignore them.
|
||||||
## emitting bytes into our virtual memory spaces. Empty lines or lines
|
|
||||||
## with only comments parse to nil, and we just ignore them.
|
|
||||||
def assemble_one_line(line)
|
def assemble_one_line(line)
|
||||||
parsed_object = Parser.parse(line)
|
parsed_object = Parser.parse(line)
|
||||||
|
|
||||||
unless parsed_object.nil?
|
unless parsed_object.nil?
|
||||||
exec_result = parsed_object.exec(self)
|
exec_result = parsed_object.exec(self)
|
||||||
|
|
||||||
## TODO
|
# TODO: I could perhaps keep a tally of cycles used per top level scope here
|
||||||
## I could perhaps keep a tally of cycles used per top level scope here
|
|
||||||
if parsed_object.respond_to?(:cycles)
|
if parsed_object.respond_to?(:cycles)
|
||||||
#puts "Line: #{line}"
|
# puts "Line: #{line}"
|
||||||
#puts "Cycles #{parsed_object.cycles}"
|
# puts "Cycles #{parsed_object.cycles}"
|
||||||
#puts "Sym: #{@symbol_table.scope_stack}"
|
# puts "Sym: #{@symbol_table.scope_stack}"
|
||||||
@symbol_table.add_cycles(parsed_object.cycles)
|
@symbol_table.add_cycles(parsed_object.cycles)
|
||||||
end
|
end
|
||||||
|
|
||||||
## If we have returned a promise save it for the second pass
|
# If we have returned a promise save it for the second pass
|
||||||
@promises << exec_result if exec_result.kind_of?(Proc)
|
@promises << exec_result if exec_result.is_a?(Proc)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This will empty out our promise queue and try to fullfil operations
|
||||||
####
|
# that required an undefined symbol when first encountered.
|
||||||
## This will empty out our promise queue and try to fullfil operations
|
|
||||||
## that required an undefined symbol when first encountered.
|
|
||||||
def fulfill_promises
|
def fulfill_promises
|
||||||
while promise = @promises.pop
|
while (promise = @promises.pop)
|
||||||
promise.call
|
promise.call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This rewinds the state of the assembler, so a promise can be
|
||||||
####
|
# executed with a previous state, for example if we can't resolve
|
||||||
## This rewinds the state of the assembler, so a promise can be
|
# a symbol right now, and want to try during the second pass
|
||||||
## executed with a previous state, for example if we can't resolve
|
|
||||||
## a symbol right now, and want to try during the second pass
|
|
||||||
def with_saved_state(&block)
|
def with_saved_state(&block)
|
||||||
## Save the current state of the assembler
|
## Save the current state of the assembler
|
||||||
old_state = get_current_state
|
old_state = get_current_state
|
||||||
|
|
||||||
lambda do
|
lambda do
|
||||||
|
# Set the assembler state back to the old state and run the block like that
|
||||||
## Set the assembler state back to the old state and run the block like that
|
|
||||||
set_current_state(old_state)
|
set_current_state(old_state)
|
||||||
block.call(self)
|
block.call(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Write to memory space. Typically, we are going to want to write
|
||||||
####
|
# to the location of the current PC, current segment, and current bank.
|
||||||
## Write to memory space. Typically, we are going to want to write
|
# Bounds check is inside MemorySpace#write
|
||||||
## to the location of the current PC, current segment, and current bank.
|
|
||||||
## Bounds check is inside MemorySpace#write
|
|
||||||
def write_memory(bytes, pc = @program_counter, segment = @current_segment, bank = @current_bank)
|
def write_memory(bytes, pc = @program_counter, segment = @current_segment, bank = @current_bank)
|
||||||
memory_space = get_virtual_memory_space(segment, bank)
|
memory_space = get_virtual_memory_space(segment, bank)
|
||||||
memory_space.write(pc, bytes)
|
memory_space.write(pc, bytes)
|
||||||
@program_counter += bytes.size
|
@program_counter += bytes.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the iNES header
|
||||||
####
|
|
||||||
## Set the iNES header
|
|
||||||
def set_ines_header(ines_header)
|
def set_ines_header(ines_header)
|
||||||
fail(INESHeaderAlreadySet) unless @ines_header.nil?
|
raise(INESHeaderAlreadySet) unless @ines_header.nil?
|
||||||
|
|
||||||
@ines_header = ines_header
|
@ines_header = ines_header
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the program counter
|
||||||
####
|
|
||||||
## Set the program counter
|
|
||||||
def program_counter=(address)
|
def program_counter=(address)
|
||||||
fail(AddressOutOfRange) unless address_within_range?(address)
|
raise(AddressOutOfRange) unless address_within_range?(address)
|
||||||
|
|
||||||
@program_counter = address
|
@program_counter = address
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the current segment, prog or char.
|
||||||
####
|
|
||||||
## Set the current segment, prog or char.
|
|
||||||
def current_segment=(segment)
|
def current_segment=(segment)
|
||||||
segment = segment.to_sym
|
segment = segment.to_sym
|
||||||
unless valid_segment?(segment)
|
raise(InvalidSegment, "#{segment} is not a valid segment. Try prog or char") unless valid_segment?(segment)
|
||||||
fail(InvalidSegment, "#{segment} is not a valid segment. Try prog or char")
|
|
||||||
end
|
|
||||||
@current_segment = segment
|
@current_segment = segment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set the current bank, create it if it does not exist
|
||||||
####
|
|
||||||
## Set the current bank, create it if it does not exist
|
|
||||||
def current_bank=(bank_number)
|
def current_bank=(bank_number)
|
||||||
memory_space = get_virtual_memory_space(@current_segment, bank_number)
|
memory_space = get_virtual_memory_space(@current_segment, bank_number)
|
||||||
if memory_space.nil?
|
if memory_space.nil?
|
||||||
|
@ -211,9 +185,6 @@ module N65
|
||||||
@current_bank = bank_number
|
@current_bank = bank_number
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Emit a binary ROM
|
|
||||||
def emit_binary_rom
|
def emit_binary_rom
|
||||||
progs = @virtual_memory[:prog]
|
progs = @virtual_memory[:prog]
|
||||||
chars = @virtual_memory[:char]
|
chars = @virtual_memory[:char]
|
||||||
|
@ -237,13 +208,11 @@ module N65
|
||||||
rom.emit_bytes.pack('C*')
|
rom.emit_bytes.pack('C*')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Use StringIO to build output
|
||||||
####
|
|
||||||
## Display information about the bank sizes and total usage
|
|
||||||
def print_bank_usage
|
def print_bank_usage
|
||||||
puts
|
puts
|
||||||
puts "ROM Structure {"
|
puts 'ROM Structure {'
|
||||||
puts " iNES 1.0 Header: $10 bytes"
|
puts ' iNES 1.0 Header: $10 bytes'
|
||||||
|
|
||||||
@virtual_memory[:prog].each_with_index do |prog_rom, bank_number|
|
@virtual_memory[:prog].each_with_index do |prog_rom, bank_number|
|
||||||
puts " PROG ROM bank #{bank_number}: #{prog_rom.usage_info}"
|
puts " PROG ROM bank #{bank_number}: #{prog_rom.usage_info}"
|
||||||
|
@ -252,33 +221,21 @@ module N65
|
||||||
@virtual_memory[:char].each_with_index do |char_rom, bank_number|
|
@virtual_memory[:char].each_with_index do |char_rom, bank_number|
|
||||||
puts " CHAR ROM bank #{bank_number}: #{char_rom.usage_info}"
|
puts " CHAR ROM bank #{bank_number}: #{char_rom.usage_info}"
|
||||||
end
|
end
|
||||||
puts "}"
|
puts '}'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Get virtual memory space
|
|
||||||
def get_virtual_memory_space(segment, bank_number)
|
def get_virtual_memory_space(segment, bank_number)
|
||||||
@virtual_memory[segment][bank_number]
|
@virtual_memory[segment][bank_number]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Is this a 16-bit address within range?
|
|
||||||
def address_within_range?(address)
|
def address_within_range?(address)
|
||||||
address >= 0 && address < 2**16
|
address >= 0 && address < 2**16
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
## Is this a valid segment?
|
|
||||||
def valid_segment?(segment)
|
def valid_segment?(segment)
|
||||||
[:prog, :char].include?(segment)
|
%i[prog char].include?(segment)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# frozen-string-literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
lib = File.expand_path('lib', __dir__)
|
lib = File.expand_path('lib', __dir__)
|
||||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||||
|
|
Loading…
Reference in New Issue