parent
1c461041c1
commit
29a065049e
|
@ -24,8 +24,8 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
private var tokenizer: Tokenizer<String.Iterator>!
|
||||
private var str: NSMutableAttributedString!
|
||||
|
||||
private var actionStack: InlineArray3<ElementAction> = []
|
||||
private var styleStack: InlineArray3<Style> = []
|
||||
private var actionStack: [ElementAction] = []
|
||||
private var styleStack: [Style] = []
|
||||
// The current run of text w/o styles changing
|
||||
private var currentRun: String = ""
|
||||
|
||||
|
@ -77,7 +77,7 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
return str
|
||||
}
|
||||
|
||||
private mutating func handleStartTag(_ name: String, attributes: InlineArray3<HTMLStreamer.Attribute>) {
|
||||
private mutating func handleStartTag(_ name: String, attributes: [HTMLStreamer.Attribute]) {
|
||||
switch name {
|
||||
case "br":
|
||||
currentRun.append("\n")
|
||||
|
@ -287,7 +287,7 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
|
||||
public protocol AttributedStringCallbacks {
|
||||
static func makeURL(string: String) -> URL?
|
||||
static func elementAction(name: String, attributes: InlineArray3<Attribute>) -> ElementAction
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction
|
||||
}
|
||||
|
||||
public enum ElementAction: Equatable {
|
||||
|
@ -308,7 +308,7 @@ public extension AttributedStringCallbacks {
|
|||
static func makeURL(string: String) -> URL? {
|
||||
URL(string: string)
|
||||
}
|
||||
static func elementAction(name: String, attributes: InlineArray3<Attribute>) -> ElementAction {
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
||||
.default
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
//
|
||||
// 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.
|
||||
public struct InlineArray3<E> {
|
||||
private var storage: Storage
|
||||
|
||||
public init() {
|
||||
self.storage = .inline(nil, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension InlineArray3 {
|
||||
fileprivate enum Storage {
|
||||
case inline(Element?, Element?, Element?)
|
||||
case array(ContiguousArray<Element>)
|
||||
}
|
||||
}
|
||||
|
||||
extension InlineArray3: ExpressibleByArrayLiteral {
|
||||
public 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 {
|
||||
public typealias Element = E
|
||||
public typealias Index = Int
|
||||
public typealias Indices = Range<Int>
|
||||
|
||||
public 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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var startIndex: Int {
|
||||
0
|
||||
}
|
||||
|
||||
public 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 {
|
||||
public 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 {
|
||||
public 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ struct Tokenizer<Chars: IteratorProtocol<Character>>: IteratorProtocol {
|
|||
typealias Element = Token
|
||||
|
||||
private var chars: Chars
|
||||
private var reconsumeStack: InlineArray3<Character> = []
|
||||
private var reconsumeStack: [Character] = []
|
||||
private var state = State.data
|
||||
private var returnState: State?
|
||||
private var temporaryBuffer: String?
|
||||
|
@ -195,7 +195,7 @@ struct Tokenizer<Chars: IteratorProtocol<Character>>: IteratorProtocol {
|
|||
enum Token: Equatable {
|
||||
case character(Character)
|
||||
case comment(String)
|
||||
case startTag(String, selfClosing: Bool, attributes: InlineArray3<Attribute>)
|
||||
case startTag(String, selfClosing: Bool, attributes: [Attribute])
|
||||
case endTag(String)
|
||||
case doctype(String, forceQuirks: Bool, publicIdentifier: String?, systemIdentifier: String?)
|
||||
}
|
||||
|
@ -561,7 +561,6 @@ private extension Tokenizer {
|
|||
switch c {
|
||||
case .some("0"..."9"), .some("a"..."z"), .some("A"..."Z"):
|
||||
if case .attributeValue(_) = returnState {
|
||||
// TODO: append the current input character to the current attribute's value
|
||||
if case .startTag(let s, let selfClosing, var attributes) = currentToken {
|
||||
attributes[attributes.count - 1].value.append(c!)
|
||||
currentToken = .startTag(s, selfClosing: selfClosing, attributes: attributes)
|
||||
|
|
|
@ -175,7 +175,7 @@ final class AttributedStringConverterTests: XCTestCase {
|
|||
|
||||
func testElementActionCallback() {
|
||||
struct Callbacks: AttributedStringCallbacks {
|
||||
static func elementAction(name: String, attributes: InlineArray3<Attribute>) -> ElementAction {
|
||||
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
||||
let clazz = attributes.attributeValue(for: "class")
|
||||
if clazz == "invisible" {
|
||||
return .skip
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// InlineArray3Tests.swift
|
||||
//
|
||||
//
|
||||
// Created by Shadowfacts on 11/19/23.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import HTMLStreamer
|
||||
|
||||
final class InlineArray3Tests: XCTestCase {
|
||||
|
||||
func testReplaceSubrange() {
|
||||
// same size
|
||||
var a: InlineArray3 = [0, 1, 2]
|
||||
a.replaceSubrange(0..<2, with: [3, 4])
|
||||
XCTAssertEqual(a, [3, 4, 2])
|
||||
|
||||
// grow
|
||||
a = [0, 1]
|
||||
a.replaceSubrange(1..<2, with: [2, 3])
|
||||
XCTAssertEqual(a, [0, 2, 3])
|
||||
|
||||
// shrink
|
||||
a = [0, 1, 2]
|
||||
a.replaceSubrange(0..<2, with: [])
|
||||
XCTAssertEqual(a, [2])
|
||||
a.removeFirst()
|
||||
XCTAssertEqual(a, [])
|
||||
}
|
||||
|
||||
func testRemoveLast() {
|
||||
var a: InlineArray3 = [0, 1, 2]
|
||||
a.removeLast(2)
|
||||
XCTAssertEqual(a, [0])
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue