[开发过程]运算节点的测试

准备

在语法分析完结篇中我给出了一个压缩包, 里面包含了词法和语法分析的各个方面. 其中有一个叫做 jerry-ui.h 的文件中定义了一些 ui 以及 io 相关函数. 作为指令生成测试这样局部性质的测试, 也需要实现这些接口以免发生编译错误, 但没有必要使用 jerry-ui.c 中实现, 因此先在名为 test-ui.c 的文件中给出伪实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "jerry-ui.h"

FILE* treeout;
struct Stack analyserStack;
struct Token* firstToken(void) { return NULL; }
struct Token* nextToken(void) { return NULL; }
void eofToken(void) {}
int isFailed(void) { return 0; }
unsigned int getLineNumber(void) { return 0; }
void lexicalError(struct Token* token) {}
void syntaxError(ErrMsg e, struct Token* t) {}
void semanticErr(ErrMsg err, int line) {}
void symError(ErrMsg err, struct VariableNode* var) {}
int nextChar(void) { return 0; }

而在测试中, 还需要给出这些实现

struct Stack loopStack;

struct AbstractInstruction* loopOutlet(void)
{
    return (struct AbstractInstruction*)(loopStack.peek(&loopStack));
}

void enterLoop(void* outlet)
{
    loopStack.push(&loopStack, outlet);
}

void leaveLoop(void)
{
    loopStack.pop(&loopStack);
}

名称包含 loop 的是循环栈及对应的数据访问接口, 虽然这一次并不测试循环语句, 但是先写上备用; analyserStack 是一个没有用到的栈, 定义在这里. 将这些它们放入新文件 test-common.c 中, 并且还得弄一个 test-common.h 存放对应的声明. 此外, 好要写一些小工具

 

// 判定两个指令是相同的, 并且结束后释放它们
void assertInsEqual_Del(void* i0, void* i1)
{
    assert(((struct AbstractInstruction*)i0)->code ==
           ((struct AbstractInstruction*)i1)->code);
    if (isIntParamIns(((struct AbstractInstruction*)i0)->code)) {
        assert(((struct IntParamInstruction*)i0)->param ==
               ((struct IntParamInstruction*)i1)->param);
    } else if (isRealParamIns(((struct AbstractInstruction*)i0)->code)) {
        assert(doubleEqual(((struct IntParamInstruction*)i0)->param,
                           ((struct IntParamInstruction*)i1)->param));
    }
    revert(i0);
    revert(i1);
}

// 双精度浮点判同
int doubleEqual(double a, double b)
{
    return 1e-9 > fabs(a - b);
} 

也放在这一块.

接下来是数据预备. 这次进行测试的是

 

IntegerNode

RealNode

UnaryOpNode

BinaryOpNode

VariableNode

这些节点类型的指令生成, 下面是我准备的数据, 你也可以准备你自己的数据进行测试

#define NR_TEST_CASE (4)
#define MAX_ARRAY_SIZE (5)

// 数组维度
int DIM[NR_TEST_CASE][MAX_ARRAY_SIZE] = {
    { 2, 3, 4, 5, 0 },
    { 2, 4, 0 },
    { 0 },
    { 2, 3, 4, 0 }
};

// 变量, 包括从标识符到符号表信息等一系列域
struct {
    char* ident;
    AcceptType type;
    int nrDim;
    int offsets[MAX_ARRAY_SIZE];
    int size;
    int base;
} data[NR_TEST_CASE] = {
    { "testee", INTEGER },
    { "tom", INTEGER },
    { "jerry", REAL },
    { "mANDg", REAL }
};

// 纯标识符
char* ident = "param";
char* ident1 = "param1";

因为测试不依赖任何框架 (啊啊, 我是个无聊喜欢做轮子的人~), 因此有些测前准备和测后清场工作也得自己来弄

 

void prepare(void)
{
    initialSymTabManager(); // 初始化符号表
    int i, j;
    struct VariableNode* var;
    for (i = 0; i < NR_TEST_CASE; ++i) {
        var = newVariableNode(data[i].ident);
        for (j = 0; 0 != DIM[i][j]; ++j) {
            var->dimInfor->enqueue(var->dimInfor, newIntegerNode(DIM[i][j])); // 添加数组信息
        }
        declare(var, data[i].type); // 在符号表中注册所有的变量
        var->delNode(var);
    }
}

