From b752a54700cf694d37b214d747bdf8c65463a55d Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 16 Apr 2026 12:34:08 -0500 Subject: [PATCH] Switch to using a deque for DSLList storage --- .../ByteCodeGen+DSLList.swift | 32 +- .../DequeModule/CollectionHelpers.swift | 505 ++++++ .../DequeModule/Deque/Deque+Codable.swift | 53 + .../DequeModule/Deque/Deque+Collection.swift | 918 +++++++++++ .../Deque/Deque+CustomReflectable.swift | 21 + .../Deque/Deque+Descriptions.swift | 28 + .../DequeModule/Deque/Deque+Equatable.swift | 34 + .../Deque+ExpressibleByArrayLiteral.swift | 29 + .../DequeModule/Deque/Deque+Extras.swift | 190 +++ .../DequeModule/Deque/Deque+Hashable.swift | 26 + .../DequeModule/Deque/Deque+Testing.swift | 88 + .../DequeModule/Deque/Deque._Storage.swift | 222 +++ .../Deque/Deque._UnsafeHandle.swift | 831 ++++++++++ .../DequeModule/Deque/Deque.swift | 109 ++ .../DequeModule/Deque/_DequeBuffer.swift | 51 + .../Deque/_DequeBufferHeader.swift | 51 + .../DequeModule/_DequeSlot.swift | 80 + .../DequeModule/_UnsafeDequeHandle.swift | 1458 +++++++++++++++++ .../DequeModule/_UnsafeDequeSegments.swift | 292 ++++ .../_StringProcessing/LiteralPrinter.swift | 12 +- .../_StringProcessing/PrintAsPattern.swift | 4 +- .../Regex/ASTConversion.swift | 2 +- Sources/_StringProcessing/Regex/DSLList.swift | 10 +- Sources/_StringProcessing/Regex/DSLTree.swift | 6 +- 24 files changed, 5019 insertions(+), 33 deletions(-) create mode 100644 Sources/_StringProcessing/DequeModule/CollectionHelpers.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Codable.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Collection.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+CustomReflectable.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Descriptions.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Equatable.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+ExpressibleByArrayLiteral.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Extras.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Hashable.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque+Testing.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque._Storage.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque._UnsafeHandle.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/Deque.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/_DequeBuffer.swift create mode 100644 Sources/_StringProcessing/DequeModule/Deque/_DequeBufferHeader.swift create mode 100644 Sources/_StringProcessing/DequeModule/_DequeSlot.swift create mode 100644 Sources/_StringProcessing/DequeModule/_UnsafeDequeHandle.swift create mode 100644 Sources/_StringProcessing/DequeModule/_UnsafeDequeSegments.swift diff --git a/Sources/_StringProcessing/ByteCodeGen+DSLList.swift b/Sources/_StringProcessing/ByteCodeGen+DSLList.swift index 4abf2b90..4f6278af 100644 --- a/Sources/_StringProcessing/ByteCodeGen+DSLList.swift +++ b/Sources/_StringProcessing/ByteCodeGen+DSLList.swift @@ -49,7 +49,7 @@ fileprivate extension Compiler.ByteCodeGen { /// In particular, non-required groups and option-setting groups are /// inconclusive about where they can match. private mutating func _canOnlyMatchAtStartImpl( - _ list: inout ArraySlice + _ list: inout Deque.SubSequence ) -> Bool? { guard let node = list.popFirst() else { return false } switch node { @@ -164,10 +164,10 @@ fileprivate extension Compiler.ByteCodeGen { } mutating func emitAlternationGen( - _ elements: inout ArraySlice, + _ elements: inout Deque.SubSequence, alternationCount: Int, withBacktracking: Bool, - _ body: (inout Compiler.ByteCodeGen, inout ArraySlice) throws -> Void + _ body: (inout Compiler.ByteCodeGen, inout Deque.SubSequence) throws -> Void ) rethrows { // Alternation: p0 | p1 | ... | pn // save next_p1 @@ -201,7 +201,7 @@ fileprivate extension Compiler.ByteCodeGen { } mutating func emitAlternation( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, alternationCount count: Int ) throws { try emitAlternationGen(&list, alternationCount: count, withBacktracking: true) { @@ -209,7 +209,7 @@ fileprivate extension Compiler.ByteCodeGen { } } - mutating func emitPositiveLookahead(_ list: inout ArraySlice) throws { + mutating func emitPositiveLookahead(_ list: inout Deque.SubSequence) throws { /* save(restoringAt: success) save(restoringAt: intercept) @@ -238,7 +238,7 @@ fileprivate extension Compiler.ByteCodeGen { builder.label(success) } - mutating func emitNegativeLookahead(_ list: inout ArraySlice) throws { + mutating func emitNegativeLookahead(_ list: inout Deque.SubSequence) throws { /* save(restoringAt: success) save(restoringAt: intercept) @@ -269,7 +269,7 @@ fileprivate extension Compiler.ByteCodeGen { mutating func emitLookaround( _ kind: (forwards: Bool, positive: Bool), - _ list: inout ArraySlice + _ list: inout Deque.SubSequence ) throws { guard kind.forwards else { throw Unsupported("backwards assertions") @@ -282,7 +282,7 @@ fileprivate extension Compiler.ByteCodeGen { } mutating func emitAtomicNoncapturingGroup( - _ list: inout ArraySlice + _ list: inout Deque.SubSequence ) throws { /* save(continuingAt: success) @@ -315,7 +315,7 @@ fileprivate extension Compiler.ByteCodeGen { mutating func emitNoncapturingGroup( _ kind: AST.Group.Kind, - _ list: inout ArraySlice + _ list: inout Deque.SubSequence ) throws { assert(!kind.isCapturing) @@ -351,7 +351,7 @@ fileprivate extension Compiler.ByteCodeGen { } } - func _guaranteesForwardProgressImpl(_ list: ArraySlice, position: inout Int) -> Bool { + func _guaranteesForwardProgressImpl(_ list: Deque.SubSequence, position: inout Int) -> Bool { guard position < list.endIndex else { return false } let node = list[position] position += 1 @@ -404,7 +404,7 @@ fileprivate extension Compiler.ByteCodeGen { } } - func guaranteesForwardProgress(_ list: ArraySlice) -> Bool { + func guaranteesForwardProgress(_ list: Deque.SubSequence) -> Bool { var pos = list.startIndex return _guaranteesForwardProgressImpl(list, position: &pos) } @@ -412,7 +412,7 @@ fileprivate extension Compiler.ByteCodeGen { mutating func emitQuantification( _ amount: AST.Quantification.Amount, _ kind: DSLTree.QuantificationKind, - _ list: inout ArraySlice + _ list: inout Deque.SubSequence ) throws { let updatedKind = kind.applying(options: options) @@ -652,7 +652,7 @@ fileprivate extension Compiler.ByteCodeGen { /// - single grapheme consumgin built in character classes /// - .any, .anyNonNewline, .dot mutating func tryEmitFastQuant( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, _ kind: AST.Quantification.Kind, _ minTrips: Int, _ maxExtraTrips: Int? @@ -736,7 +736,7 @@ fileprivate extension Compiler.ByteCodeGen { } mutating func emitConcatenation( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, componentCount: Int ) throws { // Unlike the tree-based bytecode generator, in a DSLList concatenations @@ -747,7 +747,7 @@ fileprivate extension Compiler.ByteCodeGen { } @discardableResult - mutating func emitNode(_ list: inout ArraySlice) throws -> ValueRegister? { + mutating func emitNode(_ list: inout Deque.SubSequence) throws -> ValueRegister? { guard let node = list.popFirst() else { return nil } switch node { @@ -845,7 +845,7 @@ fileprivate extension Compiler.ByteCodeGen { extension Compiler.ByteCodeGen { mutating func skipNode( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, preservingCaptures: Bool = true ) throws { guard let node = list.popFirst() else { return } diff --git a/Sources/_StringProcessing/DequeModule/CollectionHelpers.swift b/Sources/_StringProcessing/DequeModule/CollectionHelpers.swift new file mode 100644 index 00000000..8c772ce0 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/CollectionHelpers.swift @@ -0,0 +1,505 @@ +// +// CollectionHelpers.swift +// swift-experimental-string-processing +// +// Created by Nate Cook on 4/16/26. +// + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `UnsafeBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } + + @inlinable + @inline(__always) + static var _empty: Self { + .init(start: nil, count: 0) + } +} + +extension UnsafeBufferPointer where Element: ~Copyable { + @_alwaysEmitIntoClient + func _extracting(uncheckedFrom start: Int, to end: Int) -> Self { + guard let base = self.baseAddress else { + return Self(_empty: ()) + } + return Self(start: base + start, count: end - start) + } + + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing all but the given number of initial + /// elements. + /// + /// If the number of elements to drop exceeds the number of elements in the + /// buffer, the result is an empty buffer. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to drop. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(droppingFirst maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let cut = Swift.min(maxLength, count) + return Self(start: baseAddress?.advanced(by: cut), count: count &- cut) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } + + /// Returns a buffer pointer containing all but the given number of trailing + /// elements. + /// + /// If the number of elements to drop exceeds the number of elements in the + /// buffer, the result is an empty buffer. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to drop. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(droppingLast maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let newCount = count &- Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } + + @inlinable + @inline(__always) + static var _empty: Self { + .init(start: nil, count: 0) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @_alwaysEmitIntoClient + func _extracting(uncheckedFrom start: Int, to end: Int) -> Self { + guard let base = self.baseAddress else { + return Self(_empty: ()) + } + return Self(start: base + start, count: end - start) + } + + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing all but the given number of initial + /// elements. + /// + /// If the number of elements to drop exceeds the number of elements in the + /// buffer, the result is an empty buffer. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to drop. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(droppingFirst maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let cut = Swift.min(maxLength, count) + return Self(start: baseAddress?.advanced(by: cut), count: count &- cut) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } + + /// Returns a buffer pointer containing all but the given number of trailing + /// elements. + /// + /// If the number of elements to drop exceeds the number of elements in the + /// buffer, the result is an empty buffer. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to drop. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + func _extracting(droppingLast maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let newCount = count &- Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @_alwaysEmitIntoClient + @inline(__always) + mutating func _trim(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a prefix of negative length") + let cut = Swift.min(maxLength, count) + guard cut > 0 else { return .init(start: nil, count: 0) } + let oldStart = baseAddress.unsafelyUnwrapped + self = Self(start: oldStart + cut, count: count - cut) + return Self(start: baseAddress, count: cut) + } + + @_alwaysEmitIntoClient + @inline(__always) + mutating func _trim(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Cannot have a suffix of negative length") + let cut = Swift.min(maxLength, count) + guard cut > 0 else { return .init(start: nil, count: 0) } + self = .init(start: baseAddress, count: count &- cut) + return Self(start: baseAddress.unsafelyUnwrapped + (count &- cut), count: cut) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `source` must be fully initialized. + /// + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @inlinable + func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } + + @inlinable + func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + _initializePrefix(copying: UnsafeBufferPointer(source)) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @available(SwiftStdlib 5.7, *) + @inlinable + func _initializePrefix(copying source: Span) -> Int { + source.withUnsafeBufferPointer { self._initializePrefix(copying: $0) } + } +#endif + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = _initializePrefix(copying: source) + self = self.extracting(i...) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftStdlib 5.7, *) + @inlinable + mutating func _initializeAndDropPrefix(copying span: Span) { + span.withUnsafeBufferPointer { buffer in + self._initializeAndDropPrefix(copying: buffer) + } + } +#endif +} + +extension UnsafeMutableBufferPointer { + @inlinable + func initialize(fromContentsOf source: Self) -> Index { + guard source.count > 0 else { return 0 } + precondition( + source.count <= self.count, + "buffer cannot contain every element from source.") + baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) + return source.count + } + + @inlinable + func initialize(fromContentsOf source: Slice) -> Index { + let sourceCount = source.count + guard sourceCount > 0 else { return 0 } + precondition( + sourceCount <= self.count, + "buffer cannot contain every element from source.") + baseAddress.unsafelyUnwrapped.initialize( + from: source.base.baseAddress.unsafelyUnwrapped + source.startIndex, + count: sourceCount) + return sourceCount + } +} + +extension Slice { + @inlinable @inline(__always) + func initialize( + fromContentsOf source: UnsafeMutableBufferPointer + ) -> Index + where Base == UnsafeMutableBufferPointer + { + let target = UnsafeMutableBufferPointer(rebasing: self) + let i = target.initialize(fromContentsOf: source) + return self.startIndex + i + } + + @inlinable @inline(__always) + func initialize( + fromContentsOf source: Slice> + ) -> Index + where Base == UnsafeMutableBufferPointer + { + let target = UnsafeMutableBufferPointer(rebasing: self) + let i = target.initialize(fromContentsOf: source) + return self.startIndex + i + } +} + +extension UnsafeMutableBufferPointer { + @inlinable @inline(__always) + func initializeAll( + fromContentsOf source: C + ) where C.Element == Element { + let i = self.initialize(fromContentsOf: source) + assert(i == self.endIndex) + } + + @inlinable @inline(__always) + func initializeAll(fromContentsOf source: Self) { + let i = self.initialize(fromContentsOf: source) + assert(i == self.endIndex) + } + + @inlinable @inline(__always) + func initializeAll(fromContentsOf source: Slice) { + let i = self.initialize(fromContentsOf: source) + assert(i == self.endIndex) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable @inline(__always) + func moveInitializeAll(fromContentsOf source: Self) { + let i = self.moveInitialize(fromContentsOf: source) + assert(i == self.endIndex) + } +} + +extension UnsafeMutableBufferPointer { + @inlinable @inline(__always) + func moveInitializeAll(fromContentsOf source: Slice) { + let i = self.moveInitialize(fromContentsOf: source) + assert(i == self.endIndex) + } +} + +extension Slice { + @inlinable @inline(__always) + func initializeAll( + fromContentsOf source: C + ) where Base == UnsafeMutableBufferPointer { + let i = self.initialize(fromContentsOf: source) + assert(i == self.endIndex) + } + + @inlinable @inline(__always) + func initializeAll( + fromContentsOf source: UnsafeMutableBufferPointer + ) where Base == UnsafeMutableBufferPointer { + let target = UnsafeMutableBufferPointer(rebasing: self) + target.initializeAll(fromContentsOf: source) + } + + @inlinable @inline(__always) + func initializeAll( + fromContentsOf source: Slice> + ) where Base == UnsafeMutableBufferPointer { + let target = UnsafeMutableBufferPointer(rebasing: self) + target.initializeAll(fromContentsOf: source) + } + + @inlinable @inline(__always) + func moveInitializeAll( + fromContentsOf source: UnsafeMutableBufferPointer + ) where Base == UnsafeMutableBufferPointer { + let target = UnsafeMutableBufferPointer(rebasing: self) + target.moveInitializeAll(fromContentsOf: source) + } + + @inlinable @inline(__always) + func moveInitializeAll( + fromContentsOf source: Slice> + ) where Base == UnsafeMutableBufferPointer { + let target = UnsafeMutableBufferPointer(rebasing: self) + target.moveInitializeAll(fromContentsOf: source) + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Codable.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Codable.swift new file mode 100644 index 00000000..c56b86af --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Codable.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +#if !$Embedded +extension Deque: Encodable where Element: Encodable { + /// Encodes the elements of this deque into the given encoder in an unkeyed + /// container. + /// + /// This function throws an error if any values are invalid for the given + /// encoder's format. + /// + /// - Parameter encoder: The encoder to write data to. + @inlinable + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + for element in self { + try container.encode(element) + } + } +} + +extension Deque: Decodable where Element: Decodable { + /// Creates a new deque by decoding from the given decoder. + /// + /// This initializer throws an error if reading from the decoder fails, or + /// if the data read is corrupted or otherwise invalid. + /// + /// - Parameter decoder: The decoder to read data from. + @inlinable + public init(from decoder: Decoder) throws { + self.init() + + var container = try decoder.unkeyedContainer() + if let count = container.count { + self.reserveCapacity(count) + } + while !container.isAtEnd { + let element = try container.decode(Element.self) + self.append(element) + } + } +} +#endif diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Collection.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Collection.swift new file mode 100644 index 00000000..b68f5e66 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Collection.swift @@ -0,0 +1,918 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque: Sequence { + // Implementation note: we could also use the default `IndexingIterator` here. + // This custom implementation performs direct storage access to eliminate any + // and all index validation overhead. It also optimizes away repeated + // conversions from indices to storage slots. + + /// An iterator over the members of a deque. + @frozen + public struct Iterator: IteratorProtocol { + @usableFromInline + internal var _storage: Deque._Storage + + @usableFromInline + internal var _nextSlot: _Slot + + @usableFromInline + internal var _endSlot: _Slot + + @inlinable + internal init(_storage: Deque._Storage, start: _Slot, end: _Slot) { + self._storage = _storage + self._nextSlot = start + self._endSlot = end + } + + @inlinable + internal init(_base: Deque) { + self = _base._storage.read { handle in + let start = handle.startSlot + let end = Swift.min(start.advanced(by: handle.count), handle.limSlot) + return Self(_storage: _base._storage, start: start, end: end) + } + } + + @inlinable + internal init(_base: Deque, from index: Int) { + self = _base._storage.read { handle in + assert(index >= 0 && index <= handle.count) + let start = handle.slot(forOffset: index) + if index == handle.count { + return Self(_storage: _base._storage, start: start, end: start) + } + var end = handle.endSlot + if start >= end { end = handle.limSlot } + return Self(_storage: _base._storage, start: start, end: end) + } + } + + @inlinable + @inline(never) + internal mutating func _swapSegment() -> Bool { + assert(_nextSlot == _endSlot) + return _storage.read { handle in + let end = handle.endSlot + if end == .zero || end == _nextSlot { + return false + } + _endSlot = end + _nextSlot = .zero + return true + } + } + + /// Advances to the next element and returns it, or `nil` if no next element + /// exists. + /// + /// Once `nil` has been returned, all subsequent calls return `nil`. + @inlinable + public mutating func next() -> Element? { + if _nextSlot == _endSlot { + guard _swapSegment() else { return nil } + } + assert(_nextSlot < _endSlot) + let slot = _nextSlot + _nextSlot = _nextSlot.advanced(by: 1) + return _storage.read { handle in + return handle.ptr(at: slot).pointee + } + } + } + + /// Returns an iterator over the elements of the deque. + /// + /// - Complexity: O(1) + @inlinable + public func makeIterator() -> Iterator { + Iterator(_base: self) + } + + @inlinable + public __consuming func _copyToContiguousArray() -> ContiguousArray { + ContiguousArray(unsafeUninitializedCapacity: _storage.count) { target, count in + _storage.read { source in + let segments = source.segments() + let c = segments.first.count + target[.. + ) -> (Iterator, UnsafeMutableBufferPointer.Index) { + _storage.read { source in + let segments = source.segments() + let c1 = Swift.min(segments.first.count, target.count) + target[.. c1, let second = segments.second else { + return (Iterator(_base: self, from: c1), c1) + } + let c2 = Swift.min(second.count, target.count - c1) + target[c1 ..< c1 + c2].initializeAll(fromContentsOf: second.prefix(c2)) + return (Iterator(_base: self, from: c1 + c2), c1 + c2) + } + } + + /// Call `body(b)`, where `b` is an unsafe buffer pointer to the deque's + /// contiguous storage, if available. If the deque's contents aren't stored + /// contiguously, `body` is not called and `nil` is returned. The supplied + /// buffer pointer is only valid for the duration of the call. + /// + /// Often, the optimizer can eliminate bounds- and uniqueness-checks within an + /// algorithm, but when that fails, invoking the same algorithm on the unsafe + /// buffer supplied to `body` lets you trade safety for speed. + /// + /// - Parameters: + /// - body: The function to invoke. + /// + /// - Returns: The value returned by `body`, or `nil` if `body` wasn't called. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + return try _storage.read { handle in + let endSlot = handle.startSlot.advanced(by: handle.count) + guard endSlot.position <= handle.capacity else { return nil } + return try body(handle.buffer(for: handle.startSlot ..< endSlot)) + } + } +} + +extension Deque.Iterator: @unchecked Sendable where Element: Sendable {} + +extension Deque: RandomAccessCollection { + public typealias Index = Int + public typealias SubSequence = Slice + public typealias Indices = Range + + /// The number of elements in the deque. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _storage.count } + + /// The position of the first element in a nonempty deque. + /// + /// For an instance of `Deque`, `startIndex` is always zero. If the deque is + /// empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + /// The deque's "past the end" position—that is, the position one greater than + /// the last valid subscript argument. + /// + /// For an instance of `Deque`, `endIndex` is always equal to its `count`. If + /// the deque is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { count } + + /// The indices that are valid for subscripting this deque, in ascending order. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var indices: Range { 0 ..< count } + + /// Returns the position immediately after the given index. + /// + /// - Parameter `i`: A valid index of the deque. `i` must be less than + /// `endIndex`. + /// + /// - Returns: The next valid index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { + // Note: Like `Array`, index manipulation methods on deques don't trap on + // invalid indices. (Indices are still validated on element access.) + return i + 1 + } + + /// Replaces the given index with its successor. + /// + /// - Parameter `i`: A valid index of the deque. `i` must be less than + /// `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { + // Note: Like `Array`, index manipulation methods on deques + // don't trap on invalid indices. + // (Indices are still validated on element access.) + i += 1 + } + + /// Returns the position immediately before the given index. + /// + /// - Parameter `i`: A valid index of the deque. `i` must be greater than + /// `startIndex`. + /// + /// - Returns: The preceding valid index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { + // Note: Like `Array`, index manipulation methods on deques don't trap on + // invalid indices. (Indices are still validated on element access.) + return i - 1 + } + + /// Replaces the given index with its predecessor. + /// + /// - Parameter `i`: A valid index of the deque. `i` must be greater than `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { + // Note: Like `Array`, index manipulation methods on deques don't trap on + // invalid indices. (Indices are still validated on element access.) + i -= 1 + } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection. + /// + /// - Parameters: + /// - i: A valid index of the deque. + /// - distance: The distance by which to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls + /// to `index(after:)`. If `distance` is negative, this is the same value + /// as the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + // Note: Like `Array`, index manipulation methods on deques don't trap on + // invalid indices. (Indices are still validated on element access.) + return i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// - Parameters: + /// - i: A valid index of the array. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the deque to use as a limit. + /// If `distance > 0`, then `limit` has no effect it is less than `i`. + /// Likewise, if `distance < 0`, then `limit` has no effect if it is + /// greater than `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + // Note: Like `Array`, index manipulation methods on deques + // don't trap on invalid indices. + // (Indices are still validated on element access.) + let l = limit - i + if distance > 0 ? l >= 0 && l < distance : l <= 0 && distance < l { + return nil + } + return i + distance + } + + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. + /// + /// - Returns: The distance between `start` and `end`. If `end` is equal to + /// `start`, the result is zero. Otherwise the result is positive if `end` + /// is greater than `start`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + // Note: Like `Array`, index manipulation method on deques + // don't trap on invalid indices. + // (Indices are still validated on element access.) + return end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameters: + /// - index: The position of the element to access. `index` must be greater + /// than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: Reading an element from a deque is O(1). Writing is O(1) + /// unless the deque's storage is shared with another deque, in which case + /// writing is O(`count`). + @inlinable + public subscript(index: Int) -> Element { + get { + precondition(index >= 0 && index < count, "Index out of bounds") + return _storage.read { $0.ptr(at: $0.slot(forOffset: index)).pointee } + } + set { + precondition(index >= 0 && index < count, "Index out of bounds") + _storage.ensureUnique() + _storage.update { handle in + let slot = handle.slot(forOffset: index) + handle.ptr(at: slot).pointee = newValue + } + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + precondition(index >= 0 && index < count, "Index out of bounds") + var (slot, value) = _prepareForModify(at: index) + defer { + _finalizeModify(slot, value) + } + yield &value + } + } + + @inlinable + internal mutating func _prepareForModify(at index: Int) -> (_Slot, Element) { + _storage.ensureUnique() + // We technically aren't supposed to escape storage pointers out of a + // managed buffer, so we escape a `(slot, value)` pair instead, leaving + // the corresponding slot temporarily uninitialized. + return _storage.update { handle in + let slot = handle.slot(forOffset: index) + return (slot, handle.ptr(at: slot).move()) + } + } + + @inlinable + internal mutating func _finalizeModify(_ slot: _Slot, _ value: Element) { + _storage.update { handle in + handle.ptr(at: slot).initialize(to: value) + } + } + + /// Accesses a contiguous subrange of the deque's elements. + /// + /// - Parameters: + /// - bounds: A range of the deque's indices. The bounds of the range must + /// be valid indices of the deque (including the `endIndex`). + /// + /// The accessed slice uses the same indices for the same elements as the + /// original collection. + @inlinable + public subscript(bounds: Range) -> Slice { + get { + precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Invalid bounds") + return Slice(base: self, bounds: bounds) + } + set(source) { + precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Invalid bounds") + self.replaceSubrange(bounds, with: source) + } + } +} + +extension Deque: MutableCollection { + /// Exchanges the values at the specified indices of the collection. + /// + /// Both parameters must be valid indices of the collection and not equal to + /// `endIndex`. Passing the same index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition(i >= 0 && i < count, "Index out of bounds") + precondition(j >= 0 && j < count, "Index out of bounds") + _storage.ensureUnique() + _storage.update { handle in + let slot1 = handle.slot(forOffset: i) + let slot2 = handle.slot(forOffset: j) + handle.mutableBuffer.swapAt(slot1.position, slot2.position) + } + } + + // FIXME: Implement `partition(by:)` by making storage contiguous, + // and partitioning that. + + /// Call `body(b)`, where `b` is an unsafe buffer pointer to the deque's + /// mutable contiguous storage. If the deque's contents aren't stored + /// contiguously, `body` is not called and `nil` is returned. The supplied + /// buffer pointer is only valid for the duration of the call. + /// + /// Often, the optimizer can eliminate bounds- and uniqueness-checks within an + /// algorithm, but when that fails, invoking the same algorithm on the unsafe + /// buffer supplied to `body` lets you trade safety for speed. + /// + /// - Parameters: + /// - body: The function to invoke. + /// + /// - Returns: The value returned by `body`, or `nil` if `body` wasn't called. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. (Not counting the call to + /// `body`.) + @inlinable + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + _storage.ensureUnique() + return try _storage.update { handle in + let endSlot = handle.startSlot.advanced(by: handle.count) + guard endSlot.position <= handle.capacity else { + // FIXME: Rotate storage such that it becomes contiguous. + return nil + } + let original = handle.mutableBuffer(for: handle.startSlot ..< endSlot) + var extract = original + defer { + precondition(extract.baseAddress == original.baseAddress && extract.count == original.count, + "Closure must not replace the provided buffer") + } + return try body(&extract) + } + } + + @inlinable + public mutating func _withUnsafeMutableBufferPointerIfSupported( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return try withContiguousMutableStorageIfAvailable(body) + } +} + +extension Deque: RangeReplaceableCollection { + /// Creates a new, empty deque. + /// + /// This is equivalent to initializing with an empty array literal. + /// For example: + /// + /// let deque1 = Deque() + /// print(deque1.isEmpty) // true + /// + /// let deque2: Deque = [] + /// print(deque2.isEmpty) // true + /// + /// - Complexity: O(1) + @inlinable + public init() { + _storage = _Storage() + } + + /// Reserves enough space to store the specified number of elements. + /// + /// If you are adding a known number of elements to a deque, use this method + /// to avoid multiple reallocations. It ensures that the deque has unique + /// storage, with space allocated for at least the requested number of + /// elements. + /// + /// - Parameters: + /// - minimumCapacity: The requested number of elements to store. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reserveCapacity(_ minimumCapacity: Int) { + _storage.ensureUnique(minimumCapacity: minimumCapacity, linearGrowth: true) + } + + /// Replaces a range of elements with the elements in the specified + /// collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the deque and inserting the new elements at the same location. The + /// number of new elements need not match the number of elements being + /// removed. + /// + /// - Parameters: + /// - subrange: The subrange of the deque to replace. The bounds of the + /// subrange must be valid indices of the deque (including the + /// `endIndex`). + /// - newElements: The new elements to add to the deque. + /// + /// - Complexity: O(`self.count + newElements.count`). If the operation needs + /// to change the size of the deque, it minimizes the number of existing + /// items that need to be moved by shifting elements either before or after + /// `subrange`. + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + with newElements: __owned some Collection + ) { + precondition(subrange.lowerBound >= 0 && subrange.upperBound <= count, "Index range out of bounds") + let removalCount = subrange.count + let insertionCount = newElements.count + let deltaCount = insertionCount - removalCount + _storage.ensureUnique(minimumCapacity: count + deltaCount) + + let replacementCount = Swift.min(removalCount, insertionCount) + let targetCut = subrange.lowerBound + replacementCount + let sourceCut = newElements.index(newElements.startIndex, offsetBy: replacementCount) + + _storage.update { target in + target.uncheckedReplaceInPlace( + inOffsets: subrange.lowerBound ..< targetCut, + with: newElements[.. 0 { + target.uncheckedInsert( + contentsOf: newElements[sourceCut...], + count: deltaCount, + atOffset: targetCut) + } + } + } + + /// Creates a new deque containing the specified number of a single, repeated + /// value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the element. `count` must be zero + /// or greater. + /// + /// - Complexity: O(`count`) + @inlinable + public init(repeating repeatedValue: Element, count: Int) { + precondition(count >= 0) + self.init(minimumCapacity: count) + _storage.update { handle in + assert(handle.startSlot == .zero) + if count > 0 { + handle.ptr(at: .zero).initialize(repeating: repeatedValue, count: count) + } + handle.count = count + } + } + + /// Creates a deque containing the elements of a sequence. + /// + /// - Parameters: + /// - elements: The sequence of elements to turn into a deque. + /// + /// - Complexity: O(*n*), where *n* is the number of elements in the sequence. + @inlinable + public init(_ elements: some Sequence) { + self.init() + self.append(contentsOf: elements) + } + + /// Creates a deque containing the elements of a collection. + /// + /// - Parameters: + /// - elements: The collection of elements to turn into a deque. + /// + /// - Complexity: O(`elements.count`) + @inlinable + public init(_ elements: some Collection) { + let c = elements.count + guard c > 0 else { _storage = _Storage(); return } + self._storage = _Storage(minimumCapacity: c) + _storage.update { handle in + assert(handle.startSlot == .zero) + let target = handle.mutableBuffer(for: .zero ..< _Slot(at: c)) + let done: Void? = elements.withContiguousStorageIfAvailable { source in + target.initializeAll(fromContentsOf: source) + } + if done == nil { + target.initializeAll(fromContentsOf: elements) + } + handle.count = c + } + } + + /// Adds a new element at the end of the deque. + /// + /// Use this method to append a single element to the end of a deque. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.append(100) + /// print(numbers) + /// // Prints "[1, 2, 3, 4, 5, 100]" + /// + /// Because deques increase their allocated capacity using an exponential + /// strategy, appending a single element to a deque is an O(1) operation when + /// averaged over many calls to the `append(_:)` method. When a deque has + /// additional capacity and is not sharing its storage with another instance, + /// appending an element is O(1). When a deque needs to reallocate storage + /// before prepending or its storage is shared with another copy, appending is + /// O(`count`). + /// + /// - Parameters: + /// - newElement: The element to append to the deque. + /// + /// - Complexity: Amortized O(1) + /// + /// - SeeAlso: `prepend(_:)` + @inlinable + public mutating func append(_ newElement: Element) { + _storage.ensureUnique(minimumCapacity: count + 1) + _storage.update { + $0.uncheckedAppend(newElement) + } + } + + /// Adds the elements of a sequence to the end of the deque. + /// + /// Use this method to append the elements of a sequence to the front of this + /// deque. This example appends the elements of a `Range` instance to a + /// deque of integers. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.append(contentsOf: 10...15) + /// print(numbers) + /// // Prints "[1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15]" + /// + /// - Parameter newElements: The elements to append to the deque. + /// + /// - Complexity: Amortized O(`newElements.count`). + @inlinable + public mutating func append(contentsOf newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { source in + _storage.ensureUnique(minimumCapacity: count + source.count) + _storage.update { $0.uncheckedAppend(contentsOf: source) } + } + if done != nil { + return + } + + let underestimatedCount = newElements.underestimatedCount + _storage.ensureUnique(minimumCapacity: count + underestimatedCount) + var it = _storage.update { target in + let gaps = target.availableSegments() + let (it, copied) = gaps.initialize(fromSequencePrefix: newElements) + target.count += copied + return it + } + while let next = it.next() { + _storage.ensureUnique(minimumCapacity: count + 1) + _storage.update { target in + target.uncheckedAppend(next) + let gaps = target.availableSegments() + target.count += gaps.initialize(copyingPrefixOf: &it) + } + } + } + + /// Adds the elements of a collection to the end of the deque. + /// + /// Use this method to append the elements of a collection to the front of + /// this deque. This example appends the elements of a `Range` instance + /// to a deque of integers. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.append(contentsOf: 10...15) + /// print(numbers) + /// // Prints "[1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15]" + /// + /// - Parameter newElements: The elements to append to the deque. + /// + /// - Complexity: Amortized O(`newElements.count`). + @inlinable + public mutating func append( + contentsOf newElements: some Collection + ) { + let done: Void? = newElements.withContiguousStorageIfAvailable { source in + _storage.ensureUnique(minimumCapacity: count + source.count) + _storage.update { $0.uncheckedAppend(contentsOf: source) } + } + guard done == nil else { return } + + let c = newElements.count + guard c > 0 else { return } + _storage.ensureUnique(minimumCapacity: count + c) + _storage.update { target in + let gaps = target.availableSegments().prefix(c) + gaps.initialize(copying: newElements) + target.count += c + } + } + + /// Inserts a new element at the specified position. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the deque's `endIndex` as the `index` parameter, the + /// new element is appended to the deque. + /// + /// - Parameters: + /// - newElement: The new element to insert into the deque. + /// - index: The position at which to insert the new element. `index` must + /// be a valid index of the deque (including `endIndex`). + /// + /// - Complexity: O(`count`). The operation shifts existing elements either + /// towards the beginning or the end of the deque to minimize the number of + /// elements that need to be moved. When inserting at the start or the end, + /// this reduces the complexity to amortized O(1). + @inlinable + public mutating func insert(_ newElement: Element, at index: Int) { + precondition(index >= 0 && index <= count, "Index out of bounds") + _storage.ensureUnique(minimumCapacity: count + 1) + _storage.update { target in + if index == 0 { + target.uncheckedPrepend(newElement) + return + } + if index == count { + target.uncheckedAppend(newElement) + return + } + let gap = target.openGap(ofSize: 1, atOffset: index) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) + } + } + + /// Inserts the elements of a collection into the deque at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the deque's `endIndex` property as the + /// `index` parameter, the new elements are appended to the deque. + /// + /// - Parameters: + /// - newElements: The new elements to insert into the deque. + /// - index: The position at which to insert the new elements. `index` must + /// be a valid index of the deque (including `endIndex`). + /// + /// - Complexity: O(`count + newElements.count`). The operation shifts + /// existing elements either towards the beginning or the end of the deque + /// to minimize the number of elements that need to be moved. When + /// inserting at the start or the end, this reduces the complexity to + /// amortized O(1). + @inlinable + public mutating func insert( + contentsOf newElements: __owned some Collection, + at index: Int + ) { + precondition(index >= 0 && index <= count, "Index out of bounds") + let newCount = newElements.count + _storage.ensureUnique(minimumCapacity: count + newCount) + _storage.update { target in + target.uncheckedInsert(contentsOf: newElements, count: newCount, atOffset: index) + } + } + + /// Removes and returns the element at the specified position. + /// + /// To close the resulting gap, all elements following the specified position + /// are (logically) moved up by one index position. (Internally, the deque may + /// actually decide to shift previous elements forward instead to minimize the + /// number of elements that need to be moved.) + /// + /// - Parameters: + /// - index: The position of the element to remove. `index` must be a valid + /// index of the array. + /// + /// - Returns: The element originally at the specified index. + /// + /// - Complexity: O(`count`). Removing elements from the start or end of the + /// deque costs O(1) if the deque's storage isn't shared. + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < self.count, "Index out of bounds") + // FIXME: Implement storage shrinking + _storage.ensureUnique() + return _storage.update { target in + // FIXME: Add direct implementation & see if it makes a difference + let result = self[index] + target.uncheckedRemove(offsets: index ..< index + 1) + return result + } + } + + /// Removes the elements in the specified subrange from the deque. + + /// All elements following the specified range are (logically) moved up to + /// close the resulting gap. (Internally, the deque may actually decide to + /// shift previous elements forward instead to minimize the number of elements + /// that need to be moved.) + /// + /// - Parameters: + /// - bounds: The range of the collection to be removed. The bounds of the + /// range must be valid indices of the collection. + /// + /// - Complexity: O(`count`). Removing elements from the start or end of the + /// deque costs O(`bounds.count`) if the deque's storage isn't shared. + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition(bounds.lowerBound >= 0 && bounds.upperBound <= self.count, + "Index range out of bounds") + _storage.ensureUnique() + _storage.update { $0.uncheckedRemove(offsets: bounds) } + } + + @inlinable + public mutating func _customRemoveLast() -> Element? { + precondition(!isEmpty, "Cannot remove last element of an empty Deque") + _storage.ensureUnique() + return _storage.update { $0.uncheckedRemoveLast() } + } + + @inlinable + public mutating func _customRemoveLast(_ n: Int) -> Bool { + precondition(n >= 0, "Cannot remove a negative number of elements") + precondition(n <= count, "Cannot remove more elements than there are in the Collection") + _storage.ensureUnique() + _storage.update { $0.uncheckedRemoveLast(n) } + return true + } + + /// Removes and returns the first element of the deque. + /// + /// The collection must not be empty. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(1) if the underlying storage isn't shared; otherwise + /// O(`count`). + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty Deque") + _storage.ensureUnique() + return _storage.update { $0.uncheckedRemoveFirst() } + } + + /// Removes the specified number of elements from the beginning of the deque. + /// + /// - Parameter n: The number of elements to remove from the deque. `n` must + /// be greater than or equal to zero and must not exceed the number of + /// elements in the deque. + /// + /// - Complexity: O(`n`) if the underlying storage isn't shared; otherwise + /// O(`count`). + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Cannot remove a negative number of elements") + precondition(n <= count, "Cannot remove more elements than there are in the Collection") + _storage.ensureUnique() + return _storage.update { $0.uncheckedRemoveFirst(n) } + } + + /// Removes all elements from the deque. + /// + /// - Parameter keepCapacity: Pass true to keep the existing storage capacity + /// of the deque after removing its elements. The default value is false. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + if keepCapacity { + _storage.ensureUnique() + _storage.update { $0.uncheckedRemoveAll() } + } else { + self = Deque() + } + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+CustomReflectable.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+CustomReflectable.swift new file mode 100644 index 00000000..a8a169ec --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+CustomReflectable.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +#if !$Embedded +extension Deque: CustomReflectable { + /// The custom mirror for this instance. + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: self, displayStyle: .collection) + } +} +#endif diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Descriptions.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Descriptions.swift new file mode 100644 index 00000000..4c4e8cbd --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Descriptions.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +#if !$Embedded +extension Deque: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + "" //_arrayDescription(for: self) + } +} + +extension Deque: CustomDebugStringConvertible { + /// A textual representation of this instance, suitable for debugging. + public var debugDescription: String { + description + } +} +#endif diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Equatable.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Equatable.swift new file mode 100644 index 00000000..c65ea07d --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Equatable.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque: Equatable where Element: Equatable { + /// Returns a Boolean value indicating whether two values are equal. Two + /// deques are considered equal if they contain the same elements in the same + /// order. + /// + /// - Complexity: O(`min(left.count, right.count)`) + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + let lhsCount = left.count + if lhsCount != right.count { + return false + } + + // Test referential equality. + if lhsCount == 0 || left._storage.isIdentical(to: right._storage) { + return true + } + + return left.elementsEqual(right) + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+ExpressibleByArrayLiteral.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+ExpressibleByArrayLiteral.swift new file mode 100644 index 00000000..b2565a5b --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+ExpressibleByArrayLiteral.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque: ExpressibleByArrayLiteral { + /// Creates a new deque from the contents of an array literal. + /// + /// Do not call this initializer directly. It is used by the compiler when + /// you use an array literal. Instead, create a new deque using an array + /// literal as its value by enclosing a comma-separated list of values in + /// square brackets. You can use an array literal anywhere a deque is expected + /// by the type context. + /// + /// - Parameter elements: A variadic list of elements of the new deque. + @inlinable + @inline(__always) + public init(arrayLiteral elements: Element...) { + self.init(elements) + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Extras.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Extras.swift new file mode 100644 index 00000000..b5b65aa7 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Extras.swift @@ -0,0 +1,190 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque { + /// Creates a deque with the specified capacity, then calls the given + /// closure with a buffer covering the array's uninitialized memory. + /// + /// Inside the closure, set the `initializedCount` parameter to the number of + /// elements that are initialized by the closure. The memory in the range + /// `buffer[0.., inout Int) throws -> Void + ) rethrows { + self._storage = .init(minimumCapacity: capacity) + try _storage.update { handle in + handle.startSlot = .zero + var count = 0 + var buffer = handle.mutableBuffer(for: .zero ..< _Slot(at: capacity)) + defer { + precondition(count <= capacity, + "Initialized count set to greater than specified capacity") + let b = handle.mutableBuffer(for: .zero ..< _Slot(at: capacity)) + precondition(buffer.baseAddress == b.baseAddress && buffer.count == b.count, + "Initializer relocated Deque storage") + handle.count = count + } + try initializer(&buffer, &count) + } + } +} + +extension Deque { + /// Removes and returns the first element of this deque, if it exists. + /// + /// - Returns: The first element of the original collection if the collection + /// isn't empty; otherwise, `nil`. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. + @inlinable + public mutating func popFirst() -> Element? { + // FIXME: Add this to the stdlib on BidirectionalCollection + // where Self == Self.SubSequence + guard count > 0 else { return nil } + _storage.ensureUnique() + return _storage.update { + $0.uncheckedRemoveFirst() + } + } + + // Note: `popLast` is implemented by the stdlib as a + // `RangeReplaceableCollection` extension, defined in terms of + // `_customRemoveLast`. + + + /// Adds a new element at the front of the deque. + /// + /// Use this method to append a single element to the front of a deque. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.prepend(100) + /// print(numbers) + /// // Prints "[100, 1, 2, 3, 4, 5]" + /// + /// Because deques increase their allocated capacity using an exponential + /// strategy, prepending a single element to a deque is an O(1) operation when + /// averaged over many calls to the `prepend(_:)` method. When a deque has + /// additional capacity and is not sharing its storage with another instance, + /// prepending an element is O(1). When a deque needs to reallocate storage + /// before prepending or its storage is shared with another copy, prepending + /// is O(`count`). + /// + /// - Parameter newElement: The element to prepend to the deque. + /// + /// - Complexity: Amortized O(1). + /// + /// - SeeAlso: `append(_:)` + @inlinable + public mutating func prepend(_ newElement: Element) { + _storage.ensureUnique(minimumCapacity: count + 1) + return _storage.update { + $0.uncheckedPrepend(newElement) + } + } + + /// Adds the elements of a collection to the front of the deque. + /// + /// Use this method to prepend the elements of a collection to the front of + /// this deque. This example prepends the elements of a `Range` instance + /// to a deque of integers. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.prepend(contentsOf: 10...15) + /// print(numbers) + /// // Prints "[10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5]" + /// + /// - Parameter newElements: The elements to prepend to the deque. + /// + /// - Complexity: Amortized O(`newElements.count`). + /// + /// - SeeAlso: `append(contentsOf:)` + @inlinable + public mutating func prepend( + contentsOf newElements: some Collection + ) { + let done: Void? = newElements.withContiguousStorageIfAvailable { source in + _storage.ensureUnique(minimumCapacity: count + source.count) + _storage.update { $0.uncheckedPrepend(contentsOf: source) } + } + guard done == nil else { return } + + let c = newElements.count + guard c > 0 else { return } + _storage.ensureUnique(minimumCapacity: count + c) + _storage.update { target in + let gaps = target.availableSegments().suffix(c) + gaps.initialize(copying: newElements) + target.count += c + target.startSlot = target.slot(target.startSlot, offsetBy: -c) + } + } + + /// Adds the elements of a sequence to the front of the deque. + /// + /// Use this method to prepend the elements of a sequence to the front of this + /// deque. This example prepends the elements of a `Range` instance to a + /// deque of integers. + /// + /// var numbers: Deque = [1, 2, 3, 4, 5] + /// numbers.prepend(contentsOf: 10...15) + /// print(numbers) + /// // Prints "[10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5]" + /// + /// - Parameter newElements: The elements to prepend to the deque. + /// + /// - Complexity: Amortized O(`newElements.count`). + /// + /// - SeeAlso: `append(contentsOf:)` + @inlinable + public mutating func prepend(contentsOf newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { source in + _storage.ensureUnique(minimumCapacity: count + source.count) + _storage.update { $0.uncheckedPrepend(contentsOf: source) } + } + guard done == nil else { return } + + let originalCount = self.count + self.append(contentsOf: newElements) + let newCount = self.count + let c = newCount - originalCount + _storage.update { target in + target.startSlot = target.slot(forOffset: originalCount) + target.count = target.capacity + target.closeGap(offsets: c ..< c + (target.capacity - newCount)) + assert(target.count == newCount) + } + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Hashable.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Hashable.swift new file mode 100644 index 00000000..3250016b --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Hashable.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque: Hashable where Element: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // discriminator + for element in self { + hasher.combine(element) + } + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque+Testing.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque+Testing.swift new file mode 100644 index 00000000..2bf77981 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque+Testing.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +// This file contains exported but non-public entry points to support clear box +// testing. + +extension Deque { + /// True if consistency checking is enabled in the implementation of this + /// type, false otherwise. + /// + /// Documented performance promises are null and void when this property + /// returns true -- for example, operations that are documented to take + /// O(1) time might take O(*n*) time, or worse. + public static var _isConsistencyCheckingEnabled: Bool { + false + } + + /// The maximum number of elements this deque is currently able to store + /// without reallocating its storage buffer. + /// + /// This information isn't exposed as public, as the returned value isn't + /// guaranteed to be stable, and may even differ between equal deques, + /// violating value semantics. + /// + /// This property isn't intended to be used outside of `Deque`'s own test + /// target. + @usableFromInline + var _capacity: Int { + _storage.capacity + } + + /// The number of the storage slot in this deque that holds the first element. + /// (Or would hold it after an insertion in case the deque is currently + /// empty.) + /// + /// This property isn't intended to be used outside of `Deque`'s own test + /// target. + @usableFromInline + var _startSlot: Int { + _storage.startSlot.position + } + + /// Constructs a deque instance of the specified contents and layout. Exposed + /// as public to allow exhaustive input/output tests for `Deque`'s members. + /// This isn't intended to be used outside of `Deque`'s own test target. + @usableFromInline + init( + _capacity capacity: Int, + startSlot: Int, + contents: some Sequence + ) { + let contents = ContiguousArray(contents) + precondition(capacity >= 0) + precondition(startSlot >= 0 && (startSlot < capacity || (capacity == 0 && startSlot == 0))) + precondition(contents.count <= capacity) + let startSlot = _Slot(at: startSlot) + let buffer = _DequeBuffer.create(minimumCapacity: capacity) { _ in + _DequeBufferHeader(capacity: capacity, count: contents.count, startSlot: startSlot) + } + let storage = Deque._Storage(unsafeDowncast(buffer, to: _DequeBuffer.self)) + if contents.count > 0 { + contents.withUnsafeBufferPointer { source in + storage.update { target in + let segments = target.mutableSegments() + let c = segments.first.count + segments.first.initializeAll(fromContentsOf: source.prefix(c)) + if let second = segments.second { + second.initializeAll(fromContentsOf: source.dropFirst(c)) + } + } + } + } + self.init(_storage: storage) + assert(self._capacity == capacity) + assert(self._startSlot == startSlot.position) + assert(self.count == contents.count) + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque._Storage.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque._Storage.swift new file mode 100644 index 00000000..6f441d69 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque._Storage.swift @@ -0,0 +1,222 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque { + @frozen + @usableFromInline + struct _Storage { + @usableFromInline + internal typealias _Buffer = ManagedBufferPointer<_DequeBufferHeader, Element> + + @usableFromInline + internal var _buffer: _Buffer + + @inlinable + @inline(__always) + internal init(_buffer: _Buffer) { + self._buffer = _buffer + } + } +} + +extension Deque._Storage: CustomStringConvertible { + @usableFromInline + internal var description: String { + "Deque<\(Element.self)>._Storage\(_buffer.header)" + } +} + +extension Deque._Storage { + @inlinable + internal init() { + self.init(_buffer: _Buffer(unsafeBufferObject: _emptyDequeStorage)) + } + + @inlinable + internal init(_ object: _DequeBuffer) { + self.init(_buffer: _Buffer(unsafeBufferObject: object)) + } + + @inlinable + internal init(minimumCapacity: Int) { + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { + #if os(OpenBSD) + let capacity = minimumCapacity + #else + let capacity = $0.capacity + #endif + return _DequeBufferHeader(capacity: capacity, count: 0, startSlot: .zero) + }) + self.init(_buffer: _Buffer(unsafeBufferObject: object)) + } +} + +extension Deque._Storage { + #if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + _buffer.withUnsafeMutablePointerToHeader { $0.pointee._checkInvariants() } + } + #else + @inlinable @inline(__always) + internal func _checkInvariants() {} + #endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension Deque._Storage { + @inlinable + @inline(__always) + internal var identity: AnyObject { _buffer.buffer } + + + @inlinable + @inline(__always) + internal var capacity: Int { + _buffer.withUnsafeMutablePointerToHeader { $0.pointee.capacity } + } + + @inlinable + @inline(__always) + internal var count: Int { + _buffer.withUnsafeMutablePointerToHeader { $0.pointee.count } + } + + @inlinable + @inline(__always) + internal var startSlot: _DequeSlot { + _buffer.withUnsafeMutablePointerToHeader { $0.pointee.startSlot + } + } +} + +extension Deque._Storage { + @usableFromInline + internal typealias Index = Int + + @usableFromInline + internal typealias _UnsafeHandle = Deque._UnsafeHandle + + @inlinable + @inline(__always) + internal func read(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { + try _buffer.withUnsafeMutablePointers { header, elements in + let handle = _UnsafeHandle(header: header, + elements: elements, + isMutable: false) + return try body(handle) + } + } + + @inlinable + @inline(__always) + internal func update(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { + try _buffer.withUnsafeMutablePointers { header, elements in + let handle = _UnsafeHandle(header: header, + elements: elements, + isMutable: true) + return try body(handle) + } + } +} + +extension Deque._Storage { + /// Return a boolean indicating whether this storage instance is known to have + /// a single unique reference. If this method returns true, then it is safe to + /// perform in-place mutations on the deque. + @inlinable + @inline(__always) + internal mutating func isUnique() -> Bool { + _buffer.isUniqueReference() + } + + /// Ensure that this storage refers to a uniquely held buffer by copying + /// elements if necessary. + @inlinable + @inline(__always) + internal mutating func ensureUnique() { + if isUnique() { return } + self._makeUniqueCopy() + } + + @inlinable + @inline(never) + internal mutating func _makeUniqueCopy() { + self = self.read { $0.copyElements() } + } + + @usableFromInline + internal func _growCapacity( + to minimumCapacity: Int, + linearly: Bool + ) -> Int { + if linearly { return Swift.max(capacity, minimumCapacity) } + let c = (3 &* UInt(bitPattern: capacity) &+ 1) &>> 1 + return Swift.max(Int(bitPattern: c), minimumCapacity) + } + + /// Ensure that we have a uniquely referenced buffer with enough space to + /// store at least `minimumCapacity` elements. + /// + /// - Parameter minimumCapacity: The minimum number of elements the buffer + /// needs to be able to hold on return. + /// + /// - Parameter linearGrowth: If true, then don't use an exponential growth + /// factor when reallocating the buffer -- just allocate space for the + /// requested number of elements + @inlinable + @inline(__always) + internal mutating func ensureUnique( + minimumCapacity: Int, + linearGrowth: Bool = false + ) { + let unique = isUnique() + if _slowPath(capacity < minimumCapacity || !unique) { + _ensureUnique(isUnique: unique, minimumCapacity: minimumCapacity, linearGrowth: linearGrowth) + } + } + + @inlinable + @inline(never) + internal mutating func _ensureUnique( + isUnique: Bool, + minimumCapacity: Int, + linearGrowth: Bool + ) { + if capacity >= minimumCapacity { + assert(!isUnique) + self = self.read { $0.copyElements() } + return + } + + let minimumCapacity = _growCapacity(to: minimumCapacity, linearly: linearGrowth) + if isUnique { + self = self.update { source in + source.moveElements(minimumCapacity: minimumCapacity) + } + } else { + self = self.read { source in + source.copyElements(minimumCapacity: minimumCapacity) + } + } + } +} + +extension Deque._Storage { + @inlinable + @inline(__always) + internal func isIdentical(to other: Self) -> Bool { + self._buffer.buffer === other._buffer.buffer + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque._UnsafeHandle.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque._UnsafeHandle.swift new file mode 100644 index 00000000..91916abb --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque._UnsafeHandle.swift @@ -0,0 +1,831 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +extension Deque { + @frozen + @usableFromInline + internal struct _UnsafeHandle { + @usableFromInline + let _header: UnsafeMutablePointer<_DequeBufferHeader> + @usableFromInline + let _elements: UnsafeMutablePointer + #if DEBUG + @usableFromInline + let _isMutable: Bool + #endif + + @inlinable + @inline(__always) + init( + header: UnsafeMutablePointer<_DequeBufferHeader>, + elements: UnsafeMutablePointer, + isMutable: Bool + ) { + self._header = header + self._elements = elements + #if DEBUG + self._isMutable = isMutable + #endif + } + } +} + +extension Deque._UnsafeHandle { + @inlinable + @inline(__always) + func assertMutable() { + #if DEBUG + assert(_isMutable) + #endif + } +} + +extension Deque._UnsafeHandle { + @usableFromInline + internal typealias Slot = _DequeSlot + + @inlinable + @inline(__always) + var header: _DequeBufferHeader { + _header.pointee + } + + @inlinable + @inline(__always) + var capacity: Int { + _header.pointee.capacity + } + + @inlinable + @inline(__always) + var count: Int { + get { _header.pointee.count } + nonmutating set { _header.pointee.count = newValue } + } + + @inlinable + @inline(__always) + var startSlot: Slot { + get { _header.pointee.startSlot } + nonmutating set { _header.pointee.startSlot = newValue } + } + + @inlinable + @inline(__always) + func ptr(at slot: Slot) -> UnsafeMutablePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return _elements + slot.position + } +} + +extension Deque._UnsafeHandle { + @inlinable + @inline(__always) + var mutableBuffer: UnsafeMutableBufferPointer { + assertMutable() + return .init(start: _elements, count: _header.pointee.capacity) + } + + @inlinable + internal func buffer(for range: Range) -> UnsafeBufferPointer { + assert(range.upperBound.position <= capacity) + return .init(start: _elements + range.lowerBound.position, count: range._count) + } + + @inlinable + @inline(__always) + internal func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { + assertMutable() + return .init(mutating: buffer(for: range)) + } +} + +extension Deque._UnsafeHandle { + /// The slot immediately following the last valid one. (`endSlot` refers to + /// the valid slot corresponding to `endIndex`, which is a different thing + /// entirely.) + @inlinable + @inline(__always) + internal var limSlot: Slot { + Slot(at: capacity) + } + + @inlinable + internal func slot(after slot: Slot) -> Slot { + assert(slot.position < capacity) + let position = slot.position + 1 + if position >= capacity { + return Slot(at: 0) + } + return Slot(at: position) + } + + @inlinable + internal func slot(before slot: Slot) -> Slot { + assert(slot.position < capacity) + if slot.position == 0 { return Slot(at: capacity - 1) } + return Slot(at: slot.position - 1) + } + + @inlinable + internal func slot(_ slot: Slot, offsetBy delta: Int) -> Slot { + assert(slot.position <= capacity) + let position = slot.position + delta + if delta >= 0 { + if position >= capacity { return Slot(at: position - capacity) } + } else { + if position < 0 { return Slot(at: position + capacity) } + } + return Slot(at: position) + } + + @inlinable + @inline(__always) + internal var endSlot: Slot { + slot(startSlot, offsetBy: count) + } + + /// Return the storage slot corresponding to the specified offset, which may + /// or may not address an existing element. + @inlinable + internal func slot(forOffset offset: Int) -> Slot { + assert(offset >= 0) + assert(offset <= capacity) // Not `count`! + + // Note: The use of wrapping addition/subscription is justified here by the + // fact that `offset` is guaranteed to fall in the range `0 ..< capacity`. + // Eliminating the overflow checks leads to a measurable speedup for + // random-access subscript operations. (Up to 2x on some microbenchmarks.) + let position = startSlot.position &+ offset + guard position < capacity else { return Slot(at: position &- capacity) } + return Slot(at: position) + } +} + +extension Deque._UnsafeHandle { + @inlinable + internal func segments() -> _UnsafeDequeSegments { + let wrap = capacity - startSlot.position + if count <= wrap { + return .init(start: ptr(at: startSlot), count: count) + } + return .init(first: ptr(at: startSlot), count: wrap, + second: ptr(at: .zero), count: count - wrap) + } + + @inlinable + internal func segments( + forOffsets offsets: Range + ) -> _UnsafeDequeSegments { + assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) + let lower = slot(forOffset: offsets.lowerBound) + let upper = slot(forOffset: offsets.upperBound) + if offsets.count == 0 || lower < upper { + return .init(start: ptr(at: lower), count: offsets.count) + } + return .init(first: ptr(at: lower), count: capacity - lower.position, + second: ptr(at: .zero), count: upper.position) + } + + @inlinable + @inline(__always) + internal func mutableSegments() -> _UnsafeMutableDequeSegments { + assertMutable() + return .init(mutating: segments()) + } + + @inlinable + @inline(__always) + internal func mutableSegments( + forOffsets range: Range + ) -> _UnsafeMutableDequeSegments { + assertMutable() + return .init(mutating: segments(forOffsets: range)) + } +} + +extension Deque._UnsafeHandle { + @inlinable + internal func availableSegments() -> _UnsafeMutableDequeSegments { + assertMutable() + let endSlot = self.endSlot + guard count < capacity else { return .init(start: ptr(at: endSlot), count: 0) } + if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } + return .init(mutableBuffer(for: endSlot ..< limSlot), + mutableBuffer(for: .zero ..< startSlot)) + } +} + +extension Deque._UnsafeHandle { + @inlinable + @discardableResult + func initialize( + at start: Slot, + from source: UnsafeBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + ptr(at: start).initialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } + + @inlinable + @inline(__always) + @discardableResult + func moveInitialize( + at start: Slot, + from source: UnsafeMutableBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + ptr(at: start).moveInitialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } + + @inlinable + @inline(__always) + @discardableResult + public func move( + from source: Slot, + to target: Slot, + count: Int + ) -> (source: Slot, target: Slot) { + assert(count >= 0) + assert(source.position + count <= self.capacity) + assert(target.position + count <= self.capacity) + guard count > 0 else { return (source, target) } + ptr(at: target).moveInitialize(from: ptr(at: source), count: count) + return (slot(source, offsetBy: count), slot(target, offsetBy: count)) + } +} + +extension Deque._UnsafeHandle { + /// Copy elements into a new storage instance without changing capacity or + /// layout. + @inlinable + internal func copyElements() -> Deque._Storage { + let object = _DequeBuffer.create( + minimumCapacity: capacity, + makingHeaderWith: { _ in header }) + let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard self.count > 0 else { return result } + result.update { target in + let source = self.segments() + target.initialize(at: startSlot, from: source.first) + if let second = source.second { + target.initialize(at: .zero, from: second) + } + } + return result + } + + /// Copy elements into a new storage instance with the specified minimum + /// capacity. + @inlinable + internal func copyElements(minimumCapacity: Int) -> Deque._Storage { + assert(minimumCapacity >= count) + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { + #if os(OpenBSD) + let capacity = minimumCapacity + #else + let capacity = $0.capacity + #endif + return _DequeBufferHeader( + capacity: capacity, + count: count, + startSlot: .zero) + }) + let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard count > 0 else { return result } + result.update { target in + assert(target.count == count && target.startSlot.position == 0) + let source = self.segments() + let next = target.initialize(at: .zero, from: source.first) + if let second = source.second { + target.initialize(at: next, from: second) + } + } + return result + } + + /// Move elements into a new storage instance with the specified minimum + /// capacity. Existing indices in `self` won't necessarily be valid in the + /// result. `self` is left empty. + @inlinable + internal func moveElements(minimumCapacity: Int) -> Deque._Storage { + assertMutable() + let count = self.count + assert(minimumCapacity >= count) + let object = _DequeBuffer.create( + minimumCapacity: minimumCapacity, + makingHeaderWith: { + #if os(OpenBSD) + let capacity = minimumCapacity + #else + let capacity = $0.capacity + #endif + return _DequeBufferHeader( + capacity: capacity, + count: count, + startSlot: .zero) + }) + let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) + guard count > 0 else { return result } + result.update { target in + let source = self.mutableSegments() + let next = target.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + target.moveInitialize(at: next, from: second) + } + } + self.count = 0 + return result + } +} + +extension Deque._UnsafeHandle { + @inlinable + internal func withUnsafeSegment( + startingAt start: Int, + maximumCount: Int?, + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> (end: Int, result: R) { + assert(start <= count) + guard start < count else { + return try (count, body(UnsafeBufferPointer(start: nil, count: 0))) + } + let endSlot = self.endSlot + + let segmentStart = self.slot(forOffset: start) + let segmentEnd = segmentStart < endSlot ? endSlot : limSlot + let count = Swift.min(maximumCount ?? Int.max, segmentEnd.position - segmentStart.position) + let result = try body(UnsafeBufferPointer(start: ptr(at: segmentStart), count: count)) + return (start + count, result) + } +} + +// MARK: Replacement + +extension Deque._UnsafeHandle { + /// Replace the elements in `range` with `newElements`. The deque's count must + /// not change as a result of calling this function. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func uncheckedReplaceInPlace( + inOffsets range: Range, + with newElements: C + ) where C.Element == Element { + assertMutable() + assert(range.upperBound <= count) + assert(newElements.count == range.count) + guard !range.isEmpty else { return } + let target = mutableSegments(forOffsets: range) + target.reassign(copying: newElements) + } +} + +// MARK: Appending + +extension Deque._UnsafeHandle { + /// Append `element` to this buffer. The buffer must have enough free capacity + /// to insert one new element. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func uncheckedAppend(_ element: Element) { + assertMutable() + assert(count < capacity) + ptr(at: endSlot).initialize(to: element) + count += 1 + } + + /// Append the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func uncheckedAppend(contentsOf source: UnsafeBufferPointer) { + assertMutable() + assert(count + source.count <= capacity) + guard source.count > 0 else { return } + let c = self.count + count += source.count + let gap = mutableSegments(forOffsets: c ..< count) + gap.initialize(copying: source) + } +} + +// MARK: Prepending + +extension Deque._UnsafeHandle { + @inlinable + internal func uncheckedPrepend(_ element: Element) { + assertMutable() + assert(count < capacity) + let slot = self.slot(before: startSlot) + ptr(at: slot).initialize(to: element) + startSlot = slot + count += 1 + } + + /// Prepend the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func uncheckedPrepend(contentsOf source: UnsafeBufferPointer) { + assertMutable() + assert(count + source.count <= capacity) + guard source.count > 0 else { return } + let oldStart = startSlot + let newStart = self.slot(startSlot, offsetBy: -source.count) + startSlot = newStart + count += source.count + + let gap = mutableWrappedBuffer(between: newStart, and: oldStart) + gap.initialize(copying: source) + } +} + +// MARK: Insertion + +extension Deque._UnsafeHandle { + /// Insert all elements from `newElements` into this deque, starting at + /// `offset`. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter newElements: The elements to insert. + /// - Parameter newCount: Must be equal to `newElements.count`. Used to + /// prevent calling `count` more than once. + /// - Parameter offset: The desired offset from the start at which to place + /// the first element. + @inlinable + internal func uncheckedInsert( + contentsOf newElements: __owned C, + count newCount: Int, + atOffset offset: Int + ) where C.Element == Element { + assertMutable() + assert(offset <= count) + assert(newElements.count == newCount) + guard newCount > 0 else { return } + let gap = openGap(ofSize: newCount, atOffset: offset) + gap.initialize(copying: newElements) + } + + @inlinable + internal func mutableWrappedBuffer( + between start: Slot, + and end: Slot + ) -> _UnsafeMutableDequeSegments { + assert(start.position <= capacity) + assert(end.position <= capacity) + if start < end { + return .init(start: ptr(at: start), count: end.position - start.position) + } + return .init( + first: ptr(at: start), count: capacity - start.position, + second: ptr(at: .zero), count: end.position) + } + + /// Slide elements around so that there is a gap of uninitialized slots of + /// size `gapSize` starting at `offset`, and return a (potentially wrapped) + /// buffer holding the newly inserted slots. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter gapSize: The number of uninitialized slots to create. + /// - Parameter offset: The offset from the start at which the uninitialized + /// slots should start. + @inlinable + internal func openGap( + ofSize gapSize: Int, + atOffset offset: Int + ) -> _UnsafeMutableDequeSegments { + assertMutable() + assert(offset >= 0 && offset <= self.count) + assert(self.count + gapSize <= capacity) + assert(gapSize > 0) + + let headCount = offset + let tailCount = count - offset + if tailCount <= headCount { + // Open the gap by sliding elements to the right. + + let originalEnd = self.slot(startSlot, offsetBy: count) + let newEnd = self.slot(startSlot, offsetBy: count + gapSize) + let gapStart = self.slot(forOffset: offset) + let gapEnd = self.slot(gapStart, offsetBy: gapSize) + + let sourceIsContiguous = gapStart <= originalEnd.orIfZero(capacity) + let targetIsContiguous = gapEnd <= newEnd.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping; we just need to slide + // elements after the gap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲........ABCD .̲.......ABCDEFGH̲.̲ + // 1) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH......ABCD .̲H......ABCDEFG.̲.̲ + move(from: gapStart, to: gapEnd, count: tailCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) E̲FGH.........ABC̲D̲ + // 1) .̲..EFGH......ABC̲D̲ + // 2) .̲CDEFGH......AB.̲.̲ + assert(startSlot > originalEnd.orIfZero(capacity)) + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: gapStart, to: gapEnd, count: capacity - gapStart.position) + } else if sourceIsContiguous { + // Opening the gap pushes subsequent elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ........ABC̲D̲E̲FGH. + // 1) GH......ABC̲D̲E̲F... + // 2) GH......AB.̲.̲.̲CDEF + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: newEnd.position) + move(from: gapStart, to: gapEnd, count: tailCount - newEnd.position) + } else { + // The rest of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) GH.........AB̲C̲D̲EF + // 1) ...GH......AB̲C̲D̲EF + // 2) DEFGH......AB̲C̲.̲.. + // 3) DEFGH......A.̲.̲.̲BC + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) + } + count += gapSize + return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + } + + // Open the gap by sliding elements to the left. + + let originalStart = self.startSlot + let newStart = self.slot(originalStart, offsetBy: -gapSize) + let gapEnd = self.slot(forOffset: offset) + let gapStart = self.slot(gapEnd, offsetBy: -gapSize) + + let sourceIsContiguous = originalStart <= gapEnd.orIfZero(capacity) + let targetIsContiguous = newStart <= gapStart.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with any wrapping. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....A̲B̲C̲DEFGH... GH.........̲A̲B̲CDEF .̲A̲B̲CDEFGH.......̲.̲ + // 1) .ABC.̲.̲.̲DEFGH... GH......AB.̲.̲.̲CDEF .̲.̲.̲CDEFGH....AB.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) C̲D̲EFGH.........A̲B̲ + // 1) C̲D̲EFGH.....AB...̲.̲ + // 2) .̲.̲EFGH.....ABCD.̲.̲ + assert(originalStart >= newStart) + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapEnd.position) + } else if sourceIsContiguous { + // Opening the gap pushes preceding elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) .AB̲C̲D̲EFGH......... + // 1) ...̲C̲D̲EFGH.......AB + // 2) CD.̲.̲.̲EFGH.......AB + move(from: originalStart, to: newStart, count: capacity - newStart.position) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } else { + // The preceding of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // 0) CD̲E̲F̲GHIJKL.........AB + // 1) CD̲E̲F̲GHIJKL......AB... + // 2) ..̲.̲F̲GHIJKL......ABCDE + // 3) F.̲.̲.̲GHIJKL......ABCDE + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapSize) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } + startSlot = newStart + count += gapSize + return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + } +} + +// MARK: Removal + +extension Deque._UnsafeHandle { + @inlinable + internal func uncheckedRemoveFirst() -> Element { + assertMutable() + assert(count > 0) + let result = ptr(at: startSlot).move() + startSlot = slot(after: startSlot) + count -= 1 + return result + } + + @inlinable + internal func uncheckedRemoveLast() -> Element { + assertMutable() + assert(count > 0) + let slot = self.slot(forOffset: count - 1) + let result = ptr(at: slot).move() + count -= 1 + return result + } + + @inlinable + internal func uncheckedRemoveFirst(_ n: Int) { + assertMutable() + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: 0 ..< n) + target.deinitialize() + startSlot = slot(startSlot, offsetBy: n) + count -= n + } + + @inlinable + internal func uncheckedRemoveLast(_ n: Int) { + assertMutable() + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: count - n ..< count) + target.deinitialize() + count -= n + } + + /// Remove all elements stored in this instance, deinitializing their storage. + /// + /// This method does not ensure that the storage buffer is uniquely + /// referenced. + @inlinable + internal func uncheckedRemoveAll() { + assertMutable() + guard count > 0 else { return } + let target = mutableSegments() + target.deinitialize() + count = 0 + startSlot = .zero + } + + /// Remove all elements in `bounds`, deinitializing their storage and sliding + /// remaining elements to close the resulting gap. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func uncheckedRemove(offsets bounds: Range) { + assertMutable() + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + + // Deinitialize elements in `bounds`. + mutableSegments(forOffsets: bounds).deinitialize() + closeGap(offsets: bounds) + } + + /// Close the gap of already uninitialized elements in `bounds`, sliding + /// elements outside of the gap to eliminate it. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal func closeGap(offsets bounds: Range) { + assertMutable() + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + let gapSize = bounds.count + guard gapSize > 0 else { return } + + let gapStart = self.slot(forOffset: bounds.lowerBound) + let gapEnd = self.slot(forOffset: bounds.upperBound) + + let headCount = bounds.lowerBound + let tailCount = count - bounds.upperBound + + if headCount >= tailCount { + // Close the gap by sliding elements to the left. + let originalEnd = endSlot + let newEnd = self.slot(forOffset: count - gapSize) + + let sourceIsContiguous = gapEnd < originalEnd.orIfZero(capacity) + let targetIsContiguous = gapStart <= newEnd.orIfZero(capacity) + if tailCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH........ABCD .̲.̲.̲E..........ABCD.̲.̲ .̲.̲.̲EF........ABCD .̲.̲.̲DE.......ABC + // 1) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲..........ABCD .̲.̲.̲...........ABCDE̲.̲ E̲F̲.̲..........ABCD D̲E̲.̲.........ABC + move(from: gapEnd, to: gapStart, count: tailCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the subsequent elements. + + // 0) .̲.̲.̲EFGH.......ABCD.̲.̲ EFGH.......ABCD.̲.̲.̲ + // 1) .̲.̲.̲..GH.......ABCDE̲F̲ ..GH.......ABCDE̲F̲G̲ + // 2) G̲H̲.̲...........ABCDE̲F̲ GH.........ABCDE̲F̲G̲ + let c = capacity - gapStart.position + assert(tailCount > c) + let next = move(from: gapEnd, to: gapStart, count: c) + move(from: next.source, to: .zero, count: tailCount - c) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) HI....ABCDE.̲.̲.̲FG + // 1) HI....ABCDEF̲G̲.̲.. + // 2) ......ABCDEF̲G̲H̲I. + let next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + move(from: .zero, to: next.target, count: originalEnd.position) + } else { + // We need to move elements across a wrap that won't go away. + + // 0) HIJKL....ABCDE.̲.̲.̲FG + // 1) HIJKL....ABCDEF̲G̲.̲.. + // 2) ...KL....ABCDEF̲G̲H̲IJ + // 3) KL.......ABCDEF̲G̲H̲IJ + var next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + next = move(from: .zero, to: next.target, count: gapSize) + move(from: next.source, to: .zero, count: newEnd.position) + } + count -= gapSize + } else { + // Close the gap by sliding elements to the right. + let originalStart = startSlot + let newStart = slot(startSlot, offsetBy: gapSize) + + let sourceIsContiguous = originalStart < gapStart.orIfZero(capacity) + let targetIsContiguous = newStart <= gapEnd.orIfZero(capacity) + + if headCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH..... EFGH........AB.̲.̲.̲CD .̲.̲.̲CDEFGH.......AB.̲.̲ DEFGH.......ABC.̲.̲ + // 1) .......AB̲C̲D̲EFGH..... EFGH...........̲A̲B̲CD .̲A̲B̲CDEFGH..........̲.̲ DEFGH.........AB̲C̲ ABCDEFGH........̲.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the preceding elements. + + // 0) .̲.̲DEFGH.......ABC.̲.̲ .̲.̲.̲EFGH.......ABCD + // 1) B̲C̲DEFGH.......A...̲.̲ B̲C̲D̲DEFGH......A... + // 2) B̲C̲DEFGH...........̲A̲ B̲C̲D̲DEFGH.........A + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapEnd.position) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) CD.̲.̲.̲EFGHI.....AB + // 1) ...̲C̲D̲EFGHI.....AB + // 1) .AB̲C̲D̲EFGHI....... + move(from: .zero, to: gapEnd.advanced(by: -gapStart.position), count: gapStart.position) + move(from: startSlot, to: newStart, count: headCount - gapStart.position) + } else { + // We need to move elements across a wrap that won't go away. + // 0) FG.̲.̲.̲HIJKLMNO....ABCDE + // 1) ...̲F̲G̲HIJKLMNO....ABCDE + // 2) CDE̲F̲G̲HIJKLMNO....AB... + // 3) CDE̲F̲G̲HIJKLMNO.......AB + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: gapStart.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } + startSlot = newStart + count -= gapSize + } + } +} diff --git a/Sources/_StringProcessing/DequeModule/Deque/Deque.swift b/Sources/_StringProcessing/DequeModule/Deque/Deque.swift new file mode 100644 index 00000000..0b1a57b1 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/Deque.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +/// A collection implementing a double-ended queue. `Deque` (pronounced "deck") +/// implements an ordered random-access collection that supports efficient +/// insertions and removals from both ends. +/// +/// var colors: Deque = ["red", "yellow", "blue"] +/// +/// Deques implement the same indexing semantics as arrays: they use integer +/// indices, and the first element of a nonempty deque is always at index zero. +/// Like arrays, deques conform to `RangeReplaceableCollection`, +/// `MutableCollection` and `RandomAccessCollection`, providing a familiar +/// interface for manipulating their contents: +/// +/// print(colors[1]) // "yellow" +/// print(colors[3]) // Runtime error: Index out of range +/// +/// colors.insert("green", at: 1) +/// // ["red", "green", "yellow", "blue"] +/// +/// colors.remove(at: 2) // "yellow" +/// // ["red", "green", "blue"] +/// +/// Like all variable-size collections on the standard library, `Deque` +/// implements value semantics: each deque has an independent value that +/// includes the values of its elements. Modifying one deque does not affect any +/// others: +/// +/// var copy = deque +/// copy[1] = "violet" +/// print(copy) // ["red", "violet", "blue"] +/// print(deque) // ["red", "green", "blue"] +/// +/// This is implemented with the copy-on-write optimization. Multiple copies of +/// a deque share the same underlying storage until you modify one of the +/// copies. When that happens, the deque being modified replaces its storage +/// with a uniquely owned copy of itself, which is then modified in place. +/// +/// `Deque` stores its elements in a circular buffer, which allows efficient +/// insertions and removals at both ends of the collection; however, this comes +/// at the cost of potentially discontiguous storage. In contrast, `Array` is +/// (usually) backed by a contiguous buffer, where new data can be efficiently +/// appended to the end, but inserting at the front is relatively slow, as +/// existing elements need to be shifted to make room. +/// +/// This difference in implementation means that while the interface of a deque +/// is very similar to an array, the operations have different performance +/// characteristics. Mutations near the front are expected to be significantly +/// faster in deques, but arrays may measure slightly faster for general +/// random-access lookups. +/// +/// Deques provide a handful of additional operations that make it easier to +/// insert and remove elements at the front. This includes queue operations such +/// as `popFirst` and `prepend`, including the ability to directly prepend a +/// sequence of elements: +/// +/// colors.append("green") +/// colors.prepend("orange") +/// // colors: ["orange", "red", "blue", "yellow", "green"] +/// +/// colors.popLast() // "green" +/// colors.popFirst() // "orange" +/// // colors: ["red", "blue", "yellow"] +/// +/// colors.prepend(contentsOf: ["purple", "teal"]) +/// // colors: ["purple", "teal", "red", "blue", "yellow"] +/// +/// Unlike arrays, deques do not currently provide direct unsafe access to their +/// underlying storage. They also lack a `capacity` property -- the size of the +/// storage buffer at any given point is an unstable implementation detail that +/// should not affect application logic. (However, deques do provide a +/// `reserveCapacity` method.) +@frozen +public struct Deque { + @usableFromInline + internal typealias _Slot = _DequeSlot + + @usableFromInline + internal var _storage: _Storage + + @inlinable + internal init(_storage: _Storage) { + self._storage = _storage + } + + /// Creates an empty deque with preallocated space for at least the specified + /// number of elements. + /// + /// - Parameter minimumCapacity: The minimum number of elements that the + /// newly created deque should be able to store without reallocating its + /// storage buffer. + @inlinable + public init(minimumCapacity: Int) { + self._storage = _Storage(minimumCapacity: minimumCapacity) + } +} + +extension Deque: @unchecked Sendable where Element: Sendable {} diff --git a/Sources/_StringProcessing/DequeModule/Deque/_DequeBuffer.swift b/Sources/_StringProcessing/DequeModule/Deque/_DequeBuffer.swift new file mode 100644 index 00000000..41e3e8ad --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/_DequeBuffer.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +@_fixed_layout +@usableFromInline +internal final class _DequeBuffer: ManagedBuffer<_DequeBufferHeader, Element> { + @inlinable + deinit { + self.withUnsafeMutablePointers { header, elements in + header.pointee._checkInvariants() + + let capacity = header.pointee.capacity + let count = header.pointee.count + let startSlot = header.pointee.startSlot + + if startSlot.position + count <= capacity { + (elements + startSlot.position).deinitialize(count: count) + } else { + let firstRegion = capacity - startSlot.position + (elements + startSlot.position).deinitialize(count: firstRegion) + elements.deinitialize(count: count - firstRegion) + } + } + } +} + +extension _DequeBuffer: CustomStringConvertible { + @usableFromInline + internal var description: String { + withUnsafeMutablePointerToHeader { "_DequeStorage<\(Element.self)>\($0.pointee)" } + } +} + +/// The type-punned empty singleton storage instance. +@usableFromInline +nonisolated(unsafe) internal let _emptyDequeStorage + = _DequeBuffer.create( + minimumCapacity: 0, + makingHeaderWith: { _ in + _DequeBufferHeader(capacity: 0, count: 0, startSlot: .init(at: 0)) + }) diff --git a/Sources/_StringProcessing/DequeModule/Deque/_DequeBufferHeader.swift b/Sources/_StringProcessing/DequeModule/Deque/_DequeBufferHeader.swift new file mode 100644 index 00000000..31f37179 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/Deque/_DequeBufferHeader.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +@usableFromInline +internal struct _DequeBufferHeader { + @usableFromInline + var capacity: Int + + @usableFromInline + var count: Int + + @usableFromInline + var startSlot: _DequeSlot + + @usableFromInline + init(capacity: Int, count: Int, startSlot: _DequeSlot) { + self.capacity = capacity + self.count = count + self.startSlot = startSlot + _checkInvariants() + } + + #if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + precondition(capacity >= 0) + precondition(count >= 0 && count <= capacity) + precondition(startSlot.position >= 0 && startSlot.position <= capacity) + } + #else + @inlinable @inline(__always) + internal func _checkInvariants() {} + #endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension _DequeBufferHeader: CustomStringConvertible { + @usableFromInline + internal var description: String { + "(capacity: \(capacity), count: \(count), startSlot: \(startSlot))" + } +} diff --git a/Sources/_StringProcessing/DequeModule/_DequeSlot.swift b/Sources/_StringProcessing/DequeModule/_DequeSlot.swift new file mode 100644 index 00000000..87df7048 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/_DequeSlot.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +@usableFromInline +@frozen +struct _DequeSlot { + @usableFromInline + internal var position: Int + + @_alwaysEmitIntoClient + @_transparent + init(at position: Int) { + assert(position >= 0) + self.position = position + } +} + +extension _DequeSlot { + @_alwaysEmitIntoClient + @_transparent + internal static var zero: Self { Self(at: 0) } + + @_alwaysEmitIntoClient + @_transparent + internal func advanced(by delta: Int) -> Self { + Self(at: position &+ delta) + } + + @_alwaysEmitIntoClient + @_transparent + internal func orIfZero(_ value: Int) -> Self { + guard position > 0 else { return Self(at: value) } + return self + } +} + +extension _DequeSlot: CustomStringConvertible { + @usableFromInline + var description: String { + "@\(position)" + } +} + +extension _DequeSlot: Equatable { + @_alwaysEmitIntoClient + @_transparent + static func ==(left: Self, right: Self) -> Bool { + left.position == right.position + } +} + +extension _DequeSlot: Comparable { + @_alwaysEmitIntoClient + @_transparent + static func <(left: Self, right: Self) -> Bool { + left.position < right.position + } +} + +extension Range where Bound == _DequeSlot { + @_alwaysEmitIntoClient + @_transparent + internal var _count: Int { upperBound.position - lowerBound.position } + + @_alwaysEmitIntoClient + @_transparent + internal var _offsets: Range { + Range(uncheckedBounds: (lowerBound.position, upperBound.position)) + } +} diff --git a/Sources/_StringProcessing/DequeModule/_UnsafeDequeHandle.swift b/Sources/_StringProcessing/DequeModule/_UnsafeDequeHandle.swift new file mode 100644 index 00000000..42874d21 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/_UnsafeDequeHandle.swift @@ -0,0 +1,1458 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +@frozen +@usableFromInline +struct _UnsafeDequeHandle: ~Copyable { + @usableFromInline + typealias Slot = _DequeSlot + + @usableFromInline + var _buffer: UnsafeMutableBufferPointer + + @usableFromInline + var count: Int + + @usableFromInline + var startSlot: Slot + + @_alwaysEmitIntoClient + @_transparent + internal init( + buffer: UnsafeMutableBufferPointer, + count: Int, + startSlot: _DequeSlot + ) { + self._buffer = buffer + self.count = count + self.startSlot = startSlot + } + + @inlinable + internal consuming func dispose() { + _checkInvariants() + self.mutableSegments().deinitialize() + _buffer.deallocate() + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal static var empty: Self { + Self(buffer: ._empty, count: 0, startSlot: .zero) + } + + @_alwaysEmitIntoClient + @_transparent + internal static func allocate( + capacity: Int + ) -> Self { + Self( + buffer: capacity > 0 ? .allocate(capacity: capacity) : ._empty, + count: 0, + startSlot: .zero) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + precondition(capacity >= 0) + precondition(count >= 0 && count <= capacity) + precondition(startSlot.position >= 0 && startSlot.position <= capacity) + } +#else + @inlinable @inline(__always) + internal func _checkInvariants() {} +#endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @usableFromInline + internal var description: String { + "(capacity: \(capacity), count: \(count), start: \(startSlot))" + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal var _baseAddress: UnsafeMutablePointer { + _buffer.baseAddress.unsafelyUnwrapped + } + + @_alwaysEmitIntoClient + @_transparent + internal var capacity: Int { + _buffer.count + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal func isIdentical(to other: borrowing Self) -> Bool { + self._buffer._isIdentical(to: other._buffer) + && self.count == other.count + && self.startSlot == other.startSlot + } +} + +// MARK: Test initializer + +extension _UnsafeDequeHandle where Element: ~Copyable { + internal static func allocate( + capacity: Int, + startSlot: Slot, + count: Int, + generator: (Int) -> Element + ) -> Self { + precondition(capacity >= 0) + precondition( + startSlot.position >= 0 && + (startSlot.position < capacity || (capacity == 0 && startSlot.position == 0))) + precondition(count <= capacity) + + var h = Self.allocate(capacity: capacity) + h.count = count + h.startSlot = startSlot + if h.count > 0 { + let segments = h.mutableSegments() + let c = segments.first.count + for i in 0 ..< c { + segments.first.initializeElement(at: i, to: generator(i)) + } + if let second = segments.second { + for i in c ..< h.count { + second.initializeElement(at: i - c, to: generator(i)) + } + } + } + return h + } +} + +// MARK: Slots + +extension _UnsafeDequeHandle where Element: ~Copyable { + /// The slot immediately following the last valid one. (`endSlot` refers to + /// the valid slot corresponding to `endIndex`, which is a different thing + /// entirely.) + @_alwaysEmitIntoClient + @_transparent + internal var limSlot: Slot { + Slot(at: capacity) + } + + @_alwaysEmitIntoClient + @_transparent + internal func slot(after slot: Slot) -> Slot { + assert(slot.position < capacity) + let position = slot.position + 1 + if position >= capacity { + return Slot(at: 0) + } + return Slot(at: position) + } + + @_alwaysEmitIntoClient + @_transparent + internal func slot(before slot: Slot) -> Slot { + assert(slot.position < capacity) + if slot.position == 0 { return Slot(at: capacity - 1) } + return Slot(at: slot.position - 1) + } + + @_alwaysEmitIntoClient + @_transparent + internal func slot(_ slot: Slot, offsetBy delta: Int) -> Slot { + assert(slot.position <= capacity) + let position = slot.position + delta + if delta >= 0 { + if position >= capacity { return Slot(at: position - capacity) } + } else { + if position < 0 { return Slot(at: position + capacity) } + } + return Slot(at: position) + } + + @_alwaysEmitIntoClient + @_transparent + internal var endSlot: Slot { + slot(startSlot, offsetBy: count) + } + + /// Return the storage slot corresponding to the specified offset, which may + /// or may not address an existing element. + @_alwaysEmitIntoClient + @_transparent + internal func slot(forOffset offset: Int) -> Slot { + assert(offset >= 0) + assert(offset <= capacity) // Not `count`! + + // Note: The use of wrapping addition/subscription is justified here by the + // fact that `offset` is guaranteed to fall in the range `0 ..< capacity`. + // Eliminating the overflow checks leads to a measurable speedup for + // random-access subscript operations. (Up to 2x on some microbenchmarks.) + let position = startSlot.position &+ offset + guard position < capacity else { return Slot(at: position &- capacity) } + return Slot(at: position) + } + + @_alwaysEmitIntoClient + @_transparent + internal func distance(from start: Slot, to end: Slot) -> Int { + assert(start.position >= 0 && start.position <= capacity) + assert(end.position >= 0 && end.position <= capacity) + + if end.position >= start.position { + return end.position - start.position + } else { + // Handle wrap-around case + return capacity - start.position + end.position + } + } +} + +// MARK: Element Access + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal func ptr(at slot: Slot) -> UnsafePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return UnsafePointer(_baseAddress + slot.position) + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func mutablePtr( + at slot: Slot + ) -> UnsafeMutablePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return _baseAddress + slot.position + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal subscript(offset offset: Int) -> Element { + @inline(__always) + _read { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield ptr(at: slot).pointee + } + @inline(__always) + _modify { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield &mutablePtr(at: slot).pointee + } + } +} + +// MARK: Access to contiguous regions + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal var mutableBuffer: UnsafeMutableBufferPointer { + mutating get { + _buffer + } + } + + @_alwaysEmitIntoClient + @_transparent + internal func buffer(for range: Range) -> UnsafeBufferPointer { + assert(range.upperBound.position <= capacity) + return .init(_buffer._extracting(unchecked: range._offsets)) + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func mutableBuffer( + for range: Range + ) -> UnsafeMutableBufferPointer { + assert(range.upperBound.position <= capacity) + return _buffer._extracting(unchecked: range._offsets) + } +} + +extension _UnsafeDequeHandle { + @discardableResult + @_alwaysEmitIntoClient + internal mutating func initialize( + at start: Slot, + from source: UnsafeBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start).initialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @discardableResult + @_alwaysEmitIntoClient + internal mutating func moveInitialize( + at start: Slot, + from source: UnsafeMutableBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start) + .moveInitialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +// MARK: Access to Segments + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal func nextSegment( + after startOffset: Int + ) -> UnsafeBufferPointer { + assert(startOffset >= 0 && startOffset <= count) + guard _buffer.baseAddress != nil else { + return .init(._empty) + } + let position = startSlot.position &+ startOffset + if position < capacity { + return UnsafeBufferPointer( + start: ptr(at: Slot(at: position)), + count: Swift.min(count &- startOffset, capacity &- position)) + } + // We're after the wrap + return UnsafeBufferPointer( + start: ptr(at: Slot(at: position &- capacity)), + count: startSlot.position &+ count &- position) + } + + @_alwaysEmitIntoClient + internal func previousSegment( + before startOffset: Int + ) -> UnsafeBufferPointer { + assert(startOffset >= 0 && startOffset <= count) + guard _buffer.baseAddress != nil else { + return .init(._empty) + } + let position = startSlot.position &+ startOffset + if position <= capacity { + return UnsafeBufferPointer( + start: ptr(at: startSlot), + count: startOffset) + } + // We're after the wrap + return UnsafeBufferPointer( + start: ptr(at: Slot(at: 0)), + count: position - capacity) + } + + @_alwaysEmitIntoClient + internal func segments() -> _UnsafeDequeSegments { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } + let wrap = capacity &- startSlot.position + if count <= wrap { + return .init(start: ptr(at: startSlot), count: count) + } + return .init(first: ptr(at: startSlot), count: wrap, + second: ptr(at: .zero), count: count &- wrap) + } + + @_alwaysEmitIntoClient + internal func segments( + forOffsets offsets: Range + ) -> _UnsafeDequeSegments { + // Note: no asserts for bounds checks, as this is used to implement + // appends/prepends + assert(offsets.count <= capacity) + guard _buffer.baseAddress != nil else { + return .init(._empty) + } + let start = slot(forOffset: offsets.lowerBound) + let wrap = capacity &- start.position + if offsets.count <= wrap { + return .init(start: ptr(at: start), count: offsets.count) + } + return .init( + first: ptr(at: start), count: capacity &- start.position, + second: ptr(at: .zero), count: offsets.count &- wrap) + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func mutableSegments() -> _UnsafeMutableDequeSegments { + .init(mutating: segments()) + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func mutableSegments( + forOffsets range: Range + ) -> _UnsafeMutableDequeSegments { + .init(mutating: segments(forOffsets: range)) + } + + @_alwaysEmitIntoClient + internal mutating func mutableSegments( + between start: Slot, + and end: Slot + ) -> _UnsafeMutableDequeSegments { + assert(start.position <= capacity) + assert(end.position <= capacity) + if start < end { + return .init( + start: mutablePtr(at: start), + count: end.position - start.position) + } + return .init( + first: mutablePtr(at: start), count: capacity - start.position, + second: mutablePtr(at: .zero), count: end.position) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func availableSegments() -> _UnsafeMutableDequeSegments { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } + let endSlot = self.endSlot + guard count < capacity else { return .init(start: mutablePtr(at: endSlot), count: 0) } + if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } + return .init(mutableBuffer(for: endSlot ..< limSlot), + mutableBuffer(for: .zero ..< startSlot)) + } +} + +// MARK: Wholesale Copying and Reallocation + +extension _UnsafeDequeHandle { + /// Copy elements in `handle` into a newly allocated handle without changing its + /// capacity or layout. + @_alwaysEmitIntoClient + internal borrowing func allocateCopy() -> Self { + var result: _UnsafeDequeHandle = .allocate(capacity: self.capacity) + result.count = self.count + result.startSlot = self.startSlot + let src = self.segments() + result.initialize(at: self.startSlot, from: src.first) + if let second = src.second { + result.initialize(at: .zero, from: second) + } + return result + } + + /// Copy elements in `handle` into a newly allocated handle with the specified + /// minimum capacity. This operation does not preserve layout. + @_alwaysEmitIntoClient + internal func allocateCopy(capacity: Int) -> Self { + precondition(capacity >= self.count) + var result: _UnsafeDequeHandle = .allocate(capacity: capacity) + result.count = self.count + let src = self.segments() + let next = result.initialize(at: .zero, from: src.first) + if let second = src.second { + result.initialize(at: next, from: second) + } + return result + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count, "Capacity overflow") + guard newCapacity != capacity else { return } + + var new = _UnsafeDequeHandle.allocate(capacity: newCapacity) + let source = self.mutableSegments() + let next = new.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + new.moveInitialize(at: next, from: second) + } + _buffer.deallocate() + _buffer = new._buffer + startSlot = .zero + } +} + +// MARK: Iteration + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal func slotRange(following offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) + } + let wrapOffset = Swift.min(capacity - startSlot.position, count) + + if offset < wrapOffset { + defer { offset += wrapOffset - offset } + return Range( + uncheckedBounds: (startSlot.advanced(by: offset), startSlot.advanced(by: wrapOffset))) + } + let lowerSlot = Slot.zero.advanced(by: offset - wrapOffset) + let upperSlot = lowerSlot.advanced(by: count - wrapOffset) + defer { offset += count - offset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) + } + + @_alwaysEmitIntoClient + internal func slotRange(preceding offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) + } + let wrapOffset = Swift.min(capacity - startSlot.position, count) + + if offset <= wrapOffset { + defer { offset = 0 } + return Range( + uncheckedBounds: (startSlot, startSlot.advanced(by: offset))) + } + let lowerSlot = Slot.zero + let upperSlot = lowerSlot.advanced(by: offset - wrapOffset) + defer { offset = wrapOffset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) + } +} + +// MARK: Swap + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedSwapAt(_ i: Int, _ j: Int) { + let slot1 = self.slot(forOffset: i) + let slot2 = self.slot(forOffset: j) + self.mutableBuffer.swapAt(slot1.position, slot2.position) + } +} + +// MARK: Replacement + +extension _UnsafeDequeHandle { + /// Replace the elements in `range` with `newElements`. The deque's count must + /// not change as a result of calling this function. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + internal mutating func uncheckedReplaceInPlace( + inOffsets range: Range, + with newElements: C + ) where C.Element == Element { + assert(range.upperBound <= count) + assert(newElements.count == range.count) + guard !range.isEmpty else { return } + let target = mutableSegments(forOffsets: range) + target.reassign(copying: newElements) + } +} + +// MARK: Consumption + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal mutating func unsafeConsumeAll( + with body: (UnsafeMutableBufferPointer) -> Void + ) { + let segments = mutableSegments() + body(segments.first) + if let second = segments.second { + body(second) + } + self.count = 0 + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func unsafeConsumePrefix( + upTo offset: Int, + with body: (UnsafeMutableBufferPointer) -> Void + ) { + assert(offset >= 0 && offset <= count) + let segments = mutableSegments(forOffsets: Range(uncheckedBounds: (0, offset))) + body(segments.first) + if let second = segments.second { + body(second) + } + self.startSlot = self.slot(forOffset: offset) + self.count &-= offset + } +} + +// MARK: Appending and prepending + +extension _UnsafeDequeHandle where Element: ~Copyable { + /// Append `element` to the end of this buffer. The buffer must have enough + /// free capacity to insert one new element. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedAppend(_ element: consuming Element) { + assert(count < capacity) + mutablePtr(at: endSlot).initialize(to: element) + count &+= 1 + } + + /// Prepend `element` to the front of this buffer. The buffer must have enough + /// free capacity to insert one new element. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedPrepend(_ element: consuming Element) { + assert(count < capacity) + let slot = self.slot(before: startSlot) + mutablePtr(at: slot).initialize(to: element) + startSlot = slot + count &+= 1 + } +} + +#if compiler(>=6.2) +@available(SwiftStdlib 5.7, *) +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal func _initialize( + initializedCount: inout Int, + initializingWith body: (inout OutputSpan) throws(E) -> R + ) throws(E) -> R { + var span = OutputSpan(buffer: self, initializedCount: 0) + defer { + initializedCount &+= span.finalize(for: self) + span = OutputSpan() + } + return try body(&span) + } +} +#endif + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func _append( + count: Int + ) -> _UnsafeMutableDequeSegments? { + guard count > 0 else { return nil } + let origCount = self.count + self.count &+= count + return self.mutableSegments(forOffsets: origCount ..< origCount + count) + } + + @_alwaysEmitIntoClient + internal mutating func _prepend( + count: Int + ) -> _UnsafeMutableDequeSegments? { + assert(self.count + count <= capacity) + guard count > 0 else { return .init() } + let oldStart = self.startSlot + self.startSlot = self.slot(startSlot, offsetBy: -count) + self.count &+= count + + return self.mutableSegments(between: self.startSlot, and: oldStart) + } +} + +#if compiler(>=6.2) +@available(SwiftStdlib 5.7, *) +extension _UnsafeDequeHandle where Element: ~Copyable { + /// Append a given number of items to the end of this deque by populating + /// a series of storage regions through repeated calls of the specified + /// callback function. + /// + /// This unchecked routine does not verify that the deque has sufficient + /// capacity to store the new items. + /// + /// The newly appended items are not guaranteed to form a single contiguous + /// storage region. Therefore, the supplied callback may be invoked multiple + /// times to initialize each successive chunk of storage. However, invocations + /// cease when the callback fails to fully populate its output span or when if + /// it throws an error. In such cases, the deque keeps all items that were + /// successfully initialized before the callback terminated. + /// + /// - Parameters: + /// - capacity: The maximum number of items to append to the deque. + /// - body: A callback that gets called at most twice to directly + /// populate newly reserved storage within the deque. The function + /// is allowed to initialize fewer than `count` items. The deque is + /// appended however many items the callback adds to the output span + /// before it returns (or before it throws an error). + /// + /// - Complexity: O(`capacity`) + @_alwaysEmitIntoClient + internal mutating func uncheckedAppend( + addingCount newItemCount: Int, + initializingWith body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + let gap = self.mutableSegments(forOffsets: count ..< count + newItemCount) + let c = self.count &+ gap.first.count + try gap.first._initialize( + initializedCount: &self.count, initializingWith: body) + if self.count == c, let second = gap.second { + try second._initialize( + initializedCount: &self.count, initializingWith: body) + } + } + + /// Prepend a given number of items to the end of this deque by populating + /// a series of storage regions through repeated calls of the specified + /// callback function. + /// + /// This unchecked routine does not verify that the deque has sufficient + /// capacity to store the new items. + /// + /// var buffer = RigidDeque(capacity: 20) + /// buffer.append(10) + /// var i = 0 + /// buffer.prepend(count: 10) { target in + /// while !target.isFull { + /// target.append(i) + /// i += 1 + /// } + /// } + /// // `buffer` now contains [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + /// + /// The newly prepended items are not guaranteed to form a single contiguous + /// storage region. Therefore, the supplied callback may be invoked multiple + /// times to initialize each successive chunk of storage. However, invocations + /// cease when the callback fails to fully populate its output span or when if + /// it throws an error. In such cases, the deque keeps all items that were + /// successfully initialized before the callback terminated. + /// + /// var buffer = RigidDeque(capacity: 20) + /// buffer.append(10) + /// var i = 0 + /// buffer.prepend(count: 10) { target in + /// while !target.isFull, i <= 5 { + /// target.append(i) + /// i += 1 + /// } + /// } + /// // `buffer` now contains [0, 1, 2, 3, 4, 5, 10] + /// + /// - Parameters: + /// - count: The number of items to append to the deque. + /// - body: A callback that gets called at most twice to directly + /// populate newly reserved storage within the deque. The function + /// is allowed to initialize fewer than `count` items. The deque is + /// prepended however many items the callback adds to output span + /// before it returns (or before it throws an error). + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + internal mutating func uncheckedPrepend( + addingCount newItemCount: Int, + initializingWith body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + guard let gap = self._prepend(count: newItemCount) else { return } + + var c = 0 + defer { + if c < newItemCount { + closeGap(offsets: c ..< newItemCount) + } + } + try gap.first._initialize(initializedCount: &c, initializingWith: body) + if c == gap.first.count, let second = gap.second { + try second._initialize(initializedCount: &c, initializingWith: body) + } + } +} +#endif + +@available(SwiftStdlib 5.7, *) +extension _UnsafeDequeHandle where Element: ~Copyable { + /// Appends the elements of a buffer to the end of this deque, leaving the + /// buffer uninitialized. + /// + /// This unchecked routine does not verify that the deque has sufficient + /// capacity to store the new items. + /// + /// - Parameters: + /// - items: A fully initialized buffer whose contents to move into + /// the deque. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + internal mutating func uncheckedAppend( + moving items: UnsafeMutableBufferPointer + ) { + guard let gap = _append(count: items.count) else { return } + + gap.first.moveInitializeAll( + fromContentsOf: items._extracting(first: gap.first.count)) + + if let second = gap.second { + assert(gap.first.count + second.count == items.count) + second.moveInitializeAll( + fromContentsOf: items._extracting(last: second.count)) + } + } + + /// Prepends the elements of a buffer to the front of this deque, leaving the + /// buffer uninitialized. + /// + /// This unchecked routine does not verify that the deque has sufficient + /// capacity to store the new items. + /// + /// - Parameters: + /// - items: A fully initialized buffer whose contents to move into + /// the deque. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + internal mutating func uncheckedPrepend( + moving items: UnsafeMutableBufferPointer + ) { + guard let gap = _prepend(count: items.count) else { return } + + gap.first.moveInitializeAll( + fromContentsOf: items._extracting(first: gap.first.count)) + + if let second = gap.second { + assert(gap.first.count + second.count == items.count) + second.moveInitializeAll( + fromContentsOf: items._extracting(last: second.count)) + } + } +} + +extension _UnsafeDequeHandle { + /// Append the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedAppend(copying source: UnsafeBufferPointer) { + guard let gap = _append(count: source.count) else { return } + gap.initialize(copying: source) + } + + /// Prepend the contents of `source` to this buffer. The buffer must have + /// enough free capacity to insert the new elements. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedPrepend(copying source: UnsafeBufferPointer) { + guard let gap = _prepend(count: source.count) else { return } + gap.initialize(copying: source) + } +} + +@available(SwiftStdlib 5.7, *) +extension _UnsafeDequeHandle { + @_alwaysEmitIntoClient + @inline(__always) + internal mutating func uncheckedAppend>( + copyingPrefixOf items: S + ) -> S.Iterator { + // Note: You're supposed to handle contiguous sequences before calling + // this function. You do this by invoking `withContiguousStorageIfAvailable`. + let (it, c) = self.availableSegments().initialize(fromSequencePrefix: items) + self.count += c + return it + } + + @_alwaysEmitIntoClient + @inline(__always) + internal mutating func uncheckedPrepend>( + copying items: C, + exactCount: Int + ) { + // Note: You're supposed to handle contiguous sequences before calling + // this function. You do this by invoking `withContiguousStorageIfAvailable`. + guard let gap = self._prepend(count: exactCount) else { return } + gap.initialize(copying: items) + } +} + +// MARK: Opening and Closing Gaps + +extension _UnsafeDequeHandle where Element: ~Copyable { + @discardableResult + @_alwaysEmitIntoClient + @_transparent + internal mutating func move( + from source: Slot, + to target: Slot, + count: Int + ) -> (source: Slot, target: Slot) { + assert(count >= 0) + assert(source.position + count <= self.capacity) + assert(target.position + count <= self.capacity) + guard count > 0 else { return (source, target) } + mutablePtr(at: target) + .moveInitialize(from: mutablePtr(at: source), count: count) + return (slot(source, offsetBy: count), slot(target, offsetBy: count)) + } + + @_alwaysEmitIntoClient + @_transparent + func isLeftLeaning(_ offset: Int) -> Bool { + offset < count - offset + } + + @_alwaysEmitIntoClient + @_transparent + func isLeftLeaning(_ subrange: Range) -> Bool { + subrange.lowerBound < count - subrange.upperBound + } + + /// Slide elements around so that there is a gap of uninitialized slots of + /// size `gapSize` starting at `offset`, and return a (potentially wrapped) + /// buffer holding the newly inserted slots. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter gapSize: The number of uninitialized slots to create. + /// - Parameter offset: The offset from the start at which the uninitialized + /// slots should start. + @_alwaysEmitIntoClient + internal mutating func openGap( + ofSize gapSize: Int, + atOffset offset: Int + ) -> _UnsafeMutableDequeSegments { + assert(offset >= 0 && offset <= self.count) + assert(self.count + gapSize <= capacity) + assert(gapSize > 0) + + let headCount = offset + let tailCount = count - offset + if tailCount <= headCount { + // Open the gap by sliding elements to the right. + + let originalEnd = self.slot(startSlot, offsetBy: count) + let newEnd = self.slot(startSlot, offsetBy: count + gapSize) + let gapStart = self.slot(forOffset: offset) + let gapEnd = self.slot(gapStart, offsetBy: gapSize) + + let sourceIsContiguous = gapStart <= originalEnd.orIfZero(capacity) + let targetIsContiguous = gapEnd <= newEnd.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping; we just need to slide + // elements after the gap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲........ABCD .̲.......ABCDEFGH̲.̲ + // 1) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH......ABCD .̲H......ABCDEFG.̲.̲ + move(from: gapStart, to: gapEnd, count: tailCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) E̲FGH.........ABC̲D̲ + // 1) .̲..EFGH......ABC̲D̲ + // 2) .̲CDEFGH......AB.̲.̲ + assert(startSlot > originalEnd.orIfZero(capacity)) + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: gapStart, to: gapEnd, count: capacity - gapStart.position) + } else if sourceIsContiguous { + // Opening the gap pushes subsequent elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ........ABC̲D̲E̲FGH. + // 1) GH......ABC̲D̲E̲F... + // 2) GH......AB.̲.̲.̲CDEF + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: newEnd.position) + move(from: gapStart, to: gapEnd, count: tailCount - newEnd.position) + } else { + // The rest of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) GH.........AB̲C̲D̲EF + // 1) ...GH......AB̲C̲D̲EF + // 2) DEFGH......AB̲C̲.̲.. + // 3) DEFGH......A.̲.̲.̲BC + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: originalEnd.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) + } + count += gapSize + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) + } + + // Open the gap by sliding elements to the left. + + let originalStart = self.startSlot + let newStart = self.slot(originalStart, offsetBy: -gapSize) + let gapEnd = self.slot(forOffset: offset) + let gapStart = self.slot(gapEnd, offsetBy: -gapSize) + + let sourceIsContiguous = originalStart <= gapEnd.orIfZero(capacity) + let targetIsContiguous = newStart <= gapStart.orIfZero(capacity) + + if sourceIsContiguous && targetIsContiguous { + // No need to deal with any wrapping. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) ....A̲B̲C̲DEFGH... GH.........̲A̲B̲CDEF .̲A̲B̲CDEFGH.......̲.̲ + // 1) .ABC.̲.̲.̲DEFGH... GH......AB.̲.̲.̲CDEF .̲.̲.̲CDEFGH....AB.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if targetIsContiguous { + // The gap itself will be wrapped. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) C̲D̲EFGH.........A̲B̲ + // 1) C̲D̲EFGH.....AB...̲.̲ + // 2) .̲.̲EFGH.....ABCD.̲.̲ + assert(originalStart >= newStart) + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapEnd.position) + } else if sourceIsContiguous { + // Opening the gap pushes preceding elements across the wrap. + + // Illustrated steps: (underscores mark eventual gap position) + // + // 0) .AB̲C̲D̲EFGH......... + // 1) ...̲C̲D̲EFGH.......AB + // 2) CD.̲.̲.̲EFGH.......AB + move(from: originalStart, to: newStart, count: capacity - newStart.position) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } else { + // The preceding of the items are wrapped, and will remain so. + + // Illustrated steps: (underscores mark eventual gap position) + // 0) CD̲E̲F̲GHIJKL.........AB + // 1) CD̲E̲F̲GHIJKL......AB... + // 2) ..̲.̲F̲GHIJKL......ABCDE + // 3) F.̲.̲.̲GHIJKL......ABCDE + move(from: originalStart, to: newStart, count: capacity - originalStart.position) + move(from: .zero, to: limSlot.advanced(by: -gapSize), count: gapSize) + move(from: Slot.zero.advanced(by: gapSize), to: .zero, count: gapStart.position) + } + startSlot = newStart + count += gapSize + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) + } + + /// Close the gap of already uninitialized elements in `bounds`, sliding + /// elements outside of the gap to eliminate it, and updating `count` to + /// reflect the removal. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func closeGap(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + let gapSize = bounds.count + guard gapSize > 0 else { return } + + let gapStart = self.slot(forOffset: bounds.lowerBound) + let gapEnd = self.slot(forOffset: bounds.upperBound) + + let headCount = bounds.lowerBound + let tailCount = count - bounds.upperBound + + if headCount >= tailCount { + // Close the gap by sliding elements to the left. + let originalEnd = endSlot + let newEnd = self.slot(forOffset: count - gapSize) + + let sourceIsContiguous = gapEnd < originalEnd.orIfZero(capacity) + let targetIsContiguous = gapStart <= newEnd.orIfZero(capacity) + if tailCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH.. EF.̲.̲.̲GH........ABCD .̲.̲.̲E..........ABCD.̲.̲ .̲.̲.̲EF........ABCD .̲.̲.̲DE.......ABC + // 1) ....ABCDE̲F̲G̲H..... EFG̲H̲.̲..........ABCD .̲.̲.̲...........ABCDE̲.̲ E̲F̲.̲..........ABCD D̲E̲.̲.........ABC + move(from: gapEnd, to: gapStart, count: tailCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the subsequent elements. + + // 0) .̲.̲.̲EFGH.......ABCD.̲.̲ EFGH.......ABCD.̲.̲.̲ + // 1) .̲.̲.̲..GH.......ABCDE̲F̲ ..GH.......ABCDE̲F̲G̲ + // 2) G̲H̲.̲...........ABCDE̲F̲ GH.........ABCDE̲F̲G̲ + let c = capacity - gapStart.position + assert(tailCount > c) + let next = move(from: gapEnd, to: gapStart, count: c) + move(from: next.source, to: .zero, count: tailCount - c) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) HI....ABCDE.̲.̲.̲FG + // 1) HI....ABCDEF̲G̲.̲.. + // 2) ......ABCDEF̲G̲H̲I. + let next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + move(from: .zero, to: next.target, count: originalEnd.position) + } else { + // We need to move elements across a wrap that won't go away. + + // 0) HIJKL....ABCDE.̲.̲.̲FG + // 1) HIJKL....ABCDEF̲G̲.̲.. + // 2) ...KL....ABCDEF̲G̲H̲IJ + // 3) KL.......ABCDEF̲G̲H̲IJ + var next = move(from: gapEnd, to: gapStart, count: capacity - gapEnd.position) + next = move(from: .zero, to: next.target, count: gapSize) + move(from: next.source, to: .zero, count: newEnd.position) + } + count -= gapSize + } else { + // Close the gap by sliding elements to the right. + let originalStart = startSlot + let newStart = slot(startSlot, offsetBy: gapSize) + + let sourceIsContiguous = originalStart < gapStart.orIfZero(capacity) + let targetIsContiguous = newStart <= gapEnd.orIfZero(capacity) + + if headCount == 0 { + // No need to move any elements. + } else if sourceIsContiguous && targetIsContiguous { + // No need to deal with wrapping. + + // 0) ....ABCD.̲.̲.̲EFGH..... EFGH........AB.̲.̲.̲CD .̲.̲.̲CDEFGH.......AB.̲.̲ DEFGH.......ABC.̲.̲ + // 1) .......AB̲C̲D̲EFGH..... EFGH...........̲A̲B̲CD .̲A̲B̲CDEFGH..........̲.̲ DEFGH.........AB̲C̲ ABCDEFGH........̲.̲.̲ + move(from: originalStart, to: newStart, count: headCount) + } else if sourceIsContiguous { + // The gap lies across the wrap from the preceding elements. + + // 0) .̲.̲DEFGH.......ABC.̲.̲ .̲.̲.̲EFGH.......ABCD + // 1) B̲C̲DEFGH.......A...̲.̲ B̲C̲D̲DEFGH......A... + // 2) B̲C̲DEFGH...........̲A̲ B̲C̲D̲DEFGH.........A + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapEnd.position) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } else if targetIsContiguous { + // We need to move elements across a wrap, but the wrap will + // disappear when we're done. + + // 0) CD.̲.̲.̲EFGHI.....AB + // 1) ...̲C̲D̲EFGHI.....AB + // 1) .AB̲C̲D̲EFGHI....... + move(from: .zero, to: gapEnd.advanced(by: -gapStart.position), count: gapStart.position) + move(from: startSlot, to: newStart, count: headCount - gapStart.position) + } else { + // We need to move elements across a wrap that won't go away. + // 0) FG.̲.̲.̲HIJKLMNO....ABCDE + // 1) ...̲F̲G̲HIJKLMNO....ABCDE + // 2) CDE̲F̲G̲HIJKLMNO....AB... + // 3) CDE̲F̲G̲HIJKLMNO.......AB + move(from: .zero, to: Slot.zero.advanced(by: gapSize), count: gapStart.position) + move(from: limSlot.advanced(by: -gapSize), to: .zero, count: gapSize) + move(from: startSlot, to: newStart, count: headCount - gapEnd.position) + } + startSlot = newStart + count -= gapSize + } + } +} + +// MARK: Rotating elements + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func rotate( + toStartAtOffset newStart: Int + ) { + let oldCount = count + // Open a gap at `newStart` that's the size of the free capacity, swapping + // the positions of the prefix (`startSlot.. 0 { + _ = openGap(ofSize: gapSize, atOffset: newStart) + } + + // With the prefix and suffix swapped, update the starting slot and + // restore the count. + startSlot = slot(startSlot, offsetBy: newStart - oldCount) + count = oldCount + } +} + +// MARK: Insertion + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedInsert( + _ newElement: consuming Element, at offset: Int + ) { + assert(count < capacity) + if offset == 0 { + uncheckedPrepend(newElement) + return + } + if offset == count { + uncheckedAppend(newElement) + return + } + let gap = openGap(ofSize: 1, atOffset: offset) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) + } +} + +#if compiler(>=6.2) +@available(SwiftStdlib 5.7, *) +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func uncheckedInsert( + addingCount newItemCount: Int, + at offset: Int, + initializingWith body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + guard newItemCount > 0 else { return } + let gap = self.openGap(ofSize: newItemCount, atOffset: offset) + + var c = 0 + defer { + if c < newItemCount { + closeGap(offsets: offset + c ..< offset + newItemCount) + } + } + try gap.first._initialize(initializedCount: &c, initializingWith: body) + if c == gap.first.count, let second = gap.second { + try second._initialize(initializedCount: &c, initializingWith: body) + } + } +} +#endif + +extension _UnsafeDequeHandle { + /// Insert all elements from `newElements` into this deque, starting at + /// `offset`. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter newElements: The elements to insert. + /// - Parameter newCount: Must be equal to `newElements.count`. Used to + /// prevent calling `count` more than once. + /// - Parameter offset: The desired offset from the start at which to place + /// the first element. + @_alwaysEmitIntoClient + internal mutating func uncheckedInsert( + contentsOf newElements: __owned C, + count newCount: Int, + atOffset offset: Int + ) where C.Element == Element { + assert(offset <= count) + assert(newElements.count == newCount) + guard newCount > 0 else { return } + let gap = openGap(ofSize: newCount, atOffset: offset) + gap.initialize(copying: newElements) + } +} + +// MARK: Removal + +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemove(at offset: Int) -> Element { + let slot = self.slot(forOffset: offset) + let result = mutablePtr(at: slot).move() + closeGap(offsets: Range(uncheckedBounds: (offset, offset + 1))) + return result + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemoveFirst() -> Element { + assert(count > 0) + let result = mutablePtr(at: startSlot).move() + startSlot = slot(after: startSlot) + count -= 1 + return result + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemoveLast() -> Element { + assert(count > 0) + let slot = self.slot(forOffset: count - 1) + let result = mutablePtr(at: slot).move() + count -= 1 + return result + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemoveFirst(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: 0 ..< n) + target.deinitialize() + startSlot = slot(startSlot, offsetBy: n) + count -= n + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemoveLast(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: count - n ..< count) + target.deinitialize() + count -= n + } + + /// Remove all elements stored in this instance, deinitializing their storage. + /// + /// This method does not ensure that the storage buffer is uniquely + /// referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemoveAll() { + guard count > 0 else { return } + let target = mutableSegments() + target.deinitialize() + count = 0 + startSlot = .zero + } + + /// Remove all elements in `bounds`, deinitializing their storage and sliding + /// remaining elements to close the resulting gap. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @_alwaysEmitIntoClient + @_transparent + internal mutating func uncheckedRemove(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + + // Deinitialize elements in `bounds`. + mutableSegments(forOffsets: bounds).deinitialize() + closeGap(offsets: bounds) + } +} + +// MARK: Replacement + +#if compiler(>=6.2) +@available(SwiftStdlib 5.7, *) +extension _UnsafeDequeHandle where Element: ~Copyable { + @_alwaysEmitIntoClient + internal mutating func _insertAfterReplace( + _ subrange: Range, + addingCount newItemCount: Int, + initializingWith initializer: (inout OutputSpan) throws(E) -> Void + ) throws(E) -> Void { + // Note: we're careful to open/close the gap in a direction that does not + // lead to trying to move slots we deinitialized above. + let delta = newItemCount - subrange.count + let left = isLeftLeaning(subrange) + if delta > 0 { + _ = self.openGap( + ofSize: delta, + atOffset: (left ? subrange.lowerBound : subrange.upperBound)) + } else { + self.closeGap( + offsets: (left ? subrange.prefix(-delta) : subrange.suffix(-delta))) + } + let newRange = Range( + uncheckedBounds: ( + subrange.lowerBound, + subrange.lowerBound &+ newItemCount)) + let gap = self.mutableSegments(forOffsets: newRange) + + var c = 0 + defer { + if c < newItemCount { + closeGap(offsets: newRange.dropFirst(c)) + } + } + try gap.first._initialize(initializedCount: &c, initializingWith: initializer) + if c == gap.first.count, let second = gap.second { + try second._initialize(initializedCount: &c, initializingWith: initializer) + } + } + + @_alwaysEmitIntoClient + internal mutating func uncheckedReplace( + removing subrange: Range, + addingCount newItemCount: Int, + initializingWith initializer: (inout OutputSpan) throws(E) -> Void + ) throws(E) -> Void { + assert( + subrange.lowerBound >= 0 && subrange.upperBound <= count, + "Subrange out of bounds") + assert(newItemCount >= 0, "Cannot add a negative number of items") + assert(count + newItemCount - subrange.count <= capacity, "RigidDeque capacity overflow") + self.mutableSegments(forOffsets: subrange).deinitialize() + try _insertAfterReplace( + subrange, + addingCount: newItemCount, + initializingWith: initializer) + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + @_alwaysEmitIntoClient + internal mutating func uncheckedReplace( + removing subrange: Range, + consumingWith consumer: (inout InputSpan) -> Void, + addingCount newItemCount: Int, + initializingWith initializer: (inout OutputSpan) throws(E) -> Void + ) throws(E) -> Void { + assert( + subrange.lowerBound >= 0 && subrange.upperBound <= count, + "Subrange out of bounds") + assert(newItemCount >= 0, "Cannot add a negative number of items") + assert(count + newItemCount - subrange.count <= capacity, "RigidDeque capacity overflow") + do { + let removed = self.mutableSegments(forOffsets: subrange) + var span = InputSpan( + buffer: removed.first, + initializedCount: removed.first.count) + consumer(&span) + if let second = removed.second { + span = InputSpan(buffer: second, initializedCount: second.count) + consumer(&span) + } + } + try _insertAfterReplace( + subrange, + addingCount: newItemCount, + initializingWith: initializer) + } +#endif +} +#endif diff --git a/Sources/_StringProcessing/DequeModule/_UnsafeDequeSegments.swift b/Sources/_StringProcessing/DequeModule/_UnsafeDequeSegments.swift new file mode 100644 index 00000000..c7252697 --- /dev/null +++ b/Sources/_StringProcessing/DequeModule/_UnsafeDequeSegments.swift @@ -0,0 +1,292 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2026 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 +// +// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception +// +//===----------------------------------------------------------------------===// + +@frozen +@usableFromInline +internal struct _UnsafeDequeSegments { + @usableFromInline + internal let first: UnsafeBufferPointer + + @usableFromInline + internal let second: UnsafeBufferPointer? + + @_alwaysEmitIntoClient + @_transparent + internal init( + _ first: UnsafeBufferPointer, + _ second: UnsafeBufferPointer? = nil + ) { + self.first = first + self.second = second + assert(first.count > 0 || second == nil) + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + start: UnsafePointer, + count: Int + ) { + self.init(UnsafeBufferPointer(start: start, count: count)) + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + first start1: UnsafePointer, + count count1: Int, + second start2: UnsafePointer, + count count2: Int + ) { + self.init(UnsafeBufferPointer(start: start1, count: count1), + UnsafeBufferPointer(start: start2, count: count2)) + } + + @_alwaysEmitIntoClient + @_transparent + internal var count: Int { first.count + (second?.count ?? 0) } + + @_alwaysEmitIntoClient + @_transparent + internal func isIdentical(to other: Self) -> Bool { + guard self.first._isIdentical(to: other.first) else { return false } + switch (self.second, other.second) { + case (nil, nil): return true + case let (a?, b?): return a._isIdentical(to: b) + default: return false + } + } +} + +@frozen +@usableFromInline +internal struct _UnsafeMutableDequeSegments { + @usableFromInline + internal let first: UnsafeMutableBufferPointer + + @usableFromInline + internal let second: UnsafeMutableBufferPointer? + + @_alwaysEmitIntoClient + @_transparent + internal init() { + self.first = .init(start: nil, count: 0) + self.second = nil + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + _ first: UnsafeMutableBufferPointer, + _ second: UnsafeMutableBufferPointer? = nil + ) { + self.first = first + self.second = second?.count == 0 ? nil : second + assert(first.count > 0 || second == nil) + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + start: UnsafeMutablePointer, + count: Int + ) { + self.init(UnsafeMutableBufferPointer(start: start, count: count)) + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + first start1: UnsafeMutablePointer, + count count1: Int, + second start2: UnsafeMutablePointer, + count count2: Int + ) { + self.init(UnsafeMutableBufferPointer(start: start1, count: count1), + UnsafeMutableBufferPointer(start: start2, count: count2)) + } + + @_alwaysEmitIntoClient + @_transparent + internal init(mutating buffer: _UnsafeDequeSegments) { + self.init(.init(mutating: buffer.first), + buffer.second.map { .init(mutating: $0) }) + } +} + +extension _UnsafeMutableDequeSegments { + @_alwaysEmitIntoClient + @_transparent + internal init( + _ first: UnsafeMutableBufferPointer.SubSequence, + _ second: UnsafeMutableBufferPointer? = nil + ) { + self.init(UnsafeMutableBufferPointer(rebasing: first), second) + } + + @_alwaysEmitIntoClient + @_transparent + internal init( + _ first: UnsafeMutableBufferPointer, + _ second: UnsafeMutableBufferPointer.SubSequence + ) { + self.init(first, UnsafeMutableBufferPointer(rebasing: second)) + } +} + +extension _UnsafeMutableDequeSegments where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal var count: Int { first.count + (second?.count ?? 0) } + + @_alwaysEmitIntoClient + @_transparent + internal func prefix(_ n: Int) -> Self { + assert(n >= 0) + if n >= self.count { + return self + } + if n <= first.count { + return Self(first._extracting(first: n)) + } + return Self(first, second!._extracting(first: n - first.count)) + } + + @_alwaysEmitIntoClient + @_transparent + internal func suffix(_ n: Int) -> Self { + assert(n >= 0) + if n >= self.count { + return self + } + guard let second = second else { + return Self(first._extracting(last: n)) + } + if n <= second.count { + return Self(second._extracting(last: n)) + } + return Self(first._extracting(last: n - second.count), second) + } +} + +extension _UnsafeMutableDequeSegments where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + internal func deinitialize() { + first.deinitialize() + second?.deinitialize() + } +} + +extension _UnsafeMutableDequeSegments { + @discardableResult + @_alwaysEmitIntoClient + @_transparent + internal func initialize( + copyingPrefixOf iterator: inout I + ) -> Int + where I.Element == Element { + var copied = 0 + var gap = first + var wrapped = false + while true { + if copied == gap.count { + guard !wrapped, let second = second, second.count > 0 else { break } + gap = second + copied = 0 + wrapped = true + } + guard let next = iterator.next() else { break } + (gap.baseAddress! + copied).initialize(to: next) + copied += 1 + } + return wrapped ? first.count + copied : copied + } + + @_alwaysEmitIntoClient + @_transparent + internal func initialize( + fromSequencePrefix elements: __owned S + ) -> (iterator: S.Iterator, count: Int) + where S.Element == Element { + guard second == nil || first.count >= elements.underestimatedCount else { + var it = elements.makeIterator() + let copied = initialize(copyingPrefixOf: &it) + return (it, copied) + } + // Note: Array._copyContents traps when not given enough space, so we + // need to check if we have enough contiguous space available above. + // + // FIXME: Add support for segmented (a.k.a. piecewise contiguous) + // collections to the stdlib. + var (it, copied) = elements._copyContents(initializing: first) + if copied == first.count, let second = second { + var i = 0 + while i < second.count { + guard let next = it.next() else { break } + (second.baseAddress! + i).initialize(to: next) + i += 1 + } + copied += i + } + return (it, copied) + } + + @_alwaysEmitIntoClient + @_transparent + internal func initialize( + copying elements: UnsafeBufferPointer + ) { + assert(self.count == elements.count) + if let second = second { + let wrap = first.count + first.initializeAll(fromContentsOf: elements._extracting(first: wrap)) + second.initializeAll(fromContentsOf: elements._extracting(last: second.count)) + } else { + first.initializeAll(fromContentsOf: elements) + } + } + + @_alwaysEmitIntoClient + @_transparent + internal func initialize( + copying elements: UnsafeMutableBufferPointer + ) { + self.initialize(copying: UnsafeBufferPointer(elements)) + } + + @_alwaysEmitIntoClient + @_transparent + internal func initialize( + copying elements: __owned some Collection + ) { + assert(self.count == elements.count) + if let second = second { + let wrap = elements.index(elements.startIndex, offsetBy: first.count) + first.initializeAll(fromContentsOf: elements[..( + copying elements: C + ) where C.Element == Element { + assert(elements.count == self.count) + deinitialize() + initialize(copying: elements) + } +} diff --git a/Sources/_StringProcessing/LiteralPrinter.swift b/Sources/_StringProcessing/LiteralPrinter.swift index 0b0037ec..37b331f9 100644 --- a/Sources/_StringProcessing/LiteralPrinter.swift +++ b/Sources/_StringProcessing/LiteralPrinter.swift @@ -96,7 +96,7 @@ extension LiteralPrinter { case error } - mutating func outputList(_ list: inout ArraySlice) throws { + mutating func outputList(_ list: inout Deque.SubSequence) throws { guard let node = list.popFirst() else { return } @@ -154,7 +154,7 @@ extension LiteralPrinter { } } - mutating func outputAlternation(_ list: inout ArraySlice, count: Int) throws { + mutating func outputAlternation(_ list: inout Deque.SubSequence, count: Int) throws { for i in 0.., count: Int) throws { + mutating func outputConcatenation(_ list: inout Deque.SubSequence, count: Int) throws { for _ in 0.., name: String?) throws { + mutating func outputCapture(_ list: inout Deque.SubSequence, name: String?) throws { if let name { output("(?<\(name)>") } else { @@ -179,7 +179,7 @@ extension LiteralPrinter { output(")") } - func requiresGrouping(_ list: ArraySlice) -> Bool { + func requiresGrouping(_ list: Deque.SubSequence) -> Bool { guard let node = list.first else { return false } // malformed? switch node { case .concatenation(let count): @@ -201,7 +201,7 @@ extension LiteralPrinter { } mutating func outputQuantification( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, amount: DSLTree._AST.QuantificationAmount, kind: DSLTree.QuantificationKind ) throws { diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index b2cc467a..aaff29d0 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -59,7 +59,7 @@ extension PrettyPrinter { } private mutating func printAsPatternFromList( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, isTopLevel: Bool = false ) { guard let node = list.popFirst() else { return } @@ -216,7 +216,7 @@ extension PrettyPrinter { // adjacent chars/scalars into quotedLiteral nodes, no additional coalescing // is needed here — we just iterate the count children. private mutating func printConcatenationAsPatternFromList( - _ list: inout ArraySlice, + _ list: inout Deque.SubSequence, count: Int, isTopLevel: Bool ) { diff --git a/Sources/_StringProcessing/Regex/ASTConversion.swift b/Sources/_StringProcessing/Regex/ASTConversion.swift index 9b39b967..fc16715c 100644 --- a/Sources/_StringProcessing/Regex/ASTConversion.swift +++ b/Sources/_StringProcessing/Regex/ASTConversion.swift @@ -12,7 +12,7 @@ internal import _RegexParser extension AST.Node { - func convert(into list: inout [DSLTree.Node]) throws { + func convert(into list: inout Deque) throws { switch self { case .alternation(let alternation): list.append(.orderedChoice(alternation.children.count)) diff --git a/Sources/_StringProcessing/Regex/DSLList.swift b/Sources/_StringProcessing/Regex/DSLList.swift index e18e75cf..d4fa4ae0 100644 --- a/Sources/_StringProcessing/Regex/DSLList.swift +++ b/Sources/_StringProcessing/Regex/DSLList.swift @@ -12,7 +12,7 @@ internal import _RegexParser struct DSLList { - var nodes: [DSLTree.Node] + var nodes: Deque // experimental var hasCapture: Bool = false @@ -28,7 +28,7 @@ struct DSLList { self.nodes = [initial] } - init(_ nodes: [DSLTree.Node]) { + init(_ nodes: Deque) { self.nodes = nodes } @@ -52,11 +52,11 @@ extension DSLList { } mutating func prepend(_ node: DSLTree.Node) { - nodes.insert(node, at: 0) + nodes.prepend(node) } mutating func prepend(contentsOf other: some Collection) { - nodes.insert(contentsOf: other, at: 0) + nodes.prepend(contentsOf: other) } } @@ -85,7 +85,7 @@ extension DSLTree.Node { } } -extension ArraySlice { +extension Slice> { internal func skipNode(_ position: inout Int) { guard position < endIndex else { return diff --git a/Sources/_StringProcessing/Regex/DSLTree.swift b/Sources/_StringProcessing/Regex/DSLTree.swift index 62100f14..1486f8ff 100644 --- a/Sources/_StringProcessing/Regex/DSLTree.swift +++ b/Sources/_StringProcessing/Regex/DSLTree.swift @@ -546,7 +546,7 @@ struct CaptureTransform: Hashable, CustomStringConvertible { extension CaptureList.Builder { mutating func addCaptures( - in list: inout ArraySlice, optionalNesting nesting: OptionalNesting, visibleInTypedOutput: Bool + in list: inout Deque.SubSequence, optionalNesting nesting: OptionalNesting, visibleInTypedOutput: Bool ) { guard let node = list.popFirst() else { return } switch node { @@ -652,7 +652,7 @@ extension Sequence { // MARK: Required first and last atoms -private func _requiredAtomImpl(_ list: inout ArraySlice) -> DSLTree.Atom?? { +private func _requiredAtomImpl(_ list: inout Deque.SubSequence) -> DSLTree.Atom?? { guard let node = list.popFirst() else { return nil } @@ -711,7 +711,7 @@ private func _requiredAtomImpl(_ list: inout ArraySlice) -> DSLTre } } -internal func requiredFirstAtom(_ list: inout ArraySlice) -> DSLTree.Atom? { +internal func requiredFirstAtom(_ list: inout Deque.SubSequence) -> DSLTree.Atom? { _requiredAtomImpl(&list) ?? nil }