WHY WONT IT PARSE THE LAST FUCKING BYTE AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH
This commit is contained in:
parent
69a89cf78e
commit
fa16da8178
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ deps
|
|||||||
*.beam
|
*.beam
|
||||||
*.plt
|
*.plt
|
||||||
*.swp
|
*.swp
|
||||||
|
*.swo
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
ebin/*.beam
|
ebin/*.beam
|
||||||
doc/*.html
|
doc/*.html
|
||||||
|
|||||||
95
priv/default.css
Normal file
95
priv/default.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
112
priv/index.html
112
priv/index.html
@ -1,12 +1,114 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>FEWD Hello!</title>
|
<title>WF Compiler Demo</title>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<link rel="stylesheet" href="./default.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 x-data = "{message: 'Hello, Alpine!'}"
|
<div class="content">
|
||||||
x-text = "message"></h1>
|
<h1 class="content-title">WFC Demo</h1>
|
||||||
|
|
||||||
|
<div class="content-body">
|
||||||
|
<textarea id="wfc-output"
|
||||||
|
disabled
|
||||||
|
></textarea>
|
||||||
|
<input autofocus id="wfc-input"></textarea>
|
||||||
|
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<input type="checkbox" checked id="auto-resize-output">Auto-resize output</input> <br>
|
||||||
|
<input type="checkbox" checked id="auto-scroll" >Auto-scroll output to bottom</input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ielt = document.getElementById('wfc-input');
|
||||||
|
let oelt = document.getElementById('wfc-output');
|
||||||
|
let MAX_OELT_HEIGHT = 100;
|
||||||
|
|
||||||
|
function auto_resize_output() {
|
||||||
|
// if the user has manually resized their output, we do nothing
|
||||||
|
if (document.getElementById('auto-resize-output').checked) {
|
||||||
|
// resize it automagically up to 500px
|
||||||
|
if (oelt.scrollHeight < MAX_OELT_HEIGHT) {
|
||||||
|
oelt.style.height = String(oelt.scrollHeight) + 'px';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
oelt.style.height = String(MAX_OELT_HEIGHT) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function auto_scroll_to_bottom() {
|
||||||
|
if (document.getElementById('auto-scroll').checked) {
|
||||||
|
// scroll to bottom
|
||||||
|
oelt.scrollTop = oelt.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_server_return(x) {
|
||||||
|
console.log('on_server_return:', x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_some_bullshit(x) {
|
||||||
|
console.log('on_some_bullshit:', x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_wfcin(user_line) {
|
||||||
|
let req_body_obj = {wfcin: user_line};
|
||||||
|
// let req_body_str = JSON.stringify(req_body_obj, undefined, 4);
|
||||||
|
let req_body_str = JSON.stringify(req_body_obj);
|
||||||
|
|
||||||
|
let req_options = {method: 'POST',
|
||||||
|
headers: {'content-type': 'application/json'},
|
||||||
|
body: req_body_str};
|
||||||
|
|
||||||
|
let response_promise = fetch('/wfcin', req_options);
|
||||||
|
|
||||||
|
response_promise.then(on_server_return, on_some_bullshit);
|
||||||
|
|
||||||
|
// this is a promise for a response
|
||||||
|
//console.log(response_promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when user hits any key
|
||||||
|
function on_input_key(evt) {
|
||||||
|
if (evt.key === 'Enter') {
|
||||||
|
// don't do default thing
|
||||||
|
evt.preventDefault();
|
||||||
|
// grab contents
|
||||||
|
let contents = ielt.value;
|
||||||
|
// if contents are nonempty
|
||||||
|
let nonempty_contents = contents.trim().length > 0;
|
||||||
|
if (nonempty_contents) {
|
||||||
|
// put in output
|
||||||
|
// if it's nonempty add a newline
|
||||||
|
if (oelt.value.length > 0) {
|
||||||
|
oelt.value += '\n';
|
||||||
|
}
|
||||||
|
oelt.value += '> ' + contents;
|
||||||
|
oelt.hidden = false;
|
||||||
|
|
||||||
|
// query backend for result
|
||||||
|
fetch_wfcin(contents.trim());
|
||||||
|
|
||||||
|
// clear input
|
||||||
|
ielt.value = '';
|
||||||
|
|
||||||
|
// auto-resize
|
||||||
|
auto_resize_output();
|
||||||
|
auto_scroll_to_bottom();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
ielt.addEventListener('keydown', on_input_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
56
priv/scratch.txt
Normal file
56
priv/scratch.txt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/* header */
|
||||||
|
div#header {
|
||||||
|
background: var(--lgray2);
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
img#header-logo {
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-diagram {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Pandoc makes some wacky choices with how it lays out code blocks
|
||||||
|
*
|
||||||
|
* this means all <code> blocks that do not have a <pre> 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);
|
||||||
|
}
|
||||||
@ -28,6 +28,7 @@
|
|||||||
%%% Type and Record Definitions
|
%%% Type and Record Definitions
|
||||||
|
|
||||||
-include("http.hrl").
|
-include("http.hrl").
|
||||||
|
-include("$zx_include/zx_logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
-record(s, {socket = none :: none | gen_tcp:socket(),
|
-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}]),
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, Message} ->
|
{tcp, Socket, Message} ->
|
||||||
%ok = io:format("~p received: ~tp~n", [self(), Message]),
|
tell("~p Next = ~p", [?LINE, Next]),
|
||||||
Received =
|
Received =
|
||||||
case Next of
|
case Next of
|
||||||
none -> Message;
|
none -> Message;
|
||||||
_ -> <<Next/binary, Message/binary>>
|
_ -> <<Next/binary, Message/binary>>
|
||||||
end,
|
end,
|
||||||
|
tell("qhl_parse(Socket, ~tp)", [Received]),
|
||||||
case qhl:parse(Socket, Received) of
|
case qhl:parse(Socket, Received) of
|
||||||
{ok, Req, NewNext} ->
|
{ok, Req, NewNext} ->
|
||||||
|
tell("qhl return: {ok, ~p, ~p}", [Req, NewNext]),
|
||||||
handle_request(Socket, Req),
|
handle_request(Socket, Req),
|
||||||
NewState = State#s{next = NewNext},
|
NewState = State#s{next = NewNext},
|
||||||
loop(Parent, Debug, NewState);
|
loop(Parent, Debug, NewState);
|
||||||
Error ->
|
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),
|
gen_tcp:shutdown(Socket, read_write),
|
||||||
exit(normal)
|
exit(normal)
|
||||||
end;
|
end;
|
||||||
@ -155,18 +161,6 @@ loop(Parent, Debug, State = #s{socket = Socket, next = Next}) ->
|
|||||||
Unexpected ->
|
Unexpected ->
|
||||||
ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
|
ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
|
||||||
loop(Parent, Debug, State)
|
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.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -224,12 +218,23 @@ system_replace_state(StateFun, State) ->
|
|||||||
%%% http request handling
|
%%% http request handling
|
||||||
|
|
||||||
handle_request(Sock, R = #request{method = M, path = P}) when M =/= undefined, P =/= undefined ->
|
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, M, P, R).
|
||||||
|
|
||||||
|
|
||||||
route(Sock, get, <<"/">>, _) -> home(Sock);
|
route(Sock, get, Route, _Request) ->
|
||||||
route(Sock, _, _, _) -> http_err(Sock, 404).
|
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) ->
|
home(Sock) ->
|
||||||
@ -240,13 +245,47 @@ home(Sock) ->
|
|||||||
Resp = #response{headers = [{"content-type", "text/html"}],
|
Resp = #response{headers = [{"content-type", "text/html"}],
|
||||||
body = Body},
|
body = Body},
|
||||||
respond(Sock, Resp);
|
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 ->
|
Error ->
|
||||||
io:format("~p error: ~p~n", [self(), Error]),
|
io:format("~p error: ~p~n", [self(), Error]),
|
||||||
http_err(Sock, 500)
|
http_err(Sock, 500)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
http_err(_, _) ->
|
wfcin(Sock, Request) ->
|
||||||
error(todo).
|
tell("wfcin request: ~tp", [Request]),
|
||||||
|
http_err(Sock, 501).
|
||||||
|
|
||||||
|
%% FIXME
|
||||||
|
http_err(Sock, N) ->
|
||||||
|
Slogan = qhl:slogan(N),
|
||||||
|
Body = ["<!doctype html>"
|
||||||
|
"<html lang=\"en\">"
|
||||||
|
"<head>"
|
||||||
|
"<meta charset=\"utf-8\">"
|
||||||
|
"<title>QHL: ", integer_to_list(N), " ", Slogan, "</title>"
|
||||||
|
"</head>"
|
||||||
|
"<body>"
|
||||||
|
"<h1>"
|
||||||
|
"QHL: ", integer_to_list(N), " ", Slogan,
|
||||||
|
"</h1>"
|
||||||
|
"</body>"
|
||||||
|
"</html>"],
|
||||||
|
Resp = #response{code = N,
|
||||||
|
headers = [{"content/type", "text/html"}],
|
||||||
|
body = Body},
|
||||||
|
respond(Sock, Resp).
|
||||||
|
|
||||||
|
|
||||||
respond(Sock, Response) ->
|
respond(Sock, Response) ->
|
||||||
|
|||||||
28
src/fewd.erl
28
src/fewd.erl
@ -10,9 +10,9 @@
|
|||||||
-license("BSD-2-Clause-FreeBSD").
|
-license("BSD-2-Clause-FreeBSD").
|
||||||
|
|
||||||
-export([listen/1, ignore/0]).
|
-export([listen/1, ignore/0]).
|
||||||
-export([start/0, start/1]).
|
|
||||||
-export([start/2, stop/1]).
|
-export([start/2, stop/1]).
|
||||||
|
|
||||||
|
-include("$zx_include/zx_logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
-spec listen(PortNum) -> Result
|
-spec listen(PortNum) -> Result
|
||||||
@ -35,28 +35,6 @@ ignore() ->
|
|||||||
fd_client_man: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()}.
|
-spec start(normal, term()) -> {ok, pid()}.
|
||||||
%% @private
|
%% @private
|
||||||
%% Called by OTP to kick things off. This is for the use of the "application" part of
|
%% 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
|
%% See: http://erlang.org/doc/apps/kernel/application.html
|
||||||
|
|
||||||
start(normal, _Args) ->
|
start(normal, _Args) ->
|
||||||
fd_sup:start_link().
|
Result = fd_sup:start_link(),
|
||||||
|
ok = listen(8000),
|
||||||
|
Result.
|
||||||
|
|
||||||
|
|
||||||
-spec stop(term()) -> ok.
|
-spec stop(term()) -> ok.
|
||||||
|
|||||||
26
src/qhl.erl
26
src/qhl.erl
@ -49,52 +49,62 @@ parse(Socket, Received) ->
|
|||||||
%% socket.
|
%% socket.
|
||||||
|
|
||||||
parse(Socket, Received, M = #request{method = undefined}) ->
|
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
|
case read_method(Socket, Received) of
|
||||||
{ok, Method, Rest} -> parse(Socket, Rest, M#request{method = Method});
|
{ok, Method, Rest} -> parse(Socket, Rest, M#request{method = Method});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{path = undefined}) ->
|
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
|
case read_path(Socket, Received) of
|
||||||
{ok, Path, Rest} -> parse(Socket, Rest, M#request{path = Path});
|
{ok, Path, Rest} -> parse(Socket, Rest, M#request{path = Path});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{qargs = undefined}) ->
|
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
|
case read_qargs(Socket, Received) of
|
||||||
{ok, Qargs, Rest} -> parse(Socket, Rest, M#request{qargs = Qargs});
|
{ok, Qargs, Rest} -> parse(Socket, Rest, M#request{qargs = Qargs});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{fragment = undefined}) ->
|
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
|
case read_fragment(Socket, Received) of
|
||||||
{ok, Fragment, Rest} -> parse(Socket, Rest, M#request{fragment = Fragment});
|
{ok, Fragment, Rest} -> parse(Socket, Rest, M#request{fragment = Fragment});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{version = undefined}) ->
|
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
|
case read_version(Socket, Received) of
|
||||||
{ok, Version, Rest} -> parse(Socket, Rest, M#request{version = Version});
|
{ok, Version, Rest} -> parse(Socket, Rest, M#request{version = Version});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{headers = undefined}) ->
|
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
|
case read_headers(Socket, Received) of
|
||||||
{ok, Headers, Rest} -> parse(Socket, Rest, M#request{headers = Headers});
|
{ok, Headers, Rest} -> parse(Socket, Rest, M#request{headers = Headers});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{enctype = undefined}) ->
|
parse(Socket, Received, M = #request{enctype = undefined}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_enctype(M) of
|
case read_enctype(M) of
|
||||||
{ok, Enctype} -> parse(Socket, Received, M#request{enctype = Enctype});
|
{ok, Enctype} -> parse(Socket, Received, M#request{enctype = Enctype});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{cookies = undefined}) ->
|
parse(Socket, Received, M = #request{cookies = undefined}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_cookies(M) of
|
case read_cookies(M) of
|
||||||
{ok, Cookies} -> parse(Socket, Received, M#request{cookies = Cookies});
|
{ok, Cookies} -> parse(Socket, Received, M#request{cookies = Cookies});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{size = undefined}) ->
|
parse(Socket, Received, M = #request{size = undefined}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_size(M) of
|
case read_size(M) of
|
||||||
{ok, 0} -> {ok, M#request{size = 0}, none};
|
{ok, 0} -> {ok, M#request{size = 0}, none};
|
||||||
{ok, Size} -> parse(Socket, Received, M#request{size = Size});
|
{ok, Size} -> parse(Socket, Received, M#request{size = Size});
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
parse(Socket, Received, M = #request{method = get, body = undefined, size = Size}) ->
|
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
|
case read_body(Received, Size) of
|
||||||
{ok, Body} -> {ok, M#request{body = Body}, none};
|
{ok, Body} -> {ok, M#request{body = Body}, none};
|
||||||
{ok, Body, Next} -> {ok, M#request{body = Body}, Next};
|
{ok, Body, Next} -> {ok, M#request{body = Body}, Next};
|
||||||
@ -107,6 +117,7 @@ parse(Socket,
|
|||||||
method = post,
|
method = post,
|
||||||
enctype = urlencoded,
|
enctype = urlencoded,
|
||||||
size = Size}) ->
|
size = Size}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_body(Received, Size) of
|
case read_body(Received, Size) of
|
||||||
{ok, Body} ->
|
{ok, Body} ->
|
||||||
{ok, M#request{body = parts_to_map(posted(Body))}, none};
|
{ok, M#request{body = parts_to_map(posted(Body))}, none};
|
||||||
@ -130,6 +141,7 @@ parse(Socket,
|
|||||||
method = post,
|
method = post,
|
||||||
enctype = {multipart, Boundary},
|
enctype = {multipart, Boundary},
|
||||||
size = Size}) ->
|
size = Size}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_multipart(Socket, Received, Boundary, Size) of
|
case read_multipart(Socket, Received, Boundary, Size) of
|
||||||
{ok, Parts} -> {ok, M#request{body = parts_to_map(Parts)}, none};
|
{ok, Parts} -> {ok, M#request{body = parts_to_map(Parts)}, none};
|
||||||
{ok, Parts, Next} -> {ok, M#request{body = parts_to_map(Parts)}, Next};
|
{ok, Parts, Next} -> {ok, M#request{body = parts_to_map(Parts)}, Next};
|
||||||
@ -141,10 +153,12 @@ parse(Socket,
|
|||||||
method = post,
|
method = post,
|
||||||
enctype = json,
|
enctype = json,
|
||||||
size = Size}) ->
|
size = Size}) ->
|
||||||
|
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||||
case read_body(Received, Size) of
|
case read_body(Received, Size) of
|
||||||
{ok, Body} -> read_json(M#request{body = Body}, none);
|
{ok, Body} -> read_json(M#request{body = Body}, none);
|
||||||
{ok, Body, Next} -> read_json(M#request{body = Body}, Next);
|
{ok, Body, Next} -> read_json(M#request{body = Body}, Next);
|
||||||
{incomplete, Body} ->
|
{incomplete, Body} ->
|
||||||
|
io:format("~p {incomplete, ~p}~n", [?LINE, Body]),
|
||||||
case accumulate(Socket, M#request{body = Body}) of
|
case accumulate(Socket, M#request{body = Body}) of
|
||||||
{ok, NewM = #request{body = NewBody}} ->
|
{ok, NewM = #request{body = NewBody}} ->
|
||||||
read_json(NewM#request{body = NewBody}, none);
|
read_json(NewM#request{body = NewBody}, none);
|
||||||
@ -514,6 +528,7 @@ read_size(#request{method = options}) ->
|
|||||||
|
|
||||||
|
|
||||||
read_body(Received, Size) ->
|
read_body(Received, Size) ->
|
||||||
|
io:format("~p read_body(~p, ~p)~n", [?LINE, Received, Size]),
|
||||||
case Received of
|
case Received of
|
||||||
<<Bin:Size/binary>> ->
|
<<Bin:Size/binary>> ->
|
||||||
{ok, Bin};
|
{ok, Bin};
|
||||||
@ -811,9 +826,11 @@ accumulate(Socket, M = #request{size = Size, body = Body}) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
accumulate(Socket, Remaining, Received) when Remaining > 0 ->
|
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}]),
|
ok = inet:setopts(Socket, [{active, once}]),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, Bin} ->
|
{tcp, Socket, Bin} ->
|
||||||
|
io:format("~p~n", [?LINE]),
|
||||||
Size = byte_size(Bin),
|
Size = byte_size(Bin),
|
||||||
if
|
if
|
||||||
Size == Remaining ->
|
Size == Remaining ->
|
||||||
@ -828,10 +845,15 @@ accumulate(Socket, Remaining, Received) when Remaining > 0 ->
|
|||||||
{ok, NewReceived, Next}
|
{ok, NewReceived, Next}
|
||||||
end;
|
end;
|
||||||
{tcp_closed, Socket} ->
|
{tcp_closed, Socket} ->
|
||||||
|
io:format("~p~n", [?LINE]),
|
||||||
{error, tcp_closed};
|
{error, tcp_closed};
|
||||||
{tcp_error, Socket, Reason} ->
|
{tcp_error, Socket, Reason} ->
|
||||||
{error, {tcp_error, Reason}}
|
io:format("~p~n", [?LINE]),
|
||||||
after 3000 ->
|
{error, {tcp_error, Reason}};
|
||||||
|
X ->
|
||||||
|
io:format("~p raseevd: ~p~n", [?LINE, X])
|
||||||
|
after 10_000 ->
|
||||||
|
io:format("~p~n", [?LINE]),
|
||||||
{error, timeout}
|
{error, timeout}
|
||||||
end;
|
end;
|
||||||
accumulate(_, 0, Received) ->
|
accumulate(_, 0, Received) ->
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
-module(wfc_parse).
|
|
||||||
|
|
||||||
-export([
|
|
||||||
]).
|
|
||||||
694
src/zj.erl
Normal file
694
src/zj.erl
Normal file
@ -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 <zxq9@zxq9.com>").
|
||||||
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
|
-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}.
|
||||||
Loading…
x
Reference in New Issue
Block a user