diff --git a/lib/intcode/assembler.ex b/lib/intcode/assembler.ex index 0c1ba2d..c51036a 100644 --- a/lib/intcode/assembler.ex +++ b/lib/intcode/assembler.ex @@ -67,16 +67,39 @@ defmodule Assembler do it -> it end) - IO.inspect(labels) + # IO.inspect(labels) Enum.reverse(memory) end - def repeat(_, 0), do: [] - def repeat(n, count), do: [n | repeat(n, count - 1)] + @doc """ + Raises the base to to the given power. + ## Examples + iex> Assembler.pow(10, 3) + 1000 + iex> Assembler.pow(5, 4) + 625 + """ def pow(base, 1), do: base def pow(base, exp), do: base * pow(base, exp - 1) + @doc """ + Parses assembly instruction parameter values and modes. + + ## Examples + iex> Assembler.parse_params("1 2 3", [:read, :read, :read]) + {[1, 2, 3], 11100} + iex> Assembler.parse_params("1 2 3", [:read, :read, :write]) + {[1, 2, 3], 1100} + iex> Assembler.parse_params("1 2 $3", [:read, :read, :write]) + {[1, 2, 3], 1100} + iex> Assembler.parse_params("$1 2 3", [:read, :read, :write]) + {[1, 2, 3], 1000} + iex> Assembler.parse_params("$label 2 3", [:read, :read, :write]) + {[{:label, "label"}, 2, 3], 1000} + iex> Assembler.parse_params("1 label 3", [:read, :read, :write]) + {[1, {:label, "label"}, 3], 1100} + """ def parse_params(params, param_types) do params |> String.split(" ") @@ -110,54 +133,130 @@ defmodule Assembler do end) end + @doc """ + Assembles the given instruction and adds it to the memory (in reverse order). + """ def assemble_instruction(insn, memory \\ []) + @doc """ + Assemble add instruction. + + ## Examples + iex> Assembler.assemble_instruction("add 1 2 3") + [3, 2, 1, 1101] + """ def assemble_instruction("add " <> params, memory) do {[a, b, dest], modes} = parse_params(params, [:read, :read, :write]) [dest, b, a, 1 + modes | memory] end + @doc """ + Assemble multiply instruction. + + ## Examples + iex> Assembler.assemble_instruction("mul 1 2 3") + [3, 2, 1, 1102] + """ def assemble_instruction("mul " <> params, memory) do {[a, b, dest], modes} = parse_params(params, [:read, :read, :write]) [dest, b, a, 2 + modes | memory] end + @doc """ + Assemble halt instruction. + + ## Examples + iex> Assembler.assemble_instruction("hlt") + [99] + """ def assemble_instruction("hlt", memory) do [99 | memory] end + @doc """ + Assemble input instruction. + + ## Examples + iex> Assembler.assemble_instruction("in 7") + [7, 3] + """ def assemble_instruction("in " <> params, memory) do {[dest], modes} = parse_params(params, [:write]) [dest, 3 + modes | memory] end + @doc """ + Assemble input instruction. + + ## Examples + iex> Assembler.assemble_instruction("out 7") + [7, 104] + iex> Assembler.assemble_instruction("out $7") + [7, 4] + """ def assemble_instruction("out " <> params, memory) do {[val], modes} = parse_params(params, [:read]) [val, 4 + modes | memory] end + @doc """ + Assemble jump-if-non-zero instruction. + + ## Examples + iex> Assembler.assemble_instruction("jnz 1 2") + [2, 1, 105] + iex> Assembler.assemble_instruction("jnz $var 2") + [2, {:label, "var"}, 5] + """ def assemble_instruction("jnz " <> params, memory) do {[target, dest], modes} = parse_params(params, [:read, :read]) [dest, target, 5 + modes | memory] end + @doc """ + Assemble jump-if-zero instruction. + + ## Examples + iex> Assembler.assemble_instruction("jez 1 2") + [2, 1, 106] + iex> Assembler.assemble_instruction("jez $var 2") + [2, {:label, "var"}, 6] + """ def assemble_instruction("jez " <> params, memory) do {[target, dest], modes} = parse_params(params, [:read, :read]) [dest, target, 6 + modes | memory] end + @doc """ + Assemble compare-less-than instruction. + + ## Examples + iex> Assembler.assemble_instruction("clt 1 2 3") + [3, 2, 1, 1107] + iex> Assembler.assemble_instruction("clt $1 $2 3") + [3, 2, 1, 7] + """ def assemble_instruction("clt " <> params, memory) do {[a, b, dest], modes} = parse_params(params, [:read, :read, :write]) [dest, b, a, 7 + modes | memory] end + @doc """ + Assemble compare-equal instruction. + + ## Examples + iex> Assembler.assemble_instruction("ceq 1 2 3") + [3, 2, 1, 1108] + iex> Assembler.assemble_instruction("ceq $1 $2 3") + [3, 2, 1, 8] + """ def assemble_instruction("ceq " <> params, memory) do {[a, b, dest], modes} = parse_params(params, [:read, :read, :write]) diff --git a/test/intcode/assembler_test.exs b/test/intcode/assembler_test.exs new file mode 100644 index 0000000..df526d9 --- /dev/null +++ b/test/intcode/assembler_test.exs @@ -0,0 +1,71 @@ +defmodule AssemblerTest do + use ExUnit.Case + doctest Assembler + + test "assembles empty program" do + assert Assembler.assemble("") == [] + end + + test "assembles simple program" do + program = """ + add 1 2 10 + mul 2 3 11 + add $10 $11 12 + hlt + """ + + assert Assembler.assemble(program) == [ + 1101, + 1, + 2, + 10, + 1102, + 2, + 3, + 11, + 1, + 10, + 11, + 12, + 99 + ] + end + + test "assembles simple program with a label" do + program = """ + add 1 2 10 + jnz $10 nonZero + nonZero: + hlt + """ + + assert Assembler.assemble(program) == [ + 1101, + 1, + 2, + 10, + 1005, + 10, + 7, + 99 + ] + end + + test "assembles a simple program with a data label" do + program = """ + add 1 2 var + out $var + var: 0 + """ + + assert Assembler.assemble(program) == [ + 1101, + 1, + 2, + 6, + 4, + 6, + 0 + ] + end +end