2015-03-04 17:58:50 +00:00
|
|
|
require_relative 'opcodes'
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
module Assembler6502
|
|
|
|
|
|
|
|
####
|
|
|
|
## Represents a single 6502 Instruction
|
|
|
|
class Instruction
|
|
|
|
attr_reader :op, :arg, :mode, :hex, :description, :length, :cycle, :boundry_add, :flags, :address
|
|
|
|
|
|
|
|
## Custom Exceptions
|
|
|
|
class InvalidInstruction < StandardError; end
|
|
|
|
class UnresolvedSymbols < StandardError; end
|
|
|
|
class InvalidAddressingMode < StandardError; end
|
|
|
|
class AddressOutOfRange < StandardError; end
|
|
|
|
|
2015-02-23 02:49:24 +00:00
|
|
|
Mnemonic = '([A-Za-z]{3})'
|
|
|
|
Hex8 = '\$([A-Fa-f0-9]{2})'
|
|
|
|
Hex16 = '\$([A-Fa-f0-9]{4})'
|
2015-02-18 03:05:37 +00:00
|
|
|
Immediate = '\#\$([0-9A-F]{2})'
|
2015-03-04 17:58:50 +00:00
|
|
|
Sym = '([a-zZ-Z_][a-zA-Z0-9_\.]+)'
|
2015-02-23 02:49:24 +00:00
|
|
|
Branches = '(BPL|BMI|BVC|BVS|BCC|BCS|BNE|BEQ|bpl|bmi|bvc|bvs|bcc|bcs|bne|beq)'
|
|
|
|
XReg = '[Xx]'
|
|
|
|
YReg = '[Yy]'
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
AddressingModes = {
|
|
|
|
:relative => {
|
|
|
|
:example => 'B** my_label',
|
|
|
|
:display => '%s $%.4X',
|
2015-02-19 02:36:22 +00:00
|
|
|
:regex => /$^/i, # Will never match this one
|
2015-02-18 03:05:37 +00:00
|
|
|
:regex_label => /^#{Branches}\s+#{Sym}$/
|
|
|
|
},
|
|
|
|
|
|
|
|
:immediate => {
|
|
|
|
:example => 'AAA #$FF',
|
|
|
|
:display => '%s #$%.2X',
|
2015-02-19 02:36:22 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+#{Immediate}$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+#(<|>)#{Sym}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:implied => {
|
|
|
|
:example => 'AAA',
|
|
|
|
:display => '%s',
|
|
|
|
:regex => /^#{Mnemonic}$/
|
|
|
|
},
|
|
|
|
|
|
|
|
:zero_page => {
|
|
|
|
:example => 'AAA $FF',
|
|
|
|
:display => '%s $%.2X',
|
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex8}$/
|
|
|
|
},
|
|
|
|
|
|
|
|
:zero_page_x => {
|
|
|
|
:example => 'AAA $FF, X',
|
|
|
|
:display => '%s $%.2X, X',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex8}\s?,\s?#{XReg}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:zero_page_y => {
|
|
|
|
:example => 'AAA $FF, Y',
|
|
|
|
:display => '%s $%.2X, Y',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex8}\s?,\s?#{YReg}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:absolute => {
|
|
|
|
:example => 'AAA $FFFF',
|
|
|
|
:display => '%s $%.4X',
|
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex16}$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+#{Sym}$/
|
|
|
|
},
|
|
|
|
|
|
|
|
:absolute_x => {
|
|
|
|
:example => 'AAA $FFFF, X',
|
|
|
|
:display => '%s $%.4X, X',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex16}\s?,\s?#{XReg}$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:absolute_y => {
|
|
|
|
:example => 'AAA $FFFF, Y',
|
|
|
|
:display => '%s $%.4X, Y',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+#{Hex16}\s?,\s?#{YReg}$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:indirect => {
|
|
|
|
:example => 'AAA ($FFFF)',
|
|
|
|
:display => '%s ($%.4X)',
|
|
|
|
:regex => /^#{Mnemonic}\s+\(#{Hex16}\)$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)$/
|
|
|
|
},
|
|
|
|
|
|
|
|
:indirect_x => {
|
|
|
|
:example => 'AAA ($FF, X)',
|
|
|
|
:display => '%s ($%.2X, X)',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+\(#{Hex8}\s?,\s?#{XReg}\)$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/
|
2015-02-18 03:05:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
:indirect_y => {
|
2015-02-19 02:36:22 +00:00
|
|
|
:example => 'AAA ($FF), Y)',
|
2015-02-18 03:05:37 +00:00
|
|
|
:display => '%s ($%.2X), Y',
|
2015-02-23 02:49:24 +00:00
|
|
|
:regex => /^#{Mnemonic}\s+\(#{Hex8}\)\s?,\s?#{YReg}$/,
|
|
|
|
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/
|
2015-02-18 03:05:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
####
|
|
|
|
## Parse one line of assembly, returns nil if the line
|
2015-03-04 17:58:50 +00:00
|
|
|
## is ultimately empty of asm instructions
|
2015-02-18 03:05:37 +00:00
|
|
|
## Raises SyntaxError if the line is malformed in some way
|
2015-03-04 17:58:50 +00:00
|
|
|
def self.parse(line)
|
2015-02-18 03:05:37 +00:00
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
## Try to parse this line in each addressing mode
|
2015-02-18 03:05:37 +00:00
|
|
|
AddressingModes.each do |mode, parse_info|
|
|
|
|
|
|
|
|
## We have regexes that match each addressing mode
|
2015-03-04 17:58:50 +00:00
|
|
|
match_data = parse_info[:regex].match(line)
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
unless match_data.nil?
|
2015-03-04 17:58:50 +00:00
|
|
|
## We must have a straight instruction without symbols, construct
|
2015-02-18 03:05:37 +00:00
|
|
|
## an Instruction from the match_data, and return it
|
|
|
|
_, op, arg = match_data.to_a
|
2015-03-04 17:58:50 +00:00
|
|
|
arg = arg.to_i(16) unless arg.nil?
|
|
|
|
return Instruction.new(op, arg, mode)
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
else
|
|
|
|
## Can this addressing mode even use labels?
|
|
|
|
unless parse_info[:regex_label].nil?
|
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
## See if it does in fact have a symbolic argument
|
|
|
|
match_data = parse_info[:regex_label].match(line)
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
unless match_data.nil?
|
2015-03-04 17:58:50 +00:00
|
|
|
## We have found an assembly instruction containing a symbolic
|
|
|
|
## argument. We can resolve this symbol later by looking at the
|
|
|
|
## symbol table in the #exec method
|
2015-02-19 02:36:22 +00:00
|
|
|
match_array = match_data.to_a
|
|
|
|
|
|
|
|
## If we have a 4 element array, this means we matched something
|
|
|
|
## like LDA #<label, which is a legal immediate one byte value
|
|
|
|
## by taking the msb. We need to make that distinction in the
|
|
|
|
## Instruction, by passing an extra argument
|
|
|
|
if match_array.size == 4
|
|
|
|
_, op, byte_selector, arg = match_array
|
2015-03-04 17:58:50 +00:00
|
|
|
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
2015-02-19 02:36:22 +00:00
|
|
|
puts "I found one with #{byte_selector} #{arg}"
|
|
|
|
else
|
|
|
|
_, op, arg = match_array
|
2015-03-04 17:58:50 +00:00
|
|
|
return Instruction.new(op, arg, mode)
|
2015-02-19 02:36:22 +00:00
|
|
|
end
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
## We just don't recognize this line of asm, it must be a Syntax Error
|
2015-03-04 17:58:50 +00:00
|
|
|
fail(SyntaxError, line)
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## Create an instruction. Having the instruction op a downcased symbol is nice
|
|
|
|
## because that can later be used to index into our opcodes hash in OpCodes
|
|
|
|
## OpCodes contains the definitions of each OpCode
|
2015-03-04 17:58:50 +00:00
|
|
|
def initialize(op, arg, mode, byte_selector = nil)
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
2015-02-19 02:36:22 +00:00
|
|
|
@byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym
|
|
|
|
fail(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
|
2015-03-04 17:58:50 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
@op = op.downcase.to_sym
|
|
|
|
definition = OpCodes[@op]
|
|
|
|
fail(InvalidInstruction, op) if definition.nil?
|
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
@arg = arg
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
## Be sure the mode is an actually supported mode.
|
|
|
|
@mode = mode.to_sym
|
|
|
|
fail(InvalidAddressingMode, mode) unless AddressingModes.has_key?(@mode)
|
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
if definition[@mode].nil?
|
|
|
|
fail(InvalidInstruction, "#{op} cannot be used in #{mode} mode")
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
@description, @flags = definition.values_at(:description, :flags)
|
|
|
|
@hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## Execute writes the emitted bytes to virtual memory, and updates PC
|
|
|
|
## If there is a symbolic argument, we can try to resolve it now, or
|
|
|
|
## promise to resolve it later.
|
|
|
|
def exec(assembler)
|
|
|
|
|
|
|
|
## Save these current values into the closure/promise
|
|
|
|
pc = assembler.program_counter
|
|
|
|
segment = assembler.current_segment
|
|
|
|
bank = assembler.current_bank
|
|
|
|
|
|
|
|
## Create a promise if this symbol is not defined yet.
|
|
|
|
promise = lambda do
|
|
|
|
@arg = assembler.symbol_table.resolve_symbol(@arg)
|
|
|
|
|
|
|
|
## If the instruction uses a byte selector, we need to apply that.
|
|
|
|
@arg = apply_byte_selector(@byte_selector, @arg)
|
|
|
|
|
|
|
|
## If the instruction is relative we need to work out how far away it is
|
|
|
|
@arg = @arg - pc - 2 if @mode == :relative
|
|
|
|
|
|
|
|
assembler.write_memory(emit_bytes, pc, segment, bank)
|
|
|
|
end
|
|
|
|
|
|
|
|
case @arg
|
|
|
|
when Fixnum, NilClass
|
|
|
|
assembler.write_memory(emit_bytes)
|
2015-02-18 03:05:37 +00:00
|
|
|
when String
|
2015-03-04 17:58:50 +00:00
|
|
|
begin
|
|
|
|
promise.call
|
|
|
|
rescue SymbolTable::UndefinedSymbol
|
|
|
|
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
|
|
|
## I still have to write a placeholder instruction of the right
|
|
|
|
## length. The promise will come back and resolve the address.
|
|
|
|
assembler.write_memory(placeholder, pc, segment, bank)
|
|
|
|
return promise
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
end
|
2015-03-04 17:58:50 +00:00
|
|
|
end
|
2015-02-18 03:05:37 +00:00
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
|
|
|
|
####
|
|
|
|
## Apply a byte selector to an argument
|
|
|
|
def apply_byte_selector(byte_selector, value)
|
|
|
|
return value if byte_selector.nil?
|
|
|
|
case byte_selector
|
|
|
|
when :>
|
|
|
|
high_byte(value)
|
|
|
|
when :<
|
|
|
|
low_byte(value)
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
2015-03-04 17:58:50 +00:00
|
|
|
## Emit bytes from asm structure
|
|
|
|
def emit_bytes
|
|
|
|
case @length
|
|
|
|
when 1
|
|
|
|
[@hex]
|
|
|
|
when 2
|
|
|
|
[@hex, @arg]
|
|
|
|
when 3
|
|
|
|
[@hex] + break_16(@arg)
|
|
|
|
else
|
|
|
|
fail("Can't handle instructions > 3 bytes")
|
|
|
|
end
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2015-03-04 17:58:50 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
####
|
|
|
|
## Resolve symbols
|
2015-03-04 17:58:50 +00:00
|
|
|
=begin
|
2015-02-18 03:05:37 +00:00
|
|
|
def resolve_symbols(symbols)
|
|
|
|
if unresolved_symbols?
|
|
|
|
if symbols[@arg].nil?
|
|
|
|
fail(SyntaxError, "Unknown symbol #{@arg.inspect}")
|
|
|
|
end
|
|
|
|
|
2015-02-19 02:36:22 +00:00
|
|
|
## It is possible to resolve a symbol to a 16-bit address and then
|
|
|
|
## use byte_selector to select the msb or lsb
|
|
|
|
unless @byte_selector.nil?
|
|
|
|
arg_16 = symbols[@arg].address
|
|
|
|
@arg = case @byte_selector
|
|
|
|
when :>
|
2015-02-23 02:49:24 +00:00
|
|
|
high_byte(arg_16)
|
2015-02-19 02:36:22 +00:00
|
|
|
when :<
|
2015-02-23 02:49:24 +00:00
|
|
|
low_byte(arg_16)
|
2015-02-19 02:36:22 +00:00
|
|
|
end
|
|
|
|
return @arg
|
|
|
|
end
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
## Based on this instructions length, we should resolve the address
|
|
|
|
## to either an absolute one, or a relative one. The only relative addresses
|
|
|
|
## are the branching ones, which are 2 bytes in size, hence the extra 2 byte difference
|
|
|
|
case @length
|
|
|
|
when 2
|
|
|
|
@arg = symbols[@arg].address - @address - 2
|
|
|
|
when 3
|
|
|
|
@arg = symbols[@arg].address
|
|
|
|
else
|
|
|
|
fail(SyntaxError, "Probably can't use symbol #{@arg.inspect} with #{@op}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-03-04 17:58:50 +00:00
|
|
|
=end
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## Pretty Print
|
|
|
|
def to_s
|
2015-03-04 17:58:50 +00:00
|
|
|
#display = AddressingModes[@mode][:display]
|
|
|
|
#if @arg.kind_of?(String)
|
|
|
|
#sprintf("#{display} (#{@mode}, #{@arg})", @op, 0x0)
|
|
|
|
#else
|
|
|
|
#sprintf("#{display} (#{@mode})", @op, @arg)
|
|
|
|
#end
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
2015-02-19 02:36:22 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
private
|
|
|
|
####
|
|
|
|
## Break an integer into two 8-bit parts
|
|
|
|
def break_16(integer)
|
|
|
|
[integer & 0x00FF, (integer & 0xFF00) >> 8]
|
|
|
|
end
|
|
|
|
|
2015-02-23 02:49:24 +00:00
|
|
|
|
|
|
|
####
|
|
|
|
## Take the high byte of a 16-bit integer
|
|
|
|
def high_byte(word)
|
|
|
|
(word & 0xFF00) >> 8
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## Take the low byte of a 16-bit integer
|
|
|
|
def low_byte(word)
|
|
|
|
word & 0xFF
|
|
|
|
end
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|