blob: 8df007ff72a63b5a50187a0c62bdff527569d670 [file] [log] [blame]
%% @author Bob Ippolito <bob@mochimedia.com>
%% @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)
%% <a href="http://www.erlang.org/pipermail/erlang-questions/2009-March/042503.html">[1]</a>.
-module(mochiglobal).
-author("Bob Ippolito <bob@mochimedia.com>").
-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("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.