diff --git a/.gitignore b/.gitignore index e3412016..567609b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -*/build/ +build/ diff --git a/tutorial01/CMakeLists.txt b/CMakeLists.txt similarity index 100% rename from tutorial01/CMakeLists.txt rename to CMakeLists.txt diff --git a/tutorial01/images/cmake-gui.png b/images/cmake-gui.png similarity index 100% rename from tutorial01/images/cmake-gui.png rename to images/cmake-gui.png diff --git a/tutorial01/images/makefile b/images/makefile similarity index 100% rename from tutorial01/images/makefile rename to images/makefile diff --git a/tutorial01/images/requirement.dot b/images/requirement.dot similarity index 100% rename from tutorial01/images/requirement.dot rename to images/requirement.dot diff --git a/tutorial01/images/requirement.png b/images/requirement.png similarity index 100% rename from tutorial01/images/requirement.png rename to images/requirement.png diff --git a/leptjson.c b/leptjson.c new file mode 100644 index 00000000..56acb346 --- /dev/null +++ b/leptjson.c @@ -0,0 +1,95 @@ +#include "leptjson.h" +#include /* assert() */ +#include /* NULL */ + +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) + +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_null(lept_context* c, lept_value* v) { + EXPECT(c, 'n'); + if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') + return LEPT_PARSE_INVALID_VALUE; + c->json += 3; + v->type = LEPT_NULL; + return LEPT_PARSE_OK; +} + +static int lept_parse_true(lept_context *c, lept_value *v) +{ + EXPECT(c, 't'); + if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') + return LEPT_PARSE_INVALID_VALUE; + c->json += 3; + v->type = LEPT_TRUE; + return LEPT_PARSE_OK; +} + +static int lept_parse_false(lept_context *c, lept_value *v) +{ + EXPECT(c, 'f'); + if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') + return LEPT_PARSE_INVALID_VALUE; + c->json += 4; + v->type = LEPT_FALSE; + return LEPT_PARSE_OK; +} + +static int lept_parse_number(lept_context *c, lept_value *v) +{ + char *end; + v->n = strtod(c->json, &end); + if (c->json == end) + return LEPT_PARSE_INVALID_VALUE; + c->json = end; + v->type = LEPT_NUMBER; + return LEPT_PARSE_OK; +} + + +static int lept_parse_value(lept_context* c, lept_value* v) { + switch (*c->json) { + case 'n': return lept_parse_null(c, v); + case 't': return lept_parse_true(c, v); + case 'f': return lept_parse_false(c, v); + case '\0': return LEPT_PARSE_EXPECT_VALUE; + default: return lept_parse_number(c,v); + } +} + + + +int lept_parse(lept_value* v, const char* json) { + lept_context c; + int ret; + assert(v != NULL); + c.json = json; + v->type = LEPT_NULL; + lept_parse_whitespace(&c); + if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) { + lept_parse_whitespace(&c); + if (*c.json != '\0') + ret = LEPT_PARSE_ROOT_NOT_SINGULAR; + } + return ret; + //return lept_parse_value(&c, v); +} + +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/tutorial01_answer/leptjson.h b/leptjson.h similarity index 61% rename from tutorial01_answer/leptjson.h rename to leptjson.h index 9b65d22a..f4ff9b6d 100644 --- a/tutorial01_answer/leptjson.h +++ b/leptjson.h @@ -1,9 +1,18 @@ -#ifndef LEPTJSON_H__ +#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 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; @@ -16,6 +25,8 @@ enum { int lept_parse(lept_value* v, const char* json); +double lept_get_number(const lept_value *v); + lept_type lept_get_type(const lept_value* v); #endif /* LEPTJSON_H__ */ diff --git a/tutorial01_answer/test.c b/test.c similarity index 56% rename from tutorial01_answer/test.c rename to test.c index a601f157..7d0517dc 100644 --- a/tutorial01_answer/test.c +++ b/test.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include "leptjson.h" @@ -18,8 +18,24 @@ static int test_pass = 0; }\ } while(0) +#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) + #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") +#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_null() { lept_value v; v.type = LEPT_FALSE; @@ -27,20 +43,6 @@ static void test_parse_null() { EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); } -static void test_parse_true() { - lept_value v; - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); - EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); -} - -static void test_parse_false() { - lept_value v; - v.type = LEPT_TRUE; - EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); - EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); -} - static void test_parse_expect_value() { lept_value v; @@ -71,10 +73,51 @@ static void test_parse_root_not_singular() { EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); } +static void test_parse_true(){ + lept_value v; + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); +} + +static void test_parse_false() { + lept_value v; + v.type = LEPT_TRUE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); + EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); +} + +static void test_parse_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); +} + +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 */ +} + static void test_parse() { test_parse_null(); - test_parse_true(); - test_parse_false(); + test_parse_true(); + test_parse_false(); test_parse_expect_value(); test_parse_invalid_value(); test_parse_root_not_singular(); diff --git a/tutorial01/tutorial01.md b/tutorial01.md similarity index 100% rename from tutorial01/tutorial01.md rename to tutorial01.md diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c deleted file mode 100644 index 5299fe1d..00000000 --- a/tutorial01/leptjson.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "leptjson.h" -#include /* assert() */ -#include /* NULL */ - -#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) - -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_null(lept_context* c, lept_value* v) { - EXPECT(c, 'n'); - if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_NULL; - return LEPT_PARSE_OK; -} - -static int lept_parse_value(lept_context* c, lept_value* v) { - switch (*c->json) { - case 'n': return lept_parse_null(c, v); - case '\0': return LEPT_PARSE_EXPECT_VALUE; - default: return LEPT_PARSE_INVALID_VALUE; - } -} - -int lept_parse(lept_value* v, const char* json) { - lept_context c; - assert(v != NULL); - c.json = json; - v->type = LEPT_NULL; - lept_parse_whitespace(&c); - return lept_parse_value(&c, v); -} - -lept_type lept_get_type(const lept_value* v) { - assert(v != NULL); - return v->type; -} diff --git a/tutorial01/leptjson.h b/tutorial01/leptjson.h deleted file mode 100644 index 9b65d22a..00000000 --- a/tutorial01/leptjson.h +++ /dev/null @@ -1,21 +0,0 @@ -#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 { - lept_type type; -}lept_value; - -enum { - LEPT_PARSE_OK = 0, - LEPT_PARSE_EXPECT_VALUE, - LEPT_PARSE_INVALID_VALUE, - LEPT_PARSE_ROOT_NOT_SINGULAR -}; - -int lept_parse(lept_value* v, const char* json); - -lept_type lept_get_type(const lept_value* v); - -#endif /* LEPTJSON_H__ */ diff --git a/tutorial01/test.c b/tutorial01/test.c deleted file mode 100644 index e7672181..00000000 --- a/tutorial01/test.c +++ /dev/null @@ -1,71 +0,0 @@ -#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") - -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_expect_value() { - lept_value v; - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, "")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " ")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); -} - -static void test_parse_invalid_value() { - lept_value v; - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); -} - -static void test_parse_root_not_singular() { - lept_value v; - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); -} - -static void test_parse() { - test_parse_null(); - test_parse_expect_value(); - test_parse_invalid_value(); - test_parse_root_not_singular(); -} - -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/tutorial01_answer/CMakeLists.txt b/tutorial01_answer/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial01_answer/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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/tutorial01_answer/leptjson.c b/tutorial01_answer/leptjson.c deleted file mode 100644 index f4f2b17b..00000000 --- a/tutorial01_answer/leptjson.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "leptjson.h" -#include /* assert() */ -#include /* NULL */ - -#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) - -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_true(lept_context* c, lept_value* v) { - EXPECT(c, 't'); - if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_TRUE; - return LEPT_PARSE_OK; -} - -static int lept_parse_false(lept_context* c, lept_value* v) { - EXPECT(c, 'f'); - if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 4; - v->type = LEPT_FALSE; - return LEPT_PARSE_OK; -} - -static int lept_parse_null(lept_context* c, lept_value* v) { - EXPECT(c, 'n'); - if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_NULL; - return LEPT_PARSE_OK; -} - -static int lept_parse_value(lept_context* c, lept_value* v) { - switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); - case '\0': return LEPT_PARSE_EXPECT_VALUE; - default: return LEPT_PARSE_INVALID_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') - ret = LEPT_PARSE_ROOT_NOT_SINGULAR; - } - return ret; -} - -lept_type lept_get_type(const lept_value* v) { - assert(v != NULL); - return v->type; -} diff --git a/tutorial01_answer/tutorial01_answer.md b/tutorial01_answer/tutorial01_answer.md deleted file mode 100644 index 99865363..00000000 --- a/tutorial01_answer/tutorial01_answer.md +++ /dev/null @@ -1,128 +0,0 @@ -# 从零开始的 JSON 库教程(一):启程解答篇 - -* Milo Yip -* 2016/9/17 - -本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第一个单元解答篇。解答代码位于 [json-tutorial/tutorial01_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial01_answer/)。 - -## 1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR - -单元测试失败的是这一行: - -~~~c -EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x")); -~~~ - -我们从 JSON 语法发现,JSON 文本应该有 3 部分: - -~~~ -JSON-text = ws value ws -~~~ - -但原来的 `lept_parse()` 只处理了前两部分。我们只需要加入第三部分,解析空白,然后检查 JSON 文本是否完结: - -~~~c -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') - ret = LEPT_PARSE_ROOT_NOT_SINGULAR; - } - return ret; -} -~~~ - -有一些 JSON 解析器完整解析一个值之后就会顺利返回,这是不符合标准的。但有时候也有另一种需求,文本中含多个 JSON 或其他文本串接在一起,希望当完整解析一个值之后就停下来。因此,有一些 JSON 解析器会提供这种选项,例如 RapidJSON 的 `kParseStopWhenDoneFlag`。 - -## 2. true/false 单元测试 - -此问题很简单,只需参考 `test_parse_null()` 加入两个测试函数: - -~~~c -static void test_parse_true() { - lept_value v; - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); - EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v)); -} - -static void test_parse_false() { - lept_value v; - v.type = LEPT_TRUE; - EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false")); - EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v)); -} - -static void test_parse() { - test_parse_null(); - test_parse_true(); - test_parse_false(); - test_parse_expect_value(); - test_parse_invalid_value(); - test_parse_root_not_singular(); -} -~~~ - -但要记得在上一级的测试函数 `test_parse()` 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出告警: - -~~~ -test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function] -static void test_parse_true() { - ^ -~~~ - -因为 static 函数的意思是指,该函数只作用于编译单元中,那么没有被调用时,编译器是能发现的。 - -### 3. true/false 解析 - -这部分很简单,只要参考 `lept_parse_null()`,再写两个函数,然后在 `lept_parse_value` 按首字符分派。 - -~~~c -static int lept_parse_true(lept_context* c, lept_value* v) { - EXPECT(c, 't'); - if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_TRUE; - return LEPT_PARSE_OK; -} - -static int lept_parse_false(lept_context* c, lept_value* v) { - EXPECT(c, 'f'); - if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 4; - v->type = LEPT_FALSE; - return LEPT_PARSE_OK; -} - -static int lept_parse_value(lept_context* c, lept_value* v) { - switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); - case '\0': return LEPT_PARSE_EXPECT_VALUE; - default: return LEPT_PARSE_INVALID_VALUE; - } -} -~~~ - -其实这 3 种类型都是解析字面量,可以使用单一个函数实现,例如用这种方式调用: - -~~~c - case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL); -~~~ - -这样可以减少一些重复代码,不过可能有少许额外性能开销。 - -## 4. 总结 - -如果你能完成这个练习,恭喜你!我想你通过亲自动手,会对教程里所说的有更深入的理解。如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 - -下一单元是和数字类型相关,敬请期待。 diff --git a/tutorial02/CMakeLists.txt b/tutorial02/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial02/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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/images/number.png b/tutorial02/images/number.png deleted file mode 100644 index cc58babe..00000000 Binary files a/tutorial02/images/number.png and /dev/null differ diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c deleted file mode 100644 index 7693e43b..00000000 --- a/tutorial02/leptjson.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "leptjson.h" -#include /* assert() */ -#include /* NULL, strtod() */ - -#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) - -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_true(lept_context* c, lept_value* v) { - EXPECT(c, 't'); - if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_TRUE; - return LEPT_PARSE_OK; -} - -static int lept_parse_false(lept_context* c, lept_value* v) { - EXPECT(c, 'f'); - if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 4; - v->type = LEPT_FALSE; - return LEPT_PARSE_OK; -} - -static int lept_parse_null(lept_context* c, lept_value* v) { - EXPECT(c, 'n'); - if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_NULL; - return LEPT_PARSE_OK; -} - -static int lept_parse_number(lept_context* c, lept_value* v) { - char* end; - /* \TODO validate number */ - v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; - c->json = end; - v->type = LEPT_NUMBER; - return LEPT_PARSE_OK; -} - -static int lept_parse_value(lept_context* c, lept_value* v) { - switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); - 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/leptjson.h b/tutorial02/leptjson.h deleted file mode 100644 index 4818278c..00000000 --- a/tutorial02/leptjson.h +++ /dev/null @@ -1,25 +0,0 @@ -#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/test.c b/tutorial02/test.c deleted file mode 100644 index 6e3ebed2..00000000 --- a/tutorial02/test.c +++ /dev/null @@ -1,138 +0,0 @@ -#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 */ -} - -#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, "?"); - -#if 0 - /* 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"); -#endif -} - -static void test_parse_root_not_singular() { - TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); - -#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, "0x0"); - TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123"); -#endif -} - -static void test_parse_number_too_big() { -#if 0 - TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); - TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); -#endif -} - -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/tutorial02.md b/tutorial02/tutorial02.md deleted file mode 100644 index 5f520b2b..00000000 --- a/tutorial02/tutorial02.md +++ /dev/null @@ -1,232 +0,0 @@ -# 从零开始的 JSON 库教程(二):解析数字 - -* Milo Yip -* 2016/9/18 - -本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第二个单元。本单元的源代码位于 [json-tutorial/tutorial02](https://github.com/miloyip/json-tutorial/blob/master/tutorial02/)。 - -本单元内容: - -1. [初探重构](#初探重构) -2. [JSON 数字语法](#json-数字语法) -3. [数字表示方式](#数字表示方式) -4. [单元测试](#单元测试) -5. [十进制转换至二进制](#十进制转换至二进制) -6. [总结与练习](#总结与练习) -7. [参考](#参考) -8. [常见问题](#常见问题) - -# 1. 初探重构 - -在讨论解析数字之前,我们再补充 TDD 中的一个步骤──重构(refactoring)。根据[1],重构是一个这样的过程: - -> 在不改变代码外在行为的情况下,对代码作出修改,以改进程序的内部结构。 - -在 TDD 的过程中,我们的目标是编写代码去通过测试。但由于这个目标的引导性太强,我们可能会忽略正确性以外的软件品质。在通过测试之后,代码的正确性得以保证,我们就应该审视现时的代码,看看有没有地方可以改进,而同时能维持测试顺利通过。我们可以安心地做各种修改,因为我们有单元测试,可以判断代码在修改后是否影响原来的行为。 - -那么,哪里要作出修改?Beck 和 Fowler([1] 第 3 章)认为程序员要培养一种判断能力,找出程序中的坏味道。例如,在第一单元的练习中,可能大部分人都会复制 `lept_parse_null()` 的代码,作一些修改,成为 `lept_parse_true()` 和 `lept_parse_false()`。如果我们再审视这 3 个函数,它们非常相似。这违反编程中常说的 DRY(don't repeat yourself)原则。本单元的第一个练习题,就是尝试合并这 3 个函数。 - -另外,我们也可能发现,单元测试代码也有很重复的代码,例如 `test_parse_invalid_value()` 中我们每次测试一个不合法的 JSON 值,都有 4 行相似的代码。我们可以把它用宏的方式把它们简化: - -~~~c -#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, " "); -} -~~~ - -最后,我希望指出,软件的架构难以用单一标准评分,重构时要考虑平衡各种软件品质。例如上述把 3 个函数合并后,优点是减少重复的代码,维护较容易,但缺点可能是带来性能的少量影响。 - -# 2. JSON 数字语法 - -回归正题,本单元的重点在于解析 JSON number 类型。我们先看看它的语法: - -~~~ -number = [ "-" ] int [ frac ] [ exp ] -int = "0" / digit1-9 *digit -frac = "." 1*digit -exp = ("e" / "E") ["-" / "+"] 1*digit -~~~ - -number 是以十进制表示,它主要由 4 部分顺序组成:负号、整数、小数、指数。只有整数是必需部分。注意和直觉可能不同的是,正号是不合法的。 - -整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,`0123` 不是一个合法的 JSON 数字。 - -小数部分比较直观,就是小数点后是一或多个数字(0-9)。 - -JSON 可使用科学记数法,指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。 - -JSON 标准 [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径: - -![number](images/number.png) - -上一单元的 null、false、true 在解析后,我们只需把它们存储为类型。但对于数字,我们要考虑怎么存储解析后的结果。 - -# 3. 数字表示方式 - -从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数(floating point number),因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,leptjson 选择以双精度浮点数(C 中的 `double` 类型)来存储 JSON 数字。 - -我们为 `lept_value` 添加成员: - -~~~c -typedef struct { - double n; - lept_type type; -}lept_value; -~~~ - -仅当 `type == LEPT_NUMBER` 时,`n` 才表示 JSON 数字的数值。所以获取该值的 API 是这么实现的: - -~~~c -double lept_get_number(const lept_value* v) { - assert(v != NULL && v->type == LEPT_NUMBER); - return v->n; -} -~~~ - -使用者应确保类型正确,才调用此 API。我们继续使用断言来保证。 - -# 4. 单元测试 - -我们定义了 API 之后,按照 TDD,我们可以先写一些单元测试。这次我们使用多行的宏的减少重复代码: - -~~~c -#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 */ -} -~~~ - -以上这些都是很基本的测试用例,也可供调试用。大部分情况下,测试案例不能穷举所有可能性。因此,除了加入一些典型的用例,我们也常会使用一些边界值,例如最大值等。练习中会让同学找一些边界值作为用例。 - -除了这些合法的 JSON,我们也要写一些不合语法的用例: - -~~~c -static void test_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"); -} -~~~ - -# 5. 十进制转换至二进制 - -我们需要把十进制的数字转换成二进制的 `double`。这并不是容易的事情 [2]。为了简单起见,leptjson 将使用标准库的 [`strtod()`](http://en.cppreference.com/w/c/string/byte/strtof) 来进行转换。`strtod()` 可转换 JSON 所要求的格式,但问题是,一些 JSON 不容许的格式,`strtod()` 也可转换,所以我们需要自行做格式校验。 - -~~~c -#include /* NULL, strtod() */ - -static int lept_parse_number(lept_context* c, lept_value* v) { - char* end; - /* \TODO validate number */ - v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; - c->json = end; - v->type = LEPT_NUMBER; - return LEPT_PARSE_OK; -} -~~~ - -加入了 number 后,value 的语法变成: - -~~~ -value = null / false / true / number -~~~ - -记得在第一单元中,我们说可以用一个字符就能得知 value 是什么类型,有 11 个字符可判断 number: - -* 0-9/- ➔ number - -但是,由于我们在 `lept_parse_number()` 内部将会校验输入是否正确的值,我们可以简单地把余下的情况都交给 `lept_parse_number()`: - -~~~c -static int lept_parse_value(lept_context* c, lept_value* v) { - switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); - default: return lept_parse_number(c, v); - case '\0': return LEPT_PARSE_EXPECT_VALUE; - } -} -~~~ - -# 6. 总结与练习 - -本单元讲述了 JSON 数字类型的语法,以及 leptjson 所采用的自行校验+`strtod()`转换为 `double` 的方案。实际上一些 JSON 库会采用更复杂的方案,例如支持 64 位带符号/无符号整数,自行实现转换。以我的个人经验,解析/生成数字类型可以说是 RapidJSON 中最难实现的部分,也是 RapidJSON 高效性能的原因,有机会再另外撰文解释。 - -此外我们谈及,重构与单元测试是互相依赖的软件开发技术,适当地运用可提升软件的品质。之后的单元还会有相关的话题。 - -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` 额外两个标准库头文件。) - -以上最重要的是第 3 条题目,就是要校验 JSON 的数字语法。建议可使用以下两个宏去简化一下代码: - -~~~c -#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') -#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9') -~~~ - -另一提示,在校验成功以后,我们不再使用 `end` 指针去检测 `strtod()` 的正确性,第二个参数可传入 `NULL`。 - -如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。 - -# 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. 常见问题 - -1. 为什么要把一些测试代码以 `#if 0 ... #endif` 禁用? - - 因为在做第 1 个练习题时,我希望能 100% 通过测试,方便做重构。另外,使用 `#if 0 ... #endif` 而不使用 `/* ... */`,是因为 C 的注释不支持嵌套(nested),而 `#if ... #endif` 是支持嵌套的。代码中已有注释时,用 `#if 0 ... #endif` 去禁用代码是一个常用技巧,而且可以把 `0` 改为 `1` 去恢复。 - -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 提出及协助解答这个问题。 - -其他常见问答将会从评论中整理。 diff --git a/tutorial02_answer/CMakeLists.txt b/tutorial02_answer/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial02_answer/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 0a2ca875..00000000 --- a/tutorial02_answer/leptjson.c +++ /dev/null @@ -1,96 +0,0 @@ -#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 || 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 deleted file mode 100644 index 4818278c..00000000 --- a/tutorial02_answer/leptjson.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 deleted file mode 100644 index ac0186d1..00000000 --- a/tutorial02_answer/test.c +++ /dev/null @@ -1,142 +0,0 @@ -#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"); - 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 deleted file mode 100644 index 37451ae9..00000000 --- a/tutorial02_answer/tutorial02_answer.md +++ /dev/null @@ -1,166 +0,0 @@ -# 从零开始的 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 的 [double-conversion](https://github.com/google/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 用一行来排版,而没用采用传统两行缩进风格,我个人认为在不影响阅读时可以这样弹性处理。当然那些 for 也可分拆成三行: - -~~~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) 中提出,让所有人一起讨论。 diff --git a/tutorial03/CMakeLists.txt b/tutorial03/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial03/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 55ed2163..00000000 --- a/tutorial03/images/makefile +++ /dev/null @@ -1,5 +0,0 @@ -%.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 deleted file mode 100644 index 2fcc402a..00000000 --- a/tutorial03/images/union_layout.dot +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index d5598433..00000000 Binary files a/tutorial03/images/union_layout.png and /dev/null differ diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c deleted file mode 100644 index 07f7e2c7..00000000 --- a/tutorial03/leptjson.c +++ /dev/null @@ -1,191 +0,0 @@ -#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, (const char*)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 deleted file mode 100644 index d1d4e9d1..00000000 --- a/tutorial03/leptjson.h +++ /dev/null @@ -1,47 +0,0 @@ -#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 deleted file mode 100644 index ac788aca..00000000 --- a/tutorial03/test.c +++ /dev/null @@ -1,233 +0,0 @@ -#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 deleted file mode 100644 index 74817e4e..00000000 --- a/tutorial03/tutorial03.md +++ /dev/null @@ -1,279 +0,0 @@ -# 从零开始的 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-字符串语法) -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 种转义序列: - -~~~ -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]): - -~~~c -#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, (const char*)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. 常见问题 - -其他常见问答将会从评论中整理。 diff --git a/tutorial03_answer/CMakeLists.txt b/tutorial03_answer/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial03_answer/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 89117e15..00000000 --- a/tutorial03_answer/leptjson.c +++ /dev/null @@ -1,217 +0,0 @@ -#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 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, (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; - 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 deleted file mode 100644 index d1d4e9d1..00000000 --- a/tutorial03_answer/leptjson.h +++ /dev/null @@ -1,47 +0,0 @@ -#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 deleted file mode 100644 index 3e91754e..00000000 --- a/tutorial03_answer/test.c +++ /dev/null @@ -1,245 +0,0 @@ -#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\""); -} - -#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() { -#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 deleted file mode 100644 index 77d56084..00000000 --- a/tutorial03_answer/tutorial03_answer.md +++ /dev/null @@ -1,232 +0,0 @@ -# 从零开始的 JSON 库教程(三):解析字符串解答篇 - -* Milo Yip -* 2016/9/27 - -本文是[《从零开始的 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()`,怎样能发现这些内存泄漏? - -## 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)`: - -~~~c -void lept_set_boolean(lept_value* v, int b) { - /* lept_free(v); */ - v->type = b ? LEPT_TRUE : LEPT_FALSE; -} -~~~ - -再次按 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 -==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) 中提出,让所有人一起讨论。 diff --git a/tutorial04/CMakeLists.txt b/tutorial04/CMakeLists.txt deleted file mode 100644 index 49ba19de..00000000 --- a/tutorial04/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 45c5e086..00000000 Binary files a/tutorial04/images/Utf8webgrowth.png and /dev/null differ diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c deleted file mode 100644 index 0a123bf2..00000000 --- a/tutorial04/leptjson.c +++ /dev/null @@ -1,231 +0,0 @@ -#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 deleted file mode 100644 index 1a5aa367..00000000 --- a/tutorial04/leptjson.h +++ /dev/null @@ -1,49 +0,0 @@ -#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 deleted file mode 100644 index beaa8724..00000000 --- a/tutorial04/test.c +++ /dev/null @@ -1,279 +0,0 @@ -#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\""); -} - -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 deleted file mode 100644 index a5091176..00000000 --- a/tutorial04/tutorial04.md +++ /dev/null @@ -1,157 +0,0 @@ -# 从零开始的 JSON 库教程(四):Unicode - -* Milo Yip -* 2016/10/2 - -本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/json-tutorial)的第四个单元。代码位于 [json-tutorial/tutorial04](https://github.com/miloyip/json-tutorial/tree/master/tutorial04)。 - -本单元内容: - -1. [Unicode](#1-unicode) -2. [需求](#2-需求) -3. [UTF-8 编码](#3-utf-8-编码) -4. [实现 `\uXXXX` 解析](#4-实现-uxxxx-解析) -5. [总结与练习](#5-总结与练习) - -## 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/Utf8webgrowth.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 = 11100000 */ - 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) 中提出,让所有人一起讨论。