(1)词法分析概述
词法分析将Java源文件的字符流转变为对应的Token流。涉及到的主要类的继承关系如下图所示。
Lexer表示词法分析器,而实现这个接口的Scanner与DocCommentScanner可以将输入的ASCII字符流与Unicode转义字符转换为合法的Token流。其中的DocCommentScanner词法分析器扩展了Scanner的功能,可处理注释相关的部分。ScannerFactory是工厂类,可通过调用这个类的工厂方法获取到具体的词法分析器实例。
(2)Token类型
/** An interface that defines codes for Java source tokens
* returned from lexical analysis.
*/
public enum Token implements Formattable {
EOF,
ERROR,
IDENTIFIER, // 如类名、包名、变量名、方法名等
ABSTRACT("abstract"),
ASSERT("assert"),
BOOLEAN("boolean"),
BREAK("break"),
BYTE("byte"),
CASE("case"),
CATCH("catch"),
CHAR("char"),
CLASS("class"),
CONST("const"),
CONTINUE("continue"),
DEFAULT("default"),
DO("do"),
DOUBLE("double"),
ELSE("else"),
ENUM("enum"),
EXTENDS("extends"),
FINAL("final"),
FINALLY("finally"),
FLOAT("float"),
FOR("for"),
GOTO("goto"),
IF("if"),
IMPLEMENTS("implements"),
IMPORT("import"),
INSTANCEOF("instanceof"),
INT("int"),
INTERFACE("interface"),
LONG("long"),
NATIVE("native"),
NEW("new"),
PACKAGE("package"),
PRIVATE("private"),
PROTECTED("protected"),
PUBLIC("public"),
RETURN("return"),
SHORT("short"),
STATIC("static"),
STRICTFP("strictfp"),
SUPER("super"),
SWITCH("switch"),
SYNCHRONIZED("synchronized"),
THIS("this"),
THROW("throw"),
THROWS("throws"),
TRANSIENT("transient"),
TRY("try"),
VOID("void"),
VOLATILE("volatile"),
WHILE("while"),
INTLITERAL,
LONGLITERAL,
FLOATLITERAL,
DOUBLELITERAL,
CHARLITERAL,
STRINGLITERAL,
TRUE("true"),
FALSE("false"),
NULL("null"),
LPAREN("("),
RPAREN(")"),
LBRACE("{"),
RBRACE("}"),
LBRACKET("["),
RBRACKET("]"),
SEMI(";"),
COMMA(","),
DOT("."),
ELLIPSIS("..."),
EQ("="),
GT(">"),
LT("<"),
BANG("!"),
TILDE("~"),
QUES("?"),
COLON(":"),
EQEQ("=="),
LTEQ("<="),
GTEQ(">="),
BANGEQ("!="),
AMPAMP("&&"),
BARBAR("||"),
PLUSPLUS("++"),
SUBSUB("--"),
PLUS("+"),
SUB("-"),
STAR("*"),
SLASH("/"),
AMP("&"),
BAR("|"),
CARET("^"),
PERCENT("%"),
LTLT("<<"),
GTGT(">>"),
GTGTGT(">>>"),
PLUSEQ("+="),
SUBEQ("-="),
STAREQ("*="),
SLASHEQ("/="),
AMPEQ("&="),
BAREQ("|="),
CARETEQ("^="),
PERCENTEQ("%="),
LTLTEQ("<<="),
GTGTEQ(">>="),
GTGTGTEQ(">>>="),
MONKEYS_AT("@"),
CUSTOM;
// 省略相关的方法
}
大概可将如上定义的Token类别分为如下3类:
(1)标识符号:如Token.PLUS、Token.EQ、Token.LBRACE、Token.RBRACE、
Token.ELLIPSIS等,其中Token.ELLIPSIS是为了支持Java 7中增加的可变参数的语法。
(2)Java的保留关键字:
(3)Name对象的生成与存储
词法分析是从调用Java的parseFiles(Iterable<JavaFileObject> fileObjects)方法开始的,这个方法通过间接通过调用JavaFileObject的getCharContent()获取字符流输入,这一部分在第二间中有详细介绍,这里为节省篇幅不再重复介绍。将字符流转Token最重要的就是Scanner中的nextToken()方法了,可能读者还不太明白获取到字符流后是如何调用nextToken()方法的,下一章节将会详细的说明。
在Java中,哪些字符组合成为一个Token是通过调用nextToken方法实现的,这个方法决定了字符的组合,大概的逻辑如下:
调用nextToken生成的字符集合都是一个Name对象,所有的Name对象都存储在Name.Table这个内部类中。接下来可以通过Keywords类查找到Name对象所对应的Token类型。由于Keywords会将在Token中所有的元素按照它们的Token.name先转化成Name对象,然后建立Name和Token的对应关系,这个关系保存在Keyworks类的key数组中。
每调用一次方法就会构造一个Token,而这些Token必然是com.sun.tools.javac.parser.Token中的任何元素之一。
(4)映射Token
Keywords会将在Token中所有的元素按照它们的Token.name先转化成Name对象,然后建立Name和Token的对应关系,这个关系保存在Keyworks类的key数组中。 Keywords类
中定义了关键的属性如下:
/** The names of all tokens.
*/
private Name[] tokenName = new Name[values().length];
tokenName保存了Token到name的映射,准确说是tokenName的下标为各个Token在枚举类中定义的序号,序号是从0开始的。如下:
Name[0]=null;
Name[1]=null;
Name[2]=null;
Name[3]=NameImpl("abstract");
Name[4]=NameImpl("assert");
...
Name[108]=NameImpl(">>>=");
Name[109]=NameImpl("@");
Name[110]=null;
可以看到有Token名称的都被转换为了NameImpl对象。这样我们就可以根据Token找到对应的NameImple了。
定义了Name到Token的映射,如下:
/**
* Keyword array. Maps name indices to Token.
*/
private final Token[] key;
同样在调用构造函数时进行初始化,具体的初始化过程如下:
protected Keywords(Context context) {
// ...
key = new Token[maxKey+1];
for (int i = 0; i <= maxKey; i++) {
key[i] = IDENTIFIER;
}
for (Token t : values()) {
if (t.name != null) {
int oi = t.ordinal();
int ti = tokenName[oi].getIndex();
key[ti] = t;
}
}
}
其中的maxKey保存了??的最大值。调用Name对象的getIndex()方法为key数组下标赋值,而值为这个Token对象,这样我们可以在最短时间内根据Name对象获取对应Token了。
例如现在从字符流对象中查找到一串字符串"abstract",首先转换为Name对象,接着调用此Name对象的getIndex()方法获取到值后可直接将值做为key数组的下标获取到Token。如调用如下Keywords中的方法:
public Token key(Name name) {
if(name.getIndex() > maxKey){
return IDENTIFIER;
}else{
return key[name.getIndex()];
}
}
此方法还根据maxKey来判断是否为标识符,假如某个字符串为自定义标识符时会返回IDENTIFIER。