扫描器(Scanner,词法分析器)是编译器/解释器前端的一个组件,用来执行读取源程序,将程序分成Token等语法动作。解析器(语法分析器)每次需要从源程序获取Token时就会调用扫描器。本章你将会为Pascal Token设计和开发一个扫描器。
==>> 本章中文版源代码下载:svn co http://wci.googlecode.com/svn/branches/ch3/ 源代码使用了UTF-8编码,下载到本地请修改!
目标和方法
前面章节中,你已在前端实现一个语言无关的Scanner框架类和一个初级版本与Pascal有关的框架子类PascalScanner。本章的目标是完成Pascal扫描器的设计与开发并测试它。扫描器将能够:
- 从源程序中抽取Pascal单词,数字,字符串以及特殊符号Token(比如+,:= 等)。
- 判断一个单词Token是一个标识符(identifier,一般标识变量名称,程序名称,过程/函数名称等)还是一个Pascal关键字(reserved word)。
- 计算数字token的值并判断它是一个整数还是实数(real,包含小数点)。
- 处理语法错误。
本章的方法是开发一系列可被PascalScanner创建的Pascal token子类。你将创建一个框架类Token的子类PascalToken并接着开发PascalToken的子类用来表示Pascal单词,数字,特殊符号token等。你会定义Pascal Token类型并实现上一章的TokenType接口。
图3-1 概括了我们的扫描器设计
图3-1:Pascal相关的Scanner和Token类
灰色部分表示你已在frontend包中实现的语言无关的框架类。现在你将完成frontend.pascal包中的Pascal子类。你也将创建frontend.pascal.tokens包用来容纳PascalToken子类。
| 设计笔记 |
| 定义将单个的Pascal Token类型子类分开是策略设计模式的另一个例子。这儿的策略是从源文件中创建各种token类型的算法。在每个子类封装自己策略(通过extract()方法实现)使得代码更加模块化,也使得扫描器能够更容易的选择合适的token类型去创建。所有token子类将继承自PascalToken类,而PascalToken继承自语言无关的Token类。 图3-1 也演示了上一章开始处提到的设计准则:永远基于可运行代码进行构建。此时你正在给早些时候开发的框架添加子类。 |
就如第一章提到的,扫描器也称词法分析器,它的扫描操作也称词法分析。Token同样有另一个名字:Lexeme(词单位)
程序-3:Pascal分词器
从frontend.pascal包中,上一章开发的解析器子类PascalParserTD开始,扩展它的parser方法以便练习和测试整个Pascal扫描器。尽管你还没有编写它需要的各种token子类,parser方法还是展示了怎样扫描各种Pascal Token并处理错误。见清单3-1。
清单3-1:PascalParserTD类中的Parse和getErrorCount方法
extends Parser {
2:
new PascalErrorHandler();
4:
public PascalParserTD(Scanner scanner) {
super(scanner);
7: }
8:
/**
* Pascal的解析过程,产生Pascal相关的iCode和symbol table
*/
throws Exception {
13: Token token;
long startTime = System.currentTimeMillis();
15:
try {
instanceof EofToken)) {
18: TokenType tokenType = token.getType();
if (tokenType != PascalTokenType.ERROR) {
new Message(MessageType.TOKEN,
new Object[] { token.getLineNumber(),
22: token.getPosition(), token.getType(),
23: token.getText(), token.getValue() }));
else {
// 留意当token有问题是,它的值表示其错误编码
26: errorHandler.flag(token,
this);
28: }
29: }
// 发送编译摘要信息
float elapsedTime = (System.currentTimeMillis() - startTime) / 1000f;
new Number[] {
33: token.getLineNumber(), getErrorCount(), elapsedTime }));
catch (IOException e) {
this);
36: }
37: }
38:
int getErrorCount() {
return errorHandler.getErrorCount();
41: }
42: }