void clear(void)
{
    finalizeSymTabManager(); // 清理符号表
}

其实还有一项初始化工作, 那就是根据数组维度大小计算每一维的偏移, 当然这计算并不是被测试的, 而是因为这些需要在变量节点测试中用到, 这个放在 main 中进行好了, 因为只需要进行一次.

 

 

IntegerNode 及 RealNode

 

 

先来两个简单的来阐述我的测试思路

void testIntegerNode(void)
{
    struct IntegerNode* i; // 节点
    struct List* ins; // 节点产生的指令列表
    prepare(); // 准备

    i = newIntegerNode(14); // 构造一个节点
    ins = i->createInstruction(i);
    assert(1 == ins->count(ins)); // 测试1: 验证指令数量
    assert(CONST_INT == ((struct IntParamInstruction*)
                         (ins->elementAt(ins, 0)))->code); // 测试2: 验证指令码
    assert(14 == ((struct IntParamInstruction*)
                  (ins->elementAt(ins, 0)))->param); // 测试3: 验证指令参数
    revert(ins->elementAt(ins, 0)); // 销毁指令
    i->delNode(i); // 析构节点
    ins->finalize(ins); // 析构列表
    clear(); // 测试结束, 清理现场
    showAlloc; // 看看有没有内存泄漏
    puts("    Integer node test done.\n"); // 啦啦啦, 结束
}

为了讲述方便, 这里只给一个案例. 因为 IntegerNode (以及 RealNode) 是不依赖任何其它节点的, 所以测它们的指令只需要验证这些即可. 下面是 RealNode 的, 几乎一样.

void testRealNode(void)
{
    struct RealNode* r;
    struct List* ins;
    prepare();

    r = newRealNode(3.14159);
    ins = r->createInstruction(r);
    assert(1 == ins->count(ins));
    assert(CONST_REAL == ((struct RealParamInstruction*)
                          (ins->elementAt(ins, 0)))->code);
    assert(doubleEqual(3.14159, ((struct RealParamInstruction*)
                                 (ins->elementAt(ins, 0)))->param));
    revert(ins->elementAt(ins, 0));
    r->delNode(r);
    ins->finalize(ins);
    clear();
    showAlloc;
    puts("    Real node test done.\n");
}

变量

先假定我们需要的那一项初始化 (就是之前提到要放在 main 中的那个) 已经完成, 那么 data 数组中每一个元素的 baseoffsets 数组都存放着该变量的基地址和每一维偏移.

测试包括这么几个方面

