开源项目cJSON具体实现2(数字的解析)

2. 实现JSON 数字的解析。

2.1 JSON number 的语法规则与解释。

JSON number 的语法规则是这样的:

/*
number 以十进制表示,它主要由 4 部分组成:负号、整数、小数、指数。只有整数是必需部分。注意:+号是不合法的。

int 整数部分如果是 0 开始,只能是单个 0;而由 1-9 开始的话,可以加任意数量的数字(0-9)。也就是说,0123 不是一个合法的 JSON 数字。
frac 小数部分比较直观,就是小数点后是一或多个数字(0-9)。

JSON 可使用科学记数法,exp 指数部分由大写 E 或小写 e 开始,然后可有正负号,之后是一或多个数字(0-9)。
*/
JSON-text = ws value ws
ws = *(%x20 / %x09 / %x0A / %x0D)
value = number
number = [ "-" ] int [ frac ] [ exp ]
int = "0" / digit1-9 *digit
frac = "." 1*digit
exp = ("e" / "E") ["-" / "+"] 1*digit

JSON 标准 ECMA-404 采用图的形式表示语法,也可以更直观地看到解析时可能经过的路径:

在这里插入图片描述

2.2 设计头文件

JSON 是一个树形结构,我们设定每个节点使用结构体 lept_value表示,对于null、false、true 在解析后,我们只需要存储它的数据类型为LEPT_NULL、LEPT_FALSE、LEPT_TRUE;但对于数字,不仅要存储结点的类型,还要存储数据本身。

从 JSON 数字的语法,我们可能直观地会认为它应该表示为一个浮点数,因为它带有小数和指数部分。然而,标准中并没有限制数字的范围或精度。为简单起见,选择以双精度浮点数来存储 JSON 数字。

所以,此时 lept_value 组成就为:

typedef struct {
    double n;  //用来存储数值
    lept_type type;
}lept_value;

API设计

/*
函数目的:获取JSON结点里的数值
参数:1. lept_value* v :传入存储解析后JSON树形结构的根节点指针
返回值:double   
*/
double lept_get_number(const lept_value* v) ;

API 实现

/*
函数目的:获取JSON结点里的数值
参数:1. lept_value* v :传入存储解析后JSON树形结构的根节点指针
返回值:double   

实现思路:仅当 type == LEPT_NUMBER时,才返回数值
*/
double lept_get_number(const lept_value* v) {
    assert(v != NULL && v->type == LEPT_NUMBER);
    return v->n;
}
2.3 TDD设计理念

先写测试函数,写测试函数时考虑的情况越多,最后在设计解析器的时候就越完善。

#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual),expect,actual, "%.17g")  

#define TEST_NUMBER(expect, json)\
    do {\
        lept_value 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));\
    } 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"); /* 必须下溢 */

//边界值测试
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");
}

除了这些上面这些合法的测试用例,根据JSON数字解析规则我们来设计一些不合语法的用例:

#define TEST_ERROR(error, json, lept_type)\
        do{\
               lept_value v; \
               v.type = LEPT_FALSE; \
               EXPECT_EQ_INT(error, lept_parse(&v, json)); \
               EXPECT_EQ_INT(lept_type, lept_get_type(&v)); \
        } while (0);


static void test_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");
     TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "e1");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "E1");
}

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");
}

拓展,double类型的数据如何设计边界值测试案例?
拓展链接:http://m.elecfans.com/article/614555.html
浮点数占64bit(8字节),其中符号位占1位,小数点前占11位,小数点后占52位(6.5个字节,可以存13个16进制数)。
11位可以取值1~ 2046,但是因为有符号的存在,实际存储范围-1022~1023
浮点数的整数部分全为0或全为1有特殊含义。
整数部分全为1,小数部分全为0表示无穷大,即inf。根据符号位的不同可以分为+inf / -inf
整数部分全为1,小数部分不全为0表示Nan。即not a number。
小数部分最高位为1的nan称为Qnan;最高位为0的nan称为Snan。通常用QNan表示不确定的操作,Snan表示无效的操作。
浮点数0000 0000 0000 0001(十六进制表示法)的小数部分(后13位)是0 0000 0000 0001,即1/252, 对应的数是1/(252) *2(-1022),即4.9406564584124654e-324

2.4 实现JSON_Number解析器

根据 API 与 单元测试 ,我们来实现解析器,首先是lept_parse函数

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);//对于number的情况,可以这样处理。
        case '\0': return LEPT_PARSE_EXPECT_VALUE;
    }
}

/*
注:这个函数因为逻辑没有变,所以本身不用改变,需要改变的是它所调用的 lept_parse_value 函数。
*/
int lept_parse(lept_value* v, const char* json) {
    /*  */
}

根据JSON的数字规则写 lept_parse_number()

#include <errno.h>   /* errno, ERANGE */
#include <math.h>    /* HUGE_VAL */
#include <stdlib.h>  /* NULL, strtod() */

#define ISDIGIT(ch)         ((ch) >= '0' && (ch) <= '9')
#define ISDIGIT1TO9(ch)     ((ch) >= '1' && (ch) <= '9')

/*
函数目的: 对number进行解析
思路:
1. 判断是不是负号,是:跳过
2. 整数部分:判断是否为单个0的情况,是便跳过;如果不是第一种情况,则整数部分第一个字符必须为1~9,否则返回错误码;然后有多少个digit就跳过多少。
3. 小数部分:如果出现小数点,我们跳过该小数点,然后检查它至少应有一个 digit,不是 digit 就返回错误码。跳过首个 digit,我们再检查有没有 digit,有多少个跳过多少个。
4. 指数部分:如果出现大小写 e,就表示有指数部分。跳过那个 e 之后,可以有一个正或负号,有的话就跳过。然后和小数的逻辑是一样的
*/
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;
        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->n = strtod(c->json, NULL);
    
    if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL))
        return LEPT_PARSE_NUMBER_TOO_BIG;
        
    v->type = LEPT_NUMBER;
    c->json = p;
    return LEPT_PARSE_OK;
}

上面的代码,要再说两点:

  1. strtod函数,将字符串转换成浮点数,这个函数的定义是 double strtod(const char str, char
    **endprt) 其中 str为要转化为双精度浮点数的字符串; endptr对char
    的对象的引用,其值由函数设置为str中数值后的下一个字符。
  2. 在math.h中, 当函数的结果不可以表示为浮点数时。如果是因为结果的幅度太大以致于无法表示,则函数会设置 errno 为 ERANGE 来表示范围错误,并返回一个由宏 HUGE_VAL 或者它的否定(- HUGE_VAL)命名的一个特定的很大的值; 如果结果的幅度太小,则会返回零值。在这种情况下,error 可能会被设置为 ERANGE,也有可能不会被设置为 ERANGE。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值