本節目的是Thompson構造實現的第一步,輸入文本預處理.本節的代碼可以在雲課堂的附件中提取。
本節代碼的目錄結構如下:
我們程序的目的,是希望將文本格式的正則表達式轉換為鏈表式的NFA,即將文本:
D[0-9]
{D}+return ICON
({D}+ | {D}*\.{D}+ | {D}+\.{D}*)(e{D}+)
轉換為
在轉換前,我們需要對文本進行預處理,在上面的文本中,其實分成了兩個不同的部分,第一部分稱為宏定義即:
D[0-9]
就像C語言中的宏定義,在代碼編譯前要將宏進行替換,我們在轉換前,也需要將正則表達式中的宏進行替換,也就是要將
{D}+return ICON
({D}+ | {D}*\.{D}+ | {D}+\.{D}*)(e{D}+)
轉換成:
{[0-9]}+ returnICON
([0-9]+ | [0-9]*\.[0-9]+ | [0-9]+\.[0-9]*)(e[0-9]+)
也就是把雙括號D 換成[0-9]
宏定義的轉換看似僅僅是簡單的字符串替換,但它有一個難點是需要處理宏定義的間套情況,例如:
D[0-9]
A [a-z]
AD{D}|{A}
{AD}\.{AD}+
大家可以看到,宏定義AD 中,它自身的定義需要其他宏定義來組成(D和A),
替換了宏AD之後,還需要繼續替換D 和A.也就是替換分成兩部
1.由{AD}\.{AD}+ 替換成( {D}|{A} ) \. ( {D} | {A} )+
2.由({D}|{A}) \. ({D} | {A})+ 替換成 ([0-9]|[a-z])\.([0-9]|[a-z])+
因此,在替換宏定義的時候,需要小心處理這種間套情況,間套甚至有可能會是很多層。
宏定義的組成方式如下:
名稱 <一系列空格> 宏定義的內容 <一系列空格或換行>
因此,程序對宏定義的解析也根據上面的格式來入手, 宏定義的解析由類MacroHandler來處理:
我們利用一個哈希表macroMap來存儲所有宏定義,如果有兩個宏的名字相同,那下一個宏將會覆蓋上一個, 輸入系統inputSystem,將從控制台獲取宏定義的內容,然後調用newMacro函數對輸入的內容進行解析(調出elipse).
newMacro 函數通過解析從控制台讀入的一行內容來構造宏定義,首先是先忽略掉空格和空行,直到遇到第一個有意義的字符才開始解析。從第一個字符開始,根據宏定義的格式,我們要構造的是宏定義的名稱,將所有字符集合起來,直到遇到空格為止,所集合的字符構成的字符串就是宏定義的名稱。
越過宏定義的名稱後面的空格,遇到的有效內容就是宏定義的內容了,將他們收集起來,放入macroContent變量,然後以宏定義的名字為key, 放入到哈希表中。
當對正則表達式進行解析時,需要進行宏替換,也就是通過給定宏的名字,獲取宏的內容,接口expandMacro 要實現的是獲取宏的內容:
正則表達式的文本替換:
在代碼中,我們使用類RegularExpressionHandler來對正則表達式的輸入進行替換,它的基本流程是,讀入正則表達式文本,然後解析讀入的內容,如果內容中有{}這一對符號時,程序確認需要進行宏替換,他將{}中的字符串提前出來作為宏定義的名字,通過上面提到的接口expandMacro獲取宏定義的內容,用得到的內容進行替換,如果替換後還有宏定義,那麼繼續重復替換流程。該類的代碼如下:
(調出eclipse)
input是輸入系統,用於獲取正則表達式的輸入,macroHandler是上面我們提到的宏定義處理器。該類將所有預處理後的正則表達式都存儲在一個數組中,以備後面的程序使用。在一系列初始化完成後,調用processRegularExprs 開始執行正則表達式的預處理流程。
preProcessExpr將輸入的正則表達式逐字符讀入,一旦遇到左括號{ 時,便准備開始進行宏替換,但如果左括號 { 是在雙引號中,例如[“{}”] , 那麼就將括號{當做普通字符處理,不進行替換,如果不是在雙引號中,就進行宏替換。
它先將處於{ 和 } 中的字符串抽出來,作為宏定義的名字,抽取的過程通過接口extractMacroNameFromInput實現,拿到宏的名字後,調用expandMacro來進行替換操作。
expandMacro 從macroHandler中獲取宏定義的內容,由於宏定義可能會間套,因此獲取內容後,還需要判斷,宏定義中是否間套了其他宏定義,macroContent.indexOf(“{“} 就是用於判斷宏定義是否間套,如果有間套,那麼將{}中的內容抽取出來,再進行宏定義替換,替換完後再次檢驗是否還有間套,一直這麼進行直到宏定義再無間套為止。
在判斷宏定義時做了一些檢查,如果遇到括號{, 但沒有遇到對應的},那表明輸入出錯,同時將出錯信息打印出來。
這節,我們只對代碼進行了簡單的介紹,下一節,我將以運行調試的方式,進一步給大家展現代碼的流程,讓大家能更好的理解代碼。