Files
n8n-openai-adapter/lib/elixir/src/elixir_import.erl
T
José Valim 89cbe93bba The AST now allows metadata to be attached to each node
This means the second item in the AST is no longer an integer
(representing the line), but a keywords list. Code that relies
on the line information from AST or that manually generate AST
nodes need to be properly updated.
2013-01-21 15:42:03 -07:00

326 lines
9.4 KiB
Erlang

%% Module responsible for handling imports and conflicts.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/5, recorded_locals/1, format_error/1,
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
build_table/1, delete_table/1, record/4]).
-include("elixir.hrl").
table(Module) -> ?atom_concat([i, Module]).
build_table(Module) ->
ets:new(table(Module), [set, named_table, public]).
delete_table(Module) ->
ets:delete(table(Module)).
record(_Kind, _Tuple, _Receiver, nil) ->
false;
record(import, Tuple, Receiver, Module) ->
try
ets:insert(table(Module), { Tuple, Receiver })
catch
error:badarg -> false
end.
recorded_locals(Module) ->
Table = table(Module),
Match = { '$1', Module },
Result = ets:match(Table, Match),
ets:match_delete(Table, Match),
lists:append(Result).
%% Update the scope to consider the imports for aliases
%% based on the given options and selector.
import(Meta, Ref, Opts, Selector, S) ->
IncludeAll = (Selector == all) or (Selector == default),
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
Functions = calculate(Meta, Ref, Opts,
S#elixir_scope.functions, FunctionsFun, S),
S#elixir_scope{functions=Functions}
end,
SM = case IncludeAll or (Selector == macros) of
false -> SF;
true ->
MacrosFun = fun(K) ->
case IncludeAll of
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
false -> get_macros(Meta, Ref, SF)
end
end,
Macros = calculate(Meta, Ref, Opts,
SF#elixir_scope.macros, MacrosFun, SF),
SF#elixir_scope{macros=Macros}
end,
SM.
%% IMPORT FUNCTION RELATED HELPERS
%% Calculates the imports based on only and except
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case keyfind(only, Opts) of
{ only, RawOnly } ->
Only = expand_fun_arity(Meta, only, RawOnly, S),
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
_ ->
intersection(Only, AvailableFun(false))
end;
false ->
case keyfind(except, Opts) of
false -> AvailableFun(true);
{ except, [] } -> AvailableFun(true);
{ except, RawExcept } ->
Except = expand_fun_arity(Meta, except, RawExcept, S),
case keyfind(Key, Old) of
false -> AvailableFun(true) -- Except;
{Key,ToRemove} -> ToRemove -- Except
end
end
end,
%% Normalize the data before storing it
Set = ordsets:from_list(New),
Final = remove_internals(Set),
case Final of
[] -> All;
_ ->
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
[{ Key, Final }|All]
end.
%% Ensure we are expanding macros and stuff
expand_fun_arity(Meta, Kind, Value, S) ->
{ TValue, _S } = elixir_translator:translate_each(Value, S),
cons_to_keywords(Meta, Kind, TValue, S).
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)];
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
cons_to_keywords(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
{ Atom, Integer };
tuple_to_fun_arity(Meta, Kind, _, S) ->
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
%% Retrieve functions and macros from modules
get_exports(Module) ->
try
Module:'__info__'(functions) ++ Module:'__info__'(macros)
catch
error:undef -> Module:module_info(exports)
end.
get_functions(Module) ->
try
Module:'__info__'(functions)
catch
error:undef -> Module:module_info(exports)
end.
get_macros(Meta, Module, S) ->
try
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
end.
get_optional_macros(Module) ->
case code:ensure_loaded(Module) of
{ module, Module } ->
try
Module:'__info__'(macros)
catch
error:undef -> []
end;
{ error, _ } -> []
end.
%% VALIDATION HELPERS
%% Check if any of the locals defined conflicts with an invoked
%% Elixir "implemented in Erlang" macro. Checking if a local
%% conflicts with an import is automatically done by Erlang.
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
Table = table(Module),
Matches = [X || X <- AllDefined, ets:member(Table, X)],
case Matches of
[{Name,Arity}|_] ->
Key = ets:lookup_element(Table, {Name, Arity }, 2),
Tuple = { import_conflict, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ok
end.
%% Ensure the given functions don't clash with any
%% of Elixir non overridable macros.
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
Values = lists:filter(fun({X,Y}) ->
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
end, non_overridable_macros()),
case Values /= [] of
true ->
Tuple = { Reason, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
%% Find conlicts in the given list of functions with the set of imports.
%% Used internally to ensure a newly imported fun or macro does not
%% conflict with an already imported set.
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
case Filtered of
[{Name,Arity}|_] ->
Tuple = { already_imported, { Key, Name, Arity } },
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Meta, File, Functions, T)
end;
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
%% ERROR HANDLING
format_error({already_imported,{Receiver, Name, Arity}}) ->
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
format_error({invalid_import,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({import_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("imported ~s.~s/~B conflicts with local function",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({local_conflict,{_, Name, Arity}}) ->
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
[elixir_errors:inspect(Receiver), Name, Arity]);
format_error({ no_macros, Module }) ->
io_lib:format("could not load macros from module ~s", [elixir_errors:inspect(Module)]).
%% LIST HELPERS
keyfind(Key, List) ->
lists:keyfind(Key, 1, List).
keydelete(Key, List) ->
lists:keydelete(Key, 1, List).
intersection([H|T], All) ->
case lists:member(H, All) of
true -> [H|intersection(T, All)];
false -> intersection(T, All)
end;
intersection([], _All) -> [].
%% INTROSPECTION
%% Internal funs that are never imported etc.
remove_underscored(default, List) -> remove_underscored(List);
remove_underscored(_, List) -> List.
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
case atom_to_list(Name) of
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
"_" ++ _ -> remove_underscored(T);
_ -> [H|remove_underscored(T)]
end;
remove_underscored(T) ->
T.
remove_internals(Set) ->
ordsets:del_element({ module_info, 1 },
ordsets:del_element({ module_info, 0 }, Set)).
%% Macros implemented in Erlang that are not overridable.
non_overridable_macros() ->
[
{'^',1},
{'=',2},
{'__op__',2},
{'__op__',3},
{'__ambiguousop__','*'},
{'__scope__',2},
{'__block__','*'},
{'->','2'},
{'<<>>','*'},
{'{}','*'},
{'[]','*'},
{'alias',1},
{'alias',2},
{'require',1},
{'require',2},
{'import',1},
{'import',2},
{'import',3},
{'__ENV__',0},
{'__CALLER__',0},
{'__MODULE__',0},
{'__FILE__',0},
{'__aliases__','*'},
{'quote',1},
{'quote',2},
{'unquote',1},
{'unquote_splicing',1},
{'fn','*'},
{'super','*'},
{'super?',0},
{'bc','*'},
{'lc','*'},
{'var!',1},
{'var!',2},
{'alias!',1}
].