mirror of
https://github.com/safiire/n65.git
synced 2024-12-13 06:29:16 +00:00
Linted instruction.rb a bit
This commit is contained in:
parent
9f5f71cd4a
commit
92e315b3ed
@ -1,153 +1,150 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'opcodes'
|
require_relative 'opcodes'
|
||||||
require_relative 'regexes'
|
require_relative 'regexes'
|
||||||
|
|
||||||
module N65
|
module N65
|
||||||
|
# Represents a single 6502 Instruction
|
||||||
####
|
|
||||||
## Represents a single 6502 Instruction
|
|
||||||
class Instruction
|
class Instruction
|
||||||
attr_reader :op, :arg, :mode, :hex, :description, :length, :cycles, :boundry_add, :flags, :address
|
attr_reader :op, :arg, :mode, :hex, :description, :length, :cycles, :boundry_add, :flags, :address
|
||||||
|
|
||||||
## Custom Exceptions
|
# Custom Exceptions
|
||||||
class InvalidInstruction < StandardError; end
|
class InvalidInstruction < StandardError; end
|
||||||
class UnresolvedSymbols < StandardError; end
|
class UnresolvedSymbols < StandardError; end
|
||||||
class InvalidAddressingMode < StandardError; end
|
class InvalidAddressingMode < StandardError; end
|
||||||
class AddressOutOfRange < StandardError; end
|
class AddressOutOfRange < StandardError; end
|
||||||
class ArgumentTooLarge < StandardError; end
|
class ArgumentTooLarge < StandardError; end
|
||||||
|
|
||||||
## Include Regexes
|
# Include Regexes
|
||||||
include Regexes
|
include Regexes
|
||||||
|
|
||||||
AddressingModes = {
|
ADDRESSING_MODES = {
|
||||||
:relative => {
|
relative: {
|
||||||
:example => 'B** my_label',
|
example: 'B** my_label',
|
||||||
:display => '%s $%.4X',
|
display: '%s $%.4X',
|
||||||
:regex => /$^/i, # Will never match this one
|
regex: /$^/i,
|
||||||
:regex_label => /^#{Branches}\s+#{Sym}$/
|
regex_label: /^#{Branches}\s+#{Sym}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:immediate => {
|
immediate: {
|
||||||
:example => 'AAA #$FF',
|
example: 'AAA #$FF',
|
||||||
:display => '%s #$%.2X',
|
display: '%s #$%.2X',
|
||||||
:regex => /^#{Mnemonic}\s+#{Immediate}$/,
|
regex: /^#{Mnemonic}\s+#{Immediate}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#(<|>)#{Sym}$/
|
regex_label: /^#{Mnemonic}\s+#(<|>)#{Sym}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:implied => {
|
implied: {
|
||||||
:example => 'AAA',
|
example: 'AAA',
|
||||||
:display => '%s',
|
display: '%s',
|
||||||
:regex => /^#{Mnemonic}$/
|
regex: /^#{Mnemonic}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:zero_page => {
|
zero_page: {
|
||||||
:example => 'AAA $FF',
|
example: 'AAA $FF',
|
||||||
:display => '%s $%.2X',
|
display: '%s $%.2X',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num8}$/,
|
regex: /^#{Mnemonic}\s+#{Num8}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s+zp$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}\s+zp$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:zero_page_x => {
|
zero_page_x: {
|
||||||
:example => 'AAA $FF, X',
|
example: 'AAA $FF, X',
|
||||||
:display => '%s $%.2X, X',
|
display: '%s $%.2X, X',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{XReg}$/,
|
regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{XReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}\s+zp$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}\s+zp$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:zero_page_y => {
|
zero_page_y: {
|
||||||
:example => 'AAA $FF, Y',
|
example: 'AAA $FF, Y',
|
||||||
:display => '%s $%.2X, Y',
|
display: '%s $%.2X, Y',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:absolute => {
|
absolute: {
|
||||||
:example => 'AAA $FFFF',
|
example: 'AAA $FFFF',
|
||||||
:display => '%s $%.4X',
|
display: '%s $%.4X',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num16}$/,
|
regex: /^#{Mnemonic}\s+#{Num16}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:absolute_x => {
|
absolute_x: {
|
||||||
:example => 'AAA $FFFF, X',
|
example: 'AAA $FFFF, X',
|
||||||
:display => '%s $%.4X, X',
|
display: '%s $%.4X, X',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num16}\s?,\s?#{XReg}$/,
|
regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{XReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:absolute_y => {
|
absolute_y: {
|
||||||
:example => 'AAA $FFFF, Y',
|
example: 'AAA $FFFF, Y',
|
||||||
:display => '%s $%.4X, Y',
|
display: '%s $%.4X, Y',
|
||||||
:regex => /^#{Mnemonic}\s+#{Num16}\s?,\s?#{YReg}$/,
|
regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{YReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/
|
regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:indirect => {
|
indirect: {
|
||||||
:example => 'AAA ($FFFF)',
|
example: 'AAA ($FFFF)',
|
||||||
:display => '%s ($%.4X)',
|
display: '%s ($%.4X)',
|
||||||
:regex => /^#{Mnemonic}\s+\(#{Num16}\)$/,
|
regex: /^#{Mnemonic}\s+\(#{Num16}\)$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)$/
|
regex_label: /^#{Mnemonic}\s+\(#{Sym}\)$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:indirect_x => {
|
indirect_x: {
|
||||||
:example => 'AAA ($FF, X)',
|
example: 'AAA ($FF, X)',
|
||||||
:display => '%s ($%.2X, X)',
|
display: '%s ($%.2X, X)',
|
||||||
:regex => /^#{Mnemonic}\s+\(#{Num8}\s?,\s?#{XReg}\)$/,
|
regex: /^#{Mnemonic}\s+\(#{Num8}\s?,\s?#{XReg}\)$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/
|
regex_label: /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/
|
||||||
},
|
},
|
||||||
|
|
||||||
:indirect_y => {
|
indirect_y: {
|
||||||
:example => 'AAA ($FF), Y)',
|
example: 'AAA ($FF), Y)',
|
||||||
:display => '%s ($%.2X), Y',
|
display: '%s ($%.2X), Y',
|
||||||
:regex => /^#{Mnemonic}\s+\(#{Num8}\)\s?,\s?#{YReg}$/,
|
regex: /^#{Mnemonic}\s+\(#{Num8}\)\s?,\s?#{YReg}$/,
|
||||||
:regex_label => /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/
|
regex_label: /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.freeze
|
||||||
|
|
||||||
####
|
# Parse one line of assembly, returns nil if the line
|
||||||
## Parse one line of assembly, returns nil if the line
|
# is ultimately empty of asm instructions
|
||||||
## is ultimately empty of asm instructions
|
# Raises SyntaxError if the line is malformed in some way
|
||||||
## Raises SyntaxError if the line is malformed in some way
|
|
||||||
def self.parse(line)
|
def self.parse(line)
|
||||||
|
# Try to parse this line in each addressing mode
|
||||||
## Try to parse this line in each addressing mode
|
ADDRESSING_MODES.each do |mode, parse_info|
|
||||||
AddressingModes.each do |mode, parse_info|
|
# We have regexes that match each addressing mode
|
||||||
|
|
||||||
## We have regexes that match each addressing mode
|
|
||||||
match_data = parse_info[:regex].match(line)
|
match_data = parse_info[:regex].match(line)
|
||||||
|
|
||||||
unless match_data.nil?
|
unless match_data.nil?
|
||||||
## We must have a straight instruction without symbols, construct
|
# We must have a straight instruction without symbols, construct
|
||||||
## an Instruction from the match_data, and return it
|
# an Instruction from the match_data, and return it
|
||||||
_, op, arg_hex, arg_bin = match_data.to_a
|
_, op, arg_hex, arg_bin = match_data.to_a
|
||||||
|
|
||||||
## Until I think of something better, it seems that the union regex
|
# Until I think of something better, it seems that the union regex
|
||||||
## puts a hexidecimal argument in one capture, and a binary in the next
|
# puts a hexidecimal argument in one capture, and a binary in the next
|
||||||
## This is annoying, but still not as annoying as using Treetop to parse
|
# This is annoying, but still not as annoying as using Treetop to parse
|
||||||
if arg_hex != nil
|
if !arg_hex.nil?
|
||||||
return Instruction.new(op, arg_hex.to_i(16), mode)
|
return Instruction.new(op, arg_hex.to_i(16), mode)
|
||||||
elsif arg_bin != nil
|
elsif !arg_bin.nil?
|
||||||
return Instruction.new(op, arg_bin.to_i(2), mode)
|
return Instruction.new(op, arg_bin.to_i(2), mode)
|
||||||
else
|
else
|
||||||
return Instruction.new(op, nil, mode)
|
return Instruction.new(op, nil, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
## Can this addressing mode even use labels?
|
# Can this addressing mode even use labels?
|
||||||
unless parse_info[:regex_label].nil?
|
unless parse_info[:regex_label].nil?
|
||||||
|
|
||||||
## See if it does in fact have a symbolic argument
|
# See if it does in fact have a symbolic argument
|
||||||
match_data = parse_info[:regex_label].match(line)
|
match_data = parse_info[:regex_label].match(line)
|
||||||
|
|
||||||
unless match_data.nil?
|
unless match_data.nil?
|
||||||
## We have found an assembly instruction containing a symbolic
|
# We have found an assembly instruction containing a symbolic
|
||||||
## argument. We can resolve this symbol later by looking at the
|
# argument. We can resolve this symbol later by looking at the
|
||||||
## symbol table in the #exec method
|
# symbol table in the #exec method
|
||||||
match_array = match_data.to_a
|
match_array = match_data.to_a
|
||||||
|
|
||||||
## If we have a 4 element array, this means we matched something
|
# If we have a 4 element array, this means we matched something
|
||||||
## like LDA #<label, which is a legal immediate one byte value
|
# like LDA #<label, which is a legal immediate one byte value
|
||||||
## by taking the msb. We need to make that distinction in the
|
# by taking the msb. We need to make that distinction in the
|
||||||
## Instruction, by passing an extra argument
|
# Instruction, by passing an extra argument
|
||||||
if match_array.size == 4
|
if match_array.size == 4
|
||||||
_, op, byte_selector, arg = match_array
|
_, op, byte_selector, arg = match_array
|
||||||
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
||||||
@ -160,60 +157,49 @@ module N65
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## We just don't recognize this line of asm, it must be a Syntax Error
|
# We just don't recognize this line of asm, it must be a Syntax Error
|
||||||
fail(SyntaxError, line)
|
raise(SyntaxError, line)
|
||||||
end
|
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
|
||||||
## Create an instruction. Having the instruction op a downcased symbol is nice
|
# OpCodes contains the definitions of each OpCode
|
||||||
## because that can later be used to index into our opcodes hash in OpCodes
|
|
||||||
## OpCodes contains the definitions of each OpCode
|
|
||||||
def initialize(op, arg, mode, byte_selector = nil)
|
def initialize(op, arg, mode, byte_selector = nil)
|
||||||
|
|
||||||
@byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym
|
@byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym
|
||||||
fail(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
|
raise(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector)
|
||||||
|
|
||||||
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
## Lookup the definition of this opcode, otherwise it is an invalid instruction
|
||||||
@op = op.downcase.to_sym
|
@op = op.downcase.to_sym
|
||||||
definition = OpCodes[@op]
|
definition = OpCodes[@op]
|
||||||
fail(InvalidInstruction, op) if definition.nil?
|
raise(InvalidInstruction, op) if definition.nil?
|
||||||
|
|
||||||
@arg = arg
|
@arg = arg
|
||||||
|
|
||||||
## Be sure the mode is an actually supported mode.
|
# Be sure the mode is an actually supported mode.
|
||||||
@mode = mode.to_sym
|
@mode = mode.to_sym
|
||||||
fail(InvalidAddressingMode, mode) unless AddressingModes.has_key?(@mode)
|
raise(InvalidAddressingMode, mode) unless ADDRESSING_MODES.key?(@mode)
|
||||||
|
raise(InvalidInstruction, "#{op} cannot be used in #{mode} mode") if definition[@mode].nil?
|
||||||
if definition[@mode].nil?
|
|
||||||
fail(InvalidInstruction, "#{op} cannot be used in #{mode} mode")
|
|
||||||
end
|
|
||||||
|
|
||||||
@description, @flags = definition.values_at(:description, :flags)
|
@description, @flags = definition.values_at(:description, :flags)
|
||||||
@hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add)
|
@hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Is this instruction a zero page instruction?
|
||||||
####
|
|
||||||
## Is this instruction a zero page instruction?
|
|
||||||
def zero_page_instruction?
|
def zero_page_instruction?
|
||||||
[:zero_page, :zero_page_x, :zero_page_y].include?(@mode)
|
%i[zero_page zero_page_x zero_page_y].include?(@mode)
|
||||||
end
|
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
|
||||||
## Execute writes the emitted bytes to virtual memory, and updates PC
|
# promise to resolve it later.
|
||||||
## If there is a symbolic argument, we can try to resolve it now, or
|
|
||||||
## promise to resolve it later.
|
|
||||||
def exec(assembler)
|
def exec(assembler)
|
||||||
|
|
||||||
promise = assembler.with_saved_state do |saved_assembler|
|
promise = assembler.with_saved_state do |saved_assembler|
|
||||||
@arg = saved_assembler.symbol_table.resolve_symbol(@arg)
|
@arg = saved_assembler.symbol_table.resolve_symbol(@arg)
|
||||||
|
|
||||||
## If the instruction uses a byte selector, we need to apply that.
|
# If the instruction uses a byte selector, we need to apply that.
|
||||||
@arg = apply_byte_selector(@byte_selector, @arg)
|
@arg = apply_byte_selector(@byte_selector, @arg)
|
||||||
|
|
||||||
## If the instruction is relative we need to work out how far away it is
|
# If the instruction is relative we need to work out how far away it is
|
||||||
@arg = @arg - saved_assembler.program_counter - 2 if @mode == :relative
|
@arg = @arg - saved_assembler.program_counter - 2 if @mode == :relative
|
||||||
|
|
||||||
saved_assembler.write_memory(emit_bytes)
|
saved_assembler.write_memory(emit_bytes)
|
||||||
@ -224,23 +210,22 @@ module N65
|
|||||||
assembler.write_memory(emit_bytes)
|
assembler.write_memory(emit_bytes)
|
||||||
when String
|
when String
|
||||||
begin
|
begin
|
||||||
## This works correctly now :)
|
# This works correctly now :)
|
||||||
promise.call
|
promise.call
|
||||||
rescue SymbolTable::UndefinedSymbol
|
rescue SymbolTable::UndefinedSymbol
|
||||||
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
||||||
## I still have to write a placeholder instruction of the right
|
# I still have to write a placeholder instruction of the right
|
||||||
## length. The promise will come back and resolve the address.
|
# length. The promise will come back and resolve the address.
|
||||||
assembler.write_memory(placeholder)
|
assembler.write_memory(placeholder)
|
||||||
return promise
|
promise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Apply a byte selector to an argument
|
||||||
####
|
|
||||||
## Apply a byte selector to an argument
|
|
||||||
def apply_byte_selector(byte_selector, value)
|
def apply_byte_selector(byte_selector, value)
|
||||||
return value if byte_selector.nil?
|
return value if byte_selector.nil?
|
||||||
|
|
||||||
case byte_selector
|
case byte_selector
|
||||||
when :>
|
when :>
|
||||||
high_byte(value)
|
high_byte(value)
|
||||||
@ -249,47 +234,39 @@ module N65
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Emit bytes from asm structure
|
||||||
####
|
|
||||||
## Emit bytes from asm structure
|
|
||||||
def emit_bytes
|
def emit_bytes
|
||||||
case @length
|
case @length
|
||||||
when 1
|
when 1
|
||||||
[@hex]
|
[@hex]
|
||||||
when 2
|
when 2
|
||||||
if zero_page_instruction? && @arg < 0 || @arg > 0xff
|
if zero_page_instruction? && @arg.netagive? || @arg > 0xff
|
||||||
fail(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed")
|
raise(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed")
|
||||||
end
|
end
|
||||||
|
|
||||||
[@hex, @arg]
|
[@hex, @arg]
|
||||||
when 3
|
when 3
|
||||||
[@hex] + break_16(@arg)
|
[@hex] + break_16(@arg)
|
||||||
else
|
else
|
||||||
fail("Can't handle instructions > 3 bytes")
|
raise("Can't handle instructions > 3 bytes")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
####
|
|
||||||
## Break an integer into two 8-bit parts
|
# Break an integer into two 8-bit parts
|
||||||
def break_16(integer)
|
def break_16(integer)
|
||||||
[integer & 0x00FF, (integer & 0xFF00) >> 8]
|
[integer & 0x00FF, (integer & 0xFF00) >> 8]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Take the high byte of a 16-bit integer
|
||||||
####
|
|
||||||
## Take the high byte of a 16-bit integer
|
|
||||||
def high_byte(word)
|
def high_byte(word)
|
||||||
(word & 0xFF00) >> 8
|
(word & 0xFF00) >> 8
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Take the low byte of a 16-bit integer
|
||||||
####
|
|
||||||
## Take the low byte of a 16-bit integer
|
|
||||||
def low_byte(word)
|
def low_byte(word)
|
||||||
word & 0xFF
|
word & 0xFF
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user