1
0
mirror of https://github.com/safiire/n65.git synced 2024-12-12 00:29:03 +00:00
n65/lib/assembler.rb
2015-02-17 19:05:37 -08:00

98 lines
2.6 KiB
Ruby

module Assembler6502
####
## An assembler
class Assembler
attr_reader :assembly_code
####
## Assemble from a file to a file
def self.from_file(infile, outfile)
assembler = self.new(File.read(infile))
byte_array = self.create_ines_header + assembler.assemble(0x8000)
File.open(outfile, 'w') do |fp|
fp.write(byte_array.pack('C*'))
end
end
####
## iNES Header
def self.create_ines_header
[0x4E, 0x45, 0x53, 0x1a, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
end
####
## Assemble 6502 Mnemomics into a program
def initialize(assembly_code, label_index = {})
@assembly_code = assembly_code
@foreign_labels = label_index
end
####
## Assemble the 6502 assembly
def assemble(start_address = 0x600)
program_data = first_pass_parse(@assembly_code, start_address, @foreign_labels)
@foreign_labels.merge!(program_data.labels)
second_pass_resolve(program_data.instructions, @foreign_labels)
rescue => exception
STDERR.puts "Error:\n\t#{exception.message}"
exit(1)
end
####
## Just a hexdump
def hexdump
assemble.map{|byte| sprintf("%.2x", (byte & 0xFF))}
end
####
## First pass of the assembler just parses each line.
## Collecting labels, and leaving labels in instructions
## as placeholders, you can provide the code's start address,
## or arbitrary labels that are not found the given asm
def first_pass_parse(assembly_code, address = 0x0600, labels = {})
instructions = []
assembly_code.split(/\n/).each do |line|
parsed_line = Assembler6502::Instruction.parse(line, address)
case parsed_line
when Label
labels[parsed_line.label.to_sym] = parsed_line
when Instruction
instructions << parsed_line
address += parsed_line.length
when nil
else
fail(SyntaxError, sprintf("%.4X: Failed to parse: #{line}"))
end
end
OpenStruct.new(:instructions => instructions, :labels => labels)
end
####
## The second pass makes each instruction emit bytes
## while also using knowledge of label addresses to
## resolve absolute and relative usage of labels.
def second_pass_resolve(instructions, labels)
instructions.inject([]) do |sum, instruction|
if instruction.unresolved_symbols?
instruction.resolve_symbols(labels)
end
puts instruction
sum += instruction.emit_bytes
sum
end
end
end
end