| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 part of dart._js_helper; | 5 part of dart._js_helper; |
| 6 | 6 |
| 7 int stringIndexOfStringUnchecked(receiver, other, startIndex) { |
| 8 return JS('int', '#.indexOf(#, #)', receiver, other, startIndex); |
| 9 } |
| 10 |
| 11 String substring1Unchecked(receiver, startIndex) { |
| 12 return JS('String', '#.substring(#)', receiver, startIndex); |
| 13 } |
| 14 |
| 15 String substring2Unchecked(receiver, startIndex, endIndex) { |
| 16 return JS('String', '#.substring(#, #)', receiver, startIndex, endIndex); |
| 17 } |
| 18 |
| 19 bool stringContainsStringUnchecked(receiver, other, startIndex) { |
| 20 return stringIndexOfStringUnchecked(receiver, other, startIndex) >= 0; |
| 21 } |
| 22 |
| 7 class StringMatch implements Match { | 23 class StringMatch implements Match { |
| 8 const StringMatch(int this.start, | 24 const StringMatch(int this.start, |
| 9 String this.input, | 25 String this.input, |
| 10 String this.pattern); | 26 String this.pattern); |
| 11 | 27 |
| 12 int get end => start + pattern.length; | 28 int get end => start + pattern.length; |
| 13 String operator[](int g) => group(g); | 29 String operator[](int g) => group(g); |
| 14 int get groupCount => 0; | 30 int get groupCount => 0; |
| 15 | 31 |
| 16 String group(int group_) { | 32 String group(int group_) { |
| 17 if (group_ != 0) { | 33 if (group_ != 0) { |
| 18 throw new RangeError.value(group_); | 34 throw new RangeError.value(group_); |
| 19 } | 35 } |
| 20 return pattern; | 36 return pattern; |
| 21 } | 37 } |
| 22 | 38 |
| 23 List<String> groups(List<int> groups_) { | 39 List<String> groups(List<int> groups_) { |
| 24 List<String> result = new List<String>(); | 40 List<String> result = new List<String>(); |
| 25 for (int g in groups_) { | 41 for (int g in groups_) { |
| 26 result.add(group(g)); | 42 result.add(group(g)); |
| 27 } | 43 } |
| 28 return result; | 44 return result; |
| 29 } | 45 } |
| 30 | 46 |
| 31 final int start; | 47 final int start; |
| 32 final String input; | 48 final String input; |
| 33 final String pattern; | 49 final String pattern; |
| 34 } | 50 } |
| 35 | 51 |
| 36 List<Match> allMatchesInStringUnchecked(String needle, String haystack, | 52 Iterable<Match> allMatchesInStringUnchecked(String pattern, String string, |
| 37 int startIndex) { | 53 int startIndex) { |
| 38 // Copied from StringBase.allMatches in | 54 return new _StringAllMatchesIterable(string, pattern, startIndex); |
| 39 // /runtime/lib/string_base.dart | |
| 40 List<Match> result = new List<Match>(); | |
| 41 int length = haystack.length; | |
| 42 int patternLength = needle.length; | |
| 43 while (true) { | |
| 44 int position = haystack.indexOf(needle, startIndex); | |
| 45 if (position == -1) { | |
| 46 break; | |
| 47 } | |
| 48 result.add(new StringMatch(position, haystack, needle)); | |
| 49 int endIndex = position + patternLength; | |
| 50 if (endIndex == length) { | |
| 51 break; | |
| 52 } else if (position == endIndex) { | |
| 53 ++startIndex; // empty match, advance and restart | |
| 54 } else { | |
| 55 startIndex = endIndex; | |
| 56 } | |
| 57 } | |
| 58 return result; | |
| 59 } | 55 } |
| 60 | 56 |
| 61 stringContainsUnchecked(receiver, other, startIndex) { | 57 class _StringAllMatchesIterable extends IterableBase<Match> { |
| 58 final String _input; |
| 59 final String _pattern; |
| 60 final int _index; |
| 61 |
| 62 _StringAllMatchesIterable(this._input, this._pattern, this._index); |
| 63 |
| 64 Iterator<Match> get iterator => |
| 65 new _StringAllMatchesIterator(_input, _pattern, _index); |
| 66 |
| 67 Match get first { |
| 68 int index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
| 69 if (index >= 0) { |
| 70 return new StringMatch(index, _input, _pattern); |
| 71 } |
| 72 throw IterableElementError.noElement(); |
| 73 } |
| 74 } |
| 75 |
| 76 class _StringAllMatchesIterator implements Iterator<Match> { |
| 77 final String _input; |
| 78 final String _pattern; |
| 79 int _index; |
| 80 Match _current; |
| 81 |
| 82 _StringAllMatchesIterator(this._input, this._pattern, this._index); |
| 83 |
| 84 bool moveNext() { |
| 85 if (_index + _pattern.length > _input.length) { |
| 86 _current = null; |
| 87 return false; |
| 88 } |
| 89 var index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
| 90 if (index < 0) { |
| 91 _index = _input.length + 1; |
| 92 _current = null; |
| 93 return false; |
| 94 } |
| 95 int end = index + _pattern.length; |
| 96 _current = new StringMatch(index, _input, _pattern); |
| 97 // Empty match, don't start at same location again. |
| 98 if (end == _index) end++; |
| 99 _index = end; |
| 100 return true; |
| 101 } |
| 102 |
| 103 Match get current => _current; |
| 104 } |
| 105 |
| 106 bool stringContainsUnchecked(String receiver, other, int startIndex) { |
| 62 if (other is String) { | 107 if (other is String) { |
| 63 return receiver.indexOf(other, startIndex) != -1; | 108 return stringContainsStringUnchecked(receiver, other, startIndex); |
| 64 } else if (other is JSSyntaxRegExp) { | 109 } else if (other is JSSyntaxRegExp) { |
| 65 return other.hasMatch(receiver.substring(startIndex)); | 110 return other.hasMatch(receiver.substring(startIndex)); |
| 66 } else { | 111 } else { |
| 67 var substr = receiver.substring(startIndex); | 112 var substr = receiver.substring(startIndex); |
| 68 return other.allMatches(substr).isNotEmpty; | 113 return other.allMatches(substr).isNotEmpty; |
| 69 } | 114 } |
| 70 } | 115 } |
| 71 | 116 |
| 72 stringReplaceJS(receiver, replacer, to) { | 117 String stringReplaceJS(receiver, replacer, replacement) { |
| 73 // The JavaScript String.replace method recognizes replacement | 118 // The JavaScript String.replace method recognizes replacement |
| 74 // patterns in the replacement string. Dart does not have that | 119 // patterns in the replacement string. Dart does not have that |
| 75 // behavior. | 120 // behavior. |
| 76 to = JS('String', r'#.replace(/\$/g, "$$$$")', to); | 121 replacement = JS('String', r'#.replace(/\$/g, "$$$$")', replacement); |
| 77 return JS('String', r'#.replace(#, #)', receiver, replacer, to); | 122 return JS('String', r'#.replace(#, #)', receiver, replacer, replacement); |
| 78 } | 123 } |
| 79 | 124 |
| 80 stringReplaceFirstRE(receiver, regexp, to, startIndex) { | 125 String stringReplaceFirstRE(String receiver, |
| 126 JSSyntaxRegExp regexp, String replacement, int startIndex) { |
| 81 var match = regexp._execGlobal(receiver, startIndex); | 127 var match = regexp._execGlobal(receiver, startIndex); |
| 82 if (match == null) return receiver; | 128 if (match == null) return receiver; |
| 83 var start = match.start; | 129 var start = match.start; |
| 84 var end = match.end; | 130 var end = match.end; |
| 85 return "${receiver.substring(0,start)}$to${receiver.substring(end)}"; | 131 return stringReplaceRangeUnchecked(receiver, start, end, replacement); |
| 86 } | 132 } |
| 87 | 133 |
| 88 const String ESCAPE_REGEXP = r'[[\]{}()*+?.\\^$|]'; | |
| 89 | 134 |
| 90 stringReplaceAllUnchecked(receiver, from, to) { | 135 /// Returns a string for a RegExp pattern that matches [string]. This is done by |
| 91 checkString(to); | 136 /// escaping all RegExp metacharacters. |
| 92 if (from is String) { | 137 String quoteStringForRegExp(string) { |
| 93 if (from == "") { | 138 return JS('String', r'#.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&")', string); |
| 139 } |
| 140 |
| 141 String stringReplaceAllUnchecked( |
| 142 String receiver, Pattern pattern, String replacement) { |
| 143 checkString(replacement); |
| 144 if (pattern is String) { |
| 145 if (pattern == "") { |
| 94 if (receiver == "") { | 146 if (receiver == "") { |
| 95 return to; | 147 return replacement; |
| 96 } else { | 148 } else { |
| 97 StringBuffer result = new StringBuffer(); | 149 StringBuffer result = new StringBuffer(); |
| 98 int length = receiver.length; | 150 int length = receiver.length; |
| 99 result.write(to); | 151 result.write(replacement); |
| 100 for (int i = 0; i < length; i++) { | 152 for (int i = 0; i < length; i++) { |
| 101 result.write(receiver[i]); | 153 result.write(receiver[i]); |
| 102 result.write(to); | 154 result.write(replacement); |
| 103 } | 155 } |
| 104 return result.toString(); | 156 return result.toString(); |
| 105 } | 157 } |
| 106 } else { | 158 } else { |
| 107 var quoter = JS('', "new RegExp(#, 'g')", ESCAPE_REGEXP); | 159 var quoted = quoteStringForRegExp(pattern); |
| 108 var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter); | |
| 109 var replacer = JS('', "new RegExp(#, 'g')", quoted); | 160 var replacer = JS('', "new RegExp(#, 'g')", quoted); |
| 110 return stringReplaceJS(receiver, replacer, to); | 161 return stringReplaceJS(receiver, replacer, replacement); |
| 111 } | 162 } |
| 112 } else if (from is JSSyntaxRegExp) { | 163 } else if (pattern is JSSyntaxRegExp) { |
| 113 var re = regExpGetGlobalNative(from); | 164 var re = regExpGetGlobalNative(pattern); |
| 114 return stringReplaceJS(receiver, re, to); | 165 return stringReplaceJS(receiver, re, replacement); |
| 115 } else { | 166 } else { |
| 116 checkNull(from); | 167 checkNull(pattern); |
| 117 // TODO(floitsch): implement generic String.replace (with patterns). | 168 // TODO(floitsch): implement generic String.replace (with patterns). |
| 118 throw "String.replaceAll(Pattern) UNIMPLEMENTED"; | 169 throw "String.replaceAll(Pattern) UNIMPLEMENTED"; |
| 119 } | 170 } |
| 120 } | 171 } |
| 121 | 172 |
| 122 String _matchString(Match match) => match[0]; | 173 String _matchString(Match match) => match[0]; |
| 123 String _stringIdentity(String string) => string; | 174 String _stringIdentity(String string) => string; |
| 124 | 175 |
| 125 stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { | 176 String stringReplaceAllFuncUnchecked( |
| 126 if (pattern is! Pattern) { | 177 String receiver, |
| 127 throw new ArgumentError("${pattern} is not a Pattern"); | 178 Pattern pattern, |
| 128 } | 179 String onMatch(Match match), |
| 180 String onNonMatch(String nonMatch)) { |
| 129 if (onMatch == null) onMatch = _matchString; | 181 if (onMatch == null) onMatch = _matchString; |
| 130 if (onNonMatch == null) onNonMatch = _stringIdentity; | 182 if (onNonMatch == null) onNonMatch = _stringIdentity; |
| 131 if (pattern is String) { | 183 if (pattern is String) { |
| 132 return stringReplaceAllStringFuncUnchecked(receiver, pattern, | 184 return stringReplaceAllStringFuncUnchecked(receiver, pattern, |
| 133 onMatch, onNonMatch); | 185 onMatch, onNonMatch); |
| 134 } | 186 } |
| 187 // Placing the Pattern test here is indistingishable from placing it at the |
| 188 // top of the method but it saves an extra check on the `pattern is String` |
| 189 // path. |
| 190 if (pattern is! Pattern) { |
| 191 throw new ArgumentError.value(pattern, 'pattern', 'is not a Pattern'); |
| 192 } |
| 135 StringBuffer buffer = new StringBuffer(); | 193 StringBuffer buffer = new StringBuffer(); |
| 136 int startIndex = 0; | 194 int startIndex = 0; |
| 137 for (Match match in pattern.allMatches(receiver)) { | 195 for (Match match in pattern.allMatches(receiver)) { |
| 138 buffer.write(onNonMatch(receiver.substring(startIndex, match.start))); | 196 buffer.write(onNonMatch(receiver.substring(startIndex, match.start))); |
| 139 buffer.write(onMatch(match)); | 197 buffer.write(onMatch(match)); |
| 140 startIndex = match.end; | 198 startIndex = match.end; |
| 141 } | 199 } |
| 142 buffer.write(onNonMatch(receiver.substring(startIndex))); | 200 buffer.write(onNonMatch(receiver.substring(startIndex))); |
| 143 return buffer.toString(); | 201 return buffer.toString(); |
| 144 } | 202 } |
| 145 | 203 |
| 146 stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { | 204 String stringReplaceAllEmptyFuncUnchecked(String receiver, |
| 205 String onMatch(Match match), |
| 206 String onNonMatch(String nonMatch)) { |
| 147 // Pattern is the empty string. | 207 // Pattern is the empty string. |
| 148 StringBuffer buffer = new StringBuffer(); | 208 StringBuffer buffer = new StringBuffer(); |
| 149 int length = receiver.length; | 209 int length = receiver.length; |
| 150 int i = 0; | 210 int i = 0; |
| 151 buffer.write(onNonMatch("")); | 211 buffer.write(onNonMatch("")); |
| 152 while (i < length) { | 212 while (i < length) { |
| 153 buffer.write(onMatch(new StringMatch(i, receiver, ""))); | 213 buffer.write(onMatch(new StringMatch(i, receiver, ""))); |
| 154 // Special case to avoid splitting a surrogate pair. | 214 // Special case to avoid splitting a surrogate pair. |
| 155 int code = receiver.codeUnitAt(i); | 215 int code = receiver.codeUnitAt(i); |
| 156 if ((code & ~0x3FF) == 0xD800 && length > i + 1) { | 216 if ((code & ~0x3FF) == 0xD800 && length > i + 1) { |
| 157 // Leading surrogate; | 217 // Leading surrogate; |
| 158 code = receiver.codeUnitAt(i + 1); | 218 code = receiver.codeUnitAt(i + 1); |
| 159 if ((code & ~0x3FF) == 0xDC00) { | 219 if ((code & ~0x3FF) == 0xDC00) { |
| 160 // Matching trailing surrogate. | 220 // Matching trailing surrogate. |
| 161 buffer.write(onNonMatch(receiver.substring(i, i + 2))); | 221 buffer.write(onNonMatch(receiver.substring(i, i + 2))); |
| 162 i += 2; | 222 i += 2; |
| 163 continue; | 223 continue; |
| 164 } | 224 } |
| 165 } | 225 } |
| 166 buffer.write(onNonMatch(receiver[i])); | 226 buffer.write(onNonMatch(receiver[i])); |
| 167 i++; | 227 i++; |
| 168 } | 228 } |
| 169 buffer.write(onMatch(new StringMatch(i, receiver, ""))); | 229 buffer.write(onMatch(new StringMatch(i, receiver, ""))); |
| 170 buffer.write(onNonMatch("")); | 230 buffer.write(onNonMatch("")); |
| 171 return buffer.toString(); | 231 return buffer.toString(); |
| 172 } | 232 } |
| 173 | 233 |
| 174 stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { | 234 String stringReplaceAllStringFuncUnchecked( |
| 235 String receiver, |
| 236 String pattern, |
| 237 String onMatch(Match match), |
| 238 String onNonMatch(String nonMatch)) { |
| 175 int patternLength = pattern.length; | 239 int patternLength = pattern.length; |
| 176 if (patternLength == 0) { | 240 if (patternLength == 0) { |
| 177 return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); | 241 return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); |
| 178 } | 242 } |
| 179 int length = receiver.length; | 243 int length = receiver.length; |
| 180 StringBuffer buffer = new StringBuffer(); | 244 StringBuffer buffer = new StringBuffer(); |
| 181 int startIndex = 0; | 245 int startIndex = 0; |
| 182 while (startIndex < length) { | 246 while (startIndex < length) { |
| 183 int position = receiver.indexOf(pattern, startIndex); | 247 int position = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
| 184 if (position == -1) { | 248 if (position == -1) { |
| 185 break; | 249 break; |
| 186 } | 250 } |
| 187 buffer.write(onNonMatch(receiver.substring(startIndex, position))); | 251 buffer.write(onNonMatch(receiver.substring(startIndex, position))); |
| 188 buffer.write(onMatch(new StringMatch(position, receiver, pattern))); | 252 buffer.write(onMatch(new StringMatch(position, receiver, pattern))); |
| 189 startIndex = position + patternLength; | 253 startIndex = position + patternLength; |
| 190 } | 254 } |
| 191 buffer.write(onNonMatch(receiver.substring(startIndex))); | 255 buffer.write(onNonMatch(receiver.substring(startIndex))); |
| 192 return buffer.toString(); | 256 return buffer.toString(); |
| 193 } | 257 } |
| 194 | 258 |
| 195 | 259 |
| 196 stringReplaceFirstUnchecked(receiver, from, to, [int startIndex = 0]) { | 260 String stringReplaceFirstUnchecked( |
| 197 if (from is String) { | 261 String receiver, Pattern pattern, String replacement, int startIndex) { |
| 198 var index = receiver.indexOf(from, startIndex); | 262 if (pattern is String) { |
| 263 int index = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
| 199 if (index < 0) return receiver; | 264 if (index < 0) return receiver; |
| 200 return '${receiver.substring(0, index)}$to' | 265 int end = index + pattern.length; |
| 201 '${receiver.substring(index + from.length)}'; | 266 return stringReplaceRangeUnchecked(receiver, index, end, replacement); |
| 202 } else if (from is JSSyntaxRegExp) { | |
| 203 return startIndex == 0 ? | |
| 204 stringReplaceJS(receiver, regExpGetNative(from), to) : | |
| 205 stringReplaceFirstRE(receiver, from, to, startIndex); | |
| 206 } else { | |
| 207 checkNull(from); | |
| 208 // TODO(floitsch): implement generic String.replace (with patterns). | |
| 209 throw "String.replace(Pattern) UNIMPLEMENTED"; | |
| 210 } | 267 } |
| 268 if (pattern is JSSyntaxRegExp) { |
| 269 return startIndex == 0 |
| 270 ? stringReplaceJS(receiver, regExpGetNative(pattern), replacement) |
| 271 : stringReplaceFirstRE(receiver, pattern, replacement, startIndex); |
| 272 } |
| 273 checkNull(pattern); |
| 274 Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
| 275 if (!matches.moveNext()) return receiver; |
| 276 Match match = matches.current; |
| 277 return receiver.replaceRange(match.start, match.end, replacement); |
| 211 } | 278 } |
| 212 | 279 |
| 213 stringJoinUnchecked(array, separator) { | 280 String stringReplaceFirstMappedUnchecked( |
| 281 String receiver, |
| 282 Pattern pattern, |
| 283 String replace(Match current), |
| 284 int startIndex) { |
| 285 Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
| 286 if (!matches.moveNext()) return receiver; |
| 287 Match match = matches.current; |
| 288 String replacement = "${replace(match)}"; |
| 289 return receiver.replaceRange(match.start, match.end, replacement); |
| 290 } |
| 291 |
| 292 String stringJoinUnchecked(array, separator) { |
| 214 return JS('String', r'#.join(#)', array, separator); | 293 return JS('String', r'#.join(#)', array, separator); |
| 215 } | 294 } |
| 295 |
| 296 String stringReplaceRangeUnchecked(String receiver, |
| 297 int start, int end, String replacement) { |
| 298 var prefix = JS('String', '#.substring(0, #)', receiver, start); |
| 299 var suffix = JS('String', '#.substring(#)', receiver, end); |
| 300 return "$prefix$replacement$suffix"; |
| 301 } |
| OLD | NEW |