簡單的正則表達(dá)式引擎實(shí)現(xiàn)


本文介紹了一個(gè)簡單的正則表達(dá)式引擎的實(shí)現(xiàn)老虫,總共用了四百五十行左右的代碼咕缎,完整的code可以在碼云上找到郭蕉。

基本的數(shù)據(jù)結(jié)構(gòu)定義

核心思路是讀取正則表達(dá)式以后生成對應(yīng)的NFA劝赔,NFA中有邊和狀態(tài)兩個(gè)結(jié)構(gòu)烘豹。邊的結(jié)構(gòu)記錄了它的起點(diǎn)和終點(diǎn)瓜贾,同時(shí)通過枚舉類型記錄匹配的其他需求。

//用于處理‘^’字符
enum { NEXCLUDED = false, EXCLUDED = true }; 
//用于處理預(yù)處理類型携悯,0-128以內(nèi)ASCII字符直接匹配
enum { LCASES=256, UCASES=257, NUM=258, EPSILON=259, ANY=260, WS=261 };
class Edge
{
public:
    State *start;
    State *end;
    int type;
    int exclude;
    Edge(State *s, State *e, int t, bool ex = NEXCLUDED) :start(s), end(e), type(t), exclude(ex) {};
}

狀態(tài)有預(yù)備祭芦,成功和失敗三種,同時(shí)每個(gè)狀態(tài)維護(hù)兩個(gè)向量憔鬼,向量存儲了出邊和入邊的指針龟劲。

enum { READY = -1, SUCCESS = 1, FAIL = 0};
class State
{
public:
    int status;
    std::list<Edge *> InEdges;
    std::list<Edge *> OutEdges;
}

Nfa類會(huì)存儲一個(gè)正則表達(dá)式,同時(shí)存儲NFA的起點(diǎn)和終點(diǎn)轴或,并使用了兩個(gè)鏈表來維護(hù)NFA的邊和狀態(tài)昌跌,同時(shí)用一個(gè)鏈表來存儲匹配成功的字符串。兩個(gè)靜態(tài)的字符串指針用于記錄文件和正則表達(dá)式字符串的讀取狀態(tài)照雁,靜態(tài)常量避矢,使得最終函數(shù)只會(huì)對文件內(nèi)容和正則表達(dá)式掃描一次,避免在匹配成功的字符串中再匹配子串囊榜。

char *regex;
    State *Start;
    State *End;
    std::list<Edge *> edgeList;
    std::list<State *> stateList;
    std::list<char> matchedChar;
    static char *regRead;
    static char *fileRead;
}

生成NFA的過程中审胸,通過currentEndcurrentStart兩個(gè)指針分別指向當(dāng)前字符讀取完成后生成的最后一個(gè)狀態(tài)和當(dāng)前字符讀取之前的開始狀態(tài),維護(hù)這兩個(gè)指針的目的是為了記錄NFA的生成過程卸勺,在處理‘*’砂沛、‘+’、‘曙求?’等字符的時(shí)候起到了重要的作用碍庵。同時(shí)我們利用list內(nèi)置的迭代器對鏈表進(jìn)行遍歷映企,這個(gè)方式在匹配過程中也用到了。

State *currentEnd, *currentStart;
State *alternate;
list<Edge *>::iterator itor;

NFA的生成

關(guān)鍵的部分在于匹配字符串時(shí)采取的思路静浴,尤其是特殊字符的生成NFA的方式堰氓,這個(gè)不同于課本上最開始的NFA生成算法,而是基于讀取字符串的過程苹享,同時(shí)避免了字符串的回退等双絮,讀取一個(gè)字符就生成一個(gè)對應(yīng)的邊并壓入鏈表中,對‘*’得问、‘+’囤攀,‘?’和特殊符號也是如此宫纬,使得處理更加簡單的同時(shí)避免生成過于冗余的狀態(tài)焚挠,兼顧了時(shí)間和空間效率。以下舉例說明漓骚。

邊和狀態(tài)的生成

邊的生成使用newEdge函數(shù),需要記錄起點(diǎn)和終點(diǎn)蝌衔,以及類型,同時(shí)在生成邊以后要用重載的兩個(gè)patch函數(shù)將狀態(tài)和邊完全連接起來蝌蹂。

void Nfa::newEdge(State * start, State * end, int type, int exclude = NEXCLUDED)
{
    Edge *out = new Edge(start, end, type, exclude);
    end->patch(out, end);
    start->patch(start, out);
    edgeList.push_back(out);
}

以普通字符的生成和‘.’字符的產(chǎn)生方式為例胚委,他們都是生成一條邊和一個(gè)新的狀態(tài)。

case '.':   /* any */
    currentStart = currentEnd;
    currentEnd = new State();
    newEdge(currentStart, currentEnd, ANY, NEXCLUDED);      
    stateList.push_back(currentEnd);
