Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 1add491851 | |
Shadowfacts | 968c3ebf95 | |
Shadowfacts | 431af67bb1 | |
Shadowfacts | a096774fa8 | |
Shadowfacts | e1e617de5e | |
Shadowfacts | 2a1c657b9c | |
Shadowfacts | f10b2fdf99 | |
Shadowfacts | d4e68f6062 | |
Shadowfacts | d2af80fa26 | |
Shadowfacts | 49b2a79d0a | |
Shadowfacts | 0f5e7f7907 | |
Shadowfacts | 4db021301b |
|
@ -1,4 +1,5 @@
|
|||
# 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]
|
||||
]
|
||||
|
|
|
@ -8,11 +8,11 @@ defmodule Day5 do
|
|||
|
||||
def run_program do
|
||||
read_program()
|
||||
|> run()
|
||||
|> run_cli()
|
||||
end
|
||||
|
||||
def test do
|
||||
run([
|
||||
run_cli([
|
||||
3,
|
||||
21,
|
||||
1008,
|
||||
|
@ -63,27 +63,65 @@ defmodule Day5 do
|
|||
])
|
||||
end
|
||||
|
||||
def run(memory, ip \\ 0)
|
||||
def run_cli(memory, opts \\ []) do
|
||||
parent = self()
|
||||
|
||||
def run(memory, ip) when ip < length(memory) do
|
||||
IO.puts("IP: #{ip}")
|
||||
IO.inspect(memory)
|
||||
proc =
|
||||
spawn(fn ->
|
||||
run(memory, parent)
|
||||
end)
|
||||
|
||||
case eval(Enum.drop(memory, ip), memory) do
|
||||
{_memory, :halt} ->
|
||||
:halt
|
||||
|
||||
{memory, :cont, offset} ->
|
||||
run(memory, ip + offset)
|
||||
|
||||
{memory, :jump, ip} ->
|
||||
run(memory, ip)
|
||||
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
|
||||
|
||||
def run(memory, _ip), do: memory
|
||||
{: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
|
||||
quote do
|
||||
|
@ -108,12 +146,12 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# halt
|
||||
def eval([99 | _], memory) do
|
||||
def eval([99 | _], memory, _) do
|
||||
{memory, :halt}
|
||||
end
|
||||
|
||||
# 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)
|
||||
b_val = get_param(op, 1, b, memory)
|
||||
|
||||
|
@ -125,7 +163,7 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# 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)
|
||||
b_val = get_param(op, 1, b, memory)
|
||||
|
||||
|
@ -137,8 +175,13 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# input
|
||||
def eval([3, addr | _], memory) do
|
||||
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
|
||||
def eval([3, addr | _], memory, parent) do
|
||||
send(parent, {:in, self()})
|
||||
|
||||
res =
|
||||
receive do
|
||||
{:in, val} -> val
|
||||
end
|
||||
|
||||
{
|
||||
List.replace_at(memory, addr, res),
|
||||
|
@ -148,15 +191,15 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# 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)
|
||||
IO.puts("Output: #{out}")
|
||||
send(parent, {:out, self(), out})
|
||||
|
||||
{memory, :cont, 2}
|
||||
end
|
||||
|
||||
# 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
|
||||
0 ->
|
||||
{memory, :cont, 3}
|
||||
|
@ -167,7 +210,7 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# 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
|
||||
0 ->
|
||||
{memory, :jump, get_param(op, 1, ip, memory)}
|
||||
|
@ -178,7 +221,7 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# 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)
|
||||
b_val = get_param(op, 1, b, memory)
|
||||
|
||||
|
@ -193,7 +236,7 @@ defmodule Day5 do
|
|||
end
|
||||
|
||||
# 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)
|
||||
b_val = get_param(op, 1, b, memory)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
File diff suppressed because one or more lines are too long
|
@ -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
|
|
@ -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
|
|
@ -1,97 +1,255 @@
|
|||
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
|
||||
@asm """
|
||||
in res
|
||||
clt $res 8 cmpRes
|
||||
jnz $cmpRes lessThan
|
||||
ceq $res 8 cmpRes
|
||||
jnz $cmpRes equal
|
||||
out 1001
|
||||
hlt
|
||||
import NimbleParsec
|
||||
import Assembler.Helpers
|
||||
|
||||
lessThan:
|
||||
out 999
|
||||
hlt
|
||||
label =
|
||||
ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||
|> ignore(ascii_char([?:]))
|
||||
|> ignore(repeat(ascii_char([?\s])))
|
||||
|
||||
equal:
|
||||
out 1000
|
||||
hlt
|
||||
param =
|
||||
optional(ignore(ascii_string([?\s], min: 1)))
|
||||
|> 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
|
||||
@asm
|
||||
|> IO.inspect()
|
||||
File.read!("test/intcode/cmp8.asm")
|
||||
|> assemble()
|
||||
|> Day9.run_cli()
|
||||
end
|
||||
|
||||
def assemble(asm) do
|
||||
{memory, labels} =
|
||||
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, labels} = do_assemble(asm)
|
||||
|
||||
memory
|
||||
|> Enum.reverse()
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn
|
||||
{{:label, name}, index} -> get_label(name, index, labels)
|
||||
{it, _} -> it
|
||||
{{:expr, expr}, index} ->
|
||||
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
|
||||
|
||||
@doc """
|
||||
Get the address of a label.
|
||||
def assemble_for_macro(asm, macro_offset) do
|
||||
{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
|
||||
{:expr, expr} -> {:expr, expand_macro_expression(expr, labels, macro_offset)}
|
||||
it -> it
|
||||
end)
|
||||
end
|
||||
|
||||
## Examples
|
||||
iex> Assembler.get_label("test", 1, %{"test" => 14})
|
||||
14
|
||||
iex> Assembler.get_label("_self", 1, %{})
|
||||
1
|
||||
def expand_macro_expression(expr, _, _) when is_integer(expr) do
|
||||
expr
|
||||
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}
|
||||
"""
|
||||
def get_label(name, index, labels)
|
||||
|
||||
def get_label("_self", index, _) do
|
||||
index
|
||||
end
|
||||
assemble_macro ["jmp", dest],
|
||||
"""
|
||||
jnz _self, #{dest}
|
||||
"""
|
||||
|
||||
def get_label(name, _, labels) do
|
||||
Map.fetch!(labels, name)
|
||||
end
|
||||
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 """
|
||||
Raises the base to to the given power.
|
||||
|
@ -109,179 +267,72 @@ defmodule Assembler do
|
|||
Parses assembly instruction parameter values and modes.
|
||||
|
||||
## 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}
|
||||
iex> Assembler.parse_params("1 2 3", [:read, :read, :write])
|
||||
iex> Assembler.parse_params(["1", "2", "3"], [:read, :read, :write])
|
||||
{[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}
|
||||
iex> Assembler.parse_params("$1 2 3", [:read, :read, :write])
|
||||
iex> Assembler.parse_params(["$1", "2", "3"], [:read, :read, :write])
|
||||
{[1, 2, 3], 1000}
|
||||
iex> Assembler.parse_params("$label 2 3", [:read, :read, :write])
|
||||
{[{:label, "label"}, 2, 3], 1000}
|
||||
iex> Assembler.parse_params("1 label 3", [:read, :read, :write])
|
||||
{[1, {:label, "label"}, 3], 1100}
|
||||
iex> Assembler.parse_params(["$label", "2", "3"], [:read, :read, :write])
|
||||
{[{:expr, ["label"]}, 2, 3], 1000}
|
||||
iex> Assembler.parse_params(["1", "label", "3"], [:read, :read, :write])
|
||||
{[1, {:expr, ["label"]}, 3], 1100}
|
||||
"""
|
||||
def parse_params(params, param_types) do
|
||||
params
|
||||
|> String.split(" ")
|
||||
|> Enum.zip(param_types)
|
||||
|> Enum.with_index()
|
||||
|> Enum.map_reduce(0, fn {{param, type}, index}, modes ->
|
||||
val =
|
||||
case Regex.run(~r/^\$?(\d+)$/, param) do
|
||||
case Regex.run(~r/^[\$#]?(\d+)$/, param) do
|
||||
[_, digits] ->
|
||||
String.to_integer(digits)
|
||||
|
||||
_ ->
|
||||
[_, name] = Regex.run(~r/^\$?(\w+)$/, param)
|
||||
{:label, name}
|
||||
case param do
|
||||
"$" <> param ->
|
||||
{:expr, parse_param_expr(param)}
|
||||
|
||||
"#" <> param ->
|
||||
{:expr, parse_param_expr(param)}
|
||||
|
||||
_ ->
|
||||
{:expr, parse_param_expr(param)}
|
||||
end
|
||||
end
|
||||
|
||||
modes =
|
||||
case type do
|
||||
:read ->
|
||||
if String.starts_with?(param, "$") do
|
||||
cond do
|
||||
String.starts_with?(param, "$") ->
|
||||
modes
|
||||
else
|
||||
|
||||
String.starts_with?(param, "#") ->
|
||||
modes + 2 * pow(10, index + 2)
|
||||
|
||||
true ->
|
||||
modes + pow(10, index + 2)
|
||||
end
|
||||
|
||||
_ ->
|
||||
:write ->
|
||||
cond do
|
||||
String.starts_with?(param, "#") ->
|
||||
modes + 2 * pow(10, index + 2)
|
||||
|
||||
true ->
|
||||
modes
|
||||
end
|
||||
end
|
||||
|
||||
{val, modes}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Assembles the given instruction and adds it to the memory (in reverse order).
|
||||
"""
|
||||
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]
|
||||
def parse_param_expr(param) do
|
||||
{:ok, expr, _, _, _, _} = ExpressionEvaluator.parse(param)
|
||||
expr
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,93 +1,106 @@
|
|||
defmodule ExpressionEvaluator do
|
||||
def eval(expr, vars) do
|
||||
expr
|
||||
|> String.to_charlist()
|
||||
|> tokenize()
|
||||
|> run(vars)
|
||||
import NimbleParsec
|
||||
|
||||
# |> parse()
|
||||
number = integer(min: 1)
|
||||
|
||||
# |> run(vars)
|
||||
end
|
||||
variable = ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||
|
||||
@digits ?0..?9
|
||||
@alpha ?a..?z
|
||||
@operators [?+, ?-, ?*]
|
||||
whitespace = ascii_string([?\s], min: 1)
|
||||
|
||||
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
|
||||
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)
|
||||
])
|
||||
|
||||
def do_tokenize([?( | rest]) do
|
||||
{:lparen, rest}
|
||||
end
|
||||
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
|
||||
])
|
||||
)
|
||||
|
||||
def do_tokenize([?) | rest]) do
|
||||
{:rparen, rest}
|
||||
end
|
||||
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)
|
||||
])
|
||||
)
|
||||
|
||||
def do_tokenize([op | rest]) when op in @operators do
|
||||
atom = [op] |> to_string() |> String.to_atom()
|
||||
{{:operator, atom}, rest}
|
||||
end
|
||||
defparsec(:parse, parsec(:expr))
|
||||
|
||||
def do_tokenize([first | _] = str) when first in @alpha do
|
||||
{var, rest} = Enum.split_while(str, &(&1 in @alpha))
|
||||
{{:variable, var}, rest}
|
||||
end
|
||||
def eval(expr, vars \\ %{})
|
||||
|
||||
def tokenize([]), do: []
|
||||
|
||||
def tokenize(str) do
|
||||
{token, rest} =
|
||||
str
|
||||
|> Enum.drop_while(&(&1 == ?\s))
|
||||
|> do_tokenize()
|
||||
|
||||
[token | tokenize(rest)]
|
||||
end
|
||||
|
||||
# def to_rpn([{:number, _} = num | rest]) do
|
||||
# [num | to_rpn(rest)]
|
||||
# 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
|
||||
def eval(expr, vars) when is_map(vars) do
|
||||
eval(expr, fn name ->
|
||||
Map.fetch!(vars, name)
|
||||
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
|
||||
|
||||
def eval(expr, get_var) when is_function(get_var) do
|
||||
{:ok, expr, _, _, _, _} = parse(expr)
|
||||
do_eval(expr, get_var)
|
||||
end
|
||||
|
||||
def do_eval([{op, [a, b]}], get_var) do
|
||||
do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
|
||||
end
|
||||
|
||||
def do_eval([it], get_var) do
|
||||
do_eval_single(it, get_var)
|
||||
end
|
||||
|
||||
def do_eval_single({:neg, [it]}, get_var) do
|
||||
-1 * do_eval_single(it, get_var)
|
||||
end
|
||||
|
||||
def do_eval_single(it, _) when is_integer(it) do
|
||||
it
|
||||
end
|
||||
|
||||
def do_eval_single(it, get_var) when is_binary(it) do
|
||||
get_var.(it)
|
||||
end
|
||||
|
||||
def do_eval_single(it, get_var) do
|
||||
do_eval([it], get_var)
|
||||
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
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -21,6 +21,7 @@ defmodule Aoc19.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:nimble_parsec, "~> 0.5.2"}
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
%{
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"},
|
||||
}
|
|
@ -1,20 +1,70 @@
|
|||
defmodule AssemblerTest do
|
||||
use ExUnit.Case
|
||||
doctest Assembler
|
||||
import Assembler
|
||||
|
||||
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
|
||||
|
||||
test "assembles simple program" do
|
||||
program = """
|
||||
add 1 2 10
|
||||
mul 2 3 11
|
||||
add $10 $11 12
|
||||
add 1, 2, 10
|
||||
mul 2, 3, 11
|
||||
add $10, $11, 12
|
||||
hlt
|
||||
"""
|
||||
|
||||
assert Assembler.assemble(program) == [
|
||||
assert assemble(program) == [
|
||||
1101,
|
||||
1,
|
||||
2,
|
||||
|
@ -33,13 +83,13 @@ defmodule AssemblerTest do
|
|||
|
||||
test "assembles simple program with a label" do
|
||||
program = """
|
||||
add 1 2 10
|
||||
jnz $10 nonZero
|
||||
add 1, 2, 10
|
||||
jnz $10, nonZero
|
||||
nonZero:
|
||||
hlt
|
||||
"""
|
||||
|
||||
assert Assembler.assemble(program) == [
|
||||
assert assemble(program) == [
|
||||
1101,
|
||||
1,
|
||||
2,
|
||||
|
@ -53,12 +103,12 @@ defmodule AssemblerTest do
|
|||
|
||||
test "assembles a simple program with a data label" do
|
||||
program = """
|
||||
add 1 2 var
|
||||
add 1, 2, var
|
||||
out $var
|
||||
var: 0
|
||||
"""
|
||||
|
||||
assert Assembler.assemble(program) == [
|
||||
assert assemble(program) == [
|
||||
1101,
|
||||
1,
|
||||
2,
|
||||
|
@ -71,9 +121,29 @@ defmodule AssemblerTest do
|
|||
|
||||
test "assembles a program with a _self label" do
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue