Exploration

This page documents some of my early explorations in the Erlex project.

Note: Joe Armstrong's classic reference, Programming Erlang: Software for a Concurrent World, 2e, contains numerous examples of Erlang source code, ranging from extremely simple to rather complex. With Joe's kind permission, I am using these as a starting point.

Starting Point

Joe's hello.erl example is about as simple as a program can get and still have a visible result. Our version, hello_0.erl, is even starker:

$ cat -n hello_0.erl
     1   -module(hello).
     2   -export([start/0]).
     3   
     4   start() ->
     5       io:format("Hello world~n").

Using Erlang's :epp.parse_file/2 function, we can generate an abstract syntax tree (AST). It seems to have most of the information we need:

iex> {:ok, ast_erl} = :epp.parse_file('hello_0.erl', []); ast_erl
[{:attribute,  1, :file,   {'hello_0.erl', 1}},
 {:attribute,  1, :module, :hello},
 {:attribute,  2, :export, [start: 0]},
 {:function,   4, :start, 0,
  [{:clause, 4, [], [],
    [{:call, 5, {:remote, 5, {:atom, 5, :io}, {:atom, 5, :format}},
      [{:string, 5, 'Hello world~n'}]}]}]},
 {:eof,        6}]

Limitations

Although each node in the AST contains the relevant line number, the character position within the line is not retained. This information could be useful for generating error messages, but we can certainly do without it for the moment.

More critically, comments aren't captured. This is understandable, from the perspective of the BEAM: comments aren't executable code, so why bother with them? However, ignoring comments is unacceptable in a translation. See Comments for some possible workarounds.

Destination

Our initial goal is a transliteration (i.e., naive translation). The output code won't be idiomatic Elixir, but it should perform the same actions:

$ cat -n hello_0.exs
     1   defmodule Hello do
     2     def start do
     3       :io.format("Hello world~n")
     4     end
     5   end
     6   Hello.start

So, let's generate an Elixir AST (i.e., "quoted" version) from the file:

iex> {:ok, str_exs} = File.read("hello_0.exs")
{:ok, "defmodule Hello do\n  def start do\n..."}

iex> {:ok, ast_exs} = Code.string_to_quoted(str_exs); ast_exs
{:__block__, [],
 [{:defmodule, [line: 1],
   [{:__aliases__, [counter: 0, line: 1], [:Hello]},
    [do: {:def, [line: 2],
      [{:start, [line: 2], nil},
       [do: {{:., [line: 3], [:io, :format]}, [line: 3],
         ["Hello world~n"]}]]}]]},
  {{:., [line: 6], [{:__aliases__, [counter: 0, line: 6], [:Hello]}, :start]},
   [line: 6], []}]}

Now, we can reconstitute a printable version and compare the two. Happily, aside from some added parentheses and white space, it's the same code:

iex> IO.write str_exs
defmodule Hello do
  def start do
    :io.format("Hello world~n")
  end
end
Hello.start
:ok

iex> IO.puts Macro.to_string(ast_exs)
(
  defmodule(Hello) do
    def(start) do
      :io.format("Hello world~n")
    end
  end
  Hello.start()
)
:ok

In fact, it turns out that Macro.to_string/1 doesn't need the counter or line values, so we don't have to produce them:

iex> ast_exs_2
{:__block__, [],
 [{:defmodule, [],
   [{:__aliases__, [], [:Hello]},
    [do: {:def, [],
      [{:start, [], nil},
       [do: {{:., [], [:io, :format]}, [], ["Hello world~n"]}]]}]]},
  {{:., [], [{:__aliases__, [], [:Hello]}, :start]}, [], []}]}

iex> IO.puts Macro.to_string(ast_exs_2)
(
  defmodule(Hello) do
    def(start) do
      :io.format("Hello world~n")
    end
  end
  Hello.start()
)
:ok

Discussion

As shown above, there are library routines to turn Erlang source code into an AST and turn an Elixir AST into source code. So, we "only" need to convert an arbitrary Erlang AST into the corresponding Elixir AST. The example ASTs reveal a number of transliteration issues, e.g.:

  • function exporting (export vs. def / defp)
  • implicit call of start() in Erlang
  • naming conventions for atoms, modules, variables, etc.
  • single-assignment vs. variable "pinning" (e.g., ^foo)
  • structural differences between Erlang and Elixir ASTs

Let's divide the task into two parts: extracting needed information from the Erlang AST (into one or more intermediate data structures) and translating it into a well-formed Elixir AST.


This wiki page is maintained by Rich Morin, an independent consultant specializing in software design, development, and documentation. Please feel free to email comments, inquiries, suggestions, etc!

Topic revision: r17 - 14 Jul 2015, RichMorin
This site is powered by Foswiki Copyright © by the contributing authors. All material on this wiki is the property of the contributing authors.
Foswiki version v2.1.6, Release Foswiki-2.1.6, Plugin API version 2.4
Ideas, requests, problems regarding CFCL Wiki? Send us email