Skip to content

Commit

Permalink
Merge pull request #19 from crichez/fnv-digest-protocol
Browse files Browse the repository at this point in the history
Loosen Digest Requirements
  • Loading branch information
crichez authored Jan 5, 2022
2 parents f4331a9 + 5bc08ad commit 724812d
Show file tree
Hide file tree
Showing 17 changed files with 305 additions and 1,071 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,24 @@ jobs:

# Build for iOS
- name: Build iOS
run: xcodebuild -scheme FowlerNollVo-Package -destination "platform=iOS Simulator,name=iPhone 12,OS=15.0"
run: xcodebuild -scheme FowlerNollVo -destination "platform=iOS Simulator,name=iPhone 12,OS=15.0"

# Test on iOS
- name: Test iOS
run: xcodebuild test -scheme FowlerNollVo-Package -destination "platform=iOS Simulator,name=iPhone 12,OS=15.0"
run: xcodebuild test -scheme FowlerNollVo -destination "platform=iOS Simulator,name=iPhone 12,OS=15.0"

# Build for watchOS
- name: Build watchOS
run: xcodebuild -scheme FowlerNollVo-Package -destination "platform=watchOS Simulator,name=Apple Watch Series 6 - 44mm,OS=8.0"
run: xcodebuild -scheme FowlerNollVo -destination "platform=watchOS Simulator,name=Apple Watch Series 6 - 44mm,OS=8.0"

# Test on watchOS
- name: Test watchOS
run: xcodebuild test -scheme FowlerNollVo-Package -destination "platform=watchOS Simulator,name=Apple Watch Series 6 - 44mm,OS=8.0"
run: xcodebuild test -scheme FowlerNollVo -destination "platform=watchOS Simulator,name=Apple Watch Series 6 - 44mm,OS=8.0"

# Build for tvOS
- name: Build tvOS
run: xcodebuild -scheme FowlerNollVo-Package -destination "platform=tvOS Simulator,name=Apple TV 4K,OS=15.0"
run: xcodebuild -scheme FowlerNollVo -destination "platform=tvOS Simulator,name=Apple TV 4K,OS=15.0"

# Test on tvOS
- name: Test tvOS
run: xcodebuild test -scheme FowlerNollVo-Package -destination "platform=tvOS Simulator,name=Apple TV 4K,OS=15.0"
run: xcodebuild test -scheme FowlerNollVo -destination "platform=tvOS Simulator,name=Apple TV 4K,OS=15.0"
43 changes: 2 additions & 41 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,16 @@ import PackageDescription
let package = Package(
name: "FowlerNollVo",
products: [
// The core library
.library(
name: "FowlerNollVo",
targets: ["FowlerNollVo"]),
.library(
name: "FNV32",
targets: ["FowlerNollVo", "FNV32"]),
.library(
name: "FNV32a",
targets: ["FowlerNollVo", "FNV32a"]),
.library(
name: "FNV64",
targets: ["FowlerNollVo", "FNV64"]),
.library(
name: "FNV64a",
targets: ["FowlerNollVo", "FNV64a"]),
],
targets: [
// The target that defines the `FNVHashable` and `FNVHasher` protocols
.target(
name: "FowlerNollVo",
dependencies: []),

// 32-bit FNV implementations
.target(
name: "FNV32",
dependencies: ["FowlerNollVo"]),
.testTarget(
name: "FNV32Tests",
dependencies: ["FNV32"]),
.target(
name: "FNV32a",
dependencies: ["FowlerNollVo"]),
.testTarget(
name: "FNV32aTests",
dependencies: ["FNV32a"]),

// 64-bit FNV implementations
.target(
name: "FNV64",
dependencies: ["FowlerNollVo"]),
.testTarget(
name: "FNV64Tests",
dependencies: ["FNV64"]),
.target(
name: "FNV64a",
dependencies: ["FowlerNollVo"]),
.testTarget(
name: "FNV64aTests",
dependencies: ["FNV64a"]),
name: "FowlerNollVoTests",
dependencies: ["FowlerNollVo"])
]
)
71 changes: 44 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ This project is tested in continuous integration on the following platforms:
* tvOS 15.0
* watchOS 8.0

All pull requests to the `main` branch will automatically be tested on all supported platforms.
All tests must pass to committed to the main branch.

## Installation

`FowlerNollVo` is a [Swift Package Manager](https://github.com/apple/swift-package-manager) project.
Expand All @@ -30,7 +27,7 @@ To depend on it, include it in your `Package.swift` dependencies:
.package(
name: "FowlerNollVo",
url: "https://github.com/crichez/swift-fowler-noll-vo",
.upToNextMinor(from: "0.0.1")),
.upToNextMinor(from: "0.1.0")),
```

**All versions below 1.0.0 are considered pre-release.**
Expand All @@ -39,22 +36,9 @@ To avoid this, use the `.upToNextMinor(from:)` version method in your package ma
like in the example above. Once the project graduates to `1.0.0`,
regular semantic versioning rules will apply.

## Modules

This package exposes 5 modules:
* `FowlerNollVo`
* `FNV32`
* `FNV32a`
* `FNV64`
* `FNV64a`

### `FowlerNollVo`
## `FNVHashable`

The `FowlerNollVo` module is at the root of the dependency tree. It defines 2 protocols:
* `FNVHashable`
* `FNVHasher`

This module intends to closely mirror the Swift standard library `Hashable` implementation.
This protocol intends to closely mirror the Swift standard library `Hashable` protocol.
You can make your custom types conform to `FNVHashable` to hash them with a `FNVHasher`.

The code looks almost exactly the same:
Expand Down Expand Up @@ -91,18 +75,22 @@ extension UUID: FNVHashable {
}
```

You can learn more about how to conform to these protocols by reading inline documentation.
## Hashers

