📄 igor.erl
字号:
%% `Tree' represents the source code that is the result of%% merging all the code in `Sources' and `Files',%% and `Stubs' is a list of stub module descriptors (see%% `merge_sources/3' for details).%%%% Options:%% <dl>%% <dt>`{comments, bool()}'</dt>%%%% <dd>If the value is `true', source code comments in%% the original files will be preserved in the output. The default%% value is `true'.</dd>%%%% <dt>`{find_src_rules, [{string(), string()}]}'</dt>%%%% <dd>Specifies a list of rules for associating object files with%% source files, to be passed to the function%% `filename:find_src/2'. This can be used to change the%% way Igor looks for source files. If this option is not specified,%% the default system rules are used. The first occurrence of this%% option completely overrides any later in the option list.</dd>%%%% <dt>`{includes, [filename()]}'</dt>%%%% <dd>Specifies a list of directory names for the Erlang%% preprocessor, if used, to search for include files (cf. the%% `preprocess' option). The default value is the empty%% list. The directory of the source file and the current directory%% are automatically appended to the list.</dd>%%%% <dt>`{macros, [{atom(), term()}]}'</dt>%%%% <dd>Specifies a list of "pre-defined" macro definitions for the%% Erlang preprocessor, if used (cf. the `preprocess'%% option). The default value is the empty list.</dd>%%%% <dt>`{preprocess, bool()}'</dt>%%%% <dd>If the value is `false', Igor will read source%% files without passing them through the Erlang preprocessor%% (`epp'), in order to avoid expansion of preprocessor%% directives such as `-include(...).',%% `-define(...).' and `-ifdef(...)', and%% macro calls such as `?LINE' and `?MY_MACRO(x,%% y)'. The default value is `false', i.e.,%% preprocessing is not done. (See the module%% `epp_dodger' for details.)%%%% Notes: If a file contains too exotic definitions or uses of%% macros, it will not be possible to read it without preprocessing.%% Furthermore, Igor does not currently try to sort out multiple%% inclusions of the same file, or redefinitions of the same macro%% name. Therefore, when preprocessing is turned off, it may become%% necessary to edit the resulting source code, removing such%% re-inclusions and redefinitions.</dd>%% </dl>%%%% See `merge_sources/3' for further options.%%%% @see merge/3%% @see merge_files/3%% @see merge_sources/3%% @see //stdlib/filename:find_src/2%% @see epp_dodgermerge_files(_, _Trees, [], _) -> report_error("no files to merge."), exit(badarg);merge_files(Name, Trees, Files, Opts) -> Opts1 = Opts ++ [{includes, ?DEFAULT_INCLUDES}, {macros, ?DEFAULT_MACROS}, {preprocess, false}, comments], Sources = [read_module(F, Opts1) || F <- Files], merge_sources(Name, Trees ++ Sources, Opts1).%% =====================================================================%% @spec merge_sources(Name::atom(), Sources::[Forms],%% Options::[term()]) ->%% {syntaxTree(), [stubDescriptor()]}%%%% Forms = syntaxTree() | [syntaxTree()]%%%% @type stubDescriptor() = [{ModuleName, Functions, [Attribute]}]%% ModuleName = atom()%% Functions = [{FunctionName, {ModuleName, FunctionName}}]%% FunctionName = {atom(), integer()}%% Attribute = {atom(), term()}.%%%% A stub module descriptor contains the module name, a list of%% exported functions, and a list of module attributes. Each%% function is described by its name (which includes its arity),%% and the corresponding module and function that it calls. (The%% arities should always match.) The attributes are simply%% described by key-value pairs.%%%% @doc Merges syntax trees to a single syntax tree. This is the main%% code merging "engine". `Name' specifies the name of the%% resulting module. `Sources' is a list of syntax trees of%% type `form_list' and/or lists of "source code form" syntax%% trees, each entry representing a module definition. All the input%% modules must be distinctly named.%%%% Unless otherwise specified by the options, all modules are assumed%% to be at least "static", and all except the target module are assumed%% to be "safe". See the `static' and `safe'%% options for details.%%%% If `Name' is also the name of one of the input modules,%% the code from that module will occur at the top of the resulting%% code, and no extra "header" comments will be added. In other words,%% the look of that module will be preserved.%%%% The result is a pair `{Tree, Stubs}', where%% `Tree' represents the source code that is the result of%% merging all the code in `Sources', and `Stubs'%% is a list of stub module descriptors (see below).%%%% `Stubs' contains one entry for each exported input%% module (cf. the `export' option), each entry describing a%% stub module that redirects calls of functions in the original module%% to the corresponding (possibly renamed) functions in the new module.%% The stub descriptors can be used to automatically generate stub%% modules; see `create_stubs/2'.%%%% Options:%% <dl>%% <dt>`{export, [atom()]}'</dt>%%%% <dd>Specifies a list of names of input modules whose interfaces%% should be exported by the output module. A stub descriptor is%% generated for each specified module, unless its name is%% `Name'. If no modules are specified, then if%% `Name' is also the name of an input module, that%% module will be exported; otherwise the first listed module in%% `Sources' will be exported. The default value is the%% empty list.</dd>%%%% <dt>`{export_all, bool()}'</dt>%%%% <dd>If the value is `true', this is equivalent to%% listing all of the input modules in the `export'%% option. The default value is `false'.</dd>%%%% <dt>`{file_attributes, Preserve}'</dt>%% <dd><ul>%% <li>`Preserve = yes | comment | no'</li>%% </ul>%% If the value is `yes', all file attributes%% `-file(...)' in the input sources will be preserved in%% the resulting code. If the value is `comment', they%% will be turned into comments, but remain in their original%% positions in the code relative to the other source code forms. If%% the value is `no', all file attributes will be removed%% from the code, unless they have attached comments, in which case%% they will be handled as in the `comment' case. The%% default value is `no'.</dd>%%%% <dt>`{no_banner, bool()}'</dt>%%%% <dd>If the value is `true', no banner comment will be%% added at the top of the resulting module, even if the target%% module does not have the same name as any of the input modules.%% Instead, Igor will try to preserve the look of the module whose%% code is at the top of the output. The default value is%% `false'.</dd>%%%% <dt>`{no_headers, bool()}'</dt>%%%% <dd>If the value is `true', no header comments will be%% added to the resulting module at the beginning of each section of%% code that originates from a particular input module. The default%% value is `false', which means that section headers are%% normally added whenever more than two or more modules are%% merged.</dd>%%%% <dt>`{no_imports, bool()}'</dt>%%%% <dd>If the value is `true', all%% `-import(...)' declarations in the original code will%% be expanded in the result; otherwise, as much as possible of the%% original import declarations will be preserved. The default value%% is `false'.</dd>%%%% <dt>`{notes, Notes}'</dt>%% <dd><ul>%% <li>`Notes = always | yes | no'</li>%% </ul>%% If the value is `yes', comments will be inserted where%% important changes have been made in the code. If the value is%% `always', <em>all</em> changes to the code will be%% commented. If the value is `no', changes will be made%% without comments. The default value is `yes'.</dd>%%%% <dt>`{redirect, [{atom(), atom()}]}'</dt>%%%% <dd>Specifies a list of pairs of module names, representing a%% mapping from old names to new. <em>The set of old names may not%% include any of the names of the input modules.</em> All calls to%% the listed old modules will be rewritten to refer to the%% corresponding new modules. <em>The redirected calls will not be%% further processed, even if the new destination is in one of the%% input modules.</em> This option mainly exists to support module%% renaming; cf. `rename/3'. The default value is the%% empty list.</dd>%%%% <dt>`{safe, [atom()]}'</dt>%%%% <dd>Specifies a list of names of input modules such that calls to%% these "safe" modules may be turned into direct local calls, that%% do not test for code replacement. Typically, this can be done for%% e.g. standard library modules. If a module is "safe", it is per%% definition also "static" (cf. below). The list may be empty. By%% default, all involved modules <em>except the target module</em>%% are considered "safe".</dd>%%%% <dt>`{static, [atom()]}'</dt>%%%% <dd>Specifies a list of names of input modules which will be%% assumed never to be replaced (reloaded) unless the target module%% is also first replaced. The list may be empty. The target module%% itself (which may also be one of the input modules) is always%% regarded as "static", regardless of the value of this option. By%% default, all involved modules are assumed to be static.</dd>%%%% <dt>`{tidy, bool()}'</dt>%%%% <dd>If the value is `true', the resulting code will be%% processed using the `erl_tidy' module, which removes%% unused functions and does general code cleanup. (See%% `erl_tidy:module/2' for additional options.) The%% default value is `true'.</dd>%%%% <dt>`{verbose, bool()}'</dt>%%%% <dd>If the value is `true', progress messages will be%% output while the program is running; the default value is%% `false'.</dd>%% </dl>%%%% Note: The distinction between "static" and "safe" modules is%% necessary in order not to break the semantics of dynamic code%% replacement. A "static" source module will not be replaced unless the%% target module also is. Now imagine a state machine implemented by%% placing the code for each state in a separate module, and suppose%% that we want to merge this into a single target module, marking all%% source modules as static. At each point in the original code where a%% call is made from one of the modules to another (i.e., the state%% transitions), code replacement is expected to be detected. Then, if%% we in the merged code do not check at these points if the%% <em>target</em> module (the result of the merge) has been replaced,%% we can not be sure in general that we will be able to do code%% replacement of the merged state machine - it could run forever%% without detecting the code change. Therefore, all such calls must%% remain remote-calls (detecting code changes), but may call the target%% module directly.%%%% If we are sure that this kind of situation cannot ensue, we may%% specify the involved modules as "safe", and all calls between them%% will become local. Note that if the target module itself is specified%% as safe, "remote" calls to itself will be turned into local calls.%% This would destroy the code replacement properties of e.g. a typical%% server loop.%%%% @see create_stubs/2%% @see rename/3%% @see erl_tidy:module/2%% Currently, there is no run-time support in Erlang for detecting%% whether some module has been changed since the current module was%% loaded. Therefore, if a source module is specified as non-static, not%% much will be gained from merging: a call to a non-static module will%% remain a remote call using the old module name, even when it is%% performed from within the merged code. If that module is specified as%% exported, the old name could then refer to an auto-generated stub,%% redirecting the call back to the corresponding function in the target%% module. This could possibly be useful in some cases, but efficiency%% is not improved by such a transformation. If support for efficient%% testing for module updates is added to Erlang in future versions,%% code merging will be able to use local calls even for non-static%% source modules, opening the way for compiler optimisations over the%% module boundaries.%% Data structure for merging environment.-record(merge, {target, % = atom() sources, % = ordset(atom()) export, % = ordset(atom()) static, % = ordset(atom()) safe, % = ordset(atom()) preserved, % = bool() no_headers, % = bool() notes, % = bool() redirect, % = dict(atom(), atom()) no_imports, % = ordset(atom()) options % = [term()] }).merge_sources(Name, Sources, Opts) -> %% Prepare the options and the inputs. Opts1 = Opts ++ [{export_all, false}, {file_attributes, no}, {no_imports, false}, {notes, yes}, tidy, {verbose, false}], Trees = case Sources of [] -> report_error("no sources to merge."), exit(badarg); _ -> [if is_list(M) -> erl_syntax:form_list(M); true -> M end || M <- Sources] end, %% There must be at least one module to work with. Modules = [get_module_info(T) || T <- Trees], merge_sources_1(Name, Modules, Trees, Opts1).%% Data structure for keeping state during transformation.-record(state, {export}).state__add_export(Name, Arity, S) -> S#state{export = sets:add_element({Name, Arity}, S#state.export)}.merge_sources_1(Name, Modules, Trees, Opts) -> %% Get the (nonempty) list of source module names, in the given %% order. Multiple occurrences of the same source module name are %% not accepted. Ns = [M#module.name || M <- Modules], case duplicates(Ns) of [] -> ok; Ns1 -> report_error("same module names repeated in input: ~p.", [Ns1]), exit(error) end, Sources = ordsets:from_list(Ns), All = ordsets:add_element(Name, Sources), %% Initialise the merging environment from the given options. %% %% If the `export' option is the empty list, then if the target %% module is the same as one of the sources, that module will be %% exported; otherwise the first listed source module is exported. %% This simplifies use in most cases, and guarantees that the %% generated module has a well-defined interface. If `export_all' is %% `true', we expand it here by including the set of source module %% names. Es = case proplists:append_values(export, Opts) of [] -> case ordsets:is_element(Name, Sources) of true -> [Name]; false -> [hd(Ns)] end; Es1 when is_list(Es1) -> ordsets:from_list(Es1) end, Export = case proplists:get_bool(export_all, Opts) of false -> Es; true -> ordsets:union(Sources, Es) end, check_module_names(Export, Sources, "declared as exported"), verbose("modules exported from `~w': ~p.", [Name, Export], Opts), %% The target module is always "static". (Particularly useful when
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -