摘要:對(duì)于而言,終結(jié)符與的是對(duì)應(yīng)的。這些內(nèi)容,我將其稱之為終結(jié)符的值。對(duì)于一個(gè)非終結(jié)符的產(chǎn)生式對(duì)于非終結(jié)符,其對(duì)象的字段則會(huì)表現(xiàn)成如下形式。對(duì)于里面的數(shù)組,其元素可能為終結(jié)符對(duì)象非終結(jié)符對(duì)象或表達(dá)式枚舉對(duì)象。
首先是 TerminalSymbol.java 即終結(jié)符。
package com.taozeyu.taolan.analysis; import java.util.HashSet; import com.taozeyu.taolan.analysis.Token.Type; public class TerminalSymbol { @SuppressWarnings("serial") private final static HashSetcareValueTypeSet = 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; } }
對(duì)于 Parser 而言,終結(jié)符 Terminal Symbol 與 Tokenizer 的 Token 是對(duì)應(yīng)的。特別的,對(duì)于以上代碼:
import com.taozeyu.taolan.analysis.Token.Type;
這里非終結(jié)符的類型(Type)我就直接用了 Token.java 中定義的內(nèi)部枚舉類啦。
特別的,
@SuppressWarnings("serial") private final static HashSetcareValueTypeSet = new HashSet () {{ add(Type.Keyword); add(Type.Sign); }};
這里將 Keyword、Sign 這兩種類型歸于 careValueType。這是什么意思呢?容我稍微說明一下。
對(duì)于 Parser 中的終結(jié)符,即便它無法繼續(xù)展開,但該終結(jié)符也并非就只能表示唯一的內(nèi)容。例如,對(duì)于一個(gè)終結(jié)符,已知它是 Identifier,那么它實(shí)際上對(duì)應(yīng)的字符串可能是 "hello_world" 或者 "accountBuilder" 之類的。這些內(nèi)容,我將其稱之為終結(jié)符的值 value。
顯然,Parser 分析語法樹的時(shí)候根本就不關(guān)心 Identifier 的值是什么,因?yàn)?Tokenizer 的工作就是將 Token 提取出來并識(shí)別其類型,以便讓 Parser 無需關(guān)心瑣碎的內(nèi)容。
因此,我們就知道了,每一個(gè)終結(jié)符都對(duì)應(yīng)一類 Token。
但是,對(duì)于 Keyword 和 Sign 而言,它的值卻影響 Parser 分析語法樹。例如 Sign 的值取 “+” 還是 “*” 會(huì)讓生成的語法樹完全不一樣。
換句話說,在定義終結(jié)符的時(shí)候,我應(yīng)該為每一個(gè) Sign 多帶帶歸于一個(gè)類型,而不是將它們統(tǒng)稱為一個(gè)類型。同樣的道理適用于 Keyword 類型。
但是,就我個(gè)人而言,將 Keyword 和 Sign 拆分成多個(gè)類型未免有些繁瑣,而且不好維護(hù)。于是我在這里做了一點(diǎn)取巧,將終結(jié)符分為 careValueType 型和非 careValueType 型。前者(目前有且僅有 Keyword、Sign) Parser 在識(shí)別它們的時(shí)候,不僅要考慮它們的 type,還要考慮它們的 value。而后者, Parser 僅考慮它們的 type,而 value 會(huì)被忽視。
具體請(qǐng)參考前面代碼中的如下函數(shù):
equals
hashCode
最后,我定義了一個(gè)特殊的非終結(jié)符:
static final TerminalSymbol Empty = new TerminalSymbol(null, null);
用來描述 ε。但是這個(gè)符號(hào)僅僅用于 Parser 初始化階段,在 Parser 編譯源代碼的時(shí)候這個(gè)符號(hào)是用不上的。
然后是 NonTerminalSymbol.java 即非終結(jié)符。
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
這個(gè)類實(shí)際上更多的是用來描述非終結(jié)符的產(chǎn)生式的。
對(duì)于一個(gè)非終結(jié)符的產(chǎn)生式:
A → abc | de | fg
對(duì)于非終結(jié)符 A,其對(duì)象的 expansionList 字段則會(huì)表現(xiàn)成如下形式。
對(duì)于里面的 Object 數(shù)組,其元素可能為 終結(jié)符對(duì)象(TerminalSymbol)、非終結(jié)符對(duì)象(NonTerminalSymbol)、或表達(dá)式枚舉對(duì)象(Exp)。
expansionList = new ArrayList<>() {{ add(new Object[] { a, b, c}); add(new Object[] { d, e}); add(new Object[] { f, g}); }};
其中表達(dá)式枚舉對(duì)象,就是代碼中 //TODO 要填寫的部分。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/65522.html
摘要:目前為止我們創(chuàng)建的文件列表新上一章中我們提到了個(gè)方法它們可以用來描述非終結(jié)符和展開式的形式,那么它們又是如何工作的呢文件中定義了一些方法。特別的,注意如下代碼這個(gè)方法可以紀(jì)錄被掉的一組非終結(jié)符,紀(jì)錄這些東西有什么用,將在隨后的章節(jié)介紹。 目前為止我們創(chuàng)建的文件列表: |- com.taozeyu.taolan.analysis |- FirstSetConstructor ...
摘要:是的,這個(gè)系列將呈現(xiàn)一個(gè)完整的編譯器從無到有的過程。但在寫這個(gè)編譯器的過程中,我可不會(huì)偷工減料,該有的一定會(huì)寫上的。該語言的虛擬機(jī)將運(yùn)行于之上,同時(shí)編譯器將使用實(shí)現(xiàn)。我早有寫編譯器的想法之前沒寫過,故希望一邊寫編譯器一邊完成這個(gè)系列。 是的,這個(gè)系列將呈現(xiàn)一個(gè)完整的編譯器從無到有的過程。當(dāng)然,為了保證該系列內(nèi)容的簡(jiǎn)潔(也為了降低難度),僅僅保證編譯器的最低要求,即僅能用。但在寫這個(gè)編譯...
摘要:自然,我們還是先從語言的編譯器下手吧。在動(dòng)手寫編譯器之前,得容我將編譯器的結(jié)構(gòu)進(jìn)行進(jìn)一步的劃分。這些將被語法分析器接收并進(jìn)行進(jìn)一步處理。由于本系列將著重于寫出編譯器,必要的理論和概念還是會(huì)交代的。從零開始寫個(gè)編譯器吧編譯器的結(jié)構(gòu)的博客 自然,我們還是先從 tao 語言的編譯器下手吧。在動(dòng)手寫編譯器之前,得容我將編譯器的結(jié)構(gòu)進(jìn)行進(jìn)一步的劃分。編譯器可視為一個(gè)黑盒,從其一端輸入源代碼,另一...
摘要:這樣的程序或稱工具有很多現(xiàn)成的可供選擇包括在平臺(tái)上可用的,但既然我這個(gè)系列叫做從零開始寫個(gè)編譯器吧,那顯然如果我用現(xiàn)成的工具,那是犯規(guī)行為。 Parser(語法分析器)的編寫相對(duì)于 Tokenizer (詞法分析器)要復(fù)雜得多,因此,在編寫之前可能也會(huì)鋪墊得更多一些。當(dāng)然,本系列旨在寫出一個(gè)編譯器,所以理論方面只會(huì)簡(jiǎn)單介紹 tao 語言所涉及的部分。 之前的幾章中,我純手寫了tao 語...
摘要:各位抱歉了,這個(gè)系列在多個(gè)平臺(tái)的專欄上連載。所以,我把從零開始寫個(gè)編譯器吧弄到了上。以后更新也是先從上開始。從零開始寫歌編譯器吧更及時(shí)的信息可以從我的公眾號(hào)上獲得雖然不怎么寫公眾號(hào),但是還是掛一下吧 各位抱歉了,這個(gè)系列在多個(gè)平臺(tái)的專欄上連載。每發(fā)一個(gè)新章節(jié),都要同步到各個(gè)專欄上,于是可能漏掉 Segmentfault 的博客。汗,其實(shí) Segmentfault 這邊已經(jīng)落后很久了。 ...
閱讀 3801·2023-04-26 02:07
閱讀 3679·2021-10-27 14:14
閱讀 2866·2021-10-14 09:49
閱讀 1632·2019-08-30 15:43
閱讀 2622·2019-08-29 18:33
閱讀 2375·2019-08-29 17:01
閱讀 922·2019-08-29 15:11
閱讀 593·2019-08-29 11:06