1. 程式人生 > >深入分析 Javac 編譯原理

深入分析 Javac 編譯原理

源碼分析 3.2 inter 計算機 out 詞法分析器 基本 image 包含

通常,一個java文件會通過編譯器編譯成字節碼文件.class,再又java虛擬機JVM翻譯成計算機可執行的文件。

我們所知道的java語言有它自己的語法規範,同樣的JVM也有它的語法規範,如何讓java的語法規則去適應語法解析規則,這就是javac的作用,簡而言之,javac的作用就是將java源代碼轉化成class字節碼文件。

Javac編譯器的基本結構
編譯步驟
技術分享圖片

  1. 詞法分析器:
    1.1作用:
    將源碼轉化為Token流

1.2流程
讀取源代碼,從源文件的一個字符開始,按照java語法規範依次找出package,import,類定義,屬性,方法定義等,最後構建出一個抽象語法樹

1.3舉例

package compile;

/**
 * 詞法解析器
 */
 public class Cifa{
     int a;
     int c = a + 1;
 }

轉化為Token流:
技術分享圖片
1.4源碼分析
com.sun.tools.javac.parser.JavacParser 規定哪些詞符合Java語言規範,具體讀取和歸類不同詞法的操作由scanner完成
com.sun.tools.javac.parser.Scanner 負責逐個讀取源代碼的單個字符,然後解析符合Java語言規範的Token序列,調用一次nextToken()都構造一個Token
com.sun.tools.javac.parser.Tokens$TokenKind 裏面包含了所有token的類型,譬如BOOLEAN,BREAK,BYTE,CASE。
com.sun.tools.javac.util.Names 用來存儲和表示解析後的詞法,每個字符集合都會是一個Name對象,所有的對象都存儲在Name.Table這個內部類中。

com.sun.tools.javac.parser.KeyWords 負責將字符集合對應到token集合中,如,packagezxy.demo.com; Token.PACKAGE = package, Token.IDENTIFIER =zxy.demo.com,(這部分又分為讀取第一個token,為zxy,判斷下一個token是否為“.”,是的話接著讀取下一個Token.IDENTIFIER類型的token,反復直至下一個token不是”.”,也就是說下一個不是Token.IDENIFIER類型的token,Token.SEMI = ;即這個TIDENTIFIER類型的token的Name讀完),KeyWords類負責此任務。
1.5問題
Javac是如何分辨這一個個Token呢?例如它時如何直到package是關鍵詞而不是自定義變量呢?
Javac在進行此法分析時會由JavacParser根據Java語言規範來控制什麽順序,地方會出現什麽Token,例如package就只能在文件的最開頭出現

Javac怎樣確定哪些字符組合在一起就是一個Token呢?它如何從一串字符流中劃分出Token來?
對於關鍵字,主要由關鍵字的語法規則,例如package就是若一個字符串package是連續的,那麽他就是關鍵字

對於自定義變量名稱,自定義名稱之間用空格隔開,每個語法表達式用分號結束

舉例:
int a = 1 + 2;

從package開始

.....

int 就是通過語法關鍵字判定的TOKEN:INT

int a之間通過空格隔開

a 就是自定義的變量被判定為TOKEN:IDENTIFIER

a =之間通過空格隔開(這時有的小夥伴就會說了, int a=b+c;這句話也不報錯啊 ,對的,大多數時候,這種不用空格分開確實能夠編譯, 這是因為java指出聲明變量的時候必須以字母、下劃線或者美元符開頭,當JavacParser讀完a去讀=的時候就直到這個=不屬於變量了 )將=判定為TOKEN:EQ

1被判定為TOKEN:INTLITERAL

.....

將;識別為TOKEN:SEMI

.....

最後讀取到類結束,也就是}被判定為TOKEN:RBRACE

2.語法分析器:
剛才,詞法解析器已經將Java源文件解析成了Token流。

現在,語法解析器就要將Token流組建成更加結構化的語法樹。也就是將這些Token流中的單詞裝成一句話,完整的語句。

2.1作用
將進行詞法分析後形成的Token流中的一個個Token組成一句句話,檢查這一句句話是不是符合Java語言規範。

2.2語法分析三部分
package
import
類(包含class、interface、enum),一下提到的類泛指這三類,並不單單是指class
2.3所用類庫
com.sun.tools.javac.tree.TreeMaker 所有語法節點都是由它生成的,根據Name對象構建一個語法節點
com.sun.tools.javac.tree.JCTree$JCIf 所有的節點都會繼承jctree和實現**tree,譬如 JCIf extends JCTree.JCStatement implements IfTree
com.sun.tools.javac.tree.JCTree的三個屬性
Tree tag:每個語法節點都會以整數的形式表示,下一個節點在上一個節點上加1;
復制代碼

