编译原理计算器的原理与实践
摘要:就 PC 机上运行的计算软件而言,目前使用较多的是 mathematica 和 matlab 两
种。其中 mathematica 擅长于代数运算和方程求解。而 mathlab(俗称矩阵实验室)则擅长于数
值计算和绘图。上述两款软件虽然功能强大,但它们的使用较复杂,特别是编程功能很难为
普通人所掌握。Windows 系统自带的计算器使用方便,但功能相当有限,无法满足多数用户
的需要。本文的实际意义在于讨论为普通用户开发一种表达式计算器,它将支持通过简单的
脚本编程,实现对运算的控制和功能的扩充。
关键词:计算器;优先关系矩阵;计算软件;有限自动机
前言:在日常工作中我们会遇到大量的计算问题,使用一般计算器既繁琐又容易出错。
编写程序可以很好的解决这些问题,但掌握一门程序设计语言对普通电脑用户来说又有一定
困难。笔者希望开发一种支持,输入输出,变量声明,表达式计算,的简单易用的计算器,
帮助用户解决这个问题。计算器的实质是一个脚本语言解析机,其开发过程主要包括以下几
个步骤:首先要将参与运算的要素从表达式中分离出来,比如运算符,数据,变量,括号等。
而后为变量申请内存空间,缓存低优先级运算符和数据,最后根据优先关系矩阵完成语法分
析和检测。以下将对这三个步骤展开讨论。
1 运算要素的自动分离
分离运算要素的作用在于从运算公式中分离出参与运算的各个要素,如:运算符、浮点
数、变量等,并为它们编号。实现这一功能的程序我们称之为有限自动机。
1.1 有限自动机的数学模型
有限自动机的数学模型实际上是一个有向循环图。程序在接收到边所代表的字符后转到
圆圈所代表的状态。有限自动机的每一个状态代表一个运算要素。
在作者编写的计算器中其实际情形如图 1:
9
10
9
9
20
13
13
23
9
21
22
11
25
12
24
图 1
其中圆圈代表各个运算要素的编号,弧线代表读出的字符编号
本计算器支持的字符集及其机内编码如下:
+ - × ÷ ^ = ( ) # 数字 点 <
11
0
10
6
7
8
9
5
1
2
3
4
>
12
其它
13
各种运算要素各远算要素及其机内编码如下:
1
2
+ - × ÷ ^ = ( ) # 浮点数 变量
0
23
输出
25
比较可知,对于前 8 种运算要素,只需一步便可以识别出,所以图中没有画出。
程序运行时,每读出一个字符,就按图中的情况变换当前节点,直至读出非法字符,就
输入
24
21,22
7
5
6
3
4
8
完成了一次识别
1.2 有限自动机的数据结构
有限自动机的数据结构实际上是一个有向循环图。图的存储结构有很多种,作者通过研
究,找到了一种双数组结构,程序实现非常方便。其具体做法是用一个数组存储弧和目标节
点信息:
Dat[20]={9,21,13,23,9,21,10,22,9,22,13,23,9,23,11,25,12,24}
用另一个数组的下标表示节点,存储的数据则表示该节点发出的弧信息在 Dat 数组中的
起始位置:
Num[10]={0,4,8,10,18,18,18}
设读入字符为 ch,编号为 a,当前状态为 now,那么核心程序如下:
for(i=Num[now/20];i
-
×
÷
^
=
(
)
#
1
1
1
1
0
0
2
0
1
1
1
1
0
0
2
0
0
1
1
1
0
0
2
0
0
1
1
1
0
0
2
0
0
0
0
0
0
0
2
0
2
2
2
2
2
2
2
0
0
0
0
0
0
0
2
0
1
1
1
1
2
3
2
2
1
1
1
1
1
2
2
4
其中 0 代表压栈,1 代表执行栈中运算,2 代表语法错误,3 代表括号的抵消,4 代表运
算结束。
在很多有关编译原理的书中都介绍了把关系矩阵转换为关系函数的方法。
关系函数的优点在于减少存储空间。而作者在实际的编写过程中发现,关系函数不仅增
加了程序的运算负担,而且无法识别出很多语法错误,如:运算符’+’后出现’=’的情况。
鉴于本程序的用户群体为普通用户,程序的容错能力很重要,作者在程序中直接采用
了关系矩阵。
为了支持分析过程中的变量,本程序采用了一个结构类型 data 来表示所有参与运算过
程的操作数,其定义为:
struct data{
int nu;
float dat;}
当 nu=1 时,dat 存储浮点数,当 nu=0 时,存储变量编号
3.2 算符优先分析过程的伪代码描述
为了便于读者理解整个程序,作者给出分析过程伪代码描述如下:
void execute (string &s)
{
nu=dfa.read(I,s,out);//从命令行 s 中读出一个运算要素存入 out 中并把它的
编号赋给 nu
if(nu= =21||nu= =22){浮点数,转化为 data 类型后压入数据栈}
if (nu= =23){变量,转化为 data 类型后压入数据栈}
if (nu= =24){输入语句,按名接受一个浮点数并存入 domain 域中}
if(nu= =25){ 输出语句,从 domain 域中读出变量的值并打印 }
if(nu<9){运算符,
while(1){
分别以操作栈栈顶元素,nu 为下标从算符优先关系矩阵中读出优
先关系 ch
if(ch= =0){将 nu 压入操作栈}
if(ch= =1){从数据栈中弹出两个操作数,从操作栈中弹出操作号
执行相应的操作并把结果压栈}
if(ch= =2){系统报错}
if(ch= =3){操作栈弹栈抵消左右括号}
if(ch= =4){若数据栈中为浮点数则打印输出,为变量则视作命令
并转入文件处理。结束分析过程}
}}
}
4 实例演示
4.1 对表达式进行计算。
//输入圆的半径。
//输出运算结果
4.2 用文本编辑器创建如下脚本,并保存为 circle.txt。
r>;
s=3.14*r*r;
l=3.14*2*r;
s<;
l<;
程序运行效果如下:
//计算圆的面积
//计算圆的周长
5 结束
程序基本实现了表达式计算和脚本支持。但作为实用程序还需要一个功能完善的图形
用户界面,以及完成大量的测试工作。
参考文献:
【1】 KENNETH.C.LOUDEN . 编译原理与实践 . 北京:机械工业出版社,2002
【2】 龚天富,候文勇. 程序设计语言与编译[M].北京:电子工业出版社 1997
【3】 LOUDEN KC. Compiler Construction: Principles and Practice[M].北京:
机械工业出版社. 2000
【4】 严蔚敏 . 数据结构 . 北京:清华大学出版社,2002.
【5】 Harvey M.Deitel . c++how to programe . 北京:电子工业出版社,2000.