CoderZQYのBlog

个人不定期更新的学习周报

0%

lexical_analyzer

编译原理实验1——词法分析器

1、实验要求

​ 一、实验目的:加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行词法分析。

​ 二、实验内容:自定义一种程序设计语言,或者选择已有的一种高级语言,编制它的词法分析程序。词法分析程序的实现可以采用任何一种编程语言和编程工具。 从输入的源程序中,识别出各个具有独立意义的单词,即关键字、标识符、常数、运算 符、界符。并依次输出各个单词的内部编码及单词符号自身值。(遇到错误时可显示“Error”,然后跳过错误部分继续显示)

​ 三、实验要求:1. 对单词的构词规则有明确的定义;2. 编写的分析程序能够正确识别源程序中的单词符号;3. 识别出的单词以<种别码,值>的形式保存在符号表中,正确设计和维护符号表;4. 对于源程序中的词法错误,能够做出简单的错误处理,给出简单的错误提示,保证顺利完成整个源程序的词法分析;

​ 四、实验步骤:1. 定义目标语言的可用符号表和构词规则;2. 依次读入源程序符号,对源程序进行单词切分和识别,直到源程序结束;3. 对正确的单词,按照它的种别以<种别码,值>的形式保存在符号表中;4. 对不正确的单词,做出错误处理。

2、实验方法

​ 根据对应的状态转换图完成编码
状态转换图

3、代码实现

1. 前期准备

1)准备一个关键字文件,用于存储要识别的关键字,方便后续添加,命名为keyword.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private 一种访问控制方式:私用模式
protected 一种访问控制方式:保护模式
public 一种访问控制方式:共用模式
abstract 表明类或者成员方法具有抽象属性
class
extends 表明一个类型是另一个类型的子类型,这里常见的类型有类和接口
final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变
implements 表明一个类实现了给定的接口
interface 接口
native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的
new 用来创建新实例对象
static 表明具有静态属性
strictfp 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范
synchronized 表明一段代码需要同步执行
transient 声明不用序列化的成员域
volatile 表明两个或者多个变量必须同步地发生变化
break 提前跳出一个块
continue 回到一个块的开始处
return 从成员方法中返回数据
do 用在do-while循环结构中
while 用在循环结构中
if 条件语句的引导词
else 用在条件语句中,表明当条件不成立时的分支
for 一种循环结构的引导词
instanceof 用来测试一个对象是否是指定类型的实例对象
switch 分支语句结构的引导词
case 用在switch语句之中,表示其中的一个分支
default 默认,例如,用在switch语句中,表明一个默认的分支
try 尝试一个可能抛出异常的程序块
catch 用在异常处理中,用来捕捉异常
throw 抛出一个异常
throws 声明在当前定义的成员方法中所有需要抛出的异常
import 表明要访问指定的类或包
package
boolean 基本数据类型之一,布尔类型
byte 基本数据类型之一,字节类型
char 基本数据类型之一,字符类型
double 基本数据类型之一,双精度浮点数类型
float 基本数据类型之一,单精度浮点数类型
int 基本数据类型之一,整数类型
long 基本数据类型之一,长整数类型
short 基本数据类型之一,短整数类型
super 表明当前对象的父类型的引用或者父类型的构造方法
this 指向当前实例对象的引用
void 声明当前成员方法没有返回值
goto 保留关键字,没有具体含义
const 保留关键字,没有具体含义

2)准备一个运算符文件,用于存储要识别的运算符,方便后续添加,命名为operator.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
+   加法 - 相加运算符两侧的值
- 减法 - 左操作数减去右操作数
* 乘法 - 相乘操作符两侧的值
/ 除法 - 左操作数除以右操作数
取余 - 左操作数除以右操作数的余数
++ 自增: 操作数的值增加1
-- 自减: 操作数的值减少1
== 检查如果两个操作数的值是否相等,如果相等则条件为真。
!= 检查如果两个操作数的值是否相等,如果值不相等则条件为真。
> 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。
< 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。
& 如果相对应位都是1,则结果为1,否则为0
| 如果相对应位都是 0,则结果为 0,否则为 1
^ 如果相对应位值相同,则结果为0,否则为1
~ 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。
>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。
&& 称为逻辑与运算符。当且仅当两个操作数都为真,条件才为真。
|| 称为逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。
! 称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。
= 简单的赋值运算符,将右操作数的值赋给左侧操作数
+= 加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数
-= 减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数
*= 乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数
/= 除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数
(%)= 取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数
<<= 左移位赋值运算符
>>= 右移位赋值运算符
&= 按位与赋值运算符
^= 按位异或赋值操作符
|= 按位或赋值操作符

3)准备一个分割符文件,用于存储要识别的分割符,方便后续添加,命名为separation.txt

1
2
3
4
5
6
7
8
9
10
{   大括号(花括号):用来定义块、类、方法及局部范围,也用来包括自己初始化的数组的值。大括号必须成对出现
}
[ 中括号(方括号):用来进行数组的声明,也用来撤销对数组值的引用
]
( 小括号(圆括号):在定义和调用方法时,用来容纳参数表。在控制语句或强制类型转换的表达式中用来表示执行或计算的优先权
)
; 分号:用来表示一条语句的结束。语句必须以分号结束,否则即使一条语句跨行或者多行,仍是未结束的
, 逗号:在变量生命中用于分隔变量表中的各个变量,在for控制语句中,用来将圆括号里的语句链接起来
: 冒号:说明语句标号
. 圆点:用于类/对象和它的属性或者方法之间的分隔。

2. 编写FileUtil工具类

定义一个工具类FileUtil,实现readFile()方法:读取文本文件,创建一个ArrayList< String >队列存取符号,读取完毕则返回该队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

