From 46828fe9d8760814ea77928ae1d2b1a33bf0bd18 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 21:43:52 +0800 Subject: [PATCH 01/23] my homework01 --- my_note.md | 74 +++++++++++++++++++++++++++++++++++++++++++ tutorial01/leptjson.c | 38 ++++++++++++++++++++++ tutorial01/test.c | 16 ++++++++++ 3 files changed, 128 insertions(+) create mode 100644 my_note.md diff --git a/my_note.md b/my_note.md new file mode 100644 index 00000000..a9265a70 --- /dev/null +++ b/my_note.md @@ -0,0 +1,74 @@ +## tutorial01学习笔记 + +要点: + +* CMake +* API设计(.h)与实现(.c) +* 测试驱动TDD + +### 关于测试驱动的开发流程 + +1. 加入一个测试 +2. 运行所有测试,新测试将会失败 +3. 编写实现代码 +4. 运行所有测试,若测试失败,回到3 +5. 重构代码 +6. 回到1 + +TDD是先写测试后实现,优点:需求明确,刚好满足测试,不会写不需要的代码,可靠。 + +不论TDD还是先实现后测试,都应该保证足够的覆盖率的单元测试。 + +我认为可以写一个test.h,设计一些基础的宏,然后每个文件写对应的测试用例。本代码为了简便只有test.c + +### 学到的知识 +milo yip的代码写得极好!思路非常清晰且层次分明,我试着还原一下思路, + +#### API的设计思路 + +(1)先考虑最终的API效果 + +首先是宏观的总体目标,是对外的pulic API + +`int lept_parse(lept_value* v, const char* json);` + +parse取词的话,返回值用于表示结果对错,参数有两个,out为一个struct lept_value,用于保存结果,in为字符串。 + +那么这个数据结构里面的内容呢?这个后续交代, + +想合理地表达null,boolean,string,array,object,float这六种JSON类型,可能的struct应该会是: + +``` c +typedef enum { LEPT_NULL, LEPT_TRUE, LEPT_FALSE, LEPT_ARRAY, LEPT_OBJECT, LEPT_FLOAT } lept_type; +typedef struct lept_value_t { + lept_type t; //保存数据类型 +} lept_value; +``` + +注意这里的lept_value数据结构只包含一个枚举值,这是null和boolean特有的简化版, + +如果是array或者其他需要修改此结构,对此我还没有比较好的思路,因为如果子结构包含信息过多,对于null和boolean而言又略显多余。 + +如果是一个较少信息量的类型,估计应该是加入一个`void*`类型指针,用于指向特定结构,例如string指向字符串,array指向`lept_value_t*`数值. + +是否需要size等信息,拭目以待,我觉得如果不放在lept_value_t里面也会放在子类型里面。可能放在子类型里面更好,毕竟很多类型不需要。 + +(2)余下内容太多,自行体会吧,不写了 + + +#### TDD的设计思路 +首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 + +**Milo的解答中说,原因是为了出错后显示的__LINE__宏找到对应位置,而不是每次都在该函数一个位置** + +Milo Yip用了 EXPECT_EQ_BASE这个宏作为EXPECT_EQ_INT的辅助宏非常巧妙, +具体参见`test.c` + +EXPECT_EQ_BASE中包含了与类型无关的内容,用format还获得实际不同类型的情况, +以及equality表达式更像是一个谓词Predicate,这样不仅可用于相等表达式。 + +我个人认为这种通用性强的代码应该在重构中写,实现的时候暂时别想这么复杂。 + +#### 解答篇收获 + + diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index 5299fe1d..ca01ab9d 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -2,8 +2,10 @@ #include /* assert() */ #include /* NULL */ +/* 这个EXPECT有副作用啊,而不是单纯地assert,意味着EXPECT不能注释掉。*/ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +/* 因为此字符串多次使用,为了减少传递多个参数,故放到一个struct中 */ typedef struct { const char* json; }lept_context; @@ -20,13 +22,49 @@ static int lept_parse_null(lept_context* c, lept_value* v) { if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') return LEPT_PARSE_INVALID_VALUE; c->json += 3; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + v->type = LEPT_NULL; 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; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + + v->type = LEPT_FALSE; + 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; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + + v->type = LEPT_TRUE; + 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 'f': return lept_parse_false(c, v); + case 't': return lept_parse_true(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; default: return LEPT_PARSE_INVALID_VALUE; } diff --git a/tutorial01/test.c b/tutorial01/test.c index e7672181..75fda11a 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -25,6 +25,14 @@ static void test_parse_null() { v.type = LEPT_FALSE; EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&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() { @@ -48,6 +56,14 @@ static void test_parse_invalid_value() { v.type = LEPT_FALSE; EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "tru")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "fals")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); } static void test_parse_root_not_singular() { From 883e804d6523db7ad5c2f293fdf937581ae20dac Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 21:56:40 +0800 Subject: [PATCH 02/23] homework01 review --- my_note.md | 4 +++- tutorial01/leptjson.c | 20 +++++++------------- tutorial01/test.c | 18 +++++++++++++----- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/my_note.md b/my_note.md index a9265a70..bdcc4f83 100644 --- a/my_note.md +++ b/my_note.md @@ -57,7 +57,7 @@ typedef struct lept_value_t { #### TDD的设计思路 -首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 +首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 **Milo的解答中说,原因是为了出错后显示的__LINE__宏找到对应位置,而不是每次都在该函数一个位置** @@ -70,5 +70,7 @@ EXPECT_EQ_BASE中包含了与类型无关的内容,用format还获得实际不 我个人认为这种通用性强的代码应该在重构中写,实现的时候暂时别想这么复杂。 #### 解答篇收获 +有个测试空白字符末尾是否有其他字符的过程,用于每个parse的结束,因此,可以思考下是否有非常适合放置这个过程的位置。 +对于内部函数,最好加上static,一方面便于查阅时了解其用途,一方面,编译器检查,如果此函数未被使用,也会提示警告。 diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index ca01ab9d..0e40378b 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -23,10 +23,6 @@ static int lept_parse_null(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 3; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_NULL; return LEPT_PARSE_OK; } @@ -38,10 +34,6 @@ static int lept_parse_false(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 4; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_FALSE; return LEPT_PARSE_OK; } @@ -52,10 +44,6 @@ static int lept_parse_true(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 3; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_TRUE; return LEPT_PARSE_OK; } @@ -72,11 +60,17 @@ static int lept_parse_value(lept_context* c, lept_value* 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); - return lept_parse_value(&c, v); + 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) { diff --git a/tutorial01/test.c b/tutorial01/test.c index 75fda11a..441e4405 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -20,21 +20,27 @@ static int test_pass = 0; #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") -static void test_parse_null() { +static void test_parse_true() { 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)); - 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_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; @@ -75,6 +81,8 @@ static void test_parse_root_not_singular() { static void test_parse() { test_parse_null(); + test_parse_true(); + test_parse_false(); test_parse_expect_value(); test_parse_invalid_value(); test_parse_root_not_singular(); From d09615a82c195dbb85e529ef4426eca44396fe30 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 22:10:52 +0800 Subject: [PATCH 03/23] refactor by tutorial02 --- my_note.md => tutorial01/my_note01.md | 0 tutorial01/test.c | 39 ++++++++++----------------- 2 files changed, 14 insertions(+), 25 deletions(-) rename my_note.md => tutorial01/my_note01.md (100%) diff --git a/my_note.md b/tutorial01/my_note01.md similarity index 100% rename from my_note.md rename to tutorial01/my_note01.md diff --git a/tutorial01/test.c b/tutorial01/test.c index 441e4405..ddba115d 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -41,35 +41,24 @@ static void test_parse_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)); +#define TEST_ERROR(error, json) \ + do { \ + 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)); \ + } while(0) - 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_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); } 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)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "tru")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "fals")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "tru"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "fals"); } static void test_parse_root_not_singular() { From 3699e2d1a624e783ad0522065fbe507aa46d4a4e Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 23:16:43 +0800 Subject: [PATCH 04/23] homework2 task1 --- tutorial02/leptjson.c | 39 ++++++++++++--------------------------- tutorial02/my_note02.md | 4 ++++ 2 files changed, 16 insertions(+), 27 deletions(-) create mode 100644 tutorial02/my_note02.md diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 7693e43b..780067d9 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -15,30 +15,15 @@ static void lept_parse_whitespace(lept_context* c) { c->json = p; } -static int lept_parse_true(lept_context* c, lept_value* v) { - EXPECT(c, 't'); - if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_TRUE; - 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; +static int lept_parse_literal(lept_context* c, lept_value* v, int type, char* literal) { + int i; + EXPECT(c, *literal); + for(i = 0; literal[i + 1] != '\0'; i++) { + if (c->json[i] =='\0' || c->json[i] != literal[i+1]) + return LEPT_PARSE_INVALID_VALUE; + } + c->json += i; + v->type = type; return LEPT_PARSE_OK; } @@ -55,9 +40,9 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); + case 't': return lept_parse_literal(c, v, LEPT_TRUE, "true"); + case 'f': return lept_parse_literal(c, v, LEPT_FALSE, "false"); + case 'n': return lept_parse_literal(c, v, LEPT_NULL, "null"); default: return lept_parse_number(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; } diff --git a/tutorial02/my_note02.md b/tutorial02/my_note02.md new file mode 100644 index 00000000..6dd9804d --- /dev/null +++ b/tutorial02/my_note02.md @@ -0,0 +1,4 @@ +## 学习笔记02 + +### 为什么用#if 0宏 +因为其支持嵌套,而用`/**/`不支持嵌套。 From fcf961535a9a5532e0585c05208cea6b30efcf89 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 23:26:42 +0800 Subject: [PATCH 05/23] code review for homework02, task1 --- tutorial02/leptjson.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 780067d9..ffc02eee 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -15,11 +15,11 @@ static void lept_parse_whitespace(lept_context* c) { c->json = p; } -static int lept_parse_literal(lept_context* c, lept_value* v, int type, char* literal) { - int i; +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; EXPECT(c, *literal); - for(i = 0; literal[i + 1] != '\0'; i++) { - if (c->json[i] =='\0' || c->json[i] != literal[i+1]) + for(i = 0; literal[i + 1]; i++) { + if(c->json[i] != literal[i + 1]) return LEPT_PARSE_INVALID_VALUE; } c->json += i; @@ -40,9 +40,9 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { - case 't': return lept_parse_literal(c, v, LEPT_TRUE, "true"); - case 'f': return lept_parse_literal(c, v, LEPT_FALSE, "false"); - case 'n': return lept_parse_literal(c, v, LEPT_NULL, "null"); + 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; } From 056305515fdd42fc840c9559274bc072f4b9ae80 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Tue, 10 Jan 2017 02:14:13 +0800 Subject: [PATCH 06/23] homework2 incomplete --- tutorial02/leptjson.c | 11 +++++++++-- tutorial02/my_note02.md | 5 +++++ tutorial02/test.c | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index ffc02eee..4c9359ee 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -2,7 +2,9 @@ #include /* assert() */ #include /* NULL, strtod() */ -#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT_1TO9(ch) ((ch) >= '1' && (ch) <= '9') typedef struct { const char* json; @@ -29,12 +31,17 @@ static int lept_parse_literal(lept_context* c, lept_value* v, const char* litera static int lept_parse_number(lept_context* c, lept_value* v) { char* end; - /* \TODO validate number */ + + if(*c->json != '-' && !ISDIGIT(*c->json)) + return LEPT_PARSE_INVALID_VALUE; + 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; } diff --git a/tutorial02/my_note02.md b/tutorial02/my_note02.md index 6dd9804d..9e392f7e 100644 --- a/tutorial02/my_note02.md +++ b/tutorial02/my_note02.md @@ -2,3 +2,8 @@ ### 为什么用#if 0宏 因为其支持嵌套,而用`/**/`不支持嵌套。 + +### code review +注意不变字符串作为参数用`const char*` + +注意参数顺序,最好输入放前面,输出放后面 diff --git a/tutorial02/test.c b/tutorial02/test.c index 6e3ebed2..fdc78bda 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -70,6 +70,21 @@ static void test_parse_number() { TEST_NUMBER(1.234E+10, "1.234E+10"); TEST_NUMBER(1.234E-10, "1.234E-10"); TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + /* the smallest number > 1 */ + TEST_NUMBER(1.0000000000000002,"1.0000000000000002"); + /* mininum denormal */ + TEST_NUMBER( 4.9406564584124654e-324," 4.9406564584124654e-324"); + TEST_NUMBER(-4.9406564584124654e-324,"-4.9406564584124654e-324"); + /* Max subnormal double */ + TEST_NUMBER( 2.2250738585072009e-308," 2.2250738585072009e-308"); + TEST_NUMBER(-2.2250738585072009e-308,"-2.2250738585072009e-308"); + /* Min normal positive double */ + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + /* Max double */ + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); } #define TEST_ERROR(error, json)\ @@ -89,7 +104,7 @@ static void test_parse_invalid_value() { TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); @@ -105,7 +120,7 @@ static void test_parse_invalid_value() { static void test_parse_root_not_singular() { TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); From 3a873c348ba976d96ff88d42df778565a76e6af5 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Wed, 11 Jan 2017 12:24:55 +0800 Subject: [PATCH 07/23] homework2 complete, ref answer --- tutorial02/leptjson.c | 34 +++++++++++++++++++++++++--------- tutorial02/test.c | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 4c9359ee..1e449ca3 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -1,6 +1,8 @@ #include "leptjson.h" #include /* assert() */ #include /* NULL, strtod() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') @@ -30,18 +32,32 @@ static int lept_parse_literal(lept_context* c, lept_value* v, const char* litera } static int lept_parse_number(lept_context* c, lept_value* v) { - char* end; - - if(*c->json != '-' && !ISDIGIT(*c->json)) - return LEPT_PARSE_INVALID_VALUE; + const char* p = c->json; + if(*p == '-') p++; + if(*p == '0') p++; + else { + if(!ISDIGIT_1TO9(*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++); + } - v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; + 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; - c->json = end; v->type = LEPT_NUMBER; - + c->json = p; return LEPT_PARSE_OK; } diff --git a/tutorial02/test.c b/tutorial02/test.c index fdc78bda..1dba1821 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -129,7 +129,7 @@ static void test_parse_root_not_singular() { } static void test_parse_number_too_big() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); #endif From 642f914b2c56edf0d5bb82485427c5eb028d0247 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sat, 14 Jan 2017 21:21:04 +0800 Subject: [PATCH 08/23] finish homework3, ref answer --- .gitignore | 2 ++ tutorial03/leptjson.c | 29 +++++++++++++++++++++++---- tutorial03/my_note03.md | 39 ++++++++++++++++++++++++++++++++++++ tutorial03/test.c | 16 ++++++++++++--- tutorial03_answer/leptjson.c | 2 +- 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 tutorial03/my_note03.md diff --git a/.gitignore b/.gitignore index e3412016..36a4b8db 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ */build/ +*/tags +*/cscope.* diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 07f7e2c7..74efea2d 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -86,6 +86,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } + static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; const char* p; @@ -99,6 +100,21 @@ static int lept_parse_string(lept_context* c, lept_value* v) { 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; @@ -153,12 +169,14 @@ lept_type lept_get_type(const lept_value* v) { } int lept_get_boolean(const lept_value* v) { - /* \TODO */ - return 0; + assert(v != NULL); + return (v->type == LEPT_TRUE); } void lept_set_boolean(lept_value* v, int b) { - /* \TODO */ + assert(v != NULL); + v->type = (b == 0) ? LEPT_FALSE : LEPT_TRUE; + return; } double lept_get_number(const lept_value* v) { @@ -167,7 +185,10 @@ double lept_get_number(const lept_value* v) { } void lept_set_number(lept_value* v, double n) { - /* \TODO */ + assert(v != NULL); + v->type = LEPT_NUMBER; + v->u.n = n; + return; } const char* lept_get_string(const lept_value* v) { diff --git a/tutorial03/my_note03.md b/tutorial03/my_note03.md new file mode 100644 index 00000000..b5b1fdc4 --- /dev/null +++ b/tutorial03/my_note03.md @@ -0,0 +1,39 @@ +## tutorial03笔记 + +escape和unescape是非常容易出bug的地方,很多时候没注意就会忘了加斜杠之类。 + +程序执行前后细抠细节,比如如下程序,在字符串退出前`c->top=head`,保证后置条件成立,这个常容易忘掉。 + +``` c +static int lept_parse_string(lept_context* c, lept_value* v) { +//some code... + 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: + PUTC(c, ch); + } + } +} +``` diff --git a/tutorial03/test.c b/tutorial03/test.c index ac788aca..e447ef7a 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -107,7 +107,7 @@ static void test_parse_number() { static void test_parse_string() { TEST_STRING("", "\"\""); TEST_STRING("Hello", "\"Hello\""); -#if 0 +#if 1 TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); #endif @@ -188,12 +188,22 @@ static void test_access_null() { } static void test_access_boolean() { - /* \TODO */ /* Use EXPECT_TRUE() and EXPECT_FALSE() */ + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); } static void test_access_number() { - /* \TODO */ + lept_value v; + lept_init(&v); + lept_set_number(&v, 255); + EXPECT_EQ_INT(255, lept_get_number(&v)); + lept_free(&v); } static void test_access_string() { diff --git a/tutorial03_answer/leptjson.c b/tutorial03_answer/leptjson.c index 89117e15..7cc0cdfb 100644 --- a/tutorial03_answer/leptjson.c +++ b/tutorial03_answer/leptjson.c @@ -122,7 +122,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; default: - if ((unsigned char)ch < 0x20) { + if ((unsigned char)ch < 0x20) { c->top = head; return LEPT_PARSE_INVALID_STRING_CHAR; } From 51b738d783578d63c565cff1e6bb2c7bc274bc50 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 15:33:32 +0800 Subject: [PATCH 09/23] homework3 code review by answer --- tutorial03/leptjson.c | 23 +++++++++++++++++------ tutorial03/test.c | 14 +++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 74efea2d..09dfc746 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -2,9 +2,16 @@ #include /* assert() */ #include /* errno, ERANGE */ #include /* HUGE_VAL */ -#include /* NULL, malloc(), realloc(), free(), strtod() */ #include /* memcpy() */ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#endif +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#ifdef _WINDOWS +#include +#endif + #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 #endif @@ -119,6 +126,10 @@ static int lept_parse_string(lept_context* c, lept_value* v) { 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); } } @@ -169,13 +180,13 @@ lept_type lept_get_type(const lept_value* v) { } int lept_get_boolean(const lept_value* v) { - assert(v != NULL); - return (v->type == LEPT_TRUE); + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); /*注意这里还需判断v.type*/ + return v->type == LEPT_TRUE; } void lept_set_boolean(lept_value* v, int b) { - assert(v != NULL); - v->type = (b == 0) ? LEPT_FALSE : LEPT_TRUE; + lept_free(v); /*注意这里如果之前是字符串还得先释放内存,lept_free中包含assert,不需此处断言,下同*/ + v->type = b ? LEPT_TRUE : LEPT_FALSE; return; } @@ -185,7 +196,7 @@ double lept_get_number(const lept_value* v) { } void lept_set_number(lept_value* v, double n) { - assert(v != NULL); + lept_free(v); /*注意这里如果之前是字符串还得先释放内存,lept_free中包含assert,不需此处断言,下同*/ v->type = LEPT_NUMBER; v->u.n = n; return; diff --git a/tutorial03/test.c b/tutorial03/test.c index e447ef7a..7a74c5a2 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -1,8 +1,15 @@ #include -#include #include #include "leptjson.h" +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#endif +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#ifdef _WINDOWS +#include +#endif + static int main_ret = 0; static int test_count = 0; static int test_pass = 0; @@ -191,6 +198,7 @@ static void test_access_boolean() { /* Use EXPECT_TRUE() and EXPECT_FALSE() */ lept_value v; lept_init(&v); + lept_set_string(&v, "a", 1); /*故意设置一个string类型来测试最后的lept_free有没有异常*/ lept_set_boolean(&v, 1); EXPECT_TRUE(lept_get_boolean(&v)); lept_set_boolean(&v, 0); @@ -201,6 +209,7 @@ static void test_access_boolean() { static void test_access_number() { lept_value v; lept_init(&v); + lept_set_string(&v, "a", 1); /*故意设置一个string类型来测试最后的lept_free有没有异常*/ lept_set_number(&v, 255); EXPECT_EQ_INT(255, lept_get_number(&v)); lept_free(&v); @@ -237,6 +246,9 @@ static void test_parse() { } int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif test_parse(); printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); return main_ret; From 56e9d7ca1d87984593d22f249ba203830c75dc21 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 23:24:03 +0800 Subject: [PATCH 10/23] finish homework4 with bug --- tutorial04/leptjson.c | 44 ++++++++++++++++++++++++++++++++++++++--- tutorial04/my_note04.md | 29 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tutorial04/my_note04.md diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index 0a123bf2..cb16a92c 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -91,12 +91,40 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } static const char* lept_parse_hex4(const char* p, unsigned* u) { - /* \TODO */ + *u = 0; + int i; + for (i = 3; i > 0; i--, p++) + if (ISDIGIT(*p)) + *u |= ((*p - '0') << (i*4)); + else if ( *p >= 'a' && *p <= 'f') + *u |= ((*p - 'a' + 10) << (i*4)); + else if ( *p >= 'A' && *p <= 'F') + *u |= ((*p - 'A' + 10) << (i*4)); + else + return NULL; return p; } static void lept_encode_utf8(lept_context* c, unsigned u) { - /* \TODO */ + assert(u <= 0x10FFFF); + if (u <= 0x007F) { + PUTC(c, u); + } + else if (u >= 0x0080 && u <= 0x07FF) { + PUTC(c, (0xC0 | ((u >> 6) & 0xFF))); + PUTC(c, (0x80 | ( u & 0x3F))); + } + else if (u >= 0x0800 && u <= 0xFFFF) { + PUTC(c, (0xE0 | ((u >> 12) & 0xFF))); + PUTC(c, (0x80 | ((u >> 6) & 0x3F))); + PUTC(c, (0x80 | ( u & 0x3F))); + } + else if (u >= 0x10000 && u <= 0x10FFFF) { + PUTC(c, (0xF0 | ((u >> 18) & 0xFF))); + PUTC(c, (0x80 | ((u >> 12) & 0x3F))); + PUTC(c, (0x80 | ((u >> 6) & 0x3F))); + PUTC(c, (0x80 | ( u >> 6 & 0x3F))); + } } #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) @@ -104,6 +132,7 @@ static void lept_encode_utf8(lept_context* c, unsigned u) { static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; unsigned u; + unsigned l; const char* p; EXPECT(c, '\"'); p = c->json; @@ -128,7 +157,16 @@ static int lept_parse_string(lept_context* c, lept_value* v) { case 'u': if (!(p = lept_parse_hex4(p, &u))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); - /* \TODO surrogate handling */ + if(u >= 0xD800 && u <= 0xDBFF) { + if (p[0] != '\\' || p[1] != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + p += 2; + if(!(p = lept_parse_hex4(p, &l))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if(!(l >= 0xDC00 && l <= 0xDFFF)) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + u = 0x10000 + (u - 0xD800) * 0x400 + (l - 0xDC00); + } lept_encode_utf8(c, u); break; default: diff --git a/tutorial04/my_note04.md b/tutorial04/my_note04.md new file mode 100644 index 00000000..9a9bf5a4 --- /dev/null +++ b/tutorial04/my_note04.md @@ -0,0 +1,29 @@ + +Unicode 返回为 0 至 0x10FFFF,收录了当今几乎所有文字,每个文字映射为Unicode的一个码点。 + +注意U+FFFF也只能表示`16*16*16*16=65536`个字,所以不能容纳所有文字(2016年收录共约128237个), +所以需要扩展: + +``` +同学可能会发现,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) 变换成真实的码点 +``` + + +然而Unicode使用不太方便,为了将Unicode转为比较方便的字节,于是采用(Uniform Transformation Format)UTF, +来存储各个码点,比方说UTF-8,UTF-16和UTF-32,其中UTF-8最流行,为可变长编码。 + +对于 JSON字符串中的 \uXXXX 是以 16 进制表示码点 U+0000 至 U+FFFF,我们需要: + +1. 解析 4 位十六进制整数为码点; +2. 由于字符串是以 UTF-8 存储,我们要把这个码点编码成 UTF-8。 + +也就是说U+XXXX是unicode格式,也是我们可见格式,转化为UTF-8的字节序列(char*)便于程序处理的格式, + +UTF-8 成为现时互联网上最流行的格式,有几个原因: + +1. 它采用字节为编码单元,不会有字节序(endianness)的问题。 +2. 每个 ASCII 字符只需一个字节去储存。 +3. 如果程序原来是以字节方式储存字符,理论上不需要特别改动就能处理 UTF-8 的数据。 From 4eb330e6a301ef1f6627004ba5e518b5364a0ddf Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 23:52:09 +0800 Subject: [PATCH 11/23] homework4 code review by answer --- tutorial04/leptjson.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index cb16a92c..ac1cbde8 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -93,37 +93,36 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static const char* lept_parse_hex4(const char* p, unsigned* u) { *u = 0; int i; - for (i = 3; i > 0; i--, p++) - if (ISDIGIT(*p)) - *u |= ((*p - '0') << (i*4)); - else if ( *p >= 'a' && *p <= 'f') - *u |= ((*p - 'a' + 10) << (i*4)); - else if ( *p >= 'A' && *p <= 'F') - *u |= ((*p - 'A' + 10) << (i*4)); - else - return NULL; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ISDIGIT(ch)) *u |= (ch - '0'); + else if ( ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if ( ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } return p; } static void lept_encode_utf8(lept_context* c, unsigned u) { assert(u <= 0x10FFFF); - if (u <= 0x007F) { - PUTC(c, u); + if (u <= 0x7F) { + PUTC(c, u & 0xFF); /* 之所有 & 0xFF是因为编译器会将unsigned转char时提示最高位的截断误差*/ } - else if (u >= 0x0080 && u <= 0x07FF) { + else if (u <= 0x07FF) { PUTC(c, (0xC0 | ((u >> 6) & 0xFF))); PUTC(c, (0x80 | ( u & 0x3F))); } - else if (u >= 0x0800 && u <= 0xFFFF) { + else if (u <= 0xFFFF) { PUTC(c, (0xE0 | ((u >> 12) & 0xFF))); PUTC(c, (0x80 | ((u >> 6) & 0x3F))); PUTC(c, (0x80 | ( u & 0x3F))); } - else if (u >= 0x10000 && u <= 0x10FFFF) { + else if (u <= 0x10FFFF) { PUTC(c, (0xF0 | ((u >> 18) & 0xFF))); PUTC(c, (0x80 | ((u >> 12) & 0x3F))); PUTC(c, (0x80 | ((u >> 6) & 0x3F))); - PUTC(c, (0x80 | ( u >> 6 & 0x3F))); + PUTC(c, (0x80 | ( u & 0x3F))); } } @@ -159,13 +158,13 @@ static int lept_parse_string(lept_context* c, lept_value* v) { STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); if(u >= 0xD800 && u <= 0xDBFF) { if (p[0] != '\\' || p[1] != 'u') - STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); p += 2; if(!(p = lept_parse_hex4(p, &l))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); if(!(l >= 0xDC00 && l <= 0xDFFF)) - STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); - u = 0x10000 + (u - 0xD800) * 0x400 + (l - 0xDC00); + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = ((u - 0xD800) << 10 ) | (l - 0xDC00) + 0x10000; } lept_encode_utf8(c, u); break; From 887c2e49460ac47c93e0f13979c6b9192cb27243 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 21:43:52 +0800 Subject: [PATCH 12/23] my homework01 --- my_note.md | 74 +++++++++++++++++++++++++++++++++++++++++++ tutorial01/leptjson.c | 38 ++++++++++++++++++++++ tutorial01/test.c | 16 ++++++++++ 3 files changed, 128 insertions(+) create mode 100644 my_note.md diff --git a/my_note.md b/my_note.md new file mode 100644 index 00000000..a9265a70 --- /dev/null +++ b/my_note.md @@ -0,0 +1,74 @@ +## tutorial01学习笔记 + +要点: + +* CMake +* API设计(.h)与实现(.c) +* 测试驱动TDD + +### 关于测试驱动的开发流程 + +1. 加入一个测试 +2. 运行所有测试,新测试将会失败 +3. 编写实现代码 +4. 运行所有测试,若测试失败,回到3 +5. 重构代码 +6. 回到1 + +TDD是先写测试后实现,优点:需求明确,刚好满足测试,不会写不需要的代码,可靠。 + +不论TDD还是先实现后测试,都应该保证足够的覆盖率的单元测试。 + +我认为可以写一个test.h,设计一些基础的宏,然后每个文件写对应的测试用例。本代码为了简便只有test.c + +### 学到的知识 +milo yip的代码写得极好!思路非常清晰且层次分明,我试着还原一下思路, + +#### API的设计思路 + +(1)先考虑最终的API效果 + +首先是宏观的总体目标,是对外的pulic API + +`int lept_parse(lept_value* v, const char* json);` + +parse取词的话,返回值用于表示结果对错,参数有两个,out为一个struct lept_value,用于保存结果,in为字符串。 + +那么这个数据结构里面的内容呢?这个后续交代, + +想合理地表达null,boolean,string,array,object,float这六种JSON类型,可能的struct应该会是: + +``` c +typedef enum { LEPT_NULL, LEPT_TRUE, LEPT_FALSE, LEPT_ARRAY, LEPT_OBJECT, LEPT_FLOAT } lept_type; +typedef struct lept_value_t { + lept_type t; //保存数据类型 +} lept_value; +``` + +注意这里的lept_value数据结构只包含一个枚举值,这是null和boolean特有的简化版, + +如果是array或者其他需要修改此结构,对此我还没有比较好的思路,因为如果子结构包含信息过多,对于null和boolean而言又略显多余。 + +如果是一个较少信息量的类型,估计应该是加入一个`void*`类型指针,用于指向特定结构,例如string指向字符串,array指向`lept_value_t*`数值. + +是否需要size等信息,拭目以待,我觉得如果不放在lept_value_t里面也会放在子类型里面。可能放在子类型里面更好,毕竟很多类型不需要。 + +(2)余下内容太多,自行体会吧,不写了 + + +#### TDD的设计思路 +首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 + +**Milo的解答中说,原因是为了出错后显示的__LINE__宏找到对应位置,而不是每次都在该函数一个位置** + +Milo Yip用了 EXPECT_EQ_BASE这个宏作为EXPECT_EQ_INT的辅助宏非常巧妙, +具体参见`test.c` + +EXPECT_EQ_BASE中包含了与类型无关的内容,用format还获得实际不同类型的情况, +以及equality表达式更像是一个谓词Predicate,这样不仅可用于相等表达式。 + +我个人认为这种通用性强的代码应该在重构中写,实现的时候暂时别想这么复杂。 + +#### 解答篇收获 + + diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index 5299fe1d..ca01ab9d 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -2,8 +2,10 @@ #include /* assert() */ #include /* NULL */ +/* 这个EXPECT有副作用啊,而不是单纯地assert,意味着EXPECT不能注释掉。*/ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +/* 因为此字符串多次使用,为了减少传递多个参数,故放到一个struct中 */ typedef struct { const char* json; }lept_context; @@ -20,13 +22,49 @@ static int lept_parse_null(lept_context* c, lept_value* v) { if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l') return LEPT_PARSE_INVALID_VALUE; c->json += 3; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + v->type = LEPT_NULL; 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; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + + v->type = LEPT_FALSE; + 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; + + lept_parse_whitespace(c); + if (c->json[0] != '\0') + return LEPT_PARSE_ROOT_NOT_SINGULAR; + + v->type = LEPT_TRUE; + 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 'f': return lept_parse_false(c, v); + case 't': return lept_parse_true(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; default: return LEPT_PARSE_INVALID_VALUE; } diff --git a/tutorial01/test.c b/tutorial01/test.c index e7672181..75fda11a 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -25,6 +25,14 @@ static void test_parse_null() { v.type = LEPT_FALSE; EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null")); EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true")); + EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&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() { @@ -48,6 +56,14 @@ static void test_parse_invalid_value() { v.type = LEPT_FALSE; EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?")); EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "tru")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + + v.type = LEPT_FALSE; + EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "fals")); + EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); } static void test_parse_root_not_singular() { From acfcf405893f525710eb775ada076e98f0d7447e Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 21:56:40 +0800 Subject: [PATCH 13/23] homework01 review --- my_note.md | 4 +++- tutorial01/leptjson.c | 20 +++++++------------- tutorial01/test.c | 18 +++++++++++++----- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/my_note.md b/my_note.md index a9265a70..bdcc4f83 100644 --- a/my_note.md +++ b/my_note.md @@ -57,7 +57,7 @@ typedef struct lept_value_t { #### TDD的设计思路 -首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 +首先写一个test的宏来做测试,为什么要用宏呢?因为c没有泛型编程嘛。 **Milo的解答中说,原因是为了出错后显示的__LINE__宏找到对应位置,而不是每次都在该函数一个位置** @@ -70,5 +70,7 @@ EXPECT_EQ_BASE中包含了与类型无关的内容,用format还获得实际不 我个人认为这种通用性强的代码应该在重构中写,实现的时候暂时别想这么复杂。 #### 解答篇收获 +有个测试空白字符末尾是否有其他字符的过程,用于每个parse的结束,因此,可以思考下是否有非常适合放置这个过程的位置。 +对于内部函数,最好加上static,一方面便于查阅时了解其用途,一方面,编译器检查,如果此函数未被使用,也会提示警告。 diff --git a/tutorial01/leptjson.c b/tutorial01/leptjson.c index ca01ab9d..0e40378b 100644 --- a/tutorial01/leptjson.c +++ b/tutorial01/leptjson.c @@ -23,10 +23,6 @@ static int lept_parse_null(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 3; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_NULL; return LEPT_PARSE_OK; } @@ -38,10 +34,6 @@ static int lept_parse_false(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 4; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_FALSE; return LEPT_PARSE_OK; } @@ -52,10 +44,6 @@ static int lept_parse_true(lept_context* c, lept_value* v) { return LEPT_PARSE_INVALID_VALUE; c->json += 3; - lept_parse_whitespace(c); - if (c->json[0] != '\0') - return LEPT_PARSE_ROOT_NOT_SINGULAR; - v->type = LEPT_TRUE; return LEPT_PARSE_OK; } @@ -72,11 +60,17 @@ static int lept_parse_value(lept_context* c, lept_value* 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); - return lept_parse_value(&c, v); + 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) { diff --git a/tutorial01/test.c b/tutorial01/test.c index 75fda11a..441e4405 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -20,21 +20,27 @@ static int test_pass = 0; #define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d") -static void test_parse_null() { +static void test_parse_true() { 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)); - 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_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; @@ -75,6 +81,8 @@ static void test_parse_root_not_singular() { static void test_parse() { test_parse_null(); + test_parse_true(); + test_parse_false(); test_parse_expect_value(); test_parse_invalid_value(); test_parse_root_not_singular(); From 6b14c72411f4bff4f7530141f8f39ef2800db545 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 22:10:52 +0800 Subject: [PATCH 14/23] refactor by tutorial02 --- my_note.md => tutorial01/my_note01.md | 0 tutorial01/test.c | 39 ++++++++++----------------- 2 files changed, 14 insertions(+), 25 deletions(-) rename my_note.md => tutorial01/my_note01.md (100%) diff --git a/my_note.md b/tutorial01/my_note01.md similarity index 100% rename from my_note.md rename to tutorial01/my_note01.md diff --git a/tutorial01/test.c b/tutorial01/test.c index 441e4405..ddba115d 100644 --- a/tutorial01/test.c +++ b/tutorial01/test.c @@ -41,35 +41,24 @@ static void test_parse_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)); +#define TEST_ERROR(error, json) \ + do { \ + 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)); \ + } while(0) - 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_expect_value() { + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, ""); + TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " "); } 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)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "tru")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); - - v.type = LEPT_FALSE; - EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "fals")); - EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v)); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "tru"); + TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "fals"); } static void test_parse_root_not_singular() { From a99bbe7a8e939c1e6590ffe4f4d0b45551177328 Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 23:16:43 +0800 Subject: [PATCH 15/23] homework2 task1 --- tutorial02/leptjson.c | 39 ++++++++++++--------------------------- tutorial02/my_note02.md | 4 ++++ 2 files changed, 16 insertions(+), 27 deletions(-) create mode 100644 tutorial02/my_note02.md diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 7693e43b..780067d9 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -15,30 +15,15 @@ static void lept_parse_whitespace(lept_context* c) { c->json = p; } -static int lept_parse_true(lept_context* c, lept_value* v) { - EXPECT(c, 't'); - if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e') - return LEPT_PARSE_INVALID_VALUE; - c->json += 3; - v->type = LEPT_TRUE; - 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; +static int lept_parse_literal(lept_context* c, lept_value* v, int type, char* literal) { + int i; + EXPECT(c, *literal); + for(i = 0; literal[i + 1] != '\0'; i++) { + if (c->json[i] =='\0' || c->json[i] != literal[i+1]) + return LEPT_PARSE_INVALID_VALUE; + } + c->json += i; + v->type = type; return LEPT_PARSE_OK; } @@ -55,9 +40,9 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { - case 't': return lept_parse_true(c, v); - case 'f': return lept_parse_false(c, v); - case 'n': return lept_parse_null(c, v); + case 't': return lept_parse_literal(c, v, LEPT_TRUE, "true"); + case 'f': return lept_parse_literal(c, v, LEPT_FALSE, "false"); + case 'n': return lept_parse_literal(c, v, LEPT_NULL, "null"); default: return lept_parse_number(c, v); case '\0': return LEPT_PARSE_EXPECT_VALUE; } diff --git a/tutorial02/my_note02.md b/tutorial02/my_note02.md new file mode 100644 index 00000000..6dd9804d --- /dev/null +++ b/tutorial02/my_note02.md @@ -0,0 +1,4 @@ +## 学习笔记02 + +### 为什么用#if 0宏 +因为其支持嵌套,而用`/**/`不支持嵌套。 From f90733b6291406b00abd8bceda61c78725f3dfdb Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 9 Jan 2017 23:26:42 +0800 Subject: [PATCH 16/23] code review for homework02, task1 --- tutorial02/leptjson.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 780067d9..ffc02eee 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -15,11 +15,11 @@ static void lept_parse_whitespace(lept_context* c) { c->json = p; } -static int lept_parse_literal(lept_context* c, lept_value* v, int type, char* literal) { - int i; +static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) { + size_t i; EXPECT(c, *literal); - for(i = 0; literal[i + 1] != '\0'; i++) { - if (c->json[i] =='\0' || c->json[i] != literal[i+1]) + for(i = 0; literal[i + 1]; i++) { + if(c->json[i] != literal[i + 1]) return LEPT_PARSE_INVALID_VALUE; } c->json += i; @@ -40,9 +40,9 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static int lept_parse_value(lept_context* c, lept_value* v) { switch (*c->json) { - case 't': return lept_parse_literal(c, v, LEPT_TRUE, "true"); - case 'f': return lept_parse_literal(c, v, LEPT_FALSE, "false"); - case 'n': return lept_parse_literal(c, v, LEPT_NULL, "null"); + 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; } From 9af0a481c35debab20e69b308df0965f43fdabee Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Tue, 10 Jan 2017 02:14:13 +0800 Subject: [PATCH 17/23] homework2 incomplete --- tutorial02/leptjson.c | 11 +++++++++-- tutorial02/my_note02.md | 5 +++++ tutorial02/test.c | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index ffc02eee..4c9359ee 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -2,7 +2,9 @@ #include /* assert() */ #include /* NULL, strtod() */ -#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) +#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') +#define ISDIGIT_1TO9(ch) ((ch) >= '1' && (ch) <= '9') typedef struct { const char* json; @@ -29,12 +31,17 @@ static int lept_parse_literal(lept_context* c, lept_value* v, const char* litera static int lept_parse_number(lept_context* c, lept_value* v) { char* end; - /* \TODO validate number */ + + if(*c->json != '-' && !ISDIGIT(*c->json)) + return LEPT_PARSE_INVALID_VALUE; + 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; } diff --git a/tutorial02/my_note02.md b/tutorial02/my_note02.md index 6dd9804d..9e392f7e 100644 --- a/tutorial02/my_note02.md +++ b/tutorial02/my_note02.md @@ -2,3 +2,8 @@ ### 为什么用#if 0宏 因为其支持嵌套,而用`/**/`不支持嵌套。 + +### code review +注意不变字符串作为参数用`const char*` + +注意参数顺序,最好输入放前面,输出放后面 diff --git a/tutorial02/test.c b/tutorial02/test.c index 6e3ebed2..fdc78bda 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -70,6 +70,21 @@ static void test_parse_number() { TEST_NUMBER(1.234E+10, "1.234E+10"); TEST_NUMBER(1.234E-10, "1.234E-10"); TEST_NUMBER(0.0, "1e-10000"); /* must underflow */ + + /* the smallest number > 1 */ + TEST_NUMBER(1.0000000000000002,"1.0000000000000002"); + /* mininum denormal */ + TEST_NUMBER( 4.9406564584124654e-324," 4.9406564584124654e-324"); + TEST_NUMBER(-4.9406564584124654e-324,"-4.9406564584124654e-324"); + /* Max subnormal double */ + TEST_NUMBER( 2.2250738585072009e-308," 2.2250738585072009e-308"); + TEST_NUMBER(-2.2250738585072009e-308,"-2.2250738585072009e-308"); + /* Min normal positive double */ + TEST_NUMBER( 2.2250738585072014e-308, "2.2250738585072014e-308"); + TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308"); + /* Max double */ + TEST_NUMBER( 1.7976931348623157e+308, "1.7976931348623157e+308"); + TEST_NUMBER(-1.7976931348623157e+308, "-1.7976931348623157e+308"); } #define TEST_ERROR(error, json)\ @@ -89,7 +104,7 @@ static void test_parse_invalid_value() { TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nul"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "?"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1"); @@ -105,7 +120,7 @@ static void test_parse_invalid_value() { static void test_parse_root_not_singular() { TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x"); -#if 0 +#if 1 /* invalid number */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */ TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0"); From 908a15576e788670a654334c01606da216ed6b0e Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Wed, 11 Jan 2017 12:24:55 +0800 Subject: [PATCH 18/23] homework2 complete, ref answer --- tutorial02/leptjson.c | 34 +++++++++++++++++++++++++--------- tutorial02/test.c | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/tutorial02/leptjson.c b/tutorial02/leptjson.c index 4c9359ee..1e449ca3 100644 --- a/tutorial02/leptjson.c +++ b/tutorial02/leptjson.c @@ -1,6 +1,8 @@ #include "leptjson.h" #include /* assert() */ #include /* NULL, strtod() */ +#include /* errno, ERANGE */ +#include /* HUGE_VAL */ #define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0) #define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9') @@ -30,18 +32,32 @@ static int lept_parse_literal(lept_context* c, lept_value* v, const char* litera } static int lept_parse_number(lept_context* c, lept_value* v) { - char* end; - - if(*c->json != '-' && !ISDIGIT(*c->json)) - return LEPT_PARSE_INVALID_VALUE; + const char* p = c->json; + if(*p == '-') p++; + if(*p == '0') p++; + else { + if(!ISDIGIT_1TO9(*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++); + } - v->n = strtod(c->json, &end); - if (c->json == end) - return LEPT_PARSE_INVALID_VALUE; + 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; - c->json = end; v->type = LEPT_NUMBER; - + c->json = p; return LEPT_PARSE_OK; } diff --git a/tutorial02/test.c b/tutorial02/test.c index fdc78bda..1dba1821 100644 --- a/tutorial02/test.c +++ b/tutorial02/test.c @@ -129,7 +129,7 @@ static void test_parse_root_not_singular() { } static void test_parse_number_too_big() { -#if 0 +#if 1 TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309"); TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309"); #endif From a63d7ce636e863fb860e861812790f2e052d366f Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sat, 14 Jan 2017 21:21:04 +0800 Subject: [PATCH 19/23] finish homework3, ref answer --- .gitignore | 2 ++ tutorial03/leptjson.c | 29 +++++++++++++++++++++++---- tutorial03/my_note03.md | 39 ++++++++++++++++++++++++++++++++++++ tutorial03/test.c | 16 ++++++++++++--- tutorial03_answer/leptjson.c | 2 +- 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 tutorial03/my_note03.md diff --git a/.gitignore b/.gitignore index e3412016..36a4b8db 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ */build/ +*/tags +*/cscope.* diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 07f7e2c7..74efea2d 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -86,6 +86,7 @@ static int lept_parse_number(lept_context* c, lept_value* v) { return LEPT_PARSE_OK; } + static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; const char* p; @@ -99,6 +100,21 @@ static int lept_parse_string(lept_context* c, lept_value* v) { 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; @@ -153,12 +169,14 @@ lept_type lept_get_type(const lept_value* v) { } int lept_get_boolean(const lept_value* v) { - /* \TODO */ - return 0; + assert(v != NULL); + return (v->type == LEPT_TRUE); } void lept_set_boolean(lept_value* v, int b) { - /* \TODO */ + assert(v != NULL); + v->type = (b == 0) ? LEPT_FALSE : LEPT_TRUE; + return; } double lept_get_number(const lept_value* v) { @@ -167,7 +185,10 @@ double lept_get_number(const lept_value* v) { } void lept_set_number(lept_value* v, double n) { - /* \TODO */ + assert(v != NULL); + v->type = LEPT_NUMBER; + v->u.n = n; + return; } const char* lept_get_string(const lept_value* v) { diff --git a/tutorial03/my_note03.md b/tutorial03/my_note03.md new file mode 100644 index 00000000..b5b1fdc4 --- /dev/null +++ b/tutorial03/my_note03.md @@ -0,0 +1,39 @@ +## tutorial03笔记 + +escape和unescape是非常容易出bug的地方,很多时候没注意就会忘了加斜杠之类。 + +程序执行前后细抠细节,比如如下程序,在字符串退出前`c->top=head`,保证后置条件成立,这个常容易忘掉。 + +``` c +static int lept_parse_string(lept_context* c, lept_value* v) { +//some code... + 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: + PUTC(c, ch); + } + } +} +``` diff --git a/tutorial03/test.c b/tutorial03/test.c index ac788aca..e447ef7a 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -107,7 +107,7 @@ static void test_parse_number() { static void test_parse_string() { TEST_STRING("", "\"\""); TEST_STRING("Hello", "\"Hello\""); -#if 0 +#if 1 TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\""); TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\""); #endif @@ -188,12 +188,22 @@ static void test_access_null() { } static void test_access_boolean() { - /* \TODO */ /* Use EXPECT_TRUE() and EXPECT_FALSE() */ + lept_value v; + lept_init(&v); + lept_set_boolean(&v, 1); + EXPECT_TRUE(lept_get_boolean(&v)); + lept_set_boolean(&v, 0); + EXPECT_FALSE(lept_get_boolean(&v)); + lept_free(&v); } static void test_access_number() { - /* \TODO */ + lept_value v; + lept_init(&v); + lept_set_number(&v, 255); + EXPECT_EQ_INT(255, lept_get_number(&v)); + lept_free(&v); } static void test_access_string() { diff --git a/tutorial03_answer/leptjson.c b/tutorial03_answer/leptjson.c index 89117e15..7cc0cdfb 100644 --- a/tutorial03_answer/leptjson.c +++ b/tutorial03_answer/leptjson.c @@ -122,7 +122,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { c->top = head; return LEPT_PARSE_MISS_QUOTATION_MARK; default: - if ((unsigned char)ch < 0x20) { + if ((unsigned char)ch < 0x20) { c->top = head; return LEPT_PARSE_INVALID_STRING_CHAR; } From 37792786b103c4d412328c0365f8501c4db4c4fd Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 15:33:32 +0800 Subject: [PATCH 20/23] homework3 code review by answer --- tutorial03/leptjson.c | 23 +++++++++++++++++------ tutorial03/test.c | 14 +++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/tutorial03/leptjson.c b/tutorial03/leptjson.c index 74efea2d..09dfc746 100644 --- a/tutorial03/leptjson.c +++ b/tutorial03/leptjson.c @@ -2,9 +2,16 @@ #include /* assert() */ #include /* errno, ERANGE */ #include /* HUGE_VAL */ -#include /* NULL, malloc(), realloc(), free(), strtod() */ #include /* memcpy() */ +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#endif +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#ifdef _WINDOWS +#include +#endif + #ifndef LEPT_PARSE_STACK_INIT_SIZE #define LEPT_PARSE_STACK_INIT_SIZE 256 #endif @@ -119,6 +126,10 @@ static int lept_parse_string(lept_context* c, lept_value* v) { 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); } } @@ -169,13 +180,13 @@ lept_type lept_get_type(const lept_value* v) { } int lept_get_boolean(const lept_value* v) { - assert(v != NULL); - return (v->type == LEPT_TRUE); + assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE)); /*注意这里还需判断v.type*/ + return v->type == LEPT_TRUE; } void lept_set_boolean(lept_value* v, int b) { - assert(v != NULL); - v->type = (b == 0) ? LEPT_FALSE : LEPT_TRUE; + lept_free(v); /*注意这里如果之前是字符串还得先释放内存,lept_free中包含assert,不需此处断言,下同*/ + v->type = b ? LEPT_TRUE : LEPT_FALSE; return; } @@ -185,7 +196,7 @@ double lept_get_number(const lept_value* v) { } void lept_set_number(lept_value* v, double n) { - assert(v != NULL); + lept_free(v); /*注意这里如果之前是字符串还得先释放内存,lept_free中包含assert,不需此处断言,下同*/ v->type = LEPT_NUMBER; v->u.n = n; return; diff --git a/tutorial03/test.c b/tutorial03/test.c index e447ef7a..7a74c5a2 100644 --- a/tutorial03/test.c +++ b/tutorial03/test.c @@ -1,8 +1,15 @@ #include -#include #include #include "leptjson.h" +#ifdef _WINDOWS +#define _CRTDBG_MAP_ALLOC +#endif +#include /* NULL, malloc(), realloc(), free(), strtod() */ +#ifdef _WINDOWS +#include +#endif + static int main_ret = 0; static int test_count = 0; static int test_pass = 0; @@ -191,6 +198,7 @@ static void test_access_boolean() { /* Use EXPECT_TRUE() and EXPECT_FALSE() */ lept_value v; lept_init(&v); + lept_set_string(&v, "a", 1); /*故意设置一个string类型来测试最后的lept_free有没有异常*/ lept_set_boolean(&v, 1); EXPECT_TRUE(lept_get_boolean(&v)); lept_set_boolean(&v, 0); @@ -201,6 +209,7 @@ static void test_access_boolean() { static void test_access_number() { lept_value v; lept_init(&v); + lept_set_string(&v, "a", 1); /*故意设置一个string类型来测试最后的lept_free有没有异常*/ lept_set_number(&v, 255); EXPECT_EQ_INT(255, lept_get_number(&v)); lept_free(&v); @@ -237,6 +246,9 @@ static void test_parse() { } int main() { +#ifdef _WINDOWS + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif test_parse(); printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count); return main_ret; From d1d6884a7ff15b0bf52722420d55d5b71860ed0d Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 23:24:03 +0800 Subject: [PATCH 21/23] finish homework4 with bug --- tutorial04/leptjson.c | 44 ++++++++++++++++++++++++++++++++++++++--- tutorial04/my_note04.md | 29 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 tutorial04/my_note04.md diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index 0a123bf2..cb16a92c 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -91,12 +91,40 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } static const char* lept_parse_hex4(const char* p, unsigned* u) { - /* \TODO */ + *u = 0; + int i; + for (i = 3; i > 0; i--, p++) + if (ISDIGIT(*p)) + *u |= ((*p - '0') << (i*4)); + else if ( *p >= 'a' && *p <= 'f') + *u |= ((*p - 'a' + 10) << (i*4)); + else if ( *p >= 'A' && *p <= 'F') + *u |= ((*p - 'A' + 10) << (i*4)); + else + return NULL; return p; } static void lept_encode_utf8(lept_context* c, unsigned u) { - /* \TODO */ + assert(u <= 0x10FFFF); + if (u <= 0x007F) { + PUTC(c, u); + } + else if (u >= 0x0080 && u <= 0x07FF) { + PUTC(c, (0xC0 | ((u >> 6) & 0xFF))); + PUTC(c, (0x80 | ( u & 0x3F))); + } + else if (u >= 0x0800 && u <= 0xFFFF) { + PUTC(c, (0xE0 | ((u >> 12) & 0xFF))); + PUTC(c, (0x80 | ((u >> 6) & 0x3F))); + PUTC(c, (0x80 | ( u & 0x3F))); + } + else if (u >= 0x10000 && u <= 0x10FFFF) { + PUTC(c, (0xF0 | ((u >> 18) & 0xFF))); + PUTC(c, (0x80 | ((u >> 12) & 0x3F))); + PUTC(c, (0x80 | ((u >> 6) & 0x3F))); + PUTC(c, (0x80 | ( u >> 6 & 0x3F))); + } } #define STRING_ERROR(ret) do { c->top = head; return ret; } while(0) @@ -104,6 +132,7 @@ static void lept_encode_utf8(lept_context* c, unsigned u) { static int lept_parse_string(lept_context* c, lept_value* v) { size_t head = c->top, len; unsigned u; + unsigned l; const char* p; EXPECT(c, '\"'); p = c->json; @@ -128,7 +157,16 @@ static int lept_parse_string(lept_context* c, lept_value* v) { case 'u': if (!(p = lept_parse_hex4(p, &u))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); - /* \TODO surrogate handling */ + if(u >= 0xD800 && u <= 0xDBFF) { + if (p[0] != '\\' || p[1] != 'u') + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + p += 2; + if(!(p = lept_parse_hex4(p, &l))) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + if(!(l >= 0xDC00 && l <= 0xDFFF)) + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + u = 0x10000 + (u - 0xD800) * 0x400 + (l - 0xDC00); + } lept_encode_utf8(c, u); break; default: diff --git a/tutorial04/my_note04.md b/tutorial04/my_note04.md new file mode 100644 index 00000000..9a9bf5a4 --- /dev/null +++ b/tutorial04/my_note04.md @@ -0,0 +1,29 @@ + +Unicode 返回为 0 至 0x10FFFF,收录了当今几乎所有文字,每个文字映射为Unicode的一个码点。 + +注意U+FFFF也只能表示`16*16*16*16=65536`个字,所以不能容纳所有文字(2016年收录共约128237个), +所以需要扩展: + +``` +同学可能会发现,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) 变换成真实的码点 +``` + + +然而Unicode使用不太方便,为了将Unicode转为比较方便的字节,于是采用(Uniform Transformation Format)UTF, +来存储各个码点,比方说UTF-8,UTF-16和UTF-32,其中UTF-8最流行,为可变长编码。 + +对于 JSON字符串中的 \uXXXX 是以 16 进制表示码点 U+0000 至 U+FFFF,我们需要: + +1. 解析 4 位十六进制整数为码点; +2. 由于字符串是以 UTF-8 存储,我们要把这个码点编码成 UTF-8。 + +也就是说U+XXXX是unicode格式,也是我们可见格式,转化为UTF-8的字节序列(char*)便于程序处理的格式, + +UTF-8 成为现时互联网上最流行的格式,有几个原因: + +1. 它采用字节为编码单元,不会有字节序(endianness)的问题。 +2. 每个 ASCII 字符只需一个字节去储存。 +3. 如果程序原来是以字节方式储存字符,理论上不需要特别改动就能处理 UTF-8 的数据。 From 92c3e3e29279d21c8856d3e5f82f69ab9db4e06f Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Sun, 15 Jan 2017 23:52:09 +0800 Subject: [PATCH 22/23] homework4 code review by answer --- tutorial04/leptjson.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index cb16a92c..ac1cbde8 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -93,37 +93,36 @@ static int lept_parse_number(lept_context* c, lept_value* v) { static const char* lept_parse_hex4(const char* p, unsigned* u) { *u = 0; int i; - for (i = 3; i > 0; i--, p++) - if (ISDIGIT(*p)) - *u |= ((*p - '0') << (i*4)); - else if ( *p >= 'a' && *p <= 'f') - *u |= ((*p - 'a' + 10) << (i*4)); - else if ( *p >= 'A' && *p <= 'F') - *u |= ((*p - 'A' + 10) << (i*4)); - else - return NULL; + for (i = 0; i < 4; i++) { + char ch = *p++; + *u <<= 4; + if (ISDIGIT(ch)) *u |= (ch - '0'); + else if ( ch >= 'A' && ch <= 'F') *u |= ch - ('A' - 10); + else if ( ch >= 'a' && ch <= 'f') *u |= ch - ('a' - 10); + else return NULL; + } return p; } static void lept_encode_utf8(lept_context* c, unsigned u) { assert(u <= 0x10FFFF); - if (u <= 0x007F) { - PUTC(c, u); + if (u <= 0x7F) { + PUTC(c, u & 0xFF); /* 之所有 & 0xFF是因为编译器会将unsigned转char时提示最高位的截断误差*/ } - else if (u >= 0x0080 && u <= 0x07FF) { + else if (u <= 0x07FF) { PUTC(c, (0xC0 | ((u >> 6) & 0xFF))); PUTC(c, (0x80 | ( u & 0x3F))); } - else if (u >= 0x0800 && u <= 0xFFFF) { + else if (u <= 0xFFFF) { PUTC(c, (0xE0 | ((u >> 12) & 0xFF))); PUTC(c, (0x80 | ((u >> 6) & 0x3F))); PUTC(c, (0x80 | ( u & 0x3F))); } - else if (u >= 0x10000 && u <= 0x10FFFF) { + else if (u <= 0x10FFFF) { PUTC(c, (0xF0 | ((u >> 18) & 0xFF))); PUTC(c, (0x80 | ((u >> 12) & 0x3F))); PUTC(c, (0x80 | ((u >> 6) & 0x3F))); - PUTC(c, (0x80 | ( u >> 6 & 0x3F))); + PUTC(c, (0x80 | ( u & 0x3F))); } } @@ -159,13 +158,13 @@ static int lept_parse_string(lept_context* c, lept_value* v) { STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); if(u >= 0xD800 && u <= 0xDBFF) { if (p[0] != '\\' || p[1] != 'u') - STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); p += 2; if(!(p = lept_parse_hex4(p, &l))) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); if(!(l >= 0xDC00 && l <= 0xDFFF)) - STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); - u = 0x10000 + (u - 0xD800) * 0x400 + (l - 0xDC00); + STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); + u = ((u - 0xD800) << 10 ) | (l - 0xDC00) + 0x10000; } lept_encode_utf8(c, u); break; From 5633216f839ba14472262bef37812b00bea68f2c Mon Sep 17 00:00:00 2001 From: Sen Zhang Date: Mon, 16 Jan 2017 19:30:16 +0800 Subject: [PATCH 23/23] warning fix --- tutorial04/leptjson.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial04/leptjson.c b/tutorial04/leptjson.c index ac1cbde8..6079ad86 100644 --- a/tutorial04/leptjson.c +++ b/tutorial04/leptjson.c @@ -91,8 +91,8 @@ static int lept_parse_number(lept_context* c, lept_value* v) { } static const char* lept_parse_hex4(const char* p, unsigned* u) { - *u = 0; int i; + *u = 0; for (i = 0; i < 4; i++) { char ch = *p++; *u <<= 4; @@ -164,7 +164,7 @@ static int lept_parse_string(lept_context* c, lept_value* v) { STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX); if(!(l >= 0xDC00 && l <= 0xDFFF)) STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE); - u = ((u - 0xD800) << 10 ) | (l - 0xDC00) + 0x10000; + u = ((u - 0xD800) << 10 ) | (l - 0xDC00) | 0x10000; } lept_encode_utf8(c, u); break;