Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stdlib] One-sided ranges and RangeExpression #8710

Merged
merged 5 commits into from
Apr 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions stdlib/public/core/Policy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,9 @@ infix operator ^ : AdditionPrecedence
// FIXME: is this the right precedence level for "..." ?
infix operator ... : RangeFormationPrecedence
infix operator ..< : RangeFormationPrecedence
postfix operator ...
prefix operator ...
prefix operator ..<

// The cast operators 'as' and 'is' are hardcoded as if they had the
// following attributes:
Expand Down
234 changes: 234 additions & 0 deletions stdlib/public/core/Range.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@
//
//===----------------------------------------------------------------------===//

/// A type which can be used to slice a collection. A `RangeExpression` can
/// convert itself to a `Range<Bound>` of indices within a given collection;
/// the collection can then slice itself with that `Range`.
public protocol RangeExpression {
associatedtype Bound: Comparable
/// Returns `self` expressed as a range of indices within `collection`.
///
/// -Parameter collection: The collection `self` should be
/// relative to.
///
/// -Returns: A `Range<Bound>` suitable for slicing `collection`.
/// The return value is *not* guaranteed to be inside
/// its bounds. Callers should apply the same preconditions
/// to the return value as they would to a range provided
/// directly by the user.
func relative<C: _Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound

func contains(_ element: Bound) -> Bool
}

extension RangeExpression {
@_inlineable
public static func ~= (pattern: Self, value: Bound) -> Bool {
return pattern.contains(value)
}
}

// FIXME(ABI)#55 (Statically Unavailable/Dynamically Available): remove this type, it creates an ABI burden
// on the library.
//
Expand Down Expand Up @@ -513,6 +540,18 @@ extension ${Self} {
}
}

