javac源码详解openJDKSE8版本2Lexer源码详解
lexer概述
- Lexer:Lexer主要不停扫描源文件,根据文法将源文件转成token。主要的方法为nextToken(计算下一个token)和token(计算当前的探针下的token)和token(在token缓冲池中寻找特定位置的token)
- Scanner: Scanner是Lexer的实现类。主要的属性有:tokens(token工具类),token,prevToken, savedTokens(token的缓冲池),tokenizer(真正把java文件转成token的类)
- Tokens: Tokens是token工具类。主要的属性有:names(name工具类),key(TokenKind),tokenName(所有token的name的容器)
- Names: Names是name工具类。主要提供进入tokenName这个容器的方法。
- TokenKind: TokenKind是token的种类。有两个属性:name(比如break,int等等)和tag(标签分为:DEFAULT,NAMED,STRING,NUMERIC)。
- Token: Token主要有属性:kind(种类),pos(起点位置),endPos(结束位置),comments(token上的注释)。又有子类NamedToken(token的名字),NumericToken(token的进制),StringToken(String的值)
- JavaTokenizer: 真正把java文件转成token的类。比较重要的属性有reader(读java源文件)。比较重要的方法:readToken(读下一个token)
lexer源码详解
入口
从com.sun.tools.javac.main.JavaCompiler的860行
// These method calls must be chained to avoid memory leaks
delegateCompiler =
processAnnotations(
enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))),
classnames);
一直点进来到com.sun.tools.javac.parser.JavaTokenizer的450行
/** Read token.
*/
public Token readToken() {
reader.sp = 0;
name = null;
radix = 0;
转换源文件的token主要方法readToken()
总体上是:主循环通过不停的扫描字符,根据当前字符和后面的字符,来进行判定需要进行什么样的处理,即生成什么样的token。
- 当碰到空格等时,一直读下一个字符,直到字符不再是空格等时,进行processWhiteSpace过程。
- 当碰到换行符时,进行processLineTerminator过程。
- 当碰到大小写字母和美元符和下划线时,进行scanIdent过程。
- 当碰到0时。在读一个字符下一个字符是x或者X时,设置进制为16,进行scanNumber过程;在读一个字符下一个字符是b或者B时,设置进制为2,进行scanNumber过程;在读一个字符下一个字符是b或者B时,设置进制为2,进行scanNumber过程;否者放入字符‘0‘,设置进制为8,进行scanNumber过程。当然过程中也有可能会去处理double或者float类型的token
- 当碰到1-9时,设置进制为10,进行scanNumber过程。
- 当碰到.时,读入后面的字符,判断是否为double或者float类型的token或者不确定数量的参数符号…或者是符号.
- 当碰到()[]{}时,直接生成对应的token
- 当碰到/时,认为是注解或者是特殊字符。注解分为//和JavaDoc注解和/***/注解
- 当碰到’时,读入下一个字符,判断是8进制数或者是一个字符
- 当碰到"时,一直读直到字符为",生成字符串token
- 当碰到!%&*?±<=>^|~@时,进入scanOperator过程
- 其他情况,如果符合java的命名规范则进行scanIdent,否则读入下一个字符
- 跳出主循环后根据tokenKind的Tag,生成对应的token
/** Read token.
*/
public Token readToken() {
reader.sp = 0;
name = null;
radix = 0;
int pos = 0;
int endPos = 0;
List<Comment> comments = null;
try {
loop: while (true) {
pos = reader.bp;
switch (reader.ch) {
case ' ': // (Spec 3.6)
case '\t': // (Spec 3.6)
case FF: // (Spec 3.6)
do {
reader.scanChar();
} while (reader.ch == ' ' || reader.ch == '\t' || reader.ch == FF);
processWhiteSpace(pos, reader.bp);
break;
case LF: // (Spec 3.4)
reader.scanChar();
processLineTerminator(pos, reader.bp);
break;
case CR: // (Spec 3.4)
reader.scanChar();
if (reader.ch == LF) {
reader.scanChar();
}
processLineTerminator(pos, reader.bp);
break;
case 'A': case 'B': case 'C': case 'D': case 'E':
case 'F': case 'G': case 'H': case 'I': case 'J':
case 'K': case 'L': case 'M': case 'N': case 'O':
case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y':
case 'Z':
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f': case 'g': case 'h': case 'i': case 'j':
case 'k': case 'l': case 'm': case 'n': case 'o':
case 'p': case 'q': case 'r': case 's': case 't':
case 'u': case 'v': case 'w': case 'x': case 'y':
case 'z':
case '$': case '_':
scanIdent();
break loop;
case '0':
reader.scanChar();
if (reader.ch == 'x' || reader.ch == 'X') {
reader.scanChar();
skipIllegalUnderscores();
if (reader.ch == '.') {
scanHexFractionAndSuffix(pos, false);
} else if (reader.digit(pos, 16) < 0) {
lexError(pos, "invalid.hex.number");
} else {
scanNumber(pos, 16);
}
} else if (reader.ch == 'b' || reader.ch == 'B') {
if (!allowBinaryLiterals) {
lexError(pos, "unsupported.binary.lit", source.name);
allowBinaryLiterals = true;
}
reader.scanChar();
skipIllegalUnderscores();
if (reader.digit(pos, 2) < 0) {
lexError(pos, "invalid.binary.number");
} else {
scanNumber(pos, 2);
}
} else {
reader.putChar('0');
if (reader.ch == '_') {
int savePos = reader.bp;
do {
reader.scanChar();
} while (reader.ch == '_');
if (reader.digit(pos, 10) < 0) {
lexError(savePos, "illegal.underscore");
}
}
scanNumber(pos, 8);
}
break loop;
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
scanNumber(pos, 10);
break loop;
case '.':
reader.scanChar();
if ('0' <= reader.ch && reader.ch <= '9') {
reader.putChar('.');
scanFractionAndSuffix(pos);
} else if (reader.ch == '.') {
int savePos = reader.bp;
reader.putChar('.'); reader.putChar('.', true);
if (reader.ch == '.') {
reader.scanChar();
reader.putChar('.');
tk = TokenKind.ELLIPSIS;
} else {
lexError(savePos, "illegal.dot");
}
} else {
tk = TokenKind.DOT;
}
break loop;
case ',':
reader.scanChar(); tk = TokenKind.COMMA; break loop;
case ';':
reader.scanChar(); tk = TokenKind.SEMI; break loop;
case '(':
reader.scanChar(); tk = TokenKind.LPAREN; break loop;
case ')':
reader.scanChar(); tk = TokenKind.RPAREN; break loop;
case '[':
reader.scanChar(); tk = TokenKind.LBRACKET; break loop;
case ']':
reader.scanChar(); tk = TokenKind.RBRACKET; break loop;
case '{':
reader.scanChar(); tk = TokenKind.LBRACE; break loop;
case '}':
reader.scanChar(); tk = TokenKind.RBRACE; break loop;
case '/':
reader.scanChar();
if (reader.ch == '/') {
do {
reader.scanCommentChar();
} while (reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen);
if (reader.bp < reader.buflen) {
comments = addComment(comments, processComment(pos, reader.bp, CommentStyle.LINE));
}
break;
} else if (reader.ch == '*') {
boolean isEmpty = false;
reader.scanChar();
CommentStyle style;
if (reader.ch == '*') {
style = CommentStyle.JAVADOC;
reader.scanCommentChar();
if (reader.ch == '/') {
isEmpty = true;
}
} else {
style = CommentStyle.BLOCK;
}
while (!isEmpty && reader.bp < reader.buflen) {
if (reader.ch == '*') {
reader.scanChar();
if (reader.ch == '/') break;
} else {
reader.scanCommentChar();
}
}
if (reader.ch == '/') {
reader.scanChar();
comments = addComment(comments, processComment(pos, reader.bp, style));
break;
} else {
lexError(pos, "unclosed.comment");
break loop;
}
} else if (reader.ch == '=') {
tk = TokenKind.SLASHEQ;
reader.scanChar();
} else {
tk = TokenKind.SLASH;
}
break loop;
case '\'':
reader.scanChar();
if (reader.ch == '\'') {
lexError(pos, "empty.char.lit");
} else {
if (reader.ch == CR || reader.ch == LF)
lexError(pos, "illegal.line.end.in.char.lit");
scanLitChar(pos);
char ch2 = reader.ch;
if (reader.ch == '\'') {
reader.scanChar();
tk = TokenKind.CHARLITERAL;
} else {
lexError(pos, "unclosed.char.lit");
}
}
break loop;
case '\"':
reader.scanChar();
while (reader.ch != '\"' && reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen)
scanLitChar(pos);
if (reader.ch == '\"') {
tk = TokenKind.STRINGLITERAL;
reader.scanChar();
} else {
lexError(pos, "unclosed.str.lit");
}
break loop;
default:
if (isSpecial(reader.ch)) {
scanOperator();
} else {
boolean isJavaIdentifierStart;
if (reader.ch < '\u0080') {
// all ASCII range chars already handled, above
isJavaIdentifierStart = false;
} else {
char high = reader.scanSurrogates();
if (high != 0) {
reader.putChar(high);
isJavaIdentifierStart = Character.isJavaIdentifierStart(
Character.toCodePoint(high, reader.ch));
} else {
isJavaIdentifierStart = Character.isJavaIdentifierStart(reader.ch);
}
}
if (isJavaIdentifierStart) {
scanIdent();
} else if (reader.bp == reader.buflen || reader.ch == EOI && reader.bp + 1 == reader.buflen) { // JLS 3.5
tk = TokenKind.EOF;
pos = reader.buflen;
} else {
String arg = (32 < reader.ch && reader.ch < 127) ?
String.format("%s", reader.ch) :
String.format("\\u%04x", (int)reader.ch);
lexError(pos, "illegal.char", arg);
reader.scanChar();
}
}
break loop;
}
}
endPos = reader.bp;
switch (tk.tag) {
case DEFAULT: return new Token(tk, pos, endPos, comments);
case NAMED: return new NamedToken(tk, pos, endPos, name, comments);
case STRING: return new StringToken(tk, pos, endPos, reader.chars(), comments);
case NUMERIC: return new NumericToken(tk, pos, endPos, reader.chars(), radix, comments);
default: throw new AssertionError();
}
}
finally {
if (scannerDebug) {
System.out.println("nextToken(" + pos
+ "," + endPos + ")=|" +
new String(reader.getRawCharacters(pos, endPos))
+ "|");
}
}
}
转换源文件的token子过程scanNumber(int, int)
private void scanNumber(int pos, int radix) {
// for octal, allow base-10 digit in case it's a float literal
this.radix = radix;
int digitRadix = (radix == 8 ? 10 : radix);
boolean seendigit = false;
if (reader.digit(pos, digitRadix) >= 0) {
seendigit = true;
scanDigits(pos, digitRadix);
}
if (radix == 16 && reader.ch == '.') {
scanHexFractionAndSuffix(pos, seendigit);
} else if (seendigit && radix == 16 && (reader.ch == 'p' || reader.ch == 'P')) {
scanHexExponentAndSuffix(pos);
} else if (digitRadix == 10 && reader.ch == '.') {
reader.putChar(true);
scanFractionAndSuffix(pos);
} else if (digitRadix == 10 &&
(reader.ch == 'e' || reader.ch == 'E' ||
reader.ch == 'f' || reader.ch == 'F' ||
reader.ch == 'd' || reader.ch == 'D')) {
scanFractionAndSuffix(pos);
} else {
if (reader.ch == 'l' || reader.ch == 'L') {
reader.scanChar();
tk = TokenKind.LONGLITERAL;
} else {
tk = TokenKind.INTLITERAL;
}
}
}
- 初始化radix和seedDigit参数
- 检查当前字符是否在进制范围内,如果是进行scanDigits(int, int),一直扫描数字。 scanDigits(int, int)会一直扫描数字(默认最大进制为16)最后不是数字或者是下划线 时停止。
private void scanDigits(int pos, int digitRadix) {
char saveCh;
int savePos;
do {
if (reader.ch != '_') {
reader.putChar(false);
} else {
if (!allowUnderscoresInLiterals) {
lexError(pos, "unsupported.underscore.lit", source.name);
allowUnderscoresInLiterals = true;
}
}
saveCh = reader.ch;
savePos = reader.bp;
reader.scanChar();
} while (reader.digit(pos, digitRadix) >= 0 || reader.ch == '_');
if (saveCh == '_')
lexError(savePos, "illegal.underscore");
}
- 如果进制为16或者碰到小数点后,需要处理进行scanHexFractionAndSuffix(int, boolean),来处理小数部分。同时内部会处理掉一些不符合规范的格式:如:0X.33
private void scanHexFractionAndSuffix(int pos, boolean seendigit) {
radix = 16;
Assert.check(reader.ch == '.');
reader.putChar(true);
skipIllegalUnderscores();
if (reader.digit(pos, 16) >= 0) {
seendigit = true;
scanDigits(pos, 16);
}
if (!seendigit)
lexError(pos, "invalid.hex.number");
else
scanHexExponentAndSuffix(pos);
}
- 如果是数字,并且进制为16,并且读入的字符为p或者P则进行scanHexExponentAndSuffix(int)来处理。scanHexExponentAndSuffix(int),会一直读入小数点后面的有效数字(10进制)。当以f或者F结尾时,生成浮点型数据;当以d或者D结尾时,生成双精度数据。
private void scanHexExponentAndSuffix(int pos) {
if (reader.ch == 'p' || reader.ch == 'P') {
reader.putChar(true);
skipIllegalUnderscores();
if (reader.ch == '+' || reader.ch == '-') {
reader.putChar(true);
}
skipIllegalUnderscores();
if ('0' <= reader.ch && reader.ch <= '9') {
scanDigits(pos, 10);
if (!allowHexFloats) {
lexError(pos, "unsupported.fp.lit", source.name);
allowHexFloats = true;
}
else if (!hexFloatsWork)
lexError(pos, "unsupported.cross.fp.lit");
} else
lexError(pos, "malformed.fp.lit");
} else {
lexError(pos, "malformed.fp.lit");
}
if (reader.ch == 'f' || reader.ch == 'F') {
reader.putChar(true);
tk = TokenKind.FLOATLITERAL;
radix = 16;
} else {
if (reader.ch == 'd' || reader.ch == 'D') {
reader.putChar(true);
}
tk = TokenKind.DOUBLELITERAL;
radix = 16;
}
}
- 当为10进制并且有小数点时,调用scanFractionAndSuffix(int)进行处理。但是会进行scanFraction(int)处理小数点后面的数字。当以f或者F结尾时,生成浮点型数据;当以d或者D结尾时,生成双精度数据。scanFraction(int)。会先一直扫描0-9的数字。当碰到E或者e的科学计数格式时,会再次接着扫描0-9的指数部分。
private void scanFractionAndSuffix(int pos) {
radix = 10;
scanFraction(pos);
if (reader.ch == 'f' || reader.ch == 'F') {
reader.putChar(true);
tk = TokenKind.FLOATLITERAL;
} else {
if (reader.ch == 'd' || reader.ch == 'D') {
reader.putChar(true);
}
tk = TokenKind.DOUBLELITERAL;
}
}
private void scanFraction(int pos) {
skipIllegalUnderscores();
if ('0' <= reader.ch && reader.ch <= '9') {
scanDigits(pos, 10);
}
int sp1 = reader.sp;
if (reader.ch == 'e' || reader.ch == 'E') {
reader.putChar(true);
skipIllegalUnderscores();
if (reader.ch == '+' || reader.ch == '-') {
reader.putChar(true);
}
skipIllegalUnderscores();
if ('0' <= reader.ch && reader.ch <= '9') {
scanDigits(pos, 10);
return;
}
lexError(pos, "malformed.fp.lit");
reader.sp = sp1;
}
}
- 当为10进制并且下一个字符为e|E|d|D|f|F时,进行scanFractionAndSuffix(int)处理。
- 当下一个字符为l|L时认为是长整型。
- 否则认为是整型。
转换源文件的token子过程scanIdent()
private void scanIdent() {
boolean isJavaIdentifierPart;
char high;
reader.putChar(true);
do {
switch (reader.ch) {
case 'A': case 'B': case 'C': case 'D': case 'E':
case 'F': case 'G': case 'H': case 'I': case 'J':
case 'K': case 'L': case 'M': case 'N': case 'O':
case 'P': case 'Q': case 'R': case 'S': case 'T':
case 'U': case 'V': case 'W': case 'X': case 'Y':
case 'Z':
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f': case 'g': case 'h': case 'i': case 'j':
case 'k': case 'l': case 'm': case 'n': case 'o':
case 'p': case 'q': case 'r': case 's': case 't':
case 'u': case 'v': case 'w': case 'x': case 'y':
case 'z':
case '$': case '_':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break;
case '\u0000': case '\u0001': case '\u0002': case '\u0003':
case '\u0004': case '\u0005': case '\u0006': case '\u0007':
case '\u0008': case '\u000E': case '\u000F': case '\u0010':
case '\u0011': case '\u0012': case '\u0013': case '\u0014':
case '\u0015': case '\u0016': case '\u0017':
case '\u0018': case '\u0019': case '\u001B':
case '\u007F':
reader.scanChar();
continue;
case '\u001A': // EOI is also a legal identifier part
if (reader.bp >= reader.buflen) {
name = reader.name();
tk = tokens.lookupKind(name);
return;
}
reader.scanChar();
continue;
default:
if (reader.ch < '\u0080') {
// all ASCII range chars already handled, above
isJavaIdentifierPart = false;
} else {
if (Character.isIdentifierIgnorable(reader.ch)) {
reader.scanChar();
continue;
} else {
high = reader.scanSurrogates();
if (high != 0) {
reader.putChar(high);
isJavaIdentifierPart = Character.isJavaIdentifierPart(
Character.toCodePoint(high, reader.ch));
} else {
isJavaIdentifierPart = Character.isJavaIdentifierPart(reader.ch);
}
}
}
if (!isJavaIdentifierPart) {
name = reader.name();
tk = tokens.lookupKind(name);
return;
}
}
reader.putChar(true);
} while (true);
}
- 当碰到大小写字母,数字,$,_ 时,放入一个字符,接着往下读。
- 当碰到\0000等字符时,读入一 个字符,接着往下读。
- 当碰到\u001A字符时,如果下一个读入的字符的位置大于当前缓冲池的长度,获取当前缓冲池中的所有的字符,并在java关键字表中对比,如果是java关键字则返回java关键字的tokenKind,否则返回为TokenKind.IDENTIFIER。
/**
* Create a new token given a name; if the name corresponds to a token name,
* a new token of the corresponding kind is returned; otherwise, an
* identifier token is returned.
*/
TokenKind lookupKind(Name name) {
return (name.getIndex() > maxKey) ? TokenKind.IDENTIFIER : key[name.getIndex()];
}
key数组是关键字表
/**
* Keyword array. Maps name indices to Token.
*/
private final TokenKind[] key;
4.其他的默认处理,会先判定是不是uincode字符。如果不是ASCII组成的字符,会再次判断是不是关键字,如果不是,则设置tokenKind为TokenKind.IDENTIFIER。
lexer源码读后感
- 源码的介绍程度:上面的源码我主要主要选取比较典型的读数字和读标识符两个部分。关于reader如何加载源文件,以及里面的nameTable是如何生成的暂时就不做介绍了。
- 编译原理:token生成方式使用代码硬读源文件,尚未使用NFA或者DFA等自动化的手段来处理;错误恢复过程使用最简单的skip方式,未使用其他手段。文法的设计尚未涉及到。
- 算法导论:此处暂时尚未涉及到常见的算法,或者编译本身就是一件浩大的算法。
- 对于此过程实现,对于我本次的目标印证情况,不是很友好,很多书中的知识,尚未涉及到,或者涉及到较少的使用,或者使用的都是书本中非常浅显的技术。