diff --git a/CHANGES.md b/CHANGES.md index 4a66843..0de2a3a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +v2.4.0 + +* enough performance improvements to justify a new version. 2-3x + speedup depending on mode of operation + v2.3.1 * fixes an issue where astral plane json escape sequences were diff --git a/README.md b/README.md index dd2bcd0..9f1e998 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# jsx (v2.3.1) # +# jsx (v2.4) # an erlang application for consuming, producing and manipulating [json][json]. inspired by [yajl][yajl] @@ -387,10 +387,6 @@ additional options beyond these. see control codes and problematic codepoints and replacing them with the appropriate escapes -- `repeat_keys` - - this flag circumvents checking for repeated keys in generated json - - `stream` see [incomplete input](#incomplete-input) diff --git a/config/maps b/config/maps deleted file mode 100644 index 8aa3993..0000000 --- a/config/maps +++ /dev/null @@ -1 +0,0 @@ -maps:keys(#{0 => false, 1 => true}) == [0,1]. \ No newline at end of file diff --git a/package.exs b/package.exs deleted file mode 100644 index d5708a6..0000000 --- a/package.exs +++ /dev/null @@ -1,30 +0,0 @@ -defmodule JSX.Mixfile do -use Mix.Project - - def project do - [ - app: :jsx, - version: "2.3.0", - description: "an erlang application for consuming, producing and manipulating json. inspired by yajl", - package: package - ] - end - - defp package do - [ - files: [ - "CHANGES.md", - "LICENSE", - "package.exs", - "README.md", - "rebar.config", - "rebar.config.script", - "config", - "src" - ], - contributors: ["alisdair sullivan"], - links: %{"github" => "https://github.com/talentdeficit/jsx"}, - licenses: ["MIT"] - ] - end -end \ No newline at end of file diff --git a/rebar.config b/rebar.config index c59e185..b2653a2 100644 --- a/rebar.config +++ b/rebar.config @@ -1,2 +1,4 @@ -% uncomment to disable encoding support for erlang maps -% {jsx_nomaps, true}. \ No newline at end of file +{erl_opts, [ + {platform_define, "R14|R15", 'no_binary_to_whatever'}, + {platform_define, "^((?!R1[456]).)*$", 'maps_support'} +]}. \ No newline at end of file diff --git a/rebar.config.script b/rebar.config.script deleted file mode 100644 index c5d8d92..0000000 --- a/rebar.config.script +++ /dev/null @@ -1,9 +0,0 @@ -case os:getenv("JSX_NOMAPS") or proplists:get_value(jsx_nomaps, CONFIG, false) of - false -> - try file:script("config/maps") of - {ok, true} -> [{erl_opts, [{d, maps_support}]}] ++ CONFIG; - _ -> CONFIG - catch _:_ -> CONFIG - end; - _ -> CONFIG -end. \ No newline at end of file diff --git a/src/jsx.app.src b/src/jsx.app.src index a81e5c0..f0090de 100644 --- a/src/jsx.app.src +++ b/src/jsx.app.src @@ -1,7 +1,7 @@ {application, jsx, [ {description, "a streaming, evented json parsing toolkit"}, - {vsn, "2.3.1"}, + {vsn, "2.4.0"}, {modules, [ jsx, jsx_encoder, diff --git a/src/jsx_config.erl b/src/jsx_config.erl index f7b8d7a..b720033 100644 --- a/src/jsx_config.erl +++ b/src/jsx_config.erl @@ -63,12 +63,14 @@ parse_config([unescaped_jsonp|Rest], Config) -> parse_config(Rest, Config#config{unescaped_jsonp=true}); parse_config([dirty_strings|Rest], Config) -> parse_config(Rest, Config#config{dirty_strings=true}); +%% retained for backwards compat, now does nothing however parse_config([repeat_keys|Rest], Config) -> - parse_config(Rest, Config#config{repeat_keys=true}); + parse_config(Rest, Config); parse_config([uescape|Rest], Config) -> parse_config(Rest, Config#config{uescape=true}); parse_config([strict|Rest], Config) -> - parse_config(Rest, Config#config{strict_comments=true, + parse_config(Rest, Config#config{ + strict_comments=true, strict_commas=true, strict_utf8=true, strict_single_quotes=true, @@ -190,7 +192,6 @@ config_test_() -> escaped_strings = true, unescaped_jsonp = true, dirty_strings = true, - repeat_keys = true, strict_comments = true, strict_commas = true, strict_utf8 = true, @@ -274,7 +275,6 @@ config_to_list_test_() -> escaped_strings, unescaped_jsonp, dirty_strings, - repeat_keys, stream, uescape, strict @@ -284,7 +284,6 @@ config_to_list_test_() -> escaped_strings = true, unescaped_jsonp = true, dirty_strings = true, - repeat_keys = true, strict_comments = true, strict_utf8 = true, strict_single_quotes = true, diff --git a/src/jsx_config.hrl b/src/jsx_config.hrl index e72247a..3b87da1 100644 --- a/src/jsx_config.hrl +++ b/src/jsx_config.hrl @@ -3,7 +3,6 @@ escaped_strings = false :: boolean(), unescaped_jsonp = false :: boolean(), dirty_strings = false :: boolean(), - repeat_keys = false :: boolean(), strict_comments = false :: boolean(), strict_commas = false :: boolean(), strict_utf8 = false :: boolean(), diff --git a/src/jsx_decoder.erl b/src/jsx_decoder.erl index a91c27c..4a049ed 100644 --- a/src/jsx_decoder.erl +++ b/src/jsx_decoder.erl @@ -58,10 +58,7 @@ resume(Rest, State, Handler, Acc, Stack, Config) -> colon -> colon(Rest, Handler, Stack, Config); key -> key(Rest, Handler, Stack, Config); string -> string(Rest, Handler, Acc, Stack, Config); - integer -> integer(Rest, Handler, Acc, Stack, Config); - decimal -> decimal(Rest, Handler, Acc, Stack, Config); - exp -> exp(Rest, Handler, Acc, Stack, Config); - zero -> zero(Rest, Handler, Acc, Stack, Config); + number -> number(Rest, Handler, Acc, Stack, Config); true -> true(Rest, Handler, Stack, Config); false -> false(Rest, Handler, Stack, Config); null -> null(Rest, Handler, Stack, Config); @@ -119,10 +116,6 @@ resume(Rest, State, Handler, Acc, Stack, Config) -> Symbol >= $1 andalso Symbol =< $9 ). --define(is_whitespace(Symbol), - Symbol =:= ?space; Symbol =:= ?tab; Symbol =:= ?cr; Symbol =:= ?newline -). - %% error is a macro so the stack trace shows the error site when possible -ifndef(error). @@ -177,26 +170,54 @@ start(Bin, Handler, Stack, Config) -> value(<>, Handler, Stack, Config) -> string(Rest, Handler, Stack, Config); -value(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> - string(Rest, Handler, [singlequote|Stack], Config); +value(<>, Handler, Stack, Config) -> + value(Rest, Handler, Stack, Config); +value(<>, Handler, Stack, Config) -> + object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config); +value(<>, Handler, Stack, Config) -> + array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config); +value(<<$t, $r, $u, $e, Rest/binary>>, Handler, Stack, Config) -> + maybe_done(Rest, handle_event({literal, true}, Handler, Config), Stack, Config); +value(<<$f, $a, $l, $s, $e, Rest/binary>>, Handler, Stack, Config) -> + maybe_done(Rest, handle_event({literal, false}, Handler, Config), Stack, Config); +value(<<$n, $u, $l, $l, Rest/binary>>, Handler, Stack, Config) -> + maybe_done(Rest, handle_event({literal, null}, Handler, Config), Stack, Config); +value(<>, Handler, Stack, Config) -> + number(Rest, Handler, [?zero], [zero|Stack], Config); +value(<<$1, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$1], [integer|Stack], Config); +value(<<$2, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$2], [integer|Stack], Config); +value(<<$3, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$3], [integer|Stack], Config); +value(<<$4, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$4], [integer|Stack], Config); +value(<<$5, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$5], [integer|Stack], Config); +value(<<$6, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$6], [integer|Stack], Config); +value(<<$7, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$7], [integer|Stack], Config); +value(<<$8, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$8], [integer|Stack], Config); +value(<<$9, Rest/binary>>, Handler, Stack, Config) -> + number(Rest, Handler, [$9], [integer|Stack], Config); +value(<>, Handler, Stack, Config) -> + number(Rest, Handler, [$-], [negative|Stack], Config); +value(<>, Handler, Stack, Config) -> + value(Rest, Handler, Stack, Config); value(<<$t, Rest/binary>>, Handler, Stack, Config) -> true(Rest, Handler, Stack, Config); value(<<$f, Rest/binary>>, Handler, Stack, Config) -> false(Rest, Handler, Stack, Config); value(<<$n, Rest/binary>>, Handler, Stack, Config) -> null(Rest, Handler, Stack, Config); -value(<>, Handler, Stack, Config) -> - negative(Rest, Handler, [$-], Stack, Config); -value(<>, Handler, Stack, Config) -> - zero(Rest, Handler, [$0], Stack, Config); -value(<>, Handler, Stack, Config) when ?is_nonzero(S) -> - integer(Rest, Handler, [S], Stack, Config); -value(<>, Handler, Stack, Config) -> - object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config); -value(<>, Handler, Stack, Config) -> - array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config); -value(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +value(<>, Handler, Stack, Config) -> value(Rest, Handler, Stack, Config); +value(<>, Handler, Stack, Config) -> + value(Rest, Handler, Stack, Config); +value(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> + string(Rest, Handler, [singlequote|Stack], Config); value(<> = Rest, Handler, Stack, Config=#config{strict_commas=false}) -> maybe_done(Rest, Handler, Stack, Config); value(<>, Handler, Stack, Config=#config{strict_comments=true}) -> @@ -215,12 +236,18 @@ value(Bin, Handler, Stack, Config) -> object(<>, Handler, Stack, Config) -> string(Rest, Handler, Stack, Config); -object(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> - string(Rest, Handler, [singlequote|Stack], Config); +object(<>, Handler, Stack, Config) -> + object(Rest, Handler, Stack, Config); object(<>, Handler, [key|Stack], Config) -> maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config); -object(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +object(<>, Handler, Stack, Config) -> object(Rest, Handler, Stack, Config); +object(<>, Handler, Stack, Config) -> + object(Rest, Handler, Stack, Config); +object(<>, Handler, Stack, Config) -> + object(Rest, Handler, Stack, Config); +object(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> + string(Rest, Handler, [singlequote|Stack], Config); object(<>, Handler, Stack, Config=#config{strict_comments=true}) -> ?error(object, <>, Handler, Stack, Config); object(<>, Handler, Stack, Config) -> @@ -237,7 +264,13 @@ object(Bin, Handler, Stack, Config) -> array(<>, Handler, [array|Stack], Config) -> maybe_done(Rest, handle_event(end_array, Handler, Config), Stack, Config); -array(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +array(<>, Handler, Stack, Config) -> + array(Rest, Handler, Stack, Config); +array(<>, Handler, Stack, Config) -> + array(Rest, Handler, Stack, Config); +array(<>, Handler, Stack, Config) -> + array(Rest, Handler, Stack, Config); +array(<>, Handler, Stack, Config) -> array(Rest, Handler, Stack, Config); array(<>, Handler, Stack, Config=#config{strict_comments=true}) -> value(<>, Handler, Stack, Config); @@ -255,7 +288,13 @@ array(Bin, Handler, Stack, Config) -> colon(<>, Handler, [key|Stack], Config) -> value(Rest, Handler, [object|Stack], Config); -colon(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +colon(<>, Handler, Stack, Config) -> + colon(Rest, Handler, Stack, Config); +colon(<>, Handler, Stack, Config) -> + colon(Rest, Handler, Stack, Config); +colon(<>, Handler, Stack, Config) -> + colon(Rest, Handler, Stack, Config); +colon(<>, Handler, Stack, Config) -> colon(Rest, Handler, Stack, Config); colon(<>, Handler, Stack, Config=#config{strict_comments=true}) -> ?error(colon, <>, Handler, Stack, Config); @@ -273,12 +312,18 @@ colon(Bin, Handler, Stack, Config) -> key(<>, Handler, Stack, Config) -> string(Rest, Handler, Stack, Config); -key(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> - string(Rest, Handler, [singlequote|Stack], Config); -key(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +key(<>, Handler, Stack, Config) -> key(Rest, Handler, Stack, Config); key(<>, Handler, [key|Stack], Config=#config{strict_commas=false}) -> maybe_done(<>, Handler, [object|Stack], Config); +key(<>, Handler, Stack, Config) -> + key(Rest, Handler, Stack, Config); +key(<>, Handler, Stack, Config) -> + key(Rest, Handler, Stack, Config); +key(<>, Handler, Stack, Config) -> + key(Rest, Handler, Stack, Config); +key(<>, Handler, Stack, Config=#config{strict_single_quotes=false}) -> + string(Rest, Handler, [singlequote|Stack], Config); key(<>, Handler, Stack, Config=#config{strict_comments=true}) -> ?error(key, <>, Handler, Stack, Config); key(<>, Handler, Stack, Config) -> @@ -310,10 +355,17 @@ string(<>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, maybe_replace(?solidus, Config)], Stack, Config); string(<>, Handler, Acc, Stack, Config) -> unescape(Rest, Handler, Acc, Stack, Config); -string(<>, Handler, Acc, Stack, Config=#config{uescape=true}) when X >= 16#80 -> - string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config); -string(<>, Handler, Acc, Stack, Config) when X == 16#2028; X == 16#2029 -> - string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config); +string(<>, Handler, Acc, Stack, Config=#config{uescape=true}) -> + case X of + X when X < 16#80 -> string(Rest, Handler, [Acc, X], Stack, Config); + X -> string(Rest, Handler, [Acc, json_escape_sequence(X)], Stack, Config) + end; +%% u+2028 +string(<<226, 128, 168, Rest/binary>>, Handler, Acc, Stack, Config) -> + string(Rest, Handler, [Acc, maybe_replace(16#2028, Config)], Stack, Config); +%% u+2029 +string(<<226, 128, 169, Rest/binary>>, Handler, Acc, Stack, Config) -> + string(Rest, Handler, [Acc, maybe_replace(16#2029, Config)], Stack, Config); string(<<_/utf8, _/binary>> = Bin, Handler, Acc, Stack, Config) -> Size = count(Bin, 0, Config), <> = Bin, @@ -327,9 +379,9 @@ string(<<239, 191, 190, Rest/binary>>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, <<16#fffe/utf8>>], Stack, Config); string(<<239, 191, 191, Rest/binary>>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, <<16#ffff/utf8>>], Stack, Config); -%% partial utf8 codepoints string(<<>>, Handler, Acc, Stack, Config) -> incomplete(string, <<>>, Handler, Acc, Stack, Config); +%% partial utf8 codepoints string(<>, Handler, Acc, Stack, Config) when X >= 2#11000000 -> incomplete(string, <>, Handler, Acc, Stack, Config); string(<>, Handler, Acc, Stack, Config) when X >= 2#11100000, Y >= 2#10000000 -> @@ -614,13 +666,16 @@ count(<<127, Rest/binary>>, N, Config) -> count(<<_, Rest/binary>>, N, Config=#config{dirty_strings=true}) -> count(Rest, N + 1, Config); count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N; -count(<>, N, Config) when X < 16#800 -> - count(Rest, N + 2, Config); -count(<>, N, _) when X == 16#2028; X == 16#2029 -> N; -count(<>, N, Config) when X < 16#10000 -> - count(Rest, N + 3, Config); -count(<<_/utf8, Rest/binary>>, N, Config) -> - count(Rest, N + 4, Config); +%% u+2028 +count(<<226, 128, 168, _/binary>>, N, _) -> N; +%% u+2029 +count(<<226, 128, 169, _/binary>>, N, _) -> N; +count(<>, N, Config) -> + case X of + X when X < 16#800 -> count(Rest, N + 2, Config); + X when X < 16#10000 -> count(Rest, N + 3, Config); + _ -> count(Rest, N + 4, Config) + end; count(_, N, _) -> N. @@ -666,9 +721,9 @@ strip_continuations(<>, Handler, Acc, Stack, Config, _) -> %% this all gets really gross and should probably eventually be folded into %% but for now it fakes being part of string on incompletes and errors unescape(<>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) -> - string(<>, Handler, [Acc, ?rsolidus], Stack, Config); + string(<>, Handler, [Acc, <>], Stack, Config); unescape(<>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) -> - string(Rest, Handler, [Acc, ?rsolidus, C], Stack, Config); + string(Rest, Handler, [Acc, <>], Stack, Config); unescape(<<$b, Rest/binary>>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, maybe_replace($\b, Config)], Stack, Config); unescape(<<$f, Rest/binary>>, Handler, Acc, Stack, Config) -> @@ -682,7 +737,7 @@ unescape(<<$t, Rest/binary>>, Handler, Acc, Stack, Config) -> unescape(<>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config); unescape(<>, Handler, Acc, Stack, Config=#config{strict_single_quotes=false}) -> - string(Rest, Handler, [Acc, ?singlequote], Stack, Config); + string(Rest, Handler, [Acc, <>], Stack, Config); unescape(<>, Handler, Acc, Stack, Config) -> string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config); unescape(<>, Handler, Acc, Stack, Config) -> @@ -733,7 +788,7 @@ unescape(Bin, Handler, Acc, Stack, Config) -> true -> incomplete(string, <>, Handler, Acc, Stack, Config); false -> case Config#config.strict_escapes of true -> ?error(string, <>, Handler, Acc, Stack, Config); - false -> string(Bin, Handler, [Acc, ?rsolidus], Stack, Config) + false -> string(Bin, Handler, [Acc, <>], Stack, Config) end end. @@ -746,19 +801,19 @@ is_partial_escape(<<>>) -> true; is_partial_escape(_) -> false. -maybe_replace(C, #config{dirty_strings=true}) -> C; -maybe_replace($\b, #config{escaped_strings=true}) -> [$\\, $b]; -maybe_replace($\t, #config{escaped_strings=true}) -> [$\\, $t]; -maybe_replace($\n, #config{escaped_strings=true}) -> [$\\, $n]; -maybe_replace($\f, #config{escaped_strings=true}) -> [$\\, $f]; -maybe_replace($\r, #config{escaped_strings=true}) -> [$\\, $r]; -maybe_replace($\", #config{escaped_strings=true}) -> [$\\, $\"]; +maybe_replace(C, #config{dirty_strings=true}) -> <>; +maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>; +maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>; +maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>; +maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>; +maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>; +maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>; maybe_replace($/, Config=#config{escaped_strings=true}) -> case Config#config.escaped_forward_slashes of - true -> [$\\, $/] - ; false -> $/ + true -> <<$\\, $/>> + ; false -> <<$/>> end; -maybe_replace($\\, #config{escaped_strings=true}) -> [$\\, $\\]; +maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>; maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 -> case Config#config.unescaped_jsonp of true -> <> @@ -766,20 +821,17 @@ maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == end; maybe_replace(X, #config{escaped_strings=true}) when X < 32 -> json_escape_sequence(X); -%% escaped even if no other escaping requested! -maybe_replace(X, #config{uescape=true}) when X >= 16#80 -> - json_escape_sequence(X); maybe_replace(X, _Config) -> <>. %% convert a codepoint to it's \uXXXX equiv. json_escape_sequence(X) when X < 65536 -> <> = <>, - [$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))]; + <<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>; json_escape_sequence(X) -> Adjusted = X - 16#10000, <> = <>, - json_escape_sequence(A + 16#d800) ++ json_escape_sequence(B + 16#dc00). + [json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)]. %% ascii "1" is [49], "2" is [50], etc... @@ -792,93 +844,174 @@ to_hex(15) -> $f; to_hex(X) -> X + 48. -%% like in strings, there's some pseudo states in here that will never -%% show up in errors or incompletes. some show up in value, some show -%% up in integer, decimal or exp -negative(<<$0, Rest/binary>>, Handler, Acc, Stack, Config) -> - zero(Rest, Handler, acc_seq(Acc, $0), Stack, Config); -negative(<>, Handler, Acc, Stack, Config) when ?is_nonzero(S) -> - integer(Rest, Handler, acc_seq(Acc, S), Stack, Config); -negative(<<>>, Handler, [?negative], Stack, Config) -> - incomplete(value, <>, Handler, Stack, Config); -negative(Bin, Handler, Acc, Stack, Config) -> - ?error(value, <>, Handler, Acc, Stack, Config). +number(<<$e, Rest/binary>>, Handler, Acc, [integer|Stack], Config) -> + number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config); +number(<<$E, Rest/binary>>, Handler, Acc, [integer|Stack], Config) -> + number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config); +number(<<$e, Rest/binary>>, Handler, Acc, [zero|Stack], Config) -> + number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config); +number(<<$E, Rest/binary>>, Handler, Acc, [zero|Stack], Config) -> + number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config); +number(<<>>, Handler, Acc, [State|Stack], Config=#config{stream=false}) -> + NumType = case State of + zero -> integer; + integer -> integer; + decimal -> float; + exp -> float + end, + finish_number(<<>>, Handler, {NumType, iolist_to_binary(Acc)}, Stack, Config); +number(<<>>, Handler, Acc, Stack, Config) -> + incomplete(number, <<>>, Handler, Acc, Stack, Config); +number(Bin, Handler, Acc, [State|Stack], Config) -> + Counted = case State of + zero -> zero(Bin, 0); + integer -> integer(Bin, 0); + negative -> negative(Bin, 0); + initialdecimal -> initialdecimal(Bin, 0); + decimal -> decimal(Bin, 0); + e -> e(Bin, 0); + ex -> ex(Bin, 0); + exp -> exp(Bin, 0) + end, + case Counted of + {finish_integer, Size} -> + <> = Bin, + finish_number(Rest, Handler, {integer, iolist_to_binary([Acc, Clean])}, Stack, Config); + {finish_float, Size} -> + <> = Bin, + finish_number(Rest, Handler, {float, iolist_to_binary([Acc, Clean])}, Stack, Config); + {error, Size} -> + <> = Bin, + ?error(number, Rest, Handler, [Acc, Clean], Stack, Config); + {NewState, Size} -> + <> = Bin, + number(Rest, Handler, [Acc, Clean], [NewState|Stack], Config) + end. -zero(<>, Handler, Acc, Stack, Config) -> - decimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config); -zero(<>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E -> - e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config); -zero(Bin, Handler, Acc, Stack, Config) -> - finish_number(Bin, Handler, {zero, Acc}, Stack, Config). +zero(<>, N) -> initialdecimal(Rest, N + 1); +zero(<<>>, N) -> {zero, N}; +zero(_, N) -> {finish_integer, N}. -integer(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - integer(Rest, Handler, acc_seq(Acc, S), Stack, Config); -integer(<>, Handler, Acc, Stack, Config) -> - initialdecimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config); -integer(<>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E -> - e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config); -integer(Bin, Handler, Acc, Stack, Config) -> - finish_number(Bin, Handler, {integer, Acc}, Stack, Config). +integer(<<$0, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1); +integer(<>, N) -> initialdecimal(Rest, N + 1); +integer(<<$e, _/binary>>, N) -> {integer, N}; +integer(<<$E, _/binary>>, N) -> {integer, N}; +integer(<<>>, N) -> {integer, N}; +integer(_, N) -> {finish_integer, N}. -initialdecimal(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config); -initialdecimal(<<>>, Handler, [?decimalpoint|Acc], Stack, Config) -> - incomplete(integer, <>, Handler, Acc, Stack, Config); -initialdecimal(Bin, Handler, Acc, Stack, Config) -> - ?error(decimal, Bin, Handler, Acc, Stack, Config). +negative(<<$0, Rest/binary>>, N) -> zero(Rest, N + 1); +negative(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1); +negative(<<>>, N) -> {negative, N}; +negative(_, N) -> {error, N}. -decimal(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config); -decimal(<>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E -> - e(Rest, Handler, acc_seq(Acc, $e), Stack, Config); -decimal(Bin, Handler, Acc, Stack, Config) -> - finish_number(Bin, Handler, {decimal, Acc}, Stack, Config). +initialdecimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1); +initialdecimal(<<>>, N) -> {initialdecimal, N}; +initialdecimal(_, N) -> {error, N}. -e(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - exp(Rest, Handler, acc_seq(Acc, S), Stack, Config); -e(<>, Handler, Acc, Stack, Config) when Sign =:= ?positive; Sign =:= ?negative -> - ex(Rest, Handler, acc_seq(Acc, Sign), Stack, Config); -e(<<>>, Handler, [$e|Acc], Stack, Config) -> - incomplete(decimal, <<$e>>, Handler, Acc, Stack, Config); -e(Bin, Handler, Acc, Stack, Config) -> - ?error(decimal, <<$e, Bin/binary>>, Handler, Acc, Stack, Config). +decimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1); +decimal(<<$e, Rest/binary>>, N) -> e(Rest, N + 1); +decimal(<<$E, Rest/binary>>, N) -> e(Rest, N + 1); +decimal(<<>>, N) -> {decimal, N}; +decimal(_, N) -> {finish_float, N}. -ex(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - exp(Rest, Handler, acc_seq(Acc, S), Stack, Config); -ex(<<>>, Handler, [S, $e|Acc], Stack, Config) -> - incomplete(decimal, <<$e, S/utf8>>, Handler, Acc, Stack, Config); -ex(Bin, Handler, [S, $e|Acc], Stack, Config) -> - ?error(decimal, <<$e, S, Bin/binary>>, Handler, Acc, Stack, Config). +e(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1); +e(<>, N) -> ex(Rest, N + 1); +e(<>, N) -> ex(Rest, N + 1); +e(<<>>, N) -> {e, N}; +e(_, N) -> {error, N}. -exp(<>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) -> - exp(Rest, Handler, acc_seq(Acc, S), Stack, Config); -exp(Bin, Handler, Acc, Stack, Config) -> - finish_number(Bin, Handler, {exp, Acc}, Stack, Config). +ex(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1); +ex(<<>>, N) -> {ex, N}; +ex(_, N) -> {error, N}. -acc_seq(Seq, C) when is_list(C) -> lists:reverse(C) ++ Seq; -acc_seq(Seq, C) -> [C] ++ Seq. +exp(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1); +exp(<<>>, N) -> {exp, N}; +exp(_, N) -> {finish_float, N}. -finish_number(Rest, Handler, Acc, [], Config=#config{stream=false}) -> - maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), [], Config); -finish_number(<<>>, Handler, {NumType, Acc}, Stack, Config) -> - incomplete(NumType, <<>>, Handler, Acc, Stack, Config); finish_number(Rest, Handler, Acc, Stack, Config) -> maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), Stack, Config). -format_number({zero, Acc}) -> {integer, list_to_integer(lists:reverse(Acc))}; -format_number({integer, Acc}) -> {integer, list_to_integer(lists:reverse(Acc))}; -format_number({decimal, Acc}) -> {float, list_to_float(lists:reverse(Acc))}; -format_number({exp, Acc}) -> {float, list_to_float(lists:reverse(Acc))}. +-ifndef(no_binary_to_whatever). +format_number({integer, Acc}) -> {integer, binary_to_integer(Acc)}; +format_number({float, Acc}) -> {float, binary_to_float(Acc)}. +-endif. + +-ifdef(no_binary_to_whatever). +format_number({integer, Acc}) -> {integer, list_to_integer(unicode:characters_to_list(Acc))}; +format_number({float, Acc}) -> {float, list_to_float(unicode:characters_to_list(Acc))}. +-endif. true(<<$r, $u, $e, Rest/binary>>, Handler, Stack, Config) -> @@ -947,6 +1080,8 @@ comment(Bin, Handler, Resume, Stack, Config) -> maybe_done(<>, Handler, [], Config) -> done(Rest, handle_event(end_json, Handler, Config), [], Config); +maybe_done(<>, Handler, Stack, Config) -> + maybe_done(Rest, Handler, Stack, Config); maybe_done(<>, Handler, [object|Stack], Config) -> maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config); maybe_done(<>, Handler, [array|Stack], Config) -> @@ -955,7 +1090,11 @@ maybe_done(<>, Handler, [object|Stack], Config) -> key(Rest, Handler, [key|Stack], Config); maybe_done(<>, Handler, [array|_] = Stack, Config) -> value(Rest, Handler, Stack, Config); -maybe_done(<>, Handler, Stack, Config) when ?is_whitespace(S) -> +maybe_done(<>, Handler, Stack, Config) -> + maybe_done(Rest, Handler, Stack, Config); +maybe_done(<>, Handler, Stack, Config) -> + maybe_done(Rest, Handler, Stack, Config); +maybe_done(<>, Handler, Stack, Config) -> maybe_done(Rest, Handler, Stack, Config); maybe_done(<>, Handler, Stack, Config=#config{strict_comments=true}) -> ?error(maybe_done, <>, Handler, Stack, Config); @@ -971,7 +1110,13 @@ maybe_done(Bin, Handler, Stack, Config) -> ?error(maybe_done, Bin, Handler, Stack, Config). -done(<>, Handler, [], Config) when ?is_whitespace(S) -> +done(<>, Handler, [], Config) -> + done(Rest, Handler, [], Config); +done(<>, Handler, [], Config) -> + done(Rest, Handler, [], Config); +done(<>, Handler, [], Config) -> + done(Rest, Handler, [], Config); +done(<>, Handler, [], Config) -> done(Rest, Handler, [], Config); done(<>, Handler, Stack, Config=#config{strict_comments=true}) -> ?error(done, <>, Handler, Stack, Config); diff --git a/src/jsx_encoder.erl b/src/jsx_encoder.erl index d39d49a..12b1c67 100644 --- a/src/jsx_encoder.erl +++ b/src/jsx_encoder.erl @@ -23,7 +23,7 @@ -module(jsx_encoder). --export([encoder/3, encode/1, encode/2, unzip/1]). +-export([encoder/3, encode/1, encode/2]). -spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder(). @@ -44,11 +44,10 @@ encode(Term, EntryPoint) -> encode_(Term, EntryPoint). -endif. -ifdef(maps_support). -encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> [start_object, end_object]; +encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 -> + [start_object, end_object]; encode(Term, EntryPoint) when is_map(Term) -> - lists:flatten( - [start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unpack(Term) ] ++ [end_object] - ); + [start_object] ++ unpack(Term, EntryPoint); encode(Term, EntryPoint) -> encode_(Term, EntryPoint). -endif. @@ -56,28 +55,29 @@ encode_([], _EntryPoint) -> [start_array, end_array]; encode_([{}], _EntryPoint) -> [start_object, end_object]; encode_([{_, _}|_] = Term, EntryPoint) -> - lists:flatten( - [start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unzip(Term) ] ++ [end_object] - ); + [start_object] ++ unzip(Term, EntryPoint); encode_(Term, EntryPoint) when is_list(Term) -> - lists:flatten( - [start_array] ++ [ EntryPoint:encode(T, EntryPoint) || T <- Term ] ++ [end_array] - ); + [start_array] ++ unhitch(Term, EntryPoint); encode_(Else, _EntryPoint) -> [Else]. -unzip(List) -> unzip(List, []). +unzip([{K, V}|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) -> + [K] ++ EntryPoint:encode(V, EntryPoint) ++ unzip(Rest, EntryPoint); +unzip([], _) -> [end_object]. + + +unhitch([V|Rest], EntryPoint) -> + EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint); +unhitch([], _) -> [end_array]. -unzip([], Acc) -> lists:reverse(Acc); -unzip([{K, V}|Rest], Acc) when is_binary(K); is_atom(K); is_integer(K) -> unzip(Rest, [V, K] ++ Acc). -ifdef(maps_support). -unpack(Map) -> unpack(maps:keys(Map), Map, []). +unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint). -unpack([], _, Acc) -> lists:reverse(Acc); -unpack([K|Rest], Map, Acc) when is_binary(K); is_atom(K); is_integer(K) -> - unpack(Rest, Map, [maps:get(K, Map), K] ++ Acc). +unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) -> + [K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint); +unpack(_, [], _) -> [end_object]. -endif. diff --git a/src/jsx_parser.erl b/src/jsx_parser.erl index da128ff..7113c71 100644 --- a/src/jsx_parser.erl +++ b/src/jsx_parser.erl @@ -87,36 +87,26 @@ incomplete(State, Handler, Stack, Config=#config{incomplete_handler=F}) -> handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}. -value([start_object|Tokens], Handler, Stack, Config) -> - object(Tokens, handle_event(start_object, Handler, Config), [{object, sets:new()}|Stack], Config); -value([start_array|Tokens], Handler, Stack, Config) -> - array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config); -value([{literal, Literal}|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null -> - maybe_done(Tokens, handle_event({literal, Literal}, Handler, Config), Stack, Config); -value([Literal|Tokens], Handler, Stack, Config) when Literal == true; Literal == false; Literal == null -> - value([{literal, Literal}] ++ Tokens, Handler, Stack, Config); -value([{integer, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) -> - maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config); -value([{float, Number}|Tokens], Handler, Stack, Config) when is_float(Number) -> - maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config); -value([{number, Number}|Tokens], Handler, Stack, Config) when is_integer(Number) -> - value([{integer, Number}] ++ Tokens, Handler, Stack, Config); -value([{number, Number}|Tokens], Handler, Stack, Config) when is_float(Number) -> - value([{float, Number}] ++ Tokens, Handler, Stack, Config); -value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) -> - value([{integer, Number}] ++ Tokens, Handler, Stack, Config); -value([Number|Tokens], Handler, Stack, Config) when is_float(Number) -> - value([{float, Number}] ++ Tokens, Handler, Stack, Config); -value([{string, String}|Tokens], Handler, Stack, Config) when is_binary(String) -> +value([String|Tokens], Handler, Stack, Config) when is_binary(String) -> try clean_string(String, Config) of Clean -> maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config) catch error:badarg -> ?error(value, [{string, String}|Tokens], Handler, Stack, Config) end; -value([String|Tokens], Handler, Stack, Config) when is_binary(String) -> - value([{string, String}] ++ Tokens, Handler, Stack, Config); -value([String|Tokens], Handler, Stack, Config) when is_atom(String) -> - value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config); +value([true|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, true}, Handler, Config), Stack, Config); +value([false|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, false}, Handler, Config), Stack, Config); +value([null|Tokens], Handler, Stack, Config) -> + maybe_done(Tokens, handle_event({literal, null}, Handler, Config), Stack, Config); +value([start_object|Tokens], Handler, Stack, Config) -> + object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config); +value([start_array|Tokens], Handler, Stack, Config) -> + array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config); +value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) -> + maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config); +value([Number|Tokens], Handler, Stack, Config) when is_float(Number) -> + maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config); value([{raw, Raw}|Tokens], Handler, Stack, Config) when is_binary(Raw) -> value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config); value([{{Year, Month, Day}, {Hour, Min, Sec}}|Tokens], Handler, Stack, Config) @@ -129,6 +119,10 @@ when is_integer(Year), is_integer(Month), is_integer(Day), is_integer(Hour), is_ Stack, Config ); +value([{_, Value}|Tokens], Handler, Stack, Config) -> + value([Value] ++ Tokens, Handler, Stack, Config); +value([String|Tokens], Handler, Stack, Config) when is_atom(String) -> + value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config); value([], Handler, Stack, Config) -> incomplete(value, Handler, Stack, Config); value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) -> @@ -136,35 +130,19 @@ value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) -> value(Token, Handler, Stack, Config) -> value([Token], Handler, Stack, Config). -object([end_object|Tokens], Handler, [{object, _}|Stack], Config) -> +object([end_object|Tokens], Handler, [object|Stack], Config) -> maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config); object([{key, Key}|Tokens], Handler, Stack, Config) when is_atom(Key); is_binary(Key); is_integer(Key) -> object([Key|Tokens], Handler, Stack, Config); -object([Key|Tokens], Handler, [{object, _Keys}|Stack], Config=#config{repeat_keys=true}) +object([Key|Tokens], Handler, [object|Stack], Config) when is_atom(Key); is_binary(Key); is_integer(Key) -> try clean_string(fix_key(Key), Config) of K -> value( Tokens, handle_event({key, K}, Handler, Config), - [{object, []}|Stack], - Config - ) - catch error:badarg -> - ?error(object, [{string, Key}|Tokens], Handler, Stack, Config) - end; -object([Key|Tokens], Handler, [{object, Keys}|Stack], Config) -when is_atom(Key); is_binary(Key); is_integer(Key) -> - try - CleanKey = clean_string(fix_key(Key), Config), - case sets:is_element(CleanKey, Keys) of true -> erlang:error(badarg); _ -> ok end, - CleanKey - of K -> - value( - Tokens, - handle_event({key, K}, Handler, Config), - [{object, sets:add_element(K, Keys)}|Stack], + [object|Stack], Config ) catch error:badarg -> @@ -186,7 +164,7 @@ array(Token, Handler, Stack, Config) -> maybe_done([end_json], Handler, [], Config) -> done([end_json], Handler, [], Config); -maybe_done(Tokens, Handler, [{object, _}|_] = Stack, Config) when is_list(Tokens) -> +maybe_done(Tokens, Handler, [object|_] = Stack, Config) when is_list(Tokens) -> object(Tokens, Handler, Stack, Config); maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) -> array(Tokens, Handler, Stack, Config); @@ -219,35 +197,35 @@ clean_string(Bin, Config) -> clean(Bin, [], Config). clean(<<>>, Acc, _) -> iolist_to_binary(Acc); clean(<>, Acc, Config) when X < 16#20 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(X, Config)], Config); clean(<<34, Rest/binary>>, Acc, Config) -> - maybe_replace(34, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(34, Config)], Config); clean(<<47, Rest/binary>>, Acc, Config) -> - maybe_replace(47, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(47, Config)], Config); clean(<<92, Rest/binary>>, Acc, Config) -> - maybe_replace(92, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(92, Config)], Config); clean(<>, Acc, Config=#config{uescape=true}) when X >= 16#80 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, json_escape_sequence(X)], Config); clean(<>, Acc, Config) when X == 16#2028; X == 16#2029 -> - maybe_replace(X, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(X, Config)], Config); clean(<<_/utf8, _/binary>> = Bin, Acc, Config) -> Size = count(Bin, 0, Config), <> = Bin, clean(Rest, [Acc, Clean], Config); %% surrogates clean(<<237, X, _, Rest/binary>>, Acc, Config) when X >= 160 -> - maybe_replace(surrogate, Rest, Acc, Config); + clean(Rest, [Acc, maybe_replace(surrogate, Config)], Config); %% overlong encodings and missing continuations of a 2 byte sequence clean(<>, Acc, Config) when X >= 192, X =< 223 -> - maybe_replace(badutf, strip_continuations(Rest, 1), Acc, Config); + clean(strip_continuations(Rest, 1), [Acc, maybe_replace(badutf, Config)], Config); %% overlong encodings and missing continuations of a 3 byte sequence clean(<>, Acc, Config) when X >= 224, X =< 239 -> - maybe_replace(badutf, strip_continuations(Rest, 2), Acc, Config); + clean(strip_continuations(Rest, 2), [Acc, maybe_replace(badutf, Config)], Config); %% overlong encodings and missing continuations of a 4 byte sequence clean(<>, Acc, Config) when X >= 240, X =< 247 -> - maybe_replace(badutf, strip_continuations(Rest, 3), Acc, Config); + clean(strip_continuations(Rest, 3), [Acc, maybe_replace(badutf, Config)], Config); clean(<<_, Rest/binary>>, Acc, Config) -> - maybe_replace(badutf, Rest, Acc, Config). + clean(Rest, [Acc, maybe_replace(badutf, Config)], Config). count(<<>>, N, _) -> N; @@ -473,13 +451,16 @@ count(<<126, Rest/binary>>, N, Config) -> count(<<127, Rest/binary>>, N, Config) -> count(Rest, N + 1, Config); count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N; -count(<>, N, _) when X == 16#2028; X == 16#2029 -> N; -count(<>, N, Config) when X < 16#800 -> - count(Rest, N + 2, Config); -count(<>, N, Config) when X < 16#10000 -> - count(Rest, N + 3, Config); -count(<<_/utf8, Rest/binary>>, N, Config) -> - count(Rest, N + 4, Config); +%% u+2028 +count(<<226, 128, 168, _/binary>>, N, _) -> N; +%% u+2029 +count(<<226, 128, 169, _/binary>>, N, _) -> N; +count(<>, N, Config) -> + case X of + X when X < 16#800 -> count(Rest, N + 2, Config); + X when X < 16#10000 -> count(Rest, N + 3, Config); + _ -> count(Rest, N + 4, Config) + end; count(<<_, _/binary>>, N, _) -> N. @@ -490,53 +471,43 @@ strip_continuations(<>, N) when X >= 128, X =< 191 -> strip_continuations(Bin, _) -> Bin. -maybe_replace($\b, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $b], Config); -maybe_replace($\t, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $t], Config); -maybe_replace($\n, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $n], Config); -maybe_replace($\f, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $f], Config); -maybe_replace($\r, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $r], Config); -maybe_replace($\", Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $\"], Config); -maybe_replace($/, Rest, Acc, Config=#config{escaped_strings=true}) -> +maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>; +maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>; +maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>; +maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>; +maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>; +maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>; +maybe_replace($/, Config=#config{escaped_strings=true}) -> case Config#config.escaped_forward_slashes of - true -> clean(Rest, [Acc, $\\, $/], Config); - false -> clean(Rest, [Acc, $/], Config) + true -> <<$\\, $/>>; + false -> <<$/>> end; -maybe_replace($\\, Rest, Acc, Config=#config{escaped_strings=true}) -> - clean(Rest, [Acc, $\\, $\\], Config); -maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X < 32 -> - clean(Rest, [Acc, json_escape_sequence(X)], Config); -%% escaped even if no other escaping was requested! -maybe_replace(X, Rest, Acc, Config=#config{uescape=true}) when X >= 16#80 -> - clean(Rest, [Acc, json_escape_sequence(X)], Config); -maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 -> +maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>; +maybe_replace(X, #config{escaped_strings=true}) when X < 32 -> + json_escape_sequence(X); +maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 -> case Config#config.unescaped_jsonp of - true -> clean(Rest, [Acc, <>], Config); - false -> clean(Rest, [Acc, json_escape_sequence(X)], Config) + true -> <>; + false -> json_escape_sequence(X) end; -maybe_replace(Atom, _, _, #config{strict_utf8=true}) when is_atom(Atom) -> +maybe_replace(Atom, #config{strict_utf8=true}) when is_atom(Atom) -> erlang:error(badarg); -maybe_replace(surrogate, Rest, Acc, Config) -> - clean(Rest, [Acc, <<16#fffd/utf8>>], Config); -maybe_replace(badutf, Rest, Acc, Config) -> - clean(Rest, [Acc, <<16#fffd/utf8>>], Config); -maybe_replace(X, Rest, Acc, Config) -> - clean(Rest, [Acc, <>], Config). +maybe_replace(surrogate, _Config) -> + <<16#fffd/utf8>>; +maybe_replace(badutf, _Config) -> + <<16#fffd/utf8>>; +maybe_replace(X, _Config) -> + <>. %% convert a codepoint to it's \uXXXX equiv. json_escape_sequence(X) when X < 65536 -> <> = <>, - [$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))]; + <<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>; json_escape_sequence(X) -> Adjusted = X - 16#10000, <> = <>, - json_escape_sequence(A + 16#d800) ++ json_escape_sequence(B + 16#dc00). + [json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)]. to_hex(10) -> $a; @@ -1052,11 +1023,12 @@ bad_utf8_test_() -> json_escape_sequence_test_() -> [ - {"json escape sequence test - 16#0000", ?_assertEqual(json_escape_sequence(16#0000), "\\u0000")}, - {"json escape sequence test - 16#abc", ?_assertEqual(json_escape_sequence(16#abc), "\\u0abc")}, - {"json escape sequence test - 16#def", ?_assertEqual(json_escape_sequence(16#def), "\\u0def")} + {"json escape sequence test - 16#0000", ?_assertEqual(<<"\\u0000"/utf8>>, json_escape_sequence(16#0000))}, + {"json escape sequence test - 16#abc", ?_assertEqual(<<"\\u0abc"/utf8>>, json_escape_sequence(16#abc))}, + {"json escape sequence test - 16#def", ?_assertEqual(<<"\\u0def"/utf8>>, json_escape_sequence(16#def))} ]. + uescape_test_() -> [ {"\"\\u0080\"", ?_assertEqual( @@ -1080,6 +1052,7 @@ uescape_test_() -> )} ]. + fix_key_test_() -> [ {"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)}, @@ -1088,16 +1061,6 @@ fix_key_test_() -> ]. -repeated_key_test_() -> - Parse = fun(Events, Config) -> (parser(?MODULE, [], Config))(Events ++ [end_json]) end, - [ - {"repeated key", ?_assertError( - badarg, - Parse([start_object, <<"key">>, true, <<"key">>, true, end_object], []) - )} - ]. - - datetime_test_() -> [ {"datetime", ?_assertEqual( diff --git a/src/jsx_to_term.erl b/src/jsx_to_term.erl index 25cb227..b23450c 100644 --- a/src/jsx_to_term.erl +++ b/src/jsx_to_term.erl @@ -23,10 +23,17 @@ -module(jsx_to_term). --export([to_term/2]). +-export([to_term/2, flatify/1]). -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). +%% return it if it is the root object +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,49 @@ 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; @@ -185,6 +242,15 @@ get_value({Value, _Config}) -> Value; get_value(_) -> erlang:error(badarg). +%% we know the structure of our accumulator so we can safely +%% flatten like this +flatify(List) -> flatify(List, []). +%% head of list should always be [] +flatify([], Tail) -> Tail; +flatify([H, T], Tail) -> flatify(H, [T] ++ Tail). + + + %% eunit tests -ifdef(TEST). @@ -235,10 +301,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 +345,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 +367,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 +439,6 @@ return_maps_test_() -> jsx:decode(<<"[{}]">>, [return_maps]) )} ]. - -endif. diff --git a/src/jsx_verify.erl b/src/jsx_verify.erl index 09a668d..2073b99 100644 --- a/src/jsx_verify.erl +++ b/src/jsx_verify.erl @@ -27,15 +27,7 @@ -export([init/1, handle_event/2]). --record(config, { - repeated_keys = true -}). - --type config() :: []. --export_type([config/0]). - - --spec is_json(Source::binary(), Config::config()) -> true | false | {incomplete, jsx:decoder()}. +-spec is_json(Source::binary(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:decoder()}. is_json(Source, Config) when is_list(Config) -> try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source) @@ -43,7 +35,7 @@ is_json(Source, Config) when is_list(Config) -> end. --spec is_term(Source::any(), Config::config()) -> true | false | {incomplete, jsx:encoder()}. +-spec is_term(Source::any(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:encoder()}. is_term(Source, Config) when is_list(Config) -> try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source) @@ -51,15 +43,15 @@ is_term(Source, Config) when is_list(Config) -> end. -parse_config(Config) -> parse_config(Config, #config{}). +parse_config(Config) -> parse_config(Config, []). +%% ignore deprecated flags parse_config([no_repeated_keys|Rest], Config) -> - parse_config(Rest, Config#config{repeated_keys=false}); -%% deprecated, use `no_repeated_keys` + parse_config(Rest, Config); parse_config([{repeated_keys, Val}|Rest], Config) when Val == true; Val == false -> - parse_config(Rest, Config#config{repeated_keys=Val}); + parse_config(Rest, Config); parse_config([repeated_keys|Rest], Config) -> - parse_config(Rest, Config#config{repeated_keys=true}); + parse_config(Rest, Config); parse_config([{K, _}|Rest] = Options, Config) -> case lists:member(K, jsx_config:valid_flags()) of true -> parse_config(Rest, Config); @@ -73,27 +65,18 @@ parse_config([K|Rest] = Options, Config) -> parse_config([], Config) -> Config. --type state() :: {#config{}, any()}. + +%% we don't actually need any state for this +-type state() :: []. -spec init(Config::proplists:proplist()) -> state(). -init(Config) -> {parse_config(Config), []}. +init(Config) -> parse_config(Config). -spec handle_event(Event::any(), State::state()) -> state(). handle_event(end_json, _) -> true; -handle_event(_, {Config, _} = State) when Config#config.repeated_keys == true -> State; - -handle_event(start_object, {Config, Keys}) -> {Config, [dict:new()] ++ Keys}; -handle_event(end_object, {Config, [_|Keys]}) -> {Config, Keys}; - -handle_event({key, Key}, {Config, [CurrentKeys|Keys]}) -> - case dict:is_key(Key, CurrentKeys) of - true -> erlang:error(badarg); - false -> {Config, [dict:store(Key, blah, CurrentKeys)|Keys]} - end; - handle_event(_, State) -> State. @@ -105,15 +88,15 @@ handle_event(_, State) -> State. config_test_() -> [ - {"empty config", ?_assertEqual(#config{}, parse_config([]))}, - {"no repeat keys", ?_assertEqual(#config{repeated_keys=false}, parse_config([no_repeated_keys]))}, - {"bare repeated keys", ?_assertEqual(#config{}, parse_config([repeated_keys]))}, + {"empty config", ?_assertEqual([], parse_config([]))}, + {"no repeat keys", ?_assertEqual([], parse_config([no_repeated_keys]))}, + {"bare repeated keys", ?_assertEqual([], parse_config([repeated_keys]))}, {"repeated keys true", ?_assertEqual( - #config{}, + [], parse_config([{repeated_keys, true}]) )}, {"repeated keys false", ?_assertEqual( - #config{repeated_keys=false}, + [], parse_config([{repeated_keys, false}]) )}, {"invalid opt flag", ?_assertError(badarg, parse_config([error]))}, @@ -121,50 +104,13 @@ config_test_() -> ]. -repeated_keys_test_() -> - RepeatedKey = [ - start_object, - {key, <<"alpha">>}, - {literal, true}, - {key, <<"alpha">>}, - {literal, false}, - end_object, - end_json - ], - NestedKey = [ - start_object, - {key, <<"alpha">>}, - start_object, - {key, <<"alpha">>}, - start_object, - {key, <<"alpha">>}, - {literal, true}, - end_object, - end_object, - end_object, - end_json - ], - [ - {"repeated key", ?_assert( - lists:foldl(fun handle_event/2, {#config{}, []}, RepeatedKey) - )}, - {"no repeated key", ?_assertError( - badarg, - lists:foldl(fun handle_event/2, {#config{repeated_keys=false}, []}, RepeatedKey) - )}, - {"nested key", ?_assert( - lists:foldl(fun handle_event/2, {#config{repeated_keys=false}, []}, NestedKey) - )} - ]. - - handle_event_test_() -> Data = jsx:test_cases() ++ jsx:special_test_cases(), [ { Title, ?_assertEqual( true, - lists:foldl(fun handle_event/2, {#config{}, []}, Events ++ [end_json]) + lists:foldl(fun handle_event/2, [], Events ++ [end_json]) ) } || {Title, _, _, Events} <- Data ].