Files
n8n-openai-adapter/lib/elixir/src/elixir_module.erl
T
José Valim 441bf29f16 Remove special case for specs in elixir_module.erl
Previously, since the functions were not concrete
but stubs replaced at compilation time, we had to
special the module definition to not actually include
the specs, which would become compilation errors.

Now we provide a concrete implementation of the
functions and therefore the spec will be available
in the final source. Those implementations are not
actually invoked in practice, since they are still
transformed into erlang ones for performance reasons.
2012-10-05 15:45:29 -07:00

360 lines
12 KiB
Erlang

-module(elixir_module).
-export([translate/4, compile/5, data_table/1, eval_quoted/4, format_error/1]).
-include("elixir.hrl").
-compile({parse_transform, elixir_transform}).
eval_quoted(Module, Quoted, RawBinding, Opts) ->
Binding = binding_for_eval(Module, RawBinding),
Scope = scope_for_eval(Module, Opts),
elixir_def:reset_last(Module),
case orddict:find(line, Opts) of
{ ok, Line } -> Line;
error -> 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)).
binding_for_eval(Module, Binding) -> [{'_@MODULE',Module}|Binding].
%% TABLE METHODS
data_table(Module) ->
Module.
docs_table(Module) ->
?ELIXIR_ATOM_CONCAT([o, Module]).
spec_table(Module) ->
?ELIXIR_ATOM_CONCAT([s, Module]).
%% 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(Line, Ref, Block, S) ->
MetaBlock = elixir_tree_helpers:abstract_syntax(Block),
MetaS = elixir_scope:serialize(S),
Vars = orddict:fold(fun(Key, Value, { Acc, Counter }) ->
{ { cons, Line, { tuple, Line, [
{ atom, Line, Key },
{ atom, Line, ?ELIXIR_ATOM_CONCAT(["_@", Counter]) },
{ var, Line, Value }
] }, Acc }, Counter + 1 }
end, { { nil, Line }, 0 }, S#elixir_scope.vars),
Args = [{integer, Line, Line}, Ref, MetaBlock, element(1, Vars), MetaS],
?ELIXIR_WRAP_CALL(Line, ?MODULE, compile, Args).
%% The compilation hook.
compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
C = elixir_compiler:get_opts(),
File = S#elixir_scope.file,
check_module_availability(Line, File, Module, C),
build(Line, File, Module),
try
Result = eval_form(Line, Module, Block, Vars, S),
{ Export, Private, Def, Defmacro, Defmacrop, Functions } = elixir_def:unwrap_stored_definitions(Module),
{ All, Forms0 } = functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C),
Forms1 = attributes_form(Line, File, Module, Forms0),
Forms2 = specs_form(Line, Module, Defmacro, Defmacrop, Forms1),
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
Final = [
{attribute, Line, file, {File,Line}},
{attribute, Line, module, Module} | Forms2
],
Binary = load_form(Line, Final, S),
{ module, Module, Binary, Result }
after
ets:delete(data_table(Module)),
ets:delete(docs_table(Module)),
ets:delete(spec_table(Module)),
elixir_def:delete_table(Module),
elixir_import:delete_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 = [{ X, Y } || { X, Y, _ } <- Vars],
S = elixir_scope:deserialize(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_attributes', [before_compile,after_compile,on_definition|Attributes] }),
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
%% Keep docs in another table since we don't want to pull out
%% all the binaries every time a new documentation is stored.
DocsTable = docs_table(Module),
ets:new(DocsTable, [ordered_set, named_table, public]),
%% Holds specs in a format it is easier to query.
SpecTable = spec_table(Module),
ets:new(SpecTable, [duplicate_bag, named_table, public]),
%% We keep a separated table for function definitions
%% and another one for imports. We keep them in different
%% tables for organization and speed purpose (since the
%% imports table is frequently written to).
elixir_def:build_table(Module),
elixir_import:build_table(Module).
%% Receives the module representation and evaluates it.
eval_form(Line, Module, Block, Vars, RawS) ->
Temp = ?ELIXIR_ATOM_CONCAT(["COMPILE-",Module]),
S = scope_for_eval(Module, RawS),
{ Value, NewS } = elixir_compiler:eval_forms([Block], Line, Temp, Vars, S),
elixir_def_overridable:store_pending(Module),
eval_callbacks(Line, Module, before_compile, [Module], NewS),
Value.
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, Export, Private, Def, Defmacro, Defmacrop, Functions, C) ->
{ FinalExport, FinalFunctions } =
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
Recorded = elixir_import:recorded_locals(Module),
elixir_def_local:check_unused_local_macros(File, Recorded, Defmacrop),
{ FinalExport ++ Private, [
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
] }.
%% 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, Defmacro, Defmacrop, Forms) ->
{ Specs, Callbacks } = ets:foldl(fun(X, Acc) ->
translate_spec(X, Defmacro, Defmacrop, Acc)
end, { [], [] }, spec_table(Module)),
Temp = specs_attributes(Line, spec, Forms, Specs),
specs_attributes(Line, callback, Temp, Callbacks).
specs_attributes(Line, Type, Forms, Specs) ->
Keys = lists:ukeysort(1, Specs),
lists:foldl(fun({ Tuple, _ }, Acc) ->
[{ attribute, Line, Type, { Tuple, proplists:append_values(Tuple, Specs) } }|Acc]
end, Forms, Keys).
translate_spec({ { spec, Spec }, Rest }, Defmacro, Defmacrop, { Specs, Callbacks } = Acc) ->
case ordsets:is_element(Spec, Defmacrop) of
true -> Acc;
false ->
case ordsets:is_element(Spec, Defmacro) of
true ->
{ Name, Arity } = Spec,
New = { { ?ELIXIR_MACRO(Name), Arity + 1 }, [spec_for_macro(X) || X <- Rest] },
{ [New|Specs], Callbacks };
false ->
{ [{ Spec, Rest }|Specs], Callbacks }
end
end;
translate_spec({ { callback, Spec }, Rest }, _, _, { Specs, Callbacks }) ->
{ Specs, [{ Spec, Rest }|Callbacks] }.
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, S) ->
elixir_compiler:module(Forms, S, fun(Module, Binary) ->
EvalS = scope_for_eval(Module, S),
eval_callbacks(Line, Module, after_compile, [Module, 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(), Module, Binary }
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.
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Functions, 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),
Contents = { function, Line, '__info__', 1, [
functions_clause(Line, Def),
macros_clause(Line, Module, Def, Defmacro),
docs_clause(Line, Module, Docs),
moduledoc_clause(Line, Module, Docs),
compile_clause(Line),
self_clause(Line, Module),
else_clause(Line)
] },
{ [Pair|Export], [Contents|Functions] }
end.
functions_clause(Line, Def) ->
All = ordsets:add_element({'__info__',1}, Def),
{ clause, Line, [{ atom, Line, functions }], [], [elixir_tree_helpers:abstract_syntax(All)] }.
macros_clause(Line, Module, Def, Defmacro) ->
All = handle_builtin_macros(Module, Def, Defmacro),
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(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.
self_clause(Line, Module) ->
{ clause, Line, [{ atom, Line, self }], [], [{ atom, Line, Module }] }.
docs_clause(Line, Module, true) ->
Docs = ordsets:from_list(ets:tab2list(docs_table(Module))),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };
docs_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.
moduledoc_clause(Line, Module, true) ->
Docs = 'Elixir.Module':get_attribute(Module, moduledoc),
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };
moduledoc_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, moduledoc }], [], [{ atom, Line, nil }] }.
compile_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ atom, Line, compile }] },
WrappedInfo = ?ELIXIR_WRAP_CALL(Line, 'Elixir.Keyword', 'from_enum', [Info]),
{ clause, Line, [{ atom, Line, compile }], [], [WrappedInfo] }.
else_clause(Line) ->
Info = { call, Line, { atom, Line, module_info }, [{ var, Line, atom }] },
{ clause, Line, [{ var, Line, atom }], [], [Info] }.
% HELPERS
eval_callbacks(Line, Module, Name, Args, RawS) ->
S = RawS#elixir_scope{check_clauses=false},
Binding = binding_for_eval(Module, []),
Callbacks = lists:reverse(ets:lookup_element(data_table(Module), Name, 2)),
Requires = S#elixir_scope.requires,
lists:foreach(fun({M,F}) ->
Expr = { { '.', Line, [M,F] }, Line, Args },
Scope = case ordsets:is_element(M, Requires) of
true -> S;
false -> S#elixir_scope{requires=ordsets:add_element(M, Requires)}
end,
{ Tree, _ } = elixir_translator:translate_each(Expr, Scope),
try
erl_eval:exprs([Tree], Binding)
catch
Kind:Reason ->
Info = { M, F, Args, [{ file, binary_to_list(S#elixir_scope.file) }, { line, Line }] },
erlang:raise(Kind, Reason, [Info|erlang:get_stacktrace()])
end
end, Callbacks).
% ERROR HANDLING
format_error({ internal_function_overridden, { Name, Arity } }) ->
io_lib:format("function ~s/~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 ~s", [elixir_errors:inspect(Module)]);
format_error({ module_in_definition, Module }) ->
io_lib:format("cannot define module ~s because it is currently being defined",
[elixir_errors:inspect(Module)]).