From 121fe344744dbf2fe2b91c51b8b3807a7a2fbd7c Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Tue, 18 May 2010 12:16:25 -0700 Subject: [PATCH] first working version --- src/jsx_common.hrl | 57 ++++++ src/jsx_utf8.erl | 460 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+) create mode 100644 src/jsx_common.hrl create mode 100644 src/jsx_utf8.erl diff --git a/src/jsx_common.hrl b/src/jsx_common.hrl new file mode 100644 index 0000000..65decf6 --- /dev/null +++ b/src/jsx_common.hrl @@ -0,0 +1,57 @@ +-record(opts, { + comments = false, + escaped_unicode = ascii +}). + +%% whitespace +-define(space, 16#20). +-define(tab, 16#09). +-define(cr, 16#0D). +-define(newline, 16#0A). + +%% object delimiters +-define(start_object, 16#7B). +-define(end_object, 16#7D). + +%% array delimiters +-define(start_array, 16#5B). +-define(end_array, 16#5D). + +%% kv seperator +-define(comma, 16#2C). +-define(quote, 16#22). +-define(colon, 16#3A). + +%% string escape sequences +-define(escape, 16#5C). +-define(rsolidus, 16#5C). +-define(solidus, 16#2F). +-define(formfeed, 16#0C). +-define(backspace, 16#08). +-define(unicode, 16#75). + +%% math +-define(zero, 16#30). +-define(decimalpoint, 16#2E). +-define(negative, 16#2D). +-define(positive, 16#2B). + +%% comments +-define(star, 16#2a). + +-define(is_hex(Symbol), + (Symbol >= $a andalso Symbol =< $z); (Symbol >= $A andalso Symbol =< $Z); + (Symbol >= $0 andalso Symbol =< $9) +). + +-define(is_nonzero(Symbol), + Symbol >= $1 andalso Symbol =< $9 +). + +-define(is_noncontrol(Symbol), + Symbol >= ?space +). + +-define(is_whitespace(Symbol), + Symbol =:= ?space; Symbol =:= ?tab; Symbol =:= ?cr; Symbol =:= ?newline +). \ No newline at end of file diff --git a/src/jsx_utf8.erl b/src/jsx_utf8.erl new file mode 100644 index 0000000..6580807 --- /dev/null +++ b/src/jsx_utf8.erl @@ -0,0 +1,460 @@ +-module(jsx_utf8). + +-export([start/4]). + +-include("jsx_common.hrl"). + +callback(eof, Callbacks) -> + lists:reverse(Callbacks); +callback(Event, Callbacks) -> + [Event] ++ Callbacks. + + +%% this code is mostly autogenerated and mostly ugly. apologies. for more insight on +%% Callbacks or Opts, see the comments accompanying callback/2 (in this file) and +%% parse_opts/1 (in jsx.erl). Stack is a stack of flags used to track depth. all +%% pops, peeks and pushes are inlined. comments are handled in function to get around +%% a limitation of erlang's binary matching optimizations, and a function_clause +%% error is raised if comments are disallowed to maintain consistency with behaviour +%% of other invalid input + +start(<>, Stack, Callbacks, Opts) -> + object(Rest, [key|Stack], callback(start_object, Callbacks), Opts); +start(<>, Stack, Callbacks, Opts) -> + array(Rest, [array|Stack], callback(start_array, Callbacks), Opts); +start(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> start(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +start(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + start(Rest, Stack, Callbacks, Opts); +start(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> start(Stream, Stack, Callbacks, Opts) end. + + +maybe_done(<>, [object|Stack], Callbacks, Opts) -> + maybe_done(Rest, Stack, callback(end_object, Callbacks), Opts); +maybe_done(<>, [array|Stack], Callbacks, Opts) -> + maybe_done(Rest, Stack, callback(end_array, Callbacks), Opts); +maybe_done(<>, [object|Stack], Callbacks, Opts) -> + key(Rest, [key|Stack], Callbacks, Opts); +maybe_done(<>, [array|_] = Stack, Callbacks, Opts) -> + value(Rest, Stack, Callbacks, Opts); +maybe_done(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> maybe_done(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +maybe_done(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + maybe_done(Rest, Stack, Callbacks, Opts); +maybe_done(<<>>, [], Callbacks, _Opts) -> + callback(eof, Callbacks); +maybe_done(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> maybe_done(Stream, Stack, Callbacks, Opts) end. + + +object(<>, [key|Stack], Callbacks, Opts) -> + maybe_done(Rest, Stack, callback(end_object, Callbacks), Opts); +object(<>, Stack, Callbacks, Opts) -> + string(Rest, Stack, Callbacks, Opts, []); +object(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> object(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +object(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + object(Rest, Stack, Callbacks, Opts); +object(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> object(Stream, Stack, Callbacks, Opts) end. + + +array(<>, Stack, Callbacks, Opts) -> + string(Rest, Stack, Callbacks, Opts, []); +array(<>, Stack, Callbacks, Opts) -> + object(Rest, [key|Stack], callback(start_object, Callbacks), Opts); +array(<>, Stack, Callbacks, Opts) -> + array(Rest, [array|Stack], callback(start_array, Callbacks), Opts); +array(<>, [array|Stack], Callbacks, Opts) -> + maybe_done(Rest, Stack, callback(end_array, Callbacks), Opts); +array(<<$t/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + tr(Rest, Stack, Callbacks, Opts); +array(<<$f/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + fa(Rest, Stack, Callbacks, Opts); +array(<<$n/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + nu(Rest, Stack, Callbacks, Opts); +array(<>, Stack, Callbacks, Opts) -> + negative(Rest, Stack, Callbacks, Opts, "-"); +array(<>, Stack, Callbacks, Opts) -> + zero(Rest, Stack, Callbacks, Opts, "0"); +array(<>, Stack, Callbacks, Opts) when ?is_nonzero(S) -> + integer(Rest, Stack, Callbacks, Opts, [S]); +array(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> array(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +array(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + array(Rest, Stack, Callbacks, Opts); +array(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> array(Stream, Stack, Callbacks, Opts) end. + + +value(<>, Stack, Callbacks, Opts) -> + string(Rest, Stack, Callbacks, Opts, []); +value(<>, Stack, Callbacks, Opts) -> + object(Rest, [key|Stack], callback(start_object, Callbacks), Opts); +value(<>, Stack, Callbacks, Opts) -> + array(Rest, [array|Stack], callback(start_array, Callbacks), Opts); +value(<<$t/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + tr(Rest, Stack, Callbacks, Opts); +value(<<$f/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + fa(Rest, Stack, Callbacks, Opts); +value(<<$n/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + nu(Rest, Stack, Callbacks, Opts); +value(<>, Stack, Callbacks, Opts) -> + negative(Rest, Stack, Callbacks, Opts, "-"); +value(<>, Stack, Callbacks, Opts) -> + zero(Rest, Stack, Callbacks, Opts, "0"); +value(<>, Stack, Callbacks, Opts) when ?is_nonzero(S) -> + integer(Rest, Stack, Callbacks, Opts, [S]); +value(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> value(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +value(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + value(Rest, Stack, Callbacks, Opts); +value(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> value(Stream, Stack, Callbacks, Opts) end. + + +colon(<>, [key|Stack], Callbacks, Opts) -> + value(Rest, [object|Stack], Callbacks, Opts); +colon(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> colon(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +colon(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + colon(Rest, Stack, Callbacks, Opts); +colon(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> colon(Stream, Stack, Callbacks, Opts) end. + + +key(<>, Stack, Callbacks, Opts) -> + string(Rest, Stack, Callbacks, Opts, []); +key(<>, Stack, Callbacks, Opts) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> key(Resume, Stack, Callbacks, Opts) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +key(<>, Stack, Callbacks, Opts) when ?is_whitespace(S) -> + key(Rest, Stack, Callbacks, Opts); +key(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> key(Stream, Stack, Callbacks, Opts) end. + + +%% string has an additional parameter, an accumulator (Acc) used to hold the intermediate +%% representation of the string being parsed. using a list of integers representing +%% unicode codepoints is faster than constructing binaries, many of which will be +%% converted back to lists by the user anyways. + +string(<>, [key|_] = Stack, Callbacks, Opts, Acc) -> + colon(Rest, Stack, callback({key, lists:reverse(Acc)}, Callbacks), Opts); +string(<>, Stack, Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback({string, lists:reverse(Acc)}, Callbacks), Opts); +string(<>, Stack, Callbacks, Opts, Acc) -> + escape(Rest, Stack, Callbacks, Opts, Acc); +string(<>, Stack, Callbacks, Opts, Acc) when ?is_noncontrol(S) -> + string(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +string(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> string(Stream, Stack, Callbacks, Opts, Acc) end. + + +%% only thing to note here is the additional accumulator passed to escaped_unicode used +%% to hold the codepoint sequence. unescessary, but nicer than using the string +%% accumulator. + +escape(<<"b"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + string(Rest, Stack, Callbacks, Opts, "\b" ++ Acc); +escape(<<"f"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + string(Rest, Stack, Callbacks, Opts, "\f" ++ Acc); +escape(<<"n"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + string(Rest, Stack, Callbacks, Opts, "\n" ++ Acc); +escape(<<"r"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + string(Rest, Stack, Callbacks, Opts, "\r" ++ Acc); +escape(<<"t"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + string(Rest, Stack, Callbacks, Opts, "\t" ++ Acc); +escape(<<"u"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + escaped_unicode(Rest, Stack, Callbacks, Opts, Acc, []); +escape(<>, Stack, Callbacks, Opts, Acc) + when S =:= ?quote; S =:= ?solidus; S =:= ?rsolidus -> + string(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +escape(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> escape(Stream, Stack, Callbacks, Opts, Acc) end. + + +%% this code is ugly and unfortunate, but so is json's handling of escaped unicode +%% codepoint sequences. if the ascii option is present, the sequence is converted +%% to a codepoint and inserted into the string if it represents an ascii value. if +%% the codepoint option is present the sequence is converted and inserted as long +%% as it represents a valid 16 bit integer value (this is where json's spec gets +%% insane). any other option and the sequence is converted back to an erlang string +%% and appended to the string in place. + +escaped_unicode(<>, Stack, Callbacks, Opts, String, [C, B, A]) -> + X = erlang:list_to_integer([A, B, C, D], 16), + case Opts#opts.escaped_unicode of + ascii when X < 16#0080 -> + string(Rest, Stack, Callbacks, Opts, [X] ++ String) + ; codepoint -> + string(Rest, Stack, Callbacks, Opts, [X] ++ String) + ; _ -> + string(Rest, Stack, Callbacks, Opts, [?rsolidus, $u, A, B, C, D] ++ String) + end; +escaped_unicode(<>, Stack, Callbacks, Opts, String, Acc) when ?is_hex(S) -> + escaped_unicode(Rest, Stack, Callbacks, Opts, String, [S] ++ Acc); +escaped_unicode(<<>>, Stack, Callbacks, Opts, String, Acc) -> + fun(Stream) -> escaped_unicode(Stream, Stack, Callbacks, Opts, String, Acc) end. + + +%% like strings, numbers are collected in an intermediate accumulator before +%% being emitted to the callback handler. no processing of numbers is done in +%% process, it's left for the user, though there are convenience functions to +%% convert them into erlang floats/integers in jsx_utils.erl. + +negative(<<"0"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + zero(Rest, Stack, Callbacks, Opts, "0" ++ Acc); +negative(<>, Stack, Callbacks, Opts, Acc) when ?is_nonzero(S) -> + integer(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +negative(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> negative(Stream, Stack, Callbacks, Opts, Acc) end. + + +zero(<>, [object|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_object, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +zero(<>, [array|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_array, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +zero(<>, [object|Stack], Callbacks, Opts, Acc) -> + key(Rest, [key|Stack], callback({number, lists:reverse(Acc)}, Callbacks), Opts); +zero(<>, [array|_] = Stack, Callbacks, Opts, Acc) -> + value(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +zero(<>, Stack, Callbacks, Opts, Acc) -> + fraction(Rest, Stack, Callbacks, Opts, [?decimalpoint] ++ Acc); +zero(<>, Stack, Callbacks, Opts, Acc) when ?is_whitespace(S) -> + maybe_done(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +zero(<>, Stack, Callbacks, Opts, Acc) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> zero(Resume, Stack, Callbacks, Opts, Acc) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +zero(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> zero(Stream, Stack, Callbacks, Opts, Acc) end. + + +integer(<>, [object|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_object, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +integer(<>, [array|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_array, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +integer(<>, [object|Stack], Callbacks, Opts, Acc) -> + key(Rest, [key|Stack], callback({number, lists:reverse(Acc)}, Callbacks), Opts); +integer(<>, [array|_] = Stack, Callbacks, Opts, Acc) -> + value(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +integer(<>, Stack, Callbacks, Opts, Acc) -> + fraction(Rest, Stack, Callbacks, Opts, [?decimalpoint] ++ Acc); +integer(<>, Stack, Callbacks, Opts, Acc) -> + integer(Rest, Stack, Callbacks, Opts, [?zero] ++ Acc); +integer(<<"e"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + e(Rest, Stack, Callbacks, Opts, "e" ++ Acc); +integer(<<"E"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + e(Rest, Stack, Callbacks, Opts, "e" ++ Acc); +integer(<>, Stack, Callbacks, Opts, Acc) when ?is_nonzero(S) -> + integer(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +integer(<>, Stack, Callbacks, Opts, Acc) when ?is_whitespace(S) -> + maybe_done(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +integer(<>, Stack, Callbacks, Opts, Acc) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> integer(Resume, Stack, Callbacks, Opts, Acc) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +integer(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> integer(Stream, Stack, Callbacks, Opts, Acc) end. + + +fraction(<>, [object|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_object, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +fraction(<>, [array|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_array, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +fraction(<>, [object|Stack], Callbacks, Opts, Acc) -> + key(Rest, [key|Stack], callback({number, lists:reverse(Acc)}, Callbacks), Opts); +fraction(<>, [array|_] = Stack, Callbacks, Opts, Acc) -> + value(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +fraction(<>, Stack, Callbacks, Opts, Acc) -> + fraction(Rest, Stack, Callbacks, Opts, [?zero] ++ Acc); +fraction(<<"e"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + e(Rest, Stack, Callbacks, Opts, "e" ++ Acc); +fraction(<<"E"/utf8, Rest/binary>>, Stack, Callbacks, Opts, Acc) -> + e(Rest, Stack, Callbacks, Opts, "e" ++ Acc); +fraction(<>, Stack, Callbacks, Opts, Acc) when ?is_nonzero(S) -> + fraction(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +fraction(<>, Stack, Callbacks, Opts, Acc) when ?is_whitespace(S) -> + maybe_done(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +fraction(<>, Stack, Callbacks, Opts, Acc) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> fraction(Resume, Stack, Callbacks, Opts, Acc) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +fraction(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> fraction(Stream, Stack, Callbacks, Opts, Acc) end. + + +e(<>, Stack, Callbacks, Opts, Acc) when S =:= ?positive; S =:= ?negative -> + ex(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +e(<>, Stack, Callbacks, Opts, Acc) when S =:= ?zero; ?is_nonzero(S) -> + exp(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +e(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> e(Stream, Stack, Callbacks, Opts, Acc) end. + + +ex(<>, Stack, Callbacks, Opts, Acc) when S =:= ?zero; ?is_nonzero(S) -> + exp(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +ex(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> ex(Stream, Stack, Callbacks, Opts, Acc) end. + + +exp(<>, [object|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_object, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +exp(<>, [array|Stack], Callbacks, Opts, Acc) -> + maybe_done(Rest, Stack, callback(end_array, callback({number, lists:reverse(Acc)}, Callbacks)), Opts); +exp(<>, [object|Stack], Callbacks, Opts, Acc) -> + key(Rest, [key|Stack], callback({number, lists:reverse(Acc)}, Callbacks), Opts); +exp(<>, [array|_] = Stack, Callbacks, Opts, Acc) -> + value(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +exp(<>, Stack, Callbacks, Opts, Acc) -> + exp(Rest, Stack, Callbacks, Opts, [?zero] ++ Acc); +exp(<>, Stack, Callbacks, Opts, Acc) when ?is_nonzero(S) -> + exp(Rest, Stack, Callbacks, Opts, [S] ++ Acc); +exp(<>, Stack, Callbacks, Opts, Acc) when ?is_whitespace(S) -> + maybe_done(Rest, Stack, callback({number, lists:reverse(Acc)}, Callbacks), Opts); +exp(<>, Stack, Callbacks, Opts, Acc) -> + case Opts#opts.comments of + true -> + maybe_comment(Rest, fun(Resume) -> exp(Resume, Stack, Callbacks, Opts, Acc) end) + ; false -> + erlang:error(function_clause, [<>, Stack, Callbacks, Opts]) + end; +exp(<<>>, Stack, Callbacks, Opts, Acc) -> + fun(Stream) -> exp(Stream, Stack, Callbacks, Opts, Acc) end. + + +tr(<<"r"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + tru(Rest, Stack, Callbacks, Opts); +tr(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> tr(Stream, Stack, Callbacks, Opts) end. + + +tru(<<"u"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + true(Rest, Stack, Callbacks, Opts); +tru(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> tru(Stream, Stack, Callbacks, Opts) end. + + +true(<<"e"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + maybe_done(Rest, Stack, callback({literal, true}, Callbacks), Opts); +true(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> true(Stream, Stack, Callbacks, Opts) end. + + +fa(<<"a"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + fal(Rest, Stack, Callbacks, Opts); +fa(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> fa(Stream, Stack, Callbacks, Opts) end. + + +fal(<<"l"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + fals(Rest, Stack, Callbacks, Opts); +fal(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> fal(Stream, Stack, Callbacks, Opts) end. + + +fals(<<"s"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + false(Rest, Stack, Callbacks, Opts); +fals(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> fals(Stream, Stack, Callbacks, Opts) end. + + +false(<<"e"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + maybe_done(Rest, Stack, callback({literal, false}, Callbacks), Opts); +false(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> false(Stream, Stack, Callbacks, Opts) end. + + +nu(<<"u"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + nul(Rest, Stack, Callbacks, Opts); +nu(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> nu(Stream, Stack, Callbacks, Opts) end. + + +nul(<<"l"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + null(Rest, Stack, Callbacks, Opts); +nul(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> nul(Stream, Stack, Callbacks, Opts) end. + + +null(<<"l"/utf8, Rest/binary>>, Stack, Callbacks, Opts) -> + maybe_done(Rest, Stack, callback({literal, null}, Callbacks), Opts); +null(<<>>, Stack, Callbacks, Opts) -> + fun(Stream) -> null(Stream, Stack, Callbacks, Opts) end. + + +%% comments are c style, /* blah blah */ and are STRONGLY discouraged. any unicode +%% character is valid in a comment, except, obviously the */ sequence which ends +%% the comment. they're implemented as a closure called when the comment ends that +%% returns execution to the point where the comment began. comments are not +%% recorded in any way, simply parsed. + +maybe_comment(<>, Resume) -> + comment(Rest, Resume); +maybe_comment(<<>>, Resume) -> + fun(Stream) -> maybe_comment(Stream, Resume) end. + + +comment(<>, Resume) -> + maybe_comment_done(Rest, Resume); +comment(<<_/utf8, Rest/binary>>, Resume) -> + comment(Rest, Resume); +comment(<<>>, Resume) -> + fun(Stream) -> comment(Stream, Resume) end. + + +maybe_comment_done(<>, Resume) -> + Resume(Rest); +maybe_comment_done(<<>>, Resume) -> + fun(Stream) -> maybe_comment_done(Stream, Resume) end. + + + + + +