Files
n8n-openai-adapter/lib/elixir/src/elixir_module.erl
T

404 lines
13 KiB
Erlang

-module(elixir_module).
-export([translate/4, compile/5, data_table/1, docs_table/1,
eval_quoted/4, format_error/1, eval_callbacks/5]).
-include("elixir.hrl").
-define(docs_attr, '__docs_table').
-define(acc_attr, '__acc_attributes').
-define(persisted_attr, '__persisted_attributes').
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = elixir_scope:binding_for_eval(RawBinding, Module),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
case lists:keyfind(line, 1, Opts) of
{ line, Line } -> Line;
false -> Line = 1
end,
{ Value, FinalBinding, _Scope } = elixir:eval_quoted([Quoted], Binding, Line, Scope),
{ Value, FinalBinding }.
scope_for_eval(Module, #elixir_scope{} = S) ->
S#elixir_scope{module=Module};
scope_for_eval(Module, Opts) ->
scope_for_eval(Module, elixir:scope_for_eval(Opts)).
%% TABLE METHODS
data_table(Module) ->
Module.
docs_table(Module) ->
ets:lookup_element(Module, ?docs_attr, 2).
%% TRANSFORMATION FUNCTIONS
%% Transformation of args and scope into a compiled erlang call.
%% The abstract form for extra arguments may be given and they
%% will be passed to the invoked function.
translate(Meta, Ref, Block, S) ->
Line = ?line(Meta),
MetaBlock = elixir_utils:elixir_to_erl(Block),
{ MetaS, Vars } = elixir_scope:serialize_with_vars(Line, S),
Args = [{integer, Line, Line}, Ref, MetaBlock, Vars, MetaS],
?wrap_call(Line, ?MODULE, compile, Args).
%% The compilation hook.
compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} = RawS) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
S = case lists:member(Module, FileModules) of
true -> RawS;
false -> RawS#elixir_scope{context_modules=[Module|FileModules]}
end,
File = S#elixir_scope.file,
FileList = unicode:characters_to_list(File),
check_module_availability(Line, File, Module, C),
build(Line, File, Module),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Base, Export, Private, Def, Defmacro, Functions } = elixir_def:unwrap_stored_definitions(FileList, Module),
{ All, Forms0 } = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions, C),
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
Forms2 = attributes_form(Line, File, Module, Forms1),
case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
AllFunctions = Def ++ [T || { T, defp, _, _, _ } <- Private],
elixir_tracker:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_tracker:ensure_all_imports_used(Line, File, Module),
elixir_tracker:warn_unused_local(File, Module, Private),
warn_unused_docs(Line, File, Module, All),
Final = [
{ attribute, Line, file, { FileList, Line } },
{ attribute, Line, module, Module } | Forms2
],
Binary = load_form(Line, Final, S),
{ module, Module, Binary, Result }
after
elixir_tracker:cleanup(Module),
elixir_def:cleanup(Module),
ets:delete(docs_table(Module)),
ets:delete(data_table(Module))
end;
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, { invalid_module, Other });
compile(Line, Module, Block, Vars, RawS) ->
Dict = [{ { Name, Kind }, Value } || { Name, Kind, Value, _ } <- Vars],
S = elixir_scope:deserialize_with_vars(RawS, Dict),
compile(Line, Module, Block, Vars, S).
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
case ets:info(DataTable, name) == DataTable of
true -> elixir_errors:form_error(Line, File, ?MODULE, { module_in_definition, Module });
false -> []
end,
ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, { '__overridable', [] }),
ets:insert(DataTable, { before_compile, [] }),
ets:insert(DataTable, { after_compile, [] }),
case elixir_compiler:get_opt(docs) of
true -> ets:insert(DataTable, { on_definition, [{ 'Elixir.Module', compile_doc }] });
_ -> ets:insert(DataTable, { on_definition, [] })
end,
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
%% Setup other modules
elixir_def:setup(Module),
elixir_tracker:setup(Module).
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, RawS) ->
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Vars, S),
elixir_def_overridable:store_pending(Module),
Env = elixir_scope:to_ex_env({ Line, S }),
eval_callbacks(Line, Module, before_compile, [Env], NewS),
elixir_def_overridable:store_pending(Module),
Value.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, RawFunctions, C) ->
BaseFunctions = case elixir_compiler:get_opt(internal, C) of
true -> RawFunctions;
false -> record_rewrite_functions(Module, RawFunctions)
end,
Info = add_info_function(Line, File, Module, BaseExport, Def, Defmacro, C),
All = [{ '__info__', 1 }|BaseAll],
Export = [{ '__info__', 1 }|BaseExport],
Functions = [Info|BaseFunctions],
{ All, [
{ attribute, Line, export, lists:sort(Export) } | Functions
] }.
record_rewrite_functions(Module, Functions) ->
lists:map(fun
({ function, Line, Name, Arity, Clauses }) ->
Rewriten = [begin
{ C, _, _ } = 'Elixir.Kernel.RecordRewriter':optimize_clause(Module, Clause),
C
end || Clause <- Clauses],
{ function, Line, Name, Arity, Rewriten };
(Other) -> Other
end, Functions).
%% Add attributes handling to the form
attributes_form(Line, _File, Module, Current) ->
Table = data_table(Module),
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
PersistedAttrs = ets:lookup_element(Table, '__persisted_attributes', 2),
Transform = fun({ Key, Value }, Acc) ->
case lists:member(Key, PersistedAttrs) of
false -> Acc;
true ->
Attrs = case lists:member(Key, AccAttrs) of
true -> Value;
false -> [Value]
end,
lists:foldl(fun(X, Final) -> [{ attribute, Line, Key, X }|Final] end, Acc, Attrs)
end
end,
ets:foldl(Transform, Current, Table).
%% Specs
specs_form(Line, Module, Private, Defmacro, Forms, C) ->
Defmacrop = [Tuple || { Tuple, defmacrop, _, _, _ } <- Private],
case elixir_compiler:get_opt(internal, C) of
true -> Forms;
_ ->
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
'Elixir.Module':delete_attribute(Module, spec),
'Elixir.Module':delete_attribute(Module, callback),
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks)
end.
specs_attributes(Line, Type, Forms, Specs) ->
Keys = lists:foldl(fun({ Tuple, Value }, Acc) ->
lists:keystore(Tuple, 1, Acc, { Tuple, Value } )
end, [], Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
Values = [V || { K, V } <- Specs, K == Tuple],
[{ attribute, Line, Type, { Tuple, Values } }|Acc]
end, Forms, Keys).
translate_spec({ Spec, Rest }, Defmacro, Defmacrop) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> { Spec, Rest };
false ->
case ordsets:is_element(Spec, Defmacro) of
true ->
{ Name, Arity } = Spec,
{ { ?elixir_macro(Name), Arity + 1 }, spec_for_macro(Rest) };
false ->
{ Spec, Rest }
end
end.
spec_for_macro({ type, Line, 'fun', [{ type, _, product, Args }|T] }) ->
NewArgs = [{type,Line,term,[]}|Args],
{ type, Line, 'fun', [{ type, Line, product, NewArgs }|T] };
spec_for_macro(Else) -> Else.
%% Loads the form into the code server.
load_form(Line, Forms, #elixir_scope{file=File} = S) ->
elixir_compiler:module(Forms, File, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
Env = elixir_scope:to_ex_env({ Line, EvalS }),
eval_callbacks(Line, Module, after_compile, [Env, Binary], EvalS),
case get(elixir_compiled) of
Current when is_list(Current) ->
put(elixir_compiled, [{Module,Binary}|Current]),
case get(elixir_compiler_pid) of
undefined -> [];
PID ->
PID ! { module_available, self(), File, Module, Binary },
receive { PID, ack } -> ok end
end;
_ ->
[]
end,
Binary
end).
check_module_availability(Line, File, Module, Compiler) ->
case elixir_compiler:get_opt(ignore_module_conflict, Compiler) of
false ->
case code:ensure_loaded(Module) of
{ module, _ } ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { module_defined, Module } });
{ error, _ } ->
[]
end;
true ->
[]
end.
warn_unused_docs(_Line, _File, 'Elixir.Kernel', _All) -> ok;
warn_unused_docs(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_unused_docs(_Line, File, Module, All) ->
ets:foldl(fun
({ Tuple, Line, _, _, _ }, Acc) ->
case lists:member(Tuple, All) of
true -> Acc;
false ->
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { invalid_doc, Tuple } })
end
end, ok, docs_table(Module)).
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Def, Defmacro, C) ->
Pair = { '__info__', 1 },
case lists:member(Pair, Export) of
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
false ->
Docs = elixir_compiler:get_opt(docs, C),
{ function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Module, Def, Defmacro),
docs_clause(Module, Docs),
moduledoc_clause(Line, Module, Docs),
module_clause(Module),
else_clause()
] }
end.
functions_clause(Def) ->
{ clause, 0, [{ atom, 0, functions }], [], [elixir_utils:elixir_to_erl(Def)] }.
macros_clause(Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, 0, [{ atom, 0, macros }], [], [elixir_utils:elixir_to_erl(All)] }.
handle_builtin_macros('Elixir.Kernel', Def, Defmacro) ->
ordsets:subtract(ordsets:union(Defmacro,
elixir_dispatch:in_erlang_macros()), Def);
handle_builtin_macros(_, _Def, Defmacro) -> Defmacro.
module_clause(Module) ->
{ clause, 0, [{ atom, 0, module }], [], [{ atom, 0, Module }] }.
docs_clause(Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, 0, [{ atom, 0, docs }], [], [elixir_utils:elixir_to_erl(Docs)] };
docs_clause(_Module, _) ->
{ clause, 0, [{ atom, 0, docs }], [], [{ atom, 0, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, 0, [{ atom, 0, moduledoc }], [], [elixir_utils:elixir_to_erl({ Line, Docs })] };
moduledoc_clause(_Line, _Module, _) ->
{ clause, 0, [{ atom, 0, moduledoc }], [], [{ atom, 0, nil }] }.
else_clause() ->
Info = { call, 0, { atom, 0, module_info }, [{ var, 0, atom }] },
{ clause, 0, [{ var, 0, atom }], [], [Info] }.
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
Binding = elixir_scope:binding_for_eval([], Module),
S = elixir_scope:vars_from_binding(RawS, Binding),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Meta = [{line,Line},{require,false}],
lists:foreach(fun({M,F}) ->
{ Tree, _ } = elixir_dispatch:dispatch_require(Meta, M, F, Args, S, fun() ->
apply(M, F, Args),
{ { atom, 0, nil }, S }
end),
case Tree of
{ atom, _, Atom } ->
Atom;
_ ->
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, length(Args), [{ file, unicode:characters_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace()))
end
end
end, Callbacks).
%% We've reached the elixir_dispatch internals, skip it with the rest
munge_stacktrace(Info, [{ elixir_module, _, _, _ }|_]) ->
[Info];
munge_stacktrace(Info, [H|T]) ->
[H|munge_stacktrace(Info, T)];
munge_stacktrace(Info, []) ->
[Info].
% ERROR HANDLING
format_error({ invalid_doc, { Name, Arity } }) ->
io_lib:format("docs provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
format_error({ internal_function_overridden, { Name, Arity } }) ->
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
format_error({ invalid_module, Module}) ->
io_lib:format("invalid module name: ~p", [Module]);
format_error({ module_defined, Module }) ->
io_lib:format("redefining module ~ts", [elixir_errors:inspect(Module)]);
format_error({ module_in_definition, Module }) ->
io_lib:format("cannot define module ~ts because it is currently being defined",
[elixir_errors:inspect(Module)]).