Compare commits

...

5 Commits
master ... expr

3 changed files with 336 additions and 32 deletions

View File

@ -1,28 +1,31 @@
defmodule Assembler do
@asm """
in #30
clt 30 #8 #31
jnz 31 #lessThan
ceq 30 #8 #31
jnz 31 #equal
out #1001
in res
clt $res 8 cmpRes
jnz $cmpRes lessThan
ceq $res 8 cmpRes
jnz $cmpRes equal
out 1001
hlt
lessThan:
out #999
out 999
hlt
equal:
out #1000
out 1000
hlt
res: 0
cmpRes: 0
"""
def test do
@asm
|> IO.inspect()
|> assemble(32)
|> assemble()
end
def assemble(asm, pad_length \\ 0) do
def assemble(asm) do
{memory, labels} =
asm
|> String.split("\n")
@ -44,34 +47,81 @@ defmodule Assembler do
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)
memory =
Enum.map(memory, fn
{:label, name} -> Map.fetch!(labels, name)
it -> it
end)
# IO.inspect(labels)
memory =
if pad_length > length(memory) do
repeat(0, pad_length - length(memory)) ++ memory
else
memory
end
IO.inspect(labels)
Enum.reverse(memory)
memory
|> Enum.reverse()
|> Enum.with_index()
|> Enum.map(fn
{{:label, name}, index} -> get_label(name, index, labels)
{it, _} -> it
end)
end
def repeat(_, 0), do: []
def repeat(n, count), do: [n | repeat(n, count - 1)]
@doc """
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, 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(" ")
@ -79,74 +129,156 @@ defmodule Assembler do
|> Enum.with_index()
|> Enum.map_reduce(0, fn {{param, type}, index}, modes ->
val =
case Regex.run(~r/^#?(\d+)$/, param) do
case Regex.run(~r/^\$?(\d+)$/, param) do
[_, digits] ->
String.to_integer(digits)
_ ->
[_, name] = Regex.run(~r/^#(\w+)$/, param)
[_, name] = Regex.run(~r/^\$?(\w+)$/, param)
{:label, name}
end
modes =
if type == :read && String.starts_with?(param, "#") do
modes + pow(10, index + 2)
else
modes
case type do
:read ->
if String.starts_with?(param, "$") do
modes
else
modes + pow(10, index + 2)
end
_ ->
modes
end
{val, modes}
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])

View File

@ -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

View File

@ -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