Add StringScanner.scanChar() and .expectChar().
R=jmesserly@google.com
Review URL: https://codereview.chromium.org//2041813002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea1268c..db73686 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
* Add `new SpanScanner.within()`, which scans within a existing `FileSpan`.
+* Add `StringScanner.scanChar()` and `StringScanner.expectChar()`.
+
## 0.1.4+1
* Remove the dependency on `path`, since we don't actually import it.
diff --git a/lib/src/eager_span_scanner.dart b/lib/src/eager_span_scanner.dart
index c537b0c..f80dce5 100644
--- a/lib/src/eager_span_scanner.dart
+++ b/lib/src/eager_span_scanner.dart
@@ -67,15 +67,26 @@
EagerSpanScanner(String string, {sourceUrl, int position})
: super(string, sourceUrl: sourceUrl, position: position);
+ bool scanChar(int character) {
+ if (!super.scanChar(character)) return false;
+ _adjustLineAndColumn(character);
+ return true;
+ }
+
int readChar() {
- var char = super.readChar();
- if (char == $lf || (char == $cr && peekChar() != $lf)) {
+ var character = super.readChar();
+ _adjustLineAndColumn(character);
+ return character;
+ }
+
+ /// Adjusts [_line] and [_column] after having consumed [character].
+ void _adjustLineAndColumn(int character) {
+ if (character == $lf || (character == $cr && peekChar() != $lf)) {
_line += 1;
_column = 0;
} else {
_column += 1;
}
- return char;
}
bool scan(Pattern pattern) {
diff --git a/lib/src/line_scanner.dart b/lib/src/line_scanner.dart
index b439193..fe63592 100644
--- a/lib/src/line_scanner.dart
+++ b/lib/src/line_scanner.dart
@@ -73,15 +73,26 @@
LineScanner(String string, {sourceUrl, int position})
: super(string, sourceUrl: sourceUrl, position: position);
+ bool scanChar(int character) {
+ if (!super.scanChar(character)) return false;
+ _adjustLineAndColumn(character);
+ return true;
+ }
+
int readChar() {
- var char = super.readChar();
- if (char == $lf || (char == $cr && peekChar() != $lf)) {
+ var character = super.readChar();
+ _adjustLineAndColumn(character);
+ return character;
+ }
+
+ /// Adjusts [_line] and [_column] after having consumed [character].
+ void _adjustLineAndColumn(int character) {
+ if (character == $lf || (character == $cr && peekChar() != $lf)) {
_line += 1;
_column = 0;
} else {
_column += 1;
}
- return char;
}
bool scan(Pattern pattern) {
diff --git a/lib/src/string_scanner.dart b/lib/src/string_scanner.dart
index 775dd5e..8334ccb 100644
--- a/lib/src/string_scanner.dart
+++ b/lib/src/string_scanner.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:charcode/charcode.dart';
import 'package:source_span/source_span.dart';
import 'exception.dart';
@@ -79,6 +80,38 @@
return string.codeUnitAt(index);
}
+ /// If the next character in the string is [character], consumes it.
+ ///
+ /// Returns whether or not [character] was consumed.
+ bool scanChar(int character) {
+ if (isDone) return false;
+ if (string.codeUnitAt(_position) != character) return false;
+ _position++;
+ return true;
+ }
+
+ /// If the next character in the string is [character], consumes it.
+ ///
+ /// If [character] could not be consumed, throws a [FormatException]
+ /// describing the position of the failure. [name] is used in this error as
+ /// the expected name of the character being matched; if it's `null`, the
+ /// character itself is used instead.
+ void expectChar(int character, {String name}) {
+ if (scanChar(character)) return;
+
+ if (name == null) {
+ if (character == $backslash) {
+ name = r'"\"';
+ } else if (character == $double_quote) {
+ name = r'"\""';
+ } else {
+ name = '"${new String.fromCharCode(character)}"';
+ }
+ }
+
+ _fail('Expected $name.');
+ }
+
/// If [pattern] matches at the current position of the string, scans forward
/// until the end of the match.
///
diff --git a/pubspec.yaml b/pubspec.yaml
index 24bd6bb..7f7e33a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: string_scanner
-version: 0.1.5-dev
+version: 0.1.5
author: "Dart Team <misc@dartlang.org>"
homepage: https://github.com/dart-lang/string_scanner
description: >
diff --git a/test/line_scanner_test.dart b/test/line_scanner_test.dart
index 9874cb3..ed04b37 100644
--- a/test/line_scanner_test.dart
+++ b/test/line_scanner_test.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:charcode/charcode.dart';
import 'package:string_scanner/string_scanner.dart';
import 'package:test/test.dart';
@@ -80,6 +81,39 @@
});
});
+ group("scanChar()", () {
+ test("on a non-newline character increases the column but not the line",
+ () {
+ scanner.scanChar($f);
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(1));
+ });
+
+ test("consuming a newline resets the column and increases the line", () {
+ scanner.expect('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($lf);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(0));
+ });
+
+ test("consuming halfway through a CR LF doesn't count as a line", () {
+ scanner.expect('foo\nbar');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(3));
+
+ scanner.scanChar($cr);
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.scanChar($lf);
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(0));
+ });
+ });
+
group("position=", () {
test("forward through newlines sets the line and column", () {
scanner.position = 10; // "foo\nbar\r\nb"
diff --git a/test/string_scanner_test.dart b/test/string_scanner_test.dart
index 10e622a..0b4d482 100644
--- a/test/string_scanner_test.dart
+++ b/test/string_scanner_test.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'package:charcode/charcode.dart';
import 'package:string_scanner/string_scanner.dart';
import 'package:test/test.dart';
@@ -41,6 +42,18 @@
expect(scanner.position, equals(0));
});
+ test("scanChar returns false and doesn't change the state", () {
+ expect(scanner.scanChar($f), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("expectChar fails and doesn't change the state", () {
+ expect(() => scanner.expectChar($f), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
test("scan returns false and doesn't change the state", () {
expect(scanner.scan(new RegExp('.')), isFalse);
expect(scanner.lastMatch, isNull);
@@ -117,6 +130,30 @@
expect(scanner.position, equals(0));
});
+ test("a matching scanChar returns true moves forward", () {
+ expect(scanner.scanChar($f), isTrue);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test("a non-matching scanChar returns false and does nothing", () {
+ expect(scanner.scanChar($x), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("a matching expectChar moves forward", () {
+ scanner.expectChar($f);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test("a non-matching expectChar fails", () {
+ expect(() => scanner.expectChar($x), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
test("a matching scan returns true and changes the state", () {
expect(scanner.scan(new RegExp('f(..)')), isTrue);
expect(scanner.lastMatch[1], equals('oo'));
@@ -256,6 +293,18 @@
expect(scanner.position, equals(7));
});
+ test("scanChar returns false and doesn't change the state", () {
+ expect(scanner.scanChar($f), isFalse);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("expectChar fails and doesn't change the state", () {
+ expect(() => scanner.expectChar($f), throwsFormatException);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
test("scan returns false and sets lastMatch to null", () {
expect(scanner.scan(new RegExp('.')), isFalse);
expect(scanner.lastMatch, isNull);