Sfoglia il codice sorgente

Initial import.

Passes all eep0018 tests.
pull/8/merge
Paul J. Davis 14 anni fa
commit
fdf9f61795
97 ha cambiato i file con 2885 aggiunte e 0 eliminazioni
  1. +4
    -0
      .gitignore
  2. +11
    -0
      Makefile
  3. +934
    -0
      c_src/decoder.c
  4. +556
    -0
      c_src/encoder.c
  5. +52
    -0
      c_src/jiffy.c
  6. +30
    -0
      c_src/jiffy.h
  7. +128
    -0
      c_src/utf8.c
  8. +23
    -0
      c_src/util.c
  9. BIN
      rebar
  10. +17
    -0
      rebar.config
  11. +7
    -0
      src/jiffy.app.src
  12. +62
    -0
      src/jiffy.erl
  13. +33
    -0
      test/001-yajl-tests.t
  14. +19
    -0
      test/002-literals.t
  15. +47
    -0
      test/003-numbers.t
  16. +36
    -0
      test/004-strings.t
  17. +34
    -0
      test/005-arrays.t
  18. +34
    -0
      test/006-maps.t
  19. +39
    -0
      test/007-compound.t
  20. +16
    -0
      test/cases/array.erl
  21. +6
    -0
      test/cases/array.json
  22. +1
    -0
      test/cases/array_close.erl
  23. +1
    -0
      test/cases/array_close.json
  24. +1
    -0
      test/cases/array_open.erl
  25. +1
    -0
      test/cases/array_open.json
  26. +1
    -0
      test/cases/bogus_char.erl
  27. +4
    -0
      test/cases/bogus_char.json
  28. +1
    -0
      test/cases/codepoints_from_unicode_org.erl
  29. +1
    -0
      test/cases/codepoints_from_unicode_org.json
  30. +1
    -0
      test/cases/deep_arrays.erl
  31. +1
    -0
      test/cases/deep_arrays.json
  32. +19
    -0
      test/cases/difficult_json_c_test_case.erl
  33. +1
    -0
      test/cases/difficult_json_c_test_case.json
  34. +1
    -0
      test/cases/doubles.erl
  35. +1
    -0
      test/cases/doubles.json
  36. +1
    -0
      test/cases/empty_array.erl
  37. +1
    -0
      test/cases/empty_array.json
  38. +1
    -0
      test/cases/empty_string.erl
  39. +1
    -0
      test/cases/empty_string.json
  40. +6
    -0
      test/cases/escaped_bulgarian.erl
  41. +4
    -0
      test/cases/escaped_bulgarian.json
  42. +1
    -0
      test/cases/escaped_foobar.erl
  43. +1
    -0
      test/cases/escaped_foobar.json
  44. +1
    -0
      test/cases/false.erl
  45. +1
    -0
      test/cases/false.json
  46. +1
    -0
      test/cases/false_then_garbage.erl
  47. +1
    -0
      test/cases/false_then_garbage.json
  48. +1
    -0
      test/cases/four_byte_utf8.erl
  49. +2
    -0
      test/cases/four_byte_utf8.json
  50. +13
    -0
      test/cases/integers.erl
  51. +3
    -0
      test/cases/integers.json
  52. +1
    -0
      test/cases/invalid_utf8.erl
  53. +1
    -0
      test/cases/invalid_utf8.json
  54. +1
    -0
      test/cases/isolated_surrogate_marker.erl
  55. +1
    -0
      test/cases/isolated_surrogate_marker.json
  56. +1
    -0
      test/cases/leading_zero_in_number.erl
  57. +1
    -0
      test/cases/leading_zero_in_number.json
  58. +1
    -0
      test/cases/lonely_minus_sign.erl
  59. +7
    -0
      test/cases/lonely_minus_sign.json
  60. +1
    -0
      test/cases/lonely_number.erl
  61. +1
    -0
      test/cases/lonely_number.json
  62. +1
    -0
      test/cases/map_close.erl
  63. +1
    -0
      test/cases/map_close.json
  64. +1
    -0
      test/cases/map_open.erl
  65. +1
    -0
      test/cases/map_open.json
  66. +1
    -0
      test/cases/missing_integer_after_decimal_point.erl
  67. +1
    -0
      test/cases/missing_integer_after_decimal_point.json
  68. +1
    -0
      test/cases/missing_integer_after_exponent.erl
  69. +1
    -0
      test/cases/missing_integer_after_exponent.json
  70. +1
    -0
      test/cases/non_utf8_char_in_string.erl
  71. +1
    -0
      test/cases/non_utf8_char_in_string.json
  72. +1
    -0
      test/cases/null.erl
  73. +1
    -0
      test/cases/null.json
  74. +1
    -0
      test/cases/null_then_garbage.erl
  75. +1
    -0
      test/cases/null_then_garbage.json
  76. +5
    -0
      test/cases/nulls_and_bools.erl
  77. +5
    -0
      test/cases/nulls_and_bools.json
  78. +5
    -0
      test/cases/simple.erl
  79. +5
    -0
      test/cases/simple.json
  80. +1
    -0
      test/cases/string_invalid_escape.erl
  81. +1
    -0
      test/cases/string_invalid_escape.json
  82. +1
    -0
      test/cases/string_invalid_hex_char.erl
  83. +1
    -0
      test/cases/string_invalid_hex_char.json
  84. +5
    -0
      test/cases/string_with_escapes.erl
  85. +3
    -0
      test/cases/string_with_escapes.json
  86. +1
    -0
      test/cases/string_with_invalid_newline.erl
  87. +2
    -0
      test/cases/string_with_invalid_newline.json
  88. +4
    -0
      test/cases/three_byte_utf8.erl
  89. +1
    -0
      test/cases/three_byte_utf8.json
  90. +1
    -0
      test/cases/true.erl
  91. +1
    -0
      test/cases/true.json
  92. +1
    -0
      test/cases/true_then_garbage.erl
  93. +1
    -0
      test/cases/true_then_garbage.json
  94. +5
    -0
      test/cases/unescaped_bulgarian.erl
  95. +1
    -0
      test/cases/unescaped_bulgarian.json
  96. +612
    -0
      test/etap.erl
  97. +31
    -0
      test/util.erl

