原理方面就不赘述,本篇博客主要记录编译原理实验上机对于简单 C 语言子集的词法分析程序的 Java 实现。
C语言子集输入/输出以及要求
要求输入如下:
1 | main() |
要求输出:
同时要求:
- 识别保留字:
if
,int
,for
,while
,do
,return
,break
,continue
,其单词种别码为 1 - 其余字符识别为标识符,单词种别码为 2
- 常数为无符号整型数,单词种别码为 3
- 运算符:
+
,-
,*
,/
,=
,>
,<
,>=
,<=
,!=
,单词种别码为 4 - 分隔符:
,
,;
,{
,}
,(
,)
,单词种别码为 5
分析
分析程序流程
对于输入程序,我们不可能套用一套规则从而直接分析出结果。可以根据输入字符串构建缓冲区,然后从缓冲区逐个取出字符进行分析。
保留字、标识符
对于语言中的保留字,可以在程序中硬编码保存。当成功从输入串中构建一个 单词 的时候,进行比对来判断该 单词 是保留字还是标识符。
1 | private static final String[] reverses = new String[]{"if", "int", "for", "while", "do", "return", "break", "continue"}; |
常数
常数与保留字、标识符的最大区别是:常数以数字开始,而保留字和标识符不能以数字起始。以此,我们可以明确区分出输入程序中的常数。
运算符与分隔符
运算符与分隔符的识别十分简单,利用 switch
即可。
实现
识别字的保存
保留字、标识符、常数、运算符、分隔符的单词种别码是固定的,如果我们在分析程序中直接写数值可能会影响可读性,可以用 enum
来表示:
1 | private enum Code { |
因为我们最终是要以上面图中的形式输出,所以需要另一种数据结构来表示识别出来的 单词种别码-单词
对:
1 | private List<Kind> list = new ArrayList<>(); |
字符串分析器
我们可以使用 String
来保存输入串,并用一个类变量 index
来保存当前字符的位置,这样我们就可以利用 index
来进行逐字符分析:
1 | private int index = 0; |
单字符分析器
1 | private void analyzer() { |
源代码
LexicalAnalysis.java
1 | import java.io.BufferedReader; |
另一种实现方式
使用 index
控制当前字符的位置可能有些不够优雅,我们可以使用 java.nio.CharBuffer
来替代手动控制识别流程的方式。
LexicalAnalysisCharBuffer.java
1 | import java.io.BufferedReader; |
思路与上一种实现一样,不过使用这种方式我们无需操作 index。