《两周自制脚本语言》第三天:词法分析器

第三天要实现的功能是词法分析器,但并不是完全自己从头写,而是利用java正则表达式(regular expression)及java中的几个类库实现。由于时间原因,只是说明了一般方法,而并未进行代码实现。

1、简介
词法分析是对将源代码分为多个子字符串。在平时的开发工程中,代码的书写都是有着各种各样的缩进以及空格的,但是类似换行是可用专门的(不可见)换行符表示的,这样所有的代码就成了一长串的字符串,将这些字符串按照事先定义的类型进行分组,就得到了多个子字符串。我们使用一个名为Token的对象记录该单词对应的字符串,并且保存单词的类型,以及单词所处位置行号等信息。Stone语言将定义三种单词类型,分别是:标识符(包括变量名、函数名、类名以及+,-等运算符、标点符号),整型字面量,字符串字面量(这里简单定义为双引号中包含的字符序列),将它们都定义为子类,并都具备isIdentifier,isNumber,isString方法,用以判断单词类型。

2.定义正则表达式匹配模式

(1)定义字符串字面量匹配模式:[0-9+]
(2)定义标识符匹配模式:[A-Z_a-z][A-Z_a-z0-9]*|==|<=|>=|&&|\|\|\p{Punct}
对以上正则表达式进行简要说明:
|\|\| #匹配|| \p{Punct} #与任一符号字符匹配

(3)定义字符串字面量:

"(\\|\\\\|\\n|[^"])*"

对以上正则表达式进行简要说明:

"(\\"|	#第一个竖线左边表示匹配一个"\"
\\\\|	#第二个竖线左边表示匹配"\\"
\\n|	#第三个竖线左边表示匹配"\n"
[^"]	#表示匹配除'	"  '外的字符

我们将借助java.util.regex设计词法分析器,定义类似以下正则匹配模式:

\S*((//.*)|(pat1)|(pat2)|(pat3)?

对以上正则表达式进行简要说明:

\S*	#与0个及0个以上空字符匹配
(//.*)	#匹配注释(Stone中的注释定义为"//")
(pat1)	#匹配整型字面量
(pat2)	#匹配字符串字面量
(pat3)	#匹配标识符

匹配时,将逐行读取源代码,并从头开始检查与以上模式进行匹配,重复简单哪一个括号对应的不是null,即匹配除了单词的模式,也确定出了其类型。

3.java中的语法分析器
Lexer类是一个词法分析器,Lexer对象的构造函数用以接收一个java.io.Reader对象,可用以逐行读取源码。将正则表达式保存在regxPat字段。

Lexer的两个方法,read()将返回一个新单词;peek()进行预读,事先能够知道在调用read()时能够获得什么单词。peek(i)将返回read()即将返回的单词之后的第i个单词。当所有单词读取完毕时,read()和peek()将返回Token.EOF.

语法分析之后开始构造抽象语法树,这里存在一个问题,即如果抽象语法树的构造出错,该如何处理?第一种方式是可以取消前几次read()调用,还原结果,退后重构(即回溯);也可以采用第二种方式,因为peek()被定义为事先获取单词,因此可以利用peek()方法先进行判断,确定无误之后再进行构造。既然peek()有read()所不具备的好处,是否read()的存在多余呢?答案是否定的。peek()由于事先获知单词,因此必须保存所有返回的单词,这样无意中增加了内存消耗。而read()的返回值不必保留。

4.语法分析器的内部执行流程
(1)首先使用peek()或和ead()方法读取代码,获取单词,read()方法会进行取值并将值返回存在queue对象中。
(2)然后调用事先编译的Pattern对象的matcher()方法,用于获取Matcher对象用于实际的检查匹配。
(3)一方面通过region()限定检查范围,一方面调用lookingAt()进行匹配,调用group()方法获取与各括号对应的子字符串,用end()获取匹配结束位置,反复这一操作,直到所有单词均被匹配。

你可能感兴趣的:(语言开发)