Index: tool/input_sdk/private/string_helper.dart |
diff --git a/tool/input_sdk/private/string_helper.dart b/tool/input_sdk/private/string_helper.dart |
index 85383c6b9512cea2d2bf2773a70c08916507b392..e97448a23af431a9d4f498823e0d5e951a3159f3 100644 |
--- a/tool/input_sdk/private/string_helper.dart |
+++ b/tool/input_sdk/private/string_helper.dart |
@@ -4,6 +4,22 @@ |
part of dart._js_helper; |
+int stringIndexOfStringUnchecked(receiver, other, startIndex) { |
+ return JS('int', '#.indexOf(#, #)', receiver, other, startIndex); |
+} |
+ |
+String substring1Unchecked(receiver, startIndex) { |
+ return JS('String', '#.substring(#)', receiver, startIndex); |
+} |
+ |
+String substring2Unchecked(receiver, startIndex, endIndex) { |
+ return JS('String', '#.substring(#, #)', receiver, startIndex, endIndex); |
+} |
+ |
+bool stringContainsStringUnchecked(receiver, other, startIndex) { |
+ return stringIndexOfStringUnchecked(receiver, other, startIndex) >= 0; |
+} |
+ |
class StringMatch implements Match { |
const StringMatch(int this.start, |
String this.input, |
@@ -33,34 +49,63 @@ class StringMatch implements Match { |
final String pattern; |
} |
-List<Match> allMatchesInStringUnchecked(String needle, String haystack, |
- int startIndex) { |
- // Copied from StringBase.allMatches in |
- // /runtime/lib/string_base.dart |
- List<Match> result = new List<Match>(); |
- int length = haystack.length; |
- int patternLength = needle.length; |
- while (true) { |
- int position = haystack.indexOf(needle, startIndex); |
- if (position == -1) { |
- break; |
+Iterable<Match> allMatchesInStringUnchecked(String pattern, String string, |
+ int startIndex) { |
+ return new _StringAllMatchesIterable(string, pattern, startIndex); |
+} |
+ |
+class _StringAllMatchesIterable extends IterableBase<Match> { |
+ final String _input; |
+ final String _pattern; |
+ final int _index; |
+ |
+ _StringAllMatchesIterable(this._input, this._pattern, this._index); |
+ |
+ Iterator<Match> get iterator => |
+ new _StringAllMatchesIterator(_input, _pattern, _index); |
+ |
+ Match get first { |
+ int index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
+ if (index >= 0) { |
+ return new StringMatch(index, _input, _pattern); |
} |
- result.add(new StringMatch(position, haystack, needle)); |
- int endIndex = position + patternLength; |
- if (endIndex == length) { |
- break; |
- } else if (position == endIndex) { |
- ++startIndex; // empty match, advance and restart |
- } else { |
- startIndex = endIndex; |
+ throw IterableElementError.noElement(); |
+ } |
+} |
+ |
+class _StringAllMatchesIterator implements Iterator<Match> { |
+ final String _input; |
+ final String _pattern; |
+ int _index; |
+ Match _current; |
+ |
+ _StringAllMatchesIterator(this._input, this._pattern, this._index); |
+ |
+ bool moveNext() { |
+ if (_index + _pattern.length > _input.length) { |
+ _current = null; |
+ return false; |
+ } |
+ var index = stringIndexOfStringUnchecked(_input, _pattern, _index); |
+ if (index < 0) { |
+ _index = _input.length + 1; |
+ _current = null; |
+ return false; |
} |
+ int end = index + _pattern.length; |
+ _current = new StringMatch(index, _input, _pattern); |
+ // Empty match, don't start at same location again. |
+ if (end == _index) end++; |
+ _index = end; |
+ return true; |
} |
- return result; |
+ |
+ Match get current => _current; |
} |
-stringContainsUnchecked(receiver, other, startIndex) { |
+bool stringContainsUnchecked(String receiver, other, int startIndex) { |
if (other is String) { |
- return receiver.indexOf(other, startIndex) != -1; |
+ return stringContainsStringUnchecked(receiver, other, startIndex); |
} else if (other is JSSyntaxRegExp) { |
return other.hasMatch(receiver.substring(startIndex)); |
} else { |
@@ -69,51 +114,57 @@ stringContainsUnchecked(receiver, other, startIndex) { |
} |
} |
-stringReplaceJS(receiver, replacer, to) { |
+String stringReplaceJS(receiver, replacer, replacement) { |
// The JavaScript String.replace method recognizes replacement |
// patterns in the replacement string. Dart does not have that |
// behavior. |
- to = JS('String', r'#.replace(/\$/g, "$$$$")', to); |
- return JS('String', r'#.replace(#, #)', receiver, replacer, to); |
+ replacement = JS('String', r'#.replace(/\$/g, "$$$$")', replacement); |
+ return JS('String', r'#.replace(#, #)', receiver, replacer, replacement); |
} |
-stringReplaceFirstRE(receiver, regexp, to, startIndex) { |
+String stringReplaceFirstRE(String receiver, |
+ JSSyntaxRegExp regexp, String replacement, int startIndex) { |
var match = regexp._execGlobal(receiver, startIndex); |
if (match == null) return receiver; |
var start = match.start; |
var end = match.end; |
- return "${receiver.substring(0,start)}$to${receiver.substring(end)}"; |
+ return stringReplaceRangeUnchecked(receiver, start, end, replacement); |
} |
-const String ESCAPE_REGEXP = r'[[\]{}()*+?.\\^$|]'; |
-stringReplaceAllUnchecked(receiver, from, to) { |
- checkString(to); |
- if (from is String) { |
- if (from == "") { |
+/// Returns a string for a RegExp pattern that matches [string]. This is done by |
+/// escaping all RegExp metacharacters. |
+String quoteStringForRegExp(string) { |
+ return JS('String', r'#.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&")', string); |
+} |
+ |
+String stringReplaceAllUnchecked( |
+ String receiver, Pattern pattern, String replacement) { |
+ checkString(replacement); |
+ if (pattern is String) { |
+ if (pattern == "") { |
if (receiver == "") { |
- return to; |
+ return replacement; |
} else { |
StringBuffer result = new StringBuffer(); |
int length = receiver.length; |
- result.write(to); |
+ result.write(replacement); |
for (int i = 0; i < length; i++) { |
result.write(receiver[i]); |
- result.write(to); |
+ result.write(replacement); |
} |
return result.toString(); |
} |
} else { |
- var quoter = JS('', "new RegExp(#, 'g')", ESCAPE_REGEXP); |
- var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter); |
+ var quoted = quoteStringForRegExp(pattern); |
var replacer = JS('', "new RegExp(#, 'g')", quoted); |
- return stringReplaceJS(receiver, replacer, to); |
+ return stringReplaceJS(receiver, replacer, replacement); |
} |
- } else if (from is JSSyntaxRegExp) { |
- var re = regExpGetGlobalNative(from); |
- return stringReplaceJS(receiver, re, to); |
+ } else if (pattern is JSSyntaxRegExp) { |
+ var re = regExpGetGlobalNative(pattern); |
+ return stringReplaceJS(receiver, re, replacement); |
} else { |
- checkNull(from); |
+ checkNull(pattern); |
// TODO(floitsch): implement generic String.replace (with patterns). |
throw "String.replaceAll(Pattern) UNIMPLEMENTED"; |
} |
@@ -122,16 +173,23 @@ stringReplaceAllUnchecked(receiver, from, to) { |
String _matchString(Match match) => match[0]; |
String _stringIdentity(String string) => string; |
-stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { |
- if (pattern is! Pattern) { |
- throw new ArgumentError("${pattern} is not a Pattern"); |
- } |
+String stringReplaceAllFuncUnchecked( |
+ String receiver, |
+ Pattern pattern, |
+ String onMatch(Match match), |
+ String onNonMatch(String nonMatch)) { |
if (onMatch == null) onMatch = _matchString; |
if (onNonMatch == null) onNonMatch = _stringIdentity; |
if (pattern is String) { |
return stringReplaceAllStringFuncUnchecked(receiver, pattern, |
onMatch, onNonMatch); |
} |
+ // Placing the Pattern test here is indistingishable from placing it at the |
+ // top of the method but it saves an extra check on the `pattern is String` |
+ // path. |
+ if (pattern is! Pattern) { |
+ throw new ArgumentError.value(pattern, 'pattern', 'is not a Pattern'); |
+ } |
StringBuffer buffer = new StringBuffer(); |
int startIndex = 0; |
for (Match match in pattern.allMatches(receiver)) { |
@@ -143,7 +201,9 @@ stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { |
return buffer.toString(); |
} |
-stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { |
+String stringReplaceAllEmptyFuncUnchecked(String receiver, |
+ String onMatch(Match match), |
+ String onNonMatch(String nonMatch)) { |
// Pattern is the empty string. |
StringBuffer buffer = new StringBuffer(); |
int length = receiver.length; |
@@ -171,7 +231,11 @@ stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { |
return buffer.toString(); |
} |
-stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { |
+String stringReplaceAllStringFuncUnchecked( |
+ String receiver, |
+ String pattern, |
+ String onMatch(Match match), |
+ String onNonMatch(String nonMatch)) { |
int patternLength = pattern.length; |
if (patternLength == 0) { |
return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); |
@@ -180,7 +244,7 @@ stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { |
StringBuffer buffer = new StringBuffer(); |
int startIndex = 0; |
while (startIndex < length) { |
- int position = receiver.indexOf(pattern, startIndex); |
+ int position = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
if (position == -1) { |
break; |
} |
@@ -193,23 +257,45 @@ stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { |
} |
-stringReplaceFirstUnchecked(receiver, from, to, [int startIndex = 0]) { |
- if (from is String) { |
- var index = receiver.indexOf(from, startIndex); |
+String stringReplaceFirstUnchecked( |
+ String receiver, Pattern pattern, String replacement, int startIndex) { |
+ if (pattern is String) { |
+ int index = stringIndexOfStringUnchecked(receiver, pattern, startIndex); |
if (index < 0) return receiver; |
- return '${receiver.substring(0, index)}$to' |
- '${receiver.substring(index + from.length)}'; |
- } else if (from is JSSyntaxRegExp) { |
- return startIndex == 0 ? |
- stringReplaceJS(receiver, regExpGetNative(from), to) : |
- stringReplaceFirstRE(receiver, from, to, startIndex); |
- } else { |
- checkNull(from); |
- // TODO(floitsch): implement generic String.replace (with patterns). |
- throw "String.replace(Pattern) UNIMPLEMENTED"; |
+ int end = index + pattern.length; |
+ return stringReplaceRangeUnchecked(receiver, index, end, replacement); |
+ } |
+ if (pattern is JSSyntaxRegExp) { |
+ return startIndex == 0 |
+ ? stringReplaceJS(receiver, regExpGetNative(pattern), replacement) |
+ : stringReplaceFirstRE(receiver, pattern, replacement, startIndex); |
} |
+ checkNull(pattern); |
+ Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
+ if (!matches.moveNext()) return receiver; |
+ Match match = matches.current; |
+ return receiver.replaceRange(match.start, match.end, replacement); |
} |
-stringJoinUnchecked(array, separator) { |
+String stringReplaceFirstMappedUnchecked( |
+ String receiver, |
+ Pattern pattern, |
+ String replace(Match current), |
+ int startIndex) { |
+ Iterator<Match> matches = pattern.allMatches(receiver, startIndex).iterator; |
+ if (!matches.moveNext()) return receiver; |
+ Match match = matches.current; |
+ String replacement = "${replace(match)}"; |
+ return receiver.replaceRange(match.start, match.end, replacement); |
+} |
+ |
+String stringJoinUnchecked(array, separator) { |
return JS('String', r'#.join(#)', array, separator); |
} |
+ |
+String stringReplaceRangeUnchecked(String receiver, |
+ int start, int end, String replacement) { |
+ var prefix = JS('String', '#.substring(0, #)', receiver, start); |
+ var suffix = JS('String', '#.substring(#)', receiver, end); |
+ return "$prefix$replacement$suffix"; |
+} |