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阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容