Skip to content

Commit 20653fc

Browse files
committed
Add String.replaceRange and use it in replaceFirst{,Mapped}.
R=floitsch@google.com Review URL: https://codereview.chromium.org//949753005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@44022 260f80e4-7a28-3924-810f-c04153c831b5
1 parent 854959e commit 20653fc

File tree

6 files changed

+95
-23
lines changed

6 files changed

+95
-23
lines changed

runtime/lib/string_patch.dart

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -572,11 +572,30 @@ class _StringBase {
572572
: pattern.allMatches(this, startIndex).iterator;
573573
if (!iterator.moveNext()) return this;
574574
Match match = iterator.current;
575-
return "${this.substring(0, match.start)}"
576-
"$replacement"
577-
"${this.substring(match.end)}";
575+
return replaceRange(match.start, match.end, replacement);
578576
}
579577

578+
String replaceRange(int start, int end, String replacement) {
579+
int length = this.length;
580+
end = RangeError.checkValidRange(start, end, length);
581+
bool replacementIsOneByte = replacement._isOneByte;
582+
int replacementLength = replacement.length;
583+
int totalLength = start + (length - end) + replacementLength;
584+
if (replacementIsOneByte && this._isOneByte) {
585+
var result = _OneByteString._allocate(totalLength);
586+
int index = 0;
587+
index = result._setRange(index, this, 0, start);
588+
index = result._setRange(start, replacement, 0, replacementLength);
589+
result._setRange(index, this, end, length);
590+
return result;
591+
}
592+
List slices = [];
593+
_addReplaceSlice(slices, 0, start);
594+
if (replacement.length > 0) slices.add(replacement);
595+
_addReplaceSlice(slices, end, length);
596+
return _joinReplaceAllResult(this, slices, totalLength,
597+
replacementIsOneByte);
598+
}
580599

581600
static int _addReplaceSlice(List matches, int start, int end) {
582601
int length = end - start;
@@ -719,23 +738,7 @@ class _StringBase {
719738
if (!matches.moveNext()) return this;
720739
var match = matches.current;
721740
var replacement = "${replace(match)}";
722-
var slices = [];
723-
int length = 0;
724-
if (match.start > 0) {
725-
length += _addReplaceSlice(slices, 0, match.start);
726-
}
727-
slices.add(replacement);
728-
length += replacement.length;
729-
if (match.end < this.length) {
730-
length += _addReplaceSlice(slices, match.end, this.length);
731-
}
732-
bool replacementIsOneByte = replacement._isOneByte;
733-
if (replacementIsOneByte &&
734-
length < _maxJoinReplaceOneByteStringLength &&
735-
this._isOneByte) {
736-
return _joinReplaceAllOneByteResult(this, slices, length);
737-
}
738-
return _joinReplaceAllResult(this, slices, length, replacementIsOneByte);
741+
return replaceRange(match.start, match.end, replacement);
739742
}
740743

741744
static String _matchString(Match match) => match[0];
@@ -1216,6 +1219,23 @@ class _OneByteString extends _StringBase implements String {
12161219
// This is internal helper method. Code point value must be a valid
12171220
// Latin1 value (0..0xFF), index must be valid.
12181221
void _setAt(int index, int codePoint) native "OneByteString_setAt";
1222+
1223+
// Should be optimizable to a memory move.
1224+
// Accepts both _OneByteString and _ExternalOneByteString as argument.
1225+
// Returns index after last character written.
1226+
int _setRange(int index, String oneByteString, int start, int end) {
1227+
assert(oneByteString._isOneByte);
1228+
assert(0 <= start);
1229+
assert(start <= end);
1230+
assert(end <= oneByteString.length);
1231+
assert(0 <= index);
1232+
assert(index + (end - start) <= length);
1233+
for (int i = start; i < end; i++) {
1234+
_setAt(index, oneByteString.codeUnitAt(i));
1235+
index += 1;
1236+
}
1237+
return index;
1238+
}
12191239
}
12201240

12211241

sdk/lib/_internal/compiler/js_lib/interceptors.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'dart:_js_helper' show allMatchesInStringUnchecked,
3131
stringReplaceAllUnchecked,
3232
stringReplaceFirstUnchecked,
3333
stringReplaceFirstMappedUnchecked,
34+
stringReplaceRangeUnchecked,
3435
lookupAndCacheInterceptor,
3536
lookupDispatchRecord,
3637
StringMatch,

sdk/lib/_internal/compiler/js_lib/js_string.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ class JSString extends Interceptor implements String, JSIndexable {
9797
}
9898
}
9999

