use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use sim_kernel::{Cx, Expr, NumberLiteral, Result, Symbol, Value};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RuntimeKey {
Bool(bool),
Integer(i64),
FloatBits(u64),
Str(String),
Symbol(Symbol),
ObjectIdentity(u64),
}
impl RuntimeKey {
pub fn from_value(cx: &mut Cx, value: &Value) -> Result<Option<Self>> {
match value.object().as_expr(cx)? {
Expr::Nil => Ok(None),
Expr::Bool(value) => Ok(Some(Self::Bool(value))),
Expr::Number(number) => Ok(number_key(&number)),
Expr::String(value) => Ok(Some(Self::Str(value))),
Expr::Symbol(symbol) => Ok(Some(Self::Symbol(symbol))),
_ => Ok(Some(Self::ObjectIdentity(object_identity(value)))),
}
}
pub fn as_integer_index(&self) -> Option<i64> {
match self {
Self::Integer(index) => Some(*index),
_ => None,
}
}
pub fn as_expr(&self) -> Expr {
match self {
Self::Bool(value) => Expr::Bool(*value),
Self::Integer(value) => Expr::Number(NumberLiteral {
domain: Symbol::qualified("runtime-key", "integer"),
canonical: value.to_string(),
}),
Self::FloatBits(bits) => Expr::Number(NumberLiteral {
domain: Symbol::qualified("runtime-key", "float-bits"),
canonical: bits.to_string(),
}),
Self::Str(value) => Expr::String(value.clone()),
Self::Symbol(symbol) => Expr::Symbol(symbol.clone()),
Self::ObjectIdentity(identity) => Expr::Extension {
tag: Symbol::qualified("mutation", "object-identity-key"),
payload: Box::new(Expr::String(identity.to_string())),
},
}
}
}
pub trait RuntimeKeyPolicy: Send + Sync {
fn key_for(&self, cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PrimitiveRuntimeKeyPolicy;
impl RuntimeKeyPolicy for PrimitiveRuntimeKeyPolicy {
fn key_for(&self, cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>> {
RuntimeKey::from_value(cx, value)
}
}
fn number_key(number: &NumberLiteral) -> Option<RuntimeKey> {
if let Ok(value) = number.canonical.parse::<i64>() {
return Some(RuntimeKey::Integer(value));
}
let value = number.canonical.parse::<f64>().ok()?;
(!value.is_nan()).then_some(RuntimeKey::FloatBits(value.to_bits()))
}
fn object_identity(value: &Value) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}