ZEAM開発ログ v.0.4.13 マクロを使って micro Elixir のフロントエンドを作ってみる (野望編)

ZACKYこと山崎進です。

fukuoka.ex もくもく会明けのプログラミングフィーバーは続きます。今回は NIF を Elixir の中で記述する micro Elixir の仕様について，プロトタイピングしながら検討しました。

[「ZEAM開発ログ 目次」はこちら](https://qiita.com/zacky1972/items/70593ab2b70d192813df)

# 当初構想

最初はこんなのがいいかなと思っていました。

たとえば

```elixir
def_nif_module Foo do
  def_nif add, do: asm add a, b
  def_nif mul, do: asm mul a, b
end
```

とか書かれていたら，

```elixir
defmodule Foo do
  require Asm
  import Asm

  def_nif add, do: asm add a, b
  def_nif mul, do: asm mul a, b

  generate_nif
end
```

とかに変換し，`add` と `mul` の情報を集めて `generate_nif` で NIFモジュールのネイティブコードを生成する，というようなことを考えていました。

# 当初構想の問題点

`def_nif_module` のマクロ定義をいつどこで読むのよ？ という問題があり，あえなくボツに。

# 第2案

既存の文法との整合性も加味して，こんな感じの文法を考えました。

```elixir
defmodule Foo do
  require Asm
  import Asm

  nif_module do
    def_nif add, do: asm add a, b
    def_nif mul, do: asm mul a, b
  end
end
```

`nif_module` のブロックの最後で NIFモジュールのネイティブコードを生成するという案です。

# 第2案プロトタイプ

そこで実験してみました。

([「ZEAM開発ログ v.0.4.11 マクロを使って micro Elixir のフロントエンドを作ってみる (黎明編)」](https://qiita.com/zacky1972/items/39e7ea9d849e9574a9c3)との差分)

```elixir
  def get_env(atom) do
    atom
    |> Atom.to_string
    |> System.get_env
  end

  def put_env(atom, value) do
    atom
    |> Atom.to_string
    |> System.put_env(value)
  end

  @doc """
  def_nif defines a NIF that includes micro Elixir code.
  """
  defmacro def_nif func, do_clause do
    put_env(__ENV__.module, get_name_arity(func))
  	quote do
  		def unquote(func), unquote(do_clause)
  		def unquote(when_and_int64(func, __ENV__.module)), unquote(do_clause)
  		def unquote(when_and_uint64(func, __ENV__.module)), unquote(do_clause)
  		def unquote(when_and_float(func, __ENV__.module)), unquote(do_clause)
  	end
  end

  @doc """
  """
  defmacro nif_module do_clause do
    ret = quote do: unquote(do_clause)
    IO.puts get_env(__ENV__.module)
    ret
  end
```

もし狙い通りに行けば，下記のコードを実行すると，`add/2` を表示してくれるはずです。

```elixir
defmodule Foo do           
  nif_module do              
    def_nif add(a, b), do: asm add a, b
  end
end
```

しかし，何も表示してくれませんでした。

調べてみるとどうやら，`IO.puts get_env(__ENV__.module)` は `nif_module` が定義されている文の位置で実行が終わってしまい，`def_nif add(a, b), do: asm add a, b` よりも前に実行されているようなのです。

# 第3案

`generate_nif` を `nif_module` の最後に書くことにしました。

```elixir
defmodule Foo do
  require Asm
  import Asm

  nif_module do
    def_nif add, do: asm add a, b
    def_nif mul, do: asm mul a, b
    generate_nif
  end
end
```

# 第3案プロトタイプ

実験してみました。第2案からの差分です。

```elixir
  defmacro nif_module do_clause do
    quote do: unquote(do_clause)
  end

  defmacro generate_nif do
    IO.puts get_env(__ENV__.module)
  end
```

もし狙い通りにいけば下記のコードで`add/1`と表示してくれるはずです。 

```elixir
defmodule Foo do           
  nif_module do              
    def_nif add(a, b), do: asm add a, b
    generate_nif
  end
end
```

果たして！

```elixir
$ iex -S mix
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [sharing-preserving]

Compiling 1 file (.ex)
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> require Asm
Asm
iex(2)> import Asm
Asm
iex(3)> defmodule Foo do                 
...(3)>   nif_module do                    
...(3)>     def_nif add(a,b), do: asm add a,b
...(3)>     generate_nif                     
...(3)>   end                              
...(3)> end                              
add/2
{:module, Foo,
 <<70, 79, 82, 49, 0, 0, 6, 0, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 144, 0,
   0, 0, 17, 10, 69, 108, 105, 120, 105, 114, 46, 70, 111, 111, 8, 95, 95, 105,
   110, 102, 111, 95, 95, 7, 99, 111, ...>>, [do: :ok]}
iex(4)> 
```

うまくいきました！

# まとめ

そういうわけで，インラインアセンブラの文法はこんな感じにします。

```elixir
defmodule Foo do
  require Asm
  import Asm

  nif_module do
    def_nif add, do: asm add a, b
    def_nif mul, do: asm mul a, b
    generate_nif
  end
end
```

次回は[「ZEAM開発ログ v.0.4.14 Elixir / NIF 間で BigNum をやりとりする (Elixir 側)」](https://qiita.com/zacky1972/items/a6e7cff3dcdddca3312c)をお送りします。お楽しみに！