I have implemented a piece table in Swift, based on a Javascript implementation. I don't have much experience with Swift yet, so I'm especially interested if I'm doing things "the Swift way". Any feedback is highly appreciated.
import Foundation
public class PieceTable {
private let original: String
private var buffer: String
private var bufferLength: Int
private var pieces: [Piece]
public convenience init() {
self.init(from: "")
}
public init(from original: String) {
self.original = original
self.buffer = ""
self.bufferLength = 0
self.pieces = [Piece(buffer: false, offset: 0, length: original.characters.count)]
}
public func insert(string: String, offset: Int) {
if string.isEmpty {
return
}
let addBufferOffset = bufferLength
self.buffer += string
self.bufferLength += string.characters.count
let (piece, index, offsetInPiece) = self.pieceIndexAndOffset(fromSequenceOffset: offset)
// If we are inserting at the end of the piece and at the end of the buffer, we can just increase its length
if piece.buffer && offsetInPiece == piece.length && (piece.offset + piece.length) == addBufferOffset {
self.pieces[index].length += string.characters.count
return
}
self.pieces.replaceSubrange(index...index, with: [
Piece(buffer: piece.buffer, offset: piece.offset, length: offsetInPiece),
Piece(buffer: true, offset: addBufferOffset, length: string.characters.count),
Piece(buffer: piece.buffer, offset: piece.offset + offsetInPiece, length: piece.length - offsetInPiece)
].filter({$0.length > 0}))
}
public func delete(offset: Int, length: Int) {
if length == 0 {
return
}
if length < 0 {
self.delete(offset: offset + length, length: -length)
return
}
let (firstPiece, firstPieceIndex, offsetInFirstPiece) = self.pieceIndexAndOffset(fromSequenceOffset: offset)
let (lastPiece, lastPieceIndex, offsetInLastPiece) = self.pieceIndexAndOffset(fromSequenceOffset: offset + length)
// If the delete spans only one piece and is at the very start or end of the piece, we can just modify it
if firstPieceIndex == lastPieceIndex {
if offsetInFirstPiece == 0 {
self.pieces[firstPieceIndex].offset += length
self.pieces[firstPieceIndex].length -= length
return
} else if offsetInLastPiece == lastPiece.length {
self.pieces[firstPieceIndex].length -= length
return
}
}
self.pieces.replaceSubrange(firstPieceIndex...lastPieceIndex, with: [
Piece(buffer: firstPiece.buffer, offset: firstPiece.offset, length: offsetInFirstPiece),
Piece(buffer: lastPiece.buffer, offset: lastPiece.offset + offsetInLastPiece, length: lastPiece.length - offsetInLastPiece)
].filter({$0.length > 0}))
}
public func get() -> String {
var string = ""
for piece in self.pieces {
string += self.substring(for: piece)
}
return string
}
public func get(offset: Int, length: Int) -> String {
if length < 0 {
return self.get(offset: offset + length, length: -length)
}
let (firstPiece, firstPieceIndex, offsetInFirstPiece) = self.pieceIndexAndOffset(fromSequenceOffset: offset)
let (lastPiece, lastPieceIndex, offsetInLastPiece) = self.pieceIndexAndOffset(fromSequenceOffset: offset + length)
if offsetInFirstPiece + length < firstPiece.length {
return self.substring(for: firstPiece, offset: offsetInFirstPiece, length: length)
}
var string = self.substring(for: firstPiece, offset: offsetInFirstPiece, length: firstPiece.length - offsetInFirstPiece)
for index in (firstPieceIndex + 1)..<lastPieceIndex {
string += self.substring(for: self.pieces[index])
}
string += self.substring(for: lastPiece, offset: 0, length: offsetInLastPiece)
return string
}
private func pieceIndexAndOffset(fromSequenceOffset offset: Int) -> (piece: Piece, index: Int, offset: Int) {
precondition(offset >= 0, "Offset out of bounds")
var remainingOffset = offset
for (index, piece) in self.pieces.enumerated() {
if remainingOffset <= piece.length {
return (piece, index, remainingOffset)
}
remainingOffset -= piece.length
}
precondition(false, "Offset out of bounds")
}
private func substring(for piece: Piece) -> String {
let buffer = piece.buffer ? self.buffer : self.original
let startIndex = buffer.index(buffer.startIndex, offsetBy: piece.offset)
let endIndex = buffer.index(startIndex, offsetBy: piece.length)
return buffer[startIndex..<endIndex]
}
private func substring(for piece: Piece, offset: Int, length: Int) -> String {
let buffer = piece.buffer ? self.buffer : self.original
let startIndex = buffer.index(buffer.startIndex, offsetBy: piece.offset + offset)
let endIndex = buffer.index(startIndex, offsetBy: length)
return buffer[startIndex..<endIndex]
}
}
private struct Piece {
public var buffer: Bool
public var offset: Int
public var length: Int
}
Usage example:
var buffer = PieceTable(from: "Hello, world!")
buffer.insert(string: "crazy ", offset: 7)
buffer.get() // "Hello, crazy world!"