📄 igor.erl
字号:
%% =====================================================================%% This library is free software; you can redistribute it and/or modify%% it under the terms of the GNU Lesser General Public License as%% published by the Free Software Foundation; either version 2 of the%% License, or (at your option) any later version.%%%% This library is distributed in the hope that it will be useful, but%% WITHOUT ANY WARRANTY; without even the implied warranty of%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU%% Lesser General Public License for more details.%%%% You should have received a copy of the GNU Lesser General Public%% License along with this library; if not, write to the Free Software%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307%% USA%%%% $Id$%%%% @copyright 1998-2006 Richard Carlsson%% @author Richard Carlsson <richardc@it.uu.se>%% @end%% =====================================================================%% @doc Igor: the Module Merger and Renamer.%%%% The program Igor merges the source code of one or more Erlang%% modules into a single module, which can then replace the original set%% of modules. Igor is also able to rename a set of (possibly%% interdependent) modules, without joining them into a single%% module.%%%% The main user interface consists of the functions {@link merge/3} and%% {@link rename/3}. See also the function {@link parse_transform/2}.%%%% A note of warning: Igor cannot do anything about the case when the%% name of a remote function is passed to the built-in functions%% `apply' and `spawn' <em>unless</em> the module%% and function names are explicitly stated in the call, as in e.g.%% `apply(lists, reverse, [Xs])'. In all other cases, Igor%% leaves such calls unchanged, and warns the user that manual editing%% might be necessary.%%%% Also note that Erlang records will be renamed as necessary to%% avoid non-equivalent definitions using the same record name. This%% does not work if the source code accesses the name field of such%% record tuples by `element/2' or similar methods. Always%% use the record syntax to handle record tuples, if possible.%%%% Disclaimer: the author of this program takes no responsibility for%% the correctness of the produced output, or for any effects of its%% execution. In particular, the author may not be held responsible%% should Igor include the code of a deceased madman in the result.%%%% For further information on Igors in general, see e.g. "Young%% Frankenstein", Mel Brooks, 1974, and "The Fifth Elephant", Terry%% Pratchett, 1999.%% @end%% =====================================================================%% This program is named after the character Igor, assistant to Dr.%% Frankenstein, in the 1939 film "Son of Frankenstein" (with Boris%% Karloff playing The Monster for the last time; Igor was played by%% Bela Lugosi). Igor's job (in the film) was mainly to bring reasonably%% fresh parts of various human corpses to the good Doctor, for his%% purpose of reanimating them in the shape of a new formidable, living%% creature.%%%% Merging code is done by joining the sources, possibly changing the%% order of declarations as necessary, renaming functions and records to%% avoid name clashes, and changing remote calls to local calls where%% possible. Stub modules may be automatically generated to redirect any%% calls that still use the old names. Indirectly, code merging can be%% used to simply rename a set of modules.%%%% What Igor does not do is to optimise the resulting code, which%% typically can benefit from techniques such as inlining, constant%% folding, specialisation, etc. This task is left to the Doctor.%% (Luckily, Igor can call on Inga to do some cleanup; cf. 'erl_tidy'.)%% TODO: FIXME: don't remove module qualifier if name is (auto-)imported!%% TODO: handle merging of parameterized modules (somehow).%% TODO: check for redefinition of macros; check equivalence; comment out.%% TODO: {export, [E]}, E = atom() | {atom(), atom(), integer()}.%% TODO: improve documentation. %% TODO: optionally rename all functions from specified (or all) modules.-module(igor).-export([create_stubs/2, merge/2, merge/3, merge_files/3, merge_files/4, merge_sources/3, parse_transform/2, rename/2, rename/3]).-include_lib("kernel/include/file.hrl").%% =====================================================================%% Global Constants-define(NOTE_HEADER, "Note from Igor: ").-define(COMMENT_PREFIX, "% ").-define(COMMENT_BAR, "=======================" "=======================" "=======================").-define(NOTE_PREFIX, "%! ").-define(KILL_PREFIX, "%<<< ").-define(DEFAULT_INCLUDES, ["."]).-define(DEFAULT_MACROS, []).-define(DEFAULT_SUFFIX, ".erl").-define(DEFAULT_BACKUP_SUFFIX, ".bak").-define(DEFAULT_DIR, "").-define(DEFAULT_STUB_DIR, "stubs").-define(TIDY_OPTS, [quiet]).%% This may also be used in patterns. R must not be an integer, i.e.,%% the structure must be distinct from function names.-define(record_name(R), {record, R}).%% Data structure for module information-record(module, {name, % = atom() vars = none, % = [atom()] | none functions, % = ordset({atom(), int()}) exports, % = ordset({atom(), int()}) % | ordset({{atom(), int()}, % term()}) aliases, % = ordset({{atom(), int()}, % {atom(), % {atom(), int()}}}) attributes, % = ordset({atom(), term()}) records % = [{atom(), [{atom(), term()}]}] }).%% The default pretty-printing function.default_printer(Tree, Options) -> erl_prettypr:format(Tree, Options).%% =====================================================================%% @spec parse_transform(Forms::[syntaxTree()], Options::[term()]) ->%% [syntaxTree()]%%%% syntaxTree() = erl_syntax:syntaxTree()%%%% @doc Allows Igor to work as a component of the Erlang compiler.%% Including the term `{parse_transform, igor}' in the%% compile options when compiling an Erlang module (cf.%% `compile:file/2'), will call upon Igor to process the%% source code, allowing automatic inclusion of other source files. No%% files are created or overwritten when this function is used.%%%% Igor will look for terms `{igor, List}' in the compile%% options, where `List' is a list of Igor-specific options,%% as follows:%% <dl>%% <dt>`{files, [filename()]}'</dt>%% <dd>The value specifies a list of source files to be merged with%% the file being compiled; cf. `merge_files/4'.</dd>%% </dl>%%%% See `merge_files/4' for further options. Note, however,%% that some options are preset by this function and cannot be%% overridden by the user; in particular, all cosmetic features are%% turned off, for efficiency. Preprocessing is turned on.%%%% @see merge_files/4%% @see //compiler/compile:file/2parse_transform(Forms, Options) -> M = get_module_info(Forms), Name = M#module.name, Opts = proplists:append_values(igor, Options), Files = proplists:append_values(files, Opts), %% We turn off all features that are only cosmetic, and make sure to %% turn on preservation of `file' attributes. Opts1 = [{comments, false}, {notes, no}, {no_imports, true}, {file_attributes, yes}, {preprocess, true}, {export, [Name]} | Opts], {T, _} = merge_files(Name, [Forms], Files, Opts1), verbose("done.", Opts1), erl_syntax:revert_forms(T).%% =====================================================================%% @spec merge(Name::atom(), Files::[filename()]) -> [filename()]%% @equiv merge(Name, Files, [])merge(Name, Files) -> merge(Name, Files, []).%% =====================================================================%% @spec merge(Name::atom(), Files::[filename()], Options::[term()]) ->%% [filename()]%%%% filename() = file:filename()%%%% @doc Merges source code files to a single file. `Name'%% specifies the name of the resulting module - not the name of the%% output file. `Files' is a list of file names and/or module%% names of source modules to be read and merged (see%% `merge_files/4' for details). All the input modules must%% be distinctly named.%%%% The resulting source code is written to a file named%% "`<em>Name</em>.erl'" in the current directory, unless%% otherwise specified by the options `dir' and%% `outfile' described below.%%%% Examples:%% <ul>%% <li>given a module `m' in file "`m.erl'"%% which uses the standard library module `lists', calling%% `igor:merge(m, [m, lists])' will create a new file%% "`m.erl' which contains the code from `m' and%% exports the same functions, and which includes the referenced code%% from the `lists' module. The original file will be%% renamed to "`m.erl.bak'".</li>%%%% <li>given modules `m1' and `m2', in%% corresponding files, calling `igor:merge(m, [m1, m2])'%% will create a file "`m.erl'" which contains the code%% from `m1' and `m2' and exports the functions%% of `m1'.</li>%% </ul>%%%% Stub module files are created for those modules that are to be%% exported by the target module (see options `export',%% `stubs' and `stub_dir').%%%% The function returns the list of file names of all created%% modules, including any automatically created stub modules. The file%% name of the target module is always first in the list.%%%% Note: If you get a "syntax error" message when trying to merge%% files (and you know those files to be correct), then try the%% `preprocess' option. It typically means that your code%% contains too strange macros to be handled without actually performing%% the preprocessor expansions.%% %% Options:%% <dl>%% <dt>`{backup_suffix, string()}'</dt>%%%% <dd>Specifies the file name suffix to be used when a backup file%% is created; the default value is `".bak"'.</dd>%%%% <dt>`{backups, bool()}'</dt>%%%% <dd>If the value is `true', existing files will be%% renamed before new files are opened for writing. The new names%% are formed by appending the string given by the%% `backup_suffix' option to the original name. The%% default value is `true'.</dd>%%%% <dt>`{dir, filename()}'</dt>%%%% <dd>Specifies the name of the directory in which the output file%% is to be written. An empty string is interpreted as the current%% directory. By default, the current directory is used.</dd>%%%% <dt>`{outfile, filename()}'</dt>%%%% <dd>Specifies the name of the file (without suffix) to which the%% resulting source code is to be written. By default, this is the%% same as the `Name' argument.</dd>%%%% <dt>`{preprocess, bool()}'</dt>%%%% <dd>If the value is `true', preprocessing will be done%% when reading the source code. See `merge_files/4' for%% details.</dd>%%%% <dt>`{printer, Function}'</dt>%% <dd><ul>%% <li>`Function = (syntaxTree()) -> string()'</li>%% </ul>%% Specifies a function for prettyprinting Erlang syntax trees.%% This is used for outputting the resulting module definition, as%% well as for creating stub files. The function is assumed to%% return formatted text for the given syntax tree, and should raise%% an exception if an error occurs. The default formatting function%% calls `erl_prettypr:format/2'.</dd>%%%% <dt>`{stub_dir, filename()}'</dt>%%%% <dd>Specifies the name of the directory to which any generated%% stub module files are written. The default value is%% `"stubs"'.</dd>%%%% <dt>`{stubs, bool()}'</dt>%%%% <dd>If the value is `true', stub module files will be%% automatically generated for all exported modules that do not have%% the same name as the target module. The default value is%% `true'.</dd>%%%% <dt>`{suffix, string()}'</dt>%%%% <dd>Specifies the suffix to be used for the output file names;%% the default value is `".erl"'.</dd>%% </dl>%%%% See `merge_files/4' for further options.%%%% @see merge/2%% @see merge_files/4%% The defaults for 'merge' are also used for 'create_stubs'.-define(DEFAULT_MERGE_OPTS, [{backup_suffix, ?DEFAULT_BACKUP_SUFFIX}, backups, {dir, ?DEFAULT_DIR}, {printer, fun default_printer/2}, {stub_dir, ?DEFAULT_STUB_DIR}, stubs, {suffix, ?DEFAULT_SUFFIX}, {verbose, false}]).merge(Name, Files, Opts) -> Opts1 = Opts ++ ?DEFAULT_MERGE_OPTS, {Tree, Stubs} = merge_files(Name, Files, Opts1), Dir = proplists:get_value(dir, Opts1, ""), Filename = proplists:get_value(outfile, Opts1, Name), File = write_module(Tree, Filename, Dir, Opts1), [File | maybe_create_stubs(Stubs, Opts1)].%% =====================================================================%% @spec merge_files(Name::atom(), Files::[filename()],%% Options::[term()]) ->%% {syntaxTree(), [stubDescriptor()]}%% @equiv merge_files(Name, [], Files, Options)merge_files(Name, Files, Options) -> merge_files(Name, [], Files, Options).%% =====================================================================%% @spec merge_files(Name::atom(), Sources::[Forms],%% Files::[filename()], Options::[term()]) ->%% {syntaxTree(), [stubDescriptor()]}%% Forms = syntaxTree() | [syntaxTree()]%%%% @doc Merges source code files and syntax trees to a single syntax%% tree. This is a file-reading front end to%% `merge_sources/3'. `Name' specifies the name of%% the resulting module - not the name of the output file.%% `Sources' is a list of syntax trees and/or lists of%% "source code form" syntax trees, each entry representing a module%% definition. `Files' is a list of file names and/or module%% names of source modules to be read and included. All the input%% modules must be distinctly named.%%%% If a name in `Files' is not the name of an existing%% file, Igor assumes it represents a module name, and tries to locate%% and read the corresponding source file. The parsed files are appended%% to `Sources' and passed on to%% `merge_sources/3', i.e., entries in `Sources'%% are listed before entries read from files.%%%% If no exports are listed by an `export' option (see%% `merge_sources/3' for details), then if `Name'%% is also the name of one of the input modules, that module will be%% exported; otherwise, the first listed module will be exported. Cf.%% the examples under `merge/3'.%%%% The result is a pair `{Tree, Stubs}', where
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -