%% Helpers related to dispatching to imports. -module(elixir_dispatch). -export([get_functions/1, get_macros/3, get_optional_macros/1, default_macros/0, default_functions/0, default_refer/0, format_error/1, dispatch_refer/6, dispatch_imports/5]). -include("elixir.hrl"). default_functions() -> [ { '::Elixir::Builtin', get_functions('::Elixir::Builtin') }, { erlang, in_erlang_functions() } ]. default_macros() -> [ { '::Elixir::Builtin', get_optional_macros('::Elixir::Builtin') } ]. default_refer() -> [ { '::Elixir::Builtin', '::Elixir::Builtin' } ]. %% Get macros/functions from the given module and %% raise an exception if appropriated. get_functions('::Elixir::Builtin' = Module) -> try ordsets:from_list((Module:module_info(exports) -- get_optional_macros(Module)) -- [{module_info,0},{module_info,1},{'__info__',1}]) catch error:undef -> [] end; get_functions(Module) -> ordsets:from_list(Module:module_info(exports) -- get_optional_macros(Module)). get_macros(_Line, '::Elixir::Builtin', _S) -> ordsets:from_list(get_optional_macros('::Elixir::Builtin')); get_macros(Line, Module, S) -> try ordsets:from_list(Module:'__info__'(macros)) catch error:undef -> Tuple = { no_macros, Module }, elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple) end. get_optional_macros('::Elixir::Builtin') -> try ordsets:from_list('::Elixir::Builtin':'__info__'(macros) ++ in_erlang_macros()) catch error:undef -> ordsets:from_list(in_erlang_macros()) end; %% Do not try to get macros from Erlang. Speeds up compilation a bit. get_optional_macros(erlang) -> []; get_optional_macros(Receiver) -> try ordsets:from_list(Receiver:'__info__'(macros)) catch error:undef -> [] end. %% Dispatch based on scope's imports dispatch_imports(Line, Name, Args, S, Callback) -> Arity = length(Args), Tuple = { Name, Arity }, case find_dispatch(Tuple, S#elixir_scope.functions) of nil -> case find_dispatch(Tuple, S#elixir_scope.macros) of nil -> Callback(); Receiver -> elixir_import:record(import, Tuple, Receiver, S), dispatch_macro(Line, Receiver, Tuple, Args, S) end; Receiver -> elixir_import:record(import, Tuple, Receiver, S), elixir_translator:translate_each({ { '.', Line, [Receiver, Name] }, Line, Args }, S) end. %% Dispatch based on scope's refer dispatch_refer(Line, Receiver, Name, Args, S, Callback) -> Arity = length(Args), Tuple = {Name, Arity}, case lists:member(Tuple, get_optional_macros(Receiver)) of true -> dispatch_macro(Line, Receiver, Tuple, Args, S); false -> Callback() end. %% HELPERS dispatch_macro(Line, '::Elixir::Builtin' = Receiver, { Name, Arity } = Tuple, Args, S) -> case lists:member(Tuple, in_erlang_macros()) of true -> elixir_macros:translate_macro({ Name, Line, Args }, S); false -> dispatch_macro(Line, Receiver, Name, Arity, Args, S) end; dispatch_macro(Line, Receiver, { Name, Arity }, Args, S) -> dispatch_macro(Line, Receiver, Name, Arity, Args, S). dispatch_macro(Line, Receiver, Name, Arity, Args, S) -> ensure_required(Line, Receiver, Name, Arity, S), Tree = try apply(Receiver, Name, Args) catch Kind:Reason -> Info = { Receiver, Name, length(Args), [{ file, S#elixir_scope.filename }, { line, Line }] }, erlang:raise(Kind, Reason, insert_before_dispatch_macro(Info, erlang:get_stacktrace())) end, NewS = S#elixir_scope{macro={Receiver,Name,Arity}}, { TTree, TS } = elixir_translator:translate_each(elixir_quote:linify(Line, Tree), NewS), { TTree, TS#elixir_scope{macro=S#elixir_scope.macro} }. find_dispatch(Tuple, [{ Name, Values }|T]) -> case ordsets:is_element(Tuple, Values) of true -> Name; false -> find_dispatch(Tuple, T) end; find_dispatch(_Tuple, []) -> nil. %% Insert call site into backtrace right after dispatch macro insert_before_dispatch_macro(Info, [{ elixir_dispatch, dispatch_macro, _, _ }|_] = T) -> [Info|T]; insert_before_dispatch_macro(Info, [H|T]) -> [H|insert_before_dispatch_macro(Info, T)]; insert_before_dispatch_macro(_, []) -> []. %% ERROR HANDLING ensure_required(Line, Receiver, Name, Arity, S) -> Required = [V || { _, V } <- S#elixir_scope.refer], case lists:member(Receiver, Required) of true -> ok; false -> Tuple = { unrequired_module, { Receiver, Name, Arity, Required } }, elixir_errors:form_error(Line, S#elixir_scope.filename, ?MODULE, Tuple) end. format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) -> io_lib:format("tried to invoke macro ~s.~s/~B but module was not required. Required: ~p", [Receiver, Name, Arity, Required]); format_error({ no_macros, Module }) -> io_lib:format("could not load macros from module ~s", [Module]). %% Implemented in Erlang. in_erlang_functions() -> [ { abs, 1 }, { atom_to_binary, 2 }, { atom_to_list, 1 }, { binary_part, 2 }, { binary_part, 3 }, { binary_to_atom, 2 }, { binary_to_existing_atom, 2 }, { binary_to_list, 1 }, { binary_to_list, 3 }, { binary_to_term, 1 }, { binary_to_term, 2 }, { bit_size, 1 }, { bitstring_to_list, 1 }, { byte_size, 1 }, { date, 0 }, { disconnect_node, 1 }, { exit, 1 }, { exit, 2 }, { float, 1 }, { float_to_list, 1 }, { garbage_collect, 0 }, { garbage_collect, 1 }, { group_leader, 0 }, { group_leader, 2 }, { halt, 0 }, { halt, 1 }, { hd, 1 }, { integer_to_list, 1 }, { integer_to_list, 2 }, { iolist_size, 1 }, { iolist_to_binary, 1 }, { is_alive, 0 }, { is_atom, 1 }, { is_binary, 1 }, { is_bitstring, 1 }, { is_boolean, 1 }, { is_float, 1 }, { is_function, 1 }, { is_function, 2 }, { is_integer, 1 }, { is_list, 1 }, { is_number, 1 }, { is_pid, 1 }, { is_port, 1 }, { is_reference, 1 }, { is_tuple, 1 }, { length, 1 }, { list_to_atom, 1 }, { list_to_binary, 1 }, { list_to_bitstring, 1 }, { list_to_existing_atom, 1 }, { list_to_float, 1 }, { list_to_integer, 1 }, { list_to_integer, 2 }, { list_to_pid, 1 }, { list_to_tuple, 1 }, { make_ref, 0 }, { max, 2 }, { min, 2 }, { monitor_node, 2 }, { node, 0 }, { node, 1 }, { nodes, 0 }, { nodes, 1 }, { now, 0 }, { pid_to_list, 1 }, { round, 1 }, { size, 1 }, { spawn, 1 }, { spawn, 2 }, { spawn, 3 }, { spawn, 4 }, { spawn_link, 1 }, { spawn_link, 2 }, { spawn_link, 3 }, { spawn_link, 4 }, { spawn_monitor, 1 }, { spawn_monitor, 3 }, { spawn_opt, 2 }, { spawn_opt, 3 }, { spawn_opt, 4 }, { spawn_opt, 5 }, { split_binary, 2 }, { term_to_binary, 1 }, { term_to_binary, 2 }, { throw, 1 }, { time, 0 }, { tl, 1 }, { trunc, 1 }, { tuple_size, 1 }, { tuple_to_list, 1 } ]. in_erlang_macros() -> [ {'@',1}, {'+',1}, {'::',1}, {'+',2}, {'-',1}, {'-',2}, {'*',2}, {'/',2}, {'<-',2}, {'++',2}, {'--',2}, {'::',2}, {'<',2}, {'>',2}, {'<=',2}, {'>=',2}, {'==',2}, {'!=',2}, {'===',2}, {'!==',2}, {'apply',3}, {'not',1}, {'and',2}, {'or',2}, {'xor',2}, {'use',1}, {'use',2}, {'defmodule',2}, {'defmodule',3}, {'def',1}, {'def',2}, {'def',4}, {'defp',1}, {'defp',2}, {'defp',4}, {'defmacro',1}, {'defmacro',2}, {'defmacro',4}, {'case',2}, {'receive',1}, {'try',1}, {'var!',1} ].