void testVarNode(void)
{
    struct VariableNode* var;
    struct List* ins;
    int i, j, paramAddr;
    struct VariableNode* param;
    prepare();

    // 1: addressOf
    for (i = 0; i < NR_TEST_CASE; ++i) {
        var = newVariableNode(data[i].ident);
        for (j = 0; 0 != DIM[i][j]; ++j) {
            var->dimInfor->enqueue(var->dimInfor, newIntegerNode(0)); // 每一维都是静态给定的, 而且是 0
        }
        ins = var->addressOf(var);
        // 那么加载变量地址只需一个指令
        assert(1 == ins->count(ins));
        // 指令就是加载其数组基地址
        assert(CONST_INT == ((struct IntParamInstruction*)
                                         (ins->elementAt(ins, 0)))->code);
        assert(data[i].base == ((struct IntParamInstruction*)
                                (ins->elementAt(ins, 0)))->param);
        revert(ins->elementAt(ins, 0));
        ins->finalize(ins);
        var->delNode(var);
    }

    // 2: addressOf 每一维是变量
    param = newVariableNode(ident);
    paramAddr = declare(param, INTEGER); // 注册变量
    assert(0 != paramAddr);
    var = newVariableNode(data[0].ident);
    for (j = 0; 0 != DIM[0][j]; ++j) {
        var->dimInfor->enqueue(var->dimInfor, newVariableNode(ident)); // 灌入数组
    }
    ins = var->addressOf(var);
    assert(1 + j * 5 == ins->count(ins)); // 指令数量增加很多个~
    // 第一条指令仍旧是加载基地址
    assert(CONST_INT == ((struct IntParamInstruction*)
                                         (ins->elementAt(ins, 0)))->code);
    assert(data[0].base == ((struct IntParamInstruction*)
                                         (ins->elementAt(ins, 0)))->param);

    for (i = 0; i < j; ++i) {
        // 加载偏移量的指令
        assert(CONST_INT == ((struct IntParamInstruction*)
                                       (ins->elementAt(ins, 1 + i * 5)))->code);
        assert(data[0].offsets[i] == ((struct IntParamInstruction*)
                                      (ins->elementAt(ins, 1 + i * 5)))->param);
        // 加载变量 (ident) 的地址
        assert(CONST_INT == ((struct IntParamInstruction*)
                                       (ins->elementAt(ins, 2 + i * 5)))->code);
        assert(paramAddr == ((struct IntParamInstruction*)
                                      (ins->elementAt(ins, 2 + i * 5)))->param);
        // 加载变量 (ident) 的值
        assert(LOAD_INT == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 3 + i * 5)))->code);
        // 计算 (ident) 与偏移量之积
        assert(INT_MULTIPLY == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 4 + i * 5)))->code);
        // 累加地址
        assert(INT_PLUS == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 5 + i * 5)))->code);
    }

    while (0 != ins->count(ins)) {
        revert(ins->popElementAt(ins, 0)); // 销毁指令啦~
    }
    ins->finalize(ins);
    var->delNode(var);
    param->delNode(param);
    clear();

    prepare(); // 重置符号表 (主要目的是干掉 ident), 准备 3: 每一维是实型变量
    param = newVariableNode(ident);
    paramAddr = declare(param, REAL);
    var = newVariableNode(data[1].ident);
    for (j = 0; 0 != DIM[1][j]; ++j) {
        var->dimInfor->enqueue(var->dimInfor, newVariableNode(ident));
    }
    ins = var->addressOf(var);
    assert(1 + j * 6 == ins->count(ins)); // 每一维多 1 指令
    assert(CONST_INT == ((struct IntParamInstruction*)
                                         (ins->elementAt(ins, 0)))->code);
    assert(data[1].base == ((struct IntParamInstruction*)
                                         (ins->elementAt(ins, 0)))->param);

    for (i = 0; i < j; ++i) {
        assert(CONST_INT == ((struct IntParamInstruction*)
                                       (ins->elementAt(ins, 1 + i * 6)))->code);
        assert(data[1].offsets[i] == ((struct IntParamInstruction*)
                                      (ins->elementAt(ins, 1 + i * 6)))->param);

        assert(CONST_INT == ((struct IntParamInstruction*)
                                       (ins->elementAt(ins, 2 + i * 6)))->code);
        assert(paramAddr == ((struct IntParamInstruction*)
                                      (ins->elementAt(ins, 2 + i * 6)))->param);

        assert(LOAD_REAL == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 3 + i * 6)))->code);
        // 多的这条指令就是类型转换指令
        assert(REAL_2_INT == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 4 + i * 6)))->code);

        assert(INT_MULTIPLY == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 5 + i * 6)))->code);

        assert(INT_PLUS == ((struct NoParamInstruction*)
                                       (ins->elementAt(ins, 6 + i * 6)))->code);
    }

    while (0 != ins->count(ins)) {
        revert(ins->popElementAt(ins, 0));
    }
    ins->finalize(ins);
    var->delNode(var);
    param->delNode(param);
    clear();
    
    showAlloc;
    puts("    Variable node test done.\n"); // 结束 :-)
}

单目运算

运算测试需要涵盖两个方面, 一是一般情况, 二是当这个节点实际上是一个编译时可以确定值的节点. 对于编译时可以确定值的节点的测试, 这里给出一个示例

    struct IntegerNode* i;
    struct UnaryOperationNode* u;
    struct List* ins;
    struct List* subIns;
    i = newIntegerNode(0x7fffffff); // 常量
    u = newUnaryOperationNode(MINUS, (struct AbstractValueNode*)i);
    ins = u->createInstruction(u); // 这是单目运算节点指令
    assert(1 == ins->count(ins)); // 常量, 所以个数是 1
    assert(CONST_INT == ((struct IntParamInstruction*)
                         (ins->elementAt(ins, 0)))->code);
    assert(-0x7fffffff == ((struct IntParamInstruction*)
                          (ins->elementAt(ins, 0)))->param); // 是负数哦
    revert(ins->elementAt(ins, 0));
    u->delNode(u);
    ins->finalize(ins); // 结束