extension ${Self}: RangeExpression {
public func relative<C: _Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound {
% if 'Closed' in Self:
return Range(uncheckedBounds:
(lower: lowerBound, upper: collection.index(after: self.upperBound))
% else:
return Range(uncheckedBounds: (lower: lowerBound, upper: upperBound)
% end
)
}
}

extension ${Self} : CustomStringConvertible {
/// A textual representation of the range.
public var description: String {
Expand Down Expand Up @@ -697,6 +736,201 @@ public func ..< <Bound>(
return CountableRange(uncheckedBounds: (lower: minimum, upper: maximum))
}

@_fixed_layout
public struct PartialRangeUpTo<Bound: Comparable>: RangeExpression {
public init(_ upperBound: Bound) { self.upperBound = upperBound }
public let upperBound: Bound
@_transparent
public func relative<C: _Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound {
return collection.startIndex..<self.upperBound
}
@_transparent
public func contains(_ element: Bound) -> Bool {
return element < upperBound
}
}

@_fixed_layout
public struct PartialRangeThrough<Bound: Comparable>: RangeExpression {
public init(_ upperBound: Bound) { self.upperBound = upperBound }
public let upperBound: Bound
@_transparent
public func relative<C: _Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound {
return collection.startIndex..<collection.index(after: self.upperBound)
}
@_transparent
public func contains(_ element: Bound) -> Bool {
return element <= upperBound
}
}

@_fixed_layout
public struct PartialRangeFrom<Bound: Comparable>: RangeExpression {
public init(_ lowerBound: Bound) { self.lowerBound = lowerBound }
public let lowerBound: Bound
@_transparent
public func relative<C: _Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound {
return self.lowerBound..<collection.endIndex
}
@_transparent
public func contains(_ element: Bound) -> Bool {
return lowerBound <= element
}
}

@_fixed_layout
public struct CountablePartialRangeFrom<
Bound: Strideable
>: RangeExpression where Bound.Stride : SignedInteger {
public init(_ lowerBound: Bound) { self.lowerBound = lowerBound }
public let lowerBound: Bound
@_transparent
public func relative<C: _Indexable>(
to collection: C
) -> Range<Bound> where C.Index == Bound {
return self.lowerBound..<collection.endIndex
}
public func contains(_ element: Bound) -> Bool {
return lowerBound <= element
}
}

extension CountablePartialRangeFrom: Sequence {
@_fixed_layout
public struct Iterator: IteratorProtocol {
@_inlineable
public init(_current: Bound) { self._current = _current }
@_inlineable
public mutating func next() -> Bound? {
defer { _current = _current.advanced(by: 1) }
return _current
}
@_versioned
var _current: Bound
}
@_inlineable
public func makeIterator() -> Iterator {
return Iterator(_current: lowerBound)
}
}

extension Comparable {
/// Returns a partial range up to but not including its maximum.
///
/// Use the partial range up to operator (`..<`) to create a partial range of
/// any type that conforms to the `Comparable` protocol. This example creates
/// a `PartialRangeUpTo<Double>` up to, but not including, 5.0.
///
/// let lessThanFive = ..<5.0
/// print(lessThanFive.contains(3.14)) // Prints "true"
/// print(lessThanFive.contains(5.0)) // Prints "false"
///
/// Partial ranges can be used to slice types conforming to `Collection`
/// from the start of the collection up to, but not including, the maximum.
///
/// let array = Array(0..<5)
/// print(array[..<3]) // prints [0, 1, 2]
///
/// - Parameters:
/// - maximum: The upper bound for the range.
@_transparent
public static prefix func ..<(maximum: Self) -> PartialRangeUpTo<Self> {
return PartialRangeUpTo(maximum)
}

/// Returns a partial range up to and including its maximum.
///
/// Use the partial range up to operator (`...`) to create a partial range of
/// any type that conforms to the `Comparable` protocol. This example creates
/// a `PartialRangeThrough<Double>` up to 5.
///
/// let upToFive = ..<5.0
/// print(upToFive.contains(4)) // Prints "true"
/// print(upToFive.contains(5)) // Prints "true"
/// print(upToFive.contains(6)) // Prints "false"
///
/// Partial ranges can be used to slice types conforming to `Collection`
/// from the start of the collection up to the maximum.
///
/// let array = Array(0..<5)
/// print(array[...3]) // prints [0, 1, 2, 3]
///
/// - Parameters:
/// - maximum: The upper bound for the range.
@_transparent
public static prefix func ...(maximum: Self) -> PartialRangeThrough<Self> {
return PartialRangeThrough(maximum)
}

/// Returns a countable partial range from a lower bound.
///
/// Use the partial range up to operator (`...`) to create a range of any type
/// that conforms to the `Strideable` protocol with an associated integer
/// `Stride` type, such as any of the standard library's integer types. This
/// example creates a `CountablePartialRangeFrom<Int>` from 5 up.
///
/// let fiveOrMore = 5...
/// print(fiveOrMore.contains(3)) // Prints "false"
/// print(fiveOrMore.contains(5)) // Prints "true"
///
/// - Parameters:
/// - minimum: The lower bound for the range.
@_transparent
public static postfix func ...(minimum: Self) -> PartialRangeFrom<Self> {
return PartialRangeFrom(minimum)
}
}

extension Strideable where Stride: SignedInteger {
/// Returns a countable partial range from a lower bound.
///
/// Use the partial range up to operator (`...`) to create a range of any type
/// that conforms to the `Strideable` protocol with an associated integer
/// `Stride` type, such as any of the standard library's integer types. This
/// example creates a `CountablePartialRangeFrom<Int>` from 5 up.
///
/// let fiveOrMore = 5...
/// print(fiveOrMore.contains(3)) // Prints "false"
/// print(fiveOrMore.contains(5)) // Prints "true"
///
/// You can use sequence methods on these partial ranges.
///
/// let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
/// let asciiTable = zip(0x41..., alphabet)
/// for (code, letter) in asciiTable { print(code, letter) }
///
/// Note that these sequences count up indefinitely. You should not use them
/// with algorithms such as `map` or `filter` that will try to read the entire
/// sequence eagerly. The upper limit for the sequence is determined by the
/// type of `Bound`. For example, `CountablePartialRangeFrom<Int>` will trap
/// when the sequences' next value would be above `Int.max`.
///
/// - Parameters:
/// - minimum: The lower bound for the range.
@_transparent
public static postfix func ...(minimum: Self) -> CountablePartialRangeFrom<Self> {
return CountablePartialRangeFrom(minimum)
}
}

extension _Indexable {
@_inlineable
public subscript<R: RangeExpression>(r: R) -> SubSequence where R.Bound == Index {
return self[r.relative(to: self)]
}
}
extension _MutableIndexable {
@_inlineable
public subscript<R: RangeExpression>(r: R) -> SubSequence where R.Bound == Index {
get {
return self[r.relative(to: self)]
}
set {
self[r.relative(to: self)] = newValue
}
}
}

// swift-3-indexing-model: this is not really a proper rename
@available(*, unavailable, renamed: "IndexingIterator")
public struct RangeGenerator<Bound> {}
Expand Down
38 changes: 9 additions & 29 deletions stdlib/public/core/RangeReplaceableCollection.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -890,28 +890,7 @@ extension RangeReplaceableCollection where SubSequence == Self {
}
}

% for Range in ['CountableRange', 'ClosedRange', 'CountableClosedRange']:
extension RangeReplaceableCollection
% if 'Countable' in Range:
where
Index : Strideable, Index.Stride : SignedInteger
% end
{
/// Returns a half-open range denoting the same positions as `r`.
@_versioned
@_inlineable
internal func _makeHalfOpen(_ r: ${Range}<Index>) -> Range<Index> {
// The upperBound of the result depends on whether `r` is a closed
// range.
% if 'Closed' in Range:
return Range(uncheckedBounds: (
lower: r.lowerBound,
upper: index(after: r.upperBound)))
% else:
return Range(r)
% end
}

extension RangeReplaceableCollection {
/// Replaces the specified subrange of elements with the given collection.
///
/// This method has the effect of removing the specified range of elements
Expand Down Expand Up @@ -949,11 +928,11 @@ extension RangeReplaceableCollection
/// contents of `newElements` to the collection, the complexity is O(*n*),
/// where *n* is the length of `newElements`.
@_inlineable
public mutating func replaceSubrange<C>(
_ subrange: ${Range}<Index>,
public mutating func replaceSubrange<C: Collection, R: RangeExpression>(
_ subrange: R,
with newElements: C
) where C : Collection, C.Iterator.Element == Iterator.Element {
self.replaceSubrange(_makeHalfOpen(subrange), with: newElements)
) where C.Iterator.Element == Iterator.Element, R.Bound == Index {
self.replaceSubrange(subrange.relative(to: self), with: newElements)
}

/// Removes the elements in the specified subrange from the collection.
Expand All @@ -975,11 +954,12 @@ extension RangeReplaceableCollection
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@_inlineable
public mutating func removeSubrange(_ bounds: ${Range}<Index>) {
removeSubrange(_makeHalfOpen(bounds))
public mutating func removeSubrange<R: RangeExpression>(
_ bounds: R
) where R.Bound == Index {
removeSubrange(bounds.relative(to: self))
}
}
% end

extension RangeReplaceableCollection {
@_inlineable
Expand Down
31 changes: 2 additions & 29 deletions stdlib/public/core/StringRangeReplaceableCollection.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ extension String {
}
}

% for Range in ['Range', 'ClosedRange']:
/// Replaces the text within the specified bounds with the given characters.
///
/// Calling this method invalidates any existing indices for use with this
Expand All @@ -263,7 +262,7 @@ extension String {
/// removes text at the end of the string, the complexity is O(*n*), where
/// *n* is equal to `bounds.count`.
public mutating func replaceSubrange<C>(
_ bounds: ${Range}<Index>,
_ bounds: Range<Index>,
with newElements: C
) where C : Collection, C.Iterator.Element == Character {
withMutableCharacters {
Expand All @@ -272,27 +271,6 @@ extension String {
}
}

/// Replaces the text within the specified bounds with the given string.
///
/// Calling this method invalidates any existing indices for use with this
/// string.
///
/// - Parameters:
/// - bounds: The range of text to replace. The bounds of the range must be
/// valid indices of the string.
/// - newElements: The new text to add to the string.
///
/// - Complexity: O(*m*), where *m* is the combined length of the string and
/// `newElements`. If the call to `replaceSubrange(_:with:)` simply
/// removes text at the end of the string, the complexity is O(*n*), where
/// *n* is equal to `bounds.count`.
public mutating func replaceSubrange(
_ bounds: ${Range}<Index>, with newElements: String
) {
replaceSubrange(bounds, with: newElements.characters)
}
% end

/// Inserts a new character at the specified position.
///
/// Calling this method invalidates any existing indices for use with this
Expand Down Expand Up @@ -357,26 +335,21 @@ extension String {
}
}

% for Range in ['Range', 'ClosedRange']:
/// Removes the characters in the given range.
///
/// Calling this method invalidates any existing indices for use with this
/// string.
///
% if Range == 'ClosedRange':
/// - Parameter bounds: The range of the elements to remove. The upper and
/// lower bounds of `bounds` must be valid indices of the string and not
/// equal to the string's end index.
% else:
/// - Parameter bounds: The range of the elements to remove. The upper and
/// lower bounds of `bounds` must be valid indices of the string.
% end
public mutating func removeSubrange(_ bounds: ${Range}<Index>) {
public mutating func removeSubrange(_ bounds: Range<Index>) {
withMutableCharacters {
(v: inout CharacterView) in v.removeSubrange(bounds)
}
}
% end

/// Replaces this string with the empty string.
///
Expand Down