+ 4
- 0
.gitignore Vedi File

@ -0,0 +1,4 @@
*.beam
*.o
*.so
ebin/jiffy.app

+ 11
- 0
Makefile Vedi File

@ -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

+ 934
- 0
c_src/decoder.c Vedi File

@ -0,0 +1,934 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

+ 556
- 0
c_src/encoder.c Vedi File

@ -0,0 +1,556 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#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;
}

+ 52
- 0
c_src/jiffy.c Vedi File

@ -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);

+ 30
- 0
c_src/jiffy.h Vedi File

@ -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

+ 128
- 0
c_src/utf8.c Vedi File

@ -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;
}

+ 23
- 0
c_src/util.c Vedi File

@ -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));
}

BIN
rebar Vedi File


+ 17
- 0
rebar.config Vedi File

@ -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"}
]}.

+ 7
- 0
src/jiffy.app.src Vedi File

@ -0,0 +1,7 @@
{application, jiffy, [
{description, "JSON Decoder/Encoder."},
{vsn, "%VSN%"},
{registered, []},
{mod, reloading, []},
{applications, [kernel]}
]}.

+ 62
- 0
src/jiffy.erl Vedi File

@ -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}]}).

+ 33
- 0
test/001-yajl-tests.t Vedi File

@ -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.

+ 19
- 0
test/002-literals.t Vedi File

@ -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().

+ 47
- 0
test/003-numbers.t Vedi File

@ -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">>
].

+ 36
- 0
test/004-strings.t Vedi File

@ -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,"\"">>
].

+ 34
- 0
test/005-arrays.t Vedi File

@ -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]">>
].

+ 34
- 0
test/006-maps.t Vedi File

@ -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,}">>
].

+ 39
- 0
test/007-compound.t Vedi File

@ -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() ->
[
<<"[{}">>,
<<"}]">>
].

