语法子集及解析思路:
上面的子集表示json字符串是由两个双引号夹着零至多个字符构成。
字符分为无转义字符和转义字符,转义字符有九种,比较特殊的是\uXXXX表示的Unicode字符。
解析思路
先解析了左双引号,然后开始解析中间的字符,遇到右双引号“"”表示解析结束,遇到"\"表示解析转义字符,继续检测’\‘后面的字符是否为合法转义字符,遇到’\0’表示字符串结尾,没有遇到右双引号,报错,否则就是默认字符,检测是否为合法默认字符。
由于事先不知道字符串长度,需要一个地方来存储解析结果,而且在解析字符串,数组,对象的时候,总是采取先进后出的方式访问数组,所以选择实现一个动态堆栈结构来存储解析结果。
在解析过程中可能会报错,这时候已经把之前合法的字符压入栈里,需要提前备份栈顶位置,在遇到错误的时候回退。
实现细节及遇到的问题;
- 动态堆栈有点特殊,是以字节存储的,每次可以压入任意大小的数据:
typedef struct {
const char* json;
char* stack;
size_t size, top;
}lept_context;
- 解析完成将字符串压栈:
static void* lept_context_push(lept_context* c, size_t size) {
void* ret;
assert(size > 0);
if (c->top + size >= c->size) {
if (c->size == 0)
c->size = LEPT_PARSE_STACK_INIT_SIZE;
while (c->top + size >= c->size)
c->size += c->size >> 1; /* c->size * 1.5 */
c->stack = (char*)realloc(c->stack, c->size);
}
ret = c->stack + c->top;
c->top += size;
return ret;
}
在压栈之前检查新加入的数据会不会超过栈容量,会的话对栈扩容,扩容前检查栈容量是否为0,为0要先初始化容量。
遇到的问题
在 C 语言中,字符串一般表示为空结尾字符串(null-terminatedstring),即以空字符(’\0’)代表字符串的结束。然而,JSON 字符串是允许含有空字符的,例如这个 JSON
“Hello\u0000World” 就是单个字符串,解析后为11个字符。如果纯粹使用空结尾字符来表示 JSON 解析后的结果,就没法处理空字符。
刚开始没看懂这段话,仔细想想如果把包含\0的json文本解析存到c数组里,那么\0后面的元素就看不到了,程序以为数组在\0处结束了。
见代码:
char str[] = "aa\0b";
printf("%s", str); //aa
-
c->top = head;
作用:还原栈顶,因为出错前可能把字符压入栈中,这些字符都是不需要的,直接将栈顶位置回退,后续继续解析就覆盖那些不需要的字符。 -
为什么需要字符串解析缓冲区?
听缓冲区可能不太理解,其实就是需要一个中间区域保存解析了一半的结果,为什么不直接将解析的部分写入结果呢?我想的是这样会造成内存多次写入,浪费时间。