From fdf9f6179562d206a12a2b3953b4c87ec4c25641 Mon Sep 17 00:00:00 2001 From: "Paul J. Davis" Date: Sun, 3 Apr 2011 19:27:04 -0400 Subject: [PATCH] Initial import. Passes all eep0018 tests. --- .gitignore | 4 + Makefile | 11 + c_src/decoder.c | 934 ++++++++++++++++++ c_src/encoder.c | 556 +++++++++++ c_src/jiffy.c | 52 + c_src/jiffy.h | 30 + c_src/utf8.c | 128 +++ c_src/util.c | 23 + rebar | Bin 0 -> 102892 bytes rebar.config | 17 + src/jiffy.app.src | 7 + src/jiffy.erl | 62 ++ test/001-yajl-tests.t | 33 + test/002-literals.t | 19 + test/003-numbers.t | 47 + test/004-strings.t | 36 + test/005-arrays.t | 34 + test/006-maps.t | 34 + test/007-compound.t | 39 + test/cases/array.erl | 16 + test/cases/array.json | 6 + test/cases/array_close.erl | 1 + test/cases/array_close.json | 1 + test/cases/array_open.erl | 1 + test/cases/array_open.json | 1 + test/cases/bogus_char.erl | 1 + test/cases/bogus_char.json | 4 + test/cases/codepoints_from_unicode_org.erl | 1 + test/cases/codepoints_from_unicode_org.json | 1 + test/cases/deep_arrays.erl | 1 + test/cases/deep_arrays.json | 1 + test/cases/difficult_json_c_test_case.erl | 19 + test/cases/difficult_json_c_test_case.json | 1 + test/cases/doubles.erl | 1 + test/cases/doubles.json | 1 + test/cases/empty_array.erl | 1 + test/cases/empty_array.json | 1 + test/cases/empty_string.erl | 1 + test/cases/empty_string.json | 1 + test/cases/escaped_bulgarian.erl | 6 + test/cases/escaped_bulgarian.json | 4 + test/cases/escaped_foobar.erl | 1 + test/cases/escaped_foobar.json | 1 + test/cases/false.erl | 1 + test/cases/false.json | 1 + test/cases/false_then_garbage.erl | 1 + test/cases/false_then_garbage.json | 1 + test/cases/four_byte_utf8.erl | 1 + test/cases/four_byte_utf8.json | 2 + test/cases/integers.erl | 13 + test/cases/integers.json | 3 + test/cases/invalid_utf8.erl | 1 + test/cases/invalid_utf8.json | 1 + test/cases/isolated_surrogate_marker.erl | 1 + test/cases/isolated_surrogate_marker.json | 1 + test/cases/leading_zero_in_number.erl | 1 + test/cases/leading_zero_in_number.json | 1 + test/cases/lonely_minus_sign.erl | 1 + test/cases/lonely_minus_sign.json | 7 + test/cases/lonely_number.erl | 1 + test/cases/lonely_number.json | 1 + test/cases/map_close.erl | 1 + test/cases/map_close.json | 1 + test/cases/map_open.erl | 1 + test/cases/map_open.json | 1 + .../missing_integer_after_decimal_point.erl | 1 + .../missing_integer_after_decimal_point.json | 1 + test/cases/missing_integer_after_exponent.erl | 1 + .../cases/missing_integer_after_exponent.json | 1 + test/cases/non_utf8_char_in_string.erl | 1 + test/cases/non_utf8_char_in_string.json | 1 + test/cases/null.erl | 1 + test/cases/null.json | 1 + test/cases/null_then_garbage.erl | 1 + test/cases/null_then_garbage.json | 1 + test/cases/nulls_and_bools.erl | 5 + test/cases/nulls_and_bools.json | 5 + test/cases/simple.erl | 5 + test/cases/simple.json | 5 + test/cases/string_invalid_escape.erl | 1 + test/cases/string_invalid_escape.json | 1 + test/cases/string_invalid_hex_char.erl | 1 + test/cases/string_invalid_hex_char.json | 1 + test/cases/string_with_escapes.erl | 5 + test/cases/string_with_escapes.json | 3 + test/cases/string_with_invalid_newline.erl | 1 + test/cases/string_with_invalid_newline.json | 2 + test/cases/three_byte_utf8.erl | 4 + test/cases/three_byte_utf8.json | 1 + test/cases/true.erl | 1 + test/cases/true.json | 1 + test/cases/true_then_garbage.erl | 1 + test/cases/true_then_garbage.json | 1 + test/cases/unescaped_bulgarian.erl | 5 + test/cases/unescaped_bulgarian.json | 1 + test/etap.erl | 612 ++++++++++++ test/util.erl | 31 + 97 files changed, 2885 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 c_src/decoder.c create mode 100644 c_src/encoder.c create mode 100644 c_src/jiffy.c create mode 100644 c_src/jiffy.h create mode 100644 c_src/utf8.c create mode 100644 c_src/util.c create mode 100755 rebar create mode 100644 rebar.config create mode 100644 src/jiffy.app.src create mode 100644 src/jiffy.erl create mode 100755 test/001-yajl-tests.t create mode 100755 test/002-literals.t create mode 100755 test/003-numbers.t create mode 100755 test/004-strings.t create mode 100755 test/005-arrays.t create mode 100755 test/006-maps.t create mode 100755 test/007-compound.t create mode 100644 test/cases/array.erl create mode 100644 test/cases/array.json create mode 100644 test/cases/array_close.erl create mode 100644 test/cases/array_close.json create mode 100644 test/cases/array_open.erl create mode 100644 test/cases/array_open.json create mode 100644 test/cases/bogus_char.erl create mode 100644 test/cases/bogus_char.json create mode 100644 test/cases/codepoints_from_unicode_org.erl create mode 100644 test/cases/codepoints_from_unicode_org.json create mode 100644 test/cases/deep_arrays.erl create mode 100644 test/cases/deep_arrays.json create mode 100644 test/cases/difficult_json_c_test_case.erl create mode 100644 test/cases/difficult_json_c_test_case.json create mode 100644 test/cases/doubles.erl create mode 100644 test/cases/doubles.json create mode 100644 test/cases/empty_array.erl create mode 100644 test/cases/empty_array.json create mode 100644 test/cases/empty_string.erl create mode 100644 test/cases/empty_string.json create mode 100644 test/cases/escaped_bulgarian.erl create mode 100644 test/cases/escaped_bulgarian.json create mode 100644 test/cases/escaped_foobar.erl create mode 100644 test/cases/escaped_foobar.json create mode 100644 test/cases/false.erl create mode 100644 test/cases/false.json create mode 100644 test/cases/false_then_garbage.erl create mode 100644 test/cases/false_then_garbage.json create mode 100644 test/cases/four_byte_utf8.erl create mode 100644 test/cases/four_byte_utf8.json create mode 100644 test/cases/integers.erl create mode 100644 test/cases/integers.json create mode 100644 test/cases/invalid_utf8.erl create mode 100644 test/cases/invalid_utf8.json create mode 100644 test/cases/isolated_surrogate_marker.erl create mode 100644 test/cases/isolated_surrogate_marker.json create mode 100644 test/cases/leading_zero_in_number.erl create mode 100644 test/cases/leading_zero_in_number.json create mode 100644 test/cases/lonely_minus_sign.erl create mode 100644 test/cases/lonely_minus_sign.json create mode 100644 test/cases/lonely_number.erl create mode 100644 test/cases/lonely_number.json create mode 100644 test/cases/map_close.erl create mode 100644 test/cases/map_close.json create mode 100644 test/cases/map_open.erl create mode 100644 test/cases/map_open.json create mode 100644 test/cases/missing_integer_after_decimal_point.erl create mode 100644 test/cases/missing_integer_after_decimal_point.json create mode 100644 test/cases/missing_integer_after_exponent.erl create mode 100644 test/cases/missing_integer_after_exponent.json create mode 100644 test/cases/non_utf8_char_in_string.erl create mode 100644 test/cases/non_utf8_char_in_string.json create mode 100644 test/cases/null.erl create mode 100644 test/cases/null.json create mode 100644 test/cases/null_then_garbage.erl create mode 100644 test/cases/null_then_garbage.json create mode 100644 test/cases/nulls_and_bools.erl create mode 100644 test/cases/nulls_and_bools.json create mode 100644 test/cases/simple.erl create mode 100644 test/cases/simple.json create mode 100644 test/cases/string_invalid_escape.erl create mode 100644 test/cases/string_invalid_escape.json create mode 100644 test/cases/string_invalid_hex_char.erl create mode 100644 test/cases/string_invalid_hex_char.json create mode 100644 test/cases/string_with_escapes.erl create mode 100644 test/cases/string_with_escapes.json create mode 100644 test/cases/string_with_invalid_newline.erl create mode 100644 test/cases/string_with_invalid_newline.json create mode 100644 test/cases/three_byte_utf8.erl create mode 100644 test/cases/three_byte_utf8.json create mode 100644 test/cases/true.erl create mode 100644 test/cases/true.json create mode 100644 test/cases/true_then_garbage.erl create mode 100644 test/cases/true_then_garbage.json create mode 100644 test/cases/unescaped_bulgarian.erl create mode 100644 test/cases/unescaped_bulgarian.json create mode 100644 test/etap.erl create mode 100644 test/util.erl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32533cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.o +*.so +ebin/jiffy.app diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d573bb6 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ + +all: build + +%.beam: %.erl + erlc -o test/ $< + +build: c_src/decoder.c + ./rebar compile + +check: test/etap.beam test/util.beam + prove test/*.t diff --git a/c_src/decoder.c b/c_src/decoder.c new file mode 100644 index 0000000..16effd6 --- /dev/null +++ b/c_src/decoder.c @@ -0,0 +1,934 @@ +#include +#include +#include +#include +#include + +#include "erl_nif.h" +#include "jiffy.h" + +#define U(c) ((unsigned char) (c)) +#define ERROR(i, msg) make_error(st, env, msg) + +#define STACK_SIZE_INC 64 +#define NUM_BUF_LEN 256 + +enum { + st_value=0, + st_object, + st_array, + st_key, + st_colon, + st_comma, + st_done, + st_invalid +} JsonState; + +enum { + nst_init=0, + nst_sign, + nst_mantissa, + nst_frac0, + nst_frac1, + nst_frac, + nst_esign, + nst_edigit +} JsonNumState; + +typedef struct { + ErlNifEnv* env; + jiffy_st* atoms; + + ERL_NIF_TERM arg; + ErlNifBinary bin; + + int has_bignum; + + char* p; + unsigned char* u; + int i; + int len; + + char* st_data; + int st_size; + int st_top; +} Decoder; + +void +dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ErlNifBinary* bin) +{ + int i; + + d->env = env; + d->atoms = enif_priv_data(env); + d->arg = arg; + + d->has_bignum = 0; + + d->p = (char*) bin->data; + d->u = bin->data; + d->len = bin->size; + d->i = 0; + + d->st_data = (char*) enif_alloc(STACK_SIZE_INC * sizeof(char)); + d->st_size = STACK_SIZE_INC; + d->st_top = 0; + + for(i = 0; i < d->st_size; i++) { + d->st_data[i] = st_invalid; + } + + d->st_data[0] = st_value; + d->st_top++; +} + +void +dec_destroy(Decoder* d) +{ + if(d->st_data != NULL) { + enif_free(d->st_data); + } +} + +ERL_NIF_TERM +dec_error(Decoder* d, const char* atom) +{ + ERL_NIF_TERM pos = enif_make_int(d->env, d->i+1); + ERL_NIF_TERM msg = make_atom(d->env, atom); + ERL_NIF_TERM ret = enif_make_tuple2(d->env, pos, msg); + return enif_make_tuple2(d->env, d->atoms->atom_error, ret); +} + +char +dec_curr(Decoder* d) +{ + return d->st_data[d->st_top-1]; +} + +int +dec_top(Decoder* d) +{ + return d->st_top; +} + +void +dec_push(Decoder* d, char val) +{ + char* tmp; + int new_sz; + int i; + + if(d->st_top >= d->st_size) { + new_sz = d->st_size + STACK_SIZE_INC; + tmp = (char*) enif_alloc(new_sz * sizeof(char)); + memcpy(tmp, d->st_data, d->st_size * sizeof(char)); + enif_free(d->st_data); + d->st_data = tmp; + d->st_size = new_sz; + for(i = d->st_top; i < d->st_size; i++) { + d->st_data[i] = st_invalid; + } + } + + d->st_data[d->st_top++] = val; +} + +void +dec_pop(Decoder* d, char val) +{ + assert(d->st_data[d->st_top-1] == val && "popped invalid state."); + d->st_data[d->st_top-1] = st_invalid; + d->st_top--; +} + +int +dec_string(Decoder* d, ERL_NIF_TERM* value) +{ + int has_escape = 0; + int num_escapes = 0; + int st; + int ulen; + int ui; + int hi; + int lo; + char* chrbuf; + int chrpos; + + if(d->p[d->i] != '\"') { + return 0; + } + d->i++; + + st = d->i; + + while(d->i < d->len) { + if(d->u[d->i] < 0x20) { + return 0; + } else if(d->p[d->i] == '\"') { + d->i++; + goto parse; + } else if(d->p[d->i] == '\\') { + if(d->i+1 >= d->len) { + return 0; + } + has_escape = 1; + num_escapes += 1; + d->i++; + switch(d->p[d->i]) { + case '\"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + d->i++; + break; + case 'u': + hi = 0; + lo = 0; + d->i++; + if(d->i + 4 >= d->len) { + return 0; + } + hi = int_from_hex(&(d->u[d->i])); + d->i += 4; + if(hi < 0) { + return 0; + } + if(hi >= 0xD800 && hi < 0xDC00) { + if(d->i + 6 >= d->len) { + return 0; + } + if(d->p[d->i++] != '\\') { + return 0; + } else if(d->p[d->i++] != 'u') { + return 0; + } + lo = int_from_hex(&(d->u[d->i])); + if(lo < 0) { + return 0; + } + hi = utf8_from_pair(hi, lo); + if(hi < 0) { + return 0; + } + } + hi = utf8_len(hi); + if(hi < 0) { + return 0; + } + if(lo == 0) { + num_escapes += 5 - hi; + } else { + num_escapes += 11 - hi; + } + break; + default: + return 0; + } + } else if(d->u[d->i] < 0x80) { + d->i++; + } else { + ulen = -1; + if((d->u[d->i] & 0xE0) == 0xC0) { + ulen = 1; + } else if((d->u[d->i] & 0xF0) == 0xE0) { + ulen = 2; + } else if((d->u[d->i] & 0xF8) == 0xF0) { + ulen = 3; + } else if((d->u[d->i] & 0xFC) == 0xF8) { + ulen = 4; + } else if((d->u[d->i] & 0xFE) == 0xFC) { + ulen = 5; + } + if(ulen < 0) { + return 0; + } + if(d->i + ulen >= d->len) { + return 0; + } + for(ui = 0; ui < ulen; ui++) { + if((d->u[d->i+1+ui] & 0xC0) != 0x80) { + return 0; + } + } + // Wikipedia says I have to check that a UTF-8 encoding + // uses as few bits as possible. This means that we + // can't do things like encode 't' in three bytes. + // To check this all we need to ensure is that for each + // of the following bit patterns that there is at least + // one 1 bit in each of the x's + // 11: 110xxxxy 10yyyyyy + // 16: 1110xxxx 10xyyyyy 10yyyyyy + // 21: 11110xxx 10xxyyyy 10yyyyyy 10yyyyyy + // 26: 111110xx 10xxxyyy 10yyyyyy 10yyyyyy 10yyyyyy + // 31: 1111110x 10xxxxyy 10yyyyyy 10yyyyyy 10yyyyyy 10yyyyyy + if(ulen == 1) { + if((d->u[d->i] & 0x1E) == 0) return 0; + } else if(ulen == 2) { + if((d->u[d->i] & 0x0F) + (d->u[d->i+1] & 0x20) == 0) return 0; + } else if(ulen == 3) { + if((d->u[d->i] & 0x07) + (d->u[d->i+1] & 0x30) == 0) return 0; + } else if(ulen == 4) { + if((d->u[d->i] & 0x03) + (d->u[d->i+1] & 0x38) == 0) return 0; + } else if(ulen == 5) { + if((d->u[d->i] & 0x01) + (d->u[d->i+1] & 0x3C) == 0) return 0; + } + d->i += 1 + ulen; + } + } + +parse: + if(!has_escape) { + *value = enif_make_sub_binary(d->env, d->arg, st, (d->i - st - 1)); + return 1; + } + + hi = 0; + lo = 0; + + ulen = (d->i - 1) - st - num_escapes; + chrbuf = (char*) enif_make_new_binary(d->env, ulen, value); + chrpos = 0; + ui = st; + while(ui < d->i - 1) { + if(d->p[ui] != '\\') { + chrbuf[chrpos++] = d->p[ui++]; + continue; + } + ui++; + switch(d->p[ui]) { + case '\"': + case '\\': + case '/': + chrbuf[chrpos++] = d->p[ui]; + ui++; + break; + case 'b': + chrbuf[chrpos++] = '\b'; + ui++; + break; + case 'f': + chrbuf[chrpos++] = '\f'; + ui++; + break; + case 'n': + chrbuf[chrpos++] = '\n'; + ui++; + break; + case 'r': + chrbuf[chrpos++] = '\r'; + ui++; + break; + case 't': + chrbuf[chrpos++] = '\t'; + ui++; + break; + case 'u': + ui++; + hi = int_from_hex(&(d->u[ui])); + if(hi >= 0xD800 && hi < 0xDC00) { + lo = int_from_hex(&(d->u[ui+6])); + hi = utf8_from_pair(hi, lo); + ui += 10; + } else { + ui += 4; + } + hi = utf8_to_binary(hi, (unsigned char*) chrbuf+chrpos); + if(hi < 0) { + return 0; + } + chrpos += hi; + break; + default: + return 0; + } + } + + return 1; +} + +int +dec_number(Decoder* d, ERL_NIF_TERM* value) +{ + char state = nst_init; + char nbuf[NUM_BUF_LEN]; + int st = d->i; + int is_double = 0; + double dval; + long lval; + + while(d->i < d->len) { + switch(state) { + case nst_init: + switch(d->p[d->i]) { + case '-': + state = nst_sign; + d->i++; + break; + case '0': + state = nst_frac0; + d->i++; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state = nst_mantissa; + d->i++; + break; + default: + return 0; + } + break; + + case nst_sign: + switch(d->p[d->i]) { + case '0': + state = nst_frac0; + d->i++; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state = nst_mantissa; + d->i++; + break; + default: + return 0; + } + break; + + case nst_mantissa: + switch(d->p[d->i]) { + case '.': + state = nst_frac1; + d->i++; + break; + case 'e': + case 'E': + state = nst_esign; + d->i++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d->i++; + break; + default: + goto parse; + } + break; + + case nst_frac0: + switch(d->p[d->i]) { + case '.': + state = nst_frac1; + d->i++; + break; + case 'e': + case 'E': + state = nst_esign; + d->i++; + break; + default: + goto parse; + } + break; + + case nst_frac1: + is_double = 1; + switch(d->p[d->i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state = nst_frac; + d->i++; + break; + default: + goto parse; + } + break; + + case nst_frac: + switch(d->p[d->i]) { + case 'e': + case 'E': + state = nst_esign; + d->i++; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d->i++; + break; + default: + goto parse; + } + break; + + case nst_esign: + is_double = 1; + switch(d->p[d->i]) { + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state = nst_edigit; + d->i++; + break; + default: + return 0; + } + break; + + case nst_edigit: + switch(d->p[d->i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d->i++; + break; + default: + goto parse; + } + break; + + default: + return 0; + } + } + +parse: + + switch(state) { + case nst_init: + case nst_sign: + case nst_frac1: + case nst_esign: + return 0; + default: + break; + } + + + if(st - d->i > NUM_BUF_LEN && is_double) { + return 0; + } else if(st - d->i > NUM_BUF_LEN) { + d->has_bignum = 1; + *value = enif_make_sub_binary(d->env, d->arg, st, d->i - st); + *value = enif_make_tuple2(d->env, d->atoms->atom_bignum, *value); + return 1; + } + + memset(nbuf, 0, NUM_BUF_LEN); + memcpy(nbuf, &(d->p[st]), d->i - st); + + errno = 0; + if(is_double) { + dval = strtod(nbuf, NULL); + if(errno == ERANGE) { + return 0; + } + *value = enif_make_double(d->env, dval); + return 1; + } + + lval = strtol(nbuf, NULL, 10); + if(errno == ERANGE) { + d->has_bignum = 1; + *value = enif_make_sub_binary(d->env, d->arg, st, d->i - st); + *value = enif_make_tuple2(d->env, d->atoms->atom_bignum, *value); + } else { + *value = enif_make_int64(d->env, lval); + } + return 1; +} + +ERL_NIF_TERM +make_object(ErlNifEnv* env, ERL_NIF_TERM pairs) +{ + ERL_NIF_TERM ret = enif_make_list(env, 0); + ERL_NIF_TERM key, val; + + while(enif_get_list_cell(env, pairs, &val, &pairs)) { + if(!enif_get_list_cell(env, pairs, &key, &pairs)) { + assert(0 == 1 && "Unbalanced object pairs."); + } + val = enif_make_tuple2(env, key, val); + ret = enif_make_list_cell(env, val, ret); + } + + return enif_make_tuple1(env, ret); +} + +ERL_NIF_TERM +make_array(ErlNifEnv* env, ERL_NIF_TERM list) +{ + ERL_NIF_TERM ret = enif_make_list(env, 0); + ERL_NIF_TERM item; + + while(enif_get_list_cell(env, list, &item, &list)) { + ret = enif_make_list_cell(env, item, ret); + } + + return ret; +} + +ERL_NIF_TERM +decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + Decoder dec; + Decoder* d = &dec; + + ErlNifBinary bin; + + ERL_NIF_TERM objs = enif_make_list(env, 0); + ERL_NIF_TERM curr; + ERL_NIF_TERM val; + ERL_NIF_TERM ret; + + if(argc != 1) { + 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); + + //fprintf(stderr, "Parsing:\r\n"); + while(d->i < bin.size) { + //fprintf(stderr, "state: %d\r\n", dec_curr(d)); + switch(dec_curr(d)) { + case st_value: + switch(d->p[d->i]) { + case ' ': + case '\n': + case '\r': + case '\t': + d->i++; + break; + case 'n': + if(d->i + 3 >= d->len) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + if(memcmp(&(d->p[d->i]), "null", 4) != 0) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + val = d->atoms->atom_null; + dec_pop(d, st_value); + d->i += 4; + break; + case 't': + if(d->i + 3 >= d->len) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + if(memcmp(&(d->p[d->i]), "true", 4) != 0) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + val = d->atoms->atom_true; + dec_pop(d, st_value); + d->i += 4; + break; + case 'f': + if(d->i + 4 >= bin.size) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + if(memcmp(&(d->p[d->i]), "false", 5) != 0) { + ret = dec_error(d, "invalid_literal"); + goto done; + } + val = d->atoms->atom_false; + dec_pop(d, st_value); + d->i += 5; + break; + case '\"': + if(!dec_string(d, &val)) { + ret = dec_error(d, "invalid_string"); + goto done; + } + dec_pop(d, st_value); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if(!dec_number(d, &val)) { + ret = dec_error(d, "invalid_number"); + goto done; + } + dec_pop(d, st_value); + break; + case '{': + dec_push(d, st_object); + dec_push(d, st_key); + objs = enif_make_list_cell(env, curr, objs); + curr = enif_make_list(env, 0); + d->i++; + break; + case '[': + dec_push(d, st_array); + dec_push(d, st_value); + objs = enif_make_list_cell(env, curr, objs); + curr = enif_make_list(env, 0); + d->i++; + break; + case ']': + if(!enif_is_empty_list(env, curr)) { + ret = dec_error(d, "invalid_json"); + goto done; + } + dec_pop(d, st_value); + if(dec_curr(d) != st_array) { + ret = dec_error(d, "invalid_json"); + goto done; + } + dec_pop(d, st_array); + dec_pop(d, st_value); + val = curr; // curr is [] + if(!enif_get_list_cell(env, objs, &curr, &objs)) { + ret = dec_error(d, "internal_error"); + goto done; + } + d->i++; + break; + default: + ret = dec_error(d, "invalid_json"); + goto done; + } + if(dec_top(d) == 0) { + dec_push(d, st_done); + } else if(dec_curr(d) != st_value && dec_curr(d) != st_key) { + dec_push(d, st_comma); + curr = enif_make_list_cell(env, val, curr); + } + break; + + case st_key: + switch(d->p[d->i]) { + case ' ': + case '\n': + case '\r': + case '\t': + d->i++; + break; + case '\"': + if(!dec_string(d, &val)) { + ret = dec_error(d, "invalid_string"); + goto done; + } + dec_pop(d, st_key); + dec_push(d, st_colon); + curr = enif_make_list_cell(env, val, curr); + break; + case '}': + if(!enif_is_empty_list(env, curr)) { + ret = dec_error(d, "invalid_json"); + goto done; + } + dec_pop(d, st_key); + dec_pop(d, st_object); + dec_pop(d, st_value); + val = enif_make_tuple1(env, curr); + if(!enif_get_list_cell(env, objs, &curr, &objs)) { + ret = dec_error(d, "internal_error"); + goto done; + } + if(dec_top(d) == 0) { + dec_push(d, st_done); + } else { + dec_push(d, st_comma); + curr = enif_make_list_cell(env, val, curr); + } + d->i++; + break; + default: + ret = dec_error(d, "invalid_json"); + goto done; + } + break; + + case st_colon: + switch(d->p[d->i]) { + case ' ': + case '\n': + case '\r': + case '\t': + d->i++; + break; + case ':': + dec_pop(d, st_colon); + dec_push(d, st_value); + d->i++; + break; + default: + ret = dec_error(d, "invalid_json"); + goto done; + } + break; + + case st_comma: + switch(d->p[d->i]) { + case ' ': + case '\n': + case '\r': + case '\t': + d->i++; + break; + case ',': + dec_pop(d, st_comma); + switch(dec_curr(d)) { + case st_object: + dec_push(d, st_key); + break; + case st_array: + dec_push(d, st_value); + break; + default: + ret = dec_error(d, "internal_error"); + goto done; + } + d->i++; + break; + case '}': + dec_pop(d, st_comma); + if(dec_curr(d) != st_object) { + ret = dec_error(d, "invalid_json"); + goto done; + } + dec_pop(d, st_object); + dec_pop(d, st_value); + val = make_object(env, curr); + if(!enif_get_list_cell(env, objs, &curr, &objs)) { + ret = dec_error(d, "internal_error"); + goto done; + } + if(dec_top(d) > 0) { + dec_push(d, st_comma); + curr = enif_make_list_cell(env, val, curr); + } else { + dec_push(d, st_done); + } + d->i++; + break; + case ']': + dec_pop(d, st_comma); + if(dec_curr(d) != st_array) { + ret = dec_error(d, "invalid_json"); + goto done; + } + dec_pop(d, st_array); + dec_pop(d, st_value); + val = make_array(env, curr); + if(!enif_get_list_cell(env, objs, &curr, &objs)) { + ret = dec_error(d, "internal_error"); + goto done; + } + if(dec_top(d) > 0) { + dec_push(d, st_comma); + curr = enif_make_list_cell(env, val, curr); + } else { + dec_push(d, st_done); + } + d->i++; + break; + default: + ret = dec_error(d, "invalid_json"); + goto done; + } + break; + + case st_done: + switch(d->p[d->i]) { + case ' ': + case '\n': + case '\r': + case '\t': + d->i++; + break; + default: + ret = dec_error(d, "invalid_trailing_data"); + goto done; + } + break; + + default: + ret = dec_error(d, "invalid_internal_state"); + goto done; + } + } + + if(dec_curr(d) != st_done) { + ret = dec_error(d, "truncated_json"); + } else if(d->has_bignum) { + ret = enif_make_tuple2(env, d->atoms->atom_bignum, val); + } else { + ret = enif_make_tuple2(env, d->atoms->atom_ok, val); + } + +done: + dec_destroy(d); + + return ret; +} diff --git a/c_src/encoder.c b/c_src/encoder.c new file mode 100644 index 0000000..37dc822 --- /dev/null +++ b/c_src/encoder.c @@ -0,0 +1,556 @@ +#include +#include +#include + +#include "erl_nif.h" +#include "jiffy.h" + +#define BIN_INC_SIZE 1024 + +typedef struct { + ErlNifEnv* env; + jiffy_st* atoms; + + int count; + + ERL_NIF_TERM iolist; + ErlNifBinary curr; + int cleared; + + char* p; + unsigned char* u; + size_t i; +} Encoder; + +int +enc_init(Encoder* e, ErlNifEnv* env) +{ + e->env = env; + e->atoms = enif_priv_data(env); + + e->count = 0; + + e->iolist = enif_make_list(env, 0); + if(!enif_alloc_binary(BIN_INC_SIZE, &(e->curr))) { + return 0; + } + e->cleared = 0; + + e->p = (char*) e->curr.data; + e->u = (unsigned char*) e->curr.data; + e->i = 0; + + return 1; +} + +void +enc_destroy(Encoder* e) +{ + if(!e->cleared) { + enif_release_binary(&(e->curr)); + } +} + +ERL_NIF_TERM +enc_error(Encoder* e, const char* msg) +{ + assert(0 && msg); + return make_error(e->atoms, e->env, msg); +} + +int +enc_result(Encoder* e, ERL_NIF_TERM* value) +{ + if(e->i != e->curr.size) { + if(!enif_realloc_binary(&(e->curr), e->i)) { + return 0; + } + } + + *value = enif_make_binary(e->env, &(e->curr)); + e->cleared = 1; + return 1; +} + +int +enc_ensure(Encoder* e, size_t req) +{ + size_t new_sz; + + if(req < e->curr.size - e->i) { + return 1; + } + + new_sz = req - (e->curr.size - e->i); + new_sz += BIN_INC_SIZE - (new_sz % BIN_INC_SIZE); + assert(new_sz % BIN_INC_SIZE == 0 && "Invalid modulo math."); + + if(!enif_realloc_binary(&(e->curr), new_sz)) { + return 0; + } + + memset(&(e->u[e->i]), 0, e->curr.size - e->i); + + return 1; +} + +int +enc_literal(Encoder* e, const char* literal, size_t len) +{ + if(!enc_ensure(e, len)) { + return 0; + } + + memcpy(&(e->p[e->i]), literal, len); + e->i += len; + e->count++; + return 1; +} + +int +enc_string(Encoder* e, ERL_NIF_TERM val) +{ + ErlNifBinary bin; + char atom[512]; + + int esc_extra = 0; + int ulen; + int ui; + int i; + + if(enif_is_binary(e->env, val)) { + if(!enif_inspect_binary(e->env, val, &bin)) { + return 0; + } + } else if(enif_is_atom(e->env, val)) { + if(!enif_get_atom(e->env, val, atom, 512, ERL_NIF_LATIN1)) { + return 0; + } + // Fake as a binary for code below. + bin.data = (unsigned char*) atom; + bin.size = strlen(atom); + } else { + return 0; + } + + i = 0; + while(i < bin.size) { + switch((char) bin.data[i]) { + case '\"': + case '\\': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + esc_extra += 1; + i++; + continue; + default: + if(bin.data[i] < 0x20) { + esc_extra += 5; + i++; + continue; + } else if(bin.data[i] < 0x80) { + i++; + continue; + } + ulen = -1; + if((bin.data[i] & 0xE0) == 0xC0) { + ulen = 1; + } else if((bin.data[i] & 0xF0) == 0xE0) { + ulen = 2; + } else if((bin.data[i] & 0xF8) == 0xF0) { + ulen = 3; + } else if((bin.data[i] & 0xFC) == 0xF8) { + ulen = 4; + } else if((bin.data[i] & 0xFE) == 0xFC) { + ulen = 5; + } + if(ulen < 0) { + return 0; + } + if(i+1+ulen > bin.size) { + return 0; + } + for(ui = 0; ui < ulen; ui++) { + if((bin.data[i+1+ui] & 0xC0) != 0x80) { + return 0; + } + } + if(ulen == 1) { + if((bin.data[i] & 0x1E) == 0) + return 0; + } else if(ulen == 2) { + if((bin.data[i] & 0x0F) + (bin.data[i+1] & 0x20) == 0) + return 0; + } else if(ulen == 3) { + if((bin.data[i] & 0x07) + (bin.data[i+1] & 0x30) == 0) + return 0; + } else if(ulen == 4) { + if((bin.data[i] & 0x03) + (bin.data[i+1] & 0x38) == 0) + return 0; + } else if(ulen == 5) { + if((bin.data[i] & 0x01) + (bin.data[i+1] & 0x3C) == 0) + return 0; + } + i += 1 + ulen; + } + } + + if(!enc_ensure(e, bin.size + esc_extra + 2)) { + return 0; + } + + e->p[e->i++] = '\"'; + + i = 0; + while(i < bin.size) { + switch((char) bin.data[i]) { + case '\"': + case '\\': + case '/': + e->p[e->i++] = '\\'; + e->u[e->i++] = bin.data[i]; + i++; + continue; + case '\b': + e->p[e->i++] = '\\'; + e->p[e->i++] = 'b'; + i++; + continue; + case '\f': + e->p[e->i++] = '\\'; + e->p[e->i++] = 'f'; + i++; + continue; + case '\n': + e->p[e->i++] = '\\'; + e->p[e->i++] = 'n'; + i++; + continue; + case '\r': + e->p[e->i++] = '\\'; + e->p[e->i++] = 'r'; + i++; + continue; + case '\t': + e->p[e->i++] = '\\'; + e->p[e->i++] = 't'; + i++; + continue; + default: + if(bin.data[i] < 0x20) { + e->p[e->i++] = '\\'; + e->p[e->i++] = 'u'; + if(!int_to_hex(bin.data[i], &(e->p[e->i]))) { + return 0; + } + e->i += 4; + i++; + } else { + e->u[e->i++] = bin.data[i++]; + } + } + } + + e->p[e->i++] = '\"'; + e->count++; + + return 1; +} + +int +enc_long(Encoder* e, long val) +{ + if(!enc_ensure(e, 32)) { + return 0; + } + + snprintf(&(e->p[e->i]), 32, "%ld", val); + e->i += strlen(&(e->p[e->i])); + e->count++; + + return 1; +} + +int +enc_double(Encoder* e, double val) +{ + if(!enc_ensure(e, 32)) { + return 0; + } + + snprintf(&(e->p[e->i]), 32, "%g", val); + e->i += strlen(&(e->p[e->i])); + e->count++; + + return 1; +} + +int +enc_char(Encoder* e, char c) +{ + if(!enc_ensure(e, 1)) { + return 0; + } + + e->p[e->i++] = c; + return 1; +} + +int +enc_start_object(Encoder* e) +{ + e->count++; + return enc_char(e, '{'); +} + +int +enc_end_object(Encoder* e) +{ + return enc_char(e, '}'); +} + +int +enc_start_array(Encoder* e) +{ + e->count++; + return enc_char(e, '['); +} + +int +enc_end_array(Encoder* e) +{ + return enc_char(e, ']'); +} + +int +enc_colon(Encoder* e) +{ + return enc_char(e, ':'); +} + +int +enc_comma(Encoder* e) +{ + return enc_char(e, ','); +} + +ERL_NIF_TERM +encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + Encoder enc; + Encoder* e = &enc; + + ERL_NIF_TERM ret; + + ERL_NIF_TERM stack; + ERL_NIF_TERM curr; + ERL_NIF_TERM item; + const ERL_NIF_TERM* tuple; + int arity; + double dval; + long lval; + + int has_unknown = 0; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enc_init(e, env)) { + return enif_make_badarg(env); + } + + stack = enif_make_list1(env, argv[0]); + + while(!enif_is_empty_list(env, stack)) { + if(!enif_get_list_cell(env, stack, &curr, &stack)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(enif_is_identical(curr, e->atoms->ref_object)) { + if(!enif_get_list_cell(env, stack, &curr, &stack)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(enif_is_empty_list(env, curr)) { + if(!enc_end_object(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + continue; + } + if(!enif_get_list_cell(env, curr, &item, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(!enif_get_tuple(env, item, &arity, &tuple)) { + ret = enc_error(e, "invalid_object_pair"); + goto done; + } + if(arity != 2) { + ret = enc_error(e, "invalid_object_pair"); + goto done; + } + if(!enc_comma(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(!enc_string(e, tuple[0])) { + ret = enc_error(e, "invalid_object_key"); + goto done; + } + if(!enc_colon(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + 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); + } else if(enif_is_identical(curr, e->atoms->ref_array)) { + if(!enif_get_list_cell(env, stack, &curr, &stack)) { + ret = enc_error(e, "internal_error.5"); + goto done; + } + if(enif_is_empty_list(env, curr)) { + if(!enc_end_array(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + continue; + } + if(!enc_comma(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(!enif_get_list_cell(env, curr, &item, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + stack = enif_make_list_cell(env, curr, stack); + stack = enif_make_list_cell(env, e->atoms->ref_array, stack); + stack = enif_make_list_cell(env, item, stack); + } else if(enif_compare(curr, e->atoms->atom_null) == 0) { + if(!enc_literal(e, "null", 4)) { + ret = enc_error(e, "null"); + goto done; + } + } else if(enif_compare(curr, e->atoms->atom_true) == 0) { + if(!enc_literal(e, "true", 4)) { + ret = enc_error(e, "true"); + goto done; + } + } else if(enif_compare(curr, e->atoms->atom_false) == 0) { + if(!enc_literal(e, "false", 5)) { + ret = enc_error(e, "false"); + goto done; + } + } else if(enif_is_binary(env, curr)) { + if(!enc_string(e, curr)) { + ret = enc_error(e, "invalid_string"); + goto done; + } + } else if(enif_is_atom(env, curr)) { + if(!enc_string(e, curr)) { + ret = enc_error(e, "invalid_string"); + goto done; + } + } else if(enif_get_int64(env, curr, &lval)) { + if(!enc_long(e, lval)) { + ret = enc_error(e, "internal_error"); + goto done; + } + } else if(enif_get_double(env, curr, &dval)) { + if(!enc_double(e, dval)) { + ret = enc_error(e, "internal_error"); + goto done; + } + } else if(enif_get_tuple(env, curr, &arity, &tuple)) { + if(arity != 1) { + ret = enc_error(e, "invalid_ejson"); + goto done; + } + if(!enif_is_list(env, tuple[0])) { + ret = enc_error(e, "invalid_object"); + goto done; + } + if(!enc_start_object(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(enif_is_empty_list(env, tuple[0])) { + if(!enc_end_object(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + continue; + } + if(!enif_get_list_cell(env, tuple[0], &item, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(!enif_get_tuple(env, item, &arity, &tuple)) { + ret = enc_error(e, "invalid_object_pair"); + goto done; + } + if(arity != 2) { + ret = enc_error(e, "invalid_object_pair"); + goto done; + } + if(!enc_string(e, tuple[0])) { + ret = enc_error(e, "invalid_object_key"); + goto done; + } + if(!enc_colon(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + 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); + } else if(enif_is_list(env, curr)) { + if(!enc_start_array(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(enif_is_empty_list(env, curr)) { + if(!enc_end_array(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + continue; + } + if(!enif_get_list_cell(env, curr, &item, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + stack = enif_make_list_cell(env, curr, stack); + stack = enif_make_list_cell(env, e->atoms->ref_array, stack); + stack = enif_make_list_cell(env, item, stack); + } else { + has_unknown = 1; + ret = enc_error(e, "invalid_ejson"); + goto done; + /* + if(!enc_unknown(env, curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + */ + } + } while(!enif_is_empty_list(env, stack)); + + if(!enc_result(e, &item)) { + ret = enc_error(e, "internal_error"); + goto done; + } + + ret = enif_make_tuple2(env, e->atoms->atom_ok, item); + +done: + enc_destroy(e); + return ret; +} diff --git a/c_src/jiffy.c b/c_src/jiffy.c new file mode 100644 index 0000000..13e2979 --- /dev/null +++ b/c_src/jiffy.c @@ -0,0 +1,52 @@ +#include "jiffy.h" + +static int +load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) +{ + jiffy_st* st = enif_alloc(sizeof(jiffy_st)); + if(st == NULL) { + return 1; + } + + st->atom_ok = make_atom(env, "ok"); + st->atom_error = make_atom(env, "error"); + st->atom_null = make_atom(env, "null"); + st->atom_true = make_atom(env, "true"); + st->atom_false = make_atom(env, "false"); + st->atom_bignum = make_atom(env, "bignum"); + + st->ref_object = enif_make_ref(env); + st->ref_array = enif_make_ref(env); + + *priv = (void*) st; + + return 0; +} + +static int +reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) +{ + return 0; +} + +static int +upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) +{ + *priv = *old_priv; + return 0; +} + +static void +unload(ErlNifEnv* env, void* priv) +{ + enif_free(priv); + return; +} + +static ErlNifFunc funcs[] = +{ + {"nif_decode", 1, decode}, + {"nif_encode", 1, encode} +}; + +ERL_NIF_INIT(jiffy, funcs, &load, &reload, &upgrade, &unload); diff --git a/c_src/jiffy.h b/c_src/jiffy.h new file mode 100644 index 0000000..7fa0b81 --- /dev/null +++ b/c_src/jiffy.h @@ -0,0 +1,30 @@ +#ifndef JIFFY_H +#define JIFFY_H + +#include "erl_nif.h" + +typedef struct { + ERL_NIF_TERM atom_ok; + ERL_NIF_TERM atom_error; + ERL_NIF_TERM atom_null; + ERL_NIF_TERM atom_true; + ERL_NIF_TERM atom_false; + ERL_NIF_TERM atom_bignum; + ERL_NIF_TERM ref_object; + ERL_NIF_TERM ref_array; +} jiffy_st; + +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); + +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[]); + +int int_from_hex(const unsigned char* p); +int int_to_hex(int val, char* p); +int utf8_len(int c); +int utf8_from_pair(int hi, int lo); +int utf8_to_binary(int c, unsigned char* buf); + +#endif // Included JIFFY_H diff --git a/c_src/utf8.c b/c_src/utf8.c new file mode 100644 index 0000000..280229c --- /dev/null +++ b/c_src/utf8.c @@ -0,0 +1,128 @@ + +static const char hexvals[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const char hexdigits[16] = { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' +}; + +int +int_from_hex(const unsigned char* p) +{ + unsigned char* h = (unsigned char*) p; + int ret; + + if(hexvals[*(h+0)] < 0) return -1; + if(hexvals[*(h+1)] < 0) return -1; + if(hexvals[*(h+2)] < 0) return -1; + if(hexvals[*(h+3)] < 0) return -1; + + ret = (hexvals[*(h+0)] << 12) + + (hexvals[*(h+1)] << 8) + + (hexvals[*(h+2)] << 4) + + (hexvals[*(h+3)] << 0); + + return ret; +} + +int +int_to_hex(int val, char* p) +{ + if(val < 0 || val > 65535) + return 0; + + p[0] = hexdigits[(val >> 12) & 0xF]; + p[1] = hexdigits[(val >> 8) & 0xF]; + p[2] = hexdigits[(val >> 4) & 0xF]; + p[3] = hexdigits[val & 0xF]; + + return 1; +} + +int +utf8_len(int c) +{ + if(c < 128) { + return 1; + } else if(c < 0x800) { + return 2; + } else if(c < 0x10000) { + if(c < 0xD800 || (c > 0xDFFF && c < 0xFFFE)) { + return 3; + } else { + return -1; + } + } else if(c < 0x200000) { + return 4; + } else if(c < 0x4000000) { + return 5; + } else if(c < 0x80000000) { + return 6; + } else { + return -1; + } +} + +int +utf8_from_pair(int hi, int lo) +{ + if(hi < 0xD800 || hi >= 0xDC00) return -1; + if(lo < 0xDC00 || lo > 0xDFFF) return -1; + return ((hi & 0x3FF) << 10) + (lo & 0x3FF) + 0x10000; +} + +int +utf8_to_binary(int c, unsigned char* buf) +{ + if(c < 0x80) { + buf[0] = (unsigned char) c; + return 1; + } else if(c < 0x800) { + buf[0] = (unsigned char) 0xC0 + (c >> 6); + buf[1] = (unsigned char) 0x80 + (c & 0x3F); + return 2; + } else if(c < 0x10000) { + if(c < 0xD800 || (c > 0xDFFF && c < 0xFFFE)) { + buf[0] = (unsigned char) 0xE0 + (c >> 12); + buf[1] = (unsigned char) 0x80 + ((c >> 6) & 0x3F); + buf[2] = (unsigned char) 0x80 + (c & 0x3F); + return 3; + } else { + return -1; + } + } else if(c < 0x200000) { + buf[0] = (unsigned char) 0xF0 + (c >> 18); + buf[1] = (unsigned char) 0x80 + ((c >> 12) & 0x3F); + buf[2] = (unsigned char) 0x80 + ((c >> 6) & 0x3F); + buf[3] = (unsigned char) 0x80 + (c & 0x3F); + return 4; + } else if(c < 0x4000000) { + buf[0] = (unsigned char) 0xF8 + (c >> 24); + buf[1] = (unsigned char) 0x80 + ((c >> 18) & 0x3F); + buf[2] = (unsigned char) 0x80 + ((c >> 12) & 0x3F); + buf[3] = (unsigned char) 0x80 + ((c >> 6) & 0x3F); + buf[4] = (unsigned char) 0x80 + (c & 0x3F); + return 5; + } else if(c < 0x80000000) { + buf[0] = (unsigned char) 0xFC + (c >> 30); + buf[1] = (unsigned char) 0x80 + ((c >> 24) & 0x3F); + buf[2] = (unsigned char) 0x80 + ((c >> 18) & 0x3F); + buf[3] = (unsigned char) 0x80 + ((c >> 12) & 0x3F); + buf[4] = (unsigned char) 0x80 + ((c >> 6) & 0x3F); + buf[5] = (unsigned char) 0x80 + (c & 0x3F); + return 6; + } + return -1; +} + diff --git a/c_src/util.c b/c_src/util.c new file mode 100644 index 0000000..eaf7afe --- /dev/null +++ b/c_src/util.c @@ -0,0 +1,23 @@ +#include "jiffy.h" + +ERL_NIF_TERM +make_atom(ErlNifEnv* env, const char* name) +{ + ERL_NIF_TERM ret; + if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { + return ret; + } + return enif_make_atom(env, name); +} + +ERL_NIF_TERM +make_ok(jiffy_st* st, ErlNifEnv* env, ERL_NIF_TERM value) +{ + return enif_make_tuple2(env, st->atom_ok, value); +} + +ERL_NIF_TERM +make_error(jiffy_st* st, ErlNifEnv* env, const char* error) +{ + return enif_make_tuple2(env, st->atom_error, make_atom(env, error)); +} diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..c55b6e76ba5e598131681aea7e4688b0d47a275a GIT binary patch literal 102892 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 literal 0 HcmV?d00001 diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..081bf89 --- /dev/null +++ b/rebar.config @@ -0,0 +1,17 @@ +{port_sources, ["c_src/*.c"]}. +{so_name, "jiffy.so"}. +{port_envs, [ + %% Link the spidermonkey library + {".*", "CFLAGS", "$CFLAGS -g -Wall"}, + + %% Make sure to link -lstdc++ on linux or solaris + {"(linux|solaris)", "LDFLAGS", "$LDFLAGS -lstdc++"}, + + %% OS X Leopard flags for 64-bit + {"darwin9.*-64$", "CXXFLAGS", "-m64"}, + {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"}, + + %% OS X Snow Leopard flags for 32-bit + {"darwin10.*-32$", "CXXFLAGS", "-m32"}, + {"darwin10.*-32$", "LDFLAGS", "-arch i386"} +]}. diff --git a/src/jiffy.app.src b/src/jiffy.app.src new file mode 100644 index 0000000..32adefa --- /dev/null +++ b/src/jiffy.app.src @@ -0,0 +1,7 @@ +{application, jiffy, [ + {description, "JSON Decoder/Encoder."}, + {vsn, "%VSN%"}, + {registered, []}, + {mod, reloading, []}, + {applications, [kernel]} +]}. diff --git a/src/jiffy.erl b/src/jiffy.erl new file mode 100644 index 0000000..144980b --- /dev/null +++ b/src/jiffy.erl @@ -0,0 +1,62 @@ +-module(jiffy). +-export([decode/1, encode/1]). +-define(NOT_LOADED, not_loaded(?LINE)). + +-on_load(init/0). + +decode(Data) -> + case nif_decode(Data) of + {bignum, EJson} -> + {ok, debignum(EJson)}; + Else -> + Else + end. + +encode(Data) -> + nif_encode(Data). + + +nif_decode(_Data) -> + ?NOT_LOADED. + +nif_encode(_Data) -> + ?NOT_LOADED. + + +debignum({bignum, Value}) -> + list_to_integer(binary_to_list(Value)); +debignum({Pairs}) when is_list(Pairs) -> + debignum_obj(Pairs, []); +debignum(Vals) when is_list(Vals) -> + debignum_arr(Vals, []); +debignum(Val) -> + Val. + +debignum_obj([], Acc) -> + {lists:reverse(Acc)}; +debignum_obj([{K, V} | Pairs], Acc) -> + debignum_obj(Pairs, [{K, debignum(V)} | Acc]). + +debignum_arr([], Acc) -> + lists:reverse(Acc); +debignum_arr([V | Vals], Acc) -> + debignum_arr(Vals, [debignum(V) | Acc]). + + +init() -> + SoName = case code:priv_dir(?MODULE) of + {error, bad_name} -> + case filelib:is_dir(filename:join(["..", priv])) of + true -> + filename:join(["..", priv, ?MODULE]); + _ -> + filename:join([priv, ?MODULE]) + end; + Dir -> + filename:join(Dir, ?MODULE) + end, + erlang:load_nif(SoName, 0). + + +not_loaded(Line) -> + exit({not_loaded, [{module, ?MODULE}, {line, Line}]}). diff --git a/test/001-yajl-tests.t b/test/001-yajl-tests.t new file mode 100755 index 0000000..0f11afb --- /dev/null +++ b/test/001-yajl-tests.t @@ -0,0 +1,33 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("test"), + code:add_pathz("ebin"), + + Cases = read_cases(), + + etap:plan(length(Cases)), + lists:foreach(fun(Case) -> test(Case) end, Cases), + etap:end_tests(). + +test({Name, Json, Erl}) -> + etap:is(jiffy:decode(Json), Erl, Name). + +read_cases() -> + CasesPath = filename:join(["test", "cases", "*.json"]), + FileNames = lists:sort(filelib:wildcard(CasesPath)), + lists:map(fun(F) -> make_pair(F) end, FileNames). + +make_pair(FileName) -> + {ok, Json} = file:read_file(FileName), + {BaseName, _} = lists:splitwith(fun(C) -> C /= $. end, FileName), + ErlFname = BaseName ++ ".erl", + {ok, [Term]} = file:consult(ErlFname), + case Term of + {error, _} -> + {BaseName, Json, Term}; + {error, _, _} -> + {BaseName, Json, Term}; + _ -> + {BaseName, Json, {ok, Term}} + end. diff --git a/test/002-literals.t b/test/002-literals.t new file mode 100755 index 0000000..a49caeb --- /dev/null +++ b/test/002-literals.t @@ -0,0 +1,19 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(6), + etap:is(jiffy:decode(<<"true">>), {ok, true}, "DEC: true -> true"), + etap:is(jiffy:encode(true), {ok, <<"true">>}, "ENC: true -> true"), + + etap:is(jiffy:decode(<<"false">>), {ok, false}, "DEC: false -> false"), + etap:is(jiffy:encode(false), {ok, <<"false">>}, "ENC: false -> false"), + + etap:is(jiffy:decode(<<"null">>), {ok, null}, "DEC: null -> null"), + etap:is(jiffy:encode(null), {ok, <<"null">>}, "ENC: null -> null"), + + etap:end_tests(). + + diff --git a/test/003-numbers.t b/test/003-numbers.t new file mode 100755 index 0000000..a596725 --- /dev/null +++ b/test/003-numbers.t @@ -0,0 +1,47 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(47), + util:test_good(good()), + util:test_errors(errors()), + etap:end_tests(). + +good() -> + [ + {<<"0">>, 0}, + {<<"-0">>, 0, <<"0">>}, + {<<"1">>, 1}, + {<<"12">>, 12}, + {<<"-3">>, -3}, + {<<"309230948234098">>, 309230948234098}, + {<<"1.0">>, 1.0, <<"1">>}, + {<<"0.3">>, 0.3}, + {<<"2.4234324">>, 2.4234324, <<"2.42343">>}, + {<<"-3.1416">>, -3.1416}, + {<<"1E4">>, 10000.0, <<"10000">>}, + {<<"1.0E+01">>, 10.0, <<"10">>}, + {<<"1e1">>, 10.0, <<"10">>}, + {<<"3.0E2">>, 300.0, <<"300">>}, + {<<"0E3">>, 0.0, <<"0">>}, + {<<"1.5E3">>, 1500.0, <<"1500">>}, + {<<"1.5E-1">>, 0.15, <<"0.15">>}, + {<<"-0.323E+2">>, -32.3, <<"-32.3">>} + ]. + +errors() -> + [ + <<"02">>, + <<"-01">>, + <<"+12">>, + <<"-">>, + <<"1.">>, + <<".1">>, + <<"1.-1">>, + <<"1E">>, + <<"1-E2">>, + <<"2E +3">>, + <<"1EA">> + ]. diff --git a/test/004-strings.t b/test/004-strings.t new file mode 100755 index 0000000..ec5f568 --- /dev/null +++ b/test/004-strings.t @@ -0,0 +1,36 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(21), + util:test_good(good()), + util:test_errors(errors()), + etap:end_tests(). + +good() -> + [ + {<<"\"\"">>, <<"">>}, + {<<"\"0\"">>, <<"0">>}, + {<<"\"foo\"">>, <<"foo">>}, + {<<"\"\\\"foobar\\\"\"">>, <<"\"foobar\"">>}, + {<<"\"\\n\\n\\n\"">>, <<"\n\n\n">>}, + {<<"\"\\\" \\b\\f\\r\\n\\t\\\"\"">>, <<"\" \b\f\r\n\t\"">>}, + {<<"\"foo\\u0005bar\"">>, <<"foo", 5, "bar">>}, + { + <<"\"\\uD834\\uDD1E\"">>, + <<240, 157, 132, 158>>, + <<34, 240, 157, 132, 158, 34>> + } + ]. + +errors() -> + [ + <<"\"", 0, "\"">>, + <<"\"\\g\"">>, + <<"\"\\uFFFF\"">>, + <<"\"\\uD834foo\\uDD1E\"">>, + % CouchDB-345 + <<"\"",78,69,73,77,69,78,32,70,216,82,82,32,70,65,69,78,33,"\"">> + ]. diff --git a/test/005-arrays.t b/test/005-arrays.t new file mode 100755 index 0000000..0a0dd5e --- /dev/null +++ b/test/005-arrays.t @@ -0,0 +1,34 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(18), + util:test_good(good()), + util:test_errors(errors()), + etap:end_tests(). + +good() -> + [ + {<<"[]">>, []}, + {<<"[\t[\n]\r]">>, [[]], <<"[[]]">>}, + {<<"[\t123, \r true\n]">>, [123, true], <<"[123,true]">>}, + {<<"[1,\"foo\"]">>, [1, <<"foo">>]}, + {<<"[1199344435545.0,1]">>, [1199344435545.0,1], <<"[1.19934e+12,1]">>}, + { + <<"[\"\\u00A1\",\"\\u00FC\"]">>, + [<<194, 161>>, <<195, 188>>], + <<"[\"", 194, 161, "\",\"", 195, 188, "\"]">> + } + ]. + +errors() -> + [ + <<"[">>, + <<"]">>, + <<"[,]">>, + <<"[123">>, + <<"[123,]">>, + <<"[32 true]">> + ]. diff --git a/test/006-maps.t b/test/006-maps.t new file mode 100755 index 0000000..ebb8588 --- /dev/null +++ b/test/006-maps.t @@ -0,0 +1,34 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(15), + util:test_good(good()), + util:test_errors(errors()), + etap:end_tests(). + +good() -> + [ + {<<"{}">>, {[]}}, + {<<"{\"foo\": \"bar\"}">>, + {[{<<"foo">>, <<"bar">>}]}, + <<"{\"foo\":\"bar\"}">>}, + {<<"\n\n{\"foo\":\r \"bar\",\n \"baz\"\t: 123 }">>, + {[{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]}, + <<"{\"foo\":\"bar\",\"baz\":123}">>} + ]. + +errors() -> + [ + <<"{">>, + <<"{,}">>, + <<"{123:true}">>, + <<"{false:123}">>, + <<"{:\"stuff\"}">>, + <<"{\"key\":}">>, + <<"{\"key\": 123">>, + <<"{\"key\": 123 true">>, + <<"{\"key\": 123,}">> + ]. diff --git a/test/007-compound.t b/test/007-compound.t new file mode 100755 index 0000000..849f844 --- /dev/null +++ b/test/007-compound.t @@ -0,0 +1,39 @@ +#! /usr/bin/env escript + +main([]) -> + code:add_pathz("ebin"), + code:add_pathz("test"), + + etap:plan(12), + util:test_good(good()), + util:test_errors(errors()), + etap:end_tests(). + +good() -> + [ + {<<"[{}]">>, [{[]}]}, + {<<"{\"foo\":[123]}">>, {[{<<"foo">>, [123]}]}}, + {<<"{\"foo\":{\"bar\":true}}">>, + {[{<<"foo">>, {[{<<"bar">>, true}]} }]} }, + {<<"{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}">>, + {[ + {<<"foo">>, []}, + {<<"bar">>, {[{<<"baz">>, true}]}}, + {<<"alice">>, <<"bob">>} + ]} + }, + {<<"[-123,\"foo\",{\"bar\":[]},null]">>, + [ + -123, + <<"foo">>, + {[{<<"bar">>, []}]}, + null + ] + } + ]. + +errors() -> + [ + <<"[{}">>, + <<"}]">> + ]. diff --git a/test/cases/array.erl b/test/cases/array.erl new file mode 100644 index 0000000..8a9688e --- /dev/null +++ b/test/cases/array.erl @@ -0,0 +1,16 @@ +[ + <<"foo">>, + <<"bar">>, + <<"baz">>, + true, + false, + null, + {[{<<"key">>, <<"value">>}]}, + [ + null, + null, + null, + [] + ], + <<"\n\r\\">> +]. \ No newline at end of file diff --git a/test/cases/array.json b/test/cases/array.json new file mode 100644 index 0000000..f76058d --- /dev/null +++ b/test/cases/array.json @@ -0,0 +1,6 @@ +["foo", + "bar", "baz", + true,false,null,{"key":"value"}, + [null,null,null,[]], + "\n\r\\" +] diff --git a/test/cases/array_close.erl b/test/cases/array_close.erl new file mode 100644 index 0000000..4aa294e --- /dev/null +++ b/test/cases/array_close.erl @@ -0,0 +1 @@ +{error,{1,invalid_json}}. diff --git a/test/cases/array_close.json b/test/cases/array_close.json new file mode 100644 index 0000000..079b579 --- /dev/null +++ b/test/cases/array_close.json @@ -0,0 +1 @@ +] diff --git a/test/cases/array_open.erl b/test/cases/array_open.erl new file mode 100644 index 0000000..9fa573a --- /dev/null +++ b/test/cases/array_open.erl @@ -0,0 +1 @@ +{error,{3,truncated_json}}. diff --git a/test/cases/array_open.json b/test/cases/array_open.json new file mode 100644 index 0000000..558ed37 --- /dev/null +++ b/test/cases/array_open.json @@ -0,0 +1 @@ +[ diff --git a/test/cases/bogus_char.erl b/test/cases/bogus_char.erl new file mode 100644 index 0000000..9ecbac7 --- /dev/null +++ b/test/cases/bogus_char.erl @@ -0,0 +1 @@ +{error,{97,invalid_literal}}. diff --git a/test/cases/bogus_char.json b/test/cases/bogus_char.json new file mode 100644 index 0000000..8163bd8 --- /dev/null +++ b/test/cases/bogus_char.json @@ -0,0 +1,4 @@ +["this","is","what","should","be", + "a happy bit of json", + "but someone, misspelled \"true\"", ture, + "who says JSON is easy for humans to generate?"] diff --git a/test/cases/codepoints_from_unicode_org.erl b/test/cases/codepoints_from_unicode_org.erl new file mode 100644 index 0000000..086d058 --- /dev/null +++ b/test/cases/codepoints_from_unicode_org.erl @@ -0,0 +1 @@ +<<77, 208, 176, 228, 186, 140, 240, 144, 140, 130>>. \ No newline at end of file diff --git a/test/cases/codepoints_from_unicode_org.json b/test/cases/codepoints_from_unicode_org.json new file mode 100644 index 0000000..f91f3be --- /dev/null +++ b/test/cases/codepoints_from_unicode_org.json @@ -0,0 +1 @@ +"\u004d\u0430\u4e8c\ud800\udf02" diff --git a/test/cases/deep_arrays.erl b/test/cases/deep_arrays.erl new file mode 100644 index 0000000..2721754 --- /dev/null +++ b/test/cases/deep_arrays.erl @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]. diff --git a/test/cases/deep_arrays.json b/test/cases/deep_arrays.json new file mode 100644 index 0000000..82d1b0d --- /dev/null +++ b/test/cases/deep_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/cases/difficult_json_c_test_case.erl b/test/cases/difficult_json_c_test_case.erl new file mode 100644 index 0000000..591fafe --- /dev/null +++ b/test/cases/difficult_json_c_test_case.erl @@ -0,0 +1,19 @@ +{[ + {<<"glossary">>, {[ + {<<"title">>, <<"example glossary">>}, + {<<"GlossDiv">>, {[ + {<<"title">>, <<"S">>}, + {<<"GlossList">>, [ + {[ + {<<"ID">>, <<"SGML">>}, + {<<"SortAs">>, <<"SGML">>}, + {<<"GlossTerm">>, <<"Standard Generalized Markup Language">>}, + {<<"Acronym">>, <<"SGML">>}, + {<<"Abbrev">>, <<"ISO 8879:1986">>}, + {<<"GlossDef">>, <<"A meta-markup language, used to create markup languages such as DocBook.">>}, + {<<"GlossSeeAlso">>, [<<"GML">>, <<"XML">>, <<"markup">>]} + ]} + ]} + ]}} + ]}} +]}. diff --git a/test/cases/difficult_json_c_test_case.json b/test/cases/difficult_json_c_test_case.json new file mode 100644 index 0000000..6998f55 --- /dev/null +++ b/test/cases/difficult_json_c_test_case.json @@ -0,0 +1 @@ +{ "glossary": { "title": "example glossary", "GlossDiv": { "title": "S", "GlossList": [ { "ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", "Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": "A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso": ["GML", "XML", "markup"] } ] } } } diff --git a/test/cases/doubles.erl b/test/cases/doubles.erl new file mode 100644 index 0000000..76a7f07 --- /dev/null +++ b/test/cases/doubles.erl @@ -0,0 +1 @@ +[10, 10, 3.141569, 1000]. diff --git a/test/cases/doubles.json b/test/cases/doubles.json new file mode 100644 index 0000000..626f21c --- /dev/null +++ b/test/cases/doubles.json @@ -0,0 +1 @@ +[ 0.1e2, 1e1, 3.141569, 10000000000000e-10] diff --git a/test/cases/empty_array.erl b/test/cases/empty_array.erl new file mode 100644 index 0000000..2600237 --- /dev/null +++ b/test/cases/empty_array.erl @@ -0,0 +1 @@ +[]. \ No newline at end of file diff --git a/test/cases/empty_array.json b/test/cases/empty_array.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/test/cases/empty_array.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/cases/empty_string.erl b/test/cases/empty_string.erl new file mode 100644 index 0000000..e0f2023 --- /dev/null +++ b/test/cases/empty_string.erl @@ -0,0 +1 @@ +<<"">>. \ No newline at end of file diff --git a/test/cases/empty_string.json b/test/cases/empty_string.json new file mode 100644 index 0000000..e16c76d --- /dev/null +++ b/test/cases/empty_string.json @@ -0,0 +1 @@ +"" diff --git a/test/cases/escaped_bulgarian.erl b/test/cases/escaped_bulgarian.erl new file mode 100644 index 0000000..15287cc --- /dev/null +++ b/test/cases/escaped_bulgarian.erl @@ -0,0 +1,6 @@ +[ + <<208, 148, 208, 176>>, + <<208, 156, 209, 131>>, + <<208, 149, 208, 177, 208, 176>>, + <<208, 156, 208, 176, 208, 185, 208, 186, 208, 176, 209, 130, 208, 176>> +]. \ No newline at end of file diff --git a/test/cases/escaped_bulgarian.json b/test/cases/escaped_bulgarian.json new file mode 100644 index 0000000..9ce1d1c --- /dev/null +++ b/test/cases/escaped_bulgarian.json @@ -0,0 +1,4 @@ +["\u0414\u0430", + "\u041c\u0443", + "\u0415\u0431\u0430", + "\u041c\u0430\u0439\u043a\u0430\u0442\u0430"] diff --git a/test/cases/escaped_foobar.erl b/test/cases/escaped_foobar.erl new file mode 100644 index 0000000..de9c37d --- /dev/null +++ b/test/cases/escaped_foobar.erl @@ -0,0 +1 @@ +<<"foobar">>. \ No newline at end of file diff --git a/test/cases/escaped_foobar.json b/test/cases/escaped_foobar.json new file mode 100644 index 0000000..2c0e25f --- /dev/null +++ b/test/cases/escaped_foobar.json @@ -0,0 +1 @@ +"\u0066\u006f\u006f\u0062\u0061\u0072" diff --git a/test/cases/false.erl b/test/cases/false.erl new file mode 100644 index 0000000..aca5de7 --- /dev/null +++ b/test/cases/false.erl @@ -0,0 +1 @@ +false. \ No newline at end of file diff --git a/test/cases/false.json b/test/cases/false.json new file mode 100644 index 0000000..c508d53 --- /dev/null +++ b/test/cases/false.json @@ -0,0 +1 @@ +false diff --git a/test/cases/false_then_garbage.erl b/test/cases/false_then_garbage.erl new file mode 100644 index 0000000..fc13df2 --- /dev/null +++ b/test/cases/false_then_garbage.erl @@ -0,0 +1 @@ +{error,{6,invalid_trailing_data}}. diff --git a/test/cases/false_then_garbage.json b/test/cases/false_then_garbage.json new file mode 100644 index 0000000..78f4e96 --- /dev/null +++ b/test/cases/false_then_garbage.json @@ -0,0 +1 @@ +falsex \ No newline at end of file diff --git a/test/cases/four_byte_utf8.erl b/test/cases/four_byte_utf8.erl new file mode 100644 index 0000000..20da680 --- /dev/null +++ b/test/cases/four_byte_utf8.erl @@ -0,0 +1 @@ +{[{<<"U+10ABCD">>, <<244, 138, 175, 141>>}]}. diff --git a/test/cases/four_byte_utf8.json b/test/cases/four_byte_utf8.json new file mode 100644 index 0000000..582c575 --- /dev/null +++ b/test/cases/four_byte_utf8.json @@ -0,0 +1,2 @@ +{ "U+10ABCD": "􊯍" } + diff --git a/test/cases/integers.erl b/test/cases/integers.erl new file mode 100644 index 0000000..bd642e4 --- /dev/null +++ b/test/cases/integers.erl @@ -0,0 +1,13 @@ +[ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 123456789, + -123456789, + 2147483647, + -2147483647 +]. \ No newline at end of file diff --git a/test/cases/integers.json b/test/cases/integers.json new file mode 100644 index 0000000..c50e4c9 --- /dev/null +++ b/test/cases/integers.json @@ -0,0 +1,3 @@ +[ 1,2,3,4,5,6,7, + 123456789 , -123456789, + 2147483647, -2147483647 ] diff --git a/test/cases/invalid_utf8.erl b/test/cases/invalid_utf8.erl new file mode 100644 index 0000000..f0baaf0 --- /dev/null +++ b/test/cases/invalid_utf8.erl @@ -0,0 +1 @@ +{error,{10,invalid_string}}. diff --git a/test/cases/invalid_utf8.json b/test/cases/invalid_utf8.json new file mode 100644 index 0000000..12f1718 --- /dev/null +++ b/test/cases/invalid_utf8.json @@ -0,0 +1 @@ +["Да М Еба Майката"] diff --git a/test/cases/isolated_surrogate_marker.erl b/test/cases/isolated_surrogate_marker.erl new file mode 100644 index 0000000..06113c7 --- /dev/null +++ b/test/cases/isolated_surrogate_marker.erl @@ -0,0 +1 @@ +{error,{8,invalid_string}}. diff --git a/test/cases/isolated_surrogate_marker.json b/test/cases/isolated_surrogate_marker.json new file mode 100644 index 0000000..36959f4 --- /dev/null +++ b/test/cases/isolated_surrogate_marker.json @@ -0,0 +1 @@ +"\ud800" diff --git a/test/cases/leading_zero_in_number.erl b/test/cases/leading_zero_in_number.erl new file mode 100644 index 0000000..5bc5d8c --- /dev/null +++ b/test/cases/leading_zero_in_number.erl @@ -0,0 +1 @@ +{error,{17,invalid_json}}. diff --git a/test/cases/leading_zero_in_number.json b/test/cases/leading_zero_in_number.json new file mode 100644 index 0000000..959f5ba --- /dev/null +++ b/test/cases/leading_zero_in_number.json @@ -0,0 +1 @@ +{ "bad thing": 01 } diff --git a/test/cases/lonely_minus_sign.erl b/test/cases/lonely_minus_sign.erl new file mode 100644 index 0000000..5f172a4 --- /dev/null +++ b/test/cases/lonely_minus_sign.erl @@ -0,0 +1 @@ +{error,{83,invalid_number}}. diff --git a/test/cases/lonely_minus_sign.json b/test/cases/lonely_minus_sign.json new file mode 100644 index 0000000..85f69bd --- /dev/null +++ b/test/cases/lonely_minus_sign.json @@ -0,0 +1,7 @@ +[ + "foo", true, + true, "blue", + "baby where are you?", "oh boo hoo!", + - +] + diff --git a/test/cases/lonely_number.erl b/test/cases/lonely_number.erl new file mode 100644 index 0000000..b52af81 --- /dev/null +++ b/test/cases/lonely_number.erl @@ -0,0 +1 @@ +123456789. \ No newline at end of file diff --git a/test/cases/lonely_number.json b/test/cases/lonely_number.json new file mode 100644 index 0000000..e2e107a --- /dev/null +++ b/test/cases/lonely_number.json @@ -0,0 +1 @@ +123456789 \ No newline at end of file diff --git a/test/cases/map_close.erl b/test/cases/map_close.erl new file mode 100644 index 0000000..4aa294e --- /dev/null +++ b/test/cases/map_close.erl @@ -0,0 +1 @@ +{error,{1,invalid_json}}. diff --git a/test/cases/map_close.json b/test/cases/map_close.json new file mode 100644 index 0000000..5c34318 --- /dev/null +++ b/test/cases/map_close.json @@ -0,0 +1 @@ +} diff --git a/test/cases/map_open.erl b/test/cases/map_open.erl new file mode 100644 index 0000000..9fa573a --- /dev/null +++ b/test/cases/map_open.erl @@ -0,0 +1 @@ +{error,{3,truncated_json}}. diff --git a/test/cases/map_open.json b/test/cases/map_open.json new file mode 100644 index 0000000..98232c6 --- /dev/null +++ b/test/cases/map_open.json @@ -0,0 +1 @@ +{ diff --git a/test/cases/missing_integer_after_decimal_point.erl b/test/cases/missing_integer_after_decimal_point.erl new file mode 100644 index 0000000..046f3c7 --- /dev/null +++ b/test/cases/missing_integer_after_decimal_point.erl @@ -0,0 +1 @@ +{error,{4,invalid_number}}. diff --git a/test/cases/missing_integer_after_decimal_point.json b/test/cases/missing_integer_after_decimal_point.json new file mode 100644 index 0000000..2369f4b --- /dev/null +++ b/test/cases/missing_integer_after_decimal_point.json @@ -0,0 +1 @@ +10.e2 diff --git a/test/cases/missing_integer_after_exponent.erl b/test/cases/missing_integer_after_exponent.erl new file mode 100644 index 0000000..046f3c7 --- /dev/null +++ b/test/cases/missing_integer_after_exponent.erl @@ -0,0 +1 @@ +{error,{4,invalid_number}}. diff --git a/test/cases/missing_integer_after_exponent.json b/test/cases/missing_integer_after_exponent.json new file mode 100644 index 0000000..a62b45d --- /dev/null +++ b/test/cases/missing_integer_after_exponent.json @@ -0,0 +1 @@ +10e diff --git a/test/cases/non_utf8_char_in_string.erl b/test/cases/non_utf8_char_in_string.erl new file mode 100644 index 0000000..0ca5740 --- /dev/null +++ b/test/cases/non_utf8_char_in_string.erl @@ -0,0 +1 @@ +{error,{125,invalid_string}}. diff --git a/test/cases/non_utf8_char_in_string.json b/test/cases/non_utf8_char_in_string.json new file mode 100644 index 0000000..253a664 --- /dev/null +++ b/test/cases/non_utf8_char_in_string.json @@ -0,0 +1 @@ +{"CoreletAPIVersion":2,"CoreletType":"standalone","documentation":"A corelet that provides the capability to upload a folders contents into a users locker.","functions":[{"documentation":"Displays a dialog box that allows user to select a folder on the local system.","name":"ShowBrowseDialog","parameters":[{"documentation":"The callback function for results.","name":"callback","required":true,"type":"callback"}]},{"documentation":"Uploads all mp3 files in the folder provided.","name":"UploadFolder","parameters":[{"documentation":"The path to upload mp3 files from.","name":"path","required":true,"type":"string"},{"documentation":"The callback function for progress.","name":"callback","required":true,"type":"callback"}]},{"documentation":"Returns the server name to the current locker service.","name":"GetLockerService","parameters":[]},{"documentation":"Changes the name of the locker service.","name":"SetLockerService","parameters":[{"documentation":"The value of the locker service to set active.","name":"LockerService","required":true,"type":"string"}]},{"documentation":"Downloads locker files to the suggested folder.","name":"DownloadFile","parameters":[{"documentation":"The origin path of the locker file.","name":"path","required":true,"type":"string"},{"documentation":"The Window destination path of the locker file.","name":"destination","required":true,"type":"integer"},{"documentation":"The callback function for progress.","name":"callback","required":true,"type":"callback"}]}],"name":"LockerUploader","version":{"major":0,"micro":1,"minor":0},"versionString":"0.0.1"} \ No newline at end of file diff --git a/test/cases/null.erl b/test/cases/null.erl new file mode 100644 index 0000000..bed1002 --- /dev/null +++ b/test/cases/null.erl @@ -0,0 +1 @@ +null. \ No newline at end of file diff --git a/test/cases/null.json b/test/cases/null.json new file mode 100644 index 0000000..19765bd --- /dev/null +++ b/test/cases/null.json @@ -0,0 +1 @@ +null diff --git a/test/cases/null_then_garbage.erl b/test/cases/null_then_garbage.erl new file mode 100644 index 0000000..30b0113 --- /dev/null +++ b/test/cases/null_then_garbage.erl @@ -0,0 +1 @@ +{error,{5,invalid_trailing_data}}. diff --git a/test/cases/null_then_garbage.json b/test/cases/null_then_garbage.json new file mode 100644 index 0000000..7b65b35 --- /dev/null +++ b/test/cases/null_then_garbage.json @@ -0,0 +1 @@ +nullx diff --git a/test/cases/nulls_and_bools.erl b/test/cases/nulls_and_bools.erl new file mode 100644 index 0000000..68544ed --- /dev/null +++ b/test/cases/nulls_and_bools.erl @@ -0,0 +1,5 @@ +{[ + {<<"boolean, true">>, true}, + {<<"boolean, false">>, false}, + {<<"null">>, null} +]}. diff --git a/test/cases/nulls_and_bools.json b/test/cases/nulls_and_bools.json new file mode 100644 index 0000000..65eb01f --- /dev/null +++ b/test/cases/nulls_and_bools.json @@ -0,0 +1,5 @@ +{ + "boolean, true": true, + "boolean, false": false, + "null": null +} diff --git a/test/cases/simple.erl b/test/cases/simple.erl new file mode 100644 index 0000000..fdc4deb --- /dev/null +++ b/test/cases/simple.erl @@ -0,0 +1,5 @@ +{[ + {<<"this">>, <<"is">>}, + {<<"really">>, <<"simple">>}, + {<<"json">>, <<"right?">>} +]}. \ No newline at end of file diff --git a/test/cases/simple.json b/test/cases/simple.json new file mode 100644 index 0000000..9ed80c9 --- /dev/null +++ b/test/cases/simple.json @@ -0,0 +1,5 @@ +{ + "this": "is", + "really": "simple", + "json": "right?" +} diff --git a/test/cases/string_invalid_escape.erl b/test/cases/string_invalid_escape.erl new file mode 100644 index 0000000..eb20890 --- /dev/null +++ b/test/cases/string_invalid_escape.erl @@ -0,0 +1 @@ +{error,{63,invalid_string}}. diff --git a/test/cases/string_invalid_escape.json b/test/cases/string_invalid_escape.json new file mode 100644 index 0000000..54a38ac --- /dev/null +++ b/test/cases/string_invalid_escape.json @@ -0,0 +1 @@ +["\n foo \/ bar \r\f\\\ufffd\t\b\"\\ and you can't escape thi\s"] diff --git a/test/cases/string_invalid_hex_char.erl b/test/cases/string_invalid_hex_char.erl new file mode 100644 index 0000000..3fc65a4 --- /dev/null +++ b/test/cases/string_invalid_hex_char.erl @@ -0,0 +1 @@ +{error,{48,invalid_string}}. diff --git a/test/cases/string_invalid_hex_char.json b/test/cases/string_invalid_hex_char.json new file mode 100644 index 0000000..bde7ee9 --- /dev/null +++ b/test/cases/string_invalid_hex_char.json @@ -0,0 +1 @@ +"foo foo, blah blah \u0123 \u4567 \u89ab \uc/ef \uABCD \uEFFE bar baz bing" diff --git a/test/cases/string_with_escapes.erl b/test/cases/string_with_escapes.erl new file mode 100644 index 0000000..51f7f25 --- /dev/null +++ b/test/cases/string_with_escapes.erl @@ -0,0 +1,5 @@ +[ + <<"\n foo \/ bar \r\f\\", 239, 191, 186, "\t\b\"\\">>, + <<"\"and this string has an escape at the beginning">>, + <<"and this string has no escapes">> +]. diff --git a/test/cases/string_with_escapes.json b/test/cases/string_with_escapes.json new file mode 100644 index 0000000..532b5f4 --- /dev/null +++ b/test/cases/string_with_escapes.json @@ -0,0 +1,3 @@ +["\n foo \/ bar \r\f\\\ufffa\t\b\"\\", + "\"and this string has an escape at the beginning", + "and this string has no escapes" ] diff --git a/test/cases/string_with_invalid_newline.erl b/test/cases/string_with_invalid_newline.erl new file mode 100644 index 0000000..626a2f4 --- /dev/null +++ b/test/cases/string_with_invalid_newline.erl @@ -0,0 +1 @@ +{error,{67,invalid_string}}. diff --git a/test/cases/string_with_invalid_newline.json b/test/cases/string_with_invalid_newline.json new file mode 100644 index 0000000..2756f26 --- /dev/null +++ b/test/cases/string_with_invalid_newline.json @@ -0,0 +1,2 @@ +"la di dah. this is a string, and I can do this, \n, but not this +" \ No newline at end of file diff --git a/test/cases/three_byte_utf8.erl b/test/cases/three_byte_utf8.erl new file mode 100644 index 0000000..111676b --- /dev/null +++ b/test/cases/three_byte_utf8.erl @@ -0,0 +1,4 @@ +{[ + {<<"matzue">>, <<230, 157, 190, 230, 177, 159>>}, + {<<"asakusa">>, <<230, 181, 133, 232, 141, 137>>} +]}. \ No newline at end of file diff --git a/test/cases/three_byte_utf8.json b/test/cases/three_byte_utf8.json new file mode 100644 index 0000000..9c9e656 --- /dev/null +++ b/test/cases/three_byte_utf8.json @@ -0,0 +1 @@ +{"matzue": "松江", "asakusa": "浅草"} diff --git a/test/cases/true.erl b/test/cases/true.erl new file mode 100644 index 0000000..60c0d88 --- /dev/null +++ b/test/cases/true.erl @@ -0,0 +1 @@ +true. \ No newline at end of file diff --git a/test/cases/true.json b/test/cases/true.json new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/test/cases/true.json @@ -0,0 +1 @@ +true diff --git a/test/cases/true_then_garbage.erl b/test/cases/true_then_garbage.erl new file mode 100644 index 0000000..30b0113 --- /dev/null +++ b/test/cases/true_then_garbage.erl @@ -0,0 +1 @@ +{error,{5,invalid_trailing_data}}. diff --git a/test/cases/true_then_garbage.json b/test/cases/true_then_garbage.json new file mode 100644 index 0000000..9151612 --- /dev/null +++ b/test/cases/true_then_garbage.json @@ -0,0 +1 @@ +truex \ No newline at end of file diff --git a/test/cases/unescaped_bulgarian.erl b/test/cases/unescaped_bulgarian.erl new file mode 100644 index 0000000..2a31b61 --- /dev/null +++ b/test/cases/unescaped_bulgarian.erl @@ -0,0 +1,5 @@ +[ + <<208, 148, 208, 176, 32, 208, 156, 209, 131, 32, 208, 149, 208, + 177, 208, 176, 32, 208, 156, 208, 176, 208, 185, 208, 186, 208, + 176, 209, 130, 208, 176>> +]. diff --git a/test/cases/unescaped_bulgarian.json b/test/cases/unescaped_bulgarian.json new file mode 100644 index 0000000..f9a70a6 --- /dev/null +++ b/test/cases/unescaped_bulgarian.json @@ -0,0 +1 @@ +["Да Му Еба Майката"] diff --git a/test/etap.erl b/test/etap.erl new file mode 100644 index 0000000..82e0cfe --- /dev/null +++ b/test/etap.erl @@ -0,0 +1,612 @@ +%% Copyright (c) 2008-2009 Nick Gerakines +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without +%% restriction, including without limitation the rights to use, +%% copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following +%% conditions: +%% +%% The above copyright notice and this permission notice shall be +%% included in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +%% OTHER DEALINGS IN THE SOFTWARE. +%% +%% @author Nick Gerakines [http://socklabs.com/] +%% @author Jeremy Wall +%% @version 0.3.4 +%% @copyright 2007-2008 Jeremy Wall, 2008-2009 Nick Gerakines +%% @reference http://testanything.org/wiki/index.php/Main_Page +%% @reference http://en.wikipedia.org/wiki/Test_Anything_Protocol +%% @todo Finish implementing the skip directive. +%% @todo Document the messages handled by this receive loop. +%% @todo Explain in documentation why we use a process to handle test input. +%% @doc etap is a TAP testing module for Erlang components and applications. +%% This module allows developers to test their software using the TAP method. +%% +%%