此外还可以弄些很挫的, 比如级联式单目运算

    struct UnaryOperationNode* cascade;
    i = newIntegerNode(0x7fffffff);
    cascade = newUnaryOperationNode(NOT, (struct AbstractValueNode*)i);
    u = newUnaryOperationNode(NOT, (struct AbstractValueNode*)cascade); // 两个套在一起了
    ins = u->createInstruction(u);
    assert(1 == ins->count(ins));
    assert(CONST_INT == ((struct IntParamInstruction*)
                         (ins->elementAt(ins, 0)))->code);
    assert((!0) == ((struct IntParamInstruction*)
                          (ins->elementAt(ins, 0)))->param);
    revert(ins->elementAt(ins, 0));
    u->delNode(u);
    ins->finalize(ins);

对于非常量的运算节点, 验证其下一级节点产生的指令是否正确的工作可以丢给 void assertInsEqual_Del(void* a, void* b) 去完成, 而不必写在运算节点内部. 比如对于逻辑非运算, 已知其指令生成是这样的

> 产生子节点指令

> 在栈顶放入整数 0

> 比较栈顶和次栈顶

那么测试可以写成这样

    struct VariableNode* var;
    struct UnaryOperationNode* u;
    struct List* ins,* subIns;

    prepare();
    var = newVariableNode(ident);
    declare(var, INTEGER);
    u = newUnaryOperationNode(NOT,
                      (struct AbstractValueNode*)newVariableNode(ident));
    ins = u->createInstruction(u);
    subIns = var->createInstruction(var);
    // 指令数量验证: 运算比子节点应该多两条指令
    assert(subIns->count(subIns) + 2 == ins->count(ins));

    // 前面的指令应该都是一样的
    while (0 != subIns->count(subIns)) {
        assertInsEqual_Del(ins->popElementAt(ins, 0),
                           subIns->popElementAt(subIns, 0));
    }
    // 多出的第一条指令: 载入 0
    assert(CONST_INT ==
                ((struct IntParamInstruction*)(ins->elementAt(ins, 0)))->code);
    assert(0 == ((struct IntParamInstruction*)(ins->elementAt(ins, 0)))->param);
    revert(ins->popElementAt(ins, 0));
    // 第二条指令: 比较栈顶和次栈顶
    assert(INT_EQ ==
                ((struct NoParamInstruction*)(ins->elementAt(ins, 0)))->code);
    revert(ins->popElementAt(ins, 0));

    u->delNode(u);
    var->delNode(var);
    ins->finalize(ins);
    subIns->finalize(subIns);
    clear();

双目运算

双目运算与单目运算的测试思路大致是一样的. 在非常量的情况下, 验证节点时需要验证两个子节点的指令; 此外, 当两个子节点类型不同时, 要验证是否生成了类型转换指令. 一个典型的例子如下

    struct BinaryOperationNode* b;
    struct VariableNode* var0,* var1;
    struct List* ins,* subIns0,* subIns1;

    prepare();
    var0 = newVariableNode(ident);
    var1 = newVariableNode(ident1);
    declare(var0, INTEGER);
    declare(var1, REAL); // Oops 类型不一样

    b = newBinaryOperationNode(ASSIGN, /* 赋值运算, 类型转换一定是将右值类型转成左值类型 */
                            (struct AbstractValueNode*)newVariableNode(ident),
                            (struct AbstractValueNode*)newVariableNode(ident1));
    ins = b->createInstruction(b);
    subIns0 = var0->addressOf(var0);
    subIns1 = var1->createInstruction(var1);
    assert(ins->count(ins) - 2 == // 多出两条指令
           subIns0->count(subIns0) + subIns1->count(subIns1));
    while (0 != subIns0->count(subIns0)) {
        assertInsEqual_Del(ins->popElementAt(ins, 0),
                           subIns0->popElementAt(subIns0, 0));
    }
    while (0 != subIns1->count(subIns1)) {
        assertInsEqual_Del(ins->popElementAt(ins, 0),
                           subIns1->popElementAt(subIns1, 0));
    }
    // +1: 类型转换
    assert(REAL_2_INT ==
           ((struct AbstractInstruction*)(ins->elementAt(ins, 0)))->code);
    revert(ins->popElementAt(ins, 0));
    // +2: 整型赋值
    assert(INT_ASSIGN ==
           ((struct NoParamInstruction*)(ins->elementAt(ins, 0)))->code);
    revert(ins->popElementAt(ins, 0));

    var0->delNode(var0);
    var1->delNode(var1);
    b->delNode(b);
    subIns0->finalize(subIns0);
    subIns1->finalize(subIns1);
    ins->finalize(ins);
    clear();