+ 16
- 0
test/cases/array.erl Vedi File

@ -0,0 +1,16 @@
[
<<"foo">>,
<<"bar">>,
<<"baz">>,
true,
false,
null,
{[{<<"key">>, <<"value">>}]},
[
null,
null,
null,
[]
],
<<"\n\r\\">>
].

+ 6
- 0
test/cases/array.json Vedi File

@ -0,0 +1,6 @@
["foo",
"bar", "baz",
true,false,null,{"key":"value"},
[null,null,null,[]],
"\n\r\\"
]

+ 1
- 0
test/cases/array_close.erl Vedi File

@ -0,0 +1 @@
{error,{1,invalid_json}}.

+ 1
- 0
test/cases/array_close.json Vedi File

@ -0,0 +1 @@
]

+ 1
- 0
test/cases/array_open.erl Vedi File

@ -0,0 +1 @@
{error,{3,truncated_json}}.

+ 1
- 0
test/cases/array_open.json Vedi File

@ -0,0 +1 @@
[

+ 1
- 0
test/cases/bogus_char.erl Vedi File

@ -0,0 +1 @@
{error,{97,invalid_literal}}.

+ 4
- 0
test/cases/bogus_char.json Vedi File

@ -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?"]

+ 1
- 0
test/cases/codepoints_from_unicode_org.erl Vedi File

@ -0,0 +1 @@
<<77, 208, 176, 228, 186, 140, 240, 144, 140, 130>>.

+ 1
- 0
test/cases/codepoints_from_unicode_org.json Vedi File

@ -0,0 +1 @@
"\u004d\u0430\u4e8c\ud800\udf02"

+ 1
- 0
test/cases/deep_arrays.erl Vedi File

@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]].

+ 1
- 0
test/cases/deep_arrays.json Vedi File

@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

+ 19
- 0
test/cases/difficult_json_c_test_case.erl Vedi File

@ -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">>]}
]}
]}
]}}
]}}
]}.

+ 1
- 0
test/cases/difficult_json_c_test_case.json Vedi File

@ -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"] } ] } } }

+ 1
- 0
test/cases/doubles.erl Vedi File

@ -0,0 +1 @@
[10, 10, 3.141569, 1000].

+ 1
- 0
test/cases/doubles.json Vedi File

@ -0,0 +1 @@
[ 0.1e2, 1e1, 3.141569, 10000000000000e-10]

+ 1
- 0
test/cases/empty_array.erl Vedi File

@ -0,0 +1 @@
[].

+ 1
- 0
test/cases/empty_array.json Vedi File

@ -0,0 +1 @@
[]

+ 1
- 0
test/cases/empty_string.erl Vedi File

@ -0,0 +1 @@
<<"">>.

+ 1
- 0
test/cases/empty_string.json Vedi File

@ -0,0 +1 @@
""

+ 6
- 0
test/cases/escaped_bulgarian.erl Vedi File

@ -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>>
].

+ 4
- 0
test/cases/escaped_bulgarian.json Vedi File

@ -0,0 +1,4 @@
["\u0414\u0430",
"\u041c\u0443",
"\u0415\u0431\u0430",
"\u041c\u0430\u0439\u043a\u0430\u0442\u0430"]

+ 1
- 0
test/cases/escaped_foobar.erl Vedi File

@ -0,0 +1 @@
<<"foobar">>.

+ 1
- 0
test/cases/escaped_foobar.json Vedi File

@ -0,0 +1 @@
"\u0066\u006f\u006f\u0062\u0061\u0072"

+ 1
- 0
test/cases/false.erl Vedi File

@ -0,0 +1 @@
false.

+ 1
- 0
test/cases/false.json Vedi File

@ -0,0 +1 @@
false

+ 1
- 0
test/cases/false_then_garbage.erl Vedi File

@ -0,0 +1 @@
{error,{6,invalid_trailing_data}}.

+ 1
- 0
test/cases/false_then_garbage.json Vedi File

@ -0,0 +1 @@
falsex

+ 1
- 0
test/cases/four_byte_utf8.erl Vedi File

