javac源码详解openJDKSE8版本2Lexer源码详解

lexer概述

  1. Lexer:Lexer主要不停扫描源文件,根据文法将源文件转成token。主要的方法为nextToken(计算下一个token)和token(计算当前的探针下的token)和token(在token缓冲池中寻找特定位置的token)
  2. Scanner: Scanner是Lexer的实现类。主要的属性有:tokens(token工具类),token,prevToken, savedTokens(token的缓冲池),tokenizer(真正把java文件转成token的类)
  3. Tokens: Tokens是token工具类。主要的属性有:names(name工具类),key(TokenKind),tokenName(所有token的name的容器)
  4. Names: Names是name工具类。主要提供进入tokenName这个容器的方法。
  5. TokenKind: TokenKind是token的种类。有两个属性:name(比如break,int等等)和tag(标签分为:DEFAULT,NAMED,STRING,NUMERIC)。
  6. Token: Token主要有属性:kind(种类),pos(起点位置),endPos(结束位置),comments(token上的注释)。又有子类NamedToken(token的名字),NumericToken(token的进制),StringToken(String的值)
  7. JavaTokenizer: 真正把java文件转成token的类。比较重要的属性有reader(读java源文件)。比较重要的方法:readToken(读下一个token)

lexer源码详解

入口

从com.sun.tools.javac.main.JavaCompiler的860行

            // These method calls must be chained to avoid memory leaks
            delegateCompiler =
                processAnnotations(
                    enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))),
                    classnames);

一直点进来到com.sun.tools.javac.parser.JavaTokenizer的450行

    /** Read token.
     */
    public Token readToken() {

        reader.sp = 0;
        name = null;
        radix = 0;

转换源文件的token主要方法readToken()

总体上是:主循环通过不停的扫描字符,根据当前字符和后面的字符,来进行判定需要进行什么样的处理,即生成什么样的token。

  1. 当碰到空格等时,一直读下一个字符,直到字符不再是空格等时,进行processWhiteSpace过程。
  2. 当碰到换行符时,进行processLineTerminator过程。
  3. 当碰到大小写字母和美元符和下划线时,进行scanIdent过程。
  4. 当碰到0时。在读一个字符下一个字符是x或者X时,设置进制为16,进行scanNumber过程;在读一个字符下一个字符是b或者B时,设置进制为2,进行scanNumber过程;在读一个字符下一个字符是b或者B时,设置进制为2,进行scanNumber过程;否者放入字符‘0‘,设置进制为8,进行scanNumber过程。当然过程中也有可能会去处理double或者float类型的token
  5. 当碰到1-9时,设置进制为10,进行scanNumber过程。
  6. 当碰到.时,读入后面的字符,判断是否为double或者float类型的token或者不确定数量的参数符号…或者是符号.
  7. 当碰到()[]{}时,直接生成对应的token
  8. 当碰到/时,认为是注解或者是特殊字符。注解分为//和JavaDoc注解和/***/注解
  9. 当碰到’时,读入下一个字符,判断是8进制数或者是一个字符
  10. 当碰到"时,一直读直到字符为",生成字符串token
  11. 当碰到!%&*?±<=>^|~@时,进入scanOperator过程
  12. 其他情况,如果符合java的命名规范则进行scanIdent,否则读入下一个字符
  13. 跳出主循环后根据tokenKind的Tag,生成对应的token
    /** Read token.
     */
    public Token readToken() {

        reader.sp = 0;
        name = null;
        radix = 0;

        int pos = 0;
        int endPos = 0;
        List<Comment> comments = null;

        try {
            loop: while (true) {
                pos = reader.bp;
                switch (reader.ch) {
                case ' ': // (Spec 3.6)
                case '\t': // (Spec 3.6)
                case FF: // (Spec 3.6)
                    do {
                        reader.scanChar();
                    } while (reader.ch == ' ' || reader.ch == '\t' || reader.ch == FF);
                    processWhiteSpace(pos, reader.bp);
                    break;
                case LF: // (Spec 3.4)
                    reader.scanChar();
                    processLineTerminator(pos, reader.bp);
                    break;
                case CR: // (Spec 3.4)
                    reader.scanChar();
                    if (reader.ch == LF) {
                        reader.scanChar();
                    }
                    processLineTerminator(pos, reader.bp);
                    break;
                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 '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 '_':
                    scanIdent();
                    break loop;
                case '0':
                    reader.scanChar();
                    if (reader.ch == 'x' || reader.ch == 'X') {
                        reader.scanChar();
                        skipIllegalUnderscores();
                        if (reader.ch == '.') {
                            scanHexFractionAndSuffix(pos, false);
                        } else if (reader.digit(pos, 16) < 0) {
                            lexError(pos, "invalid.hex.number");
                        } else {
                            scanNumber(pos, 16);
                        }
                    } else if (reader.ch == 'b' || reader.ch == 'B') {
                        if (!allowBinaryLiterals) {
                            lexError(pos, "unsupported.binary.lit", source.name);
                            allowBinaryLiterals = true;
                        }
                        reader.scanChar();
                        skipIllegalUnderscores();
                        if (reader.digit(pos, 2) < 0) {
                            lexError(pos, "invalid.binary.number");
                        } else {
                            scanNumber(pos, 2);
                        }
                    } else {
                        reader.putChar('0');
                        if (reader.ch == '_') {
                            int savePos = reader.bp;
                            do {
                                reader.scanChar();
                            } while (reader.ch == '_');
                            if (reader.digit(pos, 10) < 0) {
                                lexError(savePos, "illegal.underscore");
                            }
                        }
                        scanNumber(pos, 8);
                    }
                    break loop;
                case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    scanNumber(pos, 10);
                    break loop;
                case '.':
                    reader.scanChar();
                    if ('0' <= reader.ch && reader.ch <= '9') {
                        reader.putChar('.');
                        scanFractionAndSuffix(pos);
                    } else if (reader.ch == '.') {
                        int savePos = reader.bp;
                        reader.putChar('.'); reader.putChar('.', true);
                        if (reader.ch == '.') {
                            reader.scanChar();
                            reader.putChar('.');
                            tk = TokenKind.ELLIPSIS;
                        } else {
                            lexError(savePos, "illegal.dot");
                        }
                    } else {
                        tk = TokenKind.DOT;
                    }
                    break loop;
                case ',':
                    reader.scanChar(); tk = TokenKind.COMMA; break loop;
                case ';':
                    reader.scanChar(); tk = TokenKind.SEMI; break loop;
                case '(':
                    reader.scanChar(); tk = TokenKind.LPAREN; break loop;
                case ')':
                    reader.scanChar(); tk = TokenKind.RPAREN; break loop;
                case '[':
                    reader.scanChar(); tk = TokenKind.LBRACKET; break loop;
                case ']':
                    reader.scanChar(); tk = TokenKind.RBRACKET; break loop;
                case '{':
                    reader.scanChar(); tk = TokenKind.LBRACE; break loop;
                case '}':
                    reader.scanChar(); tk = TokenKind.RBRACE; break loop;
                case '/':
                    reader.scanChar();
                    if (reader.ch == '/') {
                        do {
                            reader.scanCommentChar();
                        } while (reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen);
                        if (reader.bp < reader.buflen) {
                            comments = addComment(comments, processComment(pos, reader.bp, CommentStyle.LINE));
                        }
                        break;
                    } else if (reader.ch == '*') {
                        boolean isEmpty = false;
                        reader.scanChar();
                        CommentStyle style;
                        if (reader.ch == '*') {
                            style = CommentStyle.JAVADOC;
                            reader.scanCommentChar();
                            if (reader.ch == '/') {
                                isEmpty = true;
                            }
                        } else {
                            style = CommentStyle.BLOCK;
                        }
                        while (!isEmpty && reader.bp < reader.buflen) {
                            if (reader.ch == '*') {
                                reader.scanChar();
                                if (reader.ch == '/') break;
                            } else {
                                reader.scanCommentChar();
                            }
                        }
                        if (reader.ch == '/') {
                            reader.scanChar();
                            comments = addComment(comments, processComment(pos, reader.bp, style));
                            break;
                        } else {
                            lexError(pos, "unclosed.comment");
                            break loop;
                        }
                    } else if (reader.ch == '=') {
                        tk = TokenKind.SLASHEQ;
                        reader.scanChar();
                    } else {
                        tk = TokenKind.SLASH;
                    }
                    break loop;
                case '\'':
                    reader.scanChar();
                    if (reader.ch == '\'') {
                        lexError(pos, "empty.char.lit");
                    } else {
                        if (reader.ch == CR || reader.ch == LF)
                            lexError(pos, "illegal.line.end.in.char.lit");
                        scanLitChar(pos);
                        char ch2 = reader.ch;
                        if (reader.ch == '\'') {
                            reader.scanChar();
                            tk = TokenKind.CHARLITERAL;
                        } else {
                            lexError(pos, "unclosed.char.lit");
                        }
                    }
                    break loop;
                case '\"':
                    reader.scanChar();
                    while (reader.ch != '\"' && reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen)
                        scanLitChar(pos);
                    if (reader.ch == '\"') {
                        tk = TokenKind.STRINGLITERAL;
                        reader.scanChar();
                    } else {
                        lexError(pos, "unclosed.str.lit");
                    }
                    break loop;
                default:
                    if (isSpecial(reader.ch)) {
                        scanOperator();
                    } else {
                        boolean isJavaIdentifierStart;
                        if (reader.ch < '\u0080') {
                            // all ASCII range chars already handled, above
                            isJavaIdentifierStart = false;
                        } else {
                            char high = reader.scanSurrogates();
                            if (high != 0) {
                                reader.putChar(high);

                                isJavaIdentifierStart = Character.isJavaIdentifierStart(
                                    Character.toCodePoint(high, reader.ch));
                            } else {
                                isJavaIdentifierStart = Character.isJavaIdentifierStart(reader.ch);
                            }
                        }
                        if (isJavaIdentifierStart) {
                            scanIdent();
                        } else if (reader.bp == reader.buflen || reader.ch == EOI && reader.bp + 1 == reader.buflen) { // JLS 3.5
                            tk = TokenKind.EOF;
                            pos = reader.buflen;
                        } else {
                            String arg = (32 < reader.ch && reader.ch < 127) ?
                                            String.format("%s", reader.ch) :
                                            String.format("\\u%04x", (int)reader.ch);
                            lexError(pos, "illegal.char", arg);
                            reader.scanChar();
                        }
                    }
                    break loop;
                }
            }
            endPos = reader.bp;
            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();
            }
        }
        finally {
            if (scannerDebug) {
                    System.out.println("nextToken(" + pos
                                       + "," + endPos + ")=|" +
                                       new String(reader.getRawCharacters(pos, endPos))
                                       + "|");
            }
        }
    }

转换源文件的token子过程scanNumber(int, int)

    private void scanNumber(int pos, int radix) {
        // for octal, allow base-10 digit in case it's a float literal
        this.radix = radix;
        int digitRadix = (radix == 8 ? 10 : radix);
        boolean seendigit = false;
        if (reader.digit(pos, digitRadix) >= 0) {
            seendigit = true;
            scanDigits(pos, digitRadix);
        }
        if (radix == 16 && reader.ch == '.') {
            scanHexFractionAndSuffix(pos, seendigit);
        } else if (seendigit && radix == 16 && (reader.ch == 'p' || reader.ch == 'P')) {
            scanHexExponentAndSuffix(pos);
        } else if (digitRadix == 10 && reader.ch == '.') {
            reader.putChar(true);
            scanFractionAndSuffix(pos);
        } else if (digitRadix == 10 &&
                   (reader.ch == 'e' || reader.ch == 'E' ||
                    reader.ch == 'f' || reader.ch == 'F' ||
                    reader.ch == 'd' || reader.ch == 'D')) {
            scanFractionAndSuffix(pos);
        } else {
            if (reader.ch == 'l' || reader.ch == 'L') {
                reader.scanChar();
                tk = TokenKind.LONGLITERAL;
            } else {
                tk = TokenKind.INTLITERAL;
            }
        }
    }
  1. 初始化radix和seedDigit参数
  2. 检查当前字符是否在进制范围内,如果是进行scanDigits(int, int),一直扫描数字。 scanDigits(int, int)会一直扫描数字(默认最大进制为16)最后不是数字或者是下划线 时停止。
private void scanDigits(int pos, int digitRadix) {
        char saveCh;
        int savePos;
        do {
            if (reader.ch != '_') {
                reader.putChar(false);
            } else {
                if (!allowUnderscoresInLiterals) {
                    lexError(pos, "unsupported.underscore.lit", source.name);
                    allowUnderscoresInLiterals = true;
                }
            }
            saveCh = reader.ch;
            savePos = reader.bp;
            reader.scanChar();
        } while (reader.digit(pos, digitRadix) >= 0 || reader.ch == '_');
        if (saveCh == '_')
            lexError(savePos, "illegal.underscore");
    }
  1. 如果进制为16或者碰到小数点后,需要处理进行scanHexFractionAndSuffix(int, boolean),来处理小数部分。同时内部会处理掉一些不符合规范的格式:如:0X.33
    private void scanHexFractionAndSuffix(int pos, boolean seendigit) {
        radix = 16;
        Assert.check(reader.ch == '.');
        reader.putChar(true);
        skipIllegalUnderscores();
        if (reader.digit(pos, 16) >= 0) {
            seendigit = true;
            scanDigits(pos, 16);
        }
        if (!seendigit)
            lexError(pos, "invalid.hex.number");
        else
            scanHexExponentAndSuffix(pos);
    }
  1. 如果是数字,并且进制为16,并且读入的字符为p或者P则进行scanHexExponentAndSuffix(int)来处理。scanHexExponentAndSuffix(int),会一直读入小数点后面的有效数字(10进制)。当以f或者F结尾时,生成浮点型数据;当以d或者D结尾时,生成双精度数据。
    private void scanHexExponentAndSuffix(int pos) {
        if (reader.ch == 'p' || reader.ch == 'P') {
            reader.putChar(true);
            skipIllegalUnderscores();
            if (reader.ch == '+' || reader.ch == '-') {
                reader.putChar(true);
            }
            skipIllegalUnderscores();
            if ('0' <= reader.ch && reader.ch <= '9') {
                scanDigits(pos, 10);
                if (!allowHexFloats) {
                    lexError(pos, "unsupported.fp.lit", source.name);
                    allowHexFloats = true;
                }
                else if (!hexFloatsWork)
                    lexError(pos, "unsupported.cross.fp.lit");
            } else
                lexError(pos, "malformed.fp.lit");
        } else {
            lexError(pos, "malformed.fp.lit");
        }
        if (reader.ch == 'f' || reader.ch == 'F') {
            reader.putChar(true);
            tk = TokenKind.FLOATLITERAL;
            radix = 16;
        } else {
            if (reader.ch == 'd' || reader.ch == 'D') {
                reader.putChar(true);
            }
            tk = TokenKind.DOUBLELITERAL;
            radix = 16;
        }
    }
  1. 当为10进制并且有小数点时,调用scanFractionAndSuffix(int)进行处理。但是会进行scanFraction(int)处理小数点后面的数字。当以f或者F结尾时,生成浮点型数据;当以d或者D结尾时,生成双精度数据。scanFraction(int)。会先一直扫描0-9的数字。当碰到E或者e的科学计数格式时,会再次接着扫描0-9的指数部分。
    private void scanFractionAndSuffix(int pos) {
        radix = 10;
        scanFraction(pos);
        if (reader.ch == 'f' || reader.ch == 'F') {
            reader.putChar(true);
            tk = TokenKind.FLOATLITERAL;
        } else {
            if (reader.ch == 'd' || reader.ch == 'D') {
                reader.putChar(true);
            }
            tk = TokenKind.DOUBLELITERAL;
        }
    }
    private void scanFraction(int pos) {
        skipIllegalUnderscores();
        if ('0' <= reader.ch && reader.ch <= '9') {
            scanDigits(pos, 10);
        }
        int sp1 = reader.sp;
        if (reader.ch == 'e' || reader.ch == 'E') {
            reader.putChar(true);
            skipIllegalUnderscores();
            if (reader.ch == '+' || reader.ch == '-') {
                reader.putChar(true);
            }
            skipIllegalUnderscores();
            if ('0' <= reader.ch && reader.ch <= '9') {
                scanDigits(pos, 10);
                return;
            }
            lexError(pos, "malformed.fp.lit");
            reader.sp = sp1;
        }
    }
  1. 当为10进制并且下一个字符为e|E|d|D|f|F时,进行scanFractionAndSuffix(int)处理。
  2. 当下一个字符为l|L时认为是长整型。
  3. 否则认为是整型。

转换源文件的token子过程scanIdent()

    private void scanIdent() {
        boolean isJavaIdentifierPart;
        char high;
        reader.putChar(true);
        do {
            switch (reader.ch) {
            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 '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 '_':
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                break;
            case '\u0000': case '\u0001': case '\u0002': case '\u0003':
            case '\u0004': case '\u0005': case '\u0006': case '\u0007':
            case '\u0008': 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 '\u001B':
            case '\u007F':
                reader.scanChar();
                continue;
            case '\u001A': // EOI is also a legal identifier part
                if (reader.bp >= reader.buflen) {
                    name = reader.name();
                    tk = tokens.lookupKind(name);
                    return;
                }
                reader.scanChar();
                continue;
            default:
                if (reader.ch < '\u0080') {
                    // all ASCII range chars already handled, above
                    isJavaIdentifierPart = false;
                } else {
                    if (Character.isIdentifierIgnorable(reader.ch)) {
                        reader.scanChar();
                        continue;
                    } else {
                        high = reader.scanSurrogates();
                        if (high != 0) {
                            reader.putChar(high);
                            isJavaIdentifierPart = Character.isJavaIdentifierPart(
                                Character.toCodePoint(high, reader.ch));
                        } else {
                            isJavaIdentifierPart = Character.isJavaIdentifierPart(reader.ch);
                        }
                    }
                }
                if (!isJavaIdentifierPart) {
                    name = reader.name();
                    tk = tokens.lookupKind(name);
                    return;
                }
            }
            reader.putChar(true);
        } while (true);
    }
  1. 当碰到大小写字母,数字,$,_ 时,放入一个字符,接着往下读。
  2. 当碰到\0000等字符时,读入一 个字符,接着往下读。
  3. 当碰到\u001A字符时,如果下一个读入的字符的位置大于当前缓冲池的长度,获取当前缓冲池中的所有的字符,并在java关键字表中对比,如果是java关键字则返回java关键字的tokenKind,否则返回为TokenKind.IDENTIFIER。
    /**
     * Create a new token given a name; if the name corresponds to a token name,
     * a new token of the corresponding kind is returned; otherwise, an
     * identifier token is returned.
     */
    TokenKind lookupKind(Name name) {
        return (name.getIndex() > maxKey) ? TokenKind.IDENTIFIER : key[name.getIndex()];
    }

key数组是关键字表

    /**
     * Keyword array. Maps name indices to Token.
     */
    private final TokenKind[] key;

4.其他的默认处理,会先判定是不是uincode字符。如果不是ASCII组成的字符,会再次判断是不是关键字,如果不是,则设置tokenKind为TokenKind.IDENTIFIER。

lexer源码读后感

  1. 源码的介绍程度:上面的源码我主要主要选取比较典型的读数字和读标识符两个部分。关于reader如何加载源文件,以及里面的nameTable是如何生成的暂时就不做介绍了。
  2. 编译原理:token生成方式使用代码硬读源文件,尚未使用NFA或者DFA等自动化的手段来处理;错误恢复过程使用最简单的skip方式,未使用其他手段。文法的设计尚未涉及到。
  3. 算法导论:此处暂时尚未涉及到常见的算法,或者编译本身就是一件浩大的算法。
  4. 对于此过程实现,对于我本次的目标印证情况,不是很友好,很多书中的知识,尚未涉及到,或者涉及到较少的使用,或者使用的都是书本中非常浅显的技术。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值