use super::*;
pub(super) fn recall_part<T: Clone>(
register: &[Reg],
r: &RegRef,
part: impl Fn(&Reg) -> &T,
none: T,
list: impl Fn(Vec<T>) -> T,
record: impl Fn(Vec<(String, T)>) -> T,
) -> T {
match r {
RegRef::Top => register.last().map(|r| part(r).clone()).unwrap_or(none),
RegRef::Index(n) => register
.get(n.saturating_sub(1))
.map(|r| part(r).clone())
.unwrap_or(none),
RegRef::Named(name) => register
.iter()
.rev()
.find(|r| r.name.as_deref() == Some(name.as_str()))
.map(|r| part(r).clone())
.unwrap_or(none),
RegRef::Whole => list(register.iter().map(|r| part(r).clone()).collect()),
RegRef::Record => record(named_register(register, part)),
RegRef::FullRecord => {
let mut fields: Vec<(String, T)> = Vec::new();
for (i, reg) in register.iter().enumerate() {
match ®.name {
Some(name) => match fields.iter_mut().find(|(n, _)| n == name) {
Some((_, v)) => *v = part(reg).clone(),
None => fields.push((name.clone(), part(reg).clone())),
},
None => fields.push((format!(".{}", i + 1), part(reg).clone())),
}
}
record(fields)
}
}
}
pub(super) fn recall(register: &[Reg], r: &RegRef) -> Value {
recall_part(
register,
r,
|r| &r.value,
Value::Null,
Value::list,
Value::Record,
)
}
pub(super) fn recall_origins(register: &[Reg], r: &RegRef) -> Origins {
let union = |items: Vec<Origins>| {
let mut o = Origins::default();
for i in &items {
o.union(i);
}
o
};
recall_part(
register,
r,
|r| &r.prov.origins,
Origins::default(),
union,
|fields| union(fields.into_iter().map(|(_, o)| o).collect()),
)
}
pub(super) fn recall_field_origins(register: &[Reg], r: &RegRef, name: &str) -> Origins {
let reg = match r {
RegRef::Top => register.last(),
RegRef::Index(n) => register.get(n.saturating_sub(1)),
RegRef::Named(key) => register
.iter()
.rev()
.find(|r| r.name.as_deref() == Some(key.as_str())),
_ => None,
};
match reg {
Some(reg) => reg.prov.part(name).origins,
None => recall_prov(register, r).part(name).origins,
}
}
pub(super) fn recall_prov(register: &[Reg], r: &RegRef) -> Prov {
recall_part(
register,
r,
|r| &r.prov,
Prov::default(),
Prov::list,
Prov::record,
)
}
pub(super) fn named_register<T: Clone>(
register: &[Reg],
part: impl Fn(&Reg) -> &T,
) -> Vec<(String, T)> {
let mut fields: Vec<(String, T)> = Vec::new();
for reg in register {
if let Some(name) = ®.name {
match fields.iter_mut().find(|(n, _)| n == name) {
Some((_, v)) => *v = part(reg).clone(),
None => fields.push((name.clone(), part(reg).clone())),
}
}
}
fields
}
pub(super) fn named_register_parts(register: &[Reg]) -> Vec<(String, Prov)> {
named_register(register, |r| &r.prov)
}
pub(super) fn named_register_fields(register: &[Reg]) -> Vec<(String, Value)> {
named_register(register, |r| &r.value)
}
pub(super) type RegKey = Vec<(Option<String>, String)>;
pub(super) type RoundState = (NodeId, Option<Value>, Vec<(String, Value)>);
pub(super) fn reg_key(register: &[Reg]) -> RegKey {
register
.iter()
.map(|r| (r.name.clone(), value_key(&r.value)))
.collect()
}
fn value_key(v: &Value) -> String {
match v {
Value::List(items) if items.len() >= 16 => {
format!("@list:{}:{:016x}", items.len(), list_digest(items))
}
_ => v.to_string(),
}
}
fn list_digest(items: &std::sync::Arc<Vec<Value>>) -> u64 {
use std::cell::RefCell;
use std::hash::{Hash, Hasher};
thread_local! {
static DIGEST: RefCell<Option<(std::sync::Weak<Vec<Value>>, u64)>> =
const { RefCell::new(None) };
}
DIGEST.with(|cell| {
let mut slot = cell.borrow_mut();
if let Some((w, d)) = slot.as_ref()
&& w.upgrade()
.is_some_and(|a| std::sync::Arc::ptr_eq(&a, items))
{
return *d;
}
let mut h = std::collections::hash_map::DefaultHasher::new();
for item in items.iter() {
item.to_string().hash(&mut h);
}
let d = h.finish();
*slot = Some((std::sync::Arc::downgrade(items), d));
d
})
}