Compare commits

..

12 Commits
expr ... master

16 changed files with 1114 additions and 337 deletions

View File

@ -1,4 +1,5 @@
# Used by "mix format" # Used by "mix format"
[ [
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: [assemble_macro: 2]
] ]

View File

@ -8,11 +8,11 @@ defmodule Day5 do
def run_program do def run_program do
read_program() read_program()
|> run() |> run_cli()
end end
def test do def test do
run([ run_cli([
3, 3,
21, 21,
1008, 1008,
@ -63,27 +63,65 @@ defmodule Day5 do
]) ])
end end
def run(memory, ip \\ 0) def run_cli(memory, opts \\ []) do
parent = self()
def run(memory, ip) when ip < length(memory) do proc =
IO.puts("IP: #{ip}") spawn(fn ->
IO.inspect(memory) run(memory, parent)
end)
case eval(Enum.drop(memory, ip), memory) do handle_cli_messages(proc, opts)
{_memory, :halt} ->
:halt
{memory, :cont, offset} ->
run(memory, ip + offset)
{memory, :jump, ip} ->
run(memory, ip)
end
:ok
end end
def run(memory, _ip), do: memory def handle_cli_messages(proc, opts) do
receive do
{:halt, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:ok, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:in, _} ->
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
send(proc, {:in, res})
handle_cli_messages(proc, opts)
{:out, _, out} ->
IO.puts("Output: #{out}")
handle_cli_messages(proc, opts)
end
end
def run(memory, parent, ip \\ 0)
def run(memory, parent, ip) when ip < length(memory) do
# IO.puts("IP: #{ip}")
# IO.inspect(memory)
case eval(Enum.drop(memory, ip), memory, parent) do
{memory, :halt} ->
send(parent, {:halt, self(), memory})
{memory, :cont, offset} ->
run(memory, parent, ip + offset)
{memory, :jump, ip} ->
run(memory, parent, ip)
end
end
def run(memory, parent, _) do
send(parent, {:ok, self(), memory})
end
defmacro opcode(op, expected) do defmacro opcode(op, expected) do
quote do quote do
@ -108,12 +146,12 @@ defmodule Day5 do
end end
# halt # halt
def eval([99 | _], memory) do def eval([99 | _], memory, _) do
{memory, :halt} {memory, :halt}
end end
# add # add
def eval([op, a, b, dest | _], memory) when opcode(op, 1) do def eval([op, a, b, dest | _], memory, _) when opcode(op, 1) do
a_val = get_param(op, 0, a, memory) a_val = get_param(op, 0, a, memory)
b_val = get_param(op, 1, b, memory) b_val = get_param(op, 1, b, memory)
@ -125,7 +163,7 @@ defmodule Day5 do
end end
# multiply # multiply
def eval([op, a, b, dest | _], memory) when opcode(op, 2) do def eval([op, a, b, dest | _], memory, _) when opcode(op, 2) do
a_val = get_param(op, 0, a, memory) a_val = get_param(op, 0, a, memory)
b_val = get_param(op, 1, b, memory) b_val = get_param(op, 1, b, memory)
@ -137,8 +175,13 @@ defmodule Day5 do
end end
# input # input
def eval([3, addr | _], memory) do def eval([3, addr | _], memory, parent) do
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer() send(parent, {:in, self()})
res =
receive do
{:in, val} -> val
end
{ {
List.replace_at(memory, addr, res), List.replace_at(memory, addr, res),
@ -148,15 +191,15 @@ defmodule Day5 do
end end
# output # output
def eval([op, param | _], memory) when opcode(op, 4) do def eval([op, param | _], memory, parent) when opcode(op, 4) do
out = get_param(op, 0, param, memory) out = get_param(op, 0, param, memory)
IO.puts("Output: #{out}") send(parent, {:out, self(), out})
{memory, :cont, 2} {memory, :cont, 2}
end end
# jump if non-zero # jump if non-zero
def eval([op, condition, ip | _], memory) when opcode(op, 5) do def eval([op, condition, ip | _], memory, _) when opcode(op, 5) do
case get_param(op, 0, condition, memory) do case get_param(op, 0, condition, memory) do
0 -> 0 ->
{memory, :cont, 3} {memory, :cont, 3}
@ -167,7 +210,7 @@ defmodule Day5 do
end end
# jump if zero # jump if zero
def eval([op, condition, ip | _], memory) when opcode(op, 6) do def eval([op, condition, ip | _], memory, _) when opcode(op, 6) do
case get_param(op, 0, condition, memory) do case get_param(op, 0, condition, memory) do
0 -> 0 ->
{memory, :jump, get_param(op, 1, ip, memory)} {memory, :jump, get_param(op, 1, ip, memory)}
@ -178,7 +221,7 @@ defmodule Day5 do
end end
# less than # less than
def eval([op, a, b, dest | _], memory) when opcode(op, 7) do def eval([op, a, b, dest | _], memory, _) when opcode(op, 7) do
a_val = get_param(op, 0, a, memory) a_val = get_param(op, 0, a, memory)
b_val = get_param(op, 1, b, memory) b_val = get_param(op, 1, b, memory)
@ -193,7 +236,7 @@ defmodule Day5 do
end end
# equals # equals
def eval([op, a, b, dest | _], memory) when opcode(op, 8) do def eval([op, a, b, dest | _], memory, _) when opcode(op, 8) do
a_val = get_param(op, 0, a, memory) a_val = get_param(op, 0, a, memory)
b_val = get_param(op, 1, b, memory) b_val = get_param(op, 1, b, memory)

95
lib/day7/day7.ex Normal file
View File

@ -0,0 +1,95 @@
defmodule Day7 do
def part1 do
[0, 1, 2, 3, 4]
|> permutations()
|> Enum.map(fn digits -> {digits, test_inputs(digits)} end)
|> Enum.max_by(fn {_, out} -> out end)
end
def part2 do
[5, 6, 7, 8, 9]
|> permutations()
|> Enum.map(&test_looped/1)
|> Enum.max()
end
def test_inputs(digits) do
Enum.reduce(digits, 0, fn phase_setting, prev_output ->
phase_setting
|> start_amplifier()
|> amplifier_step(prev_output)
end)
end
def permutations([]), do: [[]]
def permutations(list) do
for x <- list, y <- permutations(list -- [x]), do: [x | y]
end
def test_looped(digits) do
digits
|> Enum.map(&start_amplifier/1)
# |> IO.inspect()
|> Stream.cycle()
|> Enum.reduce_while(0, fn pid, prev_output ->
case amplifier_step(pid, prev_output) do
:halt ->
{:halt, prev_output}
output ->
{:cont, output}
end
end)
end
def start_amplifier(phase_setting) do
program = File.read!("lib/day7/input.txt") |> String.trim()
# part 1 examples:
# program = "3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0"
# program = "3,23,3,24,1002,24,10,24,1002,23,-1,23,101,5,23,23,1,24,23,23,4,23,99,0,0"
# program =
# "3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33,1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0"
# part 2 examples:
# program =
# "3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5"
# program =
# "3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10"
program =
program
|> String.split(",")
|> Enum.map(&String.to_integer/1)
parent = self()
pid =
spawn(fn ->
Day5.run(program, parent)
end)
receive do
{:in, ^pid} -> send(pid, {:in, phase_setting})
end
pid
end
def amplifier_step(pid, prev_output) do
receive do
{:in, ^pid} ->
send(pid, {:in, prev_output})
receive do
{:out, ^pid, val} ->
val
{:halt, ^pid} ->
:halt
end
{:halt, ^pid} ->
:halt
end
end
end

1
lib/day7/input.txt Normal file
View File

@ -0,0 +1 @@
3,8,1001,8,10,8,105,1,0,0,21,42,67,88,101,114,195,276,357,438,99999,3,9,101,3,9,9,1002,9,4,9,1001,9,5,9,102,4,9,9,4,9,99,3,9,1001,9,3,9,1002,9,2,9,101,2,9,9,102,2,9,9,1001,9,5,9,4,9,99,3,9,102,4,9,9,1001,9,3,9,102,4,9,9,101,4,9,9,4,9,99,3,9,101,2,9,9,1002,9,3,9,4,9,99,3,9,101,4,9,9,1002,9,5,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,99

71
lib/day8/day8.ex Normal file
View File

@ -0,0 +1,71 @@
defmodule Day8 do
def part1 do
File.read!("lib/day8/input.txt")
|> parse_input()
|> split_into_layers(25, 6)
|> min_zeros()
|> ones_times_twos()
# "123456789012"
# |> parse_input()
# |> split_into_layers(3, 2)
# |> min_zeros()
# |> ones_times_twos()
end
def part2 do
File.read!("lib/day8/input.txt")
|> parse_input()
|> split_into_layers(25, 6)
|> combine_layers()
|> Enum.map(fn
0 -> ?\s
1 -> ?X
end)
|> Enum.chunk_every(25)
|> Enum.each(&IO.puts/1)
# "0222112222120000"
# |> parse_input()
# |> split_into_layers(2, 2)
# |> combine_layers()
end
def parse_input(str) do
str
|> String.trim()
|> String.to_charlist()
|> Enum.map(fn digit -> digit - ?0 end)
end
def split_into_layers(input, layer_width, layer_height) do
Enum.chunk_every(input, layer_width * layer_height)
end
def min_zeros(layers) do
layers
|> Enum.min_by(fn layer ->
Enum.count(layer, &(&1 == 0))
end)
end
def ones_times_twos(layer) do
ones = Enum.count(layer, &(&1 == 1))
twos = Enum.count(layer, &(&1 == 2))
ones * twos
end
def combine_layers(layers) do
Enum.zip(layers)
|> Enum.map(&combine_pixel/1)
end
def combine_pixel(pixel) do
0..(tuple_size(pixel) - 1)
|> Enum.map(fn index -> elem(pixel, index) end)
|> Enum.reduce(2, fn
val, 2 -> val
_, it -> it
end)
end
end

1
lib/day8/input.txt Normal file

File diff suppressed because one or more lines are too long

239
lib/day9/day9.ex Normal file
View File

@ -0,0 +1,239 @@
defmodule Day9 do
def read_program do
File.read!("lib/day9/input.txt")
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
end
def run_program do
read_program()
|> expand_memory()
|> run_cli()
end
def test do
# prog = [109, 1, 204, -1, 1001, 100, 1, 100, 1008, 100, 16, 101, 1006, 101, 0, 99]
# prog = [1102, 34_915_192, 34_915_192, 7, 4, 7, 99, 0]
prog = [104, 1_125_899_906_842_624, 99]
expanded = expand_memory(prog, 150)
run_cli(expanded)
end
def run_cli(memory, opts \\ []) do
parent = self()
proc =
spawn(fn ->
run(memory, parent)
end)
handle_cli_messages(proc, opts)
end
def handle_cli_messages(proc, opts) do
receive do
{:halt, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:ok, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:in, _} ->
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
send(proc, {:in, res})
handle_cli_messages(proc, opts)
{:out, _, out} ->
IO.puts("Output: #{out}")
handle_cli_messages(proc, opts)
end
end
def expand_memory(memory, length \\ nil) do
memory ++ repeat(0, length || length(memory) * 10)
end
def repeat(_, 0), do: []
def repeat(val, count), do: [val | repeat(val, count - 1)]
def run(state, parent, ip \\ 0)
def run(memory, parent, ip) when is_list(memory) do
run({memory, 0}, parent, ip)
end
def run({memory, _relative_base} = state, parent, ip) when ip < length(memory) do
# IO.puts("IP: #{ip}")
# IO.inspect(memory)
case eval(Enum.drop(memory, ip), state, parent) do
{memory, :halt} ->
send(parent, {:halt, self(), memory})
{state, :cont, offset} ->
run(state, parent, ip + offset)
{state, :jump, ip} ->
run(state, parent, ip)
end
end
def run(state, parent, _) do
send(parent, {:ok, self(), state})
end
defmacro opcode(op, expected) do
quote do
rem(unquote(op), 100) == unquote(expected)
end
end
def get_param(op, param, val, {memory, relative_base}) do
place = param + 2
case op |> div(pow(10, place)) |> rem(10) do
0 -> Enum.at(memory, val)
1 -> val
2 -> Enum.at(memory, relative_base + val)
end
end
def get_write_param(op, param, val, {memory, relative_base}) do
place = param + 2
case op |> div(pow(10, place)) |> rem(10) do
2 -> relative_base + val
_ -> val
end
end
def pow(base, 1), do: base
def pow(base, exp) do
base * pow(base, exp - 1)
end
# halt
def eval([99 | _], memory, _) do
{memory, :halt}
end
# add
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 1) do
a_val = get_param(op, 0, a, state)
b_val = get_param(op, 1, b, state)
dest = get_write_param(op, 2, dest, state)
{
{List.replace_at(memory, dest, a_val + b_val), relative_base},
:cont,
4
}
end
# multiply
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 2) do
a_val = get_param(op, 0, a, state)
b_val = get_param(op, 1, b, state)
dest = get_write_param(op, 2, dest, state)
{
{List.replace_at(memory, dest, a_val * b_val), relative_base},
:cont,
4
}
end
# input
def eval([op, addr | _], {memory, relative_base} = state, parent) when opcode(op, 3) do
addr = get_write_param(op, 0, addr, state)
send(parent, {:in, self()})
res =
receive do
{:in, val} -> val
end
{
{List.replace_at(memory, addr, res), relative_base},
:cont,
2
}
end
# output
def eval([op, param | _], state, parent) when opcode(op, 4) do
out = get_param(op, 0, param, state)
send(parent, {:out, self(), out})
{state, :cont, 2}
end
# jump if non-zero
def eval([op, condition, ip | _], state, _) when opcode(op, 5) do
case get_param(op, 0, condition, state) do
0 ->
{state, :cont, 3}
_ ->
{state, :jump, get_param(op, 1, ip, state)}
end
end
# jump if zero
def eval([op, condition, ip | _], state, _) when opcode(op, 6) do
case get_param(op, 0, condition, state) do
0 ->
{state, :jump, get_param(op, 1, ip, state)}
_ ->
{state, :cont, 3}
end
end
# less than
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 7) do
a_val = get_param(op, 0, a, state)
b_val = get_param(op, 1, b, state)
dest = get_write_param(op, 2, dest, state)
memory =
if a_val < b_val do
List.replace_at(memory, dest, 1)
else
List.replace_at(memory, dest, 0)
end
{{memory, relative_base}, :cont, 4}
end
# equals
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 8) do
a_val = get_param(op, 0, a, state)
b_val = get_param(op, 1, b, state)
dest = get_write_param(op, 2, dest, state)
memory =
if a_val == b_val do
List.replace_at(memory, dest, 1)
else
List.replace_at(memory, dest, 0)
end
{{memory, relative_base}, :cont, 4}
end
# set relative base
def eval([op, new_base | _], {memory, relative_base} = state, _) when opcode(op, 9) do
{{memory, relative_base + get_param(op, 0, new_base, state)}, :cont, 2}
end
end

