@Wayne-Z
2017-04-02T00:00:21.000000Z
字数 1963
阅读 1622
编译原理
模仿助教去年的作业,根据新的需求,加上自己的思考琢磨出了一个这样的文法,支持print和expression的任意排布,而且在print函数中支持计算等看到submission sample中的ReadMe.txt的时候已经晚了,所以作业时按照下面的文法来写的。
经过思考以后,私以为有两种主要方式可以实现本次作业,一种就是写一个小的前端出来,基本啥五脏俱全,可以进行语法和词法分析,自然能输出计算结果以及语法树;还有一种就是直接按照文法去读取输入和计算,然后算出结果并且输出就行,此方法取巧之处在于只用识别print和其他变量的区别,且因为变量无需声明,如上文所示,自己想出来的文法相对简单,因此工程量更小。
笔者一开始是按照第一种思路去写,先仿照龙书后面的小前端去写,然后自己改代码,后面发现过于复杂,不必要,于是决定精简代码,采用第二种思路,但是在处理变量的相关问题时,发现采用小前端处理word的思路挺方便的,于是最后将代码分成了四个class:
- SimpleCalculator:程序入口,只有main函数
- Tag:记录了ID、INT、FLOAT来对变量的属性进行区分
- Var:用于变量的生成和标记,变量的值用Number进行声明,方便在计算中根据计算结果进行改变。
- Calculator : 依据文法包含了所有主要的函数,文件的读取以";"为标记的一个statement语句为单位并将其读入到一个队列中,而后对队列进行分析和读取,expression的计算采用了递归的方式,并在递归中传递队列,解决了括号的问题。用链表记录每一个变量,来确定是否声明以及赋值。
在编写代码过程中,遇到了一些困难,不过后面都逐一想办法解决了,用的是当时最快想到的方式,不一定是最优方案。
在运算过程中,数字和变量都首先被当作字符串进行分析,分析完成后,如何将变量和数字一同进行运算是一个问题。因此首先我创建了一个Var类,将分析到的数字返回成name空值的Var类型,并且更改相应的Tag标签为INT或者FLOAT。同时根据此重新定义加减乘除以及除零等边界条件的判定。
运算过程中需要能同时允许整数和浮点数的共同运算,这也是Tag中INT和FLOAT的意义所在。不仅如此,为了将其整合到同一个类Var中,将value的值声明称了Number类型,由此也产生了一些小麻烦,比如值的转化等,number类型是不能直接进行运算的,因此,要在每一个加减乘除函数中对两个变量的标签进行判定并分类转化number为int或者float值进行运算。也正是在这个环节中,产生了浮点数的精度误差问题,这个问题一直没有得到解决。
整个计算过程采用了递归的方式,并传递队列,返回计算出的Var值,这样就解决了对括号的读取问题,同时简化了运算,这个时候corner case显得有些头疼,为此声明了一个unvalid的Var,专门用于边界条件的返回,并将其加入到了加减乘除运算的边界条件判定中。
正确的打开方式在test.in文件中:
print(2/(1+2*3.5)*4);
a=(10.44*356+1.28)/2+1024*1.6;
b=a*2-a/2;
c1d23=a+b*b/5-(a-a*2)/b;
print(a);
print(b);
print(c1d23);
错误的打开方式在error1.in文件中:
a=(10.44*356+1.28)/2+1024*1.6;
c123=a+b*b/5-(a-a*2)/b;
e = a/0;
print(a);
print(c123);
print(e);
Windows环境下,calculator.bat文件内容如下:
@echo off
::set CALC environment
set CALC_PATH=%~dp0Simplecalculator.jar
::add CALC environment
SET CLASSPATH=%CLASSPATH%;%CALC_PATH%
::execute
java SimpleCalculator test.txt
java SimpleCalculator error.txt
测试环境为Windows PowerShell,直接运行,测试结果如下图
这个结果是和预期结果不符的,关于a、b、c1d23的输出都产生了一定的精度偏差。在追踪a的输出的时候发现最后一步加法仍为1858.96+1638.4,但是结果就变成了3497.3599,hin无奈······