第二节主要解析数字,提供了两个版本,第一个版本有缺陷。 首先看看json的数字语法
number = [ "-" ] int [ frac ] [ exp ]
int = "0" / digit1-9 *digit
frac = "." 1*digit
exp = ("e" / "E") ["-" / "+"] 1*digit
负号可有可无,整数必有,小数和指数可有可无
整数要么是0,要么1-9开头
小数有小数点即可,后面跟任意数
指数部分e/E混用,然后跟正负号,然后跟任意数
所以以下数字都是错误的,不符合json数字格式
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0"); // json数字中无+号
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123"); // 缺少整数部分
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1."); // 小数点后至少有一个数
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF"); // json不认inf,nan
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_ROOT_NOT_SINGULAR, "0123");
// 0是完整的number了,后面不应该还有字符,所以是LEPT_PARSE_ROOT_NOT_SINGULAR错误
// json数字不认0x(16进制)
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
leptjson.h
json对象里加入了double类型的数值
typedef struct {
double n;
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 // 解析失败,数值过大
};
leptjson.cpp
重构解析null、true、false;
literal用来判断是不是null|true|false,type用来解析完给v复制
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]) {
// 因为v.type初始化为LEPT_NULL,所以解析为无效值后,这里不用重新给type赋值
return LEPT_PARSE_INVALID_VALUE;
}
}
c->json += i;
v->type = type;
return LEPT_PARSE_OK;
}
C 库函数 double strtod(const char *str, char **endptr) 把参数 str 所指向的字符串转换为一个浮点数(类型为 double 型)。如果 endptr 不为空,则指向转换中最后一个字符后的字符的指针会存储在 endptr 引用的位置。
// 版本1
static int lept_parse_number(lept_context* c, lept_value* v) {
char* end;
v->n = strtod(c->json, &end);
if (c->json == end) // 即无数字部分
return LEPT_PARSE_INVALID_VALUE;
c->json = end; // 有数字部分,但可能数字后还有其他字符
v->type = LEPT_NUMBER;
return LEPT_PARSE_OK;
}
版本1下,部分解析结果与期望不同
// 这些在json中不是数字,但方案1会把它们解析为数字
// expect: LEPT_PARSE_INVALID_VALUE actual : LEPT_PARSE_OK
// expect : LEPT_NULL actual : LEPT_NUMBER
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123");
TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");
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"); // 这个方案一也认为这是无效值,可能是因为n开头,进了null解析函数
// 这些在json中也不是数字,但方案1会把它们解析为数字
// expect: LEPT_PARSE_ROOT_NOT_SINGULAR : LEPT_PARSE_OK
// expect: LEPT_NULL actual : LEPT_NUMBER
// 0是完整的number了,后面不应该还有字符,所以是LEPT_PARSE_ROOT_NOT_SINGULAR错误
// json数字不认0x(16进制)
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0123");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x0");
TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "0x123");
// 这些在json中过大,但方案1无法识别出它已超出界限,当作正常数字
// expect: LEPT_PARSE_NUMBER_TOO_BIG : LEPT_PARSE_OK
// expect: LEPT_NULL actual : LEPT_NUMBER
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "1e309");
TEST_ERROR(LEPT_PARSE_NUMBER_TOO_BIG, "-1e309");
// 版本2
static int lept_parse_number(lept_context* c, lept_value* v) {
const char* p = c->json;
if (*p == '-')p++; // 先检查有没有负号(不可以有+号开头)
if (*p == '0') { // 再检查整数部分,是不是0或者1-9开头的整数
p++;
}
else {
if (!ISDIGIT1TO9(*p)) {
return LEPT_PARSE_INVALID_VALUE; // 开头既不是0也不是1-9,则是无效值
}
for (p++; ISDIGIT(*p); p++); //开头是123..9,然后忽略中间的数字,如0-9,直接跳过整数部分
}
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; // e后面要跟任意一个整数
for (p++; ISDIGIT(*p); p++); // 跳过整数
}
errno = 0;
v->n = strtod(c->json, NULL);
// strtod很强大,合法的不合法的都可以识别出数字,不合法的json字符串已经剔除,仍可以使用strtod识别数字
// errno,ERANGE是固定套路
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;
}
if (errno == ERANGE && (v->n == HUGE_VAL || v->n == -HUGE_VAL))
此条件可以;表示strtod超出范围,而且数字太大
if ((v->n == HUGE_VAL || v->n == -HUGE_VAL))
此条件可以;表示数字太大
if (errno == ERANGE)
此条件不行;仅仅约束strtod超出范围;但TEST_NUMBER(0.0, "1e-10000")虽超出范围,但仍是浮点数。