100+
String replaceRange(int start, int end, String replacement) {
101+
checkString(replacement);
102+
checkInt(start);
103+
end = RangeError.checkValidRange(start, end, this.length);
104+
checkInt(end);
105+
return stringReplaceRangeUnchecked(this, start, end, replacement);
106+
}
107+
100108
List<String> _defaultSplit(Pattern pattern) {
101109
List<String> result = <String>[];
102110
// End of most recent match. That is, start of next part to add to result.

sdk/lib/_internal/compiler/js_lib/string_helper.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ stringReplaceFirstRE(receiver, regexp, to, startIndex) {
8282
if (match == null) return receiver;
8383
var start = match.start;
8484
var end = match.end;
85-
return "${receiver.substring(0,start)}$to${receiver.substring(end)}";
85+
return stringReplaceRangeUnchecked(receiver, start, end, to);
8686
}
8787

8888
const String ESCAPE_REGEXP = r'[[\]{}()*+?.\\^$|]';
@@ -197,8 +197,8 @@ stringReplaceFirstUnchecked(receiver, from, to, int startIndex) {
197197
if (from is String) {
198198
int index = receiver.indexOf(from, startIndex);
199199
if (index < 0) return receiver;
200-
return '${receiver.substring(0, index)}$to'
201-
'${receiver.substring(index + from.length)}';
200+
int end = index + from.length;
201+
return stringReplaceRangeUnchecked(receiver, index, end, to);
202202
}
203203
if (from is JSSyntaxRegExp) {
204204
return startIndex == 0 ?
@@ -226,3 +226,10 @@ stringReplaceFirstMappedUnchecked(receiver, from, replace,
226226
stringJoinUnchecked(array, separator) {
227227
return JS('String', r'#.join(#)', array, separator);
228228
}
229+
230+
String stringReplaceRangeUnchecked(String receiver,
231+
int start, int end, String replacement) {
232+
var prefix = JS('String', '#.substring(0, #)', receiver, start);
233+
var suffix = JS('String', '#.substring(#)', receiver, end);
234+
return "$prefix$replacement$suffix";
235+
}

sdk/lib/core/string.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,19 @@ abstract class String implements Comparable<String>, Pattern {
476476
*/
477477
String replaceAllMapped(Pattern from, String replace(Match match));
478478

479+
/**
480+
* Replaces the substring from [start] to [end] with [replacement].
481+
*
482+
* Returns a new string equivalent to:
483+
*
484+
* this.substring(0, start) + replacement + this.substring(end)
485+
*
486+
* The [start] and [end] indices must specify a valid range of this string.
487+
* That is `0 <= start <= end <= this.length`.
488+
* If [end] is `null`, it defaults to [length].
489+
*/
490+
String replaceRange(int start, int end, String replacement);
491+
479492
/**
480493
* Splits the string at matches of [pattern] and returns a list of substrings.
481494
*

tests/corelib/string_replace_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,29 @@ main() {
210210
var n = new Naughty();
211211
Expect.throws(
212212
() => "foo-bar".replaceFirstMapped("bar", (v) { return n; }));
213+
214+
for (var string in ["", "x", "foo", "x\u2000z"]) {
215+
for (var replacement in ["", "foo", string]) {
216+
for (int start = 0; start <= string.length; start++) {
217+
var expect;
218+
for (int end = start; end <= string.length; end++) {
219+
expect = string.substring(0, start) +
220+
replacement +
221+
string.substring(end);
222+
Expect.equals(expect, string.replaceRange(start, end, replacement),
223+
'"$string"[$start:$end]="$replacement"');
224+
}
225+
// Reuse expect from "end == string.length" case when omitting end.
226+
Expect.equals(expect, string.replaceRange(start, null, replacement),
227+
'"$string"[$start:]="$replacement"');
228+
}
229+
}
230+
Expect.throws(() => string.replaceRange(0, 0, null));
231+
Expect.throws(() => string.replaceRange(0, 0, 42));
232+
Expect.throws(() => string.replaceRange(0, 0, ["x"]));
233+
Expect.throws(() => string.replaceRange(-1, 0, "x"));
234+
Expect.throws(() => string.replaceRange(0, string.length + 1, "x"));
235+
}
213236
}
214237

215238
// Fails to return a String on toString, throws if converted by "$naughty".

0 commit comments

Comments
 (0)