@@ -342,6 +342,60 @@ extension _StringGuts {
342
342
return Range ( _uncheckedBounds: ( i, j) )
343
343
}
344
344
345
+ // - Returns: The encoded offset range of the replaced contents in the result.
346
+ @discardableResult
347
+ internal mutating func replaceSubrange< C> (
348
+ _ bounds: Range < Index > ,
349
+ with newElements: C
350
+ ) -> Range < Int >
351
+ where C: Collection , C. Iterator. Element == UnicodeScalar {
352
+ if isUniqueNative {
353
+ if let repl = newElements as? String . UnicodeScalarView {
354
+ if repl. _guts. isFastUTF8 {
355
+ return repl. _guts. withFastUTF8 {
356
+ uniqueNativeReplaceSubrange (
357
+ bounds, with: $0, isASCII: repl. _guts. isASCII)
358
+ }
359
+ }
360
+ } else if let repl = newElements as? Substring . UnicodeScalarView {
361
+ if repl. _wholeGuts. isFastUTF8 {
362
+ return repl. _wholeGuts. withFastUTF8 ( range: repl. _offsetRange) {
363
+ uniqueNativeReplaceSubrange (
364
+ bounds, with: $0, isASCII: repl. _wholeGuts. isASCII)
365
+ }
366
+ }
367
+ }
368
+ if #available( SwiftStdlib 5 . 1 , * ) {
369
+ return uniqueNativeReplaceSubrange (
370
+ bounds, with: newElements. lazy. flatMap { $0. utf8 } )
371
+ } else {
372
+ // FIXME: The stdlib should not have a deployment target this ancient.
373
+ let c = newElements. reduce ( 0 ) { $0 + UTF8. width ( $1) }
374
+ var utf8 : [ UInt8 ] = [ ]
375
+ utf8. reserveCapacity ( c)
376
+ utf8 = newElements. reduce ( into: utf8) { utf8, next in
377
+ next. withUTF8CodeUnits { utf8. append ( contentsOf: $0) }
378
+ }
379
+ return uniqueNativeReplaceSubrange ( bounds, with: utf8)
380
+ }
381
+ }
382
+
383
+ var result = String . UnicodeScalarView ( )
384
+ // FIXME: It should be okay to get rid of excess capacity
385
+ // here. rdar://problem/45635432
386
+ if let capacity = self . nativeCapacity {
387
+ result. reserveCapacity ( capacity)
388
+ }
389
+ let selfStr = String . UnicodeScalarView ( self )
390
+ result. append ( contentsOf: selfStr [ ..< bounds. lowerBound] )
391
+ let i = result. _guts. count
392
+ result. append ( contentsOf: newElements)
393
+ let j = result. _guts. count
394
+ result. append ( contentsOf: selfStr [ bounds. upperBound... ] )
395
+ self = result. _guts
396
+ return Range ( _uncheckedBounds: ( i, j) )
397
+ }
398
+
345
399
// - Returns: The encoded offset range of the replaced contents in the result.
346
400
internal mutating func uniqueNativeReplaceSubrange(
347
401
_ bounds: Range < Index > ,
@@ -386,5 +440,116 @@ extension _StringGuts {
386
440
self = _StringGuts ( _object. nativeStorage)
387
441
return Range ( _uncheckedBounds: ( start, start + replCount) )
388
442
}
443
+
444
+ /// Run `body` to mutate the given `subrange` of this string within
445
+ /// `startIndex ..< endIndex`, then update `startIndex` and `endIndex` to be
446
+ /// valid positions in the resulting string, addressing the same (logical)
447
+ /// locations as in the original string.
448
+ ///
449
+ /// This is used by both `Substring` and `Substring.UnicodeScalarView` to
450
+ /// implement their `replaceSubrange` methods.
451
+ ///
452
+ /// - Parameter subrange: A scalar-aligned offset range in this string.
453
+ /// - Parameter startIndex: The start index of the substring that performs
454
+ /// this operation.
455
+ /// - Parameter endIndex: The end index of the substring that performs this
456
+ /// operations.
457
+ /// - Parameter body: The mutation operation to execute on `self`. The
458
+ /// returned offset range must correspond to `subrange` in the resulting
459
+ /// string.
460
+ internal mutating func mutateSubrangeInSubstring(
461
+ subrange: Range < Index > ,
462
+ startIndex: inout Index ,
463
+ endIndex: inout Index ,
464
+ with body: ( inout _StringGuts ) -> Range < Int >
465
+ ) {
466
+ _internalInvariant (
467
+ subrange. lowerBound >= startIndex && subrange. upperBound <= endIndex)
468
+
469
+ if _slowPath ( isKnownUTF16) {
470
+ // UTF-16 (i.e., foreign) string. The mutation will convert this to the
471
+ // native UTF-8 encoding, so we need to do some extra work to preserve our
472
+ // bounds.
473
+ let utf8StartOffset = String ( self ) . utf8. distance (
474
+ from: self . startIndex, to: startIndex)
475
+ let oldUTF8Count = String ( self ) . utf8. distance (
476
+ from: startIndex, to: endIndex)
477
+
478
+ let oldUTF8SubrangeCount = String ( self ) . utf8. distance (
479
+ from: subrange. lowerBound, to: subrange. upperBound)
480
+
481
+ let newUTF8Subrange = body ( & self )
482
+ _internalInvariant ( !isKnownUTF16)
483
+
484
+ let newUTF8Count =
485
+ oldUTF8Count + newUTF8Subrange. count - oldUTF8SubrangeCount
486
+
487
+ // Get the character stride in the entire string, not just the substring.
488
+ // (Characters in a substring may end beyond the bounds of it.)
489
+ let newStride = _opaqueCharacterStride (
490
+ startingAt: utf8StartOffset, in: utf8StartOffset ..< count)
491
+
492
+ startIndex = String . Index (
493
+ encodedOffset: utf8StartOffset,
494
+ transcodedOffset: 0 ,
495
+ characterStride: newStride) . _scalarAligned. _knownUTF8
496
+ if isOnGraphemeClusterBoundary ( startIndex) {
497
+ startIndex = startIndex. _characterAligned
498
+ }
499
+
500
+ endIndex = String . Index (
501
+ encodedOffset: utf8StartOffset + newUTF8Count,
502
+ transcodedOffset: 0 ) . _scalarAligned. _knownUTF8
503
+ return
504
+ }
505
+
506
+ // UTF-8 string.
507
+
508
+ let oldRange = subrange. _encodedOffsetRange
509
+ let newRange = body ( & self )
510
+
511
+ let oldBounds = Range (
512
+ _uncheckedBounds: ( startIndex. _encodedOffset, endIndex. _encodedOffset) )
513
+ let newBounds = Range ( _uncheckedBounds: (
514
+ oldBounds. lowerBound,
515
+ oldBounds. upperBound &+ newRange. count &- oldRange. count) )
516
+
517
+ // Update `startIndex` if necessary. The replacement may have invalidated
518
+ // its cached character stride and character alignment flag, but not its
519
+ // stored offset, encoding, or scalar alignment.
520
+ //
521
+ // We are exploiting the fact that mutating the string _after_ the scalar
522
+ // following the end of the character at `startIndex` cannot possibly change
523
+ // the length of that character. (This is true because `index(after:)` never
524
+ // needs to look ahead by more than one Unicode scalar.)
525
+ let oldStride = startIndex. characterStride ?? 0
526
+ if oldRange. lowerBound <= oldBounds. lowerBound &+ oldStride {
527
+ // Get the character stride in the entire string, not just the substring.
528
+ // (Characters in a substring may end beyond the bounds of it.)
529
+ let newStride = _opaqueCharacterStride (
530
+ startingAt: newBounds. lowerBound,
531
+ in: newBounds. lowerBound ..< self . count)
532
+ var newStart = String . Index (
533
+ encodedOffset: newBounds. lowerBound,
534
+ characterStride: newStride
535
+ ) . _scalarAligned. _knownUTF8
536
+
537
+ // Preserve character alignment flag if possible
538
+ if startIndex. _isCharacterAligned,
539
+ ( oldRange. lowerBound > oldBounds. lowerBound ||
540
+ isOnGraphemeClusterBoundary ( newStart) ) {
541
+ newStart = newStart. _characterAligned
542
+ }
543
+
544
+ startIndex = newStart
545
+ }
546
+
547
+ // Update `endIndex`.
548
+ if newBounds. upperBound != endIndex. _encodedOffset {
549
+ endIndex = Index (
550
+ _encodedOffset: newBounds. upperBound
551
+ ) . _scalarAligned. _knownUTF8
552
+ }
553
+ }
389
554
}
390
555
0 commit comments