分享
 
 
 

编译原理学习日记<2004-2-2>简单指令型计算器(1)指令系统和汇编编译器<上>

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

声明

这一系列“编译原理学习日记”中的所有文章为作者个人的准Weblog,所有文章版权均属作者所有。转载请注明出处(CSDN文档中心)、作者(lover_P)及作者联系方式(lyb_dotNET@hotmail.com),并在转载前与作者联系。

其实,要到下学期才开“编译原理”这门课程,可漫长的假期让人觉得有点寂寞。我终于忍不住对编译原理的向往,翻开了《现代编译程序设计》一书。为什么是这本书?因为我对理论实在是不感兴趣,这本书恰恰是偏重实践的一本书,同样的还有《编译原理及实践》一书。我也是到现在才发现这两本书的,从打算好好学习编译技术开始,我就一直在为自己物色好的教程,很多教材我看了目录,就开始沮丧起来,觉得应该先把计算机原理和汇编语言什么的补一补。甚至一度想过放弃,但幸好我看到了这两本书。粗略的浏览一下,我就豁然开朗了——学些编译技术难道一定要精通x86么?不!两本书不约而同地,都用了“虚拟机”这个强大的武器。是啊,自己做个小的虚拟机,在这个虚拟机上开发自己的语言和编译器!于是我打算从一个小的虚拟机入手,开始自己的实践和学习。我选择《现代编译程序设计》一书没什么原因,就是因为我先发现的这本书。当然,两本书我都要好好地学习的。我也推荐对编译技术感兴趣的同胞们好好地看看这两本书。

我发现,现在国内网站上的一些技术文章,与“编译原理”有关的多以“表达式分析”居多,甚至我曾一度错误地认为编译原理就是表达式分析,后来才知道,这不过是编译过程中的一部分而以。不过,至少可以说,表达式分析是学习编译原理很好的入门步骤。所以,我也想从表达式分析入手。《CSDN开发高手》新增加了一个编译技术的专栏,不知道大家都看了没有,这两期都集中在“计算器”的话题上;无独有偶,《现代编译程序设计》一书也在第一章介绍了一个指令型的计算器。呵呵,那我就也作指令型计算器。《现代编译程序设计》书上介绍的指令性计算器只有个四指令——ADD, MUL, PUSH, PRINT,我就来一个能实现各种四则数值运算的计算器吧。(注:本计算器是在参考了《现代编译程序设计》的思想上自主研制出来的,极具收藏价值哦:P)

一、虚拟指令型计算器架构

首先,我接受了《现代编译程序设计》一书中的思想,将这个“虚拟机”(也就是我的“指令型计算器”)设计为基于堆栈的。该架构相当简单:

所有的指令均作用于堆栈,我们将该堆栈称为“运算栈”;

指令的基本执行流程:从运算栈中弹出操作数->执行相应操作(运算或输出或其他)[->将运算结果压入运算栈];

如果指令需要两个或更多操作数,操作数出战的顺序规定为:从右至左,即对于操作“operand1 op operand 2”,其过程为“pop operand2, pop operand1, add operand1 operand2”。这也规定了调用某项操作的压栈顺序,即push operand1, push operand2。

在某些无操作结果的指令(如OUT),无须[->将运算结果压入运算栈]这一步骤。

有了整体架构,我们就可以开始设计这个虚拟计算器的指令系统了。

二、虚拟指令型计算器指令系统

我为这个计算器设计了两组指令(详细的指令说明见附录):

1、运算栈控制指令

这一组指令用于对运算栈进行控制,包括下面这些指令:PUSH、DUP和OUT。

2、运算指令

这一组指令用于进行具体的运算,其基本运行机理是按照从右到左的规则从运算栈中推出各个操作数,再将运算结果压入运算栈。这一组指令中又分为以下一些类型:

a. 四则运算指令,包括:ADD、SUB、MUL、DIV

b. 指数运算指令,包括:EXP、SQR、CUB、POW、SQRT、CBRT

c. 对数运算指令,包括:LN、LG、LOG

d. 三角函数运算指令,包括:SIN、COS、TAN、CTN

e. 反三角函数运算指令,包括:ASIN、ACOS、ATAN、ACTN

f. 双曲函数运算指令,包括:SINH、COSH、TANH、CTNH

g. 反双曲函数运算指令,包括:ASINH、ACOSH、ATANH、ACTNH

三、虚拟指令型计算器汇编语言编译器

首先介绍一下我为这个虚拟指令型计算器设计的汇编语言,简单得让你掉下巴。就是把指令一条一条写出来就成了:P。譬如,计算3+2,可以写:

PUSH 3

PUSH 2

ADD

OUT

注:这种指令的想法参考了《现代编译程序设计》一书,你可以在该书的第一张找到相应的例子。

而sin(1.57)*cos(1.57)可以写作:

PUSH 1.57

SIN

PUSH 1.57

COS

MUL

OUT

再来一个,5*asin(cos(2)):

PUSH 2

COS

ASIN

PUSH 5

MUL

OUT

怎么样?容易吧?

那么,我们的汇编程序都做什么呢?再掉一次下巴吧,就是把这些指令写成字节,指令占一个字节、操作数用double,按机器的不同和double类型占用同样的字节数。

