JSON库教程 tutorial03 学习笔记


前言

本文紧接着上一章,探讨如何进行字符串的解析。
第三章难度比数字的解析要略难一点,主要是对字符串以及转义字符的理解。并且本章加入了一些设置与获取函数,用于补全之前的遗漏。先看看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 在初始化时为 NULLrealloc(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 这种类型。
  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值