You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.6 KiB
133 lines
3.6 KiB
# typed: strict
|
|
|
|
require "sorbet-runtime"
|
|
|
|
AnyIO = T.type_alias { T.any(IO, StringIO) }
|
|
Memory = T.type_alias { T::Array[T.nilable(Integer)] }
|
|
|
|
class Mode < T::Enum
|
|
enums do
|
|
Position = new
|
|
Immediate = new
|
|
end
|
|
end
|
|
|
|
OPCODES = T.let({
|
|
1 => ->(m, _, _, a, b, c) { m[c] = m[a] + m[b] ; nil }, # add
|
|
2 => ->(m, _, _, a, b, c) { m[c] = m[a] * m[b] ; nil }, # multiply
|
|
3 => ->(m, i, _, a) { m[a] = i.gets.to_i ; nil }, # input
|
|
4 => ->(m, _, o, a) { o.puts(m[a]) ; nil }, # output
|
|
5 => ->(m, _, _, a, b) { m[a].nonzero? ? m[b] : nil }, # jump-if-true
|
|
6 => ->(m, _, _, a, b) { m[a].zero? ? m[b] : nil }, # jump-if-false
|
|
7 => ->(m, _, _, a, b, c) { m[c] = (m[a] < m[b]) ? 1 : 0; nil }, # less than
|
|
8 => ->(m, _, _, a, b, c) { m[c] = (m[a] == m[b]) ? 1 : 0; nil }, # equals
|
|
99 => ->(*) { throw :halt },
|
|
}, T::Hash[T.untyped, T.untyped])
|
|
|
|
class Parameter
|
|
extend T::Sig
|
|
|
|
sig {params(value: T.any(Parameter, Integer)).returns(Parameter)}
|
|
def self.from(value)
|
|
case value
|
|
when Parameter
|
|
value
|
|
when Integer
|
|
new(Mode::Position, value)
|
|
else
|
|
T.absurd(value)
|
|
end
|
|
end
|
|
|
|
sig {returns(Mode)}
|
|
attr_reader :mode
|
|
|
|
sig {returns(Integer)}
|
|
attr_reader :value
|
|
|
|
sig {params(mode: Mode, value: Integer).void}
|
|
def initialize(mode, value)
|
|
@mode = T.let(mode, Mode)
|
|
@value = T.let(value, Integer)
|
|
end
|
|
end
|
|
|
|
class Computer
|
|
extend T::Sig
|
|
|
|
sig {params(input: String).returns(Computer)}
|
|
def self.from(input)
|
|
new(input.split(?,).map(&:to_i))
|
|
end
|
|
|
|
sig {returns(AnyIO)}
|
|
attr_reader :input, :output
|
|
|
|
sig {params(program: Memory, input: AnyIO, output: AnyIO).void}
|
|
def initialize(program, input=STDIN, output=STDOUT)
|
|
@memory = T.let(program.dup, Memory)
|
|
@input = T.let(input, AnyIO)
|
|
@output = T.let(output, AnyIO)
|
|
@pc = T.let(0, Integer)
|
|
end
|
|
|
|
sig {params(input: AnyIO, output: AnyIO).returns(Memory)}
|
|
def run(input=STDIN, output=STDOUT)
|
|
each = T.cast(each(input, output), T::Enumerator[Memory])
|
|
each.inject(nil) {|_,i| i }
|
|
end
|
|
|
|
sig {
|
|
params(
|
|
input: AnyIO,
|
|
output: AnyIO,
|
|
blk: T.nilable(T.proc.params(m: Memory).returns(T.nilable(Integer)))
|
|
).returns(T.any(T::Enumerator[Memory], BasicObject))
|
|
}
|
|
def each(input, output, &blk)
|
|
return enum_for(T.must(__method__), input, output) unless block_given?
|
|
|
|
catch(:halt) do
|
|
loop do
|
|
instruction = @memory[@pc].to_s.rjust(5, ?0)
|
|
opcode = OPCODES.fetch(instruction[-2..-1].to_i)
|
|
@pc += 1
|
|
|
|
n = opcode.arity - 3
|
|
args = (0...n).zip(T.must(instruction[0..2]).reverse.chars.map(&:to_i)).map {|i, mode|
|
|
value = @memory.fetch(@pc + i) || 0
|
|
mode = case mode
|
|
when 0 then Mode::Position
|
|
when 1 then Mode::Immediate
|
|
end
|
|
Parameter.new(mode, value)
|
|
}
|
|
@pc += n
|
|
|
|
@pc = opcode.call(self, input, output, *args) || @pc
|
|
|
|
yield @memory
|
|
end
|
|
end
|
|
end
|
|
|
|
sig {params(parameter: Parameter).returns(Integer)}
|
|
def [](parameter)
|
|
parameter = Parameter.from(parameter)
|
|
mode = parameter.mode
|
|
case mode
|
|
when Mode::Position then @memory.fetch(parameter.value) || 0
|
|
when Mode::Immediate then parameter.value
|
|
else T.absurd(mode)
|
|
end
|
|
end
|
|
alias_method :fetch, :[]
|
|
|
|
sig {params(parameter: Parameter, value: Integer).void}
|
|
def []=(parameter, value)
|
|
raise "writes should never be in immediate mode" if parameter.mode == Mode::Immediate
|
|
|
|
@memory[parameter.value] = value
|
|
end
|
|
end
|