mirror of
https://github.com/safiire/n65.git
synced 2024-12-12 00:29:03 +00:00
98 lines
2.6 KiB
Ruby
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
|