Add assembler macros
This commit is contained in:
parent
d2af80fa26
commit
d4e68f6062
|
@ -1,4 +1,5 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
|
||||
locals_without_parens: [assemble_macro: 2]
|
||||
]
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
defmodule Assembler.Helpers do
|
||||
defmacro assemble_macro(match, asm) do
|
||||
quote do
|
||||
def assemble_insn(unquote(match), memory) do
|
||||
{macro_mem, _} = do_assemble(unquote(asm), length(memory))
|
||||
macro_mem ++ memory
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Assembler do
|
||||
import NimbleParsec
|
||||
import Assembler.Helpers
|
||||
|
||||
label =
|
||||
ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
|
||||
|
@ -21,7 +33,7 @@ defmodule Assembler do
|
|||
|> ascii_string([{:not, ?,}], min: 1)
|
||||
|
||||
instruction =
|
||||
ignore(ascii_string([?\s], 2))
|
||||
ignore(ascii_string([?\s, ?\t], min: 0))
|
||||
|> ascii_string([?a..?z], min: 1)
|
||||
|> repeat(param |> ignore(ascii_char([?,])))
|
||||
|> optional(param)
|
||||
|
@ -37,45 +49,14 @@ defmodule Assembler do
|
|||
])
|
||||
)
|
||||
|
||||
@asm """
|
||||
in res
|
||||
clt $res, _self + 1, cmpRes
|
||||
jnz $cmpRes, lessThan
|
||||
ceq $res, 8, cmpRes
|
||||
jnz $cmpRes, equal
|
||||
out 1001
|
||||
hlt
|
||||
|
||||
lessThan:
|
||||
out 999
|
||||
hlt
|
||||
|
||||
equal:
|
||||
out 1000
|
||||
hlt
|
||||
|
||||
res: 0
|
||||
cmpRes: 0
|
||||
"""
|
||||
def test do
|
||||
assemble(@asm)
|
||||
File.read!("test/intcode/cmp8.asm")
|
||||
|> assemble()
|
||||
|> Day5.run_cli()
|
||||
end
|
||||
|
||||
def assemble(asm) do
|
||||
{memory, labels} =
|
||||
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({[], %{}}, &assemble_line/2)
|
||||
def assemble(asm, offset \\ 0) do
|
||||
{memory, labels} = do_assemble(asm, offset)
|
||||
|
||||
memory
|
||||
|> Enum.reverse()
|
||||
|
@ -92,23 +73,47 @@ defmodule Assembler do
|
|||
end)
|
||||
end
|
||||
|
||||
def assemble_line([], acc), do: acc
|
||||
def do_assemble(asm, offset) do
|
||||
asm
|
||||
|> String.trim_trailing("\n")
|
||||
|> String.split("\n")
|
||||
|> Enum.map(fn line ->
|
||||
case parse_line(line) do
|
||||
{:ok, res, _, _, _, _} ->
|
||||
res
|
||||
|
||||
def assemble_line([label: [name]], {memory, labels}) do
|
||||
err ->
|
||||
raise "Unable to parse line: '#{line}':\n#{inspect(err)}"
|
||||
end
|
||||
end)
|
||||
|> Enum.reduce({[], %{}}, fn line, acc ->
|
||||
assemble_line(line, acc, offset)
|
||||
end)
|
||||
end
|
||||
|
||||
def assemble_line([], acc, _), do: acc
|
||||
|
||||
def assemble_line([label: [name]], {memory, labels}, offset) do
|
||||
{
|
||||
memory,
|
||||
Map.put(labels, name, length(memory))
|
||||
Map.put(labels, name, length(memory) + offset)
|
||||
}
|
||||
end
|
||||
|
||||
def assemble_line([data_label: [name, value]], {memory, labels}) do
|
||||
def assemble_line([data_label: [name, value]], {memory, labels}, _) do
|
||||
value =
|
||||
case value do
|
||||
{:neg, [val]} -> -val
|
||||
val when is_integer(val) -> val
|
||||
end
|
||||
|
||||
{
|
||||
[value | memory],
|
||||
Map.put(labels, name, length(memory))
|
||||
}
|
||||
end
|
||||
|
||||
def assemble_line([insn: insn], {memory, labels}) do
|
||||
def assemble_line([insn: insn], {memory, labels}, _) do
|
||||
{
|
||||
assemble_insn(insn, memory),
|
||||
labels
|
||||
|
@ -159,6 +164,46 @@ defmodule Assembler do
|
|||
[99 | 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 ["cle", a, b, dest],
|
||||
"""
|
||||
clt #{a}, #{b}, #{dest}
|
||||
jnz $#{dest}, end
|
||||
ceq #{a}, #{b}, #{dest}
|
||||
|
||||
end:
|
||||
"""
|
||||
|
||||
assemble_macro ["cgt", a, b, dest],
|
||||
"""
|
||||
clt #{a}, #{b}, #{dest}
|
||||
jnz $#{dest}, false
|
||||
ceq #{a}, #{b}, #{dest}
|
||||
jnz $#{dest}, false
|
||||
mov 1, #{dest}
|
||||
jmp end
|
||||
|
||||
false:
|
||||
mov 0, #{dest}
|
||||
end:
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Raises the base to to the given power.
|
||||
|
||||
|
|
|
@ -8,20 +8,26 @@ defmodule AssemblerTest do
|
|||
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]
|
||||
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 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]
|
||||
end
|
||||
|
||||
test "assembles simple program" do
|
||||
|
@ -96,8 +102,8 @@ defmodule AssemblerTest do
|
|||
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, _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
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue