大學課程設計中,有一次是編寫Lex(詞法分析器的生成器)和Yacc(語法分析器的生成器),編寫這類工具軟件不是一件容易的事情。這篇文章記錄了當時編程時候的主要思想,主要仍是編譯原理的思想。node
根據輸入文件生成RE—>NFA—>DFA—>簡化的DFA—>根據DFA生成文件。c++
對正規表達式進行處理使其只有|、*、(、)等特殊符號,代換{}[]-等
將RE轉化爲後綴表達式
把下列類型的string轉換:算法
M+----->M.M*編程
M?------>M|e閉包
最後一共有四種鏈接:工具
普通字符(除了.,|,*):設計
a*:增長三條epsilon邊,而且修改終點和起點。blog
a|b:ip
新建兩個節點,並把其中一個指向原來的兩個nfa的起點,另外一個被原來的兩個終點指向。這四個邊均是epsilon。修改終點和起點。ci
a.b:鏈接,合併2號和3號節點。(紅色表明nfa的起點,黑色表明nfa的終點)
直接添加一個起點,指向全部nfa的起點。修改起點值,並把原來的終點(每一個nfa只有一個)都加入到最後的終點集合。
經過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的閉包
用j標記當前遍歷的節點,用p標記已經存在的節點數量
對當前遍歷的節點求每一個字符的出邊集合,若是有的話,就求該集合的閉包,並判斷是否已經存在,作相應的處理:
若是已經存在,則加邊
若是不存在,新建節點,再加邊,p++
參考維基百科中關於Hopcroft的算法。
可是對於DFA來講,剛開始並不能簡單地分爲兩個非終結符和終結符的集合,由於每一個終結符最後應該在單獨的一個集合中。
讀取文件設置符號和產生式的值—>計算FIRST和FOLLOW—>構造LR(1)預測分析器和PPT(預測分析表)—>LR(1)—>LALR—>打印到輸出文件。
仍是參考的「現代編譯原理」這本書。
對於LR(1)能夠不計算FOLLOW,因此只計算FIRST。
初始化:每一個終結符的FIRST是本身
設置一個是否修改的bool變量,檢測本次循環是否修改過
遍歷每一個產生式
每一個產生式的右部符號,若是當前符號前面都是可爲空,則將這個產生式左部的FIRST增長當前符號。
若是每一個符號都是可爲空的,則把這個產生式置爲可爲空。
相似Lex的計算閉包和子集
相似構造DFA
感受此次作的比較辛苦,可能這是由於是工具軟件的緣由吧。工具軟件要能針對不一樣的輸入,生成不一樣的代碼,而後生成出來的代碼能夠去分析一個文件。