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 |