WFC Demo
+ +Settings
+ Auto-resize output+ Auto-scroll output to bottom +
diff --git a/.gitignore b/.gitignore index 20177b4..1bfb900 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ deps *.beam *.plt *.swp +*.swo erl_crash.dump ebin/*.beam doc/*.html diff --git a/priv/default.css b/priv/default.css new file mode 100644 index 0000000..d7f1ad5 --- /dev/null +++ b/priv/default.css @@ -0,0 +1,95 @@ +/* color pallette */ +* { + --white: #f7f8fb; + --lgray0: #f5fbfd; + --lgray1: #daddd6; + --lgray2: #d3d8d5; + --mgray1: #687864; + --dgreen1: #21341e; + --lgreen1: #e5eef2; + --black: #003d27; + + --sans: Helvetica, Liberation Sans, FreeSans, Roboto, sans-serif; + --mono: Liberation Mono, FreeMono, Roboto Mono, monospace; + --fsdef: 12pt; +} + +/* body */ +body { + background: var(--white); + color: var(--dgray1); + font-size: var(--fsdef); + font-family: var(--sans); + margin: 0; + padding: 0; + line-height: 1.4; +} + + +.content { + max-width: 800px; + margin: 0 auto; + /* + background: #ff0; + */ +} + + +/* add some top padding to content */ + +.content-title { + text-align: center; + text-decoration: underline; +} + +.content-body { + width: 100%; + margin: 0 auto; + /* + background: #f00; + */ + + + padding-left: 10px; + padding-right: 10px; + + box-sizing: border-box; +} + +/* element-specific styling */ + +a { + color: var(--mgray1); +} + + +#wfc-input { + font-family: var(--mono); + font-size: var(--fsdef); + background: var(--lgreen1); + + width: 100%; + + box-sizing: border-box; + + border-radius: 6px; + border: 1px solid var(--lgray1); + + padding: 5px; +} + +#wfc-output { + background: var(--lgray0); + font-family: var(--mono); + font-size: var(--fsdef); + + width: 100%; + resize: vertical; + + box-sizing: border-box; + + border-radius: 6px; + border: 1px solid var(--lgray1); + + padding: 5px; +} diff --git a/priv/index.html b/priv/index.html index 245c764..ce3511f 100644 --- a/priv/index.html +++ b/priv/index.html @@ -1,12 +1,114 @@ - +
- blocks that do not have a block as an ancestor
+ *
+ * ie the `inline code`
+ */
+code:not(pre *) {
+ border-radius: 6px;
+ border: 1px solid var(--lgray1);
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ background: var(--lgreen1);
+}
+
+/* this is specifically for ```fenced blocks``` */
+pre {
+ border-radius: 6px;
+ border: 1px solid var(--lgray1);
+ padding: 5px;
+ background: var(--lgreen1);
+ overflow: scroll;
+}
+
+/* All `unfenced` or ```fenced``` blocks */
+code {
+ font-family: Liberation Mono, Roboto Mono, monospace;
+ color: var(--dgreen1);
+}
diff --git a/src/fd_client.erl b/src/fd_client.erl
index d54b592..9d16135 100644
--- a/src/fd_client.erl
+++ b/src/fd_client.erl
@@ -28,6 +28,7 @@
%%% Type and Record Definitions
-include("http.hrl").
+-include("$zx_include/zx_logger.hrl").
-record(s, {socket = none :: none | gen_tcp:socket(),
@@ -131,19 +132,24 @@ loop(Parent, Debug, State = #s{socket = Socket, next = Next}) ->
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Message} ->
- %ok = io:format("~p received: ~tp~n", [self(), Message]),
+ tell("~p Next = ~p", [?LINE, Next]),
Received =
case Next of
none -> Message;
_ -> <>
end,
+ tell("qhl_parse(Socket, ~tp)", [Received]),
case qhl:parse(Socket, Received) of
{ok, Req, NewNext} ->
+ tell("qhl return: {ok, ~p, ~p}", [Req, NewNext]),
handle_request(Socket, Req),
NewState = State#s{next = NewNext},
loop(Parent, Debug, NewState);
Error ->
- io:format("~tp:~tp error: ~tp~n", [self(), ?LINE, Error]),
+ %% should trigger bad request
+ io:format("~p QHL parse error: ~tp", [?LINE, Error]),
+ io:format("~p bad request:~n~ts", [?LINE, Received]),
+ http_err(Socket, 400),
gen_tcp:shutdown(Socket, read_write),
exit(normal)
end;
@@ -155,18 +161,6 @@ loop(Parent, Debug, State = #s{socket = Socket, next = Next}) ->
Unexpected ->
ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
loop(Parent, Debug, State)
- %{tcp, Socket, <<"bye\r\n">>} ->
- % ok = io:format("~p Client saying goodbye. Bye!~n", [self()]),
- % ok = gen_tcp:send(Socket, "Bye!\r\n"),
- % ok = gen_tcp:shutdown(Socket, read_write),
- % exit(normal);
- %{relay, Sender, Message} when Sender == self() ->
- % ok = gen_tcp:send(Socket, ["Message from YOU: ", Message]),
- % loop(Parent, Debug, State);
- %{relay, Sender, Message} ->
- % From = io_lib:format("Message from ~tp: ", [Sender]),
- % ok = gen_tcp:send(Socket, [From, Message]),
- % loop(Parent, Debug, State);
end.
@@ -224,12 +218,23 @@ system_replace_state(StateFun, State) ->
%%% http request handling
handle_request(Sock, R = #request{method = M, path = P}) when M =/= undefined, P =/= undefined ->
- zx:tell("~p ~p ~ts", [self(), M, P]),
+ tell("~p ~ts", [M, P]),
route(Sock, M, P, R).
-route(Sock, get, <<"/">>, _) -> home(Sock);
-route(Sock, _, _, _) -> http_err(Sock, 404).
+route(Sock, get, Route, _Request) ->
+ case Route of
+ <<"/">> -> home(Sock);
+ <<"/default.css">> -> default_css(Sock);
+ _ -> http_err(Sock, 404)
+ end;
+route(Sock, post, Route, Request) ->
+ case Route of
+ <<"/wfcin">> -> wfcin(Sock, Request);
+ _ -> http_err(Sock, 404)
+ end;
+route(Sock, _, _, _) ->
+ http_err(Sock, 404).
home(Sock) ->
@@ -240,13 +245,47 @@ home(Sock) ->
Resp = #response{headers = [{"content-type", "text/html"}],
body = Body},
respond(Sock, Resp);
+ Error ->
+ tell("error: ~p~n", [self(), Error]),
+ http_err(Sock, 500)
+ end.
+
+default_css(Sock) ->
+ %% fixme: cache
+ Path_IH = filename:join([zx:get_home(), "priv", "default.css"]),
+ case file:read_file(Path_IH) of
+ {ok, Body} ->
+ Resp = #response{headers = [{"content-type", "text/css"}],
+ body = Body},
+ respond(Sock, Resp);
Error ->
io:format("~p error: ~p~n", [self(), Error]),
http_err(Sock, 500)
end.
-http_err(_, _) ->
- error(todo).
+wfcin(Sock, Request) ->
+ tell("wfcin request: ~tp", [Request]),
+ http_err(Sock, 501).
+
+%% FIXME
+http_err(Sock, N) ->
+ Slogan = qhl:slogan(N),
+ Body = [""
+ ""
+ ""
+ ""
+ "QHL: ", integer_to_list(N), " ", Slogan, " "
+ ""
+ ""
+ ""
+ "QHL: ", integer_to_list(N), " ", Slogan,
+ "
"
+ ""
+ ""],
+ Resp = #response{code = N,
+ headers = [{"content/type", "text/html"}],
+ body = Body},
+ respond(Sock, Resp).
respond(Sock, Response) ->
diff --git a/src/fewd.erl b/src/fewd.erl
index 54ef416..51a2f94 100644
--- a/src/fewd.erl
+++ b/src/fewd.erl
@@ -10,9 +10,9 @@
-license("BSD-2-Clause-FreeBSD").
-export([listen/1, ignore/0]).
--export([start/0, start/1]).
-export([start/2, stop/1]).
+-include("$zx_include/zx_logger.hrl").
-spec listen(PortNum) -> Result
@@ -35,28 +35,6 @@ ignore() ->
fd_client_man:ignore().
--spec start() -> ok.
-%% @doc
-%% Start the server in an "ignore" state.
-
-start() ->
- ok = application:ensure_started(sasl),
- ok = application:start(fewd),
- io:format("Starting...").
-
-
--spec start(PortNum) -> ok
- when PortNum :: inet:port_number().
-%% @doc
-%% Start the server and begin listening immediately. Slightly more convenient when
-%% playing around in the shell.
-
-start(PortNum) ->
- ok = start(),
- ok = fd_client_man:listen(PortNum),
- io:format("Startup complete, listening on ~w~n", [PortNum]).
-
-
-spec start(normal, term()) -> {ok, pid()}.
%% @private
%% Called by OTP to kick things off. This is for the use of the "application" part of
@@ -64,7 +42,9 @@ start(PortNum) ->
%% See: http://erlang.org/doc/apps/kernel/application.html
start(normal, _Args) ->
- fd_sup:start_link().
+ Result = fd_sup:start_link(),
+ ok = listen(8000),
+ Result.
-spec stop(term()) -> ok.
diff --git a/src/qhl.erl b/src/qhl.erl
index a6c6134..a57869f 100644
--- a/src/qhl.erl
+++ b/src/qhl.erl
@@ -49,52 +49,62 @@ parse(Socket, Received) ->
%% socket.
parse(Socket, Received, M = #request{method = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_method(Socket, Received) of
{ok, Method, Rest} -> parse(Socket, Rest, M#request{method = Method});
Error -> Error
end;
parse(Socket, Received, M = #request{path = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_path(Socket, Received) of
{ok, Path, Rest} -> parse(Socket, Rest, M#request{path = Path});
Error -> Error
end;
parse(Socket, Received, M = #request{qargs = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_qargs(Socket, Received) of
{ok, Qargs, Rest} -> parse(Socket, Rest, M#request{qargs = Qargs});
Error -> Error
end;
parse(Socket, Received, M = #request{fragment = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_fragment(Socket, Received) of
{ok, Fragment, Rest} -> parse(Socket, Rest, M#request{fragment = Fragment});
Error -> Error
end;
parse(Socket, Received, M = #request{version = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_version(Socket, Received) of
{ok, Version, Rest} -> parse(Socket, Rest, M#request{version = Version});
Error -> Error
end;
parse(Socket, Received, M = #request{headers = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_headers(Socket, Received) of
{ok, Headers, Rest} -> parse(Socket, Rest, M#request{headers = Headers});
Error -> Error
end;
parse(Socket, Received, M = #request{enctype = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_enctype(M) of
{ok, Enctype} -> parse(Socket, Received, M#request{enctype = Enctype});
Error -> Error
end;
parse(Socket, Received, M = #request{cookies = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_cookies(M) of
{ok, Cookies} -> parse(Socket, Received, M#request{cookies = Cookies});
Error -> Error
end;
parse(Socket, Received, M = #request{size = undefined}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_size(M) of
{ok, 0} -> {ok, M#request{size = 0}, none};
{ok, Size} -> parse(Socket, Received, M#request{size = Size});
Error -> Error
end;
parse(Socket, Received, M = #request{method = get, body = undefined, size = Size}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} -> {ok, M#request{body = Body}, none};
{ok, Body, Next} -> {ok, M#request{body = Body}, Next};
@@ -107,6 +117,7 @@ parse(Socket,
method = post,
enctype = urlencoded,
size = Size}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} ->
{ok, M#request{body = parts_to_map(posted(Body))}, none};
@@ -130,6 +141,7 @@ parse(Socket,
method = post,
enctype = {multipart, Boundary},
size = Size}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_multipart(Socket, Received, Boundary, Size) of
{ok, Parts} -> {ok, M#request{body = parts_to_map(Parts)}, none};
{ok, Parts, Next} -> {ok, M#request{body = parts_to_map(Parts)}, Next};
@@ -141,10 +153,12 @@ parse(Socket,
method = post,
enctype = json,
size = Size}) ->
+ io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
case read_body(Received, Size) of
{ok, Body} -> read_json(M#request{body = Body}, none);
{ok, Body, Next} -> read_json(M#request{body = Body}, Next);
{incomplete, Body} ->
+ io:format("~p {incomplete, ~p}~n", [?LINE, Body]),
case accumulate(Socket, M#request{body = Body}) of
{ok, NewM = #request{body = NewBody}} ->
read_json(NewM#request{body = NewBody}, none);
@@ -514,6 +528,7 @@ read_size(#request{method = options}) ->
read_body(Received, Size) ->
+ io:format("~p read_body(~p, ~p)~n", [?LINE, Received, Size]),
case Received of
<> ->
{ok, Bin};
@@ -811,9 +826,11 @@ accumulate(Socket, M = #request{size = Size, body = Body}) ->
end.
accumulate(Socket, Remaining, Received) when Remaining > 0 ->
+ io:format("~p accumulate(~p, ~p, ~p)~n", [?LINE, Socket, Remaining, Received]),
ok = inet:setopts(Socket, [{active, once}]),
receive
{tcp, Socket, Bin} ->
+ io:format("~p~n", [?LINE]),
Size = byte_size(Bin),
if
Size == Remaining ->
@@ -828,10 +845,15 @@ accumulate(Socket, Remaining, Received) when Remaining > 0 ->
{ok, NewReceived, Next}
end;
{tcp_closed, Socket} ->
+ io:format("~p~n", [?LINE]),
{error, tcp_closed};
{tcp_error, Socket, Reason} ->
- {error, {tcp_error, Reason}}
- after 3000 ->
+ io:format("~p~n", [?LINE]),
+ {error, {tcp_error, Reason}};
+ X ->
+ io:format("~p raseevd: ~p~n", [?LINE, X])
+ after 10_000 ->
+ io:format("~p~n", [?LINE]),
{error, timeout}
end;
accumulate(_, 0, Received) ->
diff --git a/src/wfc_parse.erl b/src/wfc_parse.erl
deleted file mode 100644
index 5821c26..0000000
--- a/src/wfc_parse.erl
+++ /dev/null
@@ -1,4 +0,0 @@
--module(wfc_parse).
-
--export([
-]).
diff --git a/src/zj.erl b/src/zj.erl
new file mode 100644
index 0000000..0040ca8
--- /dev/null
+++ b/src/zj.erl
@@ -0,0 +1,694 @@
+%%% @doc
+%%% ZJ: The tiny JSON parser
+%%%
+%%% This module exports four functions and accepts no options.
+%%% @end
+
+-module(zj).
+-vsn("1.1.2").
+-author("Craig Everett ").
+-copyright("Craig Everett ").
+-license("MIT").
+
+-export([encode/1, decode/1,
+ binary_encode/1, binary_decode/1]).
+
+-export_type([value/0, bin_value/0]).
+
+-type value() :: string()
+ | number()
+ | true
+ | false
+ | undefined
+ | [value()]
+ | #{string() := value()}.
+
+
+-type bin_value() :: binary()
+ | number()
+ | true
+ | false
+ | undefined
+ | [bin_value()]
+ | #{binary() := bin_value()}.
+
+
+
+%%% Character constants
+
+-define(BKSPC, 16#08).
+-define(H_TAB, 16#09).
+-define(NEW_L, 16#0A).
+-define(FORMF, 16#0C).
+-define(CAR_R, 16#0D).
+-define(SPACE, 16#20).
+
+
+%%% Interface Functions
+
+-spec encode(term()) -> string().
+
+%% @doc
+%% Take any convertable Erlang term and convert it to a JSON string.
+%%
+%% As JSON can only satirically be referred to as "a serialization format", it is
+%% almost impossible to map any interesting data between Erlang (or any other language)
+%% and JSON. For example, tuples do not exist in JSON, so converting an Erlang tuple
+%% turns it into a list (a JSON array). Atoms also do not exist, so atoms other than
+%% the ternay logic values `true', `false' and `null' become strings (those three
+%% remain as atoms, with the added detail that JSON `null' maps to Erlang
+%% `undefined').
+%%
+%% Unless care is taken to pick types that JSON can accurately express (integers,
+%% floats, strings, maps, lists, ternary logic atoms) it is not possible to guarantee
+%% (or even reasonable to expect) that `Term == decode(encode(Term))' will be true.
+%%
+%% This function crashes when it fails. Things that will cause a crash are trying to
+%% convert non-UTF-8 binaries to strings, use non-string values as object keys,
+%% encode an unaligned bitstring, etc.
+%%
+%% Note that Erlang terms are converted as type primitives, meaning that compound
+%% functional structures like GB-trees, dicts, sets, etc. will wind up having their
+%% underlying structures converted as-is which is almost never what you want. It is
+%% usually best to reduce compound values down to primitives (lists or maps) before
+%% running encode.
+%%
+%% The only unsupported Erlang pritmitive is bitstrings. Care has NOT been taken to
+%% ensure separation between actual binary data and binaries that are supposed to be
+%% interpreted as strings. The same is true of deep list data: it just comes out raw
+%% unless you flatten or convert it to a utf8 string with the unicode module.
+%%
+%% NOTE: If you need a serialization format that is less ambiguous and expresses more
+%% types consider using BERT (language-independent implementations of Erlang external
+%% binary format) instead: http://bert-rpc.org
+
+encode(true) -> "true";
+encode(false) -> "false";
+encode(undefined) -> "null";
+encode([]) -> "[]";
+encode(T) when is_atom(T) -> quote(atom_to_list(T));
+encode(T) when is_float(T) -> float_to_list(T);
+encode(T) when is_integer(T) -> integer_to_list(T);
+encode(T) when is_pid(T) -> quote(pid_to_list(T));
+encode(T) when is_port(T) -> quote(port_to_list(T));
+encode(T) when is_function(T) -> quote(erlang:fun_to_list(T));
+encode(T) when is_reference(T) -> quote(ref_to_list(T));
+encode(T) -> unicode:characters_to_list(encode_value(T)).
+
+
+-spec decode(Stream) -> Result
+ when Stream :: unicode:chardata(),
+ Result :: {ok, value()}
+ | {error, Parsed, Remainder}
+ | {incomplete, Parsed, Remainder},
+ Parsed :: value(),
+ Remainder :: unicode:chardata()
+ | unicode:external_chardata()
+ | binary().
+%% @doc
+%% Take any IO data acceptable to the unicode module and return a parsed data structure.
+%% In the event of a parsing error whatever part of the structure could be successfully
+%% parsed will be returned along with the remainder of the string. Note that the string
+%% remainder may have been changed to a different form by unicode:characters_to_list/1.
+%% If the unicode library itself runs into a problem performing the initial conversion
+%% its error return (`error' or `incomplete') will be returned directly.
+
+decode(Stream) ->
+ case unicode:characters_to_list(Stream) of
+ E when is_tuple(E) -> E;
+ [16#FEFF | String] -> parse(seek(String));
+ String -> parse(seek(String))
+ end.
+
+
+-spec binary_encode(term()) -> binary().
+%% @doc
+%% A strict encoding routine that works very similarly to `encode/1' but with a few
+%% differences:
+%% ```
+%% - Lists and Strings are firmly separated:
+%% ALL lists are lists of discrete values, never strings.
+%% ALL binaries are always UTF-8 strings.
+%% An Erlang string or io_list will be encoded as JSON array.
+%% - This function generates a UTF-8 binary, not a list.
+%% - The burden is on the user to ensure that io_lists are collapsed to unicode
+%% binaries via `unicode:characters_to_binary/1' before passing in string values.
+%% - Erlang strings (lists) are still accepted as map/object keys.
+%% '''
+%%
+%% NOTE:
+%% Most cases are better served by `encode/1', as most code deals in strings and not
+%% arrays of integer values.
+%%
+%% Using this function requires a little bit more work up front (because ununified
+%% io_list() data will always be interpreted as a JSON array), but provides a way to
+%% reliably generate lists or strings in an unambiguous way in the special case where
+%% your code is generating both strings and lists of integer values that may overlap
+%% with valid UTF-8 codepoint values.
+
+binary_encode(true) -> <<"true">>;
+binary_encode(false) -> <<"false">>;
+binary_encode(undefined) -> <<"null">>;
+binary_encode(T) when is_atom(T) -> <<"\"", (atom_to_binary(T, utf8))/binary, "\"">>;
+binary_encode(T) when is_float(T) -> float_to_binary(T);
+binary_encode(T) when is_integer(T) -> integer_to_binary(T);
+binary_encode(T) when is_pid(T) -> <<"\"", (list_to_binary(pid_to_list(T)))/binary, "\"">>;
+binary_encode(T) when is_port(T) -> <<"\"", (list_to_binary(port_to_list(T)))/binary, "\"">>;
+binary_encode(T) when is_function(T) -> <<"\"", (list_to_binary(erlang:fun_to_list(T)))/binary, "\"">>;
+binary_encode(T) when is_reference(T) -> <<"\"", (list_to_binary(ref_to_list(T)))/binary, "\"">>;
+binary_encode(T) -> unicode:characters_to_binary(b_encode_value(T)).
+
+
+-spec binary_decode(Stream) -> Result
+ when Stream :: unicode:chardata(),
+ Result :: {ok, bin_value()}
+ | {error, Parsed, Remainder}
+ | {incomplete, Parsed, Remainder},
+ Parsed :: bin_value(),
+ Remainder :: binary().
+%% @doc
+%% Almost identical in behavior to `decode/1' except this returns strings as binaries
+%% and arrays of integers as Erlang lists (which may also be valid strings if the
+%% values are valid UTF-8 codepoints).
+%%
+%% NOTE:
+%% This function returns map keys as binaries
+
+binary_decode(Stream) ->
+ case b_decode(Stream) of
+ {error, Part, Rest} -> {error, Part, unicode:characters_to_binary(Rest)};
+ Result -> Result
+ end.
+
+
+
+%%% Encoding Functions
+
+encode_value(true) -> "true";
+encode_value(false) -> "false";
+encode_value(undefined) -> "null";
+encode_value(T) when is_atom(T) -> quote(atom_to_list(T));
+encode_value(T) when is_float(T) -> float_to_list(T);
+encode_value(T) when is_integer(T) -> integer_to_list(T);
+encode_value(T) when is_binary(T) -> maybe_string(T);
+encode_value(T) when is_list(T) -> maybe_array(T);
+encode_value(T) when is_map(T) -> pack_object(T);
+encode_value(T) when is_tuple(T) -> pack_array(tuple_to_list(T));
+encode_value(T) when is_pid(T) -> quote(pid_to_list(T));
+encode_value(T) when is_port(T) -> quote(port_to_list(T));
+encode_value(T) when is_function(T) -> quote(erlang:fun_to_list(T));
+encode_value(T) when is_reference(T) -> quote(ref_to_list(T)).
+
+
+maybe_string(T) ->
+ L = binary_to_list(T),
+ true = io_lib:printable_unicode_list(L),
+ quote(L).
+
+
+maybe_array(T) ->
+ case io_lib:printable_unicode_list(T) of
+ true -> quote(T);
+ false -> pack_array(T)
+ end.
+
+
+quote(T) -> [$" | escape(T)].
+
+escape([]) -> [$"];
+escape([$\b | T]) -> [$\\, $b | escape(T)];
+escape([$\f | T]) -> [$\\, $f | escape(T)];
+escape([$\n | T]) -> [$\\, $n | escape(T)];
+escape([$\r | T]) -> [$\\, $r | escape(T)];
+escape([$\t | T]) -> [$\\, $t | escape(T)];
+escape([$\" | T]) -> [$\\, $" | escape(T)];
+escape([$\\ | T]) -> [$\\, $\\ | escape(T)];
+escape([H | T]) -> [H | escape(T)].
+
+
+pack_array([]) -> "[]";
+pack_array([H | []]) -> [$[, encode_value(H), $]];
+pack_array([H | T]) -> [$[, encode_value(H), $,, encode_array(T), $]].
+
+encode_array([H | []]) -> encode_value(H);
+encode_array([H | T]) -> [encode_value(H), $,, encode_array(T)].
+
+
+pack_object(M) ->
+ case maps:to_list(M) of
+ [] ->
+ "{}";
+ [{K, V} | T] when is_list(K) ->
+ true = io_lib:printable_unicode_list(K),
+ Init = [$", K, $", $:, encode_value(V)],
+ [${, lists:foldl(fun pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_binary(K) ->
+ Key = unicode:characters_to_list(K),
+ true = io_lib:printable_unicode_list(Key),
+ Init = [$", Key, $", $:, encode_value(V)],
+ [${, lists:foldl(fun pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_float(K) ->
+ Key = float_to_list(K),
+ Init = [$", Key, $", $:, encode_value(V)],
+ [${, lists:foldl(fun pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_integer(K) ->
+ Key = integer_to_list(K),
+ Init = [$", Key, $", $:, encode_value(V)],
+ [${, lists:foldl(fun pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_atom(K) ->
+ Init = [$", atom_to_list(K), $", $:, encode_value(V)],
+ [${, lists:foldl(fun pack_object/2, Init, T), $}]
+ end.
+
+pack_object({K, V}, L) when is_list(K) ->
+ true = io_lib:printable_unicode_list(K),
+ [$", K, $", $:, encode_value(V), $, | L];
+pack_object({K, V}, L) when is_binary(K) ->
+ Key = unicode:characters_to_list(K),
+ true = io_lib:printable_unicode_list(Key),
+ [$", Key, $", $:, encode_value(V), $, | L];
+pack_object({K, V}, L) when is_float(K) ->
+ Key = float_to_list(K),
+ [$", Key, $", $:, encode_value(V), $, | L];
+pack_object({K, V}, L) when is_integer(K) ->
+ Key = integer_to_list(K),
+ [$", Key, $", $:, encode_value(V), $, | L];
+pack_object({K, V}, L) when is_atom(K) ->
+ [$", atom_to_list(K), $", $:, encode_value(V), $, | L].
+
+
+b_encode_value(true) -> <<"true">>;
+b_encode_value(false) -> <<"false">>;
+b_encode_value(undefined) -> <<"null">>;
+b_encode_value(T) when is_atom(T) -> [$", atom_to_binary(T, utf8), $"];
+b_encode_value(T) when is_float(T) -> float_to_binary(T);
+b_encode_value(T) when is_integer(T) -> integer_to_binary(T);
+b_encode_value(T) when is_binary(T) -> [$", b_maybe_string(T), $"];
+b_encode_value(T) when is_list(T) -> b_pack_array(T);
+b_encode_value(T) when is_map(T) -> b_pack_object(T);
+b_encode_value(T) when is_tuple(T) -> b_pack_array(tuple_to_list(T));
+b_encode_value(T) when is_pid(T) -> [$", list_to_binary(pid_to_list(T)), $"];
+b_encode_value(T) when is_port(T) -> [$", list_to_binary(port_to_list(T)), $"];
+b_encode_value(T) when is_function(T) -> [$", list_to_binary(erlang:fun_to_list(T)), $"];
+b_encode_value(T) when is_reference(T) -> [$", list_to_binary(ref_to_list(T)), $"].
+
+
+b_maybe_string(T) ->
+ S = unicode:characters_to_binary(T),
+ true = is_binary(S),
+ S.
+
+
+b_pack_array([]) -> "[]";
+b_pack_array([H | []]) -> [$[, b_encode_value(H), $]];
+b_pack_array([H | T]) -> [$[, b_encode_value(H), $,, b_encode_array(T), $]].
+
+b_encode_array([H | []]) -> b_encode_value(H);
+b_encode_array([H | T]) -> [b_encode_value(H), $,, b_encode_array(T)].
+
+
+b_pack_object(M) ->
+ case maps:to_list(M) of
+ [] ->
+ "{}";
+ [{K, V} | T] when is_list(K) ->
+ true = io_lib:printable_unicode_list(K),
+ Init = [$", K, $", $:, b_encode_value(V)],
+ [${, lists:foldl(fun b_pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_binary(K) ->
+ true = io_lib:printable_unicode_list(unicode:characters_to_list(K)),
+ Init = [$", K, $", $:, b_encode_value(V)],
+ [${, lists:foldl(fun b_pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_float(K) ->
+ Key = float_to_list(K),
+ Init = [$", Key, $", $:, b_encode_value(V)],
+ [${, lists:foldl(fun b_pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_integer(K) ->
+ Key = integer_to_list(K),
+ Init = [$", Key, $", $:, b_encode_value(V)],
+ [${, lists:foldl(fun b_pack_object/2, Init, T), $}];
+ [{K, V} | T] when is_atom(K) ->
+ Init = [$", atom_to_binary(K, utf8), $", $:, b_encode_value(V)],
+ [${, lists:foldl(fun b_pack_object/2, Init, T), $}]
+ end.
+
+b_pack_object({K, V}, L) when is_list(K) ->
+ true = io_lib:printable_unicode_list(K),
+ [$", K, $", $:, b_encode_value(V), $, | L];
+b_pack_object({K, V}, L) when is_binary(K) ->
+ true = io_lib:printable_unicode_list(unicode:characters_to_list(K)),
+ [$", K, $", $:, b_encode_value(V), $, | L];
+b_pack_object({K, V}, L) when is_float(K) ->
+ Key = float_to_list(K),
+ [$", Key, $", $:, b_encode_value(V), $, | L];
+b_pack_object({K, V}, L) when is_integer(K) ->
+ Key = integer_to_list(K),
+ [$", Key, $", $:, b_encode_value(V), $, | L];
+b_pack_object({K, V}, L) when is_atom(K) ->
+ [$", atom_to_list(K), $", $:, b_encode_value(V), $, | L].
+
+
+%%% Decode Functions
+
+-spec parse(Stream) -> Result
+ when Stream :: string(),
+ Result :: {ok, value()}
+ | {error, Extracted :: value(), Remaining :: string()}.
+%% @private
+%% The top-level dispatcher. This packages the top level value (or top-level error)
+%% for return to the caller. A very similar function (value/1) is used for inner
+%% values.
+
+parse([${ | Rest]) ->
+ case object(Rest) of
+ {ok, Object, ""} -> {ok, Object};
+ {ok, Object, More} -> polish(Object, seek(More));
+ Error -> Error
+ end;
+parse([$[ | Rest]) ->
+ case array(Rest) of
+ {ok, Array, ""} -> {ok, Array};
+ {ok, Array, More} -> polish(Array, seek(More));
+ Error -> Error
+ end;
+parse([$" | Rest]) ->
+ case string(Rest) of
+ {ok, String, ""} -> {ok, String};
+ {ok, String, More} -> polish(String, seek(More));
+ Error -> Error
+ end;
+parse([I | Rest]) when I == $-; $0 =< I, I =< $9 ->
+ case number_int(Rest, [I]) of
+ {ok, Number, ""} -> {ok, Number};
+ {ok, Number, More} -> polish(Number, seek(More));
+ Error -> Error
+ end;
+parse("true" ++ More) ->
+ polish(true, seek(More));
+parse("false" ++ More) ->
+ polish(false, seek(More));
+parse("null" ++ More) ->
+ polish(undefined, seek(More));
+parse(Other) ->
+ {error, [], Other}.
+
+
+polish(Value, "") -> {ok, Value};
+polish(Value, More) -> {error, Value, More}.
+
+
+value([${ | Rest]) -> object(Rest);
+value([$[ | Rest]) -> array(Rest);
+value([$" | Rest]) -> string(Rest);
+value([I | Rest]) when I == $-; $0 =< I, I =< $9 -> number_int(Rest, [I]);
+value("true" ++ Rest) -> {ok, true, Rest};
+value("false" ++ Rest) -> {ok, false, Rest};
+value("null" ++ Rest) -> {ok, undefined, Rest};
+value(_) -> error.
+
+
+object([$} | Rest]) -> {ok, #{}, Rest};
+object(String) -> object(seek(String), #{}).
+
+object([$} | Rest], Map) ->
+ {ok, Map, Rest};
+object([$" | Rest], Map) ->
+ case string(Rest) of
+ {ok, Key, Remainder} -> object_value(seek(Remainder), Key, Map);
+ {error, _, _} -> {error, Map, Rest}
+ end;
+object(Rest, Map) ->
+ {error, Map, Rest}.
+
+object_value([$: | Rest], Key, Map) ->
+ object_value_parse(seek(Rest), Key, Map);
+object_value(Rest, Key, Map) ->
+ {error, maps:put(Key, undefined, Map), Rest}.
+
+object_value_parse(String, Key, Map) ->
+ case value(String) of
+ {ok, Value, Rest} -> object_next(seek(Rest), maps:put(Key, Value, Map));
+ {error, Value, Rest} -> {error, maps:put(Key, Value, Map), Rest};
+ error -> {error, Map, String}
+ end.
+
+
+object_next([$, | Rest], Map) -> object(seek(Rest), Map);
+object_next([$} | Rest], Map) -> {ok, Map, seek(Rest)};
+object_next(Rest, Map) -> {error, Map, Rest}.
+
+
+array([$] | Rest]) -> {ok, [], Rest};
+array(String) -> array(seek(String), []).
+
+array([$] | Rest], List) ->
+ {ok, lists:reverse(List), seek(Rest)};
+array(String, List) ->
+ case value(String) of
+ {ok, Value, Rest} -> array_next(seek(Rest), [Value | List]);
+ {error, Value, Rest} -> {error, lists:reverse([Value | List]), Rest};
+ error -> {error, lists:reverse(List), String}
+ end.
+
+array_next([$, | Rest], List) -> array(seek(Rest), List);
+array_next([$] | Rest], List) -> {ok, lists:reverse(List), seek(Rest)};
+array_next(Rest, List) -> {error, lists:reverse(List), Rest}.
+
+
+string(Stream) -> string(Stream, "").
+
+string([$" | Rest], String) ->
+ {ok, lists:reverse(String), Rest};
+string([$\\, $" | Rest], String) ->
+ string(Rest, [$" | String]);
+string([$\\, $\\ | Rest], String) ->
+ string(Rest, [$\\ | String]);
+string([$\\, $b | Rest], String) ->
+ string(Rest, [?BKSPC | String]);
+string([$\\, $t | Rest], String) ->
+ string(Rest, [?H_TAB | String]);
+string([$\\, $n | Rest], String) ->
+ string(Rest, [?NEW_L | String]);
+string([$\\, $f | Rest], String) ->
+ string(Rest, [?FORMF | String]);
+string([$\\, $r | Rest], String) ->
+ string(Rest, [?CAR_R | String]);
+string([$\\, $u, A, B, C, D | Rest], String)
+ when (($0 =< A andalso A =< $9) or ($A =< A andalso A =< $F) or ($a =< A andalso A =< $f))
+ and (($0 =< B andalso B =< $9) or ($A =< B andalso B =< $F) or ($a =< B andalso B =< $f))
+ and (($0 =< C andalso C =< $9) or ($A =< C andalso C =< $F) or ($a =< C andalso C =< $f))
+ and (($0 =< D andalso D =< $9) or ($A =< D andalso D =< $F) or ($a =< D andalso D =< $f)) ->
+ Char = list_to_integer([A, B, C, D], 16),
+ string(Rest, [Char | String]);
+string(Stream = [$\\, $u | _], String) ->
+ {error, String, Stream};
+string([$\\, Char | Rest], String)
+ when Char == 16#20;
+ Char == 16#21;
+ 16#23 =< Char, Char =< 16#5B;
+ 16#5D =< Char, Char =< 16#10FFFF ->
+ string(Rest, [$\\, Char | String]);
+string([Char | Rest], String)
+ when Char == 16#20;
+ Char == 16#21;
+ 16#23 =< Char, Char =< 16#5B;
+ 16#5D =< Char, Char =< 16#10FFFF ->
+ string(Rest, [Char | String]);
+string(Rest, String) ->
+ {error, lists:reverse(String), Rest}.
+
+
+number_int([$. | Rest], String) ->
+ number_float(Rest, [$. | String]);
+number_int([$e, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e, $0, $. | String]);
+number_int([$E, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e, $0, $. | String]);
+number_int([$e, $+, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e, $0, $. | String]);
+number_int([$E, $+, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e, $0, $. | String]);
+number_int([$e, $-, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $-, $e, $0, $. | String]);
+number_int([$E, $-, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $-, $e, $0, $. | String]);
+number_int([Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_int(Rest, [Char | String]);
+number_int(Rest, "-") ->
+ {error, "", [$- | Rest]};
+number_int(Rest, String) ->
+ {ok, list_to_integer(lists:reverse(String)), seek(Rest)}.
+
+number_float([Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float(Rest, [Char | String]);
+number_float([$E, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e | String]);
+number_float([$e, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e | String]);
+number_float([$E, $+, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e | String]);
+number_float([$e, $+, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $+, $e | String]);
+number_float([$E, $-, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $-, $e | String]);
+number_float([$e, $-, Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char, $-, $e | String]);
+number_float(Rest, String) ->
+ Target = lists:reverse(String),
+ try
+ Number = list_to_float(Target),
+ {ok, Number, seek(Rest)}
+ catch
+ error:badarg -> {error, "", Target ++ Rest}
+ end.
+
+
+number_float_exp([Char | Rest], String) when $0 =< Char, Char =< $9 ->
+ number_float_exp(Rest, [Char | String]);
+number_float_exp(Rest, String) ->
+ Target = lists:reverse(String),
+ try
+ Number = list_to_float(Target),
+ {ok, Number, seek(Rest)}
+ catch
+ error:badarg -> {error, "", Target ++ Rest}
+ end.
+
+
+seek([?H_TAB | Rest]) -> seek(Rest);
+seek([?NEW_L | Rest]) -> seek(Rest);
+seek([?CAR_R | Rest]) -> seek(Rest);
+seek([?SPACE | Rest]) -> seek(Rest);
+seek(String) -> String.
+
+
+b_decode(Stream) ->
+ case unicode:characters_to_list(Stream) of
+ E when is_tuple(E) -> E;
+ [16#FEFF | String] -> binary_parse(seek(String));
+ String -> binary_parse(seek(String))
+ end.
+
+-spec binary_parse(Stream) -> Result
+ when Stream :: string(),
+ Result :: {ok, bin_value()}
+ | {error, Extracted :: bin_value(), Remaining :: binary()}.
+%% @private
+%% The top-level dispatcher. This packages the top level value (or top-level error)
+%% for return to the caller. A very similar function (b_value/1) is used for inner
+%% values.
+
+binary_parse([${ | Rest]) ->
+ case b_object(Rest) of
+ {ok, Object, ""} -> {ok, Object};
+ {ok, Object, More} -> b_polish(Object, seek(More));
+ Error -> Error
+ end;
+binary_parse([$[ | Rest]) ->
+ case b_array(Rest) of
+ {ok, Array, ""} -> {ok, Array};
+ {ok, Array, More} -> b_polish(Array, seek(More));
+ Error -> Error
+ end;
+binary_parse([$" | Rest]) ->
+ case string(Rest) of
+ {ok, String, ""} ->
+ case unicode:characters_to_binary(String) of
+ E when is_tuple(E) -> E;
+ Result -> {ok, Result}
+ end;
+ {ok, String, More} ->
+ case unicode:characters_to_binary(String) of
+ E when is_tuple(E) -> E;
+ Result -> b_polish(Result, seek(More))
+ end;
+ Error ->
+ Error
+ end;
+binary_parse([I | Rest]) when I == $-; $0 =< I, I =< $9 ->
+ case number_int(Rest, [I]) of
+ {ok, Number, ""} -> {ok, Number};
+ {ok, Number, More} -> b_polish(Number, seek(More));
+ Error -> Error
+ end;
+binary_parse("true" ++ More) ->
+ b_polish(true, seek(More));
+binary_parse("false" ++ More) ->
+ b_polish(false, seek(More));
+binary_parse("null" ++ More) ->
+ b_polish(undefined, seek(More));
+binary_parse(Other) ->
+ {error, [], Other}.
+
+
+b_polish(Value, "") -> {ok, Value};
+b_polish(Value, More) -> {error, Value, More}.
+
+
+b_value([${ | Rest]) -> b_object(Rest);
+b_value([$[ | Rest]) -> b_array(Rest);
+b_value([$" | Rest]) -> b_string(Rest);
+b_value([I | Rest]) when I == $-; $0 =< I, I =< $9 -> number_int(Rest, [I]);
+b_value("true" ++ Rest) -> {ok, true, Rest};
+b_value("false" ++ Rest) -> {ok, false, Rest};
+b_value("null" ++ Rest) -> {ok, undefined, Rest};
+b_value(_) -> error.
+
+
+b_string(Stream) ->
+ case string(Stream) of
+ {ok, String, More} ->
+ case unicode:characters_to_binary(String) of
+ E when is_tuple(E) -> E;
+ Result -> {ok, Result, More}
+ end;
+ Error -> Error
+ end.
+
+
+b_object([$} | Rest]) -> {ok, #{}, Rest};
+b_object(String) -> b_object(seek(String), #{}).
+
+b_object([$} | Rest], Map) ->
+ {ok, Map, Rest};
+b_object([$" | Rest], Map) ->
+ case string(Rest) of
+ {ok, Key, Remainder} ->
+ b_object_value(seek(Remainder), unicode:characters_to_binary(Key), Map);
+ {error, _, _} ->
+ {error, Map, Rest}
+ end;
+b_object(Rest, Map) ->
+ {error, Map, Rest}.
+
+b_object_value([$: | Rest], Key, Map) -> b_object_value_parse(seek(Rest), Key, Map);
+b_object_value(Rest, Key, Map) -> {error, maps:put(Key, undefined, Map), Rest}.
+
+b_object_value_parse(String, Key, Map) ->
+ case b_value(String) of
+ {ok, Value, Rest} -> b_object_next(seek(Rest), maps:put(Key, Value, Map));
+ {error, Value, Rest} -> {error, maps:put(Key, Value, Map), Rest};
+ error -> {error, Map, String}
+ end.
+
+
+b_object_next([$, | Rest], Map) -> b_object(seek(Rest), Map);
+b_object_next([$} | Rest], Map) -> {ok, Map, seek(Rest)};
+b_object_next(Rest, Map) -> {error, Map, Rest}.
+
+
+b_array([$] | Rest]) -> {ok, [], Rest};
+b_array(String) -> b_array(seek(String), []).
+
+b_array([$] | Rest], List) ->
+ {ok, lists:reverse(List), seek(Rest)};
+b_array(String, List) ->
+ case b_value(String) of
+ {ok, Value, Rest} -> b_array_next(seek(Rest), [Value | List]);
+ {error, Value, Rest} -> {error, lists:reverse([Value | List]), Rest};
+ error -> {error, lists:reverse(List), String}
+ end.
+
+b_array_next([$, | Rest], List) -> b_array(seek(Rest), List);
+b_array_next([$] | Rest], List) -> {ok, lists:reverse(List), seek(Rest)};
+b_array_next(Rest, List) -> {error, lists:reverse(List), Rest}.