@ -0,0 +1 @@
{[{<<"U+10ABCD">>, <<244, 138, 175, 141>>}]}.

+ 2
- 0
test/cases/four_byte_utf8.json Vedi File

@ -0,0 +1,2 @@
{ "U+10ABCD": "􊯍" }

+ 13
- 0
test/cases/integers.erl Vedi File

@ -0,0 +1,13 @@
[
1,
2,
3,
4,
5,
6,
7,
123456789,
-123456789,
2147483647,
-2147483647
].

+ 3
- 0
test/cases/integers.json Vedi File

@ -0,0 +1,3 @@
[ 1,2,3,4,5,6,7,
123456789 , -123456789,
2147483647, -2147483647 ]

+ 1
- 0
test/cases/invalid_utf8.erl Vedi File

@ -0,0 +1 @@
{error,{10,invalid_string}}.

+ 1
- 0
test/cases/invalid_utf8.json Vedi File

@ -0,0 +1 @@
["Да М� Еба Майката"]

+ 1
- 0
test/cases/isolated_surrogate_marker.erl Vedi File

@ -0,0 +1 @@
{error,{8,invalid_string}}.

+ 1
- 0
test/cases/isolated_surrogate_marker.json Vedi File

@ -0,0 +1 @@
"\ud800"

+ 1
- 0
test/cases/leading_zero_in_number.erl Vedi File

@ -0,0 +1 @@
{error,{17,invalid_json}}.

+ 1
- 0
test/cases/leading_zero_in_number.json Vedi File

@ -0,0 +1 @@
{ "bad thing": 01 }

+ 1
- 0
test/cases/lonely_minus_sign.erl Vedi File

@ -0,0 +1 @@
{error,{83,invalid_number}}.

+ 7
- 0
test/cases/lonely_minus_sign.json Vedi File

@ -0,0 +1,7 @@
[
"foo", true,
true, "blue",
"baby where are you?", "oh boo hoo!",
-
]

+ 1
- 0
test/cases/lonely_number.erl Vedi File

@ -0,0 +1 @@
123456789.

+ 1
- 0
test/cases/lonely_number.json Vedi File

@ -0,0 +1 @@
123456789

+ 1
- 0
test/cases/map_close.erl Vedi File

@ -0,0 +1 @@
{error,{1,invalid_json}}.

+ 1
- 0
test/cases/map_close.json Vedi File

@ -0,0 +1 @@
}

+ 1
- 0
test/cases/map_open.erl Vedi File

@ -0,0 +1 @@
{error,{3,truncated_json}}.

+ 1
- 0
test/cases/map_open.json Vedi File

