diff --git a/c_src/encoder.c b/c_src/encoder.c index a751fd7..c3281d2 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -10,12 +10,24 @@ #define BIN_INC_SIZE 2048 +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) + +#define MAYBE_PRETTY(e) \ +do { \ + if(e->pretty) { \ + if(!enc_shift(e)) \ + return 0; \ + } \ +} while(0) + typedef struct { ErlNifEnv* env; jiffy_st* atoms; int uescape; + int pretty; + int shiftcnt; int count; int iolen; @@ -28,6 +40,22 @@ typedef struct { size_t i; } Encoder; + +// String constants for pretty printing. +// Every string starts with its length. +#define NUM_SHIFTS 8 +static char* shifts[NUM_SHIFTS] = { + "\x01\n", + "\x03\n ", + "\x05\n ", + "\x07\n ", + "\x09\n ", + "\x0b\n ", + "\x0d\n ", + "\x0f\n " +}; + + int enc_init(Encoder* e, ErlNifEnv* env, ERL_NIF_TERM opts, ErlNifBinary* bin) { @@ -36,6 +64,8 @@ enc_init(Encoder* e, ErlNifEnv* env, ERL_NIF_TERM opts, ErlNifBinary* bin) e->env = env; e->atoms = enif_priv_data(env); e->uescape = 0; + e->pretty = 0; + e->shiftcnt = 0; e->count = 0; if(!enif_is_list(env, opts)) { @@ -45,6 +75,8 @@ enc_init(Encoder* e, ErlNifEnv* env, ERL_NIF_TERM opts, ErlNifBinary* bin) while(enif_get_list_cell(env, opts, &val, &opts)) { if(enif_compare(val, e->atoms->atom_uescape) == 0) { e->uescape = 1; + } else if(enif_compare(val, e->atoms->atom_pretty) == 0) { + e->pretty = 1; } else { return 0; } @@ -376,16 +408,42 @@ enc_char(Encoder* e, char c) return 1; } +static int +enc_shift(Encoder* e) { + int i; + char* shift; + assert(e->shiftcnt >= 0 && "Invalid shift count."); + shift = shifts[MIN(e->shiftcnt, NUM_SHIFTS-1)]; + + if(!enc_literal(e, shift + 1, *shift)) + return 0; + + // Finish the rest of this shift it's it bigger than + // our largest predefined constant. + for(i = NUM_SHIFTS - 1; i < e->shiftcnt; i++) { + if(!enc_literal(e, " ", 2)) + return 0; + } + + return 1; +} + static inline int enc_start_object(Encoder* e) { e->count++; - return enc_char(e, '{'); + e->shiftcnt++; + if(!enc_char(e, '{')) + return 0; + MAYBE_PRETTY(e); + return 1; } static inline int enc_end_object(Encoder* e) { + e->shiftcnt--; + MAYBE_PRETTY(e); return enc_char(e, '}'); } @@ -393,25 +451,36 @@ static inline int enc_start_array(Encoder* e) { e->count++; - return enc_char(e, '['); + e->shiftcnt++; + if(!enc_char(e, '[')) + return 0; + MAYBE_PRETTY(e); + return 1; } static inline int enc_end_array(Encoder* e) { + e->shiftcnt--; + MAYBE_PRETTY(e); return enc_char(e, ']'); } static inline int enc_colon(Encoder* e) { + if(e->pretty) + return enc_literal(e, " : ", 3); return enc_char(e, ':'); } static inline int enc_comma(Encoder* e) { - return enc_char(e, ','); + if(!enc_char(e, ',')) + return 0; + MAYBE_PRETTY(e); + return 1; } ERL_NIF_TERM diff --git a/c_src/jiffy.c b/c_src/jiffy.c index c1dd8b1..8fdde2f 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -21,6 +21,7 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_bigdbl = make_atom(env, "bigdbl"); st->atom_partial = make_atom(env, "partial"); st->atom_uescape = make_atom(env, "uescape"); + st->atom_pretty = make_atom(env, "pretty"); // Markers used in encoding st->ref_object = make_atom(env, "$object_ref$"); diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 9b1f700..327657a 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -17,6 +17,7 @@ typedef struct { ERL_NIF_TERM atom_bigdbl; ERL_NIF_TERM atom_partial; ERL_NIF_TERM atom_uescape; + ERL_NIF_TERM atom_pretty; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; diff --git a/test/jiffy_tests.erl b/test/jiffy_tests.erl index 0997bc7..8d3929a 100644 --- a/test/jiffy_tests.erl +++ b/test/jiffy_tests.erl @@ -24,6 +24,13 @@ prop_encode_decode() -> end ). +prop_encode_decode_pretty() -> + ?FORALL(Data, json(), + begin + Data == jiffy:decode(jiffy:encode(Data, [pretty])) + end + ). + prop_encode_not_crash() -> ?FORALL(Data, any(), begin catch jiffy:encode(Data), true end).