//! Managed indexed host results; values and the at method are traced edges. use endor_core::{ ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef, Value, VmError, }; #[derive(Debug)] struct ObjectList(Vec); impl ObjectPayload for ObjectList { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn std::any::Any { self } fn kind_name(&self) -> &'static str { "tessera-object-list" } fn allows_attributes(&self) -> bool { true } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { for mount in &self.0 { visit(*mount); } } } #[derive(Debug)] struct At(ObjectRef); impl NativeCallable for At { fn signature(&self) -> Option { Some(endor_core::CallSignature::exact( vec![endor_core::AnnotationType::Number], endor_core::AnnotationType::Any, endor_core::CallEffect::ReadOnly, )) } fn arity(&self) -> Option { Some(endor_core::CallArity::exact(1)) } fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let [Value::Number(index)] = args else { return Err(VmError::InvalidArgument("object list at expects an index")); }; let index = index .as_i64() .and_then(|v| usize::try_from(v).ok()) .ok_or(VmError::InvalidArgument("invalid list index"))?; let mount = *cx .store() .payload::(self.0, "Object list")? .0 .get(index) .ok_or(VmError::InvalidArgument("list index out of range"))?; Ok(NativeResult::Return(Value::Object(mount))) } } pub fn collection( cx: &mut NativeContext<'_>, mounts: Vec, desktop: ObjectRef, ) -> Result { build(cx, mounts, desktop, None) } fn build( cx: &mut NativeContext<'_>, mounts: Vec, desktop: ObjectRef, definition: Option, ) -> Result { if mounts.len() > 10_000 { return Err(VmError::InvalidArgument( "object list exceeds 10000 members", )); } let first = mounts .first() .copied() .map(Value::Object) .unwrap_or(Value::Nil); let count = mounts.len(); let set = cx.allocate(ObjectList(mounts))?; if let Some(definition) = definition { cx.attach_type(set, definition)?; } else { crate::ui_bridge::attach_desktop_type(cx, set, "ObjectList", desktop)?; } cx.set_attr(set, "first", first)?; cx.set_attr(set, "count", Value::Number((count as i64).into()))?; let at = cx.allocate(ContextNativeFunction::new("at", At(set), vec![set]))?; crate::ui_bridge::attach_desktop_type(cx, at, "Function", desktop)?; cx.set_attr(set, "at", Value::Object(at))?; Ok(set) } pub fn items(store: &endor_core::ObjectStore, list: ObjectRef) -> Result<&[ObjectRef], VmError> { Ok(&store.payload::(list, "ObjectList")?.0) } #[derive(Debug)] pub struct Constructor { pub signature: Option, pub desktop: ObjectRef, } impl NativeCallable for Constructor { fn signature(&self) -> Option { self.signature.clone() } fn arity(&self) -> Option { Some(endor_core::CallArity::range(1, usize::MAX)) } fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { let [Value::Object(definition), values @ ..] = args else { return Err(VmError::InvalidArgument("ObjectList type argument")); }; let fields = cx .store() .payload::(*definition, "Type")? .fields() .to_vec(); if fields .iter() .any(|field| matches!(field.name.as_str(), "count" | "first" | "at")) { return Err(VmError::InvalidArgument("reserved ObjectList field")); } let count = values .len() .checked_sub(fields.len()) .ok_or(VmError::InvalidArgument( "missing ObjectList subclass fields", ))?; cx.store() .check_class_field_arguments(*definition, &values[count..])?; let members = values[..count] .iter() .map(|value| { if let Value::Object(object) = value { Ok(*object) } else { Err(VmError::InvalidArgument( "ObjectList members must be objects", )) } }) .collect::, _>>()?; let list = build(cx, members, self.desktop, Some(*definition))?; for (field, value) in fields.iter().zip(&values[count..]) { cx.set_attr(list, &field.name, value.clone())?; } Ok(NativeResult::Return(Value::Object(list))) } }