正則表達式引擎的構建——基於編譯原理DFA(龍書第三章)——3 計算4個函數

整個引擎代碼在github上,地址爲:https://github.com/sun2043430/RegularExpression_Engine.git


nullable, firstpos, lastpos, followpos函數介紹

接着上兩篇文章

正則表達式引擎的構建——基於編譯原理DFA(龍書第三章)——1 概述

正則表達式引擎的構建——基於編譯原理DFA(龍書第三章)——2 構造抽象語法樹

本篇將講解對抽象語法樹上的每一個節點計算對應的4個函數:nullable, firstpos, lastpos, followpos。

鑑於龍書已經對這一部分的理論和實現步驟進行了詳細文字、圖表描述。我就不在贅述了。只摘取其中一些重要的概念、難點談談我的理解。並結合具體的例子來演示一下函數的計算過程。


nullable, firstpos, lastpos, followpos這4 個函數在龍書上是這樣解釋的:


這4個函數裏面,其中nullable函數是爲計算firstpos, lastpos函數而做準備的。而firstpos, lastpos函數又是爲計算followpos函數而做準備的。

實際上在DFA中,只有對於星號運算符的節點nullable函數才返回TRUE,因爲星號運算符可以匹配空串。

對於nullable, firstpos函數的求法,龍書上給出的表格如下:


對於lastpos的求法,和firstpos的求法類似,差別只在對於cat結點時候,求法爲

if( nullable(c2) )

{ lastpos(c1) 並上 lastpos(c2) } 

else { lastpos(c2) }。

計算完firstpos和lastpos函數後,就可以計算最後的followpos函數了。followpos函數的簡單理解就是,對於一個結點P,哪些結點是可能出現在節點P後面的,所有可能出現在P後面(緊隨其後,相鄰)的節點構成的集合就是followpos。

只有在遇到cat節點和star節點(星號節點)纔有必要計算followpos值:

1. cat節點表示兩個節點的連接,所以兩個節點中,後一個節點的firstpos集合是前一個節點的lastpos集合中每一個節點的followpos集合。

2. 對於star節點,考慮到其後可以跟隨自身,所以自身節點的firstpos集合是自身節點的lastpos集合中每一個節點的followpos集合。


在具體代碼實現中,nullable可以用一個布爾變量表示,firstpos, lastpos, followpos爲3個向量(或鏈表):

    BOOL                    m_bNullAble;
    vector<CNodeInTree*>    m_vecFirstPos;
    vector<CNodeInTree*>    m_vecLastPos;
    vector<CNodeInTree*>    m_vecFollowPos;


結合具體實例講解4個函數的計算方法

上面說的都很抽象,我們結合具體的實例來說明對應的值和集合,對於正則表達式(a|b)*abb,共有5個葉子節點,5個非葉子節點。構成的語法樹如下:


我們來填寫一下對應的集合,需要說明的一點是,在計算這些值和集合的時候,是對整個語法樹進行深度優先遍歷的,所以會先處理葉子節點再處理中間節點,最後纔到整個樹的根節點。

我們按照遍歷過程中節點被處理的先後順序列出下表:


請注意,在表中,處理完 a1,a2,或3 這3個節點後,這3個節點的followpos集合都是空的,因爲可以跟隨的節點還不知道。


接下來在處理*4節點時,因爲在前面我們說過star節點需要計算followpos集合。具體的計算方法是這樣的,遍歷*4節點的lastpos集合,對其中的每一個節點N(也就是a1,a2兩個節點),設置N的followpos節點爲*4節點的firstpos集合。也就是說a1節點的followpso集合爲{a1,a2},a2節點的followpso集合也是{a1,a2}。處理完成之後的結果如下表:


可以看出,在處理followpos集合時,並不是處理 *4 節點本身的followpos集合,而是對 *4 節點的firstpos集合中每一個節點進行處理。我們接着往下處理,看看cat節點的情況。

當處理到cat6節點時,其firstpos集合和lastpos集合如下:


因爲cat6節點表示*4節點和a5節點的連接。所以cat6節點的firstpos集合首先要包含*4節點的firstpos集合(也就是a1,a2),然後又因爲*4節點的nullable函數爲TRUE(star運算符可匹配空串),所以還得加上a5節點的firstpos集合(a5節點)。所以cat6節點的firstpos集合爲{a1,a2,a5}。

cat6節點的lastpos集合就是a5節點的lastpos集合。且因爲a5節點的nullable函數爲FALSE,所以不用再加上*4節點的lastpos集合了。最終得到的結果就是上面的表格。


下面我們在來看如何在cat節點處處理followpos集合。

還是根據我們前面提到的,"cat節點表示兩個節點的連接,所以兩個節點中,後一個節點的firstpos集合是前一個節點的lastpos集合中每一個節點的followpos集合。"

