use std::any::Any;
use std::ffi::{c_char, c_void};
use std::os::raw::c_int;
use std::ptr::{self};
use std::rc::Rc;
use std::sync::OnceLock;
use jimtcl_sys::Jim_ObjType;
use crate::object::IntoJimObj;
use crate::{JimError, sys};
use crate::{JimObject, JimResult};
static JIM_EXTENSION_TYPE_NAME: &[u8] = b"jimtcl-rs-extension\0";
static JIM_EXTENSION_TYPE: OnceLock<JimTypeWrapper> = OnceLock::new();
struct JimTypeWrapper {
type_obj: *const Jim_ObjType,
}
unsafe impl Sync for JimTypeWrapper {}
unsafe impl Send for JimTypeWrapper {}
pub fn jim_extension_type_ptr() -> *const sys::Jim_ObjType {
let wrapper = JIM_EXTENSION_TYPE.get_or_init(|| {
let obj = sys::Jim_ObjType {
name: JIM_EXTENSION_TYPE_NAME.as_ptr() as *const c_char,
freeIntRepProc: Some(rust_jim_free_rep),
dupIntRepProc: Some(rust_jim_dup_rep),
updateStringProc: Some(rust_jim_update_string),
flags: 0,
};
let ptr = Box::leak(Box::new(obj));
JimTypeWrapper { type_obj: ptr }
});
wrapper.type_obj
}
struct JimExtensionWrapper {
object: Rc<dyn JimExtensionData>,
}
pub trait JimExtensionData: Any {
fn to_tcl_string(&self) -> String;
}
pub trait JimExtensionType: JimExtensionData + Sized {
fn parse_tcl_string(repr: &str) -> JimResult<Self>;
fn extract<'jim, 'o: 'jim>(obj: &'o JimObject<'jim>) -> JimResult<Rc<Self>> {
let raw = obj.obj;
unsafe {
if (*raw).typePtr != jim_extension_type_ptr() {
let data = Self::parse_tcl_string(obj.as_str()?)?;
let _ = JimExtensionWrapper::inject(data, obj)?;
}
assert_eq!((*raw).typePtr, jim_extension_type_ptr());
let pointer = (*obj.obj).internalRep.ptr as *const JimExtensionWrapper;
if let Some(data) = pointer.as_ref() {
let ptr: Rc<dyn Any> = data.object.clone();
ptr.downcast::<Self>()
.map_err(|_| JimError::from("object has unexpected Rust type"))
} else {
Err(JimError::from("object is null (already freed?)"))
}
}
}
fn extract_ref<'jim, 'o: 'jim>(obj: &'o JimObject<'jim>) -> JimResult<&'o Self> {
let raw = obj.obj;
unsafe {
if (*raw).typePtr == jim_extension_type_ptr() {
let pointer = (*obj.obj).internalRep.ptr as *const JimExtensionWrapper;
if let Some(data) = pointer.as_ref() {
<dyn Any>::downcast_ref::<Self>(data.object.as_ref())
.ok_or_else(|| JimError::from("object has unexpected Rust type"))
} else {
Err(JimError::from("object is null (already freed?)"))
}
} else {
let data = Self::parse_tcl_string(obj.as_str()?)?;
let data = JimExtensionWrapper::inject(data, obj)?;
Ok(data)
}
}
}
}
impl JimExtensionWrapper {
fn inject<'jim, 'o: 'jim, T: JimExtensionType>(
data: T,
obj: &'o JimObject<'jim>,
) -> JimResult<&'o T> {
let wrapper = JimExtensionWrapper {
object: Rc::new(data),
};
let wrapper = Box::leak(Box::new(wrapper));
obj.free_internal();
unsafe {
(*obj.obj).typePtr = jim_extension_type_ptr();
(*obj.obj).internalRep.ptr = ptr::from_mut(wrapper).cast();
}
obj.invalidate_string_rep();
Ok(<dyn Any>::downcast_ref::<T>(wrapper.object.as_ref()).unwrap())
}
}
impl Clone for JimExtensionWrapper {
fn clone(&self) -> Self {
JimExtensionWrapper {
object: self.object.clone(),
}
}
}
impl<T: JimExtensionType + 'static> IntoJimObj for T {
fn to_jim<'jim>(self, interp: &'jim crate::Interp) -> JimObject<'jim> {
Rc::new(self).to_jim(interp)
}
}
impl<T: JimExtensionType + 'static> IntoJimObj for Rc<T> {
fn to_jim<'jim>(self, interp: &'jim crate::Interp) -> JimObject<'jim> {
let wrapper = JimExtensionWrapper { object: self };
let obj = unsafe {
let obj = sys::Jim_NewObj(interp.interp);
let o = obj.as_mut_unchecked();
o.bytes = ptr::null_mut();
o.length = 0;
o.typePtr = jim_extension_type_ptr();
o.internalRep.ptr = ptr::from_mut(Box::leak(Box::new(wrapper))) as *mut c_void;
obj
};
JimObject::wrap(interp, obj)
}
}
unsafe extern "C" fn rust_jim_free_rep(_interp: *mut sys::Jim_Interp, data: *mut sys::Jim_Obj) {
let pointer = unsafe { (*data).internalRep.ptr as *mut JimExtensionWrapper };
if !pointer.is_null() {
let _ = unsafe { Box::from_raw(pointer) };
unsafe {
(*data).internalRep.ptr = ptr::null_mut();
}
}
}
unsafe extern "C" fn rust_jim_dup_rep(
_interp: *mut sys::Jim_Interp,
src: *mut sys::Jim_Obj,
dst: *mut sys::Jim_Obj,
) {
unsafe {
let pointer = (*src).internalRep.ptr as *const JimExtensionWrapper;
if let Some(obj) = pointer.as_ref() {
let dup = Box::new(obj.clone());
(*dst).internalRep.ptr = ptr::from_mut(Box::leak(dup)) as *mut c_void;
} else {
(*dst).internalRep.ptr = ptr::null_mut();
}
(*dst).typePtr = jim_extension_type_ptr();
}
}
unsafe extern "C" fn rust_jim_update_string(obj: *mut sys::Jim_Obj) {
unsafe {
let pointer = (*obj).internalRep.ptr as *const JimExtensionWrapper;
if let Some(data) = pointer.as_ref() {
let str = data.object.to_tcl_string();
let len = str.len() as c_int;
(*obj).bytes = sys::Jim_StrDupLen(str.as_ptr() as *const i8, len);
(*obj).length = len;
} else {
(*obj).bytes = sys::Jim_StrDupLen("".as_ptr() as *const i8, 0);
(*obj).length = 0;
}
};
}