- 浏览: 59166 次
- 性别:
- 来自: 武汉
最新评论
-
NeuronR:
之前自搭 blog, 太累. 最近在研究 C艹 语言的错误处理 ...
C++ 中处理除零错误 -
lwwin:
话说很久没有看到你更新BLOG了,最近在研究LINUX开发^^ ...
C++ 中处理除零错误 -
lwwin:
RednaxelaFX 写道我的见识还是太少了……最近在某在x ...
[优化]删去 NOP 指令 -
RednaxelaFX:
我的见识还是太少了……最近在某在x86上跑的东西的手写汇编里的 ...
[优化]删去 NOP 指令 -
NeuronR:
lwwin 写道没想到一个NOP把FX也拉来了^^关于代码…… ...
[优化]删去 NOP 指令
准备
在语法分析完结篇中我给出了一个压缩包, 里面包含了词法和语法分析的各个方面. 其中有一个叫做 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
- jerry-compiler.zip (66.5 KB)
- 下载次数: 1
相关推荐
IEEE30节点测试系统是电力系统分析中常用的一个标准模型,它被广泛用于验证和比较不同的潮流计算算法、稳定性分析以及动态模拟等研究。MATLAB作为一款强大的数值计算和编程环境,非常适合进行这类复杂的工程计算。这...
Multisim是一款由National Instruments公司开发的电路仿真软件,它允许用户在虚拟环境中构建、测试和分析电子电路。在Multisim中,我们可以方便地绘制电路图,添加各种元件,并通过虚拟仪器如万用表、示波器等测量...
体布尔运算是一种在3D几何体之间进行操作的技术,例如合并、相交、差集等,这些操作在游戏开发、建筑设计和可视化应用中非常常见。 BSP树是一种数据结构,用于将三维空间分割成互不重叠的区域,每个区域要么完全...
基于QT+C++开发的数据计算和图像处理的小工具+程序控制+数据计算+逻辑运算+图像处理+可视化蓝图编辑等,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于QT+C++...
- **简介**:IEEE 6节点系统是一个简化的电力网络模型,常用于教学和测试算法,它包含6个节点和若干支路,能体现基本的电力系统特性。 - **特点**:虽然简单,但足以展示潮流计算的基本原理和方法。 3. **MATLAB...
在Android平台上,开发一个能处理字符串运算的计算器是一项挑战性的任务。这个计算器应用程序允许用户输入一个完整的数学表达式,如 "2 + 3 * (4 - 5)",然后通过字符串解析和运算来得出结果。以下是一些关于实现...
【二叉树及其运算】 二叉树是一种在计算机科学中常用的数据结构,它由节点构成,每个节点最多有两个子节点,通常称为左子节点和右子节点。在本实验报告中,我们将探讨如何以二叉链表的形式存储二叉树,并实现一系列...
在实现过程中,考虑到效率,可以利用C++的模板元编程技术进行编译时计算,或者使用多线程技术来加速并行运算。同时,对于某些常用操作,如加法和乘法,可以通过内联函数或宏来减少函数调用开销。 总之,设计和实现...
在实现过程中可能遇到的问题包括:如何有效地遍历链表、如何处理重复元素、如何在保持时间复杂度较低的同时正确实现集合运算等。解决这些问题需要深入理解和熟练运用链表数据结构。 七、运行结果 7.1、实现集合 ...
IEEE(电气与电子工程师协会)为研究和测试目的制定了一系列标准的电力系统模型,其中,IEEE33节点系统是小型配电网的一个标准模型。这个模型由33个节点(包括负荷节点和电源节点)组成,反映了实际配电网的复杂性,...
- **构造过程**: 按照给定的序列构建二叉排序树时,每个节点的左子树中所有节点的值都小于它,右子树中所有节点的值都大于它。 #### 三、简答题知识点解析 **1. 散列表** - **散列函数**: 散列函数用于将关键字...
设计复杂混合信号芯片时,面临市场窗口缩小、开发周期短、低电源电压、更小工艺节点、更高性能和更低功耗的挑战。为了满足这些需求,一次成功的设计和设计重用变得至关重要。Cadence的仿真策略基于Spectre模拟器、...
- "MathParserTest.exe":这可能是一个测试应用程序,用于验证库的正确性和性能,通过运行各种测试用例来检查表达式的解析和计算是否符合预期。 - "COPYING.TXT"、"README.txt"、"ALTERNATIVE.TXT"、"BUGS.TXT":...
总结来说,"swift-Expressions-以可视化算术和逻辑表达式运算过程"项目是一个结合了Swift语言特性、数据结构、UI设计和交互的实践示例,对于学习和提升Swift开发技能,特别是理解和应用表达式及其可视化有着积极的...
在实际的开发过程中,可能还需要考虑其他因素,如错误处理、输入/输出格式、以及可能的扩展功能,如减法、乘法等。但核心的实现是构建一个有效且灵活的数据结构,以便于处理一元多项式的运算。通过这样的实践,我们...
LabVIEW,全称Laboratory Virtual Instrument Engineering Workbench(实验室虚拟仪器工程工作台),是一款由美国国家仪器(NI)公司开发的图形化编程环境,广泛应用于数据采集、测试测量、控制系统设计等多个领域。...