Skip to main content

sim_lib_mutation/
runtime_key.rs

1use std::{
2    collections::hash_map::DefaultHasher,
3    hash::{Hash, Hasher},
4};
5
6use sim_kernel::{Cx, Expr, NumberLiteral, Result, Symbol, Value};
7
8/// A table key derived from a runtime value rather than a kernel [`Symbol`].
9///
10/// Guest languages use this when table/hash/map keys are ordinary values. The
11/// key remains policy-controlled so each language decides which values are
12/// admissible and how numeric values collapse or stay distinct.
13#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum RuntimeKey {
15    /// Boolean key.
16    Bool(bool),
17    /// Integer key.
18    Integer(i64),
19    /// Floating-point key represented by its raw IEEE-754 bits.
20    FloatBits(u64),
21    /// Text key.
22    Str(String),
23    /// Symbol key.
24    Symbol(Symbol),
25    /// Object identity key, local to this runtime process.
26    ObjectIdentity(u64),
27}
28
29impl RuntimeKey {
30    /// Derives a general-purpose key from `value`.
31    ///
32    /// Nil and NaN are rejected with `None`; booleans, numbers, strings, and
33    /// symbols become structural keys; every other value is keyed by object
34    /// identity. Guest languages can call this from their own
35    /// [`RuntimeKeyPolicy`] or provide a stricter mapping.
36    pub fn from_value(cx: &mut Cx, value: &Value) -> Result<Option<Self>> {
37        match value.object().as_expr(cx)? {
38            Expr::Nil => Ok(None),
39            Expr::Bool(value) => Ok(Some(Self::Bool(value))),
40            Expr::Number(number) => Ok(number_key(&number)),
41            Expr::String(value) => Ok(Some(Self::Str(value))),
42            Expr::Symbol(symbol) => Ok(Some(Self::Symbol(symbol))),
43            _ => Ok(Some(Self::ObjectIdentity(object_identity(value)))),
44        }
45    }
46
47    /// Returns this key as a contiguous integer index when it is one.
48    pub fn as_integer_index(&self) -> Option<i64> {
49        match self {
50            Self::Integer(index) => Some(*index),
51            _ => None,
52        }
53    }
54
55    /// Projects this key into an expression for inspection.
56    pub fn as_expr(&self) -> Expr {
57        match self {
58            Self::Bool(value) => Expr::Bool(*value),
59            Self::Integer(value) => Expr::Number(NumberLiteral {
60                domain: Symbol::qualified("runtime-key", "integer"),
61                canonical: value.to_string(),
62            }),
63            Self::FloatBits(bits) => Expr::Number(NumberLiteral {
64                domain: Symbol::qualified("runtime-key", "float-bits"),
65                canonical: bits.to_string(),
66            }),
67            Self::Str(value) => Expr::String(value.clone()),
68            Self::Symbol(symbol) => Expr::Symbol(symbol.clone()),
69            Self::ObjectIdentity(identity) => Expr::Extension {
70                tag: Symbol::qualified("mutation", "object-identity-key"),
71                payload: Box::new(Expr::String(identity.to_string())),
72            },
73        }
74    }
75}
76
77/// Maps a language value to a runtime table key.
78///
79/// Returning `None` means the language forbids that value as a key. For
80/// example, a policy can reject nil, NaN, or any object kind it cannot identify.
81pub trait RuntimeKeyPolicy: Send + Sync {
82    /// Returns the runtime key for `value`, or `None` when the key is forbidden.
83    fn key_for(&self, cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>>;
84}
85
86/// A small reusable key policy for languages that accept primitive keys and
87/// object identity.
88#[derive(Clone, Copy, Debug, Default)]
89pub struct PrimitiveRuntimeKeyPolicy;
90
91impl RuntimeKeyPolicy for PrimitiveRuntimeKeyPolicy {
92    fn key_for(&self, cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>> {
93        RuntimeKey::from_value(cx, value)
94    }
95}
96
97fn number_key(number: &NumberLiteral) -> Option<RuntimeKey> {
98    if let Ok(value) = number.canonical.parse::<i64>() {
99        return Some(RuntimeKey::Integer(value));
100    }
101    let value = number.canonical.parse::<f64>().ok()?;
102    (!value.is_nan()).then_some(RuntimeKey::FloatBits(value.to_bits()))
103}
104
105fn object_identity(value: &Value) -> u64 {
106    let mut hasher = DefaultHasher::new();
107    value.hash(&mut hasher);
108    hasher.finish()
109}