elixir 或 erlang 或其它运行在 beam vm 上的语言,都会被编译成 .beam 文件。那么能否通过这些文件重建 erlang 代码呢?答案是可以的。

[file] = System.argv()
beam = File.read!(file)

{:ok, {_, [{:abstract_code, {_, ac}}]}} = :beam_lib.chunks(beam, [:abstract_code])

out = file <> ".erl"
File.touch!(out)

File.write!(out, :erl_prettypr.format(:erl_syntax.form_list(ac)))

将上面的代码保存为 beam2erl.exs 文件。

然后我们随便找一个 elixir 文件, 比如:

defmodule Demo do
  defdelegate puts(str), to: IO
end

将其编译,然后把对应的 beam 文件复制到 beam2erl.exs 的目录下,再执行:

$ elixir beam2erl.exs Elixir.Demo.beam  

就能看到生成了一个 .erl 文件,内容是:

-file("lib/demo.ex", 1).

-module('Elixir.Demo').

-compile([no_auto_import]).

-export(['__info__'/1, puts/1]).

-spec '__info__'(attributes |
                 compile |
                 functions |
                 macros |
                 md5 |
                 exports_md5 |
                 module |
                 deprecated) -> any().

'__info__'(module) -> 'Elixir.Demo';
'__info__'(functions) -> [{puts, 1}];
'__info__'(macros) -> [];
'__info__'(exports_md5) ->
    <<"\n\025Y�a#�x�\201W��a#�">>;
'__info__'(Key = attributes) ->
    erlang:get_module_info('Elixir.Demo', Key);
'__info__'(Key = compile) ->
    erlang:get_module_info('Elixir.Demo', Key);
'__info__'(Key = md5) ->
    erlang:get_module_info('Elixir.Demo', Key);
'__info__'(deprecated) -> [].

puts(_str@1) -> 'Elixir.IO':puts(_str@1).

上面的一大串是模块中内置的函数,最后一行是我们代码的内容。

有了这个小脚本,学习和调整包含复杂的 elixir 宏的代码,就方便多了。

可以整理成一个函数

defmodule M do
  defmacro ast_to_erl(ast) do
    [{_, beam}] = Code.compile_quoted(ast)
    {:ok, {_, [{:abstract_code, {_, ac}}]}} = :beam_lib.chunks(beam, [:abstract_code])
    :erl_prettypr.format(:erl_syntax.form_list(ac), encoding: :utf8)
    |> IO.puts()
  end
end

Ljzn
399 声望102 粉丝

网络安全;函数式编程;数字货币;人工智能