Skip to content

Commit

Permalink
Improve error reporting and add tests for infinite repeaters.
Browse files Browse the repository at this point in the history
  • Loading branch information
renggli committed Nov 9, 2024
1 parent 053b283 commit c867078
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 12 deletions.
10 changes: 6 additions & 4 deletions lib/src/parser/repeater/greedy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ class GreedyRepeatingParser<R> extends LimitedRepeatingParser<R> {
while (elements.length < min) {
final result = delegate.parseOn(current);
if (result is Failure) return result;
assert(current.position < result.position, '$this must always consume');
assert(
current.position < result.position, '$delegate must always consume');
elements.add(result.value);
current = result;
}
final contexts = <Context>[current];
while (elements.length < max) {
final result = delegate.parseOn(current);
if (result is Failure) break;
assert(current.position < result.position, '$this must always consume');
assert(
current.position < result.position, '$delegate must always consume');
elements.add(result.value);
contexts.add(current = result);
}
Expand All @@ -94,15 +96,15 @@ class GreedyRepeatingParser<R> extends LimitedRepeatingParser<R> {
while (count < min) {
final result = delegate.fastParseOn(buffer, current);
if (result < 0) return -1;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
current = result;
count++;
}
final positions = <int>[current];
while (count < max) {
final result = delegate.fastParseOn(buffer, current);
if (result < 0) break;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
positions.add(current = result);
count++;
}
Expand Down
10 changes: 6 additions & 4 deletions lib/src/parser/repeater/lazy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class LazyRepeatingParser<R> extends LimitedRepeatingParser<R> {
while (elements.length < min) {
final result = delegate.parseOn(current);
if (result is Failure) return result;
assert(current.position < result.position, '$this must always consume');
assert(
current.position < result.position, '$delegate must always consume');
elements.add(result.value);
current = result;
}
Expand All @@ -72,7 +73,8 @@ class LazyRepeatingParser<R> extends LimitedRepeatingParser<R> {
if (elements.length >= max) return limiter;
final result = delegate.parseOn(current);
if (result is Failure) return limiter;
assert(current.position < result.position, '$this must always consume');
assert(current.position < result.position,
'$delegate must always consume');
elements.add(result.value);
current = result;
} else {
Expand All @@ -88,7 +90,7 @@ class LazyRepeatingParser<R> extends LimitedRepeatingParser<R> {
while (count < min) {
final result = delegate.fastParseOn(buffer, current);
if (result < 0) return -1;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
current = result;
count++;
}
Expand All @@ -98,7 +100,7 @@ class LazyRepeatingParser<R> extends LimitedRepeatingParser<R> {
if (count >= max) return -1;
final result = delegate.fastParseOn(buffer, current);
if (result < 0) return -1;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
current = result;
count++;
} else {
Expand Down
10 changes: 6 additions & 4 deletions lib/src/parser/repeater/possessive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@ class PossessiveRepeatingParser<R> extends RepeatingParser<R, List<R>> {
while (elements.length < min) {
final result = delegate.parseOn(current);
if (result is Failure) return result;
assert(current.position < result.position, '$this must always consume');
assert(
current.position < result.position, '$delegate must always consume');
elements.add(result.value);
current = result;
}
while (elements.length < max) {
final result = delegate.parseOn(current);
if (result is Failure) break;
assert(current.position < result.position, '$this must always consume');
assert(
current.position < result.position, '$delegate must always consume');
elements.add(result.value);
current = result;
}
Expand All @@ -87,14 +89,14 @@ class PossessiveRepeatingParser<R> extends RepeatingParser<R, List<R>> {
while (count < min) {
final result = delegate.fastParseOn(buffer, current);
if (result < 0) return -1;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
current = result;
count++;
}
while (count < max) {
final result = delegate.fastParseOn(buffer, current);
if (result < 0) break;
assert(current < result, '$this must always consume');
assert(current < result, '$delegate must always consume');
current = result;
count++;
}
Expand Down
57 changes: 57 additions & 0 deletions test/parser_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,25 @@ void main() {
isParseSuccess('${inputDigit.join()}1',
result: inputDigit, position: inputDigit.length));
});
test('infinite loop', () {
final inner = epsilon(), limiter = failure<void>();
expect(
() => inner.starGreedy(limiter).parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.starGreedy(limiter).fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plusGreedy(limiter).parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plusGreedy(limiter).fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
}, skip: !hasAssertionsEnabled());
});
group('lazy', () {
expectParserInvariants(any().starLazy(digit()));
Expand Down Expand Up @@ -1906,6 +1925,25 @@ void main() {
isParseSuccess('${input.join()}1111',
result: input, position: input.length));
});
test('infinite loop', () {
final inner = epsilon(), limiter = failure<void>();
expect(
() => inner.starLazy(limiter).parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.starLazy(limiter).fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plusLazy(limiter).parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plusLazy(limiter).fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
}, skip: !hasAssertionsEnabled());
});
group('possessive', () {
expectParserInvariants(any().star());
Expand Down Expand Up @@ -1964,6 +2002,25 @@ void main() {
expect(parser, isParseSuccess('aa', result: ['a', 'a']));
expect(parser, isParseSuccess('aaa', result: ['a', 'a'], position: 2));
});
test('infinite loop', () {
final inner = epsilon();
expect(
() => inner.star().parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.star().fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plus().parse(''),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
expect(
() => inner.plus().fastParseOn('', 0),
throwsA(isAssertionError.having((exception) => exception.message,
'message', '$inner must always consume')));
}, skip: !hasAssertionsEnabled());
});
group('string', () {
expectParserInvariants(any().starString());
Expand Down

0 comments on commit c867078

Please sign in to comment.