而双目运算还有一个职责, 那就是条件短路 (这篇文章中最后的部分描述了条件短路双目运算的指令的模型). 这一点的测试给出一个逻辑与运算的测试例子

    struct BinaryOperationNode* b;
    struct VariableNode* var0,* var1;
    struct List* ins,* subIns0,* subIns1;

    prepare();
    var0 = newVariableNode(ident);
    var1 = newVariableNode(ident1);
    declare(var0, INTEGER);
    declare(var1, INTEGER);
    b = newBinaryOperationNode(AND,
                            (struct AbstractValueNode*)newVariableNode(ident),
                            (struct AbstractValueNode*)newVariableNode(ident1));
    ins = b->createInstruction(b);
    subIns0 = var0->createInstruction(var0);
    subIns1 = var0->createInstruction(var1);
    assert(ins->count(ins) - 4 == // 指令 +4
           subIns0->count(subIns0) + subIns1->count(subIns1));
    while (0 != subIns0->count(subIns0)) { // 条件 1
        assertInsEqual_Del(ins->popElementAt(ins, 0),
                           subIns0->popElementAt(subIns0, 0));
    }
    // 短路跳出
    assert(JMP_NOT_TOP ==
           ((struct AbstractInstruction*)(ins->elementAt(ins, 0)))->code);
    assert(ins->elementAt(ins, ins->count(ins) - 2) ==
           ((struct JumpInstruction*)(ins->elementAt(ins, 0)))->targetIns);
    revert(ins->popElementAt(ins, 0));

    while (0 != subIns1->count(subIns1)) { // 条件 2
        assertInsEqual_Del(ins->popElementAt(ins, 0),
                           subIns1->popElementAt(subIns1, 0));
    }
    // 跳到出口
    assert(JMP ==
           ((struct AbstractInstruction*)(ins->elementAt(ins, 0)))->code);
    assert(ins->elementAt(ins, ins->count(ins) - 1) ==
           ((struct JumpInstruction*)(ins->elementAt(ins, 0)))->targetIns);
    revert(ins->popElementAt(ins, 0));
    // 加载 0
    assert(CONST_INT ==
           ((struct AbstractInstruction*)(ins->elementAt(ins, 0)))->code);
    assert(0 ==
           ((struct IntParamInstruction*)(ins->elementAt(ins, 0)))->param);
    revert(ins->popElementAt(ins, 0));
    // 出口 伪指令
    assert(NOP ==
           ((struct AbstractInstruction*)(ins->elementAt(ins, 0)))->code);
    revert(ins->popElementAt(ins, 0));

    var0->delNode(var0);
    var1->delNode(var1);
    b->delNode(b);
    subIns0->finalize(subIns0);
    subIns1->finalize(subIns1);
    ins->finalize(ins);
    clear();

文件

目前可以在 svn 上弄到一个最近版本. 使用

 

svn checkout http://bitgarden.googlecode.com/svn/trunk/Jerry Jerry

svn checkout http://cobjorntlib.googlecode.com/svn/trunk/ Jerry/COOL

以获取全部代码.

在 Jerry 目录下有 Makefile 一枚, 重要的 make 项目包括

 

semantic:Jerry.out JerryVM.out # 生成编译器及虚拟机

syntax:Syntax.out              # 仅语法分析

lexical:Lexical.out            # 仅词法分析


alltest:test-symbol-table.out test-st-manager.out test-constant-fold-type-of.out test-instruction-arith.out # 产生所有测试

runtest:alltest       # 运行所有测试

test-symbol-table.out

test-st-manager.out

test-constant-fold-type-of.out

test-instruction-arith.out

 

这里也给出全部代码下载 jerry-compiler.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值