Commits

Kevin Smith committed 035507c

Very basic mapping via Javascript anonymous functions

  • Participants
  • Parent commits 0d181e2

Comments (0)

Files changed (11)

File apps/erlang_js/src/js.erl

                        false ->
                            VarName
                    end,
-    build_bindings(T, [["var ", FinalVarName, "=", mochijson2:encode(Value), ";\n"]|Accum]).
+    build_bindings(T, [["var ", FinalVarName, "=", js_json:encode(Value), ";\n"]|Accum]).
 
 build_arg_list([], Accum) ->
     lists:reverse(Accum);
 build_arg_list([H|[]], Accum) ->
-    build_arg_list([], [mochijson2:encode(H)|Accum]);
+    build_arg_list([], [js_json:encode(H)|Accum]);
 build_arg_list([H|T], Accum) ->
-    build_arg_list(T, [[mochijson2:encode(H), ","]|Accum]).
+    build_arg_list(T, [[js_json:encode(H), ","]|Accum]).

File apps/erlang_js/src/js_json.erl

+%% @author Bob Ippolito <bob@mochimedia.com>
+%% @author Kevin A. smith <kevin@hypotheticalabs.com>
+%% @copyright 2007 Mochi Media, Inc.
+%% @copyright 2009 Basho Technologies, Inc.
+
+%% @doc Fork of the original mochijson2 JSON encoder. This version
+%%      is functionally equivalent in all ways except one. js_json
+%%      has the ability to transparently detect proplists and
+%%      encode them as JSON structs.
+
+-module(js_json).
+-author('bob@mochimedia.com').
+-author('kevin@hypotheticalabs.com').
+-export([encoder/1, encode/1]).
+-export([decoder/1, decode/1]).
+-export([test/0]).
+
+% This is a macro to placate syntax highlighters..
+-define(Q, $\").
+-define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset,
+                                 column=N+S#decoder.column}).
+-define(INC_COL(S), S#decoder{offset=1+S#decoder.offset,
+                              column=1+S#decoder.column}).
+-define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset,
+                               column=1,
+                               line=1+S#decoder.line}).
+-define(INC_CHAR(S, C),
+        case C of
+            $\n ->
+                S#decoder{column=1,
+                          line=1+S#decoder.line,
+                          offset=1+S#decoder.offset};
+            _ ->
+                S#decoder{column=1+S#decoder.column,
+                          offset=1+S#decoder.offset}
+        end).
+-define(IS_WHITESPACE(C),
+        (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
+
+%% @type iolist() = [char() | binary() | iolist()]
+%% @type iodata() = iolist() | binary()
+%% @type json_string() = atom | binary()
+%% @type json_number() = integer() | float()
+%% @type json_array() = [json_term()]
+%% @type json_object() = {struct, [{json_string(), json_term()}]}
+%% @type json_term() = json_string() | json_number() | json_array() |
+%%                     json_object()
+
+-record(encoder, {handler=null,
+                  utf8=false}).
+
+-record(decoder, {object_hook=null,
+                  offset=0,
+                  line=1,
+                  column=1,
+                  state=null}).
+
+%% @spec encoder([encoder_option()]) -> function()
+%% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
+encoder(Options) ->
+    State = parse_encoder_options(Options, #encoder{}),
+    fun (O) -> json_encode(O, State) end.
+
+%% @spec encode(json_term()) -> iolist()
+%% @doc Encode the given as JSON to an iolist.
+encode(Any) ->
+    json_encode(Any, #encoder{}).
+
+%% @spec decoder([decoder_option()]) -> function()
+%% @doc Create a decoder/1 with the given options.
+decoder(Options) ->
+    State = parse_decoder_options(Options, #decoder{}),
+    fun (O) -> json_decode(O, State) end.
+
+%% @spec decode(iolist()) -> json_term()
+%% @doc Decode the given iolist to Erlang terms.
+decode(S) ->
+    json_decode(S, #decoder{}).
+
+test() ->
+    test_all().
+
+%% Internal API
+
+parse_encoder_options([], State) ->
+    State;
+parse_encoder_options([{handler, Handler} | Rest], State) ->
+    parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+    parse_encoder_options(Rest, State#encoder{utf8=Switch}).
+
+parse_decoder_options([], State) ->
+    State;
+parse_decoder_options([{object_hook, Hook} | Rest], State) ->
+    parse_decoder_options(Rest, State#decoder{object_hook=Hook}).
+
+json_encode(true, _State) ->
+    <<"true">>;
+json_encode(false, _State) ->
+    <<"false">>;
+json_encode(null, _State) ->
+    <<"null">>;
+json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 ->
+    %% Anything outside of 32-bit integers should be encoded as a float
+    integer_to_list(I);
+json_encode(I, _State) when is_integer(I) ->
+    mochinum:digits(float(I));
+json_encode(F, _State) when is_float(F) ->
+    mochinum:digits(F);
+json_encode(S, State) when is_binary(S); is_atom(S) ->
+    json_encode_string(S, State);
+json_encode(Array, State) when is_list(Array) ->
+    case is_structable(Array) of
+        true ->
+            json_encode({struct, Array}, State);
+        false ->
+            json_encode_array(Array, State)
+    end;
+json_encode({struct, Props}, State) when is_list(Props) ->
+    json_encode_proplist(Props, State);
+json_encode(Bad, #encoder{handler=null}) ->
+    exit({json_encode, {bad_term, Bad}});
+json_encode(Bad, State=#encoder{handler=Handler}) ->
+    json_encode(Handler(Bad), State).
+
+json_encode_array([], _State) ->
+    <<"[]">>;
+json_encode_array(L, State) ->
+    F = fun (O, Acc) ->
+                [$,, json_encode(O, State) | Acc]
+        end,
+    [$, | Acc1] = lists:foldl(F, "[", L),
+    lists:reverse([$\] | Acc1]).
+
+json_encode_proplist([], _State) ->
+    <<"{}">>;
+json_encode_proplist(Props, State) ->
+    F = fun ({K, V}, Acc) ->
+                KS = json_encode_string(K, State),
+                VS = json_encode(V, State),
+                [$,, VS, $:, KS | Acc]
+        end,
+    [$, | Acc1] = lists:foldl(F, "{", Props),
+    lists:reverse([$\} | Acc1]).
+
+json_encode_string(A, State) when is_atom(A) ->
+    L = atom_to_list(A),
+    case json_string_is_safe(L) of
+        true ->
+            [?Q, L, ?Q];
+        false ->
+            json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
+    end;
+json_encode_string(B, State) when is_binary(B) ->
+    case json_bin_is_safe(B) of
+        true ->
+            [?Q, B, ?Q];
+        false ->
+            json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
+    end;
+json_encode_string(I, _State) when is_integer(I) ->
+    [?Q, integer_to_list(I), ?Q];
+json_encode_string(L, State) when is_list(L) ->
+    case json_string_is_safe(L) of
+        true ->
+            [?Q, L, ?Q];
+        false ->
+            json_encode_string_unicode(L, State, [?Q])
+    end.
+
+json_string_is_safe([]) ->
+    true;
+json_string_is_safe([C | Rest]) ->
+    case C of
+        ?Q ->
+            false;
+        $\\ ->
+            false;
+        $\b ->
+            false;
+        $\f ->
+            false;
+        $\n ->
+            false;
+        $\r ->
+            false;
+        $\t ->
+            false;
+        C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+            false;
+        C when C < 16#7f ->
+            json_string_is_safe(Rest);
+        _ ->
+            false
+    end.
+
+json_bin_is_safe(<<>>) ->
+    true;
+json_bin_is_safe(<<C, Rest/binary>>) ->
+    case C of
+        ?Q ->
+            false;
+        $\\ ->
+            false;
+        $\b ->
+            false;
+        $\f ->
+            false;
+        $\n ->
+            false;
+        $\r ->
+            false;
+        $\t ->
+            false;
+        C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+            false;
+        C when C < 16#7f ->
+            json_bin_is_safe(Rest);
+        _ ->
+            false
+    end.
+
+json_encode_string_unicode([], _State, Acc) ->
+    lists:reverse([$\" | Acc]);
+json_encode_string_unicode([C | Cs], State, Acc) ->
+    Acc1 = case C of
+               ?Q ->
+                   [?Q, $\\ | Acc];
+               %% Escaping solidus is only useful when trying to protect
+               %% against "</script>" injection attacks which are only
+               %% possible when JSON is inserted into a HTML document
+               %% in-line. mochijson2 does not protect you from this, so
+               %% if you do insert directly into HTML then you need to
+               %% uncomment the following case or escape the output of encode.
+               %%
+               %% $/ ->
+               %%    [$/, $\\ | Acc];
+               %%
+               $\\ ->
+                   [$\\, $\\ | Acc];
+               $\b ->
+                   [$b, $\\ | Acc];
+               $\f ->
+                   [$f, $\\ | Acc];
+               $\n ->
+                   [$n, $\\ | Acc];
+               $\r ->
+                   [$r, $\\ | Acc];
+               $\t ->
+                   [$t, $\\ | Acc];
+               C when C >= 0, C < $\s ->
+                   [unihex(C) | Acc];
+               C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
+                   [xmerl_ucs:to_utf8(C) | Acc];
+               C when  C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
+                   [unihex(C) | Acc];
+               C when C < 16#7f ->
+                   [C | Acc];
+               _ ->
+                   exit({json_encode, {bad_char, C}})
+           end,
+    json_encode_string_unicode(Cs, State, Acc1).
+
+hexdigit(C) when C >= 0, C =< 9 ->
+    C + $0;
+hexdigit(C) when C =< 15 ->
+    C + $a - 10.
+
+unihex(C) when C < 16#10000 ->
+    <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>,
+    Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]],
+    [$\\, $u | Digits];
+unihex(C) when C =< 16#10FFFF ->
+    N = C - 16#10000,
+    S1 = 16#d800 bor ((N bsr 10) band 16#3ff),
+    S2 = 16#dc00 bor (N band 16#3ff),
+    [unihex(S1), unihex(S2)].
+
+json_decode(L, S) when is_list(L) ->
+    json_decode(iolist_to_binary(L), S);
+json_decode(B, S) ->
+    {Res, S1} = decode1(B, S),
+    {eof, _} = tokenize(B, S1#decoder{state=trim}),
+    Res.
+
+decode1(B, S=#decoder{state=null}) ->
+    case tokenize(B, S#decoder{state=any}) of
+        {{const, C}, S1} ->
+            {C, S1};
+        {start_array, S1} ->
+            decode_array(B, S1);
+        {start_object, S1} ->
+            decode_object(B, S1)
+    end.
+
+make_object(V, #decoder{object_hook=null}) ->
+    V;
+make_object(V, #decoder{object_hook=Hook}) ->
+    Hook(V).
+
+decode_object(B, S) ->
+    decode_object(B, S#decoder{state=key}, []).
+
+decode_object(B, S=#decoder{state=key}, Acc) ->
+    case tokenize(B, S) of
+        {end_object, S1} ->
+            V = make_object({struct, lists:reverse(Acc)}, S1),
+            {V, S1#decoder{state=null}};
+        {{const, K}, S1} ->
+            {colon, S2} = tokenize(B, S1),
+            {V, S3} = decode1(B, S2#decoder{state=null}),
+            decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc])
+    end;
+decode_object(B, S=#decoder{state=comma}, Acc) ->
+    case tokenize(B, S) of
+        {end_object, S1} ->
+            V = make_object({struct, lists:reverse(Acc)}, S1),
+            {V, S1#decoder{state=null}};
+        {comma, S1} ->
+            decode_object(B, S1#decoder{state=key}, Acc)
+    end.
+
+decode_array(B, S) ->
+    decode_array(B, S#decoder{state=any}, []).
+
+decode_array(B, S=#decoder{state=any}, Acc) ->
+    case tokenize(B, S) of
+        {end_array, S1} ->
+            {lists:reverse(Acc), S1#decoder{state=null}};
+        {start_array, S1} ->
+            {Array, S2} = decode_array(B, S1),
+            decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
+        {start_object, S1} ->
+            {Array, S2} = decode_object(B, S1),
+            decode_array(B, S2#decoder{state=comma}, [Array | Acc]);
+        {{const, Const}, S1} ->
+            decode_array(B, S1#decoder{state=comma}, [Const | Acc])
+    end;
+decode_array(B, S=#decoder{state=comma}, Acc) ->
+    case tokenize(B, S) of
+        {end_array, S1} ->
+            {lists:reverse(Acc), S1#decoder{state=null}};
+        {comma, S1} ->
+            decode_array(B, S1#decoder{state=any}, Acc)
+    end.
+
+tokenize_string(B, S=#decoder{offset=O}) ->
+    case tokenize_string_fast(B, O) of
+        {escape, O1} ->
+            Length = O1 - O,
+            S1 = ?ADV_COL(S, Length),
+            <<_:O/binary, Head:Length/binary, _/binary>> = B,
+            tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
+        O1 ->
+            Length = O1 - O,
+            <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
+            {{const, String}, ?ADV_COL(S, Length + 1)}
+    end.
+
+tokenize_string_fast(B, O) ->
+    case B of
+        <<_:O/binary, ?Q, _/binary>> ->
+            O;
+        <<_:O/binary, C, _/binary>> when C =/= $\\ ->
+            tokenize_string_fast(B, 1 + O);
+        _ ->
+            {escape, O}
+    end.
+
+tokenize_string(B, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, ?Q, _/binary>> ->
+            {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)};
+        <<_:O/binary, "\\\"", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]);
+        <<_:O/binary, "\\\\", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]);
+        <<_:O/binary, "\\/", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]);
+        <<_:O/binary, "\\b", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]);
+        <<_:O/binary, "\\f", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]);
+        <<_:O/binary, "\\n", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]);
+        <<_:O/binary, "\\r", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]);
+        <<_:O/binary, "\\t", _/binary>> ->
+            tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]);
+        <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> ->
+            C = erlang:list_to_integer([C3, C2, C1, C0], 16),
+            if C > 16#D7FF, C < 16#DC00 ->
+                %% coalesce UTF-16 surrogate pair
+                <<"\\u", D3, D2, D1, D0, _/binary>> = Rest,
+                D = erlang:list_to_integer([D3,D2,D1,D0], 16),
+                [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer,
+                    D:16/big-unsigned-integer>>),
+                Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc),
+                tokenize_string(B, ?ADV_COL(S, 12), Acc1);
+            true ->
+                Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc),
+                tokenize_string(B, ?ADV_COL(S, 6), Acc1)
+            end;
+        <<_:O/binary, C, _/binary>> ->
+            tokenize_string(B, ?INC_CHAR(S, C), [C | Acc])
+    end.
+
+tokenize_number(B, S) ->
+    case tokenize_number(B, sign, S, []) of
+        {{int, Int}, S1} ->
+            {{const, list_to_integer(Int)}, S1};
+        {{float, Float}, S1} ->
+            {{const, list_to_float(Float)}, S1}
+    end.
+
+tokenize_number(B, sign, S=#decoder{offset=O}, []) ->
+    case B of
+        <<_:O/binary, $-, _/binary>> ->
+            tokenize_number(B, int, ?INC_COL(S), [$-]);
+        _ ->
+            tokenize_number(B, int, S, [])
+    end;
+tokenize_number(B, int, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, $0, _/binary>> ->
+            tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]);
+        <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 ->
+            tokenize_number(B, int1, ?INC_COL(S), [C | Acc])
+    end;
+tokenize_number(B, int1, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+            tokenize_number(B, int1, ?INC_COL(S), [C | Acc]);
+        _ ->
+            tokenize_number(B, frac, S, Acc)
+    end;
+tokenize_number(B, frac, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 ->
+            tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]);
+        <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
+            tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]);
+        _ ->
+            {{int, lists:reverse(Acc)}, S}
+    end;
+tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+            tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]);
+        <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E ->
+            tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]);
+        _ ->
+            {{float, lists:reverse(Acc)}, S}
+    end;
+tokenize_number(B, esign, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ ->
+            tokenize_number(B, eint, ?INC_COL(S), [C | Acc]);
+        _ ->
+            tokenize_number(B, eint, S, Acc)
+    end;
+tokenize_number(B, eint, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+            tokenize_number(B, eint1, ?INC_COL(S), [C | Acc])
+    end;
+tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 ->
+            tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]);
+        _ ->
+            {{float, lists:reverse(Acc)}, S}
+    end.
+
+tokenize(B, S=#decoder{offset=O}) ->
+    case B of
+        <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) ->
+            tokenize(B, ?INC_CHAR(S, C));
+        <<_:O/binary, "{", _/binary>> ->
+            {start_object, ?INC_COL(S)};
+        <<_:O/binary, "}", _/binary>> ->
+            {end_object, ?INC_COL(S)};
+        <<_:O/binary, "[", _/binary>> ->
+            {start_array, ?INC_COL(S)};
+        <<_:O/binary, "]", _/binary>> ->
+            {end_array, ?INC_COL(S)};
+        <<_:O/binary, ",", _/binary>> ->
+            {comma, ?INC_COL(S)};
+        <<_:O/binary, ":", _/binary>> ->
+            {colon, ?INC_COL(S)};
+        <<_:O/binary, "null", _/binary>> ->
+            {{const, null}, ?ADV_COL(S, 4)};
+        <<_:O/binary, "true", _/binary>> ->
+            {{const, true}, ?ADV_COL(S, 4)};
+        <<_:O/binary, "false", _/binary>> ->
+            {{const, false}, ?ADV_COL(S, 5)};
+        <<_:O/binary, "\"", _/binary>> ->
+            tokenize_string(B, ?INC_COL(S));
+        <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9)
+                                         orelse C =:= $- ->
+            tokenize_number(B, S);
+        <<_:O/binary>> ->
+            trim = S#decoder.state,
+            {eof, S}
+    end.
+
+is_structable([]) ->
+    false;
+is_structable([H|_]) ->
+    is_tuple(H) andalso size(H) == 2.
+
+%% testing constructs borrowed from the Yaws JSON implementation.
+
+%% Create an object from a list of Key/Value pairs.
+
+obj_new() ->
+    {struct, []}.
+
+is_obj({struct, Props}) ->
+    F = fun ({K, _}) when is_binary(K) ->
+                true;
+            (_) ->
+                false
+        end,
+    lists:all(F, Props).
+
+obj_from_list(Props) ->
+    Obj = {struct, Props},
+    case is_obj(Obj) of
+        true -> Obj;
+        false -> exit({json_bad_object, Obj})
+    end.
+
+%% Test for equivalence of Erlang terms.
+%% Due to arbitrary order of construction, equivalent objects might
+%% compare unequal as erlang terms, so we need to carefully recurse
+%% through aggregates (tuples and objects).
+
+equiv({struct, Props1}, {struct, Props2}) ->
+    equiv_object(Props1, Props2);
+equiv(L1, L2) when is_list(L1), is_list(L2) ->
+    equiv_list(L1, L2);
+equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2;
+equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2;
+equiv(true, true) -> true;
+equiv(false, false) -> true;
+equiv(null, null) -> true.
+
+%% Object representation and traversal order is unknown.
+%% Use the sledgehammer and sort property lists.
+
+equiv_object(Props1, Props2) ->
+    L1 = lists:keysort(1, Props1),
+    L2 = lists:keysort(1, Props2),
+    Pairs = lists:zip(L1, L2),
+    true = lists:all(fun({{K1, V1}, {K2, V2}}) ->
+                             equiv(K1, K2) and equiv(V1, V2)
+                     end, Pairs).
+
+%% Recursively compare tuple elements for equivalence.
+
+equiv_list([], []) ->
+    true;
+equiv_list([V1 | L1], [V2 | L2]) ->
+    case equiv(V1, V2) of
+        true ->
+            equiv_list(L1, L2);
+        false ->
+            false
+    end.
+
+test_all() ->
+    [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
+    <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]),
+    test_encoder_utf8(),
+    test_one(e2j_test_vec(utf8), 1).
+
+test_one([], _N) ->
+    %% io:format("~p tests passed~n", [N-1]),
+    ok;
+test_one([{E, J} | Rest], N) ->
+    %% io:format("[~p] ~p ~p~n", [N, E, J]),
+    true = equiv(E, decode(J)),
+    true = equiv(E, decode(encode(E))),
+    test_one(Rest, 1+N).
+
+e2j_test_vec(utf8) ->
+    [
+     {1, "1"},
+     {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes
+     {-1, "-1"},
+     {-3.1416, "-3.14160"},
+     {12.0e10, "1.20000e+11"},
+     {1.234E+10, "1.23400e+10"},
+     {-1.234E-10, "-1.23400e-10"},
+     {10.0, "1.0e+01"},
+     {123.456, "1.23456E+2"},
+     {10.0, "1e1"},
+     {<<"foo">>, "\"foo\""},
+     {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""},
+     {<<"">>, "\"\""},
+     {<<"\n\n\n">>, "\"\\n\\n\\n\""},
+     {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""},
+     {obj_new(), "{}"},
+     {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"},
+     {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]),
+      "{\"foo\":\"bar\",\"baz\":123}"},
+     {[], "[]"},
+     {[[]], "[[]]"},
+     {[1, <<"foo">>], "[1,\"foo\"]"},
+
+     %% json array in a json object
+     {obj_from_list([{<<"foo">>, [123]}]),
+      "{\"foo\":[123]}"},
+
+     %% json object in a json object
+     {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]),
+      "{\"foo\":{\"bar\":true}}"},
+
+     %% fold evaluation order
+     {obj_from_list([{<<"foo">>, []},
+                     {<<"bar">>, obj_from_list([{<<"baz">>, true}])},
+                     {<<"alice">>, <<"bob">>}]),
+      "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"},
+
+     %% json object in a json array
+     {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
+      "[-123,\"foo\",{\"bar\":[]},null]"}
+    ].
+
+%% test utf8 encoding
+test_encoder_utf8() ->
+    %% safe conversion case (default)
+    [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
+        encode(<<1,"\321\202\320\265\321\201\321\202">>),
+
+    %% raw utf8 output (optional)
+    Enc = mochijson2:encoder([{utf8, true}]),
+    [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
+        Enc(<<1,"\321\202\320\265\321\201\321\202">>).

File apps/erlang_js/tests/eval_tests.erl

 -include_lib("eunit/include/eunit.hrl").
 
 var_test_() ->
-  [{setup, fun test_util:port_setup/0,
-    fun test_util:port_teardown/1,
-    [fun() ->
-         P = test_util:get_thing(),
-         ?assertMatch(ok, js:define(P, <<"var x = 100;">>)),
-         ?assertMatch({ok, 100}, js:eval(P, <<"x;">>)),
-         erlang:unlink(P) end]}].
+    [{setup, fun test_util:port_setup/0,
+      fun test_util:port_teardown/1,
+      [fun() ->
+               P = test_util:get_thing(),
+               ?assertMatch(ok, js:define(P, <<"var x = 100;">>)),
+               ?assertMatch({ok, 100}, js:eval(P, <<"x;">>)),
+               erlang:unlink(P) end]}].
 
 function_test_() ->
-  [{setup, fun test_util:port_setup/0,
-    fun test_util:port_teardown/1,
-    [fun() ->
-         P = test_util:get_thing(),
-         ?assertMatch(ok, js:define(P, <<"function add_two(x, y) { return x + y; };">>)),
-         ?assertMatch({ok, 95}, js:call(P, <<"add_two">>, [85, 10])),
-         ?assertMatch({ok, <<"testing123">>}, js:call(P, <<"add_two">>, [<<"testing">>, <<"123">>])),
-         erlang:unlink(P) end,
-     fun() ->
-        P = test_util:get_thing(),
-        ?assertMatch(ok, js:define(P, <<"var f = function(x, y) { return y - x; };">>)),
-        ?assertMatch({ok, 75}, js:call(P, <<"f">>, [10, 85])),
-        erlang:unlink(P) end]}].
+    [{setup, fun test_util:port_setup/0,
+      fun test_util:port_teardown/1,
+      [fun() ->
+               P = test_util:get_thing(),
+               ?assertMatch(ok, js:define(P, <<"function add_two(x, y) { return x + y; };">>)),
+               ?assertMatch({ok, 95}, js:call(P, <<"add_two">>, [85, 10])),
+               ?assertMatch({ok, <<"testing123">>}, js:call(P, <<"add_two">>, [<<"testing">>, <<"123">>])),
+               erlang:unlink(P) end,
+       fun() ->
+               P = test_util:get_thing(),
+               ?assertMatch(ok, js:define(P, <<"var f = function(x, y) { return y - x; };">>)),
+               ?assertMatch({ok, 75}, js:call(P, <<"f">>, [10, 85])),
+               erlang:unlink(P) end,
+       fun() ->
+               P = test_util:get_thing(),
+               ?assertMatch(ok, js:define(P, <<"function get_first(data) { return data[\"first\"]; };">>)),
+               Data = [{<<"first">>, <<"abc">>}],
+               ?assertMatch({ok, <<"abc">>}, js:call(P, <<"get_first">>, [Data])),
+               erlang:unlink(P) end]}].
 
 error_test_() ->
-  [{setup, fun test_util:port_setup/0,
-    fun test_util:port_teardown/1,
-    [fun() ->
-         P = test_util:get_thing(),
-         {error, ErrorDesc} = js:define(P, <<"functoin foo(x, y) { return true; };">>),
-         ?assert(verify_error(ErrorDesc)),
-         erlang:unlink(P) end,
-     fun() ->
-         P = test_util:get_thing(),
-         ?assertMatch(ok, js:define(P, <<"function foo(x, y) { return true; };">>)),
-         {error, ErrorDesc} = js:eval(P, <<"foo(100, 200,);">>),
-         ?assert(verify_error(ErrorDesc)),
-         erlang:unlink(P) end]}].
+    [{setup, fun test_util:port_setup/0,
+      fun test_util:port_teardown/1,
+      [fun() ->
+               P = test_util:get_thing(),
+               {error, ErrorDesc} = js:define(P, <<"functoin foo(x, y) { return true; };">>),
+               ?assert(verify_error(ErrorDesc)),
+               erlang:unlink(P) end,
+       fun() ->
+               P = test_util:get_thing(),
+               ?assertMatch(ok, js:define(P, <<"function foo(x, y) { return true; };">>)),
+               {error, ErrorDesc} = js:eval(P, <<"foo(100, 200,);">>),
+               ?assert(verify_error(ErrorDesc)),
+               erlang:unlink(P) end]}].
 
 %% Internal functions
 verify_error([{<<"lineno">>, LineNo},
               {<<"source">>, Source}]) when is_number(LineNo),
                                             is_binary(Msg),
                                             is_binary(Source) ->
-  true;
+    true;
 verify_error(_) ->
-  false.
+    false.

File apps/erlang_js/tests/json_tests.erl

+-module(json_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+simple_test() ->
+    [?assertMatch(ok, js_json:test())].
+
+nested_proplists_test() ->
+    [?assertMatch(<<"{\"foo\":{\"bar\":1}}">>, list_to_binary(js_json:encode([{<<"foo">>, [{<<"bar">>, 1}]}]))),
+     ?assertMatch(<<"[123,\"abc\",{\"foo\":\"bar\"}]">>, list_to_binary(js_json:encode([123, <<"abc">>, [{<<"foo">>, <<"bar">>}]])))].

File apps/erlang_js/tests/test_suite.erl

 
 all_test_() ->
   [{module, driver_tests},
-   {module, eval_tests}].
+   {module, eval_tests},
+   {module, json_tests}].

File apps/js_data/rebar.config

Empty file added.

File apps/js_data/src/jsd_generator.erl

+-module(jsd_generator).
+
+-compile([export_all]).
+%-export([generate/2]).
+
+generate(Client, Count) ->
+    rand_init(),
+    Records = generate_data(Client, Count, []),
+    Obj = riak_object:new(<<"customers">>, <<"customer_list">>, Records),
+    Client:put(Obj, 1).
+
+%% Internal functions
+generate_data(_Client, 0, Accum) ->
+    lists:reverse(Accum);
+generate_data(Client, Count, Accum) ->
+    generate_data(Client, Count - 1, [generate_record(Count)|Accum]).
+
+generate_record(Count) ->
+    [{id, generate_id(Count)},
+     {name, generate_name()},
+     {sales_group, rand_int(8)},
+     {avg_sales, rand_float(1000)},
+     {last_sales_date, rand_timestamp(120)}].
+
+generate_id(Id) ->
+    list_to_binary(pad_id(integer_to_list(Id))).
+
+pad_id(Id) when length(Id) < 9 ->
+    pad_id(["0"|Id]);
+pad_id(Id) ->
+    Id.
+
+rand_timestamp(Limit) ->
+    Now = erlang:now(),
+    T1 = calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(Now)) -
+         calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
+    T1 - (rand_int(Limit) * 86400).
+
+generate_name() ->
+    FirstName = rand_string(),
+    LastName = rand_string(),
+    list_to_binary([LastName, ", ", FirstName]).
+
+rand_float(Limit) ->
+    [F] = io_lib:format("~6.2f", [(random:uniform() * Limit)]),
+    list_to_float(string:strip(lists:flatten(F))).
+
+rand_int() ->
+    rand_int(2000000).
+
+rand_int(Limit) ->
+    random:uniform(Limit).
+
+rand_init() ->
+    {T1, T2, T3} = erlang:now(),
+    random:seed(T1, T2, T3).
+
+rand_string() ->
+    rand_string([rand_alpha()], rand_int(15)).
+
+rand_string(Accum, 0) ->
+    lists:reverse(Accum);
+rand_string(Accum, Count) ->
+    rand_string([rand_alpha(lower)|Accum], Count - 1).
+
+rand_alpha(lower) ->
+    rand_alpha() + 31.
+
+rand_alpha() ->
+    random:uniform(26) + 64.
+
+integer_to_binary(N) ->
+    list_to_binary(integer_to_list(N)).

File apps/riak/ebin/riak.app

   {env, [
          %% Cluster name
          {cluster_name, "default"},
-         
+
          %% Default location of ringstate
          {ring_state_dir, "data/ring"},
-         
+
          %% Default ring creation size
          {ring_creation_size, 64},
-         
+
          %% Default gossip interval (milliseconds)
          {gossip_interval, 60000},
 
          {add_paths, []}
         ]}
  ]}.
