1. 詞法分析器與語(yǔ)法分析器
1.1 javac詞法、語(yǔ)法分析器概覽及Intellij IDEA調(diào)試跟蹤流程
-
詞法分析器實(shí)例的生成流程
1.2 詞法分析器——核心流程是readToken()
- 詞法分析器的接口類是com.sun.tools.javac.parser.Lexer;
實(shí)現(xiàn)類是com.sun.tools.javac.parser.Scanner。 -
字符流存放在JavaTokenizer類的成員protected UnicodeReader reader;里面,在該類的方法public Token readToken()里面將字符流組裝成Token,在這里會(huì)將收集到的新名字加入到Names字符表里面。
Scanner類token的獲取 - readToken()返回Token類別
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();
}
問(wèn)題.Javac如何劃分Token,如何知道哪些字符組合成一個(gè)Token?(詞法器)
-
Java代碼有package、import、類定義、field定義、method定義、變量定義、表達(dá)式定義等語(yǔ)法規(guī)則。
這些規(guī)則除了一些Java語(yǔ)法規(guī)定的關(guān)鍵詞,就是用戶自定義的變量名稱。
自定義的名稱包括包名、類名、變量名、方法名。
關(guān)鍵詞和自定義名稱之間用空格隔開,每個(gè)語(yǔ)法表達(dá)式用分號(hào)結(jié)束。
如何判斷哪些字符組合是一個(gè)Token的規(guī)則是在Scanner的nextToken方法中定義的,每次都會(huì)構(gòu)造一個(gè)Token。
-
PaserFactory構(gòu)建Names Tokens等
枚舉類的關(guān)鍵方法
1)靜態(tài)values方法返回一個(gè)包含全部枚舉值的數(shù)組
2)ordinal方法返回enum聲明中枚舉常量的位置,位置從0開始計(jì)數(shù)
構(gòu)建Names Tokens的流程 -
關(guān)于Tokens
為了顯示方便,下圖中對(duì)TokenKind枚舉類的枚舉值個(gè)數(shù)定義進(jìn)行了刪減
1)Tokens.tokenName存儲(chǔ)的是“關(guān)鍵字在TokenKind里面的位置(token.ordinal())”與“關(guān)鍵字Name”的關(guān)系,該數(shù)組映射這兩對(duì)關(guān)系。
2)Tokens.key存儲(chǔ)的是“關(guān)鍵字Name在Names存儲(chǔ)的位置index”與“關(guān)鍵字TokenKind本身”的關(guān)系,該數(shù)組映射這兩對(duì)關(guān)系。
3)Tokens.maxKey在初始化更新完所有關(guān)鍵字后就固定下來(lái),所以非關(guān)鍵字的index都大于maxKey
-
關(guān)于Name Table Names
分析后可知:Names是每個(gè)區(qū)域的名字表。
1)該表是利用哈希表+鏈表方式存儲(chǔ)名字
2)該表先存儲(chǔ)固定的名字(java.lang等),然后存儲(chǔ)定義的關(guān)鍵字
Names詳細(xì)構(gòu)造如下圖所示,將cs代表的string類型經(jīng)過(guò)hash后,放入到哈希表NameImpl[] hashes中,相同的hash值使用鏈表的方式存放,并且cs本身放在bytes里面。
1.2.1 詞法單元的分類
-
枚舉體TokenKind(String name, Tag tag)詞法單元分為四類。
1)NUMERIC是數(shù)字字面值
2)STRING是字符串字面值
3)NAMED主要是基本類型,包括標(biāo)識(shí)符IDENTIFIER
4)DEFAULT最多,包括各種屬性修飾符、運(yùn)算符號(hào)、關(guān)鍵字等等
enum Tag {
DEFAULT,
NAMED,
STRING,
NUMERIC
}
- readToken()生成Token,并根據(jù)Java語(yǔ)法規(guī)則對(duì)Token完成分類
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();
}
1.3 語(yǔ)法分析器——核心流程是parseCompilationUnit
- 核心類是JavacParser
Parser parser = parserFactory.newParser(content, keepComments(), genEndPos,
lineDebugInfo, filename.isNameCompatible("module-info", Kind.SOURCE));
public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap, boolean parseModuleInfo) {
Lexer lexer = scannerFactory.newScanner(input, keepDocComments);
return new JavacParser(this, lexer, keepDocComments, keepLineMap, keepEndPos, parseModuleInfo);
}
-
parseCompilationUnit流程
1)按照J(rèn)ava語(yǔ)法規(guī)范依次找出package、import、類定義、屬性和方法定義等,最后構(gòu)建一個(gè)抽象語(yǔ)法樹
2)這里核心流程不是按照token為單位,而是按照J(rèn)ava語(yǔ)法單元為單位進(jìn)行了方法封裝,一個(gè)函數(shù)處理一個(gè)語(yǔ)法單元。如JCTree def = typeDeclaration(mods, docComment);就會(huì)處理整個(gè)類的tokens。
詞法分析核心流程parseCompilationUnit
問(wèn)題.Javac如何分辨Token的類別?(語(yǔ)法器)
- Javac進(jìn)行詞法分析時(shí),JavacParser對(duì)象會(huì)根據(jù)Java語(yǔ)言規(guī)范來(lái)控制什么順序、什么地方應(yīng)該出現(xiàn)什么Token。就是前面說(shuō)的,parseCompilationUnit()里面是按照J(rèn)ava語(yǔ)言單元進(jìn)行解析,例如typeDeclaration()里面會(huì)對(duì)類型定義的所有token都讀取出來(lái),并最終構(gòu)成一棵子語(yǔ)法樹。
- 總之,Token流的順序要符合Java語(yǔ)言規(guī)范,JavacParser里面已經(jīng)根據(jù)規(guī)范編寫了各個(gè)處理Token的方法。
語(yǔ)法樹的結(jié)構(gòu)?
-
1)parseFiles()對(duì)javac需要編譯的所有文件進(jìn)行編譯,每個(gè)文件生成一個(gè)JCTree.JCCompilationUnit,所有文件形成List<JCCompilationUnit>
2)parseFiles()最終調(diào)用parseCompilationUnit()處理每個(gè)文件,每個(gè)文件生成一個(gè)JCTree.JCCompilationUnit。在文件中,對(duì)每個(gè)定義構(gòu)造一個(gè)語(yǔ)法樹JCTree,然后所有定義加入到ListBuffer<JCTree> defs,然后轉(zhuǎn)換成List<JCTree>加入到JCTree.JCCompilationUnit toplevel中。這些定義包括package/import/class定義等等。
1.3.1 語(yǔ)法樹生成示例
- Test.java
package com.wz.test;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
String ClassName = "com.wz.test.Cat";
Class cl = null;
try {
cl = Class.forName(ClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
}
Constructor[] constructors= cl.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName();
System.out.println("Constructor: " + name);
}
Cat cat = new Cat("Tiger");
System.out.println(cat.name);
}
}
- Cat.java
package com.wz.test;
public class Cat {
String name;
public Cat(String name) {
this.name = name;
}
}
-
生成的語(yǔ)法樹結(jié)構(gòu)
1.3.2 語(yǔ)法樹生成過(guò)程涉及到的步驟示例——Test.java為例
1)JCPackageDecl生成(解析package com.wz.test;)
- parseCompilationUnit中的第一個(gè)token是package,nextToken()之后就變?yōu)榱薱om
- 進(jìn)入qualident,ident()之后,token變?yōu)?."
Ident(ident())以com返回的name建立一個(gè)語(yǔ)法樹結(jié)點(diǎn):JCIdent tree = new JCIdent(name, null);
while循環(huán)里面循環(huán)處理".xxx"模式,第一個(gè)處理的是".wz"。并建立語(yǔ)法樹結(jié)點(diǎn)JCFieldAccess,其中JCExpression this.selected = 上面剛建立的結(jié)點(diǎn),Name this.name = wz返回的name。 - qualident之后是accept(SEMI),期望下個(gè)token是";",如果是的話,則獲取下一個(gè)token,否則報(bào)錯(cuò)。
- 后面將JCExpression pid加入到新建立的JCPackageDecl pd結(jié)點(diǎn)中
- 最后加入到ListBuffer<JCTree> defs = new ListBuffer<>();也即根鏈表中
if (token.kind == PACKAGE) {
int packagePos = token.pos;
List<JCAnnotation> annotations = List.nil();
seenPackage = true;
if (mods != null) {
checkNoMods(mods.flags);
annotations = mods.annotations;
mods = null;
}
nextToken();
JCExpression pid = qualident(false);
accept(SEMI);
JCPackageDecl pd = toP(F.at(packagePos).PackageDecl(annotations, pid));
attach(pd, firstToken.comment(CommentStyle.JAVADOC));
consumedToplevelDoc = true;
defs.append(pd);
}
public JCExpression qualident(boolean allowAnnos) {
JCExpression t = toP(F.at(token.pos).Ident(ident()));
while (token.kind == DOT) {
int pos = token.pos;
nextToken();
List<JCAnnotation> tyannos = null;
if (allowAnnos) {
tyannos = typeAnnotationsOpt();
}
t = toP(F.at(pos).Select(t, ident()));
if (tyannos != null && tyannos.nonEmpty()) {
t = toP(F.at(tyannos.head.pos).AnnotatedType(tyannos, t));
}
}
return t;
}
2)JCImport生成(處理import java.lang.reflect.Constructor;)
- 處理import的代碼在循環(huán)while (token.kind != EOF) 里面
- "import java.lang.reflect.Constructor;"處理跟"package com.wz.test;"是一樣的,有兩處不同:
第一,import中可能會(huì)有static修飾符,所以JCImport語(yǔ)法樹結(jié)點(diǎn)有一個(gè)staticImport屬性來(lái)記錄;
第二,import中可能會(huì)有"*"。
if (checkForImports && mods == null && token.kind == IMPORT) {
seenImport = true;
defs.append(importDeclaration());
}
protected JCTree importDeclaration() {
int pos = token.pos;
nextToken();
boolean importStatic = false;
if (token.kind == STATIC) {
importStatic = true;
nextToken();
}
JCExpression pid = toP(F.at(token.pos).Ident(ident()));
do {
int pos1 = token.pos;
accept(DOT);
if (token.kind == STAR) {
pid = to(F.at(pos1).Select(pid, names.asterisk));
nextToken();
break;
} else {
pid = toP(F.at(pos1).Select(pid, ident()));
}
} while (token.kind == DOT);
accept(SEMI);
return toP(F.at(pos).Import(pid, importStatic));
}
3)JCClassDecl生成
public class Test {
public static void main(String[] args) {
String ClassName = "com.wz.test.Cat";
Class cl = null;
try {
cl = Class.forName(ClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
}
Constructor[] constructors= cl.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName();
System.out.println("Constructor: " + name);
}
Cat cat = new Cat("Tiger");
System.out.println(cat.name);
}
}
- 首先處理修飾符,返回的是JCModifiers mods
if (mods != null || token.kind != SEMI)
mods = modifiersOpt(mods);
- 處理類型聲明
JCTree def = typeDeclaration(mods, docComment);
繼續(xù)調(diào)用return classOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment);
繼續(xù)調(diào)用
if (token.kind == CLASS) {
return classDeclaration(mods, dc);
}
protected JCClassDecl classDeclaration(JCModifiers mods, Comment dc) {
int pos = token.pos;
accept(CLASS);
Name name = typeName();
List<JCTypeParameter> typarams = typeParametersOpt();
JCExpression extending = null;
if (token.kind == EXTENDS) {
nextToken();
extending = parseType();
}
List<JCExpression> implementing = List.nil();
if (token.kind == IMPLEMENTS) {
nextToken();
implementing = typeList();
}
List<JCTree> defs = classOrInterfaceBody(name, false);
JCClassDecl result = toP(F.at(pos).ClassDef(
mods, name, typarams, extending, implementing, defs));
attach(result, dc);
return result;
}
1.3.3 語(yǔ)法器與詞法器的配合——public class Test
- Test是類類型,屬于NamedToken,NamedToken繼承Token并且含有域Name name。
每個(gè)Name都有指向符號(hào)表Names的指針,并有自身字符串的位置index。相當(dāng)于每個(gè)Name代表一個(gè)名字字符串。
因此,NamedToken將TokenKind和Name聯(lián)系起來(lái)了。
Name typeName() {
int pos = token.pos;
Name name = ident();
if (name == names.var) {
if (Feature.LOCAL_VARIABLE_TYPE_INFERENCE.allowedInSource(source)) {
reportSyntaxError(pos, "var.not.allowed", name);
} else {
warning(pos, "var.not.allowed");
}
}
return name;
}
public Name ident() {
return ident(false);
}
protected Name ident(boolean advanceOnErrors) {
if (token.kind == IDENTIFIER) {
Name name = token.name();
nextToken();
return name;
}
...
}
1.3.4 TreeMaker類
-
所有語(yǔ)法結(jié)點(diǎn)的生成都是在TreeMaker類中完成的,TreeMaker實(shí)現(xiàn)了在JCTree.Factory接口中定義的所有結(jié)點(diǎn)的構(gòu)成方法。
分析代碼"JCPackageDecl pd = toP(F.at(packagePos).PackageDecl(annotations, pid));"
- step1.F.at(packagePos)
F是JavacParser的域變量。
/** The factory to be used for abstract syntax tree construction.
*/
protected TreeMaker F;
public TreeMaker at(int pos) {
this.pos = pos;
return this;
}
- step2.TreeMaker.java——PackageDecl(annotations, pid)
public JCPackageDecl PackageDecl(List<JCAnnotation> annotations,
JCExpression pid) {
Assert.checkNonNull(annotations);
Assert.checkNonNull(pid);
JCPackageDecl tree = new JCPackageDecl(annotations, pid);
tree.pos = pos;
return tree;
}
- step3.toP()
endPosTable是JavacParser的域變量。
/** End position mappings container */
protected final AbstractEndPosTable endPosTable;
protected <T extends JCTree> T toP(T t) {
return endPosTable.toP(t);
}
//EmptyEndPosTable是JavacParser的內(nèi)部類
protected static class EmptyEndPosTable extends AbstractEndPosTable {
protected <T extends JCTree> T toP(T t) {
return t;
}
}