第三章-词法分析

 

  

(1)词法分析概述

词法分析将Java源文件的字符流转变为对应的Token流。涉及到的主要类的继承关系如下图所示。 

 

Lexer表示词法分析器,而实现这个接口的Scanner与DocCommentScanner可以将输入的ASCII字符流与Unicode转义字符转换为合法的Token流。其中的DocCommentScanner词法分析器扩展了Scanner的功能,可处理注释相关的部分。ScannerFactory是工厂类,可通过调用这个类的工厂方法获取到具体的词法分析器实例。 

 

(2)Token类型

一些规范的Token定义在Token枚举类中,如下:
其实在形成一个Token流时,在词法分析的过程中,可以归纳为三类,分别是:
/** An interface that defines codes for Java source tokens
 *  returned from lexical analysis.
 */
public enum Token implements Formattable {
    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类别分为如下3类: 

(1)标识符号:如Token.PLUS、Token.EQ、Token.LBRACE、Token.RBRACE、

Token.ELLIPSIS等,其中Token.ELLIPSIS是为了支持Java 7中增加的可变参数的语法。

 

(2)Java的保留关键字: 

数据类型:
Boolean\int\long\short\byte\float\double\char\class\interface
 
流程控制:
if\else\do\while\for\switch\case\default\break\continue\return\try\catch\finally
 
修饰符:      
public   
protected   
private   
final   
void    
static   
strictfp    
abstract    
transient
synchronized    
volatile   
native
 
动作:          
package   
import    
throw   
throws    
extends   
implements   
this   
Super   
instanceof   
new
 
保留字:      
true\false\null\goto\const
 
(3)Token.IDENTIFIER 
用来表示用户自定义的类名、包名、变量包、方法名等
 
(4)ERROR与EOF 
 

(3)Name对象的生成与存储

词法分析是从调用Java的parseFiles(Iterable<JavaFileObject> fileObjects)方法开始的,这个方法通过间接通过调用JavaFileObject的getCharContent()获取字符流输入,这一部分在第二间中有详细介绍,这里为节省篇幅不再重复介绍。将字符流转Token最重要的就是Scanner中的nextToken()方法了,可能读者还不太明白获取到字符流后是如何调用nextToken()方法的,下一章节将会详细的说明。

在Java中,哪些字符组合成为一个Token是通过调用nextToken方法实现的,这个方法决定了字符的组合,大概的逻辑如下:

调用nextToken生成的字符集合都是一个Name对象,所有的Name对象都存储在Name.Table这个内部类中。接下来可以通过Keywords类查找到Name对象所对应的Token类型。由于Keywords会将在Token中所有的元素按照它们的Token.name先转化成Name对象,然后建立Name和Token的对应关系,这个关系保存在Keyworks类的key数组中。 

 

每调用一次方法就会构造一个Token,而这些Token必然是com.sun.tools.javac.parser.Token中的任何元素之一。

 

 

 

(4)映射Token

Keywords会将在Token中所有的元素按照它们的Token.name先转化成Name对象,然后建立Name和Token的对应关系,这个关系保存在Keyworks类的key数组中。 Keywords类

中定义了关键的属性如下:

/** The names of all tokens.
 */
private Name[] tokenName = new Name[values().length];

tokenName保存了Token到name的映射,准确说是tokenName的下标为各个Token在枚举类中定义的序号,序号是从0开始的。如下:

Name[0]=null;
Name[1]=null;
Name[2]=null;
Name[3]=NameImpl("abstract");
Name[4]=NameImpl("assert");
...
Name[108]=NameImpl(">>>=");
Name[109]=NameImpl("@");
Name[110]=null;

可以看到有Token名称的都被转换为了NameImpl对象。这样我们就可以根据Token找到对应的NameImple了。

定义了Name到Token的映射,如下: 

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

   

同样在调用构造函数时进行初始化,具体的初始化过程如下: 

protected Keywords(Context context) {
        // ...
        key = new Token[maxKey+1];
        for (int i = 0; i <= maxKey; i++) {
            key[i] = IDENTIFIER;
        }
        for (Token t : values()) {
            if (t.name != null) {
                int oi = t.ordinal();
                int ti = tokenName[oi].getIndex();
                key[ti] = t;
            }
        }
}

 

其中的maxKey保存了??的最大值。调用Name对象的getIndex()方法为key数组下标赋值,而值为这个Token对象,这样我们可以在最短时间内根据Name对象获取对应Token了。

例如现在从字符流对象中查找到一串字符串"abstract",首先转换为Name对象,接着调用此Name对象的getIndex()方法获取到值后可直接将值做为key数组的下标获取到Token。如调用如下Keywords中的方法:

 

public Token key(Name name) {
    if(name.getIndex() > maxKey){
        return IDENTIFIER;
    }else{
        return key[name.getIndex()];
    }
}

 

此方法还根据maxKey来判断是否为标识符,假如某个字符串为自定义标识符时会返回IDENTIFIER。

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/extjs4/p/9695362.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值