在学习编译原理时,按照《程序设计语言编译原理》(学习教材),设计了一个简单的词法分析器的代码,可以实现对关键字、标识符,数字的识别,去掉空格符等。
下面对关键字和种别码的说明。
1、 功能:输入源程序、分析源程序的单词,输出单词
单词符号的种类如下:
(1) 关键字(本例所有关键字都是大写)。
DIM IF DO STOP END
(2) 标识符:表示各种名字,如数组名、过程名和变量名等。标识符(ID)通过以下正规式定义:
ID=letter(letter| digit)*
(3) 运算符:
= + * ** , ( )
(4)常数
各种类型的常数标识符(NUM) 通过以下正规式定义:
NUM=digit digit *
(5) 空格由空白、制表符和换行符组成。空格一般用来分隔ID、NUM,运算符、界符和关键字,词法分析阶段通常被忽略。
单词符号 |
种别编码 |
内码值 |
DIM |
1 |
- |
IF |
2 |
- |
DO |
3 |
- |
STOP |
4 |
- |
END |
5 |
- |
标识符 |
6 |
内部字符串 |
常数(整) |
7 |
标准二进制形式 |
= |
8 |
- |
+ |
9 |
- |
* |
10 |
- |
** |
11 |
- |
, |
12 |
- |
( |
13 |
- |
) |
14 |
- |
当然,如果你需要使用其它语言的关键字和运算符的话,在我的程序中也很好改。
3. 输入与输出:
输入:源程序字符串。
输出:<种别码,单词自身的值>
3. 设计流程:
(1)输入、预处理
词法分析器工作的第一步是输入源程序文本。为了更好地对单词符号识别,可以把输入串预处理一下。预处理的作用主要是跳过注释、剔除无用的空格,回车、换行符等编辑性字符。(在这里我只做了对空格符的跳转。。。)
2.超前搜索
在词法分析时,需要超前搜索,即有时需超前扫描若干个字符,才能确定该单词。
3.:主要全局变量的定义与函数说明
变量
1)ch 字符变量、存放最新读入的源程序字符
2)strToken 字符数组,存放构成单词符号的字符串
3)keysTable 字符串数组,存放关键字。
函数
1)GetChar 函数,把下一个字符读入到 ch 中
2)GetBC 函数,跳过空白符,直至 ch 中读入一非空白符
3)Concat 函数,把ch中的字符存入到 strToken
4)IsLetter和 IsDigit 布尔函数,判断ch中字符是否为字母和数字
5) Reserve 整型函数,对于 strToken 中的字符串查找保留字表,若它是保留字则给出它的编码,否则回送0
6) Retract 函数,把搜索指针回调一个字符位置
7)InsertId 函数,将strToken中的单词解析为字符串,即标识符。
8)InsertConst 整型函数过程,将strToken中的单词解析为整型常数。
4.设计流程图如下:
import java.util.HashMap;
import java.util.Map;
public class LexcicalAnalyzer {
private static char ch; // 存放最新读入的源程序字符
private static char[] strToken; // 存放构成单词符号的字符串
private static int sp; // strToken 的读取指针的索引位置
private String program ; // 存放源程序的字符内容
private int len; // 源程序的字符长度
private int cp; // 当前读取字符的索引位置
private int code ; // 保存单词的类别码
private Map keysTable; // 保留字表
public LexcicalAnalyzer() {
strToken = new char[20];
cp = 0;
initKeysTable();
}
public void Analyzer(String program) {
this.program = program;
len = program.length();
code = -1;
cp = 0;
while (cp < len) {
resetStrToken();//每次分析一个单词前要将strToken重置
getChar();
getBC();
if(ch == '\0')break;//结束此次分析
if (isLetter()) {
// 超前搜索,并将字符写入到strToken
while (isLetter() || isDigit()) {
concat();
getChar();
}
Retract();
code = reserve();
// 如果code为0则说明,strToken中存放的的是标识符,将code设置为6,对应标识符
if (code == 0) {
code = 6;
}
}
// 如果ch是字母,则本次读取到strToken的就是数字常量
else if (isDigit()) {
while (isDigit()) {
concat();
getChar();
}
Retract();
//将code设置为7,对应数字常量
code = 7;
} else if (ch == '*') {//如果遇见‘*’,进行超前搜索,判断是‘*’还是‘**’
concat();
getChar();
if (ch == '*') {// 如果是‘**’
concat();
}else{// 否则就是‘*’,
Retract();
}
code = reserve();
} else if ((ch == '=') || (ch == '+') || (ch == ',') || (ch == '(')
|| (ch == ')')) {
concat();
code = reserve();
} else {
code = -1;//将code置为-1,代表单词错误。
}
print();
}
}
/**
* 初始化保留字表
*/
private void initKeysTable() {
keysTable = new HashMap();
String[] keys = { "DIM", "IF", "DO", "STOP", "END", "=", "+", "*",
"**", ",", "(", ")" };
for (int i = 0; i < 12; i++) {
if (i < 5) {
keysTable.put(keys[i], i + 1);
} else {
keysTable.put(keys[i], i + 3);
}
}
}
/**
* 将下一个字符读入ch
* @return
*/
private void getChar() {
if(cp < len){
ch = program.charAt(cp);
cp++;
}else{
ch ='\0';//设置结束符
cp = len+1;
}
}
/**
* 跳过空白符,直至 ch 中读入一非空白符
*/
private void getBC() {
while (ch == ' ') {
getChar();
}
}
/**
* 把ch中的字符连接到 strToken
*/
private void concat() {
strToken[sp++] = ch;
}
/**
* 判断ch字符是不是字母
* @return
*/
private boolean isLetter() {
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
return true;
}
return false;
}
/**
* 判断ch字符是不是数字
*
* @return
*/
private boolean isDigit() {
if (ch >= '0' && ch <= '9') {
return true;
}
return false;
}
/**
* 对于 strToken 中的字符串查找保留字表, 若它是保留字则给出它的编码,否则回送0
*/
private int reserve() {
//s保存的是strToken的有效字符(非空字符)
String s = "";
for(char c : strToken){
if(c == ' ')continue;
s+=c;
}
//这里将词转化为大写,因为保留字表里的保留字存放的都是大写,使词法分析时不区分大小写。
String key = s.toUpperCase();
if (keysTable.containsKey(key)) {// 如果是保留字,则返回相对应的种类编码
int code = keysTable.get(key);
return code;
}
return 0;
}
/**
* 把搜索指针回调一个字符位置
*/
private void Retract() {
cp--;
}
/**
* 将strToken中的标识符插入符号表 返回符号表指针
*
* @return
*/
private String InsertId(char[] str) {
String s = "";
for(int i=0;i");
}else if(code == 7){
System.out.println("<" + code + "," + InsertConst(strToken) + ">");
}else{
System.out.println("<" + code + "," + "-" + ">");
}
}
/**
* 提示错误的方法
*/
private void showError() {
System.out.println("\'" + ch + "\'" + " is error!");
}
/**
* 重置strToken字符数组的值
*/
private void resetStrToken() {
for (int i = 0; i < 20; i++) {
strToken[i] = ' ';
}
sp = 0;
}
}
测试代码如下:
public class testAnalyzer {
public static void main(String[] args) {
String input1 = "Dim if do stop end num1 1234 = + * ** , ( )";
String input2 = "if 1234567890 stop ; ";
String input3 = "num = 1234*234, if 1 do num**";
String input4 = "str = myName";
LexcicalAnalyzer analyzer = new LexcicalAnalyzer();
System.out.println(input1);
analyzer.Analyzer(input1);
System.out.println();
System.out.println(input2);
analyzer.Analyzer(input2);
System.out.println();
System.out.println(input3);
analyzer.Analyzer(input3);
System.out.println();
System.out.println(input4);
analyzer.Analyzer(input4);
System.out.println();
}
}
6.调试结果:
程序写的并不完美,这里只是一个简单的展示,其中还有许多功能可以完善(例如对注释的剔除),
程序如有错误之处,欢迎下方留言指正。