From 738b0b1bfac47486e00e496bf8e793313ba4562e Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 8 Oct 2016 20:24:18 +0800 Subject: [PATCH 01/66] Update readme.md Fix #43 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index be36617d..a3e0786b 100644 --- a/readme.md +++ b/readme.md @@ -50,4 +50,4 @@ ## 关于作者 -叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群前台技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmarck](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++ 于各种项目。 From deff395bf840ff7055d2a60eccc386e1a8ed3620 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 10 Oct 2016 21:10:09 +0800 Subject: [PATCH 02/66] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 01a83478..61d8da11 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -99,7 +99,7 @@ static void test_parse_array() { 但和字符串有点不一样,如果把 JSON 当作一棵树的数据结构,JSON 字符串是叶节点,而 JSON 数组是中间节点。在叶节点的解析函数中,我们怎样使用那个堆栈也可以,只要最后还原就好了。但对于数组这样的中间节点,共用这个堆栈没问题么? -答案是:只要在解析函数结束时还原堆栈的状庇,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 +答案是:只要在解析函数结束时还原堆栈的状态,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 首先,我们遇到 `[`,进入 `lept_parse_array()`: From 314eeec41778e37a28029f4945eec7fb90e9674f Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 13:49:58 +0800 Subject: [PATCH 03/66] Add tutorial05 answer --- tutorial05_answer/CMakeLists.txt | 10 + tutorial05_answer/leptjson.c | 334 ++++++++++++++++++++++++ tutorial05_answer/leptjson.h | 56 ++++ tutorial05_answer/test.c | 339 +++++++++++++++++++++++++ tutorial05_answer/tutorial05_answer.md | 199 +++++++++++++++ 5 files changed, 938 insertions(+) create mode 100644 tutorial05_answer/CMakeLists.txt create mode 100644 tutorial05_answer/leptjson.c create mode 100644 tutorial05_answer/leptjson.h create mode 100644 tutorial05_answer/test.c create mode 100644 tutorial05_answer/tutorial05_answer.md diff --git a/tutorial05_answer/CMakeLists.txt b/tutorial05_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial05_answer/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/tutorial05_answer/leptjson.c b/tutorial05_answer/leptjson.c new file mode 100644 index 00000000..3b44d266 --- /dev/null +++ b/tutorial05_answer/leptjson.c @@ -0,0 +1,334 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_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) + +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(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), 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_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++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + 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++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + 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 '\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; +} + +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; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +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; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const 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]; +} diff --git a/tutorial05_answer/leptjson.h b/tutorial05_answer/leptjson.h new file mode 100644 index 00000000..e18f14f7 --- /dev/null +++ b/tutorial05_answer/leptjson.h @@ -0,0 +1,56 @@ +#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; + +typedef struct lept_value lept_value; + +struct lept_value { + union { + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +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 +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#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); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial05_answer/test.c b/tutorial05_answer/test.c new file mode 100644 index 00000000..1a17a2a7 --- /dev/null +++ b/tutorial05_answer/test.c @@ -0,0 +1,339 @@ +#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) == 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); +} + +#define TEST_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_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + 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"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + 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(); +} + +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() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + 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/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md new file mode 100644 index 00000000..72631552 --- /dev/null +++ b/tutorial05_answer/tutorial05_answer.md @@ -0,0 +1,199 @@ +# 从零开始的 JSON 库教程(五):解析数组解答篇 + +* Milo Yip +* 2016/10/13 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元解答篇。解答代码位于 [json-tutorial/tutorial05_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial05_answer)。 + +## 1. 编写 `test_parse_array()` 单元测试 + +这个练习纯粹为了熟习数组的访问 API。新增的第一个 JSON 只需平凡的检测。第二个 JSON 有特定模式,第 i 个子数组的长度为 i,每个子数组的第 j 个元素是数字值 j,所以可用两层 for 循环测试。 + +~~~c +static void test_parse_array() { + size_t i, j; + lept_value 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); +} +~~~ + +## 2. 解析空白字符 + +按现时的 `lept_parse_array()` 的编写方式,需要加入 3 个 `lept_parse_whitespace()` 调用,分别是解析 `[` 之后,元素之后,以及 `,` 之后: + +~~~c +static int lept_parse_array(lept_context* c, lept_value* v) { + /* ... */ + EXPECT(c, '['); + lept_parse_whitespace(c); + /* ... */ + for (;;) { + /* ... */ + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + return ret; + /* ... */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + /* ... */ + } +} +~~~ + +## 3. 内存泄漏 + +成功测试那 3 个 JSON 后,使用内存泄漏检测工具会发现 `lept_parse_array()` 用 `malloc()`分配的内存没有被释放: + +~~~ +==154== 124 (120 direct, 4 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 4 +==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) +==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x405261: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) +==154== +==154== 240 (96 direct, 144 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4 +==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) +==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40582C: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) +~~~ + +很明显,有 `malloc()` 就要有对应的 `free()`。正确的释放位置应该放置在 `lept_free()`,当值被释放时,该值拥有的内存也在那里释放。之前字符串的释放也是放在这里: + +~~~c +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} +~~~ + +但对于数组,我们应该先把数组内的元素通过递归调用 `lept_free()` 释放,然后才释放本身的 `v->u.a.e`: + +~~~c +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; + default: break; + } + v->type = LEPT_NULL; +} +~~~ + +修改之后,再运行内存泄漏检测工具,确保问题已被修正。 + +## 4. 解析错误时的内存处理 + +遇到解析错误时,我们可能在之前已压入了一些值在自定议堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 + +在 `lept_parse_array` 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 `break` 离开循环,在循环结束后的地方用 `lept_free()` 释放从堆栈弹出的值,然后才返回错误码: + +~~~c +static int lept_parse_array(lept_context* c, lept_value* v) { + /* ... */ + for (;;) { + /* ... */ + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + /* ... */ + if (*c->json == ',') { + /* ... */ + } + else if (*c->json == ']') { + /* ... */ + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} +~~~ + +## 5. bug 的解释 + +这个 bug 源于压栈时,会获得一个指针 `e`,指向从堆栈分配到的空间: + +~~~c + for (;;) { + /* bug! */ + lept_value* e = lept_context_push(c, sizeof(lept_value)); + lept_init(e); + size++; + if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) + return ret; + /* ... */ + } +~~~ + +然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 堆栈。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 + +在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。 + +但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: + +~~~ +static void lept_context_push(lept_context* c, void* data, size_t size); +~~~ + +这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。 + +无论如何,我们编程时都要考虑清楚变量的生命周期,特别是指针的生命周期。 + +## 6. 总结 + +经过对数组的解析,我们也了解到如何利用递归处理复合型的数据类型解析。与一些用链表或自动扩展的动态数组的实现比较,我们利用了自定义堆栈作为缓冲区,能分配最紧凑的数组作存储之用,会比其他实现更省内存。我们完成了数组类型后,只余下对象类型了。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 10c25cf111a731c210dd59cea84df9d62b93abfa Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 13:56:48 +0800 Subject: [PATCH 04/66] Typo --- tutorial05_answer/leptjson.c | 2 +- tutorial05_answer/tutorial05_answer.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial05_answer/leptjson.c b/tutorial05_answer/leptjson.c index 3b44d266..f05f6ff0 100644 --- a/tutorial05_answer/leptjson.c +++ b/tutorial05_answer/leptjson.c @@ -220,7 +220,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { break; } } - /* Pop and free values on stack */ + /* 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; diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index 72631552..73f712f9 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -155,7 +155,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { break; } } - /* Pop and free values on stack */ + /* 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; From 1f18486301e6f41a6cd95d7416a492757918d466 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 14:01:14 +0800 Subject: [PATCH 05/66] Minor fixes --- tutorial05_answer/tutorial05_answer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index 73f712f9..b284815e 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -178,14 +178,14 @@ static int lept_parse_array(lept_context* c, lept_value* v) { } ~~~ -然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 堆栈。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 +然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 扩容。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。 但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: ~~~ -static void lept_context_push(lept_context* c, void* data, size_t size); +static void lept_context_push(lept_context* c, const void* data, size_t size); ~~~ 这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。 From 54cb6fdb92be97af1baa85d0723c54c0f3df09af Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 14:02:42 +0800 Subject: [PATCH 06/66] Update readme link --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index be36617d..5f8d8f28 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 -5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 +5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 From ff1710649172be1e932feed89ad161d5290d7faa Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 14 Oct 2016 12:45:47 +0800 Subject: [PATCH 07/66] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 61d8da11..84cc000f 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -89,7 +89,7 @@ static void test_parse_array() { } ~~~ -在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C99)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 +在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C89)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 ## 3. 解析过程 From 5dc0172b5f0cdf18fe900a49966bef36f0ab3b71 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:28:36 +0800 Subject: [PATCH 08/66] Add tutorial06 --- tutorial06/CMakeLists.txt | 10 + tutorial06/leptjson.c | 389 ++++++++++++++++++++++++++++++++++ tutorial06/leptjson.h | 71 +++++++ tutorial06/test.c | 430 ++++++++++++++++++++++++++++++++++++++ tutorial06/tutorial06.md | 204 ++++++++++++++++++ 5 files changed, 1104 insertions(+) create mode 100644 tutorial06/CMakeLists.txt create mode 100644 tutorial06/leptjson.c create mode 100644 tutorial06/leptjson.h create mode 100644 tutorial06/test.c create mode 100644 tutorial06/tutorial06.md diff --git a/tutorial06/CMakeLists.txt b/tutorial06/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial06/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/tutorial06/leptjson.c b/tutorial06/leptjson.c new file mode 100644 index 00000000..64e3bd0d --- /dev/null +++ b/tutorial06/leptjson.c @@ -0,0 +1,389 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_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) + +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(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), 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_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++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + 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++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, 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 size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + lept_init(&m.v); + /* \todo parse key to m.k, m.klen */ + /* \todo parse ws colon ws */ + /* 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 */ + /* \todo parse ws [comma | right-curly-brace] ws */ + } + /* \todo Pop and free members on the stack */ + 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; +} + +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; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +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; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const 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]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +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(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].v; +} diff --git a/tutorial06/leptjson.h b/tutorial06/leptjson.h new file mode 100644 index 00000000..8a6ce68d --- /dev/null +++ b/tutorial06/leptjson.h @@ -0,0 +1,71 @@ +#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; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + 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); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#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); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +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(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial06/test.c b/tutorial06/test.c new file mode 100644 index 00000000..8d332e45 --- /dev/null +++ b/tutorial06/test.c @@ -0,0 +1,430 @@ +#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) == 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_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_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + 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"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_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(); +#if 0 + test_parse_object(); +#endif + + 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(); +#if 0 + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +#endif +} + +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() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + 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/tutorial06/tutorial06.md b/tutorial06/tutorial06.md new file mode 100644 index 00000000..bc792432 --- /dev/null +++ b/tutorial06/tutorial06.md @@ -0,0 +1,204 @@ +# 从零开始的 JSON 库教程(六):解析对象 + +* Milo Yip +* 2016/10/29 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元。代码位于 [json-tutorial/tutorial06](https://github.com/miloyip/json-tutorial/blob/master/tutorial06)。 + +本单元内容: + +1. [JSON 对象](1-JSON-对象) +2. [数据结构](2-数据结构) +3. [重构字符串解析](3-重构字符串解析) +4. [实现](4-实现) +5. [总结与练习](5-总结与练习) + +## 1. JSON 对象 + +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` 包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` 分隔,完整语法如下: + +~~~ +member = string ws %x3A ws value +object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D +~~~ + +## 2. 数据结构 + +要表示键值对的集合,有很多数据结构可供选择,例如: + +* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 +* 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 +* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现$O(1)$查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 + +设一个对象有 $n$ 个成员,数据结构的容量是 $m$,$n \le m$,那么一些常用操作的时间/空间复杂度如下: + +| |动态数组 |有序动态数组 |平衡树 |哈希表 | +|-----------------|:---------:|:-----------:|:--------------:|:---------:| +|自定成员次序 |可 |否 |否 |否 | +|初始化 $n$ 个成员|$O(n)$ |$O(n \log n)$|$O(n \log n)$ |$O(n)$ | +|加入成员 |分摊 $O(1)$|$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| +|移除成员 |$O(n)$ |$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| +|查询成员 |$O(n)$ |$O(\log n)$ |$O(\log n)$ |$O(1)$ | +|遍历成员 |$O(n)$ |$O(n)$ |$O(n)$ |$O(m)$ | +|检测对象相等 |$O(n^2)$ |$O(n)$ |$O(n)$ |$O(m)$ | +|空间 |$O(m)$ |$O(m)$ |$O(n)$ |$O(m)$ | + +在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 + +为了简单,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: + +~~~c +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; + struct { lept_value* e; size_t size; }a; + struct { char* s; size_t len; }s; + double n; + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; +~~~ + +成员结构 `lept_member` 是一个 `lept_value` 加上键的字符串。如同 JSON 字符串的值,我们也需要同时保留字符串的长度,因为字符串本身可能包含空字符 `\u0000`。 + +在这单元中,我们仅添加了最基本的访问函数,用于撰写单元测试: + +~~~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(const lept_value* v, size_t index); +~~~ + +在软件开发过程中,许多时候,选择合适的数据结构后已等于完成一半工作。没有完美的数据结构,所以最好考虑多一些应用的场合,看看时间/空间复杂度以至相关系数是否合适。 + +接下来,我们就可以着手实现。 + +## 3. 重构字符串解析 + +在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试,我们才提交改动。 + +我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 `lept_value` 存储键,因为这样会浪费了当中 `type` 这个无用的字段。由于 `lept_parse_string()` 是直接地把解析的结果写进一个 `lept_value`,所以我们先用「提取方法(extract method,见下注)」的重构方式,把解析 JSON 字符串及写入 `lept_value` 分拆成两部分: + +~~~c +/* 解析 JSON 字符串,把结果写入 str 和 len */ +/* str 指向 c->stack 中的元素,需要在 c->stack */ +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + /* \todo */ +} + +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; +} +~~~ + +这样的话,我们实现对象的解析时,就可以使用 `lept_parse_string_raw()` 来解析 JSON 字符串,然后把结果复制至 `lept_member` 的 `k` 和 `klen` 字段。 + +注:在 Fowler 的经典著作 [1] 中,把各种重构方式分门别类,每个方式都有详细的步骤说明。由于书中以 Java 为例子,所以方式的名称使用了 Java 的述语,例如方法(method)。在 C 语言中,「提取方法」其实应该称为「提取函数」。 + +[1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:熊节译,《重构——改善既有代码的设计》,人民邮电出版社,2010年。 + +## 4. 实现 + +解析对象与解析数组非常相似,所以我留空了几段作为练习。在解析数组时,我们把当前的元素以 `lept_value` 压入栈中,而在这里,我们则是以 `lept_member` 压入: + +~~~c +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + lept_init(&m.v); + /* \todo parse key to m.k, m.klen */ + /* \todo parse ws colon ws */ + /* 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 */ + /* \todo parse ws [comma | right-curly-brace] ws */ + } + /* \todo Pop and free members on the stack */ + return ret; +} +~~~ + +要注意的是,我们要为 `m.k` 分配内存去存储键的字符串,若在整个对象解析时发生错误,也要记得释放栈中的 `lept_member` 的 `k`。 + +我们为解析对象定义了几个新的错误码: + +~~~c +enum { + /* ... */ + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; +~~~ + +在此不再赘述它们的意义了,可从以下的单元测试看到例子: + +~~~c +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} +~~~ + +## 5. 总结与练习 + +在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于数据驱动开发,我们可以不断重塑软件的内部结构。 + +完成这次练习之后,恭喜你,你已经完整地实现了一个符合标准的 JSON 解析器了。之后我们会完成更简单的生成器及其他访问功能。 + +由于对象和数组的相似性,此单元留空了较多实现部分作为练习: + +1. 依第 3 节所述,重构 `lept_parse_string()`。重构前运行单元测试,重构后确保单元测试仍保持通过。 +2. 打开 `test.c` 中两个 `#if 0`,运成单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 +3. 使用工具检测内存泄漏,解决它们。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 293da831436aadf560ad63a8160c2b9f9268814a Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:30:00 +0800 Subject: [PATCH 09/66] Update tutorial06.md --- tutorial06/tutorial06.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index bc792432..241e8adf 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -7,11 +7,11 @@ 本单元内容: -1. [JSON 对象](1-JSON-对象) -2. [数据结构](2-数据结构) -3. [重构字符串解析](3-重构字符串解析) -4. [实现](4-实现) -5. [总结与练习](5-总结与练习) +1. [JSON 对象](#1-JSON-对象) +2. [数据结构](#2-数据结构) +3. [重构字符串解析](#3-重构字符串解析) +4. [实现](#4-实现) +5. [总结与练习](#5-总结与练习) ## 1. JSON 对象 From 72b695954be36b0f02d9fbcb206cb3063db6bc39 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:33:25 +0800 Subject: [PATCH 10/66] Remove LaTeX formula --- tutorial06/tutorial06.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 241e8adf..f75517a3 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,20 +29,20 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现$O(1)$查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 - -设一个对象有 $n$ 个成员,数据结构的容量是 $m$,$n \le m$,那么一些常用操作的时间/空间复杂度如下: - -| |动态数组 |有序动态数组 |平衡树 |哈希表 | -|-----------------|:---------:|:-----------:|:--------------:|:---------:| -|自定成员次序 |可 |否 |否 |否 | -|初始化 $n$ 个成员|$O(n)$ |$O(n \log n)$|$O(n \log n)$ |$O(n)$ | -|加入成员 |分摊 $O(1)$|$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| -|移除成员 |$O(n)$ |$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| -|查询成员 |$O(n)$ |$O(\log n)$ |$O(\log n)$ |$O(1)$ | -|遍历成员 |$O(n)$ |$O(n)$ |$O(n)$ |$O(m)$ | -|检测对象相等 |$O(n^2)$ |$O(n)$ |$O(n)$ |$O(m)$ | -|空间 |$O(m)$ |$O(m)$ |$O(n)$ |$O(m)$ | +* 哈希表(hash table):通过哈希函数能实现 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 + +设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: + +| |动态数组 |有序动态数组|平衡树 |哈希表 | +|---------------|:-------:|:----------:|:------------:|:-------:| +|自定成员次序 |可 |否 |否 |否 | +|初始化 n 个成员|O(n) |O(n log n) |O(n log n) |O(n) | +|加入成员 |分摊 O(1)|O(n) |分摊 O(log n) |分摊 O(1)| +|移除成员 |O(n) |O(n) |分摊 O(log n) |分摊 O(1)| +|查询成员 |O(n) |O(log n) |O(log n) |O(1) | +|遍历成员 |O(n) |O(n) |O(n) |O(m) | +|检测对象相等 |O(n^2) |O(n) |O(n) |O(m) | +|空间 |O(m) |O(m) |O(n) |O(m) | 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 From 4a529e46d9baf90dff09fb0745bf7892b3c79141 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:40:17 +0800 Subject: [PATCH 11/66] Minor edit --- tutorial06/tutorial06.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index f75517a3..c3d61a35 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -15,7 +15,7 @@ ## 1. JSON 对象 -此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` 包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` 分隔,完整语法如下: +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` (`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` (`U+003A`)分隔。完整语法如下: ~~~ member = string ws %x3A ws value @@ -46,7 +46,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 -为了简单,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: +为了简单起见,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: ~~~c typedef struct lept_value lept_value; From 2931bb37f9ed9859620fba774761b8bafb91f2a6 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:41:57 +0800 Subject: [PATCH 12/66] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index c3d61a35..a3b0a56f 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -15,7 +15,7 @@ ## 1. JSON 对象 -此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` (`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` (`U+003A`)分隔。完整语法如下: +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}`(`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:`(`U+003A`)分隔。完整语法如下: ~~~ member = string ws %x3A ws value From 8c148ca09bd1fbaf6338df8398eea8e761332ad0 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:50:38 +0800 Subject: [PATCH 13/66] Fix complexity --- tutorial06/tutorial06.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index a3b0a56f..bb235c65 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,20 +29,21 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: -| |动态数组 |有序动态数组|平衡树 |哈希表 | -|---------------|:-------:|:----------:|:------------:|:-------:| -|自定成员次序 |可 |否 |否 |否 | -|初始化 n 个成员|O(n) |O(n log n) |O(n log n) |O(n) | -|加入成员 |分摊 O(1)|O(n) |分摊 O(log n) |分摊 O(1)| -|移除成员 |O(n) |O(n) |分摊 O(log n) |分摊 O(1)| -|查询成员 |O(n) |O(log n) |O(log n) |O(1) | -|遍历成员 |O(n) |O(n) |O(n) |O(m) | -|检测对象相等 |O(n^2) |O(n) |O(n) |O(m) | -|空间 |O(m) |O(m) |O(n) |O(m) | +| |动态数组 |有序动态数组|平衡树 |哈希表 | +|---------------|:-------:|:----------:|:--------:|:--------------------:| +|有序 |否 |是 |是 |否 | +|自定成员次序 |可 |否 |否 |否 | +|初始化 n 个成员|O(n) |O(n log n) |O(n log n)|平均 O(n)、最坏 O(n^2)| +|加入成员 |分摊 O(1)|O(n) |O(log n) |平均 O(1)、最坏 O(n) | +|移除成员 |O(n) |O(n) |O(log n) |平均 O(1)、最坏 O(n) | +|查询成员 |O(n) |O(log n) |O(log n) |平均 O(1)、最坏 O(n) | +|遍历成员 |O(n) |O(n) |O(n) |O(m) | +|检测对象相等 |O(n^2) |O(n) |O(n) |平均 O(n)、最坏 O(n^2)| +|空间 |O(m) |O(m) |O(n) |O(m) | 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 From 73606d0174550d327d4fadf5be1dce302dae41eb Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:55:36 +0800 Subject: [PATCH 14/66] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index bb235c65..1fe38ac8 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,7 +29,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: From a9e10e62e370703428e31ec8c8efeeaddee0dc21 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 16:09:18 +0800 Subject: [PATCH 15/66] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5bfb0187..93d6a122 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 -6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 +6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 2564892b12b2a151477861b2dcfd5efe1af45be8 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 18:46:39 +0800 Subject: [PATCH 16/66] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 1fe38ac8..4214b956 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -47,7 +47,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 -为了简单起见,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: +为了简单起见,我们的 leptjson 选择用动态数组的方案。我们将在单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: ~~~c typedef struct lept_value lept_value; From 124df28d1a9be611d9e0c53efb81ec78512225d1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 19:04:09 +0800 Subject: [PATCH 17/66] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 4214b956..dc585d5c 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -199,7 +199,7 @@ static void test_parse_miss_comma_or_curly_bracket() { 由于对象和数组的相似性,此单元留空了较多实现部分作为练习: 1. 依第 3 节所述,重构 `lept_parse_string()`。重构前运行单元测试,重构后确保单元测试仍保持通过。 -2. 打开 `test.c` 中两个 `#if 0`,运成单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 +2. 打开 `test.c` 中两个 `#if 0`,运行单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 3. 使用工具检测内存泄漏,解决它们。 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From d72853e6c6f81ad003ff12ea4d9873104904f440 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 20:47:52 +0800 Subject: [PATCH 18/66] Update tutorial06.md --- tutorial06/tutorial06.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index dc585d5c..749270ae 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -86,7 +86,7 @@ lept_value* lept_get_object_value(const lept_value* v, size_t index); ## 3. 重构字符串解析 -在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试,我们才提交改动。 +在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试的改动,我们才提交它。 我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 `lept_value` 存储键,因为这样会浪费了当中 `type` 这个无用的字段。由于 `lept_parse_string()` 是直接地把解析的结果写进一个 `lept_value`,所以我们先用「提取方法(extract method,见下注)」的重构方式,把解析 JSON 字符串及写入 `lept_value` 分拆成两部分: @@ -192,7 +192,7 @@ static void test_parse_miss_comma_or_curly_bracket() { ## 5. 总结与练习 -在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于数据驱动开发,我们可以不断重塑软件的内部结构。 +在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于测试驱动开发(TDD),我们可以不断重塑软件的内部结构。 完成这次练习之后,恭喜你,你已经完整地实现了一个符合标准的 JSON 解析器了。之后我们会完成更简单的生成器及其他访问功能。 From a5a7abbcf328515d0b828a0c63df824ec7196250 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 15 Nov 2016 11:29:35 +0800 Subject: [PATCH 19/66] Add tutorial06 answer --- tutorial06_answer/CMakeLists.txt | 10 + tutorial06_answer/leptjson.c | 444 +++++++++++++++++++++++++ tutorial06_answer/leptjson.h | 71 ++++ tutorial06_answer/test.c | 426 ++++++++++++++++++++++++ tutorial06_answer/tutorial06_answer.md | 162 +++++++++ 5 files changed, 1113 insertions(+) create mode 100644 tutorial06_answer/CMakeLists.txt create mode 100644 tutorial06_answer/leptjson.c create mode 100644 tutorial06_answer/leptjson.h create mode 100644 tutorial06_answer/test.c create mode 100644 tutorial06_answer/tutorial06_answer.md diff --git a/tutorial06_answer/CMakeLists.txt b/tutorial06_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial06_answer/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/tutorial06_answer/leptjson.c b/tutorial06_answer/leptjson.c new file mode 100644 index 00000000..e99a8413 --- /dev/null +++ b/tutorial06_answer/leptjson.c @@ -0,0 +1,444 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_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) + +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++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + 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++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, 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++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 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 + 1); + /* 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 == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + 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; +} + +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_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; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const 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]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +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(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].v; +} diff --git a/tutorial06_answer/leptjson.h b/tutorial06_answer/leptjson.h new file mode 100644 index 00000000..8a6ce68d --- /dev/null +++ b/tutorial06_answer/leptjson.h @@ -0,0 +1,71 @@ +#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; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + 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); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#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); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +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(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial06_answer/test.c b/tutorial06_answer/test.c new file mode 100644 index 00000000..74e5f763 --- /dev/null +++ b/tutorial06_answer/test.c @@ -0,0 +1,426 @@ +#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) == 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_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_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + 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"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_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(); +} + +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() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + 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/tutorial06_answer/tutorial06_answer.md b/tutorial06_answer/tutorial06_answer.md new file mode 100644 index 00000000..4534120c --- /dev/null +++ b/tutorial06_answer/tutorial06_answer.md @@ -0,0 +1,162 @@ +# 从零开始的 JSON 库教程(六):解析对象解答篇 + +* Milo Yip +* 2016/11/15 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元解答篇。解答代码位于 [json-tutorial/tutorial06_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial06_answer)。 + +## 1. 重构 `lept_parse_string()` + +这个「提取方法」重构练习很简单,只需要把原来调用 `lept_set_string` 的地方,改为写入参数变量。因此,原来的 `lept_parse_string()` 和 答案中的 `lept_parse_string_raw()` 的 diff 仅是两处: + +~~~ +130,131c130,131 +< static int lept_parse_string(lept_context* c, lept_value* v) { +< size_t head = c->top, len; +--- +> static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { +> size_t head = c->top; +140,141c140,141 +< len = c->top - head; +< lept_set_string(v, (const char*)lept_context_pop(c, len), len); +--- +> *len = c->top - head; +> *str = lept_context_pop(c, *len); +~~~ + +以 TDD 方式开发软件,因为有单元测试确保软件的正确性,面对新需求可以安心重构,改善软件架构。 + +## 2. 实现 `lept_parse_object()` + +有了 `lept_parse_array()` 的经验,实现 `lept_parse_object()` 几乎是一样的,分别只是每个迭代要多处理一个键和冒号。我们把这个实现过程分为 5 步曲。 + +第 1 步是利用刚才重构出来的 `lept_parse_string_raw()` 去解析键的字符串。由于 `lept_parse_string_raw()` 假设第一个字符为 `"`,我们要先作校检,失败则要返回 `LEPT_PARSE_MISS_KEY` 错误。若字符串解析成功,它会把结果存储在我们的栈之中,需要把结果写入临时 `lept_member` 的 `k` 和 `klen` 字段中: + +~~~c +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + /* ... */ + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* 1. 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 + 1); + /* 2. parse ws colon ws */ + /* ... */ + } + /* 5. Pop and free members on the stack */ + /* ... */ +} +~~~ + +第 2 步是解析冒号,冒号前后可有空白字符: + +~~~c + /* 2. parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); +~~~ + +第 3 步是解析任意的 JSON 值。这部分与解析数组一样,递归调用 `lept_parse_value()`,把结果写入临时 `lept_member` 的 `v` 字段,然后把整个 `lept_member` 压入栈: + +~~~c + /* 3. 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 */ +~~~ + +但有一点要注意,如果之前缺乏冒号,或是这里解析值失败,在函数返回前我们要释放 `m.k`。如果我们成功地解析整个成员,那么就要把 `m.k` 设为空指针,其意义是说明该键的字符串的拥有权已转移至栈,之后如遇到错误,我们不会重覆释放栈里成员的键和这个临时成员的键。 + +第 4 步,解析逗号或右花括号。遇上右花括号的话,当前的 JSON 对象就解析完结了,我们把栈上的成员复制至结果,并直接返回: + +~~~c + /* 4. 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 = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } +~~~ + +最后,当 `for (;;)` 中遇到任何错误便会到达这第 5 步,要释放临时的 key 字符串及栈上的成员: + +~~~c + /* 5. 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; +~~~ + +注意我们不需要先检查 `m.k != NULL`,因为 `free(NULL)` 是完全合法的。 + +## 3. 释放内存 + +使用工具检测内存泄漏时,我们会发现以下这行代码造成内存泄漏: + +~~~c +memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); +~~~ + +类似数组,我们也需要在 `lept_free()` 释放 JSON 对象的成员(包括键及值): + +~~~c +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + /* ... */ + 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; +} +~~~ + +## 4. 总结 + +至此,你已实现一个完整的 JSON 解析器,可解析任何合法的 JSON。统计一下,不计算空行及注释,现时 `leptjson.c` 只有 405 行代码,`lept_json.h` 54 行,`test.c` 309 行。 + +另一方面,一些程序也需要生成 JSON。也许最初读者会以为生成 JSON 只需直接 `sprintf()/fprintf()` 就可以,但深入了解 JSON 的语法之后,我们应该知道 JSON 语法还是需做一些处理,例如字符串的转义、数字的格式等。然而,实现生成器还是要比解析器容易得多。而且,假设我们有一个正确的解析器,可以简单使用 roundtrip 方式实现测试。请期待下回分解。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From c90370bbb17a0e5788e81464029d4fa8eddf46c4 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 15 Nov 2016 16:27:21 +0900 Subject: [PATCH 20/66] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 93d6a122..f2a583bd 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 的解析、相关内存释放。 +6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From e6c81a6d4227414886d22a7aec39699d5cf94ca6 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 09:53:31 +0800 Subject: [PATCH 21/66] Fix missing null terminator in lept_parse_object() --- tutorial06_answer/leptjson.c | 3 ++- tutorial06_answer/test.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tutorial06_answer/leptjson.c b/tutorial06_answer/leptjson.c index e99a8413..307917c2 100644 --- a/tutorial06_answer/leptjson.c +++ b/tutorial06_answer/leptjson.c @@ -260,7 +260,8 @@ static int lept_parse_object(lept_context* c, lept_value* v) { } 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 + 1); + 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 != ':') { diff --git a/tutorial06_answer/test.c b/tutorial06_answer/test.c index 74e5f763..ad4dc6f3 100644 --- a/tutorial06_answer/test.c +++ b/tutorial06_answer/test.c @@ -25,7 +25,7 @@ static int test_pass = 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) == 0, expect, actual, "%s") + 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") From 360ca90cb2d155f9f69da1484728284c55a87838 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 09:55:02 +0800 Subject: [PATCH 22/66] Fix missing null terminator in documentation --- tutorial06_answer/tutorial06_answer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial06_answer/tutorial06_answer.md b/tutorial06_answer/tutorial06_answer.md index 4534120c..ea026142 100644 --- a/tutorial06_answer/tutorial06_answer.md +++ b/tutorial06_answer/tutorial06_answer.md @@ -50,7 +50,8 @@ static int lept_parse_object(lept_context* c, lept_value* v) { } 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 + 1); + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; /* 2. parse ws colon ws */ /* ... */ } From a5d197b8a45df5a3e1910a125991b3d39b548884 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 21:52:42 +0800 Subject: [PATCH 23/66] Update tutorial05_answer.md --- tutorial05_answer/tutorial05_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index b284815e..8b27f325 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -132,7 +132,7 @@ void lept_free(lept_value* v) { ## 4. 解析错误时的内存处理 -遇到解析错误时,我们可能在之前已压入了一些值在自定议堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 +遇到解析错误时,我们可能在之前已压入了一些值在自定义堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 在 `lept_parse_array` 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 `break` 离开循环,在循环结束后的地方用 `lept_free()` 释放从堆栈弹出的值,然后才返回错误码: From b2268c4bc42cafdaf173c84c48e40c07411a3f2c Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:34:05 +0800 Subject: [PATCH 24/66] Add tutorial07 --- tutorial07/CMakeLists.txt | 10 + tutorial07/images/makefile | 5 + tutorial07/images/parse_stringify.dot | 44 +++ tutorial07/images/parse_stringify.png | Bin 0 -> 29020 bytes tutorial07/leptjson.c | 484 +++++++++++++++++++++++++ tutorial07/leptjson.h | 72 ++++ tutorial07/test.c | 491 ++++++++++++++++++++++++++ tutorial07/tutorial07.md | 154 ++++++++ 8 files changed, 1260 insertions(+) create mode 100644 tutorial07/CMakeLists.txt create mode 100644 tutorial07/images/makefile create mode 100644 tutorial07/images/parse_stringify.dot create mode 100644 tutorial07/images/parse_stringify.png create mode 100644 tutorial07/leptjson.c create mode 100644 tutorial07/leptjson.h create mode 100644 tutorial07/test.c create mode 100644 tutorial07/tutorial07.md diff --git a/tutorial07/CMakeLists.txt b/tutorial07/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial07/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/tutorial07/images/makefile b/tutorial07/images/makefile new file mode 100644 index 00000000..55ed2163 --- /dev/null +++ b/tutorial07/images/makefile @@ -0,0 +1,5 @@ +%.png: %.dot + dot $< -Tpng -o $@ + +DOTFILES = $(basename $(wildcard *.dot)) +all: $(addsuffix .png, $(DOTFILES)) diff --git a/tutorial07/images/parse_stringify.dot b/tutorial07/images/parse_stringify.dot new file mode 100644 index 00000000..1b8eae51 --- /dev/null +++ b/tutorial07/images/parse_stringify.dot @@ -0,0 +1,44 @@ +digraph { + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] + + { + node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] + json [label="\{|\"|p|r|o|j|e|c|t|\"|:|\"|l|e|p|t|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|0|\}"] + } + + subgraph cluster1 { + margin="10,10" + labeljust="left" + label = "lept_value tree" + style=filled + fillcolor=gray95 + node [shape=Mrecord, style=filled, colorscheme=spectral7] + + root [label="{object|}", fillcolor=3] + + { + project [label="{key|\"project\"}", fillcolor=5] + leptjson [label="{string|\"leptjson\"}", fillcolor=5] + stars [label="{key|\"stars\"}", fillcolor=5] + ten [label="{number|10}", fillcolor=6] + } + + edge [arrowhead=vee] + root -> { project; stars } + + edge [arrowhead="none"] + project -> leptjson + stars -> ten + } + + json -> root [label=" lept_parse() ", lhead="cluster1"] + root -> json [label=" lept_stringify() ", ltail="cluster1"] +} \ No newline at end of file diff --git a/tutorial07/images/parse_stringify.png b/tutorial07/images/parse_stringify.png new file mode 100644 index 0000000000000000000000000000000000000000..9d40d4aedbf0f47be4b3ad1083f2faa013f7d1ff GIT binary patch literal 29020 zcmd?RWmg?tv^5yq-Q6vOKyY^_IDtTLcX!v|2X}V}?(Xgy+}+(F(8Y6akM8$3ydN?$ z&M7%nd#}CLTyxG%sDhjX5@j*^hxlOl&Fx3E7++Pl+cy)`yE^k#uMpMdWw)_LV~EMkdR

