RFC: parse_trans for code generation

Ulf Wiger ulf.wiger@REDACTED
Fri Jul 2 12:44:19 CEST 2010


I realize that quite the minority of list readers take an
interest in parse transforms. Nevertheless, I thought I'd
highlight a small addition to the parse_trans project to
assist code generation.

http://github.com/esl/parse_trans

(Note too the new location. The old repos still exists, but
I strongly recommend that you pull from the esl location.)

The parse_trans library has primarily offered various ways
to fold over code in various passes in order to collect
information and then make small transformations to code.

However, when generating significant amounts of code, you
tend to end up with tons of abstract code that looks pretty
horrible from a maintenance perspective (I've occasionally
thought about making an Abstract Form <-> XML translator and
calling it Executable XML - to be released on April 1, of course.)

To help things somewhat, I made a code generation parse transform,
called parse_trans_codegen. A small, highly contrived, example can
be found at

http://github.com/esl/parse_trans/blob/master/examples/ex_codegen.erl

Since it is small, I can include it here:

-module(ex_codegen).
-compile({parse_transform, parse_trans_codegen}).
-export([f/1, g/2, gen/2]).

f(Name) ->
     codegen:gen_function(
       Name,
       fun(1,2,3) ->
	      foo;
	  (A,B,C) ->
	      {A,B,C}
       end).

g(Name, V) ->
     codegen:gen_function(
       Name,
       fun(L) ->
	      member({'$var',V}, L)
       end).

gen(Name, X) ->
     codegen:gen_function(
       Name,
       fun(L) -> lists:member({'$var',X}, L) end).


The codegen:gen_function/2 function is just a pseudo
function that triggers the transformation. It takes as
first argument the name of the function being generated,
and as second argument an anonymous function whose body
defines the semantics of the generated function. A named
function is generated with the same clauses as the provided
fun. The {'$var',V} construct in g/2 is a trick to allow
value expansion at code generation time.

The arity of the generated function is derived from the
provided fun.

Using a utility function in parse_trans, we can inspect
the result of the compile-time transformation:

uwiger@REDACTED:~/ETC/git/parse_trans/examples$ erl -pa ../ebin
Erlang R13B04 (erts-5.7.5) [source] [rq:1] [async-threads:0] [hipe] 
[kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> parse_trans:pp_beam("ex_codegen.beam").
-file("./ex_codegen.erl", 1).

-module(ex_codegen).

-export([f/1, g/2, gen/2]).

f(Name) ->
     {function, 9, Name, 3,
      [{clause, 11,
        [{integer, 11, 1}, {integer, 11, 2}, {integer, 11, 3}],
        [], [{atom, 12, foo}]},
       {clause, 13,
        [{var, 13, 'A'}, {var, 13, 'B'}, {var, 13, 'C'}], [],
        [{tuple, 14,
          [{var, 14, 'A'}, {var, 14, 'B'}, {var, 14, 'C'}]}]}]}.

g(Name, V) ->
     {function, 20, Name, 1,
      [{clause, 22, [{var, 22, 'L'}], [],
        [{call, 23, {atom, 23, member},
          [erl_parse:abstract(V, 23), {var, 23, 'L'}]}]}]}.

gen(Name, X) ->
     {function, 28, Name, 1,
      [{clause, 28, [{var, 28, 'L'}], [],
        [{call, 28,
          {remote, 28, {atom, 28, lists}, {atom, 28, member}},
          [erl_parse:abstract(X, 28), {var, 28, 'L'}]}]}]}.
ok

(The final 'ok' is the return value from the pretty-printing.)

We can call one of the functions:

2> ex_codegen:g(foo, bar).
{function,20,foo,1,
           [{clause,22,
                    [{var,22,'L'}],
                    [],
                    [{call,23,
                           {atom,23,member},
                           [{atom,23,bar},{var,23,'L'}]}]}]}

It is easier to see what happened if we pretty-print the
result:

2> ex_codegen:gen(contains_17, 17).
{function,28,contains_17,1,
           [{clause,28,
                    [{var,28,'L'}],
                    [],
                    [{call,28,
                           {remote,28,{atom,28,lists},{atom,28,member}},
                           [{integer,28,17},{var,28,'L'}]}]}]}
3> io:fwrite("~s~n", [erl_pp:form(v(2))]).
contains_17(L) ->
     lists:member(17, L).

ok

(Again, the trailing 'ok' is just the return value.)

The {'$var', V} construct allows us to dynamically insert
values into the code at generation time.

I hope some of you find it useful. Comments are welcome.
There is not much documentation - only a couple of paragraphs
in the edoc for parse_trans_codegen:parse_transform/2.

BR,
Ulf W
-- 
Ulf Wiger
CTO, Erlang Solutions Ltd, formerly Erlang Training & Consulting Ltd
http://www.erlang-solutions.com


More information about the erlang-questions mailing list