Skip to content

Commit cfeb1f3

Browse files
committed
Add tutorial01 answer
1 parent 14966ed commit cfeb1f3

File tree

5 files changed

+318
-0
lines changed

5 files changed

+318
-0
lines changed

tutorial01_answer/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cmake_minimum_required (VERSION 2.6)
2+
project (leptjson_test C)
3+
4+
if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
5+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ansi -pedantic -Wall")
6+
endif()
7+
8+
add_library(leptjson leptjson.c)
9+
add_executable(leptjson_test test.c)
10+
target_link_libraries(leptjson_test leptjson)

tutorial01_answer/leptjson.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "leptjson.h"
2+
#include <assert.h> /* assert() */
3+
#include <stdlib.h> /* NULL */
4+
5+
#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0)
6+
7+
typedef struct {
8+
const char* json;
9+
}lept_context;
10+
11+
static void lept_parse_whitespace(lept_context* c) {
12+
const char *p = c->json;
13+
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
14+
p++;
15+
c->json = p;
16+
}
17+
18+
static int lept_parse_true(lept_context* c, lept_value* v) {
19+
EXPECT(c, 't');
20+
if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
21+
return LEPT_PARSE_INVALID_VALUE;
22+
c->json += 3;
23+
v->type = LEPT_TRUE;
24+
return LEPT_PARSE_OK;
25+
}
26+
27+
static int lept_parse_false(lept_context* c, lept_value* v) {
28+
EXPECT(c, 'f');
29+
if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e')
30+
return LEPT_PARSE_INVALID_VALUE;
31+
c->json += 4;
32+
v->type = LEPT_FALSE;
33+
return LEPT_PARSE_OK;
34+
}
35+
36+
static int lept_parse_null(lept_context* c, lept_value* v) {
37+
EXPECT(c, 'n');
38+
if (c->json[0] != 'u' || c->json[1] != 'l' || c->json[2] != 'l')
39+
return LEPT_PARSE_INVALID_VALUE;
40+
c->json += 3;
41+
v->type = LEPT_NULL;
42+
return LEPT_PARSE_OK;
43+
}
44+
45+
static int lept_parse_value(lept_context* c, lept_value* v) {
46+
switch (*c->json) {
47+
case 't': return lept_parse_true(c, v);
48+
case 'f': return lept_parse_false(c, v);
49+
case 'n': return lept_parse_null(c, v);
50+
case '\0': return LEPT_PARSE_EXPECT_VALUE;
51+
default: return LEPT_PARSE_INVALID_VALUE;
52+
}
53+
}
54+
55+
int lept_parse(lept_value* v, const char* json) {
56+
lept_context c;
57+
int ret;
58+
assert(v != NULL);
59+
c.json = json;
60+
v->type = LEPT_NULL;
61+
lept_parse_whitespace(&c);
62+
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
63+
lept_parse_whitespace(&c);
64+
if (*c.json != '\0')
65+
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
66+
}
67+
return ret;
68+
}
69+
70+
lept_type lept_get_type(const lept_value* v) {
71+
assert(v != NULL);
72+
return v->type;
73+
}

tutorial01_answer/leptjson.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ifndef LEPTJSON_H__
2+
#define LEPTJSON_H__
3+
4+
typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type;
5+
6+
typedef struct {
7+
lept_type type;
8+
}lept_value;
9+
10+
enum {
11+
LEPT_PARSE_OK = 0,
12+
LEPT_PARSE_EXPECT_VALUE,
13+
LEPT_PARSE_INVALID_VALUE,
14+
LEPT_PARSE_ROOT_NOT_SINGULAR
15+
};
16+
17+
int lept_parse(lept_value* v, const char* json);
18+
19+
lept_type lept_get_type(const lept_value* v);
20+
21+
#endif /* LEPTJSON_H__ */