-

File apps/riak/src/riak_mapreduce_fsm.erl

 %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 %% KIND, either express or implied.  See the License for the
 %% specific language governing permissions and limitations
-%% under the License.    
+%% under the License.
 
 %% @doc riak_mapreduce_fsm is the driver of a mapreduce query.
 %%
 -export([init/1, handle_event/3, handle_sync_event/4,
          handle_info/3, terminate/3, code_change/4]).
 
--export([wait/2]). 
+-export([wait/2]).
 
 -record(state, {client,reqid,fsms,starttime,timeout,ring,input_done}).
 
                                 false -> {bad_qterm, QTerm};
                                 true -> check_query_syntax(Rest)
                             end;
+                        {jsfun, JF_F} when is_binary(JF_F) ->
+                            check_query_syntax(Rest);
                         _ -> {bad_qterm, QTerm}
                     end
             end
     end;
 check_query_syntax([BadQTerm|_]) -> {bad_qterm,BadQTerm}.
 
-make_phase_fsms(Query, Ring) -> 
+make_phase_fsms(Query, Ring) ->
     make_phase_fsms(lists:reverse(Query),final,[], Ring).
 make_phase_fsms([], _NextFSM, FSMs, _Ring) -> FSMs;
-make_phase_fsms([QTerm|Rest], NextFSM, FSMs, Ring) -> 
+make_phase_fsms([QTerm|Rest], NextFSM, FSMs, Ring) ->
     PhaseMod = case QTerm of
         {reduce, _, _, _} -> riak_reduce_phase_fsm;
         {map, _, _, _} -> riak_map_phase_fsm;
 
 %% @private
 code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}.
