1pub mod murmur3;
56pub mod rapid;
57pub mod siphash;
58
59use crate::lang::protocol::HashType;
60
61pub const DEFAULT_HASH: HashType = HashType::Rapid;
63
64pub trait JavaHash {
70 fn java_hash(&self, hash_type: HashType) -> i64;
71}
72
73impl JavaHash for bool {
74 fn java_hash(&self, _: HashType) -> i64 {
75 hash_bool(*self) as i64
76 }
77}
78
79impl JavaHash for char {
80 fn java_hash(&self, _: HashType) -> i64 {
81 hash_char(*self) as i64
82 }
83}
84
85impl JavaHash for i64 {
86 fn java_hash(&self, _: HashType) -> i64 {
87 hash_long(*self) as i64
88 }
89}
90
91impl JavaHash for i32 {
92 fn java_hash(&self, _: HashType) -> i64 {
93 hash_long(*self as i64) as i64
94 }
95}
96
97impl JavaHash for usize {
98 fn java_hash(&self, _: HashType) -> i64 {
99 hash_long(*self as i64) as i64
100 }
101}
102
103impl JavaHash for u64 {
104 fn java_hash(&self, _: HashType) -> i64 {
105 hash_long(*self as i64) as i64
106 }
107}
108
109impl JavaHash for f64 {
110 fn java_hash(&self, _: HashType) -> i64 {
111 hash_double(*self) as i64
112 }
113}
114
115impl JavaHash for String {
119 fn java_hash(&self, _: HashType) -> i64 {
120 java_string_hash(self) as i64
121 }
122}
123
124impl JavaHash for &str {
125 fn java_hash(&self, _: HashType) -> i64 {
126 java_string_hash(self) as i64
127 }
128}
129
130pub fn java_string_hash(s: &str) -> i32 {
136 let mut h = 0i32;
137 for unit in s.encode_utf16() {
138 h = h.wrapping_mul(31).wrapping_add(unit as i32);
139 }
140 h
141}
142
143pub fn hash_seed(obj_name: &str) -> i32 {
145 java_string_hash(&format!("::{obj_name}"))
146}
147
148pub fn hash_bool(b: bool) -> i32 {
150 if b {
151 1231
152 } else {
153 1237
154 }
155}
156
157pub fn hash_char(c: char) -> i32 {
161 c as i32
162}
163
164pub fn hash_bytes(bytes: &[u8]) -> i32 {
166 let mut h = 1i32;
167 for b in bytes {
168 h = h.wrapping_mul(31).wrapping_add(*b as i8 as i32);
169 }
170 h
171}
172
173pub fn hash_long(n: i64) -> i32 {
177 canonical_decimal_str_hash(&n.to_string())
178}
179
180pub fn hash_long_placement(n: i64) -> i32 {
184 let text = n.to_string();
185 let Some((signum, digits, scale)) = parse_decimal(&text) else {
186 return java_string_hash(&text);
187 };
188 bigdecimal_hash(&digits_to_words_be(&digits), signum, scale as i32)
189}
190
191pub fn hash_double(d: f64) -> i32 {
195 assert!(d.is_finite(), "non-finite number");
196 if d == 0.0 {
197 return 0;
198 }
199 canonical_decimal_str_hash(&format!("{d}"))
202}
203
204pub fn canonical_decimal_str_hash(s: &str) -> i32 {
209 match parse_decimal(s) {
210 Some((signum, digits, scale)) => canonical_decimal_hash(signum, digits, scale),
211 None => java_string_hash(s),
212 }
213}
214
215pub fn hash_string_type(hash_type: HashType, hashed: &str) -> i64 {
223 match hash_type {
224 HashType::System => java_string_hash(hashed) as i64,
225 HashType::Rapid => rapid::hash(hashed.as_bytes()) as i64,
226 HashType::Murmur3 => murmur3::hash_chars(hashed) as i64,
227 HashType::Sip => -1,
228 }
229}
230
231pub fn compose_ordered(obj_name: &str, items: impl IntoIterator<Item = i64>) -> i64 {
238 let mut acc = hash_seed(obj_name) as i64;
239 for h in items {
240 acc = acc.wrapping_mul(31).wrapping_add(h);
241 }
242 acc
243}
244
245pub fn compose_unordered(obj_name: &str, items: impl IntoIterator<Item = i64>) -> i64 {
248 let mut acc = hash_seed(obj_name) as i64;
249 for h in items {
250 acc = acc.wrapping_add(h);
251 }
252 acc
253}
254
255pub fn compose_entry(key_hash: i64, value_hash: i64) -> i64 {
259 compose_ordered("SEQUENTIAL", [key_hash, value_hash])
260}
261
262fn biginteger_hash(words_be: &[u32], signum: i32) -> i32 {
269 let mut h = 0i32;
270 for w in words_be {
271 h = h.wrapping_mul(31).wrapping_add(*w as i32);
272 }
273 h.wrapping_mul(signum)
274}
275
276fn bigdecimal_hash(words_be: &[u32], signum: i32, scale: i32) -> i32 {
289 if signum == 0 {
290 return scale;
292 }
293 let mag: u128 = words_be
294 .iter()
295 .fold(0u128, |acc, w| (acc << 32) | (*w as u128));
296 if mag < (1u128 << 63) {
299 let val2 = mag as u64;
300 let temp = ((((val2 >> 32) as u32) as i32).wrapping_mul(31) as i64
301 + (val2 & 0xffff_ffff) as i64) as i32;
302 let signed = if signum < 0 {
303 temp.wrapping_neg()
304 } else {
305 temp
306 };
307 31i32.wrapping_mul(signed).wrapping_add(scale)
308 } else {
309 31i32
310 .wrapping_mul(biginteger_hash(words_be, signum))
311 .wrapping_add(scale)
312 }
313}
314
315fn parse_decimal(s: &str) -> Option<(i32, Vec<u8>, i64)> {
318 let b = s.as_bytes();
319 let mut i = 0usize;
320 let mut neg = false;
321 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
322 neg = b[i] == b'-';
323 i += 1;
324 }
325 let mut digits: Vec<u8> = Vec::new();
326 let mut seen_dot = false;
327 let mut scale: i64 = 0;
328 let mut any_digit = false;
329 while i < b.len() {
330 match b[i] {
331 c @ b'0'..=b'9' => {
332 digits.push(c - b'0');
333 if seen_dot {
334 scale += 1;
335 }
336 any_digit = true;
337 }
338 b'.' if !seen_dot => seen_dot = true,
339 b'e' | b'E' => {
340 i += 1;
341 let mut eneg = false;
342 if i < b.len() && (b[i] == b'-' || b[i] == b'+') {
343 eneg = b[i] == b'-';
344 i += 1;
345 }
346 let mut exp: i64 = 0;
347 let mut any_exp = false;
348 while i < b.len() && b[i].is_ascii_digit() {
349 exp = exp.saturating_mul(10).saturating_add((b[i] - b'0') as i64);
350 any_exp = true;
351 i += 1;
352 }
353 if !any_exp {
354 return None;
355 }
356 scale -= if eneg { -exp } else { exp };
357 break;
358 }
359 _ => return None,
360 }
361 i += 1;
362 }
363 if !any_digit {
364 return None;
365 }
366 match digits.iter().position(|d| *d != 0) {
367 None => Some((0, vec![0], 0)),
368 Some(first) => Some((if neg { -1 } else { 1 }, digits[first..].to_vec(), scale)),
369 }
370}
371
372fn canonical_decimal_hash(signum: i32, mut digits: Vec<u8>, mut scale: i64) -> i32 {
376 if signum == 0 {
377 return 0;
378 }
379 while digits.last() == Some(&0) {
380 digits.pop();
381 scale -= 1;
382 }
383 let words = digits_to_words_be(&digits);
384 bigdecimal_hash(&words, signum, scale as i32)
385}
386
387fn digits_to_words_be(digits: &[u8]) -> Vec<u32> {
390 let mut le: Vec<u32> = vec![0];
391 for d in digits {
392 let mut carry = *d as u64;
393 for w in le.iter_mut() {
394 let v = (*w as u64) * 10 + carry;
395 *w = v as u32;
396 carry = v >> 32;
397 }
398 while carry > 0 {
399 le.push(carry as u32);
400 carry >>= 32;
401 }
402 }
403 while le.len() > 1 && le.last() == Some(&0) {
404 le.pop();
405 }
406 le.iter().rev().copied().collect()
407}
408
409#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::core::Value;
417 use crate::kernel::parser::parse_forms;
418 use crate::kernel::Form;
419 use crate::lang::data::{
420 Keyword, Map as PMap, MapEntry as PMapEntry, Set as PSet, Symbol, Tuple as PTuple,
421 };
422 use crate::lang::protocol::{IDisplay, IHash, IObjType};
423
424 fn corpus_path(relative: &str) -> Option<std::path::PathBuf> {
427 crate::spec_registry::resolve(relative).filter(|candidate| candidate.is_file())
428 }
429
430 fn field<'a>(case: &'a Form, key: &str) -> &'a Form {
431 match case {
432 Form::Map(entries) => entries
433 .iter()
434 .find(|(k, _)| matches!(k, Form::Keyword(kw) if kw == key))
435 .map(|(_, v)| v)
436 .unwrap_or_else(|| panic!("case missing :{key}: {case}")),
437 other => panic!("case is not a map: {other}"),
438 }
439 }
440
441 fn kw_of(form: &Form) -> &str {
442 match form {
443 Form::Keyword(s) => s,
444 other => panic!("expected keyword, got {other}"),
445 }
446 }
447
448 fn num_of(form: &Form) -> i64 {
449 match form {
450 Form::Number(n) => *n,
451 other => panic!("expected number, got {other}"),
452 }
453 }
454
455 fn str_of(form: &Form) -> &str {
456 match form {
457 Form::String(s) => s,
458 other => panic!("expected string, got {other}"),
459 }
460 }
461
462 fn hash_type(id: &str) -> HashType {
463 match id {
464 "system" => HashType::System,
465 "rapid" => HashType::Rapid,
466 "murmur3" => HashType::Murmur3,
467 "sip" => HashType::Sip,
468 other => panic!("unknown hash type: {other}"),
469 }
470 }
471
472 fn element_value(form: &Form) -> Value {
475 match form {
476 Form::Nil => Value::Nil,
477 Form::Bool(b) => Value::Bool(*b),
478 Form::Number(n) => Value::Number(*n),
479 Form::Float(f) => Value::Float(*f),
480 Form::String(s) => Value::String(s.clone().into()),
481 Form::Vector(items) => Value::Vector(items.iter().map(element_value).collect()),
482 Form::List(items) => Value::List(items.iter().map(element_value).collect()),
483 Form::Map(pairs) => Value::Map(
484 pairs
485 .iter()
486 .map(|(k, v)| (element_value(k), element_value(v)))
487 .collect::<PMap<Value, Value>>(),
488 ),
489 Form::Set(items) => {
490 Value::Set(items.iter().map(element_value).collect::<PSet<Value>>())
491 }
492 other => panic!("unsupported collection element: {other}"),
493 }
494 }
495
496 fn collection_value(structure: &str, input: &Form) -> Value {
500 match structure {
501 "vector" | "list" | "map" | "set" => element_value(input),
502 "queue" => match input {
503 Form::Vector(items) => {
504 Value::Queue(Box::new(items.iter().map(element_value).collect()))
505 }
506 other => panic!("queue input must be a vector: {other}"),
507 },
508 "compact-vector2" => match input {
509 Form::Vector(items) if items.len() == 2 => Value::Tuple(Box::new(
510 PTuple::from_values(items.iter().map(element_value).collect()).unwrap(),
511 )),
512 other => panic!("compact-vector2 input must be a 2-vector: {other}"),
513 },
514 "map-entry" => match input {
515 Form::Vector(items) if items.len() == 2 => Value::MapEntry(Box::new(
516 PMapEntry::new(element_value(&items[0]), element_value(&items[1])),
517 )),
518 other => panic!("map-entry input must be a 2-vector: {other}"),
519 },
520 other => panic!("unknown collection structure: {other}"),
521 }
522 }
523
524 fn eval_case(case: &Form) -> i64 {
525 let hash = kw_of(field(case, "hash"));
526 let kind = kw_of(field(case, "kind"));
527 let input = field(case, "input");
528 match kind {
529 "string" => {
530 let s = str_of(input);
531 match hash {
532 "rapid" => rapid::hash(s.as_bytes()) as i64,
533 "murmur3" => murmur3::hash_chars(s) as i64,
534 "sip" => siphash::hash(&siphash::HARA, s.as_bytes()) as i64,
535 "system" => java_string_hash(s) as i64,
536 other => panic!("unknown string hash type: {other}"),
537 }
538 }
539 "int" => murmur3::hash_int(num_of(input) as i32) as i64,
540 "long" => match hash {
541 "murmur3" => murmur3::hash_long(num_of(input)) as i64,
542 "system" => hash_long(num_of(input)) as i64,
543 other => panic!("unknown long hash type: {other}"),
544 },
545 "double" => match input {
546 Form::Float(f) => hash_double(*f) as i64,
547 other => panic!("double input must be a float: {other}"),
548 },
549 "bigint" => canonical_decimal_str_hash(str_of(input)) as i64,
550 "bool" => match input {
551 Form::Bool(b) => hash_bool(*b) as i64,
552 other => panic!("bool input must be a boolean: {other}"),
553 },
554 "char" => match input {
555 Form::Character(c) => hash_char(*c) as i64,
556 other => panic!("char input must be a character: {other}"),
557 },
558 "bytes" => match input {
559 Form::Vector(items) => {
560 let bytes: Vec<u8> = items.iter().map(|f| num_of(f) as i8 as u8).collect();
561 hash_bytes(&bytes) as i64
562 }
563 other => panic!("bytes input must be a vector: {other}"),
564 },
565 "nil" => 0,
566 "seed" => hash_seed(str_of(input)) as i64,
567 "keyword" => match input {
568 Form::Keyword(s) => Keyword::parse(s).unwrap().hash_calc(hash_type(hash)) as i64,
569 other => panic!("keyword input must be a keyword: {other}"),
570 },
571 "symbol" => match input {
572 Form::Symbol(s) => Symbol::parse(s).hash_calc(hash_type(hash)) as i64,
573 other => panic!("symbol input must be a symbol: {other}"),
574 },
575 "collection" => {
576 let structure = kw_of(field(case, "structure"));
577 let value = collection_value(structure, input);
578 match hash {
579 "rapid" => value.stable_hash() as i64,
580 "murmur3" => value.java_hash(HashType::Murmur3),
581 other => panic!("unknown collection hash type: {other}"),
582 }
583 }
584 other => panic!("unknown case kind: {other}"),
585 }
586 }
587
588 #[test]
592 fn java_parity_fixture() {
593 let Some(path) =
594 corpus_path("01-lang/020-data-structures/draft/conformance/hash-parity.edn")
595 else {
596 eprintln!(
597 "skipping hash-parity corpus: specs checkout not found from {}",
598 env!("CARGO_MANIFEST_DIR")
599 );
600 return;
601 };
602 let source = std::fs::read_to_string(&path)
603 .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
604 let forms = parse_forms(&source).expect("hash-parity corpus must parse");
605 assert_eq!(forms.len(), 1, "corpus must be a single map form");
606 let Form::Vector(cases) = field(&forms[0], "cases") else {
607 panic!("corpus :cases must be a vector");
608 };
609 let mut failures: Vec<String> = Vec::new();
610 for case in cases {
611 if kw_of(field(case, "kind")) == "decimal" {
612 continue;
613 }
614 let id = kw_of(field(case, "id")).to_string();
615 let expected = num_of(field(case, "expect"));
616 let actual = eval_case(case);
617 if actual != expected {
618 failures.push(format!(":{id}: expected {expected}, got {actual}"));
619 }
620 }
621 assert!(
622 cases.len() >= 270,
623 "only {} hash-parity cases found",
624 cases.len()
625 );
626 if !failures.is_empty() {
627 panic!(
628 "{} of {} hash-parity cases failed:\n{}",
629 failures.len(),
630 cases.len(),
631 failures.join("\n")
632 );
633 }
634 }
635
636 #[test]
637 fn cross_type_numeric_equality() {
638 assert_eq!(hash_double(1.0), canonical_decimal_str_hash("1"));
641 assert_eq!(hash_double(1.0), canonical_decimal_str_hash("1.0"));
642 assert_eq!(hash_double(2.5), canonical_decimal_str_hash("2.50"));
643 assert_eq!(hash_double(100.0), canonical_decimal_str_hash("100"));
644 assert_eq!(hash_double(100.0), canonical_decimal_str_hash("100.0"));
645 }
646
647 #[test]
648 fn subnormal_double_known_divergence() {
649 assert_eq!(hash_double(f64::from_bits(1)), 479);
653 assert_eq!(hash_double(5e-324), 479);
654 }
655
656 #[test]
657 fn keyword_display_form_deviation() {
658 let kw = Keyword::create(None, "a").unwrap();
661 assert_eq!(
662 kw.hash_calc(HashType::Rapid) as i64,
663 rapid::hash("::KEYWORD|:a".as_bytes()) as i64
664 );
665 let sym = Symbol::create(None, "a");
666 assert_eq!(
667 sym.hash_calc(HashType::Rapid) as i64,
668 rapid::hash("::SYMBOL|hara.lang.data.Symbol<a>".as_bytes()) as i64
669 );
670 assert_eq!(kw.hash_calc(HashType::Sip) as i64, -1);
672 assert_eq!(kw.display(), ":a");
674 assert_eq!(sym.display(), "a");
675 assert_eq!(kw.hash_seed(), "::KEYWORD");
676 assert_eq!(sym.hash_seed(), "::SYMBOL");
677 }
678}