Class: N65::Instruction
- Inherits:
-
Object
- Object
- N65::Instruction
- Includes:
- Regexes
- Defined in:
- lib/n65/instruction.rb
Overview
Represents a single 6502 Instruction
Defined Under Namespace
Classes: AddressOutOfRange, ArgumentTooLarge, InvalidAddressingMode, InvalidInstruction, UnresolvedSymbols
Constant Summary collapse
- ADDRESSING_MODES =
{ relative: { example: 'B** my_label', display: '%s $%.4X', regex: /$^/i, regex_label: /^#{Branches}\s+#{Sym}$/ }, immediate: { example: 'AAA #$FF', display: '%s #$%.2X', regex: /^#{Mnemonic}\s+#{Immediate}$/, regex_label: /^#{Mnemonic}\s+#(<|>)#{Sym}$/ }, implied: { example: 'AAA', display: '%s', regex: /^#{Mnemonic}$/ }, zero_page: { example: 'AAA $FF', display: '%s $%.2X', regex: /^#{Mnemonic}\s+#{Num8}$/, regex_label: /^#{Mnemonic}\s+#{Sym}\s+zp$/ }, zero_page_x: { example: 'AAA $FF, X', display: '%s $%.2X, X', regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{XReg}$/, regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}\s+zp$/ }, zero_page_y: { example: 'AAA $FF, Y', display: '%s $%.2X, Y', regex: /^#{Mnemonic}\s+#{Num8}\s?,\s?#{YReg}$/, regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}\s+zp$/ }, absolute: { example: 'AAA $FFFF', display: '%s $%.4X', regex: /^#{Mnemonic}\s+#{Num16}$/, regex_label: /^#{Mnemonic}\s+#{Sym}$/ }, absolute_x: { example: 'AAA $FFFF, X', display: '%s $%.4X, X', regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{XReg}$/, regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{XReg}$/ }, absolute_y: { example: 'AAA $FFFF, Y', display: '%s $%.4X, Y', regex: /^#{Mnemonic}\s+#{Num16}\s?,\s?#{YReg}$/, regex_label: /^#{Mnemonic}\s+#{Sym}\s?,\s?#{YReg}$/ }, indirect: { example: 'AAA ($FFFF)', display: '%s ($%.4X)', regex: /^#{Mnemonic}\s+\(#{Num16}\)$/, regex_label: /^#{Mnemonic}\s+\(#{Sym}\)$/ }, indirect_x: { example: 'AAA ($FF, X)', display: '%s ($%.2X, X)', regex: /^#{Mnemonic}\s+\(#{Num8}\s?,\s?#{XReg}\)$/, regex_label: /^#{Mnemonic}\s+\(#{Sym}\s?,\s?#{XReg}\)$/ }, indirect_y: { example: 'AAA ($FF), Y)', display: '%s ($%.2X), Y', regex: /^#{Mnemonic}\s+\(#{Num8}\)\s?,\s?#{YReg}$/, regex_label: /^#{Mnemonic}\s+\(#{Sym}\)\s?,\s?#{YReg}$/ } }.freeze
Constants included from Regexes
Regexes::Bin16, Regexes::Bin8, Regexes::Branches, Regexes::Hex16, Regexes::Hex8, Regexes::Immediate, Regexes::Mnemonic, Regexes::Num16, Regexes::Num8, Regexes::Sym, Regexes::XReg, Regexes::YReg
Instance Attribute Summary collapse
-
#address ⇒ Object
readonly
Returns the value of attribute address.
-
#arg ⇒ Object
readonly
Returns the value of attribute arg.
-
#boundry_add ⇒ Object
readonly
Returns the value of attribute boundry_add.
-
#cycles ⇒ Object
readonly
Returns the value of attribute cycles.
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#flags ⇒ Object
readonly
Returns the value of attribute flags.
-
#hex ⇒ Object
readonly
Returns the value of attribute hex.
-
#length ⇒ Object
readonly
Returns the value of attribute length.
-
#mode ⇒ Object
readonly
Returns the value of attribute mode.
-
#op ⇒ Object
readonly
Returns the value of attribute op.
Class Method Summary collapse
-
.parse(line) ⇒ Object
Parse one line of assembly, returns nil if the line is ultimately empty of asm instructions Raises SyntaxError if the line is malformed in some way.
Instance Method Summary collapse
-
#apply_byte_selector(byte_selector, value) ⇒ Object
Apply a byte selector to an argument.
-
#emit_bytes ⇒ Object
Emit bytes from asm structure.
-
#exec(assembler) ⇒ Object
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.
-
#initialize(op, arg, mode, byte_selector = nil) ⇒ Instruction
constructor
Create an instruction.
-
#zero_page_instruction? ⇒ Boolean
Is this instruction a zero page instruction?.
Constructor Details
#initialize(op, arg, mode, byte_selector = nil) ⇒ Instruction
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
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/n65/instruction.rb', line 167 def initialize(op, arg, mode, byte_selector = nil) @byte_selector = byte_selector.nil? ? nil : byte_selector.to_sym raise(InvalidInstruction, "Bad Byte selector: #{byte_selector}") unless [:>, :<, nil].include?(@byte_selector) ## Lookup the definition of this opcode, otherwise it is an invalid instruction @op = op.downcase.to_sym definition = OpCodes[@op] raise(InvalidInstruction, op) if definition.nil? @arg = arg # Be sure the mode is an actually supported mode. @mode = mode.to_sym raise(InvalidAddressingMode, mode) unless ADDRESSING_MODES.key?(@mode) raise(InvalidInstruction, "#{op} cannot be used in #{mode} mode") if definition[@mode].nil? @description, @flags = definition.values_at(:description, :flags) @hex, @length, @cycles, @boundry_add = definition[@mode].values_at(:hex, :len, :cycles, :boundry_add) end |
Instance Attribute Details
#address ⇒ Object (readonly)
Returns the value of attribute address.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def address @address end |
#arg ⇒ Object (readonly)
Returns the value of attribute arg.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def arg @arg end |
#boundry_add ⇒ Object (readonly)
Returns the value of attribute boundry_add.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def boundry_add @boundry_add end |
#cycles ⇒ Object (readonly)
Returns the value of attribute cycles.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def cycles @cycles end |
#description ⇒ Object (readonly)
Returns the value of attribute description.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def description @description end |
#flags ⇒ Object (readonly)
Returns the value of attribute flags.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def flags @flags end |
#hex ⇒ Object (readonly)
Returns the value of attribute hex.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def hex @hex end |
#length ⇒ Object (readonly)
Returns the value of attribute length.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def length @length end |
#mode ⇒ Object (readonly)
Returns the value of attribute mode.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def mode @mode end |
#op ⇒ Object (readonly)
Returns the value of attribute op.
9 10 11 |
# File 'lib/n65/instruction.rb', line 9 def op @op end |
Class Method Details
.parse(line) ⇒ Object
Parse one line of assembly, returns nil if the line is ultimately empty of asm instructions Raises SyntaxError if the line is malformed in some way
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/n65/instruction.rb', line 109 def self.parse(line) # Try to parse this line in each addressing mode ADDRESSING_MODES.each do |mode, parse_info| # We have regexes that match each addressing mode match_data = parse_info[:regex].match(line) unless match_data.nil? # We must have a straight instruction without symbols, construct # an Instruction from the match_data, and return it _, op, arg_hex, arg_bin = match_data.to_a # 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 # This is annoying, but still not as annoying as using Treetop to parse if !arg_hex.nil? return Instruction.new(op, arg_hex.to_i(16), mode) elsif !arg_bin.nil? return Instruction.new(op, arg_bin.to_i(2), mode) else return Instruction.new(op, nil, mode) end else # Can this addressing mode even use labels? unless parse_info[:regex_label].nil? # See if it does in fact have a symbolic argument match_data = parse_info[:regex_label].match(line) unless match_data.nil? # 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 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 return Instruction.new(op, arg, mode, byte_selector.to_sym) else _, op, arg = match_array return Instruction.new(op, arg, mode) end end end end end # We just don't recognize this line of asm, it must be a Syntax Error raise(SyntaxError, line) end |
Instance Method Details
#apply_byte_selector(byte_selector, value) ⇒ Object
Apply a byte selector to an argument
226 227 228 229 230 231 232 233 234 235 |
# File 'lib/n65/instruction.rb', line 226 def apply_byte_selector(byte_selector, value) return value if byte_selector.nil? case byte_selector when :> high_byte(value) when :< low_byte(value) end end |
#emit_bytes ⇒ Object
Emit bytes from asm structure
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/n65/instruction.rb', line 238 def emit_bytes case @length when 1 [@hex] when 2 if zero_page_instruction? && @arg.negative? || @arg > 0xff raise(ArgumentTooLarge, "For #{@op} in #{@mode} mode, only 8-bit values are allowed") end [@hex, @arg] when 3 [@hex] + break_16(@arg) else raise("Can't handle instructions > 3 bytes") end end |
#exec(assembler) ⇒ Object
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.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/n65/instruction.rb', line 195 def exec(assembler) promise = assembler.with_saved_state do |saved_assembler| @arg = saved_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 - saved_assembler.program_counter - 2 if @mode == :relative saved_assembler.write_memory(emit_bytes) end case @arg when Integer, NilClass assembler.write_memory(emit_bytes) when String begin # This works correctly now :) 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) promise end end end |
#zero_page_instruction? ⇒ Boolean
Is this instruction a zero page instruction?
188 189 190 |
# File 'lib/n65/instruction.rb', line 188 def zero_page_instruction? %i[zero_page zero_page_x zero_page_y].include?(@mode) end |