1. 詞法分析器與語法分析器
1.1 javac詞法、語法分析器概覽及Intellij IDEA調試跟蹤流程
-
詞法分析器實例的生成流程
1.2 詞法分析器——核心流程是readToken()
- 詞法分析器的接口類是com.sun.tools.javac.parser.Lexer;
實現類是com.sun.tools.javac.parser.Scanner。 -
字符流存放在JavaTokenizer類的成員protected UnicodeReader reader;里面,在該類的方法public Token readToken()里面將字符流組裝成Token,在這里會將收集到的新名字加入到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();
}
問題.Javac如何劃分Token,如何知道哪些字符組合成一個Token?(詞法器)
-
Java代碼有package、import、類定義、field定義、method定義、變量定義、表達式定義等語法規則。
這些規則除了一些Java語法規定的關鍵詞,就是用戶自定義的變量名稱。
自定義的名稱包括包名、類名、變量名、方法名。
關鍵詞和自定義名稱之間用空格隔開,每個語法表達式用分號結束。
如何判斷哪些字符組合是一個Token的規則是在Scanner的nextToken方法中定義的,每次都會構造一個Token。
-
PaserFactory構建Names Tokens等
枚舉類的關鍵方法
1)靜態values方法返回一個包含全部枚舉值的數組
2)ordinal方法返回enum聲明中枚舉常量的位置,位置從0開始計數
構建Names Tokens的流程 -
關于Tokens
為了顯示方便,下圖中對TokenKind枚舉類的枚舉值個數定義進行了刪減
1)Tokens.tokenName存儲的是“關鍵字在TokenKind里面的位置(token.ordinal())”與“關鍵字Name”的關系,該數組映射這兩對關系。
2)Tokens.key存儲的是“關鍵字Name在Names存儲的位置index”與“關鍵字TokenKind本身”的關系,該數組映射這兩對關系。
3)Tokens.maxKey在初始化更新完所有關鍵字后就固定下來,所以非關鍵字的index都大于maxKey
-
關于Name Table Names
分析后可知:Names是每個區域的名字表。
1)該表是利用哈希表+鏈表方式存儲名字
2)該表先存儲固定的名字(java.lang等),然后存儲定義的關鍵字
Names詳細構造如下圖所示,將cs代表的string類型經過hash后,放入到哈希表NameImpl[] hashes中,相同的hash值使用鏈表的方式存放,并且cs本身放在bytes里面。
1.2.1 詞法單元的分類
-
枚舉體TokenKind(String name, Tag tag)詞法單元分為四類。
1)NUMERIC是數字字面值
2)STRING是字符串字面值
3)NAMED主要是基本類型,包括標識符IDENTIFIER
4)DEFAULT最多,包括各種屬性修飾符、運算符號、關鍵字等等
enum Tag {
DEFAULT,
NAMED,
STRING,
NUMERIC
}
- readToken()生成Token,并根據Java語法規則對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 語法分析器——核心流程是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)按照Java語法規范依次找出package、import、類定義、屬性和方法定義等,最后構建一個抽象語法樹
2)這里核心流程不是按照token為單位,而是按照Java語法單元為單位進行了方法封裝,一個函數處理一個語法單元。如JCTree def = typeDeclaration(mods, docComment);就會處理整個類的tokens。
詞法分析核心流程parseCompilationUnit
問題.Javac如何分辨Token的類別?(語法器)
- Javac進行詞法分析時,JavacParser對象會根據Java語言規范來控制什么順序、什么地方應該出現什么Token。就是前面說的,parseCompilationUnit()里面是按照Java語言單元進行解析,例如typeDeclaration()里面會對類型定義的所有token都讀取出來,并最終構成一棵子語法樹。
- 總之,Token流的順序要符合Java語言規范,JavacParser里面已經根據規范編寫了各個處理Token的方法。
語法樹的結構?
-
1)parseFiles()對javac需要編譯的所有文件進行編譯,每個文件生成一個JCTree.JCCompilationUnit,所有文件形成List<JCCompilationUnit>
2)parseFiles()最終調用parseCompilationUnit()處理每個文件,每個文件生成一個JCTree.JCCompilationUnit。在文件中,對每個定義構造一個語法樹JCTree,然后所有定義加入到ListBuffer<JCTree> defs,然后轉換成List<JCTree>加入到JCTree.JCCompilationUnit toplevel中。這些定義包括package/import/class定義等等。
1.3.1 語法樹生成示例
- 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;
}
}
-
生成的語法樹結構
1.3.2 語法樹生成過程涉及到的步驟示例——Test.java為例
1)JCPackageDecl生成(解析package com.wz.test;)
- parseCompilationUnit中的第一個token是package,nextToken()之后就變為了com
- 進入qualident,ident()之后,token變為"."
Ident(ident())以com返回的name建立一個語法樹結點:JCIdent tree = new JCIdent(name, null);
while循環里面循環處理".xxx"模式,第一個處理的是".wz"。并建立語法樹結點JCFieldAccess,其中JCExpression this.selected = 上面剛建立的結點,Name this.name = wz返回的name。 - qualident之后是accept(SEMI),期望下個token是";",如果是的話,則獲取下一個token,否則報錯。
- 后面將JCExpression pid加入到新建立的JCPackageDecl pd結點中
- 最后加入到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的代碼在循環while (token.kind != EOF) 里面
- "import java.lang.reflect.Constructor;"處理跟"package com.wz.test;"是一樣的,有兩處不同:
第一,import中可能會有static修飾符,所以JCImport語法樹結點有一個staticImport屬性來記錄;
第二,import中可能會有"*"。
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);
繼續調用return classOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment);
繼續調用
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 語法器與詞法器的配合——public class Test
- Test是類類型,屬于NamedToken,NamedToken繼承Token并且含有域Name name。
每個Name都有指向符號表Names的指針,并有自身字符串的位置index。相當于每個Name代表一個名字字符串。
因此,NamedToken將TokenKind和Name聯系起來了。
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類
-
所有語法結點的生成都是在TreeMaker類中完成的,TreeMaker實現了在JCTree.Factory接口中定義的所有結點的構成方法。
分析代碼"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的內部類
protected static class EmptyEndPosTable extends AbstractEndPosTable {
protected <T extends JCTree> T toP(T t) {
return t;
}
}