%%% ============================================================== [ 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: %%
    %%
  1. Try to get the abstract code from `Module' if it's compiled with %% `debug_info'.
  2. %%
  3. Look for the source file in the beam file's directory.
  4. %%
  5. If the file's directory ends with `ebin', then search in %% `[beamdir]/../src'
  6. %%
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) of {error, _} = Err -> get_forms_from_binary(Module, Err); {ok, Filename} -> epp:parse_file(Filename, [filename:dirname(Filename)], []) end 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), case Lastdir of "ebin" -> Rootdir = filename:dirname(Basedir), DirList0 = [Rootdir ++ "/src"], DirList = DirList0 ++ get_dirs_in_dir(Rootdir ++ "/src"), get_forms_from_file_list(Module, Rootdir, DirList); _ -> DirList = [Basedir], get_forms_from_file_list(Module, Basedir, DirList) end end, case Ret of [] -> OrigErr; _ -> Ret end. get_dirs_in_dir(Dir) -> case list_dir(Dir) of {ok, Listing} -> lists:filtermap(fun(Name) -> is_directory(Dir, Name) end, Listing); {error, _} -> undefined end. %% @equiv is_directory(filename:join(Directory, Filename)) is_directory(Directory, Filename) -> is_directory(filename:join(Directory, Filename)). is_directory(Filename) -> case read_file_info(Filename) of {ok, #file_info{type=directory}} -> true; _ -> false 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 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. do_get_func([], FuncName, Arity) -> {error, {function_not_found, {FuncName, Arity}}}; do_get_func([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest], FuncName, Arity) -> {ok, Form}; do_get_func([_Form|Rest], FuncName, Arity) -> do_get_func(Rest, FuncName, Arity). 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. compile(ModName, Forms, Options) -> case compile:forms(Forms, Options) of {ok, Module, Bin} -> OutDir = proplists:get_value(outdir, Options), maybe_write_beam_file({ModName, Module}, Bin, OutDir); Err -> Err end. maybe_write_beam_file({_ModName, Module}, Bin, undefined) -> Filename = case code:which(Module) of non_existing -> atom_to_list(Module) ++ ".erl"; Path -> Path end, code:purge(Module), case code:load_binary(Module, Filename, Bin) of {module, _Module} -> ok; Err -> Err end; maybe_write_beam_file({ModName, _Module}, Bin, OutDir) -> BeamFile = atom_to_list(ModName) ++ ".beam", file:write_file(filename:join(OutDir, BeamFile), Bin). curry_clause({clause, L1, ExistingArgs, Guards, _Exprs} = Clause, NewArgs) -> {FirstArgs, LastArgs} = lists:split(length(NewArgs), ExistingArgs), Vals = [ {Name, erl_parse:abstract(NewVal)} || {{var, _ , Name}, NewVal} <- lists:zip(FirstArgs, NewArgs) ], NewExprs = replace_vars(Clause, Vals), {clause, L1, LastArgs, 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. curry_change(MetaMod, Name, Arity, Args, Remove) -> case get_func(MetaMod, Name, Arity) of {ok, OldForm} -> case curry(OldForm, Args) of {ok, NewForm} -> MetaMod1 = ?IF(Remove, remove_func(MetaMod, Name, Arity), MetaMod), add_func(MetaMod1, NewForm); Err -> Err end; Err -> Err end. %% @see embed_args/2 new_clauses(Clauses, Vals) -> [ begin {EmbeddedVals, OtherArgs} = lists:foldr( fun({var, _, VarName} = Arg, {Embedded, Rest}) -> case proplists:lookup(VarName, Vals) of none -> {Embedded, [Arg | Rest]}; {_, Val} -> {[{VarName, erl_parse:abstract(Val)} | Embedded], Rest} end; (Arg, {Embedded, Rest}) -> {Embedded, [Arg | Rest]} end, {[], []}, Args), NewExprs = replace_vars(Clause, EmbeddedVals), {clause, L1, OtherArgs, Guards, NewExprs} end || {clause, L1, Args, Guards, _Exprs} = Clause <- Clauses]. get_extend_data(Parent, Child, Options) when is_atom(Parent) -> SrcDir = proplists:get_value(src_dir, Options), do_get_extend_data(Parent, Child, Options, SrcDir); get_extend_data(Parent, Child, Options) when is_record(Parent, meta_mod) -> Data = {get_module(Parent), get_exports(Parent), Parent}, get_extend_data(Data, Child, Options); get_extend_data(Parent, Child, Options) when is_list(Parent) -> case for_file(Parent) of {ok, M1} -> get_extend_data(M1, Child, Options); Err -> Err end; get_extend_data({_, _, _} = ParentData, Child, _Options) when is_atom(Child); is_list(Child) -> case for_module(Child) of {ok, MetaMod} -> {ParentData, MetaMod}; Err -> Err end; get_extend_data(ParentData, Child, _Options) when is_record(Child, meta_mod) -> {ParentData, Child}. do_get_extend_data(Parent, Child, Options, undefined) -> [{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, Options); do_get_extend_data(Parent, Child, Options, Dir) -> Filename = filename:join(Dir, atom_to_list(Parent) ++ ".erl"), %% Check if file exists case read_file_info(Filename) of {ok, _FileInfo} -> ParentMod = case smerl:for_file(Filename) of {ok, MetaMod} -> MetaMod; {error, _} -> undefined end, get_extend_data(ParentMod, Child, Options); Error -> Error end. get_args(_, _, 0) -> []; get_args(undefined, _FuncName, Arity) -> [{var, 1, list_to_atom("P" ++ integer_to_list(Num))} || Num <- lists:seq(1, Arity)]; get_args(ParentMod, FuncName, Arity) -> {ok, {function, _L, _Name, _Arity, [{clause, _, Args, _Guards, _Exprs} | _]}} = get_func(ParentMod, FuncName, Arity), Args. -spec list_dir(Dir :: file:dirname()) -> Result when Result :: result([file:filename()], erl_prim_loader). list_dir(Dir) -> handle_list_dir(erl_prim_loader:list_dir(Dir)). handle_list_dir(error) -> {error, erl_prim_loader}; handle_list_dir({ok, Filenames}) -> {ok, Filenames}. -spec read_file_info(Filename :: file:filename()) -> Result when Result :: result(file:file_info(), file:posix() | badarg). read_file_info(Filename) -> erl_prim_loader:read_file_info(Filename). %%% ==================================================================== [ EOF ]