资讯专栏INFORMATION COLUMN

从零开始写个编译器吧 - TerminalSymbol.java 与 NonTerminalSymb

darry / 2250人阅读

摘要:对于而言,终结符与的是对应的。这些内容,我将其称之为终结符的值。对于一个非终结符的产生式对于非终结符,其对象的字段则会表现成如下形式。对于里面的数组,其元素可能为终结符对象非终结符对象或表达式枚举对象。

首先是 TerminalSymbol.java 即终结符。

package com.taozeyu.taolan.analysis;

import java.util.HashSet;

import com.taozeyu.taolan.analysis.Token.Type;

public class TerminalSymbol {

    @SuppressWarnings("serial")
    private final static HashSet careValueTypeSet = new HashSet() {{
        add(Type.Keyword);
        add(Type.Sign);
    }};

    static final TerminalSymbol Empty = new TerminalSymbol(null, null);

    public final Type type;
    public final String value;
    final boolean careValue;
    
    TerminalSymbol(Type type, String value) {
        this.type = type;
        this.value = value;
        this.careValue = careValueTypeSet.contains(type);
    }
    
    boolean isEmpty() {
        return this.type == null;
    }
    
    @Override
    public boolean equals(Object obj) {
        boolean isEquals = false;
        if(obj instanceof TerminalSymbol) {
            TerminalSymbol other = (TerminalSymbol) obj;
            isEquals = isEquals(this.type, other.type);
            if(isEquals & careValue) {
                isEquals = isEquals(this.value, other.value);
            }
        }
        return isEquals;
    }
    
    private boolean isEquals(Object o1, Object o2) {
        boolean isEquals = false;
        if(o1 == null & o2 == null) {
            isEquals = true;
        } else if(o1 != null & o2 != null) {
            isEquals = o1.equals(o2);
        }
        return isEquals;
    }
    
    @Override
    public int hashCode() {
        int hashCode = getHashCode(this.type);
        if(careValue) {
            hashCode ^= getHashCode(this.value);
        }
        return hashCode;
    }
    
    private int getHashCode(Object obj) {
        int hashCode = 0;
        if(obj != null) {
            hashCode = obj.hashCode();
        }
        return hashCode;
    }

    @Override
    public String toString() {
        String str;
        if(this.value != null) {
            str = " “" + this.value + "”";
        } else {
            if(this.type != null) {
                str = this.type.toString();
            } else {
                str = "ε";
            }
        }
        return str;
    }
}

对于 Parser 而言,终结符 Terminal Symbol 与 Tokenizer 的 Token 是对应的。特别的,对于以上代码:

import com.taozeyu.taolan.analysis.Token.Type;

这里非终结符的类型(Type)我就直接用了 Token.java 中定义的内部枚举类啦。

特别的,

@SuppressWarnings("serial")
private final static HashSet careValueTypeSet = new HashSet() {{
    add(Type.Keyword);
    add(Type.Sign);
}};

这里将 Keyword、Sign 这两种类型归于 careValueType。这是什么意思呢?容我稍微说明一下。

对于 Parser 中的终结符,即便它无法继续展开,但该终结符也并非就只能表示唯一的内容。例如,对于一个终结符,已知它是 Identifier,那么它实际上对应的字符串可能是 "hello_world" 或者 "accountBuilder" 之类的。这些内容,我将其称之为终结符的值 value。

显然,Parser 分析语法树的时候根本就不关心 Identifier 的值是什么,因为 Tokenizer 的工作就是将 Token 提取出来并识别其类型,以便让 Parser 无需关心琐碎的内容。

因此,我们就知道了,每一个终结符都对应一类 Token。

但是,对于 Keyword 和 Sign 而言,它的值却影响 Parser 分析语法树。例如 Sign 的值取 “+” 还是 “*” 会让生成的语法树完全不一样。

换句话说,在定义终结符的时候,我应该为每一个 Sign 多带带归于一个类型,而不是将它们统称为一个类型。同样的道理适用于 Keyword 类型。

但是,就我个人而言,将 Keyword 和 Sign 拆分成多个类型未免有些繁琐,而且不好维护。于是我在这里做了一点取巧,将终结符分为 careValueType 型和非 careValueType 型。前者(目前有且仅有 Keyword、Sign) Parser 在识别它们的时候,不仅要考虑它们的 type,还要考虑它们的 value。而后者, Parser 仅考虑它们的 type,而 value 会被忽视。

具体请参考前面代码中的如下函数:

equals

hashCode

最后,我定义了一个特殊的非终结符:

static final TerminalSymbol Empty = new TerminalSymbol(null, null);

用来描述 ε。但是这个符号仅仅用于 Parser 初始化阶段,在 Parser 编译源代码的时候这个符号是用不上的。

然后是 NonTerminalSymbol.java 即非终结符。

