blob: fc365f79bc8b3c08d58224fc30f321b9d4c225c5 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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<CodeUnit> {
_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<Suffix: BidirectionalCollection>(
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<Suffix: BidirectionalCollection>(
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: StringProtocol>(_ 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: StringProtocol>(_ suffix: Suffix) -> Bool {
return self._ends(with: suffix)
}
}
extension String {
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)
}
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<T : BinaryInteger>(
_ 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)) })
}
}