Java终结符_从零开始写个编译器吧 - TerminalSymbol.java 与 NonTerminalSymbol.java

首先是 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 要填写的部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值