require "minitest"
require "minitest/pride"

OPCODES = {
  addr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] + registers[b] } },
  addi: ->(a, b, c) { ->(registers) { registers[c] = registers[a] + b } },
  mulr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] * registers[b] } },
  muli: ->(a, b, c) { ->(registers) { registers[c] = registers[a] * b } },
  banr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] & registers[b] } },
  bani: ->(a, b, c) { ->(registers) { registers[c] = registers[a] & b } },
  borr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] | registers[b] } },
  bori: ->(a, b, c) { ->(registers) { registers[c] = registers[a] | b } },
  setr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] } },
  seti: ->(a, b, c) { ->(registers) { registers[c] = a } },
  gtir: ->(a, b, c) { ->(registers) { registers[c] = (a > registers[b]) ? 1 : 0 } },
  gtri: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] > b) ? 1 : 0 } },
  gtrr: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] > registers[b]) ? 1 : 0 } },
  eqir: ->(a, b, c) { ->(registers) { registers[c] = (a == registers[b]) ? 1 : 0 } },
  eqri: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] == b) ? 1 : 0 } },
  eqrr: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] == registers[b]) ? 1 : 0 } },
}

class TestOpcodes < Minitest::Test
  def test_mulr
    registers = [3, 2, 1, 1]

    OPCODES[:mulr].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end

  def test_addi
    registers = [3, 2, 1, 1]

    OPCODES[:addi].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end

  def test_seti
    registers = [3, 2, 1, 1]

    OPCODES[:seti].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end
end

Instruction = Struct.new(:opcode, :args)

class Program
  def self.parse(input)
    lines = input.lines
    ip = lines.shift[/^#ip (\d)+$/, 1].to_i
    instructions = lines.map {|line|
      opcode, *args = line.split
      opcode = opcode.to_sym
      args = args.map(&:to_i)
      Instruction.new(opcode, args)
    }
    new(ip, instructions)
  end

  attr_reader :registers

  def initialize(ip, instructions)
    @ip, @instructions = ip, instructions
    @registers = Array.new(6, 0)
  end

  def run
    return enum_for(__method__) unless block_given?

    while (0...@instructions.length).cover?(@registers[@ip])
      instruction = @instructions.fetch(@registers[@ip])
      OPCODES.fetch(instruction.opcode)[*instruction.args][@registers]
      yield self
      @registers[@ip] += 1
    end
  end
end

class TestProgram < Minitest::Test
  def test_program
    program = Program.parse(<<~PROGRAM)
      #ip 0
      seti 5 0 1
      seti 6 0 2
      addi 0 1 0
      addr 1 2 3
      setr 1 0 0
      seti 8 0 4
      seti 9 0 5
    PROGRAM

    run = program.run
    loop do
      run.next
    end

    assert_equal [7, 5, 6, 0, 0, 9], program.registers
  end
end

def solve(input)
  program = Program.parse(input)
  run = program.run
  loop do
    run.next
  end
  program.registers[0]
end

if __FILE__ == $0
  require "minitest/autorun" and exit if ENV["TEST"]

  puts solve(ARGF.read)
end