| Index: runtime/observatory/lib/src/service/scanner.dart
|
| diff --git a/runtime/observatory/lib/src/service/scanner.dart b/runtime/observatory/lib/src/service/scanner.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..aff1008ac77ea7be7a015637175b24142a1b1ba2
|
| --- /dev/null
|
| +++ b/runtime/observatory/lib/src/service/scanner.dart
|
| @@ -0,0 +1,236 @@
|
| +// 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.
|
| +
|
| +part of service;
|
| +
|
| +/// A text scanner that keeps track of token position, line, and column.
|
| +class Scanner {
|
| + final String string;
|
| +
|
| + /// The current position of the scanner in the string, in characters.
|
| + int get tokenPos => _tokenPos;
|
| + set tokenPos(int newTokenPos) {
|
| + if (newTokenPos < 0 || newTokenPos > string.length) {
|
| + throw new ArgumentError("Invalid tokenPos $tokenPos");
|
| + }
|
| +
|
| + var oldTokenPos = _tokenPos;
|
| + _tokenPos = newTokenPos;
|
| +
|
| + if (newTokenPos > oldTokenPos) {
|
| + var newlines =
|
| + "\n".allMatches(string.substring(oldTokenPos, newTokenPos)).toList();
|
| + _line += newlines.length;
|
| + if (newlines.isEmpty) {
|
| + _column += newTokenPos - oldTokenPos;
|
| + } else {
|
| + _column = newTokenPos - newlines.last.end;
|
| + }
|
| + } else {
|
| + var newlines =
|
| + "\n".allMatches(string.substring(newTokenPos, oldTokenPos)).toList();
|
| + _line -= newlines.length;
|
| + if (newlines.isEmpty) {
|
| + _column -= oldTokenPos - newTokenPos;
|
| + } else {
|
| + _column = newTokenPos - string.lastIndexOf("\n", newTokenPos) - 1;
|
| + }
|
| + }
|
| + }
|
| + int _tokenPos = 0;
|
| +
|
| + /// 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 [ScannerState] is only valid for the
|
| + /// [Scanner] that created it.
|
| + ScannerState get state => new ScannerState._(this, tokenPos, line, column);
|
| + set state(ScannerState state) {
|
| + if (!identical(state._scanner, this)) {
|
| + throw new ArgumentError("The given LineScannerState was not returned by "
|
| + "this LineScanner.");
|
| + }
|
| +
|
| + _tokenPos = state.tokenPos;
|
| + _line = state.line;
|
| + _column = state.column;
|
| + }
|
| +
|
| + /// 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;
|
| +
|
| + /// Whether the scanner has completely consumed [string].
|
| + bool get isDone => tokenPos == string.length;
|
| +
|
| + Scanner(this.string, {int tokenPos: 0, int line: 0, int column: 0}) {
|
| + this._tokenPos = tokenPos;
|
| + this._line = line;
|
| + this._column = column;
|
| + }
|
| +
|
| + /// 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) {
|
| + throw new FormatException('no more input');
|
| + }
|
| + var char = string.codeUnitAt(_tokenPos++);
|
| + if (char == 0xA) {
|
| + _line += 1;
|
| + _column = 0;
|
| + } else {
|
| + _column += 1;
|
| + }
|
| + return char;
|
| + }
|
| +
|
| + /// Returns the character code of the character [offset] away from [tokenPos].
|
| + ///
|
| + /// [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 = 0]) {
|
| + var index = tokenPos + offset;
|
| + if (index < 0 || index >= string.length) {
|
| + return null;
|
| + }
|
| + return string.codeUnitAt(index);
|
| + }
|
| +
|
| + /// If [pattern] matches at the current tokenPos 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) {
|
| + return success;
|
| + }
|
| + _tokenPos = _lastMatch.end;
|
| + var newlines = "\n".allMatches(lastMatch[0]).toList();
|
| + _line += newlines.length;
|
| + if (newlines.isEmpty) {
|
| + _column += lastMatch[0].length;
|
| + } else {
|
| + _column = lastMatch[0].length - newlines.last.end;
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /// If [pattern] matches at the current tokenPos of the string, scans forward
|
| + /// until the end of the match.
|
| + ///
|
| + /// If [pattern] did not match, throws a [FormatException] describing the
|
| + /// tokenPos of the failure.
|
| + void expect(Pattern pattern, {String name}) {
|
| + if (scan(pattern)) {
|
| + return;
|
| + }
|
| + throw new FormatException('expect($pattern) failed.');
|
| + }
|
| +
|
| + /// If the string has not been fully consumed, this throws a
|
| + /// [FormatException].
|
| + void expectDone() {
|
| + if (isDone) {
|
| + return;
|
| + }
|
| + throw new FormatException('not done');
|
| + }
|
| +
|
| + /// Returns whether or not [pattern] matches at the current tokenPos of the
|
| + /// string.
|
| + ///
|
| + /// This doesn't move the scan pointer forward.
|
| + bool matches(Pattern pattern) {
|
| + _lastMatch = pattern.matchAsPrefix(string, tokenPos);
|
| + return _lastMatch != null;
|
| + }
|
| +
|
| + /// Returns the substring of [string] between [start] and [end].
|
| + ///
|
| + /// Unlike [String.substring], [end] defaults to [tokenPos] rather than the
|
| + /// end of the string.
|
| + String substring(int start, [int end]) {
|
| + if (end == null) end = tokenPos;
|
| + return string.substring(start, end);
|
| + }
|
| +}
|
| +
|
| +/// A class representing the state of a [LineScanner].
|
| +class ScannerState {
|
| + /// The [LineScanner] that created this.
|
| + final Scanner _scanner;
|
| +
|
| + /// The position of the scanner in this state.
|
| + final int tokenPos;
|
| +
|
| + /// 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;
|
| +
|
| + ScannerState._(this._scanner, this.tokenPos, this.line, this.column);
|
| +}
|
| +
|
| +class ScannedRegion {
|
| + final ScannerState start;
|
| + final ScannerState end;
|
| + ScannedRegion(this.start, this.end);
|
| +
|
| + bool contained(int line, int column) {
|
| + // TODO(johnmccutchan): Fix.
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +/// Computes a list of source regions that are Dart source code comments.
|
| +/// Handles comments that span multiple lines.
|
| +class CommentScanner {
|
| + Scanner _scanner;
|
| +
|
| + CommentScanner(String source, {int tokenPos: 0, int line: 0, int column: 0}) {
|
| + _scanner =
|
| + new Scanner(source, tokenPos: tokenPos, line: line, column: column);
|
| + }
|
| +
|
| + void scan() {
|
| + }
|
| +
|
| + final List<ScannedRegion> comments = [];
|
| +}
|
| +
|
| +/// Computes a list of source regions that are Dart string literals. Will split
|
| +/// string literal regions that include $expr or ${expr} regions into two string
|
| +/// literals (one before and one after). Handles string literals that span
|
| +/// multiple lines.
|
| +class StringLiteralScanner {
|
| + Scanner _scanner;
|
| + CommentScanner(String source, {int tokenPos: 0, int line: 0, int column: 0}) {
|
| + _scanner =
|
| + new Scanner(source, tokenPos: tokenPos, line: line, column: column);
|
| + }
|
| +
|
| + void scan() {
|
| + }
|
| +
|
| + final List<ScannedRegion> stringLiterals = [];
|
| +}
|
|
|