根据预告,这篇我们对“?”“+”“”进行处理,实现对重复的支持。“x?”匹配0个或1个“x”,“x+”匹配1到任意个“x”,“x”匹配0到任意个“x”。
有了重复,就有贪婪模式和非贪婪模式。在贪婪模式下,“x+”匹配“xxxyyy”中的“xxx”;在非贪婪模式下,“x+”匹配“xxxyyy”中的第一个“x”。为了区别两种模式,按照通常的语法,我们在重复控制符号后面加一个“?”表示非贪婪模式,不加默认贪婪模式。现在,有效的语法有“x?”“x??”“x+”“x+?”“x*”“x*?”。
那么,“x”可以是什么呢?重复符号的优先级挺高的,到目前为止,“x”可以是单个字符,也可以是中括号表达是,也可以是小括号括起来的表达式。
下面,我们进行语法分析。
还是回顾一下上次的文法:
Expr -> ExprNoOr { "|" ExprNoOr }
ExprNoOr -> ExprNoGroup { "(" Expr ")" ExprNoGroup }
ExprNoGroup -> ExprNoCollection { "[" ExprCollection "]" ExprNoCollection }
ExprCollection -> [ "^" ] { OrdinaryChar | OrdinaryChar "-" OrdinaryChar }
ExprNoCollection -> { OrdinaryChar }
从上面的结构看,重复符号可以加在 ExprNoCollection 的每一个字符后面,也可以加在 ExprNoGroup 里的中括号表达式后面,也可以加在 ExprNoOr 的小括号表达式后面。不过这样看上去有点乱,我们整理一下,换种写法:
Expr -> SubExpr { "|" SubExpr }
SubExpr -> { Phrase }
Phrase -> Word [ Repeater ]
Repeater -> ( "?" | "+" | "*" ) [ "?" ]
Word -> OrdinaryChar | "[" Collection "]" | "(" Expr ")"
Collection -> [ "^" ] { OrdinaryChar | OrdinaryChar "-" OrdinaryChar }
OrdinaryChar -> All ordinary characters
首先,我们把由“|”分开的各个部分称为SubExpr,SubExpr由很多Phrase构成,每个Phrase由一个Word以及可能存在的Repeater构成,Word分为三种,普通字符、中括号表达式、小括号表达式。
首先,不考虑Repeater部分,即:
Phrase -> Word
我们把现有代码调整一下,使得符合文法表述的结构,然后再添加Repeater部分。
调整现有代码不详细解释了,纯贴代码(不含Repeater处理哦):
1StateMachine::NodePtr ParseExpr(StateMachine::NodePtr pNode)
2{
3 StateMachine::NodePtr pCurrent = ParseSubExpr(pNode);
4
5 if (pCurrent == nullptr)
6 {
7 return nullptr;
8 }
9
10 while (true)
11 {
12 Token token = LookAhead();
13
14 if (token.type != TT_VerticalBar)
15 {
16 Backward(token);
17 return pCurrent;
18 }
19
20 StateMachine::NodePtr pNewNode = ParseSubExpr(pNode);
21 StateMachine::EdgePtr pEdge = NewEdge();
22 m_spStateMachine->AddEdge(pEdge, pNewNode, pCurrent);
23 }
24
25 return nullptr;
26}
27
28StateMachine::NodePtr ParseSubExpr(StateMachine::NodePtr pNode)
29{
30 StateMachine::NodePtr pCurrent = pNode;
31
32 while (true)
33 {
34 StateMachine::NodePtr pNewNode = ParsePhrase(pCurrent);
35
36 if (pNewNode == pCurrent || pNewNode == nullptr)
37 {
38 return pNewNode;
39 }
40
41 pCurrent = pNewNode;
42 }
43
44 return nullptr;
45}
46
47StateMachine::NodePtr ParsePhrase(StateMachine::NodePtr pNode)
48{
49 StateMachine::NodePtr pCurrent = ParseWord(pNode);
50
51 if (pCurrent == nullptr)
52 {
53 return nullptr;
54 }
55
56 // TODO: Parse Repeater
57
58 return pCurrent;
59}
60
61StateMachine::NodePtr ParseWord(StateMachine::NodePtr pNode)
62{
63 StateMachine::NodePtr pCurrent = pNode;
64
65 Token token = LookAhead();
66
67 switch (token.type)
68 {
69 case TT_OpenParen:
70 {
71 pCurrent = ParseExpr(pCurrent);
72
73 if (pCurrent == nullptr)
74 {
75 return nullptr;
76 }
77
78 token = LookAhead();
79
80 if (token.type != TT_CloseParen)
81 {
82 return nullptr;
83 }
84 }
85 break;
86 case TT_OpenBracket:
87 {
88 pCurrent = ParseCollection(pCurrent);
89
90 if (pCurrent == nullptr)
91 {
92 return nullptr;
93 }
94
95 token = LookAhead();
96
97 if (token.type != TT_CloseBracket)
98 {
99 return nullptr;
100 }
101 }
102 break;
103 case TT_OrdinaryChar:
104 {
105 pCurrent = AddNormalNode(pCurrent, token.ch);
106
107 if (pCurrent == nullptr)
108 {
109 return nullptr;
110 }
111 }
112 break;
113 default:
114 Backward(token);
115 return pCurrent;
116 }
117
118 return pCurrent;
119}
120
121StateMachine::NodePtr ParseCollection(StateMachine::NodePtr pNode)
122{
123 bool bFirst = true;
124 bool bInHyphen = false;
125 bool bAcceptHyphen = false;
126 Char chLastChar = 0;
127
128 bool bOpposite = false;
129 IntervalSet<Char> is;
130
131 bool bContinue = true;
132
133 while (bContinue)
134 {
135 Token token = LookAhead();
136
137 switch (token.type)
138 {
139 case TT_Caret:
140 {
141 if (!bFirst)
142 {
143 return nullptr;
144 }
145 else
146 {
147 bOpposite = true;
148 }
149 }
150 break;
151 case TT_Hyphen:
152 {
153 if (bInHyphen || !bAcceptHyphen)
154 {
155 return nullptr;
156 }
157 else
158 {
159 bInHyphen = true;
160 }
161 }
162 break;
163 case TT_OrdinaryChar:
164 {
165 if (bInHyphen)
166 {
167 is.Union(Interval<Char>(chLastChar, token.ch));
168 bInHyphen = false;
169 bAcceptHyphen = false;
170 }
171 else
172 {
173 is.Union(Interval<Char>(token.ch, token.ch));
174 chLastChar = token.ch;
175 bAcceptHyphen = true;
176 }
177 }
178 break;
179 default:
180 {
181 Backward(token);
182 bContinue = false;
183 }
184 break;
185 }
186
187 bFirst = false;
188 }
189
190 if (bOpposite)
191 {
192 IntervalSet<Char> u;
193 u.Union(Interval<Char>(0, -1));
194 is = u.Exclude(is);
195 is.MakeClose(1);
196 }
197
198 StateMachine::NodePtr pCurrent = pNode;
199
200 if (is.IsEmpty())
201 {
202 return pCurrent;
203 }
204
205 pCurrent = NewNode();
206 Set<Interval<Char>> intervals = is.GetIntervals();
207
208 for (auto it = intervals.Begin(); it != intervals.End(); ++it)
209 {
210 StateMachine::EdgePtr pEdge = NewEdge(it->left, it->right);
211 m_spStateMachine->AddEdge(pEdge, pNode, pCurrent);
212 }
213
214 m_spStateMachine->AddNode(pCurrent);
215
216 return pCurrent;
217}
改完以后,我们考虑怎样添加对重复的支持。
我们举个简单的例子——构造正则表达式“ab?c”的状态机。
首先不考虑最后的“+”,画出“abc”的状态机,这当然很简单:
状态1后,如果遇到字符b,现在直接指向了节点2,匹配了一次b。如何不匹配b呢?没错,从1直接跳到2,增加ε边:
再来考虑“ab+c”,也很明显,添加从2回到1的ε边:
再结合上面两个,就有了“ab*c”:
……不对!!!两条ε边死循环了!
可以考虑增加一些辅助节点解开这个死结:
同时把前两个也重画。“ab+c”:
“ab?c”:
可以看出一点规律:
节点2是重复单元的开始,节点3是重复单元的结束,中间如果是括,那么可能有很多节点,这些我们都不管,不影响重复的表达。0次开始的,就从节点1向节点4连一条ε边,跳过整个循环部分;1次开始的,就从节点3向节点4连一条ε边;有任意次重复的,从节点3向节点2连一条ε边。
虽然这里使用了超多的ε边,去掉它们会使状态机显得更加简洁。不过为了规律性更强,这些ε边我们都保留。
最后,说说关于贪婪和非贪婪的处理。这部分在@vczh的《构造正则表达式引擎》里也介绍过。它依赖于实现状态机的图的节点指针存储顺序。我们来看一下状态机的节点结构:
1template <typename NodeData, typename EdgeData>
2struct GraphNode
3{
4 typedef GraphEdge<EdgeData, NodeData> EdgeType;
5
6 NodeData tValue;
7
8 Array<EdgeType *> arrPrevious;
9 Array<EdgeType *> arrNext;
10
11 // ...
12
13};
其中NodeData和EdgeData是在正则表达式这边传递的节点数据和边数据的结构,图这边有序地保存了流出节点的各条边。而我们在匹配的时候,也是按保存的顺序进行遍历的,谁在前,谁就先得到尝试的机会。
所以,对于“ab?c”和“ab*c”,贪婪和非贪婪取决于流出状态1的两条ε边的顺序,如果是先到状态2,便是贪婪,如果先到状态4,便是非贪婪。对于“ab+c”,取决于流出状态3的两条ε边的顺序,如果是先到状态2,便是贪婪,如果先到状态4,便是非贪婪。
代码主要修改是ParsePhrase部分,以及增加的ParseRepeater。ParsePhrase目前代码如下:
1StateMachine::NodePtr ParsePhrase(StateMachine::NodePtr pNode)
2{
3 StateMachine::NodePtr pCurrent = ParseWord(pNode);
4
5 if (pCurrent == nullptr)
6 {
7 return nullptr;
8 }
9
10 // TODO: Parse Repeater
11
12 return pCurrent;
13}
开始部分先改成:
1StateMachine::NodePtr ParsePhrase(StateMachine::NodePtr pNode)
2{
3 StateMachine::NodePtr pFrom = NewNode();
4 StateMachine::NodePtr pCurrent = ParseWord(pFrom);
5
6 if (pCurrent == nullptr)
7 {
8 delete pFrom;
9 return nullptr;
10 }
11
12 if (pCurrent == pFrom)
13 {
14 delete pFrom;
15 return pNode;
16 }
17
18 // TODO: Parse Repeater
19 Repeator r = ParseRepeater();
这里先新建了一个pFrom,也就是上面一节状态机图的节点2,作为重复部分的开始,方便之后如果要从状态1(pNode)添加不同顺序的ε边(pNode到pFrom最多只有两条边,仅用来区分贪婪和非贪婪)。解析完Word后,尝试读入Repeater。
Repeater结构定义如下:
1enum RepeatorType
2{
3 RT_None,
4 RT_ZeroOrOne,
5 RT_OnePlus,
6 RT_ZeroPlus
7};
8
9struct Repeator
10{
11 RepeatorType type;
12 bool bGreedy;
13
14 Repeator()
15 : type(RT_None), bGreedy(true)
16 {
17
18 }
19};
RT_None表示没有Repeater,后面三种分别是“?”“+”“*”的效果。然后bGreedy表示是否贪婪模式,默认是,如果读到了额外的“?”,就设为非贪婪模式。ParseRepeater代码如下:
1Repeator ParseRepeater()
2{
3 Repeator r;
4
5 Token token = LookAhead();
6
7 switch (token.type)
8 {
9 case TT_QuestionMark:
10 r.type = RT_ZeroOrOne;
11 break;
12 case TT_Plus:
13 r.type = RT_OnePlus;
14 break;
15 case TT_Star:
16 r.type = RT_ZeroPlus;
17 break;
18 default:
19 Backward(token);
20 break;
21 }
22
23 bool bGreedy = true;
24
25 if (r.type != RT_None)
26 {
27 token = LookAhead();
28
29 if (token.type == TT_QuestionMark)
30 {
31 r.bGreedy = false;
32 }
33 else
34 {
35 Backward(token);
36 }
37 }
38
39 return r;
40}
其中新增的“?”“+”“*”已经添加到单词定义以及词法分析分析函数了。
最后回到ParsePhrase的后半部分:
1 Repeator r = ParseRepeater();
2
3 switch (r.type)
4 {
5 case RT_None:
6 {
7 m_spStateMachine->AddNode(pFrom);
8 StateMachine::EdgePtr pEdge = NewEdge();
9 m_spStateMachine->AddEdge(pEdge, pNode, pFrom);
10 }
11 break;
12 case RT_ZeroOrOne:
13 {
14 m_spStateMachine->AddNode(pFrom);
15 StateMachine::EdgePtr pEdgeNodeToFrom = NewEdge();
16 StateMachine::EdgePtr pEdgeNodeToCurrent = NewEdge();
17
18 if (r.bGreedy)
19 {
20 m_spStateMachine->AddEdge(pEdgeNodeToFrom, pNode, pFrom);
21 m_spStateMachine->AddEdge(pEdgeNodeToCurrent, pNode, pCurrent);
22 }
23 else
24 {
25 m_spStateMachine->AddEdge(pEdgeNodeToCurrent, pNode, pCurrent);
26 m_spStateMachine->AddEdge(pEdgeNodeToFrom, pNode, pFrom);
27 }
28 }
29 break;
30 case RT_OnePlus:
31 {
32 StateMachine::NodePtr pTo = NewNode();
33 m_spStateMachine->AddNode(pFrom);
34 m_spStateMachine->AddNode(pTo);
35
36 StateMachine::EdgePtr pEdgeNodeToFrom = NewEdge();
37 m_spStateMachine->AddEdge(pEdgeNodeToFrom, pNode, pFrom);
38
39 StateMachine::EdgePtr pEdgeCurrentToFrom = NewEdge();
40 StateMachine::EdgePtr pEdgeCurrentToTo = NewEdge();
41
42 if (r.bGreedy)
43 {
44 m_spStateMachine->AddEdge(pEdgeCurrentToFrom, pCurrent, pFrom);
45 m_spStateMachine->AddEdge(pEdgeCurrentToTo, pCurrent, pTo);
46 }
47 else
48 {
49 m_spStateMachine->AddEdge(pEdgeCurrentToTo, pCurrent, pTo);
50 m_spStateMachine->AddEdge(pEdgeCurrentToFrom, pCurrent, pFrom);
51 }
52
53 pCurrent = pTo;
54 }
55 break;
56 case RT_ZeroPlus:
57 {
58 StateMachine::NodePtr pTo = NewNode();
59 m_spStateMachine->AddNode(pFrom);
60 m_spStateMachine->AddNode(pTo);
61
62 StateMachine::EdgePtr pEdgeCurrentToNode = NewEdge();
63 m_spStateMachine->AddEdge(pEdgeCurrentToNode, pCurrent, pNode);
64
65 StateMachine::EdgePtr pEdgeNodeToFrom = NewEdge();
66 StateMachine::EdgePtr pEdgeNodeToTo = NewEdge();
67
68 if (r.bGreedy)
69 {
70 m_spStateMachine->AddEdge(pEdgeNodeToFrom, pNode, pFrom);
71 m_spStateMachine->AddEdge(pEdgeNodeToTo, pNode, pTo);
72 }
73 else
74 {
75 m_spStateMachine->AddEdge(pEdgeNodeToTo, pNode, pTo);
76 m_spStateMachine->AddEdge(pEdgeNodeToFrom, pNode, pFrom);
77 }
78
79 pCurrent = pTo;
80 }
81 break;
82 default:
83 break;
84 }
85
86 return pCurrent;
87}
根据三种不同的Repeater,使用不同的连接方法连状态机节点,bGready影响某两条关键的边的顺序。这里不做过多解释,该解释的都在上一节解释过了。
由于我们引入了贪婪和非贪婪的概念,匹配检验函数Match必然需要支持部分匹配字符串,不然就没法区分两种模式。原先为了方便起见,都是对整个字符串进行匹配的。我们将Match系列函数修改成下面这个样子:
1bool Match(const String &s, int *pnPos = nullptr)
2{
3 return Match(s, 0, m_pBegin, pnPos);
4}
5
6bool Match(const String &s, int i, StateMachine::NodePtr pNode, int *pnPos = nullptr)
7{
8 if (pNode == m_pEnd)
9 {
10 if (pnPos != nullptr)
11 {
12 *pnPos = i;
13 return true;
14 }
15
16 if (i < s.Length())
17 {
18 return false;
19 }
20
21 return true;
22 }
23
24 for (auto it = pNode->arrNext.Begin(); it != pNode->arrNext.End(); ++it)
25 {
26 if (Match(s, i, *it, pnPos))
27 {
28 return true;
29 }
30 }
31
32 return false;
33}
34
35bool Match(const String &s, int i, StateMachine::EdgePtr pEdge, int *pnPos = nullptr)
36{
37 if (!pEdge->tValue.bEpsilon)
38 {
39 if (i >= s.Length())
40 {
41 return false;
42 }
43
44 if (!pEdge->tValue.Match(s[i]))
45 {
46 return false;
47 }
48
49 return Match(s, i + 1, pEdge->pNext, pnPos);
50 }
51 else
52 {
53 return Match(s, i, pEdge->pNext, pnPos);
54 }
55}
新增参数int *pnPos,返回匹配结束后第一个未匹配的字符位置,也就是已匹配的字符数。如果pnPos非空,那么支持部分匹配,返回匹配成功的位置;如果pn为空,还是跟原先一样,进行全字符串匹配。
首先跑一下现有的所有case,应该能通过。然后增加几个对pnPos的case:
1RegExp r;
2int nPos = 0;
3
4XL_TEST_ASSERT(r.Parse(L"[0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5]"));
5XL_TEST_ASSERT(!r.Match(L"256"));
6XL_TEST_ASSERT(!r.Match(L"260"));
7XL_TEST_ASSERT(!r.Match(L"300"));
8XL_TEST_ASSERT(r.Match(L"256", &nPos) && nPos == 2);
9XL_TEST_ASSERT(r.Match(L"260", &nPos) && nPos == 2);
10XL_TEST_ASSERT(r.Match(L"300", &nPos) && nPos == 2);
后三则通不过。原因是0-255的正则表达式在部分匹配的时候有问题。由于正则表达式“[0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5]”将1位数“[0-9]”放到最前面,使得所有数字都只匹配了第一个就结束了。改正为先写三位数,后写两位数,最后写一位数:“[01][0-9][0-9]|2[0-4][0-9]|25[0-5]|[0-9][0-9]|[0-9]”。所有的“|”子式之间,如果有包含关系,或者有部分重叠,使用的时候就需要考虑顺序,这跟贪婪、非贪婪的道理是一样的。
然后尝试添加针对新功能的case。IPv4地址由于后面重复是3次,本次我们并没有支持计次重复“x{m,n}”,所以没法应用。
写几个土土的case吧:
1
2XL_TEST_CASE()
3{
4 RegExp r;
5 int nPos = 0;
6
7 XL_TEST_ASSERT(!r.Parse(L"?"));
8 XL_TEST_ASSERT(!r.Parse(L"+"));
9 XL_TEST_ASSERT(!r.Parse(L"*"));
10 XL_TEST_ASSERT(!r.Parse(L"??"));
11 XL_TEST_ASSERT(!r.Parse(L"+?"));
12 XL_TEST_ASSERT(!r.Parse(L"*?"));
13
14 XL_TEST_ASSERT(r.Parse(L"a?"));
15 XL_TEST_ASSERT(r.Match(L""));
16 XL_TEST_ASSERT(r.Match(L"a"));
17 XL_TEST_ASSERT(!r.Match(L"aa"));
18 XL_TEST_ASSERT(r.Match(L"", &nPos) && nPos == 0);
19 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 1);
20 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 1);
21
22 XL_TEST_ASSERT(r.Parse(L"a??"));
23 XL_TEST_ASSERT(r.Match(L""));
24 XL_TEST_ASSERT(r.Match(L"a"));
25 XL_TEST_ASSERT(!r.Match(L"aa"));
26 XL_TEST_ASSERT(r.Parse(L"a??"));
27 XL_TEST_ASSERT(r.Match(L"", &nPos) && nPos == 0);
28 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 0);
29 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 0);
30
31 XL_TEST_ASSERT(r.Parse(L"a+"));
32 XL_TEST_ASSERT(!r.Match(L""));
33 XL_TEST_ASSERT(r.Match(L"a"));
34 XL_TEST_ASSERT(r.Match(L"aa"));
35 XL_TEST_ASSERT(r.Match(L"aaa"));
36 XL_TEST_ASSERT(!r.Match(L"", &nPos));
37 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 1);
38 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 2);
39 XL_TEST_ASSERT(r.Match(L"aaa", &nPos) && nPos == 3);
40
41 XL_TEST_ASSERT(r.Parse(L"a+?"));
42 XL_TEST_ASSERT(!r.Match(L""));
43 XL_TEST_ASSERT(r.Match(L"a"));
44 XL_TEST_ASSERT(r.Match(L"aa"));
45 XL_TEST_ASSERT(r.Match(L"aaa"));
46 XL_TEST_ASSERT(!r.Match(L"", &nPos));
47 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 1);
48 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 1);
49 XL_TEST_ASSERT(r.Match(L"aaa", &nPos) && nPos == 1);
50
51 XL_TEST_ASSERT(r.Parse(L"a*"));
52 XL_TEST_ASSERT(r.Match(L""));
53 XL_TEST_ASSERT(r.Match(L"a"));
54 XL_TEST_ASSERT(r.Match(L"aa"));
55 XL_TEST_ASSERT(r.Match(L"aaa"));
56 XL_TEST_ASSERT(r.Match(L"", &nPos) && nPos == 0);
57 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 1);
58 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 2);
59 XL_TEST_ASSERT(r.Match(L"aaa", &nPos) && nPos == 3);
60 XL_TEST_ASSERT(r.Parse(L"a*?"));
61 XL_TEST_ASSERT(r.Match(L""));
62 XL_TEST_ASSERT(r.Match(L"a"));
63 XL_TEST_ASSERT(r.Match(L"aa"));
64 XL_TEST_ASSERT(r.Match(L"aaa"));
65 XL_TEST_ASSERT(r.Match(L"", &nPos) && nPos == 0);
66 XL_TEST_ASSERT(r.Match(L"a", &nPos) && nPos == 0);
67 XL_TEST_ASSERT(r.Match(L"aa", &nPos) && nPos == 0);
68 XL_TEST_ASSERT(r.Match(L"aaa", &nPos) && nPos == 0);
69
70 XL_TEST_ASSERT(r.Parse(L"w1+"));
71 XL_TEST_ASSERT(!r.Match(L""));
72 XL_TEST_ASSERT(!r.Match(L"w"));
73 XL_TEST_ASSERT(r.Match(L"w1"));
74 XL_TEST_ASSERT(r.Match(L"w11"));
75 XL_TEST_ASSERT(r.Match(L"w111"));
76 XL_TEST_ASSERT(r.Match(L"w1111"));
77 XL_TEST_ASSERT(r.Match(L"w11111"));
78 XL_TEST_ASSERT(!r.Match(L"", &nPos));
79 XL_TEST_ASSERT(!r.Match(L"w", &nPos));
80 XL_TEST_ASSERT(r.Match(L"w1", &nPos) && nPos == 2);
81 XL_TEST_ASSERT(r.Match(L"w11", &nPos) && nPos == 3);
82 XL_TEST_ASSERT(r.Match(L"w111", &nPos) && nPos == 4);
83 XL_TEST_ASSERT(r.Match(L"w1111", &nPos) && nPos == 5);
84 XL_TEST_ASSERT(r.Match(L"w11111", &nPos) && nPos == 6);
85
86 XL_TEST_ASSERT(r.Parse(L"w1+?"));
87 XL_TEST_ASSERT(!r.Match(L""));
88 XL_TEST_ASSERT(!r.Match(L"w"));
89 XL_TEST_ASSERT(r.Match(L"w1"));
90 XL_TEST_ASSERT(r.Match(L"w11"));
91 XL_TEST_ASSERT(r.Match(L"w111"));
92 XL_TEST_ASSERT(r.Match(L"w1111"));
93 XL_TEST_ASSERT(r.Match(L"w11111"));
94 XL_TEST_ASSERT(!r.Match(L"", &nPos));
95 XL_TEST_ASSERT(!r.Match(L"w", &nPos));
96 XL_TEST_ASSERT(r.Match(L"w1", &nPos) && nPos == 2);
97 XL_TEST_ASSERT(r.Match(L"w11", &nPos) && nPos == 2);
98 XL_TEST_ASSERT(r.Match(L"w111", &nPos) && nPos == 2);
99 XL_TEST_ASSERT(r.Match(L"w1111", &nPos) && nPos == 2);
100 XL_TEST_ASSERT(r.Match(L"w11111", &nPos) && nPos == 2);
101}
或许可以尝试匹配URL:
1XL_TEST_CASE()
2{
3 RegExp r;
4
5 XL_TEST_ASSERT(r.Parse(L"http://([a-zA-Z0-9\\-]+.)+[a-zA-Z]+/"));
6 XL_TEST_ASSERT(r.Match(L"http://streamlet.org/"));
7 XL_TEST_ASSERT(r.Match(L"http://www.streamlet.org/"));
8 XL_TEST_ASSERT(r.Match(L"http://www.1-2.streamlet.org/"));
9 XL_TEST_ASSERT(r.Match(L"http://www.1-2.3-4.streamlet.org/"));
10 XL_TEST_ASSERT(!r.Match(L"http://org/"));
11 XL_TEST_ASSERT(!r.Match(L"http://streamlet.o-g/"));
12}
这次我们实现了重复,且支持非贪婪模式,使得实用性大大增加了。不过,同样实用的计次重复却没有实现。这是由于以下两方面原因:
第一、计次重复需要复制状态机节点,如“ab{2,5}c”的状态机将是这样的:
如果是“a(…){2,5}c”,中间要复制的玩意儿就多了。目前状态机操作方面还没有支持复制一张子图的功能。
第二,由于“x{m,n}”中的m、n并不仅仅是1个字符,虽然处理上并不成问题,但这超出了开头提出的“每个词法单元都是单个字符”的愿望。单单为了秉承此美好愿望,也不应该支持。此事我们以后会回过头来处理。
通过本篇以及前面两篇,我们得到了一个勉强能用的正则表达式,已实现的语法与市面上的正则表达式是一样的(未实现的当然还是未实现啦)。但是,状态机部分仍然处于原始状态,特别是经过本次的修改,ε边弥漫,极大地影响匹配的效率。下一篇我们将系统了解下ε-NFA、NFA、DFA的概念,然后做一定的优化。
首发:http://www.cppblog.com/Streamlet/archive/2012/06/08/178126.html