一、学习说明
1.1 学习目标
加强对词法分析器的工作原理的理解;提高对词法分析方法的熟练程度;能够使用编程语言编写简单的词法分析程序;能够利用自己编写的分析程序对简单的代码段进行词法分析。
1.2 学习内容
本次学习的重点在于深入研究词法分析器的设计,并实现一个功能相对完整的词法分析程序。
通过实践学习词法分析器将源代码统一转换为内部表示形式——TOKEN流的过程。实现一个简单的词法分析程序,需要先能够识别并处理基本的单词符号,如标识符、关键字、常量、运算符和界符。完成学习需要理解各种单词的构词规则,还需要设计和实现一个程序来正确识别源代码中的单词符号,并以<种别码,值>的形式保存在符号表中。程序还需要对源代码中的词法错误进行基本的处理,并给出错误提示。此学习可以帮助明确词法分析器的基本功能和工作原理、词法单元的分类与识别。
二、设计实现
2.1 存储结构与主要变量
本次学习的主要变量及其存储结构如下表1所示。
表1 主要变量表
存储结构 | 主要变量 | 作用 |
ArrayList<String> | keyWords | 用于存储编程语言中的关键字,如"private"、"public"、"class"等 |
ArrayList<String> | operations | 用于存储各种运算符,如"+"、"-"、"*"、"/"等 |
ArrayList<String> | symbols | 用于存储各种界符,如","、";"、"("、")"等 |
int | p | 静态变量,在代码中作为指针,用于遍历输入字符串并控制词法分析的位置。 |
int | lines | 追踪行数,帮助在词法分析过程中定位和记录相关信息。 |
String | token | 识别和表示源代码中各种词法单元 |
String[] | keyWord | 静态数组,用于存储编程语言中预定义的关键字 |
String[] | operation | 静态数组,用于存储编程语言中预定义的运算符标志 |
String[] | symbol | 静态数组,用于存储编程语言中预定义的界符 |
2.2 算法思路与方法实现
2.2.1 总体思路
1.初始化并定义关键字、运算符、界符等词法单元集合。
2.读取测试数据文件,并逐行、逐词进行词法分析。
3.对每个字符进行分类判断:
如果是数字,则输出数字类型的结果。
如果是字母或下划线,则根据是否在关键字链表中判断是标识符类型或关键字的结果。
如果不属于上述任何一类,则判断其是否为界符或运算符,输出相应结果或错误信息。
4.循环执行上述步骤,直到分析了所有的测试数据,完成词法分析。
核心方法 digitCheck(String str) 用于检测数字,letterCheck(String str) 用于检测字母或下划线,symbolCheck(String str) 用于检测界符和运算符。这些方法的作用是对输入的字符串进行分类判断,以便进行相应的输出或处理。
2.2.2 digitCheck(String str)方法
1.初始化变量,初始化变量token存储解析出的token,初始为字符串第一个字符;初始化变量flag用于记录小数点的个数,初始为0;初始化变量err用于标记是否出现错误,初始为false;初始化变量ch用于存储当前处理的字符。
2.循环解析字符,从字符串的第一个字符开始循环解析每个字符。
如果字符是空格或者不是字母数字且不是点号,则跳出循环;否则根据字符类型进行处理。
如果err为true,则将字符添加到token中。
如果err为false:如果是数字或者点号,则将字符添加到token中,并根据情况更新flag或将err置为true。
如果是字母,则将err置为true。
3.输出结果,如果没有出现错误(err为false),则输出符合格式的 token (3, token);如果出现错误(err为true),则输出错误信息。
public static void digitCheck(String str){
String token= String.valueOf(str.charAt(p++)); // abc12.3.456
int flag=0;
boolean err=false;
char ch;
for (;p<str.length();p++) {
ch = str.charAt(p);
if (ch==' '||(!Character.isLetterOrDigit(ch)&&ch!='.')) {//!>,这些符号时break
break;//遇到空格,非字母数字和点号停止
}else if (err){
token+=ch;
}
else {//是数字或者. ,连起来
token+=ch;
if (ch == '.') {//是小数点的情况
if (flag == 1) {
err = true;
} else {//flag为0,则自加1
flag++;
}
}else if (Character.isLetter(ch)){//是字母则报错
err=true;
}
}
}
if (err==false){
System.out.println("(3,"+token+")");
}else{
System.out.println(lines +"line: " + token +" is wrong");
}
}
2.2.3 letterCheck(String str)方法
1.初始化一个空字符串token用于存储检测到的单词。
2.使用变量p作为字符串的索引,遍历输入的字符串str。
3.在while循环中,检查当前字符是否是字母或下划(Character.isLetter(str.charAt(p))||str.charAt(p) == '_'),如果是,则将该字符添加到token中,并将索引p向后移动。
4.如果遇到非字母或下划线字符,停止遍历,将索引p减一(即回退一个位置)。
5.检查token是否是关键字(假设keyWords是一个包含所有关键字的集合)。
6.如果token是关键字,则输出(1, token),否则输出(2, token)。
public static void letterCheck(String str){
String token="";
char ch;
while(p<str.length() && (Character.isLetter(str.charAt(p)) || str.charAt(p) == '_')){
ch=str.charAt(p);
token+=ch;
p++;
}
p--;
if(keyWords.contains(token)){
System.out.println("(1,"+token+")");
}else{
System.out.println("(2,"+token+")");
}
}
2.2.4 symbolCheck(String str)方法
1.通过索引p获取字符串str中对应位置的字符,并将其转换为字符串类型存储在变量sym中。
2.检查字符sym是否在预定义的symbols集合中。
3.如果字符在symbols集合中,则输出(5, sym),表示该字符是一个符号。
4.如果字符不在symbols集合中,进入下一个判断。
5.继续检查字符是否在operations集合中。
6.如果字符在operations集合中,则输出(4, sym),表示该字符是一个操作符。
7.如果字符既不在symbols集合中,也不在operations集合中,说明该字符是无效的,输出错误信息,指示该字符在第lines行是不正确的。在输出错误信息时,会显示当前行数lines和错误的字符sym。
public static void symbolCheck(String str){
String sym =String.valueOf(str.charAt(p));
if (symbols.contains(sym)){
System.out.println("(5,"+sym+")");
}else if(operations.contains(sym)){
System.out.println("(4,"+sym+")");
}else{
System.out.println(lines+"line: "+sym+" is wrong");
}
}
2.3 测试数据
通过对测试数据的逐步分析,可以得到预测的结果。
public class Hello{
public static void main(String args[]){
System.out.println("Hello World");
}
}
2.4 运行结果
运行结果如下图1所示。本次学习结果与预期相符,在头歌实验平台通过了全部测试案例,与标准答案完全匹配。
2.5总代码
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
public class ex1 {
/*
* 1表示关键字
* 2表示标识符
* 3表示常数
* 4表示运算符
* 5表示界符
* 6表示字符串
* */
//关键字
static String []keyWord={"private","protected","public","abstract","class","extends","final","implements",
"interface","native","new","static","strictfp","break","continue","return","do","while","if","else","for",
"instanceof","switch","case","default","boolean","byte","char","double","float","int","long","short",
"String","null","true","false","void","this","goto"};
//运算符
static String []operation={"+","-","*","/","%","++","--","-=","*=","/=","&","|","^","~","<<",">>",">>>","==","!=",
">","<","=",">=","<=","&&","||","!","."};
//界符
static String []symbol={",",";",":","(",")","{","}"};
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 {
init();
File file=new File("/data/workspace/myshixun/input.txt");
lines=1;
try(Scanner input=new Scanner(file)) {
while (input.hasNextLine()){
String str=input.nextLine();
analyze(str);
lines++;
}
}
}
//初始化把数组转换为ArrayList
public static void init(){
keyWords=new ArrayList<>();
operations=new ArrayList<>();
symbols=new ArrayList<>();
Collections.addAll(keyWords, keyWord);
Collections.addAll(operations, operation);
Collections.addAll(symbols, symbol);
}
public static void analyze(String str){
p=0;
char ch;
str=str.trim();
for (;p<str.length();p++){
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==' '){
continue;
}else {
symbolCheck(str);
}
}
}
/********Beign********/
/*数字的识别*/
public static void digitCheck(String str){
String token= String.valueOf(str.charAt(p++)); // abc12.3.456
int flag=0;
boolean err=false;
char ch;
for (;p<str.length();p++) {
ch = str.charAt(p);
if (ch==' '||(!Character.isLetterOrDigit(ch)&&ch!='.')) {//!>,这些符号时break
break;//遇到空格,非字母数字和点号停止
}else if (err){
token+=ch;
}
else {//是数字或者. ,连起来
token+=ch;
if (ch == '.') {//是小数点的情况
if (flag == 1) {
err = true;
} else {//flag为0,则自加1
flag++;
}
}else if (Character.isLetter(ch)){//是字母则报错
err=true;
}
}
}
if (err==false){
System.out.println("(3,"+token+")");
}else{
System.out.println(lines +"line: " + token +" is wrong");
}
// String nums="";
// char ch;
// while(p<str.length()&&Character.isDigit(str.charAt(p))){
// ch=str.charAt(p);
// nums+=ch;
// p++;
// }
// p--;
// System.out.println("(3,"+nums+")");
}
/********End********/
/********Beign********/
//标识符,关键字的识别
public static void letterCheck(String str){
String token="";
char ch;
while(p<str.length() && (Character.isLetter(str.charAt(p)) || str.charAt(p) == '_')){
ch=str.charAt(p);
token+=ch;
p++;
}
p--;
if(keyWords.contains(token)){
System.out.println("(1,"+token+")");
}else{
System.out.println("(2,"+token+")");
}
}
/********End********/
/********Beign********/
//符号的识别
public static void symbolCheck(String str){
String sym =String.valueOf(str.charAt(p));
if (symbols.contains(sym)){
System.out.println("(5,"+sym+")");
}else if(operations.contains(sym)){
System.out.println("(4,"+sym+")");
}else{
System.out.println(lines+"line: "+sym+" is wrong");
}
}
/********End********/
//字符串检查
public static void stringCheck(String str){
String token= String.valueOf(str.charAt(p++));
char ch;
for (;p<str.length();p++){
ch=str.charAt(p);
token+=ch;
if (ch=='"'){
break;
}
}
if (token.charAt(token.length()-1)!='"'){
System.out.println(lines +"line: " + token +" is wrong");
}else {
System.out.println("("+6+","+token+")");
}
}
}