add parse_trans_mod & transform example

This commit is contained in:
Ulf Wiger 2011-10-18 11:24:48 +02:00
parent 62d2cf4984
commit 69b05f4de2
6 changed files with 174 additions and 34 deletions

View File

@ -3,3 +3,6 @@
add(X,Y) -> add(X,Y) ->
X + Y. X + Y.
int() ->
int.

View File

@ -0,0 +1,17 @@
-module(test_transform_mod).
-export([ex1/0]).
-include("codegen.hrl").
ex1() ->
parse_trans_mod:transform_module(
ex1, [fun(Fs, _Os) ->
parse_trans:export_function(int, 0, Fs)
end,
fun transform_ex1/2], []).
transform_ex1(Forms, _Opts) ->
NewF = codegen:gen_function(add, fun(A, B) ->
A - B
end),
parse_trans:replace_function(add, 2, NewF, Forms).

View File

@ -20,17 +20,17 @@
%% File : ct_expand.erl %% File : ct_expand.erl
%% @author : Ulf Wiger <ulf.wiger@erlang-solutions.com> %% @author : Ulf Wiger <ulf.wiger@erlang-solutions.com>
%% @end %% @end
%% Description : %% Description :
%% %%
%% Created : 7 Apr 2010 by Ulf Wiger <ulf.wiger@erlang-solutions.com> %% Created : 7 Apr 2010 by Ulf Wiger <ulf.wiger@erlang-solutions.com>
%%------------------------------------------------------------------- %%-------------------------------------------------------------------
%% @doc Compile-time expansion utility %% @doc Compile-time expansion utility
%% %%
%% This module serves as an example of parse_trans-based transforms, %% This module serves as an example of parse_trans-based transforms,
%% but might also be a useful utility in its own right. %% but might also be a useful utility in its own right.
%% The transform searches for calls to the pseudo-function %% The transform searches for calls to the pseudo-function
%% `ct_expand:term(Expr)', and then replaces the call site with the %% `ct_expand:term(Expr)', and then replaces the call site with the
%% result of evaluating `Expr' at compile-time. %% result of evaluating `Expr' at compile-time.
%% %%
%% For example, the line %% For example, the line
@ -51,7 +51,7 @@
-spec parse_transform(forms(), options()) -> -spec parse_transform(forms(), options()) ->
forms(). forms().
parse_transform(Forms, Options) -> parse_transform(Forms, Options) ->
{NewForms,_} = {NewForms,_} =
parse_trans:depth_first(fun xform_fun/4, [], Forms, Options), parse_trans:depth_first(fun xform_fun/4, [], Forms, Options),
parse_trans:revert(NewForms). parse_trans:revert(NewForms).
@ -75,4 +75,4 @@ xform_fun(application, Form, _Ctxt, Acc) ->
end; end;
xform_fun(_, Form, _Ctxt, Acc) -> xform_fun(_, Form, _Ctxt, Acc) ->
{Form, Acc}. {Form, Acc}.

View File

