221 lines
6.4 KiB
Swift
221 lines
6.4 KiB
Swift
//
|
|
// InlineArray3.swift
|
|
// HTMLStreamer
|
|
//
|
|
// Created by Shadowfacts on 11/19/23.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// An array with inline space for up to 3 elements.
|
|
///
|
|
/// If the array grows beyond 3 elements, it will be stored out-of-line.
|
|
/// Once that happens, the array will never return to being stored inline,
|
|
/// since the allocation cost has already been paid.
|
|
struct InlineArray3<E> {
|
|
private var storage: Storage
|
|
|
|
init() {
|
|
self.storage = .inline(nil, nil, nil)
|
|
}
|
|
}
|
|
|
|
extension InlineArray3 {
|
|
fileprivate enum Storage {
|
|
case inline(Element?, Element?, Element?)
|
|
case array(ContiguousArray<Element>)
|
|
}
|
|
}
|
|
|
|
extension InlineArray3: ExpressibleByArrayLiteral {
|
|
init(arrayLiteral elements: Element...) {
|
|
switch elements.count {
|
|
case 0:
|
|
self.storage = .inline(nil, nil, nil)
|
|
case 1:
|
|
self.storage = .inline(elements[0], nil, nil)
|
|
case 2:
|
|
self.storage = .inline(elements[0], elements[1], nil)
|
|
case 3:
|
|
self.storage = .inline(elements[0], elements[1], elements[2])
|
|
default:
|
|
self.storage = .array(.init(elements))
|
|
}
|
|
}
|
|
}
|
|
|
|
extension InlineArray3: MutableCollection {
|
|
typealias Element = E
|
|
typealias Index = Int
|
|
typealias Indices = Range<Int>
|
|
|
|
subscript(position: Int) -> Element {
|
|
_read {
|
|
precondition(position < endIndex)
|
|
switch storage {
|
|
case .inline(let a, let b, let c):
|
|
switch position {
|
|
case 0:
|
|
yield a.unsafelyUnwrapped
|
|
case 1:
|
|
yield b.unsafelyUnwrapped
|
|
case 2:
|
|
yield c.unsafelyUnwrapped
|
|
default:
|
|
fatalError("unreachable")
|
|
}
|
|
case .array(let arr):
|
|
yield arr[position]
|
|
}
|
|
}
|
|
_modify {
|
|
precondition(position < endIndex)
|
|
switch storage {
|
|
case .inline(let a, let b, let c):
|
|
switch position {
|
|
case 0:
|
|
var newValue = a.unsafelyUnwrapped
|
|
yield &newValue
|
|
storage = .inline(newValue, b, c)
|
|
case 1:
|
|
var newValue = b.unsafelyUnwrapped
|
|
yield &newValue
|
|
storage = .inline(a, newValue, c)
|
|
case 2:
|
|
var newValue = c.unsafelyUnwrapped
|
|
yield &newValue
|
|
storage = .inline(a, b, newValue)
|
|
default:
|
|
fatalError("unreachable")
|
|
}
|
|
case .array(var arr):
|
|
yield &arr[position]
|
|
}
|
|
}
|
|
}
|
|
|
|
var startIndex: Int {
|
|
0
|
|
}
|
|
|
|
var endIndex: Int {
|
|
switch storage {
|
|
case .inline(let a, let b, let c):
|
|
a == nil ? 0 : b == nil ? 1 : c == nil ? 2 : 3
|
|
case .array(let arr):
|
|
arr.endIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
extension InlineArray3: BidirectionalCollection {
|
|
}
|
|
|
|
extension InlineArray3: RandomAccessCollection {
|
|
}
|
|
|
|
extension InlineArray3: RangeReplaceableCollection {
|
|
mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C: Collection, Element == C.Element {
|
|
switch storage {
|
|
case .array(var arr):
|
|
arr.replaceSubrange(subrange, with: newElements)
|
|
storage = .array(arr)
|
|
case .inline(var a, var b, var c):
|
|
if count - subrange.count + newElements.count <= 3 {
|
|
// remove elements at subrange indices
|
|
if subrange.contains(2) {
|
|
c = nil
|
|
}
|
|
if subrange.contains(1) {
|
|
b = c
|
|
c = nil
|
|
}
|
|
if subrange.contains(0) {
|
|
a = b
|
|
b = c
|
|
c = nil
|
|
}
|
|
|
|
// insert newElements starting at subrange.lowerBound
|
|
for (offset, el) in newElements.enumerated() {
|
|
// assert that we have space to insert
|
|
assert(c == nil)
|
|
let newIndex = subrange.lowerBound + offset
|
|
switch newIndex {
|
|
case 2:
|
|
c = el
|
|
case 1:
|
|
c = b
|
|
b = el
|
|
case 0:
|
|
c = b
|
|
b = a
|
|
a = el
|
|
default:
|
|
fatalError("unreachable")
|
|
}
|
|
}
|
|
|
|
storage = .inline(a, b, c)
|
|
} else {
|
|
var arr: ContiguousArray = if let a {
|
|
if let b {
|
|
if let c {
|
|
[a, b, c]
|
|
} else {
|
|
[a, b]
|
|
}
|
|
} else {
|
|
[a]
|
|
}
|
|
} else {
|
|
[]
|
|
}
|
|
arr.replaceSubrange(subrange, with: newElements)
|
|
storage = .array(arr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension Collection {
|
|
func safeIndex(_ index: Index, offsetBy: Int) -> Index? {
|
|
var index = index
|
|
var offsetBy = offsetBy
|
|
while offsetBy > 0 {
|
|
if index < endIndex {
|
|
formIndex(after: &index)
|
|
offsetBy -= 1
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
return index >= endIndex ? nil : index
|
|
}
|
|
}
|
|
|
|
extension InlineArray3.Storage: Equatable where E: Equatable {
|
|
}
|
|
|
|
extension InlineArray3: Equatable where E: Equatable {
|
|
}
|
|
|
|
extension InlineArray3: CustomStringConvertible {
|
|
var description: String {
|
|
switch storage {
|
|
case .inline(nil, nil, nil):
|
|
return "[]"
|
|
case .inline(.some(let a), nil, nil):
|
|
return "[\(a)]"
|
|
case .inline(.some(let a), .some(let b), nil):
|
|
return "[\(a), \(b)]"
|
|
case .inline(.some(let a), .some(let b), .some(let c)):
|
|
return "[\(a), \(b), \(c)]"
|
|
case .inline(_, _, _):
|
|
fatalError("InlineArray3 invariant violated")
|
|
case .array(let arr):
|
|
return arr.description
|
|
}
|
|
}
|
|
}
|