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 要填写的部分。
Java终结符_从零开始写个编译器吧 - TerminalSymbol.java 与 NonTerminalSymbol.java相关推荐
- 编译器入门 语法分析器 java_从零开始写个编译器吧 - Parser 语法分析器
Parser(语法分析器)的编写相对于 Tokenizer (词法分析器)要复杂得多,因此,在编写之前可能也会铺垫得更多一些.当然,本系列旨在"写出"一个编译器,所以理论方面只会简 ...
- 自己写编译器词法分析c语言,从零开始写个编译器吧 - 开始写词法分析器(3)...
上周周末旅游去了,就没更新了,虽然回到海拔0m的地区,不过目前似乎还在缺氧,所以本次就少更点吧. 这章将结束词法分析的部分. 在之前的章节(第7章从零开始写个编译器吧 - 开始写词法分析器(1))中我 ...
- rust 手动关闭子线程_从零开始写 OS (9) —— 内核线程
上一篇 小源:从零开始写 OS (8) -- 创建页表zhuanlan.zhihu.com 本章代码对应 commit :de86ae6e1e8bdfe3388530f82b2081fe29e40b ...
- 【Java EE】从零开始写项目【总结】
从零开发项目概述 最近这一直在复习数据结构和算法,也就是前面发出去的排序算法八大基础排序总结,Java实现单向链表,栈和队列就是这么简单,十道简单算法题等等- 被虐得不要不要的,即使是非常简单有时候绕 ...
- 如何搭建python框架_从零开始:写一个简单的Python框架
原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...
- java情人节_情人节写给女朋友Java Swing代码程序
马上又要到情人节了,再不解风情的人也得向女友表示表示.作为一个程序员,示爱的时候自然也要用我们自己的方式. 这里给大家上传一段我在今年情人节的时候写给女朋友的一段简单的Java Swing代码,主要定 ...
- python 小说爬虫_从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载
从零开始写Python爬虫 --- 1.7 爬虫实践: 排行榜小说批量下载Ehco 5 个月前 本来只是准备做一个爬起点小说名字的爬虫,后来想了一下,为啥不顺便把小说的内容也爬下来呢?于是我就写了这个 ...
- python爬虫代理的使用_从零开始写Python爬虫 --- 2.4 爬虫实践:代理的爬取和验证...
爬网站的时候,由于各种原因,ip被锁了,这个时候我们就需要通过代理来突破封锁.网上有很多代理网站,付费和免费的都有,这次我们就来写一个scrapy爬虫,爬一些免费的代理下来用. 目标分析: 本次爬取了 ...
- 送给女朋友的java程序_情人节写给女朋友Java Swing代码程序
马上又要到情人节了,再不解风情的人也得向女友表示表示.作为一个程序员,示爱的时候自然也要用我们自己的方式. 这里给大家上传一段我在今年情人节的时候写给女朋友的一段简单的Java Swing代码,主要定 ...
最新文章
- java jdbc 工具_实现JDBC的工具类
- Java并发机制的底层实现原理
- mysql栏目表设计_MySQL表设计
- 大数据视域下网络涉军舆情管控研究
- vue.js 01 模板语法
- 数据结构,Java实现递归回溯,寻找出迷宫路线,解决迷宫问题
- git忽略某个文件夹
- S3C6410 裸机硬件JPEG解码(转)
- 「雕爷学编程」Arduino动手做(33)——ESP-01S无线WIFI模块
- Python多线程(自学必备 超详细)
- recycleview 自动循环滚动_滚动真空包装机包装东北冷面和400真空机包装东北冷面的比较...
- 内存占用率火速暴降!Windows 11“效率模式”CPU性能提升4倍
- Software--IoC 依赖倒置 控制反转
- java线程wait和notify详解
- 传输层的几个部分的ALCAP、SSCOP、MTP3-B、SCCP、SAAL、SCCF、STC、IP、UDP、GTPU
- Delphi -- 农历算法
- GB28181协议之实时视频
- 仪表盘故障图像识别_一张图教你识别汽车故障灯!
- [学习日志]UI如何与数据绑定?
- 【沧海拾昧】微机原理:并行接口电路8255芯片