//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// import SwiftShims extension _StringVariant { @usableFromInline func _repeated(_ count: Int) -> _SwiftStringStorage { _sanityCheck(count > 0) let c = self.count let storage = _copyToNativeStorage( of: CodeUnit.self, unusedCapacity: (count - 1) * c) var p = storage.start + c for _ in 1 ..< count { p.initialize(from: storage.start, count: c) p += c } _sanityCheck(p == storage.start + count * c) storage.count = p - storage.start return storage } } extension String { /// Creates a new string representing the given string repeated the specified /// number of times. /// /// For example, you can use this initializer to create a string with ten /// `"ab"` strings in a row. /// /// let s = String(repeating: "ab", count: 10) /// print(s) /// // Prints "abababababababababab" /// /// - Parameters: /// - repeatedValue: The string to repeat. /// - count: The number of times to repeat `repeatedValue` in the resulting /// string. @inlinable // FIXME(sil-serialize-all) public init(repeating repeatedValue: String, count: Int) { precondition(count >= 0, "Negative count not allowed") guard count > 1 else { self = count == 0 ? "" : repeatedValue return } self = String(repeatedValue._guts._repeated(count)) } /// A Boolean value indicating whether a string has no characters. @inlinable // FIXME(sil-serialize-all) public var isEmpty: Bool { return _guts.count == 0 } } // TODO: since this is generally useful, make public via evolution proposal. extension BidirectionalCollection { @inlinable internal func _ends( with suffix: Suffix, by areEquivalent: (Element,Element) -> Bool ) -> Bool where Suffix.Element == Element { var (i,j) = (self.endIndex,suffix.endIndex) while i != self.startIndex, j != suffix.startIndex { self.formIndex(before: &i) suffix.formIndex(before: &j) if !areEquivalent(self[i],suffix[j]) { return false } } return j == suffix.startIndex } } extension BidirectionalCollection where Element: Equatable { @inlinable internal func _ends( with suffix: Suffix ) -> Bool where Suffix.Element == Element { return _ends(with: suffix, by: ==) } } extension StringProtocol { /// Returns a Boolean value indicating whether the string begins with the /// specified prefix. /// /// The comparison is both case sensitive and Unicode safe. The /// case-sensitive comparison will only match strings whose corresponding /// characters have the same case. /// /// let cafe = "Café du Monde" /// /// // Case sensitive /// print(cafe.hasPrefix("café")) /// // Prints "false" /// /// The Unicode-safe comparison matches Unicode scalar values rather than the /// code points used to compose them. The example below uses two strings /// with different forms of the `"é"` character---the first uses the composed /// form and the second uses the decomposed form. /// /// // Unicode safe /// let composedCafe = "Café" /// let decomposedCafe = "Cafe\u{0301}" /// /// print(cafe.hasPrefix(composedCafe)) /// // Prints "true" /// print(cafe.hasPrefix(decomposedCafe)) /// // Prints "true" /// /// - Parameter prefix: A possible prefix to test against this string. /// - Returns: `true` if the string begins with `prefix`; otherwise, `false`. @inlinable public func hasPrefix(_ prefix: Prefix) -> Bool { return self.starts(with: prefix) } /// Returns a Boolean value indicating whether the string ends with the /// specified suffix. /// /// The comparison is both case sensitive and Unicode safe. The /// case-sensitive comparison will only match strings whose corresponding /// characters have the same case. /// /// let plans = "Let's meet at the café" /// /// // Case sensitive /// print(plans.hasSuffix("Café")) /// // Prints "false" /// /// The Unicode-safe comparison matches Unicode scalar values rather than the /// code points used to compose them. The example below uses two strings /// with different forms of the `"é"` character---the first uses the composed /// form and the second uses the decomposed form. /// /// // Unicode safe /// let composedCafe = "café" /// let decomposedCafe = "cafe\u{0301}" /// /// print(plans.hasSuffix(composedCafe)) /// // Prints "true" /// print(plans.hasSuffix(decomposedCafe)) /// // Prints "true" /// /// - Parameter suffix: A possible suffix to test against this string. /// - Returns: `true` if the string ends with `suffix`; otherwise, `false`. @inlinable public func hasSuffix(_ suffix: Suffix) -> Bool { return self._ends(with: suffix) } } extension String { @inlinable // FIXME(sil-serialize-all) public func hasPrefix(_ prefix: String) -> Bool { let prefixCount = prefix._guts.count if prefixCount == 0 { return true } // TODO: replace with 2-way vistor if self._guts._isSmall && prefix._guts._isSmall { let selfSmall = self._guts._smallUTF8String let prefixSmall = prefix._guts._smallUTF8String if selfSmall.isASCII && prefixSmall.isASCII { return selfSmall.withUnmanagedASCII { selfASCII in return prefixSmall.withUnmanagedASCII { prefixASCII in if prefixASCII.count > selfASCII.count { return false } return (0 as CInt) == _stdlib_memcmp( selfASCII.rawStart, prefixASCII.rawStart, prefixASCII.count) } } } } if _fastPath(!self._guts._isOpaque && !prefix._guts._isOpaque) { if self._guts.isASCII && prefix._guts.isASCII { let result: Bool let selfASCII = self._guts._unmanagedASCIIView let prefixASCII = prefix._guts._unmanagedASCIIView if prefixASCII.count > selfASCII.count { // Prefix is longer than self. result = false } else { result = (0 as CInt) == _stdlib_memcmp( selfASCII.rawStart, prefixASCII.rawStart, prefixASCII.count) } _fixLifetime(self) _fixLifetime(prefix) return result } else { } } return self.starts(with: prefix) } @inlinable // FIXME(sil-serialize-all) public func hasSuffix(_ suffix: String) -> Bool { let suffixCount = suffix._guts.count if suffixCount == 0 { return true } // TODO: replace with 2-way vistor if self._guts._isSmall && suffix._guts._isSmall { let selfSmall = self._guts._smallUTF8String let suffixSmall = suffix._guts._smallUTF8String if selfSmall.isASCII && suffixSmall.isASCII { return selfSmall.withUnmanagedASCII { selfASCII in return suffixSmall.withUnmanagedASCII { suffixASCII in if suffixASCII.count > selfASCII.count { return false } return (0 as CInt) == _stdlib_memcmp( selfASCII.rawStart + (selfASCII.count - suffixASCII.count), suffixASCII.rawStart, suffixASCII.count) } } } } if _fastPath(!self._guts._isOpaque && !suffix._guts._isOpaque) { if self._guts.isASCII && suffix._guts.isASCII { let result: Bool let selfASCII = self._guts._unmanagedASCIIView let suffixASCII = suffix._guts._unmanagedASCIIView if suffixASCII.count > selfASCII.count { // Suffix is longer than self. result = false } else { result = (0 as CInt) == _stdlib_memcmp( selfASCII.rawStart + (selfASCII.count - suffixASCII.count), suffixASCII.rawStart, suffixASCII.count) } _fixLifetime(self) _fixLifetime(suffix) return result } } return self._ends(with: suffix) } } // Conversions to string from other types. extension String { /// Creates a string representing the given value in base 10, or some other /// specified base. /// /// The following example converts the maximal `Int` value to a string and /// prints its length: /// /// let max = String(Int.max) /// print("\(max) has \(max.count) digits.") /// // Prints "9223372036854775807 has 19 digits." /// /// Numerals greater than 9 are represented as Roman letters. These letters /// start with `"A"` if `uppercase` is `true`; otherwise, with `"a"`. /// /// let v = 999_999 /// print(String(v, radix: 2)) /// // Prints "11110100001000111111" /// /// print(String(v, radix: 16)) /// // Prints "f423f" /// print(String(v, radix: 16, uppercase: true)) /// // Prints "F423F" /// /// - Parameters: /// - value: The value to convert to a string. /// - radix: The base to use for the string representation. `radix` must be /// at least 2 and at most 36. The default is 10. /// - uppercase: Pass `true` to use uppercase letters to represent numerals /// greater than 9, or `false` to use lowercase letters. The default is /// `false`. @inlinable // FIXME(sil-serialize-all) public init( _ value: T, radix: Int = 10, uppercase: Bool = false ) { self = value._description(radix: radix, uppercase: uppercase) } } extension _StringGuts { @inlinable func _repeated(_ n: Int) -> _StringGuts { _sanityCheck(n > 1) if self._isSmall { // TODO: visitor pattern for something like this... if let small = self._smallUTF8String._repeated(n) { return _StringGuts(small) } } return _visitGuts(self, range: nil, args: n, ascii: { ascii, n in return _StringGuts(_large: ascii._repeated(n)) }, utf16: { utf16, n in return _StringGuts(_large: utf16._repeated(n)) }, opaque: { opaque, n in return _StringGuts(_large: opaque._repeated(n)) }) } }