准备
在语法分析完结篇中我给出了一个压缩包, 里面包含了词法和语法分析的各个方面. 其中有一个叫做 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 数组中每一个元素的 base, offsets 数组都存放着该变量的基地址和每一维偏移.
测试包括这么几个方面
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
本文介绍了一种针对编译器指令生成模块的测试方法,包括整型节点、实型节点、变量节点、单目运算及双目运算的指令生成测试,并提供了具体的测试案例。
814

被折叠的 条评论
为什么被折叠?



