lept_json的学习之parse_number

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上做笔记,有什么错误或者是需要改进的地方请即时提出
我只是一个对编程感兴趣的人,但懒得要死,学得又不认真,希望读者能骂就骂两句,真的太懒了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值