(1)词法分析概述(主要介绍词法分析的主要功能)
1. 将正规式转化为NFA
2. 将NFA转化为DFA
3. 将DFA转化为最小DFA
4. 模拟DFA
5. 识别源程序
6. 错误信息判断
(2) 采用的技术及平台安装(主要介绍采用了什么开发语言,如何部署软件运行环境,简要说明不超过300字)
本实验采用Java语言编写,IDE为Eclipse Jee Oxygen,部署时直接下载安装对应软件即可,Java语言的配置需要在安装相应文件后,设置环境变量和系统变量。
(3) 算法分析(主要介绍采用的算法及数据结构,以及如何用数据结构实现算法,包含:数据结构和算法分析两部分内容,在对应算法分析部分,必须给出示例说明算法)
1. 栈,优先级识别正规式
数据结构:使用栈,分为运算符栈和字符栈
算法:将‘#’符号压入运算符栈,当输入表达式的字符坐标不等于表达式的长度或者运算符栈顶不等于‘#’时,做循环,在循环体中,判断表达式的各个字符,若不是运算符则压入字符栈,若是运算符,则与运算符栈的栈顶元素进行优先级比较,若栈顶元素优先级低,将运算符压栈,若相等,运算符栈弹栈,若栈顶元素优先级高,则弹出运算符栈,以及连续弹出两次字符栈,将上述弹出的两个字符和一个运算符作为一个运算组。
2. Thompson算法
数据结构:构造节点和弧的结构来表示NFA,构造的结果类似图。通过结点和弧可以遍历整个图;NFA栈,用来保存中间的NFA结果。
算法:在通过优先级判断后得到的表达式单元,比如AB|(A或B)这样的正规式后,判断运算符是 | ,则调用事先编写好的五个构造函数(分别是或运算,连接运算,闭包运算,单个字符,ε运算)中的或运算,构造A|B的NFA,同时将构造好的NFA压进NFA栈,这一过程实际就是将NFA构造过程和表达式优先级判断过程同步。不断重复过程,最终得到的NFA栈的栈顶元素就是完整的NFA。
3. 最小子集法
数据结构:定义了表示DFA的矩阵和 表示状态集合的结构。
算法:首先定义了ε闭包和smove函数,设计递归的部分用栈来模拟递归。然后用表示状态集合的数据结构表示ε闭包+smove操作所得到的的状态集合,在运算过程中对于新的状态标号,并在后续对新的状态进行对应字符的smove+ε闭包运算,直到不再产生新状态为止。完成上述运算后,根据表示的新状态号和对应的字符,构造DFA矩阵。
4. 最小DFA算法
数据结构:定义了表示一个划分的数据结构,以及记录可以合并状态(即同属一个划分的两个状态)的集合。
算法:计算两个状态是否可以合并时,采用的是n!的时间复杂度的算法,即第一步为第一个状态和第2至n的状态进行是否可以合并为一组的判断,将可以合并的两个状态进行记录,第二步为判断第2个状态和第3至n的状态的判断,同样将可以合并的两个状态进行记录,循环上述步骤,直到n-1个状态和第n各状态进行上述过程。在完成上述步骤后,对于得到的记录可以合并为一组的状态的集合,判断二元组之间是否有公共状态,则表示这两个二元组的三个状态可以合并。比如,(1,2)和(2,3)表示1,2状态可以合并,2,3状态可以合并,通过判断知道,1,2,和2,3有公共状态2,则表示1,2,3这三个状态可以合并为一个组,即(1,2,3),其他组的判断也是这样,在全部合并完之后,得到最终的划分结果,再依照原先的DFA矩阵和划分结果,构造一个新的最小DFA矩阵。
5. DFA模拟器算法
数据结构:无新定义数据结构。
算法:主要是将源程序代码段的各个字符依序进行再矩阵里的状态转移,观察最终结果是处于终态,非终态,还是出现错误的字符。
6. 字符串最大匹配识别
数据结构:将构造的最小DFA进行集合化,即定义list<DFA>这样的集合
算法:主要是判断在最大匹配的过程中,会产生什么样的不同情况,比如当前字符串无法被所有DFA识别,或者能识别但是不处于终态,或者能识别且处于终态,明确各种情况下的操作即可。在这里要注意将构造的几个最小DFA按照关键字,数字,特殊字符,。。。标识符的顺序进行DFA模拟,确保没有误识别。
(4) 流程图(绘制并说明算法流程)
在这一过程中,文件流会不断将生成的中间结果写入txt文件中。
(5) 程序运行截图(根据实验指导书第3节提供的内容及要求显示输出结果,并提供对应交互信息)
1. 输入正规式
课本例子:case:(a|b)* a b b
生成的dfa和最小dfa
结果
用例:case:(0|1|2|3|4|5|6|7|8|9)
2. 对于每一个正规式所生成的DFA和最小DFA,数字代表状态,null代表空
Void
id:(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z) (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9)*
digital:(0|1|2|3|4|5|6|7|8|9) (0|1|2|3|4|5|6|7|8|9)*
因为正规式比较多且比较复杂,所以只展示出其中两个,所有的结构式保存在工程文件的resource.txt中。
3. 输入源程序
4. 识别结果
控制台
Resource.txt
5. 错误信息
对于数字开头的标识符,能将其非法性识别出来,见下图第四行的11a
(6) 测试用例
1. 正规式
KW:v o i d
KW:i n t
KW:f l o a t
KW:i f
KW:e l s e
id:(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z) (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9)*
ks:(|)|{|,|;|+|/|>|=|}
digital:(0|1|2|3|4|5|6|7|8|9) (0|1|2|3|4|5|6|7|8|9)*
(id这个用例在粘贴到界面内时注意让它的内容显示在同一行)
2. 源程序
void main(){
int x,a,b;
float y,c,d;
x=a+b;11a
y=c/d;
if(x>y)
x=10;
else
y=100;
}
(7) 代码结构说明
(8)程序分析(主要介绍编程中出现的错误以及修改的说明以及实验心得)
1. 正规式的优先级是构造NFA的关键,运用数据结构课中的算数表达式优先级可以解决。
2. 构造NFA的五个子结构,因为NFA很像图,所以定义了结点和弧这两种结构,还定义了深度优先遍历NFA的函数,更深体会到了大二学习的数据结构知识对于编程的重要性。
3.在将NFA转化为DFA的过程中,由于对于数字,或者标识符这样字符很多的正规式的NFA,会含有大量ε,所以一开始用函数递归进行空闭包操作时,堆栈会溢出,后来将函数递归的形式用数据结构栈来模拟后,成功解决,由此体会到函数调用时以堆栈形式调用这一方式,以及告诫自己在使用递归时,虽然函数递归从代码上看结构更清楚,但是用栈来模拟递归的过程的性能会更好。
4. 接上一点,在最初使用函数递归的形式模拟空闭包和Smove操作时,由于中间变量的不断更新,导致在递归函数逐层退出到最外面一层时,中间变量的值已经改变,所以为了能暂时保存最外层函数的中间变量的原值,学习了Java语言的深拷贝和浅拷贝,这一机制是为了在存储内部增加一块存储空间,而不是简单的引用变量。
5.在设计最大匹配扫描源程序的字符串的算法时,因为在扫描过程中遇到的情况特别多,如果不将所有的过程用流程图或伪代码的形式预先定义好,编写程序时代码结构会显得臃肿。所以在编写代码时,按照流程图或伪代码的参照来写,代码的结构的简洁性以及程序的正确性会提高。
6. 在对字符串进行较为复杂的操作,比如提取某个特定的中间字符串时,使用java语言本身定义的正规式函数,进行split等操作时,会方便很多,不过要明确正规式的规则。
7. 在源程序字符串匹配过程中,想象int这个关键词,被用来识别字符串的DFA集合是有次序的,如果在识别int时,标识符DFA先于关键字intDFA,则int会被识别成一个标识符,而不是关键字,所以要明确这些DFA的识别优先级。
网友评论