Files
n8n-openai-adapter/lib/elixir/src/elixir_import.erl
T
2012-09-28 15:48:23 -07:00

322 lines
9.3 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) -> ?ELIXIR_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(Line, Ref, Opts, Selector, S) ->
IncludeAll = (Selector == all) or (Selector == default),
SF = case IncludeAll or (Selector == functions) of
false -> S;
true ->
FunctionsFun = fun() -> remove_underscored(Selector, get_functions(Ref)) end,
Functions = calculate(Line, 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() ->
case IncludeAll of
true -> remove_underscored(Selector, get_optional_macros(Ref));
false -> get_macros(Line, Ref, SF)
end
end,
Macros = calculate(Line, 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(Line, Key, Opts, Old, AvailableFun, S) ->
File = S#elixir_scope.file,
All = keydelete(Key, Old),
New = case orddict:find(only, Opts) of
{ ok, RawOnly } ->
Only = expand_fun_arity(Line, only, RawOnly, S),
case Only -- get_exports(Key) of
[{Name,Arity}|_] ->
Tuple = { invalid_import, { Key, Name, Arity } },
elixir_errors:form_error(Line, File, ?MODULE, Tuple);
_ -> intersection(Only, AvailableFun())
end;
error ->
case orddict:find(except, Opts) of
{ ok, [] } -> AvailableFun();
{ ok, RawExcept } ->
Except = expand_fun_arity(Line, except, RawExcept, S),
case keyfind(Key, Old) of
false -> AvailableFun() -- Except;
{Key,ToRemove} -> ToRemove -- Except
end;
error -> AvailableFun()
end
end,
%% Normalize the data before storing it
Set = ordsets:from_list(New),
Final = remove_internals(Set),
case Final of
[] -> All;
_ ->
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.macros)),
ensure_no_conflicts(Line, File, Final, keydelete(Key, S#elixir_scope.functions)),
ensure_no_in_erlang_macro_conflict(Line, File, Key, Final, internal_conflict),
[{ Key, Final }|All]
end.
%% Ensure we are expanding macros and stuff
expand_fun_arity(Line, Kind, Value, S) ->
{ TValue, _S } = elixir_translator:translate_each(Value, S),
cons_to_keywords(Line, Kind, TValue, S).
cons_to_keywords(Line, Kind, { cons, _, Left, { nil, _ } }, S) ->
[tuple_to_fun_arity(Line, Kind, Left, S)];
cons_to_keywords(Line, Kind, { cons, _, Left, Right }, S) ->
[tuple_to_fun_arity(Line, Kind, Left, S)|cons_to_keywords(Line, Kind, Right, S)];
cons_to_keywords(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, S#elixir_scope.file,
"invalid value for :~s, expected a list with functions and arities", [Kind]).
tuple_to_fun_arity(_Line, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
{ Atom, Integer };
tuple_to_fun_arity(Line, Kind, _, S) ->
elixir_errors:syntax_error(Line, 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(Line, Module, S) ->
try
Module:'__info__'(macros)
catch
error:undef ->
Tuple = { no_macros, Module },
elixir_errors:form_error(Line, 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(Line, File, Module, AllDefined) ->
ensure_no_in_erlang_macro_conflict(Line, File, Module, AllDefined, local_conflict).
%% Find conlicts in the given list of functions with
%% the recorded set of imports.
ensure_no_import_conflict(Line, 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(Line, 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(Line, 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(Line, File, ?MODULE, Tuple);
false -> ensure_no_in_erlang_macro_conflict(Line, File, Key, T, Reason)
end;
ensure_no_in_erlang_macro_conflict(_Line, _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(Line, 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(Line, File, ?MODULE, Tuple);
[] ->
ensure_no_conflicts(Line, File, Functions, T)
end;
ensure_no_conflicts(_Line, _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 internal macros", [Name, Arity]);
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir internal macros",
[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','*'}
].