From 595058a2920dd0b6b2e8bebf052922e56907d26f Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Tue, 11 Sep 2018 11:41:56 +0800 Subject: [PATCH 1/9] unit1 answer --- tutorial01/leptjson.c | 28 +++++++++++++++++++++++++++- tutorial01/test.c | 16 ++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index 5299fe1d..2a41f07e 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -24,9 +24,29 @@ static int lept_parse_null(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } +static int lept_parse_true(lept_context* c, lept_value* v) { + EXPECT(c, 't'); + if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') + return LEPT_PARSE_INVALID_VALUE; + c->json += 3; + v->type = LEPT_TRUE; + return LEPT_PARSE_OK; +} + +static int lept_parse_false(lept_context* c, lept_value* v) { + EXPECT(c, 'f'); + if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') + return LEPT_PARSE_INVALID_VALUE; + c->json += 4; + v->type = LEPT_FALSE; + return LEPT_PARSE_OK; +} + static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { case 'n': return lept_parse_null(c, v); + case 't': return lept_parse_true(c, v); + case 'f': return lept_parse_false(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; default: return LEPT_PARSE_INVALID_VALUE; } @@ -34,11 +54,17 @@ static int lept_parse_value(lept_context* c, lept_value* v) { int lept_parse(lept_value* v, const char* json) { lept_context c; + int ret; assert(v != NULL); c.json = json; v->type = LEPT_NULL; lept_parse_whitespace(&c); - return lept_parse_value(&c, v); + if ((ret = lept_parse_value(&c,v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + return ret; } lept_type lept_get_type(const lept_value* v) { diff --git a/tutorial01/test.c b/tutorial01/test.c index e7672181..b0d825a3 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -27,6 +27,20 @@ static void test_parse_null() { EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); } +static void test_parse_true() { + lept_value v; + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); +} + +static void test_parse_false() { + lept_value v; + v.type = LEPT_TRUE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); +} + static void test_parse_expect_value() { lept_value v; @@ -59,6 +73,8 @@ static void test_parse_root_not_singular() { static void test_parse() { test_parse_null(); + test_parse_true(); + test_parse_false(); test_parse_expect_value(); test_parse_invalid_value(); test_parse_root_not_singular(); From 6a57f3b8cf05df7b1e1dcc4f6061878e4489749f Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Tue, 11 Sep 2018 22:11:03 +0800 Subject: [PATCH 2/9] my answer for qestion --- tutorial02/leptjson.c | 66 +++++++++++++++++++++++++++++++++++++++---- tutorial02/test.c | 8 ++++-- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 7693e43b..9b882405 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -1,8 +1,12 @@ #include "leptjson.h" #include /* assert() */ #include /* NULL, strtod() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch)>='0' && (ch) <='9') +#define ISDIGIT1TO9(ch) ((ch)>='1' && (ch) <='9') typedef struct { const char* json; @@ -42,22 +46,72 @@ static int lept_parse_null(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + + for (i = 0; literal[i + 1]; ++i) { + if (c->json[i] != literal[i+1]) + return LEPT_PARSE_INVALID_VALUE; + } + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + static int lept_parse_number(lept_context* c, lept_value* v) { + size_t i = 0; char* end; + const char* number = c->json; /* \TODO validate number */ - v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; + + + if (number[i] == '-' || ISDIGIT(number[i])) { + if (number[i] == '-') + ++i; + if (number[i] == '0') { + ++i; + if (ISDIGIT(number[i]) || number[i] == 'x') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + } + else { + if (ISDIGIT1TO9(number[i]))++i; + while (ISDIGIT(number[i]))++i; + } + if (number[i] == '.') { + ++i; + if (!ISDIGIT(number[i])) return LEPT_PARSE_INVALID_VALUE; + while (ISDIGIT(number[i]))++i; + } + if (number[i] == 'e' && number[i] == 'E') { + ++i; + if (number[i] == '+' || number[i] == '-') ++i; + while (ISDIGIT(number[i]))++i; + + + } + + } + else { + return LEPT_PARSE_INVALID_VALUE; + } + errno = 0; + v->n = strtod(c->json, NULL); + if (c->json == end) + return LEPT_PARSE_INVALID_VALUE; + if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; c->json = end; + v->type = LEPT_NUMBER; return LEPT_PARSE_OK; } static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); default: return lept_parse_number(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; } diff --git a/tutorial02/test.c b/tutorial02/test.c index 6e3ebed2..a5bf6063 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -42,6 +42,7 @@ static void test_parse_false() { EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); } + #define TEST_NUMBER(expect, json)\ do {\ lept_value v;\ @@ -70,6 +71,7 @@ static void test_parse_number() { TEST_NUMBER(1.234E+10, "1.234E+10"); TEST_NUMBER(1.234E-10, "1.234E-10"); TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + TEST_NUMBER(2e-1074, "2e-1074"); } #define TEST_ERROR(error, json)\ @@ -89,7 +91,7 @@ static void test_parse_invalid_value() { TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); @@ -105,7 +107,7 @@ static void test_parse_invalid_value() { static void test_parse_root_not_singular() { TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); @@ -114,7 +116,7 @@ static void test_parse_root_not_singular() { } static void test_parse_number_too_big() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); #endif From 70192311e2d3138b5b059bc3c3fc4c244de37b0c Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Wed, 12 Sep 2018 15:58:09 +0800 Subject: [PATCH 3/9] add answer for t03 --- tutorial02/leptjson.c | 2 +- tutorial03/leptjson.c | 59 ++++++++++++++++++++++++++++++++++++++++++- tutorial03/test.c | 20 ++++++++++++--- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 9b882405..e7101164 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -96,7 +96,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; } errno = 0; - v->n = strtod(c->json, NULL); + v->n = strtod(c->json, &end); if (c->json == end) return LEPT_PARSE_INVALID_VALUE; if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 07f7e2c7..e9477ba5 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -4,6 +4,8 @@ #include /* HUGE_VAL */ #include /* NULL, malloc(), realloc(), free(), strtod() */ #include /* memcpy() */ +#include +#include #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 @@ -94,6 +96,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { for (;;) { char ch = *p++; switch (ch) { + case '\"': len = c->top - head; lept_set_string(v, (const char*)lept_context_pop(c, len), len); @@ -102,8 +105,49 @@ static int lept_parse_string(lept_context* c, lept_value* v) { case '\0': c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; + case '\\': + switch (*p) + { + case 'n': + PUTC(c, '\n'); + break; + case '\\': + PUTC(c, '\\'); + break; + case '/': + PUTC(c, '\/'); + break; + case 'b': + PUTC(c, '\b'); + break; + case 'f': + PUTC(c, '\f'); + break; + case 'r': + PUTC(c, '\r'); + break; + case 't': + PUTC(c, '\t'); + break; + case '"': + PUTC(c, '\"'); + break; + + default: + return LEPT_PARSE_INVALID_STRING_ESCAPE; + break; + } + ++p; + break; + + default: + if ((ch >= '\x20' && ch <= '\x21') + || (ch >= '\x23' && ch <= '\x5B') + || ch >= '\x5D' ) PUTC(c, ch); + else + return LEPT_PARSE_INVALID_STRING_CHAR; } } } @@ -154,11 +198,21 @@ lept_type lept_get_type(const lept_value* v) { int lept_get_boolean(const lept_value* v) { /* \TODO */ - return 0; + assert(v != NULL && (v->type == LEPT_TRUE|| v->type == LEPT_FALSE)); + if (v->type == LEPT_TRUE) + return 1; + else + return 0; + } void lept_set_boolean(lept_value* v, int b) { /* \TODO */ + assert(v != NULL); + if (b == 0) + v->type = LEPT_FALSE; + else + v->type = LEPT_TRUE; } double lept_get_number(const lept_value* v) { @@ -168,6 +222,9 @@ double lept_get_number(const lept_value* v) { void lept_set_number(lept_value* v, double n) { /* \TODO */ + assert(v != NULL); + v->u.n = n; + v->type = LEPT_NUMBER; } const char* lept_get_string(const lept_value* v) { diff --git a/tutorial03/test.c b/tutorial03/test.c index ac788aca..a5e514a9 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -107,7 +107,7 @@ static void test_parse_number() { static void test_parse_string() { TEST_STRING("", "\"\""); TEST_STRING("Hello", "\"Hello\""); -#if 0 +#if 1 TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); #endif @@ -163,7 +163,7 @@ static void test_parse_missing_quotation_mark() { } static void test_parse_invalid_string_escape() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); @@ -172,7 +172,7 @@ static void test_parse_invalid_string_escape() { } static void test_parse_invalid_string_char() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); #endif @@ -190,10 +190,24 @@ static void test_access_null() { static void test_access_boolean() { /* \TODO */ /* Use EXPECT_TRUE() and EXPECT_FALSE() */ + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + + lept_free(&v); } static void test_access_number() { /* \TODO */ + lept_value v; + lept_init(&v); + lept_set_number(&v, 0.1); + EXPECT_EQ_DOUBLE(0.1, lept_get_number(&v)); + lept_free(&v); } static void test_access_string() { From b744db173398a7e1023781c0ce12613b1dde44b8 Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Wed, 12 Sep 2018 22:35:56 +0800 Subject: [PATCH 4/9] add answer for t04 --- tutorial04/leptjson.c | 53 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index 0a123bf2..e23416ae 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -8,6 +8,8 @@ #include /* HUGE_VAL */ #include /* NULL, malloc(), realloc(), free(), strtod() */ #include /* memcpy() */ +#include +#include #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 @@ -92,18 +94,50 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static const char* lept_parse_hex4(const char* p, unsigned* u) { /* \TODO */ + size_t i; + *u = 0; + for (i = 0; i < 4; ++i) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A'-10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a'-10); + else + return NULL; + + } return p; } static void lept_encode_utf8(lept_context* c, unsigned u) { /* \TODO */ + if (u <= 0x7F) { + PUTC(c, u & 0xFF); + } + else if (u <= 0x7FF) { + PUTC(c, 0xc0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | (u & 0xFF)); + + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | (u >> 12) & 0xFF); + PUTC(c, 0x80 | (u >> 6) & 0x3F); + PUTC(c, 0x80 | (u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | (u >> 18) & 0xFF); + PUTC(c, 0x80 | (u >> 12) & 0x3F); + PUTC(c, 0x80 | (u >> 6) & 0x3F); + PUTC(c, 0x80 | (u & 0x3F)); + } } #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; - unsigned u; + unsigned u, uLow; const char* p; EXPECT(c, '\"'); p = c->json; @@ -129,7 +163,24 @@ static int lept_parse_string(lept_context* c, lept_value* v) { if (!(p = lept_parse_hex4(p, &u))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); /* \TODO surrogate handling */ + if (u >= 0xD800 && u <= 0xDBFF) { + if (*p == '\\' && *(p + 1) == 'u') { + p += 2; + if (!(p = lept_parse_hex4(p, &uLow))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (uLow >= 0xDC00 && uLow <= 0xDFFF) { + u = 0x10000 + (u - 0xD800) * 0x400 + (uLow - 0xDC00); + } + else { + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + } + } + else { + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + } + } lept_encode_utf8(c, u); + break; default: STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); From d77f6ac80be90ef227dd6ee6aff13556442747f3 Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Sun, 16 Sep 2018 16:00:52 +0800 Subject: [PATCH 5/9] add answer for t05 --- tutorial05/leptjson.c | 17 ++++++++++++++- tutorial05/test.c | 48 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/tutorial05/leptjson.c b/tutorial05/leptjson.c index d2c83f34..11f17cde 100644 --- a/tutorial05/leptjson.c +++ b/tutorial05/leptjson.c @@ -183,10 +183,15 @@ static int lept_parse_string(lept_context* c, lept_value* v) { static int lept_parse_value(lept_context* c, lept_value* v); +#define ARRAY_ERROR(ret) do { c->top = head; return ret; } while(0) + + static int lept_parse_array(lept_context* c, lept_value* v) { + size_t head = c->top; size_t size = 0; int ret; EXPECT(c, '['); + lept_parse_whitespace(c); if (*c->json == ']') { c->json++; v->type = LEPT_ARRAY; @@ -195,12 +200,14 @@ static int lept_parse_array(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } for (;;) { + lept_parse_whitespace(c); lept_value e; lept_init(&e); if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) return ret; memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); size++; + lept_parse_whitespace(c); if (*c->json == ',') c->json++; else if (*c->json == ']') { @@ -212,7 +219,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } else - return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + ARRAY_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET); } } @@ -253,6 +260,14 @@ void lept_free(lept_value* v) { assert(v != NULL); if (v->type == LEPT_STRING) free(v->u.s.s); + if (v->type == LEPT_ARRAY) { + size_t i; + for (i = 0; i < v->u.a.size; ++i) { + lept_free(&v->u.a.e[i]); + } + free(v->u.a.e); + } + v->type = LEPT_NULL; } diff --git a/tutorial05/test.c b/tutorial05/test.c index 2d4dd21e..db1afee5 100644 --- a/tutorial05/test.c +++ b/tutorial05/test.c @@ -129,12 +129,57 @@ static void test_parse_string() { static void test_parse_array() { lept_value v; + size_t i; + size_t j; lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + + lept_free(&v); + + lept_init(&v); + // test + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_array_element(&v, 4)->u.s.len); + lept_free(&v); + + lept_init(&v); + // test + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + + for (i = 0; i < 4; ++i) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; ++j) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + + lept_free(&v); + + } #define TEST_ERROR(error, json)\ @@ -229,7 +274,7 @@ static void test_parse_invalid_unicode_surrogate() { } static void test_parse_miss_comma_or_square_bracket() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); @@ -290,6 +335,7 @@ static void test_access_string() { lept_init(&v); lept_set_string(&v, "", 0); EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); lept_free(&v); From bfae11325f85daf976c908226f8557372ebf7364 Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Mon, 17 Sep 2018 00:02:20 +0800 Subject: [PATCH 6/9] add answer for t06 --- tutorial06/leptjson.c | 72 +++++++++++++++++++++++++++++++++++++++++-- tutorial06/test.c | 4 +-- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/tutorial06/leptjson.c b/tutorial06/leptjson.c index 64e3bd0d..a399e831 100644 --- a/tutorial06/leptjson.c +++ b/tutorial06/leptjson.c @@ -127,7 +127,9 @@ static void lept_encode_utf8(lept_context* c, unsigned u) { #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) -static int lept_parse_string(lept_context* c, lept_value* v) { + + +static int lept_parse_string_raw(lept_context* c, char** str, size_t* plen) { size_t head = c->top, len; unsigned u, u2; const char* p; @@ -138,7 +140,9 @@ static int lept_parse_string(lept_context* c, lept_value* v) { switch (ch) { case '\"': len = c->top - head; - lept_set_string(v, (const char*)lept_context_pop(c, len), len); + *plen = len; + *str = (char*)lept_context_pop(c, len); + c->json = p; return LEPT_PARSE_OK; case '\\': @@ -181,6 +185,17 @@ static int lept_parse_string(lept_context* c, lept_value* v) { } } +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t len; + char* s; + int ret; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) { + lept_set_string(v, s, len); + } + return ret; + +} + static int lept_parse_value(lept_context* c, lept_value* v); static int lept_parse_array(lept_context* c, lept_value* v) { @@ -227,9 +242,11 @@ static int lept_parse_array(lept_context* c, lept_value* v) { } static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i; size_t size; lept_member m; int ret; + EXPECT(c, '{'); lept_parse_whitespace(c); if (*c->json == '}') { @@ -244,7 +261,26 @@ static int lept_parse_object(lept_context* c, lept_value* v) { for (;;) { lept_init(&m.v); /* \todo parse key to m.k, m.klen */ + if (*c->json != '\"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + char* str; size_t len; + if ((ret = lept_parse_string_raw(c, &str, &len)) != LEPT_PARSE_OK) + break; + m.k = (char*)malloc(len + 1); + memcpy(m.k, str, len); + m.k[len] = '\0'; + m.klen = len; /* \todo parse ws colon ws */ + lept_parse_whitespace(c); + + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); /* parse value */ if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) break; @@ -252,8 +288,34 @@ static int lept_parse_object(lept_context* c, lept_value* v) { size++; m.k = NULL; /* ownership is transferred to member on stack */ /* \todo parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + size_t s = size * sizeof(lept_member); + c->json++; + v->type = LEPT_OBJECT; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + v->u.o.size = size; + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } + + } /* \todo Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; return ret; } @@ -303,6 +365,12 @@ void lept_free(lept_value* v) { lept_free(&v->u.a.e[i]); free(v->u.a.e); break; + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); default: break; } v->type = LEPT_NULL; diff --git a/tutorial06/test.c b/tutorial06/test.c index 8d332e45..75e3f082 100644 --- a/tutorial06/test.c +++ b/tutorial06/test.c @@ -352,7 +352,7 @@ static void test_parse() { test_parse_number(); test_parse_string(); test_parse_array(); -#if 0 +#if 1 test_parse_object(); #endif @@ -366,7 +366,7 @@ static void test_parse() { test_parse_invalid_unicode_hex(); test_parse_invalid_unicode_surrogate(); test_parse_miss_comma_or_square_bracket(); -#if 0 +#if 1 test_parse_miss_key(); test_parse_miss_colon(); test_parse_miss_comma_or_curly_bracket(); From c1c2ebf9fa4fcf17bd22674898315c7531b7ab89 Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Tue, 18 Sep 2018 21:19:13 +0800 Subject: [PATCH 7/9] add answer for t07 --- tutorial07/leptjson.c | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tutorial07/leptjson.c b/tutorial07/leptjson.c index 5307b892..3b35b4fc 100644 --- a/tutorial07/leptjson.c +++ b/tutorial07/leptjson.c @@ -346,11 +346,57 @@ int lept_parse(lept_value* v, const char* json) { return ret; } + + static void lept_stringify_string(lept_context* c, const char* s, size_t len) { /* ... */ + size_t i; + assert(s != NULL); + PUTS(c, "\"", 1); + for (i = 0; i < len; ++i) { + switch (s[i]) + { + case '\n': + PUTS(c, "\\n", 2); + break; + case '\b': + PUTS(c, "\\b", 2); + break; + case '\f': + PUTS(c, "\\f", 2); + break; + case '\r': + PUTS(c, "\\r", 2); + break; + case '\t': + PUTS(c, "\\t", 2); + break; + case '\\': + PUTS(c, "\\\\", 2); + break; + case '\"': + PUTS(c, "\\\"", 2); + break; + default: + if (s[i] < 0x20) { + char buffer[7]; + unsigned char ch = s[i]; + sprintf(buffer, "\\u%04X", ch); + PUTS(c, buffer, 6); + } + else { + PUTS(c, &s[i], 1); + } + + + break; + } + } + PUTS(c, "\"", 1); } static void lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; switch (v->type) { case LEPT_NULL: PUTS(c, "null", 4); break; case LEPT_FALSE: PUTS(c, "false", 5); break; @@ -359,8 +405,24 @@ static void lept_stringify_value(lept_context* c, const lept_value* v) { case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; case LEPT_ARRAY: /* ... */ + PUTC(c, '['); + for (i = 0; i < v->u.a.size; ++i) { + lept_stringify_value(c, &v->u.a.e[i]); + if (i < v->u.a.size - 1) + PUTC(c, ','); + } + PUTC(c, ']'); break; case LEPT_OBJECT: + PUTC(c, '{'); + for (i = 0; i < v->u.o.size; ++i) { + lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen); + PUTC(c, ':'); + lept_stringify_value(c, &v->u.o.m[i].v); + if (i < v->u.a.size - 1) + PUTC(c, ','); + } + PUTC(c, '}'); /* ... */ break; default: assert(0 && "invalid type"); From ceec13ce09e376ff7b17dbe02d7ca0909e6c5111 Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Wed, 19 Sep 2018 11:01:57 +0800 Subject: [PATCH 8/9] lept_stringify_string perfomamce optimization --- tutorial07/leptjson.c | 55 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/tutorial07/leptjson.c b/tutorial07/leptjson.c index 3b35b4fc..08bebeaa 100644 --- a/tutorial07/leptjson.c +++ b/tutorial07/leptjson.c @@ -347,7 +347,7 @@ int lept_parse(lept_value* v, const char* json) { } - +#if 0 static void lept_stringify_string(lept_context* c, const char* s, size_t len) { /* ... */ size_t i; @@ -394,6 +394,59 @@ static void lept_stringify_string(lept_context* c, const char* s, size_t len) { } PUTS(c, "\"", 1); } +#else +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + /* ... */ + size_t i, size; + static const char hexdigits[] = { '0', '1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + char* p, *head; + p = head = lept_context_push(c, size = len * 6+2); + assert(s != NULL); + *p++ = '\"'; + for (i = 0; i < len; ++i) { + char ch = s[i]; + switch (ch) + { + case '\n': + *p++ = '\\'; *p++ = 'n'; + break; + case '\b': + *p++ = '\\'; *p++ = 'b'; + break; + case '\f': + *p++ = '\\'; *p++ = 'f'; + break; + case '\r': + *p++ = '\\'; *p++ = 'r'; + break; + case '\t': + *p++ = '\\'; *p++ = 't'; + break; + case '\\': + *p++ = '\\'; *p++ = '\\'; + break; + case '\"': + *p++ = '\\'; *p++ = '\"'; + break; + default: + if (s[i] < 0x20) { + + *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0'; + *p++ = hexdigits[ch >> 4]; + *p++ = hexdigits[ch & 15]; + } + else { + *p++ = ch; + } + + + break; + } + } + *p++ = '\"'; + c->top -= size - (p - head); +} +#endif static void lept_stringify_value(lept_context* c, const lept_value* v) { size_t i; From 92c9296d856ba1db94d98762eec94c3b2da8ea0d Mon Sep 17 00:00:00 2001 From: Andapeng <373770643@qq.com> Date: Fri, 21 Sep 2018 10:31:03 +0800 Subject: [PATCH 9/9] add t08 and the answer for it --- tutorial08/CMakeLists.txt | 10 + tutorial08/leptjson.c | 770 ++++++++++++++++++++++++++++++++++++++ tutorial08/leptjson.h | 97 +++++ tutorial08/test.c | 723 +++++++++++++++++++++++++++++++++++ tutorial08/tutorial08.md | 391 +++++++++++++++++++ 5 files changed, 1991 insertions(+) create mode 100644 tutorial08/CMakeLists.txt create mode 100644 tutorial08/leptjson.c create mode 100644 tutorial08/leptjson.h create mode 100644 tutorial08/test.c create mode 100644 tutorial08/tutorial08.md diff --git a/tutorial08/CMakeLists.txt b/tutorial08/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial08/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial08/leptjson.c b/tutorial08/leptjson.c new file mode 100644 index 00000000..48536818 --- /dev/null +++ b/tutorial08/leptjson.c @@ -0,0 +1,770 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* sprintf() */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE +#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) +#define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + size_t head = c->top; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + *len = c->top - head; + *str = lept_context_pop(c, *len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + int ret; + char* s; + size_t len; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) + lept_set_string(v, s, len); + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + lept_set_array(v, 0); + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + lept_set_array(v, size); + memcpy(v->u.a.e, lept_context_pop(c, size * sizeof(lept_value)), size * sizeof(lept_value)); + v->u.a.size = size; + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on the stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + lept_set_object(v, 0); + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* parse key */ + if (*c->json != '"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) + break; + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; + /* parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + c->json++; + lept_set_object(v, size); + memcpy(v->u.o.m, lept_context_pop(c, sizeof(lept_member) * size), sizeof(lept_member) * size); + v->u.o.size = size; + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } + } + /* Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '{': return lept_parse_object(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + size_t i, size; + char* head, *p; + assert(s != NULL); + p = head = lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */ + *p++ = '"'; + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': *p++ = '\\'; *p++ = '\"'; break; + case '\\': *p++ = '\\'; *p++ = '\\'; break; + case '\b': *p++ = '\\'; *p++ = 'b'; break; + case '\f': *p++ = '\\'; *p++ = 'f'; break; + case '\n': *p++ = '\\'; *p++ = 'n'; break; + case '\r': *p++ = '\\'; *p++ = 'r'; break; + case '\t': *p++ = '\\'; *p++ = 't'; break; + default: + if (ch < 0x20) { + *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0'; + *p++ = hex_digits[ch >> 4]; + *p++ = hex_digits[ch & 15]; + } + else + *p++ = s[i]; + } + } + *p++ = '"'; + c->top -= size - (p - head); +} + +static void lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + switch (v->type) { + case LEPT_NULL: PUTS(c, "null", 4); break; + case LEPT_FALSE: PUTS(c, "false", 5); break; + case LEPT_TRUE: PUTS(c, "true", 4); break; + case LEPT_NUMBER: c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; + case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; + case LEPT_ARRAY: + PUTC(c, '['); + for (i = 0; i < v->u.a.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_value(c, &v->u.a.e[i]); + } + PUTC(c, ']'); + break; + case LEPT_OBJECT: + PUTC(c, '{'); + for (i = 0; i < v->u.o.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen); + PUTC(c, ':'); + lept_stringify_value(c, &v->u.o.m[i].v); + } + PUTC(c, '}'); + break; + default: assert(0 && "invalid type"); + } +} + +char* lept_stringify(const lept_value* v, size_t* length) { + lept_context c; + assert(v != NULL); + c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); + c.top = 0; + lept_stringify_value(&c, v); + if (length) + *length = c.top; + PUTC(&c, '\0'); + return c.stack; +} + +void lept_copy(lept_value* dst, const lept_value* src) { + assert(src != NULL && dst != NULL && src != dst); + size_t i; + switch (src->type) { + case LEPT_STRING: + lept_set_string(dst, src->u.s.s, src->u.s.len); + break; + case LEPT_ARRAY: + /* \todo */ + lept_set_array(dst, src->u.a.capacity); + for (i = 0; i < src->u.a.size; i++) { + lept_copy(&dst->u.a.e[i], &src->u.a.e[i]); + } + dst->u.a.size = src->u.a.size; + break; + case LEPT_OBJECT: + /* \todo */ + lept_set_object(dst, src->u.o.capacity); + for (i = 0; i < src->u.o.size; i++) { + dst->u.o.m[i].k = (char*)malloc(src->u.o.m[i].klen + 1); + dst->u.o.m[i].klen = src->u.o.m[i].klen; + memcpy(dst->u.o.m[i].k, src->u.o.m[i].k, src->u.o.m[i].klen); + lept_copy(&dst->u.o.m[i].v, &src->u.o.m[i].v); + } + dst->u.o.size = src->u.o.size; + break; + default: + lept_free(dst); + memcpy(dst, src, sizeof(lept_value)); + break; + } +} + +void lept_move(lept_value* dst, lept_value* src) { + assert(dst != NULL && src != NULL && src != dst); + lept_free(dst); + memcpy(dst, src, sizeof(lept_value)); + lept_init(src); +} + +void lept_swap(lept_value* lhs, lept_value* rhs) { + assert(lhs != NULL && rhs != NULL); + if (lhs != rhs) { + lept_value temp; + memcpy(&temp, lhs, sizeof(lept_value)); + memcpy(lhs, rhs, sizeof(lept_value)); + memcpy(rhs, &temp, sizeof(lept_value)); + } +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_is_equal(const lept_value* lhs, const lept_value* rhs) { + size_t i, index; + assert(lhs != NULL && rhs != NULL); + if (lhs->type != rhs->type) + return 0; + switch (lhs->type) { + case LEPT_STRING: + return lhs->u.s.len == rhs->u.s.len && + memcmp(lhs->u.s.s, rhs->u.s.s, lhs->u.s.len) == 0; + case LEPT_NUMBER: + return lhs->u.n == rhs->u.n; + case LEPT_ARRAY: + if (lhs->u.a.size != rhs->u.a.size) + return 0; + for (i = 0; i < lhs->u.a.size; i++) + if (!lept_is_equal(&lhs->u.a.e[i], &rhs->u.a.e[i])) + return 0; + return 1; + case LEPT_OBJECT: + /* \todo */ + if (lhs->u.o.size != rhs->u.o.size) + return 0; + for (i = 0; i < lhs->u.o.size; i++) { + if ((index = lept_find_object_index(rhs, lhs->u.o.m[i].k, lhs->u.o.m[i].klen))== LEPT_KEY_NOT_EXIST) + return 0; + if(!lept_is_equal(&lhs->u.o.m[i].v, lept_get_object_value(rhs, index))) { + return 0; + } + + } + + return 1; + default: + return 1; + } +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +void lept_set_array(lept_value* v, size_t capacity) { + assert(v != NULL); + lept_free(v); + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.capacity = capacity; + v->u.a.e = capacity > 0 ? (lept_value*)malloc(capacity * sizeof(lept_value)) : NULL; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +size_t lept_get_array_capacity(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.capacity; +} + +void lept_reserve_array(lept_value* v, size_t capacity) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.capacity < capacity) { + v->u.a.capacity = capacity; + v->u.a.e = (lept_value*)realloc(v->u.a.e, capacity * sizeof(lept_value)); + } +} + +void lept_shrink_array(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.capacity > v->u.a.size) { + v->u.a.capacity = v->u.a.size; + v->u.a.e = (lept_value*)realloc(v->u.a.e, v->u.a.capacity * sizeof(lept_value)); + } +} + +void lept_clear_array(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + lept_erase_array_element(v, 0, v->u.a.size); +} + +lept_value* lept_get_array_element(lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} + +lept_value* lept_pushback_array_element(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.size == v->u.a.capacity) + lept_reserve_array(v, v->u.a.capacity == 0 ? 1 : v->u.a.capacity * 2); + lept_init(&v->u.a.e[v->u.a.size]); + return &v->u.a.e[v->u.a.size++]; +} + +void lept_popback_array_element(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY && v->u.a.size > 0); + lept_free(&v->u.a.e[--v->u.a.size]); +} + +lept_value* lept_insert_array_element(lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY && index <= v->u.a.size); + /* \todo */ + size_t i; + if (v->u.a.size == v->u.a.capacity) + lept_reserve_array(v, v->u.a.capacity == 0 ? 1 : v->u.a.capacity * 2); + for (i = v->u.a.size; i > index; i--) { + v->u.a.e[i] = v->u.a.e[i - 1]; + + } + v->u.a.size += 1; + return &v->u.a.e[i]; +} + +void lept_erase_array_element(lept_value* v, size_t index, size_t count) { + assert(v != NULL && v->type == LEPT_ARRAY && index + count <= v->u.a.size); + /* \todo */ + size_t i; + v->u.a.size = v->u.a.size - count; + for (i = index; i < index + count; i++) { + lept_free(&v->u.a.e[i]); + } + for (i = index; i < v->u.a.size; i++) { + v->u.a.e[i] = v->u.a.e[i + count]; + } +} + +void lept_set_object(lept_value* v, size_t capacity) { + assert(v != NULL); + lept_free(v); + v->type = LEPT_OBJECT; + v->u.o.size = 0; + v->u.o.capacity = capacity; + v->u.o.m = capacity > 0 ? (lept_member*)malloc(capacity * sizeof(lept_member)) : NULL; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +size_t lept_get_object_capacity(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ + return v->u.o.capacity; +} + +void lept_reserve_object(lept_value* v, size_t capacity) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ + if (v->u.o.capacity < capacity) { + v->u.o.m = (lept_member*)realloc(v->u.o.m, capacity * sizeof(lept_member)); + v->u.o.capacity = capacity; + } +} + +void lept_shrink_object(lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ + if (v->u.o.capacity > v->u.o.size) { + v->u.o.m = (lept_member*)realloc(v->u.o.m, v->u.o.size * sizeof(lept_member)); + v->u.o.capacity = v->u.o.size; + } +} + +void lept_clear_object(lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ + while (v->u.o.size != 0) { + lept_remove_object_value(v, 0); + } +} + +const char* lept_get_object_key(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].k; +} + +size_t lept_get_object_key_length(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].klen; +} + +lept_value* lept_get_object_value(lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return &v->u.o.m[index].v; +} + +size_t lept_find_object_index(const lept_value* v, const char* key, size_t klen) { + size_t i; + assert(v != NULL && v->type == LEPT_OBJECT && key != NULL); + for (i = 0; i < v->u.o.size; i++) + if (v->u.o.m[i].klen == klen && memcmp(v->u.o.m[i].k, key, klen) == 0) + return i; + return LEPT_KEY_NOT_EXIST; +} + +lept_value* lept_find_object_value(lept_value* v, const char* key, size_t klen) { + size_t index = lept_find_object_index(v, key, klen); + return index != LEPT_KEY_NOT_EXIST ? &v->u.o.m[index].v : NULL; +} + +lept_value* lept_set_object_value(lept_value* v, const char* key, size_t klen) { + assert(v != NULL && v->type == LEPT_OBJECT && key != NULL); + /* \todo */ + lept_value* vp = lept_find_object_value(v, key, klen); + if (vp) + return vp; + else { + if (v->u.o.size == v->u.o.capacity) + lept_reserve_object(v, v->u.o.capacity == 0 ? 1 : v->u.o.capacity * 2); + v->u.o.m[v->u.o.size].k = (char*)malloc(klen + 1); + memcpy(v->u.o.m[v->u.o.size].k, key, klen); + v->u.o.m[v->u.o.size].k[klen] = '\0'; + v->u.o.m[v->u.o.size].klen = klen; + return &v->u.o.m[v->u.o.size++].v; + } +} + + + +void lept_remove_object_value(lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT && index < v->u.o.size); + /* \todo */ + size_t i; + free(v->u.o.m[index].k); + lept_free(&v->u.o.m[index].v); + + for (i = index; i < v->u.o.size; i++) { + v->u.o.m[i] = v->u.o.m[i + 1]; + } + v->u.o.size -= 1; + assert(v->u.o.size >= 0 && v->u.o.size <= v->u.o.capacity); +} diff --git a/tutorial08/leptjson.h b/tutorial08/leptjson.h new file mode 100644 index 00000000..5526ded8 --- /dev/null +++ b/tutorial08/leptjson.h @@ -0,0 +1,97 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +#define LEPT_KEY_NOT_EXIST ((size_t)-1) + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size, capacity; }o; /* object: members, member count, capacity */ + struct { lept_value* e; size_t size, capacity; }a; /* array: elements, element count, capacity */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); +char* lept_stringify(const lept_value* v, size_t* length); + +void lept_copy(lept_value* dst, const lept_value* src); +void lept_move(lept_value* dst, lept_value* src); +void lept_swap(lept_value* lhs, lept_value* rhs); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); +int lept_is_equal(const lept_value* lhs, const lept_value* rhs); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +void lept_set_array(lept_value* v, size_t capacity); +size_t lept_get_array_size(const lept_value* v); +size_t lept_get_array_capacity(const lept_value* v); +void lept_reserve_array(lept_value* v, size_t capacity); +void lept_shrink_array(lept_value* v); +void lept_clear_array(lept_value* v); +lept_value* lept_get_array_element(lept_value* v, size_t index); +lept_value* lept_pushback_array_element(lept_value* v); +void lept_popback_array_element(lept_value* v); +lept_value* lept_insert_array_element(lept_value* v, size_t index); +void lept_erase_array_element(lept_value* v, size_t index, size_t count); + +void lept_set_object(lept_value* v, size_t capacity); +size_t lept_get_object_size(const lept_value* v); +size_t lept_get_object_capacity(const lept_value* v); +void lept_reserve_object(lept_value* v, size_t capacity); +void lept_shrink_object(lept_value* v); +void lept_clear_object(lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(lept_value* v, size_t index); +size_t lept_find_object_index(const lept_value* v, const char* key, size_t klen); +lept_value* lept_find_object_value(lept_value* v, const char* key, size_t klen); +lept_value* lept_set_object_value(lept_value* v, const char* key, size_t klen); +void lept_remove_object_value(lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial08/test.c b/tutorial08/test.c new file mode 100644 index 00000000..f5e287e5 --- /dev/null +++ b/tutorial08/test.c @@ -0,0 +1,723 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength + 1) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +static void test_parse_object() { + lept_value v; + size_t i; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } ")); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, + " { " + "\"n\" : null , " + "\"f\" : false , " + "\"t\" : true , " + "\"i\" : 123 , " + "\"s\" : \"abc\", " + "\"a\" : [ 1, 2, 3 ]," + "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }" + " } " + )); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v)); + EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0))); + EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1)); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1))); + EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2)); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2))); + EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3))); + EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4)); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), lept_get_string_length(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5)); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5))); + EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5))); + for (i = 0; i < 3; i++) { + lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e)); + } + EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6)); + { + lept_value* o = lept_get_object_value(&v, 6); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o)); + for (i = 0; i < 3; i++) { + lept_value* ov = lept_get_object_value(o, i); + EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]); + EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov)); + } + } + lept_free(&v); +} + +#define TEST_PARSE_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_object(); + + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +} + +#define TEST_ROUNDTRIP(json)\ + do {\ + lept_value v;\ + char* json2;\ + size_t length;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + json2 = lept_stringify(&v, &length);\ + EXPECT_EQ_STRING(json, json2, length);\ + lept_free(&v);\ + free(json2);\ + } while(0) + +static void test_stringify_number() { + TEST_ROUNDTRIP("0"); + TEST_ROUNDTRIP("-0"); + TEST_ROUNDTRIP("1"); + TEST_ROUNDTRIP("-1"); + TEST_ROUNDTRIP("1.5"); + TEST_ROUNDTRIP("-1.5"); + TEST_ROUNDTRIP("3.25"); + TEST_ROUNDTRIP("1e+20"); + TEST_ROUNDTRIP("1.234e+20"); + TEST_ROUNDTRIP("1.234e-20"); + + TEST_ROUNDTRIP("1.0000000000000002"); /* the smallest number > 1 */ + TEST_ROUNDTRIP("4.9406564584124654e-324"); /* minimum denormal */ + TEST_ROUNDTRIP("-4.9406564584124654e-324"); + TEST_ROUNDTRIP("2.2250738585072009e-308"); /* Max subnormal double */ + TEST_ROUNDTRIP("-2.2250738585072009e-308"); + TEST_ROUNDTRIP("2.2250738585072014e-308"); /* Min normal positive double */ + TEST_ROUNDTRIP("-2.2250738585072014e-308"); + TEST_ROUNDTRIP("1.7976931348623157e+308"); /* Max double */ + TEST_ROUNDTRIP("-1.7976931348623157e+308"); +} + +static void test_stringify_string() { + TEST_ROUNDTRIP("\"\""); + TEST_ROUNDTRIP("\"Hello\""); + TEST_ROUNDTRIP("\"Hello\\nWorld\""); + TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\""); + TEST_ROUNDTRIP("\"Hello\\u0000World\""); +} + +static void test_stringify_array() { + TEST_ROUNDTRIP("[]"); + TEST_ROUNDTRIP("[null,false,true,123,\"abc\",[1,2,3]]"); +} + +static void test_stringify_object() { + TEST_ROUNDTRIP("{}"); + TEST_ROUNDTRIP("{\"n\":null,\"f\":false,\"t\":true,\"i\":123,\"s\":\"abc\",\"a\":[1,2,3],\"o\":{\"1\":1,\"2\":2,\"3\":3}}"); +} + +static void test_stringify() { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("true"); + test_stringify_number(); + test_stringify_string(); + test_stringify_array(); + test_stringify_object(); +} + +#define TEST_EQUAL(json1, json2, equality) \ + do {\ + lept_value v1, v2;\ + lept_init(&v1);\ + lept_init(&v2);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v1, json1));\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v2, json2));\ + EXPECT_EQ_INT(equality, lept_is_equal(&v1, &v2));\ + lept_free(&v1);\ + lept_free(&v2);\ + } while(0) + +static void test_equal() { + TEST_EQUAL("true", "true", 1); + TEST_EQUAL("true", "false", 0); + TEST_EQUAL("false", "false", 1); + TEST_EQUAL("null", "null", 1); + TEST_EQUAL("null", "0", 0); + TEST_EQUAL("123", "123", 1); + TEST_EQUAL("123", "456", 0); + TEST_EQUAL("\"abc\"", "\"abc\"", 1); + TEST_EQUAL("\"abc\"", "\"abcd\"", 0); + TEST_EQUAL("[]", "[]", 1); + TEST_EQUAL("[]", "null", 0); + TEST_EQUAL("[1,2,3]", "[1,2,3]", 1); + TEST_EQUAL("[1,2,3]", "[1,2,3,4]", 0); + TEST_EQUAL("[[]]", "[[]]", 1); + TEST_EQUAL("{}", "{}", 1); + TEST_EQUAL("{}", "null", 0); + TEST_EQUAL("{}", "[]", 0); + TEST_EQUAL("{\"a\":1,\"b\":2}", "{\"a\":1,\"b\":2}", 1); + TEST_EQUAL("{\"a\":1,\"b\":2}", "{\"b\":2,\"a\":1}", 1); + TEST_EQUAL("{\"a\":1,\"b\":2}", "{\"a\":1,\"b\":3}", 0); + TEST_EQUAL("{\"a\":1,\"b\":2}", "{\"a\":1,\"b\":2,\"c\":3}", 0); + TEST_EQUAL("{\"a\":{\"b\":{\"c\":{}}}}", "{\"a\":{\"b\":{\"c\":{}}}}", 1); + TEST_EQUAL("{\"a\":{\"b\":{\"c\":{}}}}", "{\"a\":{\"b\":{\"c\":[]}}}", 0); +} + +static void test_copy() { + lept_value v1, v2; + lept_init(&v1); + lept_parse(&v1, "{\"t\":true,\"f\":false,\"n\":null,\"d\":1.5,\"a\":[1,2,3]}"); + lept_init(&v2); + lept_copy(&v2, &v1); + EXPECT_TRUE(lept_is_equal(&v2, &v1)); + lept_free(&v1); + lept_free(&v2); +} + +static void test_move() { + lept_value v1, v2, v3; + lept_init(&v1); + lept_parse(&v1, "{\"t\":true,\"f\":false,\"n\":null,\"d\":1.5,\"a\":[1,2,3]}"); + lept_init(&v2); + lept_copy(&v2, &v1); + lept_init(&v3); + lept_move(&v3, &v2); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v2)); + EXPECT_TRUE(lept_is_equal(&v3, &v1)); + lept_free(&v1); + lept_free(&v2); + lept_free(&v3); +} + +static void test_swap() { + lept_value v1, v2; + lept_init(&v1); + lept_init(&v2); + lept_set_string(&v1, "Hello", 5); + lept_set_string(&v2, "World!", 6); + lept_swap(&v1, &v2); + EXPECT_EQ_STRING("World!", lept_get_string(&v1), lept_get_string_length(&v1)); + EXPECT_EQ_STRING("Hello", lept_get_string(&v2), lept_get_string_length(&v2)); + lept_free(&v1); + lept_free(&v2); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access_array() { + lept_value a, e; + size_t i, j; + + lept_init(&a); + + for (j = 0; j <= 5; j += 5) { + lept_set_array(&a, j); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&a)); + EXPECT_EQ_SIZE_T(j, lept_get_array_capacity(&a)); + for (i = 0; i < 10; i++) { + lept_init(&e); + lept_set_number(&e, i); + lept_move(lept_pushback_array_element(&a), &e); + lept_free(&e); + } + + EXPECT_EQ_SIZE_T(10, lept_get_array_size(&a)); + for (i = 0; i < 10; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + } + + lept_popback_array_element(&a); + EXPECT_EQ_SIZE_T(9, lept_get_array_size(&a)); + for (i = 0; i < 9; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + + lept_erase_array_element(&a, 4, 0); + EXPECT_EQ_SIZE_T(9, lept_get_array_size(&a)); + for (i = 0; i < 9; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + + lept_erase_array_element(&a, 8, 1); + EXPECT_EQ_SIZE_T(8, lept_get_array_size(&a)); + for (i = 0; i < 8; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + + lept_erase_array_element(&a, 0, 2); + EXPECT_EQ_SIZE_T(6, lept_get_array_size(&a)); + for (i = 0; i < 6; i++) + EXPECT_EQ_DOUBLE((double)i + 2, lept_get_number(lept_get_array_element(&a, i))); + +#if 1 + for (i = 0; i < 2; i++) { + lept_init(&e); + lept_set_number(&e, i); + lept_move(lept_insert_array_element(&a, i), &e); + lept_free(&e); + } +#endif + + EXPECT_EQ_SIZE_T(8, lept_get_array_size(&a)); + for (i = 0; i < 8; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + + EXPECT_TRUE(lept_get_array_capacity(&a) > 8); + lept_shrink_array(&a); + EXPECT_EQ_SIZE_T(8, lept_get_array_capacity(&a)); + EXPECT_EQ_SIZE_T(8, lept_get_array_size(&a)); + for (i = 0; i < 8; i++) + EXPECT_EQ_DOUBLE((double)i, lept_get_number(lept_get_array_element(&a, i))); + + lept_set_string(&e, "Hello", 5); + lept_move(lept_pushback_array_element(&a), &e); /* Test if element is freed */ + lept_free(&e); + + i = lept_get_array_capacity(&a); + lept_clear_array(&a); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_capacity(&a)); /* capacity remains unchanged */ + lept_shrink_array(&a); + EXPECT_EQ_SIZE_T(0, lept_get_array_capacity(&a)); + + lept_free(&a); +} + +static void test_access_object() { +#if 1 + lept_value o, v, *pv; + size_t i, j, index; + + lept_init(&o); + + for (j = 0; j <= 5; j += 5) { + lept_set_object(&o, j); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&o)); + EXPECT_EQ_SIZE_T(j, lept_get_object_capacity(&o)); + for (i = 0; i < 10; i++) { + char key[2] = "a"; + key[0] += i; + lept_init(&v); + lept_set_number(&v, i); + lept_move(lept_set_object_value(&o, key, 1), &v); + lept_free(&v); + } + EXPECT_EQ_SIZE_T(10, lept_get_object_size(&o)); + for (i = 0; i < 10; i++) { + char key[] = "a"; + key[0] += i; + index = lept_find_object_index(&o, key, 1); + EXPECT_TRUE(index != LEPT_KEY_NOT_EXIST); + pv = lept_get_object_value(&o, index); + EXPECT_EQ_DOUBLE((double)i, lept_get_number(pv)); + } + } + + index = lept_find_object_index(&o, "j", 1); + EXPECT_TRUE(index != LEPT_KEY_NOT_EXIST); + lept_remove_object_value(&o, index); + index = lept_find_object_index(&o, "j", 1); + EXPECT_TRUE(index == LEPT_KEY_NOT_EXIST); + EXPECT_EQ_SIZE_T(9, lept_get_object_size(&o)); + + index = lept_find_object_index(&o, "a", 1); + EXPECT_TRUE(index != LEPT_KEY_NOT_EXIST); + lept_remove_object_value(&o, index); + index = lept_find_object_index(&o, "a", 1); + EXPECT_TRUE(index == LEPT_KEY_NOT_EXIST); + EXPECT_EQ_SIZE_T(8, lept_get_object_size(&o)); + + EXPECT_TRUE(lept_get_object_capacity(&o) > 8); + lept_shrink_object(&o); + EXPECT_EQ_SIZE_T(8, lept_get_object_capacity(&o)); + EXPECT_EQ_SIZE_T(8, lept_get_object_size(&o)); + for (i = 0; i < 8; i++) { + char key[] = "a"; + key[0] += i + 1; + EXPECT_EQ_DOUBLE((double)i + 1, lept_get_number(lept_get_object_value(&o, lept_find_object_index(&o, key, 1)))); + } + + lept_set_string(&v, "Hello", 5); + lept_move(lept_set_object_value(&o, "World", 5), &v); /* Test if element is freed */ + lept_free(&v); + + pv = lept_find_object_value(&o, "World", 5); + EXPECT_TRUE(pv != NULL); + EXPECT_EQ_STRING("Hello", lept_get_string(pv), lept_get_string_length(pv)); + + i = lept_get_object_capacity(&o); + lept_clear_object(&o); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&o)); + EXPECT_EQ_SIZE_T(i, lept_get_object_capacity(&o)); /* capacity remains unchanged */ + lept_shrink_object(&o); + EXPECT_EQ_SIZE_T(0, lept_get_object_capacity(&o)); + + lept_free(&o); +#endif +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); + test_access_array(); + test_access_object(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_stringify(); + test_equal(); + test_copy(); + test_move(); + test_swap(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md new file mode 100644 index 00000000..60874345 --- /dev/null +++ b/tutorial08/tutorial08.md @@ -0,0 +1,391 @@ +# 从零开始的 JSON 库教程(八):访问与其他功能 + +* Milo Yip +* 2018/6/2 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第八个单元。代码位于 [json-tutorial/tutorial08](https://github.com/miloyip/json-tutorial/blob/master/tutorial08)。 + +## 1. 对象键值查询 + +我们在第六个单元实现了 JSON 对象的数据结构,它仅为一个 `lept_value` 的数组: + +~~~c +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; + /* ... */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; +~~~ + +为了做相应的解析测试,我们实现了最基本的查询功能: + +~~~c +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(lept_value* v, size_t index); +~~~ + +在实际使用时,我们许多时候需要查询一个键值是否存在,如存在,要获得其相应的值。我们可以提供一个函数,简单地用线性搜寻实现这个查询功能(时间复杂度 $\mathrm{O}(n)$): + +~~~c +#define LEPT_KEY_NOT_EXIST ((size_t)-1) + +size_t lept_find_object_index(const lept_value* v, const char* key, size_t klen) { + size_t i; + assert(v != NULL && v->type == LEPT_OBJECT && key != NULL); + for (i = 0; i < v->u.o.size; i++) + if (v->u.o.m[i].klen == klen && memcmp(v->u.o.m[i].k, key, klen) == 0) + return i; + return LEPT_KEY_NOT_EXIST; +}} +~~~ + +若对象内没有所需的键,此函数返回 `LEPT_KEY_NOT_EXIST`。使用时: + +~~~c +lept_value o; +size_t index; +lept_init(&o); +lept_parse(&o, "{\"name\":\"Milo\", \"gender\":\"M\"}"); +index = lept_find_object_index(&o, "name", 4); +if (index != LEPT_KEY_NOT_EXIST) { + lept_value* v = lept_get_object_value(&o, index); + printf("%s\n", lept_get_string(v)); +} +lept_free(&o); +~~~ + +由于一般也是希望获取键对应的值,而不需要索引,我们再加入一个辅助函数,返回类型改为 `lept_value*`: + +~~~c +lept_value* lept_find_object_value(lept_value* v, const char* key, size_t klen) { + size_t index = lept_find_object_index(v, key, klen); + return index != LEPT_KEY_NOT_EXIST ? &v->u.o.m[index].v : NULL; +} +~~~ + +上述例子便可简化为: + +~~~c +lept_value o, *v; +/* ... */ +if ((v = lept_find_object_value(&o, "name", 4)) != NULL) + printf("%s\n", lept_get_string(v)); +~~~ + +## 2. 相等比较 + +在实现数组和对象的修改之前,为了测试结果的正确性,我们先实现 `lept_value` 的[相等比较](https://zh.wikipedia.org/zh-cn/%E9%97%9C%E4%BF%82%E9%81%8B%E7%AE%97%E5%AD%90)(equality comparison)。首先,两个值的类型必须相同,对于 true、false、null 这三种类型,比较类型后便完成比较。而对于数字和字符串,需进一步检查是否相等: + +~~~c +int lept_is_equal(const lept_value* lhs, const lept_value* rhs) { + assert(lhs != NULL && rhs != NULL); + if (lhs->type != rhs->type) + return 0; + switch (lhs->type) { + case LEPT_STRING: + return lhs->u.s.len == rhs->u.s.len && + memcmp(lhs->u.s.s, rhs->u.s.s, lhs->u.s.len) == 0; + case LEPT_NUMBER: + return lhs->u.n == rhs->u.n; + /* ... */ + default: + return 1; + } +} +~~~ + +由于值可能复合类型(数组和对象),也就是一个树形结构。当我们要比较两个树是否相等,可通过递归实现。例如,对于数组,我们先比较元素数目是否相等,然后递归检查对应的元素是否相等: + +~~~c +int lept_is_equal(const lept_value* lhs, const lept_value* rhs) { + size_t i; + /* ... */ + switch (lhs->type) { + /* ... */ + case LEPT_ARRAY: + if (lhs->u.a.size != rhs->u.a.size) + return 0; + for (i = 0; i < lhs->u.a.size; i++) + if (!lept_is_equal(&lhs->u.a.e[i], &rhs->u.a.e[i])) + return 0; + return 1; + /* ... */ + } +} +~~~ + +而对象与数组的不同之处,在于概念上对象的键值对是无序的。例如,`{"a":1,"b":2}` 和 `{"b":2,"a":1}` 虽然键值的次序不同,但这两个 JSON 对象是相等的。我们可以简单地利用 `lept_find_object_index()` 去找出对应的值,然后递归作比较。这部分留给读者作为练习。 + +## 3. 复制、移动与交换 + +本单元的重点,在于修改数组和对象的内容。我们将会实现一些接口做修改的操作,例如,为对象设置一个键值,我们可能会这么设计: + +~~~c +void lept_set_object_value(lept_value* v, const char* key, size_t klen, const lept_value* value); + +void f() { + lept_value v, s; + lept_init(&v); + lept_parse(&v, "{}"); + lept_init(&s); + lept_set_string(&s, "Hello", 5); + lept_set_object_keyvalue(&v, "s", &s); /* {"s":"Hello"} */ + lept_free(&v) + lept_free(&s); /* 第二次释放!*/ +} +~~~ + +凡涉及赋值,都可能会引起资源拥有权(resource ownership)的问题。值 `s` 并不能以指针方式简单地写入对象 `v`,因为这样便会有两个地方都拥有 `s`,会做成重复释放的 bug。我们有两个选择: + +1. 在 `lept_set_object_value()` 中,把参数 `value` [深度复制](https://en.wikipedia.org/wiki/Object_copying#Deep_copy)(deep copy)一个值,即把整个树复制一份,写入其新增的键值对中。 +2. 在 `lept_set_object_value()` 中,把参数 `value` 拥有权转移至新增的键值对,再把 `value` 设置成 null 值。这就是所谓的移动语意(move semantics)。 + +深度复制是一个常用功能,使用者也可能会用到,例如把一个 JSON 复制一个版本出来修改,保持原来的不变。所以,我们实现一个公开的深度复制函数: + +~~~c +void lept_copy(lept_value* dst, const lept_value* src) { + size_t i; + assert(src != NULL && dst != NULL && src != dst); + switch (src->type) { + case LEPT_STRING: + lept_set_string(dst, src->u.s.s, src->u.s.len); + break; + case LEPT_ARRAY: + /* \todo */ + break; + case LEPT_OBJECT: + /* \todo */ + break; + default: + lept_free(dst); + memcpy(dst, src, sizeof(lept_value)); + break; + } +} +~~~ + +C++11 加入了右值引用的功能,可以从语言层面区分复制和移动语意。而在 C 语言中,我们也可以通过实现不同版本的接口(不同名字的函数),实现这两种语意。但为了令接口更简单和正交(orthgonal),我们修改了 `lept_set_object_value()` 的设计,让它返回新增键值对的值指针,所以我们可以用 `lept_copy()` 去复制赋值,也可以简单地改变新增的键值: + +~~~c +/* 返回新增键值对的指针 */ +lept_value* lept_set_object_value(lept_value* v, const char* key, size_t klen); + +void f() { + lept_value v; + lept_init(&v); + lept_parse(&v, "{}"); + lept_set_string(lept_set_object_value(&v, "s"), "Hello", 5); + /* {"s":"Hello"} */ + lept_copy( + lept_add_object_keyvalue(&v, "t"), + lept_get_object_keyvalue(&v, "s", 1)); + /* {"s":"Hello","t":"Hello"} */ + lept_free(&v); +} +~~~ + +我们还提供了 `lept_move()`,它的实现也非常简单: + +~~~c +void lept_move(lept_value* dst, lept_value* src) { + assert(dst != NULL && src != NULL && src != dst); + lept_free(dst); + memcpy(dst, src, sizeof(lept_value)); + lept_init(src); +} +~~~ + +类似地,我们也实现了一个交换值的接口: + +~~~c +void lept_swap(lept_value* lhs, lept_value* rhs) { + assert(lhs != NULL && rhs != NULL); + if (lhs != rhs) { + lept_value temp; + memcpy(&temp, lhs, sizeof(lept_value)); + memcpy(lhs, rhs, sizeof(lept_value)); + memcpy(rhs, &temp, sizeof(lept_value)); + } +} +~~~ + +当我们要修改对象或数组里的值时,我们可以利用这 3 个函数。例如: + +~~~c +const char* json = "{\"a\":[1,2],\"b\":3}"; +char *out; +lept_value v; +lept_init(&v); +lept_parse(&v, json); +lept_copy( + lept_find_object_value(&v, "b", 1), + lept_find_object_value(&v, "a", 1)); +printf("%s\n", out = lept_stringify(&v, NULL)); /* {"a":[1,2],"b":[1,2]} */ +free(out); + +lept_parse(&v, json); +lept_move( + lept_find_object_value(&v, "b", 1), + lept_find_object_value(&v, "a", 1)); +printf("%s\n", out = lept_stringify(&v, NULL)); /* {"a":null,"b":[1,2]} */ +free(out); + +lept_parse(&v, json); +lept_swap( + lept_find_object_value(&v, "b", 1), + lept_find_object_value(&v, "a", 1)); +printf("%s\n", out = lept_stringify(&v, NULL)); /* {"a":3,"b":[1,2]} */ +free(out); + +lept_free(&v); +~~~ + +在使用时,可尽量避免 `lept_copy()`,而改用 `lept_move()` 或 `lept_swap()`,因为后者不需要分配内存。当中 `lept_swap()` 更是无须做释放的工作,令它达到 $\mathrm{O}(1)$ 时间复杂度,其性能与值的内容无关。 + +## 4. 动态数组 + +在此单元之前的实现里,每个数组的元素数目在解析后是固定不变的,其数据结构是: + +~~~c +struct lept_value { + union { + /* ... */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count*/ + /* ... */ + }u; + lept_type type; +}; +~~~ + +用这种数据结构增删元素时,我们需要重新分配一个数组,把适当的旧数据拷贝过去。但这种做法是非常低效的。例如我们想要从一个空的数组加入 $n$ 个元素,便要做 $n(n - 1)/2$ 次元素复制,即 $\mathrm{O}(n^2)$ 的时间复杂度。 + +其中一个改进方法,是使用动态数组(dynamic array,或称可增长数组/growable array)的数据结构。C++ STL 标准库中最常用的 `std::vector` 也是使用这种数据结构的容器。 + +改动也很简单,只需要在数组中加入容量 `capacity` 字段,表示当前已分配的元素数目,而 `size` 则表示现时的有效元素数目: + +~~~c + /* ... */ + struct { lept_value* e; size_t size, capacity; }a; /* array: elements, element count, capacity */ + /* ... */ +~~~ + +我们终于提供设置数组的函数,而且它可提供初始的容量: + +~~~c +void lept_set_array(lept_value* v, size_t capacity) { + assert(v != NULL); + lept_free(v); + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.capacity = capacity; + v->u.a.e = capacity > 0 ? (lept_value*)malloc(capacity * sizeof(lept_value)) : NULL; +} +~~~ + +我们需要稍修改 `lept_parse_array()`,调用 `lept_set_array()` 去设置类型和分配空间。 + +另外,类似于 `lept_get_array_size()`,也加入获取当前容量的函数: + +~~~c +size_t lept_get_array_capacity(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.capacity; +} +~~~ + +如果当前的容量不足,我们需要扩大容量,标准库的 `realloc()` 可以分配新的内存并把旧的数据拷背过去: + +~~~c +void lept_reserve_array(lept_value* v, size_t capacity) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.capacity < capacity) { + v->u.a.capacity = capacity; + v->u.a.e = (lept_value*)realloc(v->u.a.e, capacity * sizeof(lept_value)); + } +} +~~~ + +当数组不需要再修改,可以使用以下的函数,把容量缩小至刚好能放置现有元素: + +~~~c +void lept_shrink_array(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.capacity > v->u.a.size) { + v->u.a.capacity = v->u.a.size; + v->u.a.e = (lept_value*)realloc(v->u.a.e, v->u.a.capacity * sizeof(lept_value)); + } +} +~~~ + +我们不逐一检视每个数组修改函数,仅介绍一下两个例子: + +~~~c +lept_value* lept_pushback_array_element(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + if (v->u.a.size == v->u.a.capacity) + lept_reserve_array(v, v->u.a.capacity == 0 ? 1 : v->u.a.capacity * 2); + lept_init(&v->u.a.e[v->u.a.size]); + return &v->u.a.e[v->u.a.size++]; +} + +void lept_popback_array_element(lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY && v->u.a.size > 0); + lept_free(&v->u.a.e[--v->u.a.size]); +} +~~~ + +`lept_pushback_array_element()` 在数组末端压入一个元素,返回新的元素指针。如果现有的容量不足,就需要调用 `lept_reserve_array()` 扩容。我们现在用了一个最简单的扩容公式:若容量为 0,则分配 1 个元素;其他情况倍增容量。 + +`lept_popback_array_element()` 则做相反的工作,记得删去的元素需要调用 `lept_free()`。 + +下面这 3 个函数留给读者练习: + +1. `lept_insert_array_element()` 在 `index` 位置插入一个元素; +2. `lept_erase_array_element()` 删去在 `index` 位置开始共 `count` 个元素(不改容量); +3. `lept_clear_array()` 清除所有元素(不改容量)。 + +~~~c +lept_value* lept_insert_array_element(lept_value* v, size_t index); +void lept_erase_array_element(lept_value* v, size_t index, size_t count); +void lept_clear_array(lept_value* v); +~~~ + +## 5. 动态对象 + +动态对象也是采用上述相同的结构,所以直接留给读者修改结构体,并实现以下函数: + +~~~c +void lept_set_object(lept_value* v, size_t capacity); +size_t lept_get_object_capacity(const lept_value* v); +void lept_reserve_object(lept_value* v, size_t capacity); +void lept_shrink_object(lept_value* v); +void lept_clear_object(lept_value* v); +lept_value* lept_set_object_value(lept_value* v, const char* key, size_t klen); +void lept_remove_object_value(lept_value* v, size_t index); +~~~ + +注意 `lept_set_object_value()` 会先搜寻是否存在现有的键,若存在则直接返回该值的指针,不存在时才新增。 + +## 6. 总结与练习 + +本单元主要加入了数组和对象的访问、修改方法。当中的赋值又引申了三种赋值的方式(复制、移动、交换)。这些问题是各种编程语言中都需要考虑的事情,为了减少深度复制的成本,有些程序库或运行时还会采用[写入时复制](https://zh.wikipedia.org/zh-cn/%E5%AF%AB%E5%85%A5%E6%99%82%E8%A4%87%E8%A3%BD)(copy-on-write, COW)。而浅复制(shallow copy)则需要 [引用计数](https://zh.wikipedia.org/wiki/%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0)(reference count)或 [垃圾回收](https://zh.wikipedia.org/zh-cn/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%B8))(garbage collection, GC)等技术。 + +另外,我们实现了以动态数组的数据结构,能较高效地对数组和对象进行增删操作。至此,我们已经完成本教程的所有核心功能。做完下面的练习后,我们还会作简单讲解,然后将迎来本教程的最后一个单元。 + +本单元练习内容: + +1. 完成 `lept_is_equal()` 里的对象比较部分。不需要考虑对象内有重复键的情况。 +2. 打开 `test_array_access()` 里的 `#if 0`,实现 `lept_insert_array_element()`、`lept_erase_array_element()` 和 `lept_clear_array()`。 +3. 打开 `test_object_access()` 里的 `#if 0`,参考动态数组,实现第 5 部分列出的所有函数。 +4. 完成 `lept_copy()` 里的数组和对象的复制部分。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。