Skip to content

Commit b5e1f05

Browse files
committed
[RegExp] Add support for arbitrary repetition ranges.
1 parent a5e4572 commit b5e1f05

2 files changed

Lines changed: 59 additions & 11 deletions

File tree

lib/src/regexp/node.dart

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,29 +159,50 @@ class QuantificationNode extends Node {
159159

160160
@override
161161
Nfa toNfa() {
162-
final start = NfaState(isEnd: false);
163-
final end = NfaState(isEnd: true);
164-
final childNfa = child.toNfa();
165162
if (min == 0 && max == null) {
163+
final start = NfaState(isEnd: false);
164+
final end = NfaState(isEnd: true);
165+
final childNfa = child.toNfa();
166166
start.epsilons.add(end);
167167
start.epsilons.add(childNfa.start);
168168
childNfa.end.epsilons.add(end);
169169
childNfa.end.epsilons.add(childNfa.start);
170170
childNfa.end.isEnd = false;
171+
return Nfa(start: start, end: end);
171172
} else if (min == 0 && max == 1) {
173+
final start = NfaState(isEnd: false);
174+
final end = NfaState(isEnd: true);
175+
final childNfa = child.toNfa();
172176
start.epsilons.add(end);
173177
start.epsilons.add(childNfa.start);
174178
childNfa.end.epsilons.add(end);
175179
childNfa.end.isEnd = false;
176-
} else if (min == 1 && max == null) {
177-
start.epsilons.add(childNfa.start);
178-
childNfa.end.epsilons.add(end);
179-
childNfa.end.epsilons.add(childNfa.start);
180-
childNfa.end.isEnd = false;
180+
return Nfa(start: start, end: end);
181+
}
182+
final nfas = <Nfa>[];
183+
for (var i = 0; i < min; i++) {
184+
nfas.add(child.toNfa());
185+
}
186+
if (max == null) {
187+
nfas.add(QuantificationNode(child, 0, null).toNfa());
181188
} else {
182-
throw UnsupportedError(toString());
189+
for (var i = 0; i < max! - min; i++) {
190+
nfas.add(QuantificationNode(child, 0, 1).toNfa());
191+
}
183192
}
184-
return Nfa(start: start, end: end);
193+
if (nfas.isEmpty) {
194+
final start = NfaState(isEnd: false);
195+
final end = NfaState(isEnd: true);
196+
start.epsilons.add(end);
197+
return Nfa(start: start, end: end);
198+
}
199+
for (var i = 0; i < nfas.length - 1; i++) {
200+
final current = nfas[i];
201+
final next = nfas[i + 1];
202+
current.end.epsilons.add(next.start);
203+
current.end.isEnd = false;
204+
}
205+
return Nfa(start: nfas.first.start, end: nfas.last.end);
185206
}
186207

187208
@override

test/regexp_test.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ void main() {
136136
});
137137
}
138138
test('unsupported', () {
139-
expect(() => Node.fromString(r'a{2,}').toNfa(), throwsUnsupportedError);
140139
expect(() => Node.fromString(r'a&b').toNfa(), throwsUnsupportedError);
141140
expect(() => Node.fromString(r'!a').toNfa(), throwsUnsupportedError);
142141
});
@@ -377,6 +376,34 @@ const tests = [
377376
Expect('aababab', false),
378377
Expect('aabbbbb', false),
379378
]),
379+
// Arbitrary ranges
380+
Test(r'a{3}', [
381+
Expect('', false),
382+
Expect('a', false),
383+
Expect('aa', false),
384+
Expect('aaa', true),
385+
Expect('aaaa', false),
386+
]),
387+
Test(r'a{2,}', [
388+
Expect('', false),
389+
Expect('a', false),
390+
Expect('aa', true),
391+
Expect('aaa', true),
392+
Expect('aaaa', true),
393+
]),
394+
Test(r'a{1,3}', [
395+
Expect('', false),
396+
Expect('a', true),
397+
Expect('aa', true),
398+
Expect('aaa', true),
399+
Expect('aaaa', false),
400+
]),
401+
Test(r'a{,2}', [
402+
Expect('', true),
403+
Expect('a', true),
404+
Expect('aa', true),
405+
Expect('aaa', false),
406+
]),
380407
// https://github.com/xysun/regex/blob/master/testing.py
381408
Test(r'(ab|a)(bc|c)', [Expect('abc', true), Expect('acb', false)]),
382409
Test(r'(ab)c|abc', [Expect('abc', true), Expect('ab', false)]),

0 commit comments

Comments
 (0)