1
lib/day9/input.txt Normal file
View File

@ -0,0 +1 @@
1102,34463338,34463338,63,1007,63,34463338,63,1005,63,53,1101,0,3,1000,109,988,209,12,9,1000,209,6,209,3,203,0,1008,1000,1,63,1005,63,65,1008,1000,2,63,1005,63,904,1008,1000,0,63,1005,63,58,4,25,104,0,99,4,0,104,0,99,4,17,104,0,99,0,0,1102,1,39,1013,1102,1,21,1018,1101,0,336,1027,1102,1,38,1012,1101,534,0,1025,1101,539,0,1024,1101,0,380,1023,1102,1,23,1014,1102,29,1,1000,1102,24,1,1019,1102,1,28,1011,1101,339,0,1026,1101,31,0,1005,1102,36,1,1017,1102,26,1,1007,1102,1,407,1028,1101,387,0,1022,1101,0,30,1001,1101,34,0,1010,1102,1,32,1006,1101,0,1,1021,1102,27,1,1008,1102,22,1,1004,1102,1,20,1015,1101,0,37,1016,1101,0,0,1020,1102,1,398,1029,1101,25,0,1009,1101,0,35,1003,1101,33,0,1002,109,27,1206,-6,197,1001,64,1,64,1105,1,199,4,187,1002,64,2,64,109,-22,2107,26,3,63,1005,63,217,4,205,1105,1,221,1001,64,1,64,1002,64,2,64,109,17,21107,40,39,-8,1005,1014,241,1001,64,1,64,1105,1,243,4,227,1002,64,2,64,109,-8,1206,6,261,4,249,1001,64,1,64,1106,0,261,1002,64,2,64,109,-7,2108,24,0,63,1005,63,281,1001,64,1,64,1105,1,283,4,267,1002,64,2,64,109,11,21102,41,1,-3,1008,1015,42,63,1005,63,303,1105,1,309,4,289,1001,64,1,64,1002,64,2,64,109,1,1205,2,327,4,315,1001,64,1,64,1105,1,327,1002,64,2,64,109,10,2106,0,-2,1106,0,345,4,333,1001,64,1,64,1002,64,2,64,109,-15,21102,42,1,3,1008,1017,42,63,1005,63,367,4,351,1105,1,371,1001,64,1,64,1002,64,2,64,109,-1,2105,1,10,1001,64,1,64,1105,1,389,4,377,1002,64,2,64,109,24,2106,0,-9,4,395,1001,64,1,64,1105,1,407,1002,64,2,64,109,-30,1208,-2,32,63,1005,63,427,1001,64,1,64,1106,0,429,4,413,1002,64,2,64,109,2,1201,0,0,63,1008,63,27,63,1005,63,449,1106,0,455,4,435,1001,64,1,64,1002,64,2,64,109,5,21107,43,44,0,1005,1014,473,4,461,1106,0,477,1001,64,1,64,1002,64,2,64,109,-16,1202,3,1,63,1008,63,33,63,1005,63,501,1001,64,1,64,1106,0,503,4,483,1002,64,2,64,109,10,1207,-4,21,63,1005,63,523,1001,64,1,64,1106,0,525,4,509,1002,64,2,64,109,11,2105,1,5,4,531,1106,0,543,1001,64,1,64,1002,64,2,64,109,-8,21101,44,0,5,1008,1016,47,63,1005,63,563,1106,0,569,4,549,1001,64,1,64,1002,64,2,64,109,-13,2102,1,8,63,1008,63,34,63,1005,63,593,1001,64,1,64,1105,1,595,4,575,1002,64,2,64,109,8,1208,-1,31,63,1005,63,617,4,601,1001,64,1,64,1106,0,617,1002,64,2,64,109,-8,2108,33,4,63,1005,63,635,4,623,1105,1,639,1001,64,1,64,1002,64,2,64,109,10,1202,-1,1,63,1008,63,26,63,1005,63,665,4,645,1001,64,1,64,1105,1,665,1002,64,2,64,109,-9,2107,30,1,63,1005,63,685,1001,64,1,64,1105,1,687,4,671,1002,64,2,64,109,25,1205,-4,703,1001,64,1,64,1105,1,705,4,693,1002,64,2,64,109,-19,2101,0,-5,63,1008,63,26,63,1005,63,725,1105,1,731,4,711,1001,64,1,64,1002,64,2,64,109,6,1207,-2,26,63,1005,63,749,4,737,1105,1,753,1001,64,1,64,1002,64,2,64,109,-10,21108,45,46,9,1005,1010,769,1105,1,775,4,759,1001,64,1,64,1002,64,2,64,109,-10,1201,10,0,63,1008,63,30,63,1005,63,801,4,781,1001,64,1,64,1106,0,801,1002,64,2,64,109,21,21108,46,46,3,1005,1015,819,4,807,1106,0,823,1001,64,1,64,1002,64,2,64,109,-4,2102,1,-3,63,1008,63,31,63,1005,63,849,4,829,1001,64,1,64,1106,0,849,1002,64,2,64,109,-5,2101,0,1,63,1008,63,22,63,1005,63,875,4,855,1001,64,1,64,1105,1,875,1002,64,2,64,109,17,21101,47,0,-3,1008,1017,47,63,1005,63,897,4,881,1105,1,901,1001,64,1,64,4,64,99,21101,0,27,1,21102,1,915,0,1105,1,922,21201,1,38480,1,204,1,99,109,3,1207,-2,3,63,1005,63,964,21201,-2,-1,1,21101,0,942,0,1106,0,922,21202,1,1,-1,21201,-2,-3,1,21101,957,0,0,1105,1,922,22201,1,-1,-2,1106,0,968,22101,0,-2,-2,109,-3,2105,1,0

