从零开始写个编译器吧 - Token.java 文件的编写

635 查看

现在,让我们来动手写编译器的第一个个java文件吧。本章要写的类,是Token类。如其名字所示,这个类实例化的对象用于表示词法分析器 Tokenizer 的产物。同时,也作为下一阶段的语法分析器 Parser 的原料。

让我们开始吧!先新建一个Token.java 于 src/com/taozeyu/taolan/analysis之中。

package com.taozeyu.taolan.analysis;

public class Token {
    public static enum Type {
        Keyword, Number, Identifier, Sign, Annotation,
        String, RegEx, Space, NewLine, EndSymbol;
    }
    final Type type;
    final String value;

    Token(Type type, String value) {
        //TODO
    }
}

如之前章节讨论的一样,Token对象应该包含类型和语素两个属性。注意这个 Type 枚举类型,其内容就是我在上一章所说的 tao 语言应该具备的10种单词类型。

我希望词法分析器从源代码中提取出语素,并根据上下文推测出单词类型,从而构造出Token对象。但实际上,请注意Type这个枚举类的三个类型:

Keyword, Number, Identifier

这三个类型不同之处?实际上这三个类型的形式极其类似(甚至 Keyword 和 Identifier 的形式是完全相同的),并且可以仅通过语素准确判定其类型。因此,我希望对词法分析器 Tokenizer 隐藏着三种类型的区别,将这三种类型统称 Identifier,以简化编码。

Token(Type type, String value) {
    if(type == Type.Identifier) {
        char firstChar = value.charAt(0);
        if(firstChar >= '0' & firstChar < '9') {
            type = Type.Number;
        } else if(keywordsSet.contains(value)){
            type = Type.Keyword;
        }
    }
    this.type = type;
    this.value = value;
}

于是,Token 对 Tokenizer 隐藏了 Number、Keyword 类型。Tokenizer 只需要构造出 Identifier 类型即可,进一步细分将在 Token 的构造函数中进行。

特别的,构造函数中引用了一个 keywordsSet 变量。实际上这个变量应该包含所有 tao 语言的关键字。此处稍稍定义一下。

private static final HashSet<String> keywordsSet = new HashSet<>();

static {
    keywordsSet.add("if");
    keywordsSet.add("when");
    keywordsSet.add("elsif");
    keywordsSet.add("else");
    keywordsSet.add("while");
    keywordsSet.add("begin");
    keywordsSet.add("until");
    keywordsSet.add("for");
    keywordsSet.add("do");
    keywordsSet.add("try");
    keywordsSet.add("catch");
    keywordsSet.add("finally");
    keywordsSet.add("end");
    keywordsSet.add("def");
    keywordsSet.add("var");
    keywordsSet.add("this");
    keywordsSet.add("null");
    keywordsSet.add("throw");
    keywordsSet.add("break");
    keywordsSet.add("continue");
    keywordsSet.add("return");
    keywordsSet.add("operator");
}

好吧,tao 语言我能想出的可能有的关键字都在这里了。如果有遗漏或者多余,其实以后再回过头来改也没问题。

特别的,对于 Annotation、String、RegEx ,它们在源代码中存在的形式和具体的语素并不完全等同。

  • #我是注释(回车)
  • "我是一个字符串"
  • ^\s+\d+$
  • 对于 Tokenizer 而言,它倾向于读出如上一整行信息。但是仅仅只加下划线的文字是Token的语素。因此,我还需要再构造函数中对构造参数value进行进一步提取,以得到正确的语素。

另外,EndSymbol 的语素必须为空,不管 Tokenizer 传入什么参数都必须如此。