Add breaking changes and release 1.0.0. This makes `.error()` default to empty spans rather than single-character ones. This better represents the semantics of failing at a particular position in the text. It also makes `lastMatch` reset whenever the scanner's position changes. This makes `.error()` behave more consistently when primarily doing character-based scanning, since it won't unexpectedly emit an error for stale match data. R=jmesserly@google.com Review URL: https://codereview.chromium.org//2056933002 .
diff --git a/pkgs/string_scanner/CHANGELOG.md b/pkgs/string_scanner/CHANGELOG.md index db73686..910d30e 100644 --- a/pkgs/string_scanner/CHANGELOG.md +++ b/pkgs/string_scanner/CHANGELOG.md
@@ -1,3 +1,16 @@ +## 1.0.0 + +* **Breaking change**: `StringScanner.error()`'s `length` argument now defaults + to `0` rather than `1` when no match data is available. + +* **Breaking change**: `StringScanner.lastMatch` and related methods are now + reset when the scanner's position changes without producing a new match. + +**Note**: While the changes in `1.0.0` are user-visible, they're unlikely to +actually break any code in practice. Unless you know that your package is +incompatible with 0.1.x, consider using 0.1.5 as your lower bound rather +than 1.0.0. For example, `string_scanner: ">=0.1.5 <2.0.0"`. + ## 0.1.5 * Add `new SpanScanner.within()`, which scans within a existing `FileSpan`.
diff --git a/pkgs/string_scanner/lib/src/line_scanner.dart b/pkgs/string_scanner/lib/src/line_scanner.dart index fe63592..06f1cbc 100644 --- a/pkgs/string_scanner/lib/src/line_scanner.dart +++ b/pkgs/string_scanner/lib/src/line_scanner.dart
@@ -26,6 +26,8 @@ /// This can be used to efficiently save and restore the state of the scanner /// when backtracking. A given [LineScannerState] is only valid for the /// [LineScanner] that created it. + /// + /// This does not include the scanner's match information. LineScannerState get state => new LineScannerState._(this, position, line, column);
diff --git a/pkgs/string_scanner/lib/src/span_scanner.dart b/pkgs/string_scanner/lib/src/span_scanner.dart index dd7f0e4..043021b 100644 --- a/pkgs/string_scanner/lib/src/span_scanner.dart +++ b/pkgs/string_scanner/lib/src/span_scanner.dart
@@ -38,7 +38,10 @@ /// /// This is the span for the entire match. There's no way to get spans for /// subgroups since [Match] exposes no information about their positions. - FileSpan get lastSpan => _lastSpan; + FileSpan get lastSpan { + if (lastMatch == null) _lastSpan = null; + return _lastSpan; + } FileSpan _lastSpan; /// The current location of the scanner. @@ -102,7 +105,7 @@ if (position == null) { position = match == null ? this.position : match.start; } - if (length == null) length = match == null ? 1 : match.end - match.start; + if (length == null) length = match == null ? 0 : match.end - match.start; var span = _sourceFile.span(position, position + length); throw new StringScannerException(message, span, string);
diff --git a/pkgs/string_scanner/lib/src/string_scanner.dart b/pkgs/string_scanner/lib/src/string_scanner.dart index 8334ccb..b9714ff 100644 --- a/pkgs/string_scanner/lib/src/string_scanner.dart +++ b/pkgs/string_scanner/lib/src/string_scanner.dart
@@ -32,14 +32,21 @@ } _position = position; + _lastMatch = null; } int _position = 0; /// The data about the previous match made by the scanner. /// /// If the last match failed, this will be `null`. - Match get lastMatch => _lastMatch; + Match get lastMatch { + // Lazily unset [_lastMatch] so that we avoid extra assignments in + // character-by-character methods that are used in core loops. + if (_position != _lastMatchPosition) _lastMatch = null; + return _lastMatch; + } Match _lastMatch; + int _lastMatchPosition; /// The portion of the string that hasn't yet been scanned. String get rest => string.substring(position); @@ -118,7 +125,10 @@ /// Returns whether or not [pattern] matched. bool scan(Pattern pattern) { var success = matches(pattern); - if (success) _position = _lastMatch.end; + if (success) { + _position = _lastMatch.end; + _lastMatchPosition = _position; + } return success; } @@ -159,6 +169,7 @@ /// This doesn't move the scan pointer forward. bool matches(Pattern pattern) { _lastMatch = pattern.matchAsPrefix(string, position); + _lastMatchPosition = _position; return _lastMatch != null; } @@ -181,7 +192,7 @@ /// /// If [position] and/or [length] are passed, they are used as the error span /// instead. If only [length] is passed, [position] defaults to the current - /// position; if only [position] is passed, [length] defaults to 1. + /// position; if only [position] is passed, [length] defaults to 0. /// /// It's an error to pass [match] at the same time as [position] or [length]. void error(String message, {Match match, int position, int length}) { @@ -191,7 +202,7 @@ if (position == null) { position = match == null ? this.position : match.start; } - if (length == null) length = match == null ? 1 : match.end - match.start; + if (length == null) length = match == null ? 0 : match.end - match.start; var sourceFile = new SourceFile(string, url: sourceUrl); var span = sourceFile.span(position, position + length);
diff --git a/pkgs/string_scanner/pubspec.yaml b/pkgs/string_scanner/pubspec.yaml index 7f7e33a..48ed59d 100644 --- a/pkgs/string_scanner/pubspec.yaml +++ b/pkgs/string_scanner/pubspec.yaml
@@ -1,5 +1,5 @@ name: string_scanner -version: 0.1.5 +version: 1.0.0 author: "Dart Team <misc@dartlang.org>" homepage: https://github.com/dart-lang/string_scanner description: >
diff --git a/pkgs/string_scanner/test/error_test.dart b/pkgs/string_scanner/test/error_test.dart index ff45194..166077d 100644 --- a/pkgs/string_scanner/test/error_test.dart +++ b/pkgs/string_scanner/test/error_test.dart
@@ -59,11 +59,11 @@ }); group("with position and/or length", () { - test('defaults to length 1', () { + test('defaults to length 0', () { var scanner = new StringScanner('foo bar baz'); scanner.expect('foo '); expect(() => scanner.error('oh no!', position: 1), - throwsStringScannerException('o')); + throwsStringScannerException('')); }); test('defaults to the current position', () {
diff --git a/pkgs/string_scanner/test/string_scanner_test.dart b/pkgs/string_scanner/test/string_scanner_test.dart index 0b4d482..55ffa0a 100644 --- a/pkgs/string_scanner/test/string_scanner_test.dart +++ b/pkgs/string_scanner/test/string_scanner_test.dart
@@ -261,6 +261,32 @@ }); }); + group('after a scan', () { + var scanner; + setUp(() { + scanner = new StringScanner('foo bar'); + expect(scanner.scan('foo'), isTrue); + }); + + test('readChar returns the first character and unsets the last match', () { + expect(scanner.readChar(), equals($space)); + expect(scanner.lastMatch, isNull); + expect(scanner.position, equals(4)); + }); + + test('a matching scanChar returns true and unsets the last match', () { + expect(scanner.scanChar($space), isTrue); + expect(scanner.lastMatch, isNull); + expect(scanner.position, equals(4)); + }); + + test('a matching expectChar returns true and unsets the last match', () { + scanner.expectChar($space); + expect(scanner.lastMatch, isNull); + expect(scanner.position, equals(4)); + }); + }); + group('at the end of a string', () { var scanner; setUp(() { @@ -346,6 +372,13 @@ expect(scanner.rest, equals('bar')); }); + test('setting and resetting position clears lastMatch', () { + var oldPosition = scanner.position; + scanner.position = 1; + scanner.position = oldPosition; + expect(scanner.lastMatch, isNull); + }); + test('setting position beyond the string throws an ArgumentError', () { expect(() { scanner.position = 8;