Posted at

ElixirでBrain f*ck を書いてみた

More than 1 year has passed since last update.

Elixirで Brainf*ckのインタプリタを書いてみたくなってので書いてみた。

Brainf*ckについてはこちら。

https://ja.wikipedia.org/wiki/Brainfuck



elixir_brain_fxck.ex

defmodule ElixirBrainFxck do

@moduledoc """
Documentation for ElixirBrainFxck.
"""

alias ElixirBrainFxck.Memory

@opertors [
"+",
"-",
">",
"<",
"[",
"]",
"."
]

@doc """
Brain Fxxk!!!.

## Examples

iex> ElixirBrainFxck.bf "+++."
[3]
iex> ElixirBrainFxck.bf "+++.+."
[3,4]
iex> ElixirBrainFxck.bf "++.>+."
[2, 1]
iex> ElixirBrainFxck.bf "++.>+.<."
[2, 1, 2]
iex> ElixirBrainFxck.bf "+++[>++++<-]>."
[12]
iex> ElixirBrainFxck.bf "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+."
'Hello, world!'

"""
def bf(code) do
Memory.start_link
Memory.init()

op_list = code
|> String.graphemes
|> Enum.filter(fn x -> Enum.member?(@opertors, x) end)

process({op_list, 0})
end

def process({op_list, op_head}) do
case Enum.at(op_list, op_head) |> operation({op_list, op_head}) do
{:ok, op} -> process(op)
:end -> Memory.flush
end
end

def operation(">", {op_list, op_head}) do
Memory.right()
{:ok, {op_list, op_head + 1}}
end

def operation("<", {op_list, op_head}) do
Memory.left()
{:ok, {op_list, op_head + 1}}
end

def operation("+", {op_list, op_head}) do
Memory.incr()
{:ok, {op_list, op_head + 1}}
end

def operation("-", {op_list, op_head}) do
Memory.decr()
{:ok, {op_list, op_head + 1}}
end

def operation(".", {op_list, op_head}) do
Memory.output()
{:ok, {op_list, op_head + 1}}
end

def operation("[", {op_list, op_head}) do
case Memory.read() do
0 ->
{:ok, jump_end({op_list, op_head + 1}, 1)}
_ ->
{:ok, {op_list, op_head + 1}}
end
end

def operation("]", {op_list, op_head}) do
case Memory.read() do
0 ->
{:ok, {op_list, op_head + 1}}
_ ->
{:ok, jump_begin({op_list, op_head - 1}, 1)}
end
end

def operation(_, _) do
:end
end

def jump_begin({op_list, op_head}, 0) do
{op_list, op_head + 1}
end

def jump_begin({op_list, op_head}, s) do
case Enum.at(op_list, op_head) do
"[" -> jump_begin({op_list, op_head - 1}, s - 1)
"]" -> jump_begin({op_list, op_head - 1}, s + 1)
_ -> jump_begin({op_list, op_head - 1}, s)
end
end

def jump_end({op_list, op_head}, 0) do
{op_list, op_head}
end

def jump_end({op_list, op_head}, s) do
case Enum.at(op_list, op_head) do
"[" -> jump_end({op_list, op_head + 1}, s + 1)
"]" -> jump_end({op_list, op_head + 1}, s - 1)
_ -> jump_end({op_list, op_head + 1}, s)
end
end
end



elixir_brain_fxck/memory.ex

defmodule ElixirBrainFxck.Memory do

defmodule Tape do
@size 100
@cells for _ <- 1..@size, do: 0

defstruct cells: @cells, head: 0, tmp: []
end

def start_link do
Agent.start_link(fn -> %Tape{} end, name: __MODULE__)
end

def read do
Agent.get(__MODULE__, fn %Tape{cells: cells, head: head} ->
Enum.at(cells, head)
end)
end

def get do
Agent.get(__MODULE__, &(&1))
end

def incr do
%Tape{cells: cells, head: head} = get()
Agent.update(__MODULE__, fn tape -> %Tape{tape | cells: List.update_at(cells, head, &(&1 + 1))} end)
end

def decr do
%Tape{cells: cells, head: head} = get()
Agent.update(__MODULE__, fn tape -> %Tape{tape | cells: List.update_at(cells, head, &(&1 - 1))} end)
end

def right do
%Tape{cells: cells, head: head} = get()
Agent.update(__MODULE__, fn tape -> %Tape{tape | head: head + 1} end)
end

def left do
%Tape{cells: cells, head: head} = get()
Agent.update(__MODULE__, fn tape -> %Tape{tape | head: head - 1} end)
end

def output do
%Tape{cells: cells, head: head, tmp: tmp} = get()
cell = Enum.at(cells, head)
Agent.update(__MODULE__, fn tape -> %Tape{tape | tmp: tmp ++ [cell] } end)
end

def init do
Agent.update(__MODULE__, fn _ -> %Tape{} end)
end

def flush do
%Tape{tmp: tmp} = get()
tmp
end
end


工夫したところとしては

- バイト配列とポインタはAgentを使って保持するように。

- ジャンプ演算子([ ]) では再起をつかうように

- 動作確認はDoctestを使うように

ぐらいだろうか。。。

もうちょっときれいにかけるようになりたいですね。

コードはGithubにもあげてあります

https://github.com/tamanugi/elixir_brain_fxck