package com.taozeyu.taolan.analysis;

import java.util.ArrayList;
import java.util.HashSet;

class NonTerminalSymbol {

    static enum Exp {
        //TODO 
    }
    
    final Exp exp;
    Character sign = null;
    
    final ArrayList expansionList = new ArrayList<>();
    final ArrayList banList = new ArrayList<>();
    
    final ArrayList> firstSetList = new ArrayList<>();
    final HashSet firstSet = new HashSet<>();

    NonTerminalSymbol(Exp exp) {
        this.exp = exp;
    }
    
    NonTerminalSymbol ban(TerminalSymbol...args) {
        for(TerminalSymbol node:args) {
            banList.add(node);
        }
        return this;
    }
    
    NonTerminalSymbol or(Object...args) {
        expansionList.add(args);
        return this;
    }
    
    NonTerminalSymbol sign(char sign) {
        this.sign = sign;
        return this;
    }

    @Override
    public String toString() {
        String str = String.valueOf(exp);
        if(sign != null) {
            str += "(" + sign + ")";
        }
        return str;
    }
}

这个类实际上更多的是用来描述非终结符的产生式的。

对于一个非终结符的产生式:

A → abc | de | fg

对于非终结符 A,其对象的 expansionList 字段则会表现成如下形式。

对于里面的 Object 数组,其元素可能为 终结符对象(TerminalSymbol)、非终结符对象(NonTerminalSymbol)、或表达式枚举对象(Exp)。

expansionList  = new ArrayList<>() {{

    add(new Object[] { a, b, c});
    add(new Object[] { d, e});
    add(new Object[] { f, g});
}};

其中表达式枚举对象,就是代码中 //TODO 要填写的部分。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65522.html

相关文章

  • 从零开始写个译器 - tao 语言的文法定义(下)

    摘要:目前为止我们创建的文件列表新上一章中我们提到了个方法它们可以用来描述非终结符和展开式的形式,那么它们又是如何工作的呢文件中定义了一些方法。特别的,注意如下代码这个方法可以纪录被掉的一组非终结符,纪录这些东西有什么用,将在随后的章节介绍。 目前为止我们创建的文件列表: |- com.taozeyu.taolan.analysis |- FirstSetConstructor ...

    Michael_Lin 评论0 收藏0
  • 从零开始写个译器系列

    摘要:是的,这个系列将呈现一个完整的编译器从无到有的过程。但在写这个编译器的过程中,我可不会偷工减料,该有的一定会写上的。该语言的虚拟机将运行于之上,同时编译器将使用实现。我早有写编译器的想法之前没写过,故希望一边写编译器一边完成这个系列。 是的,这个系列将呈现一个完整的编译器从无到有的过程。当然,为了保证该系列内容的简洁(也为了降低难度),仅仅保证编译器的最低要求,即仅能用。但在写这个编译...

    genedna 评论0 收藏0
  • 从零开始写个译器 - 译器的结构

    摘要:自然,我们还是先从语言的编译器下手吧。在动手写编译器之前,得容我将编译器的结构进行进一步的划分。这些将被语法分析器接收并进行进一步处理。由于本系列将着重于写出编译器,必要的理论和概念还是会交代的。从零开始写个编译器吧编译器的结构的博客 自然,我们还是先从 tao 语言的编译器下手吧。在动手写编译器之前,得容我将编译器的结构进行进一步的划分。编译器可视为一个黑盒,从其一端输入源代码,另一...

    wudengzan 评论0 收藏0
  • 从零开始写个译器 - Parser 语法分析器

    摘要:这样的程序或称工具有很多现成的可供选择包括在平台上可用的,但既然我这个系列叫做从零开始写个编译器吧,那显然如果我用现成的工具,那是犯规行为。 Parser(语法分析器)的编写相对于 Tokenizer (词法分析器)要复杂得多,因此,在编写之前可能也会铺垫得更多一些。当然,本系列旨在写出一个编译器,所以理论方面只会简单介绍 tao 语言所涉及的部分。 之前的几章中,我纯手写了tao 语...

    fai1017 评论0 收藏0
  • 从零开始写个译器系列——将在 GitBook 上以在线电子书的形式继续连载

    摘要:各位抱歉了,这个系列在多个平台的专栏上连载。所以,我把从零开始写个编译器吧弄到了上。以后更新也是先从上开始。从零开始写歌编译器吧更及时的信息可以从我的公众号上获得虽然不怎么写公众号,但是还是挂一下吧 各位抱歉了,这个系列在多个平台的专栏上连载。每发一个新章节,都要同步到各个专栏上,于是可能漏掉 Segmentfault 的博客。汗,其实 Segmentfault 这边已经落后很久了。 ...

    justCoding 评论0 收藏0

发表评论

0条评论

darry

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<