%% @author Yariv Sadan [http://yarivsblog.com] %% @copyright Yariv Sadan 2006-2007 %% %% @doc Smerl: Simple Metaprogramming for Erlang %% %% Smerl is an Erlang library %% that simplifies the creation and manipulation of Erlang modules in %% runtime. %% %% You don't need to know Smerl in order to use ErlyWeb; Smerl %% is included in ErlyWeb because ErlyWeb uses it internally. %% %% Smerl uses Erlang's capabilities for hot code swapping and %% abstract syntax tree transformations 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 ([http://jungerl.sf.net]). %% %% 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 %% ([http://erlang.org/doc/doc-5.5/erts-5.5/doc/html/absform.html#4]). %% %% 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 easier to manipulate in code.

%% %% Copyright (c) Yariv Sadan 2006-2007 %% %% 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_module/2, for_module/3, for_file/1, for_file/2, for_file/3, 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, 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, extend/4, to_src/1, to_src/2 ]). -define(L(Obj), io:format("LOG ~s ~w ~p\n", [?FILE, ?LINE, Obj])). -define(S(Obj), io:format("LOG ~s ~w ~s\n", [?FILE, ?LINE, Obj])). -include_lib("kernel/include/file.hrl"). %% @type meta_mod(). A data structure holding the abstract representation %% for a 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 new meta_mod for a module with the given name. %% %% @spec new(Module::atom()) -> meta_mod() new(ModuleName) when is_atom(ModuleName) -> #meta_mod{module = ModuleName}. %% @equiv for_module(ModuleName, []) for_module(ModuleName) -> for_module(ModuleName, []). %% @equiv for_module(ModuleName, IncludePaths, []) for_module(ModuleName, IncludePaths) -> for_module(ModuleName, IncludePaths, []). %% @doc Create a meta_mod tuple for an existing module. If ModuleName is a %% string, it is interpreted as a file name (this is the same as calling %% @{link smerl:for_file}). If ModuleName is an atom, Smerl attempts to %% find its abstract represtation either from its source file or from %% its .beam file directly (if it has been compiled with debug_info). %% If the abstract representation can't be found, this function returns %% an error. %% %% The IncludePaths parameter is used when 'ModuleName' is a file name. %% %% @spec for_module(ModuleName::atom() | string(), IncludePaths::[string()], %% Macros::[{atom(), term()}]) -> %% {ok, meta_mod()} | {error, Error} for_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName) -> for_file(ModuleName, IncludePaths, Macros); for_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName) -> [_Exports, _Imports, _Attributes, {compile, [_Options, _Version, _Time, {source, Path}]}] = ModuleName:module_info(), case for_file(Path, IncludePaths, Macros) 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; _Err2 -> {error, {invalid_module, ModuleName}} end end. %% @equiv for_file(SrcFilePath, []) for_file(SrcFilePath) -> for_file(SrcFilePath, []). %% @equiv for_file(SrcFilePath, IncludePaths, []) for_file(SrcFilePath, IncludePaths) -> for_file(SrcFilePath, IncludePaths, []). %% @doc Create a meta_mod for a module from its source file. %% %% @spec for_file(SrcFilePath::string(), IncludePaths::[string()], %% Macros::[{atom(), term()}]) -> %% {ok, meta_mod()} | {error, invalid_module} for_file(SrcFilePath, IncludePaths, Macros) -> case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) | IncludePaths], Macros) 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(Mod) -> {error, {invalid_module, Mod}}. %% @doc Return the module name for the meta_mod. %% %% @spec(MetaMod::meta_mod()) -> atom() get_module(MetaMod) -> MetaMod#meta_mod.module. %% @doc Set the meta_mod's module 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 meta_mod. %% %% @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 meta_mod. %% %% @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 meta_mod's export list to the new list. %% %% @spec set_exports(MetaMod::meta_mod(), %% Exports::[{FuncName::atom(), Arity::integer()}]) -> NewMod::meta_mod() 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 export from the list of exports in the meta_mod. %% %% @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 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 filelib:find_source(Module, [{"ebin", "src"}]) of {error, _} = Err -> get_forms_from_binary(Module, Err); {SrcPath, _} -> Filename = SrcPath ++ ".erl", epp:parse_file(Filename, [filename:dirname(Filename)], []) end end. get_dirs_in_dir(Dir) -> case file:list_dir(Dir) of {error, _} -> undefined; {ok, Listing} -> lists:foldl( fun (Name, Acc) -> Path = Dir ++ "/" ++ Name, case file:read_file_info(Path) of {ok, #file_info{type=directory}} -> [Path | Acc]; _ -> Acc end end, [], Listing) end. %% @doc Try to infer module source files from the beam code path. get_forms_from_binary(Module, OrigErr) -> Ret = case code:where_is_file(atom_to_list(Module) ++ ".beam") of non_existing -> OrigErr; Filename -> %% We could automatically obtain a list of all dirs under this dir, but we just do %% a hack for now. Basedir = filename:dirname(Filename), Lastdir = filename:basename(Basedir), if Lastdir == "ebin" -> Rootdir = filename:dirname(Basedir), DirList = [Rootdir ++ "/src"], get_forms_from_file_list(Module, Rootdir, DirList ++ get_dirs_in_dir(Rootdir ++ "/src")); true -> DirList = [Basedir], get_forms_from_file_list(Module, Basedir, DirList) end end, if Ret == [] -> OrigErr; true -> Ret end. get_forms_from_file_list(_Module, _Basedir, []) -> []; get_forms_from_file_list(Module, Basedir, [H|T]) -> Filename = H ++ "/" ++ atom_to_list(Module) ++ ".erl", case file:read_file_info(Filename) of {ok, #file_info{type=regular}} -> epp:parse_file(Filename, [filename:dirname(Filename)], []); _ -> get_forms_from_file_list(Module, Basedir, T) end. %% @doc Add a new function to the meta_mod and return the resulting meta_mod. %% This function calls add_func(MetaMod, Form, true). %% %% @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 meta_mod and return the new MetaMod %% record. Export is a boolean variable indicating if the function should %% be added to the module's exports. %% %% @spec add_func(MetaMod::meta_mod(), Func::func_form() | string(), %% Export::boolean()) -> %% {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 meta_mod. %% If the function exists, the new meta_mod is returned. Otherwise, %% original meta_mod 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 the meta_mod 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 %% meta_mod. %% %% @spec get_func(MetaMod::meta_mod() | 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). %% @doc %% Replace an existing function with the new one. If the function doesn't exist %% the new function is added to the meta_mod. %% This is tantamount to calling 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 meta_mod and load the %% resulting BEAM into the emulator. This function calls %% compile(MetaMod, [report_errors, report_warnings]). %% %% @spec compile(MetaMod::meta_mod()) -> ok | {error, Error} compile(MetaMod) -> compile(MetaMod, undefined). %% @doc Compile the module represented by the meta_mod and load the %% resulting BEAM into the emulator. 'Options' is a list of options as %% described in the 'compile' module in the Erlang documentation. %% %% If the 'outdir' option is provided, %% the .beam file is written to the destination directory. %% %% @spec compile(MetaMod::meta_mod(), Options::[term()]) -> ok | {error, Error} compile(MetaMod, undefined) -> compile(MetaMod, [report_errors, report_warnings, return_errors]); 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(s). Currying %% involves replacing one or more of the function's leading parameters %% with predefined values. %% %% @spec curry(Form::func_form(), Param::term() | [term()]) -> %% {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} = Clause, 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}. Vals = lists:foldl( fun({{var,_,Name}, NewVal}, Acc) -> [{Name, erl_parse:abstract(NewVal)} | Acc]; (_, Acc) -> Acc end, [], lists:zip(FirstParams, NewParams)), NewExprs = replace_vars(Clause, Vals), {clause, L1, LastParams, Guards, NewExprs}. replace_vars(Clause, Vals) -> Tree = erl_syntax_lib:map( fun({var,_L2,Name} = Expr) -> case proplists:lookup(Name, Vals) of none -> Expr; {_, Val} -> Val end; (Expr) -> Expr end, Clause), {clause, _, _, _, NewExprs} = erl_syntax:revert(Tree), NewExprs. %% @doc Curry the function from the module with the given param(s) %% %% @spec curry(ModName::atom(), Name::atom(), Arity::integer(), %% Params::term() | [term()]) -> %% {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 meta_mod with %% the given param(s) %% %% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(), %% Params::term() | [terms()]) -> %% {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 meta_mod %% with the param(s), and return its renamed form. %% %% @spec curry(Module::atom() | meta_mod(), Name::atom(), Arity::integer(), %% Params::term() | [terms()], NewName::atom()) -> %% {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 meta_mod %% with its curried form. %% %% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), %% Params::term() | [term()]) -> %% {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 meta_mod with its curried form. %% %% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), %% Params::term() | [term()]) -> %% {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 meta_mod, then add it %% to the other meta_mod with a 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 meta_mod. %% %% @spec curry_add(MetaMod::meta_mod(), Module::atom() | meta_mod(), %% Name::atom(), Arity::integer(), Params::term() | [term()], %% 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 meta_mod with %% its curried form. %% %% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(), %% Params::term() | [term()]) -> %% {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 meta_mod with %% its curried form. %% %% @spec curry_replace(MetaMod::meta_mod(), Name::atom(), %% 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 This function takes a function form and list of name/value pairs, %% and replaces all the function's parameters that whose names match an %% element from the list with the predefined value. %% %% @spec embed_params(Func::func_form(), %% Vals::[{Name::atom(), Value::term()}]) -> NewForm::func_form() embed_params({function, L, Name, Arity, Clauses}, Vals) -> NewClauses = lists:map( fun({clause, L1, Params, Guards, _Exprs} = Clause) -> {EmbeddedVals, OtherParams} = lists:foldr( fun({var,_, VarName} = Param, {Embedded, Rest}) -> case proplists:lookup(VarName, Vals) of none -> {Embedded, [Param | Rest]}; {_, Val} -> {[{VarName, erl_parse:abstract(Val)} | Embedded], Rest} end; (Param, {Embedded, Rest}) -> {Embedded, [Param | Rest]} end, {[], []}, Params), NewExprs = replace_vars(Clause, EmbeddedVals), {clause, L1, OtherParams, Guards, NewExprs} %% {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, NewClauses}. %% @doc Apply {@link embed_params/2} to a function from the meta_mod and %% add the resulting function to the meta_mod, and return the resulting %% meta_mod. %% %% @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 meta_mod and %% add the resulting function to the meta_mod after renaming the function. %% %% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), %% Values::proplist(), NewName::atom()) -> %% {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 meta_mod. Exports %% for functions whose arities change due to the embedding are preserved. %% %% @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 %% Add all the parent module's functions that are missing from the child %% module to the child module. The new functions in the child module are %% shallow: they have the name and arity as their corresponding functions in %% the parent meta_mod, but instead of implementing their logic they call %% the parent module's functions. %% %% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod()) -> %% NewChildMod::meta_mod() extend(Parent, Child) -> extend(Parent, Child, 0). %% @doc Similar to extend/2, with the addition of the 'ArityDiff' parameter, %% which indicates the difference %% in arities Smerl should use when figuring out which functions to %% generate based on the modules' exports. This is sometimes %% useful when calling extend() followed by embed_all(). %% %% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod(), %% ArityDiff::integer()) -> %% NewChildMod::meta_mod() extend(Parent, Child, ArityDiff) -> extend(Parent, Child, ArityDiff, []). extend(Parent, Child, ArityDiff, Options) -> {{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) -> Func = case lists:member(copy, Options) of true -> {ok, ParentFunc} = smerl:get_func(ParentMod, FuncName, Arity), ParentFunc; _ -> Params = get_params( ParentMod, FuncName, Arity), Clause1 = {clause,1,Params,[], [{call,1, {remote,1,{atom,1,ParentName}, {atom,1,FuncName}}, Params}]}, {function,1,FuncName,Arity, [Clause1]} end, {ok, ChildMod2} = add_func(ChildMod1, Func), ChildMod2 end, ChildMod, ExportsDiff), NewChild. 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 the module. %% %% @spec to_src(MetaMod::meta_mod()) -> 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 the module %% to the file with the given file name. %% %% @spec 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)).