Compare commits
2 Commits
0f5e7f7907
...
d2af80fa26
Author | SHA1 | Date |
---|---|---|
Shadowfacts | d2af80fa26 | |
Shadowfacts | 49b2a79d0a |
|
@ -98,7 +98,7 @@ defmodule Day5 do
|
||||||
# IO.inspect(memory)
|
# IO.inspect(memory)
|
||||||
|
|
||||||
case eval(Enum.drop(memory, ip), memory, parent) do
|
case eval(Enum.drop(memory, ip), memory, parent) do
|
||||||
{memory, :halt} ->
|
{_memory, :halt} ->
|
||||||
send(parent, {:halt, self()})
|
send(parent, {:halt, self()})
|
||||||
|
|
||||||
{memory, :cont, offset} ->
|
{memory, :cont, offset} ->
|
||||||
|
@ -109,7 +109,7 @@ defmodule Day5 do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(memory, parent, _) do
|
def run(_memory, parent, _) do
|
||||||
send(parent, {:ok, self()})
|
send(parent, {:ok, self()})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,155 +1,227 @@
|
||||||
defmodule Assembler do
|
defmodule Assembler do
|
||||||
|
import NimbleParsec
|
||||||
|
|
||||||
|
label =
|
||||||
|
ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||||
|
|> ignore(ascii_char([?:]))
|
||||||
|
|> ignore(repeat(ascii_char([?\s])))
|
||||||
|
|
||||||
|
data_label =
|
||||||
|
label
|
||||||
|
|> choice([
|
||||||
|
integer(min: 1),
|
||||||
|
ignore(ascii_char([?-]))
|
||||||
|
|> integer(min: 1)
|
||||||
|
|> tag(:neg)
|
||||||
|
])
|
||||||
|
|> tag(:data_label)
|
||||||
|
|
||||||
|
param =
|
||||||
|
optional(ignore(ascii_string([?\s], min: 1)))
|
||||||
|
|> ascii_string([{:not, ?,}], min: 1)
|
||||||
|
|
||||||
|
instruction =
|
||||||
|
ignore(ascii_string([?\s], 2))
|
||||||
|
|> 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()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
@asm """
|
@asm """
|
||||||
in #30
|
in res
|
||||||
clt 30 #8 #31
|
clt $res, _self + 1, cmpRes
|
||||||
jnz 31 #lessThan
|
jnz $cmpRes, lessThan
|
||||||
ceq 30 #8 #31
|
ceq $res, 8, cmpRes
|
||||||
jnz 31 #equal
|
jnz $cmpRes, equal
|
||||||
out #1001
|
out 1001
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
lessThan:
|
lessThan:
|
||||||
out #999
|
out 999
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
equal:
|
equal:
|
||||||
out #1000
|
out 1000
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
|
res: 0
|
||||||
|
cmpRes: 0
|
||||||
"""
|
"""
|
||||||
def test do
|
def test do
|
||||||
@asm
|
assemble(@asm)
|
||||||
|> IO.inspect()
|
|
||||||
|> assemble(32)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(asm, pad_length \\ 0) do
|
def assemble(asm) do
|
||||||
{memory, labels} =
|
{memory, labels} =
|
||||||
asm
|
asm
|
||||||
|
|> String.trim_trailing("\n")
|
||||||
|> String.split("\n")
|
|> String.split("\n")
|
||||||
|> Enum.reject(&(String.length(&1) == 0))
|
|> Enum.map(fn line ->
|
||||||
|> Enum.reduce({[], %{}}, fn line, {memory, labels} ->
|
case parse_line(line) do
|
||||||
cond do
|
{:ok, res, _, _, _, _} ->
|
||||||
Regex.match?(~r/^\s+$/, line) ->
|
res
|
||||||
{memory, labels}
|
|
||||||
|
|
||||||
String.starts_with?(line, " ") ->
|
err ->
|
||||||
{
|
raise "Unable to parse line: '#{line}':\n#{inspect(err)}"
|
||||||
assemble_instruction(String.slice(line, 2..-1), memory),
|
end
|
||||||
labels
|
end)
|
||||||
}
|
|> Enum.reduce({[], %{}}, &assemble_line/2)
|
||||||
|
|
||||||
String.ends_with?(line, ":") ->
|
memory
|
||||||
|
|> Enum.reverse()
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.map(fn
|
||||||
|
{{:expr, expr}, index} ->
|
||||||
|
ExpressionEvaluator.eval(expr, fn
|
||||||
|
"_self" -> index
|
||||||
|
name -> Map.fetch!(labels, name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{it, _} ->
|
||||||
|
it
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assemble_line([], acc), do: acc
|
||||||
|
|
||||||
|
def assemble_line([label: [name]], {memory, labels}) do
|
||||||
{
|
{
|
||||||
memory,
|
memory,
|
||||||
Map.put(labels, String.slice(line, 0..-2), length(memory))
|
Map.put(labels, name, length(memory))
|
||||||
}
|
}
|
||||||
|
|
||||||
true ->
|
|
||||||
IO.inspect("Ignoring line: #{line}")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
memory =
|
|
||||||
Enum.map(memory, fn
|
|
||||||
{:label, name} -> Map.fetch!(labels, name)
|
|
||||||
it -> it
|
|
||||||
end)
|
|
||||||
|
|
||||||
memory =
|
|
||||||
if pad_length > length(memory) do
|
|
||||||
repeat(0, pad_length - length(memory)) ++ memory
|
|
||||||
else
|
|
||||||
memory
|
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.inspect(labels)
|
def assemble_line([data_label: [name, value]], {memory, labels}) do
|
||||||
Enum.reverse(memory)
|
{
|
||||||
|
[value | memory],
|
||||||
|
Map.put(labels, name, length(memory))
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def repeat(_, 0), do: []
|
def assemble_line([insn: insn], {memory, labels}) do
|
||||||
def repeat(n, count), do: [n | repeat(n, count - 1)]
|
{
|
||||||
|
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(["hlt"], memory) do
|
||||||
|
[99 | memory]
|
||||||
|
end
|
||||||
|
|
||||||
|
@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, 1), do: base
|
||||||
def pow(base, exp), do: base * pow(base, exp - 1)
|
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])
|
||||||
|
{[{:expr, "label"}, 2, 3], 1000}
|
||||||
|
iex> Assembler.parse_params(["1", "label", "3"], [:read, :read, :write])
|
||||||
|
{[1, {:expr, "label"}, 3], 1100}
|
||||||
|
"""
|
||||||
def parse_params(params, param_types) do
|
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, param}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:expr, param}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
modes =
|
modes =
|
||||||
if type == :read && String.starts_with?(param, "#") do
|
case type do
|
||||||
modes + pow(10, index + 2)
|
:read ->
|
||||||
|
if String.starts_with?(param, "$") do
|
||||||
|
modes
|
||||||
else
|
else
|
||||||
|
modes + pow(10, index + 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
modes
|
modes
|
||||||
end
|
end
|
||||||
|
|
||||||
{val, modes}
|
{val, modes}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble_instruction(insn, memory \\ [])
|
|
||||||
|
|
||||||
def assemble_instruction("add " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 1 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("mul " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 2 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("hlt", memory) do
|
|
||||||
[99 | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("in " <> params, memory) do
|
|
||||||
{[dest], modes} = parse_params(params, [:write])
|
|
||||||
|
|
||||||
[dest, 3 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("out " <> params, memory) do
|
|
||||||
{[val], modes} = parse_params(params, [:read])
|
|
||||||
|
|
||||||
[val, 4 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("jnz " <> params, memory) do
|
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
|
||||||
|
|
||||||
[dest, target, 5 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("jez " <> params, memory) do
|
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
|
||||||
|
|
||||||
[dest, target, 6 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
def assemble_instruction("clt " <> params, memory) do
|
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
|
||||||
|
|
||||||
[dest, b, a, 7 + modes | memory]
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
defmodule ExpressionEvaluator do
|
||||||
|
import NimbleParsec
|
||||||
|
|
||||||
|
number = integer(min: 1)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def eval(expr, get_var) when is_function(get_var) do
|
||||||
|
{:ok, expr, _, _, _, _} = parse(expr)
|
||||||
|
do_eval(expr, get_var)
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval([{op, [a, b]}], get_var) do
|
||||||
|
do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval([it], get_var) do
|
||||||
|
do_eval_single(it, get_var)
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval_single({:neg, [it]}, get_var) do
|
||||||
|
-1 * do_eval_single(it, get_var)
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval_single(it, _) when is_integer(it) do
|
||||||
|
it
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval_single(it, get_var) when is_binary(it) do
|
||||||
|
get_var.(it)
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_eval_single(it, get_var) do
|
||||||
|
do_eval([it], get_var)
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_op(:add, a, b), do: a + b
|
||||||
|
def do_op(:sub, a, b), do: a - b
|
||||||
|
def do_op(:mul, a, b), do: a * b
|
||||||
|
def do_op(:div, a, b), do: floor(a / b)
|
||||||
|
end
|
1
mix.exs
1
mix.exs
|
@ -21,6 +21,7 @@ defmodule Aoc19.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# 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"},
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
defmodule AssemblerTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Assembler
|
||||||
|
import Assembler
|
||||||
|
|
||||||
|
test "assembles empty program" do
|
||||||
|
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(" hlt") == [99]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles simple program" do
|
||||||
|
program = """
|
||||||
|
add 1, 2, 10
|
||||||
|
mul 2, 3, 11
|
||||||
|
add $10, $11, 12
|
||||||
|
hlt
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert 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 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 assemble(program) == [
|
||||||
|
1101,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
6,
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "assembles a program with a _self label" do
|
||||||
|
program = """
|
||||||
|
add 1, 2, _self
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
|
@ -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
|
Loading…
Reference in New Issue