JSON库(2) 函数重构,解析数字

本文最后更新于:1 年前

JSON库(2) 函数重构,解析数字

写在前面

过去的大概一周多的时间都没有更新博客,一方面是非常忙,把准备很长时间的数模国赛打了,也处理了些学生会的事务,另一方面自己好像也松懈了很多,没有放假时候那么紧张了,希望赶紧把状态调整回来。

实验内容

本节主要完成的任务已经写在题目里了,比较容易理解,直接上代码。

1 修改leptjson.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*  leptjson实现文件,此文件将编译成库 */
#include "leptjson.h"
#include <assert.h> /* assert() */
#include <errno.h> /* errno, ERANGE */
#include <math.h> /* HUGE_VAL */
#include <stdlib.h> /* NULL, strtod() */

/* 检测c->json是否指向目标值 c指向下一个字符*/
#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0)
/* 检测字符是否在0~9之间 */
#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9')
/* 检测字符是否在1~9之间 */
#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9')

/* 存放参数(JSON字符串) */
typedef struct {
const char* json;
}lept_context;

/* ws = *(%x20 / %09 / %x0A / %x0D) */
/* 消除空格字符 */
static void lept_parse_whitespace(lept_context* c) {
const char *p = c->json; /* 创建指针p指向该字符串 */
/* 将指针移动到非空格字符 */
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
p++;
c->json = p;
}

/* null = "null" */
/* true = "true" */
/* false = "false" */
/* 将三个函数进行合并,传入的literal是正确的字符串 */
static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
size_t i; /* i表示数组的长度,注意数组的长度最好用size_t类型表示 */
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); /* 用标准库strtod进行类型转换 */
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;
}

/* value */
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);
case '\0': return LEPT_PARSE_EXPECT_VALUE;
default: return lept_parse_number(c, v);
}
}

/* 解析器 */
/* 暂时只储存JSON字符串当前位置 */
int lept_parse(lept_value* v, const char* json) {
lept_context c; /* 创建新结构体c,保存字符串 */
assert(v != NULL); /* v为空,弹出报错 */
c.json = json; /* 字符串保存到c中 */
v->type = LEPT_NULL; /* v的type赋为null */
lept_parse_whitespace(&c); /* 消除JSON字符串前面的空格字符 */

/* 第1题答案 解决字符串中间还有空格的问题 */
int ret;
/* 如果字符串正确,进入循环 */
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
lept_parse_whitespace(&c); /* 每次检验之前消一次空格 */
/* 指针指向JSON末尾,退出循环 */
if (*c.json != '\0') {
v->type = LEPT_NULL; /* 注意把type重新置为null */
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
}
return ret;
}

/* 获取JSON的type*/
lept_type lept_get_type(const lept_value* v) {
assert(v != NULL); /* 断言控制 */
return v->type;
}

/* 如果JSON的type为LEPT_NUMBER,获取数值*/
double lept_get_number(const lept_value* v) {
assert(v != NULL && v->type == LEPT_NUMBER); /* 断言控制 */
return v->n;
}

主要看两个函数的实现,一个是lept_parse_literal(),这是函数重构部分,另一个是lept_parse_number(),这是解析数字部分。

先来看第一个要修改的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}

我们能观察到在上一节编写完成后,对nulltruefalse这三个字符串的处理是非常类似的,完全可以合并成一个函数。合并是非常简单的。

1
2
3
4
5
6
7
8
9
10
static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
size_t i; /* i表示数组的长度,注意数组的长度最好用size_t类型表示 */
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;
}

因为不知道要检验这三个字符串中的哪个,所以传参需要有它正确的字符串和正确的类型。然后值得注意的地方就是在其中用了一个循环去遍历字符串中的字符,而不是像上面的三个函数一样因为已知正确字符串的每个字符直接用条件判断了。

第二个要修改的地方就是要加入检验数字的函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* 检验数字 */
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); /* 用标准库strtod进行类型转换 */
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;
}

这里我为了简单地阐释清楚这一部分,直接放上原帖作者的图了。

图为JSON数字格式

这个函数与其它检验函数不同的地方就在于需要考虑到很多的不合法格式,其次就是对数字过大的处理,我们引用了errno。简单解释下,当linux中的 api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。因此推断该数字是否为大数。对于大数,需要返回其特定形式。

2 修改test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* 单元测试(TDD) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "leptjson.h"

static int main_ret = 0;
static int test_count = 0;
static int test_pass = 0;

/* 如果expect != actual,打印错误信息 */
#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)

/* 调用EXPECT_EQ_BASE,检验预期值和实际值是否相等 */
#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; /* 定义JSON值结构体v 参数为type */
v.type = LEPT_FALSE; /* v的type初始化为false */
/* 过单元测试 */
/* 预期值,实际值 */
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}

/* 1.2答案 */
/* 和test_parse_null函数只有传入JSON有差别 */
/* 注意在test_parse函数中调用这两个函数 */
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, "?");

#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 zer
o should be '.' , 'E' , 'e' 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;
}

这里面就只需要加上几个测试值就好了,首先通过TEST_NUMBER的定义式和test_parse_number()函数共同测试数字类型的字符串。还有就是我们前面说过的函数重构,test_parse_expect_value() test_parse_invalid_value() test_parse_root_not_singular() test_parse_number_too_big()由于两个函数的处理近乎相同,所以考虑对其采用重构,这里采用宏定义的方式实现,比较简单。

编译运行

直接进入build文件夹内输入make编译,然后再输入./leptjson_test运行就好,运行结果依然是100%通过率,就不再上图了。

写在后面

到这里第二节就结束了。随便写一写最近的感受吧,最近的日子真的很浮躁,好像整个九月都没有什么进步,眼看日子一天天的划过内心却毫无紧张感,很多lab、项目都在开展途中,却没有更新的动力,这学期课业的压力也相当大,自己如果没办法调整好状态很难在这学期有较大进步。前几天读到一句话感觉很好,自己作为一个小小学生组织的一员也要千万谨记,水能载舟,亦能覆舟,德不配位,必有余殃。共勉!