class SnesUtils::LineAssembler

Public Class Methods

new(raw_line, **options) click to toggle source
# File lib/vas/vas.rb, line 245
def initialize(raw_line, **options)
  @line = raw_line.split(';').first.strip.chomp
  @current_address = (options[:program_counter] + options[:origin])
  @cpu = options[:cpu]
  @label_registry = options[:label_registry]
end

Public Instance Methods

assemble() click to toggle source
# File lib/vas/vas.rb, line 252
def assemble
  instruction = @line.split(' ')
  mnemonic = instruction[0].upcase
  raw_operand = instruction[1].to_s

  raw_operand = replace_label(raw_operand)

  opcode_data = detect_opcode(mnemonic, raw_operand)
  raise "Invalid syntax" unless opcode_data

  opcode = opcode_data[:opcode]
  @mode = opcode_data[:mode]
  @length = opcode_data[:length]

  operand_data = detect_operand(raw_operand)

  operand = process_operand(operand_data)

  return [opcode, *operand]
end
bit_instruction?() click to toggle source
# File lib/vas/vas.rb, line 399
def bit_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::BIT_INSTRUCTIONS.include?(@mode)
end
contains_label?(operand) click to toggle source
# File lib/vas/vas.rb, line 273
def contains_label?(operand)
  Vas::LABEL_OPERATORS.any? { |s| operand.include?(s[-1,1]) }
end
detect_opcode(mnemonic, operand) click to toggle source
# File lib/vas/vas.rb, line 326
def detect_opcode(mnemonic, operand)
  SnesUtils.const_get(@cpu.capitalize)::Definitions::OPCODES_DATA.detect do |row|
    mode = row[:mode]
    regex = SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[mode]
    row[:mnemonic] == mnemonic && regex =~ operand
  end
end
detect_operand(raw_operand) click to toggle source
# File lib/vas/vas.rb, line 334
def detect_operand(raw_operand)
  SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[@mode].match(raw_operand)
end
double_operand_instruction?() click to toggle source
# File lib/vas/vas.rb, line 395
def double_operand_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(@mode)
end
little_endian(operand, length) click to toggle source
# File lib/vas/vas.rb, line 385
def little_endian(operand, length)
  if length > 2
    [((operand >> 0) & 0xff), ((operand >> 8) & 0xff), ((operand >> 16) & 0xff)]
  elsif length > 1
    [((operand >> 0) & 0xff), ((operand >> 8) & 0xff)]
  else
    operand
  end
end
process_bit_operand(operand_data) click to toggle source
# File lib/vas/vas.rb, line 358
def process_bit_operand(operand_data)
  m = operand_data[1].to_i(16)
  raise "Out of range: m > 0x1fff: #{m}" if m > 0x1fff

  b = operand_data[2].to_i(16)
  raise "Out of range: b > 7: #{b}" if b > 7

  little_endian(m << 3 | b, 2)
end
process_double_operand_instruction(operand_data) click to toggle source
# File lib/vas/vas.rb, line 347
def process_double_operand_instruction(operand_data)
  if bit_instruction?
    process_bit_operand(operand_data)
  else
    operands = [operand_data[1], operand_data[2]].map { |o| o.to_i(16) }
    operand_2 = rel_instruction? ? process_rel_operand(operands[1]) : operands[1]

    rel_instruction? ? [operands[0], operand_2] : [operand_2, operands[0]]
  end
end
process_operand(operand_data) click to toggle source
# File lib/vas/vas.rb, line 338
def process_operand(operand_data)
  if double_operand_instruction?
    process_double_operand_instruction(operand_data)
  else
    operand = operand_data[1]&.to_i(16)
    rel_instruction? ? process_rel_operand(operand) : little_endian(operand, @length - 1)
  end
end
process_rel_operand(operand) click to toggle source
# File lib/vas/vas.rb, line 368
def process_rel_operand(operand)
  relative_addr = operand - (@current_address & 0x00ffff) - @length

  if @cpu == Vas::WDC65816 && @mode == :rell
    raise "Relative address out of range: #{relative_addr}" if relative_addr < -32_768 || relative_addr > 32_767

    relative_addr += 0x10000 if relative_addr < 0
    little_endian(relative_addr, 2)
  else
    raise "Relative address out of range: #{relative_addr}" if relative_addr < -128 || relative_addr > 127

    relative_addr += 0x100 if relative_addr < 0
    relative_addr
  end

end
rel_instruction?() click to toggle source
# File lib/vas/vas.rb, line 403
def rel_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(@mode)
end
replace_label(operand) click to toggle source
# File lib/vas/vas.rb, line 285
def replace_label(operand)
  return operand unless contains_label?(operand)

  unless matches = /(#{Vas::LABEL_OPERATORS.join('|')})(\w+)(\+(\d+))?/.match(operand)
      raise "Invalid label syntax: #{operand}"
  end

  mode = matches[1]
  label = matches[2]
  offset = matches[4].to_i

  label_data = @label_registry.detect { |l| l[0] == label }

  value = label_data ? label_data[1] : @current_address

  value += offset

  case mode
  when '@'
    value = value & 0x00ffff
    new_value = Vas::hex(value, 4)
  when '!'
    value = value | (((@current_address >> 16) & 0xff) << 16)
    new_value = Vas::hex(value, 6)
  when '<'
    value = value & 0x0000ff
    new_value = Vas::hex(value)
  when '>'
    value = (value & 0x00ff00) >> 8
    new_value = Vas::hex(value)
  when '^'
    mode = '\^'
    value = (value & 0xff0000) >> 16
    new_value = Vas::hex(value)
  else
    raise "Mode error: #{mode}"
  end

  operand.gsub(/(#{mode})\w+(\+(\d+))?/, new_value)
end
replace_labels(operand) click to toggle source
# File lib/vas/vas.rb, line 277
def replace_labels(operand)
  while contains_label?(operand)
    operand = replace_label(operand)
  end

  operand
end