public class FileUtil {
public static ArrayList<String> readFile(String filePath) {
//以字符方式读取文本文件
FileReader fis = null;
BufferedReader br = null;
ArrayList<String> arrayList = new ArrayList<>();
try {
fis = new FileReader(filePath);
br = new BufferedReader(fis);
String line;
while ((line = br.readLine()) != null){
line = line.split("\\s+")[0]; //按空白字符进行分割
arrayList.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return arrayList;
}
}

3. 编写LexicalAnalyzer主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package 编译原理.作业1;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;

public class LexicalAnalyzer {
public static final String keywordFilePath = "src/编译原理/作业1/keyword.txt"; //关键字
public static final String operatorFilePath = "src/编译原理/作业1/operator.txt"; //运算符
public static final String separationFilePath = "src/编译原理/作业1/separation.txt"; //分隔符
public static final String targetFilePath = "src/编译原理/作业1/test_data.txt"; //测试文件
static ArrayList<String> keyWords=null;
static ArrayList<String> operations=null;
static ArrayList<String> symbols=null;
//指向当前所读到字符串的位置的指针
static int p,lines;

public static void main(String []args) throws FileNotFoundException {
//初始化,读入关键字、运算符、分隔符配置文件
keyWords = FileUtil.readFile(keywordFilePath);
operations = FileUtil.readFile(operatorFilePath);
symbols = FileUtil.readFile(separationFilePath);
//读入目标文件
File file = new File(targetFilePath);
lines = 1;
try(Scanner input = new Scanner(file)) {
while (input.hasNextLine()){
analyze(input.nextLine());
lines++;
}
}
}

public static void analyze(String str){
p = 0;
str = str.trim();
for (; p < str.length(); p++){
char ch = str.charAt(p);
if (Character.isDigit(ch)){
//实数的识别
digitCheck(str);
}else if (Character.isLetter(ch)||ch == '_'){
//标识符,关键字的识别
letterCheck(str);
}else if (ch =='"'){
//字符串检查
stringCheck(str);
}
else if (ch ==' '){
}else {
//符号的识别
symbolCheck(str);
}
}
}

/*数字的识别
* 1、识别退出:
* 1.1、遇到空格符
* 1.2、遇到运算符或者分割符
* 2、错误情况:
* 2.1、两个及以上小数点
* 2.2、掺杂字母
* */
public static void digitCheck(String str){
StringBuffer sb = new StringBuffer(String.valueOf(str.charAt(p++)));
//判断数字的小数点是否有且是否大于1
int flag = 0;
boolean err = false;
char ch;
for (; p<str.length(); p++) {
ch = str.charAt(p);
if (ch == ' '|| (!Character.isLetterOrDigit(ch) && ch!='.')) {
break;
}else if (err){
sb.append(ch);
} else {
sb.append(ch);
if (ch == '.') {
if (flag == 1) {
err = true; //多出一个小数点,Error!
} else {
flag++;
}
}else if (Character.isLetter(ch)){
err = true; //出现字母,Error!
}
}
}
String token = sb.toString();
if (token.charAt(token.length()-1) == '.'){ //最后一位为小数点,Error!
err = true;
}
if (err){
System.out.println(lines + "line" + ": " + token + " is wrong");
}else {
System.out.println("(" + "常数" + "," + token + ")");
}
if (p!=str.length()-1||(p==str.length()-1&&!Character.isDigit(str.charAt(p)))){
p--;
}
}

//标识符,关键字的识别
public static void letterCheck(String str){
StringBuffer sb = new StringBuffer(String.valueOf(str.charAt(p++)));
char ch;
for (; p < str.length(); p++){
ch = str.charAt(p);
if (!Character.isLetterOrDigit(ch) && ch!='_'){
break;
}else{
sb.append(ch);
}
}
String token = sb.toString();
if (keyWords.contains(token)){
System.out.println("(" + "关键字" + "," + token + ")");
}else {
System.out.println("(" + "标识符" + "," + token + ")");
}
if (p!=str.length()-1||(p==str.length()-1&&(!Character.isLetterOrDigit(str.charAt(p))&&str.charAt(p)!='_'))){
p--;
}
}

//符号的识别
public static void symbolCheck(String str){
String token = String.valueOf(str.charAt(p++));
char ch;
if (symbols.contains(token)){
System.out.println("("+"分隔符"+","+token+")");
p--;
}else {
if (operations.contains(token)){
if (p < str.length()){
ch = str.charAt(p);
if (operations.contains(token+ch)){
token += ch;
p++;
if (p < str.length()){
ch = str.charAt(p);
if (operations.contains(token + ch)){
token += ch;
System.out.println("("+"运算符"+","+token+")"); //三个字符的运算符
}else{
p--;
System.out.println("("+"运算符"+","+token+")"); //两个字符的运算符
}
}else{
System.out.println("("+"运算符"+","+token+")"); //两个字符的运算符
}
}else {
p--;
System.out.println("("+"运算符"+","+token+")"); //一个字符的运算符
}
}
}else {
p--;
System.out.println(lines+"line"+": "+token+" is wrong");
}
}
}

//字符串检查
public static void stringCheck(String str){
StringBuffer sb = new StringBuffer(String.valueOf(str.charAt(p++)));
char ch;
for (;p<str.length();p++){
ch=str.charAt(p);
sb.append(ch);
if (ch == '"'){
break;
}
}
String token = sb.toString();
if (token.charAt(token.length()-1)!='"'){
System.out.println(lines+"line"+": "+token+" is wrong");
}else {
System.out.println("("+"字符串"+","+token+")");
}
}
}

4、运行测试

注:测试文件为该程序代码本身,以下为部分结果截图

image-20210318204533140

-------------本文结束感谢您的阅读-------------