The following hasher types are built-in:
* `FNV1Hasher`
* `FNV1aHasher`

### Other Modules
The old `FNV1Hasher` is considered deprecated by the creators, but is included anyway.
These types are generic over a `FNVDigest`.

The other modules are actual implementations of FNV-1 & FNV-1a with 32 & 64 bit digests.
### Usage

```swift
import FowlerNollVo
import FNV64a

// Initialize a hasher in a single call
var hasher = FNV64a()
var hasher = FNV1aHasher<UInt64>()
// Hash any FNVHashable value
hasher.combine(12)
hasher.combine("this is a string!")
Expand All @@ -111,6 +99,35 @@ hasher.combine(MyCustomType())
let digest: UInt64 = hasher.digest
```

Although only four functions are provided, you may implement others using a different digest size.
Currently, the `FNVHasher` protocol requires the `Digest` to conform to `UnsignedInteger`.
This restriction is there for calculation simplicity, but feel free to experiment and pull request!
### `FNVDigest`

All digests must conform to the `FNVDigest` protocol. This protocol requires only the basic
values and operations necessary to work with the built-in hash functions.

The following code is the actual protocol definition, and the implementation for `UInt32`.

```swift
/// A type that can be used as a digest by the Fowler-Noll-Vo hash function.
public protocol FNVDigest {
/// The `fnv_prime` value for this digest size.
static var fnvPrime: Self { get }

/// The `offset_basis` value for this digest size.
static var fnvOffset: Self { get }

/// An operator that performs a bitwise `XOR` between this digest and an individual byte.
static func ^ (lhs: Self, rhs: UInt8) -> Self

/// An operator that performs an unchecked multiplication on two digests.
static func &* (lhs: Self, rhs: Self) -> Self
}

extension UInt32: FNVDigest {
public static var fnvPrime: UInt32 { 16777619 }
public static var fnvOffset: UInt32 { 2166136261 }

public static func ^ (lhs: UInt32, rhs: UInt8) -> UInt32 {
lhs ^ UInt32(truncatingIfNeeded: rhs)
}
}
```
28 changes: 0 additions & 28 deletions Sources/FNV32/FNV32.swift

This file was deleted.

21 changes: 0 additions & 21 deletions Sources/FNV32a/FNV32a.swift

This file was deleted.

28 changes: 0 additions & 28 deletions Sources/FNV64/FNV64.swift

This file was deleted.

21 changes: 0 additions & 21 deletions Sources/FNV64a/FNV64a.swift

This file was deleted.

27 changes: 27 additions & 0 deletions Sources/FowlerNollVo/FNV1Hasher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// FNV1Hasher.swift
//
//
// Created by Christopher Richez on 1/5/22.
//

/// A hasher that implements the Fowler-Noll-Vo-1 hash function.
public struct FNV1Hasher<Digest: FNVDigest>: FNVHasher {
/// The current digest produced by this hasher.
public var digest: Digest

/// Initializes a hasher.
public init() { self.digest = .fnvOffset }

/// Hashes the provided ``FNVHashable`` elements.
public mutating func combine<T>(_ data: T) where T : FNVHashable {
data.hash(into: &self)
}

/// Feeds the provided data to the hasher.
public mutating func combine<Data>(_ data: Data) where Data : Sequence, Data.Element == UInt8 {
for byte in data {
digest = (digest &* .fnvPrime) ^ byte
}
}
}
27 changes: 27 additions & 0 deletions Sources/FowlerNollVo/FNV1aHasher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// FNV1aHasher.swift
// FNV1aHasher
//
// Created by Christopher Richez on August 30th 2021
//

/// A hasher that implements the Fowler-Noll-Vo-1a hash function.
public struct FNV1aHasher<Digest: FNVDigest>: FNVHasher {
/// The current digest produced by this hasher.
public var digest: Digest

/// Initializes a hasher.
public init() { self.digest = .fnvOffset }

/// Hashes the provided ``FNVHashable`` elements.
public mutating func combine<T>(_ data: T) where T : FNVHashable {
data.hash(into: &self)
}

/// Feeds the provided data to the hasher.
public mutating func combine<Data>(_ data: Data) where Data : Sequence, Data.Element == UInt8 {
for byte in data {
digest = (digest ^ byte) &* .fnvPrime
}
}
}
39 changes: 39 additions & 0 deletions Sources/FowlerNollVo/FNVDigest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// FNVDigest.swift
//
//
// Created by Christopher Richez on 1/5/22.
//

/// A type that can be used as a digest by the Fowler-Noll-Vo hash function.
public protocol FNVDigest {
/// The `fnv_prime` value for this digest size.
static var fnvPrime: Self { get }

/// The `offset_basis` value for this digest size.
static var fnvOffset: Self { get }

/// An operator that performs a bitwise `XOR` between this digest and an individual byte.
static func ^ (lhs: Self, rhs: UInt8) -> Self

/// An operator that performs an unchecked multiplication on two digests.
static func &* (lhs: Self, rhs: Self) -> Self
}

extension UInt32: FNVDigest {
public static var fnvPrime: UInt32 { 16777619 }
public static var fnvOffset: UInt32 { 2166136261 }

public static func ^ (lhs: UInt32, rhs: UInt8) -> UInt32 {
lhs ^ UInt32(truncatingIfNeeded: rhs)
}
}

extension UInt64: FNVDigest {
public static var fnvPrime: UInt64 { 1099511628211 }
public static var fnvOffset: UInt64 { 14695981039346656037 }

public static func ^ (lhs: UInt64, rhs: UInt8) -> UInt64 {
lhs ^ UInt64(truncatingIfNeeded: rhs)
}
}
Loading

0 comments on commit 724812d

Please sign in to comment.