From ad9ee017fc9f199ec550b7b382021852569d14ba Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 2 Jun 2018 19:18:30 +0800 Subject: [PATCH 01/34] 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 02/34] 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 03/34] 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 04/34] 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 05/34] 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 06/34] =?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 07/34] 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 08/34] 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 09/34] =?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 10/34] 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 11/34] =?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 12/34] 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 13/34] 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 14/34] =?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 15/34] 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 16/34] 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 17/34] 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 18/34] =?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 19/34] 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 20/34] 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 21/34] 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 22/34] 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 23/34] 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 24/34] =?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 25/34] 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 26/34] 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 27/34] 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 28/34] 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 29/34] 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 30/34] 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 31/34] =?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 32/34] 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 33/34] 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 34/34] 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 的数字语法。建议可使用以下两个宏去简化一下代码: