Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 109 additions & 18 deletions Sources/AsyncSequenceReader/AsyncReadUpToCountSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ extension AsyncIteratorProtocol {
/// If a complete array could not be collected, an error is thrown and the sequence should be considered finished.
/// - Parameter count: The number of elements to collect.
/// - Returns: A collection with exactly `count` elements, or `nil` if the sequence is finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect(_ count: Int) async throws -> [Element]? {
assert(count >= 0, "count must be larger than or equal to 0")
return try await collect(min: count, max: count)
Expand All @@ -25,7 +26,8 @@ extension AsyncIteratorProtocol {
/// - Parameter minCount: The minimum number of elements to collect.
/// - Parameter maxCount: The maximum number of elements to collect.
/// - Returns: A collection with at least `minCount` and at most `maxCount` elements, or `nil` if the sequence is finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect(min minCount: Int = 0, max maxCount: Int) async throws -> [Element]? {
precondition(minCount <= maxCount, "maxCount must be larger than or equal to minCount")
precondition(minCount >= 0, "minCount must be larger than or equal to 0")
Expand All @@ -50,7 +52,80 @@ extension AsyncIteratorProtocol {

return result
}
}

@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
extension AsyncIteratorProtocol where Failure == Never {
/// Asynchronously advances by the specified number of elements, or ends the sequence if there is no next element.
///
/// If a complete array could not be collected, an error is thrown and the sequence should be considered finished.
/// - Parameter count: The number of elements to collect.
/// - Returns: A collection with exactly `count` elements, or `nil` if the sequence is finished.
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect(_ count: Int) async throws(AsyncSequenceReaderError) -> [Element]? {
assert(count >= 0, "count must be larger than or equal to 0")
#if compiler(<6.1.3) || compiler(>=6.2)
return try await collect(min: count, max: count)
#else /// The above crashes the Swift 6.1.3 compiler. Make sure to inline it manually:
if count == 0 { return [] }

var result = [Element]()
result.reserveCapacity(count)

while let next = await _nextIsolated() {
result.append(next)

if result.count == count {
return result
}
}

guard !result.isEmpty else { return nil }

guard result.count >= count else {
throw AsyncSequenceReaderError.insufficientElements(minimum: count, actual: result.count)
}

return result
#endif
}

/// Asynchronously advances by the specified minimum number of elements, continuing until the specified maximum number of elements, or ends the sequence if there is no next element.
///
/// If a complete array larger than `minCount` could not be constructed, an error is thrown and the sequence should be considered finished.
/// - Parameter minCount: The minimum number of elements to collect.
/// - Parameter maxCount: The maximum number of elements to collect.
/// - Returns: A collection with at least `minCount` and at most `maxCount` elements, or `nil` if the sequence is finished.
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect(min minCount: Int = 0, max maxCount: Int) async throws(AsyncSequenceReaderError) -> [Element]? {
precondition(minCount <= maxCount, "maxCount must be larger than or equal to minCount")
precondition(minCount >= 0, "minCount must be larger than or equal to 0")
if maxCount == 0 { return [] }

var result = [Element]()
result.reserveCapacity(minCount)

while let next = await _nextIsolated() {
result.append(next)

if result.count == maxCount {
return result
}
}

guard !result.isEmpty else { return nil }

guard result.count >= minCount else {
throw AsyncSequenceReaderError.insufficientElements(minimum: minCount, actual: result.count)
}

return result
}
}

extension AsyncIteratorProtocol {
/// Collect the specified number of elements into a sequence, and transform it using the provided closure.
///
/// In this example, an asynchronous sequence of Strings encodes sentences by prefixing each word sequence with a number.
Expand Down Expand Up @@ -81,11 +156,15 @@ extension AsyncIteratorProtocol {
/// - Parameter count: The number of elements the `sequenceTransform` closure will have access to.
/// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`.
/// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
public mutating func collect<Transformed>(
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect<
Transformed,
TransformFailure
>(
_ count: Int,
sequenceTransform: sending (sending AsyncReadUpToCountSequence<Self>) async throws -> Transformed
) async throws -> Transformed? {
sequenceTransform: sending (sending AsyncReadUpToCountSequence<Self>) async throws(TransformFailure) -> Transformed
) async throws(TransformFailure) -> Transformed? {
assert(count >= 0, "count must be larger than or equal to 0")
return try await collect(min: count, max: count, sequenceTransform: sequenceTransform)
}
Expand Down Expand Up @@ -123,12 +202,16 @@ extension AsyncIteratorProtocol {
/// - Parameter maxCount: The maximum number of elements the `sequenceTransform` closure will have access to.
/// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`.
/// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
public mutating func collect<Transformed>(
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect<
Transformed,
TransformFailure
>(
min minCount: Int = 1,
max maxCount: Int,
sequenceTransform: sending (sending AsyncReadUpToCountSequence<Self>) async throws -> Transformed
) async throws -> Transformed? {
sequenceTransform: sending (sending AsyncReadUpToCountSequence<Self>) async throws(TransformFailure) -> Transformed
) async throws(TransformFailure) -> Transformed? {
/// It is unsafe to read ahead in this case, so exit early if we know we won't need to read.
if maxCount == 0 { return nil }
assert(minCount >= 1, "minCount must be larger than or equal to 1, or the first value risks getting dropped")
Expand Down Expand Up @@ -167,11 +250,15 @@ extension AsyncBufferedIterator {
/// - Parameter count: The number of elements the `sequenceTransform` closure will have access to.
/// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`.
/// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
public mutating func collect<Transformed>(
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect<
Transformed,
TransformFailure
>(
_ count: Int,
sequenceTransform: sending (sending AsyncReadUpToCountSequence<BaseIterator>) async throws -> Transformed
) async throws -> Transformed? {
sequenceTransform: sending (sending AsyncReadUpToCountSequence<BaseIterator>) async throws(TransformFailure) -> Transformed
) async throws(TransformFailure) -> Transformed? {
assert(count >= 0, "count must be larger than 0")
return try await collect(min: count, max: count, sequenceTransform: sequenceTransform)
}
Expand Down Expand Up @@ -207,12 +294,16 @@ extension AsyncBufferedIterator {
/// - Parameter maxCount: The maximum number of elements the `sequenceTransform` closure will have access to.
/// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`.
/// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished.
/// - Throws: `AsyncSequenceReaderError.insufficientElements` if a complete byte sequence could not be returned by the time the sequence ended.
public mutating func collect<Transformed>(
/// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended.
@inlinable
public mutating func collect<
Transformed,
TransformFailure: Error
>(
min minCount: Int = 0,
max maxCount: Int,
sequenceTransform: sending (sending AsyncReadUpToCountSequence<BaseIterator>) async throws -> Transformed
) async throws -> Transformed? {
sequenceTransform: sending (sending AsyncReadUpToCountSequence<BaseIterator>) async throws(TransformFailure) -> Transformed
) async throws(TransformFailure) -> Transformed? {
try await transform(with: sequenceTransform) { .init($0, minCount: minCount, maxCount: maxCount) }
}
}
Expand Down
Loading
Loading