@ -20,13 +20,13 @@
%%% File : parse_trans.erl %%% File : parse_trans.erl
%%% @author : Ulf Wiger <ulf.wiger@erlang-consulting.com> %%% @author : Ulf Wiger <ulf.wiger@erlang-consulting.com>
%%% @end %%% @end
%%% Description : %%% Description :
%%% %%%
%%% Created : 13 Feb 2006 by Ulf Wiger <ulf.wiger@erlang-consulting.com> %%% Created : 13 Feb 2006 by Ulf Wiger <ulf.wiger@erlang-consulting.com>
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @doc Generic parse transform library for Erlang. %%% @doc Generic parse transform library for Erlang.
%%% %%%
%%% <p>...</p> %%% <p>...</p>
%%% %%%
%%% @end %%% @end
@ -53,7 +53,9 @@
top/3 top/3
]). ]).
-export([do_insert_forms/4]). -export([do_insert_forms/4,
replace_function/4,
export_function/3]).
-export([ -export([
context/2, context/2,
@ -106,7 +108,7 @@
{form(), Acc} {form(), Acc}
| {forms(), form(), forms(), Acc}). | {forms(), form(), forms(), Acc}).
-type insp_f() :: fun((type(), form(), #context{}, A) -> {boolean(), A}). -type insp_f() :: fun((type(), form(), #context{}, A) -> {boolean(), A}).
%%% @spec (Reason, Form, Info) -> throw() %%% @spec (Reason, Form, Info) -> throw()
%%% Info = [{Key,Value}] %%% Info = [{Key,Value}]
@ -125,7 +127,7 @@ error(R, F, I) ->
%% @spec (list()) -> integer() %% @spec (list()) -> integer()
%% %%
%% @doc %% @doc
%% Tries to retrieve the line number from an erl_syntax form. Returns a %% Tries to retrieve the line number from an erl_syntax form. Returns a
%% (very high) dummy number if not successful. %% (very high) dummy number if not successful.
%% @end %% @end
%% %%
@ -211,7 +213,7 @@ function_exists(Fname, Arity, Forms) ->
%%% %%%
%%% @doc %%% @doc
%%% Initializes a context record. When traversing through the form %%% Initializes a context record. When traversing through the form
%%% list, the context is updated to reflect the current function and %%% list, the context is updated to reflect the current function and
%%% arity. Static elements in the context are the file name, the module %%% arity. Static elements in the context are the file name, the module
%%% name and the options passed to the transform function. %%% name and the options passed to the transform function.
%%% @end %%% @end
@ -279,6 +281,24 @@ top(F, Forms, Options) ->
{error, [{File, [{Ln, ?MODULE, What}]}], []} {error, [{File, [{Ln, ?MODULE, What}]}], []}
end. end.
replace_function(F, Arity, NewForm, Forms) ->
{NewForms, _} =
do_transform(
fun(function, Form, _Ctxt, Acc) ->
case erl_syntax:revert(Form) of
{function, _, F, Arity, _} ->
{NewForm, false, Acc};
_ ->
{Form, false, Acc}
end;
(_, Form, _Ctxt, Acc) ->
{Form, false, Acc}
end, false, Forms, false),
revert(NewForms).
export_function(F, Arity, Forms) ->
do_insert_forms(above, [{attribute, 1, export, [{F, Arity}]}], Forms, false).
-spec do_insert_forms(above | below, forms(), forms(), #context{}) -> -spec do_insert_forms(above | below, forms(), forms(), #context{}) ->
forms(). forms().
do_insert_forms(above, Insert, Forms, Context) when is_list(Insert) -> do_insert_forms(above, Insert, Forms, Context) when is_list(Insert) ->
@ -359,7 +379,6 @@ outfile(File, Type) ->
ext(pp) -> ".xfm"; ext(pp) -> ".xfm";
ext(forms) -> ".xforms". ext(forms) -> ".xforms".
%% @spec (Forms, Out::filename()) -> ok %% @spec (Forms, Out::filename()) -> ok
%% %%
@ -387,7 +406,7 @@ pp_beam(Beam) ->
%% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason} %% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason}
%% %%
%% @doc %% @doc
%% Reads debug_info from the beam file Beam and pretty-prints it as %% Reads debug_info from the beam file Beam and pretty-prints it as
%% Erlang source code, storing it in the file Out. %% Erlang source code, storing it in the file Out.
%% @end %% @end
%% %%
@ -419,7 +438,7 @@ get_orig_syntax_tree(File) ->
%%% %%%
%%% @doc Reverts back from Syntax Tools format to Erlang forms. %%% @doc Reverts back from Syntax Tools format to Erlang forms.
%%% <p>Note that the Erlang forms are a subset of the Syntax Tools %%% <p>Note that the Erlang forms are a subset of the Syntax Tools
%%% syntax tree, so this function is safe to call even on a list of %%% syntax tree, so this function is safe to call even on a list of
%%% regular Erlang forms.</p> %%% regular Erlang forms.</p>
%%% @end %%% @end
%%% %%%
@ -431,7 +450,7 @@ revert(Tree) ->
%%% @spec (Attr, Context) -> any() %%% @spec (Attr, Context) -> any()
%%% Attr = module | function | arity | options %%% Attr = module | function | arity | options
%%% %%%
%%% @doc %%% @doc
%%% Accessor function for the Context record. %%% Accessor function for the Context record.
%%% @end %%% @end
@ -448,7 +467,7 @@ context(options, #context{options = O} ) -> O.
term(). term().
do_inspect(F, Acc, Forms, Context) -> do_inspect(F, Acc, Forms, Context) ->
%% io:fwrite("do_inspect/4~n", []), %% io:fwrite("do_inspect/4~n", []),
F1 = F1 =
fun(Form, Acc0) -> fun(Form, Acc0) ->
Type = type(Form), Type = type(Form),
{Recurse, Acc1} = apply_F(F, Type, Form, Context, Acc0), {Recurse, Acc1} = apply_F(F, Type, Form, Context, Acc0),
@ -505,7 +524,6 @@ do_depth_first(F, Acc, Forms, Context) ->
this_form_df(F, NewForm, Context, NewAcc) this_form_df(F, NewForm, Context, NewAcc)
end, end,
mapfoldl(F1, Acc, Forms). mapfoldl(F1, Acc, Forms).
enter_subtrees(Form, F, Context, Acc, Recurse) -> enter_subtrees(Form, F, Context, Acc, Recurse) ->
case erl_syntax:subtrees(Form) of case erl_syntax:subtrees(Form) of
@ -540,9 +558,6 @@ this_form_df(F, Form, Context, Acc) ->
{_Be1, _F1, _Af1, _Ac1} = Res1 -> {_Be1, _F1, _Af1, _Ac1} = Res1 ->
Res1 Res1
end. end.
apply_F(F, Type, Form, Context, Acc) -> apply_F(F, Type, Form, Context, Acc) ->
try F(Type, Form, Context, Acc) try F(Type, Form, Context, Acc)
@ -594,7 +609,7 @@ rpt_error(Reason, Fun, Info) ->
"*** Reason = ~p~n", "*** Reason = ~p~n",
"*** Location: ~p~n", "*** Location: ~p~n",
["*** ~10w = ~p~n" || _ <- Info]]), ["*** ~10w = ~p~n" || _ <- Info]]),
Args = [Reason, Fun | Args = [Reason, Fun |
lists:foldr( lists:foldr(
fun({K,V}, Acc) -> fun({K,V}, Acc) ->
[K, V | Acc] [K, V | Acc]

View File

@ -24,7 +24,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @doc Parse transform for code generation pseduo functions %%% @doc Parse transform for code generation pseduo functions
%%% %%%
%%% <p>...</p> %%% <p>...</p>
%%% %%%
%%% @end %%% @end
@ -42,8 +42,8 @@
%% representing the abstract form of that code. %% representing the abstract form of that code.
%% %%
%% The purpose of these functions is to let the programmer write %% The purpose of these functions is to let the programmer write
%% the actual code that is to be generated, rather than manually %% the actual code that is to be generated, rather than manually
%% writing abstract forms, which is more error prone and cannot be %% writing abstract forms, which is more error prone and cannot be
%% checked by the compiler until the generated module is compiled. %% checked by the compiler until the generated module is compiled.
%% %%
%% Supported functions: %% Supported functions:
@ -55,16 +55,16 @@
%% Substitutes the abstract code for a function with name `Name' %% Substitutes the abstract code for a function with name `Name'
%% and the same behaviour as `Fntun'. %% and the same behaviour as `Fntun'.
%% %%
%% `Fun' can either be a anonymous `fun', which is then converted to %% `Fun' can either be a anonymous `fun', which is then converted to
%% a named function. It can also be an `implicit fun', e.g. %% a named function. It can also be an `implicit fun', e.g.
%% `fun is_member/2'. In this case, the referenced function is fetched %% `fun is_member/2'. In this case, the referenced function is fetched
%% and converted to an abstract form representation. It is also renamed %% and converted to an abstract form representation. It is also renamed
%% so that the generated function has the name `Name'. %% so that the generated function has the name `Name'.
%% %%
%% <h2>gen_functions/1</h2> %% <h2>gen_functions/1</h2>
%% %%
%% Takes a list of `{Name, Fun}' tuples and produces a list of abstract %% Takes a list of `{Name, Fun}' tuples and produces a list of abstract
%% data objects, just as if one had written %% data objects, just as if one had written
%% `[codegen:gen_function(N1,F1),codegen:gen_function(N2,F2),...]'. %% `[codegen:gen_function(N1,F1),codegen:gen_function(N2,F2),...]'.
%% %%
%% <h2>exprs/1</h2> %% <h2>exprs/1</h2>
@ -78,7 +78,7 @@
%% used to ensure that all necessary variables are known to the compiler. %% used to ensure that all necessary variables are known to the compiler.
%% %%
%% <h2>Variable substitution</h2> %% <h2>Variable substitution</h2>
%% %%
%% It is possible to do some limited expansion (importing a value %% It is possible to do some limited expansion (importing a value
%% bound at compile-time), using the construct <code>{'$var', V}</code>, where %% bound at compile-time), using the construct <code>{'$var', V}</code>, where
%% `V' is a bound variable in the scope of the call to `gen_function/2'. %% `V' is a bound variable in the scope of the call to `gen_function/2'.
@ -89,7 +89,7 @@
%% codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end). %% codegen:gen_function(Name, fun(L) -> lists:member({'$var',X}, L) end).
%% </pre> %% </pre>
%% %%
%% After transformation, calling `gen(contains_17, 17)' will yield the %% After transformation, calling `gen(contains_17, 17)' will yield the
%% abstract form corresponding to: %% abstract form corresponding to:
%% <pre> %% <pre>
%% contains_17(L) -> %% contains_17(L) ->
@ -122,7 +122,7 @@ parse_transform(Forms, Options) ->
parse_trans:revert(NewForms). parse_trans:revert(NewForms).
xform_fun(application, Form, _Ctxt, Acc) -> xform_fun(application, Form, _Ctxt, Acc) ->
MFA = erl_syntax_lib:analyze_application(Form), MFA = erl_syntax_lib:analyze_application(Form),
case MFA of case MFA of
{codegen, {gen_function, 2}} -> {codegen, {gen_function, 2}} ->
[NameF, FunF] = [NameF, FunF] =
@ -190,8 +190,6 @@ find_function(Name, Arity, Forms) ->
abstract_clauses(ClauseForms) -> abstract_clauses(ClauseForms) ->
Abstract = erl_parse:abstract(parse_trans:revert(ClauseForms)), Abstract = erl_parse:abstract(parse_trans:revert(ClauseForms)),
substitute(Abstract). substitute(Abstract).
substitute({tuple,L0, substitute({tuple,L0,
[{atom,_,tuple}, [{atom,_,tuple},

107
src/parse_trans_mod.erl Normal file
View File

@ -0,0 +1,107 @@
%%============================================================================
%% Copyright 2011 Erlang Solutions Ltd.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%============================================================================
%%
%% Based on meck_mod.erl from http://github.com/esl/meck.git
%% Original author: Adam Lindberg
%%
-module(parse_trans_mod).
%% Interface exports
-export([transform_module/3]).
-export([abstract_code/1]).
-export([beam_file/1]).
-export([compile_and_load_forms/1]).
-export([compile_and_load_forms/2]).
-export([compile_options/1]).
-export([rename_module/2]).
%% Types
-type erlang_form() :: term().
-type compile_options() :: [term()].
%%============================================================================
%% Interface exports
%%============================================================================
transform_module(Mod, PT, Options) ->
Forms = abstract_code(beam_file(Mod)),
PTMods = if is_atom(PT) -> [PT];
is_function(PT, 2) -> [PT];
is_list(PT) -> PT
end,
Transformed = lists:foldl(fun(PTx, Fs) when is_function(PTx, 2) ->
PTx(Fs, Options);
(PTMod, Fs) ->
PTMod:parse_transform(Fs, Options)
end, Forms, PTMods),
compile_and_load_forms(Transformed, Options).
-spec abstract_code(binary()) -> erlang_form().
abstract_code(BeamFile) ->
case beam_lib:chunks(BeamFile, [abstract_code]) of
{ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} ->
Forms;
{ok, {_, [{abstract_code, no_abstract_code}]}} ->
error(no_abstract_code)
end.
-spec beam_file(module()) -> binary().
beam_file(Module) ->
% code:which/1 cannot be used for cover_compiled modules
case code:get_object_code(Module) of
{_, Binary, _Filename} -> Binary;
error -> throw({object_code_not_found, Module})
end.
-spec compile_and_load_forms(erlang_form()) -> ok.
compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []).
-spec compile_and_load_forms(erlang_form(), compile_options()) -> ok.
compile_and_load_forms(AbsCode, Opts) ->
case compile:forms(AbsCode, Opts) of
{ok, ModName, Binary} ->
load_binary(ModName, Binary);
{ok, ModName, Binary, _Warnings} ->
load_binary(ModName, Binary)
end.
-spec compile_options(binary() | module()) -> compile_options().
compile_options(BeamFile) when is_binary(BeamFile) ->
case beam_lib:chunks(BeamFile, [compile_info]) of
{ok, {_, [{compile_info, Info}]}} ->
proplists:get_value(options, Info);
_ ->
[]
end;
compile_options(Module) ->
proplists:get_value(options, Module:module_info(compile)).
-spec rename_module(erlang_form(), module()) -> erlang_form().
rename_module([{attribute, Line, module, _OldName}|T], NewName) ->
[{attribute, Line, module, NewName}|T];
rename_module([H|T], NewName) ->
[H|rename_module(T, NewName)].
%%==============================================================================
%% Internal functions
%%==============================================================================
load_binary(Name, Binary) ->
case code:load_binary(Name, "", Binary) of
{module, Name} -> ok;
{error, Reason} -> exit({error_loading_module, Name, Reason})
end.