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
|