正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——4 构造DFA

16 篇文章 0 订阅
5 篇文章 0 订阅

整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git

接上篇《正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数

本章将介绍如何使用followpos集合来构建DFA。相关算法和例子在龙书中文第二版的3.9.5节(根据正则表达式构建DFA),算法3.36和例3.37。


添加结尾END符号表示接受状态的语法树和函数表

我们把上一篇文章中所举的例子——正则表达式(a|b)*abb,其对应的语法树和计算得到的4个函数列表再展示一下,我们需要借助followpos集合来构建DFA。下面展示的图形和表格中,我们在正则表达式的末尾添加了表示接受状态的END符号

正则表达式(a|b)*abb对应的抽象语法树(注意最后的END标志):


正则表达式(a|b)*abb对应的nullable值、firstpos集合、lastpos集合和followpos集合(在结尾添加了END标志)



根据followpos集合创建DFA的实例说明

在构建DFA时,我们需要followpos集合。按照龙书的算法3.36,首先我们从根节点的firstpos集合入手。也就是cat11的firstpos集合{a1,b2,a5},将其放入队列Q中(此时Q中只有这一个集合)。

接下来我们的工作将转入处理队列Q。具体处理方法如下:

从头开始遍历队列Q,对其中的每一个集合S考察每一个输入字符(例如a)可匹配的节点p,所有p的followpos集合再求并集U,如果集合U在队列Q中存在则不添加到队列中,如果集合U在队列Q中不存在,则将其添加到队列Q中,最后不管U是否存在于队列Q中,我们都要记录对应的转换——集合S遇到输入字符a到达集合U。


上面讲的很抽象,我们结合例子来看,队列Q中首先放入了集合{a1,b2,a5},对于此集合存在输入字符a和b上的转换,我们逐一来处理,首先处理输入字符为a的情况,a匹配的节点是a1和a5,所以将followpos(a1)和followpos(a5)求并集,也就是{a1,b2,a5}并上{b7},得到的集合是{a1,b2,a5,b7},此集合不存在于队列Q中,所以我们需要添加此集合。这样队列Q变成下图所示:

(队列图1:2个集合)


同时我们需要记录转换关系:

队列Q中的第1个集合在输入为a时,会转换到队列Q的第2个集合。

接下来看输入字符为b的情况,字符b匹配的节点在集合{a1,b2,a5}中只有b2,所以看b2的followpos集合,followpos(b2)为{a1,b2,a5},此集合在Q中已经存在(队列Q的第一个对象),所以无需加入到队列Q中,但转换关系还是要记录的:

队列Q中的第1个集合在输入为b时,会转换到队列Q的第1个集合。


现在队列Q中的第1个集合处理完了,接下来处理第2个集合{a1,b2,a5,b7},还是一样此集合存在输入字符a,b。所以逐一处理,先看a,a匹配的节点是a1,a5,所以对followpos(a1)和followpos(a5)求并集,得到的集合是{a1,b2,a5,b7}。在队列Q中存在。只记录转换关系:

队列Q中的第2个集合在输入为a时,会转换到队列Q的第2个集合。

再来看字符b,匹配的是b2,b7两个节点,followpos(b2)和followpos(b7)求并集,得到的集合是{a1,b2,a5,b9},此集合在Q中没有,所以需要添加,这样队列变成下图所示:

(队列图2:3个集合


同时要记录转换关系:

队列Q中的第2个集合在输入为b时,会转换到队列Q的第3个集合。


第2个集合处理完之后,接下来处理第3个集合{a1,b2,a5,b9},和前面分析的一样,分别考虑输入字符a、b,对于a,匹配a1和a5节点,求这两个节点的followpos集合的并集为{a1,b2,a5,b7},此集合在队列Q中已经存在,所以只记录转换关系:

队列Q中的第3个集合在输入为a时,会转换到队列Q的第2个集合。

再来看字符b,匹配的是b2,b9两个节点,followpos(b2)和followpos(b9)求并集,得到的集合是{a1,b2,a5,END},此集合在Q中没有,所以需要添加,这样队列变成下图所示:

(队列图3:4个集合


同时要记录转换关系:

队列Q中的第3个集合在输入为b时,会转换到队列Q的第4个集合。


再处理第4个集合 {a1,b2,a5,END},当遇到的输入字符为a时,followpos(a1)和followpos(a5)求并集,得到的集合是{a1,b2,a5,b7}。在队列Q中已存在,所以只记录转换关系为:

队列Q中的第4个集合在输入为a时,会转换到队列Q的第2个集合。

当遇到的输入字符为b时,followpos(b2)到的集合是{a1,b2,a5}。在队列Q中已存在,所以只记录转换关系为:

队列Q中的第4个集合在输入为b时,会转换到队列Q的第1个集合。


至此,队列Q中已经没有未处理的集合了。遍历队列Q的过程结束。


我们整理上面的转换关系(上面红色字体部分),将队列Q中的每个集合看成一个状态,则转换关系为下表:


状态1,2,3,4分别对应队列Q中的每一个集合,表中的状态4是接受状态,因为状态4对应的集合为{a1,b2,a5,END},其中包含了END标志。根据此表建立DFA,如下图:


算法总结

至此,根据根节点的firstpos集合和其他节点的followpos集合,构建DFA的过程就讲解完毕了。最后我们总结一下算法:

1 将根节点的firstpos集合放入队列Q中。

2 当队列Q中有未处理的对象S(也就是集合、状态)时,我们处理S中的每个输入符号(例如a),令U是S中和输入符号a匹配的所有节点的followpos集合的并集。

3 如果U在不在队列Q中,则将U加入到队列尾部,如果U在队列Q中已经存在,则无需添加。

4 不管U是否已经出现在队列Q中,都需要记录转换关系:S在输入符号为a时转换到U。

5 如果队列Q中还有未处理的对象回到步骤2。否则处理结束。

6 如果队列Q中的某个对象(集合、状态)包含了END标志,则该对象(集合、状态)为接受状态。


下图为龙书中的算法介绍,我上面的算法步骤思想与之相同,只是在表述上有一些差异。



关键代码

下面是创建DFA的关键代码:

BOOL CDFA::CreateDFA(CNodeInTree *pNode)
{
    int                                     nIdxCur = 0;
    int                                     nIdxNext= 0;
    BOOL                                    bRet    = FALSE;
    vector<CNodeInTree*>::iterator          itNode  = pNode->m_vecFirstPos.begin();
    set<CNodeInTree*>                       setNode;
    map<unsigned char, set<CNodeInTree*>>   mapSet;

    m_lstSet.clear();
    m_lstNodeRelation.clear();
    m_setAcceptingIdx.clear();

    try 
    {
        // prepare start state
        for ( ; itNode != pNode->m_vecFirstPos.end(); itNode++ )
        {
            setNode.insert(*itNode);
        }
        m_lstSet.push_back(setNode);
        if (IsContainAcceptingState(setNode))
        {
            m_setAcceptingIdx.insert(nIdxCur);// the set is an accepting state
        }
        // end prepare start state

        // Construct DFA set, translation relation.
        list<set<CNodeInTree*>>::iterator it = m_lstSet.begin();
        for ( ; it != m_lstSet.end(); it++, nIdxCur++)
        {
            mapSet.clear();
            setNode.clear();
            setNode = *it;
            // collect all translation char into mapSet
            set<CNodeInTree*>::iterator itSet = setNode.begin();
            for ( ; itSet != setNode.end(); itSet++ )
            {
                unsigned char ch = (*itSet)->m_pToken->GetChar();
                mapSet[ch].insert(*itSet);
            }
            // end collect

            // for each translation char, get the set that correspond to translation char
            map<unsigned char, set<CNodeInTree*>>::iterator itMap = mapSet.begin();
            for ( ; itMap != mapSet.end(); itMap++ )
            {
                unsigned char ch = itMap->first;
                set<CNodeInTree*> & setNodeTemp = itMap->second;
                set<CNodeInTree*> setNodeNext;
                // get the union of followpos(p) for all p in the setNodeTemp
                CHECK_BOOL ( GetNextSet(setNodeTemp, setNodeNext) );
                if (setNodeNext.empty())
                    continue;
                if (!IsNodeSetInList(setNodeNext, nIdxNext))
                {
                    //if the set not in list, add it into list
                    m_lstSet.push_back(setNodeNext);
                    if (IsContainAcceptingState(setNodeNext))
                    {
                        m_setAcceptingIdx.insert(nIdxNext);// the set is an accepting state
                    }
                }
                DFANodeRelation stNodeRelation(nIdxCur, ch, nIdxNext);
                m_lstNodeRelation.push_back(stNodeRelation);
            }
            // end for
        }
    }
    catch (...)
    {
        goto Exit0;
    }

    bRet = TRUE;
Exit0:
    return bRet;
}

BOOL CDFA::GetNextSet(set<CNodeInTree*> & setNodeTemp, set<CNodeInTree*> & setNodeNext)
{
    BOOL                        bRet    = FALSE;
    set<CNodeInTree*>::iterator it      = setNodeTemp.begin();

    for ( ; it != setNodeTemp.end(); it++ )
    {
        vector<CNodeInTree*> & vecFollowPos = (*it)->m_vecFollowPos;
        vector<CNodeInTree*>::iterator itVec = vecFollowPos.begin();
        for ( ; itVec != vecFollowPos.end(); itVec++ )
        {
            try 
            {
                setNodeNext.insert(*itVec);
            }
            catch (...)
            {
                goto Exit0;
            }
        }

    }
    bRet = TRUE;
Exit0:
    return bRet;
}

BOOL CDFA::IsNodeSetInList(set<CNodeInTree*> &setNodeNext, int &nIdx)
{
    list<set<CNodeInTree*>>::iterator it = m_lstSet.begin();
    for (nIdx = 0; it != m_lstSet.end(); it++, nIdx++)
    {
        if (*it == setNodeNext)
        {
            return TRUE;
        }
    }
    return FALSE;
}

BOOL CDFA::IsContainAcceptingState(set<CNodeInTree*> &setNode)
{
    for (set<CNodeInTree*>::iterator it = setNode.begin(); it != setNode.end(); it++)
    {
        if (eType_END == (*it)->m_pToken->GetType())
        {
            return TRUE;
        }
    }
    return FALSE;
}





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值