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
|
v2.3.1
|
||||||
|
|
||||||
* fixes an issue where astral plane json escape sequences were
|
* 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].
|
an erlang application for consuming, producing and manipulating [json][json].
|
||||||
inspired by [yajl][yajl]
|
inspired by [yajl][yajl]
|
||||||
|
@ -387,10 +387,6 @@ additional options beyond these. see
|
||||||
control codes and problematic codepoints and replacing them with the
|
control codes and problematic codepoints and replacing them with the
|
||||||
appropriate escapes
|
appropriate escapes
|
||||||
|
|
||||||
- `repeat_keys`
|
|
||||||
|
|
||||||
this flag circumvents checking for repeated keys in generated json
|
|
||||||
|
|
||||||
- `stream`
|
- `stream`
|
||||||
|
|
||||||
see [incomplete input](#incomplete-input)
|
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
|
{erl_opts, [
|
||||||
% {jsx_nomaps, true}.
|
{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,
|
{application, jsx,
|
||||||
[
|
[
|
||||||
{description, "a streaming, evented json parsing toolkit"},
|
{description, "a streaming, evented json parsing toolkit"},
|
||||||
{vsn, "2.3.1"},
|
{vsn, "2.4.0"},
|
||||||
{modules, [
|
{modules, [
|
||||||
jsx,
|
jsx,
|
||||||
jsx_encoder,
|
jsx_encoder,
|
||||||
|
|
|
@ -63,12 +63,14 @@ parse_config([unescaped_jsonp|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{unescaped_jsonp=true});
|
parse_config(Rest, Config#config{unescaped_jsonp=true});
|
||||||
parse_config([dirty_strings|Rest], Config) ->
|
parse_config([dirty_strings|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{dirty_strings=true});
|
parse_config(Rest, Config#config{dirty_strings=true});
|
||||||
|
%% retained for backwards compat, now does nothing however
|
||||||
parse_config([repeat_keys|Rest], Config) ->
|
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([uescape|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{uescape=true});
|
parse_config(Rest, Config#config{uescape=true});
|
||||||
parse_config([strict|Rest], Config) ->
|
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_commas=true,
|
||||||
strict_utf8=true,
|
strict_utf8=true,
|
||||||
strict_single_quotes=true,
|
strict_single_quotes=true,
|
||||||
|
@ -190,7 +192,6 @@ config_test_() ->
|
||||||
escaped_strings = true,
|
escaped_strings = true,
|
||||||
unescaped_jsonp = true,
|
unescaped_jsonp = true,
|
||||||
dirty_strings = true,
|
dirty_strings = true,
|
||||||
repeat_keys = true,
|
|
||||||
strict_comments = true,
|
strict_comments = true,
|
||||||
strict_commas = true,
|
strict_commas = true,
|
||||||
strict_utf8 = true,
|
strict_utf8 = true,
|
||||||
|
@ -274,7 +275,6 @@ config_to_list_test_() ->
|
||||||
escaped_strings,
|
escaped_strings,
|
||||||
unescaped_jsonp,
|
unescaped_jsonp,
|
||||||
dirty_strings,
|
dirty_strings,
|
||||||
repeat_keys,
|
|
||||||
stream,
|
stream,
|
||||||
uescape,
|
uescape,
|
||||||
strict
|
strict
|
||||||
|
@ -284,7 +284,6 @@ config_to_list_test_() ->
|
||||||
escaped_strings = true,
|
escaped_strings = true,
|
||||||
unescaped_jsonp = true,
|
unescaped_jsonp = true,
|
||||||
dirty_strings = true,
|
dirty_strings = true,
|
||||||
repeat_keys = true,
|
|
||||||
strict_comments = true,
|
strict_comments = true,
|
||||||
strict_utf8 = true,
|
strict_utf8 = true,
|
||||||
strict_single_quotes = true,
|
strict_single_quotes = true,
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
escaped_strings = false :: boolean(),
|
escaped_strings = false :: boolean(),
|
||||||
unescaped_jsonp = false :: boolean(),
|
unescaped_jsonp = false :: boolean(),
|
||||||
dirty_strings = false :: boolean(),
|
dirty_strings = false :: boolean(),
|
||||||
repeat_keys = false :: boolean(),
|
|
||||||
strict_comments = false :: boolean(),
|
strict_comments = false :: boolean(),
|
||||||
strict_commas = false :: boolean(),
|
strict_commas = false :: boolean(),
|
||||||
strict_utf8 = false :: boolean(),
|
strict_utf8 = false :: boolean(),
|
||||||
|
|
|
@ -58,10 +58,7 @@ resume(Rest, State, Handler, Acc, Stack, Config) ->
|
||||||
colon -> colon(Rest, Handler, Stack, Config);
|
colon -> colon(Rest, Handler, Stack, Config);
|
||||||
key -> key(Rest, Handler, Stack, Config);
|
key -> key(Rest, Handler, Stack, Config);
|
||||||
string -> string(Rest, Handler, Acc, Stack, Config);
|
string -> string(Rest, Handler, Acc, Stack, Config);
|
||||||
integer -> integer(Rest, Handler, Acc, Stack, Config);
|
number -> number(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);
|
|
||||||
true -> true(Rest, Handler, Stack, Config);
|
true -> true(Rest, Handler, Stack, Config);
|
||||||
false -> false(Rest, Handler, Stack, Config);
|
false -> false(Rest, Handler, Stack, Config);
|
||||||
null -> null(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
|
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
|
%% error is a macro so the stack trace shows the error site when possible
|
||||||
-ifndef(error).
|
-ifndef(error).
|
||||||
|
@ -177,26 +170,54 @@ start(Bin, Handler, Stack, Config) ->
|
||||||
|
|
||||||
value(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
value(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, Stack, Config);
|
string(Rest, Handler, Stack, Config);
|
||||||
value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
value(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, [singlequote|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) ->
|
value(<<$t, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
true(Rest, Handler, Stack, Config);
|
true(Rest, Handler, Stack, Config);
|
||||||
value(<<$f, Rest/binary>>, Handler, Stack, Config) ->
|
value(<<$f, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
false(Rest, Handler, Stack, Config);
|
false(Rest, Handler, Stack, Config);
|
||||||
value(<<$n, Rest/binary>>, Handler, Stack, Config) ->
|
value(<<$n, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
null(Rest, Handler, Stack, Config);
|
null(Rest, Handler, Stack, Config);
|
||||||
value(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
|
value(<<?tab, 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(Rest, 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}) ->
|
value(<<?end_array, _/binary>> = Rest, Handler, Stack, Config=#config{strict_commas=false}) ->
|
||||||
maybe_done(Rest, Handler, Stack, Config);
|
maybe_done(Rest, Handler, Stack, Config);
|
||||||
value(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
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) ->
|
object(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, Stack, Config);
|
string(Rest, Handler, Stack, Config);
|
||||||
object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
object(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, [singlequote|Stack], Config);
|
object(Rest, Handler, Stack, Config);
|
||||||
object(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config) ->
|
object(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config) ->
|
||||||
maybe_done(Rest, handle_event(end_object, Handler, Config), 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(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}) ->
|
object(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
?error(object, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
?error(object, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||||
object(<<?solidus, ?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) ->
|
array(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
|
||||||
maybe_done(Rest, handle_event(end_array, Handler, Config), 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(Rest, Handler, Stack, Config);
|
||||||
array(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
array(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
value(<<?solidus, Rest/binary>>, Handler, Stack, Config);
|
value(<<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||||
|
@ -255,7 +288,13 @@ array(Bin, Handler, Stack, Config) ->
|
||||||
|
|
||||||
colon(<<?colon, Rest/binary>>, Handler, [key|Stack], Config) ->
|
colon(<<?colon, Rest/binary>>, Handler, [key|Stack], Config) ->
|
||||||
value(Rest, Handler, [object|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(Rest, Handler, Stack, Config);
|
||||||
colon(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
colon(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
?error(colon, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
?error(colon, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||||
|
@ -273,12 +312,18 @@ colon(Bin, Handler, Stack, Config) ->
|
||||||
|
|
||||||
key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, Stack, Config);
|
string(Rest, Handler, Stack, Config);
|
||||||
key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
|
key(<<?space, Rest/binary>>, Handler, Stack, Config) ->
|
||||||
string(Rest, Handler, [singlequote|Stack], Config);
|
|
||||||
key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
|
|
||||||
key(Rest, Handler, Stack, Config);
|
key(Rest, Handler, Stack, Config);
|
||||||
key(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config=#config{strict_commas=false}) ->
|
key(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config=#config{strict_commas=false}) ->
|
||||||
maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config);
|
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}) ->
|
key(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||||
key(<<?solidus, ?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(Rest, Handler, [Acc, maybe_replace(?solidus, Config)], Stack, Config);
|
||||||
string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||||
unescape(Rest, 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(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config=#config{uescape=true}) ->
|
||||||
string(Rest, Handler, [Acc, maybe_replace(X, Config)], Stack, Config);
|
case X of
|
||||||
string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X == 16#2028; X == 16#2029 ->
|
X when X < 16#80 -> string(Rest, Handler, [Acc, X], Stack, Config);
|
||||||
string(Rest, Handler, [Acc, maybe_replace(X, Config)], 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) ->
|
string(<<_/utf8, _/binary>> = Bin, Handler, Acc, Stack, Config) ->
|
||||||
Size = count(Bin, 0, Config),
|
Size = count(Bin, 0, Config),
|
||||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
<<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(Rest, Handler, [Acc, <<16#fffe/utf8>>], Stack, Config);
|
||||||
string(<<239, 191, 191, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
string(<<239, 191, 191, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||||
string(Rest, Handler, [Acc, <<16#ffff/utf8>>], Stack, Config);
|
string(Rest, Handler, [Acc, <<16#ffff/utf8>>], Stack, Config);
|
||||||
%% partial utf8 codepoints
|
|
||||||
string(<<>>, Handler, Acc, Stack, Config) ->
|
string(<<>>, Handler, Acc, Stack, Config) ->
|
||||||
incomplete(string, <<>>, Handler, Acc, Stack, Config);
|
incomplete(string, <<>>, Handler, Acc, Stack, Config);
|
||||||
|
%% partial utf8 codepoints
|
||||||
string(<<X>>, Handler, Acc, Stack, Config) when X >= 2#11000000 ->
|
string(<<X>>, Handler, Acc, Stack, Config) when X >= 2#11000000 ->
|
||||||
incomplete(string, <<X>>, Handler, Acc, Stack, Config);
|
incomplete(string, <<X>>, Handler, Acc, Stack, Config);
|
||||||
string(<<X, Y>>, Handler, Acc, Stack, Config) when X >= 2#11100000, Y >= 2#10000000 ->
|
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/binary>>, N, Config=#config{dirty_strings=true}) ->
|
||||||
count(Rest, N + 1, Config);
|
count(Rest, N + 1, Config);
|
||||||
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
|
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
|
||||||
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
|
%% u+2028
|
||||||
count(Rest, N + 2, Config);
|
count(<<226, 128, 168, _/binary>>, N, _) -> N;
|
||||||
count(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
|
%% u+2029
|
||||||
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#10000 ->
|
count(<<226, 128, 169, _/binary>>, N, _) -> N;
|
||||||
count(Rest, N + 3, Config);
|
count(<<X/utf8, Rest/binary>>, N, Config) ->
|
||||||
count(<<_/utf8, Rest/binary>>, N, Config) ->
|
case X of
|
||||||
count(Rest, N + 4, Config);
|
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.
|
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
|
%% 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
|
%% 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}) ->
|
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}) ->
|
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) ->
|
unescape(<<$b, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||||
string(Rest, Handler, [Acc, maybe_replace($\b, Config)], Stack, Config);
|
string(Rest, Handler, [Acc, maybe_replace($\b, Config)], Stack, Config);
|
||||||
unescape(<<$f, Rest/binary>>, Handler, Acc, 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) ->
|
unescape(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||||
string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config);
|
string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config);
|
||||||
unescape(<<?singlequote, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_single_quotes=false}) ->
|
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) ->
|
unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
||||||
string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config);
|
string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config);
|
||||||
unescape(<<?solidus, Rest/binary>>, Handler, Acc, 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);
|
true -> incomplete(string, <<?rsolidus/utf8, Bin/binary>>, Handler, Acc, Stack, Config);
|
||||||
false -> case Config#config.strict_escapes of
|
false -> case Config#config.strict_escapes of
|
||||||
true -> ?error(string, <<?rsolidus, Bin/binary>>, Handler, Acc, Stack, Config);
|
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
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -746,19 +801,19 @@ is_partial_escape(<<>>) -> true;
|
||||||
is_partial_escape(_) -> false.
|
is_partial_escape(_) -> false.
|
||||||
|
|
||||||
|
|
||||||
maybe_replace(C, #config{dirty_strings=true}) -> C;
|
maybe_replace(C, #config{dirty_strings=true}) -> <<C>>;
|
||||||
maybe_replace($\b, #config{escaped_strings=true}) -> [$\\, $b];
|
maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
|
||||||
maybe_replace($\t, #config{escaped_strings=true}) -> [$\\, $t];
|
maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
|
||||||
maybe_replace($\n, #config{escaped_strings=true}) -> [$\\, $n];
|
maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
|
||||||
maybe_replace($\f, #config{escaped_strings=true}) -> [$\\, $f];
|
maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
|
||||||
maybe_replace($\r, #config{escaped_strings=true}) -> [$\\, $r];
|
maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
|
||||||
maybe_replace($\", #config{escaped_strings=true}) -> [$\\, $\"];
|
maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
|
||||||
maybe_replace($/, Config=#config{escaped_strings=true}) ->
|
maybe_replace($/, Config=#config{escaped_strings=true}) ->
|
||||||
case Config#config.escaped_forward_slashes of
|
case Config#config.escaped_forward_slashes of
|
||||||
true -> [$\\, $/]
|
true -> <<$\\, $/>>
|
||||||
; false -> $/
|
; false -> <<$/>>
|
||||||
end;
|
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 ->
|
maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
|
||||||
case Config#config.unescaped_jsonp of
|
case Config#config.unescaped_jsonp of
|
||||||
true -> <<X/utf8>>
|
true -> <<X/utf8>>
|
||||||
|
@ -766,20 +821,17 @@ maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X ==
|
||||||
end;
|
end;
|
||||||
maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
|
maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
|
||||||
json_escape_sequence(X);
|
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>>.
|
maybe_replace(X, _Config) -> <<X/utf8>>.
|
||||||
|
|
||||||
|
|
||||||
%% convert a codepoint to it's \uXXXX equiv.
|
%% convert a codepoint to it's \uXXXX equiv.
|
||||||
json_escape_sequence(X) when X < 65536 ->
|
json_escape_sequence(X) when X < 65536 ->
|
||||||
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
|
<<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) ->
|
json_escape_sequence(X) ->
|
||||||
Adjusted = X - 16#10000,
|
Adjusted = X - 16#10000,
|
||||||
<<A:10, B:10>> = <<Adjusted:20>>,
|
<<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...
|
%% ascii "1" is [49], "2" is [50], etc...
|
||||||
|
@ -792,93 +844,174 @@ to_hex(15) -> $f;
|
||||||
to_hex(X) -> X + 48.
|
to_hex(X) -> X + 48.
|
||||||
|
|
||||||
|
|
||||||
%% like in strings, there's some pseudo states in here that will never
|
number(<<$e, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
|
||||||
%% show up in errors or incompletes. some show up in value, some show
|
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
|
||||||
%% up in integer, decimal or exp
|
number(<<$E, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
|
||||||
negative(<<$0, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
|
||||||
zero(Rest, Handler, acc_seq(Acc, $0), Stack, Config);
|
number(<<$e, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
|
||||||
negative(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when ?is_nonzero(S) ->
|
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
|
||||||
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
number(<<$E, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
|
||||||
negative(<<>>, Handler, [?negative], Stack, Config) ->
|
number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
|
||||||
incomplete(value, <<?negative>>, Handler, Stack, Config);
|
number(<<>>, Handler, Acc, [State|Stack], Config=#config{stream=false}) ->
|
||||||
negative(Bin, Handler, Acc, Stack, Config) ->
|
NumType = case State of
|
||||||
?error(value, <<?negative, Bin/binary>>, Handler, Acc, Stack, Config).
|
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) ->
|
zero(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
|
||||||
decimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
|
zero(<<>>, N) -> {zero, N};
|
||||||
zero(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
|
zero(_, N) -> {finish_integer, N}.
|
||||||
e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config);
|
|
||||||
zero(Bin, Handler, Acc, Stack, Config) ->
|
|
||||||
finish_number(Bin, Handler, {zero, Acc}, Stack, Config).
|
|
||||||
|
|
||||||
|
|
||||||
integer(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= ?zero; ?is_nonzero(S) ->
|
integer(<<$0, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
integer(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
integer(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
integer(<<?decimalpoint, Rest/binary>>, Handler, Acc, Stack, Config) ->
|
integer(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
initialdecimal(Rest, Handler, acc_seq(Acc, ?decimalpoint), Stack, Config);
|
integer(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
integer(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
|
integer(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
e(Rest, Handler, acc_seq(Acc, ".0e"), Stack, Config);
|
integer(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
integer(Bin, Handler, Acc, Stack, Config) ->
|
integer(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
finish_number(Bin, Handler, {integer, Acc}, Stack, Config).
|
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) ->
|
negative(<<$0, Rest/binary>>, N) -> zero(Rest, N + 1);
|
||||||
decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
negative(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
initialdecimal(<<>>, Handler, [?decimalpoint|Acc], Stack, Config) ->
|
negative(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
incomplete(integer, <<?decimalpoint>>, Handler, Acc, Stack, Config);
|
negative(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
initialdecimal(Bin, Handler, Acc, Stack, Config) ->
|
negative(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
|
||||||
?error(decimal, Bin, Handler, Acc, Stack, Config).
|
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) ->
|
initialdecimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
decimal(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
initialdecimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
decimal(<<S, Rest/binary>>, Handler, Acc, Stack, Config) when S =:= $e; S =:= $E ->
|
initialdecimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
e(Rest, Handler, acc_seq(Acc, $e), Stack, Config);
|
initialdecimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
decimal(Bin, Handler, Acc, Stack, Config) ->
|
initialdecimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
finish_number(Bin, Handler, {decimal, Acc}, Stack, Config).
|
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) ->
|
decimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
decimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
e(<<Sign, Rest/binary>>, Handler, Acc, Stack, Config) when Sign =:= ?positive; Sign =:= ?negative ->
|
decimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
ex(Rest, Handler, acc_seq(Acc, Sign), Stack, Config);
|
decimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
e(<<>>, Handler, [$e|Acc], Stack, Config) ->
|
decimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
incomplete(decimal, <<$e>>, Handler, Acc, Stack, Config);
|
decimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
e(Bin, Handler, Acc, Stack, Config) ->
|
decimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1);
|
||||||
?error(decimal, <<$e, Bin/binary>>, Handler, Acc, Stack, Config).
|
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) ->
|
e(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
e(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
ex(<<>>, Handler, [S, $e|Acc], Stack, Config) ->
|
e(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
incomplete(decimal, <<$e, S/utf8>>, Handler, Acc, Stack, Config);
|
e(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
ex(Bin, Handler, [S, $e|Acc], Stack, Config) ->
|
e(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
?error(decimal, <<$e, S, Bin/binary>>, Handler, Acc, Stack, Config).
|
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) ->
|
ex(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
exp(Rest, Handler, acc_seq(Acc, S), Stack, Config);
|
ex(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
exp(Bin, Handler, Acc, Stack, Config) ->
|
ex(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
finish_number(Bin, Handler, {exp, Acc}, Stack, Config).
|
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;
|
exp(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
|
||||||
acc_seq(Seq, C) -> [C] ++ Seq.
|
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) ->
|
finish_number(Rest, Handler, Acc, Stack, Config) ->
|
||||||
maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), 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))};
|
-ifndef(no_binary_to_whatever).
|
||||||
format_number({integer, Acc}) -> {integer, list_to_integer(lists:reverse(Acc))};
|
format_number({integer, Acc}) -> {integer, binary_to_integer(Acc)};
|
||||||
format_number({decimal, Acc}) -> {float, list_to_float(lists:reverse(Acc))};
|
format_number({float, Acc}) -> {float, binary_to_float(Acc)}.
|
||||||
format_number({exp, Acc}) -> {float, list_to_float(lists:reverse(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) ->
|
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) ->
|
maybe_done(<<Rest/binary>>, Handler, [], Config) ->
|
||||||
done(Rest, handle_event(end_json, Handler, Config), [], 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(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config) ->
|
||||||
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
|
maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
|
||||||
maybe_done(<<?end_array, Rest/binary>>, Handler, [array|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);
|
key(Rest, Handler, [key|Stack], Config);
|
||||||
maybe_done(<<?comma, Rest/binary>>, Handler, [array|_] = Stack, Config) ->
|
maybe_done(<<?comma, Rest/binary>>, Handler, [array|_] = Stack, Config) ->
|
||||||
value(Rest, Handler, 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(Rest, Handler, Stack, Config);
|
||||||
maybe_done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
maybe_done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
?error(maybe_done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
?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).
|
?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(Rest, Handler, [], Config);
|
||||||
done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
|
||||||
?error(done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
?error(done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
-module(jsx_encoder).
|
-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().
|
-spec encoder(Handler::module(), State::any(), Config::list()) -> jsx:encoder().
|
||||||
|
|
||||||
|
@ -44,11 +44,10 @@ encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-ifdef(maps_support).
|
-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) ->
|
encode(Term, EntryPoint) when is_map(Term) ->
|
||||||
lists:flatten(
|
[start_object] ++ unpack(Term, EntryPoint);
|
||||||
[start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unpack(Term) ] ++ [end_object]
|
|
||||||
);
|
|
||||||
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
|
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
@ -56,28 +55,29 @@ encode_([], _EntryPoint) -> [start_array, end_array];
|
||||||
encode_([{}], _EntryPoint) -> [start_object, end_object];
|
encode_([{}], _EntryPoint) -> [start_object, end_object];
|
||||||
|
|
||||||
encode_([{_, _}|_] = Term, EntryPoint) ->
|
encode_([{_, _}|_] = Term, EntryPoint) ->
|
||||||
lists:flatten(
|
[start_object] ++ unzip(Term, EntryPoint);
|
||||||
[start_object] ++ [ EntryPoint:encode(T, EntryPoint) || T <- unzip(Term) ] ++ [end_object]
|
|
||||||
);
|
|
||||||
encode_(Term, EntryPoint) when is_list(Term) ->
|
encode_(Term, EntryPoint) when is_list(Term) ->
|
||||||
lists:flatten(
|
[start_array] ++ unhitch(Term, EntryPoint);
|
||||||
[start_array] ++ [ EntryPoint:encode(T, EntryPoint) || T <- Term ] ++ [end_array]
|
|
||||||
);
|
|
||||||
|
|
||||||
encode_(Else, _EntryPoint) -> [Else].
|
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).
|
-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(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
|
||||||
unpack([K|Rest], Map, Acc) when is_binary(K); is_atom(K); is_integer(K) ->
|
[K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint);
|
||||||
unpack(Rest, Map, [maps:get(K, Map), K] ++ Acc).
|
unpack(_, [], _) -> [end_object].
|
||||||
-endif.
|
-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)}.
|
handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}.
|
||||||
|
|
||||||
|
|
||||||
value([start_object|Tokens], Handler, Stack, Config) ->
|
value([String|Tokens], Handler, Stack, Config) when is_binary(String) ->
|
||||||
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) ->
|
|
||||||
try clean_string(String, Config) of Clean ->
|
try clean_string(String, Config) of Clean ->
|
||||||
maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config)
|
maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config)
|
||||||
catch error:badarg ->
|
catch error:badarg ->
|
||||||
?error(value, [{string, String}|Tokens], Handler, Stack, Config)
|
?error(value, [{string, String}|Tokens], Handler, Stack, Config)
|
||||||
end;
|
end;
|
||||||
value([String|Tokens], Handler, Stack, Config) when is_binary(String) ->
|
value([true|Tokens], Handler, Stack, Config) ->
|
||||||
value([{string, String}] ++ Tokens, Handler, Stack, Config);
|
maybe_done(Tokens, handle_event({literal, true}, Handler, Config), Stack, Config);
|
||||||
value([String|Tokens], Handler, Stack, Config) when is_atom(String) ->
|
value([false|Tokens], Handler, Stack, Config) ->
|
||||||
value([{string, atom_to_binary(String, utf8)}] ++ 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([{raw, Raw}|Tokens], Handler, Stack, Config) when is_binary(Raw) ->
|
||||||
value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config);
|
value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config);
|
||||||
value([{{Year, Month, Day}, {Hour, Min, Sec}}|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,
|
Stack,
|
||||||
Config
|
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) ->
|
value([], Handler, Stack, Config) ->
|
||||||
incomplete(value, Handler, Stack, Config);
|
incomplete(value, Handler, Stack, Config);
|
||||||
value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
|
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) ->
|
||||||
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);
|
maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config);
|
||||||
object([{key, Key}|Tokens], Handler, Stack, Config)
|
object([{key, Key}|Tokens], Handler, Stack, Config)
|
||||||
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
||||||
object([Key|Tokens], Handler, Stack, Config);
|
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) ->
|
when is_atom(Key); is_binary(Key); is_integer(Key) ->
|
||||||
try clean_string(fix_key(Key), Config)
|
try clean_string(fix_key(Key), Config)
|
||||||
of K ->
|
of K ->
|
||||||
value(
|
value(
|
||||||
Tokens,
|
Tokens,
|
||||||
handle_event({key, K}, Handler, Config),
|
handle_event({key, K}, Handler, Config),
|
||||||
[{object, []}|Stack],
|
[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],
|
|
||||||
Config
|
Config
|
||||||
)
|
)
|
||||||
catch error:badarg ->
|
catch error:badarg ->
|
||||||
|
@ -186,7 +164,7 @@ array(Token, Handler, Stack, Config) ->
|
||||||
|
|
||||||
maybe_done([end_json], Handler, [], Config) ->
|
maybe_done([end_json], Handler, [], Config) ->
|
||||||
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);
|
object(Tokens, Handler, Stack, Config);
|
||||||
maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
|
maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
|
||||||
array(Tokens, Handler, Stack, Config);
|
array(Tokens, Handler, Stack, Config);
|
||||||
|
@ -219,35 +197,35 @@ clean_string(Bin, Config) -> clean(Bin, [], Config).
|
||||||
|
|
||||||
clean(<<>>, Acc, _) -> iolist_to_binary(Acc);
|
clean(<<>>, Acc, _) -> iolist_to_binary(Acc);
|
||||||
clean(<<X/utf8, Rest/binary>>, Acc, Config) when X < 16#20 ->
|
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) ->
|
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) ->
|
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) ->
|
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 ->
|
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 ->
|
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) ->
|
clean(<<_/utf8, _/binary>> = Bin, Acc, Config) ->
|
||||||
Size = count(Bin, 0, Config),
|
Size = count(Bin, 0, Config),
|
||||||
<<Clean:Size/binary, Rest/binary>> = Bin,
|
<<Clean:Size/binary, Rest/binary>> = Bin,
|
||||||
clean(Rest, [Acc, Clean], Config);
|
clean(Rest, [Acc, Clean], Config);
|
||||||
%% surrogates
|
%% surrogates
|
||||||
clean(<<237, X, _, Rest/binary>>, Acc, Config) when X >= 160 ->
|
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
|
%% overlong encodings and missing continuations of a 2 byte sequence
|
||||||
clean(<<X, Rest/binary>>, Acc, Config) when X >= 192, X =< 223 ->
|
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
|
%% overlong encodings and missing continuations of a 3 byte sequence
|
||||||
clean(<<X, Rest/binary>>, Acc, Config) when X >= 224, X =< 239 ->
|
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
|
%% overlong encodings and missing continuations of a 4 byte sequence
|
||||||
clean(<<X, Rest/binary>>, Acc, Config) when X >= 240, X =< 247 ->
|
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) ->
|
clean(<<_, Rest/binary>>, Acc, Config) ->
|
||||||
maybe_replace(badutf, Rest, Acc, Config).
|
clean(Rest, [Acc, maybe_replace(badutf, Config)], Config).
|
||||||
|
|
||||||
|
|
||||||
count(<<>>, N, _) -> N;
|
count(<<>>, N, _) -> N;
|
||||||
|
@ -473,13 +451,16 @@ count(<<126, Rest/binary>>, N, Config) ->
|
||||||
count(<<127, Rest/binary>>, N, Config) ->
|
count(<<127, Rest/binary>>, N, Config) ->
|
||||||
count(Rest, N + 1, Config);
|
count(Rest, N + 1, Config);
|
||||||
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
|
count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
|
||||||
count(<<X/utf8, _/binary>>, N, _) when X == 16#2028; X == 16#2029 -> N;
|
%% u+2028
|
||||||
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#800 ->
|
count(<<226, 128, 168, _/binary>>, N, _) -> N;
|
||||||
count(Rest, N + 2, Config);
|
%% u+2029
|
||||||
count(<<X/utf8, Rest/binary>>, N, Config) when X < 16#10000 ->
|
count(<<226, 128, 169, _/binary>>, N, _) -> N;
|
||||||
count(Rest, N + 3, Config);
|
count(<<X/utf8, Rest/binary>>, N, Config) ->
|
||||||
count(<<_/utf8, Rest/binary>>, N, Config) ->
|
case X of
|
||||||
count(Rest, N + 4, Config);
|
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.
|
count(<<_, _/binary>>, N, _) -> N.
|
||||||
|
|
||||||
|
|
||||||
|
@ -490,53 +471,43 @@ strip_continuations(<<X, Rest/binary>>, N) when X >= 128, X =< 191 ->
|
||||||
strip_continuations(Bin, _) -> Bin.
|
strip_continuations(Bin, _) -> Bin.
|
||||||
|
|
||||||
|
|
||||||
maybe_replace($\b, Rest, Acc, Config=#config{escaped_strings=true}) ->
|
maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
|
||||||
clean(Rest, [Acc, $\\, $b], Config);
|
maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
|
||||||
maybe_replace($\t, Rest, Acc, Config=#config{escaped_strings=true}) ->
|
maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
|
||||||
clean(Rest, [Acc, $\\, $t], Config);
|
maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
|
||||||
maybe_replace($\n, Rest, Acc, Config=#config{escaped_strings=true}) ->
|
maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
|
||||||
clean(Rest, [Acc, $\\, $n], Config);
|
maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
|
||||||
maybe_replace($\f, Rest, Acc, Config=#config{escaped_strings=true}) ->
|
maybe_replace($/, 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}) ->
|
|
||||||
case Config#config.escaped_forward_slashes of
|
case Config#config.escaped_forward_slashes of
|
||||||
true -> clean(Rest, [Acc, $\\, $/], Config);
|
true -> <<$\\, $/>>;
|
||||||
false -> clean(Rest, [Acc, $/], Config)
|
false -> <<$/>>
|
||||||
end;
|
end;
|
||||||
maybe_replace($\\, Rest, Acc, Config=#config{escaped_strings=true}) ->
|
maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>;
|
||||||
clean(Rest, [Acc, $\\, $\\], Config);
|
maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
|
||||||
maybe_replace(X, Rest, Acc, Config=#config{escaped_strings=true}) when X < 32 ->
|
json_escape_sequence(X);
|
||||||
clean(Rest, [Acc, json_escape_sequence(X)], Config);
|
maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
|
||||||
%% 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 ->
|
|
||||||
case Config#config.unescaped_jsonp of
|
case Config#config.unescaped_jsonp of
|
||||||
true -> clean(Rest, [Acc, <<X/utf8>>], Config);
|
true -> <<X/utf8>>;
|
||||||
false -> clean(Rest, [Acc, json_escape_sequence(X)], Config)
|
false -> json_escape_sequence(X)
|
||||||
end;
|
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);
|
erlang:error(badarg);
|
||||||
maybe_replace(surrogate, Rest, Acc, Config) ->
|
maybe_replace(surrogate, _Config) ->
|
||||||
clean(Rest, [Acc, <<16#fffd/utf8>>], Config);
|
<<16#fffd/utf8>>;
|
||||||
maybe_replace(badutf, Rest, Acc, Config) ->
|
maybe_replace(badutf, _Config) ->
|
||||||
clean(Rest, [Acc, <<16#fffd/utf8>>], Config);
|
<<16#fffd/utf8>>;
|
||||||
maybe_replace(X, Rest, Acc, Config) ->
|
maybe_replace(X, _Config) ->
|
||||||
clean(Rest, [Acc, <<X/utf8>>], Config).
|
<<X/utf8>>.
|
||||||
|
|
||||||
|
|
||||||
%% convert a codepoint to it's \uXXXX equiv.
|
%% convert a codepoint to it's \uXXXX equiv.
|
||||||
json_escape_sequence(X) when X < 65536 ->
|
json_escape_sequence(X) when X < 65536 ->
|
||||||
<<A:4, B:4, C:4, D:4>> = <<X:16>>,
|
<<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) ->
|
json_escape_sequence(X) ->
|
||||||
Adjusted = X - 16#10000,
|
Adjusted = X - 16#10000,
|
||||||
<<A:10, B:10>> = <<Adjusted:20>>,
|
<<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;
|
to_hex(10) -> $a;
|
||||||
|
@ -1052,11 +1023,12 @@ bad_utf8_test_() ->
|
||||||
|
|
||||||
json_escape_sequence_test_() ->
|
json_escape_sequence_test_() ->
|
||||||
[
|
[
|
||||||
{"json escape sequence test - 16#0000", ?_assertEqual(json_escape_sequence(16#0000), "\\u0000")},
|
{"json escape sequence test - 16#0000", ?_assertEqual(<<"\\u0000"/utf8>>, json_escape_sequence(16#0000))},
|
||||||
{"json escape sequence test - 16#abc", ?_assertEqual(json_escape_sequence(16#abc), "\\u0abc")},
|
{"json escape sequence test - 16#abc", ?_assertEqual(<<"\\u0abc"/utf8>>, json_escape_sequence(16#abc))},
|
||||||
{"json escape sequence test - 16#def", ?_assertEqual(json_escape_sequence(16#def), "\\u0def")}
|
{"json escape sequence test - 16#def", ?_assertEqual(<<"\\u0def"/utf8>>, json_escape_sequence(16#def))}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
uescape_test_() ->
|
uescape_test_() ->
|
||||||
[
|
[
|
||||||
{"\"\\u0080\"", ?_assertEqual(
|
{"\"\\u0080\"", ?_assertEqual(
|
||||||
|
@ -1080,6 +1052,7 @@ uescape_test_() ->
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
fix_key_test_() ->
|
fix_key_test_() ->
|
||||||
[
|
[
|
||||||
{"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
|
{"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_test_() ->
|
||||||
[
|
[
|
||||||
{"datetime", ?_assertEqual(
|
{"datetime", ?_assertEqual(
|
||||||
|
|
|
@ -23,10 +23,17 @@
|
||||||
|
|
||||||
-module(jsx_to_term).
|
-module(jsx_to_term).
|
||||||
|
|
||||||
-export([to_term/2]).
|
-export([to_term/2, flatify/1]).
|
||||||
-export([init/1, handle_event/2]).
|
-export([init/1, handle_event/2]).
|
||||||
-export([start_term/0, start_term/1]).
|
-export([
|
||||||
-export([start_object/1, start_array/1, finish/1, insert/2, insert/3, get_key/1, get_value/1]).
|
start_term/1,
|
||||||
|
start_object/1,
|
||||||
|
start_array/1,
|
||||||
|
finish/1,
|
||||||
|
insert/2,
|
||||||
|
get_key/1,
|
||||||
|
get_value/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
-record(config, {
|
-record(config, {
|
||||||
|
@ -37,15 +44,27 @@
|
||||||
-type config() :: list().
|
-type config() :: list().
|
||||||
-export_type([config/0]).
|
-export_type([config/0]).
|
||||||
|
|
||||||
|
-ifndef(maps_support).
|
||||||
-type json_value() :: list({binary(), json_value()})
|
-type json_value() :: list(json_value())
|
||||||
| list(json_value())
|
| list({binary() | atom(), json_value()})
|
||||||
| true
|
| true
|
||||||
| false
|
| false
|
||||||
| null
|
| null
|
||||||
| integer()
|
| integer()
|
||||||
| float()
|
| float()
|
||||||
| binary().
|
| 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().
|
-spec to_term(Source::binary(), Config::config()) -> json_value().
|
||||||
|
@ -79,10 +98,11 @@ parse_config([K|Rest] = Options, Config) ->
|
||||||
parse_config([], Config) ->
|
parse_config([], Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
-type state() :: {[any()], #config{}}.
|
|
||||||
|
-type state() :: {list(), #config{}}.
|
||||||
-spec init(Config::proplists:proplist()) -> state().
|
-spec init(Config::proplists:proplist()) -> state().
|
||||||
|
|
||||||
init(Config) -> {[], parse_config(Config)}.
|
init(Config) -> start_term(Config).
|
||||||
|
|
||||||
-spec handle_event(Event::any(), State::state()) -> state().
|
-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
|
%% the stack is a list of in progress objects/arrays
|
||||||
%% `[Current, Parent, Grandparent,...OriginalAncestor]`
|
%% `[Current, Parent, Grandparent,...OriginalAncestor]`
|
||||||
%% an object has the representation on the stack of
|
%% an object has the representation on the stack of
|
||||||
%% `{object, [{NthKey, NthValue}, {NMinus1Key, NthMinus1Value},...{FirstKey, FirstValue}]}`
|
%% `{object, [
|
||||||
%% of if there's a key with a yet to be matched value
|
%% {NthKey, NthValue},
|
||||||
%% `{object, Key, [{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
|
%% an array looks like
|
||||||
%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
|
%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
|
||||||
|
|
||||||
start_term() -> {[], #config{}}.
|
|
||||||
|
|
||||||
start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
|
start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
|
||||||
|
|
||||||
|
|
||||||
|
-ifndef(maps_support).
|
||||||
%% allocate a new object on top of the stack
|
%% allocate a new object on top of the stack
|
||||||
start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
|
start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
|
||||||
|
|
||||||
|
|
||||||
%% allocate a new array on top of the stack
|
%% allocate a new array on top of the stack
|
||||||
start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
|
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
|
%% finish an object or array and insert it into the parent object if it exists or
|
||||||
%% return it if it is the root object
|
%% return it if it is the root object
|
||||||
finish0({[{object, []}], Config}) -> {[{}], Config};
|
finish({[{object, []}], Config}) -> {[{}], Config};
|
||||||
finish0({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
|
finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
|
||||||
finish0({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
|
finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
|
||||||
finish0({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
|
finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
|
||||||
finish0({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
|
finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
|
||||||
finish0({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
|
finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
|
||||||
finish0(_) -> erlang:error(badarg).
|
finish(_) -> erlang:error(badarg).
|
||||||
|
|
||||||
|
|
||||||
%% insert a value when there's no parent object or array
|
%% insert a value when there's no parent object or array
|
||||||
insert(Value, {[], Config}) -> {Value, Config};
|
insert(Value, {[], Config}) -> {Value, Config};
|
||||||
|
@ -170,11 +189,49 @@ insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
|
||||||
insert(Value, {[{array, Values}|Rest], Config}) ->
|
insert(Value, {[{array, Values}|Rest], Config}) ->
|
||||||
{[{array, [Value] ++ Values}] ++ Rest, Config};
|
{[{array, [Value] ++ Values}] ++ Rest, Config};
|
||||||
insert(_, _) -> erlang:error(badarg).
|
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};
|
{[{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;
|
get_key({[{object, Key, _}|_], _}) -> Key;
|
||||||
|
@ -185,6 +242,15 @@ get_value({Value, _Config}) -> Value;
|
||||||
get_value(_) -> erlang:error(badarg).
|
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
|
%% eunit tests
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
@ -235,10 +301,6 @@ format_key_test_() ->
|
||||||
|
|
||||||
rep_manipulation_test_() ->
|
rep_manipulation_test_() ->
|
||||||
[
|
[
|
||||||
{"allocate a new context", ?_assertEqual(
|
|
||||||
{[], #config{}},
|
|
||||||
start_term()
|
|
||||||
)},
|
|
||||||
{"allocate a new context with option", ?_assertEqual(
|
{"allocate a new context with option", ?_assertEqual(
|
||||||
{[], #config{labels=atom}},
|
{[], #config{labels=atom}},
|
||||||
start_term([{labels, atom}])
|
start_term([{labels, atom}])
|
||||||
|
@ -283,10 +345,6 @@ rep_manipulation_test_() ->
|
||||||
{[{array, [value]}, junk], #config{}},
|
{[{array, [value]}, junk], #config{}},
|
||||||
insert(value, {[{array, []}, 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(
|
{"finish an object with no ancestor", ?_assertEqual(
|
||||||
{[{a, b}, {x, y}], #config{}},
|
{[{a, b}, {x, y}], #config{}},
|
||||||
finish({[{object, [{x, y}, {a, b}]}], #config{}})
|
finish({[{object, [{x, y}, {a, b}]}], #config{}})
|
||||||
|
@ -309,7 +367,54 @@ rep_manipulation_test_() ->
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
-ifdef(maps_support).
|
-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_() ->
|
return_maps_test_() ->
|
||||||
[
|
[
|
||||||
|
@ -334,7 +439,6 @@ return_maps_test_() ->
|
||||||
jsx:decode(<<"[{}]">>, [return_maps])
|
jsx:decode(<<"[{}]">>, [return_maps])
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,15 +27,7 @@
|
||||||
-export([init/1, handle_event/2]).
|
-export([init/1, handle_event/2]).
|
||||||
|
|
||||||
|
|
||||||
-record(config, {
|
-spec is_json(Source::binary(), Config::jsx_config:config()) -> true | false | {incomplete, jsx:decoder()}.
|
||||||
repeated_keys = true
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type config() :: [].
|
|
||||||
-export_type([config/0]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec is_json(Source::binary(), Config::config()) -> true | false | {incomplete, jsx:decoder()}.
|
|
||||||
|
|
||||||
is_json(Source, Config) when is_list(Config) ->
|
is_json(Source, Config) when is_list(Config) ->
|
||||||
try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
||||||
|
@ -43,7 +35,7 @@ is_json(Source, Config) when is_list(Config) ->
|
||||||
end.
|
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) ->
|
is_term(Source, Config) when is_list(Config) ->
|
||||||
try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
|
||||||
|
@ -51,15 +43,15 @@ is_term(Source, Config) when is_list(Config) ->
|
||||||
end.
|
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([no_repeated_keys|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{repeated_keys=false});
|
parse_config(Rest, Config);
|
||||||
%% deprecated, use `no_repeated_keys`
|
|
||||||
parse_config([{repeated_keys, Val}|Rest], Config) when Val == true; Val == false ->
|
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([repeated_keys|Rest], Config) ->
|
||||||
parse_config(Rest, Config#config{repeated_keys=true});
|
parse_config(Rest, Config);
|
||||||
parse_config([{K, _}|Rest] = Options, Config) ->
|
parse_config([{K, _}|Rest] = Options, Config) ->
|
||||||
case lists:member(K, jsx_config:valid_flags()) of
|
case lists:member(K, jsx_config:valid_flags()) of
|
||||||
true -> parse_config(Rest, Config);
|
true -> parse_config(Rest, Config);
|
||||||
|
@ -73,27 +65,18 @@ parse_config([K|Rest] = Options, Config) ->
|
||||||
parse_config([], Config) ->
|
parse_config([], Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
-type state() :: {#config{}, any()}.
|
|
||||||
|
%% we don't actually need any state for this
|
||||||
|
-type state() :: [].
|
||||||
-spec init(Config::proplists:proplist()) -> 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().
|
-spec handle_event(Event::any(), State::state()) -> state().
|
||||||
|
|
||||||
handle_event(end_json, _) -> true;
|
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.
|
handle_event(_, State) -> State.
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +88,15 @@ handle_event(_, State) -> State.
|
||||||
|
|
||||||
config_test_() ->
|
config_test_() ->
|
||||||
[
|
[
|
||||||
{"empty config", ?_assertEqual(#config{}, parse_config([]))},
|
{"empty config", ?_assertEqual([], parse_config([]))},
|
||||||
{"no repeat keys", ?_assertEqual(#config{repeated_keys=false}, parse_config([no_repeated_keys]))},
|
{"no repeat keys", ?_assertEqual([], parse_config([no_repeated_keys]))},
|
||||||
{"bare repeated keys", ?_assertEqual(#config{}, parse_config([repeated_keys]))},
|
{"bare repeated keys", ?_assertEqual([], parse_config([repeated_keys]))},
|
||||||
{"repeated keys true", ?_assertEqual(
|
{"repeated keys true", ?_assertEqual(
|
||||||
#config{},
|
[],
|
||||||
parse_config([{repeated_keys, true}])
|
parse_config([{repeated_keys, true}])
|
||||||
)},
|
)},
|
||||||
{"repeated keys false", ?_assertEqual(
|
{"repeated keys false", ?_assertEqual(
|
||||||
#config{repeated_keys=false},
|
[],
|
||||||
parse_config([{repeated_keys, false}])
|
parse_config([{repeated_keys, false}])
|
||||||
)},
|
)},
|
||||||
{"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
|
{"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_() ->
|
handle_event_test_() ->
|
||||||
Data = jsx:test_cases() ++ jsx:special_test_cases(),
|
Data = jsx:test_cases() ++ jsx:special_test_cases(),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
Title, ?_assertEqual(
|
Title, ?_assertEqual(
|
||||||
true,
|
true,
|
||||||
lists:foldl(fun handle_event/2, {#config{}, []}, Events ++ [end_json])
|
lists:foldl(fun handle_event/2, [], Events ++ [end_json])
|
||||||
)
|
)
|
||||||
} || {Title, _, _, Events} <- Data
|
} || {Title, _, _, Events} <- Data
|
||||||
].
|
].
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue