Translation

The Extraction page shows and describes some simple data structures, based on the Erlang AST for the "Hello World" example in Programming Erlang: Software for a Concurrent World, 2e. On this page, we'll look at the problem of translating them into an Elixir AST:

Destination

Our destination, for the moment, is a set of transliterated Elixir code:

$ 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

Here's the corresponding AST. We've emptied out the metadata lists (which we don't need):

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

This may be a bit hard to understand. So, let's recast it as a tree of tuples and lists:

1              { :__block__,    [], [ { :defmodule, ... }, { { :., ... } } ] }
1.1            { :defmodule,    [], [ { :__aliases__, ... }, [ do: ... ] ] }
1.1.1          { :__aliases__,  [], [ :Hello ] }
1.1.2          [ do: { :def, [], [ ... ] } ]
1.1.1.1        { :def,          [], [ { :start, ... }, [ do: ... ] }
1.1.1.1.1      { :start,        [], nil }
1.1.1.1.2      [ do: { :.,   [], [ ... ] }, [], [ "Hello world~n" ] ]
1.1.1.1.2.1    { :.,            [], [ :io, :format ] }
1.2            { :.,            [], [ { :__aliases__, ... }, :start ], [], [] }
1.2.1          { :__aliases__,  [], [ :Hello ] }, :start ] }

Still perplexed? Join the club, but we'll get back to this in a bit...

Starting Point

Our starting point is a pair of data structures which we extracted from the Erlang AST:

erl_map = %{                   # attribute map
  export:   [ start: 0 ],
  file:     { 'hello_0.erl', 1 },
  module:   :hello
}

erl_list = [                   # function list
  { 4, :start, 0,
    [ { :clause, 4, [], [],
        [ { :call, 5,
            { :remote, 5,
              { :atom, 5, :io },
              { :atom, 5, :format } },
            [ { :string, 5, 'Hello world~n' } ]
  } ] } ] }
]

Function definitions are the critical part of the AST, so let's destructure this one as a proof of concept. Not surprisingly, Elixir's pattern matching is a big win here:

iex> function = { _, fun_name, fun_arity, clauses } = hd(erl_list)
{4, :start, 0, [{:clause, 4, [], [], ...

iex> clause = { :clause, _, [], [], exprs } = hd(clauses)
{:clause, 4, [], [], [{:call, 5, ...

iex> expr = { :call, _,
...>     { :remote, _,
...>       { :atom,   _, module   },
...>       { :atom,   _, function } },
...>     [ arg_tuple ] } = hd(exprs)
{:call, 5, {:remote, 5, ...

iex> arg_1 = List.last(Tuple.to_list(arg_tuple))      # Hack!
'Hello world~n'

iex> IO.inspect { fun_name, fun_arity, module, function, arg_1 }
{:start, 0, :io, :format, 'Hello world~n' }

With all of the pieces in hand, generating an Elixir AST is trivial:

iex> { :def, [],
...>   [
...>     { fun_name, [], nil },
...>     [ do: {
...>             { :., [], [ module, function ] },
...>             [],
...>             [ arg_1 ]
...>           } ] ] }
{:def, [],
 [ {:start, [], nil},
   [do: {{:., [], [:io, :format]}, [], ['Hello world~n']}]]}

Production

Getting to a "production" version of this approach, however, requires us to traverse lists, handle a substantial range of tokens, etc.

To be continued...


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: r7 - 26 Jun 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