词法分析器的简单实现

实验目的

通过手工构造的最小化DFA来构建简单的词法分析器,进一步熟悉词法分析的过程以及加深对“RE→NFA→DFA→DFA(o)→Program”这个过程的了解。

实验描述

本次实验使用Java语言编写,简单实现了对C语言程序的词法分析。程序的输入是program.c文件,内含一段C语言代码,程序的输出是控制台和output.txt文件,内容是Token序列、符号表和常量表。

该词法分析器的分析能力如下:

  1. 忽略注释:单行注释//、多行注释/* */。关键字(ANSI C定义的32个):“auto”, “break”, “case”, “char”, “const”, “continue”, “default”, “do”, “double”, “else”, “enum”, “extern”, “float”, “for”, “goto”, “if”, “int”, “long”, “register”, “return”, “short”, “signed”, “static”, “sizeof”, “struct”, “switch”, “typedef”, “union”, “unsigned”, “void”, “volatile”, “while”
  2. 字符常量(含转义字符)
  3. 字符串常量(含转义字符)
  4. 整数常量
  5. 浮点数常量(不支持指数)
  6. 标识符
  7. 操作符:+, -, *, /, %, =, ++, --, +=, -=, *=, /=, %=, ==, >, <, >=, <=, >>, <<, &, &&, &=, |, ||, |=, ^, ^=
  8. 界符:‘;’, ‘,’, ‘(’, ‘)’, ‘{’, ‘}’, ‘[’, ‘]’, ‘.’, ‘:’
  9. 特殊符号:#, $

此外,该语法分析器支持的错误识别如下:

  1. 不能识别的字符
  2. 非法的字符常量
  3. 字符常量的长度大于1
  4. 非法的转义字符
  5. 非法的浮点数常量

Token序列的格式为<value, type, location>:

关键字: < 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