pos:也是一個整數,它存儲的是這個語法節點在源代碼中的起始位置,一個文件的位置是0,而-1表示不存在

type:它代表的是這個節點是什麽java類型,如int,float,還是string等
2.4 舉例

package compile;

/**
 * 語法
 */
public class Yufa {
    int a;
    private int c = a + 1;

    //getter
    public int getC() {
        return c;
    }
    //setter
    public void setC(int c) {
        this.c = c;
    }
}

技術分享圖片
每一個包package下的所有類都會放在一個JCCompilationUnit節點下,在該節點下包含:package語法樹(作為pid)、各個類的語法樹
每一個從JCClassDecl發出的分支都是一個完整的代碼塊,上述是四個分支,對應我們代碼中的兩行屬性操作語句和兩個方法塊代碼塊,這樣其實就完成了語法分析器的作用:將一個個Token單詞組成了一句句話(或者說成一句句代碼塊)
在上述的語法樹部分,對於屬性操作部分是完整的,但是對於兩個方法塊,省略了一些語法節點,例如:方法修飾符public、方法返回類型、方法參數。
註1:若類中有import關鍵字則途中還有import的語法節點

註2:所有語法節點的生成都是在TreeMaker類中完成的

3.語法分析器
3.1作用
將語法樹轉化為註解語法樹,即在這顆語法樹上做一些處理

3.2步驟
給類添加默認構造函數(由com.sun.tools.javac.comp.Enter類完成)
處理註解(由com.sun.tools.javac.processing.JavacProcessingEnvironment類完成)
檢查語義的合法性並進行邏輯判斷(由com.sun.tools.javac.comp.Attr完成)
變量的類型是否匹配
變量在使用前是否初始化
能夠推導出泛型方法的參數類型
字符串常量合並

數據流分析(由com.sun.tools.javac.comp.Flow類完成)
檢驗變量是否被正確賦值(eg.有返回值的方法必須確定有返回值)
保證final變量不會被重復修飾
確定方法的返回值類型
所有的檢查型異常是否拋出或捕獲
所有的語句都要被執行到(return後邊的語句就不會被執行到,除了finally塊兒)

對語法樹進行語義分析(由com.sun.tools.javac.comp.Flow執行)
去掉無用的代碼,如只有永假的if代碼塊
變量的自動轉換,如將int自動包裝為Integer類型
去除語法糖,將foreach的形式轉化為更簡單的for循環

最終,生成了註解語法樹

3.3所用類庫
com.sun.tools.javac.comp.Check,它用來輔助Attr類檢查語法樹中變量類型是否正確,如方法返回值是否和接收的引用值類型匹配
com.sun.tools.javac.comp.Resolve,用來檢查變量,方法或者類的訪問是否合法,變量是否是靜態變量
com.sun.tools.javac.comp.ConstFold,將一個字符串常量中的多個字符合並成一個字符串
com.sun.tools.javac.comp.Infer,幫助推導泛型方法的參數類型
3.4舉例
變量自動轉化

public class Yuyi{
    public static void main(String agrs[]){
        Integer i = 1;
        Long l = i + 2L;
        System.out.println(l);
    }
}
//經過自動轉換後
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        Integer i = Integer.valueOf(1);
        Long l = Long.valueOf(i.intValue() + 2L);
        System.out.println(l);
    }
}

解除語法

public class Yuyi{
    public static void main(String agrs[]){
        int[] array = {1,2,3};
        for (int i : array){
            System.out.println(i);
        }
    }
}
//解除語法糖後
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        int[] arrays = {1,2,3};
        for (int[] arr$ = array,len$=arr$.length,i$=0; i$<len$; ++i$){
            int i = arr$[i$];
            {
                System.out.println(i);
            }
        }
    }
}

內部類解析

public class Yuyi{
    public static void main(String agrs[]){
        Inner inner = new Inner();
        inner.print();
    }
    class Inner{
        public void print(){
            System.out.println("Yuyi$Inner.print");
        }
    }
}
//轉化後的代碼如下
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        Yuyi$Inner inner = new Yuyi$Inner(this);
        inner.print();
    }
    {
    }
}
class Yuyi$Inner{
    /*synthetic*/ final Yuyi this$0;

    Yuyi$Inner(/*synthetic*/final Yuyi this$0){
        this.this$0 = this$0;
        super();
    }

    public void print(){
        System.out.println("Yuyi$Inner.print");
    }
}

深入分析 Javac 編譯原理