Chromium Code Reviews| Index: runtime/lib/string_patch.dart |
| diff --git a/runtime/lib/string_patch.dart b/runtime/lib/string_patch.dart |
| index 3a1fd711b6cd06e4a46f44ad383d6258a8d47a76..52f9392b32d958cc5ea0f7a5c5b1ff1e422e8c63 100644 |
| --- a/runtime/lib/string_patch.dart |
| +++ b/runtime/lib/string_patch.dart |
| @@ -40,6 +40,11 @@ patch class String { |
| * implementations, e.g., _OneByteString. |
| */ |
| class _StringBase { |
| + // Constants used by replaceAll encoding of matches. |
| + static const int _lengthShift = 15; |
| + static const int _maxStartValue = (1 << _lengthShift) - 1; |
| + static const int _maxLengthValue = (1 << (30 - _lengthShift)) - 1; |
| + static const int _startMask = _maxStartValue; |
| factory _StringBase._uninstantiable() { |
| throw new UnsupportedError( |
| @@ -488,25 +493,150 @@ class _StringBase { |
| } |
| String replaceAll(Pattern pattern, String replacement) { |
| - if (pattern is! Pattern) { |
| - throw new ArgumentError("${pattern} is not a Pattern"); |
|
zerny-google
2015/01/20 13:43:11
Is it OK that we no longer have an explicit Argume
Lasse Reichstein Nielsen
2015/01/21 10:48:43
Almost. I'll add back a check for "null", but we s
|
| + List matches = []; |
| + int length = 0; |
| + int replacementLength = replacement.length; |
| + int startIndex = 0; |
| + if (replacementLength == 0) { |
| + for (Match match in pattern.allMatches(this)) { |
| + int matchStart = match.start; |
| + int preMatchLength = match.start - startIndex; |
| + if (preMatchLength > 0) { |
| + if (startIndex <= _maxStartValue && |
| + preMatchLength <= _maxLengthValue) { |
| + matches.add(-(startIndex | preMatchLength << _lengthShift)); |
|
zerny-google
2015/01/20 13:43:11
Nit: add parens around <<
Also, does compacting t
Lasse Reichstein Nielsen
2015/01/21 10:48:43
Done.
I see ~5% improvement on a small/micro bench
|
| + } else { |
| + matches.add(startIndex); |
| + matches.add(matchStart); |
| + } |
| + length += preMatchLength; |
| + } |
| + startIndex = match.end; |
| + } |
| + } else { |
| + for (Match match in pattern.allMatches(this)) { |
| + int matchStart = match.start; |
| + int preMatchLength = match.start - startIndex; |
| + if (preMatchLength > 0) { |
| + if (startIndex <= _maxStartValue && |
| + preMatchLength <= _maxLengthValue) { |
| + matches.add(-(startIndex | preMatchLength << _lengthShift)); |
| + } else { |
| + matches.add(startIndex); |
| + matches.add(matchStart); |
| + } |
| + length += preMatchLength; |
| + } |
| + matches.add(replacement); |
| + length += replacementLength; |
| + startIndex = match.end; |
| + } |
| } |
| - if (replacement is! String) { |
| - throw new ArgumentError( |
| - "${replacement} is not a String or Match->String function"); |
| + if (startIndex < this.length) { |
| + matches.add(startIndex); |
| + matches.add(this.length); |
| + length += this.length - startIndex; |
| } |
| - StringBuffer buffer = new StringBuffer(); |
| - int startIndex = 0; |
| - for (Match match in pattern.allMatches(this)) { |
| - buffer..write(this.substring(startIndex, match.start)) |
| - ..write(replacement); |
| - startIndex = match.end; |
| + bool replacementIsOneByte = (replacement is _OneByteString) || |
| + (replacement is _ExternalOneByteString); |
| + bool thisIsOneByte = (this is _OneByteString) || |
| + (this is _ExternalOneByteString); |
| + if (replacementIsOneByte && thisIsOneByte) { |
| + _joinReplaceAllOneByteResult(this, matches, length); |
| } |
| - return (buffer..write(this.substring(startIndex))).toString(); |
| + return _joinReplaceAllResult(this, matches, length, |
|
Florian Schneider
2015/01/20 16:39:33
Would it make sense to implement the 'this' two-by
Lasse Reichstein Nielsen
2015/01/21 10:48:43
The one-byte case actually turns out to be slower
|
| + replacementIsOneByte); |
| } |
| + /** |
| + * As [_joinReplaceAllResult], but knowing that the result |
| + * is always a [_OneByteString]. |
| + */ |
| + static String _joinReplaceAllOneByteResult(String base, |
| + List matches, |
| + int length) { |
| + _OneByteString result = _OneByteString._allocate(length); |
| + int writeIndex = 0; |
| + for (int i = 0; i < matches.length; i++) { |
| + var entry = matches[i]; |
| + if (entry is _Smi) { |
| + int sliceStart = entry; |
| + int sliceEnd; |
| + if (sliceStart < 0) { |
| + int bits = -sliceStart; |
| + sliceStart = bits & _startMask; |
| + sliceEnd = sliceStart + (bits >> _lengthShift); |
| + } else { |
| + i++; |
|
siva
2015/01/21 00:29:20
This is really dicey, 'i' is being incremented and
Lasse Reichstein Nielsen
2015/01/21 10:48:43
Adding assert.
This function should only be called
|
| + sliceEnd = matches[i]; |
| + } |
| + for (int j = sliceStart; j < sliceEnd; j++) { |
| + result._setAt(writeIndex++, base.codeUnitAt(j)); |
| + } |
| + } else { |
| + _OneByteString replacement = entry; |
| + for (int j = 0; j < replacement.length; j++) { |
| + result._setAt(writeIndex++, replacement.codeUnitAt(j)); |
| + } |
| + } |
| + } |
| + assert(writeIndex == length); |
| + return result; |
| + } |
| + |
| + /** |
| + * Combine the results of a [replaceAll] match into a new string. |
| + * |
| + * The [matches] lists contains Smi index pairs representing slices of |
| + * [base] and [String]s to be put in between the slices. |
| + * |
| + * The total [length] of the resulting string is known, as is |
| + * whether the replacement strings are one-byte strings. |
| + * If they are, then we have to check the base string slices to know |
| + * whether the result must be a one-byte string. |
| + */ |
| + static String _joinReplaceAllResult(String base, List matches, int length, |
| + bool replacementStringsAreOneByte) |
| + native "StringBase_joinReplaceAllResult"; |
| + |
| String replaceAllMapped(Pattern pattern, String replace(Match match)) { |
| - return splitMapJoin(pattern, onMatch: replace); |
| + List matches = []; |
| + int length = 0; |
| + int startIndex = 0; |
| + bool replacementStringsAreOneByte = true; |
| + for (Match match in pattern.allMatches(this)) { |
| + int matchStart = match.start; |
| + int preMatchLength = match.start - startIndex; |
| + if (preMatchLength > 0) { |
| + if (startIndex <= _maxStartValue && |
| + preMatchLength <= _maxLengthValue) { |
| + matches.add(-(startIndex | preMatchLength << _lengthShift)); |
| + } else { |
| + matches.add(startIndex); |
| + matches.add(matchStart); |
| + } |
| + length += preMatchLength; |
| + } |
| + String replacement = replace(match); |
| + matches.add(replacement); |
| + length += replacement.length; |
| + replacementStringsAreOneByte = replacementStringsAreOneByte && |
| + (replacement is _OneByteString || |
| + replacement is _ExternalOneByteString); |
| + startIndex = match.end; |
| + } |
| + if (startIndex < this.length) { |
| + matches.add(startIndex); |
| + matches.add(this.length); |
| + length += this.length - startIndex; |
| + } |
| + bool thisIsOneByte = (this is _OneByteString) || |
| + (this is _ExternalOneByteString); |
| + if (replacementStringsAreOneByte && thisIsOneByte) { |
| + _joinReplaceAllOneByteResult(this, matches, length); |
| + } |
| + return _joinReplaceAllResult(this, matches, length, |
| + replacementStringsAreOneByte); |
| } |
| static String _matchString(Match match) => match[0]; |