📄 igor.erl
字号:
Split = fun (M) -> ordsets:is_element(M#module.name, Export) end, {M1, M2} = split_list(Split, Modules), R = dict:new(), Acc = {sets:new(), R}, {M3, Acc1} = merge_namespaces_1(M1, Acc), %% Detect and warn about renamed interface functions {_, Maps0} = Acc1, case [{M, dict:to_list(Map)} || {M, Map} <- dict:to_list(Maps0), dict:size(Map) /= 0] of [] -> ok; Fs -> report_warning("interface functions renamed:\n\t~p.", [Fs]) end, {M4, Acc2} = merge_namespaces_1(M2, Acc1), Ms = M3 ++ M4, Acc3 = merge_namespaces_2(Ms, Acc2), {{Names, Maps}, _} = merge_namespaces_3(Ms, Acc3), {Names, make_renaming_function(Maps)}.%% Adding exported names. (Note that the list gets a new temporary%% format also containing the exports.) This first step initialises the%% Maps "dict-of-dicts" structure.merge_namespaces_1(Modules, Acc) -> lists:mapfoldl( fun (Module, {Names, Maps}) -> Exports = sets:from_list(Module#module.exports), M = Module#module.name, {Names1, Map} = add_function_renamings(M, Exports, Names, dict:new()), Maps1 = dict:store(M, Map, Maps), {{Module, Exports}, {Names1, Maps1}} end, Acc, Modules).%% Adding nonexported names.merge_namespaces_2(Modules, Acc) -> lists:foldl( fun ({Module, Exports}, {Names, Maps}) -> Other = sets:subtract( sets:from_list(Module#module.functions), Exports), M = Module#module.name, Map = dict:fetch(M, Maps), {Names1, Map1} = add_function_renamings(M, Other, Names, Map), Maps1 = dict:store(M, Map1, Maps), {Names1, Maps1} end, Acc, Modules).%% Adding record names. We need to keep a global%% "record-definition-to-new-record-name" mapping RMap while doing this.merge_namespaces_3(Modules, Acc) -> lists:foldl( fun ({Module, _Exports}, {{Names, Maps}, RMap}) -> Records = Module#module.records, M = Module#module.name, Map = dict:fetch(M, Maps), {Names1, Map1, RMap1} = add_record_renamings(M, Records, Names, Map, RMap), Maps1 = dict:store(M, Map1, Maps), {{Names1, Maps1}, RMap1} end, {Acc, dict:new()}, Modules).%% This takes the set of added function names together with the existing%% name set, creates new function names where necessary, and returns the%% final name set together with the list of renamings.add_function_renamings(Module, New, Names, Map) -> Clashes = sets:to_list(sets:intersection(New, Names)), lists:foldl( fun (F = {_, A}, {Names, Map}) when is_integer(A) -> F1 = new_function_name(Module, F, Names), {sets:add_element(F1, Names), dict:store(F, F1, Map)} end, {sets:union(New, Names), Map}, Clashes).%% This is similar to the above, but for record names. Note that we add%% both the record name and the whole definition to the namespace.add_record_renamings(Module, Records, Names, Map, RMap) -> lists:foldl( fun (N = {R, Fs}, {Names, Map, RMap}) -> case sets:is_element(?record_name(R), Names) of true -> %% The name is already in use. case sets:is_element(?record_name(N), Names) of true -> %% We have seen this definition before; %% make sure we use the same name. {R1, _} = remap_record_name(N, RMap), Map1 = dict:store(?record_name(R), ?record_name(R1), Map), {Names, Map1, RMap}; false -> %% Redefinition of existing name. Create %% new name and set up renamings. N1 = {R1, _} = new_record_name(Module, R, Fs, Names), Map1 = dict:store(?record_name(R), ?record_name(R1), Map), RMap1 = dict:store(N, N1, RMap), Names1 = sets:add_element(?record_name(N1), Names), {Names1, Map1, RMap1} end; false -> %% A previously unused record name. Names1 = sets:add_element(?record_name(R), Names), Names2 = sets:add_element(?record_name(N), Names1), {Names2, Map, RMap} end end, {Names, Map, RMap}, Records).remap_record_name(N, Map) -> case dict:find(N, Map) of {ok, N1} -> N1; error -> N end.%% This hides the implementation of the record namespace. Since Map%% yields identity for non-remapped names, the remapped names must be%% stored in wrapped form.map_record_name(R, Map) -> ?record_name(R1) = Map(?record_name(R)), R1.%% When we rename a function, we want the new name to be as close as%% possible to the old, and as informative as possible. Therefore, we%% first prefix it with the name of the originating module, followed by%% two underscore characters, and then if there still is a name clash,%% we suffix the name by "_N", where N is the smallest possible positive%% integer that does not cause a clash.new_function_name(M, {F, A}, Names) -> Base = atom_to_list(M) ++ "__" ++ atom_to_list(F), Name = {list_to_atom(Base), A}, case sets:is_element(Name, Names) of false -> Name; true -> new_function_name(1, A, Base, Names) end.new_function_name(N, Arity, Base, Names) -> Name = {list_to_atom(Base ++ "_" ++ integer_to_list(N)), Arity}, case sets:is_element(Name, Names) of false -> Name; true -> %% Increment counter and try again. new_function_name(N + 1, Arity, Base, Names) end.%% This is pretty much the same as new_function_name, for now.new_record_name(M, R, Fs, Names) -> Base = atom_to_list(M) ++ "__" ++ atom_to_list(R), Name = {list_to_atom(Base), Fs}, case sets:is_element(?record_name(Name), Names) of false -> Name; true -> new_record_name_1(1, Base, Fs, Names) end.new_record_name_1(N, Base, Fs, Names) -> Name = {list_to_atom(Base ++ "_" ++ integer_to_list(N)), Fs}, case sets:is_element(?record_name(Name), Names) of false -> Name; true -> %% Increment counter and try again. new_record_name_1(N + 1, Base, Fs, Names) end.%% This returns a *total* function from the set of module names to the%% set of *total* operators on function names, yielding identity for all%% function names that are not specified in the given partial map%% (ModuleName -> (Name -> Name)).make_renaming_function(Maps) -> fun (Module) -> case dict:find(Module, Maps) of {ok, Map} -> fun (Name) -> case dict:find(Name, Map) of {ok, Name1} -> Name1; % renamed error -> Name % identity end end; error -> %% Other module - yield identity map. fun (Name) -> Name end end end.%% ---------------------------------------------------------------------%% Merging module info records into a target module record, and finding%% necessary alias expansions. Returns `{Module, Expansions}' where%% `Expansions' has type `dict(ModuleName, dict(Alias, FullName))'merge_info(Modules, Names, Renaming, Env) -> Forbid = sets:from_list(Env#merge.no_imports), Expansions = alias_expansions(Modules, Names, Forbid), Module = merge_info_1(Modules, Renaming, Expansions, Env), {Module, Expansions}.merge_info_1(Modules, Renaming, Expansions, Env) -> lists:foldl( fun (M, A) -> Name = M#module.name, Map = Renaming(Name), Functions = join_functions(Map, M#module.functions, A#module.functions), Exports = join_exports(Env, Name, Map, M#module.exports, A#module.exports), Aliases = join_aliases(Name, Expansions, M#module.aliases, A#module.aliases), Attributes = join_attributes(Env, Name, M#module.attributes, A#module.attributes), Records = join_records(Map, M#module.records, A#module.records), A#module{functions = Functions, exports = Exports, aliases = Aliases, attributes = Attributes, records = Records} end, #module{name = Env#merge.target, functions = ordsets:new(), exports = ordsets:new(), aliases = ordsets:new(), attributes = ordsets:new(), records = ordsets:new()}, Modules).%% Functions must be renamed before including.join_functions(Map, Source, Target) -> ordsets:union(ordsets:from_list([Map(A) || A <- Source]), Target).%% Exports also need renaming, and are kept only if their originating%% modules are exported.join_exports(Env, Name, Map, Source, Target) -> case ordsets:is_element(Name, Env#merge.export) of true -> ordsets:union(ordsets:from_list([Map(F) || F <- Source]), Target); false -> Target end.%% Aliases never need renaming; instead we always expand uses which%% could cause name clashes. We must then remove the expanded names from%% the imports of the target.join_aliases(Name, Expansions, Source, Target) -> As = case dict:find(Name, Expansions) of {ok, As1} -> ordsets:from_list(dict:to_list(As1)); error -> [] end, ordsets:union(ordsets:subtract(Source, As), Target).%% We only propagate attributes if the number of source modules is 1 or%% the source module has the same name as the resulting module.join_attributes(Env, Name, Source, Target) -> if Env#merge.target == Name -> ordsets:union(Source, Target); true -> if length(Env#merge.sources) =:= 1 -> ordsets:union(Source, Target); true -> Target end end.%% The final record info in itself is not used at present, but we%% compute the join anyway. We apply renaming to records much like we do%% to functions, but records have a separate namespace.join_records(Map, Source, Target) -> Renamed = [{map_record_name(R, Map), Fs} || {R, Fs} <- Source], ordsets:union(ordsets:from_list(Renamed), Target).%% This finds aliases that are in conflict or are for other reasons%% necessary to expand while transforming the code later. It is assumed%% that each module is in itself correct, and thus does not contain%% conflicting definitions of the same alias.%%%% We could of course simply say that *all* aliases, without exception,%% should be expanded, but such a big change in the style of the code%% should not be done unless the user explicitly specifies it.%%%% The returned `Expansions' is a dictionary (module `dict') mapping%% each module name in `Modules' to a dictionary which maps those%% aliases to be expanded for that module to their corresponding full%% names.%%%% Aliases are filtered according to the following rules:%%%% 1. If a name is defined (in some source module) as an alias of a%% name `M:...', where `M' is any of the source modules(*), then%% the definition of that alias should be removed, and all its uses%% (in the same module as the definition) be expanded.%%%% 2. Then, if a name is defined (in some source module) as an%% alias, but the name occurs in the name space of the resulting%% module, then the definition should be removed and all uses (in%% the same module) expanded.%%%% 3. Finally, if a name has two or more distinct alias definitions%% in the source modules, then all definitions of that alias should%% be removed and all uses (in all modules) expanded. (We remove%% all definitions mainly for symmetry.)%%%% (*) It is actually possible for an alias to refer to the module%% in which it is itself defined. However, since we also in this%% case want to expand all uses, we don't have to do any extra work%% to handle it.%% The filtering is done in two stages.alias_expansions(Modules, Names, Forbid) -> Table = alias_expansions_1(Modules, Forbid, Names), alias_expansions_2(Modules, Table).%% First consider each alias in isolation.alias_expansions_1(Modules, Forbid, Names) -> lists:foldl( fun (M, T) -> Map = lists:foldl( fun ({A, F}, T1) -> case keep_alias(A, F, Forbid, Names) of true -> T1; false -> dict:store(A, F, T1) end end, dict:new(), M#module.aliases), dict:store(M#module.name, Map, T) end, dict:new(), Modules).keep_alias(A, {M, _}, Forbid, Names) ->
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -