通过手工构造的最小化DFA来构建简单的词法分析器,进一步熟悉词法分析的过程以及加深对“RE→NFA→DFA→DFA(o)→Program”这个过程的了解。
本次实验使用Java语言编写,简单实现了对C语言程序的词法分析。程序的输入是program.c文件,内含一段C语言代码,程序的输出是控制台和output.txt文件,内容是Token序列、符号表和常量表。
该词法分析器的分析能力如下:
此外,该语法分析器支持的错误识别如下:
Token序列的格式为
关键字: < value , _KEYWORD, _ >
数字: < value , _NUM , _ >
字符: < value , _CHAR , _ >
字符串: < value , _STRING , _ >
标识符: < value , _ID , location >
操作符: < value , _OP , _ >
界符: < value , _DELIMITER , _ >
特殊符号: < value , _SPECIAL, _ >
本次实验选用的方法是PPT中给出的第一种方法,即手动构造最小化DFA,然后根据最小化DFA编写程序,实现词法分析器。该方法的步骤如下:
定义一些RE
将RE转化为NFA
将得到的这些NFA转化为单一的NFA
将NFA转化为最小化DFA
根据得到的最小化DFA编写程序
实验设想:词法分析器的能力如实验描述所示。
在实验中,我首先根据实验描述中所示的词法分析器的能力完成了RE的定义,然后根据实验方法中的步骤最终得到了最小化DFA,所有的工作都是手工在草稿纸上完成。
程序中用到的数据结构如下所示:
定义了一个Token类,它属性包括type、value和location。其中location是指标识符在符号表中的位置,因此只有当type=”_ID”时location才是有效的。
List symbolTable:符号表。
List tokenList:Token序列。
程序的核心算法主要集中在scanner和tokenizer方法中。首先scanner扫描程序源文件,然后一个一个字符地依次传递给tokenizer方法来获取Token。在处理一个字符的时候,会遇到两种情况:第一种是该字符是buffer的一部分,下次无需再用;第二种情况是该字符只是用来判定之前的情况,下一次仍需要用到。这两种不同的情况在tokenizer方法中有不同的实现,代码如下:
private void tokenizer(char c) {
while (true) {
switch (state) {
case 0:
// 第一种情况
buffer += c;
state = ?;
return;
case 1:
// 第二种情况
state = ?
continue;
}
}
}
第一种情况最后是return,即跳出该方法,获取并处理下一个字符。第二种情况最后是continue,即继续处理当前的字符直至return。
程序实现的关键就是不同状态之间的转换,具体见代码。
C语言程序源代码:(program.c)
#include "stdio.h"
/**
* 这是多行注释
*/
int main() { // 这是注释
int a = 0;
double b = -1.23;
a = a + 1;
if (a > 0 && b < 1) {
a++;
} else {
a /= 5;
}
while (a > 0) {
printf("1234567890");
}
switch (a) {
case 1:
break;
default:
a = 1;
}
char xyz = 'a';
char c = '\t';
c= '\g'; // 错误
xyz = 'a123'; // 错误
b = 12.R; // 错误
a = ~`; // 错误
}
输出的结果如下所示:(output.txt)
Token序列如下:
Token序列如下:
< # , _SPECIAL , _ >
< include , _ID , 0 >
< "stdio.h" , _STRING , _ >
< int , _KEYWORD , _ >
< main , _ID , 1 >
< ( , _DELIMITER , _ >
< ) , _DELIMITER , _ >
< { , _DELIMITER , _ >
< int , _KEYWORD , _ >
< a , _ID , 2 >
< = , _OP , _ >
< 0 , _NUM , _ >
< ; , _DELIMITER , _ >
< double , _KEYWORD , _ >
< b , _ID , 3 >
< = , _OP , _ >
< - , _OP , _ >
< 1.23 , _NUM , _ >
< ; , _DELIMITER , _ >
< a , _ID , 2 >
< = , _OP , _ >
< a , _ID , 2 >
< + , _OP , _ >
< 1 , _NUM , _ >
< ; , _DELIMITER , _ >
< if , _KEYWORD , _ >
< ( , _DELIMITER , _ >
< a , _ID , 2 >
< > , _OP , _ >
< 0 , _NUM , _ >
< && , _OP , _ >
< b , _ID , 3 >
< < , _OP , _ >
< 1 , _NUM , _ >
< ) , _DELIMITER , _ >
< { , _DELIMITER , _ >
< a , _ID , 2 >
< ++ , _OP , _ >
< ; , _DELIMITER , _ >
< } , _DELIMITER , _ >
< else , _KEYWORD , _ >
< { , _DELIMITER , _ >
< a , _ID , 2 >
< /= , _OP , _ >
< 5 , _NUM , _ >
< ; , _DELIMITER , _ >
< } , _DELIMITER , _ >
< while , _KEYWORD , _ >
< ( , _DELIMITER , _ >
< a , _ID , 2 >
< > , _OP , _ >
< 0 , _NUM , _ >
< ) , _DELIMITER , _ >
< { , _DELIMITER , _ >
< printf , _ID , 4 >
< ( , _DELIMITER , _ >
< "1234567890" , _STRING , _ >
< ) , _DELIMITER , _ >
< ; , _DELIMITER , _ >
< } , _DELIMITER , _ >
< switch , _KEYWORD , _ >
< ( , _DELIMITER , _ >
< a , _ID , 2 >
< ) , _DELIMITER , _ >
< { , _DELIMITER , _ >
< case , _KEYWORD , _ >
< 1 , _NUM , _ >
< : , _DELIMITER , _ >
< break , _KEYWORD , _ >
< ; , _DELIMITER , _ >
< default , _KEYWORD , _ >
< : , _DELIMITER , _ >
< a , _ID , 2 >
< = , _OP , _ >
< 1 , _NUM , _ >
< ; , _DELIMITER , _ >
< } , _DELIMITER , _ >
< char , _KEYWORD , _ >
< xyz , _ID , 5 >
< = , _OP , _ >
< 'a' , _CHAR , _ >
< ; , _DELIMITER , _ >
< char , _KEYWORD , _ >
< c , _ID , 6 >
< = , _OP , _ >
< '\t' , _CHAR , _ >
< ; , _DELIMITER , _ >
< c , _ID , 6 >
< = , _OP , _ >
< 非法的转义字符 , _ERROR , _ >
< ; , _DELIMITER , _ >
< xyz , _ID , 5 >
< = , _OP , _ >
< 字符常量长度大于1 , _ERROR , _ >
< ; , _DELIMITER , _ >
< b , _ID , 3 >
< = , _OP , _ >
< 非法的浮点数常量 , _ERROR , _ >
< ; , _DELIMITER , _ >
< a , _ID , 2 >
< = , _OP , _ >
< 不能识别的字符 , _ERROR , _ >
< 不能识别的字符 , _ERROR , _ >
< ; , _DELIMITER , _ >
< } , _DELIMITER , _ >
< $ , _SPECIAL , _ >
符号表如下:
include
main
2 a
3 b
printf
xyz
6 c