%% @author Bob Ippolito %% @copyright 2010 Mochi Media, Inc. %% %% Permission is hereby granted, free of charge, to any person obtaining a %% copy of this software and associated documentation files (the "Software"), %% to deal in the Software without restriction, including without limitation %% the rights to use, copy, modify, merge, publish, distribute, sublicense, %% and/or sell copies of the Software, and to permit persons to whom the %% Software is furnished to do so, subject to the following conditions: %% %% The above copyright notice and this permission notice shall be included in %% all copies or substantial portions of the Software. %% %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %% DEALINGS IN THE SOFTWARE. %% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6) %% [1]. -module(partisan_mochiglobal). -author("Bob Ippolito "). -export([get/1, get/2, put/2, delete/1]). -spec get(atom()) -> any() | undefined. %% @equiv get(K, undefined) get(K) -> get(K, undefined). -spec get(atom(), T) -> any() | T. %% @doc Get the term for K or return Default. get(K, Default) -> get(K, Default, key_to_module(K)). get(_K, Default, Mod) -> try Mod:term() catch error:undef -> Default end. -spec put(atom(), any()) -> ok. %% @doc Store term V at K, replaces an existing term if present. put(K, V) -> put(K, V, key_to_module(K)). put(_K, V, Mod) -> Bin = compile(Mod, V), code:purge(Mod), {module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin), ok. -spec delete(atom()) -> boolean(). %% @doc Delete term stored at K, no-op if non-existent. delete(K) -> delete(K, key_to_module(K)). delete(_K, Mod) -> code:purge(Mod), code:delete(Mod). -spec key_to_module(atom()) -> atom(). key_to_module(K) -> list_to_atom("partisan_mochiglobal:" ++ atom_to_list(K)). -spec compile(atom(), any()) -> binary(). compile(Module, T) -> {ok, Module, Bin} = compile:forms(forms(Module, T), [verbose, report_errors]), Bin. -spec forms(atom(), any()) -> [erl_syntax:syntaxTree()]. forms(Module, T) -> [erl_syntax:revert(X) || X <- term_to_abstract(Module, term, T)]. -spec term_to_abstract(atom(), atom(), any()) -> [erl_syntax:syntaxTree()]. term_to_abstract(Module, Getter, T) -> [%% -module(Module). erl_syntax:attribute( erl_syntax:atom(module), [erl_syntax:atom(Module)]), %% -export([Getter/0]). erl_syntax:attribute( erl_syntax:atom(export), [erl_syntax:list( [erl_syntax:arity_qualifier( erl_syntax:atom(Getter), erl_syntax:integer(0))])]), %% Getter() -> T. erl_syntax:function( erl_syntax:atom(Getter), [erl_syntax:clause([], none, [erl_syntax:abstract(T)])])]. %% %% Tests %% -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). get_put_delete_test() -> K = '$$test$$mochiglobal', delete(K), ?assertEqual( bar, get(K, bar)), try ?MODULE:put(K, baz), ?assertEqual( baz, get(K, bar)), ?MODULE:put(K, wibble), ?assertEqual( wibble, ?MODULE:get(K)) after delete(K) end, ?assertEqual( bar, get(K, bar)), ?assertEqual( undefined, ?MODULE:get(K)), ok. -endif.