Class: SnesUtils::LineAssembler

Inherits:
Object
  • Object
show all
Defined in:
lib/vas/vas.rb

Instance Method Summary collapse

Constructor Details

#initialize(raw_line, **options) ⇒ LineAssembler

Returns a new instance of LineAssembler.



387
388
389
390
391
392
# File 'lib/vas/vas.rb', line 387

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

Instance Method Details

#assembleObject



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/vas/vas.rb', line 394

def assemble
  instruction = @line.split(' ')
  mnemonic = instruction[0].upcase
  raw_operand = instruction[1].to_s

  raw_operand = Vas.replace_eval_label(@label_registry, raw_operand)
  raw_operand = replace_label(raw_operand).downcase # TODO -> generalize replace_label_eval

  opcode_data = detect_opcode(mnemonic, raw_operand)
  raise "Invalid syntax #{@line}" unless opcode_data

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

  operand_data = detect_operand(raw_operand)

  return process_fx_instruction(opcode_data, operand_data) if special_fx_instruction?(opcode_data[:alt])

  operand = process_operand(operand_data)

  return [opcode, *operand]
end

#bit_instruction?Boolean

Returns:

  • (Boolean)


585
586
587
# File 'lib/vas/vas.rb', line 585

def bit_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::BIT_INSTRUCTIONS.include?(@mode)
end

#contains_label?(operand) ⇒ Boolean

Returns:

  • (Boolean)


418
419
420
# File 'lib/vas/vas.rb', line 418

def contains_label?(operand)
  Vas::LABEL_OPERATORS.any? { |s| operand.include?(s[-1,1]) }
end

#detect_opcode(mnemonic, operand) ⇒ Object



471
472
473
474
475
476
477
# File 'lib/vas/vas.rb', line 471

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) ⇒ Object



479
480
481
# File 'lib/vas/vas.rb', line 479

def detect_operand(raw_operand)
  SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[@mode].match(raw_operand)
end

#double_operand_instruction?Boolean

Returns:

  • (Boolean)


581
582
583
# File 'lib/vas/vas.rb', line 581

def double_operand_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(@mode)
end

#fx_mov_instruction?Boolean

Returns:

  • (Boolean)


597
598
599
# File 'lib/vas/vas.rb', line 597

def fx_mov_instruction?
  @cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::MOV_INSTRUCTIONS.include?(@mode)
end

#inverted_dest_instruction?Boolean

Returns:

  • (Boolean)


605
606
607
# File 'lib/vas/vas.rb', line 605

def inverted_dest_instruction?
  @cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::INV_DEST_INSTRUCTIONS.include?(@mode)
end

#little_endian(operand, length) ⇒ Object



571
572
573
574
575
576
577
578
579
# File 'lib/vas/vas.rb', line 571

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) ⇒ Object



545
546
547
548
549
550
551
552
553
# File 'lib/vas/vas.rb', line 545

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) ⇒ Object



534
535
536
537
538
539
540
541
542
543
# File 'lib/vas/vas.rb', line 534

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_fx_instruction(opcode_data, operand_data) ⇒ Object



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/vas/vas.rb', line 492

def process_fx_instruction(opcode_data, operand_data)
  alt = opcode_data[:alt]

  return [alt, opcode_data[:opcode]].compact if @mode == :imp

  if fx_mov_instruction?
    reg1 = operand_data[1].to_i(10)
    raise "Invalid register R#{reg1}" if reg1 > 15
    reg2 = operand_data[2].to_i(10)
    raise "Invalid register R#{reg2}" if reg2 > 15

    raw_opcode = opcode_data[:opcode]
    tmp_opcode = raw_opcode | (reg2 << 8) | reg1
    opcode = [((tmp_opcode >> 8) & 0xff), ((tmp_opcode >> 0) & 0xff)]

    operand = nil
  else
    base = @mode == :imm4 ? 16 : 10
    index = inverted_dest_instruction? ? 2 : 1

    opcode_suffix = operand_data[index].to_i(base)
    opcode_prefix = opcode_data[:opcode]
    opcode = (opcode_prefix << 4) | opcode_suffix

    operand = process_fx_operand(operand_data, alt.nil? ? 0 : 1)
  end

  [alt, *opcode, *operand].compact
end

#process_fx_operand(operand_data, alt) ⇒ Object



522
523
524
525
526
527
528
529
530
531
532
# File 'lib/vas/vas.rb', line 522

def process_fx_operand(operand_data, alt)
  return unless double_operand_instruction?

  index = inverted_dest_instruction? ? 1 : 2
  operand = operand_data[index]&.to_i(16)

  raise 'Invalid address, must be multiple of 2' if short_addr_instruction? && operand.odd?
  operand /= 2 if short_addr_instruction?

  little_endian(operand, @length - 1 - alt)
end

#process_operand(operand_data) ⇒ Object



483
484
485
486
487
488
489
490
# File 'lib/vas/vas.rb', line 483

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) ⇒ Object



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
# File 'lib/vas/vas.rb', line 555

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?Boolean

Returns:

  • (Boolean)


589
590
591
# File 'lib/vas/vas.rb', line 589

def rel_instruction?
  SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(@mode)
end

#replace_label(operand) ⇒ Object



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/vas/vas.rb', line 430

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) # BUG HERE
    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) ⇒ Object



422
423
424
425
426
427
428
# File 'lib/vas/vas.rb', line 422

def replace_labels(operand)
  while contains_label?(operand)
    operand = replace_label(operand)
  end

  operand
end

#short_addr_instruction?Boolean

Returns:

  • (Boolean)


601
602
603
# File 'lib/vas/vas.rb', line 601

def short_addr_instruction?
  @cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::SHORT_ADDR_INSTRUCTIONS.include?(@mode)
end

#special_fx_instruction?(alt) ⇒ Boolean

Returns:

  • (Boolean)


593
594
595
# File 'lib/vas/vas.rb', line 593

def special_fx_instruction?(alt)
  @cpu == Vas::SUPERFX && (SnesUtils.const_get(@cpu.capitalize)::Definitions::SFX_INSTRUCTIONS.include?(@mode) || !alt.nil?)
end