View File

@ -1,98 +1,256 @@
defmodule Assembler.Helpers do
defmacro assemble_macro(match, asm) do
quote do
def assemble_insn(unquote(match), memory) do
macro_mem = assemble_for_macro(unquote(asm), length(memory))
macro_mem ++ memory
end
end
end
end
defmodule Assembler do defmodule Assembler do
@asm """ import NimbleParsec
in res import Assembler.Helpers
clt $res 8 cmpRes
jnz $cmpRes lessThan
ceq $res 8 cmpRes
jnz $cmpRes equal
out 1001
hlt
lessThan: label =
out 999 ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
hlt |> ignore(ascii_char([?:]))
|> ignore(repeat(ascii_char([?\s])))
equal: param =
out 1000 optional(ignore(ascii_string([?\s], min: 1)))
hlt |> ascii_string([{:not, ?,}], min: 1)
data_label =
label
|> concat(param)
|> tag(:data_label)
instruction =
ignore(ascii_string([?\s, ?\t], min: 0))
|> ascii_string([?a..?z], min: 1)
|> repeat(param |> ignore(ascii_char([?,])))
|> optional(param)
|> tag(:insn)
defparsec(
:parse_line,
choice([
data_label,
label |> tag(:label),
instruction,
eos()
])
)
res: 0
cmpRes: 0
"""
def test do def test do
@asm File.read!("test/intcode/cmp8.asm")
|> IO.inspect()
|> assemble() |> assemble()
|> Day9.run_cli()
end end
def assemble(asm) do def assemble(asm) do
{memory, labels} = {memory, labels} = do_assemble(asm)
asm
|> String.split("\n")
|> Enum.reject(&(String.length(&1) == 0))
|> Enum.reduce({[], %{}}, fn line, {memory, labels} ->
cond do
Regex.match?(~r/^\s+$/, line) ->
{memory, labels}
String.starts_with?(line, " ") ->
{
assemble_instruction(String.slice(line, 2..-1), memory),
labels
}
String.ends_with?(line, ":") ->
{
memory,
Map.put(labels, String.slice(line, 0..-2), length(memory))
}
Regex.match?(~r/^\w+: \d+$/, line) ->
[name, value] = String.split(line, ": ")
value = String.to_integer(value)
{
[value | memory],
Map.put(labels, name, length(memory))
}
true ->
IO.inspect("Ignoring line: #{line}")
end
end)
# IO.inspect(labels)
memory memory
|> Enum.reverse() |> Enum.reverse()
|> Enum.with_index() |> Enum.with_index()
|> Enum.map(fn |> Enum.map(fn
{{:label, name}, index} -> get_label(name, index, labels) {{:expr, expr}, index} ->
{it, _} -> it expr_tokens =
if is_binary(expr) do
{:ok, tokens, _, _, _, _} = ExpressionEvaluator.parse(expr)
tokens
else
expr
end
ExpressionEvaluator.do_eval(expr_tokens, fn
"_self" -> index
name -> Map.fetch!(labels, name)
end)
{it, _} ->
it
end) end)
end end
@doc """ def assemble_for_macro(asm, macro_offset) do
Get the address of a label. {memory, labels} = do_assemble(asm)
`_self` is a special label that resolves to the address of where it's being inserted into the program. memory
|> Enum.map(fn
## Examples {:expr, expr} -> {:expr, expand_macro_expression(expr, labels, macro_offset)}
iex> Assembler.get_label("test", 1, %{"test" => 14}) it -> it
14 end)
iex> Assembler.get_label("_self", 1, %{})
1
"""
def get_label(name, index, labels)
def get_label("_self", index, _) do
index
end end
def get_label(name, _, labels) do def expand_macro_expression(expr, _, _) when is_integer(expr) do
Map.fetch!(labels, name) expr
end end
def expand_macro_expression(expr, macro_labels, macro_offset) when is_binary(expr) do
case Map.get(macro_labels, expr) do
nil -> expr
val -> macro_offset + val
end
end
def expand_macro_expression(expr, macro_labels, macro_offset) when is_list(expr) do
Enum.map(expr, fn expr ->
expand_macro_expression(expr, macro_labels, macro_offset)
end)
end
def expand_macro_expression({op, values}, macro_labels, macro_offset) do
{op, expand_macro_expression(values, macro_labels, macro_offset)}
end
def do_assemble(asm) do
asm
|> String.trim_trailing("\n")
|> String.split("\n")
|> Enum.map(fn line ->
case parse_line(line) do
{:ok, res, _, _, _, _} ->
res
err ->
raise "Unable to parse line: '#{line}':\n#{inspect(err)}"
end
end)
|> Enum.reduce({[], %{}}, fn line, acc ->
assemble_line(line, acc)
end)
end
def assemble_line([], acc), do: acc
def assemble_line([label: [name]], {memory, labels}) do
{
memory,
Map.put(labels, name, length(memory))
}
end
def assemble_line([data_label: [name, value]], {memory, labels}) do
value = {:expr, value}
{
[value | memory],
Map.put(labels, name, length(memory))
}
end
def assemble_line([insn: insn], {memory, labels}) do
{
assemble_insn(insn, memory),
labels
}
end
def assemble_insn(["add" | params], memory) when length(params) == 3 do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 1 + modes | memory]
end
def assemble_insn(["mul" | params], memory) when length(params) == 3 do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 2 + modes | memory]
end
def assemble_insn(["in", dest], memory) do
{[dest], modes} = parse_params([dest], [:write])
[dest, 3 + modes | memory]
end
def assemble_insn(["out", src], memory) do
{[src], modes} = parse_params([src], [:read])
[src, 4 + modes | memory]
end
def assemble_insn(["jnz" | params], memory) when length(params) == 2 do
{[target, dest], modes} = parse_params(params, [:read, :read])
[dest, target, 5 + modes | memory]
end
def assemble_insn(["jez" | params], memory) when length(params) == 2 do
{[target, dest], modes} = parse_params(params, [:read, :read])
[dest, target, 6 + modes | memory]
end
def assemble_insn(["clt" | params], memory) when length(params) == 3 do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 7 + modes | memory]
end
def assemble_insn(["ceq" | params], memory) when length(params) == 3 do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 8 + modes | memory]
end
def assemble_insn(["srel", val], memory) do
{[val], modes} = parse_params([val], [:read])
[val, 9 + modes | memory]
end
def assemble_insn(["hlt"], memory) do
[99 | memory]
end
def assemble_insn(["data" | data], memory) do
data =
data
|> Enum.map(&String.to_integer/1)
|> Enum.reverse()
data ++ memory
end
# macros
assemble_macro ["mov", src, dest],
"""
add 0, #{src}, #{dest}
"""
assemble_macro ["jmp", dest],
"""
jnz _self, #{dest}
"""
assemble_macro ["sub", a, b, dest],
"""
mul #{b}, -1, #{dest}
add #{a}, $#{dest}, #{dest}
"""
assemble_macro ["not", src, dest],
"""
sub 1, #{src}, #{dest}
"""
assemble_macro ["cle", a, b, dest],
"""
clt #{a}, #{b}, #{dest}
jnz $#{dest}, end
ceq #{a}, #{b}, #{dest}
end:
"""
assemble_macro ["cgt", a, b, dest],
"""
cle #{a}, #{b}, #{dest}
not $#{dest}, #{dest}
"""
assemble_macro ["cge", a, b, dest],
"""
clt #{a}, #{b}, #{dest}
not $#{dest}, #{dest}
"""
@doc """ @doc """
Raises the base to to the given power. Raises the base to to the given power.
@ -109,179 +267,72 @@ defmodule Assembler do
Parses assembly instruction parameter values and modes. Parses assembly instruction parameter values and modes.
## Examples ## Examples
iex> Assembler.parse_params("1 2 3", [:read, :read, :read]) iex> Assembler.parse_params(["1", "2", "3"], [:read, :read, :read])
{[1, 2, 3], 11100} {[1, 2, 3], 11100}
iex> Assembler.parse_params("1 2 3", [:read, :read, :write]) iex> Assembler.parse_params(["1", "2", "3"], [:read, :read, :write])
{[1, 2, 3], 1100} {[1, 2, 3], 1100}
iex> Assembler.parse_params("1 2 $3", [:read, :read, :write]) iex> Assembler.parse_params(["1", "2", "$3"], [:read, :read, :write])
{[1, 2, 3], 1100} {[1, 2, 3], 1100}
iex> Assembler.parse_params("$1 2 3", [:read, :read, :write]) iex> Assembler.parse_params(["$1", "2", "3"], [:read, :read, :write])
{[1, 2, 3], 1000} {[1, 2, 3], 1000}
iex> Assembler.parse_params("$label 2 3", [:read, :read, :write]) iex> Assembler.parse_params(["$label", "2", "3"], [:read, :read, :write])
{[{:label, "label"}, 2, 3], 1000} {[{:expr, ["label"]}, 2, 3], 1000}
iex> Assembler.parse_params("1 label 3", [:read, :read, :write]) iex> Assembler.parse_params(["1", "label", "3"], [:read, :read, :write])
{[1, {:label, "label"}, 3], 1100} {[1, {:expr, ["label"]}, 3], 1100}
""" """
def parse_params(params, param_types) do def parse_params(params, param_types) do
params params
|> String.split(" ")
|> Enum.zip(param_types) |> Enum.zip(param_types)
|> Enum.with_index() |> Enum.with_index()
|> Enum.map_reduce(0, fn {{param, type}, index}, modes -> |> Enum.map_reduce(0, fn {{param, type}, index}, modes ->
val = val =
case Regex.run(~r/^\$?(\d+)$/, param) do case Regex.run(~r/^[\$#]?(\d+)$/, param) do
[_, digits] -> [_, digits] ->
String.to_integer(digits) String.to_integer(digits)
_ -> _ ->
[_, name] = Regex.run(~r/^\$?(\w+)$/, param) case param do
{:label, name} "$" <> param ->
{:expr, parse_param_expr(param)}
"#" <> param ->
{:expr, parse_param_expr(param)}
_ ->
{:expr, parse_param_expr(param)}
end
end end
modes = modes =
case type do case type do
:read -> :read ->
if String.starts_with?(param, "$") do cond do
modes String.starts_with?(param, "$") ->
else modes
modes + pow(10, index + 2)
String.starts_with?(param, "#") ->
modes + 2 * pow(10, index + 2)
true ->
modes + pow(10, index + 2)
end end
_ -> :write ->
modes cond do
String.starts_with?(param, "#") ->
modes + 2 * pow(10, index + 2)
true ->
modes
end
end end
{val, modes} {val, modes}
end) end)
end end
@doc """ def parse_param_expr(param) do
Assembles the given instruction and adds it to the memory (in reverse order). {:ok, expr, _, _, _, _} = ExpressionEvaluator.parse(param)
""" expr
def assemble_instruction(insn, memory \\ [])
@doc """
Assemble add instruction.
## Examples
iex> Assembler.assemble_instruction("add 1 2 3")
[3, 2, 1, 1101]
"""
def assemble_instruction("add " <> params, memory) do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 1 + modes | memory]
end
@doc """
Assemble multiply instruction.
## Examples
iex> Assembler.assemble_instruction("mul 1 2 3")
[3, 2, 1, 1102]
"""
def assemble_instruction("mul " <> params, memory) do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 2 + modes | memory]
end
@doc """
Assemble halt instruction.
## Examples
iex> Assembler.assemble_instruction("hlt")
[99]
"""
def assemble_instruction("hlt", memory) do
[99 | memory]
end
@doc """
Assemble input instruction.
## Examples
iex> Assembler.assemble_instruction("in 7")
[7, 3]
"""
def assemble_instruction("in " <> params, memory) do
{[dest], modes} = parse_params(params, [:write])
[dest, 3 + modes | memory]
end
@doc """
Assemble input instruction.
## Examples
iex> Assembler.assemble_instruction("out 7")
[7, 104]
iex> Assembler.assemble_instruction("out $7")
[7, 4]
"""
def assemble_instruction("out " <> params, memory) do
{[val], modes} = parse_params(params, [:read])
[val, 4 + modes | memory]
end
@doc """
Assemble jump-if-non-zero instruction.
## Examples
iex> Assembler.assemble_instruction("jnz 1 2")
[2, 1, 105]
iex> Assembler.assemble_instruction("jnz $var 2")
[2, {:label, "var"}, 5]
"""
def assemble_instruction("jnz " <> params, memory) do
{[target, dest], modes} = parse_params(params, [:read, :read])
[dest, target, 5 + modes | memory]
end
@doc """
Assemble jump-if-zero instruction.
## Examples
iex> Assembler.assemble_instruction("jez 1 2")
[2, 1, 106]
iex> Assembler.assemble_instruction("jez $var 2")
[2, {:label, "var"}, 6]
"""
def assemble_instruction("jez " <> params, memory) do
{[target, dest], modes} = parse_params(params, [:read, :read])
[dest, target, 6 + modes | memory]
end
@doc """
Assemble compare-less-than instruction.
## Examples
iex> Assembler.assemble_instruction("clt 1 2 3")
[3, 2, 1, 1107]
iex> Assembler.assemble_instruction("clt $1 $2 3")
[3, 2, 1, 7]
"""
def assemble_instruction("clt " <> params, memory) do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 7 + modes | memory]
end
@doc """
Assemble compare-equal instruction.
## Examples
iex> Assembler.assemble_instruction("ceq 1 2 3")
[3, 2, 1, 1108]
iex> Assembler.assemble_instruction("ceq $1 $2 3")
[3, 2, 1, 8]
"""
def assemble_instruction("ceq " <> params, memory) do
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
[dest, b, a, 8 + modes | memory]
end end
end end

