use nebu::Goldilocks;
use super::inner::Data;
use super::hash::{Digest, hash_atom, hash_pair};
use super::cost::{Cost, PATTERN_COSTS};
use super::{Order, NIL};
#[derive(Debug, Clone, Copy)]
pub struct DataEntry {
pub inner: Data,
pub hash: Digest,
pub bound: Cost,
}
pub struct Reduction<const N: usize> {
entries: [core::mem::MaybeUninit<DataEntry>; N],
count: u32,
index_keys: [Digest; N],
index_vals: [Order; N],
index_mask: u32,
}
impl<const N: usize> Default for Reduction<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Reduction<N> {
pub fn new() -> Self {
assert!(N.is_power_of_two(), "order size must be power of 2");
Self {
entries: [const { core::mem::MaybeUninit::uninit() }; N],
count: 0,
index_keys: [[Goldilocks::ZERO; 4]; N],
index_vals: [NIL; N],
index_mask: (N as u32) - 1,
}
}
pub fn alloc_raw(&mut self, entry: DataEntry) -> Option<Order> {
if (self.count as usize) >= (N / 4) * 3 { return None; }
let idx = self.count;
self.entries[idx as usize] = core::mem::MaybeUninit::new(entry);
self.count += 1;
Some(idx)
}
pub fn get(&self, r: Order) -> Option<&DataEntry> {
if (r as usize) >= self.count as usize { return None; }
Some(unsafe { self.entries[r as usize].assume_init_ref() })
}
pub fn atom(&mut self, value: Goldilocks) -> Option<Order> {
let inner = Data::Atom { value };
let hash = hash_atom(value);
if let Some(existing) = self.index_lookup(&hash) { return Some(existing); }
let r = self.alloc_raw(DataEntry { inner, hash, bound: Cost::Exact(0) })?;
self.index_insert(&hash, r);
Some(r)
}
pub fn pair(&mut self, left: Order, right: Order) -> Option<Order> {
let lh = self.get(left)?.hash;
let rh = self.get(right)?.hash;
let hash = hash_pair(&lh, &rh);
if let Some(existing) = self.index_lookup(&hash) { return Some(existing); }
let inner = Data::Pair { left, right };
let bound = self.compute_pair_bound(left, right);
let r = self.alloc_raw(DataEntry { inner, hash, bound })?;
self.index_insert(&hash, r);
Some(r)
}
fn compute_pair_bound(&self, left: Order, right: Order) -> Cost {
let left_entry = match self.get(left) { Some(e) => e, None => return Cost::Exact(0) };
let tag = match left_entry.inner {
Data::Atom { value } => value.as_u64(),
_ => return Cost::Exact(0),
};
if (tag as usize) >= PATTERN_COSTS.len() { return Cost::Exact(0); }
let base = PATTERN_COSTS[tag as usize];
let child_bound = οΏΏid: OrderοΏΏ -> Cost {
self.get(id).map_or(Cost::Exact(0), οΏΏeοΏΏ e.bound)
};
let pair = οΏΏid: OrderοΏΏ -> Option<(Order, Order)> {
match self.get(id)?.inner {
Data::Pair { left, right } => Some((left, right)),
_ => None,
}
};
match tag {
0 οΏΏ 1 => Cost::Exact(base),
2 => match pair(right) {
Some((x, y)) => {
let total = base
.saturating_add(child_bound(x).value())
.saturating_add(child_bound(y).value());
Cost::Dynamic(total)
}
None => Cost::Exact(base),
},
3 οΏΏ 5 οΏΏ 6 οΏΏ 7 οΏΏ 9 οΏΏ 10 οΏΏ 11 οΏΏ 12 οΏΏ 14 οΏΏ 17 => match pair(right) {
Some((a, b)) => Cost::sum(base, child_bound(a), child_bound(b)),
None => Cost::Exact(base),
},
4 => match pair(right) {
Some((test, rest)) => match pair(rest) {
Some((yes, no)) => Cost::branch(
base, child_bound(test), child_bound(yes), child_bound(no),
),
None => Cost::Exact(base),
},
None => Cost::Exact(base),
},
8 οΏΏ 13 οΏΏ 15 => Cost::sum1(base, child_bound(right)),
16 => match pair(right) {
Some((tag_f, _check_f)) => {
let total = base.saturating_add(child_bound(tag_f).value());
Cost::Dynamic(total)
}
None => Cost::Exact(base),
},
_ => Cost::Exact(base),
}
}
pub fn hash_data(&mut self, digest: &Digest) -> Option<Order> {
let h0 = self.atom(digest[0])?;
let h1 = self.atom(digest[1])?;
let h2 = self.atom(digest[2])?;
let h3 = self.atom(digest[3])?;
let left = self.pair(h0, h1)?;
let right = self.pair(h2, h3)?;
self.pair(left, right)
}
pub fn read_hash_data(&self, r: Order) -> Option<Digest> {
let (left, right) = match self.get(r)?.inner {
Data::Pair { left, right } => (left, right),
_ => return None,
};
let (h0r, h1r) = match self.get(left)?.inner {
Data::Pair { left, right } => (left, right),
_ => return None,
};
let (h2r, h3r) = match self.get(right)?.inner {
Data::Pair { left, right } => (left, right),
_ => return None,
};
Some([
self.atom_value(h0r)?,
self.atom_value(h1r)?,
self.atom_value(h2r)?,
self.atom_value(h3r)?,
])
}
fn index_lookup(&self, hash: &Digest) -> Option<Order> {
let mut slot = (hash[0].as_u64() as u32) & self.index_mask;
for _ in 0..N {
let val = self.index_vals[slot as usize];
if val == NIL { return None; }
if self.index_keys[slot as usize] == *hash { return Some(val); }
slot = (slot + 1) & self.index_mask;
}
None
}
fn index_insert(&mut self, hash: &Digest, r: Order) {
let mut slot = (hash[0].as_u64() as u32) & self.index_mask;
loop {
if self.index_vals[slot as usize] == NIL {
self.index_keys[slot as usize] = *hash;
self.index_vals[slot as usize] = r;
return;
}
slot = (slot + 1) & self.index_mask;
}
}
pub fn count(&self) -> u32 { self.count }
#[cfg(feature = "std")]
pub fn fork(&self) -> Self {
let mut new = Reduction::<N>::new();
new.count = self.count;
new.index_mask = self.index_mask;
for i in 0..self.count as usize {
let entry = unsafe { self.entries[i].assume_init() };
new.entries[i] = core::mem::MaybeUninit::new(entry);
}
new.index_keys.copy_from_slice(&self.index_keys);
new.index_vals.copy_from_slice(&self.index_vals);
new
}
#[cfg(feature = "std")]
pub fn reinter(&mut self, src: &Self, id: Order) -> Option<Order> {
let entry = src.get(id)?;
match entry.inner {
Data::Atom { value } => self.atom(value),
Data::Pair { left, right } => {
let l = self.reinter(src, left)?;
let r = self.reinter(src, right)?;
self.pair(l, r)
}
}
}
pub fn is_atom(&self, r: Order) -> bool {
self.get(r).is_some_and(οΏΏeοΏΏ matches!(e.inner, Data::Atom { .. }))
}
pub fn is_pair(&self, r: Order) -> bool {
self.get(r).is_some_and(οΏΏeοΏΏ matches!(e.inner, Data::Pair { .. }))
}
pub fn head(&self, r: Order) -> Option<Order> {
match self.get(r)?.inner { Data::Pair { left, .. } => Some(left), _ => None }
}
pub fn tail(&self, r: Order) -> Option<Order> {
match self.get(r)?.inner { Data::Pair { right, .. } => Some(right), _ => None }
}
pub fn atom_value(&self, r: Order) -> Option<Goldilocks> {
match self.get(r)?.inner { Data::Atom { value } => Some(value), _ => None }
}
pub fn digest(&self, r: Order) -> Option<&Digest> {
Some(&self.get(r)?.hash)
}
}