@@ -74,8 +74,13 @@ extension NSString {
7474 public static let byLines = EnumerationOptions ( rawValue: 0 )
7575 public static let byParagraphs = EnumerationOptions ( rawValue: 1 )
7676 public static let byComposedCharacterSequences = EnumerationOptions ( rawValue: 2 )
77+
78+ @available ( * , unavailable, message: " Enumeration by words isn't supported in swift-corelibs-foundation " )
7779 public static let byWords = EnumerationOptions ( rawValue: 3 )
80+
81+ @available ( * , unavailable, message: " Enumeration by sentences isn't supported in swift-corelibs-foundation " )
7882 public static let bySentences = EnumerationOptions ( rawValue: 4 )
83+
7984 public static let reverse = EnumerationOptions ( rawValue: 1 << 8 )
8085 public static let substringNotRequired = EnumerationOptions ( rawValue: 1 << 9 )
8186 public static let localized = EnumerationOptions ( rawValue: 1 << 10 )
@@ -825,8 +830,95 @@ extension NSString {
825830 return NSRange ( location: start, length: parEnd - start)
826831 }
827832
833+ private enum EnumerateBy {
834+ case lines
835+ case paragraphs
836+ case composedCharacterSequences
837+
838+ init ? ( parsing options: EnumerationOptions ) {
839+ var me : EnumerateBy ?
840+
841+ // We don't test for .byLines because .byLines.rawValue == 0, which unfortunately means _every_ NSString.EnumerationOptions contains .byLines.
842+ // Instead, we just default to .lines below.
843+
844+ if options. contains ( . byParagraphs) {
845+ guard me == nil else { return nil }
846+ me = . paragraphs
847+ }
848+
849+ if options. contains ( . byComposedCharacterSequences) {
850+ guard me == nil else { return nil }
851+ me = . composedCharacterSequences
852+ }
853+
854+ self = me ?? . lines
855+ }
856+ }
857+
828858 public func enumerateSubstrings( in range: NSRange , options opts: EnumerationOptions = [ ] , using block: ( String ? , NSRange , NSRange , UnsafeMutablePointer < ObjCBool > ) -> Void ) {
829- NSUnimplemented ( )
859+ guard let enumerateBy = EnumerateBy ( parsing: opts) else {
860+ fatalError ( " You must specify only one of the .by… enumeration options. " )
861+ }
862+
863+ // We do not heed the .localized flag because it affects only by-words and by-sentences enumeration, which we do not support in s-c-f.
864+
865+ var currentIndex = opts. contains ( . reverse) ? length - 1 : 0
866+
867+ func shouldContinue( ) -> Bool {
868+ opts. contains ( . reverse) ? currentIndex >= 0 : currentIndex < length
869+ }
870+
871+ let reverse = opts. contains ( . reverse)
872+
873+ func nextIndex( after fullRange: NSRange , compensatingForLengthDifferenceFrom oldLength: Int ) -> Int {
874+ var index = reverse ? fullRange. location - 1 : fullRange. location + fullRange. length
875+
876+ if !reverse {
877+ index += ( oldLength - length)
878+ }
879+
880+ return index
881+ }
882+
883+ while shouldContinue ( ) {
884+ var range = NSRange ( location: currentIndex, length: 0 )
885+ var fullRange = range
886+
887+ switch enumerateBy {
888+ case . lines:
889+ var start = 0 , end = 0 , contentsEnd = 0
890+ getLineStart ( & start, end: & end, contentsEnd: & contentsEnd, for: range)
891+ range. location = start
892+ range. length = contentsEnd - start
893+ fullRange. location = start
894+ fullRange. length = end - start
895+ case . paragraphs:
896+ var start = 0 , end = 0 , contentsEnd = 0
897+ getParagraphStart ( & start, end: & end, contentsEnd: & contentsEnd, for: range)
898+ range. location = start
899+ range. length = contentsEnd - start
900+ fullRange. location = start
901+ fullRange. length = end - start
902+ case . composedCharacterSequences:
903+ range = rangeOfComposedCharacterSequences ( for: range)
904+ fullRange = range
905+ }
906+
907+ var substring : String ?
908+ if !opts. contains ( . substringNotRequired) {
909+ substring = self . substring ( with: range)
910+ }
911+
912+ let oldLength = length
913+
914+ var stop : ObjCBool = false
915+ block ( substring, range, fullRange, & stop)
916+ if stop. boolValue {
917+ return
918+ }
919+
920+ currentIndex = nextIndex ( after: fullRange, compensatingForLengthDifferenceFrom: oldLength)
921+ }
830922 }
831923
832924 public func enumerateLines( _ block: ( String , UnsafeMutablePointer < ObjCBool > ) -> Void ) {
0 commit comments