-

File apps/riak/src/riak_vnode.erl

 %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 %% KIND, either express or implied.  See the License for the
 %% specific language governing permissions and limitations
-%% under the License.    
+%% under the License.
 
 -module(riak_vnode).
 -behaviour(gen_fsm).
 
+-include_lib("eunit/include/eunit.hrl").
+
 -export([start/1]).
 -export([init/1, handle_event/3, handle_sync_event/4,
          handle_info/3, terminate/3, code_change/4]).
 
 -define(TIMEOUT, 60000).
 
--record(state, {idx,mapcache,mod,modstate,waiting_diffobjs}).
+-record(state, {idx,mapcache,mod,modstate,waiting_diffobjs,jsctx}).
 
 start(Idx) ->
     gen_fsm:start(?MODULE, [Idx], []).
     Mod = riak:get_app_env(storage_backend),
     Configuration = riak:get_app_env(),
     {ok, ModState} = Mod:start(VNodeIndex, Configuration),
-    StateData0 = #state{idx=VNodeIndex,mod=Mod,modstate=ModState},
+    {ok, Ctx} = js_driver:new(),
+    StateData0 = #state{idx=VNodeIndex,mod=Mod,modstate=ModState,jsctx=Ctx},
     {next_state, StateName, StateData, Timeout} = hometest(StateData0),
     {ok, StateName, StateData, Timeout}.
 
                StateData=#state{mod=Mod,modstate=ModState}) ->
     % send each obj (BKey) in difflist to targetnode
     % return a state with waiting_diffobjs populated
