From 0c1ec9a4f58ff1c4cdd1e817f19ff91c2db3a6ba Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sun, 10 Dec 2017 23:31:48 +0800 Subject: [PATCH 01/11] Change value parse rule --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e3412016..8d0c4338 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ */build/ +*.vs From c6cadb24e61b9408359cf03fe9fb15cfef22ee58 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Tue, 19 Dec 2017 11:47:21 +0800 Subject: [PATCH 02/11] Do tutorial01 exercises --- tutorial01/leptjson.c | 78 ++++++++++++++++++++++++++++++------------- tutorial01/test.c | 16 +++++++++ 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index 5299fe1d..289845fa 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -5,43 +5,73 @@ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) typedef struct { - const char* json; + const char* json; }lept_context; 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; + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; } static int lept_parse_null(lept_context* c, lept_value* v) { - EXPECT(c, 'n'); - if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_NULL; - return LEPT_PARSE_OK; + EXPECT(c, 'n'); + if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') + return LEPT_PARSE_INVALID_VALUE; + c->json += 3; + v->type = LEPT_NULL; + 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 '\0': return LEPT_PARSE_EXPECT_VALUE; - default: return LEPT_PARSE_INVALID_VALUE; - } + 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; + } } int lept_parse(lept_value* v, const char* json) { - lept_context c; - assert(v != NULL); - c.json = json; - v->type = LEPT_NULL; - lept_parse_whitespace(&c); - return lept_parse_value(&c, v); + lept_context c; + assert(v != NULL); + c.json = json; + v->type = LEPT_NULL; + lept_parse_whitespace(&c); + int lept_parse_result = lept_parse_value(&c, v); + if (lept_parse_result == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + else + return LEPT_PARSE_OK; + } + else + return lept_parse_result; } lept_type lept_get_type(const lept_value* v) { - assert(v != NULL); - return v->type; + assert(v != NULL); + return v->type; } diff --git a/tutorial01/test.c b/tutorial01/test.c index e7672181..aa2e151a 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_NULL; + 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_NULL; + 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 1635f7982936a62dae37df98b2d5436c72a59f99 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Wed, 20 Dec 2017 00:46:16 +0800 Subject: [PATCH 03/11] Add *.vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8d0c4338..35b2a9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ */build/ *.vs +*.vscode From 5a018390cd39cee89020f91ab52b16321ec86170 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Wed, 20 Dec 2017 00:47:09 +0800 Subject: [PATCH 04/11] Do tutorial01 exercise --- tutorial01/leptjson.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index 289845fa..0ff2d5da 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -63,12 +63,9 @@ int lept_parse(lept_value* v, const char* json) { if (lept_parse_result == LEPT_PARSE_OK) { lept_parse_whitespace(&c); if (*c.json != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - else - return LEPT_PARSE_OK; + lept_parse_result = LEPT_PARSE_ROOT_NOT_SINGULAR; } - else - return lept_parse_result; + return lept_parse_result; } lept_type lept_get_type(const lept_value* v) { From fdeca55c305244d6ba041578c0e5895f9870fc18 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Thu, 21 Dec 2017 23:00:47 +0800 Subject: [PATCH 05/11] Do tutorial02 exercise --- tutorial02/leptjson.c | 101 +++++++++++++++++++++++++++++------------- tutorial02/test.c | 9 ++-- 2 files changed, 75 insertions(+), 35 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 7693e43b..dea5332a 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -1,7 +1,9 @@ #include "leptjson.h" #include /* assert() */ #include /* NULL, strtod() */ - +#include /* HUGE_VAL*/ +#include +#include #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) typedef struct { @@ -15,49 +17,84 @@ static void lept_parse_whitespace(lept_context* c) { c->json = p; } -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; +static int lept_parse_literal(lept_context* c,lept_value* v, lept_type m){ + int i,len; + const char* str; + assert(m == LEPT_TRUE || m == LEPT_FALSE || m == LEPT_NULL); + switch(m){ + case LEPT_TRUE: len=3; str="true"; break; + case LEPT_FALSE: len=4; str="false"; break; + case LEPT_NULL: len=3; str="null"; break; + } + EXPECT(c,str[0]); + for(i=0;ijson[i] != str[i+1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += len; + v->type = m; 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_null(lept_context* c, lept_value* v) { - EXPECT(c, 'n'); - if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_NULL; - return LEPT_PARSE_OK; -} +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') static int lept_parse_number(lept_context* c, lept_value* v) { - char* end; - /* \TODO validate number */ + char* end; + const char* cur = c->json; + if (cur[0] == 'N' && cur[1] == 'A' && cur[2] == 'N' + || cur[0] == 'n' && cur[1] == 'a' && cur[2] == 'n' + || cur[0] == 'I' && cur[1] == 'N' && cur[2] == 'F' + || cur[0] == 'i' && cur[1] == 'n' && cur[2] == 'f') + return LEPT_PARSE_INVALID_VALUE; + if (*cur == '+') + return LEPT_PARSE_INVALID_VALUE; + if (*cur == '-') + cur++; + + if (*cur == '.') + return LEPT_PARSE_INVALID_VALUE; + if (*cur == '0') { + cur++; + if (ISDIGIT(*cur) || *cur == 'x' || *cur == 'X' ) + return LEPT_PARSE_ROOT_NOT_SINGULAR; + } + else if (ISDIGIT1TO9(*cur)) { + for (cur++; ISDIGIT(*cur); cur++); + } + + if (*cur == '.') { + cur++; + if (!ISDIGIT(*cur)) + return LEPT_PARSE_INVALID_VALUE; + else + while (ISDIGIT(*++cur)); + } + + if (*cur == 'e' || *cur == 'E') { + cur++; + if (*cur == '+' || *cur == '-') + cur++; + if (!ISDIGIT(*cur)) + return LEPT_PARSE_INVALID_VALUE; + else + for (cur++; ISDIGIT(*cur); cur++); + } + + errno = 0; v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; - c->json = end; + if (c->json == end) return LEPT_PARSE_INVALID_VALUE; + if ((v->n == HUGE_VAL ||v->n == -HUGE_VAL) && errno == ERANGE) + return LEPT_PARSE_NUMBER_TOO_BIG; + c->json = cur; 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,LEPT_TRUE); + case 'f': return lept_parse_literal(c, v,LEPT_FALSE); + case 'n': return lept_parse_literal(c, v,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..ded5fed1 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -70,6 +70,9 @@ 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(1.7976931348623158e+308, "1.7976931348623158e+308"); + TEST_NUMBER(2.2250738585072014e-308, "2.2250738585072014e-308"); + TEST_NUMBER(4.9406564584124654e-324, "4.9406564584124654e-324"); } #define TEST_ERROR(error, json)\ @@ -89,7 +92,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 +108,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 +117,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 e3dcab93a34e5932220d5d017d96b0c7e100d657 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sat, 23 Dec 2017 00:20:00 +0800 Subject: [PATCH 06/11] Do tutorial03 exercise --- tutorial03/leptjson.c | 31 ++++++++++++++++++++++++++----- tutorial03/test.c | 23 +++++++++++++++++------ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 07f7e2c7..e5b24db1 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -4,7 +4,7 @@ #include /* HUGE_VAL */ #include /* NULL, malloc(), realloc(), free(), strtod() */ #include /* memcpy() */ - +#include #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 #endif @@ -94,6 +94,21 @@ static int lept_parse_string(lept_context* c, lept_value* v) { for (;;) { char ch = *p++; switch (ch) { + case '\\': + ch = *p++; + switch (ch) { + 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; + default: + return LEPT_PARSE_INVALID_STRING_ESCAPE; + } + break; case '\"': len = c->top - head; lept_set_string(v, (const char*)lept_context_pop(c, len), len); @@ -103,6 +118,8 @@ static int lept_parse_string(lept_context* c, lept_value* v) { c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; default: + if (ch>=0 &&ch <=31) + return LEPT_PARSE_INVALID_STRING_CHAR; PUTC(c, ch); } } @@ -153,12 +170,14 @@ 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)); + return v->type == LEPT_TRUE ? 1 : 0; } void lept_set_boolean(lept_value* v, int b) { - /* \TODO */ + assert(v != NULL); + v->type = (b ? LEPT_TRUE : LEPT_FALSE); } double lept_get_number(const lept_value* v) { @@ -167,7 +186,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..6c3b4dde 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 @@ -188,12 +188,23 @@ 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, 5.0); + EXPECT_EQ_DOUBLE(5, lept_get_number(&v)); + lept_set_number(&v, -543.0); + EXPECT_EQ_DOUBLE(-543, lept_get_number(&v)); + lept_free(&v); } static void test_access_string() { From 146da708b687027362a7121ddebbf34f5334a955 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sat, 23 Dec 2017 00:33:36 +0800 Subject: [PATCH 07/11] Do tutorial03 exercise --- tutorial03/leptjson.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index e5b24db1..bbc66b9a 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -91,8 +91,9 @@ static int lept_parse_string(lept_context* c, lept_value* v) { const char* p; EXPECT(c, '\"'); p = c->json; + char ch; for (;;) { - char ch = *p++; + ch = *p++; switch (ch) { case '\\': ch = *p++; From 7db46205bc20abf70851b67f5d25edf2ae25904e Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sat, 23 Dec 2017 13:59:58 +0800 Subject: [PATCH 08/11] Do tutorial03 exercise --- tutorial03/leptjson.c | 18 ++++++++++++------ tutorial03/test.c | 11 +++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index bbc66b9a..b69c5f5d 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -1,3 +1,8 @@ +/*#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif*/ + #include "leptjson.h" #include /* assert() */ #include /* errno, ERANGE */ @@ -96,9 +101,8 @@ static int lept_parse_string(lept_context* c, lept_value* v) { ch = *p++; switch (ch) { case '\\': - ch = *p++; - switch (ch) { - case '"':PUTC(c, '\"'); break; + switch (*p++) { + case '\"':PUTC(c, '\"'); break; case'\\':PUTC(c, '\\'); break; case'/':PUTC(c, '/'); break; case'b':PUTC(c, '\b'); break; @@ -107,6 +111,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { case'r':PUTC(c, '\r'); break; case't':PUTC(c, '\t'); break; default: + c->top = head; return LEPT_PARSE_INVALID_STRING_ESCAPE; } break; @@ -119,7 +124,8 @@ static int lept_parse_string(lept_context* c, lept_value* v) { c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; default: - if (ch>=0 &&ch <=31) + if (ch >= 0 && ch <= 31) + c->top = head; return LEPT_PARSE_INVALID_STRING_CHAR; PUTC(c, ch); } @@ -177,7 +183,7 @@ int lept_get_boolean(const lept_value* v) { } void lept_set_boolean(lept_value* v, int b) { - assert(v != NULL); + lept_free(v); v->type = (b ? LEPT_TRUE : LEPT_FALSE); } @@ -187,7 +193,7 @@ double lept_get_number(const lept_value* v) { } void lept_set_number(lept_value* v, double n) { - assert(v != NULL); + lept_free(v); v->u.n = n; v->type = LEPT_NUMBER; } diff --git a/tutorial03/test.c b/tutorial03/test.c index 6c3b4dde..31658c14 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -1,3 +1,8 @@ +/*#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif*/ + #include #include #include @@ -190,6 +195,7 @@ static void test_access_null() { 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); @@ -200,6 +206,7 @@ static void test_access_boolean() { static void test_access_number() { lept_value v; lept_init(&v); + lept_set_string(&v, "a", 1); lept_set_number(&v, 5.0); EXPECT_EQ_DOUBLE(5, lept_get_number(&v)); lept_set_number(&v, -543.0); @@ -238,6 +245,10 @@ static void test_parse() { } int main() { +/*#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + _CrtSetBreakAlloc(80); +#endif*/ test_parse(); printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); return main_ret; From 19bafe6f9ff76b433e1122570da7496de13c2841 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sat, 23 Dec 2017 16:43:10 +0800 Subject: [PATCH 09/11] Do tutorial04 exercise --- tutorial04/leptjson.c | 56 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index 0a123bf2..ec028b59 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -91,12 +91,53 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } static const char* lept_parse_hex4(const char* p, unsigned* u) { - /* \TODO */ + unsigned hm = 0, i; + char ch; + for (i = 16 * 16 * 16; i != 0; i /= 16) { + ch = *p++; + switch (ch) { + case'a':case'b':case'c': + case'd':case'e':case'f': + hm += (ch - 87)*i; + break; + case'A':case'B':case'C': + case'D':case'E':case'F': + hm += (ch - 55)*i; + break; + case'0':case'1':case'2':case'3':case'4': + case'5':case'6':case'7':case'8':case'9': + hm += (ch - 48)*i; + break; + default: + return NULL; + } + } + *u = hm; return p; } +#define OutputByte(ch) PUTC(c,ch) + static void lept_encode_utf8(lept_context* c, unsigned u) { - /* \TODO */ + assert(u >= 0x0000 && u <= 0x10FFFF); + if (u >= 0x0000 && u <= 0x007F) { + OutputByte(u); + } + if (u >= 0x0080 && u <= 0x07FF) { + OutputByte(0xC0 | ((u >> 6) & 0xFF)); + OutputByte(0x80 | ( u & 0x3F)); + } + if (u >= 0x0800 && u <= 0xFFFF) { + OutputByte(0xE0 | ((u >> 12) & 0xFF)); + OutputByte(0x80 | ((u >> 6) & 0x3F)); + OutputByte(0x80 | ( u & 0x3F)); + } + if (u >= 0x10000 && u <= 0x10FFFF) { + OutputByte(0xF0 | ((u >> 18) & 0xFF)); + OutputByte(0x80 | ((u >> 12) & 0x3F)); + OutputByte(0x80 | ((u >> 6) & 0x3F)); + OutputByte(0x80 | ( u & 0x3F)); + } } #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) @@ -128,7 +169,16 @@ static int lept_parse_string(lept_context* c, lept_value* v) { case 'u': if (!(p = lept_parse_hex4(p, &u))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); - /* \TODO surrogate handling */ + + if (u >= 0xD800 && u <= 0xDBFF) { + unsigned l; + if ( !(*p++ == '\\') + || !(*p++ == 'u') + || !(p = lept_parse_hex4(p, &l)) + || !(l >= 0xDC00 && l <= 0xDFFF)) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = 0x10000 + (u - 0xD800) * 0x400 + (l - 0xDC00); + } lept_encode_utf8(c, u); break; default: From ded489d8bdbcd716da44437a4a61ffbe155667f3 Mon Sep 17 00:00:00 2001 From: crystalwind Date: Sat, 23 Dec 2017 17:15:10 +0800 Subject: [PATCH 10/11] Do tutorial04 exercise --- tutorial04/leptjson.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index ec028b59..6d5f4197 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -116,27 +116,25 @@ static const char* lept_parse_hex4(const char* p, unsigned* u) { return p; } -#define OutputByte(ch) PUTC(c,ch) - static void lept_encode_utf8(lept_context* c, unsigned u) { assert(u >= 0x0000 && u <= 0x10FFFF); if (u >= 0x0000 && u <= 0x007F) { - OutputByte(u); + PUTC(c,u & 0xFF); } if (u >= 0x0080 && u <= 0x07FF) { - OutputByte(0xC0 | ((u >> 6) & 0xFF)); - OutputByte(0x80 | ( u & 0x3F)); + PUTC(c,0xC0 | ((u >> 6) & 0xFF)); + PUTC(c,0x80 | ( u & 0x3F)); } if (u >= 0x0800 && u <= 0xFFFF) { - OutputByte(0xE0 | ((u >> 12) & 0xFF)); - OutputByte(0x80 | ((u >> 6) & 0x3F)); - OutputByte(0x80 | ( u & 0x3F)); + PUTC(c,0xE0 | ((u >> 12) & 0xFF)); + PUTC(c,0x80 | ((u >> 6) & 0x3F)); + PUTC(c,0x80 | ( u & 0x3F)); } if (u >= 0x10000 && u <= 0x10FFFF) { - OutputByte(0xF0 | ((u >> 18) & 0xFF)); - OutputByte(0x80 | ((u >> 12) & 0x3F)); - OutputByte(0x80 | ((u >> 6) & 0x3F)); - OutputByte(0x80 | ( u & 0x3F)); + PUTC(c,0xF0 | ((u >> 18) & 0xFF)); + PUTC(c,0x80 | ((u >> 12) & 0x3F)); + PUTC(c,0x80 | ((u >> 6) & 0x3F)); + PUTC(c,0x80 | ( u & 0x3F)); } } From 8052de661744bfdf47590fb0f497d65e8cc912f6 Mon Sep 17 00:00:00 2001 From: Crystalwindz <18846188224@163.com> Date: Sat, 9 Jun 2018 15:53:35 +0800 Subject: [PATCH 11/11] update --- readme.md | 4 +- tutorial08/CMakeLists.txt | 10 + tutorial08/leptjson.c | 696 ++++++++++++++++++++++++++++++++++++ tutorial08/leptjson.h | 97 +++++ tutorial08/test.c | 723 ++++++++++++++++++++++++++++++++++++++ tutorial08/tutorial08.md | 391 +++++++++++++++++++++ 6 files changed, 1919 insertions(+), 2 deletions(-) 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/readme.md b/readme.md index 04d33557..10332e1c 100644 --- a/readme.md +++ b/readme.md @@ -45,9 +45,9 @@ 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial06_answer/tutorial06_answer.md)(2016/11/15 完成)。 7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](tutorial07_answer/tutorial07_answer.md)(2017/1/5 完成) -8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 +8. [访问与其他功能](tutorial08/tutorial08.md)(2018/6/2 完成):JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 ## 关于作者 -叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群前台技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。 +叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群游戏客户端技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。 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..59dae292 --- /dev/null +++ b/tutorial08/leptjson.c @@ -0,0 +1,696 @@ +#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); + 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; + } +} + +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; + 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 */ + 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 */ + return NULL; +} + +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 */ +} + +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 0; +} + +void lept_reserve_object(lept_value* v, size_t capacity) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ +} + +void lept_shrink_object(lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ +} + +void lept_clear_object(lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + /* \todo */ +} + +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 */ + return NULL; +} + +void lept_remove_object_value(lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT && index < v->u.o.size); + /* \todo */ +} 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..d82c01a3 --- /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 0 + 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 0 + 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) 中提出,让所有人一起讨论。