javac編譯器框架1——詞法和語法分析器

1. 詞法分析器與語法分析器

1.1 javac詞法、語法分析器概覽及Intellij IDEA調試跟蹤流程

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();
            }
Tokens類具體內容

問題.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;
        }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容