5
votes

I want to see what happens when elixir gets transformed into beam files. Is there any way to print in console or in a file how it is translated? I want to know what would this module look like in erlang.

I was thinking if there is a debug mode of elixir, which would output any of the above.

More specifically I have this example:

defmodule Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
end

The above code raises a warning, which is understanable considering what I've done. Basically I want to understand a bit more what's happening.

2
If you're trying to get a only, you'll have to pass a 0 as the second parameter. If you don't pass a second parameter, you'll get a + 2 - Onorio Catenacci
If i understood you correct, then you are wrong. If you call Test.t1(0) the output is 0, but if you call Test.t1(0, 4) then the outcome is 4. Basically everytime you call the function with one argument you end up invoking the first one. Btw, I'm on elixir 1.3.4 - ipinak

2 Answers

9
votes

First, you need to compile the Elixir module to a .beam file:

$ cat test.ex
defmodule Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
end
$ elixirc test.ex
warning: this clause cannot match because a previous clause at line 2 always matches
  test.ex:3

This will generate Elixir.Test.beam. Then, you can decompile this .beam to Erlang source using the following escript (I copied this from some answer here on Stackoverflow a while ago. Unfortunately I can't seem to locate the exact source but this code is in many answers here including this one.):

$ cat decompile.erl
#!/usr/bin/env escript

main([BeamFile]) ->
  {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(BeamFile,[abstract_code]),
  io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

Then run it:

$ escript decompile.erl Elixir.Test.beam
-compile(no_auto_import).

-file("test.ex", 1).

-module('Elixir.Test').

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

-spec '__info__'(attributes | compile | exports |
         functions | macros | md5 | module |
         native_addresses) -> atom() |
                      [{atom(), any()} |
                       {atom(), byte(), integer()}].

'__info__'(functions) -> [{t1, 1}, {t1, 2}];
'__info__'(macros) -> [];
'__info__'(info) ->
    erlang:get_module_info('Elixir.Test', info).

t1(a@1) -> a@1;
t1(x0@1) -> t1(x0@1, 2).

t1(a@1, b@1) -> a@1 + b@1.
5
votes

This probably should be more of a comment to @Dogbert’s answer above, but I would post it as a separate answer for the sake of formatting.

One does not need to create .ex files and invoke the compiler on them to produce beams:

{:module, _, bytecode, _} =
  defmodule Elixir.Test do
    def t1(a), do: a
    def t1(a, b \\ 2), do: a + b
  end
# File.write!("Elixir.Test.beam", bytecode)

now you might have had a beam file written (we have it stored in the bytecode variable by the way.)

NB: beam_lib:chunks/2 works if and only the beam contains unencrypted debug information (elixir beams by default do.)

Also, you don’t need to write decompiled erlang code, you might simply pass a binary there, directly in Elixir:

:beam_lib.chunks(bytecode, [:abstract_code])

To extract the code itself:

{:ok,{_,[abstract_code: {_, code}]}} = 
   bytecode |> :beam_lib.chunks([:abstract_code])

Now code contains the code, it should be enough to examine it, but you still are free to use erlang build-ins:

code |> :erl_syntax.form_list

or:

code |> :erl_syntax.form_list |> :erl_prettypr.format

The latter will give you the binary charlist, containing erlang code, exactly as in @Dogbert’s answer. Use IO.puts to output it.