use std::ffi::{c_int, c_void, CStr};
use std::ptr;
use std::sync::Mutex;
use super::abi::{TclObj, TclObjInternalRep, TclObjType, TCL_ERROR, TCL_OK};
use super::obj;
const OBJTYPE_V0: usize = 0;
pub struct HostList {
pub elems: Vec<*mut TclObj>,
}
pub struct HostDict {
pub pairs: Vec<(*mut TclObj, *mut TclObj)>,
}
pub static LIST_TYPE: TclObjType = TclObjType {
name: c"list".as_ptr(),
free_internal_rep_proc: free_list_rep as *const c_void,
dup_internal_rep_proc: dup_list_rep as *const c_void,
update_string_proc: update_string_of_list as *const c_void,
set_from_any_proc: set_list_from_any as *const c_void,
version: OBJTYPE_V0,
length_proc: ptr::null(),
index_proc: ptr::null(),
slice_proc: ptr::null(),
reverse_proc: ptr::null(),
get_elements_proc: ptr::null(),
set_element_proc: ptr::null(),
replace_proc: ptr::null(),
in_oper_proc: ptr::null(),
};
pub static DICT_TYPE: TclObjType = TclObjType {
name: c"dict".as_ptr(),
free_internal_rep_proc: free_dict_rep as *const c_void,
dup_internal_rep_proc: dup_dict_rep as *const c_void,
update_string_proc: update_string_of_dict as *const c_void,
set_from_any_proc: set_dict_from_any as *const c_void,
version: OBJTYPE_V0,
length_proc: ptr::null(),
index_proc: ptr::null(),
slice_proc: ptr::null(),
reverse_proc: ptr::null(),
get_elements_proc: ptr::null(),
set_element_proc: ptr::null(),
replace_proc: ptr::null(),
in_oper_proc: ptr::null(),
};
pub static WIDE_TYPE: TclObjType = TclObjType {
name: c"int".as_ptr(),
free_internal_rep_proc: ptr::null(),
dup_internal_rep_proc: ptr::null(),
update_string_proc: update_string_of_wide as *const c_void,
set_from_any_proc: set_wide_from_any as *const c_void,
version: OBJTYPE_V0,
length_proc: ptr::null(),
index_proc: ptr::null(),
slice_proc: ptr::null(),
reverse_proc: ptr::null(),
get_elements_proc: ptr::null(),
set_element_proc: ptr::null(),
replace_proc: ptr::null(),
in_oper_proc: ptr::null(),
};
pub static DOUBLE_TYPE: TclObjType = TclObjType {
name: c"double".as_ptr(),
free_internal_rep_proc: ptr::null(),
dup_internal_rep_proc: ptr::null(),
update_string_proc: update_string_of_double as *const c_void,
set_from_any_proc: set_double_from_any as *const c_void,
version: OBJTYPE_V0,
length_proc: ptr::null(),
index_proc: ptr::null(),
slice_proc: ptr::null(),
reverse_proc: ptr::null(),
get_elements_proc: ptr::null(),
set_element_proc: ptr::null(),
replace_proc: ptr::null(),
in_oper_proc: ptr::null(),
};
pub static BOOLEAN_TYPE: TclObjType = TclObjType {
name: c"boolean".as_ptr(),
free_internal_rep_proc: ptr::null(),
dup_internal_rep_proc: ptr::null(),
update_string_proc: ptr::null(),
set_from_any_proc: set_boolean_from_any as *const c_void,
version: OBJTYPE_V0,
length_proc: ptr::null(),
index_proc: ptr::null(),
slice_proc: ptr::null(),
reverse_proc: ptr::null(),
get_elements_proc: ptr::null(),
set_element_proc: ptr::null(),
replace_proc: ptr::null(),
in_oper_proc: ptr::null(),
};
pub static HOST_TYPES: [&TclObjType; 5] = [
&LIST_TYPE,
&DICT_TYPE,
&WIDE_TYPE,
&DOUBLE_TYPE,
&BOOLEAN_TYPE,
];
#[derive(Clone, Copy)]
struct Registered(*const TclObjType);
unsafe impl Send for Registered {}
static TYPES: Mutex<Vec<(String, Registered)>> = Mutex::new(Vec::new());
pub unsafe fn register(ty: *const TclObjType) {
let name = name_of(ty);
let mut table = TYPES.lock().expect("type registry poisoned");
match table.iter_mut().find(|(n, _)| *n == name) {
Some(slot) => slot.1 = Registered(ty),
None => table.push((name, Registered(ty))),
}
}
pub fn register_host_types() {
for ty in HOST_TYPES {
unsafe { register(ty as *const TclObjType) };
}
}
pub fn lookup(name: &str) -> *const TclObjType {
TYPES
.lock()
.expect("type registry poisoned")
.iter()
.find(|(n, _)| n == name)
.map(|(_, t)| t.0)
.unwrap_or(ptr::null())
}
pub fn registered_names() -> Vec<String> {
TYPES
.lock()
.expect("type registry poisoned")
.iter()
.map(|(n, _)| n.clone())
.collect()
}
pub fn registered_count() -> usize {
TYPES.lock().expect("type registry poisoned").len()
}
pub fn registered_types() -> Vec<*const TclObjType> {
TYPES
.lock()
.expect("type registry poisoned")
.iter()
.map(|(_, t)| t.0)
.collect()
}
pub unsafe fn name_of(ty: *const TclObjType) -> String {
if ty.is_null() || (*ty).name.is_null() {
return "<none>".to_string();
}
String::from_utf8_lossy(CStr::from_ptr((*ty).name).to_bytes()).into_owned()
}
pub unsafe fn free_internal_rep(obj: *mut TclObj) {
let ty = (*obj).type_ptr;
if ty.is_null() {
return;
}
if !(*ty).free_internal_rep_proc.is_null() {
let f: unsafe extern "C" fn(*mut TclObj) =
std::mem::transmute((*ty).free_internal_rep_proc);
f(obj);
}
(*obj).type_ptr = ptr::null();
(*obj).internal_rep.ptr1 = ptr::null_mut();
(*obj).internal_rep.ptr2 = ptr::null_mut();
}
pub unsafe fn dup_internal_rep(src: *mut TclObj, dup: *mut TclObj) {
let ty = (*src).type_ptr;
if ty.is_null() {
return;
}
if (*ty).dup_internal_rep_proc.is_null() {
(*dup).internal_rep = (*src).internal_rep;
(*dup).type_ptr = ty;
return;
}
let f: unsafe extern "C" fn(*mut TclObj, *mut TclObj) =
std::mem::transmute((*ty).dup_internal_rep_proc);
f(src, dup);
}
pub unsafe fn call_update_string(obj: *mut TclObj) {
let ty = (*obj).type_ptr;
let f: unsafe extern "C" fn(*mut TclObj) = std::mem::transmute((*ty).update_string_proc);
f(obj);
}
pub unsafe fn convert_to_type(
interp: *mut c_void,
obj: *mut TclObj,
ty: *const TclObjType,
) -> c_int {
if std::ptr::eq((*obj).type_ptr, ty) {
return TCL_OK;
}
if (*ty).set_from_any_proc.is_null() {
return TCL_ERROR;
}
let f: unsafe extern "C" fn(*mut c_void, *mut TclObj) -> c_int =
std::mem::transmute((*ty).set_from_any_proc);
f(interp, obj)
}
pub unsafe fn store_internal_rep(
obj: *mut TclObj,
ty: *const TclObjType,
ir: *const TclObjInternalRep,
) {
free_internal_rep(obj);
if !ir.is_null() {
(*obj).internal_rep = *ir;
(*obj).type_ptr = ty;
}
}
pub unsafe fn fetch_internal_rep(
obj: *mut TclObj,
ty: *const TclObjType,
) -> *mut TclObjInternalRep {
if std::ptr::eq((*obj).type_ptr, ty) {
ptr::addr_of_mut!((*obj).internal_rep)
} else {
ptr::null_mut()
}
}
pub fn is_list(ty: *const TclObjType) -> bool {
std::ptr::eq(ty, &LIST_TYPE)
}
pub fn is_dict(ty: *const TclObjType) -> bool {
std::ptr::eq(ty, &DICT_TYPE)
}
pub fn is_wide(ty: *const TclObjType) -> bool {
std::ptr::eq(ty, &WIDE_TYPE)
}
pub fn is_double(ty: *const TclObjType) -> bool {
std::ptr::eq(ty, &DOUBLE_TYPE)
}
pub fn is_boolean(ty: *const TclObjType) -> bool {
std::ptr::eq(ty, &BOOLEAN_TYPE)
}
pub unsafe fn wide_bits(obj: *mut TclObj) -> i64 {
(*obj).internal_rep.ptr1 as i64
}
unsafe fn set_wide_bits(obj: *mut TclObj, v: i64) {
(*obj).internal_rep.ptr1 = v as *mut c_void;
(*obj).internal_rep.ptr2 = ptr::null_mut();
}
pub unsafe fn double_bits(obj: *mut TclObj) -> f64 {
f64::from_bits((*obj).internal_rep.ptr1 as u64)
}
unsafe fn set_double_bits(obj: *mut TclObj, v: f64) {
(*obj).internal_rep.ptr1 = v.to_bits() as *mut c_void;
(*obj).internal_rep.ptr2 = ptr::null_mut();
}
pub unsafe fn new_list(elems: &[*mut TclObj]) -> *mut TclObj {
let o = obj::alloc();
for e in elems {
obj::incr_ref(*e);
}
(*o).type_ptr = &LIST_TYPE;
(*o).internal_rep.ptr1 = Box::into_raw(Box::new(HostList {
elems: elems.to_vec(),
})) as *mut c_void;
(*o).internal_rep.ptr2 = ptr::null_mut();
obj::invalidate_string_rep(o);
o
}
pub unsafe fn new_dict(pairs: &[(*mut TclObj, *mut TclObj)]) -> *mut TclObj {
let o = obj::alloc();
for (k, v) in pairs {
obj::incr_ref(*k);
obj::incr_ref(*v);
}
(*o).type_ptr = &DICT_TYPE;
(*o).internal_rep.ptr1 = Box::into_raw(Box::new(HostDict {
pairs: pairs.to_vec(),
})) as *mut c_void;
(*o).internal_rep.ptr2 = ptr::null_mut();
obj::invalidate_string_rep(o);
o
}
pub unsafe fn new_wide(v: i64) -> *mut TclObj {
let o = obj::new_string(v.to_string().as_bytes());
(*o).type_ptr = &WIDE_TYPE;
set_wide_bits(o, v);
o
}
pub unsafe fn new_double(v: f64) -> *mut TclObj {
let o = obj::new_string(crate::runtime::format_double(v).as_bytes());
(*o).type_ptr = &DOUBLE_TYPE;
set_double_bits(o, v);
o
}
pub unsafe fn new_boolean(v: bool) -> *mut TclObj {
let o = obj::new_string(if v { b"1" } else { b"0" });
(*o).type_ptr = &BOOLEAN_TYPE;
set_wide_bits(o, i64::from(v));
o
}
pub unsafe fn list_of(obj: *mut TclObj) -> &'static mut HostList {
if !is_list((*obj).type_ptr) {
let text = obj::text_of(obj);
let words = crate::list::split(&text)
.unwrap_or_else(|e| panic!("value is not a well formed Tcl list: {e}"));
let elems: Vec<*mut TclObj> = words
.iter()
.map(|w| {
let e = obj::new_string(w.as_bytes());
obj::incr_ref(e);
e
})
.collect();
free_internal_rep(obj);
(*obj).type_ptr = &LIST_TYPE;
(*obj).internal_rep.ptr1 = Box::into_raw(Box::new(HostList { elems })) as *mut c_void;
}
&mut *((*obj).internal_rep.ptr1 as *mut HostList)
}
pub unsafe fn dict_of(obj: *mut TclObj) -> &'static mut HostDict {
if !is_dict((*obj).type_ptr) {
let text = obj::text_of(obj);
let words = crate::list::split(&text)
.unwrap_or_else(|e| panic!("value is not a well formed Tcl dictionary: {e}"));
assert!(
words.len().is_multiple_of(2),
"missing value to go with key: a dictionary needs an even number of \
elements, and this one has {}",
words.len()
);
let pairs: Vec<(*mut TclObj, *mut TclObj)> = words
.chunks(2)
.map(|kv| {
let k = obj::new_string(kv[0].as_bytes());
let v = obj::new_string(kv[1].as_bytes());
obj::incr_ref(k);
obj::incr_ref(v);
(k, v)
})
.collect();
free_internal_rep(obj);
(*obj).type_ptr = &DICT_TYPE;
(*obj).internal_rep.ptr1 = Box::into_raw(Box::new(HostDict { pairs })) as *mut c_void;
}
&mut *((*obj).internal_rep.ptr1 as *mut HostDict)
}
pub unsafe fn invalidate(obj: *mut TclObj) {
obj::invalidate_string_rep(obj);
}
unsafe extern "C" fn free_list_rep(o: *mut TclObj) {
let rep = (*o).internal_rep.ptr1 as *mut HostList;
if rep.is_null() {
return;
}
let list = Box::from_raw(rep);
for e in &list.elems {
obj::decr_ref(*e);
}
drop(list);
(*o).internal_rep.ptr1 = ptr::null_mut();
}
unsafe extern "C" fn dup_list_rep(src: *mut TclObj, dup: *mut TclObj) {
let rep = &*((*src).internal_rep.ptr1 as *mut HostList);
for e in &rep.elems {
obj::incr_ref(*e);
}
(*dup).type_ptr = (*src).type_ptr;
(*dup).internal_rep.ptr1 = Box::into_raw(Box::new(HostList {
elems: rep.elems.clone(),
})) as *mut c_void;
(*dup).internal_rep.ptr2 = ptr::null_mut();
}
unsafe extern "C" fn update_string_of_list(o: *mut TclObj) {
let rep = &*((*o).internal_rep.ptr1 as *mut HostList);
let words: Vec<String> = rep.elems.iter().map(|e| obj::text_of(*e)).collect();
obj::set_string(o, crate::list::join(&words).as_bytes());
}
unsafe extern "C" fn set_list_from_any(_interp: *mut c_void, o: *mut TclObj) -> c_int {
if is_list((*o).type_ptr) {
return TCL_OK;
}
let text = obj::text_of(o);
match crate::list::split(&text) {
Ok(words) => {
let elems: Vec<*mut TclObj> = words
.iter()
.map(|w| {
let e = obj::new_string(w.as_bytes());
obj::incr_ref(e);
e
})
.collect();
free_internal_rep(o);
(*o).type_ptr = &LIST_TYPE;
(*o).internal_rep.ptr1 = Box::into_raw(Box::new(HostList { elems })) as *mut c_void;
TCL_OK
}
Err(_) => TCL_ERROR,
}
}
unsafe extern "C" fn free_dict_rep(o: *mut TclObj) {
let rep = (*o).internal_rep.ptr1 as *mut HostDict;
if rep.is_null() {
return;
}
let dict = Box::from_raw(rep);
for (k, v) in &dict.pairs {
obj::decr_ref(*k);
obj::decr_ref(*v);
}
drop(dict);
(*o).internal_rep.ptr1 = ptr::null_mut();
}
unsafe extern "C" fn dup_dict_rep(src: *mut TclObj, dup: *mut TclObj) {
let rep = &*((*src).internal_rep.ptr1 as *mut HostDict);
for (k, v) in &rep.pairs {
obj::incr_ref(*k);
obj::incr_ref(*v);
}
(*dup).type_ptr = (*src).type_ptr;
(*dup).internal_rep.ptr1 = Box::into_raw(Box::new(HostDict {
pairs: rep.pairs.clone(),
})) as *mut c_void;
(*dup).internal_rep.ptr2 = ptr::null_mut();
}
unsafe extern "C" fn update_string_of_dict(o: *mut TclObj) {
let rep = &*((*o).internal_rep.ptr1 as *mut HostDict);
let mut words: Vec<String> = Vec::with_capacity(rep.pairs.len() * 2);
for (k, v) in &rep.pairs {
words.push(obj::text_of(*k));
words.push(obj::text_of(*v));
}
obj::set_string(o, crate::list::join(&words).as_bytes());
}
unsafe extern "C" fn set_dict_from_any(_interp: *mut c_void, o: *mut TclObj) -> c_int {
if is_dict((*o).type_ptr) {
return TCL_OK;
}
let text = obj::text_of(o);
let Ok(words) = crate::list::split(&text) else {
return TCL_ERROR;
};
if !words.len().is_multiple_of(2) {
return TCL_ERROR;
}
let pairs: Vec<(*mut TclObj, *mut TclObj)> = words
.chunks(2)
.map(|kv| {
let k = obj::new_string(kv[0].as_bytes());
let v = obj::new_string(kv[1].as_bytes());
obj::incr_ref(k);
obj::incr_ref(v);
(k, v)
})
.collect();
free_internal_rep(o);
(*o).type_ptr = &DICT_TYPE;
(*o).internal_rep.ptr1 = Box::into_raw(Box::new(HostDict { pairs })) as *mut c_void;
TCL_OK
}
unsafe extern "C" fn update_string_of_wide(o: *mut TclObj) {
let v = wide_bits(o);
obj::set_string(o, v.to_string().as_bytes());
}
unsafe extern "C" fn set_wide_from_any(_interp: *mut c_void, o: *mut TclObj) -> c_int {
if is_wide((*o).type_ptr) {
return TCL_OK;
}
let text = obj::text_of(o);
match crate::list::wide(&text) {
Ok(v) => {
free_internal_rep(o);
(*o).type_ptr = &WIDE_TYPE;
set_wide_bits(o, v);
TCL_OK
}
Err(_) => TCL_ERROR,
}
}
unsafe extern "C" fn update_string_of_double(o: *mut TclObj) {
let v = double_bits(o);
obj::set_string(o, crate::runtime::format_double(v).as_bytes());
}
unsafe extern "C" fn set_double_from_any(_interp: *mut c_void, o: *mut TclObj) -> c_int {
if is_double((*o).type_ptr) {
return TCL_OK;
}
let text = obj::text_of(o);
match crate::list::parse_double(&text) {
Some(v) => {
free_internal_rep(o);
(*o).type_ptr = &DOUBLE_TYPE;
set_double_bits(o, v);
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn set_boolean_from_any(_interp: *mut c_void, o: *mut TclObj) -> c_int {
if is_boolean((*o).type_ptr) {
return TCL_OK;
}
let value = fusevm::Value::Str(std::sync::Arc::new(obj::text_of(o)));
match crate::runtime::tcl_bool(&value) {
Ok(b) => {
free_internal_rep(o);
(*o).type_ptr = &BOOLEAN_TYPE;
set_wide_bits(o, i64::from(b));
TCL_OK
}
Err(_) => TCL_ERROR,
}
}
const MM_REP_SIZE: usize = 32;
const MM_REP_UNITS: usize = 8;
const WINDOW_REP_SIZE: usize = 24;
fn seed_name(name: &str) -> &'static str {
match name {
"mm" => "MMRep{units=-1}",
"window" => "WindowRep{zeroed}",
_ => "NULL",
}
}
pub unsafe fn exercise(ty: *const TclObjType) -> String {
let name = name_of(ty);
let mut did: Vec<&str> = Vec::new();
if name == "textindex" {
return format!(
"{name} version={} free={} dup={} update={} setany={} exercised=none \
(FreeTextIndexInternalRep dereferences its rep unconditionally, \
tkTextIndex.c:93)",
(*ty).version,
u8::from(!(*ty).free_internal_rep_proc.is_null()),
u8::from(!(*ty).dup_internal_rep_proc.is_null()),
u8::from(!(*ty).update_string_proc.is_null()),
u8::from(!(*ty).set_from_any_proc.is_null()),
);
}
let probe = obj::new_string(b"0");
(*probe).type_ptr = ty;
(*probe).internal_rep.ptr1 = ptr::null_mut();
(*probe).internal_rep.ptr2 = ptr::null_mut();
if name == "mm" {
let rep = libc::calloc(1, MM_REP_SIZE) as *mut u8;
assert!(!rep.is_null(), "out of memory seeding an MMRep");
ptr::write_unaligned(rep as *mut f64, 42.0);
ptr::write_unaligned(rep.add(MM_REP_UNITS) as *mut c_int, -1);
(*probe).internal_rep.ptr1 = rep as *mut c_void;
} else if name == "window" {
let rep = libc::calloc(1, WINDOW_REP_SIZE);
assert!(!rep.is_null(), "out of memory seeding a WindowRep");
(*probe).internal_rep.ptr1 = rep;
}
if !(*ty).dup_internal_rep_proc.is_null() {
let dup = obj::alloc();
dup_internal_rep(probe, dup);
assert!(
std::ptr::eq((*dup).type_ptr, ty),
"{name}'s dupIntRepProc did not set the duplicate's typePtr"
);
free_internal_rep(dup);
obj::free_obj(dup);
did.push("dup");
}
let mut produced = String::new();
if !(*ty).update_string_proc.is_null() {
obj::invalidate_string_rep(probe);
produced = obj::text_of(probe);
assert!(
!produced.is_empty(),
"{name}'s updateStringProc produced an empty string rep"
);
did.push("updateString");
}
if !(*ty).free_internal_rep_proc.is_null() {
free_internal_rep(probe);
assert!(
(*probe).type_ptr.is_null(),
"{name}'s freeIntRepProc left the type in place"
);
did.push("free");
}
obj::free_obj(probe);
let done = if did.is_empty() {
"none (all four procs are NULL)".to_string()
} else {
did.join("+")
};
let string_rep = if produced.is_empty() {
String::new()
} else {
format!(" string={produced:?}")
};
format!(
"{name} version={} free={} dup={} update={} setany={} seed={} exercised={done}{string_rep}",
(*ty).version,
u8::from(!(*ty).free_internal_rep_proc.is_null()),
u8::from(!(*ty).dup_internal_rep_proc.is_null()),
u8::from(!(*ty).update_string_proc.is_null()),
u8::from(!(*ty).set_from_any_proc.is_null()),
seed_name(&name),
)
}
pub unsafe fn double_of(obj: *mut TclObj) -> Option<f64> {
let ty = (*obj).type_ptr;
if is_double(ty) {
return Some(double_bits(obj));
}
if is_wide(ty) || is_boolean(ty) {
return Some(wide_bits(obj) as f64);
}
if set_double_from_any(ptr::null_mut(), obj) == TCL_OK {
return Some(double_bits(obj));
}
None
}
pub unsafe fn wide_of(obj: *mut TclObj) -> Option<i64> {
let ty = (*obj).type_ptr;
if is_wide(ty) || is_boolean(ty) {
return Some(wide_bits(obj));
}
if set_wide_from_any(ptr::null_mut(), obj) == TCL_OK {
return Some(wide_bits(obj));
}
None
}
pub unsafe fn bool_of(obj: *mut TclObj) -> Option<bool> {
let ty = (*obj).type_ptr;
if is_boolean(ty) || is_wide(ty) {
return Some(wide_bits(obj) != 0);
}
if is_double(ty) {
return Some(double_bits(obj) != 0.0);
}
if set_boolean_from_any(ptr::null_mut(), obj) == TCL_OK {
return Some(wide_bits(obj) != 0);
}
None
}