前言
本节将要学习的是第一种复合类型的解析:数组。具体的解析规则在Tutorial中已经有了,概括下简单的思想就是遇到[
符号之后挨个调用lept_parse_value
来解析数组的每一个元素,当然每次遇到逗号就要将已经解析的那个元素进栈,当遇到]
符号时,栈中所有元素一起出栈,保存到数组Json Value中。特别需要注意的是解析失败时的内存泄露问题。
代码设计
1. 编写 test_parse_array()
单元测试
在lept_parse_array
大致框架已经为我们搭建好的情况下,我们只需要为其写两个单测。由于数组元素个数、类型都是不确定的,所以不能用一个宏就搞定。这里的关键在于使用lept_get_array_element
将数组元素一个个取出来,挨个对其进行值、类型的校验。
static void test_parse_array() {
lept_value v;
lept_init(&v);
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ ]"));
EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
EXPECT_EQ_SIZE_T(0, lept_get_array_size(&v));
lept_free(&v);
lept_init(&v);
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ null , false , true , 123 , \"abc\" ]"));
EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
EXPECT_EQ_SIZE_T(5, lept_get_array_size(&v));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(lept_get_array_element(&v, 0)));
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(lept_get_array_element(&v, 1)));
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(lept_get_array_element(&v, 2)));
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(&v, 3)));
EXPECT_EQ_DOUBLE(123.0, lept_get_number(lept_get_array_element(&v, 3)));
EXPECT_EQ_STRING("abc", lept_get_string(lept_get_array_element(&v, 4)), 3);
lept_free(&v);
lept_init(&v);
size_t i;
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "[ [ ] , [ 0 ] , [ 0 , 1 ] , [ 0 , 1 , 2 ] ]"));
EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));
EXPECT_EQ_SIZE_T(4, lept_get_array_size(&v));
for (i = 0; i < 4; i++) {
lept_value* sub_v = lept_get_array_element(&v, i);
EXPECT_EQ_SIZE_T(i, lept_get_array_size(sub_v));
int j;
for (j = 0; j < i; j++) {
EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(lept_get_array_element(sub_v, j)));
EXPECT_EQ_DOUBLE((double)j, lept_get_number(lept_get_array_element(sub_v, j)));
}
}
lept_free(&v);
}
2. lept_parse_array()
里加入空白字符处理
按现时的 lept_parse_array()
的编写方式,需要加入 3 个 lept_parse_whitespace()
调用,分别是解析 [
之后,元素之后,以及 ,
之后:
static int lept_parse_array(lept_context* c, lept_value* v) {
/* ... */
EXPECT(c, '[');
lept_parse_whitespace(c);
/* ... */
for (;;) {
/* ... */
if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK)
return ret;
/* ... */
lept_parse_whitespace(c);
if (*c->json == ',') {
c->json++;
lept_parse_whitespace(c);
}
/* ... */
}
}
3. 内存泄漏
我们在遇到]
解析完成后,会为解析出来的数组使用malloc
和memcpy
为数组的Json Value分配可变内存。那么当一个单测用例结束时,我们应该将这部分内存释放,于是顺理成章地,这部分逻辑应该实现在lept_free
里面。
void lept_free(lept_value* v) {
assert(v != NULL);
size_t i;
if (v->type == LEPT_STRING)
free(v->u.s.s);
else if (v->type == LEPT_ARRAY) {
for (i = 0; i < v->u.a.size; i++) {
lept_free(&v->u.a.e[i]);
}
free(v->u.a.e);
}
v->type = LEPT_NULL;
}
这里需要把v
中的元素挨个递归调用lept_free释放内存,因为数组元素也可以是一个数组,是一种嵌套关系。
4. 解析错误时的缓冲区临时值处理
在lept_parse_array
中,有两个地方可能会导致解析失败:
lept_parse_value
解析单个值- 除了数值、
,
、]
以外的符号
以下是我一开始的初版实现,仅仅在上述的两个分支内加了lept_context_pop
函数,想将栈内的字符弹出。但是后来发现这个函数仅仅只是改了栈顶指针,并没有实际调用free
。
static int lept_parse_array(lept_context* c, lept_value* v) {
size_t size = 0;
int ret;
EXPECT(c, '[');
lept_parse_whitespace(c);
if (*c->json == ']') {
c->json++;
v->type = LEPT_ARRAY;
v->u.a.size = 0;
v->u.a.e = NULL;
return LEPT_PARSE_OK;
}
for (;;) {
lept_value e;
lept_init(&e);
if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) {
lept_context_pop(c, size * sizeof(lept_value));
return ret;
}
memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value));
size++;
lept_parse_whitespace(c);
if (*c->json == ',') {
c->json++;
lept_parse_whitespace(c);
} else if (*c->json == ']') {
c->json++;
v->type = LEPT_ARRAY;
v->u.a.size = size;
size *= sizeof(lept_value);
memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
return LEPT_PARSE_OK;
}
else {
lept_context_pop(c, size * sizeof(lept_value));
return LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
}
}
}
static void* lept_context_pop(lept_context* c, size_t size) {
assert(c->top >= size);
return c->stack + (c->top -= size);
}
实际上正确的做法是,挨个遍历栈中的lept_value然后释放。我们的栈是按照字节(char)存储的,一个lept_value的大小是24字节,即sizeof(lept_value)=24。所以每次必须要弹出24字节并转为lept_value*。
static int lept_parse_array(lept_context* c, lept_value* v) {
/*size must be initialized.*/
size_t size = 0, i;
int ret;
EXPECT(c, '[');
lept_parse_whitespace(c);
if (*c->json == ']') {
c->json++;
v->type = LEPT_ARRAY;
v->u.a.size = 0;
v->u.a.e = NULL;
return LEPT_PARSE_OK;
}
for (;;) {
lept_value e;
lept_init(&e);
if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) {
/*Break here.*/
break;
}
memcpy(lept_context_push(c, sizeof(lept_value)), &e, sizeof(lept_value));
size++;
lept_parse_whitespace(c);
if (*c->json == ',') {
c->json++;
lept_parse_whitespace(c);
} else if (*c->json == ']') {
c->json++;
v->type = LEPT_ARRAY;
v->u.a.size = size;
size *= sizeof(lept_value);
memcpy(v->u.a.e = (lept_value*)malloc(size), lept_context_pop(c, size), size);
return LEPT_PARSE_OK;
}
else {
/*Break here.*/
ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET;
break;
}
}
/*Free resource here.*/
for (i = 0; i < size; i++) {
lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value)));
}
return ret;
}
在解析遇到错误时,直接保存返回值后break。在最后遍历栈释放资源。
5. 第 4 节那段代码为什么会有 bug?
见Tutorial的解释,bug来自于:如果提前获取了lept_context_push的压栈指针,在lept_parse_value中,可能会遇到realloc函数扩容导致的悬空指针。
标签:get,Tutorial05,value,parse,Json,lept,解析,LEPT,size From: https://www.cnblogs.com/muuu520/p/17034954.html