Add StringScanner.scanChar() and .expectChar(). R=jmesserly@google.com Review URL: https://codereview.chromium.org//2041813002 .
diff --git a/pkgs/string_scanner/CHANGELOG.md b/pkgs/string_scanner/CHANGELOG.md index ea1268c..db73686 100644 --- a/pkgs/string_scanner/CHANGELOG.md +++ b/pkgs/string_scanner/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/pkgs/string_scanner/lib/src/eager_span_scanner.dart b/pkgs/string_scanner/lib/src/eager_span_scanner.dart index c537b0c..f80dce5 100644 --- a/pkgs/string_scanner/lib/src/eager_span_scanner.dart +++ b/pkgs/string_scanner/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/pkgs/string_scanner/lib/src/line_scanner.dart b/pkgs/string_scanner/lib/src/line_scanner.dart index b439193..fe63592 100644 --- a/pkgs/string_scanner/lib/src/line_scanner.dart +++ b/pkgs/string_scanner/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/pkgs/string_scanner/lib/src/string_scanner.dart b/pkgs/string_scanner/lib/src/string_scanner.dart index 775dd5e..8334ccb 100644 --- a/pkgs/string_scanner/lib/src/string_scanner.dart +++ b/pkgs/string_scanner/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/pkgs/string_scanner/pubspec.yaml b/pkgs/string_scanner/pubspec.yaml index 24bd6bb..7f7e33a 100644 --- a/pkgs/string_scanner/pubspec.yaml +++ b/pkgs/string_scanner/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/pkgs/string_scanner/test/line_scanner_test.dart b/pkgs/string_scanner/test/line_scanner_test.dart index 9874cb3..ed04b37 100644 --- a/pkgs/string_scanner/test/line_scanner_test.dart +++ b/pkgs/string_scanner/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/pkgs/string_scanner/test/string_scanner_test.dart b/pkgs/string_scanner/test/string_scanner_test.dart index 10e622a..0b4d482 100644 --- a/pkgs/string_scanner/test/string_scanner_test.dart +++ b/pkgs/string_scanner/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);