View File

@ -1,93 +1,106 @@
defmodule ExpressionEvaluator do defmodule ExpressionEvaluator do
def eval(expr, vars) do import NimbleParsec
expr
|> String.to_charlist()
|> tokenize()
|> run(vars)
# |> parse() number = integer(min: 1)
# |> run(vars) variable = ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
whitespace = ascii_string([?\s], min: 1)
factor =
choice([
ignore(ascii_char([?(]))
|> concat(parsec(:expr))
|> ignore(ascii_char([?)])),
number,
variable,
ignore(ascii_char([?-]))
|> concat(number)
|> tag(:neg),
ignore(ascii_char([?-]))
|> concat(variable)
|> tag(:neg)
])
defcombinatorp(
:term,
choice([
factor
|> optional(ignore(whitespace))
|> ignore(ascii_char([?*]))
|> optional(ignore(whitespace))
|> concat(parsec(:term))
|> tag(:mul),
factor
|> optional(ignore(whitespace))
|> ignore(ascii_char([?/]))
|> optional(ignore(whitespace))
|> concat(parsec(:term))
|> tag(:div),
factor
])
)
defcombinatorp(
:expr,
choice([
parsec(:term)
|> optional(ignore(whitespace))
|> ignore(ascii_char([?+]))
|> optional(ignore(whitespace))
|> concat(parsec(:expr))
|> tag(:add),
parsec(:term)
|> optional(ignore(whitespace))
|> ignore(ascii_char([?-]))
|> optional(ignore(whitespace))
|> concat(parsec(:expr))
|> tag(:sub),
parsec(:term)
])
)
defparsec(:parse, parsec(:expr))
def eval(expr, vars \\ %{})
def eval(expr, vars) when is_map(vars) do
eval(expr, fn name ->
Map.fetch!(vars, name)
end)
end end
@digits ?0..?9 def eval(expr, get_var) when is_function(get_var) do
@alpha ?a..?z {:ok, expr, _, _, _, _} = parse(expr)
@operators [?+, ?-, ?*] do_eval(expr, get_var)
def do_tokenize([first | _] = str) when first in @digits do
{digits, rest} = Enum.split_while(str, &(&1 in @digits))
num = digits |> to_string() |> String.to_integer()
{{:number, num}, rest}
end end
def do_tokenize([?( | rest]) do def do_eval([{op, [a, b]}], get_var) do
{:lparen, rest} do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
end end
def do_tokenize([?) | rest]) do def do_eval([it], get_var) do
{:rparen, rest} do_eval_single(it, get_var)
end end
def do_tokenize([op | rest]) when op in @operators do def do_eval_single({:neg, [it]}, get_var) do
atom = [op] |> to_string() |> String.to_atom() -1 * do_eval_single(it, get_var)
{{:operator, atom}, rest}
end end
def do_tokenize([first | _] = str) when first in @alpha do def do_eval_single(it, _) when is_integer(it) do
{var, rest} = Enum.split_while(str, &(&1 in @alpha)) it
{{:variable, var}, rest}
end end
def tokenize([]), do: [] def do_eval_single(it, get_var) when is_binary(it) do
get_var.(it)
def tokenize(str) do
{token, rest} =
str
|> Enum.drop_while(&(&1 == ?\s))
|> do_tokenize()
[token | tokenize(rest)]
end end
# def to_rpn([{:number, _} = num | rest]) do def do_eval_single(it, get_var) do
# [num | to_rpn(rest)] do_eval([it], get_var)
# end
#
# def to_rpn([{:operator, _} = op | rest]) do
# [op | to_rpn(rest)]
# end
#
# def to_rpn
# def parse(tokens) do
# {expr, rest} = do_parse(tokens)
# [expr |
# end
def run([:lparen | _] = tokens, vars) do
paren_tokens =
tokens
|> Enum.drop(1)
|> Enum.reduce_while({1, []}, fn token, {count, paren_tokens} ->
count =
case token do
:lparen -> count + 1
:rparen -> count - 1
_ -> count
end
if count == 0 do
{:halt, Enum.reverse(paren_tokens)}
else
{:cont, {count, [token | paren_tokens]}}
end
end)
# paren_expr = run(paren_tokens)
rest_tokens = Enum.drop(tokens, length(paren_tokens))
paren_res = run(paren_tokens, vars)
run([{:number, paren_res} | rest_tokens])
end end
def do_op(:add, a, b), do: a + b
def do_op(:sub, a, b), do: a - b
def do_op(:mul, a, b), do: a * b
def do_op(:div, a, b), do: floor(a / b)
end end

