From 0ee7f3d1a8fb07c4f014deb201f3872ed465cbc9 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Sep 2016 23:26:29 +0800 Subject: [PATCH 01/99] Add tutorial 02 answer --- readme.md | 2 +- tutorial02_answer/CMakeLists.txt | 10 ++ tutorial02_answer/leptjson.c | 95 ++++++++++++++ tutorial02_answer/leptjson.h | 25 ++++ tutorial02_answer/test.c | 141 +++++++++++++++++++++ tutorial02_answer/tutorial02_answer.md | 166 +++++++++++++++++++++++++ 6 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 tutorial02_answer/CMakeLists.txt create mode 100644 tutorial02_answer/leptjson.c create mode 100644 tutorial02_answer/leptjson.h create mode 100644 tutorial02_answer/test.c create mode 100644 tutorial02_answer/tutorial02_answer.md diff --git a/readme.md b/readme.md index 2476652d..0702311d 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ 本教程预计分为 9 个单元,第 1-8 个单元附带练习和解答。 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答编](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 -2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。 +2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答编](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. 解析字符串:使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。 4. Unicode:Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 diff --git a/tutorial02_answer/CMakeLists.txt b/tutorial02_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial02_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial02_answer/leptjson.c b/tutorial02_answer/leptjson.c new file mode 100644 index 00000000..3de12361 --- /dev/null +++ b/tutorial02_answer/leptjson.c @@ -0,0 +1,95 @@ +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, strtod() */ + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') + +typedef struct { + const char* json; +}lept_context; + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +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->n = strtod(c->json, NULL); + if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +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 '\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; + v->type = LEPT_NULL; + 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; + } + } + return ret; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->n; +} diff --git a/tutorial02_answer/leptjson.h b/tutorial02_answer/leptjson.h new file mode 100644 index 00000000..4818278c --- /dev/null +++ b/tutorial02_answer/leptjson.h @@ -0,0 +1,25 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct { + double n; + lept_type type; +}lept_value; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG +}; + +int lept_parse(lept_value* v, const char* json); + +lept_type lept_get_type(const lept_value* v); + +double lept_get_number(const lept_value* v); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial02_answer/test.c b/tutorial02_answer/test.c new file mode 100644 index 00000000..11c6c4e5 --- /dev/null +++ b/tutorial02_answer/test.c @@ -0,0 +1,141 @@ +#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") + +static void test_parse_null() { + lept_value v; + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); +} + +static void test_parse_true() { + lept_value v; + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); +} + +static void test_parse_false() { + lept_value v; + v.type = LEPT_TRUE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value 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));\ + } 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_ERROR(error, json)\ + do {\ + lept_value v;\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); +} + +int main() { + test_parse(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial02_answer/tutorial02_answer.md b/tutorial02_answer/tutorial02_answer.md new file mode 100644 index 00000000..0b21dc79 --- /dev/null +++ b/tutorial02_answer/tutorial02_answer.md @@ -0,0 +1,166 @@ +# 从零开始的 JSON 库教程(二):解析数字解答篇 + +* Milo Yip +* 2016/9/20 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第二个单元解答篇。解答代码位于 [json-tutorial/tutorial02_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial02_answer/)。 + +## 1. 重构合并 + +由于 true / false / null 的字符数量不一样,这个答案以 for 循环作比较,直至 `'\0'`。 + +~~~c +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_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); + /* ... */ + } +} +~~~ + +注意在 C 语言中,数组长度、索引值最好使用 `size_t` 类型,而不是 `int` 或 `unsigned`。 + +你也可以直接传送长度参数 4、5、4,只要能通过测试就行了。 + +## 2. 边界值测试 + +这问题其实涉及一些浮点数类型的细节,例如 IEEE-754 浮点数中,有所谓的 normal 和 subnormal 值,这里暂时不展开讨论了。以下是我加入的一些边界值,可能和同学的不完全一样。 + +~~~ +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"); +~~~ + +另外,这些加入的测试用例,正常的 `strtod()` 都能通过。所以不能做到测试失败、修改实现、测试成功的 TDD 步骤。 + +有一些 JSON 解析器不使用 `strtod()` 而自行转换,例如在校验的同时,记录负号、尾数(整数和小数)和指数,然后 naive 地计算: + +~~~ +int negative = 0; +int64_t mantissa = 0; +int exp = 0; + +/* 解析... 并存储 negative, mantissa, exp */ +v->n = (negative ? -mantissa : mantissa) * pow(10.0, exp); +~~~ + +这种做法会有精度问题。实现正确的答案是很复杂的,RapidJSON 的初期版本也是 naive 的,后来 RapidJSON 就内部实现了三种算法(使用 `kParseFullPrecision` 选项开启),最后一种算法用到了大整数(高精度计算)。有兴趣的同学也可以先尝试做一个 naive 版本,不使用 `strtod()`。之后可再参考 Google 的 [https://github.com/google/double-conversion](double-conversion) 开源项目及相关论文。 + +## 3. 校验数字 + +这条题目是本单元的重点,考验同学是否能把语法手写为校验规则。我详细说明。 + +首先,如同 `lept_parse_whitespace()`,我们使用一个指针 `p` 来表示当前的解析字符位置。这样做有两个好处,一是代码更简单,二是在某些编译器下性能更好(因为不能确定 `c` 会否被改变,从而每次更改 `c->json` 都要做一次间接访问)。如果校验成功,才把 `p` 赋值至 `c->json`。 + +~~~c +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + /* 负号 ... */ + /* 整数 ... */ + /* 小数 ... */ + /* 指数 ... */ + v->n = strtod(c->json, NULL); + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} +~~~ + +我们把语法再看一遍: + +~~~ +number = [ "-" ] int [ frac ] [ exp ] +int = "0" / digit1-9 *digit +frac = "." 1*digit +exp = ("e" / "E") ["-" / "+"] 1*digit +~~~ + +负号最简单,有的话跳过便行: + +~~~c + if (*p == '-') p++; +~~~ + +整数部分有两种合法情况,一是单个 `0`,否则是一个 1-9 再加上任意数量的 digit。对于第一种情况,我们像负数般跳过便行。对于第二种情况,第一个字符必须为 1-9,如果否定的就是不合法的,可立即返回错误码。然后,有多少个 digit 就跳过多少个。 + +~~~c + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } +~~~ + +如果出现小数点,我们跳过该小数点,然后检查它至少应有一个 digit,不是 digit 就返回错误码。跳过首个 digit,我们再检查有没有 digit,有多少个跳过多少个。这里用了 for 循环技巧来做这件事。 + +~~~c + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } +~~~ + +最后,如果出现大小写 `e`,就表示有指数部分。跳过那个 `e` 之后,可以有一个正或负号,有的话就跳过。然后和小数的逻辑是一样的。 + +~~~c + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } +~~~ + +这里用了 18 行代码去做这个校验。当中把一些 if 用一行来排版,而没用采用传统两行缩进风格,我个人认为在不影响阅读时可以这样弹性处理。当然也可分拆成三行: + +~~~c + p++; + while (ISDIGIT(*p)) + p++; +~~~ + +## 4. 数字过大的处理 + +最后这题纯粹是阅读理解题。 + +~~~c +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ + +static int lept_parse_number(lept_context* c, lept_value* v) { + /* ... */ + errno = 0; + v->n = strtod(c->json, NULL); + if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG; + /* ... */ +} +~~~ + +许多时候课本/书籍也不会把每个标准库功能说得很仔细,我想藉此提醒同学要好好看参考文档,学会读文档编程就简单得多![cppreference.com](http://cppreference.com) 是 C/C++ 程序员的宝库。 + +## 5. 总结 + +本单元的习题比上个单元较有挑战性一些,所以我花了多一些篇幅在解答篇。纯以语法来说,数字类型已经是 JSON 中最复杂的类型。如果同学能完成本单元的练习(特别是第 3 条),之后的字符串、数组和对象的语法一定难不到你。然而,接下来也会有一些新挑战,例如内存管理、数据结构、编码等,希望你能满载而归。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From fbb85a4050120f186bb32e09311953d1e1c75b8c Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Sep 2016 23:41:08 +0800 Subject: [PATCH 02/99] Fix link --- 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 0b21dc79..39fbc21a 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -64,7 +64,7 @@ int exp = 0; v->n = (negative ? -mantissa : mantissa) * pow(10.0, exp); ~~~ -这种做法会有精度问题。实现正确的答案是很复杂的,RapidJSON 的初期版本也是 naive 的,后来 RapidJSON 就内部实现了三种算法(使用 `kParseFullPrecision` 选项开启),最后一种算法用到了大整数(高精度计算)。有兴趣的同学也可以先尝试做一个 naive 版本,不使用 `strtod()`。之后可再参考 Google 的 [https://github.com/google/double-conversion](double-conversion) 开源项目及相关论文。 +这种做法会有精度问题。实现正确的答案是很复杂的,RapidJSON 的初期版本也是 naive 的,后来 RapidJSON 就内部实现了三种算法(使用 `kParseFullPrecision` 选项开启),最后一种算法用到了大整数(高精度计算)。有兴趣的同学也可以先尝试做一个 naive 版本,不使用 `strtod()`。之后可再参考 Google 的 [double-conversion](https://github.com/google/double-conversion) 开源项目及相关论文。 ## 3. 校验数字 From 273b0202caa689168fbf91d5400136665e4b23d5 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 21 Sep 2016 00:02:40 +0800 Subject: [PATCH 03/99] typo --- 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 39fbc21a..37451ae9 100644 --- a/tutorial02_answer/tutorial02_answer.md +++ b/tutorial02_answer/tutorial02_answer.md @@ -132,7 +132,7 @@ exp = ("e" / "E") ["-" / "+"] 1*digit } ~~~ -这里用了 18 行代码去做这个校验。当中把一些 if 用一行来排版,而没用采用传统两行缩进风格,我个人认为在不影响阅读时可以这样弹性处理。当然也可分拆成三行: +这里用了 18 行代码去做这个校验。当中把一些 if 用一行来排版,而没用采用传统两行缩进风格,我个人认为在不影响阅读时可以这样弹性处理。当然那些 for 也可分拆成三行: ~~~c p++; From 7027c868fa7efecc7c173a650b446d6e0c4efc23 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 21 Sep 2016 10:04:37 +0800 Subject: [PATCH 04/99] Fix LEPT_PARSE_NUMBER_TOO_BIG --- tutorial02_answer/leptjson.c | 2 +- tutorial02_answer/test.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial02_answer/leptjson.c b/tutorial02_answer/leptjson.c index 3de12361..52285928 100644 --- a/tutorial02_answer/leptjson.c +++ b/tutorial02_answer/leptjson.c @@ -51,7 +51,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } errno = 0; v->n = strtod(c->json, NULL); - if (errno == ERANGE && v->n == HUGE_VAL) return LEPT_PARSE_NUMBER_TOO_BIG; + if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) return LEPT_PARSE_NUMBER_TOO_BIG; v->type = LEPT_NUMBER; c->json = p; return LEPT_PARSE_OK; diff --git a/tutorial02_answer/test.c b/tutorial02_answer/test.c index 11c6c4e5..ac0186d1 100644 --- a/tutorial02_answer/test.c +++ b/tutorial02_answer/test.c @@ -121,6 +121,7 @@ static void test_parse_root_not_singular() { static void test_parse_number_too_big() { TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); } static void test_parse() { From 18b5b0490a5308805554c1a04fc5d34a702050d5 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 21 Sep 2016 10:21:48 +0800 Subject: [PATCH 05/99] formatting --- tutorial02_answer/leptjson.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial02_answer/leptjson.c b/tutorial02_answer/leptjson.c index 52285928..0a2ca875 100644 --- a/tutorial02_answer/leptjson.c +++ b/tutorial02_answer/leptjson.c @@ -51,7 +51,8 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } errno = 0; v->n = strtod(c->json, NULL); - if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) return LEPT_PARSE_NUMBER_TOO_BIG; + if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; v->type = LEPT_NUMBER; c->json = p; return LEPT_PARSE_OK; From 5b61b8f6f5d2e17ea7cee77c3941d9bf147eef79 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 22 Sep 2016 12:00:55 +0800 Subject: [PATCH 06/99] Add tutorial03 --- readme.md | 2 +- tutorial03/CMakeLists.txt | 10 ++ tutorial03/images/makefile | 5 + tutorial03/images/union_layout.dot | 17 ++ tutorial03/images/union_layout.png | Bin 0 -> 22196 bytes tutorial03/leptjson.c | 191 ++++++++++++++++++++ tutorial03/leptjson.h | 47 +++++ tutorial03/test.c | 233 +++++++++++++++++++++++++ tutorial03/tutorial03.md | 270 +++++++++++++++++++++++++++++ 9 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 tutorial03/CMakeLists.txt create mode 100644 tutorial03/images/makefile create mode 100644 tutorial03/images/union_layout.dot create mode 100644 tutorial03/images/union_layout.png create mode 100644 tutorial03/leptjson.c create mode 100644 tutorial03/leptjson.h create mode 100644 tutorial03/test.c create mode 100644 tutorial03/tutorial03.md diff --git a/readme.md b/readme.md index 0702311d..c6ef083f 100644 --- a/readme.md +++ b/readme.md @@ -40,7 +40,7 @@ 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答编](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答编](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 -3. 解析字符串:使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。 +3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。 4. Unicode:Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 diff --git a/tutorial03/CMakeLists.txt b/tutorial03/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial03/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/tutorial03/images/makefile b/tutorial03/images/makefile new file mode 100644 index 00000000..55ed2163 --- /dev/null +++ b/tutorial03/images/makefile @@ -0,0 +1,5 @@ +%.png: %.dot + dot $< -Tpng -o $@ + +DOTFILES = $(basename $(wildcard *.dot)) +all: $(addsuffix .png, $(DOTFILES)) diff --git a/tutorial03/images/union_layout.dot b/tutorial03/images/union_layout.dot new file mode 100644 index 00000000..2fcc402a --- /dev/null +++ b/tutorial03/images/union_layout.dot @@ -0,0 +1,17 @@ +digraph { + rankdir=LR + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.5 + nodesep=1 + penwidth=0.5 + + node [shape=Mrecord, fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, style=filled, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + old [fillcolor=6, label="double n (8 bytes) |const char* s (4 bytes) |size_t len (4 bytes)|int type (4 bytes)"] + new [fillcolor=5, label="{double n (8 bytes)|{const char* s (4 bytes)|size_t len (4 bytes)}}|int type (4 bytes)"] + old -> new [style=invis] +} \ No newline at end of file diff --git a/tutorial03/images/union_layout.png b/tutorial03/images/union_layout.png new file mode 100644 index 0000000000000000000000000000000000000000..d55984335255606c24ddf34e470b66615f59fcb6 GIT binary patch literal 22196 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 b2z^f=igaEdi%b;I(B4s1(oigyfA#)f_hW2e literal 0 HcmV?d00001 diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c new file mode 100644 index 00000000..722d4ec0 --- /dev/null +++ b/tutorial03/leptjson.c @@ -0,0 +1,191 @@ +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\0': + c->top = head; + return LEPT_PARSE_MISS_QUOTATION_MARK; + default: + PUTC(c, ch); + } + } +} + +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 '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + /* \TODO */ + return 0; +} + +void lept_set_boolean(lept_value* v, int b) { + /* \TODO */ +} + +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) { + /* \TODO */ +} + +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; +} diff --git a/tutorial03/leptjson.h b/tutorial03/leptjson.h new file mode 100644 index 00000000..d1d4e9d1 --- /dev/null +++ b/tutorial03/leptjson.h @@ -0,0 +1,47 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct { + union { + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}lept_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 +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial03/test.c b/tutorial03/test.c new file mode 100644 index 00000000..e7fa66a9 --- /dev/null +++ b/tutorial03/test.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +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\""); +#if 0 + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\"\\/\b\f\n\r\t", "\"\\\"\\\\/\\b\\f\\n\\r\\t\""); +#endif +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_missing_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { +#if 0 + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +#endif +} + +static void test_parse_invalid_string_char() { +#if 0 + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +#endif +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + /* \TODO */ + /* Use EXPECT_TRUE() and EXPECT_FALSE() */ +} + +static void test_access_number() { + /* \TODO */ +} + +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_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_missing_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { + test_parse(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md new file mode 100644 index 00000000..2ad21c41 --- /dev/null +++ b/tutorial03/tutorial03.md @@ -0,0 +1,270 @@ +# 从零开始的 JSON 库教程(二):解析字符串 + +* Milo Yip +* 2016/9/22 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第三个单元。本单元的练习源代码位于 [json-tutorial/tutorial03](https://github.com/miloyip/json-tutorial/blob/master/tutorial03)。 + +本单元内容: + +## 1. JSON 字符串语法 + +JSON 的字符串语法和 C 语言很相似,都是以双引号把字符括起来,如 `"Hello"`。但字符串采用了双引号作分隔,那么怎样可以在字符串中插入一个双引号? 把 `a"b` 写成 `"a"b"` 肯定不行,都不知道那里是字符串的结束了。因此,我们需要引入转义字符(escape character),C 语言和 JSON 都使用 `\`(反斜线)作为转义字符,那么 `"` 在字符串中就表示为 `\"`,`a"b` 的 JSON 字符串则写成 `"a\"b"`。如以下的字符串语法所示,JSON 共支持 9 种转义序列: + +~~~ +string = quotation-mark *char quotation-mark +char = unescaped / + escape ( + %x22 / ; " quotation mark U+0022 + %x5C / ; \ reverse solidus U+005C + %x2F / ; / solidus U+002F + %x62 / ; b backspace U+0008 + %x66 / ; f form feed U+000C + %x6E / ; n line feed U+000A + %x72 / ; r carriage return U+000D + %x74 / ; t tab U+0009 + %x75 4HEXDIG ) ; uXXXX U+XXXX +escape = %x5C ; \ +quotation-mark = %x22 ; " +unescaped = %x20-21 / %x23-5B / %x5D-10FFFF +~~~ + +简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分开无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 + +无转义字符就是普通的字符,语法中列出了合法的码点范围(码点还是在下单元才介绍)。要注意的是,该范围不包括 0 至 31、双引号和反斜线,这些码点都必须要使用转义方式表示。 + +## 2. 字符串表示 + +在 C 语言中,字符串一般表示为空结尾字符串(null-terminated string),即以空字符(`'\0'`)代表字符串的结束。然而,JSON 字符串是允许含有空字符的,例如这个 JSON `"Hello\u0000World"` 就是单个字符串,解析后为11个字符。如果纯粹使用空结尾字符来表示 JSON 解析后的结果,就没法处理空字符。 + +因此,我们可以分配内存来储存解析后的字符,以及记录字符的数目(即字符串长度)。由于大部分 C 程序都假设字符串是空结尾字符串,我们还是在最后加上一个空字符,那么不需处理 `\u0000` 这种字符的应用可以简单地把它当作是空结尾字符串。 + +了解需求后,我们考虑实现。`lept_value` 事实上是一种变体类型(variant type),我们通过 `type` 来决定它现时是哪种类型,而这也决定了哪些成员是有效的。首先我们简单地在这个结构中加入两个成员: + +~~~c +typedef struct { + char* s; + size_t len; + double n; + lept_type type; +}lept_value; +~~~ + +然而我们知道,一个值不可能同时为数字和字符串,因此我们可使用 C 语言的 `union` 来节省内存: + +~~~c +typedef struct { + union { + struct { char* s; size_t len; }s; /* string */ + double n; /* number */ + }u; + lept_type type; +}lept_value; +~~~ + +这两种设计在 32 位平台时的内存布局如下,可看出右方使用 `union` 的能省下内存。 + +![union_layout](images/union_layout.png) + +我们要把之前的 `v->n` 改成 `v->u.n`。而要访问字符串的数据,则要使用 `v->u.s.s` 和 `v->u.s.len`。这种写法比较麻烦吧,其实 C11 新增了匿名 struct/union 语法,就可以采用 `v->n`、`v->s`、`v->len` 来作访问。 + +## 3. 内存管理 + +由于字符串的长度不是固定的,我们要动态分配内存。为简单起见,我们使用标准库 `` 中的 `malloc()`、`realloc()` 和 `free()` 来分配/释放内存。 + +当设置一个值为字符串时,我们需要把参数中的字符串复制一份: + +~~~c +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; +} +~~~ + +断言中的条件是,非空指针(有具体的字符串)或是零长度的字符串都是合法的。 + +注意,在设置这个 `v` 之前,我们需要先调用 `lept_free(v)` 去清空 `v` 可能分配到的内存。例如原来已有一字符串,我们要先把它释放。然后就是简单地用 `malloc()` 分配及用 `memcpy()` 复制,并补上结尾空字符。`malloc(len + 1)` 中的 1 是因为结尾空字符。 + +那么,再看看 `lept_free()`: + +~~~c +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} +~~~ + +现时仅当值是字符串类型,我们才要处理,之后我们还要加上对数组及对象的释放。`lept_free(v)` 之后,会把它的类型变成 null。这个设计能避免重复释放。 + +但也由于我们会检查 `v` 的类型,在调用所有访问函数之前,我们必须初始化该类型。所以我们加入 `lept_init(v)`,因非常简单我们用宏实现: + +~~~c +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) +~~~ + +用上 `do { ... } while(0)` 是为了把表达式转为语句,模仿无返回值的函数。 + +其实在前两个单元中,我们只提供读取值的 API,没有写入的 API,就是因为写入时我们还要考虑释放内存。我们在本单元中把它们补全: + +~~~c +#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); +~~~ + +由于 `lept_free()` 实际上也会把 `v` 变成 null 值,我们只用一个宏来提供 `lept_set_null()` 这个 API。 + +应用方的代码在调用 `lept_parse()` 之后,最终也应该调用 `lept_free()` 去释放内存。我们把之前的单元测试也加入此调用。 + +如果不使用 `lept_parse()`,我们需要初始化值,那么就像以下的单元测试,先 `lept_init()`,最后 `lept_free()`。 + +~~~c +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); +} +~~~ + +## 4. 缓冲区与堆栈 + +我们解析字符串(以及之后的数组、对象)时,需要把解析的结果先储存在一个临时的缓冲区,最后再用 `lept_set_string()` 把缓冲区的结果设进值之中。在完成解析一个字符串之前,这个缓冲区的大小是不能预知的。因此,我们可以采用动态数组(dynamic array)这种数据结构,即数组空间不足时,能自动扩展。C++ 标准库的 `std::vector` 也是一种动态数组。 + +如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈数据结构。 + +我们把一个动态堆栈的数据放进 `lept_context` 里: + +~~~c +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; +~~~ + +当中 `size` 是当前的堆栈容量,`top` 是栈顶的位置(由于我们会扩展 `stack`,所以不要把 `top` 用指针形式存储)。 + +然后,我们在创建 `lept_context` 的时候初始化 `stack` 并最终释放内存: + +~~~c +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) { + /* ... */ + } + assert(c.top == 0); /* <- */ + free(c.stack); /* <- */ + return ret; +} +~~~ + +在释放时,加入了断言确保所有数据都被弹出。 + +然后,我们实现堆栈的压入及弹出操作。和普通的堆栈不一样,我们这个堆栈是以字节储存的。每次可要求压入任意大小的数据,它会返回数据起始的指针(会 C++ 的同学可再参考[1]): + +~~~ +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +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); +} +~~~ + +压入时若空间不足,便回以 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)` 的,所以我们不需要为第一次分配内存作特别处理。 + +另外,我们把初始大小以宏 `LEPT_PARSE_STACK_INIT_SIZE` 的形式定义,使用 `#ifndef X #define X ... #endif` 方式的好处是,使用者可在编译选项中自行设置宏,没设置的话就用缺省值。 + +## 5. 解析字符串 + +有了以上的工具,解析字符串的任务就变得很简单。我们只需要先备份栈顶,然后把解析到的字符压栈,最后计算出长度并一次性把所有字符弹出,再设置至值里便可以。以下是部分实现,没有处理转义和一些不合法字符的校验。 + +~~~c +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\0': + c->top = head; + return LEPT_PARSE_MISS_QUOTATION_MARK; + default: + PUTC(c, ch); + } + } +} +~~~ + +## 6. 总结和练习 + +之前的单元都是固定长度的数据类型(fixed length data type),而字符串类型是可变长度的数据类型(variable length data type),因此本单元花了较多篇幅讲述内存管理和数据结构的设计和实现。字符串的解析相对数字简单,以下的习题难度不高,同学们应该可轻松完成。 + +1. 编写 `lept_get_boolean()` 等访问函数的单元测试,然后实现。 +2. 实现除了 `\u` 以外的转义序列解析,令 `test_parse_string()` 中所有测试通过。 +3. 解决 `test_parse_invalid_string_escape()` 和 `test_parse_invalid_string_char()` 中的失败测试。 +4. 思考如何优化 `test_parse_string()` 的性能,那些优化方法有没有缺点。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 + +## 7. 参考 + +[1] [RapidJSON 代码剖析(一):混合任意类型的堆栈](https://zhuanlan.zhihu.com/p/20029820) + +# 8. 常见问题 + +其他常见问答将会从评论中整理。 From b32de9d7b5ce5dad919d64c4c028999ed4ffed8d Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 22 Sep 2016 12:01:38 +0800 Subject: [PATCH 07/99] Fix title... --- tutorial03/tutorial03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 2ad21c41..60ba944e 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -1,4 +1,4 @@ -# 从零开始的 JSON 库教程(二):解析字符串 +# 从零开始的 JSON 库教程(三):解析字符串 * Milo Yip * 2016/9/22 From 0166f0d82e2f4d8f497b0c74fa31dd368ac63251 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 22 Sep 2016 12:05:36 +0800 Subject: [PATCH 08/99] Add TOC --- tutorial03/tutorial03.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 60ba944e..1014c084 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -7,6 +7,15 @@ 本单元内容: +1. [JSON 字符串语法](#json-字符串语法) +2. [字符串表示](#字符串表示) +3. [内存管理](#内存管理) +4. [缓冲区与堆栈](#缓冲区与堆栈) +5. [解析字符串](#解析字符串) +6. [总结和练习](#总结和练习) +7. [参考](#参考) +8. [常见问题](#常见问题) + ## 1. JSON 字符串语法 JSON 的字符串语法和 C 语言很相似,都是以双引号把字符括起来,如 `"Hello"`。但字符串采用了双引号作分隔,那么怎样可以在字符串中插入一个双引号? 把 `a"b` 写成 `"a"b"` 肯定不行,都不知道那里是字符串的结束了。因此,我们需要引入转义字符(escape character),C 语言和 JSON 都使用 `\`(反斜线)作为转义字符,那么 `"` 在字符串中就表示为 `\"`,`a"b` 的 JSON 字符串则写成 `"a\"b"`。如以下的字符串语法所示,JSON 共支持 9 种转义序列: From ed2d210f065e5f9bbadbe39b3743f9d27641ebf9 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 24 Sep 2016 07:06:02 +0800 Subject: [PATCH 09/99] Change `/` to `\\/` in test_parse_string() #2 --- tutorial03/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03/test.c b/tutorial03/test.c index e7fa66a9..ac788aca 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -109,7 +109,7 @@ static void test_parse_string() { TEST_STRING("Hello", "\"Hello\""); #if 0 TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); - TEST_STRING("\"\\/\b\f\n\r\t", "\"\\\"\\\\/\\b\\f\\n\\r\\t\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); #endif } From 8073fc773c86829cab7c981ec44db6f3a3e4837a Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 20:07:13 +0800 Subject: [PATCH 10/99] Add tutorial03 answer draft --- tutorial03_answer/CMakeLists.txt | 10 ++ tutorial03_answer/leptjson.c | 213 ++++++++++++++++++++++ tutorial03_answer/leptjson.h | 47 +++++ tutorial03_answer/test.c | 238 +++++++++++++++++++++++++ tutorial03_answer/tutorial03_answer.md | 198 ++++++++++++++++++++ 5 files changed, 706 insertions(+) create mode 100644 tutorial03_answer/CMakeLists.txt create mode 100644 tutorial03_answer/leptjson.c create mode 100644 tutorial03_answer/leptjson.h create mode 100644 tutorial03_answer/test.c create mode 100644 tutorial03_answer/tutorial03_answer.md diff --git a/tutorial03_answer/CMakeLists.txt b/tutorial03_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial03_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial03_answer/leptjson.c b/tutorial03_answer/leptjson.c new file mode 100644 index 00000000..d28ce5b3 --- /dev/null +++ b/tutorial03_answer/leptjson.c @@ -0,0 +1,213 @@ +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + default: + c->top = head; + return LEPT_PARSE_INVALID_STRING_ESCAPE; + } + break; + case '\0': + c->top = head; + return LEPT_PARSE_MISS_QUOTATION_MARK; + default: + if ((unsigned char)ch < 0x20) { + c->top = head; + return LEPT_PARSE_INVALID_STRING_CHAR; + } + PUTC(c, ch); + } + } +} + +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 '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} diff --git a/tutorial03_answer/leptjson.h b/tutorial03_answer/leptjson.h new file mode 100644 index 00000000..d1d4e9d1 --- /dev/null +++ b/tutorial03_answer/leptjson.h @@ -0,0 +1,47 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct { + union { + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}lept_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 +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial03_answer/test.c b/tutorial03_answer/test.c new file mode 100644 index 00000000..47d9f18b --- /dev/null +++ b/tutorial03_answer/test.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +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\""); +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_missing_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_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_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_missing_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { + test_parse(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md new file mode 100644 index 00000000..3848c998 --- /dev/null +++ b/tutorial03_answer/tutorial03_answer.md @@ -0,0 +1,198 @@ +# 从零开始的 JSON 库教程(三):解析字符串解答编 + +* Milo Yip +* 2016/9/24 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第三个单元解答编。解答代码位于 [json-tutorial/tutorial03_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial03_answer)。 + +## 1. 访问的单元测试 + +在编写单元测试时,我们故意先把值设为字符串,那么做可以测试设置其他类型时,有没有调用 `lept_free()` 去释放内存。 + +~~~c +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); +} +~~~ + +以下是访问函数的实现: + +~~~c +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; +} +~~~ + +那问题是,如果我们没有调用 `lept_free()`,怎样能发现这些内存泄漏? + +在 Windows 下,Visual C++ 的 C Runtime Library(CRT)可以检测内存泄漏。 + +首先,我们⋯⋯ + +然后,我们删去 `lept_set_boolean()` 中的 `lept_free(v)`: + +~~~c +void lept_set_boolean(lept_value* v, int b) { + /* lept_free(v); */ + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} +~~~ + +在 Linux、OS X 下,我们可以使用 [valgrind](http://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们不用修改代码,只要在命令行执行: + +~~~ +$ valgrind --leak-check=full ./leptjson_test +==22078== Memcheck, a memory error detector +==22078== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. +==22078== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info +==22078== Command: ./leptjson_test +==22078== +--22078-- run: /usr/bin/dsymutil "./leptjson_test" +160/160 (100.00%) passed +==22078== +==22078== HEAP SUMMARY: +==22078== in use at exit: 27,728 bytes in 209 blocks +==22078== total heap usage: 301 allocs, 92 frees, 34,966 bytes allocated +==22078== +==22078== 2 bytes in 1 blocks are definitely lost in loss record 1 of 79 +==22078== at 0x100012EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so) +==22078== by 0x100008F36: lept_set_string (leptjson.c:208) +==22078== by 0x100008415: test_access_boolean (test.c:187) +==22078== by 0x100001849: test_parse (test.c:229) +==22078== by 0x1000017A3: main (test.c:235) +==22078== +... +~~~ + +它发现了在 `test_access_boolean()` 中,由 `lept_set_string()` 分配的 2 个字节(`"a"`)泄漏了。 + +Valgrind 还有很多功能,例如可以发现未初始化变量。我们若在应用程序或测试程序中,忘了调用 `lept_init(&v)`,那么 `v.type` 的值没被初始化,其值是不确定的(indeterministic),一些函数如果读取那个值就会出现问题: + +~~~c +static void test_access_boolean() { + lept_value v; + /* lept_init(&v); */ + lept_set_string(&v, "a", 1); + ... +} +~~~ + +这种错误有时候测试时能正确运行(刚好 `v.type` 被设为 `0`),使我们误以为程序正确,而在发布后一些机器上却可能崩溃。这种误以为正确的假像是很危险的,我们可利用 valgrind 能自动测出来: + +~~~ +$ valgrind --leak-check=full ./leptjson_test +... +==22174== Conditional jump or move depends on uninitialised value(s) +==22174== at 0x100008B5D: lept_free (leptjson.c:164) +==22174== by 0x100008F26: lept_set_string (leptjson.c:207) +==22174== by 0x1000083FE: test_access_boolean (test.c:187) +==22174== by 0x100001839: test_parse (test.c:229) +==22174== by 0x100001793: main (test.c:235) +==22174== +~~~ + +它发现 `lept_free()` 中依靠了一个未初始化的值来跳转,就是 `v.type`,而错误是沿自 `test_access_boolean()`。 + +编写单元测试时,应考虑哪些执行次序会有机会出错,例如内存相关的错误。然后我们可以利用 TDD 的步骤,先令测试失败(以内存工具检测),修正代码,再确认测试是否成功。 + +## 2. 转义序列的解析 + +转义序列的解析很直观,对其他不合法的字符返回 `LEPT_PARSE_INVALID_STRING_ESCAPE`: + +~~~c +static int lept_parse_string(lept_context* c, lept_value* v) { + /* ... */ + for (;;) { + char ch = *p++; + switch (ch) { + /* ... */ + 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; + default: + c->top = head; + return LEPT_PARSE_INVALID_STRING_ESCAPE; + } + break; + /* ... */ + } + } + } +~~~ + +## 3. 不合法的字符串 + +上面已解决不合法转义,余下部分的唯一难度,是要从语法中知道哪些是不合法字符: + +~~~ +unescaped = %x20-21 / %x23-5B / %x5D-10FFFF +~~~ + +当中空缺的 %x22 是双括号,%x5C 是反斜线,都已经处理。所以不合法的字符是 %x00 至 %x1F。我们简单地在 default 里处理: + +~~~c + /* ... */ + default: + if ((unsigned char)ch < 0x20) { + c->top = head; + return LEPT_PARSE_INVALID_STRING_CHAR; + } + PUTC(c, ch); + /* ... */ +~~~ + +注意到 `char` 带不带符号,是实现定义的。如果编译器定义 `char` 为带符号的话,`(unsigned char)ch >= 0x80` 的字符,都会变成负数,并产生 `LEPT_PARSE_INVALID_STRING_CHAR` 错误。我们现时还没有测试 ASCII 以外的字符,所以有没有转型至不带符号都不影响,但下一单元开始处理 Unicode 的时候就要考虑了。 + +## 4. 性能优化的思考 + +这是本教程第一次的开放式问题,没有标准答案。以下列出一些我想到的。 + +1. 如果整个字符串都没有转义符,我们不就是把字符复制了两次?第一次是从 `json` 到 `stack`,第二次是从 `stack` 到 `v->u.s.s`。我们可以在 `json` 扫瞄 `'\0'`、`'\"'` 和 `'\\'` 3 个字符( `ch < 0x20` 还是要检查),直至它们其中一个出现,才开始用现在的解析方法。这样做的话,前半没转义的部分可以只复制一次。缺点是,代码变得复杂一些,我们也不能使用 `lept_set_string()`。 +2. 对于扫瞄没转义部分,我们可考虑用 SIMD 加速,如 [RapidJSON 代码剖析(二):使用 SSE4.2 优化字符串扫描](https://zhuanlan.zhihu.com/p/20037058) 的做法。这类底层优化的缺点是不跨平台,需要设置编译选项等。 +3. 在 gcc/clang 上使用 `__builtin_expect()` 指令来处理低概率事件,例如需要对每个字符做 `LEPT_PARSE_INVALID_STRING_CHAR` 检测,我们可以假设出现不合法字符是低概率事件,然后用这个指令告之编译器,那么编译器可能可生成较快的代码。然而,这类做法明显是不跨编译器,甚至是某个版本后的 gcc 才支持。 + +## 5. 总结 + +本解答篇除了给出一些建议方案,也介绍了内存泄漏的检测方法。JSON 字符串本身的语法并不复杂,但它需要相关的内存分配与数据结构的设计,还好这些设计都能用于之后的数组和对象类型。下一单元专门针对 Unicode,这部分也是许多 JSON 库没有妥善处理的地方。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From b3f9de6eda56a81824be5c79427f0f0fb7c380f7 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 21:11:19 +0800 Subject: [PATCH 11/99] Add VC memory leak checking --- tutorial03_answer/leptjson.c | 4 +++ tutorial03_answer/test.c | 9 +++++- tutorial03_answer/tutorial03_answer.md | 40 ++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/tutorial03_answer/leptjson.c b/tutorial03_answer/leptjson.c index d28ce5b3..0f857b82 100644 --- a/tutorial03_answer/leptjson.c +++ b/tutorial03_answer/leptjson.c @@ -1,3 +1,7 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif #include "leptjson.h" #include /* assert() */ #include /* errno, ERANGE */ diff --git a/tutorial03_answer/test.c b/tutorial03_answer/test.c index 47d9f18b..3e91754e 100644 --- a/tutorial03_answer/test.c +++ b/tutorial03_answer/test.c @@ -1,3 +1,7 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif #include #include #include @@ -183,7 +187,7 @@ static void test_access_null() { static void test_access_boolean() { lept_value v; - /* lept_init(&v); */ + lept_init(&v); lept_set_string(&v, "a", 1); lept_set_boolean(&v, 1); EXPECT_TRUE(lept_get_boolean(&v)); @@ -232,6 +236,9 @@ static void test_parse() { } int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif test_parse(); printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); return main_ret; diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 3848c998..1ff194a4 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -58,9 +58,29 @@ void lept_set_number(lept_value* v, double n) { 那问题是,如果我们没有调用 `lept_free()`,怎样能发现这些内存泄漏? -在 Windows 下,Visual C++ 的 C Runtime Library(CRT)可以检测内存泄漏。 +## 1A. Windows 下的内存泄漏检测方法 -首先,我们⋯⋯ +在 Windows 下,可使用 Visual C++ 的 [C Runtime Library(CRT) 检测内存泄漏](https://msdn.microsoft.com/zh-cn/library/x98tx3cf.aspx)。 + +首先,我们在两个 .c 文件首行插入这一段代码: + +~~~c +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +~~~ + +并在 `main()` 开始位置插入: + +~~~c +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif +~~~ + +在 Debug 配置下按 F5 生成、开始调试程序,没有任何异样。 然后,我们删去 `lept_set_boolean()` 中的 `lept_free(v)`: @@ -71,7 +91,21 @@ void lept_set_boolean(lept_value* v, int b) { } ~~~ -在 Linux、OS X 下,我们可以使用 [valgrind](http://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们不用修改代码,只要在命令行执行: +再次按 F5 生成、开始调试程序,在输出会看到内存泄漏信息: + +~~~ +Detected memory leaks! +Dumping objects -> +C:\GitHub\json-tutorial\tutorial03_answer\leptjson.c(212) : {79} normal block at 0x013D9868, 2 bytes long. + Data: 61 00 +Object dump complete. +~~~ + +这正是我们在单元测试中,先设置字符串,然后设布尔值时设释放字符串所分配的内存。比较麻烦的是,它没有显示调用堆栈。从输出信息中 `... {79} ...` 我们知道是第 79 次分配的内存做成问题,我们可以加上 `_CrtSetBreakAlloc(79);` 来调试,那么它便会在第 79 次时中断于分配调用的位置,那时候就能从调用堆栈去找出来龙去脉。 + +## 1B. Linux/OSX 下的内存泄漏检测方法 + +在 Linux、OS X 下,我们可以使用 [valgrind](http://valgrind.org/) 工具(用 `apt-get install valgrind`、 `brew install valgrind`)。我们完全不用修改代码,只要在命令行执行: ~~~ $ valgrind --leak-check=full ./leptjson_test From 1c2f63430f7d6a2984475bd9c3e41a89f488f61e Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 22:03:12 +0800 Subject: [PATCH 12/99] Update tutorial03_answer.md --- tutorial03_answer/tutorial03_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 1ff194a4..1c3956f8 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -1,4 +1,4 @@ -# 从零开始的 JSON 库教程(三):解析字符串解答编 +# 从零开始的 JSON 库教程(三):解析字符串解答篇 * Milo Yip * 2016/9/24 From 98fe958f32a3eb8248b699ab5e5a3282e74875a4 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 22:03:43 +0800 Subject: [PATCH 13/99] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index c6ef083f..ae08010a 100644 --- a/readme.md +++ b/readme.md @@ -38,8 +38,8 @@ 本教程预计分为 9 个单元,第 1-8 个单元附带练习和解答。 -1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答编](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 -2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答编](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 +1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答篇](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 +2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。 4. Unicode:Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 From 2d719670c9cf00363d9b4a07f875b64ece41c5b4 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 22:04:04 +0800 Subject: [PATCH 14/99] Update tutorial03_answer.md --- tutorial03_answer/tutorial03_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 1c3956f8..a2feb2ff 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -1,7 +1,7 @@ # 从零开始的 JSON 库教程(三):解析字符串解答篇 * Milo Yip -* 2016/9/24 +* 2016/9/27 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第三个单元解答编。解答代码位于 [json-tutorial/tutorial03_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial03_answer)。 From b72f696ff47276530bf7ec4f1607723ea764f564 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 22:45:25 +0800 Subject: [PATCH 15/99] Update tutorial03_answer.md --- tutorial03_answer/tutorial03_answer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index a2feb2ff..1760971a 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -221,8 +221,8 @@ unescaped = %x20-21 / %x23-5B / %x5D-10FFFF 这是本教程第一次的开放式问题,没有标准答案。以下列出一些我想到的。 -1. 如果整个字符串都没有转义符,我们不就是把字符复制了两次?第一次是从 `json` 到 `stack`,第二次是从 `stack` 到 `v->u.s.s`。我们可以在 `json` 扫瞄 `'\0'`、`'\"'` 和 `'\\'` 3 个字符( `ch < 0x20` 还是要检查),直至它们其中一个出现,才开始用现在的解析方法。这样做的话,前半没转义的部分可以只复制一次。缺点是,代码变得复杂一些,我们也不能使用 `lept_set_string()`。 -2. 对于扫瞄没转义部分,我们可考虑用 SIMD 加速,如 [RapidJSON 代码剖析(二):使用 SSE4.2 优化字符串扫描](https://zhuanlan.zhihu.com/p/20037058) 的做法。这类底层优化的缺点是不跨平台,需要设置编译选项等。 +1. 如果整个字符串都没有转义符,我们不就是把字符复制了两次?第一次是从 `json` 到 `stack`,第二次是从 `stack` 到 `v->u.s.s`。我们可以在 `json` 扫描 `'\0'`、`'\"'` 和 `'\\'` 3 个字符( `ch < 0x20` 还是要检查),直至它们其中一个出现,才开始用现在的解析方法。这样做的话,前半没转义的部分可以只复制一次。缺点是,代码变得复杂一些,我们也不能使用 `lept_set_string()`。 +2. 对于扫描没转义部分,我们可考虑用 SIMD 加速,如 [RapidJSON 代码剖析(二):使用 SSE4.2 优化字符串扫描](https://zhuanlan.zhihu.com/p/20037058) 的做法。这类底层优化的缺点是不跨平台,需要设置编译选项等。 3. 在 gcc/clang 上使用 `__builtin_expect()` 指令来处理低概率事件,例如需要对每个字符做 `LEPT_PARSE_INVALID_STRING_CHAR` 检测,我们可以假设出现不合法字符是低概率事件,然后用这个指令告之编译器,那么编译器可能可生成较快的代码。然而,这类做法明显是不跨编译器,甚至是某个版本后的 gcc 才支持。 ## 5. 总结 From fb1a5125d7794158e40db16711f2fad77803e4cc Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 27 Sep 2016 23:07:50 +0800 Subject: [PATCH 16/99] Update tutorial03_answer.md --- tutorial03_answer/tutorial03_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 1760971a..574f23d1 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -101,7 +101,7 @@ C:\GitHub\json-tutorial\tutorial03_answer\leptjson.c(212) : {79} normal block at Object dump complete. ~~~ -这正是我们在单元测试中,先设置字符串,然后设布尔值时设释放字符串所分配的内存。比较麻烦的是,它没有显示调用堆栈。从输出信息中 `... {79} ...` 我们知道是第 79 次分配的内存做成问题,我们可以加上 `_CrtSetBreakAlloc(79);` 来调试,那么它便会在第 79 次时中断于分配调用的位置,那时候就能从调用堆栈去找出来龙去脉。 +这正是我们在单元测试中,先设置字符串,然后设布尔值时没释放字符串所分配的内存。比较麻烦的是,它没有显示调用堆栈。从输出信息中 `... {79} ...` 我们知道是第 79 次分配的内存做成问题,我们可以加上 `_CrtSetBreakAlloc(79);` 来调试,那么它便会在第 79 次时中断于分配调用的位置,那时候就能从调用堆栈去找出来龙去脉。 ## 1B. Linux/OSX 下的内存泄漏检测方法 From 9e6861e80d7a139801e2f2f46e83f6e7df9c5350 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 28 Sep 2016 09:28:21 +0800 Subject: [PATCH 17/99] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ae08010a..4a018c94 100644 --- a/readme.md +++ b/readme.md @@ -40,7 +40,7 @@ 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答篇](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 -3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。 +3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. Unicode:Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 From dff6953f52b7758a801a153628c7c30998f43e78 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 29 Sep 2016 09:21:21 +0800 Subject: [PATCH 18/99] Add casting --- tutorial03/leptjson.c | 2 +- tutorial03/tutorial03.md | 4 ++-- tutorial03_answer/leptjson.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 722d4ec0..07f7e2c7 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -96,7 +96,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { switch (ch) { case '\"': len = c->top - head; - lept_set_string(v, lept_context_pop(c, len), len); + lept_set_string(v, (const char*)lept_context_pop(c, len), len); c->json = p; return LEPT_PARSE_OK; case '\0': diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 1014c084..74817e4e 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -197,7 +197,7 @@ int lept_parse(lept_value* v, const char* json) { 然后,我们实现堆栈的压入及弹出操作。和普通的堆栈不一样,我们这个堆栈是以字节储存的。每次可要求压入任意大小的数据,它会返回数据起始的指针(会 C++ 的同学可再参考[1]): -~~~ +~~~c #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 #endif @@ -246,7 +246,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { switch (ch) { case '\"': len = c->top - head; - lept_set_string(v, lept_context_pop(c, len), len); + lept_set_string(v, (const char*)lept_context_pop(c, len), len); c->json = p; return LEPT_PARSE_OK; case '\0': diff --git a/tutorial03_answer/leptjson.c b/tutorial03_answer/leptjson.c index 0f857b82..89117e15 100644 --- a/tutorial03_answer/leptjson.c +++ b/tutorial03_answer/leptjson.c @@ -100,7 +100,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { switch (ch) { case '\"': len = c->top - head; - lept_set_string(v, lept_context_pop(c, len), len); + lept_set_string(v, (const char*)lept_context_pop(c, len), len); c->json = p; return LEPT_PARSE_OK; case '\\': From 5b0dee7d9f05cdb4798e865d7b0b5db31c2aac76 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 01:26:21 +0800 Subject: [PATCH 19/99] Add tutorial04 --- readme.md | 2 +- tutorial04/CMakeLists.txt | 10 + tutorial04/images/Utf8webgrowth.png | Bin 0 -> 37658 bytes tutorial04/leptjson.c | 231 +++++++++++++++++++++++ tutorial04/leptjson.h | 49 +++++ tutorial04/test.c | 279 ++++++++++++++++++++++++++++ tutorial04/tutorial04.md | 149 +++++++++++++++ 7 files changed, 719 insertions(+), 1 deletion(-) create mode 100644 tutorial04/CMakeLists.txt create mode 100644 tutorial04/images/Utf8webgrowth.png create mode 100644 tutorial04/leptjson.c create mode 100644 tutorial04/leptjson.h create mode 100644 tutorial04/test.c create mode 100644 tutorial04/tutorial04.md diff --git a/readme.md b/readme.md index 4a018c94..0fae74a5 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答篇](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 -4. Unicode:Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 +4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 diff --git a/tutorial04/CMakeLists.txt b/tutorial04/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial04/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/tutorial04/images/Utf8webgrowth.png b/tutorial04/images/Utf8webgrowth.png new file mode 100644 index 0000000000000000000000000000000000000000..45c5e086e4ca0e2ccc4d692bb709ce6476c7df72 GIT binary patch literal 37658 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�!LyJ9~Ou6u|c40BwF%T-vgi5a>iR@Wx3j6HxJW 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 QZ7U26p00i_>zopr0JA~C+W-In literal 0 HcmV?d00001 diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c new file mode 100644 index 00000000..0a123bf2 --- /dev/null +++ b/tutorial04/leptjson.c @@ -0,0 +1,231 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + /* \TODO */ + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + /* \TODO */ +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + /* \TODO surrogate handling */ + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + 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 '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} diff --git a/tutorial04/leptjson.h b/tutorial04/leptjson.h new file mode 100644 index 00000000..1a5aa367 --- /dev/null +++ b/tutorial04/leptjson.h @@ -0,0 +1,49 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct { + union { + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}lept_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 +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial04/test.c b/tutorial04/test.c new file mode 100644 index 00000000..9c8bb600 --- /dev/null +++ b/tutorial04/test.c @@ -0,0 +1,279 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +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 */ +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_missing_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01234\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_missing_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md new file mode 100644 index 00000000..02d6984d --- /dev/null +++ b/tutorial04/tutorial04.md @@ -0,0 +1,149 @@ +# 从零开始的 JSON 库教程(四):Unicode + +* Milo Yip +* 2016/10/2 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 + +## 1. Unicode + +在上一个单元,我们已经能解析「一般」的 JSON 字符串,仅仅没有处理 `\uXXXX` 这种转义序列。为了解析这种序列,我们必须了解有关 Unicode 的基本概念。 + +读者应该知道 ASCII,它是一种字符编码,把 128 个字符映射至整数 0 ~ 127。例如,`1` → 49,`A` → 65,`B` → 66 等等。这种 7-bit 字符编码系统非常简单,在计算机中以一个字节存储一个字符。然而,它仅适合美国英语,甚至一些英语中常用的标点符号、重音符号都不能表示,无法表示各国语言,特别是中日韩语等表意文字。 + +在 Unicode 出现之前,各地区制定了不同的编码系统,如中文主要用 GB 2312 和大五码、日文主要用 JIS 等。这样会造成很多不便,例如一个文本信息很难混合各种语言的文字。 + +因此,在上世纪80年代末,Xerox、Apple 等公司开始研究,是否能制定一套多语言的统一编码系统。后来,多个机构成立了 Unicode 联盟,在 1991 年释出 Unicode 1.0,收录了 24 种语言共 7161 个字符。在四分之一个世纪后的 2016年,Unicode 已释出 9.0 版本,收录 135 种语言共 128237 个字符。 + +这些字符被收录为统一字符集(Universal Coded Character Set, UCS),每个字符映射至一个整数码点(code point),码点的范围是 0 至 0x10FFFF,码点又通常记作 U+XXXX,当中 XXXX 为 16 进位数字。例如 `劲` → U+52B2、`峰` → U+5CF0。很明显,UCS 中的字符无法像 ASCII 般以一个字节存储。 + +因此,Unicode 还制定了各种储存码点的方式,这些方式称为 Unicode 转换格式(Uniform Transformation Format, UTF)。现时流行的 UTF 为 UTF-8、UTF-16 和 UTF-32。每种 UTF 会把一个码点储存为一至多个编码单元(code unit)。例如 UTF-8 的编码单元是 8 位的字节、UTF-16 为 16 位、UTF-32 为 32 位。除 UTF-32 外,UTF-8 和 UTF-16 都是可变长度编码。 + +UTF-8 成为现时互联网上最流行的格式,有几个原因: + +1. 它采用字节为编码单元,不会有字节序(endianness)的问题。 +2. 每个 ASCII 字符只需一个字节去储存。 +3. 如果程序原来是以字节方式储存字符,理论上不需要特别改动就能处理 UTF-8 的数据。 + +## 2. 需求 + +由于 UTF-8 的普及性,大部分的 JSON 也通常会以 UTF-8 存储。我们的 JSON 库也会只支持 UTF-8。(RapidJSON 同时支持 UTF-8、UTF-16LE/BE、UTF-32LE/BE、ASCII。) + +C 标准库没有关于 Unicode 的处理功能(C++11 有),我们会实现 JSON 库所需的字符编码处理功能。 + +对于非转义(unescaped)的字符,只要它们不少于 32(0 ~ 31 是不合法的编码单元),我们可以直接复制至结果,这一点我们稍后再说明。我们假设输入是以合法 UTF-8 编码。 + +而对于 JSON字符串中的 `\uXXXX` 是以 16 进制表示码点 U+0000 至 U+FFFF,我们需要: + +1. 解析 4 位十六进制整数为码点; +2. 由于字符串是以 UTF-8 存储,我们要把这个码点编码成 UTF-8。 + +同学可能会发现,4 位的 16 进制数字只能表示 0 至 0xFFFF,但之前我们说 UCS 的码点是从 0 至 0x10FFFF,那怎么能表示多出来的码点? + +其实,U+0000 至 U+FFFF 这组 Unicode 字符称为基本多文种平面(basic multilingual plane, BMP),还有另外 16 个平面。那么 BMP 以外的字符,JSON 会使用代理对(surrogate pair)表示 `\uXXXX\uYYYY`。在 BMP 中,保留了 2048 个代理码点。如果第一个码点是 U+D800 至 U+DBFF,我们便知道它的代码对的高代理项(high surrogate),之后应该伴随一个 U+DC00 至 U+DFFF 的低代理项(low surrogate)。然后,我们用下列公式把代理对 (H, L) 变换成真实的码点: + +~~~ +codepoint = 0x10000 + (H − 0xD800) × 0x400 + (L − 0xDC00) +~~~ + +举个例子,高音谱号字符 `𝄞` → U+1D11E 不是 BMP 之内的字符。在 JSON 中可写成转义序列 `\uD834\uDD1E`,我们解析第一个 `\uD834` 得到码点 U+D834,我们发现它是 U+D800 至 U+DBFF 内的码点,所以它是高代理项。然后我们解析下一个转义序列 `\uDD1E` 得到码点 U+DD1E,它在 U+DC00 至 U+DFFF 之内,是合法的低代理项。我们计算其码点: + +~~~ +H = 0xD834, L = 0xDD1E +codepoint = 0x10000 + (H − 0xD800) × 0x400 + (L − 0xDC00) + = 0x10000 + (0xD834 - 0xD800) × 0x400 + (0xDD1E − 0xDC00) + = 0x10000 + 0x34 × 0x400 + 0x11E + = 0x10000 + 0xD000 + 0x11E + = 0x1D11E +~~~ + +这样就得出这转义序列的码点,然后我们再把它编码成 UTF-8。如果只有高代理项而欠缺低代理项,或是低代理项不在合法码点范围,我们都返回 `LEPT_PARSE_INVALID_UNICODE_SURROGATE` 错误。如果 `\u` 后不是 4 位十六进位数字,则返回 `LEPT_PARSE_INVALID_UNICODE_HEX` 错误。 + +## 3. UTF-8 编码 + +UTF-8 在网页上的使用率势无可挡: + +![ ](images/Utf8webgroth.png) + +(图片来自 [Wikipedia Common](https://commons.wikimedia.org/wiki/File:Utf8webgrowth.svg),数据来自 Google 对网页字符编码的统计。) + +由于我们的 JSON 库也只支持 UTF-8,我们需要把码点编码成 UTF-8。这里简单介绍一下 UTF-8 的编码方式。 + +UTF-8 的编码单元是 8 位字节,每个码点编码成 1 至 4 个字节。它的编码方式很简单,按照码点的范围,把码点的二进位分拆成 1 至最多 4 个字节: + +| 码点范围 | 码点位数 | 字节1 | 字节2 | 字节3 | 字节4 | +|:------------------:|:--------:|:--------:|:--------:|:--------:|:--------:| +| U+0000 ~ U+007F | 7 | 0xxxxxxx | +| U+0080 ~ U+07FF | 11 | 110xxxxx | 10xxxxxx | +| U+0800 ~ U+FFFF | 16 | 1110xxxx | 10xxxxxx | 10xxxxxx | +| U+10000 ~ U+10FFFF | 21 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | + +这个编码方法的好处之一是,码点范围 U+0000 ~ U+007F 编码为一字节,与 ASCII 编码兼容。这范围的 Unicode 码点也是和 ASCII 的字符相同。因此,一个 ASCII 文本也是一个 UTF-8 文本。 + +我们举一个例子解析多字节的情况,欧元符号 `€` → U+20AC: + +1. U+20AC 在 U+0800 ~ U+FFFF 的范围内,应编码成 3 个字节。 +2. U+20AC 的二进位为 10000010101100 +3. 3 个字节的情况我们要 16 位的码点,所以在前面补两个 0,成为 0010000010101100 +4. 按上表把二进位分成 3 组:0010, 000010, 101100 +5. 加上每个字节的前缀:11100010, 10000010, 10101100 +6. 用十六进位表示即:0xE2, 0x82, 0xAC + +对于这例子的范围,对应的 C 代码是这样的: + +~~~c +if (u >= 0x0800 && u <= 0xFFFF) { + OutputByte(0xE0 | ((u >> 12) & 0xFF)); /* 0xE0 = 11000000 */ + OutputByte(0x80 | ((u >> 6) & 0x3F)); /* 0x80 = 10000000 */ + OutputByte(0x80 | ( u & 0x3F)); /* 0x3F = 00111111 */ +} +~~~ + +UTF-8 的解码稍复杂一点,但我们的 JSON 库不会校验 JSON 文本是否符合 UTF-8,所以这里也不展开了。 + +## 4. 实现 `\uXXXX` 解析 + +我们只需要在其它转义符的处理中加入对 `\uXXXX` 的处理: + +~~~c +static int lept_parse_string(lept_context* c, lept_value* v) { + unsigned u; + /* ... */ + for (;;) { + char ch = *p++; + switch (ch) { + /* ... */ + case '\\': + switch (*p++) { + /* ... */ + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + /* \TODO surrogate handling */ + lept_encode_utf8(c, u); + break; + /* ... */ + } + /* ... */ + } + } +} +~~~ + +上面代码的过程很简单,遇到 `\u` 转义时,调用 `lept_parse_hex4()` 解析 4 位十六进数字,存储为码点 `u`。这个函数在成功时返回解析后的文本指针,失败返回 `NULL`。如果失败,就返回 `LEPT_PARSE_INVALID_UNICODE_HEX` 错误。最后,把码点编码成 UTF-8,写进缓冲区。这里没有处理代理对,留作练习。 + +顺带一提,我为 `lept_parse_string()` 做了个简单的重构,把返回错误码的处理抽取为宏: + +~~~c +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) +~~~ + +## 5. 总结与练习 + +本单元介绍了 Unicode 的基本知识,同学应该了解到一些常用的 Unicode 术语,如码点、编码单元、UTF-8、代理对等。这次的练习代码只有个空壳,要由同学填充。完成后应该能通过所有单元测试,届时我们的 JSON 字符串解析就完全符合标准了。 + +1. 实现 `lept_parse_hex4()`,不合法的十六进位数返回 `LEPT_PARSE_INVALID_UNICODE_HEX`。 +2. 按第 3 节谈到的 UTF-8 编码原理,实现 `lept_encode_utf8()`。这函数假设码点在正确范围 U+0000 ~ U+10FFFF(用断言检测)。 +3. 加入对代理对的处理,不正确的代理对范围要返回 `LEPT_PARSE_INVALID_UNICODE_SURROGATE` 错误。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 1f8339b118bc4f537be10268b803e705b187ed2c Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 01:27:56 +0800 Subject: [PATCH 20/99] Fix image link --- tutorial04/tutorial04.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index 02d6984d..8e8b1753 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -63,7 +63,7 @@ codepoint = 0x10000 + (H − 0xD800) × 0x400 + (L − 0xDC00) UTF-8 在网页上的使用率势无可挡: -![ ](images/Utf8webgroth.png) +![ ](images/Utf8webgrowth.png) (图片来自 [Wikipedia Common](https://commons.wikimedia.org/wiki/File:Utf8webgrowth.svg),数据来自 Google 对网页字符编码的统计。) From b05b70ff9db704a370f80865414a5c46dff5736e Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 01:39:06 +0800 Subject: [PATCH 21/99] Add TOC --- tutorial04/tutorial04.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index 8e8b1753..91483bad 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -5,6 +5,14 @@ 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 +本单元内容: + +1. [Unicode](#1-unicode) +2. [2.需求](#2-需求) +3. [3. UTF-8 编码](#3-utf-8-编码) +4. [4. 实现 `\uXXXX` 解析](#4-实现-uxxxx-解析) +5. [5. 总结与练习](#5-总结与练习) + ## 1. Unicode 在上一个单元,我们已经能解析「一般」的 JSON 字符串,仅仅没有处理 `\uXXXX` 这种转义序列。为了解析这种序列,我们必须了解有关 Unicode 的基本概念。 @@ -78,7 +86,7 @@ UTF-8 的编码单元是 8 位字节,每个码点编码成 1 至 4 个字节 | U+0800 ~ U+FFFF | 16 | 1110xxxx | 10xxxxxx | 10xxxxxx | | U+10000 ~ U+10FFFF | 21 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | -这个编码方法的好处之一是,码点范围 U+0000 ~ U+007F 编码为一字节,与 ASCII 编码兼容。这范围的 Unicode 码点也是和 ASCII 的字符相同。因此,一个 ASCII 文本也是一个 UTF-8 文本。 +这个编码方法的好处之一是,码点范围 U+0000 ~ U+007F 编码为一个字节,与 ASCII 编码兼容。这范围的 Unicode 码点也是和 ASCII 字符相同的。因此,一个 ASCII 文本也是一个 UTF-8 文本。 我们举一个例子解析多字节的情况,欧元符号 `€` → U+20AC: From 19993ddebf7edfcbc7c4a26d679133fe33e99090 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 01:42:37 +0800 Subject: [PATCH 22/99] Fix TOC --- tutorial04/tutorial04.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index 91483bad..54b1e0ee 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -8,10 +8,10 @@ 本单元内容: 1. [Unicode](#1-unicode) -2. [2.需求](#2-需求) -3. [3. UTF-8 编码](#3-utf-8-编码) -4. [4. 实现 `\uXXXX` 解析](#4-实现-uxxxx-解析) -5. [5. 总结与练习](#5-总结与练习) +2. [需求](#2-需求) +3. [UTF-8 编码](#3-utf-8-编码) +4. [实现 `\uXXXX` 解析](#4-实现-uxxxx-解析) +5. [总结与练习](#5-总结与练习) ## 1. Unicode From 3b12a5a9605ef78dbc4af2ab36adf5d9f3545034 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 02:03:55 +0800 Subject: [PATCH 23/99] Add and fix test --- tutorial04/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04/test.c b/tutorial04/test.c index 9c8bb600..beaa8724 100644 --- a/tutorial04/test.c +++ b/tutorial04/test.c @@ -118,6 +118,7 @@ static void test_parse_string() { 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 */ } #define TEST_ERROR(error, json)\ @@ -186,7 +187,6 @@ static void test_parse_invalid_unicode_hex() { TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); - TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01234\""); 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\""); From 4f113f3cd76dd98e490226241ea923e528a558cb Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 2 Oct 2016 02:06:41 +0800 Subject: [PATCH 24/99] Update tutorial04.md --- tutorial04/tutorial04.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index 54b1e0ee..9cfef1b4 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -3,7 +3,7 @@ * Milo Yip * 2016/10/2 -本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/tree/master/tutorial04)。 本单元内容: From f0aa553bf4fb644af3570631fb738523cc9ab458 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 3 Oct 2016 17:05:52 +0800 Subject: [PATCH 25/99] Update tutorial04.md --- tutorial04/tutorial04.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial04/tutorial04.md b/tutorial04/tutorial04.md index 9cfef1b4..a5091176 100644 --- a/tutorial04/tutorial04.md +++ b/tutorial04/tutorial04.md @@ -101,7 +101,7 @@ UTF-8 的编码单元是 8 位字节,每个码点编码成 1 至 4 个字节 ~~~c if (u >= 0x0800 && u <= 0xFFFF) { - OutputByte(0xE0 | ((u >> 12) & 0xFF)); /* 0xE0 = 11000000 */ + OutputByte(0xE0 | ((u >> 12) & 0xFF)); /* 0xE0 = 11100000 */ OutputByte(0x80 | ((u >> 6) & 0x3F)); /* 0x80 = 10000000 */ OutputByte(0x80 | ( u & 0x3F)); /* 0x3F = 00111111 */ } From 71385aadc71c03653a8ebe31fc8cf980854a4784 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 3 Oct 2016 22:41:14 +0800 Subject: [PATCH 26/99] Update tutorial03_answer.md --- tutorial03_answer/tutorial03_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03_answer/tutorial03_answer.md b/tutorial03_answer/tutorial03_answer.md index 574f23d1..77d56084 100644 --- a/tutorial03_answer/tutorial03_answer.md +++ b/tutorial03_answer/tutorial03_answer.md @@ -202,7 +202,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { unescaped = %x20-21 / %x23-5B / %x5D-10FFFF ~~~ -当中空缺的 %x22 是双括号,%x5C 是反斜线,都已经处理。所以不合法的字符是 %x00 至 %x1F。我们简单地在 default 里处理: +当中空缺的 %x22 是双引号,%x5C 是反斜线,都已经处理。所以不合法的字符是 %x00 至 %x1F。我们简单地在 default 里处理: ~~~c /* ... */ From 23577f6123f4a0311db6161782e7637e03671807 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 6 Oct 2016 14:54:19 +0800 Subject: [PATCH 27/99] Add 04 answer --- readme.md | 2 +- tutorial04_answer/CMakeLists.txt | 10 + tutorial04_answer/leptjson.c | 267 +++++++++++++++++++++++ tutorial04_answer/leptjson.h | 49 +++++ tutorial04_answer/test.c | 280 +++++++++++++++++++++++++ tutorial04_answer/tutorial04_answer.md | 98 +++++++++ 6 files changed, 705 insertions(+), 1 deletion(-) create mode 100644 tutorial04_answer/CMakeLists.txt create mode 100644 tutorial04_answer/leptjson.c create mode 100644 tutorial04_answer/leptjson.h create mode 100644 tutorial04_answer/test.c create mode 100644 tutorial04_answer/tutorial04_answer.md diff --git a/readme.md b/readme.md index 0fae74a5..e091d552 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ 1. [启程](tutorial01/tutorial01.md)(2016/9/15 完成):编译环境、JSON 简介、测试驱动、解析器主要函数及各数据结构。练习 JSON 布尔类型的解析。[启程解答篇](tutorial01_answer/tutorial01_answer.md)(2016/9/17 完成)。 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 -4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。 +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. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 diff --git a/tutorial04_answer/CMakeLists.txt b/tutorial04_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial04_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial04_answer/leptjson.c b/tutorial04_answer/leptjson.c new file mode 100644 index 00000000..590d1220 --- /dev/null +++ b/tutorial04_answer/leptjson.c @@ -0,0 +1,267 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + 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 '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} diff --git a/tutorial04_answer/leptjson.h b/tutorial04_answer/leptjson.h new file mode 100644 index 00000000..1a5aa367 --- /dev/null +++ b/tutorial04_answer/leptjson.h @@ -0,0 +1,49 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct { + union { + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}lept_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 +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial04_answer/test.c b/tutorial04_answer/test.c new file mode 100644 index 00000000..46a1d1f7 --- /dev/null +++ b/tutorial04_answer/test.c @@ -0,0 +1,280 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +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 */ +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_missing_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_missing_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md new file mode 100644 index 00000000..d75bf787 --- /dev/null +++ b/tutorial04_answer/tutorial04_answer.md @@ -0,0 +1,98 @@ +# 从零开始的 JSON 库教程(四):Unicode 解答篇 + +* Milo Yip +* 2016/10/6 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元解答篇。解答代码位于 [json-tutorial/tutorial04_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 + +1. 实现 `lept_parse_hex4()` + +这个函数只是读 4 位 16 进制数字,可以简单地自行实现: + +~~~c +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; +} +~~~ + +可能有同学想到用标准库的 [`strtol()`](http://en.cppreference.com/w/c/string/byte/strtol),因为它也能解析 16 进制数字,那么可以简短的写成: + +~~~c +static const char* lept_parse_hex4(const char* p, unsigned* u) { + char* end; + *u = (unsigned)strtol(p, &end, 16); + return end == p + 4 ? end : NULL; +} +~~~ + +但这个实现会错误地接受 `"\u 123"` 这种不合法的 JSON,因为 `strtol()` 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 `[0-9A-Fa-f]`,或者 `!isspace(*p)`。但为了 `strtol()` 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 `strtod()` 就没辨法,因为它的实现要复杂得多。) + +2. 实现 `lept_encode_utf8()` + +这个函数只需要根据那个 UTF-8 编码表就可以实现: + +~~~c +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)); + } +} +~~~ + +其实超过 1 个字符输出时,可以只调用 1 次 `lept_context_push()`。这里全用 `PUTC()` 只是为了代码看上去简单一点。 + +3. 代理对的处理 + +遇到高代理项,就需要把低代理项 `\uxxxx` 也解析进来,然后用这两个项去计算出码点: + +~~~c +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; +~~~ + +4. 总结 + +JSON 的字符串解析终于完成了。但更重要的是,同学通过教程和练习后,应该对于 Unicode 和 UTF-8 编码有基本了解。使用 Unicode 标准去处理文本数据已是世界潮流。虽然 C11/C++11 引入了 Unicode 字符串字面量及少量函数,但仍然有很多不足,一般需要借助第三方库。 + +我们在稍后的单元还要处理生成时的 Unicode 问题。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 870d538a2410cac373aef22fb56e6c0499dbbbb6 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 6 Oct 2016 14:56:11 +0800 Subject: [PATCH 28/99] Add header --- tutorial04_answer/tutorial04_answer.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md index d75bf787..1bb2c0b5 100644 --- a/tutorial04_answer/tutorial04_answer.md +++ b/tutorial04_answer/tutorial04_answer.md @@ -5,7 +5,7 @@ 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元解答篇。解答代码位于 [json-tutorial/tutorial04_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial04_answer)。 -1. 实现 `lept_parse_hex4()` +## 1. 实现 `lept_parse_hex4()` 这个函数只是读 4 位 16 进制数字,可以简单地自行实现: @@ -37,7 +37,7 @@ static const char* lept_parse_hex4(const char* p, unsigned* u) { 但这个实现会错误地接受 `"\u 123"` 这种不合法的 JSON,因为 `strtol()` 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 `[0-9A-Fa-f]`,或者 `!isspace(*p)`。但为了 `strtol()` 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 `strtod()` 就没辨法,因为它的实现要复杂得多。) -2. 实现 `lept_encode_utf8()` +## 2. 实现 `lept_encode_utf8()` 这个函数只需要根据那个 UTF-8 编码表就可以实现: @@ -66,7 +66,7 @@ static void lept_encode_utf8(lept_context* c, unsigned u) { 其实超过 1 个字符输出时,可以只调用 1 次 `lept_context_push()`。这里全用 `PUTC()` 只是为了代码看上去简单一点。 -3. 代理对的处理 +## 3. 代理对的处理 遇到高代理项,就需要把低代理项 `\uxxxx` 也解析进来,然后用这两个项去计算出码点: @@ -89,10 +89,10 @@ case 'u': break; ~~~ -4. 总结 +## 4. 总结 JSON 的字符串解析终于完成了。但更重要的是,同学通过教程和练习后,应该对于 Unicode 和 UTF-8 编码有基本了解。使用 Unicode 标准去处理文本数据已是世界潮流。虽然 C11/C++11 引入了 Unicode 字符串字面量及少量函数,但仍然有很多不足,一般需要借助第三方库。 -我们在稍后的单元还要处理生成时的 Unicode 问题。 +我们在稍后的单元还要处理生成时的 Unicode 问题,接下来我们要继续讨论数组和对象的解析。 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 69eec01370fcaec4c7d1670cea934c9bf0ee6a66 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 6 Oct 2016 15:07:42 +0800 Subject: [PATCH 29/99] Add paragraph about x & 0xFF --- tutorial04_answer/tutorial04_answer.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tutorial04_answer/tutorial04_answer.md b/tutorial04_answer/tutorial04_answer.md index 1bb2c0b5..e634a069 100644 --- a/tutorial04_answer/tutorial04_answer.md +++ b/tutorial04_answer/tutorial04_answer.md @@ -64,6 +64,8 @@ static void lept_encode_utf8(lept_context* c, unsigned u) { } ~~~ +有同学可能觉得奇怪,最终也是写进一个 `char`,为什么要做 `x & 0xFF` 这种操作呢?这是因为 `u` 是 `unsigned` 类型,一些编译器可能会警告这个转型可能会截断数据。但实际上,配合了范围的检测然后右移之后,可以保证写入的是 0~255 内的值。为了避免一些编译器的警告误判,我们加上 `x & 0xFF`。一般来说,编译器在优化之后,这与操作是会被消去的,不会影响性能。 + 其实超过 1 个字符输出时,可以只调用 1 次 `lept_context_push()`。这里全用 `PUTC()` 只是为了代码看上去简单一点。 ## 3. 代理对的处理 From 2dcfba8016412142c1ba46a42b9cc4399d78fd87 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 7 Oct 2016 21:42:53 +0800 Subject: [PATCH 30/99] Add tutotial05 --- tutorial05/CMakeLists.txt | 10 + tutorial05/images/makefile | 5 + tutorial05/images/parse_array01.dot | 30 +++ tutorial05/images/parse_array01.png | Bin 0 -> 13985 bytes tutorial05/images/parse_array02.dot | 36 ++++ tutorial05/images/parse_array02.png | Bin 0 -> 20175 bytes tutorial05/images/parse_array03.dot | 37 ++++ tutorial05/images/parse_array03.png | Bin 0 -> 21103 bytes tutorial05/images/parse_array04.dot | 39 ++++ tutorial05/images/parse_array04.png | Bin 0 -> 25382 bytes tutorial05/images/parse_array05.dot | 32 +++ tutorial05/images/parse_array05.png | Bin 0 -> 18936 bytes tutorial05/images/parse_array06.dot | 39 ++++ tutorial05/images/parse_array06.png | Bin 0 -> 30309 bytes tutorial05/images/parse_array07.dot | 32 +++ tutorial05/images/parse_array07.png | Bin 0 -> 24394 bytes tutorial05/images/parse_array08.dot | 44 ++++ tutorial05/images/parse_array08.png | Bin 0 -> 30274 bytes tutorial05/images/parse_array09.dot | 42 ++++ tutorial05/images/parse_array09.png | Bin 0 -> 25969 bytes tutorial05/images/parse_array10.dot | 50 +++++ tutorial05/images/parse_array10.png | Bin 0 -> 41552 bytes tutorial05/leptjson.c | 314 ++++++++++++++++++++++++++++ tutorial05/leptjson.h | 56 +++++ tutorial05/test.c | 313 +++++++++++++++++++++++++++ tutorial05/tutorial05.md | 220 +++++++++++++++++++ 26 files changed, 1299 insertions(+) create mode 100644 tutorial05/CMakeLists.txt create mode 100644 tutorial05/images/makefile create mode 100644 tutorial05/images/parse_array01.dot create mode 100644 tutorial05/images/parse_array01.png create mode 100644 tutorial05/images/parse_array02.dot create mode 100644 tutorial05/images/parse_array02.png create mode 100644 tutorial05/images/parse_array03.dot create mode 100644 tutorial05/images/parse_array03.png create mode 100644 tutorial05/images/parse_array04.dot create mode 100644 tutorial05/images/parse_array04.png create mode 100644 tutorial05/images/parse_array05.dot create mode 100644 tutorial05/images/parse_array05.png create mode 100644 tutorial05/images/parse_array06.dot create mode 100644 tutorial05/images/parse_array06.png create mode 100644 tutorial05/images/parse_array07.dot create mode 100644 tutorial05/images/parse_array07.png create mode 100644 tutorial05/images/parse_array08.dot create mode 100644 tutorial05/images/parse_array08.png create mode 100644 tutorial05/images/parse_array09.dot create mode 100644 tutorial05/images/parse_array09.png create mode 100644 tutorial05/images/parse_array10.dot create mode 100644 tutorial05/images/parse_array10.png create mode 100644 tutorial05/leptjson.c create mode 100644 tutorial05/leptjson.h create mode 100644 tutorial05/test.c create mode 100644 tutorial05/tutorial05.md diff --git a/tutorial05/CMakeLists.txt b/tutorial05/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial05/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial05/images/makefile b/tutorial05/images/makefile new file mode 100644 index 00000000..55ed2163 --- /dev/null +++ b/tutorial05/images/makefile @@ -0,0 +1,5 @@ +%.png: %.dot + dot $< -Tpng -o $@ + +DOTFILES = $(basename $(wildcard *.dot)) +all: $(addsuffix .png, $(DOTFILES)) diff --git a/tutorial05/images/parse_array01.dot b/tutorial05/images/parse_array01.dot new file mode 100644 index 00000000..55871bff --- /dev/null +++ b/tutorial05/images/parse_array01.dot @@ -0,0 +1,30 @@ +digraph { + rankdir=TB + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + nodesep=1 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + { + node [shape=record, style=filled, margin=0.1, height=0.3] + json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label=" | | | | | |"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value() \l 3. lept_parse_array()"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] +} \ No newline at end of file diff --git a/tutorial05/images/parse_array01.png b/tutorial05/images/parse_array01.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1ab703d35bf67d6a891e6f715e965b4aa7a077 GIT binary patch literal 13985 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 json:j + ctop -> stack:t + json -> desc [style=invis] + stack -> e [style=invis] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array02.png b/tutorial05/images/parse_array02.png new file mode 100644 index 0000000000000000000000000000000000000000..731bd1246e50b23fb3ad29f79ee6247f43b1b423 GIT binary patch literal 20175 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 literal 0 HcmV?d00001 diff --git a/tutorial05/images/parse_array03.dot b/tutorial05/images/parse_array03.dot new file mode 100644 index 00000000..ddf68667 --- /dev/null +++ b/tutorial05/images/parse_array03.dot @@ -0,0 +1,37 @@ +digraph { + rankdir=TB + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + nodesep=1 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + { + node [shape=record, style=filled, margin=0.1, height=0.3] + json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label="a|b|c|| | |"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_string()"] + } + + { + node [shape=Mrecord,style=filled] + + e [fillcolor=5,label="{null|}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack -> e [style=invis] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array03.png b/tutorial05/images/parse_array03.png new file mode 100644 index 0000000000000000000000000000000000000000..0d0986a848fd66e2c9328b7edd4b29227c844fe8 GIT binary patch literal 21103 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 literal 0 HcmV?d00001 diff --git a/tutorial05/images/parse_array04.dot b/tutorial05/images/parse_array04.dot new file mode 100644 index 00000000..db72a8b5 --- /dev/null +++ b/tutorial05/images/parse_array04.dot @@ -0,0 +1,39 @@ +digraph { + rankdir=TB + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + nodesep=1 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + { + node [shape=record, style=filled, margin=0.1, height=0.3] + json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label=" | | | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_string()"] + } + + { + node [shape=Mrecord,style=filled] + + s [fillcolor=6,label="{string|s|len=3}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack -> s [style=invis] + s:s -> abc:h + } \ No newline at end of file diff --git a/tutorial05/images/parse_array04.png b/tutorial05/images/parse_array04.png new file mode 100644 index 0000000000000000000000000000000000000000..e4238ebe2f5919208a0aa2120ac2c9f4f22adf66 GIT binary patch literal 25382 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,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack:s -> abc:h + } \ No newline at end of file diff --git a/tutorial05/images/parse_array05.png b/tutorial05/images/parse_array05.png new file mode 100644 index 0000000000000000000000000000000000000000..a28d411f6cf351dda63289f1bbfffdddcb05df83 GIT binary patch literal 18936 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 a=~&X^V)0!R_$em@IcX)SN(qzT|7QR&B^THL literal 0 HcmV?d00001 diff --git a/tutorial05/images/parse_array06.dot b/tutorial05/images/parse_array06.dot new file mode 100644 index 00000000..7d244b9d --- /dev/null +++ b/tutorial05/images/parse_array06.dot @@ -0,0 +1,39 @@ +digraph { + rankdir=TB + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + nodesep=1 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + { + node [shape=record, style=filled, margin=0.1, height=0.3] + json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_array()\l 6. lept_parse_value()\l 7. lept_parse_number()"] + } + + { + node [shape=Mrecord,style=filled] + + n1 [fillcolor=7,label="{number|n=1}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack:s -> abc:h + stack -> n1 [style=invis] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array06.png b/tutorial05/images/parse_array06.png new file mode 100644 index 0000000000000000000000000000000000000000..53be0e98fc67dfcbc86b5035d2173c59eab74653 GIT binary patch literal 30309 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,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label="{string|s|len=3}|{number|n=1}| | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l 4. lept_parse_value()\l 5. lept_parse_array()"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack:s -> abc:h + } \ No newline at end of file diff --git a/tutorial05/images/parse_array07.png b/tutorial05/images/parse_array07.png new file mode 100644 index 0000000000000000000000000000000000000000..2b9792fddee0db7b3b0953c115f10b9cd2a82be1 GIT binary patch literal 24394 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 CJ<,|3|]|\\0"] + stack [fillcolor=4, label="{string|s|len=3}| | | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] + } + + { + node [shape=Mrecord,style=filled] + + a2 [fillcolor=2,label="{array|e|size=2}"] + n1 [fillcolor=7,label="{number|n=1}"] + n2 [fillcolor=7,label="{number|n=2}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack:s -> abc:h + a2:e -> n1; + a2 -> n2 [style=invis] + n1 -> n2 [style=dashed,constraint=false] + stack -> a2 [style=invis] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array08.png b/tutorial05/images/parse_array08.png new file mode 100644 index 0000000000000000000000000000000000000000..956c3a215f86e2564116f8540738ee294dd51cfd GIT binary patch literal 30274 zcmdSBWmuKn7A_133L+pa(%m2k(%p@VZWIs%>F$#5?vj%3Zs`_~?p9Jjy3SnkzWeNb zet&;Hujjgi$K{MM$CxATd#oTuc?p!KFQ3A|z@SJ;ib7yu9+APoz;Yly25(gRPxZiG zu(l8hVVKepf?qH&f-q8|Lds6CdudPfu~er0(Vhz21zA-MI2^k*Gqi zOrKycvb$ge9v?D9X=Z73(PXxd<64L9wQT*={P9-STHP*#N zgcJ$$-wz|%&9nb~gT$E*!$=lxJc2Ie1uyxZr+pns5&h^Q0tWVfKJEp5ZOv;NF8Mz* z5-E5E2ljfxBmU?A|NVgg`g%RdR`h?C{M!jmS(NvVToC4e7QF&}O)#2D9{t}d_UgsV zBwITohK2iIiTr)-v_%m0Uu*V)=S_#fMvu5heE#1PsMrHtgn3rj~vbdOE~^FKSc zg1-L$PSn~l?Vph_4X@AkOSzm!lK+*2;Mc(4B4Cv((eMt3ONIn_3xIo zF#!_@uTxn5-G!;pw>MigIbUJWD;-59mZMw%iDuU0pDlmqJLhqK=Z^R0Q=;K$x=x{5 z8I$wzMwA~i?hYBet@Hiu`J{D6M+e69=Op+X7S(&($Y7Nxh=Q$cB`5vmqN8aSEJOM!SAlQGg*}1YW90kvCcw= z%X;ne-1%0DgwWcPP6< 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,|3|]|\\0"] + stack [fillcolor=4, label="{string|s|len=3}|{array|e|size=2}| | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] + } + + { + node [shape=Mrecord,style=filled] + + n1 [fillcolor=7,label="{number|n=1}"] + n2 [fillcolor=7,label="{number|n=2}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack:s -> abc:h + stack:e -> n1; + stack:e -> n2 [style=invis] + n1 -> n2 [style=dashed,constraint=false] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array09.png b/tutorial05/images/parse_array09.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9305431056db5c627ad43a93c286adad5b2626 GIT binary patch literal 25969 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 YQ6rCer;@h8|4@Nw+|f~~P_jn)&Kwi literal 0 HcmV?d00001 diff --git a/tutorial05/images/parse_array10.dot b/tutorial05/images/parse_array10.dot new file mode 100644 index 00000000..d6cc8077 --- /dev/null +++ b/tutorial05/images/parse_array10.dot @@ -0,0 +1,50 @@ +digraph { + rankdir=TB + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + nodesep=1 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5, colorscheme=spectral7] + edge [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + + { + node [shape=record, style=filled, margin=0.1, height=0.3] + json [fillcolor=3, label="[|\"|a|b|c|\"|,|[|1|,|2|]|,|3|]|\\0"] + stack [fillcolor=4, label=" | | | | | |"] + abc [fillcolor=3, label="a|b|c|\\0"] + } + { + node [shape=plaintext, margin=0] + + cjson [label="c->json"] + ctop [label="c->top"] + desc [style=solid,label="\l1. lept_parse()\l 2. lept_parse_value()\l 3. lept_parse_array()\l"] + } + + { + node [shape=Mrecord,style=filled] + + a1 [fillcolor=2,label="{array|e|size=3}"] + s [fillcolor=6,label="{string|s|len=3}"] + a2 [fillcolor=2,label="{array|e|size=2}"] + n1 [fillcolor=7,label="{number|n=1}"] + n2 [fillcolor=7,label="{number|n=2}"] + n3 [fillcolor=7,label="{number|n=3}"] + } + + cjson -> json:j + ctop -> stack:t + json -> desc [style=invis] + stack -> a1 [style=invis] + a1:e -> s + s:s -> abc:h + a2:e -> n1; + a1 -> { a2; n3 } [style=invis] + a2:e -> n2 [style=invis] + n1 -> n2 [style=dashed,constraint=false] + s -> a2 -> n3 [style=dashed,constraint=false] + } \ No newline at end of file diff --git a/tutorial05/images/parse_array10.png b/tutorial05/images/parse_array10.png new file mode 100644 index 0000000000000000000000000000000000000000..c61b974e9de3ec40a72fa8055bcbd577fc2bf826 GIT binary patch literal 41552 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%0A2iTA8nKffr^TH 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 K4Qlk<68;A|zA1qK literal 0 HcmV?d00001 diff --git a/tutorial05/leptjson.c b/tutorial05/leptjson.c new file mode 100644 index 00000000..d2c83f34 --- /dev/null +++ b/tutorial05/leptjson.c @@ -0,0 +1,314 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t size = 0; + int ret; + EXPECT(c, '['); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + return ret; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + if (*c->json == ',') + c->json++; + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else + return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + } +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} diff --git a/tutorial05/leptjson.h b/tutorial05/leptjson.h new file mode 100644 index 00000000..e18f14f7 --- /dev/null +++ b/tutorial05/leptjson.h @@ -0,0 +1,56 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; + +struct lept_value { + union { + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial05/test.c b/tutorial05/test.c new file mode 100644 index 00000000..2d4dd21e --- /dev/null +++ b/tutorial05/test.c @@ -0,0 +1,313 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + 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); +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ +#if 0 + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +#endif +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { +#if 0 + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +#endif +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md new file mode 100644 index 00000000..5228225d --- /dev/null +++ b/tutorial05/tutorial05.md @@ -0,0 +1,220 @@ +# 从零开始的 JSON 库教程(五):解析数组 + +* Milo Yip +* 2016/10/7 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元。代码位于 [json-tutorial/tutorial05](https://github.com/miloyip/json-tutorial/blob/master/tutorial05)。 + +## 1. JSON 数组 + +从零到这第五单元,我们终于要解析一个 JSON 的复合数据类型了。一个 JSON 数组可以包含零至多个元素,而这些元素也可以是数组类型。换句话说,我们可以表示嵌套(nested)的数据结构。先来看看 JSON 数组的语法: + +~~~ +array = %x5B ws [ value *( ws %x2C ws value ) ] ws %x5D +~~~ + +当中,`%x5B` 是左中括号 `[`,`%x2C` 是逗号 `,`,`%x5D` 是右中括号 `]` ,`ws` 是空白字符。一个数组可以包含零至多个值,以逗号分隔,例如 `[]`、`[1,2,true]`、`[[1,2],[3,4],"abc"]` 都是合法的数组。但注意 JSON 不接受末端额外的逗号,例如 `[1,2,]` 是不合法的(许多编程语言如 C/C++、Javascript、Java、C# 都容许数组初始值包含末端逗号)。 + +JSON 数组的语法很简单,实现的难点不在语法上,而是怎样管理内存。 + +## 2. 数据结构 + +首先,我们需要设计存储 JSON 数组类型的数据结构。 + +JSON 数组存储零至多个元素,最简单就是使用 C 语言的数组。数组最大的好处是能以 $O(1)$ 用索引访问任意元素,次要好处是内存布局紧凑,省内存之余还有高缓存一致性(cache coherence)。但数组的缺点是不能快速插入元素,而且我们在解析 JSON 数组的时候,还不知道应该分配多大的数组才合适。 + +另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且片历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 + +我见过一些 JSON 库选择了链表,而这里则选择了数组。我们将会通过之前在解析字符串时实现的堆栈,来解决解析 JSON 数组时未知数组大小的问题。 + +决定之后,我们在 `kept_value` 的 `union` 中加入数组的结构: + +~~~c +typedef struct lept_value lept_value; + +struct lept_value { + union { + struct { lept_value* e; size_t size; }a; /* array */ + struct { char* s; size_t len; }s; + double n; + }u; + lept_type type; +}; +~~~ + +由于 `lept_value` 内使用了自身类型的指针,我们必须前向声明(forward declare)此类型。 + +另外,注意这里 `size` 是元素的个数,不是字节单位。我们增加两个 API 去访问 JSON 数组类型的值: + +~~~c +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} +~~~ + +暂时我们不考虑增删数组元素,这些功能留待第八单元讨论。 + +然后,我们写一个单元测试去试用这些 API(练习需要更多测试)。 + +~~~c +#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_array() { + 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); +} +~~~ + +在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C99)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 + +## 3. 解析过程 + +我们在解析 JSON 字符串时,因为在开始时不能知道字符串的长度,而又需要进行转义,所以需要一个临时缓冲区去存储解析后的结果。我们为此实现了一个动态增长的堆栈,可以不断压入字符,最后一次性把整个字符串弹出,复制至新分配的内存之中。 + +对于 JSON 数组,我们也可以用相同的方法,而且,我们可以用同一个堆栈!我们只需要把每个解析好的元素压入堆栈,解析到数组结束时,再一次性把所有元素弹出,复制至新分配的内存之中。 + +但和字符串有点不一样,如果把 JSON 当作一棵树的数据结构,JSON 字符串是叶节点,而 JSON 数组是中间节点。在叶节点的解析函数中,我们怎样使用那个堆栈也可以,只要最后还原就好了。但对于数组这样的中间节点,共用这个堆栈没问题么? + +答案是:只要在解析函数结束时还原堆栈的状庇,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 + +首先,我们遇到 `[`,进入 `lept_parse_array()`: + +![ ](images/parse_array01.png) + +生成一个临时的 `lept_value`,用于存储之后的元素。我们再调用 `lept_parse_value()` 去解析这个元素值,因为遇到 `"` 进入 `lept_parse_string()`: + +![ ](images/parse_array02.png) + +在 `lept_parse_string()` 中,不断解析字符直至遇到 `"`,过程中把每个字符压栈: + +![ ](images/parse_array03.png) + +最后在 `lept_parse_string()` 中,把栈上 3 个字符弹出,分配内存,生成字符串值: + +![ ](images/parse_array04.png) + +返回上一层 `lept_parse_array()`,把临时元素压栈: + +![ ](images/parse_array05.png) + +然后我们再遇到 `[`,进入另一个 `lept_parse_array()`。它发现第一个元素是数字类型,所认调用 `lept_parse_number()`,生成一个临时的元素值: + +![ ](images/parse_array06.png) + +之后把该临时的元素值压栈: + +![ ](images/parse_array07.png) + +接着再解析第二个元素。我们遇到了 `]`,从栈上弹出 2 个元素,分配内存,生成数组(虚线代表是连续的内存): + +![ ](images/parse_array08.png) + +那个数组是上层数组的元素,我们把它压栈。现时栈内已有两个元素,我们再继续解析下一个元素: + +![ ](images/parse_array09.png) + +最后,遇到了 `]`,可以弹出栈内 3 个元素,分配内存,生成数组: + +![ ](images/parse_array10.png) + +## 4. 实现 + +经过这个详细的图解,实现 `lept_parse_array()` 应该没有难度。以下是半制成品: + +~~~c +static int lept_parse_value(lept_context* c, lept_value* v); /* 前向声明 */ + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t size = 0; + int ret; + EXPECT(c, '['); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + return ret; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + if (*c->json == ',') + c->json++; + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else + return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + } +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + /* ... */ + case '[': return lept_parse_array(c, v); + } +} +~~~ + +简单说明的话,就是在循环中建立一个临时值(`lept_value e`),然后调用 `lept_parse_value()` 去把元素解析至这个临时值,完成后把临时值压栈。当遇到 `]`,把栈内的元素弹出,分配内存,生成数组值。 + +注意到,`lept_parse_value()` 会调用 `lept_parse_array()`,而 `lept_parse_array()` 又会调用 `lept_parse_value()`,这是互相引用,所以必须要加入函数前向声明。 + +最后,我想告诉同学,实现这个函数时,我曾经制造一个不明显的 bug。这个函数有两个 `memcpy()`,第一个「似乎」是可以避免的,先压栈取得元素的指针,给 `lept_parse_value`: + +~~~c + for (;;) { + /* bug! */ + lept_value* e = lept_context_push(c, sizeof(lept_value)); + lept_init(e); + if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) + return ret; + /* ... */ + } +~~~ + +这种写法为什么会有 bug?这是第 5 条练习题。 + +## 5. 总结和练习 + +1. 编写 `test_parse_array()` 单元测试,解析以下 2 个 JSON。由于数组是复合的类型,不能使用一个宏去测试结果,请使用各个 API 检查解析后的内容。 + +~~~js +[ null , false , true , 123 , "abc" ] +[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ] +~~~ + +2. 现时的测试结果应该是失败的,因为 `lept_parse_array()` 里没有处理空白字符,加进合适的 `lept_parse_whitespace()` 令测试通过。 + +3. 使用第四单元介绍的检测内存泄漏工具,会发现测试中有内存泄漏。很明显在 `lept_parse_array()` 中使用到 `malloc()` 分配内存,但却没有对应的 `free()`。应该在哪里释放内存?修改代码,使工具不再检测到相关的内存泄漏。 + +4. 开启 test.c 中两处被 `#if 0 ... #endif` 关闭的测试,本来 `test_parse_array()` 已经能处理这些测试。然而,运行时会发现 `Assertion failed: (c.top == 0)` 断言失败。这是由于,当错误发生时,仍然有一些临时值在堆栈里,既没有放进数组,也没有被释放。修改 `test_parse_array()`,当遇到错误时,从堆栈中弹出并释放那些临时值,然后才返回错误码。 + +5. 第 4 节那段代码为什么会有 bug? + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 53d911e3e77425e3feb4c16b1e60bba5cc2d2f08 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 7 Oct 2016 21:51:59 +0800 Subject: [PATCH 31/99] Add readme link and minor fix --- readme.md | 2 +- tutorial05/tutorial05.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e091d552..be36617d 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 -5. 解析数组:JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 +5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 5228225d..a09b0067 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -192,6 +192,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { /* bug! */ lept_value* e = lept_context_push(c, sizeof(lept_value)); lept_init(e); + size++; if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) return ret; /* ... */ From 6af7159119b958f957326f0ab520d9af4f38f6e9 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 7 Oct 2016 21:54:39 +0800 Subject: [PATCH 32/99] Add TOC --- tutorial05/tutorial05.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index a09b0067..ca5b72b1 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -5,6 +5,14 @@ 本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元。代码位于 [json-tutorial/tutorial05](https://github.com/miloyip/json-tutorial/blob/master/tutorial05)。 +本单元内容: + +1. [JSON 数组](#1-json-数组) +2. [数据结构](#2-数据结构) +3. [解析过程](#3-解析过程) +4. [实现](#4-实现) +5. [总结与练习](#5-总结与练习) + ## 1. JSON 数组 从零到这第五单元,我们终于要解析一个 JSON 的复合数据类型了。一个 JSON 数组可以包含零至多个元素,而这些元素也可以是数组类型。换句话说,我们可以表示嵌套(nested)的数据结构。先来看看 JSON 数组的语法: @@ -201,7 +209,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 这种写法为什么会有 bug?这是第 5 条练习题。 -## 5. 总结和练习 +## 5. 总结与练习 1. 编写 `test_parse_array()` 单元测试,解析以下 2 个 JSON。由于数组是复合的类型,不能使用一个宏去测试结果,请使用各个 API 检查解析后的内容。 From 5170176385980eb1d1383106a0ec52359be3eab7 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 8 Oct 2016 09:20:20 +0800 Subject: [PATCH 33/99] Update tutorial05.md --- tutorial05/tutorial05.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index ca5b72b1..01a83478 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -35,7 +35,7 @@ JSON 数组存储零至多个元素,最简单就是使用 C 语言的数组。 我见过一些 JSON 库选择了链表,而这里则选择了数组。我们将会通过之前在解析字符串时实现的堆栈,来解决解析 JSON 数组时未知数组大小的问题。 -决定之后,我们在 `kept_value` 的 `union` 中加入数组的结构: +决定之后,我们在 `lept_value` 的 `union` 中加入数组的结构: ~~~c typedef struct lept_value lept_value; @@ -220,7 +220,7 @@ static int lept_parse_value(lept_context* c, lept_value* v) { 2. 现时的测试结果应该是失败的,因为 `lept_parse_array()` 里没有处理空白字符,加进合适的 `lept_parse_whitespace()` 令测试通过。 -3. 使用第四单元介绍的检测内存泄漏工具,会发现测试中有内存泄漏。很明显在 `lept_parse_array()` 中使用到 `malloc()` 分配内存,但却没有对应的 `free()`。应该在哪里释放内存?修改代码,使工具不再检测到相关的内存泄漏。 +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()`,当遇到错误时,从堆栈中弹出并释放那些临时值,然后才返回错误码。 From 738b0b1bfac47486e00e496bf8e793313ba4562e Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 8 Oct 2016 20:24:18 +0800 Subject: [PATCH 34/99] Update readme.md Fix #43 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index be36617d..a3e0786b 100644 --- a/readme.md +++ b/readme.md @@ -50,4 +50,4 @@ ## 关于作者 -叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群前台技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmarck](https://github.com/miloyip/nativejson-benchmark) 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。 +叶劲峰(Milo Yip)现任腾讯 T4 专家、互动娱乐事业群魔方工作室群前台技术总监。他获得香港大学认知科学学士(BCogSc)、香港中文大学系统工程及工程管理哲学硕士(MPhil)。他是《游戏引擎架构》译者、《C++ Primer 中文版(第五版)》审校。他曾参与《天涯明月刀》、《斗战神》、《爱丽丝:疯狂回归》、《美食从天降》、《王子传奇》等游戏项目,以及多个游戏引擎及中间件的研发。他是开源项目 [RapidJSON](https://github.com/miloyip/rapidjson) 的作者,开发 [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) 比较 41 个开源原生 JSON 库的标准符合程度及性能。他在 1990 年学习 C 语言,1995 年开始使用 C++ 于各种项目。 From deff395bf840ff7055d2a60eccc386e1a8ed3620 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 10 Oct 2016 21:10:09 +0800 Subject: [PATCH 35/99] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 01a83478..61d8da11 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -99,7 +99,7 @@ static void test_parse_array() { 但和字符串有点不一样,如果把 JSON 当作一棵树的数据结构,JSON 字符串是叶节点,而 JSON 数组是中间节点。在叶节点的解析函数中,我们怎样使用那个堆栈也可以,只要最后还原就好了。但对于数组这样的中间节点,共用这个堆栈没问题么? -答案是:只要在解析函数结束时还原堆栈的状庇,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 +答案是:只要在解析函数结束时还原堆栈的状态,就没有问题。为了直观地了解这个解析过程,我们用连环图去展示 `["abc",[1,2],3]` 的解析过程。 首先,我们遇到 `[`,进入 `lept_parse_array()`: From 314eeec41778e37a28029f4945eec7fb90e9674f Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 13:49:58 +0800 Subject: [PATCH 36/99] Add tutorial05 answer --- tutorial05_answer/CMakeLists.txt | 10 + tutorial05_answer/leptjson.c | 334 ++++++++++++++++++++++++ tutorial05_answer/leptjson.h | 56 ++++ tutorial05_answer/test.c | 339 +++++++++++++++++++++++++ tutorial05_answer/tutorial05_answer.md | 199 +++++++++++++++ 5 files changed, 938 insertions(+) create mode 100644 tutorial05_answer/CMakeLists.txt create mode 100644 tutorial05_answer/leptjson.c create mode 100644 tutorial05_answer/leptjson.h create mode 100644 tutorial05_answer/test.c create mode 100644 tutorial05_answer/tutorial05_answer.md diff --git a/tutorial05_answer/CMakeLists.txt b/tutorial05_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial05_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial05_answer/leptjson.c b/tutorial05_answer/leptjson.c new file mode 100644 index 00000000..3b44d266 --- /dev/null +++ b/tutorial05_answer/leptjson.c @@ -0,0 +1,334 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} diff --git a/tutorial05_answer/leptjson.h b/tutorial05_answer/leptjson.h new file mode 100644 index 00000000..e18f14f7 --- /dev/null +++ b/tutorial05_answer/leptjson.h @@ -0,0 +1,56 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; + +struct lept_value { + union { + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial05_answer/test.c b/tutorial05_answer/test.c new file mode 100644 index 00000000..1a17a2a7 --- /dev/null +++ b/tutorial05_answer/test.c @@ -0,0 +1,339 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md new file mode 100644 index 00000000..72631552 --- /dev/null +++ b/tutorial05_answer/tutorial05_answer.md @@ -0,0 +1,199 @@ +# 从零开始的 JSON 库教程(五):解析数组解答篇 + +* Milo Yip +* 2016/10/13 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第五个单元解答篇。解答代码位于 [json-tutorial/tutorial05_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial05_answer)。 + +## 1. 编写 `test_parse_array()` 单元测试 + +这个练习纯粹为了熟习数组的访问 API。新增的第一个 JSON 只需平凡的检测。第二个 JSON 有特定模式,第 i 个子数组的长度为 i,每个子数组的第 j 个元素是数字值 j,所以可用两层 for 循环测试。 + +~~~c +static void test_parse_array() { + size_t i, j; + lept_value v; + + /* ... */ + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} +~~~ + +## 2. 解析空白字符 + +按现时的 `lept_parse_array()` 的编写方式,需要加入 3 个 `lept_parse_whitespace()` 调用,分别是解析 `[` 之后,元素之后,以及 `,` 之后: + +~~~c +static int lept_parse_array(lept_context* c, lept_value* v) { + /* ... */ + EXPECT(c, '['); + lept_parse_whitespace(c); + /* ... */ + for (;;) { + /* ... */ + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + return ret; + /* ... */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + /* ... */ + } +} +~~~ + +## 3. 内存泄漏 + +成功测试那 3 个 JSON 后,使用内存泄漏检测工具会发现 `lept_parse_array()` 用 `malloc()`分配的内存没有被释放: + +~~~ +==154== 124 (120 direct, 4 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 4 +==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) +==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x405261: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) +==154== +==154== 240 (96 direct, 144 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4 +==154== at 0x4C28C20: malloc (vg_replace_malloc.c:296) +==154== by 0x409D82: lept_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409E91: lept_parse_value (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x409F14: lept_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40582C: test_parse_array (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x408C72: test_parse (in /json-tutorial/tutorial05/build/leptjson_test) +==154== by 0x40916A: main (in /json-tutorial/tutorial05/build/leptjson_test) +~~~ + +很明显,有 `malloc()` 就要有对应的 `free()`。正确的释放位置应该放置在 `lept_free()`,当值被释放时,该值拥有的内存也在那里释放。之前字符串的释放也是放在这里: + +~~~c +void lept_free(lept_value* v) { + assert(v != NULL); + if (v->type == LEPT_STRING) + free(v->u.s.s); + v->type = LEPT_NULL; +} +~~~ + +但对于数组,我们应该先把数组内的元素通过递归调用 `lept_free()` 释放,然后才释放本身的 `v->u.a.e`: + +~~~c +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + default: break; + } + v->type = LEPT_NULL; +} +~~~ + +修改之后,再运行内存泄漏检测工具,确保问题已被修正。 + +## 4. 解析错误时的内存处理 + +遇到解析错误时,我们可能在之前已压入了一些值在自定议堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 + +在 `lept_parse_array` 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 `break` 离开循环,在循环结束后的地方用 `lept_free()` 释放从堆栈弹出的值,然后才返回错误码: + +~~~c +static int lept_parse_array(lept_context* c, lept_value* v) { + /* ... */ + for (;;) { + /* ... */ + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + /* ... */ + if (*c->json == ',') { + /* ... */ + } + else if (*c->json == ']') { + /* ... */ + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} +~~~ + +## 5. bug 的解释 + +这个 bug 源于压栈时,会获得一个指针 `e`,指向从堆栈分配到的空间: + +~~~c + for (;;) { + /* bug! */ + lept_value* e = lept_context_push(c, sizeof(lept_value)); + lept_init(e); + size++; + if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) + return ret; + /* ... */ + } +~~~ + +然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 堆栈。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 + +在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。 + +但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: + +~~~ +static void lept_context_push(lept_context* c, void* data, size_t size); +~~~ + +这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。 + +无论如何,我们编程时都要考虑清楚变量的生命周期,特别是指针的生命周期。 + +## 6. 总结 + +经过对数组的解析,我们也了解到如何利用递归处理复合型的数据类型解析。与一些用链表或自动扩展的动态数组的实现比较,我们利用了自定义堆栈作为缓冲区,能分配最紧凑的数组作存储之用,会比其他实现更省内存。我们完成了数组类型后,只余下对象类型了。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 10c25cf111a731c210dd59cea84df9d62b93abfa Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 13:56:48 +0800 Subject: [PATCH 37/99] Typo --- tutorial05_answer/leptjson.c | 2 +- tutorial05_answer/tutorial05_answer.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial05_answer/leptjson.c b/tutorial05_answer/leptjson.c index 3b44d266..f05f6ff0 100644 --- a/tutorial05_answer/leptjson.c +++ b/tutorial05_answer/leptjson.c @@ -220,7 +220,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { break; } } - /* Pop and free values on stack */ + /* Pop and free values on the stack */ for (i = 0; i < size; i++) lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); return ret; diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index 72631552..73f712f9 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -155,7 +155,7 @@ static int lept_parse_array(lept_context* c, lept_value* v) { break; } } - /* Pop and free values on stack */ + /* Pop and free values on the stack */ for (i = 0; i < size; i++) lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); return ret; From 1f18486301e6f41a6cd95d7416a492757918d466 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 14:01:14 +0800 Subject: [PATCH 38/99] Minor fixes --- tutorial05_answer/tutorial05_answer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index 73f712f9..b284815e 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -178,14 +178,14 @@ static int lept_parse_array(lept_context* c, lept_value* v) { } ~~~ -然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 堆栈。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 +然后,我们把这个指针调用 `lept_parse_value(c, e)`,这里会出现问题,因为 `lept_parse_value()` 及之下的函数都需要调用 `lept_context_push()`,而 `lept_context_push()` 在发现栈满了的时候会用 `realloc()` 扩容。这时候,我们上层的 `e` 就会失效,变成一个悬挂指针(dangling pointer),而且 `lept_parse_value(c, e)` 会通过这个指针写入解析结果,造成非法访问。 在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。 但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 `lept_context_push()` 的 API 改为: ~~~ -static void lept_context_push(lept_context* c, void* data, size_t size); +static void lept_context_push(lept_context* c, const void* data, size_t size); ~~~ 这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。 From 54cb6fdb92be97af1baa85d0723c54c0f3df09af Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 13 Oct 2016 14:02:42 +0800 Subject: [PATCH 39/99] Update readme link --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index be36617d..5f8d8f28 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ 2. [解析数字](tutorial02/tutorial02.md)(2016/9/18 完成):JSON number 的语法。练习 JSON number 类型的校验。[解析数字解答篇](tutorial02_answer/tutorial02_answer.md)(2016/9/20 完成)。 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 -5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。 +5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 From ff1710649172be1e932feed89ad161d5290d7faa Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 14 Oct 2016 12:45:47 +0800 Subject: [PATCH 40/99] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 61d8da11..84cc000f 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -89,7 +89,7 @@ static void test_parse_array() { } ~~~ -在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C99)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 +在之前的单元中,作者已多次重申,C 语言的数组大小应该使用 `size_t` 类型。因为我们要验证 `lept_get_array_size()` 返回值是否正确,所以再为单元测试框架添加一个宏 `EXPECT_EQ_SIZE_T`。麻烦之处在于,ANSI C(C89)并没有的 `size_t` 打印方法,在 C99 则加入了 `"%zu"`,但 VS2015 中才有,之前的 VC 版本使用非标准的 `"%Iu"`。因此,上面的代码使用条件编译去区分 VC 和其他编译器。虽然这部分不跨平台也不是 ANSI C 标准,但它只在测试程序中,不太影响程序库的跨平台性。 ## 3. 解析过程 From 5dc0172b5f0cdf18fe900a49966bef36f0ab3b71 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:28:36 +0800 Subject: [PATCH 41/99] Add tutorial06 --- tutorial06/CMakeLists.txt | 10 + tutorial06/leptjson.c | 389 ++++++++++++++++++++++++++++++++++ tutorial06/leptjson.h | 71 +++++++ tutorial06/test.c | 430 ++++++++++++++++++++++++++++++++++++++ tutorial06/tutorial06.md | 204 ++++++++++++++++++ 5 files changed, 1104 insertions(+) create mode 100644 tutorial06/CMakeLists.txt create mode 100644 tutorial06/leptjson.c create mode 100644 tutorial06/leptjson.h create mode 100644 tutorial06/test.c create mode 100644 tutorial06/tutorial06.md diff --git a/tutorial06/CMakeLists.txt b/tutorial06/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial06/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial06/leptjson.c b/tutorial06/leptjson.c new file mode 100644 index 00000000..64e3bd0d --- /dev/null +++ b/tutorial06/leptjson.c @@ -0,0 +1,389 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string(lept_context* c, lept_value* v) { + size_t head = c->top, len; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + len = c->top - head; + lept_set_string(v, (const char*)lept_context_pop(c, len), len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on the stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + lept_init(&m.v); + /* \todo parse key to m.k, m.klen */ + /* \todo parse ws colon ws */ + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* \todo parse ws [comma | right-curly-brace] ws */ + } + /* \todo Pop and free members on the stack */ + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '{': return lept_parse_object(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +const char* lept_get_object_key(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].k; +} + +size_t lept_get_object_key_length(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].klen; +} + +lept_value* lept_get_object_value(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return &v->u.o.m[index].v; +} diff --git a/tutorial06/leptjson.h b/tutorial06/leptjson.h new file mode 100644 index 00000000..8a6ce68d --- /dev/null +++ b/tutorial06/leptjson.h @@ -0,0 +1,71 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial06/test.c b/tutorial06/test.c new file mode 100644 index 00000000..8d332e45 --- /dev/null +++ b/tutorial06/test.c @@ -0,0 +1,430 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +static void test_parse_object() { + lept_value v; + size_t i; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } ")); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, + " { " + "\"n\" : null , " + "\"f\" : false , " + "\"t\" : true , " + "\"i\" : 123 , " + "\"s\" : \"abc\", " + "\"a\" : [ 1, 2, 3 ]," + "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }" + " } " + )); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v)); + EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0))); + EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1)); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1))); + EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2)); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2))); + EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3))); + EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4)); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), lept_get_string_length(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5)); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5))); + EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5))); + for (i = 0; i < 3; i++) { + lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e)); + } + EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6)); + { + lept_value* o = lept_get_object_value(&v, 6); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o)); + for (i = 0; i < 3; i++) { + lept_value* ov = lept_get_object_value(o, i); + EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]); + EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov)); + } + } + lept_free(&v); +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); +#if 0 + test_parse_object(); +#endif + + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); +#if 0 + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +#endif +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md new file mode 100644 index 00000000..bc792432 --- /dev/null +++ b/tutorial06/tutorial06.md @@ -0,0 +1,204 @@ +# 从零开始的 JSON 库教程(六):解析对象 + +* Milo Yip +* 2016/10/29 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元。代码位于 [json-tutorial/tutorial06](https://github.com/miloyip/json-tutorial/blob/master/tutorial06)。 + +本单元内容: + +1. [JSON 对象](1-JSON-对象) +2. [数据结构](2-数据结构) +3. [重构字符串解析](3-重构字符串解析) +4. [实现](4-实现) +5. [总结与练习](5-总结与练习) + +## 1. JSON 对象 + +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` 包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` 分隔,完整语法如下: + +~~~ +member = string ws %x3A ws value +object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D +~~~ + +## 2. 数据结构 + +要表示键值对的集合,有很多数据结构可供选择,例如: + +* 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 +* 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 +* 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现$O(1)$查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 + +设一个对象有 $n$ 个成员,数据结构的容量是 $m$,$n \le m$,那么一些常用操作的时间/空间复杂度如下: + +| |动态数组 |有序动态数组 |平衡树 |哈希表 | +|-----------------|:---------:|:-----------:|:--------------:|:---------:| +|自定成员次序 |可 |否 |否 |否 | +|初始化 $n$ 个成员|$O(n)$ |$O(n \log n)$|$O(n \log n)$ |$O(n)$ | +|加入成员 |分摊 $O(1)$|$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| +|移除成员 |$O(n)$ |$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| +|查询成员 |$O(n)$ |$O(\log n)$ |$O(\log n)$ |$O(1)$ | +|遍历成员 |$O(n)$ |$O(n)$ |$O(n)$ |$O(m)$ | +|检测对象相等 |$O(n^2)$ |$O(n)$ |$O(n)$ |$O(m)$ | +|空间 |$O(m)$ |$O(m)$ |$O(n)$ |$O(m)$ | + +在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 + +为了简单,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: + +~~~c +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; + struct { lept_value* e; size_t size; }a; + struct { char* s; size_t len; }s; + double n; + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; +~~~ + +成员结构 `lept_member` 是一个 `lept_value` 加上键的字符串。如同 JSON 字符串的值,我们也需要同时保留字符串的长度,因为字符串本身可能包含空字符 `\u0000`。 + +在这单元中,我们仅添加了最基本的访问函数,用于撰写单元测试: + +~~~c +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(const lept_value* v, size_t index); +~~~ + +在软件开发过程中,许多时候,选择合适的数据结构后已等于完成一半工作。没有完美的数据结构,所以最好考虑多一些应用的场合,看看时间/空间复杂度以至相关系数是否合适。 + +接下来,我们就可以着手实现。 + +## 3. 重构字符串解析 + +在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试,我们才提交改动。 + +我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 `lept_value` 存储键,因为这样会浪费了当中 `type` 这个无用的字段。由于 `lept_parse_string()` 是直接地把解析的结果写进一个 `lept_value`,所以我们先用「提取方法(extract method,见下注)」的重构方式,把解析 JSON 字符串及写入 `lept_value` 分拆成两部分: + +~~~c +/* 解析 JSON 字符串,把结果写入 str 和 len */ +/* str 指向 c->stack 中的元素,需要在 c->stack */ +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + /* \todo */ +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + int ret; + char* s; + size_t len; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) + lept_set_string(v, s, len); + return ret; +} +~~~ + +这样的话,我们实现对象的解析时,就可以使用 `lept_parse_string_raw()` 来解析 JSON 字符串,然后把结果复制至 `lept_member` 的 `k` 和 `klen` 字段。 + +注:在 Fowler 的经典著作 [1] 中,把各种重构方式分门别类,每个方式都有详细的步骤说明。由于书中以 Java 为例子,所以方式的名称使用了 Java 的述语,例如方法(method)。在 C 语言中,「提取方法」其实应该称为「提取函数」。 + +[1] Fowler, Martin. Refactoring: improving the design of existing code. Pearson Education India, 2009. 中译本:熊节译,《重构——改善既有代码的设计》,人民邮电出版社,2010年。 + +## 4. 实现 + +解析对象与解析数组非常相似,所以我留空了几段作为练习。在解析数组时,我们把当前的元素以 `lept_value` 压入栈中,而在这里,我们则是以 `lept_member` 压入: + +~~~c +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + lept_init(&m.v); + /* \todo parse key to m.k, m.klen */ + /* \todo parse ws colon ws */ + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* \todo parse ws [comma | right-curly-brace] ws */ + } + /* \todo Pop and free members on the stack */ + return ret; +} +~~~ + +要注意的是,我们要为 `m.k` 分配内存去存储键的字符串,若在整个对象解析时发生错误,也要记得释放栈中的 `lept_member` 的 `k`。 + +我们为解析对象定义了几个新的错误码: + +~~~c +enum { + /* ... */ + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; +~~~ + +在此不再赘述它们的意义了,可从以下的单元测试看到例子: + +~~~c +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} +~~~ + +## 5. 总结与练习 + +在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于数据驱动开发,我们可以不断重塑软件的内部结构。 + +完成这次练习之后,恭喜你,你已经完整地实现了一个符合标准的 JSON 解析器了。之后我们会完成更简单的生成器及其他访问功能。 + +由于对象和数组的相似性,此单元留空了较多实现部分作为练习: + +1. 依第 3 节所述,重构 `lept_parse_string()`。重构前运行单元测试,重构后确保单元测试仍保持通过。 +2. 打开 `test.c` 中两个 `#if 0`,运成单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 +3. 使用工具检测内存泄漏,解决它们。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 293da831436aadf560ad63a8160c2b9f9268814a Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:30:00 +0800 Subject: [PATCH 42/99] Update tutorial06.md --- tutorial06/tutorial06.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index bc792432..241e8adf 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -7,11 +7,11 @@ 本单元内容: -1. [JSON 对象](1-JSON-对象) -2. [数据结构](2-数据结构) -3. [重构字符串解析](3-重构字符串解析) -4. [实现](4-实现) -5. [总结与练习](5-总结与练习) +1. [JSON 对象](#1-JSON-对象) +2. [数据结构](#2-数据结构) +3. [重构字符串解析](#3-重构字符串解析) +4. [实现](#4-实现) +5. [总结与练习](#5-总结与练习) ## 1. JSON 对象 From 72b695954be36b0f02d9fbcb206cb3063db6bc39 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:33:25 +0800 Subject: [PATCH 43/99] Remove LaTeX formula --- tutorial06/tutorial06.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 241e8adf..f75517a3 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,20 +29,20 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现$O(1)$查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 - -设一个对象有 $n$ 个成员,数据结构的容量是 $m$,$n \le m$,那么一些常用操作的时间/空间复杂度如下: - -| |动态数组 |有序动态数组 |平衡树 |哈希表 | -|-----------------|:---------:|:-----------:|:--------------:|:---------:| -|自定成员次序 |可 |否 |否 |否 | -|初始化 $n$ 个成员|$O(n)$ |$O(n \log n)$|$O(n \log n)$ |$O(n)$ | -|加入成员 |分摊 $O(1)$|$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| -|移除成员 |$O(n)$ |$O(n)$ |分摊 $O(\log n)$|分摊 $O(1)$| -|查询成员 |$O(n)$ |$O(\log n)$ |$O(\log n)$ |$O(1)$ | -|遍历成员 |$O(n)$ |$O(n)$ |$O(n)$ |$O(m)$ | -|检测对象相等 |$O(n^2)$ |$O(n)$ |$O(n)$ |$O(m)$ | -|空间 |$O(m)$ |$O(m)$ |$O(n)$ |$O(m)$ | +* 哈希表(hash table):通过哈希函数能实现 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 + +设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: + +| |动态数组 |有序动态数组|平衡树 |哈希表 | +|---------------|:-------:|:----------:|:------------:|:-------:| +|自定成员次序 |可 |否 |否 |否 | +|初始化 n 个成员|O(n) |O(n log n) |O(n log n) |O(n) | +|加入成员 |分摊 O(1)|O(n) |分摊 O(log n) |分摊 O(1)| +|移除成员 |O(n) |O(n) |分摊 O(log n) |分摊 O(1)| +|查询成员 |O(n) |O(log n) |O(log n) |O(1) | +|遍历成员 |O(n) |O(n) |O(n) |O(m) | +|检测对象相等 |O(n^2) |O(n) |O(n) |O(m) | +|空间 |O(m) |O(m) |O(n) |O(m) | 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 From 4a529e46d9baf90dff09fb0745bf7892b3c79141 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:40:17 +0800 Subject: [PATCH 44/99] Minor edit --- tutorial06/tutorial06.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index f75517a3..c3d61a35 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -15,7 +15,7 @@ ## 1. JSON 对象 -此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` 包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` 分隔,完整语法如下: +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` (`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` (`U+003A`)分隔。完整语法如下: ~~~ member = string ws %x3A ws value @@ -46,7 +46,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 -为了简单,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: +为了简单起见,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: ~~~c typedef struct lept_value lept_value; From 2931bb37f9ed9859620fba774761b8bafb91f2a6 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:41:57 +0800 Subject: [PATCH 45/99] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index c3d61a35..a3b0a56f 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -15,7 +15,7 @@ ## 1. JSON 对象 -此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}` (`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:` (`U+003A`)分隔。完整语法如下: +此单元是本教程最后一个关于 JSON 解析器的部分。JSON 对象和 JSON 数组非常相似,区别包括 JSON 对象以花括号 `{}`(`U+007B`、`U+007D`)包裹表示,另外 JSON 对象由对象成员(member)组成,而 JSON 数组由 JSON 值组成。所谓对象成员,就是键值对,键必须为 JSON 字符串,然后值是任何 JSON 值,中间以冒号 `:`(`U+003A`)分隔。完整语法如下: ~~~ member = string ws %x3A ws value From 8c148ca09bd1fbaf6338df8398eea8e761332ad0 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:50:38 +0800 Subject: [PATCH 46/99] Fix complexity --- tutorial06/tutorial06.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index a3b0a56f..bb235c65 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,20 +29,21 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: -| |动态数组 |有序动态数组|平衡树 |哈希表 | -|---------------|:-------:|:----------:|:------------:|:-------:| -|自定成员次序 |可 |否 |否 |否 | -|初始化 n 个成员|O(n) |O(n log n) |O(n log n) |O(n) | -|加入成员 |分摊 O(1)|O(n) |分摊 O(log n) |分摊 O(1)| -|移除成员 |O(n) |O(n) |分摊 O(log n) |分摊 O(1)| -|查询成员 |O(n) |O(log n) |O(log n) |O(1) | -|遍历成员 |O(n) |O(n) |O(n) |O(m) | -|检测对象相等 |O(n^2) |O(n) |O(n) |O(m) | -|空间 |O(m) |O(m) |O(n) |O(m) | +| |动态数组 |有序动态数组|平衡树 |哈希表 | +|---------------|:-------:|:----------:|:--------:|:--------------------:| +|有序 |否 |是 |是 |否 | +|自定成员次序 |可 |否 |否 |否 | +|初始化 n 个成员|O(n) |O(n log n) |O(n log n)|平均 O(n)、最坏 O(n^2)| +|加入成员 |分摊 O(1)|O(n) |O(log n) |平均 O(1)、最坏 O(n) | +|移除成员 |O(n) |O(n) |O(log n) |平均 O(1)、最坏 O(n) | +|查询成员 |O(n) |O(log n) |O(log n) |平均 O(1)、最坏 O(n) | +|遍历成员 |O(n) |O(n) |O(n) |O(m) | +|检测对象相等 |O(n^2) |O(n) |O(n) |平均 O(n)、最坏 O(n^2)| +|空间 |O(m) |O(m) |O(n) |O(m) | 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 From 73606d0174550d327d4fadf5be1dce302dae41eb Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 15:55:36 +0800 Subject: [PATCH 47/99] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index bb235c65..1fe38ac8 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -29,7 +29,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D * 动态数组(dynamic array):可扩展容量的数组,如 C++ 的 [`std::vector`](http://en.cppreference.com/w/cpp/container/vector)。 * 有序动态数组(sorted dynamic array):和动态数组相同,但保证元素已排序,可用二分搜寻查询成员。 * 平衡树(balanced tree):平衡二叉树可有序地遍历成员,如红黑树和 C++ 的 [`std::map`](http://en.cppreference.com/w/cpp/container/map)([`std::multi_map`](http://en.cppreference.com/w/cpp/container/multimap) 支持重复键)。 -* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)(`http://en.cppreference.com/w/cpp/container/unordered_multimap`(http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 +* 哈希表(hash table):通过哈希函数能实现平均 O(1) 查询,如 C++11 的 [`std::unordered_map`](http://en.cppreference.com/w/cpp/container/unordered_map)([`unordered_multimap`](http://en.cppreference.com/w/cpp/container/unordered_multimap) 支持重复键)。 设一个对象有 n 个成员,数据结构的容量是 m,n ⩽ m,那么一些常用操作的时间/空间复杂度如下: From a9e10e62e370703428e31ec8c8efeeaddee0dc21 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 16:09:18 +0800 Subject: [PATCH 48/99] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5bfb0187..93d6a122 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 -6. 解析对象:JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 +6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 2564892b12b2a151477861b2dcfd5efe1af45be8 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 18:46:39 +0800 Subject: [PATCH 49/99] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 1fe38ac8..4214b956 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -47,7 +47,7 @@ object = %x7B ws [ member *( ws %x2C ws member ) ] ws %x7D 在 ECMA-404 标准中,并没有规定对象中每个成员的键一定要唯一的,也没有规定是否需要维持成员的次序。 -为了简单起见,我们的 leptjson 选择用动态数组的方案。我们会再单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: +为了简单起见,我们的 leptjson 选择用动态数组的方案。我们将在单元八才加入动态功能,所以这单元中,每个对象仅仅是成员的数组。那么它跟上一单元的数组非常接近: ~~~c typedef struct lept_value lept_value; From 124df28d1a9be611d9e0c53efb81ec78512225d1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 19:04:09 +0800 Subject: [PATCH 50/99] Update tutorial06.md --- tutorial06/tutorial06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index 4214b956..dc585d5c 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -199,7 +199,7 @@ static void test_parse_miss_comma_or_curly_bracket() { 由于对象和数组的相似性,此单元留空了较多实现部分作为练习: 1. 依第 3 节所述,重构 `lept_parse_string()`。重构前运行单元测试,重构后确保单元测试仍保持通过。 -2. 打开 `test.c` 中两个 `#if 0`,运成单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 +2. 打开 `test.c` 中两个 `#if 0`,运行单元测试,证实单元测试不通过。然后实现 `lept_parse_object()` 中的 `\todo` 部分。验证实现能通过单元测试。 3. 使用工具检测内存泄漏,解决它们。 如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From d72853e6c6f81ad003ff12ea4d9873104904f440 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 29 Oct 2016 20:47:52 +0800 Subject: [PATCH 51/99] Update tutorial06.md --- tutorial06/tutorial06.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial06/tutorial06.md b/tutorial06/tutorial06.md index dc585d5c..749270ae 100644 --- a/tutorial06/tutorial06.md +++ b/tutorial06/tutorial06.md @@ -86,7 +86,7 @@ lept_value* lept_get_object_value(const lept_value* v, size_t index); ## 3. 重构字符串解析 -在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试,我们才提交改动。 +在软件工程中,[代码重构](https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%87%8D%E6%9E%84)(code refactoring)是指在不改变软件外在行为时,修改代码以改进结构。代码重构十分依赖于单元测试,因为我们是通过单元测试去维护代码的正确性。有了足够的单元测试,我们可以放胆去重构,尝试并评估不同的改进方式,找到合乎心意而且能通过单元测试的改动,我们才提交它。 我们知道,成员的键也是一个 JSON 字符串,然而,我们不使用 `lept_value` 存储键,因为这样会浪费了当中 `type` 这个无用的字段。由于 `lept_parse_string()` 是直接地把解析的结果写进一个 `lept_value`,所以我们先用「提取方法(extract method,见下注)」的重构方式,把解析 JSON 字符串及写入 `lept_value` 分拆成两部分: @@ -192,7 +192,7 @@ static void test_parse_miss_comma_or_curly_bracket() { ## 5. 总结与练习 -在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于数据驱动开发,我们可以不断重塑软件的内部结构。 +在本单元中,除了谈及 JSON 对象的语法、可选的数据结构、实现方式,我们也轻轻谈及了重构的概念。有赖于测试驱动开发(TDD),我们可以不断重塑软件的内部结构。 完成这次练习之后,恭喜你,你已经完整地实现了一个符合标准的 JSON 解析器了。之后我们会完成更简单的生成器及其他访问功能。 From a5a7abbcf328515d0b828a0c63df824ec7196250 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 15 Nov 2016 11:29:35 +0800 Subject: [PATCH 52/99] Add tutorial06 answer --- tutorial06_answer/CMakeLists.txt | 10 + tutorial06_answer/leptjson.c | 444 +++++++++++++++++++++++++ tutorial06_answer/leptjson.h | 71 ++++ tutorial06_answer/test.c | 426 ++++++++++++++++++++++++ tutorial06_answer/tutorial06_answer.md | 162 +++++++++ 5 files changed, 1113 insertions(+) create mode 100644 tutorial06_answer/CMakeLists.txt create mode 100644 tutorial06_answer/leptjson.c create mode 100644 tutorial06_answer/leptjson.h create mode 100644 tutorial06_answer/test.c create mode 100644 tutorial06_answer/tutorial06_answer.md diff --git a/tutorial06_answer/CMakeLists.txt b/tutorial06_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial06_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial06_answer/leptjson.c b/tutorial06_answer/leptjson.c new file mode 100644 index 00000000..e99a8413 --- /dev/null +++ b/tutorial06_answer/leptjson.c @@ -0,0 +1,444 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + size_t head = c->top; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + *len = c->top - head; + *str = lept_context_pop(c, *len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + int ret; + char* s; + size_t len; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) + lept_set_string(v, s, len); + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on the stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* parse key */ + if (*c->json != '"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) + break; + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen + 1); + /* parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } + } + /* Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '{': return lept_parse_object(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +const char* lept_get_object_key(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].k; +} + +size_t lept_get_object_key_length(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].klen; +} + +lept_value* lept_get_object_value(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return &v->u.o.m[index].v; +} diff --git a/tutorial06_answer/leptjson.h b/tutorial06_answer/leptjson.h new file mode 100644 index 00000000..8a6ce68d --- /dev/null +++ b/tutorial06_answer/leptjson.h @@ -0,0 +1,71 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial06_answer/test.c b/tutorial06_answer/test.c new file mode 100644 index 00000000..74e5f763 --- /dev/null +++ b/tutorial06_answer/test.c @@ -0,0 +1,426 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +static void test_parse_object() { + lept_value v; + size_t i; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } ")); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, + " { " + "\"n\" : null , " + "\"f\" : false , " + "\"t\" : true , " + "\"i\" : 123 , " + "\"s\" : \"abc\", " + "\"a\" : [ 1, 2, 3 ]," + "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }" + " } " + )); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v)); + EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0))); + EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1)); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1))); + EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2)); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2))); + EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3))); + EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4)); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), lept_get_string_length(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5)); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5))); + EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5))); + for (i = 0; i < 3; i++) { + lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e)); + } + EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6)); + { + lept_value* o = lept_get_object_value(&v, 6); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o)); + for (i = 0; i < 3; i++) { + lept_value* ov = lept_get_object_value(o, i); + EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]); + EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov)); + } + } + lept_free(&v); +} + +#define TEST_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_object(); + + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial06_answer/tutorial06_answer.md b/tutorial06_answer/tutorial06_answer.md new file mode 100644 index 00000000..4534120c --- /dev/null +++ b/tutorial06_answer/tutorial06_answer.md @@ -0,0 +1,162 @@ +# 从零开始的 JSON 库教程(六):解析对象解答篇 + +* Milo Yip +* 2016/11/15 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第六个单元解答篇。解答代码位于 [json-tutorial/tutorial06_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial06_answer)。 + +## 1. 重构 `lept_parse_string()` + +这个「提取方法」重构练习很简单,只需要把原来调用 `lept_set_string` 的地方,改为写入参数变量。因此,原来的 `lept_parse_string()` 和 答案中的 `lept_parse_string_raw()` 的 diff 仅是两处: + +~~~ +130,131c130,131 +< static int lept_parse_string(lept_context* c, lept_value* v) { +< size_t head = c->top, len; +--- +> static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { +> size_t head = c->top; +140,141c140,141 +< len = c->top - head; +< lept_set_string(v, (const char*)lept_context_pop(c, len), len); +--- +> *len = c->top - head; +> *str = lept_context_pop(c, *len); +~~~ + +以 TDD 方式开发软件,因为有单元测试确保软件的正确性,面对新需求可以安心重构,改善软件架构。 + +## 2. 实现 `lept_parse_object()` + +有了 `lept_parse_array()` 的经验,实现 `lept_parse_object()` 几乎是一样的,分别只是每个迭代要多处理一个键和冒号。我们把这个实现过程分为 5 步曲。 + +第 1 步是利用刚才重构出来的 `lept_parse_string_raw()` 去解析键的字符串。由于 `lept_parse_string_raw()` 假设第一个字符为 `"`,我们要先作校检,失败则要返回 `LEPT_PARSE_MISS_KEY` 错误。若字符串解析成功,它会把结果存储在我们的栈之中,需要把结果写入临时 `lept_member` 的 `k` 和 `klen` 字段中: + +~~~c +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + /* ... */ + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* 1. parse key */ + if (*c->json != '"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) + break; + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen + 1); + /* 2. parse ws colon ws */ + /* ... */ + } + /* 5. Pop and free members on the stack */ + /* ... */ +} +~~~ + +第 2 步是解析冒号,冒号前后可有空白字符: + +~~~c + /* 2. parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); +~~~ + +第 3 步是解析任意的 JSON 值。这部分与解析数组一样,递归调用 `lept_parse_value()`,把结果写入临时 `lept_member` 的 `v` 字段,然后把整个 `lept_member` 压入栈: + +~~~c + /* 3. parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ +~~~ + +但有一点要注意,如果之前缺乏冒号,或是这里解析值失败,在函数返回前我们要释放 `m.k`。如果我们成功地解析整个成员,那么就要把 `m.k` 设为空指针,其意义是说明该键的字符串的拥有权已转移至栈,之后如遇到错误,我们不会重覆释放栈里成员的键和这个临时成员的键。 + +第 4 步,解析逗号或右花括号。遇上右花括号的话,当前的 JSON 对象就解析完结了,我们把栈上的成员复制至结果,并直接返回: + +~~~c + /* 4. parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } +~~~ + +最后,当 `for (;;)` 中遇到任何错误便会到达这第 5 步,要释放临时的 key 字符串及栈上的成员: + +~~~c + /* 5. Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; + return ret; +~~~ + +注意我们不需要先检查 `m.k != NULL`,因为 `free(NULL)` 是完全合法的。 + +## 3. 释放内存 + +使用工具检测内存泄漏时,我们会发现以下这行代码造成内存泄漏: + +~~~c +memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); +~~~ + +类似数组,我们也需要在 `lept_free()` 释放 JSON 对象的成员(包括键及值): + +~~~c +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + /* ... */ + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); + break; + default: break; + } + v->type = LEPT_NULL; +} +~~~ + +## 4. 总结 + +至此,你已实现一个完整的 JSON 解析器,可解析任何合法的 JSON。统计一下,不计算空行及注释,现时 `leptjson.c` 只有 405 行代码,`lept_json.h` 54 行,`test.c` 309 行。 + +另一方面,一些程序也需要生成 JSON。也许最初读者会以为生成 JSON 只需直接 `sprintf()/fprintf()` 就可以,但深入了解 JSON 的语法之后,我们应该知道 JSON 语法还是需做一些处理,例如字符串的转义、数字的格式等。然而,实现生成器还是要比解析器容易得多。而且,假设我们有一个正确的解析器,可以简单使用 roundtrip 方式实现测试。请期待下回分解。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From c90370bbb17a0e5788e81464029d4fa8eddf46c4 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 15 Nov 2016 16:27:21 +0900 Subject: [PATCH 53/99] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 93d6a122..f2a583bd 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 -6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。 +6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From e6c81a6d4227414886d22a7aec39699d5cf94ca6 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 09:53:31 +0800 Subject: [PATCH 54/99] Fix missing null terminator in lept_parse_object() --- tutorial06_answer/leptjson.c | 3 ++- tutorial06_answer/test.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tutorial06_answer/leptjson.c b/tutorial06_answer/leptjson.c index e99a8413..307917c2 100644 --- a/tutorial06_answer/leptjson.c +++ b/tutorial06_answer/leptjson.c @@ -260,7 +260,8 @@ static int lept_parse_object(lept_context* c, lept_value* v) { } if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) break; - memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen + 1); + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; /* parse ws colon ws */ lept_parse_whitespace(c); if (*c->json != ':') { diff --git a/tutorial06_answer/test.c b/tutorial06_answer/test.c index 74e5f763..ad4dc6f3 100644 --- a/tutorial06_answer/test.c +++ b/tutorial06_answer/test.c @@ -25,7 +25,7 @@ static int test_pass = 0; #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") #define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") #define EXPECT_EQ_STRING(expect, actual, alength) \ - EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s") + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength + 1) == 0, expect, actual, "%s") #define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") #define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") From 360ca90cb2d155f9f69da1484728284c55a87838 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 09:55:02 +0800 Subject: [PATCH 55/99] Fix missing null terminator in documentation --- tutorial06_answer/tutorial06_answer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial06_answer/tutorial06_answer.md b/tutorial06_answer/tutorial06_answer.md index 4534120c..ea026142 100644 --- a/tutorial06_answer/tutorial06_answer.md +++ b/tutorial06_answer/tutorial06_answer.md @@ -50,7 +50,8 @@ static int lept_parse_object(lept_context* c, lept_value* v) { } if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) break; - memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen + 1); + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; /* 2. parse ws colon ws */ /* ... */ } From a5d197b8a45df5a3e1910a125991b3d39b548884 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 23 Nov 2016 21:52:42 +0800 Subject: [PATCH 56/99] Update tutorial05_answer.md --- tutorial05_answer/tutorial05_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05_answer/tutorial05_answer.md b/tutorial05_answer/tutorial05_answer.md index b284815e..8b27f325 100644 --- a/tutorial05_answer/tutorial05_answer.md +++ b/tutorial05_answer/tutorial05_answer.md @@ -132,7 +132,7 @@ void lept_free(lept_value* v) { ## 4. 解析错误时的内存处理 -遇到解析错误时,我们可能在之前已压入了一些值在自定议堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 +遇到解析错误时,我们可能在之前已压入了一些值在自定义堆栈上。如果没有处理,最后会在 `lept_parse()` 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。 在 `lept_parse_array` 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 `break` 离开循环,在循环结束后的地方用 `lept_free()` 释放从堆栈弹出的值,然后才返回错误码: From b2268c4bc42cafdaf173c84c48e40c07411a3f2c Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:34:05 +0800 Subject: [PATCH 57/99] Add tutorial07 --- tutorial07/CMakeLists.txt | 10 + tutorial07/images/makefile | 5 + tutorial07/images/parse_stringify.dot | 44 +++ tutorial07/images/parse_stringify.png | Bin 0 -> 29020 bytes tutorial07/leptjson.c | 484 +++++++++++++++++++++++++ tutorial07/leptjson.h | 72 ++++ tutorial07/test.c | 491 ++++++++++++++++++++++++++ tutorial07/tutorial07.md | 154 ++++++++ 8 files changed, 1260 insertions(+) create mode 100644 tutorial07/CMakeLists.txt create mode 100644 tutorial07/images/makefile create mode 100644 tutorial07/images/parse_stringify.dot create mode 100644 tutorial07/images/parse_stringify.png create mode 100644 tutorial07/leptjson.c create mode 100644 tutorial07/leptjson.h create mode 100644 tutorial07/test.c create mode 100644 tutorial07/tutorial07.md diff --git a/tutorial07/CMakeLists.txt b/tutorial07/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial07/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial07/images/makefile b/tutorial07/images/makefile new file mode 100644 index 00000000..55ed2163 --- /dev/null +++ b/tutorial07/images/makefile @@ -0,0 +1,5 @@ +%.png: %.dot + dot $< -Tpng -o $@ + +DOTFILES = $(basename $(wildcard *.dot)) +all: $(addsuffix .png, $(DOTFILES)) diff --git a/tutorial07/images/parse_stringify.dot b/tutorial07/images/parse_stringify.dot new file mode 100644 index 00000000..1b8eae51 --- /dev/null +++ b/tutorial07/images/parse_stringify.dot @@ -0,0 +1,44 @@ +digraph { + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.3 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] + + { + node [shape=record, fontsize="8", margin="0.04", height=0.2, color=gray] + json [label="\{|\"|p|r|o|j|e|c|t|\"|:|\"|l|e|p|t|j|s|o|n|\"|,|\"|s|t|a|r|s|\"|:|1|0|\}"] + } + + subgraph cluster1 { + margin="10,10" + labeljust="left" + label = "lept_value tree" + style=filled + fillcolor=gray95 + node [shape=Mrecord, style=filled, colorscheme=spectral7] + + root [label="{object|}", fillcolor=3] + + { + project [label="{key|\"project\"}", fillcolor=5] + leptjson [label="{string|\"leptjson\"}", fillcolor=5] + stars [label="{key|\"stars\"}", fillcolor=5] + ten [label="{number|10}", fillcolor=6] + } + + edge [arrowhead=vee] + root -> { project; stars } + + edge [arrowhead="none"] + project -> leptjson + stars -> ten + } + + json -> root [label=" lept_parse() ", lhead="cluster1"] + root -> json [label=" lept_stringify() ", ltail="cluster1"] +} \ No newline at end of file diff --git a/tutorial07/images/parse_stringify.png b/tutorial07/images/parse_stringify.png new file mode 100644 index 0000000000000000000000000000000000000000..9d40d4aedbf0f47be4b3ad1083f2faa013f7d1ff GIT binary patch literal 29020 zcmd?RWmg?tv^5yq-Q6vOKyY^_IDtTLcX!v|2X}V}?(Xgy+}+(F(8Y6akM8$3ydN?$ z&M7%nd#}CLTyxG%sDhjX5@j*^hxlOl&Fx3E7++Pl+cy)`yE^k#uMpMdWw)_LV~EMkdR

