diff --git a/Makefile b/Makefile index 7227ce4..831deb5 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ REBAR?=./rebar - all: build - clean: $(REBAR) clean rm -rf logs @@ -41,7 +39,7 @@ check: build etap eunit %.beam: %.erl - erlc -o test/ $< + erlc -DTEST_MAP -o test/ $< .PHONY: all clean distclean depends build etap eunit check diff --git a/c_src/decoder.c b/c_src/decoder.c index fc892da..58bb0ae 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -59,10 +59,11 @@ typedef struct { char* st_data; int st_size; int st_top; + int to_map; } Decoder; -void -dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ErlNifBinary* bin) +int +dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ERL_NIF_TERM opts, ErlNifBinary* bin) { int i; @@ -80,6 +81,7 @@ dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ErlNifBinary* bin) d->st_data = (char*) enif_alloc(STACK_SIZE_INC * sizeof(char)); d->st_size = STACK_SIZE_INC; d->st_top = 0; + d->to_map = 0; for(i = 0; i < d->st_size; i++) { d->st_data[i] = st_invalid; @@ -87,6 +89,16 @@ dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ErlNifBinary* bin) d->st_data[0] = st_value; d->st_top++; + + ERL_NIF_TERM val; + while(enif_get_list_cell(env, opts, &val, &opts)) { + if(enif_compare(val, d->atoms->atom_map) == 0) { + d->to_map = 1; + } else { + return 0; + } + } + return 1; } void @@ -575,8 +587,58 @@ parse: } ERL_NIF_TERM -make_object(ErlNifEnv* env, ERL_NIF_TERM pairs) +make_object_map(ErlNifEnv* env, ERL_NIF_TERM pairs) +{ + ERL_NIF_TERM ret; + ERL_NIF_TERM key, val; + +#if MAP_SUPPORT + ret = enif_make_new_map(env); + + while(enif_get_list_cell(env, pairs, &val, &pairs)) { + if(!enif_get_list_cell(env, pairs, &key, &pairs)) { + assert(0 == 1 && "Unbalanced object pairs."); + } + enif_make_map_put(env, ret, key, val, &ret); + } +#else + assert(0 == 1 && "maps not supported"); +#endif + + return ret; +} + + +ERL_NIF_TERM +make_empty_object_map(ErlNifEnv* env) { + ERL_NIF_TERM ret; +#if MAP_SUPPORT + ret = enif_make_new_map(env); +#else + assert(0 == 1 && "maps not supported"); +#endif + + return ret; +} + +ERL_NIF_TERM +make_empty_object(Decoder* d) { + ErlNifEnv* env = d->env; + if(d->to_map) { + return make_empty_object_map(env); + } + + return enif_make_tuple1(env, enif_make_list(env, 0)); +} + +ERL_NIF_TERM +make_object(Decoder* d, ERL_NIF_TERM pairs) { + ErlNifEnv* env = d->env; + if(d->to_map) { + return make_object_map(env, pairs); + } + ERL_NIF_TERM ret = enif_make_list(env, 0); ERL_NIF_TERM key, val; @@ -617,13 +679,20 @@ decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ERL_NIF_TERM val; ERL_NIF_TERM ret; - if(argc != 1) { + if(argc != 2) { return enif_make_badarg(env); } else if(!enif_inspect_binary(env, argv[0], &bin)) { return enif_make_badarg(env); } - dec_init(d, env, argv[0], &bin); + if(!dec_init(d, env, argv[0], argv[1], &bin)) { + return enif_make_badarg(env); + } + + if(d->to_map && !maps_enabled()) { + ret = dec_error(d, "map_unavailable"); + goto done; + } //fprintf(stderr, "Parsing:\r\n"); while(d->i < bin.size) { @@ -770,7 +839,7 @@ decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) dec_pop(d, st_key); dec_pop(d, st_object); dec_pop(d, st_value); - val = enif_make_tuple1(env, curr); + val = make_empty_object(d); if(!enif_get_list_cell(env, objs, &curr, &objs)) { ret = dec_error(d, "internal_error"); goto done; @@ -839,7 +908,7 @@ decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } dec_pop(d, st_object); dec_pop(d, st_value); - val = make_object(env, curr); + val = make_object(d, curr); if(!enif_get_list_cell(env, objs, &curr, &objs)) { ret = dec_error(d, "internal_error"); goto done; diff --git a/c_src/encoder.c b/c_src/encoder.c index c23b309..e748e59 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -623,6 +623,7 @@ encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) goto done; } } else if(enif_get_tuple(env, curr, &arity, &tuple)) { + // Old-fasioned object definition: {[{Key1, Val1}, {Key2, Val2}]} if(arity != 1) { ret = enc_error(e, "invalid_ejson"); goto done; @@ -665,6 +666,30 @@ encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) stack = enif_make_list_cell(env, curr, stack); stack = enif_make_list_cell(env, e->atoms->ref_object, stack); stack = enif_make_list_cell(env, tuple[1], stack); +#if MAP_SUPPORT + } else if(enif_is_map(env, curr)) { + ErlNifMapIterator iter; + if(!enif_map_iterator_create(env, curr, &iter, ERL_NIF_MAP_ITERATOR_HEAD)) { + ret = enc_error(e, "internal_error"); + goto done; + } + + curr = enif_make_list(env, 0); + ERL_NIF_TERM key, val, tup; + + while(!enif_map_iterator_is_tail(env, &iter)) { + if(!enif_map_iterator_get_pair(env, &iter, &key, &val)) { + ret = enc_error(e, "internal_error"); + goto done; + } + tup = enif_make_tuple2(env, key, val); + curr = enif_make_list_cell(env, tup, curr); + + enif_map_iterator_next(env, &iter); + } + + stack = enif_make_list_cell(env, enif_make_tuple1(env, curr), stack); +#endif } else if(enif_is_list(env, curr)) { if(!enc_start_array(e)) { ret = enc_error(e, "internal_error"); diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 964222c..0f106c3 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -23,6 +23,7 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_uescape = make_atom(env, "uescape"); st->atom_pretty = make_atom(env, "pretty"); st->atom_force_utf8 = make_atom(env, "force_utf8"); + st->atom_map = make_atom(env, "map"); // Markers used in encoding st->ref_object = make_atom(env, "$object_ref$"); @@ -54,7 +55,7 @@ unload(ErlNifEnv* env, void* priv) static ErlNifFunc funcs[] = { - {"nif_decode", 1, decode}, + {"nif_decode", 2, decode}, {"nif_encode", 2, encode} }; diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 3dda545..f609aa3 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -6,6 +6,8 @@ #include "erl_nif.h" +#define MAP_SUPPORT ((ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 6) || (ERL_NIF_MAJOR_VERSION > 2)) + typedef struct { ERL_NIF_TERM atom_ok; ERL_NIF_TERM atom_error; @@ -19,6 +21,7 @@ typedef struct { ERL_NIF_TERM atom_uescape; ERL_NIF_TERM atom_pretty; ERL_NIF_TERM atom_force_utf8; + ERL_NIF_TERM atom_map; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; @@ -27,6 +30,7 @@ typedef struct { ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name); ERL_NIF_TERM make_ok(jiffy_st* st, ErlNifEnv* env, ERL_NIF_TERM data); ERL_NIF_TERM make_error(jiffy_st* st, ErlNifEnv* env, const char* error); +int maps_enabled(void); ERL_NIF_TERM decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -42,4 +46,5 @@ int unicode_from_pair(int hi, int lo); int unicode_uescape(int c, char* buf); int double_to_shortest(char *buf, size_t size, size_t* len, double val); + #endif // Included JIFFY_H diff --git a/c_src/util.c b/c_src/util.c index f1be3ec..460f332 100644 --- a/c_src/util.c +++ b/c_src/util.c @@ -24,3 +24,12 @@ make_error(jiffy_st* st, ErlNifEnv* env, const char* error) { return enif_make_tuple2(env, st->atom_error, make_atom(env, error)); } + +int +maps_enabled(void) { +#if MAP_SUPPORT + return 1; +#else + return 0; +#endif +} diff --git a/src/jiffy.erl b/src/jiffy.erl index c4b3d69..e4458f6 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -2,22 +2,25 @@ % See the LICENSE file for more information. -module(jiffy). --export([decode/1, encode/1, encode/2]). +-export([decode/1, decode/2, encode/1, encode/2]). -define(NOT_LOADED, not_loaded(?LINE)). -on_load(init/0). -decode(Data) when is_binary(Data) -> - case nif_decode(Data) of +decode(Data) -> + decode(Data, []). + +decode(Data, Options) when is_list(Data) -> + decode(iolist_to_binary(Data), Options); +decode(Data, Options) when is_binary(Data) -> + case nif_decode(Data, Options) of {error, _} = Error -> throw(Error); {partial, EJson} -> finish_decode(EJson); EJson -> EJson - end; -decode(Data) when is_list(Data) -> - decode(iolist_to_binary(Data)). + end. encode(Data) -> @@ -99,7 +102,7 @@ init() -> not_loaded(Line) -> erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). -nif_decode(_Data) -> +nif_decode(_Data, _Options) -> ?NOT_LOADED. nif_encode(_Data, _Options) -> diff --git a/test/util.erl b/test/util.erl index ceaef89..6f35810 100644 --- a/test/util.erl +++ b/test/util.erl @@ -27,8 +27,19 @@ check_good({J, E}, Options) -> etap:is(do_encode(E, Options), J, ok_enc(E, J)); check_good({J, E, J2}, Options) -> etap:is(jiffy:decode(J), E, ok_dec(J, E)), + check_map({J, J2}, Options), etap:is(do_encode(E, Options), J2, ok_enc(E, J2)). +check_map({J, J2}, Options) -> + try jiffy:decode(J, [map]) of + E2 -> + % etap function breaks all tests because of etap:plan, so stop + % if map test failed + J2 = do_encode(E2, Options) + catch throw:{error, {1, map_unavailable}} -> + ok + end. + check_error({J, E}) -> etap:fun_is( fun({error, E1}) when E1 == E -> true; (E1) -> E1 end,