| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// A library for parsing strings using a sequence of patterns. | 5 /// A library for parsing strings using a sequence of patterns. |
| 6 library string_scanner; | 6 library string_scanner; |
| 7 | 7 |
| 8 import 'dart:math' as math; |
| 9 |
| 8 // TODO(nweiz): Add some integration between this and source maps. | 10 // TODO(nweiz): Add some integration between this and source maps. |
| 9 /// A class that scans through a string using [Pattern]s. | 11 /// A class that scans through a string using [Pattern]s. |
| 10 class StringScanner { | 12 class StringScanner { |
| 11 /// The string being scanned through. | 13 /// The string being scanned through. |
| 12 final String string; | 14 final String string; |
| 13 | 15 |
| 14 /// The current position of the scanner in the string, in characters. | 16 /// The current position of the scanner in the string, in characters. |
| 15 int get position => _position; | 17 int get position => _position; |
| 16 set position(int position) { | 18 set position(int position) { |
| 17 if (position < 0 || position > string.length) { | 19 if (position < 0 || position > string.length) { |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 | 84 |
| 83 /// Returns whether or not [pattern] matches at the current position of the | 85 /// Returns whether or not [pattern] matches at the current position of the |
| 84 /// string. | 86 /// string. |
| 85 /// | 87 /// |
| 86 /// This doesn't move the scan pointer forward. | 88 /// This doesn't move the scan pointer forward. |
| 87 bool matches(Pattern pattern) { | 89 bool matches(Pattern pattern) { |
| 88 _lastMatch = pattern.matchAsPrefix(string, position); | 90 _lastMatch = pattern.matchAsPrefix(string, position); |
| 89 return _lastMatch != null; | 91 return _lastMatch != null; |
| 90 } | 92 } |
| 91 | 93 |
| 92 // TODO(nweiz): Make this handle long lines more gracefully. | 94 /// Throws a [FormatException] with [message] as well as a detailed |
| 93 /// Throws a [FormatException] describing that [name] is expected at the | 95 /// description of the location of the error in the string. |
| 94 /// current position in the string. | 96 /// |
| 95 void _fail(String name) { | 97 /// [match] is the match information for the span of the string with which the |
| 98 /// error is associated. This should be a match returned by this scanner's |
| 99 /// [lastMatch] property. By default, the error is associated with the last |
| 100 /// match. |
| 101 /// |
| 102 /// If [position] and/or [length] are passed, they are used as the error span |
| 103 /// instead. If only [length] is passed, [position] defaults to the current |
| 104 /// position; if only [position] is passed, [length] defaults to 1. |
| 105 /// |
| 106 /// It's an error to pass [match] at the same time as [position] or [length]. |
| 107 void error(String message, {Match match, int position, int length}) { |
| 108 if (match != null && (position != null || length != null)) { |
| 109 throw new ArgumentError("Can't pass both match and position/length."); |
| 110 } |
| 111 |
| 112 if (position != null && position < 0) { |
| 113 throw new RangeError("position must be greater than or equal to 0."); |
| 114 } |
| 115 |
| 116 if (length != null && length < 1) { |
| 117 throw new RangeError("length must be greater than or equal to 0."); |
| 118 } |
| 119 |
| 120 if (match == null && position == null && length == null) match = lastMatch; |
| 121 if (position == null) { |
| 122 position = match == null ? this.position : match.start; |
| 123 } |
| 124 if (length == null) length = match == null ? 1 : match.end - match.start; |
| 125 |
| 96 var newlines = "\n".allMatches(string.substring(0, position)).toList(); | 126 var newlines = "\n".allMatches(string.substring(0, position)).toList(); |
| 97 var line = newlines.length + 1; | 127 var line = newlines.length + 1; |
| 98 var column; | 128 var column; |
| 99 var lastLine; | 129 var lastLine; |
| 100 if (newlines.isEmpty) { | 130 if (newlines.isEmpty) { |
| 101 column = position + 1; | 131 column = position + 1; |
| 102 lastLine = string.substring(0, position); | 132 lastLine = string.substring(0, position); |
| 103 } else { | 133 } else { |
| 104 column = position - newlines.last.end + 1; | 134 column = position - newlines.last.end + 1; |
| 105 lastLine = string.substring(newlines.last.end, position); | 135 lastLine = string.substring(newlines.last.end, position); |
| 106 } | 136 } |
| 107 lastLine += rest.replaceFirst(new RegExp(r"\n.*"), ''); | 137 |
| 138 var remaining = string.substring(position); |
| 139 var nextNewline = remaining.indexOf("\n"); |
| 140 if (nextNewline == -1) { |
| 141 lastLine += remaining; |
| 142 } else { |
| 143 length = math.min(length, nextNewline); |
| 144 lastLine += remaining.substring(0, nextNewline); |
| 145 } |
| 146 |
| 147 var spaces = new List.filled(column - 1, ' ').join(); |
| 148 var underline = new List.filled(length, '^').join(); |
| 149 |
| 108 throw new FormatException( | 150 throw new FormatException( |
| 109 "Expected $name on line $line, column $column.\n" | 151 "Error on line $line, column $column: $message\n" |
| 110 "$lastLine\n" | 152 "$lastLine\n" |
| 111 "${new List.filled(column - 1, ' ').join()}^"); | 153 "$spaces$underline"); |
| 154 } |
| 155 |
| 156 // TODO(nweiz): Make this handle long lines more gracefully. |
| 157 /// Throws a [FormatException] describing that [name] is expected at the |
| 158 /// current position in the string. |
| 159 void _fail(String name) { |
| 160 error("expected $name.", position: this.position, length: 1); |
| 112 } | 161 } |
| 113 } | 162 } |
| OLD | NEW |