use std::any::Any; use std::collections::BTreeMap; use std::collections::hash_map::DefaultHasher; use std::fmt; use std::hash::{Hash, Hasher}; use crate::{EndorNumber, ObjectRef, VmError}; /// Immediate values stay outside the object heap. Copying an ObjectRef or /// cloning a Value never retains a reference; ownership follows the API that /// produced it. In particular, call/execute results own a root until released. #[derive(Debug, Clone, PartialEq)] pub enum Value { Nil, Bool(bool), /// Rust equality preserves representation; it is not numeric equivalence. Number(EndorNumber), Text(String), Object(ObjectRef), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompareOp { Eq, Ne, Lt, Le, Gt, Ge, } impl CompareOp { pub(crate) fn reflected(self) -> Self { match self { Self::Eq => Self::Eq, Self::Ne => Self::Ne, Self::Lt => Self::Gt, Self::Le => Self::Ge, Self::Gt => Self::Lt, Self::Ge => Self::Le, } } } /// Rust-side operation interface, not a language type or metaclass. /// /// Implementations must report every payload-owned object reference in trace(), /// including duplicates. Those edges must stay immutable after allocation; /// mutable object references belong in VM-managed attributes or built-in /// containers updated transactionally by the VM instead. /// Rust Drop releases host resources when the payload is destroyed. /// Equal payloads must provide compatible hashes, or reject hashing explicitly. pub trait ObjectPayload: Any + fmt::Debug + Send + Sync { /// Explicit function category; callable Type objects are not functions. fn is_function(&self) -> bool { false } /// Ordinary field storage may be indirect, independently of type identity. /// Targets must be traced; retargeting records Contents on the forwarder. fn attribute_target(&self, _store: &crate::ObjectStore) -> Result, VmError> { Ok(None) } /// Shared-reference facades may expose the current referent's type. The /// referent must also be held by traced managed edges. Ordinary objects /// keep their immutable header type; this does not change inheritance. fn type_target(&self, _store: &crate::ObjectStore) -> Result, VmError> { Ok(None) } /// Optional context-aware native entry. The VM clones this dispatcher before /// borrowing the store mutably; callbacks never borrow a live payload. fn native_callable(&self) -> Option> { None } fn as_any(&self) -> &dyn Any; /// A diagnostic label only, with no language type or inheritance semantics. fn kind_name(&self) -> &'static str; fn trace(&self, _visit: &mut dyn FnMut(ObjectRef)) {} fn allows_attributes(&self) -> bool { false } fn repr(&self, identity: ObjectRef) -> Result { Ok(format!("<{} {identity}>", self.kind_name())) } fn str(&self, identity: ObjectRef) -> Result { self.repr(identity) } fn is_true(&self) -> Result { Ok(true) } fn hash(&self, identity: ObjectRef) -> Result { let mut hasher = DefaultHasher::new(); identity.hash(&mut hasher); Ok(hasher.finish()) } /// None means "not implemented"; the VM tries the reflected operation, /// then falls back to identity for Eq/Ne or rejects ordering. fn compare( &self, _other: &dyn ObjectPayload, _operation: CompareOp, ) -> Result, VmError> { Ok(None) } /// Host-call entry: arguments and raw results are borrowed handles. The VM /// validates/retains the result before exposing it as an owned call result. /// Language functions are dispatched by Vm's frame evaluator instead. /// Positional metadata for native call protocols; source code is queried by the store. fn call_arity(&self) -> Option { self.native_callable().and_then(|callable| callable.arity()) } fn call(&self, _arguments: &[Value]) -> Result { Err(VmError::UnsupportedOperation("call")) } /// Opt-in shared-reference assignment. Returning true preserves the existing /// object identity after replacing its host-owned target. Ordinary payloads /// return false and retain normal value assignment. Implementations must /// validate before mutation and must not add untraced managed-object edges. /// True only when legacy assign_from always returns false. Read-only /// callers can then rebind a local reference without attempting mutation. /// assignment_cell is checked first and remains a protected write protocol. fn reference_assignment_only(&self) -> bool { false } fn assign_from(&self, _source: &dyn ObjectPayload) -> Result { Ok(false) } /// Optional managed shared-reference assignment. Return a built-in Cell /// owned by this payload after validating the source and any domain cycles. /// The store writes the source object to the Cell transactionally. This /// keeps mutable edges inside the GC graph, unlike native assign_from. fn assignment_cell( &self, _source: ObjectRef, _store: &crate::ObjectStore, ) -> Result, VmError> { Ok(None) } } #[derive(Debug, Default)] pub struct Record; impl ObjectPayload for Record { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "record" } fn allows_attributes(&self) -> bool { true } } pub type EndorNativeCall = fn(&[Value]) -> Result; pub type NativeCall = EndorNativeCall; #[derive(Debug)] pub struct EndorNativeFunctionObject { name: String, function: NativeCall, arity: Option, } impl EndorNativeFunctionObject { pub fn new(name: impl Into, function: NativeCall) -> Self { Self { name: name.into(), function, arity: None, } } pub fn name(&self) -> &str { &self.name } pub fn with_arity(mut self, arity: crate::CallArity) -> Self { self.arity = Some(arity); self } } /// Compatibility name for existing embedders. pub type NativeFunction = EndorNativeFunctionObject; impl ObjectPayload for EndorNativeFunctionObject { fn is_function(&self) -> bool { true } fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "native-function" } fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.name)) } fn call_arity(&self) -> Option { self.arity } fn call(&self, arguments: &[Value]) -> Result { if let Some(arity) = self.arity { arity.check(self.name.clone(), arguments.len())?; } (self.function)(arguments) } } /// Common runtime state. Embedders may attach an immutable managed type. #[derive(Debug)] pub struct ObjectHeader { pub(crate) identity: ObjectRef, pub(crate) strong_refs: usize, pub(crate) external_refs: usize, pub(crate) change_version: u64, pub(crate) type_object: Option, } impl ObjectHeader { pub fn type_object(&self) -> Option { self.type_object } pub fn change_version(&self) -> u64 { self.change_version } pub fn identity(&self) -> ObjectRef { self.identity } pub fn ref_count(&self) -> usize { self.strong_refs } /// Host-owned references and live execution roots, excluding heap edges. pub fn external_ref_count(&self) -> usize { self.external_refs } } /// Runtime object foundation, analogous in role to CPython's common object /// structure, without its binary layout. The heap boxes this /// structure so its address remains stable throughout its lifetime. #[derive(Debug)] pub struct EndorObject { pub(crate) header: ObjectHeader, pub(crate) payload: Box, pub(crate) attributes: BTreeMap, pub(crate) payload_edges: Vec, } impl EndorObject { pub fn header(&self) -> &ObjectHeader { &self.header } pub fn payload(&self) -> &dyn ObjectPayload { self.payload.as_ref() } /// Visits owned edges, not external roots. Payload edges are snapshotted at /// allocation and refreshed on VM-managed payload replacement; attributes /// are maintained by the VM's reference accounting. pub fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { if let Some(reference) = self.header.type_object { visit(reference); } for reference in &self.payload_edges { visit(*reference); } for value in self.attributes.values() { if let Value::Object(reference) = value { visit(*reference); } } } pub(crate) fn edges(&self) -> Vec { let mut edges = Vec::new(); self.trace(&mut |reference| edges.push(reference)); edges } }