v2.4.0
This commit is contained in:
commit
e751e3324f
14 changed files with 547 additions and 428 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
maps:keys(#{0 => false, 1 => true}) == [0,1].
|
30
package.exs
30
package.exs
|
@ -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
|
|
@ -1,2 +1,4 @@
|
|||
% uncomment to disable encoding support for erlang maps
|
||||
% {jsx_nomaps, true}.
|
||||
{erl_opts, [
|
||||
{platform_define, "R14|R15", 'no_binary_to_whatever'},
|
||||
{platform_define, "^((?!R1[456]).)*$", 'maps_support'}
|
||||
]}.
|
|
@ -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.
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||
string(Rest, Handler, Stack, Config);
|
||||
value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
value(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
value(Rest, Handler, Stack, Config);
|
||||
value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config);
|
||||
value(<<?start_array, Rest/binary>>, 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(<<?zero, Rest/binary>>, 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(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
|
||||
number(Rest, Handler, [$-], [negative|Stack], Config);
|
||||
value(<<?newline, Rest/binary>>, 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(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
|
||||
negative(Rest, Handler, [$-], Stack, Config);
|
||||
value(<<?zero, Rest/binary>>, Handler, Stack, Config) ->
|
||||
zero(Rest, Handler, [$0], Stack, Config);
|
||||
value(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_nonzero(S) ->
|
||||
integer(Rest, Handler, [S], Stack, Config);
|
||||
value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config);
|
||||
value(<<?start_array, Rest/binary>>, Handler, Stack, Config) ->
|
||||
array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config);
|
||||
value(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
value(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
value(Rest, Handler, Stack, Config);
|
||||
value(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
value(Rest, Handler, Stack, Config);
|
||||
value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
value(<<?end_array, _/binary>> = Rest, Handler, Stack, Config=#config{strict_commas=false}) ->
|
||||
maybe_done(Rest, Handler, Stack, Config);
|
||||
value(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
|
@ -215,12 +236,18 @@ value(Bin, Handler, Stack, Config) ->
|
|||
|
||||
object(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||
string(Rest, Handler, Stack, Config);
|
||||
object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
object(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, Handler, Stack, Config);
|
||||
object(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config) ->
|
||||
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
|
||||
object(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
object(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, Handler, Stack, Config);
|
||||
object(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, Handler, Stack, Config);
|
||||
object(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
object(Rest, Handler, Stack, Config);
|
||||
object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
object(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
?error(object, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
object(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
|
||||
|
@ -237,7 +264,13 @@ object(Bin, Handler, Stack, Config) ->
|
|||
|
||||
array(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
|
||||
maybe_done(Rest, handle_event(end_array, Handler, Config), Stack, Config);
|
||||
array(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
array(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
array(Rest, Handler, Stack, Config);
|
||||
array(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
|
||||
array(Rest, Handler, Stack, Config);
|
||||
array(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
array(Rest, Handler, Stack, Config);
|
||||
array(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
array(Rest, Handler, Stack, Config);
|
||||
array(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
value(<<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
|
@ -255,7 +288,13 @@ array(Bin, Handler, Stack, Config) ->
|
|||
|
||||
colon(<<?colon, Rest/binary>>, Handler, [key|Stack], Config) ->
|
||||
value(Rest, Handler, [object|Stack], Config);
|
||||
colon(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
colon(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
colon(Rest, Handler, Stack, Config);
|
||||
colon(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
|
||||
colon(Rest, Handler, Stack, Config);
|
||||
colon(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
colon(Rest, Handler, Stack, Config);
|
||||
colon(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
colon(Rest, Handler, Stack, Config);
|
||||
colon(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
?error(colon, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
|
@ -273,12 +312,18 @@ colon(Bin, Handler, Stack, Config) ->
|
|||
|
||||
key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||
string(Rest, Handler, Stack, Config);
|
||||
key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
key(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
key(Rest, Handler, Stack, Config);
|
||||
key(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config=#config{strict_commas=false}) ->
|
||||
maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config);
|
||||
key(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
|
||||
key(Rest, Handler, Stack, Config);
|
||||
key(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
key(Rest, Handler, Stack, Config);
|
||||
key(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
key(Rest, Handler, Stack, Config);
|
||||
key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [singlequote|Stack], Config);
|
||||
key(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
key(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
|
||||
|
@ -310,10 +355,17 @@ string(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
|||
string(Rest, Handler, [Acc, maybe_replace(?solidus, Config)], Stack, Config);
|
||||
string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
unescape(Rest, Handler, Acc, Stack, Config);
|
||||
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config=#config{uescape=true}) when X >= 16#80 ->
|
||||
string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config);
|
||||
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X == 16#2028; X == 16#2029 ->
|
||||
string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config);
|
||||
string(<<X/utf8, Rest/binary>>, 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),
|
||||
<<Clean:Size/binary, Rest/binary>> = 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(<<X>>, Handler, Acc, Stack, Config) when X >= 2#11000000 ->
|
||||
incomplete(string, <<X>>, Handler, Acc, Stack, Config);
|
||||
string(<<X, Y>>, 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(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
|
||||
count(Rest, N + 2, Config);
|
||||
count(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
|
||||
count(<<X/utf8, Rest/binary>>, 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(<<X/utf8, Rest/binary>>, 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(<<Rest/binary>>, 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(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
|
||||
string(<<?rsolidus, Rest/binary>>, Handler, [Acc, ?rsolidus], Stack, Config);
|
||||
string(<<?rsolidus, Rest/binary>>, Handler, [Acc, <<?rsolidus>>], Stack, Config);
|
||||
unescape(<<C, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
|
||||
string(Rest, Handler, [Acc, ?rsolidus, C], Stack, Config);
|
||||
string(Rest, Handler, [Acc, <<?rsolidus, C>>], 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(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config);
|
||||
unescape(<<?singlequote, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_single_quotes=false}) ->
|
||||
string(Rest, Handler, [Acc, ?singlequote], Stack, Config);
|
||||
string(Rest, Handler, [Acc, <<?singlequote>>], Stack, Config);
|
||||
unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config);
|
||||
unescape(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
|
@ -733,7 +788,7 @@ unescape(Bin, Handler, Acc, Stack, Config) ->
|
|||
true -> incomplete(string, <<?rsolidus/utf8, Bin/binary>>, Handler, Acc, Stack, Config);
|
||||
false -> case Config#config.strict_escapes of
|
||||
true -> ?error(string, <<?rsolidus, Bin/binary>>, Handler, Acc, Stack, Config);
|
||||
false -> string(Bin, Handler, [Acc, ?rsolidus], Stack, Config)
|
||||
false -> string(Bin, Handler, [Acc, <<?rsolidus>>], 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}) -> <<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($/, 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 -> <<X/utf8>>
|
||||
|
@ -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) -> <<X/utf8>>.
|
||||
|
||||
|
||||
%% convert a codepoint to it's \uXXXX equiv.
|
||||
json_escape_sequence(X) when X < 65536 ->
|
||||
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
|
||||
[$\\, $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,
|
||||
<<A:10, B:10>> = <<Adjusted:20>>,
|
||||
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(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when ?is_nonzero(S) ->
|
||||
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
||||
negative(<<>>, Handler, [?negative], Stack, Config) ->
|
||||
incomplete(value, <<?negative>>, Handler, Stack, Config);
|
||||
negative(Bin, Handler, Acc, Stack, Config) ->
|
||||
?error(value, <<?negative, Bin/binary>>, 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} ->
|
||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
||||
finish_number(Rest, Handler, {integer, iolist_to_binary([Acc, Clean])}, Stack, Config);
|
||||
{finish_float, Size} ->
|
||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
||||
finish_number(Rest, Handler, {float, iolist_to_binary([Acc, Clean])}, Stack, Config);
|
||||
{error, Size} ->
|
||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
||||
?error(number, Rest, Handler, [Acc, Clean], Stack, Config);
|
||||
{NewState, Size} ->
|
||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
||||
number(Rest, Handler, [Acc, Clean], [NewState|Stack], Config)
|
||||
end.
|
||||
|
||||
|
||||
zero(<<?decimalpoint, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
decimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
|
||||
zero(<<S, Rest/binary>>, 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(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
|
||||
zero(<<>>, N) -> {zero, N};
|
||||
zero(_, N) -> {finish_integer, N}.
|
||||
|
||||
|
||||
integer(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
|
||||
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
||||
integer(<<?decimalpoint, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||
initialdecimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
|
||||
integer(<<S, Rest/binary>>, 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(<<?decimalpoint, Rest/binary>>, 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(<<S, Rest/binary>>, 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, <<?decimalpoint>>, 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(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
|
||||
decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
||||
decimal(<<S, Rest/binary>>, 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(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
|
||||
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
||||
e(<<Sign, Rest/binary>>, 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(<<S, Rest/binary>>, 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(<<?positive, Rest/binary>>, N) -> ex(Rest, N + 1);
|
||||
e(<<?negative, Rest/binary>>, N) -> ex(Rest, N + 1);
|
||||
e(<<>>, N) -> {e, N};
|
||||
e(_, N) -> {error, N}.
|
||||
|
||||
|
||||
exp(<<S, Rest/binary>>, 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(<<Rest/binary>>, Handler, [], Config) ->
|
||||
done(Rest, handle_event(end_json, Handler, Config), [], Config);
|
||||
maybe_done(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||
maybe_done(Rest, Handler, Stack, Config);
|
||||
maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config) ->
|
||||
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
|
||||
maybe_done(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
|
||||
|
@ -955,7 +1090,11 @@ maybe_done(<<?comma, Rest/binary>>, Handler, [object|Stack], Config) ->
|
|||
key(Rest, Handler, [key|Stack], Config);
|
||||
maybe_done(<<?comma, Rest/binary>>, Handler, [array|_] = Stack, Config) ->
|
||||
value(Rest, Handler, Stack, Config);
|
||||
maybe_done(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
||||
maybe_done(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
|
||||
maybe_done(Rest, Handler, Stack, Config);
|
||||
maybe_done(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
|
||||
maybe_done(Rest, Handler, Stack, Config);
|
||||
maybe_done(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
|
||||
maybe_done(Rest, Handler, Stack, Config);
|
||||
maybe_done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
?error(maybe_done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
|
@ -971,7 +1110,13 @@ maybe_done(Bin, Handler, Stack, Config) ->
|
|||
?error(maybe_done, Bin, Handler, Stack, Config).
|
||||
|
||||
|
||||
done(<<S, Rest/binary>>, Handler, [], Config) when ?is_whitespace(S) ->
|
||||
done(<<?space, Rest/binary>>, Handler, [], Config) ->
|
||||
done(Rest, Handler, [], Config);
|
||||
done(<<?newline, Rest/binary>>, Handler, [], Config) ->
|
||||
done(Rest, Handler, [], Config);
|
||||
done(<<?tab, Rest/binary>>, Handler, [], Config) ->
|
||||
done(Rest, Handler, [], Config);
|
||||
done(<<?cr, Rest/binary>>, Handler, [], Config) ->
|
||||
done(Rest, Handler, [], Config);
|
||||
done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||
?error(done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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(<<X/utf8, Rest/binary>>, 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(<<X/utf8, Rest/binary>>, Acc, Config=#config{uescape=true}) when X >= 16#80 ->
|
||||
maybe_replace(X, Rest, Acc, Config);
|
||||
clean(Rest, [Acc, json_escape_sequence(X)], Config);
|
||||
clean(<<X/utf8, Rest/binary>>, 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),
|
||||
<<Clean:Size/binary, Rest/binary>> = 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(<<X, Rest/binary>>, 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(<<X, Rest/binary>>, 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(<<X, Rest/binary>>, 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(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
|
||||
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
|
||||
count(Rest, N + 2, Config);
|
||||
count(<<X/utf8, Rest/binary>>, 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(<<X/utf8, Rest/binary>>, 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(<<X, Rest/binary>>, 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, <<X/utf8>>], Config);
|
||||
false -> clean(Rest, [Acc, json_escape_sequence(X)], Config)
|
||||
true -> <<X/utf8>>;
|
||||
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, <<X/utf8>>], Config).
|
||||
maybe_replace(surrogate, _Config) ->
|
||||
<<16#fffd/utf8>>;
|
||||
maybe_replace(badutf, _Config) ->
|
||||
<<16#fffd/utf8>>;
|
||||
maybe_replace(X, _Config) ->
|
||||
<<X/utf8>>.
|
||||
|
||||
|
||||
%% convert a codepoint to it's \uXXXX equiv.
|
||||
json_escape_sequence(X) when X < 65536 ->
|
||||
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
|
||||
[$\\, $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,
|
||||
<<A:10, B:10>> = <<Adjusted:20>>,
|
||||
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(
|
||||
|
|
|
@ -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).
|
||||
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.
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
].
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue