ex_to_erl v0.1.0 ExToErl
Utilities to convert Elixir expressions into the corresponding Erlang.
This package is meant to be used as a learning tool or as part of development workflow. It was written to answer questions like: "What does this Elixir expression compile to?". It's very useful to explore the output of the Elixir compiler in a user-friendly way.
One should be careful when using this in production with user supplied input because most functions in this module run the Elixir compiler and generate atoms dynamically at runtime (as the Elixir compiler does).
The code might also be victim of race conditions (I haven't tested running it in parallel, though).
It has no tests yet, but I hope it will have some in the future.
The API will probably change a lot.
I might switch from raising errors to returning {:ok, value}
and :error
.
Link to this section Summary
Functions
Extracts the Erlang abstract code from a BEAM module.
Extracts the Erlang abstract code from a BEAM module and converts it into Erlang source code.
Extracts the Erlang abstract code from a BEAM module, converts it into Erlang source code and writes it into a file.
Converts Elixir AST into Erlang abstract code.
Converts a string containing Elixir code into an Erlang expression.
Converts a string containing Elixir code into Erlang source code.
Pretty prints Erlang abstract code as Erlang source code.
Parses an Erlang expression into erlang abstract code.
Link to this section Functions
beam_to_erlang_abstract_code(module)
Extracts the Erlang abstract code from a BEAM module.
The argument to this function can be either:
- The module name (an atom)
- A
{:module, module, binary, _}
tuple, returned byModule.create/3
- The
binary
part from the tuple above
Examples
TODO
beam_to_erlang_source(module)
Extracts the Erlang abstract code from a BEAM module and converts it into Erlang source code.
The argument to this function can be either:
- The module name (an atom)
- A
{:module, module, binary, _}
tuple, returned byModule.create/3
- The
binary
part from the tuple above
Examples
iex> module = Module.create(MyModule, quote(do: def f(x) do x end), __ENV__)
{:module, MyModule,
<<70, 79, 82, 49, 0, 0, 3, 220, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 124,
0, 0, 0, 13, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
108, 101, 8, 95, 95, 105, 110, 102, 111, ...>>, {:f, 1}}
iex> ExToErl.beam_to_erlang_abstract_code(module)
[
{:attribute, 6, :file, {'iex', 6}},
{:attribute, 6, :module, MyModule},
{:attribute, 6, :compile, [:no_auto_import]},
{:attribute, 6, :export, [__info__: 1, f: 1]},
{:attribute, 6, :spec,
{{:__info__, 1},
[
{:type, 6, :fun,
[
{:type, 6, :product,
[
{:type, 6, :union,
[
{:atom, 6, :attributes},
{:atom, 6, :compile},
{:atom, 6, :functions},
{:atom, 6, :macros},
{:atom, 6, :md5},
{:atom, 6, :module},
{:atom, 6, :deprecated}
]}
]},
{:type, 6, :any, []}
]}
]}},
{:function, 0, :__info__, 1,
[
{:clause, 0, [{:atom, 0, :module}], [], [{:atom, 0, MyModule}]},
{:clause, 0, [{:atom, 0, :functions}], [],
[{:cons, 0, {:tuple, 0, [{:atom, 0, :f}, {:integer, 0, 1}]}, {nil, 0}}]},
{:clause, 0, [{:atom, 0, :macros}], [], [nil: 0]},
{:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :attributes}}], [],
[
{:call, 0,
{:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
[{:atom, 0, MyModule}, {:var, 0, :Key}]}
]},
{:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :compile}}], [],
[
{:call, 0,
{:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
[{:atom, 0, MyModule}, {:var, 0, :Key}]}
]},
{:clause, 0, [{:match, 0, {:var, 0, :Key}, {:atom, 0, :md5}}], [],
[
{:call, 0,
{:remote, 0, {:atom, 0, :erlang}, {:atom, 0, :get_module_info}},
[{:atom, 0, MyModule}, {:var, 0, :Key}]}
]},
{:clause, 0, [{:atom, 0, :deprecated}], [], [nil: 0]}
]},
{:function, 6, :f, 1,
[{:clause, 6, [{:var, 6, :__@1}], [], [{:var, 6, :__@1}]}]}
]
beam_to_erlang_source(module, filename)
Extracts the Erlang abstract code from a BEAM module, converts it into Erlang source code and writes it into a file.
The first argument to this function can be either:
- The module name (an atom)
- A
{:module, module, binary, _}
tuple, returned byModule.create/3
- The
binary
part from the tuple above
Examples
iex> module = Module.create(MyModule, quote(do: def f(x) do x end), __ENV__)
{:module, MyModule,
<<70, 79, 82, 49, 0, 0, 3, 220, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 124,
0, 0, 0, 13, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
108, 101, 8, 95, 95, 105, 110, 102, 111, ...>>, {:f, 1}}
iex> ExToErl.beam_to_erlang_source(module) |> IO.puts()
-file("iex", 3).
-module('Elixir.MyModule').
-compile([no_auto_import]).
-export(['__info__'/1, f/1]).
-spec '__info__'(attributes | compile | functions |
macros | md5 | module | deprecated) -> any().
'__info__'(module) -> 'Elixir.MyModule';
'__info__'(functions) -> [{f, 1}];
'__info__'(macros) -> [];
'__info__'(Key = attributes) ->
erlang:get_module_info('Elixir.MyModule', Key);
'__info__'(Key = compile) ->
erlang:get_module_info('Elixir.MyModule', Key);
'__info__'(Key = md5) ->
erlang:get_module_info('Elixir.MyModule', Key);
'__info__'(deprecated) -> [].
f(__@1) -> __@1.
:ok
elixir_ast_to_erlang_abstract_code(ast)
Converts Elixir AST into Erlang abstract code.
This function expects an Elixir expression. If you supply a block (which is a valid Elixir expression), only the last one will be converted into an Erlang expression. This limitation is a result of the fact that in Erlang a sequence of instructions if not a an Erlang expression (on the other hand, a sequence of Elixir expressions is an Elixir expression).
As with most functions in this module, this function creates atoms at runtime because valid Erlang AST contains atoms.
Examples
iex> ExToErl.elixir_ast_to_erlang_abstract_code({:+, [line: 1], [{:a, [line: 1], nil}, {:b, [line: 1], nil}]})
{:op, 1, :+, {:var, 1, :_a@1}, {:var, 1, :_b@1}}
iex> Code.string_to_quoted!("a - b") |> ExToErl.elixir_ast_to_erlang_abstract_code()
{:op, 1, :-, {:var, 1, :_a@1}, {:var, 1, :_b@1}}
elixir_source_to_erlang_abstract_code(elixir)
Converts a string containing Elixir code into an Erlang expression.
This function expects an Elixir expression.
If you supply a block (which is a valid Elixir expression), only the last one
will be converted into an Erlang expression.
This limitation is a result of the fact that in Erlang a sequence of instructions
if not a an Erlang expression (on the other hand, a sequence of Elixir
expressions is an Elixir expression).
Don't use this function to convert entire Elixir modules to Erlang.
Use ExToErl.beam_to_erlang_source/1
instead.
The function raises if the string is not valid Elixir. As with most functions in this module, this function creates atoms at runtime because valid Erlang AST contains atoms.
Examples
Single expressions:
iex> ExToErl.elixir_source_to_erlang_abstract_code("a + b")
{:op, 1, :+, {:var, 1, :_a@1}, {:var, 1, :_b@1}}
iex> ExToErl.elixir_source_to_erlang_abstract_code("a <= b")
{:op, 1, :"=<", {:var, 1, :_a@1}, {:var, 1, :_b@1}}
Elixir blocks (only the last expression is returned):
iex> ExToErl.elixir_source_to_erlang_abstract_code("_ = a + b; c + d")
{:op, 1, :+, {:var, 1, :_c@1}, {:var, 1, :_d@1}}
You can import functions and macros inside your Elixir expression:
iex> ExToErl.elixir_source_to_erlang_abstract_code("import Bitwise; a >>> b")
{:op, 1, :bsr, {:var, 1, :_a@1}, {:var, 1, :_b@1}}
iex> ExToErl.elixir_source_to_erlang_abstract_code("import Bitwise; a &&& b")
{:op, 1, :band, {:var, 1, :_a@1}, {:var, 1, :_b@1}}
Some expressions may raise warnings, although they should be the same wanings as if the Elixir expression were to be compiled inside a normal Elixir module:
iex> ExToErl.elixir_source_to_erlang_abstract_code("a = b")
warning: variable "a" is unused
warning: variable "a" is unused
{:match, 1, {:var, 1, :_a@2}, {:var, 1, :_b@1}}
Some Elixir operators are actually macros or special forms which can be expanded into quite complex Erlang code:
iex> ExToErl.elixir_source_to_erlang_abstract_code("a or b")
{:case, 1, {:var, 1, :_a@1},
[
{:clause, [generated: true, location: 1], [{:atom, 0, false}], [],
[{:var, 1, :_b@1}]},
{:clause, [generated: true, location: 1], [{:atom, 0, true}], [],
[{:atom, 0, true}]},
{:clause, [generated: true, location: 1], [{:var, 1, :__@1}], [],
[
{:call, 1, {:remote, 1, {:atom, 0, :erlang}, {:atom, 1, :error}},
[{:tuple, 1, [{:atom, 0, :badbool}, {:atom, 0, :or}, {:var, 1, :__@1}]}]}
]}
]}
elixir_source_to_erlang_source(elixir)
Converts a string containing Elixir code into Erlang source code.
This function expects an Elixir expression.
If you supply a block (which is a valid Elixir expression), only the last one
will be converted into an Erlang expression.
This limitation is a result of the fact that in Erlang a sequence of instructions
if not a an Erlang expression (on the other hand, a sequence of Elixir expressions
is an Elixir expression).
Don't use this function to convert entire Elixir modules to Erlang source code.
Use ExToErl.beam_to_erlang_source/1
instead.
The function raises if the string is not valid Elixir. As with most functions in this module, this function creates atoms at runtime because valid Erlang AST contains atoms.
Examples
iex> ExToErl.elixir_source_to_erlang_source("a")
"_a@1\n"
iex> ExToErl.elixir_source_to_erlang_source("a + b")
"_a@1 + _b@1\n"
iex> ExToErl.elixir_source_to_erlang_source("a + b < f.(x)")
"_a@1 + _b@1 < _f@1(_x@1)\n"
iex> ExToErl.elixir_source_to_erlang_source("a or b") |> IO.puts()
case _a@1 of
false -> _b@1;
true -> true;
__@1 -> erlang:error({badbool, 'or', __@1})
end
:ok
iex(3)> ExToErl.elixir_source_to_erlang_source("a.b") |> IO.puts()
case _a@1 of
#{b := __@1} -> __@1;
__@1 when erlang:is_map(__@1) ->
erlang:error({badkey, b, __@1});
__@1 -> __@1:b()
end
:ok
erlang_abstract_code_to_string(abstract_code, opts \\ [])
Pretty prints Erlang abstract code as Erlang source code.
Examples
TODO
erlang_source_to_abstract_code(bin)
Parses an Erlang expression into erlang abstract code.
Examples
iex> ExToErl.erlang_source_to_abstract_code("A + B.")
{:op, 1, :+, {:var, 1, :A}, {:var, 1, :B}}
iex> ExToErl.erlang_source_to_abstract_code("A < B.")
{:op, 1, :<, {:var, 1, :A}, {:var, 1, :B}}
iex> ExToErl.erlang_source_to_abstract_code("A + B * C < F + G.")
{:op, 1, :<,
{:op, 1, :+, {:var, 1, :A}, {:op, 1, :*, {:var, 1, :B}, {:var, 1, :C}}},
{:op, 1, :+, {:var, 1, :F}, {:var, 1, :G}}}
iex> ExToErl.erlang_source_to_abstract_code("A + B * C < f(x) + g(y).")
{:op, 1, :<,
{:op, 1, :+, {:var, 1, :A}, {:op, 1, :*, {:var, 1, :B}, {:var, 1, :C}}},
{:op, 1, :+, {:call, 1, {:atom, 1, :f}, [{:atom, 1, :x}]},
{:call, 1, {:atom, 1, :g}, [{:atom, 1, :y}]}}}
iex(9)> ExToErl.erlang_source_to_abstract_code("A + B * C < f(X) + g(Y).")
{:op, 1, :<,
{:op, 1, :+, {:var, 1, :A}, {:op, 1, :*, {:var, 1, :B}, {:var, 1, :C}}},
{:op, 1, :+, {:call, 1, {:atom, 1, :f}, [{:var, 1, :X}]},
{:call, 1, {:atom, 1, :g}, [{:var, 1, :Y}]}}}