package vals import ( "math" "math/big" "src.elv.sh/pkg/persistent/hash" "src.elv.sh/pkg/persistent/hashmap" ) // Hasher wraps the Hash method. type Hasher interface { // Hash computes the hash code of the receiver. Hash() uint32 } // Hash returns the 32-bit hash of a value. It is implemented for the builtin // types bool and string, the File, List, Map types, StructMap types, and types // satisfying the Hasher interface. For other values, it returns 0 (which is OK // in terms of correctness). func Hash(v any) uint32 { switch v := v.(type) { case bool: if v { return 1 } return 0 case int: return hash.UIntPtr(uintptr(v)) case *big.Int: h := hash.DJBCombine(hash.DJBInit, uint32(v.Sign())) for _, word := range v.Bits() { h = hash.DJBCombine(h, hash.UIntPtr(uintptr(word))) } return h case *big.Rat: return hash.DJB(Hash(v.Num()), Hash(v.Denom())) case float64: return hash.UInt64(math.Float64bits(v)) case string: return hash.String(v) case Hasher: return v.Hash() case File: return hash.UIntPtr(v.Fd()) case List: h := hash.DJBInit for it := v.Iterator(); it.HasElem(); it.Next() { h = hash.DJBCombine(h, Hash(it.Elem())) } return h case Map: return hashMap(v.Iterator()) case StructMap: return hashMap(iterateStructMap(v)) } return 0 } func hashMap(it hashmap.Iterator) uint32 { // The iteration order of maps only depends on the hash of the keys. It is // almost deterministic, with only one exception: when two keys have the // same hash, they get produced in insertion order. As a result, it is // possible for two maps that should be considered equal to produce entries // in different orders. // // So instead of using hash.DJBCombine, combine the hash result from each // key-value pair by summing, so that the order doesn't matter. // // TODO: This may not have very good hashing properties. var h uint32 for ; it.HasElem(); it.Next() { k, v := it.Elem() h += hash.DJB(Hash(k), Hash(v)) } return h }