tutorial01_answer/test.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include "leptjson.h"
5+
6+
static int main_ret = 0;
7+
static int test_count = 0;
8+
static int test_pass = 0;
9+
10+
#define EXPECT_EQ_BASE(equality, expect, actual, format) \
11+
do {\
12+
test_count++;\
13+
if (equality)\
14+
test_pass++;\
15+
else {\
16+
fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\
17+
main_ret = 1;\
18+
}\
19+
} while(0)
20+
21+
#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d")
22+
23+
static void test_parse_null() {
24+
lept_value v;
25+
v.type = LEPT_FALSE;
26+
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
27+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
28+
}
29+
30+
static void test_parse_true() {
31+
lept_value v;
32+
v.type = LEPT_FALSE;
33+
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
34+
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
35+
}
36+
37+
static void test_parse_false() {
38+
lept_value v;
39+
v.type = LEPT_TRUE;
40+
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
41+
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
42+
}
43+
44+
static void test_parse_expect_value() {
45+
lept_value v;
46+
47+
v.type = LEPT_FALSE;
48+
EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, ""));
49+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
50+
51+
v.type = LEPT_FALSE;
52+
EXPECT_EQ_INT(LEPT_PARSE_EXPECT_VALUE, lept_parse(&v, " "));
53+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
54+
}
55+
56+
static void test_parse_invalid_value() {
57+
lept_value v;
58+
v.type = LEPT_FALSE;
59+
EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "nul"));
60+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
61+
62+
v.type = LEPT_FALSE;
63+
EXPECT_EQ_INT(LEPT_PARSE_INVALID_VALUE, lept_parse(&v, "?"));
64+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
65+
}
66+
67+
static void test_parse_root_not_singular() {
68+
lept_value v;
69+
v.type = LEPT_FALSE;
70+
EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x"));
71+
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
72+
}
73+
74+
static void test_parse() {
75+
test_parse_null();
76+
test_parse_true();
77+
test_parse_false();
78+
test_parse_expect_value();
79+
test_parse_invalid_value();
80+
test_parse_root_not_singular();
81+
}
82+
83+
int main() {
84+
test_parse();
85+
printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count);
86+
return main_ret;
87+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
% 从零开始的 JSON 库教程(一):启程解答编
2+
% Milo Yip
3+
% 2016/9/16
4+
5+
本文是[《从零开始的 JSON 库教程》](https://zhuanlan.zhihu.com/p/22457315)的第一个单元。解答代码位于 [json-tutorial/tutorial01_answer](https://github.com/miloyip/json-tutorial/blob/master/tutorial01_answer/)
6+
7+
## 1. 修正 LEPT_PARSE_ROOT_NOT_SINGULAR
8+
9+
单元测试失败的是这一行:
10+
11+
~~~c
12+
EXPECT_EQ_INT(LEPT_PARSE_ROOT_NOT_SINGULAR, lept_parse(&v, "null x"));
13+
~~~
14+
15+
我们从 JSON 语法发现,JSON 文本应该有 3 部分:
16+
17+
~~~
18+
JSON-text = ws value ws
19+
~~~
20+
21+
但原来的 `lept_parse()` 只处理了前两部分。我们只需要加入第三部分,解析空白,然后检查 JSON 文本是否完结:
22+
23+
~~~c
24+
int lept_parse(lept_value* v, const char* json) {
25+
lept_context c;
26+
int ret;
27+
assert(v != NULL);
28+
c.json = json;
29+
v->type = LEPT_NULL;
30+
lept_parse_whitespace(&c);
31+
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
32+
lept_parse_whitespace(&c);
33+
if (*c.json != '\0')
34+
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
35+
}
36+
return ret;
37+
}
38+
~~~
39+
40+
有一些 JSON 解析器完整解析一个值之后就会顺利返回,这是不符合标准的。但有时候也有另一种需求,文本中含多个 JSON 或其他文本串接在一起,希望当完整解析一个值之后就停下来。因此,有一些 JSON 解析器会提供这种选项,例如 RapidJSON 的 `kParseStopWhenDoneFlag`
41+
42+
## 2. true/false 单元测试
43+
44+
此问题很简单,只需参考 `test_parse_null()` 加入两个测试函数:
45+
46+
~~~c
47+
static void test_parse_true() {
48+
lept_value v;
49+
v.type = LEPT_FALSE;
50+
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
51+
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
52+
}
53+
54+
static void test_parse_false() {
55+
lept_value v;
56+
v.type = LEPT_TRUE;
57+
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
58+
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
59+
}
60+
61+
static void test_parse() {
62+
test_parse_null();
63+
test_parse_true();
64+
test_parse_false();
65+
test_parse_expect_value();
66+
test_parse_invalid_value();
67+
test_parse_root_not_singular();
68+
}
69+
~~~
70+
71+
但要记得在上一级的测试函数 test_parse() 调用这函数,否则会不起作用。还好如果我们记得用 `static` 修饰这两个函数,编译器会发出告警:
72+
73+
~~~
74+
test.c:30:13: warning: unused function 'test_parse_true' [-Wunused-function]
75+
static void test_parse_true() {
76+
^
77+
~~~
78+
79+
因为 static 函数的意思是指,该函数只作用于编译单元中,那么没有被调用时,编译器是能发现的。
80+
81+
### 3. true/false 解析
82+
83+
这部分很简单,只要参考 `lept_parse_null()`,再写两个函数,然后在 `lept_parse_value` 按首字符分派。
84+
85+
~~~c
86+
static int lept_parse_true(lept_context* c, lept_value* v) {
87+
EXPECT(c, 't');
88+
if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
89+
return LEPT_PARSE_INVALID_VALUE;
90+
c->json += 3;
91+
v->type = LEPT_TRUE;
92+
return LEPT_PARSE_OK;
93+
}
94+
95+
static int lept_parse_false(lept_context* c, lept_value* v) {
96+
EXPECT(c, 'f');
97+
if (c->json[0] != 'a' || c->json[1] != 'l' || c->json[2] != 's' || c->json[3] != 'e')
98+
return LEPT_PARSE_INVALID_VALUE;
99+
c->json += 4;
100+
v->type = LEPT_FALSE;
101+
return LEPT_PARSE_OK;
102+
}
103+
104+
static int lept_parse_value(lept_context* c, lept_value* v) {
105+
switch (*c->json) {
106+
case 't': return lept_parse_true(c, v);
107+
case 'f': return lept_parse_false(c, v);
108+
case 'n': return lept_parse_null(c, v);
109+
case '\0': return LEPT_PARSE_EXPECT_VALUE;
110+
default: return LEPT_PARSE_INVALID_VALUE;
111+
}
112+
}
113+
~~~
114+
115+
其实这 3 种类型都是解析字面量,可以使用单一个函数实现,例如用这种方式调用:
116+
117+
~~~c
118+
case 'n': return lept_parse_literal(c, v, "null", LEPT_NULL);
119+
~~~
120+
121+
这样可以减少一些重复代码,不过可能有少许额外性能开销。
122+
123+
## 4. 总结
124+
125+
如果你能完成这个练习,恭喜你!我想你通过亲自动手,会对教程里所说的有更深入的理解。如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 [issue](https://github.com/miloyip/json-tutorial/issues) 中提出,让所有人一起讨论。
126+
127+
下一单元是和数字类型相关,敬请期待。

0 commit comments

Comments
 (0)