build maps immediately when returning maps instead of building a

proplist and converting
This commit is contained in:
alisdair sullivan 2014-12-07 22:04:41 +00:00
parent 56c3bdb578
commit 43ba093ec5

View file

@ -25,8 +25,15 @@
-export([to_term/2]).
-export([init/1, handle_event/2]).
-export([start_term/0, start_term/1]).
-export([start_object/1, start_array/1, finish/1, insert/2, insert/3, get_key/1, get_value/1]).
-export([
start_term/1,
start_object/1,
start_array/1,
finish/1,
insert/2,
get_key/1,
get_value/1
]).
-record(config, {
@ -37,15 +44,27 @@
-type config() :: list().
-export_type([config/0]).
-type json_value() :: list({binary(), json_value()})
| list(json_value())
-ifndef(maps_support).
-type json_value() :: list(json_value())
| list({binary() | atom(), json_value()})
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-ifdef(maps_support).
-type json_value() :: list(json_value())
| map()
| true
| false
| null
| integer()
| float()
| binary().
-endif.
-spec to_term(Source::binary(), Config::config()) -> json_value().
@ -79,10 +98,11 @@ parse_config([K|Rest] = Options, Config) ->
parse_config([], Config) ->
Config.
-type state() :: {[any()], #config{}}.
-type state() :: {list(), #config{}}.
-spec init(Config::proplists:proplist()) -> state().
init(Config) -> {[], parse_config(Config)}.
init(Config) -> start_term(Config).
-spec handle_event(Event::any(), State::state()) -> state().
@ -118,47 +138,46 @@ format_key(Key, Config) ->
%% the stack is a list of in progress objects/arrays
%% `[Current, Parent, Grandparent,...OriginalAncestor]`
%% an object has the representation on the stack of
%% `{object, [{NthKey, NthValue}, {NMinus1Key, NthMinus1Value},...{FirstKey, FirstValue}]}`
%% of if there's a key with a yet to be matched value
%% `{object, Key, [{NthKey, NthValue},...]}`
%% `{object, [
%% {NthKey, NthValue},
%% {NMinus1Key, NthMinus1Value},
%% ...,
%% {FirstKey, FirstValue}
%% ]}`
%% or if returning maps
%% `{object, #{
%% FirstKey => FirstValue,
%% SecondKey => SecondValue,
%% ...,
%% NthKey => NthValue
%% }}`
%% or if there's a key with a yet to be matched value
%% `{object, Key, ...}`
%% an array looks like
%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
start_term() -> {[], #config{}}.
start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
-ifndef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
%% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
-ifndef(maps_support).
finish(Any) -> finish0(Any).
-endif.
-ifdef(maps_support).
finish({[{object, []}], Config=#config{return_maps=true}}) ->
{#{}, Config};
finish({[{object, []}|Rest], Config=#config{return_maps=true}}) ->
insert(#{}, {Rest, Config});
finish({[{object, Pairs}], Config=#config{return_maps=true}}) ->
{maps:from_list(Pairs), Config};
finish({[{object, Pairs}|Rest], Config=#config{return_maps=true}}) ->
insert(maps:from_list(Pairs), {Rest, Config});
finish(Else) -> finish0(Else).
-endif.
%% finish an object or array and insert it into the parent object if it exists or
%% return it if it is the root object
finish0({[{object, []}], Config}) -> {[{}], Config};
finish0({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish0({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish0({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish0({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish0({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish0(_) -> erlang:error(badarg).
finish({[{object, []}], Config}) -> {[{}], Config};
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array
insert(Value, {[], Config}) -> {Value, Config};
@ -170,11 +189,51 @@ insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
%% insert a key/value pair into an object
insert(Key, Value, {[{object, Pairs}|Rest], Config}) ->
-ifdef(maps_support).
%% allocate a new object on top of the stack
start_object({Stack, Config=#config{return_maps=true}}) ->
{[{object, #{}}] ++ Stack, Config};
start_object({Stack, Config}) ->
{[{object, []}] ++ Stack, Config}.
%% allocate a new array on top of the stack
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
%% finish an object or array and insert it into the parent object if it exists or
%% return it if it is the root object
finish({[{object, Map}], Config=#config{return_maps=true}}) ->
{Map, Config};
finish({[{object, Map}|Rest], Config=#config{return_maps=true}}) ->
insert(Map, {Rest, Config});
finish({[{object, []}], Config}) -> {[{}], Config};
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
finish(_) -> erlang:error(badarg).
%% insert a value when there's no parent object or array
insert(Value, {[], Config}) -> {Value, Config};
%% insert a key or value into an object or array, autodetects the 'right' thing
insert(Key, {[{object, Map}|Rest], Config=#config{return_maps=true}}) ->
{[{object, Key, Map}] ++ Rest, Config};
insert(Key, {[{object, Pairs}|Rest], Config}) ->
{[{object, Key, Pairs}] ++ Rest, Config};
insert(Value, {[{object, Key, Map}|Rest], Config=#config{return_maps=true}}) ->
{[{object, maps:put(Key, Value, Map)}] ++ Rest, Config};
insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
{[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
insert(_, _, _) -> erlang:error(badarg).
insert(Value, {[{array, Values}|Rest], Config}) ->
{[{array, [Value] ++ Values}] ++ Rest, Config};
insert(_, _) -> erlang:error(badarg).
-endif.
get_key({[{object, Key, _}|_], _}) -> Key;
@ -235,10 +294,6 @@ format_key_test_() ->
rep_manipulation_test_() ->
[
{"allocate a new context", ?_assertEqual(
{[], #config{}},
start_term()
)},
{"allocate a new context with option", ?_assertEqual(
{[], #config{labels=atom}},
start_term([{labels, atom}])
@ -283,10 +338,6 @@ rep_manipulation_test_() ->
{[{array, [value]}, junk], #config{}},
insert(value, {[{array, []}, junk], #config{}})
)},
{"insert a key/value pair into an object", ?_assertEqual(
{[{object, [{key, value}, {x, y}]}, junk], #config{}},
insert(key, value, {[{object, [{x, y}]}, junk], #config{}})
)},
{"finish an object with no ancestor", ?_assertEqual(
{[{a, b}, {x, y}], #config{}},
finish({[{object, [{x, y}, {a, b}]}], #config{}})
@ -309,7 +360,54 @@ rep_manipulation_test_() ->
)}
].
-ifdef(maps_support).
rep_manipulation_with_maps_test_() ->
[
{"allocate a new object on an empty stack", ?_assertEqual(
{[{object, #{}}], #config{return_maps=true}},
start_object({[], #config{return_maps=true}})
)},
{"allocate a new object on a stack", ?_assertEqual(
{[{object, #{}}, {object, #{}}], #config{return_maps=true}},
start_object({[{object, #{}}], #config{return_maps=true}})
)},
{"insert a key into an object", ?_assertEqual(
{[{object, key, #{}}, junk], #config{return_maps=true}},
insert(key, {[{object, #{}}, junk], #config{return_maps=true}})
)},
{"get current key", ?_assertEqual(
key,
get_key({[{object, key, #{}}], #config{return_maps=true}})
)},
{"try to get non-key from object", ?_assertError(
badarg,
get_key({[{object, #{}}], #config{return_maps=true}})
)},
{"insert a value into an object", ?_assertEqual(
{[{object, #{key => value}}, junk], #config{return_maps=true}},
insert(value, {[{object, key, #{}}, junk], #config{return_maps=true}})
)},
{"finish an object with no ancestor", ?_assertEqual(
{#{a => b, x => y}, #config{return_maps=true}},
finish({[{object, #{x => y, a => b}}], #config{return_maps=true}})
)},
{"finish an empty object", ?_assertEqual(
{#{}, #config{return_maps=true}},
finish({[{object, #{}}], #config{return_maps=true}})
)},
{"finish an object with an ancestor", ?_assertEqual(
{
[{object, #{key => #{a => b, x => y}, foo => bar}}],
#config{return_maps=true}
},
finish({
[{object, #{x => y, a => b}}, {object, key, #{foo => bar}}],
#config{return_maps=true}
})
)}
].
return_maps_test_() ->
[
@ -334,7 +432,6 @@ return_maps_test_() ->
jsx:decode(<<"[{}]">>, [return_maps])
)}
].
-endif.