现在,让我们来动手写编译器的第一个个java文件吧。本章要写的类,是Token类。如其名字所示,这个类实例化的对象用于表示词法分析器 Tokenizer 的产物。同时,也作为下一阶段的语法分析器 Parser 的原料。
让我们开始吧!先新建一个Token.java 于 src/com/taozeyu/taolan/analysis之中。
package com.taozeyu.taolan.analysis;
public class Token {
public static enum Type {
Keyword, Number, Identifier, Sign, Annotation,
String, RegEx, Space, NewLine, EndSymbol;
}
final Type type;
final String value;
Token(Type type, String value) {
//TODO
}
}
如之前章节讨论的一样,Token对象应该包含类型和语素两个属性。注意这个 Type 枚举类型,其内容就是我在上一章所说的 tao 语言应该具备的10种单词类型。
我希望词法分析器从源代码中提取出语素,并根据上下文推测出单词类型,从而构造出Token对象。但实际上,请注意Type这个枚举类的三个类型:
Keyword, Number, Identifier
这三个类型不同之处?实际上这三个类型的形式极其类似(甚至 Keyword 和 Identifier 的形式是完全相同的),并且可以仅通过语素准确判定其类型。因此,我希望对词法分析器 Tokenizer 隐藏着三种类型的区别,将这三种类型统称 Identifier,以简化编码。
Token(Type type, String value) {
if(type == Type.Identifier) {
char firstChar = value.charAt(0);
if(firstChar >= '0' & firstChar < '9') {
type = Type.Number;
} else if(keywordsSet.contains(value)){
type = Type.Keyword;
}
}
this.type = type;
this.value = value;
}
于是,Token 对 Tokenizer 隐藏了 Number、Keyword 类型。Tokenizer 只需要构造出 Identifier 类型即可,进一步细分将在 Token 的构造函数中进行。
特别的,构造函数中引用了一个 keywordsSet 变量。实际上这个变量应该包含所有 tao 语言的关键字。此处稍稍定义一下。
private static final HashSet<String> keywordsSet = new HashSet<>();
static {
keywordsSet.add("if");
keywordsSet.add("when");
keywordsSet.add("elsif");
keywordsSet.add("else");
keywordsSet.add("while");
keywordsSet.add("begin");
keywordsSet.add("until");
keywordsSet.add("for");
keywordsSet.add("do");
keywordsSet.add("try");
keywordsSet.add("catch");
keywordsSet.add("finally");
keywordsSet.add("end");
keywordsSet.add("def");
keywordsSet.add("var");
keywordsSet.add("this");
keywordsSet.add("null");
keywordsSet.add("throw");
keywordsSet.add("break");
keywordsSet.add("continue");
keywordsSet.add("return");
keywordsSet.add("operator");
}
好吧,tao 语言我能想出的可能有的关键字都在这里了。如果有遗漏或者多余,其实以后再回过头来改也没问题。
特别的,对于 Annotation、String、RegEx ,它们在源代码中存在的形式和具体的语素并不完全等同。
- #我是注释(回车)
- "我是一个字符串"
^\s+\d+$
- 对于 Tokenizer 而言,它倾向于读出如上一整行信息。但是仅仅只加下划线的文字是Token的语素。因此,我还需要再构造函数中对构造参数value进行进一步提取,以得到正确的语素。
另外,EndSymbol 的语素必须为空,不管 Tokenizer 传入什么参数都必须如此。