1. 程式人生 > >java編譯器原始碼分析之語法分析器

java編譯器原始碼分析之語法分析器

token流到抽象語法樹的過程是語法分析。 前面認識到token流,這部分將介紹抽象語法樹(AST)。 那麼什麼是抽象語法樹(AST)?AST長啥樣?我們的token流是如何轉變成AST的?下面圍繞這三個問題展開討論。

針對什麼是抽象語法樹以及語法樹長啥樣兩個問題。可以看看這篇部落格,文章對於語法樹的結構和原理闡述的很清楚。在這裡我想說的是:①抽象語法樹是原始碼抽象樹結構的另一種表示;②抽象語法樹是一種獨立於源語言的語言結構,它有自己的語言規範;③抽象語法樹在不同編譯器中實現的方法不一樣,比如c的編譯器和javac,因此抽象語法樹適用於多種語言。

由於語法分析的原始碼過於繁雜,不可能窮盡原始碼中的各種情況,因此我將以解析下面例項程式碼為路徑說明語法解析的過程。 例項程式碼:

package com.test.syx;
import java.util.ArrayList;
import java.util.List;

// test
public class ASTTest {
	// a
	private static int a = 3;
	
	public int getA() {
		return a;
	}
	
	public static void main() {
		List<Integer> list = new ArrayList<Integer>();
		list.add(a);
		System.out.println
("hello world!"); } }

javac根據token來生成不同的樹節點。比如token是PACKAGE時生成一個JCExpression樹節點;token是IMPORT時生成一個JCTree節點;token為CLASS時生成一個JCTree節點;每個樹節點帶有不同的子節點。最後三個節點形成一個JCTree.JCCompilationUnit物件,此物件就是這個類的抽象語法樹。下面可分別來看一下他們的主要步驟:

Token.PACKAGE
if (S.token() == PACKAGE) {
            if (mods != null) {
                checkNoMods
(mods.flags); packageAnnotations = mods.annotations; mods = null; } S.nextToken(); pid = qualident(); accept(SEMI); }

步驟1:if程式碼塊的意思是判斷package前是否有@註解,如果有則儲存註解到packageAnnotations列表中; 步驟2:通過com.sun.tools.javac.parser.Scanner.nextToken()獲取下一個token; 步驟3:com.sun.tools.javac.parser.Parser.qualident()解析包名生成包的AST(JCExpression); 步驟4:以分號結尾;

生成語法樹的步驟3是重點,可詳細來看下:

public JCExpression qualident() {    	
        JCExpression t = toP(F.at(S.pos()).Ident(ident()));
        while (S.token() == DOT) {
            int pos = S.pos();
            S.nextToken(); 
            t = toP(F.at(pos).Select(t, ident()));
        }
        
        return t;
    }

步驟1:通過com.sun.tools.javac.tree.TreeMaker.Ident(Name)方法建立一個JCIdent型別的樹節點;節點name為token.name,節點tag為IDENT(35),節點的symbol為null,節點的pos為當前token的pos; 步驟2:如果token為DOT(.),則進入步驟3;否則直接返回返回生成的樹節點(JCExpress); 步驟3:記錄當前詞法解析器的token位置pos,並獲取該token; 步驟4:在pos位置通過com.sun.tools.javac.tree.TreeMaker.Select(JCExpression, Name)方法生成JCFieldAccess型別的節點;節點name為token.name,節點tag為SELECT(34),節點symbol為null,節點pos為token的位置;節點JCExpression為上一個的樹節點;獲取下一個token 步驟5:執行步驟2;

此時,package這一行構建了一個抽象語法樹(包含JCIdent和JCFieldAccess樹節點)。 package的抽象語法樹節點

Token.IMPORT
JCTree importDeclaration() {
        int pos = S.pos();
        S.nextToken();
        boolean importStatic = false;
        if (S.token() == STATIC) {
            checkStaticImports();
            importStatic = true;
            S.nextToken();
        }
        JCExpression pid = toP(F.at(S.pos()).Ident(ident()));
        do {
            int pos1 = S.pos();
            accept(DOT);
            if (S.token() == STAR) {
                pid = to(F.at(pos1).Select(pid, names.asterisk));//匯入“.*"的情況
                S.nextToken();
                break;
            } else {
                pid = toP(F.at(pos1).Select(pid, ident()));
            }
        } while (S.token() == DOT);
        accept(SEMI);
        return toP(F.at(pos).Import(pid, importStatic));
    }

步驟1:處理靜態匯入的情況,如果jdk大於1.5則checkStaticImports()返回true; 步驟2:通過com.sun.tools.javac.tree.TreeMaker.Ident(Name)方法生成JCIdent型別的樹節點; 步驟3:處理DOT後面的識別符號,如果token為STAR(*),則生成JCFieldAccess樹節點,並結束迴圈返回生成的樹;如果是正常的識別符號則正常生成樹節點; 步驟4:分號結束; 步驟5:生成JCImport型別的樹節點,節點tag為IMPORT(2),節點子節點為上步驟的各樹節點; import的樹節點

Token.CLASS

針對類以及類的屬性和方法構建抽象語法樹的過程比較繁雜,原始碼中對約定位置出現約定的內容進行不同的處理,可以看出java語言是一種規範性較強的語言。 步驟1:通過com.sun.tools.javac.parser.Parser.modifiersOpt(JCModifiers)方法建立class關鍵字前面修飾符(public/static/final)的樹節點JCModifiers; 步驟2:通過com.sun.tools.javac.parser.Parser.classOrInterfaceOrEnumDeclaration(JCModifiers, String)建立類或者介面或者列舉類的抽象語法樹;

整個例項程式碼的抽象語法是如圖

例項程式碼的抽象語法樹

總結

通過對原始碼的分析可知: 1> 抽象語法樹的每一個節點都是一個JCXXX型別的物件;這個JCXXX繼承了JCTree類並實現XXXTree介面; 2> JCTree類中定義了各種各樣的標籤,用來鑑別樹節點的型別。標籤用int整形值表示,IMPORT= 2,CLASSDEF=3,METHODDEF=4等;針對每一種標籤定義了對應標籤的樹節點,比如JCTree.INDETIFIER的節點型別是JCIden,JCTree.SEMI的節點型別是JCSkip,JCTree.PRIVATE或者JCTree.STATIC的節點型別是JCModifiers,JCTree.CLASSDEF原生資料型別int/Boolean/double等的節點型別是JCPrimitiveTypeTree。這裡可以思考一下為什麼要定義不同型別的節點? 3> 每一個樹節點下除了tag標籤外,還有不同的資訊;特別對於樹節點的巢狀,比如建立package的抽象語法樹時,name為com,type為JCIdent的樹節點作為一個JCExpression巢狀在name為test,type為JCFieldAccess樹節點內,而它又作為JCExpression巢狀在name為syx,type為JCFieldAccess樹節點內。 4> 最後把package的節點樹,import的節點樹和class節點樹封裝成一個JCCompilationUnit節點,此節點標籤tag為TOPLEVEL,表明為頂層節點。

至此Token流到抽象語法樹的過程已經結束,可以看出詞法分析和語法分析是融為一體的,我在分析的時候是拆開分析的。即得到一個token就會形成樹節點(這裡表述不太準確,並非一個token是一個樹節點,比如定義的成員變數前面的修飾符private static則為一個樹節點)。

參考資料

注:個人還在學習階段,部落格僅以記載學習過程,如有錯誤還望大佬批評指正。