TCVY+b-)W?D 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 K(JJBZf&T~G5E;7w literal 0 HcmV?d00001 diff --git a/tutorial07/leptjson.c b/tutorial07/leptjson.c new file mode 100644 index 00000000..5307b892 --- /dev/null +++ b/tutorial07/leptjson.c @@ -0,0 +1,484 @@ +#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++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + 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++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, 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++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 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 == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + 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 void lept_stringify_value(lept_context* c, const lept_value* v) { + 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: + /* ... */ + break; + case LEPT_OBJECT: + /* ... */ + 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_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_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; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const 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]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +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(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].v; +} diff --git a/tutorial07/leptjson.h b/tutorial07/leptjson.h new file mode 100644 index 00000000..92cbfc51 --- /dev/null +++ b/tutorial07/leptjson.h @@ -0,0 +1,72 @@ +#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; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + 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_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#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); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +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(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial07/test.c b/tutorial07/test.c new file mode 100644 index 00000000..7e34cbb7 --- /dev/null +++ b/tutorial07/test.c @@ -0,0 +1,491 @@ +#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(); +} + +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() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_stringify(); + 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/tutorial07/tutorial07.md b/tutorial07/tutorial07.md new file mode 100644 index 00000000..b23e8f08 --- /dev/null +++ b/tutorial07/tutorial07.md @@ -0,0 +1,154 @@ +# 从零开始的 JSON 库教程(七):生成器 + +* Milo Yip +* 2016/12/20 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元。代码位于 [json-tutorial/tutorial07](https://github.com/miloyip/json-tutorial/blob/master/tutorial07)。 + +## 1. JSON 生成器 + +我们在前 6 个单元实现了一个合乎标准的 JSON 解析器,它把 JSON 文本解析成一个树形数据结构,整个结构以 `lept_value` 的节点组成。 + +JSON 生成器(generator)负责相反的事情,就是把树形数据结构转换成 JSON 文本。这个过程又称为「字符串化(stringify)」。 + +![JSON 的解析与生成](images/parse_stringify.png) + +相对于解析器,通常生成器更容易实现,而且生成器几乎不会造成运行时错误。因此,生成器的 API 设计为以下形式,直接返回 JSON 的字符串: + +~~~c +char* lept_stringify(const lept_value* v, size_t* length); +~~~ + +`length` 参数是可选的,它会存储 JSON 的长度,传入 `NULL` 可忽略此参数。使用方需负责用 `free()` 释放内存。 + +为了简单起见,我们不做换行、缩进等美化(prettify)处理,因此它生成的 JSON 会是单行、无空白字符的最紧凑形式。 + +## 2. 再利用 lept_context 做动态数组 + +在实现 JSON 解析时,我们加入了一个动态变长的堆栈,用于存储临时的解析结果。而现在,我们也需要存储生成的结果,所以最简单是再利用该数据结构,作为输出缓冲区。 + +~~~c +#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE +#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 +#endif + +int lept_stringify(const lept_value* v, char** json, size_t* length) { + lept_context c; + int ret; + assert(v != NULL); + assert(json != NULL); + c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); + c.top = 0; + if ((ret = lept_stringify_value(&c, v)) != LEPT_STRINGIFY_OK) { + free(c.stack); + *json = NULL; + return ret; + } + if (length) + *length = c.top; + PUTC(&c, '\0'); + *json = c.stack; + return LEPT_STRINGIFY_OK; +} +~~~ + +生成根节点的值之后,我需还需要加入一个空字符作结尾。 + +如前所述,此 API 还提供了 `length` 可选参数,当传入非空指针时,就能获得生成 JSON 的长度。或许读者会疑问,为什么需要获得长度,我们不是可以用 `strlen()` 获得么?是的,因为 JSON 不会含有空字符(若 JSON 字符串中含空字符,必须转义为 `\u0000`),用 `strlen()` 是没有问题的。但这样做会带来不必要的性能消耗,理想地是避免调用方有额外消耗。 + +## 3. 生成 null、false 和 true + +接下来,我们生成最简单的 JSON 类型,就是 3 种 JSON 字面值。为贯彻 TDD,先写测试: + +~~~c +#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));\ + EXPECT_EQ_INT(LEPT_STRINGIFY_OK, lept_stringify(&v, &json2, &length));\ + EXPECT_EQ_STRING(json, json2, length);\ + lept_free(&v);\ + free(json2);\ + } while(0) + +static void test_stringify() { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("true"); + /* ... */ +} +~~~ + +这里我们采用一个最简单的测试方式,把一个 JSON 解析,然后再生成另一 JSON,逐字符比较两个 JSON 是否一模一样。这种测试可称为往返(roundtrip)测试。但需要注意,同一个 JSON 的内容可以有多种不同的表示方式,例如可以插入不定数量的空白字符,数字 `1.0` 和 `1` 也是等价的。所以另一种测试方式,是比较两次解析的结果(`lept_value` 的树)是否相同,此功能将会在下一单元讲解。 + +然后,我们实现 `lept_stringify_value`,加入一个 `PUTS()` 宏去输出字符串: + +~~~c +#define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) + +static int lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + int ret; + 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; + /* ... */ + } + return LEPT_STRINGIFY_OK; +} +~~~ + +## 4. 生成数字 + +为了简单起见,我们使用 `sprintf("%.17g", ...)` 来把浮点数转换成文本。`"%.17g"` 是足够把双精度浮点转换成可还原的文本。 + +最简单的实现方式可能是这样的: + +~~~c + case LEPT_NUMBER: + { + char buffer[32]; + int length = sprintf(buffer, "%.17g", v->u.n); + PUTS(c, buffer, length); + } + break; +~~~ + +但这样需要在 `PUTS()` 中做一次 `memcpy()`,实际上我们可以避免这次复制,只需要生成的时候直接写进 `c` 里的推栈,然后再按实际长度调查 `c->top`: + +~~~c + case LEPT_NUMBER: + { + char* buffer = lept_context_push(c, 32); + int length = sprintf(buffer, "%.17g", v->u.n); + c->top -= 32 - length; + } + break; +~~~ + +因每个临时变量只用了一次,我们可以把代码压缩成一行: + +~~~c + case LEPT_NUMBER: + c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; +~~~ + +## 5. 总结与练习 + +我们在此单元中简介了 JSON 的生成功能和 leptjson 中的实现方式。 + +leptjson 重复利用了 `lept_context` 中的数据结构作为输出缓冲,可以节省代码量。 + +生成通常比解析简单(一个例外是 RapidJSON 自行实现了浮点数至字符串的算法),余下的 3 种 JSON 类型就当作练习吧: + +1. 由于有两个地方需要生成字符串(JSON 字符串和对象类型),所以先实现 `lept_stringify_string()`。注意,字符串的语法比较复杂,一些字符必须转义,其他少于 `0x20` 的字符需要转义为 `\u00xx` 形式。 + +2. 直接在 `lept_stringify_value()` 的 `switch` 内实现 JSON 数组和对象类型的生成。这些实现里都会递归调用 `lept_stringify_value()` 。 + +3. 在你的 `lept_stringify_string()` 是否使用了多次 `PUTC()`?如果是,它每次输出一个字符时,都要检测缓冲区是否有足够空间(不够时需扩展)。能否优化这部分的性能?这种优化有什么代价么? + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From de37772c15deb2d9a0e9547202c9106db618c980 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:35:15 +0800 Subject: [PATCH 25/66] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f2a583bd..05d12139 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 30ba5b6bbfb193bfba773b0bbfd882a6d3144d75 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:35:45 +0800 Subject: [PATCH 26/66] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 05d12139..adafd9c6 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 5a4fcabbcda7500f15725caad90259e9fca591a1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:48:39 +0800 Subject: [PATCH 27/66] Update tutorial07.md --- tutorial07/tutorial07.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index b23e8f08..02fe8255 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -134,7 +134,8 @@ static int lept_stringify_value(lept_context* c, const lept_value* v) { ~~~c case LEPT_NUMBER: - c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; + c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); + break; ~~~ ## 5. 总结与练习 From 5d79f14729c6bdd79ad9fcfeba8bf2ddf89b34c7 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 5 Jan 2017 13:55:11 +0800 Subject: [PATCH 28/66] Add tutorial07 answer --- readme.md | 2 +- tutorial07_answer/CMakeLists.txt | 10 + tutorial07_answer/leptjson.c | 556 +++++++++++++++++++++++++ tutorial07_answer/leptjson.h | 72 ++++ tutorial07_answer/test.c | 491 ++++++++++++++++++++++ tutorial07_answer/tutorial07_answer.md | 155 +++++++ 6 files changed, 1285 insertions(+), 1 deletion(-) create mode 100644 tutorial07_answer/CMakeLists.txt create mode 100644 tutorial07_answer/leptjson.c create mode 100644 tutorial07_answer/leptjson.h create mode 100644 tutorial07_answer/test.c create mode 100644 tutorial07_answer/tutorial07_answer.md diff --git a/readme.md b/readme.md index adafd9c6..b67b650f 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](2017/1/5 完成) 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 diff --git a/tutorial07_answer/CMakeLists.txt b/tutorial07_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial07_answer/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/tutorial07_answer/leptjson.c b/tutorial07_answer/leptjson.c new file mode 100644 index 00000000..b6f64834 --- /dev/null +++ b/tutorial07_answer/leptjson.c @@ -0,0 +1,556 @@ +#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++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + 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++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, 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++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 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 == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + 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; +} + +#if 0 +// Unoptimized +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + size_t i; + assert(s != NULL); + PUTC(c, '"'); + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': PUTS(c, "\\\"", 2); break; + case '\\': PUTS(c, "\\\\", 2); break; + case '\b': PUTS(c, "\\b", 2); break; + case '\f': PUTS(c, "\\f", 2); break; + case '\n': PUTS(c, "\\n", 2); break; + case '\r': PUTS(c, "\\r", 2); break; + case '\t': PUTS(c, "\\t", 2); break; + default: + if (ch < 0x20) { + char buffer[7]; + sprintf(buffer, "\\u%04X", ch); + PUTS(c, buffer, 6); + } + else + PUTC(c, s[i]); + } + } + PUTC(c, '"'); +} +#else +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); +} +#endif + +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_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_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; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const 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]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +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(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].v; +} diff --git a/tutorial07_answer/leptjson.h b/tutorial07_answer/leptjson.h new file mode 100644 index 00000000..92cbfc51 --- /dev/null +++ b/tutorial07_answer/leptjson.h @@ -0,0 +1,72 @@ +#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; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + 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_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#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); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +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(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial07_answer/test.c b/tutorial07_answer/test.c new file mode 100644 index 00000000..7e34cbb7 --- /dev/null +++ b/tutorial07_answer/test.c @@ -0,0 +1,491 @@ +#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(); +} + +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() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_stringify(); + 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/tutorial07_answer/tutorial07_answer.md b/tutorial07_answer/tutorial07_answer.md new file mode 100644 index 00000000..07e3d612 --- /dev/null +++ b/tutorial07_answer/tutorial07_answer.md @@ -0,0 +1,155 @@ +# 从零开始的 JSON 库教程(七):生成器解答篇 + +* Milo Yip +* 2017/1/5 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元解答篇。解答代码位于 [json-tutorial/tutorial07_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial07_answer)。 + +## 1. 生成字符串 + +我们需要对一些字符进行转义,最简单的实现如下: + +~~~c +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + size_t i; + assert(s != NULL); + PUTC(c, '"'); + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': PUTS(c, "\\\"", 2); break; + case '\\': PUTS(c, "\\\\", 2); break; + case '\b': PUTS(c, "\\b", 2); break; + case '\f': PUTS(c, "\\f", 2); break; + case '\n': PUTS(c, "\\n", 2); break; + case '\r': PUTS(c, "\\r", 2); break; + case '\t': PUTS(c, "\\t", 2); break; + default: + if (ch < 0x20) { + char buffer[7]; + sprintf(buffer, "\\u%04X", ch); + PUTS(c, buffer, 6); + } + else + PUTC(c, s[i]); + } + } + PUTC(c, '"'); +} + +static void lept_stringify_value(lept_context* c, const lept_value* v) { + switch (v->type) { + /* ... */ + case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; + /* ... */ + } +} +~~~ + +注意到,十六进位输出的字母可以用大写或小写,我们这里选择了大写,所以 roundstrip 测试时也用大写。但这个并不是必然的,输出小写(用 `"\\u%04x"`)也可以。 + +## 2. 生成数组和对象 + +生成数组也是非常简单,只要输出 `[` 和 `]`,中间对逐个子值递归调用 `lept_stringify_value()`。只要注意在第一个元素后才加入 `,`。而对象也仅是多了一个键和 `:`。 + +~~~cs +static void lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + switch (v->type) { + /* ... */ + 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; + /* ... */ + } +} +~~~ + +## 3. 优化 `lept_stringify_string()` + +最后,我们讨论一下优化。上面的 `lept_stringify_string()` 实现中,每次输出一个字符/字符串,都要调用 `lept_context_push()`。如果我们使用一些性能剖测工具,也可能会发现这个函数消耗较多 CPU。 + +~~~c +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { // (1) + 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; // (2) + c->top += size; // (3) + return ret; // (4) +} +~~~ + +中间最花费时间的,应该会是 (1),需要计算而且作分支检查。即使使用 C99 的 `inline` 关键字(或使用宏)去减少函数调用的开销,这个分支也无法避免。 + +所以,一个优化的点子是,预先分配足够的内存,每次加入字符就不用做这个检查了。但多大的内存才足够呢?我们可以看到,每个字符可生成最长的形式是 `\u00XX`,占 6 个字符,再加上前后两个双引号,也就是共 `len * 6 + 2` 个输出字符。那么,使用 `char* p = lept_context_push()` 作一次分配后,便可以用 `*p++ = c` 去输出字符了。最后,再按实际输出量调整堆栈指针。 + +另一个小优化点,是自行编写十六进位输出,避免了 `printf()` 内解析格式的开销。 + +~~~c +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); +} +~~~ + +要注意的是,很多优化都是有代优的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 + +而第二个优化的缺点,就是有稍增加了一点程序体积。也许有人会问,为什么 `hex_digits` 不用字符串字面量 `"0123456789ABCDEF"`?其实是可以的,但这会多浪费 1 个字节(实际因数据对齐可能会浪费 4 个或更多)。 + +## 4. 总结 + +我们用 80 行左右的代码就实现了 JSON 生成器,并尝试了做一些简单的优化。除了这种最简单的功能,有一些 JSON 库还会提供一些美化功能,即加入缩进及换行。另外,有一些应用可能需要大量输出数字,那么就可能需要优化数字的输出。这方面可考虑 C++ 开源库 [double-conversion](https://github.com/google/double-conversion),以及参考本人另一篇文章《[RapidJSON 代码剖析(四):优化 Grisu](https://zhuanlan.zhihu.com/p/20092285)》。 + +现时数组和对象类型只有最基本的访问、修改函数,我们会在下一篇补完。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 933c4899a29b30e2a3bd66fe54f00e555d5694b0 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 5 Jan 2017 13:56:20 +0800 Subject: [PATCH 29/66] Fix links --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index b67b650f..04d33557 100644 --- a/readme.md +++ b/readme.md @@ -43,8 +43,8 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](2017/1/5 完成) +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 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 434c63055c09cf8a9876e09e8bcdf8a3d415cf0d Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 6 Jan 2017 11:16:25 +0800 Subject: [PATCH 30/66] Typo --- tutorial07_answer/tutorial07_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07_answer/tutorial07_answer.md b/tutorial07_answer/tutorial07_answer.md index 07e3d612..b10ea1e3 100644 --- a/tutorial07_answer/tutorial07_answer.md +++ b/tutorial07_answer/tutorial07_answer.md @@ -142,7 +142,7 @@ static void lept_stringify_string(lept_context* c, const char* s, size_t len) { } ~~~ -要注意的是,很多优化都是有代优的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 +要注意的是,很多优化都是有代价的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 而第二个优化的缺点,就是有稍增加了一点程序体积。也许有人会问,为什么 `hex_digits` 不用字符串字面量 `"0123456789ABCDEF"`?其实是可以的,但这会多浪费 1 个字节(实际因数据对齐可能会浪费 4 个或更多)。 From 97a11ee740e7cebd044c7e1126d7f85dcc441754 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 9 Jan 2017 15:27:37 +0800 Subject: [PATCH 31/66] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 84cc000f..c82c457d 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -31,7 +31,7 @@ JSON 数组的语法很简单,实现的难点不在语法上,而是怎样管 JSON 数组存储零至多个元素,最简单就是使用 C 语言的数组。数组最大的好处是能以 $O(1)$ 用索引访问任意元素,次要好处是内存布局紧凑,省内存之余还有高缓存一致性(cache coherence)。但数组的缺点是不能快速插入元素,而且我们在解析 JSON 数组的时候,还不知道应该分配多大的数组才合适。 -另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且片历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 +另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且遍历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 我见过一些 JSON 库选择了链表,而这里则选择了数组。我们将会通过之前在解析字符串时实现的堆栈,来解决解析 JSON 数组时未知数组大小的问题。 From a7def3d6026e33862ba8519ce8bb5f52174cb4f5 Mon Sep 17 00:00:00 2001 From: Nov11 <529079634@qq.com> Date: Wed, 21 Jun 2017 01:48:57 +0800 Subject: [PATCH 32/66] fix typo of tutorial03 readme --- tutorial03/tutorial03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 74817e4e..113eea78 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -38,7 +38,7 @@ quotation-mark = %x22 ; " unescaped = %x20-21 / %x23-5B / %x5D-10FFFF ~~~ -简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分开无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 +简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分为无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 无转义字符就是普通的字符,语法中列出了合法的码点范围(码点还是在下单元才介绍)。要注意的是,该范围不包括 0 至 31、双引号和反斜线,这些码点都必须要使用转义方式表示。 From ad9ee017fc9f199ec550b7b382021852569d14ba Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 2 Jun 2018 19:18:30 +0800 Subject: [PATCH 33/66] Add tutorial08 --- 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..179352b0 --- /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); +~~~ + +## 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) 中提出,让所有人一起讨论。 From 645d3a71f1cb6e9d84b9a7da75799c5694d3f618 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 2 Jun 2018 20:47:41 +0800 Subject: [PATCH 34/66] Fix tutorial08 --- tutorial08/tutorial08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md index 179352b0..60874345 100644 --- a/tutorial08/tutorial08.md +++ b/tutorial08/tutorial08.md @@ -276,7 +276,6 @@ struct lept_value { /* ... */ struct { lept_value* e; size_t size, capacity; }a; /* array: elements, element count, capacity */ /* ... */ -}; ~~~ 我们终于提供设置数组的函数,而且它可提供初始的容量: @@ -357,6 +356,7 @@ void lept_popback_array_element(lept_value* v) { ~~~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. 动态对象 From 4fb2b709c1b8be99301b9aaf42657e84640a394a Mon Sep 17 00:00:00 2001 From: rustberry <34592958+rustberry@users.noreply.github.com> Date: Fri, 20 Jul 2018 09:51:09 +0800 Subject: [PATCH 35/66] fix typo of tutorial 05 readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原文:本来 'test_parse_array` 已经能处理这些测试 修改:本来 'lept_parse_array` 已经能处理这些测试 --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index c82c457d..0ae79878 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -222,7 +222,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 3. 使用[第三单元解答篇](../tutorial03_answer/tutorial03_answer.md)介绍的检测内存泄漏工具,会发现测试中有内存泄漏。很明显在 `lept_parse_array()` 中使用到 `malloc()` 分配内存,但却没有对应的 `free()`。应该在哪里释放内存?修改代码,使工具不再检测到相关的内存泄漏。 -4. 开启 test.c 中两处被 `#if 0 ... #endif` 关闭的测试,本来 `test_parse_array()` 已经能处理这些测试。然而,运行时会发现 `Assertion failed: (c.top == 0)` 断言失败。这是由于,当错误发生时,仍然有一些临时值在堆栈里,既没有放进数组,也没有被释放。修改 `test_parse_array()`,当遇到错误时,从堆栈中弹出并释放那些临时值,然后才返回错误码。 +4. 开启 test.c 中两处被 `#if 0 ... #endif` 关闭的测试,本来 `lept_parse_array()` 已经能处理这些测试。然而,运行时会发现 `Assertion failed: (c.top == 0)` 断言失败。这是由于,当错误发生时,仍然有一些临时值在堆栈里,既没有放进数组,也没有被释放。修改 `lept_parse_array()`,当遇到错误时,从堆栈中弹出并释放那些临时值,然后才返回错误码。 5. 第 4 节那段代码为什么会有 bug? From ce96b9a157350b75add36fed36662ac159441c5f Mon Sep 17 00:00:00 2001 From: rustberry <34592958+rustberry@users.noreply.github.com> Date: Fri, 20 Jul 2018 10:11:05 +0800 Subject: [PATCH 36/66] fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原文第 121 行:只需要生成的时候直接写进 `c` 里的 推 栈 修改: 只需要生成的时候直接写进 `c` 里的 堆 栈 --- tutorial07/tutorial07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index 02fe8255..c301abe3 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -118,7 +118,7 @@ static int lept_stringify_value(lept_context* c, const lept_value* v) { break; ~~~ -但这样需要在 `PUTS()` 中做一次 `memcpy()`,实际上我们可以避免这次复制,只需要生成的时候直接写进 `c` 里的推栈,然后再按实际长度调查 `c->top`: +但这样需要在 `PUTS()` 中做一次 `memcpy()`,实际上我们可以避免这次复制,只需要生成的时候直接写进 `c` 里的堆栈,然后再按实际长度调查 `c->top`: ~~~c case LEPT_NUMBER: From 3b8f312a9e67d0b0d13ee3dee872f45e6efc0eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Thu, 30 Aug 2018 22:02:06 +0800 Subject: [PATCH 37/66] Update tutorial01.md: Specify code language --- tutorial01/tutorial01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 0f966fe5..1a9f749c 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -243,7 +243,7 @@ TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试 回到 leptjson 项目,`test.c` 包含了一个极简的单元测试框架: -~~~ +~~~c #include #include #include From 29c68bd210ff947c24ba8133fe0a00d812a0e9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Thu, 30 Aug 2018 22:18:08 +0800 Subject: [PATCH 38/66] =?UTF-8?q?Update=20tutorial01=5Fanswer.md:=20?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=20->=20=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial01_answer/tutorial01_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial01_answer/tutorial01_answer.md b/tutorial01_answer/tutorial01_answer.md index 99865363..63bf0c69 100644 --- a/tutorial01_answer/tutorial01_answer.md +++ b/tutorial01_answer/tutorial01_answer.md @@ -69,7 +69,7 @@ static void test_parse() { } ~~~ -但要记得在上一级的测试函数 `test_parse()` 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出告警: +但要记得在上一级的测试函数 `test_parse()` 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出警告: ~~~ test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function] From e3ccddf021ae344ceb9407822e6c23407cccc3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Fri, 31 Aug 2018 17:07:40 +0800 Subject: [PATCH 39/66] Update tutorial02/leptjson.h: Fix indentation --- tutorial02/leptjson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial02/leptjson.h b/tutorial02/leptjson.h index 4818278c..0a2652bf 100644 --- a/tutorial02/leptjson.h +++ b/tutorial02/leptjson.h @@ -4,7 +4,7 @@ typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; typedef struct { - double n; + double n; lept_type type; }lept_value; From d3bfdb6e2351169fd640a67d7e16d9eafe16fd4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Fri, 31 Aug 2018 17:26:07 +0800 Subject: [PATCH 40/66] Update tutorial02.md: Fix blank space --- tutorial02/tutorial02.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 5f520b2b..9ab7ea17 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -200,7 +200,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true` 为 `lept_parse_literal()`。 2. 加入 [维基百科双精度浮点数](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples) 的一些边界值至单元测试,如 min subnormal positive double、max double 等。 -3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 +3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) 以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: @@ -217,6 +217,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { # 7. 参考 [1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:《重构:改善既有代码的设计》,熊节译,人民邮电出版社,2010年。 + [2] Gay, David M. "Correctly rounded binary-decimal and decimal-binary conversions." Numerical Analysis Manuscript 90-10 (1990). # 8. 常见问题 From 75c263a11663a0363f4812a2f8124a93a0389b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Fri, 31 Aug 2018 17:58:32 +0800 Subject: [PATCH 41/66] =?UTF-8?q?Update=20tutorial01.md:=20Add=20wiki=20re?= =?UTF-8?q?ference=20for=20=E5=89=AF=E4=BD=9C=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial01/tutorial01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 1a9f749c..68942fe8 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -427,7 +427,7 @@ C 语言的标准库含有 [`assert()`](http://en.cppreference.com/w/c/error/ass 例如上面的 `lept_parse_null()` 开始时,当前字符应该是 `'n'`,所以我们使用一个宏 `EXPECT(c, ch)` 进行断言,并跳到下一字符。 -初使用断言的同学,可能会错误地把含副作用的代码放在 `assert()` 中: +初使用断言的同学,可能会错误地把含[副作用](https://en.wikipedia.org/wiki/Side_effect_(computer_science))的代码放在 `assert()` 中: ~~~c assert(x++ == 0); /* 这是错误的! */ From cc60136ddbe646a0966e81b4faf7cf1e4dafb7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Sun, 2 Sep 2018 12:13:08 +0800 Subject: [PATCH 42/66] Update readme.md: Fix typo && Add a period --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 10332e1c..64538a65 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ 为什么选择 JSON?因为它足够简单,除基本编程外不需大量技术背景知识。JSON 有标准,可按照标准逐步实现。JSON 也是实际在许多应用上会使用的格式,所以才会有大量的开源库。 -这是一个免费、开源的教程,如果你喜欢,也可以打赏鼓励。因为工作及家庭因素,不能保证每篇文章的首发时间,请各为见谅。 +这是一个免费、开源的教程,如果你喜欢,也可以打赏鼓励。因为工作及家庭因素,不能保证每篇文章的首发时间,请各位见谅。 ## 对象与目标 @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 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 完成) +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](tutorial07_answer/tutorial07_answer.md)(2017/1/5 完成)。 8. [访问与其他功能](tutorial08/tutorial08.md)(2018/6/2 完成):JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From c80b0953906c557a438288e9d14ff665f9e92c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 3 Sep 2018 12:59:27 +0800 Subject: [PATCH 43/66] =?UTF-8?q?Update=20tutorial02=5Fanswer.md:=20?= =?UTF-8?q?=E8=B4=9F=E6=95=B0=20->=20=E8=B4=9F=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial02_answer/tutorial02_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial02_answer/tutorial02_answer.md b/tutorial02_answer/tutorial02_answer.md index 37451ae9..524b27e2 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -101,7 +101,7 @@ exp = ("e" / "E") ["-" / "+"] 1*digit if (*p == '-') p++; ~~~ -整数部分有两种合法情况,一是单个 `0`,否则是一个 1-9 再加上任意数量的 digit。对于第一种情况,我们像负数般跳过便行。对于第二种情况,第一个字符必须为 1-9,如果否定的就是不合法的,可立即返回错误码。然后,有多少个 digit 就跳过多少个。 +整数部分有两种合法情况,一是单个 `0`,否则是一个 1-9 再加上任意数量的 digit。对于第一种情况,我们像负号般跳过便行。对于第二种情况,第一个字符必须为 1-9,如果否定的就是不合法的,可立即返回错误码。然后,有多少个 digit 就跳过多少个。 ~~~c if (*p == '0') p++; From 858db9174b85c45cb67d95285cf26324aedd1ea8 Mon Sep 17 00:00:00 2001 From: imba-tjd <109224573@qq.com> Date: Mon, 3 Sep 2018 13:12:41 +0800 Subject: [PATCH 44/66] Update All: Rplace http urls with https urls --- tutorial01/tutorial01.md | 8 ++++---- tutorial02/tutorial02.md | 8 ++++---- tutorial02_answer/tutorial02_answer.md | 2 +- tutorial03/tutorial03.md | 2 +- tutorial03_answer/tutorial03_answer.md | 2 +- tutorial04_answer/tutorial04_answer.md | 2 +- tutorial06/tutorial06.md | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 68942fe8..50902693 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -20,7 +20,7 @@ ## JSON 是什么 -JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)。 +JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)。 虽然 JSON 源至于 JavaScript 语言,但它只是一种数据格式,可用于任何编程语言。现时具类似功能的格式有 XML、YAML,当中以 JSON 的语法最为简单。 @@ -90,7 +90,7 @@ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式 按 Configure,选择编译器,然后按 Generate 便会生成 Visual Studio 的 .sln 和 .vcproj 等文件。注意这个 build 目录都是生成的文件,可以随时删除,也不用上传至仓库。 -在 OS X 下,建议安装 [Homebrew](http://brew.sh/),然后在命令行键入: +在 OS X 下,建议安装 [Homebrew](https://brew.sh/),然后在命令行键入: ~~~ $ brew install cmake @@ -226,7 +226,7 @@ true = "true" 许多同学在做练习题时,都是以 `printf`/`cout` 打印结果,再用肉眼对比结果是否乎合预期。但当软件项目越来越复杂,这个做法会越来越低效。一般我们会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能确保其他人修改代码后,原来的功能维持正确(这称为回归测试/regression testing)。 -常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](http://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。 +常用的单元测试框架有 xUnit 系列,如 C++ 的 [Google Test](https://github.com/google/googletest)、C# 的 [NUnit](https://www.nunit.org/)。我们为了简单起见,会编写一个极简单的单元测试方式。 一般来说,软件开发是以周期进行的。例如,加入一个功能,再写关于该功能的单元测试。但也有另一种软件开发方法论,称为测试驱动开发(test-driven development, TDD),它的主要循环步骤是: @@ -423,7 +423,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 断言(assertion)是 C 语言中常用的防御式编程方式,减少编程错误。最常用的是在函数开始的地方,检测所有参数。有时候也可以在调用函数后,检查上下文是否正确。 -C 语言的标准库含有 [`assert()`](http://en.cppreference.com/w/c/error/assert) 这个宏(需 `#include `),提供断言功能。当程序以 release 配置编译时(定义了 `NDEBUG` 宏),`assert()` 不会做检测;而当在 debug 配置时(没定义 `NDEBUG` 宏),则会在运行时检测 `assert(cond)` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。 +C 语言的标准库含有 [`assert()`](https://en.cppreference.com/w/c/error/assert) 这个宏(需 `#include `),提供断言功能。当程序以 release 配置编译时(定义了 `NDEBUG` 宏),`assert()` 不会做检测;而当在 debug 配置时(没定义 `NDEBUG` 宏),则会在运行时检测 `assert(cond)` 中的条件是否为真(非 0),断言失败会直接令程序崩溃。 例如上面的 `lept_parse_null()` 开始时,当前字符应该是 `'n'`,所以我们使用一个宏 `EXPECT(c, ch)` 进行断言,并跳到下一字符。 diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 9ab7ea17..6eb7d8d7 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -64,7 +64,7 @@ number 是以十进制表示,它主要由 4 部分顺序组成:负号、整 JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。 -JSON 标准 [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: +JSON 标准 [ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: ![number](images/number.png) @@ -151,7 +151,7 @@ static void test_parse_invalid_value() { # 5. 十进制转换至二进制 -我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 +我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 ~~~c #include /* NULL, strtod() */ @@ -201,7 +201,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true` 为 `lept_parse_literal()`。 2. 加入 [维基百科双精度浮点数](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples) 的一些边界值至单元测试,如 min subnormal positive double、max double 等。 3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 -4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) +4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) 以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: @@ -228,6 +228,6 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 2. 科学计数法的指数部分没有对前导零作限制吗?`1E012` 也是合法的吗? - 是的,这是合法的。JSON 源自于 JavaScript([ECMA-262, 3rd edition](http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf)),数字语法取自 JavaScript 的十进位数字的语法(§7.8.3 Numeric Literals)。整数不容许前导零(leading zero),是因为更久的 JavaScript 版本容许以前导零来表示八进位数字,如 `052 == 42`,这种八进位常数表示方式来自于 [C 语言](http://en.cppreference.com/w/c/language/integer_constant)。禁止前导零避免了可能出现的歧义。但是在指数里就不会出现这个问题。多谢 @Smallay 提出及协助解答这个问题。 + 是的,这是合法的。JSON 源自于 JavaScript([ECMA-262, 3rd edition](https://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf)),数字语法取自 JavaScript 的十进位数字的语法(§7.8.3 Numeric Literals)。整数不容许前导零(leading zero),是因为更久的 JavaScript 版本容许以前导零来表示八进位数字,如 `052 == 42`,这种八进位常数表示方式来自于 [C 语言](https://en.cppreference.com/w/c/language/integer_constant)。禁止前导零避免了可能出现的歧义。但是在指数里就不会出现这个问题。多谢 @Smallay 提出及协助解答这个问题。 其他常见问答将会从评论中整理。 diff --git a/tutorial02_answer/tutorial02_answer.md b/tutorial02_answer/tutorial02_answer.md index 524b27e2..4c83eebd 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -157,7 +157,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } ~~~ -许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](http://cppreference.com) 是 C/C++ 程序员的宝库。 +许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](https://cppreference.com) 是 C/C++ 程序员的宝库。 ## 5. 总结 diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 113eea78..162f2dc6 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -225,7 +225,7 @@ static void* lept_context_pop(lept_context* c, size_t size) { 压入时若空间不足,便回以 1.5 倍大小扩展。为什么是 1.5 倍而不是两倍?可参考我在 [STL 的 vector 有哪些封装上的技巧?](https://www.zhihu.com/question/25079705/answer/30030883) 的答案。 -注意到这里使用了 [`realloc()`](http://en.cppreference.com/w/c/memory/realloc) 来重新分配内存,`c->stack` 在初始化时为 `NULL`,`realloc(NULL, size)` 的行为是等价于 `malloc(size)` 的,所以我们不需要为第一次分配内存作特别处理。 +注意到这里使用了 [`realloc()`](https://en.cppreference.com/w/c/memory/realloc) 来重新分配内存,`c->stack` 在初始化时为 `NULL`,`realloc(NULL, size)` 的行为是等价于 `malloc(size)` 的,所以我们不需要为第一次分配内存作特别处理。 另外,我们把初始大小以宏 `LEPT_PARSE_STACK_INIT_SIZE` 的形式定义,使用 `#ifndef X #define X ... #endif` 方式的好处是,使用者可在编译选项中自行设置宏,没设置的话就用缺省值。 diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 77d56084..8747fccc 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -105,7 +105,7 @@ Object dump complete. ## 1B. Linux/OSX 下的内存泄漏检测方法 -在 Linux、OS X 下,我们可以使用 [valgrind](http://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: +在 Linux、OS X 下,我们可以使用 [valgrind](https://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: ~~~ $ valgrind --leak-check=full ./leptjson_test diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md index e634a069..6c75e783 100644 --- a/tutorial04_answer/tutorial04_answer.md +++ b/tutorial04_answer/tutorial04_answer.md @@ -25,7 +25,7 @@ static const char* lept_parse_hex4(const char* p, unsigned* u) { } ~~~ -可能有同学想到用标准库的 [`strtol()`](http://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: +可能有同学想到用标准库的 [`strtol()`](https://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: ~~~c static const char* lept_parse_hex4(const char* p, unsigned* u) { diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 749270ae..e83ed259 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -26,10 +26,10 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 要表示键值对的集合,有很多数据结构可供选择,例如: -* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 +* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](https://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 -* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](https://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](https://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](https://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](https://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: From be5fcec434a3d4dd889fc097f40e7416589deb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 3 Sep 2018 16:25:54 +0800 Subject: [PATCH 45/66] Update tutorial02_answer.md: Specify code language --- tutorial02_answer/tutorial02_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial02_answer/tutorial02_answer.md b/tutorial02_answer/tutorial02_answer.md index 4c83eebd..7079201a 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -55,7 +55,7 @@ TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); 有一些 JSON 解析器不使用 `strtod()` 而自行转换,例如在校验的同时,记录负号、尾数(整数和小数)和指数,然后 naive 地计算: -~~~ +~~~c int negative = 0; int64_t mantissa = 0; int exp = 0; From bb44841c727c45efbcc1ce22be8632f6f56b7f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 3 Sep 2018 19:51:44 +0800 Subject: [PATCH 46/66] =?UTF-8?q?Update=20tutorial03.md:=20Fix=20typo=20&&?= =?UTF-8?q?=20Add=20English=20of=20=E5=A0=86=E6=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial03/tutorial03.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 162f2dc6..58ef0381 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -44,7 +44,7 @@ unescaped = %x20-21 / %x23-5B / %x5D-10FFFF ## 2. 字符串表示 -在 C 语言中,字符串一般表示为空结尾字符串(null-terminated string),即以空字符(`'\0'`)代表字符串的结束。然而,JSON 字符串是允许含有空字符的,例如这个 JSON `"Hello\u0000World"` 就是单个字符串,解析后为11个字符。如果纯粹使用空结尾字符来表示 JSON 解析后的结果,就没法处理空字符。 +在 C 语言中,字符串一般表示为空结尾字符串(null-terminated string),即以空字符(`'\0'`)代表字符串的结束。然而,JSON 字符串是允许含有空字符的,例如这个 JSON `"Hello\u0000World"` 就是单个字符串,解析后为11个字符。如果纯粹使用空结尾字符串来表示 JSON 解析后的结果,就没法处理空字符。 因此,我们可以分配内存来储存解析后的字符,以及记录字符的数目(即字符串长度)。由于大部分 C 程序都假设字符串是空结尾字符串,我们还是在最后加上一个空字符,那么不需处理 `\u0000` 这种字符的应用可以简单地把它当作是空结尾字符串。 @@ -158,7 +158,7 @@ static void test_access_string() { 我们解析字符串(以及之后的数组、对象)时,需要把解析的结果先储存在一个临时的缓冲区,最后再用 `lept_set_string()` 把缓冲区的结果设进值之中。在完成解析一个字符串之前,这个缓冲区的大小是不能预知的。因此,我们可以采用动态数组(dynamic array)这种数据结构,即数组空间不足时,能自动扩展。C++ 标准库的 `std::vector` 也是一种动态数组。 -如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈数据结构。 +如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈(stack)数据结构。 我们把一个动态堆栈的数据放进 `lept_context` 里: From bb48a2193dbe78353e3deb2df6750bc5171cd566 Mon Sep 17 00:00:00 2001 From: imba-tjd <109224573@qq.com> Date: Mon, 3 Sep 2018 20:01:15 +0800 Subject: [PATCH 47/66] Update tutorial01.md: Add serial number for headers --- tutorial01/tutorial01.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 50902693..76fc027a 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -18,7 +18,7 @@ 9. [总结与练习](#总结与练习) 10. [常见问答](#常见问答) -## JSON 是什么 +## 1. JSON 是什么 JSON(JavaScript Object Notation)是一个用于数据交换的文本格式,现时的标准为[ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf)。 @@ -68,7 +68,7 @@ JSON(JavaScript Object Notation)是一个用于数据交换的文本格式 我们会逐步实现这些需求。在本单元中,我们只实现最简单的 null 和 boolean 解析。 -## 搭建编译环境 +## 2. 搭建编译环境 我们要做的库是跨平台、跨编译器的,同学可使用任意平台进行练习。 @@ -126,7 +126,7 @@ $ ./leptjson_test 若看到类似以上的结果,说明已成功搭建编译环境,我们可以去看看那几个代码文件的内容了。 -## 头文件与 API 设计 +## 3. 头文件与 API 设计 C 语言有头文件的概念,需要使用 `#include`去引入头文件中的类型声明和函数声明。但由于头文件也可以 `#include` 其他头文件,为避免重复声明,通常会利用宏加入 include 防范(include guard): @@ -193,7 +193,7 @@ enum { lept_type lept_get_type(const lept_value* v); ~~~ -## JSON 语法子集 +## 4. JSON 语法子集 下面是此单元的 JSON 语法子集,使用 [RFC7159](http://rfc7159.net/rfc7159) 中的 [ABNF](https://tools.ietf.org/html/rfc5234) 表示: @@ -222,7 +222,7 @@ true = "true" * 若一个值之后,在空白之后还有其他字符,传回 `LEPT_PARSE_ROOT_NOT_SINGULAR`。 * 若值不是那三种字面值,传回 `LEPT_PARSE_INVALID_VALUE`。 -## 单元测试 +## 5. 单元测试 许多同学在做练习题时,都是以 `printf`/`cout` 打印结果,再用肉眼对比结果是否乎合预期。但当软件项目越来越复杂,这个做法会越来越低效。一般我们会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能确保其他人修改代码后,原来的功能维持正确(这称为回归测试/regression testing)。 @@ -299,7 +299,7 @@ int main() { 然而,完全按照 TDD 的步骤来开发,是会减慢开发进程。所以我个人会在这两种极端的工作方式取平衡。通常会在设计 API 后,先写部分测试代码,再写满足那些测试的实现。 -## 宏的编写技巧 +## 6. 宏的编写技巧 有些同学可能不了解 `EXPECT_EQ_BASE` 宏的编写技巧,简单说明一下。反斜线代表该行未结束,会串接下一行。而如果宏里有多过一个语句(statement),就需要用 `do { /*...*/ } while(0)` 包裹成单个语句,否则会有如下的问题: @@ -344,7 +344,7 @@ else c(); ~~~ -## 实现解析器 +## 7. 实现解析器 有了 API 的设计、单元测试,终于要实现解析器了。 @@ -419,7 +419,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 由于 `lept_parse_whitespace()` 是不会出现错误的,返回类型为 `void`。其它的解析函数会返回错误码,传递至顶层。 -## 关于断言 +## 8. 关于断言 断言(assertion)是 C 语言中常用的防御式编程方式,减少编程错误。最常用的是在函数开始的地方,检测所有参数。有时候也可以在调用函数后,检查上下文是否正确。 @@ -437,7 +437,7 @@ assert(x++ == 0); /* 这是错误的! */ 另一个问题是,初学者可能会难于分辨何时使用断言,何时处理运行时错误(如返回错误值或在 C++ 中抛出异常)。简单的答案是,如果那个错误是由于程序员错误编码所造成的(例如传入不合法的参数),那么应用断言;如果那个错误是程序员无法避免,而是由运行时的环境所造成的,就要处理运行时错误(例如开启文件失败)。 -## 总结与练习 +## 9. 总结与练习 本文介绍了如何配置一个编程环境,单元测试的重要性,以至于一个 JSON 解析器的子集实现。如果你读到这里,还未动手,建议你快点试一下。以下是本单元的练习,很容易的,但我也会在稍后发出解答篇。 @@ -445,7 +445,7 @@ assert(x++ == 0); /* 这是错误的! */ 2. 参考 `test_parse_null()`,加入 `test_parse_true()`、`test_parse_false()` 单元测试。 3. 参考 `lept_parse_null()` 的实现和调用方,解析 true 和 false 值。 -## 常见问答 +## 10. 常见问答 1. 为什么把例子命名为 leptjson? From 7157a343f0959f362619f99ba2f9376aa6d33d22 Mon Sep 17 00:00:00 2001 From: imba-tjd <109224573@qq.com> Date: Mon, 3 Sep 2018 20:02:09 +0800 Subject: [PATCH 48/66] Update tutorial02.md: Fix headers level --- tutorial02/tutorial02.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 6eb7d8d7..83a1abdd 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -16,7 +16,7 @@ 7. [参考](#参考) 8. [常见问题](#常见问题) -# 1. 初探重构 +## 1. 初探重构 在讨论解析数字之前,我们再补充 TDD 中的一个步骤──重构(refactoring)。根据[1],重构是一个这样的过程: @@ -45,7 +45,7 @@ static void test_parse_expect_value() { 最后,我希望指出,软件的架构难以用单一标准评分,重构时要考虑平衡各种软件品质。例如上述把 3 个函数合并后,优点是减少重复的代码,维护较容易,但缺点可能是带来性能的少量影响。 -# 2. JSON 数字语法 +## 2. JSON 数字语法 回归正题,本单元的重点在于解析 JSON number 类型。我们先看看它的语法: @@ -70,7 +70,7 @@ JSON 标准 [ECMA-404](https://www.ecma-international.org/publications/files/ECM 上一单元的 null、false、true 在解析后,我们只需把它们存储为类型。但对于数字,我们要考虑怎么存储解析后的结果。 -# 3. 数字表示方式 +## 3. 数字表示方式 从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 `double` 类型)来存储 JSON 数字。 @@ -94,7 +94,7 @@ double lept_get_number(const lept_value* v) { 使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。 -# 4. 单元测试 +## 4. 单元测试 我们定义了 API 之后,按照 TDD,我们可以先写一些单元测试。这次我们使用多行的宏的减少重复代码: @@ -149,7 +149,7 @@ static void test_parse_invalid_value() { } ~~~ -# 5. 十进制转换至二进制 +## 5. 十进制转换至二进制 我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 @@ -192,7 +192,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { } ~~~ -# 6. 总结与练习 +## 6. 总结与练习 本单元讲述了 JSON 数字类型的语法,以及 leptjson 所采用的自行校验+`strtod()`转换为 `double` 的方案。实际上一些 JSON 库会采用更复杂的方案,例如支持 64 位带符号/无符号整数,自行实现转换。以我的个人经验,解析/生成数字类型可以说是 RapidJSON 中最难实现的部分,也是 RapidJSON 高效性能的原因,有机会再另外撰文解释。 @@ -214,13 +214,13 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 -# 7. 参考 +## 7. 参考 [1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:《重构:改善既有代码的设计》,熊节译,人民邮电出版社,2010年。 [2] Gay, David M. "Correctly rounded binary-decimal and decimal-binary conversions." Numerical Analysis Manuscript 90-10 (1990). -# 8. 常见问题 +## 8. 常见问题 1. 为什么要把一些测试代码以 `#if 0 ... #endif` 禁用? From ca12f4c1f4a6331157ddbd93707eab99e1cb66c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 3 Sep 2018 20:21:07 +0800 Subject: [PATCH 49/66] Update All: Fix TOC --- tutorial01/tutorial01.md | 20 ++++++++++---------- tutorial02/tutorial02.md | 16 ++++++++-------- tutorial03/tutorial03.md | 16 ++++++++-------- tutorial07/tutorial07.md | 8 ++++++++ tutorial08/tutorial08.md | 9 +++++++++ 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 76fc027a..50e940a1 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -7,16 +7,16 @@ 本单元内容: -1. [JSON 是什么](#json-是什么) -2. [搭建编译环境](#搭建编译环境) -3. [头文件与 API 设计](#头文件与-api-设计) -4. [JSON 语法子集](#json-语法子集) -5. [单元测试](#单元测试) -6. [宏的编写技巧](#宏的编写技巧) -7. [实现解析器](#实现解析器) -8. [关于断言](#关于断言) -9. [总结与练习](#总结与练习) -10. [常见问答](#常见问答) +1. [JSON 是什么](#1-json-是什么) +2. [搭建编译环境](#2-搭建编译环境) +3. [头文件与 API 设计](#3-头文件与-api-设计) +4. [JSON 语法子集](#4-json-语法子集) +5. [单元测试](#5-单元测试) +6. [宏的编写技巧](#6-宏的编写技巧) +7. [实现解析器](#7-实现解析器) +8. [关于断言](#8-关于断言) +9. [总结与练习](#9-总结与练习) +10. [常见问答](#10-常见问答) ## 1. JSON 是什么 diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 83a1abdd..820ba35f 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -7,14 +7,14 @@ 本单元内容: -1. [初探重构](#初探重构) -2. [JSON 数字语法](#json-数字语法) -3. [数字表示方式](#数字表示方式) -4. [单元测试](#单元测试) -5. [十进制转换至二进制](#十进制转换至二进制) -6. [总结与练习](#总结与练习) -7. [参考](#参考) -8. [常见问题](#常见问题) +1. [初探重构](#1-初探重构) +2. [JSON 数字语法](#2-json-数字语法) +3. [数字表示方式](#3-数字表示方式) +4. [单元测试](#4-单元测试) +5. [十进制转换至二进制](#5-十进制转换至二进制) +6. [总结与练习](#6-总结与练习) +7. [参考](#7-参考) +8. [常见问题](#8-常见问题) ## 1. 初探重构 diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 58ef0381..8179e0d4 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -7,14 +7,14 @@ 本单元内容: -1. [JSON 字符串语法](#json-字符串语法) -2. [字符串表示](#字符串表示) -3. [内存管理](#内存管理) -4. [缓冲区与堆栈](#缓冲区与堆栈) -5. [解析字符串](#解析字符串) -6. [总结和练习](#总结和练习) -7. [参考](#参考) -8. [常见问题](#常见问题) +1. [JSON 字符串语法](#1-json-字符串语法) +2. [字符串表示](#2-字符串表示) +3. [内存管理](#3-内存管理) +4. [缓冲区与堆栈](#4-缓冲区与堆栈) +5. [解析字符串](#5-解析字符串) +6. [总结和练习](#6-总结和练习) +7. [参考](#7-参考) +8. [常见问题](#8-常见问题) ## 1. JSON 字符串语法 diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index c301abe3..0f9ce4cf 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -5,6 +5,14 @@ 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元。代码位于 [json-tutorial/tutorial07](https://github.com/miloyip/json-tutorial/blob/master/tutorial07)。 +本单元内容: + +1. [JSON 生成器](#1-JSON-生成器) +2. [再利用 lept_context 做动态数组](#2-再利用-lept_context-做动态数组) +3. [生成 null、false 和 true](#3-生成-nullfalse-和-true) +4. [生成数字](#4-生成数字) +5. [总结与练习](#5-总结与练习) + ## 1. JSON 生成器 我们在前 6 个单元实现了一个合乎标准的 JSON 解析器,它把 JSON 文本解析成一个树形数据结构,整个结构以 `lept_value` 的节点组成。 diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md index 60874345..49c5a8d7 100644 --- a/tutorial08/tutorial08.md +++ b/tutorial08/tutorial08.md @@ -5,6 +5,15 @@ 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第八个单元。代码位于 [json-tutorial/tutorial08](https://github.com/miloyip/json-tutorial/blob/master/tutorial08)。 +本单元内容: + +1. [对象键值查询](#1-对象键值查询) +2. [相等比较](#2-相等比较) +3. [复制、移动与交换](#3-复制移动与交换) +4. [动态数组](#4-动态数组) +5. [动态对象](#5-动态对象) +6. [总结与练习](#6-总结与练习) + ## 1. 对象键值查询 我们在第六个单元实现了 JSON 对象的数据结构,它仅为一个 `lept_value` 的数组: From 71812e580dfe40e5a85aba42de3128e3794b2137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Fri, 7 Sep 2018 19:04:18 +0800 Subject: [PATCH 50/66] =?UTF-8?q?Update=20tutorial04=5Fanswer.md:=20?= =?UTF-8?q?=E8=BE=A8=E6=B3=95=20->=20=E5=8A=9E=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial04_answer/tutorial04_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md index 6c75e783..f7020877 100644 --- a/tutorial04_answer/tutorial04_answer.md +++ b/tutorial04_answer/tutorial04_answer.md @@ -35,7 +35,7 @@ static const char* lept_parse_hex4(const char* p, unsigned* u) { } ~~~ -但这个实现会错误地接受 `"\u 123"` 这种不合法的 JSON,因为 `strtol()` 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 `[0-9A-Fa-f]`,或者 `!isspace(*p)`。但为了 `strtol()` 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 `strtod()` 就没辨法,因为它的实现要复杂得多。) +但这个实现会错误地接受 `"\u 123"` 这种不合法的 JSON,因为 `strtol()` 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 `[0-9A-Fa-f]`,或者 `!isspace(*p)`。但为了 `strtol()` 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 `strtod()` 就没办法,因为它的实现要复杂得多。) ## 2. 实现 `lept_encode_utf8()` From 51f0c88632eae1dd39d13966cc845259741620b1 Mon Sep 17 00:00:00 2001 From: imba-tjd <109224573@qq.com> Date: Fri, 7 Sep 2018 22:22:21 +0800 Subject: [PATCH 51/66] Update Unit Test: Fix solidus position for test_parse_invalid_unicode_hex --- tutorial04/test.c | 2 +- tutorial04_answer/test.c | 2 +- tutorial05/test.c | 2 +- tutorial05_answer/test.c | 4 ++-- tutorial06/test.c | 2 +- tutorial06_answer/test.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tutorial04/test.c b/tutorial04/test.c index beaa8724..a0c2e54d 100644 --- a/tutorial04/test.c +++ b/tutorial04/test.c @@ -191,7 +191,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); diff --git a/tutorial04_answer/test.c b/tutorial04_answer/test.c index 46a1d1f7..db947998 100644 --- a/tutorial04_answer/test.c +++ b/tutorial04_answer/test.c @@ -191,7 +191,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); diff --git a/tutorial05/test.c b/tutorial05/test.c index 2d4dd21e..62389f0a 100644 --- a/tutorial05/test.c +++ b/tutorial05/test.c @@ -213,7 +213,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); diff --git a/tutorial05_answer/test.c b/tutorial05_answer/test.c index 1a17a2a7..9140ae7c 100644 --- a/tutorial05_answer/test.c +++ b/tutorial05_answer/test.c @@ -240,8 +240,8 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\"") + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); diff --git a/tutorial06/test.c b/tutorial06/test.c index 8d332e45..544eaeb3 100644 --- a/tutorial06/test.c +++ b/tutorial06/test.c @@ -300,7 +300,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); diff --git a/tutorial06_answer/test.c b/tutorial06_answer/test.c index ad4dc6f3..04319804 100644 --- a/tutorial06_answer/test.c +++ b/tutorial06_answer/test.c @@ -300,7 +300,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); From 2dbaf73c2519ee3f5ecc7d13043e142c47718039 Mon Sep 17 00:00:00 2001 From: imba-tjd <109224573@qq.com> Date: Sat, 8 Sep 2018 14:00:48 +0800 Subject: [PATCH 52/66] Update All: Fix TOC --- tutorial06/tutorial06.md | 8 ++++---- tutorial07/tutorial07.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index e83ed259..90cf5714 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -7,7 +7,7 @@ 本单元内容: -1. [JSON 对象](#1-JSON-对象) +1. [JSON 对象](#1-json-对象) 2. [数据结构](#2-数据结构) 3. [重构字符串解析](#3-重构字符串解析) 4. [实现](#4-实现) @@ -33,12 +33,12 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: -| |动态数组 |有序动态数组|平衡树 |哈希表 | -|---------------|:-------:|:----------:|:--------:|:--------------------:| +| |动态数组 |有序动态数组|平衡树 |哈希表 | +|---------------|:-------:|:----------:|:--------:|:--------------------:| |有序 |否 |是 |是 |否 | |自定成员次序 |可 |否 |否 |否 | |初始化 n 个成员|O(n) |O(n log n) |O(n log n)|平均 O(n)、最坏 O(n^2)| -|加入成员 |分摊 O(1)|O(n) |O(log n) |平均 O(1)、最坏 O(n) | +|加入成员 |分摊 O(1)|O(n) |O(log n) |平均 O(1)、最坏 O(n) | |移除成员 |O(n) |O(n) |O(log n) |平均 O(1)、最坏 O(n) | |查询成员 |O(n) |O(log n) |O(log n) |平均 O(1)、最坏 O(n) | |遍历成员 |O(n) |O(n) |O(n) |O(m) | diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index 0f9ce4cf..38e9363b 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -7,7 +7,7 @@ 本单元内容: -1. [JSON 生成器](#1-JSON-生成器) +1. [JSON 生成器](#1-json-生成器) 2. [再利用 lept_context 做动态数组](#2-再利用-lept_context-做动态数组) 3. [生成 null、false 和 true](#3-生成-nullfalse-和-true) 4. [生成数字](#4-生成数字) From c84fe9e1bc5bd3e79e765ca6576ce4c1cabf5586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Sun, 9 Sep 2018 10:32:26 +0800 Subject: [PATCH 53/66] Update tutorial07.md: Remove a space --- tutorial07/tutorial07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index 38e9363b..625bc761 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -156,7 +156,7 @@ leptjson 重复利用了 `lept_context` 中的数据结构作为输出缓冲, 1. 由于有两个地方需要生成字符串(JSON 字符串和对象类型),所以先实现 `lept_stringify_string()`。注意,字符串的语法比较复杂,一些字符必须转义,其他少于 `0x20` 的字符需要转义为 `\u00xx` 形式。 -2. 直接在 `lept_stringify_value()` 的 `switch` 内实现 JSON 数组和对象类型的生成。这些实现里都会递归调用 `lept_stringify_value()` 。 +2. 直接在 `lept_stringify_value()` 的 `switch` 内实现 JSON 数组和对象类型的生成。这些实现里都会递归调用 `lept_stringify_value()`。 3. 在你的 `lept_stringify_string()` 是否使用了多次 `PUTC()`?如果是,它每次输出一个字符时,都要检测缓冲区是否有足够空间(不够时需扩展)。能否优化这部分的性能?这种优化有什么代价么? From 5e5b13880b552de360dd32da9d092c261a790dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Sun, 9 Sep 2018 16:27:16 +0800 Subject: [PATCH 54/66] Update tutorial05_answer.md: Specify code language --- tutorial05_answer/tutorial05_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index 8b27f325..466c4f01 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -184,7 +184,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { 但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: -~~~ +~~~c static void lept_context_push(lept_context* c, const void* data, size_t size); ~~~ From 44557b183acd4f2d40703bc30be95ad4650d1233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Sun, 9 Sep 2018 19:55:24 +0800 Subject: [PATCH 55/66] Update tutorial07_answer.md: Fix typo --- tutorial07_answer/tutorial07_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07_answer/tutorial07_answer.md b/tutorial07_answer/tutorial07_answer.md index b10ea1e3..724779b5 100644 --- a/tutorial07_answer/tutorial07_answer.md +++ b/tutorial07_answer/tutorial07_answer.md @@ -46,7 +46,7 @@ static void lept_stringify_value(lept_context* c, const lept_value* v) { } ~~~ -注意到,十六进位输出的字母可以用大写或小写,我们这里选择了大写,所以 roundstrip 测试时也用大写。但这个并不是必然的,输出小写(用 `"\\u%04x"`)也可以。 +注意到,十六进位输出的字母可以用大写或小写,我们这里选择了大写,所以 roundtrip 测试时也用大写。但这个并不是必然的,输出小写(用 `"\\u%04x"`)也可以。 ## 2. 生成数组和对象 From c5878c190adcb96f51dd9a2fc4e0ccd1d381cce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 15 Oct 2018 14:20:54 +0800 Subject: [PATCH 56/66] =?UTF-8?q?Update=20tutorial04:=20=E6=98=AF=208=20?= =?UTF-8?q?=E4=BD=8D=E5=AD=97=E8=8A=82=20->=20=E4=B8=BA=208=20=E4=BD=8D?= =?UTF-8?q?=EF=BC=881=20=E5=AD=97=E8=8A=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial04/tutorial04.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index a5091176..5009cb6c 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -77,7 +77,7 @@ UTF-8 在网页上的使用率势无可挡: 由于我们的 JSON 库也只支持 UTF-8,我们需要把码点编码成 UTF-8。这里简单介绍一下 UTF-8 的编码方式。 -UTF-8 的编码单元是 8 位字节,每个码点编码成 1 至 4 个字节。它的编码方式很简单,按照码点的范围,把码点的二进位分拆成 1 至最多 4 个字节: +UTF-8 的编码单元为 8 位(1 字节),每个码点编码成 1 至 4 个字节。它的编码方式很简单,按照码点的范围,把码点的二进位分拆成 1 至最多 4 个字节: | 码点范围 | 码点位数 | 字节1 | 字节2 | 字节3 | 字节4 | |:------------------:|:--------:|:--------:|:--------:|:--------:|:--------:| From a3af0e161a92ac3cca5dcf520d5ea480833589a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 22 Oct 2018 17:40:14 +0800 Subject: [PATCH 57/66] Update tutorial01.md: Fix RFC 7159 link --- tutorial01/tutorial01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial01/tutorial01.md b/tutorial01/tutorial01.md index 50e940a1..4665e47e 100644 --- a/tutorial01/tutorial01.md +++ b/tutorial01/tutorial01.md @@ -195,7 +195,7 @@ lept_type lept_get_type(const lept_value* v); ## 4. JSON 语法子集 -下面是此单元的 JSON 语法子集,使用 [RFC7159](http://rfc7159.net/rfc7159) 中的 [ABNF](https://tools.ietf.org/html/rfc5234) 表示: +下面是此单元的 JSON 语法子集,使用 [RFC7159](https://tools.ietf.org/html/rfc7159) 中的 [ABNF](https://tools.ietf.org/html/rfc5234) 表示: ~~~ JSON-text = ws value ws From 5dbd5723600ed047670a5cdd2c55f5b847405f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Wed, 28 Nov 2018 09:10:19 +0800 Subject: [PATCH 58/66] Update test.c --- tutorial05_answer/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05_answer/test.c b/tutorial05_answer/test.c index 9140ae7c..d49419e7 100644 --- a/tutorial05_answer/test.c +++ b/tutorial05_answer/test.c @@ -240,7 +240,7 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\"") + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00/0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); From 21b638445a06273f432530d136380149c1027949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BD=98=E9=95=9B=E5=9C=B3?= <37260254+yongzhenPan@users.noreply.github.com> Date: Mon, 27 May 2019 18:52:02 +0800 Subject: [PATCH 59/66] Update test.c --- tutorial02/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial02/test.c b/tutorial02/test.c index 6e3ebed2..7ed4d66b 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -107,7 +107,7 @@ static void test_parse_root_not_singular() { #if 0 /* invalid number */ - TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'E' or nothing */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); #endif From ee5c60a23f69418fea47ea7b7fe89eb4540dc27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BD=98=E9=95=9B=E5=9C=B3?= <37260254+yongzhenPan@users.noreply.github.com> Date: Mon, 27 May 2019 19:05:24 +0800 Subject: [PATCH 60/66] Update test.c --- tutorial02/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial02/test.c b/tutorial02/test.c index 7ed4d66b..eaa5db69 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -107,7 +107,7 @@ static void test_parse_root_not_singular() { #if 0 /* invalid number */ - TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'E' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' , 'E' , 'e' or nothing */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); #endif From 4b764491f7e45ed9993052f0730f803f4607b3d4 Mon Sep 17 00:00:00 2001 From: Keith Null <20233656+keithnull@users.noreply.github.com> Date: Sat, 31 Aug 2019 17:23:09 +0800 Subject: [PATCH 61/66] Fix some incorrect function names. --- tutorial08/tutorial08.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md index 49c5a8d7..e34b1b50 100644 --- a/tutorial08/tutorial08.md +++ b/tutorial08/tutorial08.md @@ -393,8 +393,8 @@ void lept_remove_object_value(lept_value* v, size_t index); 本单元练习内容: 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 部分列出的所有函数。 +2. 打开 `test_access_array()` 里的 `#if 0`,实现 `lept_insert_array_element()`、`lept_erase_array_element()` 和 `lept_clear_array()`。 +3. 打开 `test_access_object()` 里的 `#if 0`,参考动态数组,实现第 5 部分列出的所有函数。 4. 完成 `lept_copy()` 里的数组和对象的复制部分。 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 6bcb8a2b8db3d8aa74de2dcec4f0760b14450ae2 Mon Sep 17 00:00:00 2001 From: entropy2333 <40735723+entropy2333@users.noreply.github.com> Date: Mon, 12 Apr 2021 00:42:12 +0800 Subject: [PATCH 62/66] orthgonal -> orthogonal --- tutorial08/tutorial08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md index e34b1b50..6ee5e6ee 100644 --- a/tutorial08/tutorial08.md +++ b/tutorial08/tutorial08.md @@ -182,7 +182,7 @@ void lept_copy(lept_value* dst, const lept_value* src) { } ~~~ -C++11 加入了右值引用的功能,可以从语言层面区分复制和移动语意。而在 C 语言中,我们也可以通过实现不同版本的接口(不同名字的函数),实现这两种语意。但为了令接口更简单和正交(orthgonal),我们修改了 `lept_set_object_value()` 的设计,让它返回新增键值对的值指针,所以我们可以用 `lept_copy()` 去复制赋值,也可以简单地改变新增的键值: +C++11 加入了右值引用的功能,可以从语言层面区分复制和移动语意。而在 C 语言中,我们也可以通过实现不同版本的接口(不同名字的函数),实现这两种语意。但为了令接口更简单和正交(orthogonal),我们修改了 `lept_set_object_value()` 的设计,让它返回新增键值对的值指针,所以我们可以用 `lept_copy()` 去复制赋值,也可以简单地改变新增的键值: ~~~c /* 返回新增键值对的指针 */ From 7abb2cac8c4c098fb4de18510b324977f1845155 Mon Sep 17 00:00:00 2001 From: Yu Dou Date: Thu, 26 Aug 2021 11:14:04 +0800 Subject: [PATCH 63/66] =?UTF-8?q?Update=20tutorial07.md=20=E6=88=91?= =?UTF-8?q?=E9=9C=80=E8=BF=98=E9=9C=80=E8=A6=81->=E6=88=91=E4=BB=AC?= =?UTF-8?q?=E8=BF=98=E9=9C=80=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial07/tutorial07.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index 625bc761..224a8beb 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -60,7 +60,7 @@ int lept_stringify(const lept_value* v, char** json, size_t* length) { } ~~~ -生成根节点的值之后,我需还需要加入一个空字符作结尾。 +生成根节点的值之后,我们还需要加入一个空字符作结尾。 如前所述,此 API 还提供了 `length` 可选参数,当传入非空指针时,就能获得生成 JSON 的长度。或许读者会疑问,为什么需要获得长度,我们不是可以用 `strlen()` 获得么?是的,因为 JSON 不会含有空字符(若 JSON 字符串中含空字符,必须转义为 `\u0000`),用 `strlen()` 是没有问题的。但这样做会带来不必要的性能消耗,理想地是避免调用方有额外消耗。 From ae48084ff03eed7899042726f68e5021551ce723 Mon Sep 17 00:00:00 2001 From: Yu Dou Date: Fri, 27 Aug 2021 10:52:17 +0800 Subject: [PATCH 64/66] Update tutorial08.md --- tutorial08/tutorial08.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial08/tutorial08.md b/tutorial08/tutorial08.md index 6ee5e6ee..1738e771 100644 --- a/tutorial08/tutorial08.md +++ b/tutorial08/tutorial08.md @@ -39,7 +39,7 @@ struct lept_member { 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); +lept_value* lept_get_object_value(const lept_value* v, size_t index); ~~~ 在实际使用时,我们许多时候需要查询一个键值是否存在,如存在,要获得其相应的值。我们可以提供一个函数,简单地用线性搜寻实现这个查询功能(时间复杂度 $\mathrm{O}(n)$): @@ -75,7 +75,7 @@ lept_free(&o); 由于一般也是希望获取键对应的值,而不需要索引,我们再加入一个辅助函数,返回类型改为 `lept_value*`: ~~~c -lept_value* lept_find_object_value(lept_value* v, const char* key, size_t klen) { +lept_value* lept_find_object_value(const 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; } From 0c79e51e14c8579516513366e05dd319c6bac671 Mon Sep 17 00:00:00 2001 From: RelaxCN <64476349+relaxcn@users.noreply.github.com> Date: Wed, 15 Dec 2021 15:25:30 +0800 Subject: [PATCH 65/66] Macro argument should be enclosed in parentheses --- tutorial03/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03/test.c b/tutorial03/test.c index ac788aca..92607f50 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -21,7 +21,7 @@ static int test_pass = 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) == 0, expect, actual, "%s") + EXPECT_EQ_BASE(sizeof(expect) - 1 == (alength) && memcmp(expect, actual, alength) == 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") From a24629e88a0fc61a104e97f36369bc1e08ca7dfd Mon Sep 17 00:00:00 2001 From: wuyang Date: Tue, 11 Oct 2022 15:34:02 +0800 Subject: [PATCH 66/66] fix typo --- tutorial02/tutorial02.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial02/tutorial02.md b/tutorial02/tutorial02.md index 820ba35f..46ece0b4 100644 --- a/tutorial02/tutorial02.md +++ b/tutorial02/tutorial02.md @@ -198,9 +198,9 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 此外我们谈及,重构与单元测试是互相依赖的软件开发技术,适当地运用可提升软件的品质。之后的单元还会有相关的话题。 -1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true` 为 `lept_parse_literal()`。 +1. 重构合并 `lept_parse_null()`、`lept_parse_false()`、`lept_parse_true()` 为 `lept_parse_literal()`。 2. 加入 [维基百科双精度浮点数](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples) 的一些边界值至单元测试,如 min subnormal positive double、max double 等。 -3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的程况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 +3. 去掉 `test_parse_invalid_value()` 和 `test_parse_root_not_singular()` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。按 JSON number 的语法在 lept_parse_number() 校验,不符合标准的情况返回 `LEPT_PARSE_INVALID_VALUE` 错误码。 4. 去掉 `test_parse_number_too_big` 中的 `#if 0 ... #endif`,执行测试,证实测试失败。仔细阅读 [`strtod()`](https://en.cppreference.com/w/c/string/byte/strtof),看看怎样从返回值得知数值是否过大,以返回 `LEPT_PARSE_NUMBER_TOO_BIG` 错误码。(提示:这里需要 `#include` 额外两个标准库头文件。) 以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: