一. 实验概述
1.使用bison 和 flex 实现扩展版计算器
该计算器支持实型的两种表达,分别是小数和科学计数法.
该计算器支持 加, 减, 乘 除 四种运算 和括号()运算符.
该计算器支持整形,实型混合运算
2.通过递归下降分析法自行编写的语法分析和使用flex进行的词法分析的计算器.
支持整数,实数
支持加减乘除和括号
支持混合运算.
二. 测试和结果
如图所示,编写test.txt测试内容,其中有加减乘除混合运算,连除,指数e表达式等
调用calculator_self 分析执行,与手算结果均一致,且没有bug.
三. 总体架构
1.flex+bison的软件总体架构:
2.flex+手动语法分析的架构:
四. 详细设计
1.在calculator.l 文件中实现对实数和减,除的词法支持:
eee可识别科学计数法的e或E符号;
point 用于识别 点 .
plus_minu 用于识别 科学计数法中的正负号
reald 用于识别 实性数字,设计思想为:先识别一个数字,再接一个点,再接一堆数字,再接一个e或E的克林闭包,
增加对reald的操作:
调用库函数识别realnumber, 并返回REALDIGIT;
white_line返回’\n’便于我们后续分析.
2.在calculator.y中增加强化计算器的语法支持.
在union中添加对double类型的支持.
修改Factor,增加一项对REALDIGIT的支持.
修改Line 文法使之支持对换行符的识别.
3.在expr.c中增加对两种类型的加减乘除的支持.
例如:
在加法中,先判断是否又实数,如果是在判断哪个是实数,然后加减乘除,如果不是就直接加两个src.val.
减乘除的方法类似,就是把运算法号一改变就可.详情见源码expr.c .
4. 在main.c中调用设计的内容.
在main.c中引用lex和yacc的头文件,然后判断文件是否为空,不是就调用yyparse();
二. 自己设计的语法分析部分:
1.自己设计的calculator_myyacc.c
总体设计如图所示,根据文法使用递归下降分析法逐级分析.
myprint()的功能是打印结构体中的值.
yyparse() 是主函数,文法开始调用Input();
Input()是输入函数, 先判断下一个token是否在input的First集里面,不是就直接报错,如果是,则根据情况调用line();如果是文件末尾就返回.
line()是line文法分析函数.同理也是先判断是否正在First集合里,然后调用expr()分析,如果遇到\n 则tonken取下一个,继续调用line(),直到最后到EOF.
expr()函数支持对expr文法的支持.这里要返回具体的运算结果了,因此先malloc所需结构体的内存.
然后判断token是否在First集里面,如果是则调用term()分析,将结果赋给src1,然后term()中最终会交还数据并且token会指向没有识别的下一个token,这里不需要再next_token(),直接判断token是否是’+’, 或 ‘-‘, 如果是则继续调用term,并把结果赋给src2,然后调用expr中的加减算法,最后使用完了free掉src2.以此类推,直到expr中遇到该文法无法处理的符号则返回,交给上级处理.
term()与expr文法类似,函数结构也类似,在这里不再赘述,详情见calculator_myyacc.c
factor()用于判断realdigit和digit和( .如果是’(‘,则读取下一个token ,调用expr()分析,然后看下一个token是否是 ‘)’ 或’\n’不是就报错,是就返回,交给上级处理.
如果是digit 或 realdigit 则直接到yylval中读取数字,把数字内容返回.
next_token()就是读取下一个token.
2.calculator_myyacc.h
在这里设计了myyacc.c中需要的联合体和头文件,和函数声明.详情见calculator_myyacc.h,在此不再赘述.
3. 对Makefile的修改:
修改makefile使之支持calculator 和 calculator_self的同时编译,并且调用不同的设计文件.
五. 实验总结
5.1 调试和bug修改总结
1.一开始编写.l文件时不知道lex的语法,写完后总是报错,后来去ppt里学习了相关语法,完成实验. 一开始不知道c语言怎么将实型数字字符串识别为double ,最后上网查询调用strtod函数可以直接转换,并且 科学计数法也可以直接识别,这样省去了很多麻烦.
2.在编写calculator.y时,模仿老师的代码,在Factor中增加realdigit的支持,发现expr中没有double类型参数的调用,遂在expr.c中增加一项double realdigit,并且作为参数传入,完成实验.
3.一开始编写expr.c时,本来想如果有real类型的src,就直接把所有的val 全部赋给ralval,然后全部使用realval来计算.后来调试时发现编译器的优化会使result和src1是一个相同的的地址,最后返回的是src1,这样的话参数会总是有问题.
(个人觉得这样的优化是不合理的,目前还不知道为什么.)
4.自己根据文法写calculator_myyacc.c时,一开始根据文法规则总是递归的调用下一级分析器,发现当出现 连除 的时候,总是最后两个数先算,然后逐级向上回溯,这样和我们一般理解的从左往右计算是截然相反的,就比如4/2/2这个表达式,上述计算方法结果是4,而我们一般认为应该是1,因此后来更改为使用循环,当遇到一个term就继续判断然后计算,将结果存入src1,再继续读取下一个token直到遇到无法处理的运算符返回交给上级.
六.程序源码
TODO