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"
|
# 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]
|
||||||
]
|
]
|
||||||
|
|
103
lib/day5/day5.ex
103
lib/day5/day5.ex
|
@ -8,11 +8,11 @@ defmodule Day5 do
|
||||||
|
|
||||||
def run_program do
|
def run_program do
|
||||||
read_program()
|
read_program()
|
||||||
|> run()
|
|> run_cli()
|
||||||
end
|
end
|
||||||
|
|
||||||
def test do
|
def test do
|
||||||
run([
|
run_cli([
|
||||||
3,
|
3,
|
||||||
21,
|
21,
|
||||||
1008,
|
1008,
|
||||||
|
@ -63,27 +63,65 @@ defmodule Day5 do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(memory, ip \\ 0)
|
def run_cli(memory, opts \\ []) do
|
||||||
|
parent = self()
|
||||||
|
|
||||||
def run(memory, ip) when ip < length(memory) do
|
proc =
|
||||||
IO.puts("IP: #{ip}")
|
spawn(fn ->
|
||||||
IO.inspect(memory)
|
run(memory, parent)
|
||||||
|
end)
|
||||||
|
|
||||||
case eval(Enum.drop(memory, ip), memory) do
|
handle_cli_messages(proc, opts)
|
||||||
{_memory, :halt} ->
|
|
||||||
:halt
|
|
||||||
|
|
||||||
{memory, :cont, offset} ->
|
|
||||||
run(memory, ip + offset)
|
|
||||||
|
|
||||||
{memory, :jump, ip} ->
|
|
||||||
run(memory, ip)
|
|
||||||
end
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(memory, _ip), do: memory
|
def handle_cli_messages(proc, opts) do
|
||||||
|
receive do
|
||||||
|
{:halt, _, memory} ->
|
||||||
|
if Keyword.get(opts, :memory, false) do
|
||||||
|
memory
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, _, memory} ->
|
||||||
|
if Keyword.get(opts, :memory, false) do
|
||||||
|
memory
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
{:in, _} ->
|
||||||
|
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
|
||||||
|
send(proc, {:in, res})
|
||||||
|
handle_cli_messages(proc, opts)
|
||||||
|
|
||||||
|
{:out, _, out} ->
|
||||||
|
IO.puts("Output: #{out}")
|
||||||
|
handle_cli_messages(proc, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(memory, parent, ip \\ 0)
|
||||||
|
|
||||||
|
def run(memory, parent, ip) when ip < length(memory) do
|
||||||
|
# IO.puts("IP: #{ip}")
|
||||||
|
# IO.inspect(memory)
|
||||||
|
|
||||||
|
case eval(Enum.drop(memory, ip), memory, parent) do
|
||||||
|
{memory, :halt} ->
|
||||||
|
send(parent, {:halt, self(), memory})
|
||||||
|
|
||||||
|
{memory, :cont, offset} ->
|
||||||
|
run(memory, parent, ip + offset)
|
||||||
|
|
||||||
|
{memory, :jump, ip} ->
|
||||||
|
run(memory, parent, ip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(memory, parent, _) do
|
||||||
|
send(parent, {:ok, self(), memory})
|
||||||
|
end
|
||||||
|
|
||||||
defmacro opcode(op, expected) do
|
defmacro opcode(op, expected) do
|
||||||
quote do
|
quote do
|
||||||
|
@ -108,12 +146,12 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# halt
|
# halt
|
||||||
def eval([99 | _], memory) do
|
def eval([99 | _], memory, _) do
|
||||||
{memory, :halt}
|
{memory, :halt}
|
||||||
end
|
end
|
||||||
|
|
||||||
# add
|
# add
|
||||||
def eval([op, a, b, dest | _], memory) when opcode(op, 1) do
|
def eval([op, a, b, dest | _], memory, _) when opcode(op, 1) do
|
||||||
a_val = get_param(op, 0, a, memory)
|
a_val = get_param(op, 0, a, memory)
|
||||||
b_val = get_param(op, 1, b, memory)
|
b_val = get_param(op, 1, b, memory)
|
||||||
|
|
||||||
|
@ -125,7 +163,7 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# multiply
|
# multiply
|
||||||
def eval([op, a, b, dest | _], memory) when opcode(op, 2) do
|
def eval([op, a, b, dest | _], memory, _) when opcode(op, 2) do
|
||||||
a_val = get_param(op, 0, a, memory)
|
a_val = get_param(op, 0, a, memory)
|
||||||
b_val = get_param(op, 1, b, memory)
|
b_val = get_param(op, 1, b, memory)
|
||||||
|
|
||||||
|
@ -137,8 +175,13 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# input
|
# input
|
||||||
def eval([3, addr | _], memory) do
|
def eval([3, addr | _], memory, parent) do
|
||||||
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
|
send(parent, {:in, self()})
|
||||||
|
|
||||||
|
res =
|
||||||
|
receive do
|
||||||
|
{:in, val} -> val
|
||||||
|
end
|
||||||
|
|
||||||
{
|
{
|
||||||
List.replace_at(memory, addr, res),
|
List.replace_at(memory, addr, res),
|
||||||
|
@ -148,15 +191,15 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# output
|
# output
|
||||||
def eval([op, param | _], memory) when opcode(op, 4) do
|
def eval([op, param | _], memory, parent) when opcode(op, 4) do
|
||||||
out = get_param(op, 0, param, memory)
|
out = get_param(op, 0, param, memory)
|
||||||
IO.puts("Output: #{out}")
|
send(parent, {:out, self(), out})
|
||||||
|
|
||||||
{memory, :cont, 2}
|
{memory, :cont, 2}
|
||||||
end
|
end
|
||||||
|
|
||||||
# jump if non-zero
|
# jump if non-zero
|
||||||
def eval([op, condition, ip | _], memory) when opcode(op, 5) do
|
def eval([op, condition, ip | _], memory, _) when opcode(op, 5) do
|
||||||
case get_param(op, 0, condition, memory) do
|
case get_param(op, 0, condition, memory) do
|
||||||
0 ->
|
0 ->
|
||||||
{memory, :cont, 3}
|
{memory, :cont, 3}
|
||||||
|
@ -167,7 +210,7 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# jump if zero
|
# jump if zero
|
||||||
def eval([op, condition, ip | _], memory) when opcode(op, 6) do
|
def eval([op, condition, ip | _], memory, _) when opcode(op, 6) do
|
||||||
case get_param(op, 0, condition, memory) do
|
case get_param(op, 0, condition, memory) do
|
||||||
0 ->
|
0 ->
|
||||||
{memory, :jump, get_param(op, 1, ip, memory)}
|
{memory, :jump, get_param(op, 1, ip, memory)}
|
||||||
|
@ -178,7 +221,7 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# less than
|
# less than
|
||||||
def eval([op, a, b, dest | _], memory) when opcode(op, 7) do
|
def eval([op, a, b, dest | _], memory, _) when opcode(op, 7) do
|
||||||
a_val = get_param(op, 0, a, memory)
|
a_val = get_param(op, 0, a, memory)
|
||||||
b_val = get_param(op, 1, b, memory)
|
b_val = get_param(op, 1, b, memory)
|
||||||
|
|
||||||
|
@ -193,7 +236,7 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# equals
|
# equals
|
||||||
def eval([op, a, b, dest | _], memory) when opcode(op, 8) do
|
def eval([op, a, b, dest | _], memory, _) when opcode(op, 8) do
|
||||||
a_val = get_param(op, 0, a, memory)
|
a_val = get_param(op, 0, a, memory)
|
||||||
b_val = get_param(op, 1, b, memory)
|
b_val = get_param(op, 1, b, memory)
|
||||||
|
|
||||||
|
|
|
@ -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,98 +1,256 @@
|
||||||
|
defmodule Assembler.Helpers do
|
||||||
|
defmacro assemble_macro(match, asm) do
|
||||||
|
quote do
|
||||||
|
def assemble_insn(unquote(match), memory) do
|
||||||
|
macro_mem = assemble_for_macro(unquote(asm), length(memory))
|
||||||
|
macro_mem ++ memory
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defmodule Assembler do
|
defmodule Assembler do
|
||||||
@asm """
|
import NimbleParsec
|
||||||
in res
|
import Assembler.Helpers
|
||||||
clt $res 8 cmpRes
|
|
||||||
jnz $cmpRes lessThan
|
|
||||||
ceq $res 8 cmpRes
|
|
||||||
jnz $cmpRes equal
|
|
||||||
out 1001
|
|
||||||
hlt
|
|
||||||
|
|
||||||
lessThan:
|
label =
|
||||||
out 999
|
ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||||
hlt
|
|> ignore(ascii_char([?:]))
|
||||||
|
|> ignore(repeat(ascii_char([?\s])))
|
||||||
|
|
||||||
equal:
|
param =
|
||||||
out 1000
|
optional(ignore(ascii_string([?\s], min: 1)))
|
||||||
hlt
|
|> ascii_string([{:not, ?,}], min: 1)
|
||||||
|
|
||||||
|
data_label =
|
||||||
|
label
|
||||||
|
|> concat(param)
|
||||||
|
|> tag(:data_label)
|
||||||
|
|
||||||
|
instruction =
|
||||||
|
ignore(ascii_string([?\s, ?\t], min: 0))
|
||||||
|
|> ascii_string([?a..?z], min: 1)
|
||||||
|
|> repeat(param |> ignore(ascii_char([?,])))
|
||||||
|
|> optional(param)
|
||||||
|
|> tag(:insn)
|
||||||
|
|
||||||
|
defparsec(
|
||||||
|
:parse_line,
|
||||||
|
choice([
|
||||||
|
data_label,
|
||||||
|
label |> tag(:label),
|
||||||
|
instruction,
|
||||||
|
eos()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
res: 0
|
|
||||||
cmpRes: 0
|
|
||||||
"""
|
|
||||||
def test do
|
def test do
|
||||||
@asm
|
File.read!("test/intcode/cmp8.asm")
|
||||||
|> IO.inspect()
|
|
||||||
|> assemble()
|
|> assemble()
|
||||||
|
|> Day9.run_cli()
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(asm) do
|
def assemble(asm) do
|
||||||
{memory, labels} =
|
{memory, labels} = do_assemble(asm)
|
||||||
asm
|
|
||||||
|> String.split("\n")
|
|
||||||
|> Enum.reject(&(String.length(&1) == 0))
|
|
||||||
|> Enum.reduce({[], %{}}, fn line, {memory, labels} ->
|
|
||||||
cond do
|
|
||||||
Regex.match?(~r/^\s+$/, line) ->
|
|
||||||
{memory, labels}
|
|
||||||
|
|
||||||
String.starts_with?(line, " ") ->
|
|
||||||
{
|
|
||||||
assemble_instruction(String.slice(line, 2..-1), memory),
|
|
||||||
labels
|
|
||||||
}
|
|
||||||
|
|
||||||
String.ends_with?(line, ":") ->
|
|
||||||
{
|
|
||||||
memory,
|
|
||||||
Map.put(labels, String.slice(line, 0..-2), length(memory))
|
|
||||||
}
|
|
||||||
|
|
||||||
Regex.match?(~r/^\w+: \d+$/, line) ->
|
|
||||||
[name, value] = String.split(line, ": ")
|
|
||||||
value = String.to_integer(value)
|
|
||||||
|
|
||||||
{
|
|
||||||
[value | memory],
|
|
||||||
Map.put(labels, name, length(memory))
|
|
||||||
}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
IO.inspect("Ignoring line: #{line}")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
# IO.inspect(labels)
|
|
||||||
|
|
||||||
memory
|
memory
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|> Enum.with_index()
|
|> Enum.with_index()
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
{{:label, name}, index} -> get_label(name, index, labels)
|
{{:expr, expr}, index} ->
|
||||||
{it, _} -> it
|
expr_tokens =
|
||||||
|
if is_binary(expr) do
|
||||||
|
{:ok, tokens, _, _, _, _} = ExpressionEvaluator.parse(expr)
|
||||||
|
tokens
|
||||||
|
else
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
|
||||||
|
ExpressionEvaluator.do_eval(expr_tokens, fn
|
||||||
|
"_self" -> index
|
||||||
|
name -> Map.fetch!(labels, name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{it, _} ->
|
||||||
|
it
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
def assemble_for_macro(asm, macro_offset) do
|
||||||
Get the address of a label.
|
{memory, labels} = do_assemble(asm)
|
||||||
|
|
||||||
`_self` is a special label that resolves to the address of where it's being inserted into the program.
|
memory
|
||||||
|
|> Enum.map(fn
|
||||||
## Examples
|
{:expr, expr} -> {:expr, expand_macro_expression(expr, labels, macro_offset)}
|
||||||
iex> Assembler.get_label("test", 1, %{"test" => 14})
|
it -> it
|
||||||
14
|
end)
|
||||||
iex> Assembler.get_label("_self", 1, %{})
|
|
||||||
1
|
|
||||||
"""
|
|
||||||
def get_label(name, index, labels)
|
|
||||||
|
|
||||||
def get_label("_self", index, _) do
|
|
||||||
index
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_label(name, _, labels) do
|
def expand_macro_expression(expr, _, _) when is_integer(expr) do
|
||||||
Map.fetch!(labels, name)
|
expr
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def expand_macro_expression(expr, macro_labels, macro_offset) when is_binary(expr) do
|
||||||
|
case Map.get(macro_labels, expr) do
|
||||||
|
nil -> expr
|
||||||
|
val -> macro_offset + val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def expand_macro_expression(expr, macro_labels, macro_offset) when is_list(expr) do
|
||||||
|
Enum.map(expr, fn expr ->
|
||||||
|
expand_macro_expression(expr, macro_labels, macro_offset)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def expand_macro_expression({op, values}, macro_labels, macro_offset) do
|
||||||
|
{op, expand_macro_expression(values, macro_labels, macro_offset)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_assemble(asm) do
|
||||||
|
asm
|
||||||
|
|> String.trim_trailing("\n")
|
||||||
|
|> String.split("\n")
|
||||||
|
|> Enum.map(fn line ->
|
||||||
|
case parse_line(line) do
|
||||||
|
{:ok, res, _, _, _, _} ->
|
||||||
|
res
|
||||||
|
|
||||||
|
err ->
|
||||||
|
raise "Unable to parse line: '#{line}':\n#{inspect(err)}"
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Enum.reduce({[], %{}}, fn line, acc ->
|
||||||
|
assemble_line(line, acc)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_line([], acc), do: acc
|
||||||
|
|
||||||
|
def assemble_line([label: [name]], {memory, labels}) do
|
||||||
|
{
|
||||||
|
memory,
|
||||||
|
Map.put(labels, name, length(memory))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_line([data_label: [name, value]], {memory, labels}) do
|
||||||
|
value = {:expr, value}
|
||||||
|
|
||||||
|
{
|
||||||
|
[value | memory],
|
||||||
|
Map.put(labels, name, length(memory))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_line([insn: insn], {memory, labels}) do
|
||||||
|
{
|
||||||
|
assemble_insn(insn, memory),
|
||||||
|
labels
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["add" | params], memory) when length(params) == 3 do
|
||||||
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
[dest, b, a, 1 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["mul" | params], memory) when length(params) == 3 do
|
||||||
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
[dest, b, a, 2 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["in", dest], memory) do
|
||||||
|
{[dest], modes} = parse_params([dest], [:write])
|
||||||
|
[dest, 3 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["out", src], memory) do
|
||||||
|
{[src], modes} = parse_params([src], [:read])
|
||||||
|
[src, 4 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["jnz" | params], memory) when length(params) == 2 do
|
||||||
|
{[target, dest], modes} = parse_params(params, [:read, :read])
|
||||||
|
[dest, target, 5 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["jez" | params], memory) when length(params) == 2 do
|
||||||
|
{[target, dest], modes} = parse_params(params, [:read, :read])
|
||||||
|
[dest, target, 6 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["clt" | params], memory) when length(params) == 3 do
|
||||||
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
[dest, b, a, 7 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["ceq" | params], memory) when length(params) == 3 do
|
||||||
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
[dest, b, a, 8 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["srel", val], memory) do
|
||||||
|
{[val], modes} = parse_params([val], [:read])
|
||||||
|
[val, 9 + modes | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["hlt"], memory) do
|
||||||
|
[99 | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_insn(["data" | data], memory) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> Enum.map(&String.to_integer/1)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
data ++ memory
|
||||||
|
end
|
||||||
|
|
||||||
|
# macros
|
||||||
|
assemble_macro ["mov", src, dest],
|
||||||
|
"""
|
||||||
|
add 0, #{src}, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["jmp", dest],
|
||||||
|
"""
|
||||||
|
jnz _self, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["sub", a, b, dest],
|
||||||
|
"""
|
||||||
|
mul #{b}, -1, #{dest}
|
||||||
|
add #{a}, $#{dest}, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["not", src, dest],
|
||||||
|
"""
|
||||||
|
sub 1, #{src}, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["cle", a, b, dest],
|
||||||
|
"""
|
||||||
|
clt #{a}, #{b}, #{dest}
|
||||||
|
jnz $#{dest}, end
|
||||||
|
ceq #{a}, #{b}, #{dest}
|
||||||
|
|
||||||
|
end:
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["cgt", a, b, dest],
|
||||||
|
"""
|
||||||
|
cle #{a}, #{b}, #{dest}
|
||||||
|
not $#{dest}, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
assemble_macro ["cge", a, b, dest],
|
||||||
|
"""
|
||||||
|
clt #{a}, #{b}, #{dest}
|
||||||
|
not $#{dest}, #{dest}
|
||||||
|
"""
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Raises the base to to the given power.
|
Raises the base to to the given power.
|
||||||
|
|
||||||
|
@ -109,179 +267,72 @@ defmodule Assembler do
|
||||||
Parses assembly instruction parameter values and modes.
|
Parses assembly instruction parameter values and modes.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
iex> Assembler.parse_params("1 2 3", [:read, :read, :read])
|
iex> Assembler.parse_params(["1", "2", "3"], [:read, :read, :read])
|
||||||
{[1, 2, 3], 11100}
|
{[1, 2, 3], 11100}
|
||||||
iex> Assembler.parse_params("1 2 3", [:read, :read, :write])
|
iex> Assembler.parse_params(["1", "2", "3"], [:read, :read, :write])
|
||||||
{[1, 2, 3], 1100}
|
{[1, 2, 3], 1100}
|
||||||
iex> Assembler.parse_params("1 2 $3", [:read, :read, :write])
|
iex> Assembler.parse_params(["1", "2", "$3"], [:read, :read, :write])
|
||||||
{[1, 2, 3], 1100}
|
{[1, 2, 3], 1100}
|
||||||
iex> Assembler.parse_params("$1 2 3", [:read, :read, :write])
|
iex> Assembler.parse_params(["$1", "2", "3"], [:read, :read, :write])
|
||||||
{[1, 2, 3], 1000}
|
{[1, 2, 3], 1000}
|
||||||
iex> Assembler.parse_params("$label 2 3", [:read, :read, :write])
|
iex> Assembler.parse_params(["$label", "2", "3"], [:read, :read, :write])
|
||||||
{[{:label, "label"}, 2, 3], 1000}
|
{[{:expr, ["label"]}, 2, 3], 1000}
|
||||||
iex> Assembler.parse_params("1 label 3", [:read, :read, :write])
|
iex> Assembler.parse_params(["1", "label", "3"], [:read, :read, :write])
|
||||||
{[1, {:label, "label"}, 3], 1100}
|
{[1, {:expr, ["label"]}, 3], 1100}
|
||||||
"""
|
"""
|
||||||
def parse_params(params, param_types) do
|
def parse_params(params, param_types) do
|
||||||
params
|
params
|
||||||
|> String.split(" ")
|
|
||||||
|> Enum.zip(param_types)
|
|> Enum.zip(param_types)
|
||||||
|> Enum.with_index()
|
|> Enum.with_index()
|
||||||
|> Enum.map_reduce(0, fn {{param, type}, index}, modes ->
|
|> Enum.map_reduce(0, fn {{param, type}, index}, modes ->
|
||||||
val =
|
val =
|
||||||
case Regex.run(~r/^\$?(\d+)$/, param) do
|
case Regex.run(~r/^[\$#]?(\d+)$/, param) do
|
||||||
[_, digits] ->
|
[_, digits] ->
|
||||||
String.to_integer(digits)
|
String.to_integer(digits)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
[_, name] = Regex.run(~r/^\$?(\w+)$/, param)
|
case param do
|
||||||
{:label, name}
|
"$" <> param ->
|
||||||
|
{:expr, parse_param_expr(param)}
|
||||||
|
|
||||||
|
"#" <> param ->
|
||||||
|
{:expr, parse_param_expr(param)}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:expr, parse_param_expr(param)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
modes =
|
modes =
|
||||||
case type do
|
case type do
|
||||||
:read ->
|
:read ->
|
||||||
if String.starts_with?(param, "$") do
|
cond do
|
||||||
modes
|
String.starts_with?(param, "$") ->
|
||||||
else
|
modes
|
||||||
modes + pow(10, index + 2)
|
|
||||||
|
String.starts_with?(param, "#") ->
|
||||||
|
modes + 2 * pow(10, index + 2)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
modes + pow(10, index + 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
:write ->
|
||||||
modes
|
cond do
|
||||||
|
String.starts_with?(param, "#") ->
|
||||||
|
modes + 2 * pow(10, index + 2)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
modes
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
{val, modes}
|
{val, modes}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
def parse_param_expr(param) do
|
||||||
Assembles the given instruction and adds it to the memory (in reverse order).
|
{:ok, expr, _, _, _, _} = ExpressionEvaluator.parse(param)
|
||||||
"""
|
expr
|
||||||
def assemble_instruction(insn, memory \\ [])
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble add instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("add 1 2 3")
|
|
||||||
[3, 2, 1, 1101]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("add " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 1 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble multiply instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("mul 1 2 3")
|
|
||||||
[3, 2, 1, 1102]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("mul " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 2 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble halt instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("hlt")
|
|
||||||
[99]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("hlt", memory) do
|
|
||||||
[99 | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble input instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("in 7")
|
|
||||||
[7, 3]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("in " <> params, memory) do
|
|
||||||
{[dest], modes} = parse_params(params, [:write])
|
|
||||||
|
|
||||||
[dest, 3 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble input instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("out 7")
|
|
||||||
[7, 104]
|
|
||||||
iex> Assembler.assemble_instruction("out $7")
|
|
||||||
[7, 4]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("out " <> params, memory) do
|
|
||||||
{[val], modes} = parse_params(params, [:read])
|
|
||||||
|
|
||||||
[val, 4 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble jump-if-non-zero instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("jnz 1 2")
|
|
||||||
[2, 1, 105]
|
|
||||||
iex> Assembler.assemble_instruction("jnz $var 2")
|
|
||||||
[2, {:label, "var"}, 5]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("jnz " <> params, memory) do
|
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
|
||||||
|
|
||||||
[dest, target, 5 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble jump-if-zero instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("jez 1 2")
|
|
||||||
[2, 1, 106]
|
|
||||||
iex> Assembler.assemble_instruction("jez $var 2")
|
|
||||||
[2, {:label, "var"}, 6]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("jez " <> params, memory) do
|
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
|
||||||
|
|
||||||
[dest, target, 6 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble compare-less-than instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("clt 1 2 3")
|
|
||||||
[3, 2, 1, 1107]
|
|
||||||
iex> Assembler.assemble_instruction("clt $1 $2 3")
|
|
||||||
[3, 2, 1, 7]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("clt " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 7 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Assemble compare-equal instruction.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> Assembler.assemble_instruction("ceq 1 2 3")
|
|
||||||
[3, 2, 1, 1108]
|
|
||||||
iex> Assembler.assemble_instruction("ceq $1 $2 3")
|
|
||||||
[3, 2, 1, 8]
|
|
||||||
"""
|
|
||||||
def assemble_instruction("ceq " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 8 + modes | memory]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,93 +1,106 @@
|
||||||
defmodule ExpressionEvaluator do
|
defmodule ExpressionEvaluator do
|
||||||
def eval(expr, vars) do
|
import NimbleParsec
|
||||||
expr
|
|
||||||
|> String.to_charlist()
|
|
||||||
|> tokenize()
|
|
||||||
|> run(vars)
|
|
||||||
|
|
||||||
# |> parse()
|
number = integer(min: 1)
|
||||||
|
|
||||||
# |> run(vars)
|
variable = ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||||
|
|
||||||
|
whitespace = ascii_string([?\s], min: 1)
|
||||||
|
|
||||||
|
factor =
|
||||||
|
choice([
|
||||||
|
ignore(ascii_char([?(]))
|
||||||
|
|> concat(parsec(:expr))
|
||||||
|
|> ignore(ascii_char([?)])),
|
||||||
|
number,
|
||||||
|
variable,
|
||||||
|
ignore(ascii_char([?-]))
|
||||||
|
|> concat(number)
|
||||||
|
|> tag(:neg),
|
||||||
|
ignore(ascii_char([?-]))
|
||||||
|
|> concat(variable)
|
||||||
|
|> tag(:neg)
|
||||||
|
])
|
||||||
|
|
||||||
|
defcombinatorp(
|
||||||
|
:term,
|
||||||
|
choice([
|
||||||
|
factor
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> ignore(ascii_char([?*]))
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> concat(parsec(:term))
|
||||||
|
|> tag(:mul),
|
||||||
|
factor
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> ignore(ascii_char([?/]))
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> concat(parsec(:term))
|
||||||
|
|> tag(:div),
|
||||||
|
factor
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
defcombinatorp(
|
||||||
|
:expr,
|
||||||
|
choice([
|
||||||
|
parsec(:term)
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> ignore(ascii_char([?+]))
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> concat(parsec(:expr))
|
||||||
|
|> tag(:add),
|
||||||
|
parsec(:term)
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> ignore(ascii_char([?-]))
|
||||||
|
|> optional(ignore(whitespace))
|
||||||
|
|> concat(parsec(:expr))
|
||||||
|
|> tag(:sub),
|
||||||
|
parsec(:term)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
defparsec(:parse, parsec(:expr))
|
||||||
|
|
||||||
|
def eval(expr, vars \\ %{})
|
||||||
|
|
||||||
|
def eval(expr, vars) when is_map(vars) do
|
||||||
|
eval(expr, fn name ->
|
||||||
|
Map.fetch!(vars, name)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@digits ?0..?9
|
def eval(expr, get_var) when is_function(get_var) do
|
||||||
@alpha ?a..?z
|
{:ok, expr, _, _, _, _} = parse(expr)
|
||||||
@operators [?+, ?-, ?*]
|
do_eval(expr, get_var)
|
||||||
|
|
||||||
def do_tokenize([first | _] = str) when first in @digits do
|
|
||||||
{digits, rest} = Enum.split_while(str, &(&1 in @digits))
|
|
||||||
num = digits |> to_string() |> String.to_integer()
|
|
||||||
{{:number, num}, rest}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_tokenize([?( | rest]) do
|
def do_eval([{op, [a, b]}], get_var) do
|
||||||
{:lparen, rest}
|
do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_tokenize([?) | rest]) do
|
def do_eval([it], get_var) do
|
||||||
{:rparen, rest}
|
do_eval_single(it, get_var)
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_tokenize([op | rest]) when op in @operators do
|
def do_eval_single({:neg, [it]}, get_var) do
|
||||||
atom = [op] |> to_string() |> String.to_atom()
|
-1 * do_eval_single(it, get_var)
|
||||||
{{:operator, atom}, rest}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_tokenize([first | _] = str) when first in @alpha do
|
def do_eval_single(it, _) when is_integer(it) do
|
||||||
{var, rest} = Enum.split_while(str, &(&1 in @alpha))
|
it
|
||||||
{{:variable, var}, rest}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def tokenize([]), do: []
|
def do_eval_single(it, get_var) when is_binary(it) do
|
||||||
|
get_var.(it)
|
||||||
def tokenize(str) do
|
|
||||||
{token, rest} =
|
|
||||||
str
|
|
||||||
|> Enum.drop_while(&(&1 == ?\s))
|
|
||||||
|> do_tokenize()
|
|
||||||
|
|
||||||
[token | tokenize(rest)]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# def to_rpn([{:number, _} = num | rest]) do
|
def do_eval_single(it, get_var) do
|
||||||
# [num | to_rpn(rest)]
|
do_eval([it], get_var)
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def to_rpn([{:operator, _} = op | rest]) do
|
|
||||||
# [op | to_rpn(rest)]
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def to_rpn
|
|
||||||
|
|
||||||
# def parse(tokens) do
|
|
||||||
# {expr, rest} = do_parse(tokens)
|
|
||||||
# [expr |
|
|
||||||
# end
|
|
||||||
|
|
||||||
def run([:lparen | _] = tokens, vars) do
|
|
||||||
paren_tokens =
|
|
||||||
tokens
|
|
||||||
|> Enum.drop(1)
|
|
||||||
|> Enum.reduce_while({1, []}, fn token, {count, paren_tokens} ->
|
|
||||||
count =
|
|
||||||
case token do
|
|
||||||
:lparen -> count + 1
|
|
||||||
:rparen -> count - 1
|
|
||||||
_ -> count
|
|
||||||
end
|
|
||||||
|
|
||||||
if count == 0 do
|
|
||||||
{:halt, Enum.reverse(paren_tokens)}
|
|
||||||
else
|
|
||||||
{:cont, {count, [token | paren_tokens]}}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
# paren_expr = run(paren_tokens)
|
|
||||||
rest_tokens = Enum.drop(tokens, length(paren_tokens))
|
|
||||||
|
|
||||||
paren_res = run(paren_tokens, vars)
|
|
||||||
|
|
||||||
run([{:number, paren_res} | rest_tokens])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def do_op(:add, a, b), do: a + b
|
||||||
|
def do_op(:sub, a, b), do: a - b
|
||||||
|
def do_op(:mul, a, b), do: a * b
|
||||||
|
def do_op(:div, a, b), do: floor(a / b)
|
||||||
end
|
end
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -21,6 +21,7 @@ defmodule Aoc19.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
|
{:nimble_parsec, "~> 0.5.2"}
|
||||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
%{
|
||||||
|
"nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"},
|
||||||
|
}
|
|
@ -1,20 +1,70 @@
|
||||||
defmodule AssemblerTest do
|
defmodule AssemblerTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
doctest Assembler
|
doctest Assembler
|
||||||
|
import Assembler
|
||||||
|
|
||||||
test "assembles empty program" do
|
test "assembles empty program" do
|
||||||
assert Assembler.assemble("") == []
|
assert assemble("") == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles instructions correctly" do
|
||||||
|
assert assemble("add 1, 2, 3") == [1101, 1, 2, 3]
|
||||||
|
assert assemble("mul 1, 2, 3") == [1102, 1, 2, 3]
|
||||||
|
assert assemble("in 7") == [3, 7]
|
||||||
|
assert assemble("out 7") == [104, 7]
|
||||||
|
assert assemble("out $7") == [4, 7]
|
||||||
|
assert assemble("jnz 1, 2") == [1105, 1, 2]
|
||||||
|
assert assemble("jnz $1, 2") == [1005, 1, 2]
|
||||||
|
assert assemble("jez 1, 2") == [1106, 1, 2]
|
||||||
|
assert assemble("jez $1, 2") == [1006, 1, 2]
|
||||||
|
assert assemble("clt 1, 2, 3") == [1107, 1, 2, 3]
|
||||||
|
assert assemble("clt $1, $2, $3") == [7, 1, 2, 3]
|
||||||
|
assert assemble("ceq 1, 2, 3") == [1108, 1, 2, 3]
|
||||||
|
assert assemble("ceq $1, $2, $3") == [8, 1, 2, 3]
|
||||||
|
assert assemble("srel 1234") == [109, 1234]
|
||||||
|
assert assemble("hlt") == [99]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles macros" do
|
||||||
|
assert assemble("mov 1, 2") == [1101, 0, 1, 2]
|
||||||
|
assert assemble("jmp 2") == [1105, 1, 2]
|
||||||
|
assert assemble("sub 5, 3, res\nres: 0") == [1102, 3, -1, 8, 101, 5, 8, 8, 0]
|
||||||
|
assert assemble("not 4, 4") == [1102, 4, -1, 4, 101, 1, 4, 4]
|
||||||
|
assert assemble("cle 1, 2, res\nres: 0") == [1107, 1, 2, 11, 1005, 11, 11, 1108, 1, 2, 11, 0]
|
||||||
|
|
||||||
|
assert assemble("cgt 1, 2, res\nres: 0") == [
|
||||||
|
1107,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
19,
|
||||||
|
1005,
|
||||||
|
19,
|
||||||
|
11,
|
||||||
|
1108,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
19,
|
||||||
|
1002,
|
||||||
|
19,
|
||||||
|
-1,
|
||||||
|
19,
|
||||||
|
101,
|
||||||
|
1,
|
||||||
|
19,
|
||||||
|
19,
|
||||||
|
0
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "assembles simple program" do
|
test "assembles simple program" do
|
||||||
program = """
|
program = """
|
||||||
add 1 2 10
|
add 1, 2, 10
|
||||||
mul 2 3 11
|
mul 2, 3, 11
|
||||||
add $10 $11 12
|
add $10, $11, 12
|
||||||
hlt
|
hlt
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert Assembler.assemble(program) == [
|
assert assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -33,13 +83,13 @@ defmodule AssemblerTest do
|
||||||
|
|
||||||
test "assembles simple program with a label" do
|
test "assembles simple program with a label" do
|
||||||
program = """
|
program = """
|
||||||
add 1 2 10
|
add 1, 2, 10
|
||||||
jnz $10 nonZero
|
jnz $10, nonZero
|
||||||
nonZero:
|
nonZero:
|
||||||
hlt
|
hlt
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert Assembler.assemble(program) == [
|
assert assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -53,12 +103,12 @@ defmodule AssemblerTest do
|
||||||
|
|
||||||
test "assembles a simple program with a data label" do
|
test "assembles a simple program with a data label" do
|
||||||
program = """
|
program = """
|
||||||
add 1 2 var
|
add 1, 2, var
|
||||||
out $var
|
out $var
|
||||||
var: 0
|
var: 0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert Assembler.assemble(program) == [
|
assert assemble(program) == [
|
||||||
1101,
|
1101,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
@ -71,9 +121,29 @@ defmodule AssemblerTest do
|
||||||
|
|
||||||
test "assembles a program with a _self label" do
|
test "assembles a program with a _self label" do
|
||||||
program = """
|
program = """
|
||||||
add 1 2 _self
|
add 1, 2, _self
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert Assembler.assemble(program) == [1101, 1, 2, 3]
|
assert assemble(program) == [1101, 1, 2, 3]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles parameters with expressions" do
|
||||||
|
assert assemble("add 1, _self + 1, 3") == [1101, 1, 3, 3]
|
||||||
|
assert assemble("add 1, $(_self + 1), 3") == [101, 1, 3, 3]
|
||||||
|
|
||||||
|
assert assemble("""
|
||||||
|
add 1, label + 4 - 2, 3
|
||||||
|
label: 42
|
||||||
|
""") == [1101, 1, 6, 3, 42]
|
||||||
|
|
||||||
|
assert assemble("""
|
||||||
|
add 1, $(label + 4 - 2), 3
|
||||||
|
label: 42
|
||||||
|
""") == [101, 1, 6, 3, 42]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles relative mode parameters" do
|
||||||
|
assert assemble("add #1, #2, #3") == [22201, 1, 2, 3]
|
||||||
|
assert assemble("in #4") == [203, 4]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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