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