// SPDX-FileCopyrightText: 2025 eaon // 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 { <> } 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