Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 458210f513 | |
Shadowfacts | fe5bc1de7c | |
Shadowfacts | cbbd6d8692 | |
Shadowfacts | b933c30bea | |
Shadowfacts | 36ea4a177d |
|
@ -1,5 +1,4 @@
|
||||||
# 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]
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,11 +8,11 @@ defmodule Day5 do
|
||||||
|
|
||||||
def run_program do
|
def run_program do
|
||||||
read_program()
|
read_program()
|
||||||
|> run_cli()
|
|> run()
|
||||||
end
|
end
|
||||||
|
|
||||||
def test do
|
def test do
|
||||||
run_cli([
|
run([
|
||||||
3,
|
3,
|
||||||
21,
|
21,
|
||||||
1008,
|
1008,
|
||||||
|
@ -63,65 +63,27 @@ defmodule Day5 do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_cli(memory, opts \\ []) do
|
def run(memory, ip \\ 0)
|
||||||
parent = self()
|
|
||||||
|
|
||||||
proc =
|
def run(memory, ip) when ip < length(memory) do
|
||||||
spawn(fn ->
|
IO.puts("IP: #{ip}")
|
||||||
run(memory, parent)
|
IO.inspect(memory)
|
||||||
end)
|
|
||||||
|
|
||||||
handle_cli_messages(proc, opts)
|
case eval(Enum.drop(memory, ip), memory) do
|
||||||
end
|
{_memory, :halt} ->
|
||||||
|
:halt
|
||||||
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} ->
|
{memory, :cont, offset} ->
|
||||||
run(memory, parent, ip + offset)
|
run(memory, ip + offset)
|
||||||
|
|
||||||
{memory, :jump, ip} ->
|
{memory, :jump, ip} ->
|
||||||
run(memory, parent, ip)
|
run(memory, ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(memory, parent, _) do
|
def run(memory, _ip), do: memory
|
||||||
send(parent, {:ok, self(), memory})
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro opcode(op, expected) do
|
defmacro opcode(op, expected) do
|
||||||
quote do
|
quote do
|
||||||
|
@ -146,12 +108,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)
|
||||||
|
|
||||||
|
@ -163,7 +125,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)
|
||||||
|
|
||||||
|
@ -175,13 +137,8 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# input
|
# input
|
||||||
def eval([3, addr | _], memory, parent) do
|
def eval([3, addr | _], memory) do
|
||||||
send(parent, {:in, self()})
|
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
|
||||||
|
|
||||||
res =
|
|
||||||
receive do
|
|
||||||
{:in, val} -> val
|
|
||||||
end
|
|
||||||
|
|
||||||
{
|
{
|
||||||
List.replace_at(memory, addr, res),
|
List.replace_at(memory, addr, res),
|
||||||
|
@ -191,15 +148,15 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# output
|
# output
|
||||||
def eval([op, param | _], memory, parent) when opcode(op, 4) do
|
def eval([op, param | _], memory) when opcode(op, 4) do
|
||||||
out = get_param(op, 0, param, memory)
|
out = get_param(op, 0, param, memory)
|
||||||
send(parent, {:out, self(), out})
|
IO.puts("Output: #{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}
|
||||||
|
@ -210,7 +167,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)}
|
||||||
|
@ -221,7 +178,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)
|
||||||
|
|
||||||
|
@ -236,7 +193,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)
|
||||||
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
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
|
|
|
@ -1,71 +0,0 @@
|
||||||
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
239
lib/day9/day9.ex
239
lib/day9/day9.ex
|
@ -1,239 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
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,256 +1,98 @@
|
||||||
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
|
||||||
import NimbleParsec
|
@asm """
|
||||||
import Assembler.Helpers
|
in res
|
||||||
|
clt $res 8 cmpRes
|
||||||
|
jnz $cmpRes lessThan
|
||||||
|
ceq $res 8 cmpRes
|
||||||
|
jnz $cmpRes equal
|
||||||
|
out 1001
|
||||||
|
hlt
|
||||||
|
|
||||||
label =
|
lessThan:
|
||||||
ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
out 999
|
||||||
|> ignore(ascii_char([?:]))
|
hlt
|
||||||
|> ignore(repeat(ascii_char([?\s])))
|
|
||||||
|
|
||||||
param =
|
equal:
|
||||||
optional(ignore(ascii_string([?\s], min: 1)))
|
out 1000
|
||||||
|> ascii_string([{:not, ?,}], min: 1)
|
hlt
|
||||||
|
|
||||||
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
|
||||||
File.read!("test/intcode/cmp8.asm")
|
@asm
|
||||||
|
|> IO.inspect()
|
||||||
|> assemble()
|
|> assemble()
|
||||||
|> Day9.run_cli()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(asm) do
|
def assemble(asm) do
|
||||||
{memory, labels} = do_assemble(asm)
|
{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
|
memory
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|> Enum.with_index()
|
|> Enum.with_index()
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
{{:expr, expr}, index} ->
|
{{:label, name}, index} -> get_label(name, index, labels)
|
||||||
expr_tokens =
|
{it, _} -> it
|
||||||
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
|
||||||
|
|
||||||
def assemble_for_macro(asm, macro_offset) do
|
@doc """
|
||||||
{memory, labels} = do_assemble(asm)
|
Get the address of a label.
|
||||||
|
|
||||||
memory
|
`_self` is a special label that resolves to the address of where it's being inserted into the program.
|
||||||
|> Enum.map(fn
|
|
||||||
{:expr, expr} -> {:expr, expand_macro_expression(expr, labels, macro_offset)}
|
## Examples
|
||||||
it -> it
|
iex> Assembler.get_label("test", 1, %{"test" => 14})
|
||||||
end)
|
14
|
||||||
|
iex> Assembler.get_label("_self", 1, %{})
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
def get_label(name, index, labels)
|
||||||
|
|
||||||
|
def get_label("_self", index, _) do
|
||||||
|
index
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_macro_expression(expr, _, _) when is_integer(expr) do
|
def get_label(name, _, labels) do
|
||||||
expr
|
Map.fetch!(labels, name)
|
||||||
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.
|
||||||
|
|
||||||
|
@ -267,72 +109,179 @@ 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])
|
||||||
{[{:expr, ["label"]}, 2, 3], 1000}
|
{[{:label, "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, {:expr, ["label"]}, 3], 1100}
|
{[1, {:label, "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)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
case param do
|
[_, name] = Regex.run(~r/^\$?(\w+)$/, param)
|
||||||
"$" <> param ->
|
{:label, name}
|
||||||
{: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 ->
|
||||||
cond do
|
if String.starts_with?(param, "$") do
|
||||||
String.starts_with?(param, "$") ->
|
modes
|
||||||
modes
|
else
|
||||||
|
modes + pow(10, index + 2)
|
||||||
String.starts_with?(param, "#") ->
|
|
||||||
modes + 2 * pow(10, index + 2)
|
|
||||||
|
|
||||||
true ->
|
|
||||||
modes + pow(10, index + 2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
:write ->
|
_ ->
|
||||||
cond do
|
modes
|
||||||
String.starts_with?(param, "#") ->
|
|
||||||
modes + 2 * pow(10, index + 2)
|
|
||||||
|
|
||||||
true ->
|
|
||||||
modes
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{val, modes}
|
{val, modes}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_param_expr(param) do
|
@doc """
|
||||||
{:ok, expr, _, _, _, _} = ExpressionEvaluator.parse(param)
|
Assembles the given instruction and adds it to the memory (in reverse order).
|
||||||
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
|
||||||
|
|
|
@ -1,106 +1,93 @@
|
||||||
defmodule ExpressionEvaluator do
|
defmodule ExpressionEvaluator do
|
||||||
import NimbleParsec
|
def eval(expr, vars) do
|
||||||
|
expr
|
||||||
|
|> String.to_charlist()
|
||||||
|
|> tokenize()
|
||||||
|
|> run(vars)
|
||||||
|
|
||||||
number = integer(min: 1)
|
# |> parse()
|
||||||
|
|
||||||
variable = ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
# |> run(vars)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
def eval(expr, get_var) when is_function(get_var) do
|
@digits ?0..?9
|
||||||
{:ok, expr, _, _, _, _} = parse(expr)
|
@alpha ?a..?z
|
||||||
do_eval(expr, get_var)
|
@operators [?+, ?-, ?*]
|
||||||
|
|
||||||
|
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_eval([{op, [a, b]}], get_var) do
|
def do_tokenize([?( | rest]) do
|
||||||
do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
|
{:lparen, rest}
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_eval([it], get_var) do
|
def do_tokenize([?) | rest]) do
|
||||||
do_eval_single(it, get_var)
|
{:rparen, rest}
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_eval_single({:neg, [it]}, get_var) do
|
def do_tokenize([op | rest]) when op in @operators do
|
||||||
-1 * do_eval_single(it, get_var)
|
atom = [op] |> to_string() |> String.to_atom()
|
||||||
|
{{:operator, atom}, rest}
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_eval_single(it, _) when is_integer(it) do
|
def do_tokenize([first | _] = str) when first in @alpha do
|
||||||
it
|
{var, rest} = Enum.split_while(str, &(&1 in @alpha))
|
||||||
|
{{:variable, var}, rest}
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_eval_single(it, get_var) when is_binary(it) do
|
def tokenize([]), do: []
|
||||||
get_var.(it)
|
|
||||||
|
def tokenize(str) do
|
||||||
|
{token, rest} =
|
||||||
|
str
|
||||||
|
|> Enum.drop_while(&(&1 == ?\s))
|
||||||
|
|> do_tokenize()
|
||||||
|
|
||||||
|
[token | tokenize(rest)]
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_eval_single(it, get_var) do
|
# def to_rpn([{:number, _} = num | rest]) do
|
||||||
do_eval([it], get_var)
|
# [num | to_rpn(rest)]
|
||||||
end
|
# end
|
||||||
|
#
|
||||||
|
# def to_rpn([{:operator, _} = op | rest]) do
|
||||||
|
# [op | to_rpn(rest)]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def to_rpn
|
||||||
|
|
||||||
def do_op(:add, a, b), do: a + b
|
# def parse(tokens) do
|
||||||
def do_op(:sub, a, b), do: a - b
|
# {expr, rest} = do_parse(tokens)
|
||||||
def do_op(:mul, a, b), do: a * b
|
# [expr |
|
||||||
def do_op(:div, a, b), do: floor(a / b)
|
# 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
|
end
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -21,7 +21,6 @@ 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
3
mix.lock
|
@ -1,3 +0,0 @@
|
||||||
%{
|
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"},
|
|
||||||
}
|
|
|
@ -1,70 +1,20 @@
|
||||||
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 assemble("") == []
|
assert Assembler.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 assemble(program) == [
|
assert Assembler.assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -83,13 +33,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 assemble(program) == [
|
assert Assembler.assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -103,12 +53,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 assemble(program) == [
|
assert Assembler.assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -121,29 +71,9 @@ 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 assemble(program) == [1101, 1, 2, 3]
|
assert Assembler.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
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
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
|
|
|
@ -1,120 +0,0 @@
|
||||||
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