设计一个简易的词法分析器

在学习编译原理时,按照《程序设计语言编译原理》(学习教材),设计了一个简单的词法分析器的代码,可以实现对关键字、标识符,数字的识别,去掉空格符等。

下面对关键字和种别码的说明。

1、 功能:输入源程序、分析源程序的单词,输出单词

单词符号的种类如下:

(1)    关键字(本例所有关键字都是大写)。

DIM IF DO STOP END

(2)    标识符:表示各种名字,如数组名、过程名和变量名等。标识符(ID)通过以下正规式定义:

ID=letter(letter| digit)*

(3)    运算符:

=  +   *  ** , (  )

(4)常数

各种类型的常数标识符(NUM)   通过以下正规式定义:

NUM=digit digit *

(5)    空格由空白、制表符和换行符组成。空格一般用来分隔ID、NUM,运算符、界符和关键字,词法分析阶段通常被忽略。

2、    种别码:

单词符号

种别编码

内码值

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中的单词解析为整型常数。 

9)initKeysTable 函数,初始化关键字,将关键字放入程序。

4.设计流程图如下:

设计一个简易的词法分析器_第1张图片


5.程序代码,代码中基本都有注释,这里就不再作详细解释了:
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.调试结果:

设计一个简易的词法分析器_第2张图片

设计一个简易的词法分析器_第3张图片



程序写的并不完美,这里只是一个简单的展示,其中还有许多功能可以完善(例如对注释的剔除),

程序如有错误之处,欢迎下方留言指正。



你可能感兴趣的:(编译原理)