+%% TAP, the Test Anything Protocol, is a simple text-based interface between +%% testing modules in a test harness. TAP started life as part of the test +%% harness for Perl but now has implementations in C/C++, Python, PHP, Perl +%% and probably others by the time you read this. +%%

+%% +%% The testing process begins by defining a plan using etap:plan/1, running +%% a number of etap tests and then calling eta:end_tests/0. Please refer to +%% the Erlang modules in the t directory of this project for example tests. +-module(etap). +-vsn("0.3.4"). + +-export([ + ensure_test_server/0, + start_etap_server/0, + test_server/1, + msg/1, msg/2, + diag/1, diag/2, + expectation_mismatch_message/3, + plan/1, + end_tests/0, + not_ok/2, ok/2, is_ok/2, is/3, isnt/3, any/3, none/3, + fun_is/3, expect_fun/3, expect_fun/4, + is_greater/3, + skip/1, skip/2, + datetime/1, + skip/3, + bail/0, bail/1, + test_state/0, failure_count/0 +]). + +-export([ + contains_ok/3, + is_before/4 +]). + +-export([ + is_pid/2, + is_alive/2, + is_mfa/3 +]). + +-export([ + loaded_ok/2, + can_ok/2, can_ok/3, + has_attrib/2, is_attrib/3, + is_behaviour/2 +]). + +-export([ + dies_ok/2, + lives_ok/2, + throws_ok/3 +]). + + +-record(test_state, { + planned = 0, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" +}). + +%% @spec plan(N) -> Result +%% N = unknown | skip | {skip, string()} | integer() +%% Result = ok +%% @doc Create a test plan and boot strap the test server. +plan(unknown) -> + ensure_test_server(), + etap_server ! {self(), plan, unknown}, + ok; +plan(skip) -> + io:format("1..0 # skip~n"); +plan({skip, Reason}) -> + io:format("1..0 # skip ~s~n", [Reason]); +plan(N) when is_integer(N), N > 0 -> + ensure_test_server(), + etap_server ! {self(), plan, N}, + ok. + +%% @spec end_tests() -> ok +%% @doc End the current test plan and output test results. +%% @todo This should probably be done in the test_server process. +end_tests() -> + case whereis(etap_server) of + undefined -> self() ! true; + _ -> etap_server ! {self(), state} + end, + State = receive X -> X end, + if + State#test_state.planned == -1 -> + io:format("1..~p~n", [State#test_state.count]); + true -> + ok + end, + case whereis(etap_server) of + undefined -> ok; + _ -> etap_server ! done, ok + end. + +bail() -> + bail(""). + +bail(Reason) -> + etap_server ! {self(), diag, "Bail out! " ++ Reason}, + etap_server ! done, ok, + ok. + +%% @spec test_state() -> Return +%% Return = test_state_record() | {error, string()} +%% @doc Return the current test state +test_state() -> + etap_server ! {self(), state}, + receive + X when is_record(X, test_state) -> X + after + 1000 -> {error, "Timed out waiting for etap server reply.~n"} + end. + +%% @spec failure_count() -> Return +%% Return = integer() | {error, string()} +%% @doc Return the current failure count +failure_count() -> + case test_state() of + #test_state{fail=FailureCount} -> FailureCount; + X -> X + end. + +%% @spec msg(S) -> ok +%% S = string() +%% @doc Print a message in the test output. +msg(S) -> etap_server ! {self(), diag, S}, ok. + +%% @spec msg(Format, Data) -> ok +%% Format = atom() | string() | binary() +%% Data = [term()] +%% UnicodeList = [Unicode] +%% Unicode = int() +%% @doc Print a message in the test output. +%% Function arguments are passed through io_lib:format/2. +msg(Format, Data) -> msg(io_lib:format(Format, Data)). + +%% @spec diag(S) -> ok +%% S = string() +%% @doc Print a debug/status message related to the test suite. +diag(S) -> msg("# " ++ S). + +%% @spec diag(Format, Data) -> ok +%% Format = atom() | string() | binary() +%% Data = [term()] +%% UnicodeList = [Unicode] +%% Unicode = int() +%% @doc Print a debug/status message related to the test suite. +%% Function arguments are passed through io_lib:format/2. +diag(Format, Data) -> diag(io_lib:format(Format, Data)). + +%% @spec expectation_mismatch_message(Got, Expected, Desc) -> ok +%% Got = any() +%% Expected = any() +%% Desc = string() +%% @doc Print an expectation mismatch message in the test output. +expectation_mismatch_message(Got, Expected, Desc) -> + msg(" ---"), + msg(" description: ~p", [Desc]), + msg(" found: ~p", [Got]), + msg(" wanted: ~p", [Expected]), + msg(" ..."), + ok. + +% @spec evaluate(Pass, Got, Expected, Desc) -> Result +%% Pass = true | false +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Evaluate a test statement, printing an expectation mismatch message +%% if the test failed. +evaluate(Pass, Got, Expected, Desc) -> + case mk_tap(Pass, Desc) of + false -> + expectation_mismatch_message(Got, Expected, Desc), + false; + true -> + true + end. + +%% @spec ok(Expr, Desc) -> Result +%% Expr = true | false +%% Desc = string() +%% Result = true | false +%% @doc Assert that a statement is true. +ok(Expr, Desc) -> evaluate(Expr == true, Expr, true, Desc). + +%% @spec not_ok(Expr, Desc) -> Result +%% Expr = true | false +%% Desc = string() +%% Result = true | false +%% @doc Assert that a statement is false. +not_ok(Expr, Desc) -> evaluate(Expr == false, Expr, false, Desc). + +%% @spec is_ok(Expr, Desc) -> Result +%% Expr = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are the same. +is_ok(Expr, Desc) -> evaluate(Expr == ok, Expr, ok, Desc). + +%% @spec is(Got, Expected, Desc) -> Result +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are the same. +is(Got, Expected, Desc) -> evaluate(Got == Expected, Got, Expected, Desc). + +%% @spec isnt(Got, Expected, Desc) -> Result +%% Got = any() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Assert that two values are not the same. +isnt(Got, Expected, Desc) -> evaluate(Got /= Expected, Got, Expected, Desc). + +%% @spec is_greater(ValueA, ValueB, Desc) -> Result +%% ValueA = number() +%% ValueB = number() +%% Desc = string() +%% Result = true | false +%% @doc Assert that an integer is greater than another. +is_greater(ValueA, ValueB, Desc) when is_integer(ValueA), is_integer(ValueB) -> + mk_tap(ValueA > ValueB, Desc). + +%% @spec any(Got, Items, Desc) -> Result +%% Got = any() +%% Items = [any()] +%% Desc = string() +%% Result = true | false +%% @doc Assert that an item is in a list. +any(Got, Items, Desc) when is_function(Got) -> + is(lists:any(Got, Items), true, Desc); +any(Got, Items, Desc) -> + is(lists:member(Got, Items), true, Desc). + +%% @spec none(Got, Items, Desc) -> Result +%% Got = any() +%% Items = [any()] +%% Desc = string() +%% Result = true | false +%% @doc Assert that an item is not in a list. +none(Got, Items, Desc) when is_function(Got) -> + is(lists:any(Got, Items), false, Desc); +none(Got, Items, Desc) -> + is(lists:member(Got, Items), false, Desc). + +%% @spec fun_is(Fun, Expected, Desc) -> Result +%% Fun = function() +%% Expected = any() +%% Desc = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match. +fun_is(Fun, Expected, Desc) when is_function(Fun) -> + is(Fun(Expected), true, Desc). + +%% @spec expect_fun(ExpectFun, Got, Desc) -> Result +%% ExpectFun = function() +%% Got = any() +%% Desc = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match, using actual +%% value as the argument to the function. +expect_fun(ExpectFun, Got, Desc) -> + evaluate(ExpectFun(Got), Got, ExpectFun, Desc). + +%% @spec expect_fun(ExpectFun, Got, Desc, ExpectStr) -> Result +%% ExpectFun = function() +%% Got = any() +%% Desc = string() +%% ExpectStr = string() +%% Result = true | false +%% @doc Use an anonymous function to assert a pattern match, using actual +%% value as the argument to the function. +expect_fun(ExpectFun, Got, Desc, ExpectStr) -> + evaluate(ExpectFun(Got), Got, ExpectStr, Desc). + +%% @equiv skip(TestFun, "") +skip(TestFun) when is_function(TestFun) -> + skip(TestFun, ""). + +%% @spec skip(TestFun, Reason) -> ok +%% TestFun = function() +%% Reason = string() +%% @doc Skip a test. +skip(TestFun, Reason) when is_function(TestFun), is_list(Reason) -> + begin_skip(Reason), + catch TestFun(), + end_skip(), + ok. + +%% @spec skip(Q, TestFun, Reason) -> ok +%% Q = true | false | function() +%% TestFun = function() +%% Reason = string() +%% @doc Skips a test conditionally. The first argument to this function can +%% either be the 'true' or 'false' atoms or a function that returns 'true' or +%% 'false'. +skip(QFun, TestFun, Reason) when is_function(QFun), is_function(TestFun), is_list(Reason) -> + case QFun() of + true -> begin_skip(Reason), TestFun(), end_skip(); + _ -> TestFun() + end, + ok; + +skip(Q, TestFun, Reason) when is_function(TestFun), is_list(Reason), Q == true -> + begin_skip(Reason), + TestFun(), + end_skip(), + ok; + +skip(_, TestFun, Reason) when is_function(TestFun), is_list(Reason) -> + TestFun(), + ok. + +%% @private +begin_skip(Reason) -> + etap_server ! {self(), begin_skip, Reason}. + +%% @private +end_skip() -> + etap_server ! {self(), end_skip}. + +%% @spec contains_ok(string(), string(), string()) -> true | false +%% @doc Assert that a string is contained in another string. +contains_ok(Source, String, Desc) -> + etap:isnt( + string:str(Source, String), + 0, + Desc + ). + +%% @spec is_before(string(), string(), string(), string()) -> true | false +%% @doc Assert that a string comes before another string within a larger body. +is_before(Source, StringA, StringB, Desc) -> + etap:is_greater( + string:str(Source, StringB), + string:str(Source, StringA), + Desc + ). + +%% @doc Assert that a given variable is a pid. +is_pid(Pid, Desc) when is_pid(Pid) -> etap:ok(true, Desc); +is_pid(_, Desc) -> etap:ok(false, Desc). + +%% @doc Assert that a given process/pid is alive. +is_alive(Pid, Desc) -> + etap:ok(erlang:is_process_alive(Pid), Desc). + +%% @doc Assert that the current function of a pid is a given {M, F, A} tuple. +is_mfa(Pid, MFA, Desc) -> + etap:is({current_function, MFA}, erlang:process_info(Pid, current_function), Desc). + +%% @spec loaded_ok(atom(), string()) -> true | false +%% @doc Assert that a module has been loaded successfully. +loaded_ok(M, Desc) when is_atom(M) -> + etap:fun_is(fun({module, _}) -> true; (_) -> false end, code:load_file(M), Desc). + +%% @spec can_ok(atom(), atom()) -> true | false +%% @doc Assert that a module exports a given function. +can_ok(M, F) when is_atom(M), is_atom(F) -> + Matches = [X || {X, _} <- M:module_info(exports), X == F], + etap:ok(Matches > 0, lists:concat([M, " can ", F])). + +%% @spec can_ok(atom(), atom(), integer()) -> true | false +%% @doc Assert that a module exports a given function with a given arity. +can_ok(M, F, A) when is_atom(M); is_atom(F), is_number(A) -> + Matches = [X || X <- M:module_info(exports), X == {F, A}], + etap:ok(Matches > 0, lists:concat([M, " can ", F, "/", A])). + +%% @spec has_attrib(M, A) -> true | false +%% M = atom() +%% A = atom() +%% @doc Asserts that a module has a given attribute. +has_attrib(M, A) when is_atom(M), is_atom(A) -> + etap:isnt( + proplists:get_value(A, M:module_info(attributes), 'asdlkjasdlkads'), + 'asdlkjasdlkads', + lists:concat([M, " has attribute ", A]) + ). + +%% @spec has_attrib(M, A. V) -> true | false +%% M = atom() +%% A = atom() +%% V = any() +%% @doc Asserts that a module has a given attribute with a given value. +is_attrib(M, A, V) when is_atom(M) andalso is_atom(A) -> + etap:is( + proplists:get_value(A, M:module_info(attributes)), + [V], + lists:concat([M, "'s ", A, " is ", V]) + ). + +%% @spec is_behavior(M, B) -> true | false +%% M = atom() +%% B = atom() +%% @doc Asserts that a given module has a specific behavior. +is_behaviour(M, B) when is_atom(M) andalso is_atom(B) -> + is_attrib(M, behaviour, B). + +%% @doc Assert that an exception is raised when running a given function. +dies_ok(F, Desc) -> + case (catch F()) of + {'EXIT', _} -> etap:ok(true, Desc); + _ -> etap:ok(false, Desc) + end. + +%% @doc Assert that an exception is not raised when running a given function. +lives_ok(F, Desc) -> + etap:is(try_this(F), success, Desc). + +%% @doc Assert that the exception thrown by a function matches the given exception. +throws_ok(F, Exception, Desc) -> + try F() of + _ -> etap:ok(nok, Desc) + catch + _:E -> + etap:is(E, Exception, Desc) + end. + +%% @private +%% @doc Run a function and catch any exceptions. +try_this(F) when is_function(F, 0) -> + try F() of + _ -> success + catch + throw:E -> {throw, E}; + error:E -> {error, E}; + exit:E -> {exit, E} + end. + +%% @private +%% @doc Start the etap_server process if it is not running already. +ensure_test_server() -> + case whereis(etap_server) of + undefined -> + proc_lib:start(?MODULE, start_etap_server,[]); + _ -> + diag("The test server is already running.") + end. + +%% @private +%% @doc Start the etap_server loop and register itself as the etap_server +%% process. +start_etap_server() -> + catch register(etap_server, self()), + proc_lib:init_ack(ok), + etap:test_server(#test_state{ + planned = 0, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }). + + +%% @private +%% @doc The main etap_server receive/run loop. The etap_server receive loop +%% responds to seven messages apperatining to failure or passing of tests. +%% It is also used to initiate the testing process with the {_, plan, _} +%% message that clears the current test state. +test_server(State) -> + NewState = receive + {_From, plan, unknown} -> + io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), + io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), + State#test_state{ + planned = -1, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }; + {_From, plan, N} -> + io:format("# Current time local ~s~n", [datetime(erlang:localtime())]), + io:format("# Using etap version ~p~n", [ proplists:get_value(vsn, proplists:get_value(attributes, etap:module_info())) ]), + io:format("1..~p~n", [N]), + State#test_state{ + planned = N, + count = 0, + pass = 0, + fail = 0, + skip = 0, + skip_reason = "" + }; + {_From, begin_skip, Reason} -> + State#test_state{ + skip = 1, + skip_reason = Reason + }; + {_From, end_skip} -> + State#test_state{ + skip = 0, + skip_reason = "" + }; + {_From, pass, Desc} -> + FullMessage = skip_diag( + " - " ++ Desc, + State#test_state.skip, + State#test_state.skip_reason + ), + io:format("ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), + State#test_state{ + count = State#test_state.count + 1, + pass = State#test_state.pass + 1 + }; + + {_From, fail, Desc} -> + FullMessage = skip_diag( + " - " ++ Desc, + State#test_state.skip, + State#test_state.skip_reason + ), + io:format("not ok ~p ~s~n", [State#test_state.count + 1, FullMessage]), + State#test_state{ + count = State#test_state.count + 1, + fail = State#test_state.fail + 1 + }; + {From, state} -> + From ! State, + State; + {_From, diag, Message} -> + io:format("~s~n", [Message]), + State; + {From, count} -> + From ! State#test_state.count, + State; + {From, is_skip} -> + From ! State#test_state.skip, + State; + done -> + exit(normal) + end, + test_server(NewState). + +%% @private +%% @doc Process the result of a test and send it to the etap_server process. +mk_tap(Result, Desc) -> + IsSkip = lib:sendw(etap_server, is_skip), + case [IsSkip, Result] of + [_, true] -> + etap_server ! {self(), pass, Desc}, + true; + [1, _] -> + etap_server ! {self(), pass, Desc}, + true; + _ -> + etap_server ! {self(), fail, Desc}, + false + end. + +%% @private +%% @doc Format a date/time string. +datetime(DateTime) -> + {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, + io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B", [Year, Month, Day, Hour, Min, Sec]). + +%% @private +%% @doc Craft an output message taking skip/todo into consideration. +skip_diag(Message, 0, _) -> + Message; +skip_diag(_Message, 1, "") -> + " # SKIP"; +skip_diag(_Message, 1, Reason) -> + " # SKIP : " ++ Reason. diff --git a/test/util.erl b/test/util.erl new file mode 100644 index 0000000..fecc49b --- /dev/null +++ b/test/util.erl @@ -0,0 +1,31 @@ +-module(util). +-export([test_good/1, test_errors/1]). + +test_good(Cases) -> + lists:foreach(fun(Case) -> check_good(Case) end, Cases). + +test_errors(Cases) -> + lists:foreach(fun(Case) -> check_error(Case) end, Cases). + +ok_dec(J, _E) -> + lists:flatten(io_lib:format("Decoded ~p.", [J])). + +ok_enc(E, _J) -> + lists:flatten(io_lib:format("Encoded ~p", [E])). + +error_mesg(J) -> + lists:flatten(io_lib:format("Decoding ~p returns an error.", [J])). + +check_good({J, E}) -> + etap:is(jiffy:decode(J), {ok, E}, ok_dec(J, E)), + etap:is(jiffy:encode(E), {ok, J}, ok_enc(E, J)); +check_good({J, E, J2}) -> + etap:is(jiffy:decode(J), {ok, E}, ok_dec(J, E)), + etap:is(jiffy:encode(E), {ok, J2}, ok_enc(E, J2)). + +check_error(J) -> + etap:fun_is( + fun({error, _}) -> true; (_) -> false end, + jiffy:decode(J), + error_mesg(J) + ).