Skip to content

Commit

Permalink
Kill your darlings, BigInt pod is so good we only describe it using a…
Browse files Browse the repository at this point in the history
… protocol adding some extra functionality
  • Loading branch information
Sajjon committed Jun 12, 2018
1 parent db901f2 commit b447188
Show file tree
Hide file tree
Showing 22 changed files with 724 additions and 951 deletions.
22 changes: 11 additions & 11 deletions Source/Array_Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import Foundation

extension Array where Element: BinaryInteger {

func droppingTrailingZeros() -> [Element] {
var elements = self
elements.dropTrailingZeros()
return elements
}

mutating func dropTrailingZeros() {
while last == 0 {
removeLast()
}
}
// func droppingTrailingZeros() -> [Element] {
// var elements = self
// elements.dropTrailingZeros()
// return elements
// }
//
// mutating func dropTrailingZeros() {
// while last == 0 {
// removeLast()
// }
// }

func droppingLeadingZeros() -> [Element] {
var elements = self
Expand Down
23 changes: 9 additions & 14 deletions Source/KeyData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,19 @@
//

import Foundation
import BigInt

public typealias KeyData = KeyDataStruct
public struct KeyDataStruct: KeyDataConvertible {
public typealias Element = UInt8
public typealias KeyData = BigUInt

// Least significant element at index 0
public private(set) var elements: [Element]
extension BigUInt: KeyDataConvertible {}
public extension BigUInt {

public init(lsbZeroIndexed: [Element]) {
self.elements = lsbZeroIndexed.droppingTrailingZeros()
init(lsbZeroIndexed words: [Word]) {
self.init(words: words)
}
}

import BigInt
extension BigUInt: KeyDataConvertible {
public typealias Element = Word
public var elements: [Element] { return storage }
public init(lsbZeroIndexed elements: [Element]) {
self.init(words: elements)
init?(msbZeroIndexed string: String, radix: Int) {
let string = radix == 16 ? string.droppingTwoLeadinHexCharsIfNeeded() : string
self.init(string, radix: radix)
}
}
246 changes: 33 additions & 213 deletions Source/KeyDataConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,245 +8,65 @@

import Foundation

public protocol KeyDataConvertible: Comparable, Collection, ExpressibleByArrayLiteral, CustomStringConvertible, CustomDebugStringConvertible where Element: FixedWidthInteger & UnsignedInteger & CVarArg, Index == Int {

var elements: [Element] { get }
init(lsbZeroIndexed: [Element])
init(msbZeroIndexed: [Element])
init(msbZeroIndexed: String) throws
init(msbZeroIndexed hexString: String, separatedBy separator: Separator?) throws
}

public extension KeyDataConvertible {
public init(msbZeroIndexed: [Element]) {
self.init(lsbZeroIndexed: Array(msbZeroIndexed.droppingLeadingZeros().reversed()))
}
public protocol KeyDataConvertible:
UnsignedInteger,
ExpressibleByArrayLiteral,
ExpressibleByStringLiteral, // On Decimal format
CustomDebugStringConvertible
where
Words: RandomAccessCollection,
Words.Index == Int,
Word == UInt
{
associatedtype Word

init(lsbZeroIndexed: [Word])
init(msbZeroIndexed: [Word])
init?(msbZeroIndexed: String, radix: Int)
}

// MARK: - ExpressibleByArrayLiteral
public extension KeyDataConvertible {
public init(arrayLiteral elements: Element...) {
self.init(msbZeroIndexed: elements)
}
}

// MARK: - Collection
public extension KeyDataConvertible {

var startIndex: Int {
return elements.startIndex
}

var endIndex: Int {
return elements.endIndex
}

subscript(position: Int) -> Element {
return elements[position]
}

func index(after i: Int) -> Int {
return elements.index(after: i)
}
}

public enum Separator: Character {
case comma = ","
case space = " "

}

// MARK: - Convenience Init
public extension KeyDataConvertible {

init(msbZeroIndexed hexStringArray: [String]) throws {
let msbZeroIndexed = hexStringArray.compactMap { Element($0, radix: 16) }
guard hexStringArray.count == msbZeroIndexed.count else { throw KeyDataConvertibleError.stringInvalidChars }
self.init(msbZeroIndexed: msbZeroIndexed)
}

init(msbZeroIndexed hexString: String, separatedBy separator: Separator?) throws {
var hexString = hexString.droppingTwoLeadinHexCharsIfNeeded()

func splitting(_ string: String) throws -> [String] {

if let separator = separator {
guard string.containsOnlyHexChars(or: separator.rawValue) else { throw KeyDataConvertibleError.separatorMismatch }
return string.split(separator: separator.rawValue).map { String($0) }
} else {
var string = string
while string.count % Self.bitsPerHex != 0 {
string = "0" + string
}
// Element.bitWidth: 8
// 'A4B1CDE5' -> ['A4', 'B1', 'CD', 'E5']
// Element.bitWidth: 64
// 'A4B1' -> ['A4B1', 'CDE5']
return try string.splittingIntoSubStringsOfLength(Self.bitsPerHex)
}
}

let hexStringArray = try splitting(hexString)

for part in hexStringArray {
guard part.containsOnlyHexChars() else { throw KeyDataConvertibleError.stringInvalidChars }
}

try self.init(msbZeroIndexed: hexStringArray)
}

init(msbZeroIndexed hexString: String) throws {
try self.init(msbZeroIndexed: hexString, separatedBy: nil)
}
}

public enum HexFormatting {
case noTrimming(separator: String)
case trim(nonEmptySeparator: String)

var separator: String {
switch self {
case .noTrimming(let separator): return separator
case .trim(let separator): return separator
}
}

var shouldTrim: Bool {
switch self {
case .noTrimming: return false
case .trim: return true
}
}

var isValid: Bool {
switch self {
case .trim(let separator): return !separator.isEmpty
case .noTrimming: return true
}
}

var guaranteedValid: HexFormatting {
guard isValid else { return .default }
return self
}

}

public extension HexFormatting {
static var `default`: HexFormatting {
let value = HexFormatting.noTrimmingEmptySeparator
guard value.isValid else { fatalError("default formatting should be valid") }
return value
}


static var noTrimmingEmptySeparator: HexFormatting {
return .noTrimming(separator: "")
init(msbZeroIndexed: [Word]) {
self.init(lsbZeroIndexed: Array(msbZeroIndexed.droppingLeadingZeros().reversed()))
}
}

// MARK: - Public Extension
// MARK: - Convenience
public extension KeyDataConvertible {

var length: Int { return elements.count }
var isEmpty: Bool { return length == 0 }

func asHexString(uppercased: Bool = true, formatting: HexFormatting = .default) -> String {
guard !isEmpty else { return "" }
let formatting = formatting.guaranteedValid

func trimmingIfNeeded(_ string: String) -> String {
guard formatting.shouldTrim else { return string }
return string.droppingLeadingZerosIfNeeded()
}

let bits = "\(elementHexCharacterCount)"
let hex = uppercased ? "X" : "x"
let format = "%0" + bits + hex // e.g. "%016X"

return elements.reversed()
.map { trimmingIfNeeded(String(format: format, $0)) }
.joined(separator: formatting.separator)
}

var arrayOfHexAsString: String {
let csv = asHexString(formatting: .trim(nonEmptySeparator: ", "))
return "[\(csv)]"
init?(msbZeroIndexed string: String) {
self.init(msbZeroIndexed: string, radix: 16)
}
}



// MARK: - CustomStringConvertible
// MARK: - ExpressibleByArrayLiteral
public extension KeyDataConvertible {
public var description: String {
let csv = elements.map { "\($0)" }.joined(separator: ", ")
return "[\(csv)]"
init(arrayLiteral elements: Word...) {
self.init(msbZeroIndexed: elements)
}
}

// MARK: - CustomDebugStringConvertible
public extension KeyDataConvertible {
public var debugDescription: String {
return "Decimal: `\(description)`\nHex: `\(arrayOfHexAsString)`"
}
}

// MARK: - Comparable
public extension KeyDataConvertible {

/// Compare `a` to `b` and return an `NSComparisonResult` indicating their order.
///
/// - Complexity: O(count)
static func compare(_ a: Self, _ b: Self) -> ComparisonResult {
if a.count != b.count { return a.count > b.count ? .orderedDescending : .orderedAscending }
for i in (0 ..< a.count) {
let ad = a[i]
let bd = b[i]
if ad != bd { return ad > bd ? .orderedDescending : .orderedAscending }
}
return .orderedSame
}

/// Return true iff `a` is equal to `b`.
///
/// - Complexity: O(count)
static func ==(a: Self, b: Self) -> Bool {
return Self.compare(a, b) == .orderedSame
}

/// Return true iff `a` is less than `b`.
///
/// - Complexity: O(count)
static func <(a: Self, b: Self) -> Bool {
return Self.compare(a, b) == .orderedAscending
return "Decimal: `\(description)`\nHex: `\(asHexString())`"
}
}

// MARK: - Public Extension
public extension KeyDataConvertible {
static var elementBitWidth: Int {
return Element.bitWidth
}

static var bitsPerHex: Int {
return 4 // 16 = 2^4
}

static var elementHexCharacterCount: Int {
return elementBitWidth / bitsPerHex
}
}

public extension KeyDataConvertible {
var elementBitWidth: Int {
return Self.elementBitWidth
func toString(uppercased: Bool = true, radix: Int) -> String {
let stringRepresentation = String(self, radix: radix)
guard uppercased else { return stringRepresentation }
return stringRepresentation.uppercased()
}

var bitsPerHex: Int {
return Self.bitsPerHex
func asHexString(uppercased: Bool = true) -> String {
return toString(uppercased: uppercased, radix: 16)
}

var elementHexCharacterCount: Int {
return Self.elementHexCharacterCount
func asDecimalString(uppercased: Bool = true) -> String {
return toString(uppercased: uppercased, radix: 10)
}
}
6 changes: 3 additions & 3 deletions Source/KeyDataError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@

import Foundation

public enum KeyDataConvertibleError: Int, Swift.Error {
case stringInvalidChars, invalidLength, separatorMismatch
}
//public enum KeyDataConvertibleError: Int, Swift.Error {
// case stringInvalidChars, invalidLength, separatorMismatch
//}
Loading

0 comments on commit b447188

Please sign in to comment.