lept_json库的学习3
上一篇我们讲完了Parse()里的Null/Bool两种类型的解析函数。
这一篇我们就讲下Number类型的解析函数
一、Number语法
要想写好解析器,我们脑子里必须要有一个Number的语法框架。
整数 123
负数 -123
浮点数 123.3
小数 0.123
科学计数法 1E10、1E-10
这里不得不用这张路径图来直观的表示一下了,因为这张图真的很直观。
跟着这张路径图,我们可以大致的写出number的解析函数:
//直接很粗暴的定义了一个宏来判断ch是否是数字-----------------------number
#define ISDIGIT(ch) ((ch) >= '0' && (ch) <= '9')
//然后又很粗暴的定义了一个宏来判断ch是否为不为0的数字--------------number
#define ISDIGIT1TO9(ch) ((ch) >= '1' && (ch) <= '9')
/*****************数字*************************/
static int lept_parse_number(lept_context& c, lept_value& v) {
const char* p = c.json;
if (*p == '-')p++;//如果碰到负号,读取下一个
if (*p == '0')p++;//如果第一个数字是0,那它后面必须跟浮点
else {
if (!ISDIGIT1TO9(*p))return LEPT_PARSE_INVALID_VALUE;//如果第一个字符不是数字,则为invalid value
for (p++; ISDIGIT(*p); 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++);
}
v.u.m_num = strtod(c.json, NULL);//将json读取到的字符串
//转化为double 并赋值给v.n
v.type = LEPT_NUMBER;
c.json = p;//更新字符串读取位置
return LEPT_PARSE_OK;
}
当然,这里我们要在value数据结构中加入number类型,代码如下:
struct lept_value{
double m_num; /* number */
lept_type type;
};
目前如此,之后会加入string啊,array啊,object啊,那都是后话。
那么我们就可以把number的parse函数也写到parse_value里了
我们还记得因为number的首字符不固定,所以将number放到了default里
/*....*/
switch(首字符){/*....*/
default: return lept_parse_number(c, v);
/*....*/}
/*....*/
二、边界值测定
但是number可不是这么简单就写完了,因为数字是有上限的,我们计算机不可能存储一个无限大的数字,所以要加入上溢下溢的情况判断。
#include <errno.h> /* errno, ERANGE */
#include <math.h> /* HUGE_VAL */
parse_number()
{/*...*/
errno = 0;
v.u.m_num = strtod(c.json, NULL);//如果这里出现错误,
//会改变errno的值
if (errno == ERANGE && (v.u.m_num == HUGE_VAL || v.u.m_num == -HUGE_VAL))
return LEPT_PARSE_NUMBER_TOO_BIG;//parse状态里的number too big
/*...*/
}
三、单元测试
单元测试我们需要测试数字的合理性,还有边界值,在边界内还是在边界外区分着正确测试和错误测试。
可以先写一个number测试的宏:
#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)
其中用到的DOUBLE宏函数和INT宏函数相比,只是最后面的参数换了一下,%d是用来输出整数的,而 %.17g 则用来输出高精度浮点数
代码如下:
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%.17g")
当然,在我们的test number宏函数中,我们调用了接口函数:
lept_get_type()
lept_get_number()
这两个函数的作用就如字面意思所描述的,所以直接贴代码:
lept_type lept_get_type(const lept_value& v)
{
assert(&v!=NULL);
return v.type;
}
double lept_get_number(const lept_value& v)
{
assert(&v != NULL);
assert(v.type == LEPT_NUMBER);
return v.u.m_num;
}
这里面将assert()里的内容写作两条而不是用&&连接只是个人习惯。为了代码的简练完全可以写作一条:
assert(&v != NULL&&v.type == LEPT_NUMBER);
正确测试:
static void test_parse_number() {
TEST_NUMBER(0.0, "0");//单一个零
TEST_NUMBER(0.0, "-0");//负零也是0,很奇怪吧,但是电脑认识,所以我们要测试
TEST_NUMBER(0.0, "-0.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 */
//因无限趋近于0而直接取0
}
错误测试
static void test_parse_invalid_value() {
/*...*/
/* invalid number */
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");//电脑认识-0却不认识+0,所以+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_number_too_big() {
#if 1
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");//overflow上溢
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");//underflow下溢
#endif
}
废话
这章看起来就是一直在贴代码,没讲什么内容,雀食。
这个过程跟着走一遍,心中有个印象就可以了,知道了就知道了,不知道就弄明白为止。数字这一节,没有引入内存管理,可能在解析上稍微复杂了些,(其实也就是循环里面if套if,if加if)但是一遍下来看懂就可以了,对于Number的语法,了解一下规定的标准语法是什么样的,就可以了。
lept_json Github:https://github.com/miloyip/json-tutorial
本人流星画魂第三次在csdn上做笔记,有什么错误或者是需要改进的地方请即时提出
我只是一个对编程感兴趣的人,但懒得要死,学得又不认真,希望读者能骂就骂两句,真的太懒了