具體在這裏,也就是對於cat6節點所連接的兩個節點來說,將前一個節點*4節點的lastpos集合({a1,a2})取過來,對裏面的每一個節點(a1,a2)設置followpos集合。用什麼來設置followpos集合呢?用後一個節點(a5節點)的firstpos集合({a5})來設置。也就是說,在a1節點和a2節點的followpos集合中再加入a5節點。處理完畢後的結果見下表:


可以看出,在對cat6進行followpos函數的處理時,改變的是*4節點的firstpos集合中的節點(a1,a2)的followpos集合,和cat6節點自身的followpos集合無關。

接下來的節點處理情況和以上過程類似,最終得到的結果如下表:



至此,結合實例講解的4個函數計算方法就講完了。下一章將介紹DFA的構建。

整個引擎代碼在github上,地址爲:https://github.com/sun2043430/RegularExpression_Engine.git


關鍵代碼

以下是計算本文中4個函數的關鍵代碼

BOOL CNodeInTree::CalculateFunction(CNodeInTree *pNode)
{
    BOOL bRet = FALSE;
    if (!pNode)
        return TRUE;

    CHECK_BOOL ( CalculateFunction(pNode->m_Node1) );
    CHECK_BOOL ( CalculateFunction(pNode->m_Node2) );
    
    switch (pNode->m_pToken->GetType())
    {
    case eType_END:
    case eType_NORMAL:
    case eType_WILDCARD:
        pNode->m_bNullAble = FALSE;
        try 
        {
            pNode->m_vecFirstPos.push_back(pNode);
            pNode->m_vecLastPos.push_back(pNode);
        }
        catch (...)
        {
            goto Exit0;
        }
    	break;
    case eType_STAR:
        pNode->m_bNullAble = TRUE;
        CHECK_BOOL ( AppendVector(pNode->m_vecFirstPos, pNode->m_Node1->m_vecFirstPos) );
        CHECK_BOOL ( AppendVector(pNode->m_vecLastPos, pNode->m_Node1->m_vecLastPos) );
        CHECK_BOOL ( CalcFollowPos(pNode) );
        break;
    case eType_UNION:
        pNode->m_bNullAble = pNode->m_Node1->m_bNullAble || pNode->m_Node2->m_bNullAble;
        CHECK_BOOL ( AppendVector(pNode->m_vecFirstPos, pNode->m_Node1->m_vecFirstPos) );
        CHECK_BOOL ( AppendVector(pNode->m_vecFirstPos, pNode->m_Node2->m_vecFirstPos) );
        CHECK_BOOL ( AppendVector(pNode->m_vecLastPos, pNode->m_Node1->m_vecLastPos) );
        CHECK_BOOL ( AppendVector(pNode->m_vecLastPos, pNode->m_Node2->m_vecLastPos) );
        break;
    case eType_CONCAT:
        pNode->m_bNullAble = pNode->m_Node1->m_bNullAble && pNode->m_Node2->m_bNullAble;
        // firstpos(n)
        CHECK_BOOL ( AppendVector(pNode->m_vecFirstPos, pNode->m_Node1->m_vecFirstPos) );
        if (pNode->m_Node1->m_bNullAble)
        {
            CHECK_BOOL ( AppendVector(pNode->m_vecFirstPos, pNode->m_Node2->m_vecFirstPos) );
        }
        // lastpos(n)
        if (pNode->m_Node2->m_bNullAble)
        {
            CHECK_BOOL ( AppendVector(pNode->m_vecLastPos, pNode->m_Node1->m_vecLastPos) );
        }
        CHECK_BOOL ( AppendVector(pNode->m_vecLastPos, pNode->m_Node2->m_vecLastPos) );
        CHECK_BOOL ( CalcFollowPos(pNode) );
        break;
    default:
        goto Exit0;
    }

    bRet = TRUE;
Exit0:
    return bRet;
}

BOOL CNodeInTree::CalcFollowPos(CNodeInTree *pNode)
{
    BOOL bRet = FALSE;

    switch (pNode->m_pToken->GetType())
    {
    case eType_STAR:
        for (vector<CNodeInTree*>::iterator it = pNode->m_vecLastPos.begin();
             it != pNode->m_vecLastPos.end();
             it++)
        {
            AppendVector((*it)->m_vecFollowPos, pNode->m_vecFirstPos);
        }
    	break;
    case eType_CONCAT:
        for (vector<CNodeInTree*>::iterator it = pNode->m_Node1->m_vecLastPos.begin();
            it != pNode->m_Node1->m_vecLastPos.end();
            it++)
        {
            AppendVector((*it)->m_vecFollowPos, pNode->m_Node2->m_vecFirstPos);
        }
        break;
    default:
        goto Exit0;
    }

    bRet = TRUE;
Exit0:
    return bRet;
}





發佈了49 篇原創文章 · 獲贊 71 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章