default:
    currentStart = currentEnd;
    currentEnd = new State();
    newEdge(currentStart, currentEnd, *regRead, NEXCLUDED);
    stateList.push_back(currentEnd);
    break;

如下圖所示

create new edge
create new edge

接下來的符號處理都假定初始狀態(tài)如下圖所示

current stage
current stage

'|'的處理

currentStart指向的狀態(tài)作為子NFA的起點(diǎn)叉信,同時(shí)將子NFA的終點(diǎn)狀態(tài)和原NFA的終點(diǎn)進(jìn)行合并亩冬。

case '|':   // alternate 
    regRead++;
    currentStart = start;
    alternate= regex2nfa(regRead, start);
    currentEnd->merge(alternate);
    stateList.remove(alternate);
    regRead--;

如下圖所示

spilt the edges
spilt the edges

'?' & '*' & '+'的處理

讀取到問號只需要在上一條邊的基礎(chǔ)上繼續(xù)連接原有的邊即可。

case '?':   // zero or one 
    newEdge(currentStart, currentEnd, EPSILON, NEXCLUDED);
    break;

讀取到''后硼身,直接將currentStartcurrentEnd*進(jìn)行合并成環(huán)

case '*':   // zero or more 
    alternate = currentEnd;
    currentStart->merge(alternate);
    stateList.remove(alternate);
    currentEnd = currentStart;
    break;

讀取到‘+’后硅急,只需添加若干條邊從currentEnd狀態(tài)指向currentStart狀態(tài)的下一個(gè)狀態(tài)即可。

case '+':   /* one or more */
    itor = currentStart->OutEdges.begin();
    for (;itor != currentStart->OutEdges.end();itor++)
        newEdge(currentEnd, (*itor)->end, (*itor)->type, (*itor)->exclude);
    break;

如下圖所示:

special caracters
special caracters

簡單的分組支持

對于中括號和括號進(jìn)行了一定的支持佳遂,括號直接遞歸調(diào)用NFA的生成函數(shù)营袜,中括號和預(yù)定義字符都有其對應(yīng)的函數(shù)進(jìn)行支持。

NFA匹配

匹配過程采用了遞歸的方式丑罪,step函數(shù)調(diào)用match函數(shù)匹配邊和文件字符荚板,匹配成功后即遞歸調(diào)用進(jìn)入下一個(gè)狀態(tài)。

if (End->status == SUCCESS) 
        return SUCCESS;

for(;itor != current->OutEdges.end();itor++)
{   
    if ((*itor)->match(fileRead))
    {
        (*itor)->end->status = SUCCESS;
        matchedChar.push_back(*fileRead);
        ++fileRead;
        if (step((*itor)->end))
            return SUCCESS;
            --fileRead;
        matchedChar.pop_back();
    }
    if ((*itor)->type == EPSILON && step((*itor)->end))
        return SUCCESS;
}
return FAIL;

結(jié)果

較好的通過了測試用例,但是沒有進(jìn)一步拓展功能,只是一個(gè)簡單的正則表達(dá)式纫溃,同時(shí)也有些取巧崎岂,都只對字符進(jìn)行了一次掃描染乌,沒有進(jìn)行完整的詞法分析和語法分析,程序在四百五十行左右的情況下,其實(shí)是不夠健壯的窒舟。


routcome
routcome
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘲驾,一起剝皮案震驚了整個(gè)濱河市淌哟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辽故,老刑警劉巖徒仓,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異誊垢,居然都是意外死亡掉弛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門彤枢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人筒饰,你說我怎么就攤上這事缴啡。” “怎么了瓷们?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵业栅,是天一觀的道長。 經(jīng)常有香客問我谬晕,道長碘裕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任攒钳,我火速辦了婚禮帮孔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘不撑。我一直安慰自己文兢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布焕檬。 她就那樣靜靜地躺著姆坚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪实愚。 梳的紋絲不亂的頭發(fā)上兼呵,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音腊敲,去河邊找鬼击喂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碰辅,可吹牛的內(nèi)容都是我干的茫负。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼乎赴,長吁一口氣:“原來是場噩夢啊……” “哼忍法!你這毒婦竟也來了潮尝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤饿序,失蹤者是張志新(化名)和其女友劉穎勉失,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體原探,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乱凿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咽弦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徒蟆。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖型型,靈堂內(nèi)的尸體忽然破棺而出段审,到底是詐尸還是另有隱情,我是刑警寧澤闹蒜,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布寺枉,位于F島的核電站,受9級特大地震影響绷落,放射性物質(zhì)發(fā)生泄漏姥闪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一砌烁、第九天 我趴在偏房一處隱蔽的房頂上張望筐喳。 院中可真熱鬧,春花似錦函喉、人聲如沸疏唾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽槐脏。三九已至,卻和暖如春撇寞,著一層夾襖步出監(jiān)牢的瞬間顿天,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工蔑担, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牌废,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓啤握,卻偏偏與公主長得像鸟缕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容