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 = []; |
+} |