TCVY+b-)W?D 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 K(JJBZf&T~G5E;7w literal 0 HcmV?d00001 diff --git a/tutorial07/leptjson.c b/tutorial07/leptjson.c new file mode 100644 index 00000000..5307b892 --- /dev/null +++ b/tutorial07/leptjson.c @@ -0,0 +1,484 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* sprintf() */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE +#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) +#define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + size_t head = c->top; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + *len = c->top - head; + *str = lept_context_pop(c, *len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + int ret; + char* s; + size_t len; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) + lept_set_string(v, s, len); + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on the stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* parse key */ + if (*c->json != '"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) + break; + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; + /* parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } + } + /* Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '{': return lept_parse_object(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + /* ... */ +} + +static void lept_stringify_value(lept_context* c, const lept_value* v) { + switch (v->type) { + case LEPT_NULL: PUTS(c, "null", 4); break; + case LEPT_FALSE: PUTS(c, "false", 5); break; + case LEPT_TRUE: PUTS(c, "true", 4); break; + case LEPT_NUMBER: c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; + case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; + case LEPT_ARRAY: + /* ... */ + break; + case LEPT_OBJECT: + /* ... */ + break; + default: assert(0 && "invalid type"); + } +} + +char* lept_stringify(const lept_value* v, size_t* length) { + lept_context c; + assert(v != NULL); + c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); + c.top = 0; + lept_stringify_value(&c, v); + if (length) + *length = c.top; + PUTC(&c, '\0'); + return c.stack; +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +const char* lept_get_object_key(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].k; +} + +size_t lept_get_object_key_length(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].klen; +} + +lept_value* lept_get_object_value(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return &v->u.o.m[index].v; +} diff --git a/tutorial07/leptjson.h b/tutorial07/leptjson.h new file mode 100644 index 00000000..92cbfc51 --- /dev/null +++ b/tutorial07/leptjson.h @@ -0,0 +1,72 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); +char* lept_stringify(const lept_value* v, size_t* length); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial07/test.c b/tutorial07/test.c new file mode 100644 index 00000000..7e34cbb7 --- /dev/null +++ b/tutorial07/test.c @@ -0,0 +1,491 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength + 1) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +static void test_parse_object() { + lept_value v; + size_t i; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } ")); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, + " { " + "\"n\" : null , " + "\"f\" : false , " + "\"t\" : true , " + "\"i\" : 123 , " + "\"s\" : \"abc\", " + "\"a\" : [ 1, 2, 3 ]," + "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }" + " } " + )); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v)); + EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0))); + EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1)); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1))); + EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2)); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2))); + EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3))); + EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4)); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), lept_get_string_length(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5)); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5))); + EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5))); + for (i = 0; i < 3; i++) { + lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e)); + } + EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6)); + { + lept_value* o = lept_get_object_value(&v, 6); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o)); + for (i = 0; i < 3; i++) { + lept_value* ov = lept_get_object_value(o, i); + EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]); + EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov)); + } + } + lept_free(&v); +} + +#define TEST_PARSE_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_object(); + + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +} + +#define TEST_ROUNDTRIP(json)\ + do {\ + lept_value v;\ + char* json2;\ + size_t length;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + json2 = lept_stringify(&v, &length);\ + EXPECT_EQ_STRING(json, json2, length);\ + lept_free(&v);\ + free(json2);\ + } while(0) + +static void test_stringify_number() { + TEST_ROUNDTRIP("0"); + TEST_ROUNDTRIP("-0"); + TEST_ROUNDTRIP("1"); + TEST_ROUNDTRIP("-1"); + TEST_ROUNDTRIP("1.5"); + TEST_ROUNDTRIP("-1.5"); + TEST_ROUNDTRIP("3.25"); + TEST_ROUNDTRIP("1e+20"); + TEST_ROUNDTRIP("1.234e+20"); + TEST_ROUNDTRIP("1.234e-20"); + + TEST_ROUNDTRIP("1.0000000000000002"); /* the smallest number > 1 */ + TEST_ROUNDTRIP("4.9406564584124654e-324"); /* minimum denormal */ + TEST_ROUNDTRIP("-4.9406564584124654e-324"); + TEST_ROUNDTRIP("2.2250738585072009e-308"); /* Max subnormal double */ + TEST_ROUNDTRIP("-2.2250738585072009e-308"); + TEST_ROUNDTRIP("2.2250738585072014e-308"); /* Min normal positive double */ + TEST_ROUNDTRIP("-2.2250738585072014e-308"); + TEST_ROUNDTRIP("1.7976931348623157e+308"); /* Max double */ + TEST_ROUNDTRIP("-1.7976931348623157e+308"); +} + +static void test_stringify_string() { + TEST_ROUNDTRIP("\"\""); + TEST_ROUNDTRIP("\"Hello\""); + TEST_ROUNDTRIP("\"Hello\\nWorld\""); + TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\""); + TEST_ROUNDTRIP("\"Hello\\u0000World\""); +} + +static void test_stringify_array() { + TEST_ROUNDTRIP("[]"); + TEST_ROUNDTRIP("[null,false,true,123,\"abc\",[1,2,3]]"); +} + +static void test_stringify_object() { + TEST_ROUNDTRIP("{}"); + TEST_ROUNDTRIP("{\"n\":null,\"f\":false,\"t\":true,\"i\":123,\"s\":\"abc\",\"a\":[1,2,3],\"o\":{\"1\":1,\"2\":2,\"3\":3}}"); +} + +static void test_stringify() { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("true"); + test_stringify_number(); + test_stringify_string(); + test_stringify_array(); + test_stringify_object(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_stringify(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md new file mode 100644 index 00000000..b23e8f08 --- /dev/null +++ b/tutorial07/tutorial07.md @@ -0,0 +1,154 @@ +# 从零开始的 JSON 库教程(七):生成器 + +* Milo Yip +* 2016/12/20 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元。代码位于 [json-tutorial/tutorial07](https://github.com/miloyip/json-tutorial/blob/master/tutorial07)。 + +## 1. JSON 生成器 + +我们在前 6 个单元实现了一个合乎标准的 JSON 解析器,它把 JSON 文本解析成一个树形数据结构,整个结构以 `lept_value` 的节点组成。 + +JSON 生成器(generator)负责相反的事情,就是把树形数据结构转换成 JSON 文本。这个过程又称为「字符串化(stringify)」。 + +![JSON 的解析与生成](images/parse_stringify.png) + +相对于解析器,通常生成器更容易实现,而且生成器几乎不会造成运行时错误。因此,生成器的 API 设计为以下形式,直接返回 JSON 的字符串: + +~~~c +char* lept_stringify(const lept_value* v, size_t* length); +~~~ + +`length` 参数是可选的,它会存储 JSON 的长度,传入 `NULL` 可忽略此参数。使用方需负责用 `free()` 释放内存。 + +为了简单起见,我们不做换行、缩进等美化(prettify)处理,因此它生成的 JSON 会是单行、无空白字符的最紧凑形式。 + +## 2. 再利用 lept_context 做动态数组 + +在实现 JSON 解析时,我们加入了一个动态变长的堆栈,用于存储临时的解析结果。而现在,我们也需要存储生成的结果,所以最简单是再利用该数据结构,作为输出缓冲区。 + +~~~c +#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE +#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 +#endif + +int lept_stringify(const lept_value* v, char** json, size_t* length) { + lept_context c; + int ret; + assert(v != NULL); + assert(json != NULL); + c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); + c.top = 0; + if ((ret = lept_stringify_value(&c, v)) != LEPT_STRINGIFY_OK) { + free(c.stack); + *json = NULL; + return ret; + } + if (length) + *length = c.top; + PUTC(&c, '\0'); + *json = c.stack; + return LEPT_STRINGIFY_OK; +} +~~~ + +生成根节点的值之后,我需还需要加入一个空字符作结尾。 + +如前所述,此 API 还提供了 `length` 可选参数,当传入非空指针时,就能获得生成 JSON 的长度。或许读者会疑问,为什么需要获得长度,我们不是可以用 `strlen()` 获得么?是的,因为 JSON 不会含有空字符(若 JSON 字符串中含空字符,必须转义为 `\u0000`),用 `strlen()` 是没有问题的。但这样做会带来不必要的性能消耗,理想地是避免调用方有额外消耗。 + +## 3. 生成 null、false 和 true + +接下来,我们生成最简单的 JSON 类型,就是 3 种 JSON 字面值。为贯彻 TDD,先写测试: + +~~~c +#define TEST_ROUNDTRIP(json)\ + do {\ + lept_value v;\ + char* json2;\ + size_t length;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRINGIFY_OK, lept_stringify(&v, &json2, &length));\ + EXPECT_EQ_STRING(json, json2, length);\ + lept_free(&v);\ + free(json2);\ + } while(0) + +static void test_stringify() { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("true"); + /* ... */ +} +~~~ + +这里我们采用一个最简单的测试方式,把一个 JSON 解析,然后再生成另一 JSON,逐字符比较两个 JSON 是否一模一样。这种测试可称为往返(roundtrip)测试。但需要注意,同一个 JSON 的内容可以有多种不同的表示方式,例如可以插入不定数量的空白字符,数字 `1.0` 和 `1` 也是等价的。所以另一种测试方式,是比较两次解析的结果(`lept_value` 的树)是否相同,此功能将会在下一单元讲解。 + +然后,我们实现 `lept_stringify_value`,加入一个 `PUTS()` 宏去输出字符串: + +~~~c +#define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) + +static int lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + int ret; + switch (v->type) { + case LEPT_NULL: PUTS(c, "null", 4); break; + case LEPT_FALSE: PUTS(c, "false", 5); break; + case LEPT_TRUE: PUTS(c, "true", 4); break; + /* ... */ + } + return LEPT_STRINGIFY_OK; +} +~~~ + +## 4. 生成数字 + +为了简单起见,我们使用 `sprintf("%.17g", ...)` 来把浮点数转换成文本。`"%.17g"` 是足够把双精度浮点转换成可还原的文本。 + +最简单的实现方式可能是这样的: + +~~~c + case LEPT_NUMBER: + { + char buffer[32]; + int length = sprintf(buffer, "%.17g", v->u.n); + PUTS(c, buffer, length); + } + break; +~~~ + +但这样需要在 `PUTS()` 中做一次 `memcpy()`,实际上我们可以避免这次复制,只需要生成的时候直接写进 `c` 里的推栈,然后再按实际长度调查 `c->top`: + +~~~c + case LEPT_NUMBER: + { + char* buffer = lept_context_push(c, 32); + int length = sprintf(buffer, "%.17g", v->u.n); + c->top -= 32 - length; + } + break; +~~~ + +因每个临时变量只用了一次,我们可以把代码压缩成一行: + +~~~c + case LEPT_NUMBER: + c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; +~~~ + +## 5. 总结与练习 + +我们在此单元中简介了 JSON 的生成功能和 leptjson 中的实现方式。 + +leptjson 重复利用了 `lept_context` 中的数据结构作为输出缓冲,可以节省代码量。 + +生成通常比解析简单(一个例外是 RapidJSON 自行实现了浮点数至字符串的算法),余下的 3 种 JSON 类型就当作练习吧: + +1. 由于有两个地方需要生成字符串(JSON 字符串和对象类型),所以先实现 `lept_stringify_string()`。注意,字符串的语法比较复杂,一些字符必须转义,其他少于 `0x20` 的字符需要转义为 `\u00xx` 形式。 + +2. 直接在 `lept_stringify_value()` 的 `switch` 内实现 JSON 数组和对象类型的生成。这些实现里都会递归调用 `lept_stringify_value()` 。 + +3. 在你的 `lept_stringify_string()` 是否使用了多次 `PUTC()`?如果是,它每次输出一个字符时,都要检测缓冲区是否有足够空间(不够时需扩展)。能否优化这部分的性能?这种优化有什么代价么? + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From de37772c15deb2d9a0e9547202c9106db618c980 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:35:15 +0800 Subject: [PATCH 58/99] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f2a583bd..05d12139 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. 生成器:JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 30ba5b6bbfb193bfba773b0bbfd882a6d3144d75 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:35:45 +0800 Subject: [PATCH 59/99] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 05d12139..adafd9c6 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 5a4fcabbcda7500f15725caad90259e9fca591a1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Tue, 20 Dec 2016 14:48:39 +0800 Subject: [PATCH 60/99] Update tutorial07.md --- tutorial07/tutorial07.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorial07/tutorial07.md b/tutorial07/tutorial07.md index b23e8f08..02fe8255 100644 --- a/tutorial07/tutorial07.md +++ b/tutorial07/tutorial07.md @@ -134,7 +134,8 @@ static int lept_stringify_value(lept_context* c, const lept_value* v) { ~~~c case LEPT_NUMBER: - c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; + c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); + break; ~~~ ## 5. 总结与练习 From 5d79f14729c6bdd79ad9fcfeba8bf2ddf89b34c7 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 5 Jan 2017 13:55:11 +0800 Subject: [PATCH 61/99] Add tutorial07 answer --- readme.md | 2 +- tutorial07_answer/CMakeLists.txt | 10 + tutorial07_answer/leptjson.c | 556 +++++++++++++++++++++++++ tutorial07_answer/leptjson.h | 72 ++++ tutorial07_answer/test.c | 491 ++++++++++++++++++++++ tutorial07_answer/tutorial07_answer.md | 155 +++++++ 6 files changed, 1285 insertions(+), 1 deletion(-) create mode 100644 tutorial07_answer/CMakeLists.txt create mode 100644 tutorial07_answer/leptjson.c create mode 100644 tutorial07_answer/leptjson.h create mode 100644 tutorial07_answer/test.c create mode 100644 tutorial07_answer/tutorial07_answer.md diff --git a/readme.md b/readme.md index adafd9c6..b67b650f 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](2017/1/5 完成) 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 diff --git a/tutorial07_answer/CMakeLists.txt b/tutorial07_answer/CMakeLists.txt new file mode 100644 index 00000000..49ba19de --- /dev/null +++ b/tutorial07_answer/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required (VERSION 2.6) +project (leptjson_test C) + +if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall") +endif() + +add_library(leptjson leptjson.c) +add_executable(leptjson_test test.c) +target_link_libraries(leptjson_test leptjson) diff --git a/tutorial07_answer/leptjson.c b/tutorial07_answer/leptjson.c new file mode 100644 index 00000000..b6f64834 --- /dev/null +++ b/tutorial07_answer/leptjson.c @@ -0,0 +1,556 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include "leptjson.h" +#include /* assert() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ +#include /* sprintf() */ +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#include /* memcpy() */ + +#ifndef LEPT_PARSE_STACK_INIT_SIZE +#define LEPT_PARSE_STACK_INIT_SIZE 256 +#endif + +#ifndef LEPT_PARSE_STRINGIFY_INIT_SIZE +#define LEPT_PARSE_STRINGIFY_INIT_SIZE 256 +#endif + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') +#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0) +#define PUTS(c, s, len) memcpy(lept_context_push(c, len), s, len) + +typedef struct { + const char* json; + char* stack; + size_t size, top; +}lept_context; + +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; + c->top += size; + return ret; +} + +static void* lept_context_pop(lept_context* c, size_t size) { + assert(c->top >= size); + return c->stack + (c->top -= size); +} + +static void lept_parse_whitespace(lept_context* c) { + const char *p = c->json; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') + p++; + c->json = p; +} + +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; + EXPECT(c, literal[0]); + for (i = 0; literal[i + 1]; i++) + if (c->json[i] != literal[i + 1]) + return LEPT_PARSE_INVALID_VALUE; + c->json += i; + v->type = type; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context* c, lept_value* v) { + const char* p = c->json; + if (*p == '-') p++; + if (*p == '0') p++; + else { + if (!ISDIGIT1TO9(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == '.') { + p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!ISDIGIT(*p)) return LEPT_PARSE_INVALID_VALUE; + for (p++; ISDIGIT(*p); p++); + } + errno = 0; + v->u.n = strtod(c->json, NULL); + if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL)) + return LEPT_PARSE_NUMBER_TOO_BIG; + v->type = LEPT_NUMBER; + c->json = p; + return LEPT_PARSE_OK; +} + +static const char* lept_parse_hex4(const char* p, unsigned* u) { + int i; + *u = 0; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ch >= '0' && ch <= '9') *u |= ch - '0'; + else if (ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if (ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } + return p; +} + +static void lept_encode_utf8(lept_context* c, unsigned u) { + if (u <= 0x7F) + PUTC(c, u & 0xFF); + else if (u <= 0x7FF) { + PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else if (u <= 0xFFFF) { + PUTC(c, 0xE0 | ((u >> 12) & 0xFF)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } + else { + assert(u <= 0x10FFFF); + PUTC(c, 0xF0 | ((u >> 18) & 0xFF)); + PUTC(c, 0x80 | ((u >> 12) & 0x3F)); + PUTC(c, 0x80 | ((u >> 6) & 0x3F)); + PUTC(c, 0x80 | ( u & 0x3F)); + } +} + +#define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) + +static int lept_parse_string_raw(lept_context* c, char** str, size_t* len) { + size_t head = c->top; + unsigned u, u2; + const char* p; + EXPECT(c, '\"'); + p = c->json; + for (;;) { + char ch = *p++; + switch (ch) { + case '\"': + *len = c->top - head; + *str = lept_context_pop(c, *len); + c->json = p; + return LEPT_PARSE_OK; + case '\\': + switch (*p++) { + case '\"': PUTC(c, '\"'); break; + case '\\': PUTC(c, '\\'); break; + case '/': PUTC(c, '/' ); break; + case 'b': PUTC(c, '\b'); break; + case 'f': PUTC(c, '\f'); break; + case 'n': PUTC(c, '\n'); break; + case 'r': PUTC(c, '\r'); break; + case 't': PUTC(c, '\t'); break; + case 'u': + if (!(p = lept_parse_hex4(p, &u))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u >= 0xD800 && u <= 0xDBFF) { /* surrogate pair */ + if (*p++ != '\\') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (*p++ != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + if (!(p = lept_parse_hex4(p, &u2))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if (u2 < 0xDC00 || u2 > 0xDFFF) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = (((u - 0xD800) << 10) | (u2 - 0xDC00)) + 0x10000; + } + lept_encode_utf8(c, u); + break; + default: + STRING_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE); + } + break; + case '\0': + STRING_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK); + default: + if ((unsigned char)ch < 0x20) + STRING_ERROR(LEPT_PARSE_INVALID_STRING_CHAR); + PUTC(c, ch); + } + } +} + +static int lept_parse_string(lept_context* c, lept_value* v) { + int ret; + char* s; + size_t len; + if ((ret = lept_parse_string_raw(c, &s, &len)) == LEPT_PARSE_OK) + lept_set_string(v, s, len); + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v); + +static int lept_parse_array(lept_context* c, lept_value* v) { + size_t i, size = 0; + int ret; + EXPECT(c, '['); + lept_parse_whitespace(c); + if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = 0; + v->u.a.e = NULL; + return LEPT_PARSE_OK; + } + for (;;) { + lept_value e; + lept_init(&e); + if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value)); + size++; + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == ']') { + c->json++; + v->type = LEPT_ARRAY; + v->u.a.size = size; + size *= sizeof(lept_value); + memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; + break; + } + } + /* Pop and free values on the stack */ + for (i = 0; i < size; i++) + lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); + return ret; +} + +static int lept_parse_object(lept_context* c, lept_value* v) { + size_t i, size; + lept_member m; + int ret; + EXPECT(c, '{'); + lept_parse_whitespace(c); + if (*c->json == '}') { + c->json++; + v->type = LEPT_OBJECT; + v->u.o.m = 0; + v->u.o.size = 0; + return LEPT_PARSE_OK; + } + m.k = NULL; + size = 0; + for (;;) { + char* str; + lept_init(&m.v); + /* parse key */ + if (*c->json != '"') { + ret = LEPT_PARSE_MISS_KEY; + break; + } + if ((ret = lept_parse_string_raw(c, &str, &m.klen)) != LEPT_PARSE_OK) + break; + memcpy(m.k = (char*)malloc(m.klen + 1), str, m.klen); + m.k[m.klen] = '\0'; + /* parse ws colon ws */ + lept_parse_whitespace(c); + if (*c->json != ':') { + ret = LEPT_PARSE_MISS_COLON; + break; + } + c->json++; + lept_parse_whitespace(c); + /* parse value */ + if ((ret = lept_parse_value(c, &m.v)) != LEPT_PARSE_OK) + break; + memcpy(lept_context_push(c, sizeof(lept_member)), &m, sizeof(lept_member)); + size++; + m.k = NULL; /* ownership is transferred to member on stack */ + /* parse ws [comma | right-curly-brace] ws */ + lept_parse_whitespace(c); + if (*c->json == ',') { + c->json++; + lept_parse_whitespace(c); + } + else if (*c->json == '}') { + size_t s = sizeof(lept_member) * size; + c->json++; + v->type = LEPT_OBJECT; + v->u.o.size = size; + memcpy(v->u.o.m = (lept_member*)malloc(s), lept_context_pop(c, s), s); + return LEPT_PARSE_OK; + } + else { + ret = LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET; + break; + } + } + /* Pop and free members on the stack */ + free(m.k); + for (i = 0; i < size; i++) { + lept_member* m = (lept_member*)lept_context_pop(c, sizeof(lept_member)); + free(m->k); + lept_free(&m->v); + } + v->type = LEPT_NULL; + return ret; +} + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 't': return lept_parse_literal(c, v, "true", LEPT_TRUE); + case 'f': return lept_parse_literal(c, v, "false", LEPT_FALSE); + case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); + default: return lept_parse_number(c, v); + case '"': return lept_parse_string(c, v); + case '[': return lept_parse_array(c, v); + case '{': return lept_parse_object(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + } +} + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + c.stack = NULL; + c.size = c.top = 0; + lept_init(v); + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') { + v->type = LEPT_NULL; + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + } + assert(c.top == 0); + free(c.stack); + return ret; +} + +#if 0 +// Unoptimized +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + size_t i; + assert(s != NULL); + PUTC(c, '"'); + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': PUTS(c, "\\\"", 2); break; + case '\\': PUTS(c, "\\\\", 2); break; + case '\b': PUTS(c, "\\b", 2); break; + case '\f': PUTS(c, "\\f", 2); break; + case '\n': PUTS(c, "\\n", 2); break; + case '\r': PUTS(c, "\\r", 2); break; + case '\t': PUTS(c, "\\t", 2); break; + default: + if (ch < 0x20) { + char buffer[7]; + sprintf(buffer, "\\u%04X", ch); + PUTS(c, buffer, 6); + } + else + PUTC(c, s[i]); + } + } + PUTC(c, '"'); +} +#else +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + size_t i, size; + char* head, *p; + assert(s != NULL); + p = head = lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */ + *p++ = '"'; + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': *p++ = '\\'; *p++ = '\"'; break; + case '\\': *p++ = '\\'; *p++ = '\\'; break; + case '\b': *p++ = '\\'; *p++ = 'b'; break; + case '\f': *p++ = '\\'; *p++ = 'f'; break; + case '\n': *p++ = '\\'; *p++ = 'n'; break; + case '\r': *p++ = '\\'; *p++ = 'r'; break; + case '\t': *p++ = '\\'; *p++ = 't'; break; + default: + if (ch < 0x20) { + *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0'; + *p++ = hex_digits[ch >> 4]; + *p++ = hex_digits[ch & 15]; + } + else + *p++ = s[i]; + } + } + *p++ = '"'; + c->top -= size - (p - head); +} +#endif + +static void lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + switch (v->type) { + case LEPT_NULL: PUTS(c, "null", 4); break; + case LEPT_FALSE: PUTS(c, "false", 5); break; + case LEPT_TRUE: PUTS(c, "true", 4); break; + case LEPT_NUMBER: c->top -= 32 - sprintf(lept_context_push(c, 32), "%.17g", v->u.n); break; + case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; + case LEPT_ARRAY: + PUTC(c, '['); + for (i = 0; i < v->u.a.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_value(c, &v->u.a.e[i]); + } + PUTC(c, ']'); + break; + case LEPT_OBJECT: + PUTC(c, '{'); + for (i = 0; i < v->u.o.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen); + PUTC(c, ':'); + lept_stringify_value(c, &v->u.o.m[i].v); + } + PUTC(c, '}'); + break; + default: assert(0 && "invalid type"); + } +} + +char* lept_stringify(const lept_value* v, size_t* length) { + lept_context c; + assert(v != NULL); + c.stack = (char*)malloc(c.size = LEPT_PARSE_STRINGIFY_INIT_SIZE); + c.top = 0; + lept_stringify_value(&c, v); + if (length) + *length = c.top; + PUTC(&c, '\0'); + return c.stack; +} + +void lept_free(lept_value* v) { + size_t i; + assert(v != NULL); + switch (v->type) { + case LEPT_STRING: + free(v->u.s.s); + break; + case LEPT_ARRAY: + for (i = 0; i < v->u.a.size; i++) + lept_free(&v->u.a.e[i]); + free(v->u.a.e); + break; + case LEPT_OBJECT: + for (i = 0; i < v->u.o.size; i++) { + free(v->u.o.m[i].k); + lept_free(&v->u.o.m[i].v); + } + free(v->u.o.m); + break; + default: break; + } + v->type = LEPT_NULL; +} + +lept_type lept_get_type(const lept_value* v) { + assert(v != NULL); + return v->type; +} + +int lept_get_boolean(const lept_value* v) { + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); + return v->type == LEPT_TRUE; +} + +void lept_set_boolean(lept_value* v, int b) { + lept_free(v); + v->type = b ? LEPT_TRUE : LEPT_FALSE; +} + +double lept_get_number(const lept_value* v) { + assert(v != NULL && v->type == LEPT_NUMBER); + return v->u.n; +} + +void lept_set_number(lept_value* v, double n) { + lept_free(v); + v->u.n = n; + v->type = LEPT_NUMBER; +} + +const char* lept_get_string(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.s; +} + +size_t lept_get_string_length(const lept_value* v) { + assert(v != NULL && v->type == LEPT_STRING); + return v->u.s.len; +} + +void lept_set_string(lept_value* v, const char* s, size_t len) { + assert(v != NULL && (s != NULL || len == 0)); + lept_free(v); + v->u.s.s = (char*)malloc(len + 1); + memcpy(v->u.s.s, s, len); + v->u.s.s[len] = '\0'; + v->u.s.len = len; + v->type = LEPT_STRING; +} + +size_t lept_get_array_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_ARRAY); + return v->u.a.size; +} + +lept_value* lept_get_array_element(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_ARRAY); + assert(index < v->u.a.size); + return &v->u.a.e[index]; +} + +size_t lept_get_object_size(const lept_value* v) { + assert(v != NULL && v->type == LEPT_OBJECT); + return v->u.o.size; +} + +const char* lept_get_object_key(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].k; +} + +size_t lept_get_object_key_length(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return v->u.o.m[index].klen; +} + +lept_value* lept_get_object_value(const lept_value* v, size_t index) { + assert(v != NULL && v->type == LEPT_OBJECT); + assert(index < v->u.o.size); + return &v->u.o.m[index].v; +} diff --git a/tutorial07_answer/leptjson.h b/tutorial07_answer/leptjson.h new file mode 100644 index 00000000..92cbfc51 --- /dev/null +++ b/tutorial07_answer/leptjson.h @@ -0,0 +1,72 @@ +#ifndef LEPTJSON_H__ +#define LEPTJSON_H__ + +#include /* size_t */ + +typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type; + +typedef struct lept_value lept_value; +typedef struct lept_member lept_member; + +struct lept_value { + union { + struct { lept_member* m; size_t size; }o; /* object: members, member count */ + struct { lept_value* e; size_t size; }a; /* array: elements, element count */ + struct { char* s; size_t len; }s; /* string: null-terminated string, string length */ + double n; /* number */ + }u; + lept_type type; +}; + +struct lept_member { + char* k; size_t klen; /* member key string, key string length */ + lept_value v; /* member value */ +}; + +enum { + LEPT_PARSE_OK = 0, + LEPT_PARSE_EXPECT_VALUE, + LEPT_PARSE_INVALID_VALUE, + LEPT_PARSE_ROOT_NOT_SINGULAR, + LEPT_PARSE_NUMBER_TOO_BIG, + LEPT_PARSE_MISS_QUOTATION_MARK, + LEPT_PARSE_INVALID_STRING_ESCAPE, + LEPT_PARSE_INVALID_STRING_CHAR, + LEPT_PARSE_INVALID_UNICODE_HEX, + LEPT_PARSE_INVALID_UNICODE_SURROGATE, + LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, + LEPT_PARSE_MISS_KEY, + LEPT_PARSE_MISS_COLON, + LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET +}; + +#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0) + +int lept_parse(lept_value* v, const char* json); +char* lept_stringify(const lept_value* v, size_t* length); + +void lept_free(lept_value* v); + +lept_type lept_get_type(const lept_value* v); + +#define lept_set_null(v) lept_free(v) + +int lept_get_boolean(const lept_value* v); +void lept_set_boolean(lept_value* v, int b); + +double lept_get_number(const lept_value* v); +void lept_set_number(lept_value* v, double n); + +const char* lept_get_string(const lept_value* v); +size_t lept_get_string_length(const lept_value* v); +void lept_set_string(lept_value* v, const char* s, size_t len); + +size_t lept_get_array_size(const lept_value* v); +lept_value* lept_get_array_element(const lept_value* v, size_t index); + +size_t lept_get_object_size(const lept_value* v); +const char* lept_get_object_key(const lept_value* v, size_t index); +size_t lept_get_object_key_length(const lept_value* v, size_t index); +lept_value* lept_get_object_value(const lept_value* v, size_t index); + +#endif /* LEPTJSON_H__ */ diff --git a/tutorial07_answer/test.c b/tutorial07_answer/test.c new file mode 100644 index 00000000..7e34cbb7 --- /dev/null +++ b/tutorial07_answer/test.c @@ -0,0 +1,491 @@ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include +#include +#include +#include "leptjson.h" + +static int main_ret = 0; +static int test_count = 0; +static int test_pass = 0; + +#define EXPECT_EQ_BASE(equality, expect, actual, format) \ + do {\ + test_count++;\ + if (equality)\ + test_pass++;\ + else {\ + fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\ + main_ret = 1;\ + }\ + } while(0) + +#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g") +#define EXPECT_EQ_STRING(expect, actual, alength) \ + EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength + 1) == 0, expect, actual, "%s") +#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s") +#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s") + +#if defined(_MSC_VER) +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%Iu") +#else +#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE((expect) == (actual), (size_t)expect, (size_t)actual, "%zu") +#endif + +static void test_parse_null() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_true() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 0); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); + lept_free(&v); +} + +static void test_parse_false() { + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); + lept_free(&v); +} + +#define TEST_NUMBER(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));\ + EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_number() { + TEST_NUMBER(0.0, "0"); + TEST_NUMBER(0.0, "-0"); + TEST_NUMBER(0.0, "-0.0"); + TEST_NUMBER(1.0, "1"); + TEST_NUMBER(-1.0, "-1"); + TEST_NUMBER(1.5, "1.5"); + TEST_NUMBER(-1.5, "-1.5"); + TEST_NUMBER(3.1416, "3.1416"); + TEST_NUMBER(1E10, "1E10"); + TEST_NUMBER(1e10, "1e10"); + TEST_NUMBER(1E+10, "1E+10"); + TEST_NUMBER(1E-10, "1E-10"); + TEST_NUMBER(-1E10, "-1E10"); + TEST_NUMBER(-1e10, "-1e10"); + TEST_NUMBER(-1E+10, "-1E+10"); + TEST_NUMBER(-1E-10, "-1E-10"); + TEST_NUMBER(1.234E+10, "1.234E+10"); + TEST_NUMBER(1.234E-10, "1.234E-10"); + TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + TEST_NUMBER(1.0000000000000002, "1.0000000000000002"); /* the smallest number > 1 */ + TEST_NUMBER( 4.9406564584124654e-324, "4.9406564584124654e-324"); /* minimum denormal */ + TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324"); + TEST_NUMBER( 2.2250738585072009e-308, "2.2250738585072009e-308"); /* Max subnormal double */ + TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308"); + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); /* Min normal positive double */ + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); /* Max double */ + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); +} + +#define TEST_STRING(expect, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\ + EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_string() { + TEST_STRING("", "\"\""); + TEST_STRING("Hello", "\"Hello\""); + TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); + TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); + TEST_STRING("Hello\0World", "\"Hello\\u0000World\""); + TEST_STRING("\x24", "\"\\u0024\""); /* Dollar sign U+0024 */ + TEST_STRING("\xC2\xA2", "\"\\u00A2\""); /* Cents sign U+00A2 */ + TEST_STRING("\xE2\x82\xAC", "\"\\u20AC\""); /* Euro sign U+20AC */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\uD834\\uDD1E\""); /* G clef sign U+1D11E */ + TEST_STRING("\xF0\x9D\x84\x9E", "\"\\ud834\\udd1e\""); /* G clef sign U+1D11E */ +} + +static void test_parse_array() { + size_t i, j; + lept_value v; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0))); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1))); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2))); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3))); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_array_element(&v, 4))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), lept_get_string_length(lept_get_array_element(&v, 4))); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]")); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v)); + for (i = 0; i < 4; i++) { + lept_value* a = lept_get_array_element(&v, i); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(a)); + EXPECT_EQ_SIZE_T(i, lept_get_array_size(a)); + for (j = 0; j < i; j++) { + lept_value* e = lept_get_array_element(a, j); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE((double)j, lept_get_number(e)); + } + } + lept_free(&v); +} + +static void test_parse_object() { + lept_value v; + size_t i; + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, " { } ")); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(0, lept_get_object_size(&v)); + lept_free(&v); + + lept_init(&v); + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, + " { " + "\"n\" : null , " + "\"f\" : false , " + "\"t\" : true , " + "\"i\" : 123 , " + "\"s\" : \"abc\", " + "\"a\" : [ 1, 2, 3 ]," + "\"o\" : { \"1\" : 1, \"2\" : 2, \"3\" : 3 }" + " } " + )); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(&v)); + EXPECT_EQ_SIZE_T(7, lept_get_object_size(&v)); + EXPECT_EQ_STRING("n", lept_get_object_key(&v, 0), lept_get_object_key_length(&v, 0)); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_object_value(&v, 0))); + EXPECT_EQ_STRING("f", lept_get_object_key(&v, 1), lept_get_object_key_length(&v, 1)); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_object_value(&v, 1))); + EXPECT_EQ_STRING("t", lept_get_object_key(&v, 2), lept_get_object_key_length(&v, 2)); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_object_value(&v, 2))); + EXPECT_EQ_STRING("i", lept_get_object_key(&v, 3), lept_get_object_key_length(&v, 3)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_object_value(&v, 3))); + EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_object_value(&v, 3))); + EXPECT_EQ_STRING("s", lept_get_object_key(&v, 4), lept_get_object_key_length(&v, 4)); + EXPECT_EQ_INT(LEPT_STRING, lept_get_type(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("abc", lept_get_string(lept_get_object_value(&v, 4)), lept_get_string_length(lept_get_object_value(&v, 4))); + EXPECT_EQ_STRING("a", lept_get_object_key(&v, 5), lept_get_object_key_length(&v, 5)); + EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(lept_get_object_value(&v, 5))); + EXPECT_EQ_SIZE_T(3, lept_get_array_size(lept_get_object_value(&v, 5))); + for (i = 0; i < 3; i++) { + lept_value* e = lept_get_array_element(lept_get_object_value(&v, 5), i); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(e)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(e)); + } + EXPECT_EQ_STRING("o", lept_get_object_key(&v, 6), lept_get_object_key_length(&v, 6)); + { + lept_value* o = lept_get_object_value(&v, 6); + EXPECT_EQ_INT(LEPT_OBJECT, lept_get_type(o)); + for (i = 0; i < 3; i++) { + lept_value* ov = lept_get_object_value(o, i); + EXPECT_TRUE('1' + i == lept_get_object_key(o, i)[0]); + EXPECT_EQ_SIZE_T(1, lept_get_object_key_length(o, i)); + EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(ov)); + EXPECT_EQ_DOUBLE(i + 1.0, lept_get_number(ov)); + } + } + lept_free(&v); +} + +#define TEST_PARSE_ERROR(error, json)\ + do {\ + lept_value v;\ + lept_init(&v);\ + v.type = LEPT_FALSE;\ + EXPECT_EQ_INT(error, lept_parse(&v, json));\ + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\ + lept_free(&v);\ + } while(0) + +static void test_parse_expect_value() { + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_PARSE_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +static void test_parse_invalid_value() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); /* at least one digit before '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); /* at least one digit after '.' */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "inf"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "nan"); + + /* invalid value in array */ + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[1,]"); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_VALUE, "[\"a\", nul]"); +} + +static void test_parse_root_not_singular() { + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); + + /* invalid number */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); + TEST_PARSE_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); +} + +static void test_parse_number_too_big() { + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); + TEST_PARSE_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); +} + +static void test_parse_miss_quotation_mark() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc"); +} + +static void test_parse_invalid_string_escape() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\""); +} + +static void test_parse_invalid_string_char() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\""); +} + +static void test_parse_invalid_unicode_hex() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u01\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u012\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u/000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\uG000\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0G00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u0/00\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u00G0\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000/\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u000G\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX, "\"\\u 123\""); +} + +static void test_parse_invalid_unicode_surrogate() { + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\\\\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uDBFF\""); + TEST_PARSE_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\uD800\\uE000\""); +} + +static void test_parse_miss_comma_or_square_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[1 2"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET, "[[]"); +} + +static void test_parse_miss_key() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{1:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{true:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{false:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{null:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{[]:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{{}:1,"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_KEY, "{\"a\":1,"); +} + +static void test_parse_miss_colon() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\"}"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COLON, "{\"a\",\"b\"}"); +} + +static void test_parse_miss_comma_or_curly_bracket() { + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1]"); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":1 \"b\""); + TEST_PARSE_ERROR(LEPT_PARSE_MISS_COMMA_OR_CURLY_BRACKET, "{\"a\":{}"); +} + +static void test_parse() { + test_parse_null(); + test_parse_true(); + test_parse_false(); + test_parse_number(); + test_parse_string(); + test_parse_array(); + test_parse_object(); + + test_parse_expect_value(); + test_parse_invalid_value(); + test_parse_root_not_singular(); + test_parse_number_too_big(); + test_parse_miss_quotation_mark(); + test_parse_invalid_string_escape(); + test_parse_invalid_string_char(); + test_parse_invalid_unicode_hex(); + test_parse_invalid_unicode_surrogate(); + test_parse_miss_comma_or_square_bracket(); + test_parse_miss_key(); + test_parse_miss_colon(); + test_parse_miss_comma_or_curly_bracket(); +} + +#define TEST_ROUNDTRIP(json)\ + do {\ + lept_value v;\ + char* json2;\ + size_t length;\ + lept_init(&v);\ + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\ + json2 = lept_stringify(&v, &length);\ + EXPECT_EQ_STRING(json, json2, length);\ + lept_free(&v);\ + free(json2);\ + } while(0) + +static void test_stringify_number() { + TEST_ROUNDTRIP("0"); + TEST_ROUNDTRIP("-0"); + TEST_ROUNDTRIP("1"); + TEST_ROUNDTRIP("-1"); + TEST_ROUNDTRIP("1.5"); + TEST_ROUNDTRIP("-1.5"); + TEST_ROUNDTRIP("3.25"); + TEST_ROUNDTRIP("1e+20"); + TEST_ROUNDTRIP("1.234e+20"); + TEST_ROUNDTRIP("1.234e-20"); + + TEST_ROUNDTRIP("1.0000000000000002"); /* the smallest number > 1 */ + TEST_ROUNDTRIP("4.9406564584124654e-324"); /* minimum denormal */ + TEST_ROUNDTRIP("-4.9406564584124654e-324"); + TEST_ROUNDTRIP("2.2250738585072009e-308"); /* Max subnormal double */ + TEST_ROUNDTRIP("-2.2250738585072009e-308"); + TEST_ROUNDTRIP("2.2250738585072014e-308"); /* Min normal positive double */ + TEST_ROUNDTRIP("-2.2250738585072014e-308"); + TEST_ROUNDTRIP("1.7976931348623157e+308"); /* Max double */ + TEST_ROUNDTRIP("-1.7976931348623157e+308"); +} + +static void test_stringify_string() { + TEST_ROUNDTRIP("\"\""); + TEST_ROUNDTRIP("\"Hello\""); + TEST_ROUNDTRIP("\"Hello\\nWorld\""); + TEST_ROUNDTRIP("\"\\\" \\\\ / \\b \\f \\n \\r \\t\""); + TEST_ROUNDTRIP("\"Hello\\u0000World\""); +} + +static void test_stringify_array() { + TEST_ROUNDTRIP("[]"); + TEST_ROUNDTRIP("[null,false,true,123,\"abc\",[1,2,3]]"); +} + +static void test_stringify_object() { + TEST_ROUNDTRIP("{}"); + TEST_ROUNDTRIP("{\"n\":null,\"f\":false,\"t\":true,\"i\":123,\"s\":\"abc\",\"a\":[1,2,3],\"o\":{\"1\":1,\"2\":2,\"3\":3}}"); +} + +static void test_stringify() { + TEST_ROUNDTRIP("null"); + TEST_ROUNDTRIP("false"); + TEST_ROUNDTRIP("true"); + test_stringify_number(); + test_stringify_string(); + test_stringify_array(); + test_stringify_object(); +} + +static void test_access_null() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_null(&v); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + lept_free(&v); +} + +static void test_access_boolean() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); +} + +static void test_access_number() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "a", 1); + lept_set_number(&v, 1234.5); + EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v)); + lept_free(&v); +} + +static void test_access_string() { + lept_value v; + lept_init(&v); + lept_set_string(&v, "", 0); + EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v)); + lept_set_string(&v, "Hello", 5); + EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v)); + lept_free(&v); +} + +static void test_access() { + test_access_null(); + test_access_boolean(); + test_access_number(); + test_access_string(); +} + +int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + test_parse(); + test_stringify(); + test_access(); + printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); + return main_ret; +} diff --git a/tutorial07_answer/tutorial07_answer.md b/tutorial07_answer/tutorial07_answer.md new file mode 100644 index 00000000..07e3d612 --- /dev/null +++ b/tutorial07_answer/tutorial07_answer.md @@ -0,0 +1,155 @@ +# 从零开始的 JSON 库教程(七):生成器解答篇 + +* Milo Yip +* 2017/1/5 + +本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第七个单元解答篇。解答代码位于 [json-tutorial/tutorial07_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial07_answer)。 + +## 1. 生成字符串 + +我们需要对一些字符进行转义,最简单的实现如下: + +~~~c +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + size_t i; + assert(s != NULL); + PUTC(c, '"'); + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': PUTS(c, "\\\"", 2); break; + case '\\': PUTS(c, "\\\\", 2); break; + case '\b': PUTS(c, "\\b", 2); break; + case '\f': PUTS(c, "\\f", 2); break; + case '\n': PUTS(c, "\\n", 2); break; + case '\r': PUTS(c, "\\r", 2); break; + case '\t': PUTS(c, "\\t", 2); break; + default: + if (ch < 0x20) { + char buffer[7]; + sprintf(buffer, "\\u%04X", ch); + PUTS(c, buffer, 6); + } + else + PUTC(c, s[i]); + } + } + PUTC(c, '"'); +} + +static void lept_stringify_value(lept_context* c, const lept_value* v) { + switch (v->type) { + /* ... */ + case LEPT_STRING: lept_stringify_string(c, v->u.s.s, v->u.s.len); break; + /* ... */ + } +} +~~~ + +注意到,十六进位输出的字母可以用大写或小写,我们这里选择了大写,所以 roundstrip 测试时也用大写。但这个并不是必然的,输出小写(用 `"\\u%04x"`)也可以。 + +## 2. 生成数组和对象 + +生成数组也是非常简单,只要输出 `[` 和 `]`,中间对逐个子值递归调用 `lept_stringify_value()`。只要注意在第一个元素后才加入 `,`。而对象也仅是多了一个键和 `:`。 + +~~~cs +static void lept_stringify_value(lept_context* c, const lept_value* v) { + size_t i; + switch (v->type) { + /* ... */ + case LEPT_ARRAY: + PUTC(c, '['); + for (i = 0; i < v->u.a.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_value(c, &v->u.a.e[i]); + } + PUTC(c, ']'); + break; + case LEPT_OBJECT: + PUTC(c, '{'); + for (i = 0; i < v->u.o.size; i++) { + if (i > 0) + PUTC(c, ','); + lept_stringify_string(c, v->u.o.m[i].k, v->u.o.m[i].klen); + PUTC(c, ':'); + lept_stringify_value(c, &v->u.o.m[i].v); + } + PUTC(c, '}'); + break; + /* ... */ + } +} +~~~ + +## 3. 优化 `lept_stringify_string()` + +最后,我们讨论一下优化。上面的 `lept_stringify_string()` 实现中,每次输出一个字符/字符串,都要调用 `lept_context_push()`。如果我们使用一些性能剖测工具,也可能会发现这个函数消耗较多 CPU。 + +~~~c +static void* lept_context_push(lept_context* c, size_t size) { + void* ret; + assert(size > 0); + if (c->top + size >= c->size) { // (1) + if (c->size == 0) + c->size = LEPT_PARSE_STACK_INIT_SIZE; + while (c->top + size >= c->size) + c->size += c->size >> 1; /* c->size * 1.5 */ + c->stack = (char*)realloc(c->stack, c->size); + } + ret = c->stack + c->top; // (2) + c->top += size; // (3) + return ret; // (4) +} +~~~ + +中间最花费时间的,应该会是 (1),需要计算而且作分支检查。即使使用 C99 的 `inline` 关键字(或使用宏)去减少函数调用的开销,这个分支也无法避免。 + +所以,一个优化的点子是,预先分配足够的内存,每次加入字符就不用做这个检查了。但多大的内存才足够呢?我们可以看到,每个字符可生成最长的形式是 `\u00XX`,占 6 个字符,再加上前后两个双引号,也就是共 `len * 6 + 2` 个输出字符。那么,使用 `char* p = lept_context_push()` 作一次分配后,便可以用 `*p++ = c` 去输出字符了。最后,再按实际输出量调整堆栈指针。 + +另一个小优化点,是自行编写十六进位输出,避免了 `printf()` 内解析格式的开销。 + +~~~c +static void lept_stringify_string(lept_context* c, const char* s, size_t len) { + static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + size_t i, size; + char* head, *p; + assert(s != NULL); + p = head = lept_context_push(c, size = len * 6 + 2); /* "\u00xx..." */ + *p++ = '"'; + for (i = 0; i < len; i++) { + unsigned char ch = (unsigned char)s[i]; + switch (ch) { + case '\"': *p++ = '\\'; *p++ = '\"'; break; + case '\\': *p++ = '\\'; *p++ = '\\'; break; + case '\b': *p++ = '\\'; *p++ = 'b'; break; + case '\f': *p++ = '\\'; *p++ = 'f'; break; + case '\n': *p++ = '\\'; *p++ = 'n'; break; + case '\r': *p++ = '\\'; *p++ = 'r'; break; + case '\t': *p++ = '\\'; *p++ = 't'; break; + default: + if (ch < 0x20) { + *p++ = '\\'; *p++ = 'u'; *p++ = '0'; *p++ = '0'; + *p++ = hex_digits[ch >> 4]; + *p++ = hex_digits[ch & 15]; + } + else + *p++ = s[i]; + } + } + *p++ = '"'; + c->top -= size - (p - head); +} +~~~ + +要注意的是,很多优化都是有代优的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 + +而第二个优化的缺点,就是有稍增加了一点程序体积。也许有人会问,为什么 `hex_digits` 不用字符串字面量 `"0123456789ABCDEF"`?其实是可以的,但这会多浪费 1 个字节(实际因数据对齐可能会浪费 4 个或更多)。 + +## 4. 总结 + +我们用 80 行左右的代码就实现了 JSON 生成器,并尝试了做一些简单的优化。除了这种最简单的功能,有一些 JSON 库还会提供一些美化功能,即加入缩进及换行。另外,有一些应用可能需要大量输出数字,那么就可能需要优化数字的输出。这方面可考虑 C++ 开源库 [double-conversion](https://github.com/google/double-conversion),以及参考本人另一篇文章《[RapidJSON 代码剖析(四):优化 Grisu](https://zhuanlan.zhihu.com/p/20092285)》。 + +现时数组和对象类型只有最基本的访问、修改函数,我们会在下一篇补完。 + +如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 From 933c4899a29b30e2a3bd66fe54f00e555d5694b0 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 5 Jan 2017 13:56:20 +0800 Subject: [PATCH 62/99] Fix links --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index b67b650f..04d33557 100644 --- a/readme.md +++ b/readme.md @@ -43,8 +43,8 @@ 3. [解析字符串](tutorial03/tutorial03.md)(2016/9/22 完成):使用 union 存储 variant、自动扩展的堆栈、JSON string 的语法、valgrind。练习最基本的 JSON string 类型的解析、内存释放。[解析字符串解答篇](tutorial03_answer/tutorial03_answer.md)(2016/9/27 完成)。 4. [Unicode](tutorial04/tutorial04.md)(2016/10/2 完成):Unicode 和 UTF-8 的基本知识、JSON string 的 unicode 处理。练习完成 JSON string 类型的解析。[Unicode 解答篇](tutorial04_answer/tutorial04_answer.md)(2016/10/6 完成)。 5. [解析数组](tutorial05/tutorial05.md)(2016/10/7 完成):JSON array 的语法。练习完成 JSON array 类型的解析、相关内存释放。[解析数组解答篇](tutorial05_answer/tutorial05_answer.md)(2016/10/13 完成)。 -6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial05_answer/tutorial06_answer.md)(2016/11/15 完成)。 -7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](2017/1/5 完成) +6. [解析对象](tutorial06/tutorial06.md)(2016/10/29 完成):JSON object 的语法、重构 string 解析函数。练习完成 JSON object 的解析、相关内存释放。[解析对象解答篇](tutorial06_answer/tutorial06_answer.md)(2016/11/15 完成)。 +7. [生成器](tutorial07/tutorial07.md)(2016/12/20 完成):JSON 生成过程、注意事项。练习完成 JSON 生成器。[生成器解答篇](tutorial07_answer/tutorial07_answer.md)(2017/1/5 完成) 8. 访问:JSON array/object 的访问及修改。练习完成相关功能。 9. 终点及新开始:加入 nativejson-benchmark 测试,与 RapidJSON 对比及展望。 From 434c63055c09cf8a9876e09e8bcdf8a3d415cf0d Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 6 Jan 2017 11:16:25 +0800 Subject: [PATCH 63/99] Typo --- tutorial07_answer/tutorial07_answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial07_answer/tutorial07_answer.md b/tutorial07_answer/tutorial07_answer.md index 07e3d612..b10ea1e3 100644 --- a/tutorial07_answer/tutorial07_answer.md +++ b/tutorial07_answer/tutorial07_answer.md @@ -142,7 +142,7 @@ static void lept_stringify_string(lept_context* c, const char* s, size_t len) { } ~~~ -要注意的是,很多优化都是有代优的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 +要注意的是,很多优化都是有代价的。第一个优化采取空间换时间的策略,对于只含一个字符串的JSON,很可能会分配多 6 倍内存;但对于正常含多个值的 JSON,多分配的内存可在之后的值所利用,不会造成太多浪费。 而第二个优化的缺点,就是有稍增加了一点程序体积。也许有人会问,为什么 `hex_digits` 不用字符串字面量 `"0123456789ABCDEF"`?其实是可以的,但这会多浪费 1 个字节(实际因数据对齐可能会浪费 4 个或更多)。 From 97a11ee740e7cebd044c7e1126d7f85dcc441754 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 9 Jan 2017 15:27:37 +0800 Subject: [PATCH 64/99] Update tutorial05.md --- tutorial05/tutorial05.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial05/tutorial05.md b/tutorial05/tutorial05.md index 84cc000f..c82c457d 100644 --- a/tutorial05/tutorial05.md +++ b/tutorial05/tutorial05.md @@ -31,7 +31,7 @@ JSON 数组的语法很简单,实现的难点不在语法上,而是怎样管 JSON 数组存储零至多个元素,最简单就是使用 C 语言的数组。数组最大的好处是能以 $O(1)$ 用索引访问任意元素,次要好处是内存布局紧凑,省内存之余还有高缓存一致性(cache coherence)。但数组的缺点是不能快速插入元素,而且我们在解析 JSON 数组的时候,还不知道应该分配多大的数组才合适。 -另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且片历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 +另一个选择是链表(linked list),它的最大优点是可快速地插入元素(开端、末端或中间),但需要以 $O(n)$ 时间去经索引取得内容。如果我们只需顺序遍历,那么是没有问题的。还有一个小缺点,就是相对数组而言,链表在存储每个元素时有额外内存开销(存储下一节点的指针),而且遍历时元素所在的内存可能不连续,令缓存不命中(cache miss)的机会上升。 我见过一些 JSON 库选择了链表,而这里则选择了数组。我们将会通过之前在解析字符串时实现的堆栈,来解决解析 JSON 数组时未知数组大小的问题。 From a7def3d6026e33862ba8519ce8bb5f52174cb4f5 Mon Sep 17 00:00:00 2001 From: Nov11 <529079634@qq.com> Date: Wed, 21 Jun 2017 01:48:57 +0800 Subject: [PATCH 65/99] fix typo of tutorial03 readme --- tutorial03/tutorial03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial03/tutorial03.md b/tutorial03/tutorial03.md index 74817e4e..113eea78 100644 --- a/tutorial03/tutorial03.md +++ b/tutorial03/tutorial03.md @@ -38,7 +38,7 @@ quotation-mark = %x22 ; " unescaped = %x20-21 / %x23-5B / %x5D-10FFFF ~~~ -简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分开无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 +简单翻译一下,JSON 字符串是由前后两个双引号夹着零至多个字符。字符分为无转义字符或转义序列。转义序列有 9 种,都是以反斜线开始,如常见的 `\n` 代表换行符。比较特殊的是 `\uXXXX`,当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。 无转义字符就是普通的字符,语法中列出了合法的码点范围(码点还是在下单元才介绍)。要注意的是,该范围不包括 0 至 31、双引号和反斜线,这些码点都必须要使用转义方式表示。 From ad9ee017fc9f199ec550b7b382021852569d14ba Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 2 Jun 2018 19:18:30 +0800 Subject: [PATCH 66/99] 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 67/99] 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 68/99] 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 69/99] 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 70/99] 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 71/99] =?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 72/99] 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 73/99] 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 74/99] =?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 75/99] 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 76/99] =?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 77/99] 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 78/99] 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 79/99] =?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 80/99] 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 81/99] 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 82/99] 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 83/99] =?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 84/99] 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 85/99] 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 86/99] 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 87/99] 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 88/99] 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 89/99] =?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 90/99] 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 91/99] 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 92/99] 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 93/99] 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 94/99] 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 95/99] 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 96/99] =?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 97/99] 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 98/99] 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 99/99] 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 的数字语法。建议可使用以下两个宏去简化一下代码: