+ Stop running `pub get` at gclient sync time. This was slow and unnecessary.
+ Stop symlinking non-dart_pkg packages into gen/dart-pkg/packages. This suffered from three problems:
1) These symlinks were not being specified as build output by any rule. They were a magic side effect.
2) There was a race where many symlinks to the same package could be created, the last one winning.
3) Related to 2, if two packages relied on two different versions of the same package, we could have hard to reproduce bugs that depend upon the winner of the race.
We now have a checked in copy of all third-party Dart packages needed by the Mojo Dart SDK. These live under //mojo/public/dart/third_party. There is a script 'update_packages.py' which uses `pub get` to handle all version constraints and updates the source tree after pub is run. It's now trivial to ensure we have a compatible set of packages checked into the tree and to update the package set.
Fixes #395
Fixes #401
Fixes #403
Fixes #429
R=jamesr@chromium.org, tonyg@chromium.org, zra@google.com
Review URL: https://codereview.chromium.org/1346773002 .
diff --git a/mojo/public/dart/third_party/string_scanner/.gitignore b/mojo/public/dart/third_party/string_scanner/.gitignore
new file mode 100644
index 0000000..7dbf035
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/.gitignore
@@ -0,0 +1,15 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
\ No newline at end of file
diff --git a/mojo/public/dart/third_party/string_scanner/.test_config b/mojo/public/dart/third_party/string_scanner/.test_config
new file mode 100644
index 0000000..412fc5c
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/.test_config
@@ -0,0 +1,3 @@
+{
+ "test_package": true
+}
\ No newline at end of file
diff --git a/mojo/public/dart/third_party/string_scanner/CHANGELOG.md b/mojo/public/dart/third_party/string_scanner/CHANGELOG.md
new file mode 100644
index 0000000..23f911c
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/CHANGELOG.md
@@ -0,0 +1,70 @@
+## 0.1.4
+
+* Add `new SpanScanner.eager()` for creating a `SpanScanner` that eagerly
+ computes its current line and column numbers.
+
+## 0.1.3+2
+
+* Fix `LineScanner`'s handling of carriage returns to match that of
+ `SpanScanner`.
+
+## 0.1.3+1
+
+* Fixed the homepage URL.
+
+## 0.1.3
+
+* Add an optional `endState` argument to `SpanScanner.spanFrom`.
+
+## 0.1.2
+
+* Add `StringScanner.substring`, which returns a substring of the source string.
+
+## 0.1.1
+
+* Declare `SpanScanner`'s exposed `SourceSpan`s and `SourceLocation`s to be
+ `FileSpan`s and `FileLocation`s. They always were underneath, but callers may
+ now rely on it.
+
+* Add `SpanScanner.location`, which returns the scanner's current
+ `SourceLocation`.
+
+## 0.1.0
+
+* Switch from `source_maps`' `Span` class to `source_span`'s `SourceSpan` class.
+
+* `new StringScanner()`'s `sourceUrl` parameter is now named to make it clear
+ that it can be safely `null`.
+
+* `new StringScannerException()` takes different arguments in a different order
+ to match `SpanFormatException`.
+
+* `StringScannerException.string` has been renamed to
+ `StringScannerException.source` to match the `FormatException` interface.
+
+## 0.0.3
+
+* Make `StringScannerException` inherit from source_map's
+ [`SpanFormatException`][].
+
+[SpanFormatException]: (http://www.dartdocs.org/documentation/source_maps/0.9.2/index.html#source_maps/source_maps.SpanFormatException)
+
+## 0.0.2
+
+* `new StringScanner()` now takes an optional `sourceUrl` argument that provides
+ the URL of the source file. This is used for error reporting.
+
+* Add `StringScanner.readChar()` and `StringScanner.peekChar()` methods for
+ doing character-by-character scanning.
+
+* Scanners now throw `StringScannerException`s which provide more detailed
+ access to information about the errors that were thrown and can provide
+ terminal-colored messages.
+
+* Add a `LineScanner` subclass of `StringScanner` that automatically tracks line
+ and column information of the text being scanned.
+
+* Add a `SpanScanner` subclass of `LineScanner` that exposes matched ranges as
+ [source map][] `Span` objects.
+
+[source_map]: http://pub.dartlang.org/packages/source_maps
diff --git a/mojo/public/dart/third_party/string_scanner/LICENSE b/mojo/public/dart/third_party/string_scanner/LICENSE
new file mode 100644
index 0000000..5c60afe
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2014, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/mojo/public/dart/third_party/string_scanner/README.md b/mojo/public/dart/third_party/string_scanner/README.md
new file mode 100644
index 0000000..90660fc
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/README.md
@@ -0,0 +1,37 @@
+This package exposes a `StringScanner` type that makes it easy to parse a string
+using a series of `Pattern`s. For example:
+
+```dart
+import 'dart:math';
+
+import 'package:string_scanner/string_scanner.dart';
+
+num parseNumber(String source) {
+ // Scan a number ("1", "1.5", "-3").
+ var scanner = new StringScanner(source);
+
+ // [Scanner.scan] tries to consume a [Pattern] and returns whether or not it
+ // succeeded. It will move the scan pointer past the end of the pattern.
+ var negative = scanner.scan("-");
+
+ // [Scanner.expect] consumes a [Pattern] and throws a [FormatError] if it
+ // fails. Like [Scanner.scan], it will move the scan pointer forward.
+ scanner.expect(new RegExp(r"\d+"));
+
+ // [Scanner.lastMatch] holds the [MatchData] for the most recent call to
+ // [Scanner.scan], [Scanner.expect], or [Scanner.matches].
+ var number = int.parse(scanner.lastMatch[0]);
+
+ if (scanner.scan(".")) {
+ scanner.expect(new RegExp(r"\d+"));
+ var decimal = scanner.lastMatch[0];
+ number += int.parse(decimal) / math.pow(10, decimal.length);
+ }
+
+ // [Scanner.expectDone] will throw a [FormatError] if there's any input that
+ // hasn't yet been consumed.
+ scanner.expectDone();
+
+ return (negative ? -1 : 1) * number;
+}
+```
diff --git a/mojo/public/dart/third_party/string_scanner/codereview.settings b/mojo/public/dart/third_party/string_scanner/codereview.settings
new file mode 100644
index 0000000..31c557f
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/string_scanner/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/eager_span_scanner.dart b/mojo/public/dart/third_party/string_scanner/lib/src/eager_span_scanner.dart
new file mode 100644
index 0000000..3fae5cc
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/eager_span_scanner.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.eager_span_scanner;
+
+import 'package:charcode/ascii.dart';
+
+import 'line_scanner.dart';
+import 'span_scanner.dart';
+
+// TODO(nweiz): Currently this duplicates code in line_scanner.dart. Once
+// sdk#23770 is fully complete, we should move the shared code into a mixin.
+
+/// A regular expression matching newlines across platforms.
+final _newlineRegExp = new RegExp(r"\r\n?|\n");
+
+/// A [SpanScanner] that tracks the line and column eagerly, like [LineScanner].
+class EagerSpanScanner extends SpanScanner {
+ int get line => _line;
+ int _line = 0;
+
+ int get column => _column;
+ int _column = 0;
+
+ LineScannerState get state =>
+ new _EagerSpanScannerState(this, position, line, column);
+
+ bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf;
+
+ set state(LineScannerState state) {
+ if (state is! _EagerSpanScannerState ||
+ !identical((state as _EagerSpanScannerState)._scanner, this)) {
+ throw new ArgumentError("The given LineScannerState was not returned by "
+ "this LineScanner.");
+ }
+
+ super.position = state.position;
+ _line = state.line;
+ _column = state.column;
+ }
+
+ set position(int newPosition) {
+ var oldPosition = position;
+ super.position = newPosition;
+
+ if (newPosition > oldPosition) {
+ var newlines = _newlinesIn(string.substring(oldPosition, newPosition));
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += newPosition - oldPosition;
+ } else {
+ _column = newPosition - newlines.last.end;
+ }
+ } else {
+ var newlines = _newlinesIn(string.substring(newPosition, oldPosition));
+ if (_betweenCRLF) newlines.removeLast();
+
+ _line -= newlines.length;
+ if (newlines.isEmpty) {
+ _column -= oldPosition - newPosition;
+ } else {
+ _column = newPosition -
+ string.lastIndexOf(_newlineRegExp, newPosition) - 1;
+ }
+ }
+ }
+
+ EagerSpanScanner(String string, {sourceUrl, int position})
+ : super(string, sourceUrl: sourceUrl, position: position);
+
+ int readChar() {
+ var char = super.readChar();
+ if (char == $lf || (char == $cr && peekChar() != $lf)) {
+ _line += 1;
+ _column = 0;
+ } else {
+ _column += 1;
+ }
+ return char;
+ }
+
+ bool scan(Pattern pattern) {
+ if (!super.scan(pattern)) return false;
+
+ var newlines = _newlinesIn(lastMatch[0]);
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += lastMatch[0].length;
+ } else {
+ _column = lastMatch[0].length - newlines.last.end;
+ }
+
+ return true;
+ }
+
+ /// Returns a list of [Match]es describing all the newlines in [text], which
+ /// is assumed to end at [position].
+ List<Match> _newlinesIn(String text) {
+ var newlines = _newlineRegExp.allMatches(text).toList();
+ if (_betweenCRLF) newlines.removeLast();
+ return newlines;
+ }
+}
+
+/// A class representing the state of an [EagerSpanScanner].
+class _EagerSpanScannerState implements LineScannerState {
+ final EagerSpanScanner _scanner;
+ final int position;
+ final int line;
+ final int column;
+
+ _EagerSpanScannerState(this._scanner, this.position, this.line, this.column);
+}
+
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/exception.dart b/mojo/public/dart/third_party/string_scanner/lib/src/exception.dart
new file mode 100644
index 0000000..50177d6
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/exception.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.exception;
+
+import 'package:source_span/source_span.dart';
+
+/// An exception thrown by a [StringScanner] that failed to parse a string.
+class StringScannerException extends SourceSpanFormatException {
+ String get source => super.source;
+
+ /// The URL of the source file being parsed.
+ ///
+ /// This may be `null`, indicating that the source URL is unknown.
+ Uri get sourceUrl => span.sourceUrl;
+
+ StringScannerException(String message, SourceSpan span, String source)
+ : super(message, span, source);
+}
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/line_scanner.dart b/mojo/public/dart/third_party/string_scanner/lib/src/line_scanner.dart
new file mode 100644
index 0000000..66d7575
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/line_scanner.dart
@@ -0,0 +1,127 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.line_scanner;
+
+import 'package:charcode/ascii.dart';
+
+import 'string_scanner.dart';
+
+// Note that much of this code is duplicated in eager_span_scanner.dart.
+
+/// A regular expression matching newlines across platforms.
+final _newlineRegExp = new RegExp(r"\r\n?|\n");
+
+/// A subclass of [StringScanner] that tracks line and column information.
+class LineScanner extends StringScanner {
+ /// The scanner's current (zero-based) line number.
+ int get line => _line;
+ int _line = 0;
+
+ /// The scanner's current (zero-based) column number.
+ int get column => _column;
+ int _column = 0;
+
+ /// The scanner's state, including line and column information.
+ ///
+ /// 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.
+ LineScannerState get state =>
+ new LineScannerState._(this, position, line, column);
+
+ /// Whether the current position is between a CR character and an LF
+ /// charactet.
+ bool get _betweenCRLF => peekChar(-1) == $cr && peekChar() == $lf;
+
+ set state(LineScannerState state) {
+ if (!identical(state._scanner, this)) {
+ throw new ArgumentError("The given LineScannerState was not returned by "
+ "this LineScanner.");
+ }
+
+ super.position = state.position;
+ _line = state.line;
+ _column = state.column;
+ }
+
+ set position(int newPosition) {
+ var oldPosition = position;
+ super.position = newPosition;
+
+ if (newPosition > oldPosition) {
+ var newlines = _newlinesIn(string.substring(oldPosition, newPosition));
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += newPosition - oldPosition;
+ } else {
+ _column = newPosition - newlines.last.end;
+ }
+ } else {
+ var newlines = _newlinesIn(string.substring(newPosition, oldPosition));
+ if (_betweenCRLF) newlines.removeLast();
+
+ _line -= newlines.length;
+ if (newlines.isEmpty) {
+ _column -= oldPosition - newPosition;
+ } else {
+ _column = newPosition -
+ string.lastIndexOf(_newlineRegExp, newPosition) - 1;
+ }
+ }
+ }
+
+ LineScanner(String string, {sourceUrl, int position})
+ : super(string, sourceUrl: sourceUrl, position: position);
+
+ int readChar() {
+ var char = super.readChar();
+ if (char == $lf || (char == $cr && peekChar() != $lf)) {
+ _line += 1;
+ _column = 0;
+ } else {
+ _column += 1;
+ }
+ return char;
+ }
+
+ bool scan(Pattern pattern) {
+ if (!super.scan(pattern)) return false;
+
+ var newlines = _newlinesIn(lastMatch[0]);
+ _line += newlines.length;
+ if (newlines.isEmpty) {
+ _column += lastMatch[0].length;
+ } else {
+ _column = lastMatch[0].length - newlines.last.end;
+ }
+
+ return true;
+ }
+
+ /// Returns a list of [Match]es describing all the newlines in [text], which
+ /// is assumed to end at [position].
+ List<Match> _newlinesIn(String text) {
+ var newlines = _newlineRegExp.allMatches(text).toList();
+ if (_betweenCRLF) newlines.removeLast();
+ return newlines;
+ }
+}
+
+/// A class representing the state of a [LineScanner].
+class LineScannerState {
+ /// The [LineScanner] that created this.
+ final LineScanner _scanner;
+
+ /// The position of the scanner in this state.
+ final int position;
+
+ /// The zero-based line number of the scanner in this state.
+ final int line;
+
+ /// The zero-based column number of the scanner in this state.
+ final int column;
+
+ LineScannerState._(this._scanner, this.position, this.line, this.column);
+}
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/span_scanner.dart b/mojo/public/dart/third_party/string_scanner/lib/src/span_scanner.dart
new file mode 100644
index 0000000..ebe230d
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/span_scanner.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.span_scanner;
+
+import 'package:source_span/source_span.dart';
+
+import 'eager_span_scanner.dart';
+import 'exception.dart';
+import 'line_scanner.dart';
+import 'string_scanner.dart';
+import 'utils.dart';
+
+/// A subclass of [LineScanner] that exposes matched ranges as source map
+/// [Span]s.
+class SpanScanner extends StringScanner implements LineScanner {
+ /// The source of the scanner.
+ ///
+ /// This caches line break information and is used to generate [Span]s.
+ final SourceFile _sourceFile;
+
+ int get line => _sourceFile.getLine(position);
+ int get column => _sourceFile.getColumn(position);
+
+ LineScannerState get state => new _SpanScannerState(this, position);
+
+ set state(LineScannerState state) {
+ if (state is! _SpanScannerState ||
+ !identical((state as _SpanScannerState)._scanner, this)) {
+ throw new ArgumentError("The given LineScannerState was not returned by "
+ "this LineScanner.");
+ }
+
+ this.position = state.position;
+ }
+
+ /// The [FileSpan] for [lastMatch].
+ ///
+ /// 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 _lastSpan;
+
+ /// The current location of the scanner.
+ FileLocation get location => _sourceFile.location(position);
+
+ /// Returns an empty span at the current location.
+ FileSpan get emptySpan => location.pointSpan();
+
+ /// Creates a new [SpanScanner] that starts scanning from [position].
+ ///
+ /// [sourceUrl] is used as [SourceLocation.sourceUrl] for the returned
+ /// [FileSpan]s as well as for error reporting. It can be a [String], a
+ /// [Uri], or `null`.
+ SpanScanner(String string, {sourceUrl, int position})
+ : _sourceFile = new SourceFile(string, url: sourceUrl),
+ super(string, sourceUrl: sourceUrl, position: position);
+
+ /// Creates a new [SpanScanner] that eagerly computes line and column numbers.
+ ///
+ /// In general [new SpanScanner] will be more efficient, since it avoids extra
+ /// computation on every scan. However, eager scanning can be useful for
+ /// situations where the normal course of parsing frequently involves
+ /// accessing the current line and column numbers.
+ ///
+ /// Note that *only* the `line` and `column` fields on the `SpanScanner`
+ /// itself and its `LineScannerState` are eagerly computed. To limit their
+ /// memory footprint, returned spans and locations will still lazily compute
+ /// their line and column numbers.
+ factory SpanScanner.eager(String string, {sourceUrl, int position}) =
+ EagerSpanScanner;
+
+ /// Creates a [FileSpan] representing the source range between [startState]
+ /// and the current position.
+ FileSpan spanFrom(LineScannerState startState, [LineScannerState endState]) {
+ var endPosition = endState == null ? position : endState.position;
+ return _sourceFile.span(startState.position, endPosition);
+ }
+
+ bool matches(Pattern pattern) {
+ if (!super.matches(pattern)) {
+ _lastSpan = null;
+ return false;
+ }
+
+ _lastSpan = _sourceFile.span(position, lastMatch.end);
+ return true;
+ }
+
+ void error(String message, {Match match, int position, int length}) {
+ validateErrorArgs(string, match, position, length);
+
+ if (match == null && position == null && length == null) match = lastMatch;
+ if (position == null) {
+ position = match == null ? this.position : match.start;
+ }
+ if (length == null) length = match == null ? 1 : match.end - match.start;
+
+ var span = _sourceFile.span(position, position + length);
+ throw new StringScannerException(message, span, string);
+ }
+}
+
+/// A class representing the state of a [SpanScanner].
+class _SpanScannerState implements LineScannerState {
+ /// The [SpanScanner] that created this.
+ final SpanScanner _scanner;
+
+ final int position;
+ int get line => _scanner._sourceFile.getLine(position);
+ int get column => _scanner._sourceFile.getColumn(position);
+
+ _SpanScannerState(this._scanner, this.position);
+}
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/string_scanner.dart b/mojo/public/dart/third_party/string_scanner/lib/src/string_scanner.dart
new file mode 100644
index 0000000..d9c1c2f
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/string_scanner.dart
@@ -0,0 +1,176 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.string_scanner;
+
+import 'package:source_span/source_span.dart';
+
+import 'exception.dart';
+import 'utils.dart';
+
+/// When compiled to JS, forward slashes are always escaped in [RegExp.pattern].
+///
+/// See issue 17998.
+final _slashAutoEscape = new RegExp("/").pattern == "\\/";
+
+/// A class that scans through a string using [Pattern]s.
+class StringScanner {
+ /// The URL of the source of the string being scanned.
+ ///
+ /// This is used for error reporting. It may be `null`, indicating that the
+ /// source URL is unknown or unavailable.
+ final Uri sourceUrl;
+
+ /// The string being scanned through.
+ final String string;
+
+ /// The current position of the scanner in the string, in characters.
+ int get position => _position;
+ set position(int position) {
+ if (position < 0 || position > string.length) {
+ throw new ArgumentError("Invalid position $position");
+ }
+
+ _position = position;
+ }
+ 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 _lastMatch;
+
+ /// The portion of the string that hasn't yet been scanned.
+ String get rest => string.substring(position);
+
+ /// Whether the scanner has completely consumed [string].
+ bool get isDone => position == string.length;
+
+ /// Creates a new [StringScanner] that starts scanning from [position].
+ ///
+ /// [position] defaults to 0, the beginning of the string. [sourceUrl] is the
+ /// URL of the source of the string being scanned, if available. It can be
+ /// a [String], a [Uri], or `null`.
+ StringScanner(this.string, {sourceUrl, int position})
+ : sourceUrl = sourceUrl is String ? Uri.parse(sourceUrl) : sourceUrl {
+ if (position != null) this.position = position;
+ }
+
+ /// Consumes a single character and returns its character code.
+ ///
+ /// This throws a [FormatException] if the string has been fully consumed. It
+ /// doesn't affect [lastMatch].
+ int readChar() {
+ if (isDone) _fail("more input");
+ return string.codeUnitAt(_position++);
+ }
+
+ /// Returns the character code of the character [offset] away from [position].
+ ///
+ /// [offset] defaults to zero, and may be negative to inspect already-consumed
+ /// characters.
+ ///
+ /// This returns `null` if [offset] points outside the string. It doesn't
+ /// affect [lastMatch].
+ int peekChar([int offset]) {
+ if (offset == null) offset = 0;
+ var index = position + offset;
+ if (index < 0 || index >= string.length) return null;
+ return string.codeUnitAt(index);
+ }
+
+ /// If [pattern] matches at the current position of the string, scans forward
+ /// until the end of the match.
+ ///
+ /// Returns whether or not [pattern] matched.
+ bool scan(Pattern pattern) {
+ var success = matches(pattern);
+ if (success) _position = _lastMatch.end;
+ return success;
+ }
+
+ /// If [pattern] matches at the current position of the string, scans forward
+ /// until the end of the match.
+ ///
+ /// If [pattern] did not match, throws a [FormatException] describing the
+ /// position of the failure. [name] is used in this error as the expected name
+ /// of the pattern being matched; if it's `null`, the pattern itself is used
+ /// instead.
+ void expect(Pattern pattern, {String name}) {
+ if (scan(pattern)) return;
+
+ if (name == null) {
+ if (pattern is RegExp) {
+ var source = pattern.pattern;
+ if (!_slashAutoEscape) source = source.replaceAll("/", "\\/");
+ name = "/$source/";
+ } else {
+ name =
+ pattern.toString().replaceAll("\\", "\\\\").replaceAll('"', '\\"');
+ name = '"$name"';
+ }
+ }
+ _fail(name);
+ }
+
+ /// If the string has not been fully consumed, this throws a
+ /// [FormatException].
+ void expectDone() {
+ if (isDone) return;
+ _fail("no more input");
+ }
+
+ /// Returns whether or not [pattern] matches at the current position of the
+ /// string.
+ ///
+ /// This doesn't move the scan pointer forward.
+ bool matches(Pattern pattern) {
+ _lastMatch = pattern.matchAsPrefix(string, position);
+ return _lastMatch != null;
+ }
+
+ /// Returns the substring of [string] between [start] and [end].
+ ///
+ /// Unlike [String.substring], [end] defaults to [position] rather than the
+ /// end of the string.
+ String substring(int start, [int end]) {
+ if (end == null) end = position;
+ return string.substring(start, end);
+ }
+
+ /// Throws a [FormatException] with [message] as well as a detailed
+ /// description of the location of the error in the string.
+ ///
+ /// [match] is the match information for the span of the string with which the
+ /// error is associated. This should be a match returned by this scanner's
+ /// [lastMatch] property. By default, the error is associated with the last
+ /// match.
+ ///
+ /// 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.
+ ///
+ /// 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}) {
+ validateErrorArgs(string, match, position, length);
+
+ if (match == null && position == null && length == null) match = lastMatch;
+ if (position == null) {
+ position = match == null ? this.position : match.start;
+ }
+ if (length == null) length = match == null ? 1 : match.end - match.start;
+
+ var sourceFile = new SourceFile(string, url: sourceUrl);
+ var span = sourceFile.span(position, position + length);
+ throw new StringScannerException(message, span, string);
+ }
+
+ // TODO(nweiz): Make this handle long lines more gracefully.
+ /// Throws a [FormatException] describing that [name] is expected at the
+ /// current position in the string.
+ void _fail(String name) {
+ error("expected $name.", position: this.position, length: 0);
+ }
+}
diff --git a/mojo/public/dart/third_party/string_scanner/lib/src/utils.dart b/mojo/public/dart/third_party/string_scanner/lib/src/utils.dart
new file mode 100644
index 0000000..107c4c5
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/src/utils.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.utils;
+
+/// Validates the arguments passed to [StringScanner.error].
+void validateErrorArgs(String string, Match match, int position, int length) {
+ if (match != null && (position != null || length != null)) {
+ throw new ArgumentError("Can't pass both match and position/length.");
+ }
+
+ if (position != null) {
+ if (position < 0) {
+ throw new RangeError("position must be greater than or equal to 0.");
+ } else if (position > string.length) {
+ throw new RangeError("position must be less than or equal to the "
+ "string length.");
+ }
+ }
+
+ if (length != null && length < 0) {
+ throw new RangeError("length must be greater than or equal to 0.");
+ }
+
+ if (position != null && length != null && position + length > string.length) {
+ throw new RangeError("position plus length must not go beyond the end of "
+ "the string.");
+ }
+}
diff --git a/mojo/public/dart/third_party/string_scanner/lib/string_scanner.dart b/mojo/public/dart/third_party/string_scanner/lib/string_scanner.dart
new file mode 100644
index 0000000..be294f4
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/lib/string_scanner.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+/// A library for parsing strings using a sequence of patterns.
+library string_scanner;
+
+export 'src/exception.dart';
+export 'src/line_scanner.dart';
+export 'src/span_scanner.dart';
+export 'src/string_scanner.dart';
diff --git a/mojo/public/dart/third_party/string_scanner/pubspec.yaml b/mojo/public/dart/third_party/string_scanner/pubspec.yaml
new file mode 100644
index 0000000..35b3fe0
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/pubspec.yaml
@@ -0,0 +1,14 @@
+name: string_scanner
+version: 0.1.4
+author: "Dart Team <misc@dartlang.org>"
+homepage: https://github.com/dart-lang/string_scanner
+description: >
+ A class for parsing strings using a sequence of patterns.
+dependencies:
+ charcode: "^1.1.0"
+ path: "^1.2.0"
+ source_span: "^1.0.0"
+dev_dependencies:
+ test: ">=0.12.0 <0.13.0"
+environment:
+ sdk: ">=1.8.0 <2.0.0"
diff --git a/mojo/public/dart/third_party/string_scanner/test/error_test.dart b/mojo/public/dart/third_party/string_scanner/test/error_test.dart
new file mode 100644
index 0000000..9c485e5
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/test/error_test.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.error_test;
+
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+void main() {
+ test('defaults to the last match', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ scanner.expect('bar');
+ expect(() => scanner.error('oh no!'), throwsStringScannerException('bar'));
+ });
+
+ group("with match", () {
+ test('supports an earlier match', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ var match = scanner.lastMatch;
+ scanner.expect('bar');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('foo '));
+ });
+
+ test('supports a match on a previous line', () {
+ var scanner =
+ new StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo ');
+ scanner.expect('re');
+ var match = scanner.lastMatch;
+ scanner.expect(' mi\nearth ');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('re'));
+ });
+
+ test('supports a multiline match', () {
+ var scanner =
+ new StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar ');
+ scanner.expect('baz\ndo');
+ var match = scanner.lastMatch;
+ scanner.expect(' re mi');
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('baz\ndo'));
+ });
+
+ test('supports a match after position', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ scanner.expect('bar');
+ var match = scanner.lastMatch;
+ scanner.position = 0;
+ expect(() => scanner.error('oh no!', match: match),
+ throwsStringScannerException('bar'));
+ });
+ });
+
+ group("with position and/or length", () {
+ test('defaults to length 1', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', position: 1),
+ throwsStringScannerException('o'));
+ });
+
+ test('defaults to the current position', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', length: 3),
+ throwsStringScannerException('bar'));
+ });
+
+ test('supports an earlier position', () {
+ var scanner = new StringScanner('foo bar baz');
+ scanner.expect('foo ');
+ expect(() => scanner.error('oh no!', position: 1, length: 2),
+ throwsStringScannerException('oo'));
+ });
+
+ test('supports a position on a previous line', () {
+ var scanner =
+ new StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo re mi\nearth');
+ expect(() => scanner.error('oh no!', position: 15, length: 2),
+ throwsStringScannerException('re'));
+ });
+
+ test('supports a multiline length', () {
+ var scanner =
+ new StringScanner('foo bar baz\ndo re mi\nearth fire water');
+ scanner.expect('foo bar baz\ndo re mi\nearth');
+ expect(() => scanner.error('oh no!', position: 8, length: 8),
+ throwsStringScannerException('baz\ndo r'));
+ });
+
+ test('supports a position after the current one', () {
+ var scanner = new StringScanner('foo bar baz');
+ expect(() => scanner.error('oh no!', position: 4, length: 3),
+ throwsStringScannerException('bar'));
+ });
+
+ test('supports a length of zero', () {
+ var scanner = new StringScanner('foo bar baz');
+ expect(() => scanner.error('oh no!', position: 4, length: 0),
+ throwsStringScannerException(''));
+ });
+ });
+
+ group("argument errors", () {
+ var scanner;
+ setUp(() {
+ scanner = new StringScanner('foo bar baz');
+ scanner.scan('foo');
+ });
+
+ test("if match is passed with position", () {
+ expect(
+ () => scanner.error("oh no!", match: scanner.lastMatch, position: 1),
+ throwsArgumentError);
+ });
+
+ test("if match is passed with length", () {
+ expect(() => scanner.error("oh no!", match: scanner.lastMatch, length: 1),
+ throwsArgumentError);
+ });
+
+ test("if position is negative", () {
+ expect(() => scanner.error("oh no!", position: -1), throwsArgumentError);
+ });
+
+ test("if position is outside the string", () {
+ expect(() => scanner.error("oh no!", position: 100), throwsArgumentError);
+ });
+
+ test("if position + length is outside the string", () {
+ expect(() => scanner.error("oh no!", position: 7, length: 7),
+ throwsArgumentError);
+ });
+
+ test("if length is negative", () {
+ expect(() => scanner.error("oh no!", length: -1), throwsArgumentError);
+ });
+ });
+}
diff --git a/mojo/public/dart/third_party/string_scanner/test/line_scanner_test.dart b/mojo/public/dart/third_party/string_scanner/test/line_scanner_test.dart
new file mode 100644
index 0000000..68e5c38
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/test/line_scanner_test.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.line_scanner_test;
+
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var scanner;
+ setUp(() {
+ scanner = new LineScanner('foo\nbar\r\nbaz');
+ });
+
+ test('begins with line and column 0', () {
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(0));
+ });
+
+ group("scan()", () {
+ test("consuming no newlines increases the column but not the line", () {
+ scanner.scan('foo');
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(3));
+ });
+
+ test("consuming a newline resets the column and increases the line", () {
+ scanner.expect('foo\nba');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(2));
+ });
+
+ test("consuming multiple newlines resets the column and increases the line",
+ () {
+ scanner.expect('foo\nbar\r\nb');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test("consuming halfway through a CR LF doesn't count as a line", () {
+ scanner.expect('foo\nbar\r');
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.expect('\nb');
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+ });
+
+ group("readChar()", () {
+ test("on a non-newline character increases the column but not the line",
+ () {
+ scanner.readChar();
+ 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.readChar();
+ 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.readChar();
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+
+ scanner.readChar();
+ 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"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test("forward through no newlines sets the column", () {
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test("backward through newlines sets the line and column", () {
+ scanner.scan("foo\nbar\r\nbaz");
+ scanner.position = 2; // "fo"
+ expect(scanner.line, equals(0));
+ expect(scanner.column, equals(2));
+ });
+
+ test("backward through no newlines sets the column", () {
+ scanner.scan("foo\nbar\r\nbaz");
+ scanner.position = 10; // "foo\nbar\r\nb"
+ expect(scanner.line, equals(2));
+ expect(scanner.column, equals(1));
+ });
+
+ test("forward halfway through a CR LF doesn't count as a line", () {
+ scanner.position = 8; // "foo\nbar\r"
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(4));
+ });
+ });
+
+ test("state= restores the line, column, and position", () {
+ scanner.scan('foo\nb');
+ var state = scanner.state;
+
+ scanner.scan('ar\nba');
+ scanner.state = state;
+ expect(scanner.rest, equals('ar\r\nbaz'));
+ expect(scanner.line, equals(1));
+ expect(scanner.column, equals(1));
+ });
+
+ test("state= rejects a foreign state", () {
+ scanner.scan('foo\nb');
+
+ expect(() => new LineScanner(scanner.string).state = scanner.state,
+ throwsArgumentError);
+ });
+}
diff --git a/mojo/public/dart/third_party/string_scanner/test/span_scanner_test.dart b/mojo/public/dart/third_party/string_scanner/test/span_scanner_test.dart
new file mode 100644
index 0000000..114bff7
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/test/span_scanner_test.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.span_scanner_test;
+
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+void main() {
+ testForImplementation("lazy", () {
+ return new SpanScanner('foo\nbar\nbaz', sourceUrl: 'source');
+ });
+
+ testForImplementation("eager", () {
+ return new SpanScanner.eager('foo\nbar\nbaz', sourceUrl: 'source');
+ });
+}
+
+void testForImplementation(String name, SpanScanner create()) {
+ group("for a $name scanner", () {
+ var scanner;
+ setUp(() => scanner = create());
+
+ test("tracks the span for the last match", () {
+ scanner.scan('fo');
+ scanner.scan('o\nba');
+
+ var span = scanner.lastSpan;
+ expect(span.start.offset, equals(2));
+ expect(span.start.line, equals(0));
+ expect(span.start.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(6));
+ expect(span.end.line, equals(1));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals('o\nba'));
+ });
+
+ test(".spanFrom() returns a span from a previous state", () {
+ scanner.scan('fo');
+ var state = scanner.state;
+ scanner.scan('o\nba');
+ scanner.scan('r\nba');
+
+ var span = scanner.spanFrom(state);
+ expect(span.text, equals('o\nbar\nba'));
+ });
+
+ test(".emptySpan returns an empty span at the current location", () {
+ scanner.scan('foo\nba');
+
+ var span = scanner.emptySpan;
+ expect(span.start.offset, equals(6));
+ expect(span.start.line, equals(1));
+ expect(span.start.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.end.offset, equals(6));
+ expect(span.end.line, equals(1));
+ expect(span.end.column, equals(2));
+ expect(span.start.sourceUrl, equals(Uri.parse('source')));
+
+ expect(span.text, equals(''));
+ });
+ });
+}
diff --git a/mojo/public/dart/third_party/string_scanner/test/string_scanner_test.dart b/mojo/public/dart/third_party/string_scanner/test/string_scanner_test.dart
new file mode 100644
index 0000000..c8e43a5
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/test/string_scanner_test.dart
@@ -0,0 +1,337 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.string_scanner_test;
+
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('with an empty string', () {
+ var scanner;
+ setUp(() {
+ scanner = new StringScanner('');
+ });
+
+ test('is done', () {
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+
+ test('rest is empty', () {
+ expect(scanner.rest, isEmpty);
+ });
+
+ test('lastMatch is null', () {
+ expect(scanner.lastMatch, isNull);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(0));
+ });
+
+ test("readChar fails and doesn't change the state", () {
+ expect(scanner.readChar, throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("peekChar returns null and doesn't change the state", () {
+ expect(scanner.peekChar(), isNull);
+ 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);
+ expect(scanner.position, equals(0));
+ });
+
+ test("expect throws a FormatException and doesn't change the state", () {
+ expect(() => scanner.expect(new RegExp('.')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("matches returns false and doesn't change the state", () {
+ expect(scanner.matches(new RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test("substring returns the empty string", () {
+ expect(scanner.substring(0), isEmpty);
+ });
+
+ test('setting position to 1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 1;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+ });
+
+ group('at the beginning of a string', () {
+ var scanner;
+ setUp(() {
+ scanner = new StringScanner('foo bar');
+ });
+
+ test('is not done', () {
+ expect(scanner.isDone, isFalse);
+ expect(scanner.expectDone, throwsFormatException);
+ });
+
+ test('rest is the whole string', () {
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test('lastMatch is null', () {
+ expect(scanner.lastMatch, isNull);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(0));
+ });
+
+ test('readChar returns the first character and moves forward', () {
+ expect(scanner.readChar(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(1));
+ });
+
+ test('peekChar returns the first character', () {
+ expect(scanner.peekChar(), equals(0x66));
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ });
+
+ test('peekChar with an argument returns the nth character', () {
+ expect(scanner.peekChar(4), equals(0x62));
+ 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'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test("a non-matching scan returns false and sets lastMatch to null", () {
+ expect(scanner.matches(new RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch, isNotNull);
+
+ expect(scanner.scan(new RegExp('b(..)')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test("a matching expect changes the state", () {
+ scanner.expect(new RegExp('f(..)'));
+ expect(scanner.lastMatch[1], equals('oo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test("a non-matching expect throws a FormatException and sets lastMatch to "
+ "null", () {
+ expect(scanner.matches(new RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch, isNotNull);
+
+ expect(() => scanner.expect(new RegExp('b(..)')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test("a matching matches returns true and only changes lastMatch", () {
+ expect(scanner.matches(new RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch[1], equals('oo'));
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test("a non-matching matches returns false and doesn't change the state",
+ () {
+ expect(scanner.matches(new RegExp('b(..)')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(0));
+ expect(scanner.rest, equals('foo bar'));
+ });
+
+ test("substring from the beginning returns the empty string", () {
+ expect(scanner.substring(0), isEmpty);
+ });
+
+ test("substring with a custom end returns the substring", () {
+ expect(scanner.substring(0, 3), equals('foo'));
+ });
+
+ test("substring with the string length returns the whole string", () {
+ expect(scanner.substring(0, 7), equals('foo bar'));
+ });
+
+ test('setting position to 1 moves the cursor forward', () {
+ scanner.position = 1;
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(new RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch[0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('setting position beyond the string throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 8;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+
+ test('scan accepts any Pattern', () {
+ expect(scanner.scan('foo'), isTrue);
+ expect(scanner.lastMatch[0], equals('foo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+ });
+
+ test('scans multiple times', () {
+ expect(scanner.scan(new RegExp('f(..)')), isTrue);
+ expect(scanner.lastMatch[1], equals('oo'));
+ expect(scanner.position, equals(3));
+ expect(scanner.rest, equals(' bar'));
+
+ expect(scanner.scan(new RegExp(' b(..)')), isTrue);
+ expect(scanner.lastMatch[1], equals('ar'));
+ expect(scanner.position, equals(7));
+ expect(scanner.rest, equals(''));
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+ });
+
+ group('at the end of a string', () {
+ var scanner;
+ setUp(() {
+ scanner = new StringScanner('foo bar');
+ expect(scanner.scan('foo bar'), isTrue);
+ });
+
+ test('is done', () {
+ expect(scanner.isDone, isTrue);
+ expect(scanner.expectDone, isNot(throwsFormatException));
+ });
+
+ test('rest is empty', () {
+ expect(scanner.rest, isEmpty);
+ });
+
+ test('position is zero', () {
+ expect(scanner.position, equals(7));
+ });
+
+ test("readChar fails and doesn't change the state", () {
+ expect(scanner.readChar, throwsFormatException);
+ expect(scanner.lastMatch, isNotNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("peekChar returns null and doesn't change the state", () {
+ expect(scanner.peekChar(), isNull);
+ 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);
+ expect(scanner.position, equals(7));
+ });
+
+ test("expect throws a FormatException and sets lastMatch to null", () {
+ expect(() => scanner.expect(new RegExp('.')), throwsFormatException);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("matches returns false sets lastMatch to null", () {
+ expect(scanner.matches(new RegExp('.')), isFalse);
+ expect(scanner.lastMatch, isNull);
+ expect(scanner.position, equals(7));
+ });
+
+ test("substring from the beginning returns the whole string", () {
+ expect(scanner.substring(0), equals('foo bar'));
+ });
+
+ test("substring with a custom start returns a substring from there", () {
+ expect(scanner.substring(4), equals('bar'));
+ });
+
+ test("substring with a custom start and end returns that substring", () {
+ expect(scanner.substring(3, 5), equals(' b'));
+ });
+
+ test('setting position to 1 moves the cursor backward', () {
+ scanner.position = 1;
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(new RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch[0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('setting position beyond the string throws an ArgumentError', () {
+ expect(() {
+ scanner.position = 8;
+ }, throwsArgumentError);
+ });
+
+ test('setting position to -1 throws an ArgumentError', () {
+ expect(() {
+ scanner.position = -1;
+ }, throwsArgumentError);
+ });
+ });
+
+ group('a scanner constructed with a custom position', () {
+ test('starts scanning from that position', () {
+ var scanner = new StringScanner('foo bar', position: 1);
+ expect(scanner.position, equals(1));
+ expect(scanner.rest, equals('oo bar'));
+
+ expect(scanner.scan(new RegExp('oo.')), isTrue);
+ expect(scanner.lastMatch[0], equals('oo '));
+ expect(scanner.position, equals(4));
+ expect(scanner.rest, equals('bar'));
+ });
+
+ test('throws an ArgumentError if the position is -1', () {
+ expect(() => new StringScanner('foo bar', position: -1),
+ throwsArgumentError);
+ });
+
+ test('throws an ArgumentError if the position is beyond the string', () {
+ expect(
+ () => new StringScanner('foo bar', position: 8), throwsArgumentError);
+ });
+ });
+}
diff --git a/mojo/public/dart/third_party/string_scanner/test/utils.dart b/mojo/public/dart/third_party/string_scanner/test/utils.dart
new file mode 100644
index 0000000..8df4b51
--- /dev/null
+++ b/mojo/public/dart/third_party/string_scanner/test/utils.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+library string_scanner.test.utils;
+
+import 'package:string_scanner/string_scanner.dart';
+import 'package:test/test.dart';
+
+/// Returns a matcher that asserts that a closure throws a [FormatException]
+/// with the given [message].
+Matcher throwsStringScannerException(String text) {
+ return throwsA(predicate((error) {
+ expect(error, new isInstanceOf<StringScannerException>());
+ expect(error.span.text, equals(text));
+ return true;
+ }));
+}