整个引擎代码在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个集合。
队列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;
}