美文网首页
浅谈Javac编译原理

浅谈Javac编译原理

作者: 雨中独奏 | 来源:发表于2018-07-21 13:45 被阅读0次

    Javac就是java编译器,它的作用就是把java源代码转化为JVM能识别的一种语言,然后JVM可以将这种语言转为当前运行机器所能识别的机器码,从而执行程序。这篇文章只谈源代码到jvm的字节码的过程。

    Javac使源码转为JVM字节码需要经历4个过程:词法分析,语法分析,语义分析,代码生成。
    本篇文章以jdk1.7版本及以下讲解,1.8后编译相关的源码改动较大,具体变化挖坑以后再补。

    词法分析

    Javac的主要词法分析器的接口类是com.sun.tools.javac.parser.Lexer,它的默认实现类是com.sun.tools.javac.parser.Scanner,Scanner会逐个读取Java源文件的单个字符,然后解析出符合Java语言规范的Token序列。

    public enum Token {
        EOF,
        ERROR,
        IDENTIFIER,
        ABSTRACT("abstract"),
        ASSERT("assert"),
        BOOLEAN("boolean"),
        BREAK("break"),
        BYTE("byte"),
        CASE("case"),
        CATCH("catch"),
        CHAR("char"),
        CLASS("class"),
        CONST("const"),
        CONTINUE("continue"),
        DEFAULT("default"),
        DO("do"),
        DOUBLE("double"),
        ELSE("else"),
        ENUM("enum"),
        EXTENDS("extends"),
        FINAL("final"),
        FINALLY("finally"),
        FLOAT("float"),
        FOR("for"),
        GOTO("goto"),
        IF("if"),
        IMPLEMENTS("implements"),
        IMPORT("import"),
        INSTANCEOF("instanceof"),
        INT("int"),
        INTERFACE("interface"),
        LONG("long"),
        NATIVE("native"),
        NEW("new"),
        PACKAGE("package"),
        PRIVATE("private"),
        PROTECTED("protected"),
        PUBLIC("public"),
        RETURN("return"),
        SHORT("short"),
        STATIC("static"),
        STRICTFP("strictfp"),
        SUPER("super"),
        SWITCH("switch"),
        SYNCHRONIZED("synchronized"),
        THIS("this"),
        THROW("throw"),
        THROWS("throws"),
        TRANSIENT("transient"),
        TRY("try"),
        VOID("void"),
        VOLATILE("volatile"),
        WHILE("while"),
        INTLITERAL,
        LONGLITERAL,
        FLOATLITERAL,
        DOUBLELITERAL,
        CHARLITERAL,
        STRINGLITERAL,
        TRUE("true"),
        FALSE("false"),
        NULL("null"),
        LPAREN("("),
        RPAREN(")"),
        LBRACE("{"),
        RBRACE("}"),
        LBRACKET("["),
        RBRACKET("]"),
        SEMI(";"),
        COMMA(","),
        DOT("."),
        ELLIPSIS("..."),
        EQ("="),
        GT(">"),
        LT("<"),
        BANG("!"),
        TILDE("~"),
        QUES("?"),
        COLON(":"),
        EQEQ("=="),
        LTEQ("<="),
        GTEQ(">="),
        BANGEQ("!="),
        AMPAMP("&&"),
        BARBAR("||"),
        PLUSPLUS("++"),
        SUBSUB("--"),
        PLUS("+"),
        SUB("-"),
        STAR("*"),
        SLASH("/"),
        AMP("&"),
        BAR("|"),
        CARET("^"),
        PERCENT("%"),
        LTLT("<<"),
        GTGT(">>"),
        GTGTGT(">>>"),
        PLUSEQ("+="),
        SUBEQ("-="),
        STAREQ("*="),
        SLASHEQ("/="),
        AMPEQ("&="),
        BAREQ("|="),
        CARETEQ("^="),
        PERCENTEQ("%="),
        LTLTEQ("<<="),
        GTGTEQ(">>="),
        GTGTGTEQ(">>>="),
        MONKEYS_AT("@"),
        CUSTOM;
    }
    

    Token是一个枚举类,定义了java语言中的系统关键字和符号,Token. IDENTIFIER用于表示用户定义的名称,如类名、包名、变量名、方法名等。

    这里有两个问题,Javac是如何分辨这一个个Token的呢?例如,它是怎么知道package就是一个Token.PACKAGE,而不是用户自定义的Token.INENTIFIER的名称呢。另一个问题是,Javac是如何知道哪些字符组合在一起就是一个Token的呢?

    答案1:Javac在进行词法分析时会由JavacParser根据Java语言规范来控制什么顺序、什么地方应该出现什么Token,Token流的顺序要符合Java语言规范。如package这个关键词后面必然要跟着用户定义的变量表示符,在每个变量表示符之间必须用“.”分隔,结束时必须跟一个“;”。
    下图是读取Token流程


    QQ图片20180721132355.png

    答案2:如何判断哪些字符组合是一个Token的规则是在Scanner的nextToken方法中定义的,每调用一次这个方法就会构造一个Token,而这些Token必然是com.sun.tools.javac.parser.Token中的任何元素之一。以下为源码:

    public void nextToken() {
            try {
                this.prevEndPos = this.endPos;
                this.sp = 0;
    
                while(true) {
                    this.pos = this.bp;
                    switch(this.ch) {
                    case '\t':
                    case '\f':
                    case ' ':
                        do {
                            do {
                                this.scanChar();
                            } while(this.ch == 32);
                        } while(this.ch == 9 || this.ch == 12);
    
                        this.endPos = this.bp;
                        this.processWhiteSpace();
                        break;
                    case '\n':
                        this.scanChar();
                        this.endPos = this.bp;
                        this.processLineTerminator();
                        break;
                    case '\u000b':
                    case '\u000e':
                    case '\u000f':
                    case '\u0010':
                    case '\u0011':
                    case '\u0012':
                    case '\u0013':
                    case '\u0014':
                    case '\u0015':
                    case '\u0016':
                    case '\u0017':
                    case '\u0018':
                    case '\u0019':
                    case '\u001a':
                    case '\u001b':
                    case '\u001c':
                    case '\u001d':
                    case '\u001e':
                    case '\u001f':
                    case '!':
                    case '#':
                    case '%':
                    case '&':
                    case '*':
                    case '+':
                    case '-':
                    case ':':
                    case '<':
                    case '=':
                    case '>':
                    case '?':
                    case '@':
                    case '\\':
                    case '^':
                    case '`':
                    case '|':
                    default:
                        if(this.isSpecial(this.ch)) {
                            this.scanOperator();
                            return;
                        } else {
                            boolean var6;
                            if(this.ch < 128) {
                                var6 = false;
                            } else {
                                char var2 = this.scanSurrogates();
                                if(var2 != 0) {
                                    if(this.sp == this.sbuf.length) {
                                        this.putChar(var2);
                                    } else {
                                        this.sbuf[this.sp++] = var2;
                                    }
    
                                    var6 = Character.isJavaIdentifierStart(Character.toCodePoint(var2, this.ch));
                                } else {
                                    var6 = Character.isJavaIdentifierStart(this.ch);
                                }
                            }
    
                            if(var6) {
                                this.scanIdent();
                                return;
                            } else {
                                if(this.bp != this.buflen && (this.ch != 26 || this.bp + 1 != this.buflen)) {
                                    this.lexError("illegal.char", new Object[]{String.valueOf(this.ch)});
                                    this.scanChar();
                                } else {
                                    this.token = Token.EOF;
                                    this.pos = this.bp = this.eofPos;
                                }
    
                                return;
                            }
                        }
                    case '\r':
                        this.scanChar();
                        if(this.ch == 10) {
                            this.scanChar();
                        }
    
                        this.endPos = this.bp;
                        this.processLineTerminator();
                        break;
                    case '\"':
                        this.scanChar();
    
                        while(this.ch != 34 && this.ch != 13 && this.ch != 10 && this.bp < this.buflen) {
                            this.scanLitChar();
                        }
    
                        if(this.ch == 34) {
                            this.token = Token.STRINGLITERAL;
                            this.scanChar();
                        } else {
                            this.lexError(this.pos, "unclosed.str.lit", new Object[0]);
                        }
    
                        return;
                    case '$':
                    case 'A':
                    case 'B':
                    case 'C':
                    case 'D':
                    case 'E':
                    case 'F':
                    case 'G':
                    case 'H':
                    case 'I':
                    case 'J':
                    case 'K':
                    case 'L':
                    case 'M':
                    case 'N':
                    case 'O':
                    case 'P':
                    case 'Q':
                    case 'R':
                    case 'S':
                    case 'T':
                    case 'U':
                    case 'V':
                    case 'W':
                    case 'X':
                    case 'Y':
                    case 'Z':
                    case '_':
                    case 'a':
                    case 'b':
                    case 'c':
                    case 'd':
                    case 'e':
                    case 'f':
                    case 'g':
                    case 'h':
                    case 'i':
                    case 'j':
                    case 'k':
                    case 'l':
                    case 'm':
                    case 'n':
                    case 'o':
                    case 'p':
                    case 'q':
                    case 'r':
                    case 's':
                    case 't':
                    case 'u':
                    case 'v':
                    case 'w':
                    case 'x':
                    case 'y':
                    case 'z':
                        this.scanIdent();
                        return;
                    case '\'':
                        this.scanChar();
                        if(this.ch == 39) {
                            this.lexError("empty.char.lit", new Object[0]);
                            return;
                        } else {
                            if(this.ch == 13 || this.ch == 10) {
                                this.lexError(this.pos, "illegal.line.end.in.char.lit", new Object[0]);
                            }
    
                            this.scanLitChar();
                            if(this.ch == 39) {
                                this.scanChar();
                                this.token = Token.CHARLITERAL;
                            } else {
                                this.lexError(this.pos, "unclosed.char.lit", new Object[0]);
                            }
    
                            return;
                        }
                    case '(':
                        this.scanChar();
                        this.token = Token.LPAREN;
                        return;
                    case ')':
                        this.scanChar();
                        this.token = Token.RPAREN;
                        return;
                    case ',':
                        this.scanChar();
                        this.token = Token.COMMA;
                        return;
                    case '.':
                        this.scanChar();
                        if(48 <= this.ch && this.ch <= 57) {
                            this.putChar('.');
                            this.scanFractionAndSuffix();
                            return;
                        }
    
                        if(this.ch == 46) {
                            this.putChar('.');
                            this.putChar('.');
                            this.scanChar();
                            if(this.ch == 46) {
                                this.scanChar();
                                this.putChar('.');
                                this.token = Token.ELLIPSIS;
                            } else {
                                this.lexError("malformed.fp.lit", new Object[0]);
                            }
    
                            return;
                        } else {
                            this.token = Token.DOT;
                            return;
                        }
                    case '/':
                        this.scanChar();
                        if(this.ch != 47) {
                            if(this.ch != 42) {
                                if(this.ch == 61) {
                                    this.name = this.names.slashequals;
                                    this.token = Token.SLASHEQ;
                                    this.scanChar();
                                } else {
                                    this.name = this.names.slash;
                                    this.token = Token.SLASH;
                                }
    
                                return;
                            }
    
                            this.scanChar();
                            Scanner.CommentStyle var1;
                            if(this.ch == 42) {
                                var1 = Scanner.CommentStyle.JAVADOC;
                                this.scanDocComment();
                            } else {
                                var1 = Scanner.CommentStyle.BLOCK;
    
                                while(this.bp < this.buflen) {
                                    if(this.ch == 42) {
                                        this.scanChar();
                                        if(this.ch == 47) {
                                            break;
                                        }
                                    } else {
                                        this.scanCommentChar();
                                    }
                                }
                            }
    
                            if(this.ch != 47) {
                                this.lexError("unclosed.comment", new Object[0]);
                                return;
                            }
    
                            this.scanChar();
                            this.endPos = this.bp;
                            this.processComment(var1);
                        } else {
                            do {
                                this.scanCommentChar();
                            } while(this.ch != 13 && this.ch != 10 && this.bp < this.buflen);
    
                            if(this.bp < this.buflen) {
                                this.endPos = this.bp;
                                this.processComment(Scanner.CommentStyle.LINE);
                            }
                        }
                        break;
                    case '0':
                        this.scanChar();
                        if(this.ch != 120 && this.ch != 88) {
                            this.putChar('0');
                            this.scanNumber(8);
                            return;
                        } else {
                            this.scanChar();
                            if(this.ch == 46) {
                                this.scanHexFractionAndSuffix(false);
                                return;
                            } else {
                                if(this.digit(16) < 0) {
                                    this.lexError("invalid.hex.number", new Object[0]);
                                } else {
                                    this.scanNumber(16);
                                }
    
                                return;
                            }
                        }
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        this.scanNumber(10);
                        return;
                    case ';':
                        this.scanChar();
                        this.token = Token.SEMI;
                        return;
                    case '[':
                        this.scanChar();
                        this.token = Token.LBRACKET;
                        return;
                    case ']':
                        this.scanChar();
                        this.token = Token.RBRACKET;
                        return;
                    case '{':
                        this.scanChar();
                        this.token = Token.LBRACE;
                        return;
                    case '}':
                        this.scanChar();
                        this.token = Token.RBRACE;
                        return;
                    }
                }
            } finally {
                this.endPos = this.bp;
                if(scannerDebug) {
                    System.out.println("nextToken(" + this.pos + "," + this.endPos + ")=|" + new String(this.getRawCharacters(this.pos, this.endPos)) + "|");
                }
    
            }
        }
    

    语法分析

    语法分析器是将词法分析器分析的Token流组建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。Javac的语法树使得Java源码更加结构化,这种结构化可以为后面的进一步处理提供方便。每个语法树上的节点都是com.sun.tools.javac.tree.JCTree的一个示例,关于语法树有以下规则 :
    1.每个语法节点都会实现一个接口xxxTree,这个接口又继承自com.sun.source.tree.Tree接口,如IfTree语法节点表示一个if类型的表达式,BinaryTree语法节点代表一个二元操作表达式。
    2.每个语法节点都是com.sun.tools.javac.tree.JCTree的子类,并且会实现第一节点中的xxxTree接口类,这个类的名称类似于JCxxx,如实现IfTree接口的实现类为JCIf,实现BinaryTree接口的类为JCBinary等。
    3.所有的JCxxx类都作为一个静态内部类定义在JCTree类中。

    JCTree类中有如下3个重要的属性项。
    1.Ttree tag:每个语法节点都会用一个整形常数表示,并且每个节点类型的数值是在前一个的基础上加1。顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL加1,等于2.
    2.pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的位置是0,而-1表示不存在。
    3.type:它表示的是这个节点是什么Java类型,如是int、float还是String.

    语义分析

    在得到结构化可操作的语法树后,还需要经过语义分析器给这棵语法树做一些处理,如给类添加默认的构造函数,检查变量在使用前是否已经初始化,将一些常量合并处理,检查操作变量类型是否匹配,检查异常是否已经捕获或抛出,解除java语法糖等等。
    一般有以下几个步骤:
    1.将Java类中的符号输入到符号表。主要由com.sun.tools.javac.comp.Enter类来完成,首先把所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号、超类符号和继承的接口类型符号都存着到一个未处理的列表中,然后在MemberEnter.completer()方法中奖未处理列表中所有类都解析到各自的类符号表中。Enter类解析中会给类添加默认构造函数。
    2.处理注解,由com.sun.tools.javac.processing.JavacProcessingEnvironment类完成
    3.进行标注com.sun.tools.javac.comp.Attr,检查语义的合法性并进行逻辑判断。如变量的类型是否匹配,使用前是否已经初始化等。
    4.进行数据流分析,检查变量在使用前是否已经被正确赋值,保证final修饰变量不会被重复赋值,确定方法的返回值类型,异常需要被捕获或者抛出,所有的语句都要被执行到(指检查是否有语句出现在return方法的后面)
    5.执行com.suntools.javac.comp.Flow,可以总结为去掉无用的代码,如用假的if代码块;变量的自动转换;去除语法糖,如foreach变成普通for循环。

    代码生成器

    把修饰后的语法树生成最终的Java字节码,通过com.sun.tools.javac.jvm.Gen类遍历语法树来生成。有以下两个步骤:
    1.将Java方法中的代码块转化成符合JVM语法的命令形式,JVM的操作都是基于栈的,所有的操作都必须经过出栈和进栈来完成。
    2.按照JVM的文件组装格式讲字节码输出到以class为扩展名的文件中
    这里还有两个辅助类:
    1.Items,这个类表示任何可寻址的操作项,包括本地变量、类实例变量或者常量池中用户自定义的常量等。
    2.Code,存储生成的字节码,并提供一些能够映射操作码的方法。
    示例说明:

    public class Daima{
      public static void main(String[] args){
        int rt = add(1,2);
    }
    
    public static int add(Integer a, Integer b){
      return a+b;
    }
    }
    

    重点说明add方法是如何转成字节码,这个方法中有一个加法表达式,JVM是基于栈来操作数值的,所以要执行一个二元操作,必须将两个数值a和b放到操作栈,然后利用加法操作符执行加法操作,将加法的结果放到当前栈的栈项,最后将这个结果返回给调用者。

    一张图总结Javac编译过程:


    QQ图片20180722232142.png

    相关文章

      网友评论

          本文标题:浅谈Javac编译原理

          本文链接:https://www.haomeiwen.com/subject/nkgxmftx.html