Skip to content

Commit

Permalink
Revert "Revert "[stdlib] One-sided ranges and RangeExpression (#8710)""
Browse files Browse the repository at this point in the history
  • Loading branch information
airspeedswift committed Apr 30, 2017
1 parent f45196d commit 3890376
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 100 deletions.
3 changes: 3 additions & 0 deletions stdlib/public/core/Policy.swift
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
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
)

This comment has been minimized.

Copy link
@dabrahams

dabrahams May 13, 2017

Collaborator

The line above creates a parenthesis imbalance in the file when it is parsed purely as swift, confusing indentation in my editor at least, which took a long time to track down. It would be better to duplicate the parenthesis in each branch of the % if

}
}

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
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
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

1 comment on commit 3890376

@dabrahams
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.