WFLILUZIPJ4S2PGS5VTH5DM4U3VFGME5O6U3VJB6Y5Q727SFLZMQC
defmodule ProgramTest do
use ExUnit.Case
test "parse program" do
example_string = File.read!("example")
parsed_program =
String.split(example_string, "\n", trim: true)
|> Enum.map(&Operation.parse/1)
assert Program.parse(example_string) == parsed_program
end
test "decorruptions" do
assert Program.decorruptions([]) == [[]]
assert Program.decorruptions([{:acc, 3}]) == [[{:acc, 3}]]
assert Program.decorruptions([{:nop, 3}]) == [[{:nop, 3}], [{:jmp, 3}]]
assert Program.decorruptions([{:jmp, 3}]) == [[{:jmp, 3}], [{:nop, 3}]]
end
end
defmodule OperationTest do
use ExUnit.Case
test "parse operation" do
assert Operation.parse("nop +0") == {:nop, 0}
assert Operation.parse("acc +1") == {:acc, 1}
assert Operation.parse("jmp +4") == {:jmp, 4}
assert Operation.parse("jmp -3") == {:jmp, -3}
end
end
defmodule MachineTest do
use ExUnit.Case
test "new machine has ip 0" do
assert Machine.init().ip == 0
end
test "new machine has acc 0" do
assert Machine.init().acc == 0
end
test "init machine with program" do
example_program = Program.parse(File.read!("example"))
machine = Machine.init(example_program)
assert machine.program == example_program
end
test "operation NOP" do
next = Machine.init() |> Machine.apply({:nop, 0})
assert next.ip == 1
assert next.acc == 0
end
test "operation ACC" do
next = Machine.init() |> Machine.apply({:acc, 23})
assert next.ip == 1
assert next.acc == 23
end
test "operation JMP" do
next = Machine.init() |> Machine.apply({:jmp, 22})
assert next.ip == 22
assert next.acc == 0
end
test "eof" do
machine = [{:acc, 3}] |> Machine.init()
assert machine.acc == 0
assert machine.halt == false
assert machine.eof == false
machine = machine |> Machine.run()
assert machine.acc == 3
assert machine.halt == true
assert machine.eof == true
end
end
test "operation NOP" do
next = Machine.init() |> Machine.apply({:nop})
assert next.ip == 1
assert next.acc == 0
end
test "operation ACC" do
next = Machine.init() |> Machine.apply({:acc, 23})
assert next.ip == 1
assert next.acc == 23
end
test "operation JMP" do
next = Machine.init() |> Machine.apply({:jmp, 22})
assert next.ip == 22
assert next.acc == 0
end
test "parse operation" do
assert HandheldHalting.parse_operation("nop +0") == {:nop}
assert HandheldHalting.parse_operation("acc +1") == {:acc, 1}
assert HandheldHalting.parse_operation("jmp +4") == {:jmp, 4}
assert HandheldHalting.parse_operation("jmp -3") == {:jmp, -3}
end
test "parse program" do
example_string = File.read!("example")
parsed_program =
String.split(example_string, "\n", trim: true)
|> Enum.map(&HandheldHalting.parse_operation/1)
assert HandheldHalting.parse_program(example_string) == parsed_program
end
test "init machine with program" do
example_program = HandheldHalting.parse_program(File.read!("example"))
machine = Machine.init(example_program)
assert machine.program == example_program
end
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
defmodule Program do
@type t() :: [Operation.t()]
@spec parse(binary) :: t()
def parse(program_string) do
program_string
|> String.split("\n", trim: true)
|> Enum.map(&Operation.parse/1)
end
@spec decorruptions(Program.t()) :: [Program.t()]
def decorruptions(program) do
[program | decorruptions([], program)]
end
@spec decorruptions(Program.t(), Program.t()) :: [Program.t()]
def decorruptions(_, []) do
[]
end
def decorruptions(left, right) do
[op | rest] = right
case op do
{:acc, _} -> decorruptions(left ++ [op], rest)
{:nop, n} -> [left ++ [{:jmp, n} | rest]] ++ decorruptions(left ++ [op], rest)
{:jmp, n} -> [left ++ [{:nop, n} | rest]] ++ decorruptions(left ++ [op], rest)
end
end
end
defmodule Operation do
@type t() :: {:nop, integer()} | {:acc, integer()} | {:jmp, integer()}
@spec parse(binary()) :: Operation.t()
def parse("nop " <> n) do
{:nop, String.to_integer(n)}
end
def parse("acc " <> n) do
{:acc, String.to_integer(n)}
end
def parse("jmp " <> n) do
{:jmp, String.to_integer(n)}
end
end
defstruct ip: 0, acc: 0, program: [], visited: [], halt: false
defstruct ip: 0, acc: 0, program: [], visited: [], halt: false, eof: false
@type t() :: %Machine{
ip: integer(),
acc: integer(),
program: Program.t(),
visited: [integer()],
halt: boolean(),
eof: boolean()
}
if Enum.member?(machine.visited, machine.ip) do
%Machine{machine | halt: true}
else
operation = machine.program |> Enum.at(machine.ip)
machine |> Machine.apply(operation)
cond do
machine.ip == length(machine.program) ->
%Machine{machine | halt: true, eof: true}
Enum.member?(machine.visited, machine.ip) ->
%Machine{machine | halt: true}
true ->
operation = machine.program |> Enum.at(machine.ip)
machine |> Machine.apply(operation)
def parse_operation("nop" <> _) do
{:nop}
end
def parse_operation("acc " <> n) do
{:acc, String.to_integer(n)}
end
def parse_operation("jmp " <> n) do
{:jmp, String.to_integer(n)}
end
def parse_program(program_string) do
program_string |> String.split("\n", trim: true) |> Enum.map(&parse_operation/1)
end
acc increases or decreases a single global value called the accumulator by the value given in the argument. For example, acc +7 would increase the accumulator by 7. The accumulator starts at 0. After an acc instruction, the instruction immediately below it is executed next.
jmp jumps to a new instruction relative to itself. The next instruction to execute is found using the argument as an offset from the jmp instruction; for example, jmp +2 would skip the next instruction, jmp +1 would continue to the instruction immediately below it, and jmp -20 would cause the instruction 20 lines above to be executed next.
nop stands for No OPeration - it does nothing. The instruction immediately below it is executed next.
**jmp** jumps to a new instruction relative to itself. The next instruction to execute is found using the argument as an offset from the jmp instruction; for example, jmp +2 would skip the next instruction, jmp +1 would continue to the instruction immediately below it, and jmp -20 would cause the instruction 20 lines above to be executed next.
**nop** stands for No OPeration - it does nothing. The instruction immediately below it is executed next.
> left = [n0]
>
> right = [a1 j4, a3, j-3, a-99, a1, j-4, a6]
>
> out = [[n0, a1, j4, a3, j-3, a-99, a1, j-4, a6],
>
> [j0, a1, j4, a3, j-3, a-99, a1, j-4, a6]]