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

Elixirで Brainf*ckのインタプリタを書いてみたくなってので書いてみた。
Brainf*ckについてはこちら。

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

------

```elixir: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: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