为什么要写这样一个汇编程序呢?让计算器直接读取指令字符串不是就可以了么?

呵呵,假装是为了提高效率和减轻对存贮器的负担吧。其实,最重要的还是回忆一下C语言。弄了这么长时间.NET和C#,把C语言都忘了。其实,最初我是想用C#作这个东西的,但最后考虑了一下,还是决定用C做。所以,险些这么个小东西熟悉一下。当然,写了这么一个编译器后,就可以尽早发现指令错误,而可以减轻计算器本身的编写难度。

废话不说了,来看看这个简单的汇编编译器吧。

其基本原理刚刚说过了,很简单,从文本文件中一次读取每一条指令然后翻译成字节,再写入到二进制文件中作为指令文件(instruction file)。

程序中的重点在一个循环中:

while(fscanf(sfile, " %s ", token) != EOF) {

if(strcmpi(token, "PUSH") == 0) {

cinst = PUSH;

fwrite((void*)&((char)cinst), sizeof(char), 1, dfile));

/* Need an operand: */

fscanf(sfile, " %lf ", &coperand));

fwrite((void*)&coperand, sizeof(coperand), 1, dfile));

}

else {

if(0 == strcmpi(token, "DUP" )) cinst = DUP ;

else if(0 == strcmpi(token, "OUT" )) cinst = OUT ;

...... /* Other instruction */

else { /* Unknown token */

cerror(lno, "Invalid instruction!");

}

/* If no error, write the instruction into temp file: */

fwrite((void*)&((char)cinst), sizeof(char), 1, dfile));

}

lno++;

} /* while */

其中,sfile是一个已打开的汇编程序源文件,token用来存贮我从文本文件中获得的指令文本。在这里,我使用了fscanf()函数来取得指令文本,而没有使用fgets(),是因为PUSH指令带有操作数,但没有必要一次把一行都读进来再分离,只需读入一个记号,如果是PUSH就再读一个操作数就可以了。这样做比较方便。所以后面我把PUSH指令单独提出来,在后面又加了一些读取操作数的代码(/* Need an operand: */注释下面的两行)。其它指令则在那一大堆if-else中进行判断,并存放在Instruction枚举类型的cinst(表示current instruction即当前指令)中。最后,将指令代码转型为char(在C中只占用一字节)并写入二进制文件(见fwrite()语句)。另外,还有个lno,这是用来存贮当前行号的,用来显示发生错误的行。

刚才提到,cinst是Instruction枚举类型。现在来介绍一下这个枚举。Instruction枚举了该指令型计算机所支持的所有指令,在inst.h中定义如下:

typedef enum tag_instruction {

PUSH, DUP, OUT,

ADD, SUB, MUL, DIV,

EXP, SQR, CUB, POW, SQRT, CBRT,

LN, LG, LOG,

SIN, COS, TAN, CTN,

ASIN, ACOS, ATAN, ACTN,

SINH, COSH, TANH, CTNH,

ASINH, ACOSH, ATANH, ACTNH

} Instruction;

没什么好解释的,一目了然。如果对指令不太熟,请看附录1。

还有文件处理方面。我为虚拟指令型计算器的汇编语言源文件起了扩展名“.scs”,意为“Simple Calculator Sourcefile”;指令文件扩展名为“.sci”,意为“Simple Calculator Instructionfile”。还有,我首先将指令写入到了一个临时文件中,也就是fwrite()语句中的dfile,原来这个变量表示的是目标文件(dest file),但后来我发现,一旦编译过程中发生错误,原来的文件就会被覆盖,而留下一个没有写完的.sci垃圾文件。所以我使用了临时文件,当编译成功后再将原有的.sci文件删除并将临时文件改名为目的文件名。而如果编译不成功,则清除临时文件,原来的.sci文件不会被损坏。我不知道别的编译器是怎样实现这一目的的,我的这种方法应该不算笨。(我还想过一种方法,就是把所有指令写入内存中,如一个void*型的buffer,不过这种方法很不方便)你在源代码中会看到一个tfn变量,这就是我用tmpnam()函数得到的临时文件名。

这个编译器真的很简单,也没什么好说的了。这一次主要也就是介绍了这个虚拟指令型计算器的构思和指令系统。至于编译器的源代码,本来打算附在附录中,但我没想到这么容易的一个东西竟被我写得乱七八糟(那里面的这个主循环可没这么好看,因为夹杂了大量的错误处理语句)。为了保留点面子,源代码就不列出来了。不过,如果你耐心地看到这了,并且对我这个计算器感兴趣,可以和我联系,我把代码悄悄地发给你,你觉得不爽就悄悄地骂我。当然,为了表示答谢,我还“附赠”一个“虚拟指令型计算器汇编语言反汇编程序”:P。其实这个反汇编程序是我用来查错的,不过我想以后也会有用的。原理和编译器差不多,就是从二进制的指令文件中获得文本方式的指令代码。

好了,这一次就贫到这里了(真没想到又贫了这么多,下次改)。以后我的编译原理就实践于这个计算器上了。当然,下一次的话题就是如何实现这个虚拟的计算器了。

=====未完待续=====

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有