Current section
Files
Jump to
Current section
Files
src/murmur3a.gleam
// SPDX-FileCopyrightText: 2025 eaon <eaon@posteo.net>
// SPDX-License-Identifier: MIT
import gleam/bit_array
import gleam/int.{
bitwise_and as and, bitwise_exclusive_or as xor, bitwise_or as or,
bitwise_shift_left as shift_left, bitwise_shift_right as shift_right,
}
import gleam/list
import gleam/string
const c1: Int = 0xcc9e2d51
const c2: Int = 0x1b873593
const m1: Int = 0xffffffff
pub opaque type Hasher {
Hasher(seed: Int, shift: Int, state: Int)
}
pub fn hash_string(key: String, seed: Int) -> Hasher {
key
|> string.to_utf_codepoints
|> list.map(string.utf_codepoint_to_int)
|> hash_ints(seed)
}
pub fn hash_ints(key: List(Int), seed: Int) -> Hasher {
key
|> list.fold(Hasher(seed:, shift: 0, state: 0), hash_chunk)
|> finalize(list.length(key))
}
fn hash_chunk(hasher: Hasher, chunk: Int) -> Hasher {
let state =
chunk
|> and(0xff)
|> shift_left(hasher.shift)
|> or(hasher.state)
case hasher.shift {
24 -> Hasher(seed: mix(hasher.seed, state), shift: 0, state: 0)
_ -> Hasher(..hasher, shift: hasher.shift + 8, state:)
}
}
fn mix(seed: Int, state: Int) -> Int {
state
|> signed_multiply(c1)
|> rotate_left_32(15)
|> signed_multiply(c2)
|> xor(seed)
|> rotate_left_32(13)
|> signed_multiply(5)
|> int.add(0xe6546b64)
}
fn finalize(hasher: Hasher, length: Int) -> Hasher {
let state =
case hasher.state {
0 -> hasher.seed
_ ->
hasher.state
|> signed_multiply(c1)
|> rotate_left_32(15)
|> signed_multiply(c2)
|> xor(hasher.seed)
|> and(m1)
}
|> xor(length)
let state =
shift_right(state, 16)
|> xor(state)
|> signed_multiply(0x85ebca6b)
|> and(m1)
let state =
shift_right(state, 13)
|> xor(state)
|> signed_multiply(0xc2b2ae35)
Hasher(
..hasher,
state: shift_right(state, 16)
|> and(0xffff)
|> xor(state),
)
}
pub fn int_digest(hasher: Hasher) -> Int {
hasher.state
}
pub fn bit_array_digest(hasher: Hasher) -> BitArray {
<<int_digest(hasher):32>>
}
pub fn hex_digest(hasher: Hasher) -> String {
bit_array.base16_encode(bit_array_digest(hasher))
}
@external(javascript, "./murmur3a_ffi.mjs", "signed_multiply")
fn signed_multiply(m: Int, n: Int) -> Int
@external(javascript, "./murmur3a_ffi.mjs", "rotate_left_32")
fn rotate_left_32(value: Int, shift: Int) -> Int