Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 458210f513 | |
Shadowfacts | fe5bc1de7c | |
Shadowfacts | cbbd6d8692 | |
Shadowfacts | b933c30bea | |
Shadowfacts | 36ea4a177d |
|
@ -1,28 +1,31 @@
|
||||||
defmodule Assembler do
|
defmodule Assembler do
|
||||||
@asm """
|
@asm """
|
||||||
in #30
|
in res
|
||||||
clt 30 #8 #31
|
clt $res 8 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
|
@asm
|
||||||
|> IO.inspect()
|
|> IO.inspect()
|
||||||
|> assemble(32)
|
|> assemble()
|
||||||
end
|
end
|
||||||
|
|
||||||
def assemble(asm, pad_length \\ 0) do
|
def assemble(asm) do
|
||||||
{memory, labels} =
|
{memory, labels} =
|
||||||
asm
|
asm
|
||||||
|> String.split("\n")
|
|> String.split("\n")
|
||||||
|
@ -44,34 +47,81 @@ defmodule Assembler do
|
||||||
Map.put(labels, String.slice(line, 0..-2), length(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 ->
|
true ->
|
||||||
IO.inspect("Ignoring line: #{line}")
|
IO.inspect("Ignoring line: #{line}")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
memory =
|
# IO.inspect(labels)
|
||||||
Enum.map(memory, fn
|
|
||||||
{:label, name} -> Map.fetch!(labels, name)
|
|
||||||
it -> it
|
|
||||||
end)
|
|
||||||
|
|
||||||
memory =
|
memory
|
||||||
if pad_length > length(memory) do
|
|> Enum.reverse()
|
||||||
repeat(0, pad_length - length(memory)) ++ memory
|
|> Enum.with_index()
|
||||||
else
|
|> Enum.map(fn
|
||||||
memory
|
{{:label, name}, index} -> get_label(name, index, labels)
|
||||||
end
|
{it, _} -> it
|
||||||
|
end)
|
||||||
IO.inspect(labels)
|
|
||||||
Enum.reverse(memory)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def repeat(_, 0), do: []
|
@doc """
|
||||||
def repeat(n, count), do: [n | repeat(n, count - 1)]
|
Get the address of a label.
|
||||||
|
|
||||||
|
`_self` is a special label that resolves to the address of where it's being inserted into the program.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
iex> Assembler.get_label("test", 1, %{"test" => 14})
|
||||||
|
14
|
||||||
|
iex> Assembler.get_label("_self", 1, %{})
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
def get_label(name, index, labels)
|
||||||
|
|
||||||
|
def get_label("_self", index, _) do
|
||||||
|
index
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_label(name, _, labels) do
|
||||||
|
Map.fetch!(labels, name)
|
||||||
|
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])
|
||||||
|
{[{: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
|
def parse_params(params, param_types) do
|
||||||
params
|
params
|
||||||
|> String.split(" ")
|
|> String.split(" ")
|
||||||
|
@ -79,74 +129,156 @@ defmodule Assembler do
|
||||||
|> 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)
|
[_, name] = Regex.run(~r/^\$?(\w+)$/, param)
|
||||||
{:label, name}
|
{:label, name}
|
||||||
end
|
end
|
||||||
|
|
||||||
modes =
|
modes =
|
||||||
if type == :read && String.starts_with?(param, "#") do
|
case type do
|
||||||
modes + pow(10, index + 2)
|
:read ->
|
||||||
else
|
if String.starts_with?(param, "$") do
|
||||||
modes
|
modes
|
||||||
|
else
|
||||||
|
modes + pow(10, index + 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
modes
|
||||||
end
|
end
|
||||||
|
|
||||||
{val, modes}
|
{val, modes}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Assembles the given instruction and adds it to the memory (in reverse order).
|
||||||
|
"""
|
||||||
def assemble_instruction(insn, memory \\ [])
|
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
|
def assemble_instruction("add " <> params, memory) do
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
|
||||||
[dest, b, a, 1 + modes | memory]
|
[dest, b, a, 1 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("mul " <> params, memory) do
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
|
||||||
[dest, b, a, 2 + modes | memory]
|
[dest, b, a, 2 + modes | memory]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Assemble halt instruction.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
iex> Assembler.assemble_instruction("hlt")
|
||||||
|
[99]
|
||||||
|
"""
|
||||||
def assemble_instruction("hlt", memory) do
|
def assemble_instruction("hlt", memory) do
|
||||||
[99 | memory]
|
[99 | memory]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Assemble input instruction.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
iex> Assembler.assemble_instruction("in 7")
|
||||||
|
[7, 3]
|
||||||
|
"""
|
||||||
def assemble_instruction("in " <> params, memory) do
|
def assemble_instruction("in " <> params, memory) do
|
||||||
{[dest], modes} = parse_params(params, [:write])
|
{[dest], modes} = parse_params(params, [:write])
|
||||||
|
|
||||||
[dest, 3 + modes | memory]
|
[dest, 3 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("out " <> params, memory) do
|
||||||
{[val], modes} = parse_params(params, [:read])
|
{[val], modes} = parse_params(params, [:read])
|
||||||
|
|
||||||
[val, 4 + modes | memory]
|
[val, 4 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("jnz " <> params, memory) do
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
{[target, dest], modes} = parse_params(params, [:read, :read])
|
||||||
|
|
||||||
[dest, target, 5 + modes | memory]
|
[dest, target, 5 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("jez " <> params, memory) do
|
||||||
{[target, dest], modes} = parse_params(params, [:read, :read])
|
{[target, dest], modes} = parse_params(params, [:read, :read])
|
||||||
|
|
||||||
[dest, target, 6 + modes | memory]
|
[dest, target, 6 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("clt " <> params, memory) do
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
|
||||||
[dest, b, a, 7 + modes | memory]
|
[dest, b, a, 7 + modes | memory]
|
||||||
end
|
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
|
def assemble_instruction("ceq " <> params, memory) do
|
||||||
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
{[a, b, dest], modes} = parse_params(params, [:read, :read, :write])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
defmodule ExpressionEvaluator do
|
||||||
|
def eval(expr, vars) do
|
||||||
|
expr
|
||||||
|
|> String.to_charlist()
|
||||||
|
|> tokenize()
|
||||||
|
|> run(vars)
|
||||||
|
|
||||||
|
# |> parse()
|
||||||
|
|
||||||
|
# |> run(vars)
|
||||||
|
end
|
||||||
|
|
||||||
|
@digits ?0..?9
|
||||||
|
@alpha ?a..?z
|
||||||
|
@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
|
||||||
|
|
||||||
|
def do_tokenize([?( | rest]) do
|
||||||
|
{:lparen, rest}
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_tokenize([?) | rest]) do
|
||||||
|
{:rparen, rest}
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_tokenize([op | rest]) when op in @operators do
|
||||||
|
atom = [op] |> to_string() |> String.to_atom()
|
||||||
|
{{:operator, atom}, rest}
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_tokenize([first | _] = str) when first in @alpha do
|
||||||
|
{var, rest} = Enum.split_while(str, &(&1 in @alpha))
|
||||||
|
{{:variable, var}, rest}
|
||||||
|
end
|
||||||
|
|
||||||
|
def tokenize([]), do: []
|
||||||
|
|
||||||
|
def tokenize(str) do
|
||||||
|
{token, rest} =
|
||||||
|
str
|
||||||
|
|> Enum.drop_while(&(&1 == ?\s))
|
||||||
|
|> do_tokenize()
|
||||||
|
|
||||||
|
[token | tokenize(rest)]
|
||||||
|
end
|
||||||
|
|
||||||
|
# def to_rpn([{:number, _} = num | rest]) do
|
||||||
|
# [num | to_rpn(rest)]
|
||||||
|
# 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
|
|
@ -0,0 +1,79 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
test "assembles a program with a _self label" do
|
||||||
|
program = """
|
||||||
|
add 1 2 _self
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert Assembler.assemble(program) == [1101, 1, 2, 3]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue