javac編譯器框架1——詞法和語(yǔ)法分析器

1. 詞法分析器與語(yǔ)法分析器

1.1 javac詞法、語(yǔ)法分析器概覽及Intellij IDEA調(diào)試跟蹤流程

javac詞法分析器Intellij IDEA調(diào)試跟蹤流程
  • 詞法分析器實(shí)例的生成流程


詞法器與語(yǔ)法器實(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();
            }
Tokens類具體內(nèi)容

問(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;
        }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,523評(píng)論 25 708
  • Java是一種可以撰寫跨平臺(tái)應(yīng)用軟件的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言。Java 技術(shù)具有卓越的通用性、高效性、平臺(tái)移植性和...
    Java小辰閱讀 32,711評(píng)論 0 17
  • 時(shí)光中的等待 每顆心都會(huì)跳躍 在措手不及的時(shí)光里 喜歡的人悄然而來(lái) 對(duì)著我回眸一笑 和你在一起的時(shí)候...
    陳汐年閱讀 9,813評(píng)論 37 76
  • 20180127【讀書清單】 書名:怕,就會(huì)輸一輩子(二) 作者:[美]奧里森·馬登 01.機(jī)會(huì)不是石頭,隨便可以...
    泉布閱讀 185評(píng)論 0 3
  • 很久沒(méi)有寫東西了。好像還有點(diǎn)不安的感覺(jué)
    花歲兒s閱讀 66評(píng)論 0 0