anchor.diff:  
           , is_clojure/1
           , is_protocol/1
  -
  -        , module_info_funs/1
           ]).
   
   %% gen_server callbacks
  
positive.code: 
    cerl:ann_c_let(Ann, [VarAst], CallAst, ApplyAst).
  
  -spec add_mappings(['clojerl.Var':type()], module() | clj_module()) ->
    clj_module().
  add_mappings(_, ?NIL) -> error(badarg);
  add_mappings(Mappings, ModuleName) when is_atom(ModuleName)  ->
    add_mappings(Mappings, clj_utils:ets_get(?MODULE, ModuleName));
  add_mappings(Mappings, Module) ->
    AddFun = fun
               ({K, V}) ->
                 clj_utils:ets_save(Module#module.mappings, {K, V});
               (V) ->
                 K = clj_rt:name(V),
                 clj_utils:ets_save(Module#module.mappings, {K, V})
             end,
    lists:foreach(AddFun, Mappings),
    Module.
  
  -spec add_alias( 'clojerl.Symbol':type()
                 , 'clojerl.Symbol':type()
                 , module() | clj_module()
                 ) -> clj_module().
  add_alias(AliasSym, AliasedNsSym, ModuleName) when is_atom(ModuleName)  ->
    ok = ensure_loaded(<<?NO_SOURCE>>, ModuleName),
    add_alias(AliasSym, AliasedNsSym, clj_utils:ets_get(?MODULE, ModuleName));
  add_alias(AliasSym, AliasedNsSym, Module) ->
    K = clj_rt:name(AliasSym),
    clj_utils:ets_save(Module#module.aliases, {K, AliasedNsSym}),
    Module.
  
  -spec add_attributes([{cerl:cerl(), cerl:cerl()}], clj_module() | module()) ->
    clj_module().
  add_attributes(_, ?NIL) -> error(badarg);
  add_attributes(Attrs, ModuleName) when is_atom(ModuleName)  ->
    add_attributes(Attrs, clj_utils:ets_get(?MODULE, ModuleName));
  add_attributes([], Module) ->
    Module;
  add_attributes(Attrs, Module) ->
    AddAttr = fun(E) -> clj_utils:ets_save(Module#module.attrs, {E}) end,
    ok = lists:foreach(AddAttr, Attrs),
    Module.
  
  -spec add_exports([{atom(), non_neg_integer()}], clj_module() | module()) ->
    clj_module().
  add_exports(_, ?NIL) -> error(badarg);
  add_exports(Exports, ModuleName) when is_atom(ModuleName)  ->
    add_exports(Exports, clj_utils:ets_get(?MODULE, ModuleName));
  add_exports(Exports, Module) ->
    AddExport = fun(E) ->
                    clj_utils:ets_save(Module#module.exports, {E})
                end,
    ok = lists:foreach(AddExport, Exports),
    Module.
  
  -spec add_functions([{cerl:cerl(), cerl:cerl()}], module() | clj_module()) ->
    clj_module().
  add_functions(_, ?NIL) -> error(badarg);
  add_functions(Funs, ModuleName) when is_atom(ModuleName)  ->
    add_functions(Funs, clj_utils:ets_get(?MODULE, ModuleName));
  add_functions(Funs, Module) ->
    SaveFun = fun(F) ->
                  FunctionId  = function_id(F),
                  ok          = delete_fake_fun(FunctionId, Module),
                  clj_utils:ets_save(Module#module.funs, {FunctionId, F})
              end,
    lists:foreach(SaveFun, Funs),
    Module.
  
  -spec remove_all_functions(module() | clj_module()) ->
    clj_module().
  remove_all_functions(?NIL) -> error(badarg);
  remove_all_functions(ModuleName) when is_atom(ModuleName)  ->
    remove_all_functions(clj_utils:ets_get(?MODULE, ModuleName));
  remove_all_functions(Module) ->
    true = ets:delete_all_objects(Module#module.funs),
    true = ets:delete_all_objects(Module#module.exports),
    Module.
  
  -spec get_functions(module() | clj_module()) ->
    [{function_id(), {cerl:c_fname(), cerl:c_fun()}}].
  get_functions(?NIL) -> error(badarg);
  get_functions(ModuleName) when is_atom(ModuleName)  ->
    get_functions(clj_utils:ets_get(?MODULE, ModuleName));
  get_functions(Module) ->
    ets:tab2list(Module#module.funs).
  
  -spec add_on_load(cerl:cerl(), module() | clj_module()) ->
    clj_module().
  add_on_load(_, ?NIL) -> error(badarg);
  add_on_load(Expr, ModuleName) when is_atom(ModuleName) ->
    case clj_utils:ets_get(?MODULE, ModuleName) of
      undefined -> error({not_found, ModuleName});
      Module    -> add_on_load(Expr, Module)
    end;
  add_on_load(Expr, Module) ->
    clj_utils:ets_save(Module#module.on_load, {Expr, Expr}),
    Module.
  
  -spec is_clojure(module()) -> boolean().
  is_clojure(Name) ->
    Key = {?MODULE, is_clojure, Name},
    case clj_cache:get(Key) of
      undefined ->
        Attrs = Name:module_info(attributes),
        IsClojure = lists:keymember(clojure, 1, Attrs),
        clj_cache:put(Key, IsClojure),
        IsClojure;
      {ok, Value} ->
        Value
    end.
  
  -spec is_protocol(module() | cerl:c_module()) -> boolean().
  is_protocol(Name) when is_atom(Name) ->
    Key = {?MODULE, is_protocol, Name},
    case clj_cache:get(Key) of
      undefined ->
        Attrs = Name:module_info(attributes),
        IsProtocol = lists:keymember(protocol, 1, Attrs),
        clj_cache:put(Key, IsProtocol),
        IsProtocol;
      {ok, Value} ->
        Value
    end;
  is_protocol(CoreModule) ->
    AllAttrs       = cerl:module_attrs(CoreModule),
    {_, Extracted} = extract_attrs(AllAttrs, [clojure, protocol]),
    maps:size(Extracted) == 2.
  
  %%------------------------------------------------------------------------------
  %% gen_server callbacks
  %%------------------------------------------------------------------------------
  
  start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  
  init([]) ->
    ets:new(?MODULE, [named_table, set, protected, {keypos, 2}]),
    %% Keeps track of the modules loaded by pid.
    ets:new(?PID_TO_MODULES, [named_table, set, protected, {keypos, 1}]),
    %% Keeps track of fake modules created by which modules.
    %% It's necessary to keep it as a separate table because the
    %% information needs to survive the compiling processes during
    %% the cleanup.
    ets:new(?MODULE_TO_FAKE_MODULES, [named_table, set, protected, {keypos, 1}]),
    {ok, #{}}.
  
  handle_call({load, Module}, {Pid, _}, State) ->
    Module = clj_utils:ets_save(?MODULE, Module),
    case clj_utils:ets_get(?PID_TO_MODULES, Pid) of
      ?NIL ->
        %% Monitor the process so we can cleanup when it dies
        erlang:monitor(process, Pid),
        clj_utils:ets_save(?PID_TO_MODULES, {Pid, [Module]});
      {Pid, Modules}  ->
        clj_utils:ets_save(?PID_TO_MODULES, {Pid, [Module | Modules]})
    end,
  
    {reply, ok, State}.
  
  handle_cast({fake_module, Module, FakeModule}, State) ->
    {_, FakeModules} = clj_utils:ets_get( ?MODULE_TO_FAKE_MODULES
                                        , Module
                                        , {Module, []}
                                        ),
    clj_utils:ets_save( ?MODULE_TO_FAKE_MODULES
                      , {Module, [FakeModule | FakeModules]}
                      ),
  
    {noreply, State};
  handle_cast(_Msg, State) ->
    {noreply, State}.
  
  %% Cleanup modules when the process dies
  handle_info({'DOWN', _MonitorRef, _Type, Pid, _Info}, State) ->
    {Pid, Modules} = clj_utils:ets_get(?PID_TO_MODULES, Pid, {Pid, []}),
  
    [cleanup(M#module.name) || M <- Modules],
  
    true = ets:delete(?PID_TO_MODULES, Pid),
  
    {noreply, State};
  handle_info(_Msg, State) ->
    {noreply, State}.
  
  terminate(_Msg, _State) ->
    ok.
  
  code_change(_Msg, _From, State) ->
    {ok, State}.
  
  %%------------------------------------------------------------------------------
  %% Helper Functions
  %%------------------------------------------------------------------------------
  
  -spec cleanup(module()) -> ok.
  cleanup(Module) ->
    ets:delete(?MODULE, Module),
    {_, FakeModules} = clj_utils:ets_get( ?MODULE_TO_FAKE_MODULES
                                        , Module
                                        , {Module, []}
                                        ),
    [begin code:purge(M), code:delete(M) end || M <- FakeModules],
    ok.
  
  %% @private
  %% @doc
  %% Loads the module `Name' into memory. This function assumes it is not
  %% loaded already, so this check should be done before calling it.
  %% The value of `Source' is used to set the `file' attribute of the module
  %% if the module's binary is not found, which is interpreted as if the
  %% module is new.
  %% @end
  -spec load(binary(), module()) -> clj_module().
  load(Path, Name) when is_binary(Path) ->
    PathStr = binary_to_list(Path),
    Module = case code:ensure_loaded(Name) of
               {module, Name} ->
                 new(PathStr, clj_utils:code_from_binary(Name));
               {error, _} ->
                 new(PathStr, Name)
             end,
    ok = gen_server:call(?MODULE, {load, Module}),
    Module.
  
  -spec is_loaded(module()) -> boolean().
  is_loaded(Name) ->
    ets:member(?MODULE, Name).
  
  %% @private
  -spec build_fake_fun(atom(), integer(), clj_module()) -> function().
  build_fake_fun(Function, Arity, Module) ->
  
negative.code: 
          , fake_fun/3
          , replace_calls/2
  
          , add_mappings/2
          , add_alias/3
  