From a86dec09efcf22db658ea0a0f1b3825bd5af3656 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Wed, 25 Aug 2010 23:17:10 -0700 Subject: [PATCH] major whitespace updates to get line lengths down to < 80 characters whenever possible (a few tests ignore this). srx/jsx_test.erl was not touched because it's shortly to be replaced --- include/jsx_common.hrl | 6 +- include/jsx_decoder.hrl | 672 +++++++++++++++++++++++++++------------- src/jsx.erl | 211 ++++++++----- src/jsx_eep0018.erl | 344 ++++++++++++++------ src/jsx_format.erl | 93 +++++- src/jsx_verify.erl | 86 ++++- 6 files changed, 998 insertions(+), 414 deletions(-) diff --git a/include/jsx_common.hrl b/include/jsx_common.hrl index fdc6748..aa31871 100644 --- a/include/jsx_common.hrl +++ b/include/jsx_common.hrl @@ -23,5 +23,9 @@ -define(is_utf_encoding(X), - X == utf8; X == utf16; X == utf32; X == {utf16, little}; X == {utf32, little} + X == utf8 + ; X == utf16 + ; X == utf32 + ; X == {utf16, little} + ; X == {utf32, little} ). \ No newline at end of file diff --git a/include/jsx_decoder.hrl b/include/jsx_decoder.hrl index b2a20c0..6feb245 100644 --- a/include/jsx_decoder.hrl +++ b/include/jsx_decoder.hrl @@ -21,9 +21,9 @@ %% THE SOFTWARE. -%% this is the implementation of the utf backends for the jsx decoder. it's included -%% by the various jsx_utfxx.erl frontends and all modifications to this file -%% should take that into account +%% this is the implementation of the utf backends for the jsx decoder. it's +%% included by the various jsx_utfxx.erl frontends and all modifications to +%% this file ?utfxshould take that into account %% opts record for decoder @@ -93,27 +93,27 @@ %% partial codepoint max size differs across encodings -ifdef(utf8). --define(encoding, utf8). +-define(utfx, utf8). -define(partial_codepoint(Bin), byte_size(Bin) < 1). -endif. -ifdef(utf16). --define(encoding, utf16). +-define(utfx, utf16). -define(partial_codepoint(Bin), byte_size(Bin) < 2). -endif. -ifdef(utf16le). --define(encoding, utf16-little). +-define(utfx, utf16-little). -define(partial_codepoint(Bin), byte_size(Bin) < 2). -endif. -ifdef(utf32). --define(encoding, utf32). +-define(utfx, utf32). -define(partial_codepoint(Bin), byte_size(Bin) < 4). -endif. -ifdef(utf32le). --define(encoding, utf32-little). +-define(utfx, utf32-little). -define(partial_codepoint(Bin), byte_size(Bin) < 4). -endif. @@ -137,13 +137,13 @@ parse_opts([], Opts) -> Opts; parse_opts([{comments, Value}|Rest], Opts) -> true = lists:member(Value, [true, false]), - parse_opts(Rest, Opts#opts{comments = Value}); + parse_opts(Rest, Opts#opts{comments=Value}); parse_opts([{escaped_unicode, Value}|Rest], Opts) -> true = lists:member(Value, [ascii, codepoint, none]), - parse_opts(Rest, Opts#opts{escaped_unicode = Value}); + parse_opts(Rest, Opts#opts{escaped_unicode=Value}); parse_opts([{multi_term, Value}|Rest], Opts) -> true = lists:member(Value, [true, false]), - parse_opts(Rest, Opts#opts{multi_term = Value}); + parse_opts(Rest, Opts#opts{multi_term=Value}); parse_opts([{encoding, _}|Rest], Opts) -> parse_opts(Rest, Opts); parse_opts(_, _) -> @@ -151,189 +151,243 @@ parse_opts(_, _) -> -start(<>, Stack, Opts) when ?is_whitespace(S) -> +start(<>, Stack, Opts) when ?is_whitespace(S) -> start(Rest, Stack, Opts); -start(<>, Stack, Opts) -> +start(<>, Stack, Opts) -> {event, start_object, fun() -> object(Rest, [key|Stack], Opts) end}; -start(<>, Stack, Opts) -> +start(<>, Stack, Opts) -> {event, start_array, fun() -> array(Rest, [array|Stack], Opts) end}; -start(<>, Stack, Opts) -> +start(<>, Stack, Opts) -> string(Rest, Stack, Opts, []); -start(<<$t/?encoding, Rest/binary>>, Stack, Opts) -> +start(<<$t/?utfx, Rest/binary>>, Stack, Opts) -> tr(Rest, Stack, Opts); -start(<<$f/?encoding, Rest/binary>>, Stack, Opts) -> +start(<<$f/?utfx, Rest/binary>>, Stack, Opts) -> fa(Rest, Stack, Opts); -start(<<$n/?encoding, Rest/binary>>, Stack, Opts) -> +start(<<$n/?utfx, Rest/binary>>, Stack, Opts) -> nu(Rest, Stack, Opts); -start(<>, Stack, Opts) -> +start(<>, Stack, Opts) -> negative(Rest, Stack, Opts, "-"); -start(<>, Stack, Opts) -> +start(<>, Stack, Opts) -> zero(Rest, Stack, Opts, "0"); -start(<>, Stack, Opts) when ?is_nonzero(S) -> +start(<>, Stack, Opts) when ?is_nonzero(S) -> integer(Rest, Stack, Opts, [S]); -start(<>, Stack, #opts{comments = true} = Opts) -> +start(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> start(Resume, Stack, Opts) end); start(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> start(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + start(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -maybe_done(<>, Stack, Opts) when ?is_whitespace(S) -> +maybe_done(<>, Stack, Opts) when ?is_whitespace(S) -> maybe_done(Rest, Stack, Opts); -maybe_done(<>, [object|Stack], Opts) -> +maybe_done(<>, [object|Stack], Opts) -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end}; -maybe_done(<>, [array|Stack], Opts) -> +maybe_done(<>, [array|Stack], Opts) -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end}; -maybe_done(<>, [object|Stack], Opts) -> +maybe_done(<>, [object|Stack], Opts) -> key(Rest, [key|Stack], Opts); -maybe_done(<>, [array|_] = Stack, Opts) -> +maybe_done(<>, [array|_] = Stack, Opts) -> value(Rest, Stack, Opts); -maybe_done(<>, Stack, #opts{comments = true} = Opts) -> +maybe_done(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> maybe_done(Resume, Stack, Opts) end); -maybe_done(Rest, [], #opts{multi_term = true} = Opts) -> +maybe_done(Rest, [], #opts{multi_term=true}=Opts) -> {event, end_json, fun() -> start(Rest, [], Opts) end}; maybe_done(Rest, [], Opts) -> done(Rest, Opts); maybe_done(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> maybe_done(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + maybe_done(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -done(<>, Opts) when ?is_whitespace(S) -> +done(<>, Opts) when ?is_whitespace(S) -> done(Rest, Opts); -done(<>, #opts{comments = true} = Opts) -> +done(<>, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> done(Resume, Opts) end); done(<<>>, Opts) -> - {event, end_json, fun() -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> done(Stream, Opts) end} end}; + {event, end_json, fun() -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + done(Stream, Opts) + end} + end}; done(Bin, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> done(<>, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + done(<>, Opts) + end} ; false -> {error, badjson} end. -object(<>, Stack, Opts) when ?is_whitespace(S) -> +object(<>, Stack, Opts) when ?is_whitespace(S) -> object(Rest, Stack, Opts); -object(<>, Stack, Opts) -> +object(<>, Stack, Opts) -> string(Rest, Stack, Opts, []); -object(<>, [key|Stack], Opts) -> +object(<>, [key|Stack], Opts) -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end}; -object(<>, Stack, #opts{comments = true} = Opts) -> +object(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> object(Resume, Stack, Opts) end); object(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> object(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + object(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -array(<>, Stack, Opts) when ?is_whitespace(S) -> +array(<>, Stack, Opts) when ?is_whitespace(S) -> array(Rest, Stack, Opts); -array(<>, Stack, Opts) -> +array(<>, Stack, Opts) -> string(Rest, Stack, Opts, []); -array(<<$t/?encoding, Rest/binary>>, Stack, Opts) -> +array(<<$t/?utfx, Rest/binary>>, Stack, Opts) -> tr(Rest, Stack, Opts); -array(<<$f/?encoding, Rest/binary>>, Stack, Opts) -> +array(<<$f/?utfx, Rest/binary>>, Stack, Opts) -> fa(Rest, Stack, Opts); -array(<<$n/?encoding, Rest/binary>>, Stack, Opts) -> +array(<<$n/?utfx, Rest/binary>>, Stack, Opts) -> nu(Rest, Stack, Opts); -array(<>, Stack, Opts) -> +array(<>, Stack, Opts) -> negative(Rest, Stack, Opts, "-"); -array(<>, Stack, Opts) -> +array(<>, Stack, Opts) -> zero(Rest, Stack, Opts, "0"); -array(<>, Stack, Opts) when ?is_nonzero(S) -> +array(<>, Stack, Opts) when ?is_nonzero(S) -> integer(Rest, Stack, Opts, [S]); -array(<>, Stack, Opts) -> +array(<>, Stack, Opts) -> {event, start_object, fun() -> object(Rest, [key|Stack], Opts) end}; -array(<>, Stack, Opts) -> +array(<>, Stack, Opts) -> {event, start_array, fun() -> array(Rest, [array|Stack], Opts) end}; -array(<>, [array|Stack], Opts) -> +array(<>, [array|Stack], Opts) -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end}; -array(<>, Stack, #opts{comments = true} = Opts) -> +array(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> array(Resume, Stack, Opts) end); array(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> array(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + array(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -value(<>, Stack, Opts) when ?is_whitespace(S) -> +value(<>, Stack, Opts) when ?is_whitespace(S) -> value(Rest, Stack, Opts); -value(<>, Stack, Opts) -> +value(<>, Stack, Opts) -> string(Rest, Stack, Opts, []); -value(<<$t/?encoding, Rest/binary>>, Stack, Opts) -> +value(<<$t/?utfx, Rest/binary>>, Stack, Opts) -> tr(Rest, Stack, Opts); -value(<<$f/?encoding, Rest/binary>>, Stack, Opts) -> +value(<<$f/?utfx, Rest/binary>>, Stack, Opts) -> fa(Rest, Stack, Opts); -value(<<$n/?encoding, Rest/binary>>, Stack, Opts) -> +value(<<$n/?utfx, Rest/binary>>, Stack, Opts) -> nu(Rest, Stack, Opts); -value(<>, Stack, Opts) -> +value(<>, Stack, Opts) -> negative(Rest, Stack, Opts, "-"); -value(<>, Stack, Opts) -> +value(<>, Stack, Opts) -> zero(Rest, Stack, Opts, "0"); -value(<>, Stack, Opts) when ?is_nonzero(S) -> +value(<>, Stack, Opts) when ?is_nonzero(S) -> integer(Rest, Stack, Opts, [S]); -value(<>, Stack, Opts) -> +value(<>, Stack, Opts) -> {event, start_object, fun() -> object(Rest, [key|Stack], Opts) end}; -value(<>, Stack, Opts) -> +value(<>, Stack, Opts) -> {event, start_array, fun() -> array(Rest, [array|Stack], Opts) end}; -value(<>, Stack, #opts{comments = true} = Opts) -> +value(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> value(Resume, Stack, Opts) end); value(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> value(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + value(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -colon(<>, Stack, Opts) when ?is_whitespace(S) -> +colon(<>, Stack, Opts) when ?is_whitespace(S) -> colon(Rest, Stack, Opts); -colon(<>, [key|Stack], Opts) -> +colon(<>, [key|Stack], Opts) -> value(Rest, [object|Stack], Opts); -colon(<>, Stack, #opts{comments = true} = Opts) -> +colon(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> colon(Resume, Stack, Opts) end); colon(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> colon(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + colon(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -key(<>, Stack, Opts) when ?is_whitespace(S) -> +key(<>, Stack, Opts) when ?is_whitespace(S) -> key(Rest, Stack, Opts); -key(<>, Stack, Opts) -> +key(<>, Stack, Opts) -> string(Rest, Stack, Opts, []); -key(<>, Stack, #opts{comments = true} = Opts) -> +key(<>, Stack, #opts{comments=true}=Opts) -> maybe_comment(Rest, fun(Resume) -> key(Resume, Stack, Opts) end); key(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> key(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + key(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -%% string has an additional parameter, an accumulator (Acc) used to hold the intermediate -%% representation of the string being parsed. using a list of integers representing -%% unicode codepoints is faster than constructing binaries, many of which will be -%% converted back to lists by the user anyways -%% string uses partial_utf/1 to cease parsing when invalid encodings are encountered -%% rather than just checking remaining binary size like other states -string(<>, [key|_] = Stack, Opts, Acc) -> +%% string has an additional parameter, an accumulator (Acc) used to hold the +%% intermediate representation of the string being parsed. using a list of +%% integers representing unicode codepoints is faster than constructing +%% binaries, many of which will be converted back to lists by the user anyways +%% string uses partial_utf/1 to cease parsing when invalid encodings are +%% encountered rather than just checking remaining binary size like other +%% states +string(<>, [key|_] = Stack, Opts, Acc) -> {event, {key, lists:reverse(Acc)}, fun() -> colon(Rest, Stack, Opts) end}; -string(<>, Stack, Opts, Acc) -> - {event, {string, lists:reverse(Acc)}, fun() -> maybe_done(Rest, Stack, Opts) end}; -string(<>, Stack, Opts, Acc) -> +string(<>, Stack, Opts, Acc) -> + {event, {string, lists:reverse(Acc)}, fun() -> + maybe_done(Rest, Stack, Opts) + end}; +string(<>, Stack, Opts, Acc) -> escape(Rest, Stack, Opts, Acc); -string(<>, Stack, Opts, Acc) when ?is_noncontrol(S) -> +string(<>, Stack, Opts, Acc) when ?is_noncontrol(S) -> string(Rest, Stack, Opts, [S] ++ Acc); string(Bin, Stack, Opts, Acc) -> case partial_utf(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> string(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + string(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. @@ -356,19 +410,24 @@ partial_utf(_) -> false. -endif. -ifdef(utf16). -partial_utf(<<>>) -> true; -%% this case is not strictly true, there are single bytes that should be rejected, but -%% they're rare enough they can be ignored -partial_utf(<<_X>>) -> true; -partial_utf(<>) when X >= 16#d8, X =< 16#df -> true; -partial_utf(<>) when X >= 16#d8, X =< 16#df, Z >= 16#dc, Z =< 16#df -> true; -partial_utf(_) -> false. +partial_utf(<<>>) -> + true; +%% this case is not strictly true, there are single bytes that should be +%% rejected, but they're rare enough they can be ignored +partial_utf(<<_X>>) -> + true; +partial_utf(<>) when X >= 16#d8, X =< 16#df -> + true; +partial_utf(<>) when X >= 16#d8, X =< 16#df, Z >= 16#dc, Z =< 16#df -> + true; +partial_utf(_) -> + false. -endif. -ifdef(utf16le). partial_utf(<<>>) -> true; -%% this case is not strictly true, there are single bytes that should be rejected, but -%% they're rare enough they can be ignored +%% this case is not strictly true, there are single bytes that should be +%% rejected, but they're rare enough they can be ignored partial_utf(<<_X>>) -> true; partial_utf(<<_Y, X>>) when X >= 16#d8, X =< 16#df -> true; partial_utf(<<_Y, X, _Z>>) when X >= 16#d8, X =< 16#df -> true; @@ -386,82 +445,108 @@ partial_utf(_) -> true. -endif. -%% only thing to note here is the additional accumulator passed to escaped_unicode used -%% to hold the codepoint sequence. unescessary, but nicer than using the string -%% accumulator -escape(<<$b/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +%% only thing to note here is the additional accumulator passed to +%% escaped_unicode used to hold the codepoint sequence. unescessary, but nicer +%% than using the string accumulator +escape(<<$b/?utfx, Rest/binary>>, Stack, Opts, Acc) -> string(Rest, Stack, Opts, "\b" ++ Acc); -escape(<<$f/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +escape(<<$f/?utfx, Rest/binary>>, Stack, Opts, Acc) -> string(Rest, Stack, Opts, "\f" ++ Acc); -escape(<<$n/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +escape(<<$n/?utfx, Rest/binary>>, Stack, Opts, Acc) -> string(Rest, Stack, Opts, "\n" ++ Acc); -escape(<<$r/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +escape(<<$r/?utfx, Rest/binary>>, Stack, Opts, Acc) -> string(Rest, Stack, Opts, "\r" ++ Acc); -escape(<<$t/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +escape(<<$t/?utfx, Rest/binary>>, Stack, Opts, Acc) -> string(Rest, Stack, Opts, "\t" ++ Acc); -escape(<<$u/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +escape(<<$u/?utfx, Rest/binary>>, Stack, Opts, Acc) -> escaped_unicode(Rest, Stack, Opts, Acc, []); -escape(<>, Stack, Opts, Acc) +escape(<>, Stack, Opts, Acc) when S =:= ?quote; S =:= ?solidus; S =:= ?rsolidus -> string(Rest, Stack, Opts, [S] ++ Acc); escape(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> escape(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + escape(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -%% this code is ugly and unfortunate, but so is json's handling of escaped unicode -%% codepoint sequences. if the ascii option is present, the sequence is converted -%% to a codepoint and inserted into the string if it represents an ascii value. if -%% the codepoint option is present the sequence is converted and inserted as long -%% as it represents a valid unicode codepoint. this means non-characters -%% representable in 16 bits are not converted (the utf16 surrogates and the two -%% special non-characters). any other option and no conversion is done -escaped_unicode(<>, +%% this code is ugly and unfortunate, but so is json's handling of escaped +%% unicode codepoint sequences. if the ascii option is present, the sequence +%% is converted to a codepoint and inserted into the string if it represents +%% an ascii value. if the codepoint option is present the sequence is +%% converted and inserted as long as it represents a valid unicode codepoint. +%% this means non-characters representable in 16 bits are not converted (the +%5 utf16 surrogates and the two special non-characters). any other option and +%% no conversion is done +escaped_unicode(<>, Stack, - #opts{escaped_unicode = ascii} = Opts, + #opts{escaped_unicode=ascii}=Opts, String, [C, B, A]) - when ?is_hex(D) -> + when ?is_hex(D) -> case erlang:list_to_integer([A, B, C, D], 16) of X when X < 128 -> string(Rest, Stack, Opts, [X] ++ String) ; _ -> string(Rest, Stack, Opts, [D, C, B, A, $u, ?rsolidus] ++ String) end; -escaped_unicode(<>, +escaped_unicode(<>, Stack, - #opts{escaped_unicode = codepoint} = Opts, + #opts{escaped_unicode=codepoint}=Opts, String, [C, B, A]) - when ?is_hex(D) -> + when ?is_hex(D) -> case erlang:list_to_integer([A, B, C, D], 16) of X when X >= 16#dc00, X =< 16#dfff -> case check_acc_for_surrogate(String) of false -> - string(Rest, Stack, Opts, [D, C, B, A, $u, ?rsolidus] ++ String) + string(Rest, + Stack, + Opts, + [D, C, B, A, $u, ?rsolidus] ++ String + ) ; {Y, NewString} -> - string(Rest, Stack, Opts, [surrogate_to_codepoint(Y, X)] ++ NewString) + string(Rest, + Stack, + Opts, + [surrogate_to_codepoint(Y, X)] ++ NewString + ) end ; X when X < 16#d800; X > 16#dfff, X < 16#fffe -> string(Rest, Stack, Opts, [X] ++ String) ; _ -> string(Rest, Stack, Opts, [D, C, B, A, $u, ?rsolidus] ++ String) end; -escaped_unicode(<>, Stack, Opts, String, [C, B, A]) when ?is_hex(D) -> +escaped_unicode(<>, Stack, Opts, String, [C, B, A]) + when ?is_hex(D) -> string(Rest, Stack, Opts, [D, C, B, A, $u, ?rsolidus] ++ String); -escaped_unicode(<>, Stack, Opts, String, Acc) when ?is_hex(S) -> +escaped_unicode(<>, Stack, Opts, String, Acc) + when ?is_hex(S) -> escaped_unicode(Rest, Stack, Opts, String, [S] ++ Acc); escaped_unicode(Bin, Stack, Opts, String, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> escaped_unicode(<>, Stack, Opts, String, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + escaped_unicode(<>, + Stack, + Opts, + String, + Acc + ) + end} ; false -> {error, badjson} end. -%% upon encountering a low pair json/hex encoded value, check to see if there's a high -%% value already in the accumulator +%% upon encountering a low pair json/hex encoded value, check to see if there's +%% a high value already in the accumulator check_acc_for_surrogate([D, C, B, A, $u, ?rsolidus|Rest]) when ?is_hex(D), ?is_hex(C), ?is_hex(B), ?is_hex(A) -> case erlang:list_to_integer([A, B, C, D], 16) of @@ -481,34 +566,45 @@ surrogate_to_codepoint(High, Low) -> %% like strings, numbers are collected in an intermediate accumulator before %% being emitted to the callback handler -negative(<<$0/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +negative(<<$0/?utfx, Rest/binary>>, Stack, Opts, Acc) -> zero(Rest, Stack, Opts, "0" ++ Acc); -negative(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> +negative(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> integer(Rest, Stack, Opts, [S] ++ Acc); negative(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> negative(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + negative(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -zero(<>, [object|Stack], Opts, Acc) -> +zero(<>, [object|Stack], Opts, Acc) -> {event, {integer, lists:reverse(Acc)}, fun() -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -zero(<>, [array|Stack], Opts, Acc) -> +zero(<>, [array|Stack], Opts, Acc) -> {event, {integer, lists:reverse(Acc)}, fun() -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -zero(<>, [object|Stack], Opts, Acc) -> - {event, {integer, lists:reverse(Acc)}, fun() -> key(Rest, [key|Stack], Opts) end}; -zero(<>, [array|_] = Stack, Opts, Acc) -> - {event, {integer, lists:reverse(Acc)}, fun() -> value(Rest, Stack, Opts) end}; -zero(<>, Stack, Opts, Acc) -> +zero(<>, [object|Stack], Opts, Acc) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + key(Rest, [key|Stack], Opts) + end}; +zero(<>, [array|_] = Stack, Opts, Acc) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + value(Rest, Stack, Opts) + end}; +zero(<>, Stack, Opts, Acc) -> initial_decimal(Rest, Stack, Opts, [?decimalpoint] ++ Acc); -zero(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> - {event, {integer, lists:reverse(Acc)}, fun() -> maybe_done(Rest, Stack, Opts) end}; -zero(<>, Stack, #opts{comments = true} = Opts, Acc) -> +zero(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + maybe_done(Rest, Stack, Opts) + end}; +zero(<>, Stack, #opts{comments=true}=Opts, Acc) -> maybe_comment(Rest, fun(Resume) -> zero(Resume, Stack, Opts, Acc) end); zero(<<>>, [], Opts, Acc) -> {incomplete, fun(end_stream) -> @@ -519,36 +615,51 @@ zero(<<>>, [], Opts, Acc) -> end}; zero(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> zero(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + zero(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -integer(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> +integer(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> integer(Rest, Stack, Opts, [S] ++ Acc); -integer(<>, [object|Stack], Opts, Acc) -> +integer(<>, [object|Stack], Opts, Acc) -> {event, {integer, lists:reverse(Acc)}, fun() -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -integer(<>, [array|Stack], Opts, Acc) -> +integer(<>, [array|Stack], Opts, Acc) -> {event, {integer, lists:reverse(Acc)}, fun() -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -integer(<>, [object|Stack], Opts, Acc) -> - {event, {integer, lists:reverse(Acc)}, fun() -> key(Rest, [key|Stack], Opts) end}; -integer(<>, [array|_] = Stack, Opts, Acc) -> - {event, {integer, lists:reverse(Acc)}, fun() -> value(Rest, Stack, Opts) end}; -integer(<>, Stack, Opts, Acc) -> +integer(<>, [object|Stack], Opts, Acc) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + key(Rest, [key|Stack], Opts) + end}; +integer(<>, [array|_] = Stack, Opts, Acc) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + value(Rest, Stack, Opts) + end}; +integer(<>, Stack, Opts, Acc) -> initial_decimal(Rest, Stack, Opts, [?decimalpoint] ++ Acc); -integer(<>, Stack, Opts, Acc) -> +integer(<>, Stack, Opts, Acc) -> integer(Rest, Stack, Opts, [?zero] ++ Acc); -integer(<<$e/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +integer(<<$e/?utfx, Rest/binary>>, Stack, Opts, Acc) -> e(Rest, Stack, Opts, "e0." ++ Acc); -integer(<<$E/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +integer(<<$E/?utfx, Rest/binary>>, Stack, Opts, Acc) -> e(Rest, Stack, Opts, "e0." ++ Acc); -integer(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> - {event, {integer, lists:reverse(Acc)}, fun() -> maybe_done(Rest, Stack, Opts) end}; -integer(<>, Stack, #opts{comments = true} = Opts, Acc) -> +integer(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> + {event, {integer, lists:reverse(Acc)}, fun() -> + maybe_done(Rest, Stack, Opts) + end}; +integer(<>, + Stack, + #opts{comments=true}=Opts, + Acc +) -> maybe_comment(Rest, fun(Resume) -> integer(Resume, Stack, Opts, Acc) end); integer(<<>>, [], Opts, Acc) -> {incomplete, fun(end_stream) -> @@ -559,45 +670,70 @@ integer(<<>>, [], Opts, Acc) -> end}; integer(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> integer(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + integer(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -initial_decimal(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> +initial_decimal(<>, Stack, Opts, Acc) + when ?is_nonzero(S) -> decimal(Rest, Stack, Opts, [S] ++ Acc); -initial_decimal(<>, Stack, Opts, Acc) -> +initial_decimal(<>, Stack, Opts, Acc) -> decimal(Rest, Stack, Opts, [?zero] ++ Acc); -initial_decimal(Bin, Stack, Opts, Acc) -> +initial_decimal(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> initial_decimal(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + initial_decimal(<>, + Stack, + Opts, + Acc + ) + end} ; false -> {error, badjson} end. -decimal(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> +decimal(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> decimal(Rest, Stack, Opts, [S] ++ Acc); -decimal(<>, [object|Stack], Opts, Acc) -> +decimal(<>, [object|Stack], Opts, Acc) -> {event, {float, lists:reverse(Acc)}, fun() -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -decimal(<>, [array|Stack], Opts, Acc) -> +decimal(<>, [array|Stack], Opts, Acc) -> {event, {float, lists:reverse(Acc)}, fun() -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -decimal(<>, [object|Stack], Opts, Acc) -> - {event, {float, lists:reverse(Acc)}, fun() -> key(Rest, [key|Stack], Opts) end}; -decimal(<>, [array|_] = Stack, Opts, Acc) -> - {event, {float, lists:reverse(Acc)}, fun() -> value(Rest, Stack, Opts) end}; -decimal(<>, Stack, Opts, Acc) -> +decimal(<>, [object|Stack], Opts, Acc) -> + {event, {float, lists:reverse(Acc)}, fun() -> + key(Rest, [key|Stack], Opts) + end}; +decimal(<>, [array|_] = Stack, Opts, Acc) -> + {event, {float, lists:reverse(Acc)}, fun() -> + value(Rest, Stack, Opts) + end}; +decimal(<>, Stack, Opts, Acc) -> decimal(Rest, Stack, Opts, [?zero] ++ Acc); -decimal(<<$e/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +decimal(<<$e/?utfx, Rest/binary>>, Stack, Opts, Acc) -> e(Rest, Stack, Opts, "e" ++ Acc); -decimal(<<$E/?encoding, Rest/binary>>, Stack, Opts, Acc) -> +decimal(<<$E/?utfx, Rest/binary>>, Stack, Opts, Acc) -> e(Rest, Stack, Opts, "e" ++ Acc); -decimal(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> - {event, {float, lists:reverse(Acc)}, fun() -> maybe_done(Rest, Stack, Opts) end}; -decimal(<>, Stack, #opts{comments = true} = Opts, Acc) -> +decimal(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> + {event, {float, lists:reverse(Acc)}, fun() -> + maybe_done(Rest, Stack, Opts) + end}; +decimal(<>, + Stack, + #opts{comments=true}=Opts, + Acc +) -> maybe_comment(Rest, fun(Resume) -> decimal(Resume, Stack, Opts, Acc) end); decimal(<<>>, [], Opts, Acc) -> {incomplete, fun(end_stream) -> @@ -608,50 +744,74 @@ decimal(<<>>, [], Opts, Acc) -> end}; decimal(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> decimal(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + decimal(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -e(<>, Stack, Opts, Acc) when S =:= ?zero; ?is_nonzero(S) -> +e(<>, Stack, Opts, Acc) + when S =:= ?zero; ?is_nonzero(S) -> exp(Rest, Stack, Opts, [S] ++ Acc); -e(<>, Stack, Opts, Acc) when S =:= ?positive; S =:= ?negative -> +e(<>, Stack, Opts, Acc) + when S =:= ?positive; S =:= ?negative -> ex(Rest, Stack, Opts, [S] ++ Acc); e(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> e(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + e(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -ex(<>, Stack, Opts, Acc) when S =:= ?zero; ?is_nonzero(S) -> +ex(<>, Stack, Opts, Acc) + when S =:= ?zero; ?is_nonzero(S) -> exp(Rest, Stack, Opts, [S] ++ Acc); ex(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> ex(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + ex(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -exp(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> +exp(<>, Stack, Opts, Acc) when ?is_nonzero(S) -> exp(Rest, Stack, Opts, [S] ++ Acc); -exp(<>, [object|Stack], Opts, Acc) -> +exp(<>, [object|Stack], Opts, Acc) -> {event, {float, lists:reverse(Acc)}, fun() -> {event, end_object, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -exp(<>, [array|Stack], Opts, Acc) -> +exp(<>, [array|Stack], Opts, Acc) -> {event, {float, lists:reverse(Acc)}, fun() -> {event, end_array, fun() -> maybe_done(Rest, Stack, Opts) end} end}; -exp(<>, [object|Stack], Opts, Acc) -> - {event, {float, lists:reverse(Acc)}, fun() -> key(Rest, [key|Stack], Opts) end}; -exp(<>, [array|_] = Stack, Opts, Acc) -> - {event, {float, lists:reverse(Acc)}, fun() -> value(Rest, Stack, Opts) end}; -exp(<>, Stack, Opts, Acc) -> +exp(<>, [object|Stack], Opts, Acc) -> + {event, {float, lists:reverse(Acc)}, fun() -> + key(Rest, [key|Stack], Opts) + end}; +exp(<>, [array|_] = Stack, Opts, Acc) -> + {event, {float, lists:reverse(Acc)}, fun() -> + value(Rest, Stack, Opts) + end}; +exp(<>, Stack, Opts, Acc) -> exp(Rest, Stack, Opts, [?zero] ++ Acc); -exp(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> - {event, {float, lists:reverse(Acc)}, fun() -> maybe_done(Rest, Stack, Opts) end}; -exp(<>, Stack, #opts{comments = true} = Opts, Acc) -> +exp(<>, Stack, Opts, Acc) when ?is_whitespace(S) -> + {event, {float, lists:reverse(Acc)}, fun() -> + maybe_done(Rest, Stack, Opts) + end}; +exp(<>, Stack, #opts{comments=true}=Opts, Acc) -> maybe_comment(Rest, fun(Resume) -> exp(Resume, Stack, Opts, Acc) end); exp(<<>>, [], Opts, Acc) -> {incomplete, fun(end_stream) -> @@ -662,132 +822,202 @@ exp(<<>>, [], Opts, Acc) -> end}; exp(Bin, Stack, Opts, Acc) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> exp(<>, Stack, Opts, Acc) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + exp(<>, Stack, Opts, Acc) + end} ; false -> {error, badjson} end. -tr(<<$r/?encoding, Rest/binary>>, Stack, Opts) -> +tr(<<$r/?utfx, Rest/binary>>, Stack, Opts) -> tru(Rest, Stack, Opts); tr(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> tr(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + tr(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -tru(<<$u/?encoding, Rest/binary>>, Stack, Opts) -> +tru(<<$u/?utfx, Rest/binary>>, Stack, Opts) -> true(Rest, Stack, Opts); tru(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> tru(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + tru(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -true(<<$e/?encoding, Rest/binary>>, Stack, Opts) -> +true(<<$e/?utfx, Rest/binary>>, Stack, Opts) -> {event, {literal, true}, fun() -> maybe_done(Rest, Stack, Opts) end}; true(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> true(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + true(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -fa(<<$a/?encoding, Rest/binary>>, Stack, Opts) -> +fa(<<$a/?utfx, Rest/binary>>, Stack, Opts) -> fal(Rest, Stack, Opts); fa(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> fa(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + fa(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -fal(<<$l/?encoding, Rest/binary>>, Stack, Opts) -> +fal(<<$l/?utfx, Rest/binary>>, Stack, Opts) -> fals(Rest, Stack, Opts); fal(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> fal(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + fal(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -fals(<<$s/?encoding, Rest/binary>>, Stack, Opts) -> +fals(<<$s/?utfx, Rest/binary>>, Stack, Opts) -> false(Rest, Stack, Opts); fals(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> fals(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + fals(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -false(<<$e/?encoding, Rest/binary>>, Stack, Opts) -> +false(<<$e/?utfx, Rest/binary>>, Stack, Opts) -> {event, {literal, false}, fun() -> maybe_done(Rest, Stack, Opts) end}; false(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> false(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + false(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -nu(<<$u/?encoding, Rest/binary>>, Stack, Opts) -> +nu(<<$u/?utfx, Rest/binary>>, Stack, Opts) -> nul(Rest, Stack, Opts); nu(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> nu(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + nu(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -nul(<<$l/?encoding, Rest/binary>>, Stack, Opts) -> +nul(<<$l/?utfx, Rest/binary>>, Stack, Opts) -> null(Rest, Stack, Opts); nul(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> nul(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + nul(<>, Stack, Opts) + end} ; false -> {error, badjson} end. -null(<<$l/?encoding, Rest/binary>>, Stack, Opts) -> +null(<<$l/?utfx, Rest/binary>>, Stack, Opts) -> {event, {literal, null}, fun() -> maybe_done(Rest, Stack, Opts) end}; null(Bin, Stack, Opts) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> null(<>, Stack, Opts) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + null(<>, Stack, Opts) + end} ; false -> {error, badjson} end. %% comments are c style, ex: /* blah blah */ -%% any unicode character is valid in a comment except the */ sequence which ends -%% the comment. they're implemented as a closure called when the comment ends that -%% returns execution to the point where the comment began. comments are not -%% reported in any way, simply parsed. -maybe_comment(<>, Resume) -> +%% any unicode character is valid in a comment except the */ sequence which +%% ends the comment. they're implemented as a closure called when the comment +%% ends that returns execution to the point where the comment began. comments +%% are not reported in any way, simply parsed. +maybe_comment(<>, Resume) -> comment(Rest, Resume); maybe_comment(Bin, Resume) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> maybe_comment(<>, Resume) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + maybe_comment(<>, Resume) + end} ; false -> {error, badjson} end. -comment(<>, Resume) -> +comment(<>, Resume) -> maybe_comment_done(Rest, Resume); -comment(<<_/?encoding, Rest/binary>>, Resume) -> +comment(<<_/?utfx, Rest/binary>>, Resume) -> comment(Rest, Resume); comment(Bin, Resume) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> comment(<>, Resume) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + comment(<>, Resume) + end} ; false -> {error, badjson} end. -maybe_comment_done(<>, Resume) -> +maybe_comment_done(<>, Resume) -> Resume(Rest); -maybe_comment_done(<<_/?encoding, Rest/binary>>, Resume) -> +maybe_comment_done(<<_/?utfx, Rest/binary>>, Resume) -> comment(Rest, Resume); maybe_comment_done(Bin, Resume) -> case ?partial_codepoint(Bin) of - true -> {incomplete, fun(end_stream) -> {error, badjson}; (Stream) -> maybe_comment_done(<>, Resume) end} + true -> + {incomplete, fun(end_stream) -> + {error, badjson} + ; (Stream) -> + maybe_comment_done(<>, Resume) + end} ; false -> {error, badjson} end. \ No newline at end of file diff --git a/src/jsx.erl b/src/jsx.erl index 3fb380e..649ef58 100644 --- a/src/jsx.erl +++ b/src/jsx.erl @@ -26,7 +26,6 @@ %% @version really, really beta %% @doc this module defines the interface to the jsx json parsing library - -module(jsx). @@ -70,7 +69,11 @@ %% | {multi_term, true | false} %% | {encoding, auto | supported_utf()}. -%% @type supported_utf() = utf8 | utf16 | {utf16, little} | utf32 | {utf32, little}. +%% @type supported_utf() = utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little}. %% @type eep0018() = eep0018_object() | eep0018_array(). @@ -79,7 +82,13 @@ %% @type eep0018_key() = binary() | atom(). -%% @type eep0018_term() = eep0018_array() | eep0018_object() | eep0018_string() | eep0018_number() | true | false | null. +%% @type eep0018_term() = eep0018_array() +%% | eep0018_object() +%% | eep0018_string() +%% | eep0018_number() +%% | true +%% | false +%% | null. %% @type eep0018_string() = binary(). @@ -127,7 +136,8 @@ parser() -> %% @spec parser(Opts::jsx_opts()) -> jsx_parser() %% @doc -%% produces a function which takes a binary which may or may not represent an encoded json document and returns a generator +%% produces a function which takes a binary which may or may not represent an +%% encoded json document and returns a generator %% %% options: %%
    @@ -137,17 +147,23 @@ parser() -> %% false

    %% %%
  • {encoded_unicode, ascii | codepoint | none} -%%

    if a \uXXXX escape sequence is encountered within a key or string, -%% this option controls how it is interpreted. none makes no attempt -%% to interpret the value, leaving it unconverted. ascii will convert -%% any value that falls within the ascii range. codepoint will convert -%% any value that is a valid unicode codepoint. note that unicode -%% non-characters (including badly formed surrogates) will never be -%% converted. codepoint is the default

  • +%%

    if a \uXXXX escape sequence is encountered within a key or +%% string, this option controls how it is interpreted. none makes no +%% attempt to interpret the value, leaving it unconverted. ascii will +%% convert any value that falls within the ascii range. codepoint will +%% convert any value that is a valid unicode codepoint. note that +%% unicode non-characters (including badly formed surrogates) will +%% never be converted. codepoint is the default

    %% -%%
  • {encoding, auto | utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } -%%

    attempt to parse the binary using the specified encoding. auto will -%% auto detect any supported encoding and is the default

  • +%%
  • {encoding, auto +%% | utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } +%%

    attempt to parse the binary using the specified encoding. auto +%% will auto detect any supported encoding and is the default

  • %% %%
  • {multi_term, true | false} %%

    usually, documents will be parsed in full before the end_json @@ -183,25 +199,32 @@ json_to_term(JSON) -> %% options: %%

      %%
    • {strict, true | false} -%%

      by default, attempting to convert unwrapped json values (numbers, strings and -%% the atoms true, false and null) result in a badarg exception. if strict equals -%% false, these are instead decoded to their equivalent eep0018 value. default is -%% false

      +%%

      by default, attempting to convert unwrapped json values (numbers, +%% strings and the atoms true, false and null) result in a badarg +%% exception. if strict equals false, these are instead decoded to +%% their equivalent eep0018 value. default is false

      %% -%%

      note that there is a problem of ambiguity when parsing unwrapped json -%% numbers that requires special handling

      +%%

      note that there is a problem of ambiguity when parsing unwrapped +%% json numbers that requires special handling

      %% -%%

      an unwrapped json number has no unambiguous end marker like a json object, -%% array or string. `1', `12' and `123' may all represent either a complete json -%% number or just the beginning of one. in this case, the parser will always -%% return `{incomplete, More}' rather than potentially terminate before input -%% is exhausted. to force termination, `More/1' may be called with the atom -%% `end_stream' as it's argument. note also that numbers followed by whitespace -%% will be parsed correctly

    • +%%

      an unwrapped json number has no unambiguous end marker like a +%% json object, array or string. `1', `12' and `123' may all represent +%% either a complete json number or just the beginning of one. in this +%% case, the parser will always return `{incomplete, More}' rather than +%% potentially terminate before input is exhausted. to force +%% termination, `More/1' may be called with the atom `end_stream' as +%% it's argument. note also that numbers followed by whitespace will be +%% parsed correctly

      %% -%%
    • {encoding, auto | utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } -%%

      assume the binary is encoded using the specified binary. default is auto, which -%% attempts to autodetect the encoding

    • +%%
    • {encoding, auto +%% | utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } +%%

      assume the binary is encoded using the specified binary. default +%% is auto, which attempts to autodetect the encoding

    • %% %%
    • {comments, true | false} %%

      if true, json documents that contain c style (/* ... */) comments @@ -230,29 +253,38 @@ term_to_json(JSON) -> %% @spec term_to_json(JSON::eep0018(), Opts::encoder_opts()) -> binary() %% @doc -%% takes the erlang representation of a json object (as defined in eep0018) and returns a (binary encoded) json string +%% takes the erlang representation of a json object (as defined in eep0018) and +%% returns a (binary encoded) json string %% %% options: %%

        %%
      • {strict, true | false} %%

        by default, attempting to convert unwrapped json values (numbers, -%% strings and the atoms true, false and null) result in a badarg exception. -%% if strict equals false, these are instead json encoded. default is false

      • +%% strings and the atoms true, false and null) result in a badarg +%% exception. if strict equals false, these are instead json encoded. +%% default is false

        %% -%%
      • {encoding, utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } +%%
      • {encoding, utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } %%

        the encoding of the resulting binary. default is utf8

      • %% %%
      • space %%

        space is equivalent to {space, 1}

      • %% %%
      • {space, N} -%%

        place N spaces after each colon and comma in the resulting binary. default is zero

      • +%%

        place N spaces after each colon and comma in the resulting +%% binary. default is zero

        %% %%
      • indent %%

        indent is equivalent to {indent, 1}

      • %% %%
      • {indent, N} -%%

        indent each 'level' of the json structure by N spaces. default is zero

      • +%%

        indent each 'level' of the json structure by N spaces. default is +%% zero

        %%
      %% @end @@ -273,13 +305,19 @@ is_json(JSON) -> %% options: %%
        %%
      • {strict, true | false} -%%

        by default, unwrapped json values (numbers, strings and the atoms -%% true, false and null) return false. if strict equals true, is_json -%% returns true. default is false

      • +%%

        by default, unwrapped json values (numbers, strings and the +%% atoms true, false and null) return false. if strict equals true, +%% is_json returns true. default is false

        %% -%%
      • {encoding, auto | utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } -%%

        assume the binary is encoded using the specified binary. default is auto, -%% which attempts to autodetect the encoding

      • +%%
      • {encoding, auto +%% | utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } +%%

        assume the binary is encoded using the specified binary. default +%% is auto, which attempts to autodetect the encoding

      • %% %%
      • {comments, true | false} %%

        if true, json documents that contain c style (/* ... */) comments @@ -300,20 +338,32 @@ format(JSON) -> %% @spec format(JSON::binary(), Opts::format_opts()) -> binary() %% @doc -%% formats a binary encoded json string according to the options chose. the defaults will produced a string stripped of all whitespace +%% formats a binary encoded json string according to the options chose. the +%% defaults will produced a string stripped of all whitespace %% %% options: %%

          %%
        • {strict, true | false} -%%

          by default, unwrapped json values (numbers, strings and the atoms -%% true, false and null) result in an error. if strict equals true, they -%% are treated as valid json. default is false

        • +%%

          by default, unwrapped json values (numbers, strings and the +%% atoms true, false and null) result in an error. if strict equals +%% true, they are treated as valid json. default is false

          %% -%%
        • {encoding, auto | utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } -%%

          assume the binary is encoded using the specified binary. default is auto, -%% which attempts to autodetect the encoding

        • +%%
        • {encoding, auto +%% | utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } +%%

          assume the binary is encoded using the specified binary. default +%% is auto, which attempts to autodetect the encoding

        • %% -%%
        • {output_encoding, utf8 | utf16 | {utf16, little} | utf32 | {utf32, little} } +%%
        • {encoding, utf8 +%% | utf16 +%% | {utf16, little} +%% | utf32 +%% | {utf32, little} +%% } %%

          the encoding of the resulting binary. default is utf8

        • %% %%
        • {comments, true | false} @@ -325,13 +375,15 @@ format(JSON) -> %%

          space is equivalent to {space, 1}

        • %% %%
        • {space, N} -%%

          place N spaces after each colon and comma in the resulting binary. default is zero

        • +%%

          place N spaces after each colon and comma in the resulting +%% binary. default is zero

          %% %%
        • indent %%

          indent is equivalent to {indent, 1}

        • %% %%
        • {indent, N} -%%

          indent each 'level' of the json structure by N spaces. default is zero

        • +%%

          indent each 'level' of the json structure by N spaces. default is +%% zero

          %%
        %% @end @@ -340,9 +392,17 @@ format(JSON, Opts) -> %% @spec eventify(List::list()) -> jsx_parser_result() -%% @doc fake the jsx api for any list. useful if you want to serialize a structure to json using the pretty printer, or verify a sequence could be valid json +%% @doc fake the jsx api for any list. useful if you want to serialize a +%% structure to json using the pretty printer, or verify a sequence could be +%% valid json eventify([]) -> - fun() -> {incomplete, fun(List) when is_list(List) -> eventify(List); (_) -> erlang:error(badarg) end} end; + fun() -> + {incomplete, fun(List) when is_list(List) -> + eventify(List) + ; (_) -> + erlang:error(badarg) + end} + end; eventify([Next|Rest]) -> fun() -> {event, Next, eventify(Rest)} end. @@ -352,43 +412,50 @@ eventify([Next|Rest]) -> %% encoding detection -%% first check to see if there's a bom, if not, use the rfc4627 method for determining -%% encoding. this function makes some assumptions about the validity of the stream -%% which may delay failure later than if an encoding is explicitly provided +%% first check to see if there's a bom, if not, use the rfc4627 method for +%% determining encoding. this function makes some assumptions about the +%% validity of the stream which may delay failure later than if an encoding is +%% explicitly provided detect_encoding(OptsList) -> fun(Stream) -> detect_encoding(Stream, OptsList) end. %% utf8 bom detection -detect_encoding(<<16#ef, 16#bb, 16#bf, Rest/binary>>, Opts) -> (jsx_utf8:parser(Opts))(Rest); -%% utf32-little bom detection (this has to come before utf16-little or it'll match that) -detect_encoding(<<16#ff, 16#fe, 0, 0, Rest/binary>>, Opts) -> (jsx_utf32le:parser(Opts))(Rest); +detect_encoding(<<16#ef, 16#bb, 16#bf, Rest/binary>>, Opts) -> + (jsx_utf8:parser(Opts))(Rest); +%% utf32-little bom detection (this has to come before utf16-little or it'll +%% match that) +detect_encoding(<<16#ff, 16#fe, 0, 0, Rest/binary>>, Opts) -> + (jsx_utf32le:parser(Opts))(Rest); %% utf16-big bom detection -detect_encoding(<<16#fe, 16#ff, Rest/binary>>, Opts) -> (jsx_utf16:parser(Opts))(Rest); +detect_encoding(<<16#fe, 16#ff, Rest/binary>>, Opts) -> + (jsx_utf16:parser(Opts))(Rest); %% utf16-little bom detection -detect_encoding(<<16#ff, 16#fe, Rest/binary>>, Opts) -> (jsx_utf16le:parser(Opts))(Rest); +detect_encoding(<<16#ff, 16#fe, Rest/binary>>, Opts) -> + (jsx_utf16le:parser(Opts))(Rest); %% utf32-big bom detection -detect_encoding(<<0, 0, 16#fe, 16#ff, Rest/binary>>, Opts) -> (jsx_utf32:parser(Opts))(Rest); +detect_encoding(<<0, 0, 16#fe, 16#ff, Rest/binary>>, Opts) -> + (jsx_utf32:parser(Opts))(Rest); %% utf32-little null order detection detect_encoding(<> = JSON, Opts) when X =/= 0 -> (jsx_utf32le:parser(Opts))(JSON); -%% utf16-big null order detection -detect_encoding(<<0, X, 0, Y, _Rest/binary>> = JSON, Opts) when X =/= 0, Y =/= 0 -> - (jsx_utf16:parser(Opts))(JSON); -%% utf16-little null order detection -detect_encoding(<> = JSON, Opts) when X =/= 0, Y =/= 0 -> - (jsx_utf16le:parser(Opts))(JSON); %% utf32-big null order detection detect_encoding(<<0, 0, 0, X, _Rest/binary>> = JSON, Opts) when X =/= 0 -> (jsx_utf32:parser(Opts))(JSON); +%% utf16-little null order detection +detect_encoding(<> = JSON, Opts) when X =/= 0 -> + (jsx_utf16le:parser(Opts))(JSON); +%% utf16-big null order detection +detect_encoding(<<0, X, 0, _, _Rest/binary>> = JSON, Opts) when X =/= 0 -> + (jsx_utf16:parser(Opts))(JSON); %% utf8 null order detection detect_encoding(<> = JSON, Opts) when X =/= 0, Y =/= 0 -> (jsx_utf8:parser(Opts))(JSON); -%% a problem, to autodetect naked single digits' encoding, there is not enough data -%% to conclusively determine the encoding correctly. below is an attempt to solve -%% the problem +%% a problem, to autodetect naked single digits' encoding, there is not enough +%% data to conclusively determine the encoding correctly. below is an attempt +%% to solve the problem detect_encoding(<>, Opts) when X =/= 0 -> {incomplete, fun(end_stream) -> diff --git a/src/jsx_eep0018.erl b/src/jsx_eep0018.erl index 465907d..fdbe1af 100644 --- a/src/jsx_eep0018.erl +++ b/src/jsx_eep0018.erl @@ -33,7 +33,6 @@ -include("./include/jsx_common.hrl"). - -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. @@ -48,10 +47,10 @@ json_to_term(JSON, Opts) -> end. -%% the jsx formatter (pretty printer) can do most of the heavy lifting in converting erlang -%% terms to json strings, but it expects a jsx event iterator. luckily, the mapping from -%% erlang terms to jsx events is straightforward and the iterator can be faked with an -%% anonymous function +%% the jsx formatter (pretty printer) can do most of the heavy lifting in +%% converting erlang terms to json strings, but it expects a jsx event +%% iterator. luckily, the mapping from erlang terms to jsx events is +%% straightforward and the iterator can be faked with an anonymous function term_to_json(List, Opts) -> case proplists:get_value(strict, Opts, true) of true when is_list(List) -> continue @@ -59,7 +58,9 @@ term_to_json(List, Opts) -> ; false -> continue end, Encoding = proplists:get_value(encoding, Opts, utf8), - jsx:format(jsx:eventify(lists:reverse([end_json] ++ term_to_events(List))), [{output_encoding, Encoding}] ++ Opts). + jsx:format(jsx:eventify(lists:reverse([end_json] ++ term_to_events(List))), + [{output_encoding, Encoding}] ++ Opts + ). %% parse opts for the decoder @@ -67,7 +68,9 @@ opts_to_jsx_opts(Opts) -> opts_to_jsx_opts(Opts, []). opts_to_jsx_opts([{encoding, Val}|Rest], Acc) -> - case lists:member(Val, [auto, utf8, utf16, {utf16, little}, utf32, {utf32, little}]) of + case lists:member(Val, + [auto, utf8, utf16, {utf16, little}, utf32, {utf32, little}] + ) of true -> opts_to_jsx_opts(Rest, [{encoding, Val}] ++ Acc) ; false -> opts_to_jsx_opts(Rest, Acc) end; @@ -85,49 +88,55 @@ opts_to_jsx_opts([], Acc) -> %% ensure the first jsx event we get is start_object or start_array when running %% in strict mode -collect_strict({event, Start, Next}, Acc, Opts) when Start =:= start_object; Start =:= start_array -> +collect_strict({event, Start, Next}, Acc, Opts) + when Start =:= start_object; Start =:= start_array -> collect(Next(), [[]|Acc], Opts); collect_strict(_, _, _) -> erlang:error(badarg). %% collect decoder events and convert to eep0018 format -collect({event, Start, Next}, Acc, Opts) when Start =:= start_object; Start =:= start_array -> +collect({event, Start, Next}, Acc, Opts) + when Start =:= start_object; Start =:= start_array -> collect(Next(), [[]|Acc], Opts); %% special case for empty object -collect({event, end_object, Next}, [[], Parent|Rest], Opts) when is_list(Parent) -> +collect({event, end_object, Next}, [[], Parent|Rest], Opts) + when is_list(Parent) -> collect(Next(), [[[{}]] ++ Parent] ++ Rest, Opts); %% reverse the array/object accumulator before prepending it to it's parent -collect({event, end_object, Next}, [Current, Parent|Rest], Opts) when is_list(Parent) -> +collect({event, end_object, Next}, [Current, Parent|Rest], Opts) + when is_list(Parent) -> collect(Next(), [[lists:reverse(Current)] ++ Parent] ++ Rest, Opts); -collect({event, end_array, Next}, [Current, Parent|Rest], Opts) when is_list(Parent) -> +collect({event, end_array, Next}, [Current, Parent|Rest], Opts) + when is_list(Parent) -> collect(Next(), [[lists:reverse(Current)] ++ Parent] ++ Rest, Opts); %% special case for empty object collect({event, end_object, Next}, [[], Key, Parent|Rest], Opts) -> collect(Next(), [[{Key, [{}]}] ++ Parent] ++ Rest, Opts); collect({event, End, Next}, [Current, Key, Parent|Rest], Opts) - when End =:= end_object; End =:= end_array -> + when End =:= end_object; End =:= end_array -> collect(Next(), [[{Key, lists:reverse(Current)}] ++ Parent] ++ Rest, Opts); collect({event, end_json, _Next}, [[Acc]], _Opts) -> Acc; -%% key can only be emitted inside of a json object, so just insert it directly into -%% the head of the accumulator and deal with it when we receive it's paired value +%% key can only be emitted inside of a json object, so just insert it directly +%% into the head of the accumulator and deal with it when we receive it's +%% paired value collect({event, {key, _} = PreKey, Next}, [Current|_] = Acc, Opts) -> Key = event(PreKey, Opts), case decode_key_repeats(Key, Current) of true -> erlang:error(badarg) ; false -> collect(Next(), [Key] ++ Acc, Opts) end; -%% check acc to see if we're inside an object or an array. because inside an object -%% context the events that fall this far are always preceded by a key (which are -%% binaries or atoms), if Current is a list, we're inside an array, else, an -%% object +%% check acc to see if we're inside an object or an array. because inside an +%% object context the events that fall this far are always preceded by a key +%% (which are binaries or atoms), if Current is a list, we're inside an array, +%% else, an object collect({event, Event, Next}, [Current|Rest], Opts) when is_list(Current) -> collect(Next(), [[event(Event, Opts)] ++ Current] ++ Rest, Opts); collect({event, Event, Next}, [Key, Current|Rest], Opts) -> collect(Next(), [[{Key, event(Event, Opts)}] ++ Current] ++ Rest, Opts); -%% if our first returned event is {incomplete, ...} try to force end and return the -%% Event if one is returned +%% if our first returned event is {incomplete, ...} try to force end and return +%% the Event if one is returned collect({incomplete, More}, [[]], Opts) -> case More(end_stream) of {event, Event, _Next} -> event(Event, Opts) @@ -170,7 +179,8 @@ decode_key_repeats(_Key, []) -> false. -%% convert eep0018 representation to jsx events. note special casing for the empty object +%% convert eep0018 representation to jsx events. note special casing for the +%% empty object term_to_events([{}]) -> [end_object, start_object]; term_to_events([First|_] = List) when is_tuple(First) -> @@ -203,7 +213,7 @@ list_to_events([], Acc) -> term_to_event(List) when is_list(List) -> term_to_events(List); term_to_event(Float) when is_float(Float) -> - [{float, float_to_decimal(Float)}]; + [{float, nice_decimal(Float)}]; term_to_event(Integer) when is_integer(Integer) -> [{integer, erlang:integer_to_list(Integer)}]; term_to_event(String) when is_binary(String) -> @@ -222,24 +232,32 @@ key_to_event(Key) when is_binary(Key) -> encode_key_repeats([Key], SoFar) -> encode_key_repeats(Key, SoFar, 0). -encode_key_repeats(Key, [Key|_], 0) -> true; -encode_key_repeats(Key, [end_object|Rest], Level) -> encode_key_repeats(Key, Rest, Level + 1); -encode_key_repeats(_, [start_object|_], 0) -> false; -encode_key_repeats(Key, [start_object|Rest], Level) -> encode_key_repeats(Key, Rest, Level - 1); -encode_key_repeats(Key, [_|Rest], Level) -> encode_key_repeats(Key, Rest, Level); -encode_key_repeats(_, [], 0) -> false. +encode_key_repeats(Key, [Key|_], 0) -> + true; +encode_key_repeats(Key, [end_object|Rest], Level) -> + encode_key_repeats(Key, Rest, Level + 1); +encode_key_repeats(_, [start_object|_], 0) -> + false; +encode_key_repeats(Key, [start_object|Rest], Level) -> + encode_key_repeats(Key, Rest, Level - 1); +encode_key_repeats(Key, [_|Rest], Level) -> + encode_key_repeats(Key, Rest, Level); +encode_key_repeats(_, [], 0) -> + false. -%% conversion of floats to 'nice' decimal output. erlang's float implementation is almost -%% but not quite ieee 754. it converts negative zero to plain zero silently, and throws -%% exceptions for any operations that would produce NaN or infinity. as far as I can -%% tell that is. trying to match against NaN or infinity binary patterns produces nomatch -%% exceptions, and arithmetic operations produce badarg exceptions. with that in mind, this -%% function makes no attempt to handle special values (except for zero) +%% conversion of floats to 'nice' decimal output. erlang's float implementation +%% is almost but not quite ieee 754. it converts negative zero to plain zero +%% silently, and throws exceptions for any operations that would produce NaN +%% or infinity. as far as I can tell that is. trying to match against NaN or +%% infinity binary patterns produces nomatch exceptions, and arithmetic +%% operations produce badarg exceptions. with that in mind, this function +%% makes no attempt to handle special values (except for zero) -%% algorithm from "Printing FLoating-Point Numbers Quickly and Accurately" by Burger & Dybvig -float_to_decimal(0.0) -> "0.0"; -float_to_decimal(Num) when is_float(Num) -> +%% algorithm from "Printing FLoating-Point Numbers Quickly and Accurately" by +%% Burger & Dybvig +nice_decimal(0.0) -> "0.0"; +nice_decimal(Num) when is_float(Num) -> {F, E} = extract(<>), {R, S, MP, MM} = initial_vals(F, E), K = ceiling(math:log10(abs(Num)) - 1.0e-10), @@ -315,7 +333,8 @@ generate(RT, S, MP, MM, Round) -> end. -%% this is not efficient at all and should be replaced with a lookup table probably +%% this is not efficient at all and should be replaced with a lookup table +%% probably pow(_B, 0) -> 1; pow(B, E) when E > 0 -> pow(B, E, 1). @@ -331,8 +350,10 @@ format(Dpoint, Digits) when Dpoint =< length(Digits), Dpoint > 0 -> format(Dpoint, Digits) when Dpoint > 0 -> Pad = Dpoint - length(Digits), case Pad of - X when X > 6 -> format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) - ; _ -> format(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) + X when X > 6 -> + format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) + ; _ -> + format(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) end; format(Dpoint, Digits) when Dpoint < 0 -> format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1). @@ -344,32 +365,41 @@ format([], ignore, Acc) -> format(Digits, 0, Acc) -> format(Digits, ignore, "." ++ Acc); format([Digit|Digits], Dpoint, Acc) -> - format(Digits, case Dpoint of ignore -> ignore; X -> X - 1 end, to_ascii(Digit) ++ Acc). + format(Digits, + case Dpoint of ignore -> ignore; X -> X - 1 end, to_ascii(Digit) ++ Acc + ). to_ascii(X) -> [X + 48]. %% ascii "1" is [49], "2" is [50], etc... -%% json string escaping, for utf8 binaries. escape the json control sequences to their -%% json equivalent, escape other control characters to \uXXXX sequences, everything -%% else should be a legal json string component +%% json string escaping, for utf8 binaries. escape the json control sequences to +%% their json equivalent, escape other control characters to \uXXXX sequences, +%% everything else should be a legal json string component json_escape(String) -> json_escape(String, <<>>). %% double quote -json_escape(<<$\", Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\", Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% backslash \ reverse solidus -json_escape(<<$\\, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\\, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% backspace -json_escape(<<$\b, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\b, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% form feed -json_escape(<<$\f, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\f, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% newline -json_escape(<<$\n, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\n, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% cr -json_escape(<<$\r, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\r, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% tab -json_escape(<<$\t, Rest/binary>>, Acc) -> json_escape(Rest, <>); +json_escape(<<$\t, Rest/binary>>, Acc) -> + json_escape(Rest, <>); %% other control characters json_escape(<>, Acc) when C >= 0, C < $\s -> json_escape(Rest, <>); @@ -382,8 +412,8 @@ json_escape(_, _) -> erlang:error(badarg). -%% convert a codepoint to it's \uXXXX equiv. for laziness, this only handles codepoints -%% this module might escape, ie, control characters +%% convert a codepoint to it's \uXXXX equiv. for laziness, this only handles +%% codepoints this module might escape, ie, control characters json_escape_sequence(C) when C < 16#20 -> <<_:8, A:4, B:4>> = <>, % first two hex digits are always zero <<$\\, $u, $0, $0, (to_hex(A)), (to_hex(B))>>. @@ -405,64 +435,194 @@ decode_test_() -> [ {"empty object", ?_assert(json_to_term(<<"{}">>, []) =:= [{}])}, {"empty array", ?_assert(json_to_term(<<"[]">>, []) =:= [])}, - {"simple object", ?_assert(json_to_term(<<"{\"a\": true, \"b\": true, \"c\": true}">>, [{label, atom}]) =:= [{a, true}, {b, true}, {c, true}])}, - {"simple array", ?_assert(json_to_term(<<"[true,true,true]">>, []) =:= [true, true, true])}, - {"nested structures", ?_assert(json_to_term(<<"{\"list\":[{\"list\":[{}, {}],\"object\":{}}, []],\"object\":{}}">>, [{label, atom}]) =:= [{list, [[{list, [[{}], [{}]]}, {object, [{}]}],[]]}, {object, [{}]}])}, - {"numbers", ?_assert(json_to_term(<<"[-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0]">>, []) =:= [-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0])}, - {"numbers (all floats)", ?_assert(json_to_term(<<"[-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0]">>, [{float, true}]) =:= [-10000000000.0, -1.0, 0.0, 0.0, 1.0, 10000000000.0, 1000000000.0])}, - {"strings", ?_assert(json_to_term(<<"[\"a string\"]">>, []) =:= [<<"a string">>])}, - {"literals", ?_assert(json_to_term(<<"[true,false,null]">>, []) =:= [true,false,null])}, - {"naked true", ?_assert(json_to_term(<<"true">>, [{strict, false}]) =:= true)}, - {"naked short number", ?_assert(json_to_term(<<"1">>, [{strict, false}]) =:= 1)}, + {"simple object", + ?_assert(json_to_term( + <<"{\"a\": true, \"b\": true, \"c\": true}">>, + [{label, atom}] + ) =:= [{a, true}, {b, true}, {c, true}] + ) + }, + {"simple array", + ?_assert(json_to_term(<<"[true,true,true]">>, + [] + ) =:= [true, true, true] + ) + }, + {"nested structures", + ?_assert(json_to_term( + <<"{\"x\":[{\"x\":[{}, {}],\"y\":{}}, []],\"y\":{}}">>, + [{label, atom}] + ) =:= [{x, [[{x, [[{}], [{}]]}, {y, [{}]}],[]]}, {y, [{}]}] + ) + }, + {"numbers", + ?_assert(json_to_term( + <<"[-100000000.0, -1, 0.0, 0, 1, 100000000, 10000000.0]">>, + [] + ) =:= [-100000000.0, -1, 0.0, 0, 1, 100000000, 10000000.0] + ) + }, + {"numbers (all floats)", + ?_assert(json_to_term( + <<"[-100000000.0, -1, 0.0, 0, 1, 1000, 10000000.0]">>, + [{float, true}] + ) =:= [-100000000.0, -1.0, 0.0, 0.0, 1.0, 1000.0, 10000000.0] + ) + }, + {"strings", + ?_assert(json_to_term(<<"[\"a string\"]">>, + [] + ) =:= [<<"a string">>]) + }, + {"literals", + ?_assert(json_to_term(<<"[true,false,null]">>, + [] + ) =:= [true,false,null] + ) + }, + {"naked true", + ?_assert(json_to_term(<<"true">>, [{strict, false}]) =:= true) + }, + {"naked short number", + ?_assert(json_to_term(<<"1">>, [{strict, false}]) =:= 1) + }, {"float", ?_assert(json_to_term(<<"1.0">>, [{strict, false}]) =:= 1.0)}, - {"naked string", ?_assert(json_to_term(<<"\"hello world\"">>, [{strict, false}]) =:= <<"hello world">>)}, - {"comments", ?_assert(json_to_term(<<"[ /* a comment in an empty array */ ]">>, [{comments, true}]) =:= [])} + {"naked string", + ?_assert(json_to_term(<<"\"hello world\"">>, + [{strict, false}] + ) =:= <<"hello world">> + ) + }, + {"comments", + ?_assert(json_to_term(<<"[ /* a comment in an empty array */ ]">>, + [{comments, true}] + ) =:= [] + ) + } ]. encode_test_() -> [ {"empty object", ?_assert(term_to_json([{}], []) =:= <<"{}">>)}, {"empty array", ?_assert(term_to_json([], []) =:= <<"[]">>)}, - {"simple object", ?_assert(term_to_json([{a, true}, {b, true}, {c, true}], []) =:= <<"{\"a\":true,\"b\":true,\"c\":true}">>)}, - {"simple array", ?_assert(term_to_json([true, true, true], []) =:= <<"[true,true,true]">>)}, - {"nested structures", ?_assert(term_to_json([{list, [[{list, [[{}], [{}]]}, {object, [{}]}],[]]}, {object, [{}]}], []) =:= <<"{\"list\":[{\"list\":[{},{}],\"object\":{}},[]],\"object\":{}}">>)}, - {"numbers", ?_assert(term_to_json([-10000000000.0, -1, 0.0, 0, 1, 10000000000, 1000000000.0], []) =:= <<"[-1.0e10,-1,0.0,0,1,10000000000,1.0e9]">>)}, - {"strings", ?_assert(term_to_json([<<"a string">>], []) =:= <<"[\"a string\"]">>)}, - {"literals", ?_assert(term_to_json([true,false,null], []) =:= <<"[true,false,null]">>)}, - {"naked true", ?_assert(term_to_json(true, [{strict, false}]) =:= <<"true">>)}, - {"naked number", ?_assert(term_to_json(1, [{strict, false}]) =:= <<"1">>)}, + {"simple object", + ?_assert(term_to_json([{a, true}, {b, true}, {c, true}], + [] + ) =:= <<"{\"a\":true,\"b\":true,\"c\":true}">> + ) + }, + {"simple array", + ?_assert(term_to_json([true, true, true], + [] + ) =:= <<"[true,true,true]">> + ) + }, + {"nested structures", + ?_assert(term_to_json( + [{x, [[{x, [[{}], [{}]]}, {y, [{}]}],[]]}, {y, [{}]}], + [] + ) =:= <<"{\"x\":[{\"x\":[{},{}],\"y\":{}},[]],\"y\":{}}">> + ) + }, + {"numbers", + ?_assert(term_to_json( + [-10000000000.0, -1, 0.0, 0, 1, 10000000, 1000000000.0], + [] + ) =:= <<"[-1.0e10,-1,0.0,0,1,10000000,1.0e9]">> + ) + }, + {"strings", + ?_assert(term_to_json([<<"a string">>], + [] + ) =:= <<"[\"a string\"]">> + ) + }, + {"literals", + ?_assert(term_to_json([true,false,null], + [] + ) =:= <<"[true,false,null]">> + ) + }, + {"naked true", + ?_assert(term_to_json(true, [{strict, false}]) =:= <<"true">>) + }, + {"naked number", + ?_assert(term_to_json(1, [{strict, false}]) =:= <<"1">>) + }, {"float", ?_assert(term_to_json(1.0, [{strict, false}]) =:= <<"1.0">>)}, - {"naked string", ?_assert(term_to_json(<<"hello world">>, [{strict, false}]) =:= <<"\"hello world\"">>)} + {"naked string", + ?_assert(term_to_json(<<"hello world">>, + [{strict, false}] + ) =:= <<"\"hello world\"">> + ) + } ]. repeated_keys_test_() -> [ - {"encode", ?_assertError(badarg, term_to_json([{k, true}, {k, false}], []))}, - {"decode", ?_assertError(badarg, json_to_term(<<"{\"k\": true, \"k\": false}">>, []))} + {"encode", + ?_assertError(badarg, term_to_json([{k, true}, {k, false}], [])) + }, + {"decode", + ?_assertError(badarg, json_to_term( + <<"{\"k\": true, \"k\": false}">>, + [] + ) + ) + } ]. escape_test_() -> [ - {"json string escaping", ?_assert(json_escape(<<"\"\\\b\f\n\r\t">>) =:= <<"\\\"\\\\\\b\\f\\n\\r\\t">>)}, - {"json string hex escape", ?_assert(json_escape(<<1, 2, 3, 11, 26, 30, 31>>) =:= <<"\\u0001\\u0002\\u0003\\u000b\\u001a\\u001e\\u001f">>)} + {"json string escaping", + ?_assert(json_escape( + <<"\"\\\b\f\n\r\t">> + ) =:= <<"\\\"\\\\\\b\\f\\n\\r\\t">> + ) + }, + {"json string hex escape", + ?_assert(json_escape( + <<1, 2, 3, 11, 26, 30, 31>> + ) =:= <<"\\u0001\\u0002\\u0003\\u000b\\u001a\\u001e\\u001f">> + ) + } ]. nice_decimal_test_() -> [ - {"0.0", ?_assert(float_to_decimal(0.0) =:= "0.0")}, - {"1.0", ?_assert(float_to_decimal(1.0) =:= "1.0")}, - {"-1.0", ?_assert(float_to_decimal(-1.0) =:= "-1.0")}, - {"3.1234567890987654321", ?_assert(float_to_decimal(3.1234567890987654321) =:= "3.1234567890987655")}, - {"1.0e23", ?_assert(float_to_decimal(1.0e23) =:= "1.0e23")}, - {"0.3", ?_assert(float_to_decimal(3.0/10.0) =:= "0.3")}, - {"0.0001", ?_assert(float_to_decimal(0.0001) =:= "1.0e-4")}, - {"0.00000001", ?_assert(float_to_decimal(0.00000001) =:= "1.0e-8")}, - {"1.0e-323", ?_assert(float_to_decimal(1.0e-323) =:= "1.0e-323")}, - {"1.0e308", ?_assert(float_to_decimal(1.0e308) =:= "1.0e308")}, - {"min normalized float", ?_assert(float_to_decimal(math:pow(2, -1022)) =:= "2.2250738585072014e-308")}, - {"max normalized float", ?_assert(float_to_decimal((2 - math:pow(2, -52)) * math:pow(2, 1023)) =:= "1.7976931348623157e308")}, - {"min denormalized float", ?_assert(float_to_decimal(math:pow(2, -1074)) =:= "5.0e-324")}, - {"max denormalized float", ?_assert(float_to_decimal((1 - math:pow(2, -52)) * math:pow(2, -1022)) =:= "2.225073858507201e-308")} + {"0.0", ?_assert(nice_decimal(0.0) =:= "0.0")}, + {"1.0", ?_assert(nice_decimal(1.0) =:= "1.0")}, + {"-1.0", ?_assert(nice_decimal(-1.0) =:= "-1.0")}, + {"3.1234567890987654321", + ?_assert( + nice_decimal(3.1234567890987654321) =:= "3.1234567890987655") + }, + {"1.0e23", ?_assert(nice_decimal(1.0e23) =:= "1.0e23")}, + {"0.3", ?_assert(nice_decimal(3.0/10.0) =:= "0.3")}, + {"0.0001", ?_assert(nice_decimal(0.0001) =:= "1.0e-4")}, + {"0.00000001", ?_assert(nice_decimal(0.00000001) =:= "1.0e-8")}, + {"1.0e-323", ?_assert(nice_decimal(1.0e-323) =:= "1.0e-323")}, + {"1.0e308", ?_assert(nice_decimal(1.0e308) =:= "1.0e308")}, + {"min normalized float", + ?_assert( + nice_decimal(math:pow(2, -1022)) =:= "2.2250738585072014e-308" + ) + }, + {"max normalized float", + ?_assert( + nice_decimal((2 - math:pow(2, -52)) * math:pow(2, 1023)) + =:= "1.7976931348623157e308" + ) + }, + {"min denormalized float", + ?_assert(nice_decimal(math:pow(2, -1074)) =:= "5.0e-324") + }, + {"max denormalized float", + ?_assert( + nice_decimal((1 - math:pow(2, -52)) * math:pow(2, -1022)) + =:= "2.225073858507201e-308" + ) + } ]. -endif. \ No newline at end of file diff --git a/src/jsx_format.erl b/src/jsx_format.erl index 9ecab6f..970aa83 100644 --- a/src/jsx_format.erl +++ b/src/jsx_format.erl @@ -79,7 +79,11 @@ format_something({event, start_object, Next}, Opts, Level) -> {Continue, [?start_object, ?end_object]} ; Event -> {Continue, Object} = format_object(Event, [], Opts, Level + 1), - {Continue, [?start_object, Object, indent(Opts, Level), ?end_object]} + {Continue, [?start_object, + Object, + indent(Opts, Level), + ?end_object + ]} end; format_something({event, start_array, Next}, Opts, Level) -> case Next() of @@ -99,10 +103,24 @@ format_object({event, {key, Key}, Next}, Acc, Opts, Level) -> {Continue, Value} = format_something(Next(), Opts, Level), case Continue() of {event, end_object, NextNext} -> - {NextNext, [Acc, indent(Opts, Level), encode(string, Key), ?colon, space(Opts), Value]} + {NextNext, [Acc, + indent(Opts, Level), + encode(string, Key), + ?colon, + space(Opts), + Value + ]} ; Else -> format_object(Else, - [Acc, indent(Opts, Level), encode(string, Key), ?colon, space(Opts), Value, ?comma, space(Opts)], + [Acc, + indent(Opts, Level), + encode(string, Key), + ?colon, + space(Opts), + Value, + ?comma, + space(Opts) + ], Opts, Level ) @@ -117,14 +135,24 @@ format_array(Event, Acc, Opts, Level) -> {event, end_array, NextNext} -> {NextNext, [Acc, indent(Opts, Level), Value]} ; Else -> - format_array(Else, [Acc, indent(Opts, Level), Value, ?comma, space(Opts)], Opts, Level) + format_array(Else, + [Acc, + indent(Opts, Level), + Value, + ?comma, + space(Opts) + ], + Opts, + Level + ) end. encode(Acc, Opts) when is_list(Acc) -> case Opts#format_opts.output_encoding of iolist -> Acc - ; UTF when ?is_utf_encoding(UTF) -> unicode:characters_to_binary(Acc, utf8, UTF) + ; UTF when ?is_utf_encoding(UTF) -> + unicode:characters_to_binary(Acc, utf8, UTF) ; _ -> erlang:throw(badarg) end; encode(string, String) -> @@ -162,17 +190,58 @@ space(Opts) -> minify_test_() -> [ - {"minify object", ?_assert(format(<<" { \"key\" :\n\t \"value\"\r\r\r\n } ">>, []) =:= <<"{\"key\":\"value\"}">>)}, - {"minify array", ?_assert(format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>, []) =:= <<"[true,false,null]">>)} + {"minify object", + ?_assert(format(<<" { \"key\" :\n\t \"value\"\r\r\r\n } ">>, + [] + ) =:= <<"{\"key\":\"value\"}">> + ) + }, + {"minify array", + ?_assert(format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>, + [] + ) =:= <<"[true,false,null]">> + ) + } ]. opts_test_() -> [ - {"unspecified indent/space", ?_assert(format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>, [space, indent]) =:= <<"[\n true, \n false, \n null\n]">>)}, - {"specific indent/space", ?_assert(format(<<"\n{\n\"key\" : [],\n\"another key\" : true\n}\n">>, [{space, 2}, {indent, 4}]) =:= <<"{\n \"key\": [], \n \"another key\": true\n}">>)}, - {"nested structures", ?_assert(format(<<"[{\"key\":\"value\", \"another key\": \"another value\"}, [[true, false, null]]]">>, [{space, 2}, {indent, 2}]) =:= <<"[\n {\n \"key\": \"value\", \n \"another key\": \"another value\"\n }, \n [\n [\n true, \n false, \n null\n ]\n ]\n]">>)}, - {"just spaces", ?_assert(format(<<"[1,2,3]">>, [{space, 2}]) =:= <<"[1, 2, 3]">>)}, - {"just indent", ?_assert(format(<<"[1.0, 2.0, 3.0]">>, [{indent, 2}]) =:= <<"[\n 1.0,\n 2.0,\n 3.0\n]">>)} + {"unspecified indent/space", + ?_assert(format(<<" [\n\ttrue,\n\tfalse,\n\tnull\n] ">>, + [space, indent] + ) =:= <<"[\n true, \n false, \n null\n]">> + ) + }, + {"specific indent/space", + ?_assert(format( + <<"\n{\n\"key\" : [],\n\"another key\" : true\n}\n">>, + [{space, 2}, {indent, 3}] + ) =:= <<"{\n \"key\": [], \n \"another key\": true\n}">> + ) + }, + {"nested structures", + ?_assert(format( + <<"[{\"key\":\"value\", + \"another key\": \"another value\" + }, + [[true, false, null]] + ]">>, + [{space, 2}, {indent, 2}] + ) =:= <<"[\n {\n \"key\": \"value\", \n \"another key\": \"another value\"\n }, \n [\n [\n true, \n false, \n null\n ]\n ]\n]">> + ) + }, + {"just spaces", + ?_assert(format(<<"[1,2,3]">>, + [{space, 2}] + ) =:= <<"[1, 2, 3]">> + ) + }, + {"just indent", + ?_assert(format(<<"[1.0, 2.0, 3.0]">>, + [{indent, 2}] + ) =:= <<"[\n 1.0,\n 2.0,\n 3.0\n]">> + ) + } ]. -endif. \ No newline at end of file diff --git a/src/jsx_verify.erl b/src/jsx_verify.erl index 848112b..05097c9 100644 --- a/src/jsx_verify.erl +++ b/src/jsx_verify.erl @@ -68,8 +68,8 @@ collect({event, start_object, Next}, Keys) -> collect(Next(), [[]|Keys]); collect({event, end_object, Next}, [_|Keys]) -> collect(Next(), [Keys]); -%% check to see if key has already been encountered, if not add it to the key accumulator -%% and continue, else return false +%% check to see if key has already been encountered, if not add it to the key +%% accumulator and continue, else return false collect({event, {key, Key}, Next}, [Current|Keys]) -> case lists:member(Key, Current) of true -> false @@ -81,7 +81,8 @@ collect({event, _, Next}, Keys) -> collect(Next(), Keys); -%% needed to parse numbers that don't have trailing whitespace in less strict mode +%% needed to parse numbers that don't have trailing whitespace in less strict +%% mode collect({incomplete, More}, Keys) -> collect(More(end_stream), Keys); @@ -98,32 +99,85 @@ true_test_() -> [ {"empty object", ?_assert(is_json(<<"{}">>, []) =:= true)}, {"empty array", ?_assert(is_json(<<"[]">>, []) =:= true)}, - {"whitespace", ?_assert(is_json(<<" \n \t \r [true] \t \n\r ">>, []) =:= true)}, - {"nested terms", ?_assert(is_json(<<"[ { \"key\": [ {}, {}, {} ], \"more key\": [{}] }, {}, [[[]]] ]">>, []) =:= true)}, - {"numbers", ?_assert(is_json(<<"[ -1.0, -1, -0, 0, 1e-1, 1, 1.0, 1e1 ]">>, []) =:= true)}, - {"strings", ?_assert(is_json(<<"[ \"a\", \"string\", \"in\", \"multiple\", \"acts\" ]">>, []) =:= true)}, - {"literals", ?_assert(is_json(<<"[ true, false, null ]">>, []) =:= true)}, - {"nested objects", ?_assert(is_json(<<"{\"key\": { \"key\": true}}">>, []) =:= true)} + {"whitespace", + ?_assert(is_json(<<" \n \t \r [true] \t \n\r ">>, + [] + ) =:= true + ) + }, + {"nested terms", + ?_assert(is_json( + <<"[{ \"x\": [ {}, {}, {} ], \"y\": [{}] }, {}, [[[]]]]">>, + [] + ) =:= true + ) + }, + {"numbers", + ?_assert(is_json( + <<"[ -1.0, -1, -0, 0, 1e-1, 1, 1.0, 1e1 ]">>, + [] + ) =:= true + ) + }, + {"strings", + ?_assert(is_json( + <<"[ \"a\", \"string\", \"in\", \"multiple\", \"acts\" ]">>, + [] + ) =:= true + ) + }, + {"literals", + ?_assert(is_json(<<"[ true, false, null ]">>, []) =:= true) + }, + {"nested objects", + ?_assert(is_json(<<"{\"key\": { \"key\": true}}">>, []) =:= true) + } ]. false_test_() -> [ {"naked true", ?_assert(is_json(<<"true">>, []) =:= false)}, {"naked number", ?_assert(is_json(<<"1">>, []) =:= false)}, - {"naked string", ?_assert(is_json(<<"\"i am not json\"">>, []) =:= false)}, + {"naked string", + ?_assert(is_json(<<"\"i am not json\"">>, []) =:= false) + }, {"unbalanced list", ?_assert(is_json(<<"[[[]]">>, []) =:= false)}, - {"trailing comma", ?_assert(is_json(<<"[ true, false, null, ]">>, []) =:= false)}, + {"trailing comma", + ?_assert(is_json(<<"[ true, false, null, ]">>, []) =:= false) + }, {"unquoted key", ?_assert(is_json(<<"{ key: false }">>, []) =:= false)}, - {"repeated key", ?_assert(is_json(<<"{\"key\": true, \"key\": true}">>, []) =:= false)}, + {"repeated key", + ?_assert(is_json( + <<"{\"key\": true, \"key\": true}">>, + [] + ) =:= false + ) + }, {"comments", ?_assert(is_json(<<"[ /* a comment */ ]">>, []) =:= false)} ]. less_strict_test_() -> [ - {"naked true", ?_assert(is_json(<<"true">>, [{strict, false}]) =:= true)}, - {"naked number", ?_assert(is_json(<<"1">>, [{strict, false}]) =:= true)}, - {"naked string", ?_assert(is_json(<<"\"i am not json\"">>, [{strict, false}]) =:= true)}, - {"comments", ?_assert(is_json(<<"[ /* a comment */ ]">>, [{comments, true}]) =:= true)} + {"naked true", + ?_assert(is_json(<<"true">>, [{strict, false}]) =:= true) + }, + {"naked number", + ?_assert(is_json(<<"1">>, [{strict, false}]) =:= true) + }, + {"naked string", + ?_assert(is_json( + <<"\"i am not json\"">>, + [{strict, false}] + ) =:= true + ) + }, + {"comments", + ?_assert(is_json( + <<"[ /* a comment */ ]">>, + [{comments, true}] + ) =:= true + ) + } ].