use crate::pipeline::*;
pub(crate) fn is_reference(r: &Reg) -> bool {
matches!(r.value, Value::Node(_))
}
pub(crate) 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 {
let side = |refs: bool| register.iter().filter(move |r| is_reference(r) == refs);
let full = |refs: bool| {
let mut fields: Vec<(String, T)> = Vec::new();
for (i, reg) in side(refs).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)
};
match r {
RegRef::Session(_) | RegRef::SessionRecord => none,
RegRef::Top => side(false).last().map(|r| part(r).clone()).unwrap_or(none),
RegRef::Index(n) => side(false)
.nth(n.saturating_sub(1))
.map(|r| part(r).clone())
.unwrap_or(none),
RegRef::Back(n) => side(false)
.collect::<Vec<_>>()
.into_iter()
.rev()
.nth(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(side(false).map(|r| part(r).clone()).collect()),
RegRef::NodeWhole => list(side(true).map(|r| part(r).clone()).collect()),
RegRef::Record => record(named_side(register, false, part)),
RegRef::NodeRecord => record(named_side(register, true, part)),
RegRef::FullRecord => full(false),
RegRef::NodeFullRecord => full(true),
}
}
fn named_side<T: Clone>(register: &[Reg], refs: bool, part: impl Fn(&Reg) -> &T) -> Vec<(String, T)> {
let mut fields: Vec<(String, T)> = Vec::new();
for reg in register.iter().filter(|r| is_reference(r) == refs) {
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(crate) fn session_recall(r: &RegRef) -> Value {
let Some(memory) = crate::pipeline::run_option(|o| o.session.clone()) else {
crate::value::ambient::record_refusal(
"the session memory is not open in this run: '$,name' and '| ,name' live in a session (quai, the qua REPL, a .quai file); the capsa's register is '$.name'".into(),
);
return Value::Null;
};
let store = memory.borrow();
let memory = &store.memory;
match r {
RegRef::Session(name) => match memory.get(name) {
Some(v) => v.clone(),
None => {
let held: Vec<&str> = memory.keys().map(String::as_str).collect();
crate::value::ambient::record_refusal(format!(
"no value ',{name}' in the session memory (it holds: {}); the capsa's register is '$.{name}'",
if held.is_empty() { "nothing".to_string() } else { held.join(", ") }
));
Value::Null
}
},
_ => Value::record(memory.iter().map(|(k, v)| (k.clone(), v.clone())).collect()),
}
}
pub(crate) fn recall(register: &[Reg], r: &RegRef) -> Value {
if matches!(r, RegRef::Session(_) | RegRef::SessionRecord) {
return session_recall(r);
}
recall_part(
register,
r,
|r| &r.value,
Value::Null,
Value::list,
Value::record,
)
}
pub(crate) 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(crate) 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::Back(n) => register.iter().rev().nth(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(crate) fn recall_prov(register: &[Reg], r: &RegRef) -> Prov {
recall_part(
register,
r,
|r| &r.prov,
Prov::default(),
Prov::list,
Prov::record,
)
}
pub(crate) 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(crate) fn named_register_parts(register: &[Reg]) -> Vec<(String, Prov)> {
named_register(register, |r| &r.prov)
}
pub(crate) fn named_register_fields(register: &[Reg]) -> Vec<(String, Value)> {
named_register(register, |r| &r.value)
}
pub(crate) type RegKey = Vec<(Option<String>, String)>;
pub(crate) type RoundState<K> = (K, Option<Value>, Vec<(String, Value)>);
pub(crate) 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))
}
Value::Node(n) => format!("@node:{}", n.id),
_ => 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
})
}