From c8670784700c5be139a175006724115748b35d46 Mon Sep 17 00:00:00 2001 From: Lukas Renggli Date: Sat, 9 Nov 2024 14:19:10 +0100 Subject: [PATCH] Improve error reporting and add tests for infinite repeaters. --- lib/src/parser/repeater/greedy.dart | 10 +++-- lib/src/parser/repeater/lazy.dart | 10 +++-- lib/src/parser/repeater/possessive.dart | 10 +++-- test/parser_test.dart | 57 +++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/lib/src/parser/repeater/greedy.dart b/lib/src/parser/repeater/greedy.dart index 798e3857..3e82bc3a 100644 --- a/lib/src/parser/repeater/greedy.dart +++ b/lib/src/parser/repeater/greedy.dart @@ -62,7 +62,8 @@ class GreedyRepeatingParser extends LimitedRepeatingParser { 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; } @@ -70,7 +71,8 @@ class GreedyRepeatingParser extends LimitedRepeatingParser { 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); } @@ -94,7 +96,7 @@ class GreedyRepeatingParser extends LimitedRepeatingParser { 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++; } @@ -102,7 +104,7 @@ class GreedyRepeatingParser extends LimitedRepeatingParser { 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++; } diff --git a/lib/src/parser/repeater/lazy.dart b/lib/src/parser/repeater/lazy.dart index 753389ff..0a01cb2f 100644 --- a/lib/src/parser/repeater/lazy.dart +++ b/lib/src/parser/repeater/lazy.dart @@ -62,7 +62,8 @@ class LazyRepeatingParser extends LimitedRepeatingParser { 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; } @@ -72,7 +73,8 @@ class LazyRepeatingParser extends LimitedRepeatingParser { 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 { @@ -88,7 +90,7 @@ class LazyRepeatingParser extends LimitedRepeatingParser { 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++; } @@ -98,7 +100,7 @@ class LazyRepeatingParser extends LimitedRepeatingParser { 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 { diff --git a/lib/src/parser/repeater/possessive.dart b/lib/src/parser/repeater/possessive.dart index a85d9dc9..50905436 100644 --- a/lib/src/parser/repeater/possessive.dart +++ b/lib/src/parser/repeater/possessive.dart @@ -66,14 +66,16 @@ class PossessiveRepeatingParser extends RepeatingParser> { 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; } @@ -87,14 +89,14 @@ class PossessiveRepeatingParser extends RepeatingParser> { 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++; } diff --git a/test/parser_test.dart b/test/parser_test.dart index 2fea39e9..a0d67798 100644 --- a/test/parser_test.dart +++ b/test/parser_test.dart @@ -1793,6 +1793,25 @@ void main() { isParseSuccess('${inputDigit.join()}1', result: inputDigit, position: inputDigit.length)); }); + test('infinite loop', () { + final inner = epsilon(), limiter = failure(); + 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())); @@ -1906,6 +1925,25 @@ void main() { isParseSuccess('${input.join()}1111', result: input, position: input.length)); }); + test('infinite loop', () { + final inner = epsilon(), limiter = failure(); + 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()); @@ -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());