前言
本文紧接着上一章,探讨如何进行字符串的解析。
第三章难度比数字的解析要略难一点,主要是对字符串以及转义字符的理解。并且本章加入了一些设置与获取函数,用于补全之前的遗漏。先看看JSON的字符串语法
string = quotation-mark *char quotation-mark
char = unescaped /
escape (
%x22 / ; " quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX
escape = %x5C ; \
quotation-mark = %x22 ; "
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
- 字符串是由
" + 任意字符 + "
组成 - 转义序列的字符有9种,之后要专门写他们的识别(作业2) ,
\uxxxx
当中 XXXX 为 16 进位的 UTF-16 编码,本单元将不处理这种转义序列,留待下回分解。
escape 表示 转义字符
一、leptjson.h
#ifndef LEPTJSON_H__
#define LEPTJSON_H__
#include <stddef.h> /* size_t */
typedef enum { LEPT_NULL, LEPT_FALSE, LEPT_TRUE, LEPT_NUMBER, LEPT_STRING, LEPT_ARRAY, LEPT_OBJECT } lept_type;
typedef struct {
/*union:取决于是数字还是字符串,共用一片空间*/
union {
struct { char* s; size_t len; }s; /* string: null-terminated string, string length */
double n; /* number */
}u;
lept_type type;
}lept_value;
enum {
LEPT_PARSE_OK = 0,
LEPT_PARSE_EXPECT_VALUE,
LEPT_PARSE_INVALID_VALUE,
LEPT_PARSE_ROOT_NOT_SINGULAR,
LEPT_PARSE_NUMBER_TOO_BIG,
LEPT_PARSE_MISS_QUOTATION_MARK,
LEPT_PARSE_INVALID_STRING_ESCAPE,
LEPT_PARSE_INVALID_STRING_CHAR
};
#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0)
int lept_parse(lept_value* v, const char* json);
void lept_free(lept_value* v);
lept_type lept_get_type(const lept_value* v);
#define lept_set_null(v) lept_free(v)
//True or False
int lept_get_boolean(const lept_value* v);
void lept_set_boolean(lept_value* v, int b);
//number
double lept_get_number(const lept_value* v);
void lept_set_number(lept_value* v, double n);
//string
const char* lept_get_string(const lept_value* v);
size_t lept_get_string_length(const lept_value* v);
void lept_set_string(lept_value* v, const char* s, size_t len);
#endif /* LEPTJSON_H__ */
1. 因为这次要加入字符串解析,自然JSON的数据结构也要发生变化。
typedef struct {
/*union:取决于是数字还是字符串,共用一片空间*/
union {
struct { char* s; size_t len; }s; /* string: null-terminated string, string length */
double n; /* number */
}u;
lept_type type;
}lept_value;
- 用一个结构体表示字符串,里面分别是一个
char *
以及他的长度len
- 由于
type
已经决定了数据是什么类型的,因此本质上s
n
不可能同时存在,用union
来表示
下图为union在内存中的布局:
2. 解析的结果增加三项 缺少引号 非法的转义字符 以及 非字符串
enum {
LEPT_PARSE_OK = 0,
LEPT_PARSE_EXPECT_VALUE,
LEPT_PARSE_INVALID_VALUE,
LEPT_PARSE_ROOT_NOT_SINGULAR,
LEPT_PARSE_NUMBER_TOO_BIG,
LEPT_PARSE_MISS_QUOTATION_MARK,
LEPT_PARSE_INVALID_STRING_ESCAPE,
LEPT_PARSE_INVALID_STRING_CHAR
};
3.初始化 v->type 转化为宏定义实现
#define lept_init(v) do { (v)->type = LEPT_NULL; } while(0)
其他新增函数留在实现的时候解释,看函数名也能大致理解,是比较简单的函数。
二、leptjson.c
.c 文件越来越长了,主要是含有一些注释可能能帮助理解,具体各个部分会在后面讲
#include "leptjson.h"
#include <assert.h> /* assert() */
#include <errno.h> /* errno, ERANGE */
#include <math.h> /* HUGE_VAL */
#include <stdlib.h> /* NULL, malloc(), realloc(), free(), strtod() */
#include <string.h> /* memcpy() */
#ifndef LEPT_PARSE_STACK_INIT_SIZE
#define LEPT_PARSE_STACK_INIT_SIZE 256
#endif
#define EXPECT(c, ch) do { assert(*c->json == (ch)); c->json++; } while(0)
#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9')
#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9')
/*通过(char*)lept_context_push(c, sizeof(char)),会返回一个地址
然后让这个地址的值等于ch,也就是正在处理的那一个字符
而通过(char*)lept_context_push(c, sizeof(char)),会把c->stack这个动态栈移动到下一个空白的位置用来存储字符*/
#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0)
typedef struct {
const char* json;
/*动态堆栈, 解析字符串(以及之后的数组、对象)时
需要把解析的结果先储存在一个临时的缓冲区,最后再用 lept_set_string() 把缓冲区的结果设进值之中。*/
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) {
/*设定一下初始大小256*/
if (c->size == 0)
c->size = LEPT_PARSE_STACK_INIT_SIZE;
/*每次扩展1.5倍,直到压入的空间足够*/
while (c->top + size >= c->size)
c->size += c->size >> 1; /* c->size * 1.5 */
/*注意到这里使用了 realloc() 来重新分配内存
c->stack 在初始化时为 NULL,realloc(NULL, size) 的行为是等价于 malloc(size) */
c->stack = (char*)realloc(c->stack, c->size);
}
//让地址移动到下一个可存放数据的位置上
ret = c->stack + c->top;
//top指向下一个空白位置
c->top += size;
return ret;
}
static void* lept_context_pop(lept_context* c, size_t size) {
assert(c->top >= size);
return c->stack + (c->top -= size);
}
//解析空白
static void lept_parse_whitespace(lept_context* c) {
const char *p = c->json;
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
p++;
c->json = p;
}
//解析 N T F
static int lept_parse_literal(lept_context* c, lept_value* v, const char* literal, lept_type type) {
size_t i;
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->u.n = strtod(c->json, NULL);
if (errno == ERANGE && (v->u.n == HUGE_VAL || v->u.n == -HUGE_VAL))
return LEPT_PARSE_NUMBER_TOO_BIG;
v->type = LEPT_NUMBER;
c->json = p;
return LEPT_PARSE_OK;
}
//解析字符串
static int lept_parse_string(lept_context* c, lept_value* v) {
size_t head = c->top, len;
const char* p;
EXPECT(c, '\"'); /*老样子,先跳过一个 " */
p = c->json; /*存储了除了第一个"以外的,如 hello\" */
for (;;) {
char ch = *p++;
switch (ch) {
/* 处理转义字符 遇到 \\ 之后开始做分类讨论 若\\之后不是这些字母或者符号 则出现了语法错误 */
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:
//让top指回0,相当于释放了stack之前存的了
c->top = head;
return LEPT_PARSE_INVALID_STRING_ESCAPE;
}
break;
case '\"':
//此时head仍为0,而c->top则指向下一个空白,而case到\"说明字符串结束了,因此此时相减就是该字符串的长度
len = c->top - head;
lept_set_string(v, (const char*)lept_context_pop(c, len), len);
c->json = p;
return LEPT_PARSE_OK;
/* 若最后一个字符是\0说明缺少了一个引号,因此返回缺少引号的错误 */
case '\0':
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);
}
}
}
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);
default: return lept_parse_number(c, v);
case '"': return lept_parse_string(c, v);
case '\0': return LEPT_PARSE_EXPECT_VALUE;
}
}
int lept_parse(lept_value* v, const char* json) {
lept_context c;
int ret;
assert(v != NULL);
c.json = json;
c.stack = NULL;
c.size = c.top = 0;
lept_init(v); /* == (v->type = LEPT_NULL) */
lept_parse_whitespace(&c);
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
lept_parse_whitespace(&c);
if (*c.json != '\0') {
v->type = LEPT_NULL;
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
}
assert(c.top == 0);
free(c.stack);
return ret;
}
void lept_free(lept_value* v) {
assert(v != NULL);
if (v->type == LEPT_STRING)
free(v->u.s.s);
v->type = LEPT_NULL; /*避免重复释放。*/
}
lept_type lept_get_type(const lept_value* v) {
assert(v != NULL);
return v->type;
}
int lept_get_boolean(const lept_value* v) {
/* \TODO */
assert(v != NULL && (v->type == LEPT_TRUE || v->type == LEPT_FALSE));
return v->type == LEPT_TRUE; /*相当于做比较了,若为TRUE返回的是1,否则为0*/
}
void lept_set_boolean(lept_value* v, int b) {
/* \TODO */
lept_free(v);
if (v->type = b)
v->type = LEPT_TRUE;
else
v->type = LEPT_FALSE;
}
double lept_get_number(const lept_value* v) {
assert(v != NULL && v->type == LEPT_NUMBER);
return v->u.n;
}
void lept_set_number(lept_value* v, double n) {
/* \TODO */
lept_free(v);
v->type = LEPT_NUMBER;
v->u.n = n;
}
const char* lept_get_string(const lept_value* v) {
assert(v != NULL && v->type == LEPT_STRING);
return v->u.s.s;
}
size_t lept_get_string_length(const lept_value* v) {
assert(v != NULL && v->type == LEPT_STRING);
return v->u.s.len;
}
//const char* s是存储在缓冲区的字符串,是不带'\0'的伪字符串
void lept_set_string(lept_value* v, const char* s, size_t len) {
assert(v != NULL && (s != NULL || len == 0));
lept_free(v);
v->u.s.s = (char*)malloc(len + 1);
memcpy(v->u.s.s, s, len);
v->u.s.s[len] = '\0'; /*手动在结尾添加'\0'*/
v->u.s.len = len;
v->type = LEPT_STRING;
}
1. 先从设置string的函数开始看起,也就是把解析完的字符串存入 v
中
//const char* s是存储在缓冲区的字符串,是不带'\0'的伪字符串
void lept_set_string(lept_value* v, const char* s, size_t len) {
assert(v != NULL && (s != NULL || len == 0));
lept_free(v);
v->u.s.s = (char*)malloc(len + 1);
memcpy(v->u.s.s, s, len);
v->u.s.s[len] = '\0'; /*手动在结尾添加'\0'*/
v->u.s.len = len;
v->type = LEPT_STRING;
}
- 断言中的条件是,非空指针(有具体的字符串)或是零长度的字符串都是合法的。
- 首先后面会讲,我们通过设置一个栈,把字符串一个个字符输入到一个缓冲区,此时
s
就是指向这片缓冲区的首地址。但此时的s不是合法的字符串,因为并没有将\0
作为结尾,因此在把s
放入v->u.s.s
的时候需要用v->u.s.s = (char*)malloc(len + 1);
来增加一块大小。 - 之后用
memcpy()
函数将其复制到v->u.s.s
内。 - 注意,在设置这个 v 之前,我们需要先调用
lept_free(v)
去清空v
可能分配到的内存。例如原来已有一字符串,我们要先把它释放。
2. 那么,再看看 lept_free()
void lept_free(lept_value* v) {
assert(v != NULL);
if (v->type == LEPT_STRING)
free(v->u.s.s);
v->type = LEPT_NULL; /*避免重复释放。*/
}
- 现时仅当值是字符串类型,我们才要处理,之后我们还要加上对数组及对象的释放。lept_free(v) 之后,会把它的类型变成 null。这个设计能避免重复释放。
3. 缓冲区与堆栈
个人认为是本章难点之一。
我们解析字符串(以及之后的数组、对象)时,需要把解析的结果先储存在一个临时的缓冲区,最后再用 lept_set_string()
把缓冲区的结果设进值之中。在完成解析一个字符串之前,这个缓冲区的大小是不能预知的。因此,我们可以采用动态数组(dynamic array)这种数据结构,即数组空间不足时,能自动扩展。C++ 标准库的 std::vector
也是一种动态数组。
如果每次解析字符串时,都重新建一个动态数组,那么是比较耗时的。我们可以重用这个动态数组,每次解析 JSON 时就只需要创建一个。而且我们将会发现,无论是解析字符串、数组或对象,我们也只需要以先进后出的方式访问这个动态数组。换句话说,我们需要一个动态的堆栈(stack)数据结构。
typedef struct {
const char* json;
/*动态堆栈, 解析字符串(以及之后的数组、对象)时
需要把解析的结果先储存在一个临时的缓冲区,最后再用 lept_set_string() 把缓冲区的结果设进值之中。*/
char* stack;
size_t size, top;
}lept_context;
- 同前面说的和解析说的一致,建立一个栈去作为临时的缓冲区
- 当中
size
是当前的堆栈容量,top
是栈顶的位置(由于我们会扩展stack
,所以不要把top
用指针形式存储)。
然后,我们在创建 lept_context 的时候初始化 stack 并最终释放内存:
int lept_parse(lept_value* v, const char* json) {
lept_context c;
int ret;
assert(v != NULL);
c.json = json;
c.stack = NULL;
c.size = c.top = 0;
lept_init(v); /* == (v->type = LEPT_NULL) */
lept_parse_whitespace(&c);
if ((ret = lept_parse_value(&c, v)) == LEPT_PARSE_OK) {
lept_parse_whitespace(&c);
if (*c.json != '\0') {
v->type = LEPT_NULL;
ret = LEPT_PARSE_ROOT_NOT_SINGULAR;
}
}
assert(c.top == 0);
free(c.stack);
return ret;
}
- 应该比较好理解。
然后,我们实现堆栈的压入及弹出操作。和普通的堆栈不一样,我们这个堆栈是以字节储存的。每次可要求压入任意大小的数据,它会返回数据起始的指针:
//压栈(√)
static void* lept_context_push(lept_context* c, size_t size) {
void* ret;
assert(size > 0);
/*判断是否超过原有的大小了*/
if (c->top + size >= c->size) {
/*设定一下初始大小256*/
if (c->size == 0)
c->size = LEPT_PARSE_STACK_INIT_SIZE;
/*每次扩展1.5倍,直到压入的空间足够*/
while (c->top + size >= c->size)
c->size += c->size >> 1; /* c->size * 1.5 */
/*注意到这里使用了 realloc() 来重新分配内存
c->stack 在初始化时为 NULL,realloc(NULL, size) 的行为是等价于 malloc(size) */
c->stack = (char*)realloc(c->stack, c->size);
}
//让地址移动到下一个可存放数据的位置上
ret = c->stack + c->top;
//top指向下一个空白位置
c->top += size;
return ret;
}
- 先判断是否超过了设置的大小,即
c->size
(由于一开始设定的是0
,因此第一次存入的时候必然会进到这个if
里面) - 初始设定一下 256 的大小,不够再加
- 这里用了一个没见过的表达
c->size += c->size >> 1
,本质上表示让栈以1.5倍大小扩展,为什么是1.5倍,作者给出了链接:
STL 的 vector 有哪些封装上的技巧? - 注意到这里使用了
realloc()
来重新分配内存,c->stack
在初始化时为NULL
,realloc(NULL, size)
的行为是等价于 malloc(size) 的,所以我们不需要为第一次分配内存作特别处理。
至此,第一个难题解决。
4. 解析字符串
有了以上的工具,解析字符串的任务就变得很简单。我们只需要先备份栈顶,然后把解析到的字符压栈,最后计算出长度并一次性把所有字符弹出,再设置至值里便可以。以下是部分实现,没有处理转义和一些不合法字符的校验。此处给出作业2完成前的字符串解析。
#define PUTC(c, ch) do { *(char*)lept_context_push(c, sizeof(char)) = (ch); } while(0)
static int lept_parse_string(lept_context* c, lept_value* v) {
size_t head = c->top, len;
const char* p;
EXPECT(c, '\"');
p = c->json;
for (;;) {
char ch = *p++;
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 '\0':
c->top = head;
return LEPT_PARSE_MISS_QUOTATION_MARK;
default:
PUTC(c, ch);
}
}
}
static void* lept_context_pop(lept_context* c, size_t size) {
assert(c->top >= size);
return c->stack + (c->top -= size);
}
- 首先跳过一个
\"
,因为看后面的test.c
可知,譬如TEST_STRING("Hello", "\"Hello\"");
测试的字符串是\"Hello\"
,只有这个时候会识别为字符串 - 此时若为正常的字符,应该会调用
PUTC()
这个宏。通过(char*)lept_context_push(c, sizeof(char))
,会返回一个地址然后让这个地址的值等于ch,也就是正在处理的那一个字符而通过(char*)lept_context_push(c, sizeof(char))
,会把c->stack
这个动态栈移动到下一个空白的位置用来存储字符 - 若下一个字符为
\0
,实际上是出错了,因为最后一个字符必然为\"
,这才是一个完整的字符串,因此出现\0
表示未出现正确的引号扩住该字符串,因此返回一个LEPT_PARSE_MISS_QUOTATION_MARK
- 若下一个字符为
\"
,可以理解为是字符串的结束,此时记录长度,并且调用lept_set_string()
将缓冲区的字符串存入v
中。
下面给出作业2、3完成后可以识别转义字符与非法字符的版本(此处算第二个难点,虽然实现其实很简单):
//解析字符串
static int lept_parse_string(lept_context* c, lept_value* v) {
size_t head = c->top, len;
const char* p;
EXPECT(c, '\"'); /*老样子,先跳过一个 " */
p = c->json; /*存储了除了第一个"以外的,如 hello\" */
for (;;) {
char ch = *p++;
switch (ch) {
/* 处理转义字符 遇到 \\ 之后开始做分类讨论 若\\之后不是这些字母或者符号 则出现了语法错误 */
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:
//让top指回0,相当于释放了stack之前存的了
c->top = head;
return LEPT_PARSE_INVALID_STRING_ESCAPE;
}
break;
case '\"':
//此时head仍为0,而c->top则指向下一个空白,而case到\"说明字符串结束了,因此此时相减就是该字符串的长度
len = c->top - head;
lept_set_string(v, (const char*)lept_context_pop(c, len), len);
c->json = p;
return LEPT_PARSE_OK;
/* 若最后一个字符是\0说明缺少了一个引号,因此返回缺少引号的错误 */
case '\0':
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);
}
}
}
- 结合单元测试中给出的测试字符串,可知当遇到
\\
的时候, 此时是转义字符的标志,即\
, 后面必然是8种结果,若不为,则是错误的非法转义字符,返回一个LEPT_PARSE_INVALID_STRING_ESCAPE
, 注意此时要把top
归 0 - 识别非法字符要参考之前的JSON字符串解析
string = quotation-mark *char quotation-mark
char = unescaped /
escape (
%x22 / ; " quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX
escape = %x5C ; \
quotation-mark = %x22 ; "
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
可以看出, unescaped
的码点范围从x20开始,中间要跳过 %x22 (即"),%x5C (即),而前面的 case:
已经讨论了这两种情形了,也就是说非法字符只存在在%x01 ~ %x19 之中了。
因此就很好理解了,只要让 (unsigned char)ch < 0x20
的时候返回 LEPT_PARSE_INVALID_STRING_CHAR
即可。
其他get() 和 set() 相关函数较简单,看看实现便可理解
三. test.c
#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;
#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)
#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")
#define EXPECT_EQ_STRING(expect, actual, alength) \
EXPECT_EQ_BASE(sizeof(expect) - 1 == (alength) && memcmp(expect, actual, alength) == 0, expect, actual, "%s")
#define EXPECT_TRUE(actual) EXPECT_EQ_BASE((actual) != 0, "true", "false", "%s")
#define EXPECT_FALSE(actual) EXPECT_EQ_BASE((actual) == 0, "false", "true", "%s")
static void test_parse_null() {
lept_value v;
lept_init(&v);
lept_set_boolean(&v, 0);
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
lept_free(&v);
}
static void test_parse_true() {
lept_value v;
lept_init(&v);
lept_set_boolean(&v, 0); //一开始假设为false
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "true"));
EXPECT_EQ_INT(LEPT_TRUE, lept_get_type(&v));
lept_free(&v);
}
static void test_parse_false() {
lept_value v;
lept_init(&v);
lept_set_boolean(&v, 1); //一开始假设为true
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "false"));
EXPECT_EQ_INT(LEPT_FALSE, lept_get_type(&v));
lept_free(&v);
}
#define TEST_NUMBER(expect, json)\
do {\
lept_value v;\
lept_init(&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));\
lept_free(&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_STRING(expect, json)\
do {\
lept_value v;\
lept_init(&v);\
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));\
EXPECT_EQ_INT(LEPT_STRING, lept_get_type(&v));\
EXPECT_EQ_STRING(expect, lept_get_string(&v), lept_get_string_length(&v));\
lept_free(&v);\
} while(0)
static void test_parse_string() {
TEST_STRING("", "\"\""); /*空字符 合法*/
TEST_STRING("Hello", "\"Hello\""); /*hello字符 合法*/
#if 1
TEST_STRING("Hello\nWorld", "\"Hello\\nWorld\"");
TEST_STRING("\" \\ / \b \f \n \r \t", "\"\\\" \\\\ \\/ \\b \\f \\n \\r \\t\"");
#endif
}
#define TEST_ERROR(error, json)\
do {\
lept_value v;\
lept_init(&v);\
v.type = LEPT_FALSE;\
EXPECT_EQ_INT(error, lept_parse(&v, json));\
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));\
lept_free(&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, "?");
/* 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");
}
static void test_parse_root_not_singular() {
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "null x");
/* invalid number */
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123"); /* after zero should be '.' or nothing */
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
}
static void test_parse_number_too_big() {
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
}
static void test_parse_missing_quotation_mark() {
TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"");
TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"abc");
}
static void test_parse_invalid_string_escape() {
#if 1
TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\v\"");
TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\'\"");
TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\0\"");
TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"\\x12\"");
#endif
}
static void test_parse_invalid_string_char() {
#if 1
TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x01\""); /* \x+数字表示十六进制 */
TEST_ERROR(LEPT_PARSE_INVALID_STRING_CHAR, "\"\x1F\"");
#endif
}
static void test_access_null() {
lept_value v;
lept_init(&v);
lept_set_string(&v, "a", 1);
lept_set_null(&v);
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
lept_free(&v);
}
static void test_access_boolean() {
/* \TODO */
/* Use EXPECT_TRUE() and EXPECT_FALSE() */
lept_value v;
lept_init(&v);
lept_set_string(&v, "a", 1);
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_string(&v, "a", 1);
lept_set_number(&v, 1234.5);
EXPECT_EQ_DOUBLE(1234.5, lept_get_number(&v));
lept_free(&v);
}
static void test_access_string() {
lept_value v;
lept_init(&v);
lept_set_string(&v, "", 0);
EXPECT_EQ_STRING("", lept_get_string(&v), lept_get_string_length(&v));
lept_set_string(&v, "Hello", 5);
EXPECT_EQ_STRING("Hello", lept_get_string(&v), lept_get_string_length(&v));
lept_free(&v);
}
//执行所有测试
static void test_parse() {
test_parse_null();
test_parse_true();
test_parse_false();
test_parse_number();
test_parse_string();
test_parse_expect_value();
test_parse_invalid_value();
test_parse_root_not_singular();
test_parse_number_too_big();
test_parse_missing_quotation_mark();
test_parse_invalid_string_escape();
test_parse_invalid_string_char();
test_access_null();
test_access_boolean();
test_access_number();
test_access_string();
}
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.c的内容较容易理解
- 新增了
EXPECT_EQ_STRING()
的宏定义,验证了长度和字符串内容都是一致的。 - 注意测试之后用
lept_free()
释放空间
总结
- 本次实现了字符串的数据类型解析,但是还未完全解决问题,下一章会紧接着处理Unicode
- 本章的难点主要是两个,一个是理解动态栈的实现,理解的时候调试一行行代码过,看监视才理解。一个是作业2中对于转义字符的处理,要理解
\\
代表的是\
才能识别到是转义字符。 这也是为什么测试的字符串中是类似\\r \\b
这种类型。