feat: add managed types, source classes and scoped native calls

This commit is contained in:
2026-10-21 01:47:08 +08:00
parent 7c842789c0
commit a970fd7707
48 changed files with 2069 additions and 134 deletions
+206
View File
@@ -0,0 +1,206 @@
use std::{fmt, sync::Arc};
use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef};
/// Borrowed results produced inside a native call. Invoke delegates to another
/// callable through the VM's existing frame loop, without recursively entering it.
#[derive(Debug)]
pub enum NativeResult {
Return(Value),
Invoke {
function: ObjectRef,
arguments: Vec<Value>,
},
}
pub trait NativeCallable: fmt::Debug + Send + Sync + 'static {
fn invoke(
&self,
context: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError>;
}
/// Native allocation/mutation scope. Allocations and upgraded weak references
/// are temporary owned roots. Return/Invoke promotes only its output roots;
/// Drop releases everything else, including on error or unwinding.
pub struct NativeContext<'a> {
store: &'a mut ObjectStore,
roots: Vec<ObjectRef>,
callee: ObjectRef,
}
impl<'a> NativeContext<'a> {
/// Callable currently dispatched by the VM, borrowed for this scope.
pub fn callee(&self) -> ObjectRef {
self.callee
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.store.attach_type(object, type_object)
}
pub fn store(&self) -> &ObjectStore {
self.store
}
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
let object = self.store.allocate(payload)?;
self.roots.push(object);
Ok(object)
}
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
self.allocate(crate::Record)
}
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
let object = self.store.new_dict()?;
self.roots.push(object);
Ok(object)
}
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
let object = self.store.new_cell(value)?;
self.roots.push(object);
Ok(object)
}
pub fn set_attr(
&mut self,
object: ObjectRef,
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
self.store.set_attr(object, name, value)
}
pub fn del_attr(&mut self, object: ObjectRef, name: &str) -> Result<(), VmError> {
self.store.del_attr(object, name)
}
pub fn dict_set(&mut self, object: ObjectRef, key: Value, value: Value) -> Result<(), VmError> {
self.store.dict_set(object, key, value)
}
pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result<bool, VmError> {
self.store.dict_delete(object, key)
}
pub fn cell_set(&mut self, object: ObjectRef, value: Value) -> Result<(), VmError> {
self.store.cell_set(object, value)
}
pub fn replace_payload(
&mut self,
object: ObjectRef,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
self.store.replace_payload(object, payload)
}
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
let object = self.store.upgrade(weak)?;
if let Some(object) = object {
self.roots.push(object);
}
Ok(object)
}
fn protect(&mut self, value: &Value) -> Result<(), VmError> {
if let Value::Object(object) = value {
self.store.retain(*object)?;
self.roots.push(*object);
}
Ok(())
}
/// Internal execution bridge. Every returned object reference owns a root;
/// consumers must release Return values or all Invoke arguments/function.
pub fn execute(
store: &'a mut ObjectStore,
function: ObjectRef,
arguments: &[Value],
callable: Arc<dyn NativeCallable>,
) -> Result<NativeResult, VmError> {
let mut context = Self {
store,
roots: Vec::new(),
callee: function,
};
context.protect(&Value::Object(function))?;
for argument in arguments {
context.protect(argument)?;
}
let result = callable.invoke(&mut context, arguments)?;
let transferred_from = context.roots.len();
match &result {
NativeResult::Return(value) => context.protect(value)?,
NativeResult::Invoke {
function,
arguments,
} => {
context.protect(&Value::Object(*function))?;
for argument in arguments {
context.protect(argument)?;
}
}
}
// Transfer exactly the output roots; all other roots remain scoped.
context.roots.truncate(transferred_from);
Ok(result)
}
}
impl Drop for NativeContext<'_> {
fn drop(&mut self) {
for root in self.roots.drain(..).rev() {
self.store
.release(root)
.expect("native scope owns its roots");
}
}
}
/// A context-aware function with explicit, immutable captured object edges.
#[derive(Debug)]
pub struct ContextNativeFunction {
name: &'static str,
callable: Arc<dyn NativeCallable>,
captures: Vec<ObjectRef>,
}
impl ContextNativeFunction {
pub fn new(
name: &'static str,
callable: impl NativeCallable,
captures: Vec<ObjectRef>,
) -> Self {
Self {
name,
callable: Arc::new(callable),
captures,
}
}
}
impl ObjectPayload for ContextNativeFunction {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
self.name
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
for capture in &self.captures {
visit(*capture);
}
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(self.callable.clone())
}
}