編寫Lex和Yacc

大學課程設計中,有一次是編寫Lex(詞法分析器的生成器)和Yacc(語法分析器的生成器),編寫這類工具軟件不是一件容易的事情。這篇文章記錄了當時編程時候的主要思想,主要仍是編譯原理的思想。node

準備

Lex

根據輸入文件生成RE—>NFA—>DFA—>簡化的DFA—>根據DFA生成文件。c++

RE處理:

對正規表達式進行處理使其只有|、*、(、)等特殊符號,代換{}[]-等
將RE轉化爲後綴表達式   

生成NFA:

把下列類型的string轉換:算法

M+----->M.M*編程

M?------>M|e閉包

最後一共有四種鏈接:工具

普通字符(除了.,|,*):設計

編譯lex1

a*:增長三條epsilon邊,而且修改終點和起點。blog

編譯lex3

a|b:ip

新建兩個節點,並把其中一個指向原來的兩個nfa的起點,另外一個被原來的兩個終點指向。這四個邊均是epsilon。修改終點和起點。ci

編譯lex2

a.b:鏈接,合併2號和3號節點。(紅色表明nfa的起點,黑色表明nfa的終點)

編譯lex4

NFA合併:

直接添加一個起點,指向全部nfa的起點。修改起點值,並把原來的終點(每一個nfa只有一個)都加入到最後的終點集合。

NFA--->DFA

求閉包

經過epsilon到達的邊。迭代直到T’=T,每次

repeat:

T1 = T;
T=T1∪T1全部點能經過epsilon到達的邊
until T1==T
求經過某個字母到達的子集
for each(Node* node in d)//對d的每個節點

{
vector<Node*> eout = node->findNext(c);//求出每一個節點的出邊集合
d1.insert(eout.begin(), eout.end());//將後繼的每個節點不重複的插入d1
}
最後返回這個d1的閉包
 
生成DFA

用j標記當前遍歷的節點,用p標記已經存在的節點數量

對當前遍歷的節點求每一個字符的出邊集合,若是有的話,就求該集合的閉包,並判斷是否已經存在,作相應的處理:

    若是已經存在,則加邊

    若是不存在,新建節點,再加邊,p++

DFA最小化

參考維基百科中關於Hopcroft的算法。

可是對於DFA來講,剛開始並不能簡單地分爲兩個非終結符和終結符的集合,由於每一個終結符最後應該在單獨的一個集合中。

Yacc

讀取文件設置符號和產生式的值—>計算FIRST和FOLLOW—>構造LR(1)預測分析器和PPT(預測分析表)—>LR(1)—>LALR—>打印到輸出文件。

計算FIRST和FOLLOW

仍是參考的「現代編譯原理」這本書。

對於LR(1)能夠不計算FOLLOW,因此只計算FIRST。

初始化:每一個終結符的FIRST是本身

設置一個是否修改的bool變量,檢測本次循環是否修改過

遍歷每一個產生式

    每一個產生式的右部符號,若是當前符號前面都是可爲空,則將這個產生式左部的FIRST增長當前符號。

    若是每一個符號都是可爲空的,則把這個產生式置爲可爲空。

 

構造預測分析器(FA)和預測分析表(PPT)

計算閉包和計算GOTO

相似Lex的計算閉包和子集

構造LR狀態圖和分析表

相似構造DFA

 

反思

感受此次作的比較辛苦,可能這是由於是工具軟件的緣由吧。工具軟件要能針對不一樣的輸入,生成不一樣的代碼,而後生成出來的代碼能夠去分析一個文件。

Technorati 標記: 編譯原理, Lex, Yacc, 課程設計
相關文章
相關標籤/搜索