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/CHANGELOG.md b/CHANGELOG.md
index db73686..910d30e 100644
--- a/CHANGELOG.md
+++ b/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/lib/src/line_scanner.dart b/lib/src/line_scanner.dart
index fe63592..06f1cbc 100644
--- a/lib/src/line_scanner.dart
+++ b/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/lib/src/span_scanner.dart b/lib/src/span_scanner.dart
index dd7f0e4..043021b 100644
--- a/lib/src/span_scanner.dart
+++ b/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/lib/src/string_scanner.dart b/lib/src/string_scanner.dart
index 8334ccb..b9714ff 100644
--- a/lib/src/string_scanner.dart
+++ b/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/pubspec.yaml b/pubspec.yaml
index 7f7e33a..48ed59d 100644
--- a/pubspec.yaml
+++ b/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/test/error_test.dart b/test/error_test.dart
index ff45194..166077d 100644
--- a/test/error_test.dart
+++ b/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/test/string_scanner_test.dart b/test/string_scanner_test.dart
index 0b4d482..55ffa0a 100644
--- a/test/string_scanner_test.dart
+++ b/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;