項目的完整代碼在 C2j-Compiler
寫在前面
這個系列算作為我自己在學習寫一個編譯器的過程的一些記錄,算法之類的都沒有記錄原理性的東西,想知道原理的在龍書里都寫得非常清楚,但是我自己一開始是不怎么看得下來,到現在都還沒有完整的看完,它像是一本給已經有基礎的人寫的書。
在parse包里一共有8個文件,就是語法分析階段寫的所有東西啦
- Symbols.java
- Production.java
- SyntaxProductionInit.java
- FirstSetBuilder.java
- ProductionManager.java
- ProductionsStateNode.java
- StateNodeManager.java
- LRStateTableParser.java
項目的完整代碼在 C2j-Compiler
SyntaxProductionInit 語法初始化
在上一篇說了,竟然要驗證句子正確與否,自然就需要語法,也就是給出相應的語法推導式
所有語法的初始化工作都在SyntaxProductionInit里完成
///EXT_DECL_LIST ->EXT_DECL_LIST COMMA EXT_DECL
right = getProductionRight(new int[]{Token.EXT_DECL_LIST.ordinal(), Token.COMMA.ordinal(), Token.EXT_DECL.ordinal()});
production = new Production(productionNum, Token.EXT_DECL_LIST.ordinal(), 0, right);
productionNum++;
addProduction(production, false);
比如下面這個就對應C語言的變量聲明語句的推導式,PROGRAM是整個推導式的開始符號,EXT_DEF_LIST就是聲明列表,這里的EXT_DEF_LIST -> EXT_DEF_LIST EXT_DEF需要注意一下是左遞歸情況,LR語法是可以處理的,關於這個可以看之前的博文。
比如EXT_DECL_LIST -> EXT_DECL -> VAR_DECL 多個變量名稱聲明可以推導是一個變量名稱聲明或者多個變量名稱聲明 + 逗號 + 變量名稱聲明
VAR_DECL 則可以是一個標識符或者一個多重指針
即一個從葉子節點不斷的推導,讀入終結符,最后推導到開始符號,且輸入流也已經讀完
/*
* PROGRAM -> EXT_DEF_LIST
*
* EXT_DEF_LIST -> EXT_DEF_LIST EXT_DEF
*
* EXT_DEF -> OPT_SPECIFIERS EXT_DECL_LIST SEMI
* | OPT_SPECIFIERS SEMI
*
*
* EXT_DECL_LIST -> EXT_DECL
* | EXT_DECL_LIST COMMA EXT_DECL
*
* EXT_DECL -> VAR_DECL
*
* OPT_SPECIFIERS -> CLASS TTYPE
* | TTYPE
* | SPECIFIERS
* | EMPTY?
*
* SPECIFIERS -> TYPE_OR_CLASS
* | SPECIFIERS TYPE_OR_CLASS
*
*
* TYPE_OR_CLASS -> TYPE_SPECIFIER
* | CLASS
*
* TYPE_SPECIFIER -> TYPE
*
* NEW_NAME -> NAME
*
* NAME_NT -> NAME
*
* VAR_DECL -> | NEW_NAME
*
* | START VAR_DECL
*
*/
在語法推導式初始化的過程一共要構建三個數據結構
private HashMap<Integer, ArrayList<Production>> productionMap = new HashMap<>();
private HashMap<Integer, Symbols> symbolMap = new HashMap<>();
private ArrayList<Symbols> symbolArray = new ArrayList<>();
- ProductionMap的key就是推導式的左邊,value即對應的一個或多個產生式
- SymbolMap類似ProductionMap,key是推導式的左邊,value是一個或者多個產生式的右邊
Symbol類似Production,也是用來表示產生式的,但是稍有點不同,也還包括終結符,在后面也會有不同的作用,
//Symbols
public int value;
public ArrayList<int[]> productions;
public ArrayList<Integer> firstSet = new ArrayList<>();
public boolean isNullable;
如果一個非終結符,它可以推導出空集,那么這樣的非終結符我們稱之為nullable的非終結符
- symbolArray存儲着每一個Symbols對象,在后面也會有不一樣的作用
Production 產生式類
在上一篇里說到驗證語法的過程就是在一堆對應語法產生式中推導出答案,Production類就是來表示一個產生式
private int dotPos = 0;
private int left;
private ArrayList<Integer> right;
private ArrayList<Integer> lookAhead = new ArrayList<>();
private int productionNum = -1;
public Production(int productionNum, int left, int dot, ArrayList<Integer> right) {
this.left = left;
this.right = right;
this.productionNum = productionNum;
lookAhead.add(Token.SEMI.ordinal());
if (dot >= right.size()) {
dot = right.size();
}
this.dotPos = dot;
}
- left和right就是產生式的左邊和右邊,都是用之前Token的值來表示
- lookahead向前看集合,后面用到再提
- dos就像構造這個自動機的輔助工具,之后用到詳細說
- productionNum是這個產生式對應編號,這個編號是在初始化語法的時候給定的
小結
這一篇主要介紹了幾個數據結構,這幾個數據結構都是之后構建有限狀態自動機的基礎。本來想把狀態機的構建也寫在這一篇,但是如果加上去之后,篇幅太長,加一部分會感覺不成模塊,有點分散,所以自動機的構建就寫在下一篇。
另外我的github博客:https://dejavudwh.cn/