%% %% @title Smerl: Simple Metaprogramming for Erlang %% %% @doc

Smerl is an Erlang library %% that simplifies the creation and manipulation of Erlang modules in %% runtime.

%%

Smerl uses Erlang's capabilities for hot code swapping and %% abstract syntax tree parsing to do its magic. Smerl is inspired by %% the rdbms_codegen.erl module in the RDBMS application written by %% Ulf Wiger. RDBMS is part of Jungerl. %%

%% %%

Here's a quick example illustrating how to use Smerl:

%% `` %% test_smerl() -> %% M1 = smerl:new(foo), %% {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."), %% smerl:compile(M2), %% foo:bar(), % returns 2`` %% smerl:has_func(M2, bar, 0). % returns true %% %%

New functions can be expressed either as strings of Erlang code %% or as abstract forms. For more information, read the Abstract Format %% section in the ERTS User's guide %% (link).

%% %%

Using the abstract format, the 3rd line of the above example %% would be written as

%% `` %% {ok,M2} = smerl:add_func(M1, {function,1,bar,0, %% [{clause,1,[],[], %% [{op,1,'+',{integer,1,1},{integer,1,1}}]}]).`` %% %%

The abstact format may look more verbose in this example, but %% it's also more amenable to runtime manipulation.

%% %%

To manipulate or query an existing module rather than a new module,%% %% the first line could be rewritten such as:

%% %% ``smerl:for_module(mnesia),'' %% %%

Detailed Description

%%

With Smerl, you can both create new modules and manipulate existing %% modules in runtime. You can also query whether a module has a given %% function by calling smerl:has_func. To start creating a new module, call %% smerl:new(ModuleName). To start modifying an existing module, %% call smerl:for_module(ModuleName). (The module be accessible %% with code:which and either have been compiled debug_info or its source %% file must in the same directory as the .beam file or in a ../src directory %% relative to the .beam file's ./ebin directory.) By calling smerl:for_file, %% you can create a new module from an Erlang source file.

%% %%

smerl:new, smerl:for_module and smerl:for_file return an %% MetaMod record for the module. To manipulate the module, %% use smerl:add_func and smerl:remove_func. Just remember not to %% add the same function name with the same arity twice as it will %% eventually result in a compilation error.

%% %%

When you're ready to compile your module, call smerl:compile, %% passing in the MetaMod record. If there are no errors, %% you can start using the new module.

%% %%

New capabilities (8/16/06):

%%

smerl:get_func, retrieves the abstract form for a given function, %% and smerl:replace_func, which does a smerl:remove_func followed by a %% smerl:add_func. %% %%

New capabilities (8/17/06):

%%

smerl:add_func and smerl:replace_func can now accept fun expressions %% as parameters. This only works in the Erlang shell at the moment. %% With fun expressions, you longer have to rely on source strings and %% abstract %% forms to add behaviour to a module. Even closures are supported. Closure %% variables are expanded in the beginning the function. Example: %% %% `` %% A = 15. %% C = smerl:new(foo), %% {ok, C2} = smerl:add_func(C, bar, fun(B) -> A + B + 37 end), %% smerl:compile(C2), %% foo:bar(5). % returns 57`` %% %%

Both smerl:add_func and smerl:replace_func support fun expressions.

%% %% %% %% @author Yariv Sadan [http://yarivsblog.com] %% Copyright (c) 2006 Yariv Sadan %% %% Permission is hereby granted, free of charge, to any person %% obtaining a copy of this software and associated documentation %% files (the "Software"), to deal in the Software without restriction, %% including without limitation the rights to use, copy, modify, merge, %% publish, distribute, sublicense, and/or sell copies of the Software, %% and to permit persons to whom the Software is furnished to do %% so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included %% in all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. %% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY %% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, %% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE %% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -module(smerl). -author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com"). -export([new/1, for_module/1, for_file/1, get_module/1, set_module/2, get_forms/1, set_forms/2, get_exports/1, set_exports/2, get_export_all/1, set_export_all/2, remove_export/3, get_attribute/2, add_func/2, add_func/3, add_func/4, remove_func/3, has_func/3, get_func/3, replace_func/2, replace_func/3, compile/1, compile/2, rename/2, curry/2, curry/4, curry/5, curry_add/3, curry_add/4, curry_add/5, curry_add/6, curry_replace/3, curry_replace/4, embed_params/2, embed_params/4, embed_params/5, embed_all/2, extend/2, extend/3, to_src/1, to_src/2 ]). -define(L(Obj), io:format("LOG ~w ~p\n", [?LINE, Obj])). -define(S(Obj), io:format("LOG ~w ~s\n", [?LINE, Obj])). %% @type meta_mod(). A tuple holding the abstract representation for module. %% @type func_form(). The abstract form for the function, as described %% in the ERTS Users' manual. %% The record type holding the abstract representation for a module. -record(meta_mod, {module, file, exports = [], forms = [], export_all = false}). %% @doc Create a record for a new module with the given module name. %% %% @spec new(Module::atom()) -> meta_mod() new(ModuleName) when is_atom(ModuleName) -> #meta_mod{module = ModuleName}. %% @doc Create a MetaMod record for manipulating an existing module. %% %% @spec for_module(ModuleName::atom() || string()) -> %% {ok, meta_mod()} | {error, Error} for_module(FileName) when is_list(FileName) -> for_file(FileName); for_module(ModuleName) when is_atom(ModuleName) -> [_Exports, _Imports, _Attributes, {compile, [_Options, _Version, _Time, {source, Path}]}] = ModuleName:module_info(), case for_file(Path) of {ok, _Mod} = Res-> Res; _Err -> case code:which(ModuleName) of Path1 when is_list(Path1) -> case get_forms(ModuleName, Path1) of {ok, Forms} -> mod_for_forms(Forms); _Other -> {error, {invalid_module, ModuleName}} end; _Err -> {error, {invalid_module, ModuleName}} end end. %% @doc Create a MetaMod record for a module from its source %% file. %% %% @spec for_file(SrcFilePath::string()) -> {ok, meta_mod()} | %% {error, invalid_module} for_file(SrcFilePath) -> case epp:parse_file(SrcFilePath, [], []) of {ok, Forms} -> mod_for_forms(Forms); _err -> {error, {invalid_module, SrcFilePath}} end. mod_for_forms([{attribute,_,file,{FileName,_FileNum}}, {attribute, _, module, ModuleName}|Forms]) -> {Exports, OtherForms, ExportAll} = lists:foldl( fun({attribute, _, export, ExportList}, {ExportsAcc, FormsAcc, ExportAll}) -> {ExportList ++ ExportsAcc, FormsAcc, ExportAll}; ({attribute, _, compile, export_all}, {ExportsAcc, FormsAcc, _ExportAll}) -> {ExportsAcc, FormsAcc, true}; ({eof, _}, Acc) -> Acc; (Form, {ExportsAcc, FormsAcc, ExportAll}) -> {ExportsAcc, [Form | FormsAcc], ExportAll} end, {[], [], false}, Forms), {ok, #meta_mod{module = ModuleName, file = FileName, exports = Exports, forms = OtherForms, export_all = ExportAll }}; mod_for_forms(_) -> {error, invalid_module}. %% @doc Return the module name for the MetaMod record. %% %% @spec(MetaMod::meta_mod()) -> atom() get_module(MetaMod) -> MetaMod#meta_mod.module. %% @doc Set the MetaMod record's module name to the new name. %% %% @spec set_module(MetaMod::meta_mod(), NewName::atom()) -> %% NewMod::meta_mod() set_module(MetaMod, NewName) -> MetaMod#meta_mod{module = NewName}. %% @doc Return the list of function forms in the MetaMod record. %% %% @spec get_forms(MetaMod::meta_mod()) -> [Form] get_forms(MetaMod) -> MetaMod#meta_mod.forms. set_forms(MetaMod, Forms) -> MetaMod#meta_mod{forms = Forms}. %% @doc Return the list of exports in the MetaMod record. %% %% @spec get_exports(MetaMod::meta_mod()) -> %% [{FuncName::atom(), Arity::integer()}] get_exports(MetaMod) -> case MetaMod#meta_mod.export_all of false -> MetaMod#meta_mod.exports; true -> lists:foldl( fun({function, _L, Name, Arity, _Clauses}, Exports) -> [{Name, Arity} | Exports]; (_Form, Exports) -> Exports end, [], MetaMod#meta_mod.forms) end. %% @doc Set the export list to the given list. %% %% @spec set_exports(MetaMod::meta_mod(), %% Exports::[{FuncName::atom(), Arity::integer()}] set_exports(MetaMod, Exports) -> MetaMod#meta_mod{exports = Exports}. %% @doc Get the export_all value for the module. %% %% @spec get_export_all(MetaMod::meta_mod) -> true | false get_export_all(MetaMod) -> MetaMod#meta_mod.export_all. %% @doc Set the export_all value for the module. %% %% @spec set_export_all(MetaMod::meta_mod(), Val::true | false) -> %% NewMetaMod::meta_mod() set_export_all(MetaMod, Val) -> MetaMod#meta_mod{export_all = Val}. %% @doc Remove the given export from the list of exports in the MetaMod record. %% %% @spec remove_export(MetaMod::meta_mod(), FuncName::atom(), %% Arity::integer()) -> NewMod::meta_mod() remove_export(MetaMod, FuncName, Arity) -> MetaMod#meta_mod{exports = lists:delete({FuncName, Arity}, MetaMod#meta_mod.exports)}. %% @doc Get the value a the module's attribute. %% %% @spec get_attribute(MetaMod::meta_mod(), AttName::atom()) -> %% {ok, Val} | error get_attribute(MetaMod, AttName) -> case lists:keysearch(AttName, 3, get_forms(MetaMod)) of {value, {attribute,_,_,Val}} -> {ok, Val}; _ -> error end. %% Get the abstract representation, if available, for the module. %% %% Strategy: %% 1) Try to get the abstract code from the module if it's compiled %% with debug_info. %% 2) Look for the source file in the beam file's directory. %% 3) If the beam file's directory ends with 'ebin', then search in %% [beamdir]/../src get_forms(Module, Path) -> case beam_lib:chunks(Path, [abstract_code]) of {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> {ok, Forms}; _Err -> case filename:find_src(Module, [{"ebin", "src"}]) of {error, _} = Err -> Err; {SrcPath, _} -> Filename = SrcPath ++ ".erl", epp:parse_file(Filename, [], []) end end. %% @doc Add a new function to the MetaMod record and return the new MetaMod %% record. The new function will be added to the module's export list. %% %% @spec add_func(MetaMod::meta_mod(), Form::func_form() | string()) -> %% {ok, NewMod::meta_mod()} | {error, parse_error} add_func(MetaMod, Form) -> add_func(MetaMod, Form, true). %% @doc Add a new function to the MetaMod record and return the new MetaMod %% record. If Export == false, the function will not be added to the %% MetaMod record's export list. %% %% @spec add_func(MetaMod::meta_mod(), Func::func_form() | string()) -> %% {ok, NewMod::meta_mod()} | {error, parse_error} add_func(MetaMod, Func, Export) when is_list(Func) -> case parse_func_string(Func) of {ok, Form} -> add_func(MetaMod, Form, Export); Err -> Err end; add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, true) -> Foo = {ok, MetaMod#meta_mod{ exports = [{FuncName, Arity} | MetaMod#meta_mod.exports], forms = [Form | MetaMod#meta_mod.forms] }}, Foo; add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, false) -> {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; add_func(MetaMod, Name, Fun) when is_function(Fun) -> add_func(MetaMod, Name, Fun, true); add_func(_, _, _) -> {error, parse_error}. add_func(MetaMod, Name, Fun, Export) when is_function(Fun) -> case form_for_fun(Name, Fun) of {ok, Form} -> add_func(MetaMod, Form, Export); Err -> Err end. form_for_fun(Name, Fun) -> Line = 999, Info = erlang:fun_info(Fun), case Info of [{module, _ModName}, _FuncName, _Arity, _Env, {type, external}] -> {error, cant_add_external_funcs}; [_Pid, _Module, _NewIdx, _NewUniq, _Index, _Uniq, _Name, {arity, Arity}, {env, [Vars, _Unknown1, _Unknown2, Clauses]}, {type, local}] -> EnvVars = lists:map( fun({VarName, Val}) -> {match,Line,{var,Line,VarName}, erl_parse:abstract(Val)} end, Vars), NewClauses = lists:map( fun({clause, Line1, Params, Guards, Exprs}) -> {clause, Line1, Params, Guards, EnvVars ++ Exprs} end, Clauses), {ok, {function, Line, Name, Arity, NewClauses}}; _Other -> {error, bad_fun} end. parse_func_string(Func) -> case erl_scan:string(Func) of {ok, Toks, _} -> case erl_parse:parse_form(Toks) of {ok, _Form} = Res -> Res; _Err -> {error, parse_error} end; _Err -> {error, parse_error} end. %% @doc Try to remove the function from the MetaMod record return the %% resulting MetaMod record. %% If the function isn't found, the original MetaMod record is returned. %% %% @spec remove_func(MetaMod::meta_mod(), FuncName::string(), Arity::integer()) %% -> NewMod::meta_mod() %% remove_func(MetaMod, FuncName, Arity) -> MetaMod#meta_mod{forms = lists:filter( fun({function, _Line, FuncName1, Arity1, _Clauses}) when FuncName1 =:= FuncName, Arity =:= Arity1-> false; (_) -> true end, MetaMod#meta_mod.forms), exports = lists:filter( fun({FuncName1, Arity1}) when FuncName1 =:= FuncName, Arity1 =:= Arity -> false; (_) -> true end, MetaMod#meta_mod.exports) }. %% @doc Check whether a MetaMod record has a function with the given name %% and arity. %% @spec has_func(MetaMod::meta_mod(), FuncName::atom(), Arity::integer()) -> %% bool() has_func(MetaMod, FuncName, Arity) -> lists:any(fun({function, _Line, FuncName1, Arity1, _Clauses}) when FuncName1 == FuncName, Arity1 == Arity -> true; (_) -> false end, MetaMod#meta_mod.forms). %% @doc Get the form for the function with the specified arity in the %% MetaMod record. %% %% @spec get_func(MetaMod::meta_mod() | Module::atom(), %% FuncName::atom(), Arity::integer()) -> %% {ok, func_form()} | {error, Err} get_func(Module, FuncName, Arity) when is_atom(Module) -> case smerl:for_module(Module) of {ok, C1} -> get_func(C1, FuncName, Arity); Err -> Err end; get_func(MetaMod, FuncName, Arity) -> get_func2(MetaMod#meta_mod.forms, FuncName, Arity). get_func2([], FuncName, Arity) -> {error, {function_not_found, {FuncName, Arity}}}; get_func2([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest], FuncName, Arity) -> {ok, Form}; get_func2([_Form|Rest], FuncName, Arity) -> get_func2(Rest, FuncName, Arity). %% Replace an existing function with the new one. If the function doesn't exist %% the new function is added to the MetaMod record. %% This function calls smerl:remove_func followed by smerl:add_func. %% %% @spec replace_func(MetaMod::meta_mod(), Function::string() | func_form()) -> %% {ok, NewMod::meta_mod()} | {error, Error} replace_func(MetaMod, Function) when is_list(Function) -> case parse_func_string(Function) of {ok, Form} -> replace_func(MetaMod, Form); Err -> Err end; replace_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form) -> Mod1 = remove_func(MetaMod, FuncName, Arity), add_func(Mod1, Form); replace_func(_MetaMod, _) -> {error, parse_error}. %% @doc Simliar to replace_func/2, but accepts a function %% name + fun expression. %% %% @spec replace_func(MetaMod::meta_mod(), Name::atom(), Fun::function()) -> %% {ok, NewMod::meta_mod()} | {error, Error} replace_func(MetaMod, Name, Fun) when is_function(Fun) -> case form_for_fun(Name, Fun) of {ok, Form} -> replace_func(MetaMod, Form); Err -> Err end. %% @doc Compile the module represented by the MetaMod record. %% You should call this function once you're done manipulating your %% module and you're ready to deploy the changes into the VM in runtime. %% %% @spec compile(MetaMod::meta_mod()) -> ok | {error, Error} compile(MetaMod) -> compile(MetaMod, undefined). compile(MetaMod, undefined) -> compile(MetaMod, [report_errors, report_warnings]); compile(MetaMod, Options) -> Forms = [{attribute, 2, module, MetaMod#meta_mod.module}, {attribute, 3, export, get_exports(MetaMod)}], FileName = case MetaMod#meta_mod.file of undefined -> atom_to_list(get_module(MetaMod)); Val -> Val end, Forms1 = [{attribute, 1, file, {FileName, 1}} | Forms], Forms2 = Forms1 ++ lists:reverse(MetaMod#meta_mod.forms), case compile:forms(Forms2, Options) of {ok, Module, Bin} -> Res = case lists:keysearch(outdir, 1, Options) of {value, {outdir, OutDir}} -> file:write_file( OutDir ++ ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ ".beam", Bin); false -> ok end, case Res of ok -> code:purge(Module), case code:load_binary( Module, atom_to_list(Module) ++ ".erl", Bin) of {module, _Module} -> ok; Err -> Err end; Err -> Err end; Err -> Err end. %% @doc Change the name of the function represented by the form. %% %% @spec rename(Form::func_form(), NewName:atom()) -> %% {ok, NewForm::func_form()} | {error, Err} rename({function, Line, _Name, Arity, Clauses}, NewName) -> {function, Line, NewName, Arity, Clauses}. %% @doc Get the curried form for the function and parameter %% %% @spec curry(Form::func_form(), Param::term() | list()) -> %% {ok, NewForm::func_form()} | {error, Err} curry(Form, Param) when not is_list(Param) -> curry(Form, [Param]); curry({function, _Line, _Name, Arity, _Clauses}, Params) when length(Params) > Arity -> {error, too_many_params}; curry({function, Line, Name, Arity, Clauses}, NewParams) -> NewClauses = lists:foldl( fun(Clause, Clauses1) -> [curry_clause(Clause, NewParams) | Clauses1] end, [], Clauses), {ok, {function, Line, Name, Arity-length(NewParams), NewClauses}}. curry_clause({clause, L1, ExistingParams, Guards, Exprs}, NewParams) -> {FirstParams, LastParams} = lists:split(length(NewParams), ExistingParams), Matches = lists:foldl( fun({Var, NewVal}, Acc) -> [{match, 1, Var, erl_parse:abstract(NewVal)} | Acc] end, [], lists:zip(FirstParams, NewParams)), {clause, L1, LastParams, Guards, Matches ++ Exprs}. %% @doc Curry the function from the module with %% the given param(s) %% %% @spec curry(ModName::atom(), Name::atom(), arity::integer(), %% Params::term() | list()) -> %% {ok, NewForm} | {error, Err} curry(ModName, Name, Arity, Params) when is_atom(ModName) -> case for_module(ModName) of {ok, MetaMod} -> curry(MetaMod, Name, Arity, Params); Err -> Err end; %% @doc Curry the function from the MetaMod record with %% the given param(s) %% %% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(), %% Params::term() | list()) -> %% {ok, NewForm} | {error, Err} curry(MetaMod, Name, Arity, Params) -> case get_func(MetaMod, Name, Arity) of {ok, Form} -> curry(Form, Params); Err -> Err end. %% @doc Curry the function from the module or MetaMod %% record with the param(s), and return its renamed form. %% %% @spec curry(Module::atom() | meta_mod(), Name::atom(), arity::integer(), %% Params::term() | list()) -> %% {ok, NewForm} | {error, Err} curry(Module, Name, Arity, Params, NewName) -> case curry(Module, Name, Arity, Params) of {ok, NewForm} -> {ok, rename(NewForm, NewName)}; Err -> Err end. %% @doc Add the curried form of the function in the %% MetaMod record with its curried form. %% %% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), %% Params::term() | list()) -> %% {ok, NewMetaMod::meta_mod()} | {error, Err} curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> curry_add(MetaMod, Name, Arity, Params). %% @doc Add the curried form of the function %% in the MetaMod record with its curried form. %% %% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), %% Params::term() | list()) -> %% {ok, NewMetaMod::meta_mod()} | {error, Err} curry_add(MetaMod, Name, Arity, Params) -> curry_change(MetaMod, Name, Arity, Params, false). %% @doc Curry the function form from the given MetaMod, then add it %% to the MetaMod with the new name. %% %% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), %% Params::[term()], NewName::atom()) -> {ok, NewMod::meta_mod()} | %% {error, Err} curry_add(MetaMod, Name, Arity, Params, NewName) -> curry_add(MetaMod, MetaMod, Name, Arity, Params, NewName). %% @doc Curry the function in the module, rename the curried form, and %% add it to the MetaMod record. %% %% @spec curry_add(MetaMod::meta_mod(), Module:atom() | meta_mod(), %% Name::atom(), Arity::integer(), Params::term() | list(), %% NewName::atom()) -> %% {ok, NewMod::meta_mod()} | {error, Error} curry_add(MetaMod, Module, Name, Arity, Params, NewName) -> case curry(Module, Name, Arity, Params, NewName) of {ok, Form} -> add_func(MetaMod, Form); Err -> Err end. curry_change(MetaMod, Name, Arity, Params, Remove) -> case get_func(MetaMod, Name, Arity) of {ok, OldForm} -> case curry(OldForm, Params) of {ok, NewForm} -> MetaMod1 = case Remove of true -> remove_func(MetaMod, Name, Arity); false -> MetaMod end, add_func(MetaMod1, NewForm); Err -> Err end; Err -> Err end. %% @doc Replace the function in the MetaMod record with %% its curried form. %% %% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(), %% Params::term() | list()) -> %% {ok, NewMetaMod::meta_mod()} | {error, Err} curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> curry_replace(MetaMod, Name, Arity, Params). %% @doc Replace the function in the MetaMod record with %% its curried form. %% %% @spec curry_replace(MetaMod::meta_mod(), name::string(), %% Arity::integer(), Params::term() | list()) -> %% {ok, NewMetaMod::meta_mod()} | {error, Err} curry_replace(MetaMod, Name, Arity, Params) -> curry_change(MetaMod, Name, Arity, Params, true). %% @doc Embed all parameters that match a Name element of the list of %% {Name, Value} pairs in the body of the function by setting them %% to the Value element. %% %% @spec embed_params(Func::func_form(), %% Vals::[{Name::atom(), Value:term()}]) -> NewForm::func_form() embed_params({function, L, Name, Arity, Clauses}, Vals) -> NewClauses = lists:foldl( fun({clause, L1, Params, Guards, Exprs}, Clauses1) -> {Params1, Matches1, _RemainingVals} = lists:foldl( fun({var, _L2, ParamName} = Param, {Params2, Matches2, Vals1}) -> case lists:keysearch(ParamName, 1, Vals1) of {value, {_Name, Val} = Elem} -> Match = {match, L1, Param, erl_parse:abstract(Val)}, {Params2, [Match | Matches2], lists:delete(Elem, Vals1)}; false -> {[Param | Params2], Matches2, Vals1} end; (Param, {Params2, Matches2, Vals1}) -> {[Param | Params2], Matches2, Vals1} end, {[], [], Vals}, Params), [{clause, L1, lists:reverse(Params1), Guards, lists:reverse(Matches1) ++ Exprs} | Clauses1] end, [], Clauses), NewArity = case NewClauses of [{clause, _L2, Params, _Guards, _Exprs}|_] -> length(Params); _ -> Arity end, {function, L, Name, NewArity, lists:reverse(NewClauses)}. %% @doc Apply embed_params/2 to the function from the MetaMod and %% add the resulting function to the MetaMod, returning a new %% MetaMod. %% %% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), %% Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err} embed_params(MetaMod, Name, Arity, Values) -> embed_params(MetaMod, Name, Arity, Values, Name). %% @doc Apply embed_params/2 to the function from the MetaMod and %% add the resulting function to the MetaMod after renaming the function. %% %% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), %% Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err} embed_params(MetaMod, Name, Arity, Values, NewName) -> case get_func(MetaMod, Name, Arity) of {ok, Form} -> NewForm = embed_params(Form, Values), add_func(MetaMod, rename(NewForm, NewName)); Err -> Err end. %% @doc Apply the embed_params function with the list of {Name, Value} %% pairs to all forms in the MetaMod record. Exports are preserved even %% for functions whose arity has changed. %% %% @spec embed_all(MetaMod::meta_mod(), Vals::[{Name::atom(), %% Value::term()}]) -> NewMetaMod::meta_mod() embed_all(MetaMod, Vals) -> Forms = get_forms(MetaMod), Exports = get_exports(MetaMod), {NewForms, Exports3, NewExports} = lists:foldl( fun({function, _L, Name, Arity, _Clauses} = Form, {Forms1, Exports1, NewExports1}) -> {function, _, _, NewArity, _} = NewForm = embed_params(Form, Vals), Exports2 = lists:delete({Name, Arity}, Exports1), NewExports2 = case length(Exports2) == length(Exports1) of true -> NewExports1; false -> [{Name, NewArity} | NewExports1] end, {[NewForm | Forms1], Exports2, NewExports2}; (Form, {Forms1, Exports1, NewExports1}) -> {[Form | Forms1], Exports1, NewExports1} end, {[], Exports, []}, Forms), #meta_mod{module = get_module(MetaMod), exports = Exports3 ++ NewExports, forms = lists:reverse(NewForms), export_all = get_export_all(MetaMod)}. %% @doc extend/2 Create a child module from a parent module. %% All exported functions that are unique to the parent module are %% added to the child module in the form of remote function calls %% to functions in the parent module. %% 'ArityDiff' is an optional parameter that indicates the difference %% in arities Smerl should use when figuring out which functions to %% generate based on the modules' exports. %% %% @spec extend(Parent::atom() | meta_mod(), Child:atom() | meta_mod(), %% ArityDiff:: integer()) -> %% NewChildMod::meta_mod() extend(Parent, Child) -> extend(Parent, Child, 0). extend(Parent, Child, ArityDiff) -> {{ParentName, ParentExports, ParentMod}, ChildMod} = get_extend_data(Parent, Child), ChildExports = get_exports(ChildMod), ChildExports1 = [{ExportName, ExportArity + ArityDiff} || {ExportName, ExportArity} <- ChildExports], ExportsDiff = ParentExports -- ChildExports1, NewChild = lists:foldl( fun({FuncName, Arity}, ChildMod1) -> Params = get_params( ParentMod, FuncName, Arity), Clause1 = {clause,1,Params,[], [{call,1, {remote,1,{atom,1,ParentName}, {atom,1,FuncName}}, Params}]}, Func = {function,1,FuncName,Arity, [Clause1]}, {ok, ChildMod2} = add_func(ChildMod1, Func), ChildMod2 end, ChildMod, ExportsDiff), {ok, NewChild1} = smerl:add_func( NewChild, "parent()->" ++ atom_to_list(ParentName) ++ "."), NewChild1. get_extend_data(Parent, Child) when is_atom(Parent) -> [{exports, Exports} |_] = Parent:module_info(), Exports1 = Exports -- [{module_info, 0}], Exports2 = Exports1 -- [{module_info, 1}], ParentMod = case smerl:for_module(Parent) of {ok, M} -> M; {error, _} -> undefined end, get_extend_data({Parent, Exports2, ParentMod}, Child); get_extend_data(Parent, Child) when is_record(Parent, meta_mod) -> get_extend_data({get_module(Parent), get_exports(Parent), Parent}, Child); get_extend_data(Parent, Child) when is_list(Parent) -> case for_file(Parent) of {ok, M1} -> get_extend_data(M1, Child); Err -> Err end; get_extend_data({_,_,_} = ParentData, Child) when is_atom(Child); is_list(Child) -> case for_module(Child) of {ok, MetaMod} -> {ParentData, MetaMod}; Err -> Err end; get_extend_data(ParentData, Child) when is_record(Child, meta_mod) -> {ParentData, Child}. get_params(_, _, 0) -> []; get_params(undefined, _FuncName, Arity) -> [{var,1,list_to_atom("P" ++ integer_to_list(Num))} || Num <- lists:seq(1, Arity)]; get_params(ParentMod, FuncName, Arity) -> {ok, {function, _L, _Name, _Arity, [{clause,_,Params,_Guards,_Exprs} | _]}} = get_func(ParentMod, FuncName, Arity), Params. %% @doc Return the pretty-printed source code for this module. %% %% @spec to_src(MetaMod::meta_mod()) -> soure::string() to_src(MetaMod) -> ExportsForm = {attribute,1,export,get_exports(MetaMod)}, AllForms = [{attribute,1,module,get_module(MetaMod)}, ExportsForm | get_forms(MetaMod)], erl_prettypr:format(erl_syntax:form_list(AllForms)). %% @doc Write the pretty printed source code for this module %% to the file with the given file name. %% %% @sprc to_src(MetaMod::meta_mod(), FileName::string()) -> %% ok | {error, Error} to_src(MetaMod, FileName) -> Src = to_src(MetaMod), file:write_file(FileName, list_to_binary(Src)).