View File

@ -21,6 +21,7 @@ defmodule Aoc19.MixProject do
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
{:nimble_parsec, "~> 0.5.2"}
# {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
] ]

3
mix.lock Normal file
View File

@ -0,0 +1,3 @@
%{
"nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"},
}

View File

@ -1,20 +1,70 @@
defmodule AssemblerTest do defmodule AssemblerTest do
use ExUnit.Case use ExUnit.Case
doctest Assembler doctest Assembler
import Assembler
test "assembles empty program" do test "assembles empty program" do
assert Assembler.assemble("") == [] assert assemble("") == []
end
test "assembles instructions correctly" do
assert assemble("add 1, 2, 3") == [1101, 1, 2, 3]
assert assemble("mul 1, 2, 3") == [1102, 1, 2, 3]
assert assemble("in 7") == [3, 7]
assert assemble("out 7") == [104, 7]
assert assemble("out $7") == [4, 7]
assert assemble("jnz 1, 2") == [1105, 1, 2]
assert assemble("jnz $1, 2") == [1005, 1, 2]
assert assemble("jez 1, 2") == [1106, 1, 2]
assert assemble("jez $1, 2") == [1006, 1, 2]
assert assemble("clt 1, 2, 3") == [1107, 1, 2, 3]
assert assemble("clt $1, $2, $3") == [7, 1, 2, 3]
assert assemble("ceq 1, 2, 3") == [1108, 1, 2, 3]
assert assemble("ceq $1, $2, $3") == [8, 1, 2, 3]
assert assemble("srel 1234") == [109, 1234]
assert assemble("hlt") == [99]
end
test "assembles macros" do
assert assemble("mov 1, 2") == [1101, 0, 1, 2]
assert assemble("jmp 2") == [1105, 1, 2]
assert assemble("sub 5, 3, res\nres: 0") == [1102, 3, -1, 8, 101, 5, 8, 8, 0]
assert assemble("not 4, 4") == [1102, 4, -1, 4, 101, 1, 4, 4]
assert assemble("cle 1, 2, res\nres: 0") == [1107, 1, 2, 11, 1005, 11, 11, 1108, 1, 2, 11, 0]
assert assemble("cgt 1, 2, res\nres: 0") == [
1107,
1,
2,
19,
1005,
19,
11,
1108,
1,
2,
19,
1002,
19,
-1,
19,
101,
1,
19,
19,
0
]
end end
test "assembles simple program" do test "assembles simple program" do
program = """ program = """
add 1 2 10 add 1, 2, 10
mul 2 3 11 mul 2, 3, 11
add $10 $11 12 add $10, $11, 12
hlt hlt
""" """
assert Assembler.assemble(program) == [ assert assemble(program) == [
1101, 1101,
1, 1,
2, 2,
@ -33,13 +83,13 @@ defmodule AssemblerTest do
test "assembles simple program with a label" do test "assembles simple program with a label" do
program = """ program = """
add 1 2 10 add 1, 2, 10
jnz $10 nonZero jnz $10, nonZero
nonZero: nonZero:
hlt hlt
""" """
assert Assembler.assemble(program) == [ assert assemble(program) == [
1101, 1101,
1, 1,
2, 2,
@ -53,12 +103,12 @@ defmodule AssemblerTest do
test "assembles a simple program with a data label" do test "assembles a simple program with a data label" do
program = """ program = """
add 1 2 var add 1, 2, var
out $var out $var
var: 0 var: 0
""" """
assert Assembler.assemble(program) == [ assert assemble(program) == [
1101, 1101,
1, 1,
2, 2,
@ -71,9 +121,29 @@ defmodule AssemblerTest do
test "assembles a program with a _self label" do test "assembles a program with a _self label" do
program = """ program = """
add 1 2 _self add 1, 2, _self
""" """
assert Assembler.assemble(program) == [1101, 1, 2, 3] assert assemble(program) == [1101, 1, 2, 3]
end
test "assembles parameters with expressions" do
assert assemble("add 1, _self + 1, 3") == [1101, 1, 3, 3]
assert assemble("add 1, $(_self + 1), 3") == [101, 1, 3, 3]
assert assemble("""
add 1, label + 4 - 2, 3
label: 42
""") == [1101, 1, 6, 3, 42]
assert assemble("""
add 1, $(label + 4 - 2), 3
label: 42
""") == [101, 1, 6, 3, 42]
end
test "assembles relative mode parameters" do
assert assemble("add #1, #2, #3") == [22201, 1, 2, 3]
assert assemble("in #4") == [203, 4]
end end
end end

19
test/intcode/cmp8.asm Normal file
View File

@ -0,0 +1,19 @@
in res
clt $res, 8, cmpRes
jnz $(res + 1), lessThan
ceq $res, 8, cmpRes
jnz $cmpRes, equal
out 1001
hlt
lessThan:
out 999
hlt
equal:
out 1000
hlt
res: 0
cmpRes: 0

View File

@ -0,0 +1,48 @@
defmodule ExpressionEvaluatorTest do
use ExUnit.Case
doctest ExpressionEvaluator
import ExpressionEvaluator
test "evaluates individual numbers" do
assert eval("1") == 1
end
test "evaluates negated numbers" do
assert eval("-1") == -1
end
test "evaluates variables" do
assert eval("x", %{"x" => 3}) == 3
end
test "evaluates negated variables" do
assert eval("-x", %{"x" => 3}) == -3
assert eval("-x", %{"x" => -3}) == 3
end
test "evaluates binary operations" do
assert eval("1 + 2") == 3
assert eval("3 - 2") == 1
assert eval("3 * 2") == 6
assert eval("10 / 2") == 5
end
test "floors division" do
assert eval("3 / 2") == 1
end
test "obeys operator precedence" do
assert eval("1 + 2 * 3") == 7
assert eval("(1 + 2) * 3") == 9
assert eval("12 - 6 / 2") == 9
assert eval("(12 - 6) / 2") == 3
end
test "evaluates complex expressions" do
assert eval("1 + 2 * (3 - ((4 / 2 + 5) * 6))") == -77
end
test "evaluates complex expressions with variables" do
assert eval("x * ((4 - y) / 6 + z))", %{"x" => 2, "y" => -8, "z" => 5}) == 14
end
end

120
test/intcode/vm_test.exs Normal file
View File

@ -0,0 +1,120 @@
defmodule VMTest do
use ExUnit.Case
doctest Day5
def run(prog, io \\ []) do
parent = self()
pid =
spawn(fn ->
Day5.run(prog, parent)
end)
handle_io(pid, io)
receive do
{:halt, ^pid, memory} ->
{:halt, memory}
{:ok, ^pid, memory} ->
{:ok, memory}
msg ->
IO.inspect(msg)
end
end
def handle_io(_, []), do: :ok
def handle_io(pid, [head | rest]) do
case head do
{:in, input} ->
receive do
{:in, ^pid} -> send(pid, {:in, input})
end
{:out, expected} ->
receive do
{:out, ^pid, output} -> assert output == expected
end
end
handle_io(pid, rest)
end
test "exits" do
assert run([]) == {:ok, []}
end
test "halts" do
assert run([99]) == {:halt, [99]}
end
test "basic position mode instructions" do
assert run([1, 3, 2, 3]) == {:ok, [1, 3, 2, 5]}
assert run([2, 3, 2, 3]) == {:ok, [2, 3, 2, 6]}
end
test "basic immediate mode instructions" do
assert run([1101, 1, 2, 0]) == {:ok, [3, 1, 2, 0]}
assert run([1102, 4, 2, 0]) == {:ok, [8, 4, 2, 0]}
end
test "receives input" do
assert run([3, 1], in: 1234) == {:ok, [3, 1234]}
end
test "produces output" do
assert run([104, 77], out: 77) == {:ok, [104, 77]}
assert run([4, 0], out: 4) == {:ok, [4, 0]}
end
test "jump if non-zero" do
prog = [1105, 1, 4, 99, 1101, 1, 2, 7]
assert run(prog) == {:ok, List.replace_at(prog, 7, 3)}
prog = [1105, 0, 4, 99, 1101, 1, 2, 7]
assert run(prog) == {:halt, prog}
prog = [1005, 0, 4, 99, 1101, 1, 2, 7]
assert run(prog) == {:ok, List.replace_at(prog, 7, 3)}
prog = [5, 0, 9, 99, 1101, 1, 2, 7, 99, 4]
assert run(prog) == {:halt, List.replace_at(prog, 7, 3)}
end
test "jump if zero" do
prog = [1106, 0, 4, 99, 1101, 1, 2, 7]
assert run(prog) == {:ok, List.replace_at(prog, 7, 3)}
prog = [1106, 42, 4, 99, 1101, 1, 2, 7]
assert run(prog) == {:halt, prog}
prog = [1006, 9, 4, 99, 1101, 1, 2, 7, 99, 0]
assert run(prog) == {:halt, List.replace_at(prog, 7, 3)}
prog = [6, 9, 10, 99, 1101, 1, 2, 7, 99, 0, 4]
assert run(prog) == {:halt, List.replace_at(prog, 7, 3)}
end
test "compare less than" do
prog = [1107, 3, 8, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
prog = [1107, 12, 8, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 0)}
prog = [1007, 3, 8, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
prog = [7, 3, 0, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
end
test "compare equal to" do
prog = [1108, 4, 4, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
prog = [1108, 5, 4, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 0)}
prog = [1008, 0, 1008, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
prog = [8, 2, 2, 3]
assert run(prog) == {:ok, List.replace_at(prog, 3, 1)}
end
end