parse transformery with syntax_tools

Ulf Wiger (AL/EAB) ulf.wiger@REDACTED
Sat Nov 5 22:18:18 CET 2005


Here's a first go at a helper function for 
parse transforms. I'm working on a new incarnation
of plain_fsm, but decided that I wanted a more stable
implementation of the parse transform code. Copying
erl_id_trans.erl and modifying it seems... well, 
pretty primitive and not very future safe.

In case anyone else wants to try it out, or give
feedback, I enclose the code, as it is, below.

It doesn't do anything intelligent, apart from 
cheering when it comes across a call to 
plain_fsm:extended_receive/1.


Here's some sample output:

ws12858> erlc -W -I ../inc/ sysFsmExample.erl
xform_plainfsm(Forms)
YEAH!!
extended_receive/1 called in {idle,1}
YEAH!!
extended_receive/1 called in {c,2}


(Amazingly useful, obviously...)


The specific code for doing the above
is so far 20 lines, in xform_plainfsm/1.

The generic part is transform/5:

transform(Forms, Before, After, InitContext, InitAcc)

Forms : the compiled forms passed to parse_transform/2
Before: fun(Type, Form, Context) -> 
             {Form1, Context1, InitSubAcc}
After : fun(Type, Form, Context, SubAcc, Acc) ->
             {Form1, Acc1}
InitContext : term()
InitAcc     : term()

The idea is: 
1) First call Before/3 on a Form. This function
   determines context for the subtrees (e.g.
   the name and arity of the enclosing function)
   It also initializes an accumulator to be used
   when folding over the subtrees.
2) Check if there are subtrees, and call
   transform/5 recursively over them. The function
   returns (possibly) modified subtrees and the
   accumulator (SubAcc). This can be used to tell
   the level above that something changed.
3) Then call After(Type, Form, Context, SubAcc, Acc)
   This lets you modify the enclosing form, using 
   knowledge of what's happened with the subtrees
   (reported back with SubAcc). You can report up
   to the next higher level through Acc. One thing
   that could be done here is assigning a variable
   in a function clause (plain_fsm will do this:
   if there's a call to extended_receive/1, I
   wrap the argument with a variable assignment.
   If I do that for all functions of arity 1, 
   I will get warnings about unused variables,
   so I only want to do it if it's actually 
   going to be used.)

The main benefit would of course be that I can use
the functions in erl_syntax and erl_syntax_lib and
only act on the forms that I actually want to 
inspect or change.

/Uffe

================================

-module(sysPlainXform).

-export([parse_transform/2]).


parse_transform(Forms, _Options) ->
    {NewTree, _} = xform_plainfsm(Forms),
    [erl_syntax:revert(T) || T <- lists:flatten(NewTree)].




xform_plainfsm(Forms) ->
    Bef = fun(function, Form, _) ->
                  {Form, erl_syntax_lib:analyze_function(Form), []};
             (_, Form, State) ->
                  {Form, State, []}
          end,
    Aft = fun(application, Form, Context, _SubAcc, Acc) ->
                  case erl_syntax_lib:analyze_application(Form) of
                      {plain_fsm, {extended_receive, 1}} ->
                          io:format("YEAH!!~n"
                                    "extended_receive/1 called in ~p~n",

                                    [Context]);
                      _ ->
                          ok
                  end,
                  {Form, Acc};
             (_, Form, _Context, _SubAcc, Acc) ->
                  {Form, Acc}
          end,
    transform(Forms, Bef, Aft, top, []).



transform(Forms, Before, After, Context, Acc) ->
    F1 =
        fun(Form, Acc0) ->
                Type = erl_syntax:type(Form),
                {Form1, Context1, InitSubAcc} =
                    try Before(Type, Form, Context)
                    catch
                        error:Reason ->
                            error(Reason, 'before', Before, 
                                  [{type, Type},
                                   {context, Context},
                                   {acc, Acc},
                                   {form, Form}])
                    end,
                {Form2, SubAcc2} =
                    case erl_syntax:subtrees(Form1) of
                        [] ->
                            {Form1, InitSubAcc};
                        List ->
                            {NewList, NewSubAcc} =
                                transform(
                                  List, Before, After, Context1,
InitSubAcc),
                            NewForm = erl_syntax:update_tree(Form,
NewList),
                            {NewForm, NewSubAcc}
                    end,
                Type2 = erl_syntax:type(Form2),
                {_Form3, _Acc3} =
                    try After(Type2, Form2, Context, SubAcc2, Acc0)
                    catch
                        error:Reason2 ->
                            error(Reason2, 'after', After, 
                                  [{type, Type2},
                                   {context, Context},
                                   {sub_acc, SubAcc2},
                                   {acc, Acc0},
                                   {form, Form2}])
                    end
        end,
    F2 = fun(List, St) when is_list(List) ->
                 lists:mapfoldl(F1, St, List);
            (Form, St) ->
                 F1(Form, St)
         end,
    lists:mapfoldl(F2, Acc, Forms).



error(Reason, BeforeOrAfter, Fun, Info) ->
    Fmt = lists:flatten(
            ["*** ERROR in parse_transform function:~n"
             "*** Reason     = ~p~n"
             "*** applying ~w fun (~p)~n",
             ["*** ~10w = ~p~n" || _ <- Info]]),
    Args = [Reason, BeforeOrAfter, Fun | 
            lists:foldr(
              fun({K,V}, Acc) ->
                      [K, V | Acc]
              end, [], Info)],
    io:format(Fmt, Args),
    erlang:error(Reason).




More information about the erlang-questions mailing list