-    Sent = [K || K <- [send_diff_obj(TargetNode,BKey,Mod,ModState) 
+    Sent = [K || K <- [send_diff_obj(TargetNode,BKey,Mod,ModState)
                        || BKey <- DiffList], K /= nop],
     StateData#state{waiting_diffobjs=Sent}.
 send_diff_obj(TargetNode,BKey,Mod,ModState) ->
     StateData = StateData0#state{waiting_diffobjs=[]},
     [Mod:delete(ModState, BKey) || BKey <- WD],
     case Mod:list(ModState) of
-        [] -> 
+        [] ->
             Mod:stop(ModState),
             {stop,normal,StateData};
         _ ->
     {next_state,waiting_diffobjs,StateData,?TIMEOUT};
 merk_waiting({diffobj,{_BKey,_BinObj,_RemNode}}, StateData) ->
     hometest(StateData);
-merk_waiting({map, ClientPid, QTerm, BKey, KeyData},
-             StateData=#state{mapcache=Cache,mod=Mod,modstate=ModState}) ->
-    do_map(ClientPid,QTerm,BKey,KeyData,Cache,Mod,ModState,self()),
+merk_waiting({map, ClientPid, QTerm, BKey, KeyData}, StateData) ->
+    do_map(ClientPid,QTerm,BKey,KeyData,StateData,self()),
     {next_state,merk_waiting,StateData,?TIMEOUT};
 merk_waiting({put, FSM_pid, _BKey, _RObj, ReqID, _FSMTime},
              StateData=#state{idx=Idx}) ->
     {next_state,active,StateData#state{waiting_diffobjs=[]},?TIMEOUT};
 waiting_diffobjs({diffobj,{_BKey,_BinObj,_RemNode}}, StateData) ->
     hometest(StateData);
-waiting_diffobjs({map, ClientPid, QTerm, BKey, KeyData},
-                 StateData=#state{mapcache=Cache,mod=Mod,modstate=ModState}) ->
-    do_map(ClientPid,QTerm,BKey,KeyData,Cache,Mod,ModState,self()),
+waiting_diffobjs({map, ClientPid, QTerm, BKey, KeyData}, StateData) ->
+    do_map(ClientPid,QTerm,BKey,KeyData,StateData,self()),
     {next_state,waiting_diffobjs,StateData,?TIMEOUT};
 waiting_diffobjs({put, FSM_pid, _BKey, _RObj, ReqID, _FSMTime},
                  StateData=#state{idx=Idx}) ->
     do_diffobj_put(BKey, binary_to_term(BinObj), StateData),
     gen_fsm:send_event(FromVN,{resolved_diffobj,BKey}),
     {next_state,active,StateData,?TIMEOUT};
-active({map, ClientPid, QTerm, BKey, KeyData},
-       StateData=#state{mapcache=Cache,mod=Mod,modstate=ModState}) ->
-    do_map(ClientPid,QTerm,BKey,KeyData,Cache,Mod,ModState,self()),
+active({map, ClientPid, QTerm, BKey, KeyData}, StateData) ->
+    do_map(ClientPid,QTerm,BKey,KeyData,StateData,self()),
     {next_state,active,StateData,?TIMEOUT};
 active({put, FSM_pid, BKey, RObj, ReqID, FSMTime},
        StateData=#state{idx=Idx,mapcache=Cache}) ->
 
 %% @private
 % upon receipt of a handoff datum, there is no client FSM
-do_diffobj_put(BKey={Bucket,_}, DiffObj, 
+do_diffobj_put(BKey={Bucket,_}, DiffObj,
        _StateData=#state{mod=Mod,modstate=ModState}) ->
     ReqID = erlang:phash2(erlang:now()),
     case syntactic_put_merge(Mod, ModState, BKey, DiffObj, ReqID) of
 
 %% @private
 % upon receipt of a client-initiated put
-do_put(FSM_pid, BKey, RObj, ReqID, PruneTime, 
+do_put(FSM_pid, BKey, RObj, ReqID, PruneTime,
        _State=#state{idx=Idx,mod=Mod,modstate=ModState}) ->
-    {ok,Ring} = riak_ring_manager:get_my_ring(),    
+    {ok,Ring} = riak_ring_manager:get_my_ring(),
     {Bucket,_Key} = BKey,
     BProps = riak_bucket:get_bucket(Bucket, Ring),
     case syntactic_put_merge(Mod, ModState, BKey, RObj, ReqID) of
-        oldobj -> 
+        oldobj ->
             gen_fsm:send_event(FSM_pid, {dw, Idx, ReqID});
         {newobj, NewObj} ->
             VC = riak_object:vclock(NewObj),
 
 %% @private
 do_map(ClientPid,{map,FunTerm,Arg,_Acc},
-       BKey,KeyData,Cache,Mod,ModState,VNode) ->
+       BKey,KeyData,#state{mapcache=Cache}=State,VNode) ->
     CacheVal = case FunTerm of
         {qfun,_} -> not_cached; % live funs are not cached
+        {jsfun,_} -> not_cached; % Javascript funs are not cached
         {modfun,CMod,CFun} ->
             case orddict:find(BKey, Cache) of
                 error -> not_cached;
     end,
     RetVal = case CacheVal of
         not_cached ->
-             uncached_map(BKey,Mod,ModState,FunTerm,Arg,KeyData,VNode);
+             uncached_map(BKey,State,FunTerm,Arg,KeyData,VNode);
         CV ->
              {mapexec_reply, CV, self()}
     end,
+    ?debugFmt("RetVal: ~p~n", [RetVal]),
     gen_fsm:send_event(ClientPid, RetVal).
-uncached_map(BKey,Mod,ModState,FunTerm,Arg,KeyData,VNode) ->
+uncached_map(BKey,#state{mod=Mod,modstate=ModState,jsctx=JsCtx},FunTerm,Arg,KeyData,VNode) ->
     riak_eventer:notify(riak_vnode, uncached_map, {FunTerm,Arg,BKey}),
     case do_get_binary(BKey, Mod, ModState) of
         {ok, Binary} ->
             V = binary_to_term(Binary),
-            uncached_map1(V,FunTerm,Arg,BKey,KeyData,VNode);
+            uncached_map1(V,JsCtx,FunTerm,Arg,BKey,KeyData,VNode);
         {error, notfound} ->
-            uncached_map1({error, notfound},FunTerm,Arg,BKey,KeyData,VNode);
+            uncached_map1({error, notfound},JsCtx,FunTerm,Arg,BKey,KeyData,VNode);
         X -> {mapexec_error, self(), X}
     end.
-uncached_map1(V,FunTerm,Arg,BKey,KeyData,VNode) ->
+uncached_map1(V,JsCtx,FunTerm,Arg,BKey,KeyData,VNode) ->
+    ?debugMsg("Running uncached_map1"),
     try
         MapVal = case FunTerm of
             {qfun,F} -> (F)(V,KeyData,Arg);
+            {jsfun,F} -> invoke_js(JsCtx, [extract_values(V),KeyData,Arg], <<"riak_mapper">>, F);
             {modfun,M,F} ->
                 MF_Res = M:F(V,KeyData,Arg),
                 gen_fsm:send_event(VNode,
     end.
 
 %% @private
+extract_values(V) ->
+    case riak_object:value_count(V) of
+        0 ->
+            [];
+        1 ->
+            riak_object:get_value(V);
+        _ ->
+            riak_object:get_values(V)
+    end.
+
+invoke_js(JsCtx, {error, notfound}, FunName, F) ->
+    %% Encode error as proplist so we can jsonify the value
+    invoke_js(JsCtx, [{<<"error">>, <<"notfound">>}], FunName, F);
+invoke_js(JsCtx, V, FunName, F) ->
+    ?debugMsg("Running invoke_js"),
+    F1 = list_to_binary(["var ", FunName, "=", F]),
+    js:define(JsCtx, F1),
+    case js:call(JsCtx, FunName, [V]) of
+        {ok, Retval} ->
+            Retval;
+        Error ->
+            Error
+    end.
+%% @private
 do_merkle(Me,RemoteVN,RemoteMerkle,ObjList,StateData) ->
     % given a RemoteMerkle over the ObjList from RemoteVN
     % determine which elements in ObjList we differ on
             case riak_object:vclock(ResObj) =:= riak_object:vclock(Obj0) of
                 true -> oldobj;
                 false -> {newobj, ResObj}
-            end    
+            end
     end.
 
 %% @private
 
 %% @private
 terminate(_Reason, _StateName, _State) -> ok.
-

File rebar.config

 	    "apps/webmachine",
             "apps/riak",
 	    "apps/erlang_js",
+	    "apps/js_data",
             "rel"]}.