%%% ============================================================== [ smerl.erl ] %%% @doc Simple Metaprogramming for Erlang %%% @author Yariv Sadan %%% @copyright 2006-2007, 2016 AUTHORS %%% %%% 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.
%%% @end %%% 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. %%% ==================================================================== [ EOH ] -module(smerl). -author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com"). %% Public API. -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_args/2, embed_args/4, embed_args/5, embed_all/2, extend/2, extend/3, extend/4, to_src/1, to_src/2 ]). %%% ================================================================== [ Types ] -export_type([args/0, export/0, exports/0, func_form/0, func_forms/0, meta_mod/0, result/1, result/2, ok_t/1, error_t/1]). %% TODO: write docstring -type args() :: term() | [term()]. %% TODO: write docstring -type export() :: {Function :: atom(), Arity :: arity()}. -type exports() :: [export()]. %% A list of {@type export()}s. -type func_form() :: erl_parse:abstract_form(). %% The abstract form for the function, as described %% in the ERTS Users' manual. -type func_forms() :: [func_form()]. %% A list of {@type func_form()}s. %% The record type holding the abstract representation for a module. -record(meta_mod, {module :: module(), file :: undefined | file:filename(), exports = [] :: exports(), forms = [] :: func_forms(), export_all = false :: boolean() }). -type meta_mod() :: #meta_mod{}. %% A data structure holding the abstract representation %% for a module. -type result(Value) :: result(Value, term()). -type result(Value, Error) :: {ok, Value} | {error, Error}. -type ok_t(Value) :: {ok, Value} | error. -type error_t(Error) :: ok | {error, Error}. %%% ================================================================= [ Macros ] -define(IF(Test, Then, Else), case Test of true -> Then; false -> Else end). %%% ============================================================= [ Public API ] -include_lib("kernel/include/file.hrl"). %% @doc Create a new meta_mod for a module with the given name. -spec new(Module :: module()) -> 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 for_file/3}). 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' argument is used when `ModuleName' is a file name. -spec for_module(ModuleName, IncludePaths, Macros) -> result(meta_mod) when ModuleName :: atom() | string(), IncludePaths :: [string()], Macros :: [{atom(), term()}]. 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; _Error -> get_module_forms(ModuleName) 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, IncludePaths, Macros) -> Result when SrcFilePath :: file:filename(), IncludePaths :: [file:filename()], Macros :: [{module(), atom()}], Result :: result(meta_mod(), 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. %% @doc Return the module name for the meta_mod. -spec get_module(MetaMod :: meta_mod()) -> module(). get_module(MetaMod) -> MetaMod#meta_mod.module. %% @doc Set the meta_mod's module name. -spec set_module(MetaMod, NewName) -> NewMod when MetaMod :: meta_mod(), NewName :: module(), 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()) -> func_forms(). get_forms(MetaMod) -> MetaMod#meta_mod.forms. -spec set_forms(MetaMod, Forms) -> NewMod when MetaMod :: meta_mod(), Forms :: func_forms(), NewMod :: meta_mod(). 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()) -> exports(). get_exports(MetaMod) -> ?IF(not MetaMod#meta_mod.export_all, MetaMod#meta_mod.exports, lists:foldl( fun({function, _L, Name, Arity, _Clauses}, Exports) -> [{Name, Arity} | Exports]; (_Form, Exports) -> Exports end, [], MetaMod#meta_mod.forms)). %% @doc Set the `MetaMod''s export list to `Exports'. -spec set_exports(MetaMod, Exports) -> NewMod when MetaMod :: meta_mod(), Exports :: exports(), NewMod :: meta_mod(). set_exports(MetaMod, Exports) -> MetaMod#meta_mod{exports = Exports}. %% @doc Get the `export_all' value for `MetaMod'. -spec get_export_all(MetaMod :: meta_mod()) -> boolean(). get_export_all(MetaMod) -> MetaMod#meta_mod.export_all. %% @doc Set the `export_all' value for `MetaMod'. -spec set_export_all(MetaMod, Value) -> NewMod when MetaMod :: meta_mod(), Value :: boolean(), NewMod :: meta_mod(). set_export_all(MetaMod, Val) -> MetaMod#meta_mod{export_all = Val}. %% @doc Remove an export `{Function, Arity}' %% from the list of `exports' in `MetaMod'. -spec remove_export(MetaMod, Function, Arity) -> NewMod when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), NewMod :: meta_mod(). remove_export(MetaMod, FuncName, Arity) -> MetaMod#meta_mod{exports = lists:delete({FuncName, Arity}, MetaMod#meta_mod.exports)}. %% @doc Get the value of `MetaMod''s `Key' attribute. -spec get_attribute(MetaMod :: meta_mod(), Key :: atom()) -> result(term()). get_attribute(MetaMod, Key) -> case lists:keyfind(Key, 3, get_forms(MetaMod)) of {attribute, _, _, Val} -> {ok, Val}; _ -> error end. %% @doc Add a new exported function to `MetaMod'. %% @equiv add_func(MetaMod, Form, true) -spec add_func(MetaMod, Form) -> result(meta_mod(), parse_error) when MetaMod :: meta_mod(), Form :: func_form() | string(). add_func(MetaMod, Form) -> add_func(MetaMod, Form, true). %% @doc Add `Function' to `MetaMod' and return the new {@type meta_mod()}. If %% `Export' is `true', add `Function' to `MetaMod''s `exports'. -spec add_func(MetaMod, Func, Export) -> result(meta_mod(), parse_error) when MetaMod :: meta_mod(), Func :: func_form() | string(), Export :: boolean(). 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, _Func, _Arity, _Clauses} = Form, false) -> {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; add_func(_, _, _) -> {error, parse_error}. %% @doc Try to remove `Function' from `MetaMod'. %% If the function exists, return the new {@type meta_mod()}. %% Otherwise, return `MetaMod'. -spec remove_func(MetaMod, Function, Arity) -> NewMod when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), NewMod :: meta_mod(). remove_func(MetaMod, Function, Arity) -> Forms = [ Form || {function, _L, F, A, _Cs} = Form <- MetaMod#meta_mod.forms, F =:= Function, A =:= Arity ], Exports = [ {F, A} || {F, A} <- MetaMod#meta_mod.exports, F =:= Function, A =:= Arity ], MetaMod#meta_mod{forms = Forms, exports = Exports}. %% @doc Check whether `MetaMod' has a function `Function'/`Arity'. -spec has_func(MetaMod, Function, Arity) -> boolean() when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(). 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 Attempt to get the {@type func_form()} for `MetaMod':`Function'/`Arity'. -spec get_func(MetaMod, Function, Arity) -> result(func_form()) when MetaMod :: meta_mod() | module(), Function :: atom(), Arity :: arity(). 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) -> do_get_func(MetaMod#meta_mod.forms, FuncName, Arity). %% @doc Replace an existing function with a new one. If a matching function %% doesn't exist, add `Function' to `MetaMod'. This is tantamount to calling %% {@link remove_func/3} followed by {@link add_func/2}. -spec replace_func(MetaMod, Function) -> result(meta_mod()) when MetaMod :: meta_mod(), Function :: string() | func_form(). 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 Compile `MetaMod' and load the resulting BEAM into the emulator. %% @equiv compile(MetaMod, []) -spec compile(MetaMod :: meta_mod()) -> error_t(term()). compile(MetaMod) -> compile(MetaMod, []). %% @doc Compile `MetaMod' 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 an `outdir' is provided, write the `.beam' file to it. %% @equiv compile(MetaMod, [report_errprs, report_warnings, return_errors]) -spec compile(MetaMod, Options) -> error_t(term()) when MetaMod :: meta_mod(), Options :: [proplists:property()]. compile(MetaMod, []) -> 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), compile(MetaMod#meta_mod.module, Forms2, Options). %% @doc Change the name of the function represented by `Form' to `NewName'. -spec rename(Form :: func_form(), NewName :: atom()) -> func_form(). rename({function, Line, _Name, Arity, Clauses}, NewName) -> {function, Line, NewName, Arity, Clauses}. %% @doc Get the curried form for `Form' with `Args'. %% Here, "currying" involves replacing one or more of the function's leading %% arguments with predefined values. -spec curry(Form :: func_form(), Args :: args()) -> result(func_form()). curry(Form, Arg) when not is_list(Arg) -> curry(Form, [Arg]); curry({function, _Line, _Name, Arity, _Clauses}, Args) when length(Args) > Arity -> {error, too_many_args}; curry({function, Line, Name, Arity, Clauses}, NewArgs) -> NewClauses = lists:foldl( fun(Clause, Clauses1) -> [curry_clause(Clause, NewArgs) | Clauses1] end, [], Clauses), {ok, {function, Line, Name, Arity-length(NewArgs), NewClauses}}. %% @doc Curry `Module':`Function'/`Arity' with the given `Args'. -spec curry(Module, Function, Arity, Args) -> result(func_form()) when Module :: module() | meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(). curry(ModName, Name, Arity, Args) when is_atom(ModName) -> case for_module(ModName) of {ok, MetaMod} -> curry(MetaMod, Name, Arity, Args); Err -> Err end; curry(MetaMod, Name, Arity, Args) -> case get_func(MetaMod, Name, Arity) of {ok, Form} -> curry(Form, Args); Err -> Err end. %% @doc Curry `Module':`Function'/`Arity'with the given `Args', %% renaming it to `NewName' and return the renamed form. -spec curry(Module, Function, Arity, Args, NewName) -> result(func_form()) when Module :: module() | meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(), NewName :: atom(). curry(Module, Name, Arity, Args, NewName) -> case curry(Module, Name, Arity, Args) of {ok, NewForm} -> {ok, rename(NewForm, NewName)}; Err -> Err end. %% @doc Add `Form' curried with `Args' to `MetaMod'. -spec curry_add(MetaMod, Form, Args) -> result(meta_mod()) when MetaMod :: meta_mod(), Form :: func_form(), Args :: args(). curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Args) -> curry_add(MetaMod, Name, Arity, Args). %% @doc Add `Function'/`Arity' curried with `Args' to `MetaMod'. -spec curry_add(MetaMod, Function, Arity, Args) -> result(meta_mod()) when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(). curry_add(MetaMod, Name, Arity, Args) -> curry_change(MetaMod, Name, Arity, Args, false). %% @doc Curry `MetaMod':`Function'/`Arity' and add it to `MetaMod' as `NewName'. -spec curry_add(MetaMod, Function, Arity, Args, NewName) -> Result when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(), NewName :: atom(), Result :: result(meta_mod(), parse_error). curry_add(MetaMod, Name, Arity, Args, NewName) -> curry_add(MetaMod, MetaMod, Name, Arity, Args, NewName). %% @doc Curry `Module':`Function'/`Arity' and add it to `MetaMod' as `NewName'. -spec curry_add(MetaMod, Module, Function, Arity, Args, NewName) -> Result when MetaMod :: meta_mod(), Module :: module() | meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(), NewName :: atom(), Result :: result(meta_mod()). curry_add(MetaMod, Module, Name, Arity, Args, NewName) -> case curry(Module, Name, Arity, Args, NewName) of {ok, Form} -> add_func(MetaMod, Form); Err -> Err end. %% @doc Replace the function represented by `Form' in `MetaMod' %% with its curried form. -spec curry_replace(MetaMod, Form, Args) -> result(meta_mod()) when MetaMod :: meta_mod(), Form :: func_form(), Args :: args(). curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Args) -> curry_replace(MetaMod, Name, Arity, Args). %% @doc Replace `Function'/`Arity' in `MetaMod' with its curried form. -spec curry_replace(MetaMod, Function, Arity, Args) -> result(meta_mod()) when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), Args :: args(). curry_replace(MetaMod, Name, Arity, Args) -> curry_change(MetaMod, Name, Arity, Args, true). %% @doc Replace the arguments of the function represented by `Form', %% where the argument's `Name' matches an element from `Vals' %% with the corresponding `Value'. -spec embed_args(Form, Vals) -> NewForm when Form :: func_form(), Vals :: [{Name :: atom(), Value :: term()}], NewForm :: func_form(). embed_args({function, L, Name, Arity, Clauses}, Vals) -> NewClauses = new_clauses(Clauses, Vals), NewArity = case NewClauses of [{clause, _L2, Args, _Guards, _Exprs}|_] -> length(Args); _ -> Arity end, {function, L, Name, NewArity, NewClauses}. %% @equiv embed_args(MetaMod, Name, Arity, Values, Name) -spec embed_args(MetaMod, Function, Arity, Values) -> result(meta_mod()) when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), Values :: proplists:proplist(). embed_args(MetaMod, Name, Arity, Values) -> embed_args(MetaMod, Name, Arity, Values, Name). %% @doc Apply {@link embed_args/2} to `MetaMod':`Function'/`Arity' and %% add the resulting function to `MetMod', after renaming it to `NewName'. %% @see rename/2 -spec embed_args(MetaMod, Function, Arity, Values, NewName) -> Result when MetaMod :: meta_mod(), Function :: atom(), Arity :: arity(), Values :: proplists:proplist(), NewName :: atom(), Result :: result(meta_mod()). embed_args(MetaMod, Name, Arity, Values, NewName) -> case get_func(MetaMod, Name, Arity) of {ok, Form} -> NewForm = embed_args(Form, Values), add_func(MetaMod, rename(NewForm, NewName)); Err -> Err end. %% @doc Apply {@link embed_args/2} with `Values' to all forms in `MetaMod'. %% `exports' for functions whose arities change are preserved. -spec embed_all(MetaMod, Values) -> NewMod when MetaMod :: meta_mod(), Values :: [{Name :: atom(), Value :: term()}], NewMod :: 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_args(Form, Vals), Exports2 = lists:delete({Name, Arity}, Exports1), NewExports2 = ?IF(length(Exports2) == length(Exports1), NewExports1, [{Name, NewArity} | NewExports1]), {[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 Add aliases for `Parent''s functions missing from `Child' to `Child'. %% The new functions in `Child' are shallow, i.e. they have the name and arity %% of the corresponding functions in `Parent', but instead of implementing their %% logic they call the `Parent' functions. -spec extend(Parent, Child) -> NewChildMod when Parent :: module() | meta_mod(), Child :: module() | meta_mod(), NewChildMod :: meta_mod(). extend(Parent, Child) -> extend(Parent, Child, 0). %% @doc Similar to {@link extend/2}, with the addition of `ArityDiff', 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 {@link extend/3} followed by {@link %% embed_all/2}. -spec extend(Parent, Child, ArityDiff) -> NewChildMod when Parent :: module() | meta_mod(), Child :: module() | meta_mod(), ArityDiff :: non_neg_integer(), NewChildMod :: meta_mod(). extend(Parent, Child, ArityDiff) -> extend(Parent, Child, ArityDiff, []). -spec extend(Parent, Child, ArityDiff, Options) -> NewChildMod when Parent :: module() | meta_mod(), Child :: module() | meta_mod(), ArityDiff :: non_neg_integer(), Options :: [proplists:property()], NewChildMod :: meta_mod(). extend(Parent, Child, ArityDiff, Options) -> {{ParentName, ParentExports, ParentMod}, ChildMod} = get_extend_data(Parent, Child, Options), 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; _ -> Args = get_args( ParentMod, FuncName, Arity), Clause1 = {clause, 1, Args, [], [{call, 1, {remote, 1, {atom, 1, ParentName}, {atom, 1, FuncName}}, Args}]}, {function, 1, FuncName, Arity, [Clause1]} end, {ok, ChildMod2} = add_func(ChildMod1, Func), ChildMod2 end, ChildMod, ExportsDiff), NewChild. %% @doc Return the pretty-printed source code for `MetaMod'. -spec to_src(MetaMod :: meta_mod()) -> Source :: 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)). %% @equiv file:write_file(Filename , to_src(MetaMod)) -spec to_src(MetaMod, Filename) -> error_t(term()) when MetaMod :: meta_mod(), Filename :: file:filename(). to_src(MetaMod, Filename) -> file:write_file(Filename, to_src(MetaMod)). %%% ===================================================== [ Internal functions ] get_module_forms(ModuleName) -> case code:which(ModuleName) of Path when is_list(Path) -> case get_forms(ModuleName, Path) of {ok, Forms} -> mod_for_forms(Forms); _Error -> {error, {invalid_module, ModuleName}} end; _Error -> {error, {invalid_module, ModuleName}} 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 Get the abstract representation, if available, of `Module'. %% %% Strategy: %%