@ -0,0 +1 @@
{

+ 1
- 0
test/cases/missing_integer_after_decimal_point.erl Vedi File

@ -0,0 +1 @@
{error,{4,invalid_number}}.

+ 1
- 0
test/cases/missing_integer_after_decimal_point.json Vedi File

@ -0,0 +1 @@
10.e2

+ 1
- 0
test/cases/missing_integer_after_exponent.erl Vedi File

@ -0,0 +1 @@
{error,{4,invalid_number}}.

+ 1
- 0
test/cases/missing_integer_after_exponent.json Vedi File

@ -0,0 +1 @@
10e

+ 1
- 0
test/cases/non_utf8_char_in_string.erl Vedi File

@ -0,0 +1 @@
{error,{125,invalid_string}}.

+ 1
- 0
test/cases/non_utf8_char_in_string.json Vedi File

@ -0,0 +1 @@
{"CoreletAPIVersion":2,"CoreletType":"standalone","documentation":"A corelet that provides the capability to upload a folder’s contents into a user’s 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"}

+ 1
- 0
test/cases/null.erl Vedi File

@ -0,0 +1 @@
null.

+ 1
- 0
test/cases/null.json Vedi File

@ -0,0 +1 @@
null

+ 1
- 0
test/cases/null_then_garbage.erl Vedi File

@ -0,0 +1 @@
{error,{5,invalid_trailing_data}}.

+ 1
- 0
test/cases/null_then_garbage.json Vedi File

@ -0,0 +1 @@
nullx

+ 5
- 0
test/cases/nulls_and_bools.erl Vedi File

@ -0,0 +1,5 @@
{[
{<<"boolean, true">>, true},
{<<"boolean, false">>, false},
{<<"null">>, null}
]}.

+ 5
- 0
test/cases/nulls_and_bools.json Vedi File

@ -0,0 +1,5 @@
{
"boolean, true": true,
"boolean, false": false,
"null": null
}

+ 5
- 0
test/cases/simple.erl Vedi File

@ -0,0 +1,5 @@
{[
{<<"this">>, <<"is">>},
{<<"really">>, <<"simple">>},
{<<"json">>, <<"right?">>}
]}.

+ 5
- 0
test/cases/simple.json Vedi File

@ -0,0 +1,5 @@
{
"this": "is",
"really": "simple",
"json": "right?"
}

+ 1
- 0
test/cases/string_invalid_escape.erl Vedi File

@ -0,0 +1 @@
{error,{63,invalid_string}}.

+ 1
- 0
test/cases/string_invalid_escape.json Vedi File

@ -0,0 +1 @@
["\n foo \/ bar \r\f\\\ufffd\t\b\"\\ and you can't escape thi\s"]

+ 1
- 0
test/cases/string_invalid_hex_char.erl Vedi File

@ -0,0 +1 @@
{error,{48,invalid_string}}.

+ 1
- 0
test/cases/string_invalid_hex_char.json Vedi File

@ -0,0 +1 @@
"foo foo, blah blah \u0123 \u4567 \u89ab \uc/ef \uABCD \uEFFE bar baz bing"

+ 5
- 0
test/cases/string_with_escapes.erl Vedi File

@ -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">>
].

+ 3
- 0
test/cases/string_with_escapes.json Vedi File

@ -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" ]

+ 1
- 0
test/cases/string_with_invalid_newline.erl Vedi File

@ -0,0 +1 @@
{error,{67,invalid_string}}.

+ 2
- 0
test/cases/string_with_invalid_newline.json Vedi File

@ -0,0 +1,2 @@
"la di dah. this is a string, and I can do this, \n, but not this
"

+ 4
- 0
test/cases/three_byte_utf8.erl Vedi File

@ -0,0 +1,4 @@
{[
{<<"matzue">>, <<230, 157, 190, 230, 177, 159>>},
{<<"asakusa">>, <<230, 181, 133, 232, 141, 137>>}
]}.

+ 1
- 0
test/cases/three_byte_utf8.json Vedi File

@ -0,0 +1 @@
{"matzue": "松江", "asakusa": "浅草"}

+ 1
- 0
test/cases/true.erl Vedi File

@ -0,0 +1 @@
true.

+ 1
- 0
test/cases/true.json Vedi File

@ -0,0 +1 @@
true

+ 1
- 0
test/cases/true_then_garbage.erl Vedi File

@ -0,0 +1 @@
{error,{5,invalid_trailing_data}}.

+ 1
- 0
test/cases/true_then_garbage.json Vedi File

@ -0,0 +1 @@
truex

+ 5
- 0
test/cases/unescaped_bulgarian.erl Vedi File

@ -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>>
].

+ 1
- 0
test/cases/unescaped_bulgarian.json Vedi File

@ -0,0 +1 @@
["Да Му Еба Майката"]

+ 612
- 0
test/etap.erl Vedi File

@ -0,0 +1,612 @@
%% Copyright (c) 2008-2009 Nick Gerakines <nick@gerakines.net>
%%
%% 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 <nick@gerakines.net> [http://socklabs.com/]
%% @author Jeremy Wall <jeremy@marzhillstudios.com>
%% @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.
%%
%% <blockquote cite="http://en.wikipedia.org/wiki/Test_Anything_Protocol"><p>
%% 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.
%% </p></blockquote>
%%
%% 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.

+ 31
- 0
test/util.erl Vedi File

@ -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)
).

Caricamento…
Annulla
Salva