use std::ffi::{c_char, c_int, c_void, CStr};
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
use super::abi::*;
use super::generated::*;
use super::trace::{note, record, Table};
use super::{dstring, obj, objtype};
#[repr(C)]
pub struct HostInterp {
pub prefix: InterpPrefix,
pub host: *mut Host,
}
static CURRENT: AtomicPtr<Host> = AtomicPtr::new(ptr::null_mut());
static PRIMARY_INTERP: AtomicPtr<HostInterp> = AtomicPtr::new(ptr::null_mut());
pub fn primary_interp() -> *mut HostInterp {
PRIMARY_INTERP.load(Ordering::Relaxed)
}
pub struct Tables {
pub tcl: Box<TclStubs>,
pub tcl_int: Box<TclIntStubs>,
pub tcl_plat: Box<TclPlatStubs>,
pub tcl_int_plat: Box<TclIntPlatStubs>,
pub hooks: Box<TclStubHooks>,
}
static TABLES: AtomicPtr<Tables> = AtomicPtr::new(ptr::null_mut());
pub struct Host {
pub thread_data: Vec<(usize, *mut c_void)>,
pub result: *mut TclObj,
pub commands: Vec<Box<HostCommand>>,
pub vars: Vec<(String, String, *mut TclObj)>,
pub assoc_data: Vec<(String, *mut c_void, *mut c_void)>,
pub namespaces: Vec<(String, *mut TclNamespace)>,
pub exports: Vec<(usize, Vec<String>)>,
pub linked_vars: Vec<(String, c_int)>,
pub exit_handlers: Vec<(*mut c_void, *mut c_void)>,
pub error_info: Option<String>,
pub background_errors: Vec<(c_int, String, String)>,
pub deleted: bool,
}
pub struct HostCommand {
pub name: String,
pub proc_: *mut c_void,
pub client_data: *mut c_void,
pub delete_proc: *mut c_void,
pub proc2: bool,
pub ensemble_map: *mut TclObj,
pub dying: bool,
}
unsafe fn host() -> &'static mut Host {
let p = CURRENT.load(Ordering::Relaxed);
assert!(!p.is_null(), "no host interpreter installed");
&mut *p
}
fn slot(names: &[&str], name: &str) -> usize {
names
.iter()
.position(|n| *n == name)
.unwrap_or_else(|| panic!("no slot named {name} in this table"))
}
unsafe fn install(t: &mut TclStubs, name: &str, f: *const ()) -> usize {
let i = slot(&TCL_NAMES, name);
t.slots[i] = std::mem::transmute::<*const (), RawStub>(f);
i
}
pub fn degraded() -> bool {
std::env::var_os("TCLRS_TK_DEGRADED").is_some()
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Level {
Probe,
Hosting,
}
pub fn build() -> *mut HostInterp {
build_at(Level::Probe)
}
pub fn build_hosting() -> *mut HostInterp {
build_at(Level::Hosting)
}
fn build_at(level: Level) -> *mut HostInterp {
let mut tcl = Box::new(TclStubs {
magic: TCL_STUB_MAGIC,
hooks: ptr::null(),
slots: TCL_TRAPS,
});
let tcl_int = Box::new(TclIntStubs {
magic: TCL_STUB_MAGIC,
hooks: ptr::null(),
slots: TCL_INT_TRAPS,
});
let mut tcl_plat = Box::new(TclPlatStubs {
magic: TCL_STUB_MAGIC,
hooks: ptr::null(),
slots: TCL_PLAT_TRAPS,
});
unsafe { super::notifier::install_plat(&mut tcl_plat) };
let tcl_int_plat = Box::new(TclIntPlatStubs {
magic: TCL_STUB_MAGIC,
hooks: ptr::null(),
slots: TCL_INT_PLAT_TRAPS,
});
unsafe { install_impls(&mut tcl, degraded(), level) };
let hooks = Box::new(TclStubHooks {
tcl_plat_stubs: &*tcl_plat,
tcl_int_stubs: &*tcl_int,
tcl_int_plat_stubs: &*tcl_int_plat,
});
tcl.hooks = &*hooks;
let tables = Box::into_raw(Box::new(Tables {
tcl,
tcl_int,
tcl_plat,
tcl_int_plat,
hooks,
}));
TABLES.store(tables, Ordering::Relaxed);
let host = Box::into_raw(Box::new(empty_host()));
CURRENT.store(host, Ordering::Relaxed);
objtype::register_host_types();
let interp = unsafe { wrap_interp(host) };
PRIMARY_INTERP.store(interp, Ordering::Relaxed);
super::interp::shared_for(host);
interp
}
unsafe fn wrap_interp(host: *mut Host) -> *mut HostInterp {
Box::into_raw(Box::new(HostInterp {
prefix: InterpPrefix {
legacy_result: ptr::null(),
legacy_free_proc: ptr::null(),
error_line: 0,
_pad: 0,
stub_table: &*(*TABLES.load(Ordering::Relaxed)).tcl,
},
host,
}))
}
fn empty_host() -> Host {
Host {
thread_data: Vec::new(),
result: ptr::null_mut(),
commands: Vec::new(),
vars: Vec::new(),
assoc_data: Vec::new(),
namespaces: Vec::new(),
exports: Vec::new(),
linked_vars: Vec::new(),
exit_handlers: Vec::new(),
error_info: None,
background_errors: Vec::new(),
deleted: false,
}
}
pub fn implemented() -> Vec<(usize, &'static str)> {
implemented_at(Level::Probe)
}
pub fn implemented_at(level: Level) -> Vec<(usize, &'static str)> {
let mut scratch = TclStubs {
magic: TCL_STUB_MAGIC,
hooks: ptr::null(),
slots: TCL_TRAPS,
};
let mut seen = Vec::new();
let mut out = Vec::new();
unsafe {
for i in install_impls(&mut scratch, degraded(), level) {
if seen.contains(&i) {
continue;
}
seen.push(i);
out.push((i, TCL_NAMES[i]));
}
}
out
}
unsafe fn retain(obj: *mut TclObj) -> *mut TclObj {
assert!(
obj::is_host_allocated(obj),
"a Tcl_Obj at {obj:?} that this side never allocated is being stored \
past the call that passed it; Tk's stack objects may not be retained"
);
obj::incr_ref(obj);
obj
}
unsafe fn c_bytes(p: *const c_char, len: isize) -> &'static [u8] {
if p.is_null() {
return &[];
}
if len < 0 {
CStr::from_ptr(p).to_bytes()
} else {
std::slice::from_raw_parts(p as *const u8, len as usize)
}
}
pub fn slot_index(name: &str) -> usize {
slot(&TCL_NAMES, name)
}
pub unsafe fn c_bytes_of(p: *const c_char, len: isize) -> &'static [u8] {
c_bytes(p, len)
}
pub unsafe fn obj_bytes_of(obj: *mut TclObj) -> &'static [u8] {
obj::string_of(obj)
}
pub unsafe fn retained_obj(bytes: &[u8]) -> *mut TclObj {
let obj = obj::new_string(bytes);
obj::incr_ref(obj);
obj
}
pub unsafe fn release_obj(obj: *mut TclObj) {
obj::release(obj);
}
pub unsafe fn append_bytes_to_obj(obj: *mut TclObj, bytes: &[u8]) {
obj::append_bytes(obj, bytes);
}
pub unsafe fn set_result_bytes(interp: *mut c_void, bytes: &[u8]) {
install_result(interp, obj::new_string(bytes));
}
pub unsafe fn result_bytes(interp: *mut c_void) -> Vec<u8> {
let h = &mut *(*(interp as *mut HostInterp)).host;
if h.result.is_null() {
return Vec::new();
}
obj::string_of(h.result).to_vec()
}
unsafe fn install_impls(t: &mut TclStubs, degraded: bool, level: Level) -> Vec<usize> {
let mut slots = vec![
install(t, "tcl_PkgRequireEx", pkg_require_ex as *const ()),
install(t, "tcl_Alloc", tcl_alloc as *const ()),
install(t, "tcl_Free", tcl_free as *const ()),
install(t, "tcl_Realloc", tcl_realloc as *const ()),
install(t, "tclFreeObj", tcl_free_obj as *const ()),
install(
t,
"tcl_ListObjAppendElement",
list_obj_append_element as *const (),
),
install(t, "tcl_ListObjIndex", list_obj_index as *const ()),
install(t, "tcl_NewListObj", new_list_obj as *const ()),
install(t, "tcl_NewObj", new_empty_obj as *const ()),
install(t, "tcl_NewStringObj", new_string_obj as *const ()),
install(t, "tcl_SetObjLength", set_obj_length as *const ()),
install(t, "tcl_CreateObjCommand", create_obj_command as *const ()),
install(t, "tcl_CreateObjCommand2", create_obj_command2 as *const ()),
install(t, "tcl_CreateInterp", create_interp as *const ()),
install(t, "tcl_DeleteInterp", delete_interp as *const ()),
install(t, "tcl_InterpDeleted", interp_deleted as *const ()),
install(t, "tcl_DeleteHashEntry", delete_hash_entry as *const ()),
install(t, "tcl_DeleteHashTable", delete_hash_table as *const ()),
install(t, "tcl_DStringAppend", dstring_append as *const ()),
install(t, "tcl_DStringSetLength", dstring_set_length as *const ()),
install(t, "tcl_DStringFree", dstring_free as *const ()),
install(t, "tcl_DStringInit", dstring_init as *const ()),
install(t, "tcl_TranslateFileName", translate_file_name as *const ()),
install(t, "tcl_PosixError", posix_error as *const ()),
install(
t,
"tcl_DeleteCommandFromToken",
delete_command_from_token as *const (),
),
install(t, "tcl_GetCommandInfo", get_command_info as *const ()),
install(t, "tcl_GetAssocData", get_assoc_data as *const ()),
install(t, "tcl_GetObjResult", get_obj_result as *const ()),
install(t, "tcl_LinkVar", link_var as *const ()),
install(t, "tcl_FirstHashEntry", first_hash_entry as *const ()),
install(t, "tcl_InitHashTable", init_hash_table as *const ()),
install(t, "tcl_IsSafe", is_safe as *const ()),
install(t, "tcl_RegisterObjType", register_obj_type as *const ()),
install(
t,
"tcl_CreateThreadExitHandler",
create_thread_exit_handler as *const (),
),
install(t, "tcl_CreateExitHandler", create_exit_handler as *const ()),
install(t, "tcl_NextHashEntry", next_hash_entry as *const ()),
install(t, "tcl_ResetResult", reset_result as *const ()),
install(t, "tcl_SetAssocData", set_assoc_data as *const ()),
install(t, "tcl_SetVar2", set_var2 as *const ()),
install(t, "tcl_SetErrorCode", set_error_code as *const ()),
install(t, "tcl_SetObjResult", set_obj_result as *const ()),
install(t, "tcl_GetThreadData", get_thread_data as *const ()),
install(t, "tcl_GetVar2", get_var2 as *const ()),
install(t, "tcl_MutexLock", mutex_lock as *const ()),
install(t, "tcl_MutexUnlock", mutex_unlock as *const ()),
install(t, "tcl_GetVar2Ex", get_var2_ex as *const ()),
install(t, "tcl_UtfToTitle", utf_to_title as *const ()),
install(t, "tcl_GetTime", get_time as *const ()),
install(t, "tcl_DictObjPut", dict_obj_put as *const ()),
install(t, "tcl_CreateNamespace", create_namespace as *const ()),
install(t, "tcl_Export", export as *const ()),
install(t, "tcl_FindCommand", find_command as *const ()),
install(t, "tcl_Canceled", canceled as *const ()),
install(t, "tcl_GetStartupScript", get_startup_script as *const ()),
install(t, "tcl_SetStartupScript", set_startup_script as *const ()),
install(t, "tcl_CreateEnsemble", create_ensemble as *const ()),
install(t, "tcl_FindEnsemble", find_ensemble as *const ()),
install(
t,
"tcl_SetEnsembleMappingDict",
set_ensemble_mapping_dict as *const (),
),
install(t, "tcl_FindNamespace", find_namespace as *const ()),
install(t, "tcl_RegisterConfig", register_config as *const ()),
install(t, "tcl_GetStringFromObj", get_string_from_obj as *const ()),
install(t, "tcl_ListObjLength", list_obj_length as *const ()),
install(
t,
"tcl_ListObjGetElements",
list_obj_get_elements as *const (),
),
install(
t,
"tcl_AppendAllObjTypes",
append_all_obj_types as *const (),
),
install(t, "tcl_AppendToObj", append_to_obj as *const ()),
install(t, "tcl_ConvertToType", convert_to_type as *const ()),
install(t, "tcl_DuplicateObj", duplicate_obj as *const ()),
install(
t,
"tcl_GetBooleanFromObj",
get_boolean_from_obj as *const (),
),
install(t, "tcl_GetDoubleFromObj", get_double_from_obj as *const ()),
install(t, "tcl_GetIntFromObj", get_int_from_obj as *const ()),
install(t, "tcl_GetLongFromObj", get_long_from_obj as *const ()),
install(t, "tcl_GetObjType", get_obj_type as *const ()),
install(
t,
"tcl_InvalidateStringRep",
invalidate_string_rep as *const (),
),
install(
t,
"tcl_AppendObjToErrorInfo",
append_obj_to_error_info as *const (),
),
install(
t,
"tcl_BackgroundException",
background_exception as *const (),
),
install(t, "tcl_AllowExceptions", allow_exceptions as *const ()),
install(t, "tcl_SaveInterpState", save_interp_state as *const ()),
install(
t,
"tcl_RestoreInterpState",
restore_interp_state as *const (),
),
install(
t,
"tcl_DiscardInterpState",
discard_interp_state as *const (),
),
install(t, "tcl_ParseArgsObjv", parse_args_objv as *const ()),
install(
t,
"tcl_ListObjAppendList",
list_obj_append_list as *const (),
),
install(t, "tcl_ListObjReplace", list_obj_replace as *const ()),
install(t, "tcl_NewDoubleObj", new_double_obj as *const ()),
install(t, "tcl_SetStringObj", set_string_obj as *const ()),
install(
t,
"tcl_DStringAppendElement",
dstring_append_element as *const (),
),
install(t, "tcl_DStringEndSublist", dstring_end_sublist as *const ()),
install(t, "tcl_DStringGetResult", dstring_get_result as *const ()),
install(t, "tcl_DStringResult", dstring_result as *const ()),
install(
t,
"tcl_DStringStartSublist",
dstring_start_sublist as *const (),
),
install(t, "tcl_PrintDouble", print_double as *const ()),
install(t, "tcl_AppendObjToObj", append_obj_to_obj as *const ()),
install(
t,
"tcl_AttemptSetObjLength",
attempt_set_obj_length as *const (),
),
install(
t,
"tcl_GetWideIntFromObj",
get_wide_int_from_obj as *const (),
),
install(t, "tcl_NewWideIntObj", new_wide_int_obj as *const ()),
install(t, "tcl_DictObjGet", dict_obj_get as *const ()),
install(t, "tcl_DictObjRemove", dict_obj_remove as *const ()),
install(t, "tcl_DictObjFirst", dict_obj_first as *const ()),
install(t, "tcl_DictObjNext", dict_obj_next as *const ()),
install(t, "tcl_DictObjDone", dict_obj_done as *const ()),
install(t, "tcl_NewDictObj", new_dict_obj as *const ()),
install(
t,
"tcl_AppendLimitedToObj",
append_limited_to_obj as *const (),
),
install(t, "tcl_FreeInternalRep", free_internal_rep as *const ()),
install(t, "tcl_InitStringRep", init_string_rep as *const ()),
install(t, "tcl_FetchInternalRep", fetch_internal_rep as *const ()),
install(t, "tcl_StoreInternalRep", store_internal_rep as *const ()),
install(t, "tcl_HasStringRep", has_string_rep as *const ()),
install(t, "tcl_DictObjSize", dict_obj_size as *const ()),
install(t, "tcl_GetBoolFromObj", get_bool_from_obj as *const ()),
install(t, "tcl_DStringToObj", dstring_to_obj as *const ()),
];
slots.extend(super::utf16::install_impls(t));
slots.extend(super::preserve::install_impls(t));
slots.extend(super::pkg::install_impls(t));
slots.extend(super::index::install_impls(t));
slots.extend(super::channel::install_impls(t));
slots.extend(super::notifier::install_impls(t));
if degraded {
slots.push(install(
t,
"tcl_AppendStringsToObj",
append_strings_to_obj as *const (),
));
}
if level == Level::Hosting {
slots.extend(install_hosting(t));
}
slots
}
unsafe fn install_hosting(t: &mut TclStubs) -> Vec<usize> {
let mut slots = super::linkvar::install_impls(t);
slots.extend([
install(
t,
"tcl_AppendStringsToObj",
super::eval::tclrs_tk_append_strings_to_obj as *const (),
),
install(
t,
"tcl_Panic",
super::eval::tclrs_tk_panic_trampoline as *const (),
),
install(t, "tcl_EvalEx", super::eval::eval_ex as *const ()),
install(t, "tcl_EvalObjv", super::eval::eval_objv as *const ()),
install(t, "tcl_EvalObjEx", super::eval::eval_obj_ex as *const ()),
install(
t,
"tcl_ObjPrintf",
super::eval::tclrs_tk_obj_printf as *const (),
),
install(
t,
"tcl_AppendPrintfToObj",
super::eval::tclrs_tk_append_printf_to_obj as *const (),
),
]);
slots
}
macro_rules! entered {
($name:literal) => {
record(Table::Tcl, slot(&TCL_NAMES, $name))
};
}
unsafe extern "C" fn append_strings_to_obj(o: *mut TclObj) {
entered!("tcl_AppendStringsToObj");
note("DEGRADED-AppendStringsToObj", &obj::text_of(o));
}
unsafe extern "C" fn pkg_require_ex(
_interp: *mut c_void,
_name: *const c_char,
_version: *const c_char,
_exact: c_int,
client_data: *mut *mut c_void,
) -> *const c_char {
entered!("tcl_PkgRequireEx");
if !client_data.is_null() {
*client_data = ptr::null_mut();
}
c"9.0.4".as_ptr()
}
unsafe extern "C" fn tcl_alloc(size: usize) -> *mut c_void {
entered!("tcl_Alloc");
libc::malloc(size)
}
unsafe extern "C" fn tcl_free(p: *mut c_void) {
entered!("tcl_Free");
libc::free(p)
}
unsafe extern "C" fn tcl_realloc(p: *mut c_void, size: usize) -> *mut c_void {
entered!("tcl_Realloc");
libc::realloc(p, size)
}
unsafe extern "C" fn tcl_free_obj(o: *mut TclObj) {
entered!("tclFreeObj");
obj::free_obj(o);
}
unsafe extern "C" fn list_obj_append_element(
_interp: *mut c_void,
list: *mut TclObj,
o: *mut TclObj,
) -> c_int {
entered!("tcl_ListObjAppendElement");
let l = objtype::list_of(list);
l.elems.push(retain(o));
objtype::invalidate(list);
TCL_OK
}
unsafe extern "C" fn list_obj_append_list(
_interp: *mut c_void,
list: *mut TclObj,
from: *mut TclObj,
) -> c_int {
entered!("tcl_ListObjAppendList");
let add: Vec<*mut TclObj> = objtype::list_of(from).elems.clone();
let l = objtype::list_of(list);
for e in add {
l.elems.push(retain(e));
}
objtype::invalidate(list);
TCL_OK
}
unsafe extern "C" fn list_obj_replace(
_interp: *mut c_void,
list: *mut TclObj,
first: isize,
count: isize,
objc: isize,
objv: *const *mut TclObj,
) -> c_int {
entered!("tcl_ListObjReplace");
let l = objtype::list_of(list);
let len = l.elems.len();
let start = first.clamp(0, len as isize) as usize;
let end = (start + count.max(0) as usize).min(len);
let mut fresh = Vec::with_capacity(objc.max(0) as usize);
if !objv.is_null() {
for i in 0..objc.max(0) {
fresh.push(retain(*objv.offset(i)));
}
}
let removed: Vec<*mut TclObj> = l.elems.splice(start..end, fresh).collect();
for e in removed {
obj::decr_ref(e);
}
objtype::invalidate(list);
TCL_OK
}
unsafe extern "C" fn list_obj_index(
_interp: *mut c_void,
list: *mut TclObj,
index: isize,
out: *mut *mut TclObj,
) -> c_int {
entered!("tcl_ListObjIndex");
let l = objtype::list_of(list);
*out = if index < 0 || index as usize >= l.elems.len() {
ptr::null_mut()
} else {
l.elems[index as usize]
};
TCL_OK
}
unsafe extern "C" fn list_obj_length(
_interp: *mut c_void,
list: *mut TclObj,
out: *mut isize,
) -> c_int {
entered!("tcl_ListObjLength");
*out = objtype::list_of(list).elems.len() as isize;
TCL_OK
}
unsafe extern "C" fn new_list_obj(objc: isize, objv: *const *mut TclObj) -> *mut TclObj {
entered!("tcl_NewListObj");
let mut elems = Vec::new();
if !objv.is_null() {
for i in 0..objc.max(0) {
let e = *objv.offset(i);
assert!(
obj::is_host_allocated(e),
"Tcl_NewListObj was handed a Tcl_Obj this side never allocated"
);
elems.push(e);
}
}
objtype::new_list(&elems)
}
unsafe extern "C" fn new_empty_obj() -> *mut TclObj {
entered!("tcl_NewObj");
obj::alloc()
}
unsafe extern "C" fn new_string_obj(bytes: *const c_char, length: isize) -> *mut TclObj {
entered!("tcl_NewStringObj");
let b = c_bytes(bytes, length);
note("NewStringObj", &String::from_utf8_lossy(b));
obj::new_string(b)
}
unsafe extern "C" fn new_wide_int_obj(v: i64) -> *mut TclObj {
entered!("tcl_NewWideIntObj");
objtype::new_wide(v)
}
unsafe extern "C" fn new_double_obj(v: f64) -> *mut TclObj {
entered!("tcl_NewDoubleObj");
objtype::new_double(v)
}
unsafe extern "C" fn new_dict_obj() -> *mut TclObj {
entered!("tcl_NewDictObj");
objtype::new_dict(&[])
}
unsafe extern "C" fn duplicate_obj(o: *mut TclObj) -> *mut TclObj {
entered!("tcl_DuplicateObj");
obj::duplicate(o)
}
unsafe extern "C" fn set_obj_length(o: *mut TclObj, length: isize) {
entered!("tcl_SetObjLength");
obj::set_obj_length(o, length);
}
unsafe extern "C" fn attempt_set_obj_length(o: *mut TclObj, length: isize) -> c_int {
entered!("tcl_AttemptSetObjLength");
obj::set_obj_length(o, length);
1
}
unsafe extern "C" fn set_string_obj(o: *mut TclObj, bytes: *const c_char, length: isize) {
entered!("tcl_SetStringObj");
objtype::free_internal_rep(o);
obj::set_string(o, c_bytes(bytes, length));
}
unsafe extern "C" fn append_to_obj(o: *mut TclObj, bytes: *const c_char, length: isize) {
entered!("tcl_AppendToObj");
obj::append_bytes(o, c_bytes(bytes, length));
}
unsafe extern "C" fn append_obj_to_obj(o: *mut TclObj, from: *mut TclObj) {
entered!("tcl_AppendObjToObj");
let add = obj::string_of(from).to_vec();
obj::append_bytes(o, &add);
}
unsafe extern "C" fn append_limited_to_obj(
o: *mut TclObj,
bytes: *const c_char,
length: isize,
limit: isize,
ellipsis: *const c_char,
) {
entered!("tcl_AppendLimitedToObj");
let add = c_bytes(bytes, length);
if (add.len() as isize) <= limit {
obj::append_bytes(o, add);
return;
}
let tail = if ellipsis.is_null() {
b"...".as_slice()
} else {
CStr::from_ptr(ellipsis).to_bytes()
};
let keep = (limit as usize).saturating_sub(tail.len());
let mut out = add[..keep.min(add.len())].to_vec();
out.extend_from_slice(tail);
obj::append_bytes(o, &out);
}
unsafe extern "C" fn invalidate_string_rep(o: *mut TclObj) {
entered!("tcl_InvalidateStringRep");
obj::invalidate_string_rep(o);
}
unsafe extern "C" fn has_string_rep(o: *mut TclObj) -> c_int {
entered!("tcl_HasStringRep");
c_int::from(obj::has_string_rep(o))
}
unsafe extern "C" fn init_string_rep(
o: *mut TclObj,
bytes: *const c_char,
num: usize,
) -> *mut c_char {
entered!("tcl_InitStringRep");
obj::init_string_rep(o, bytes, num)
}
unsafe extern "C" fn free_internal_rep(o: *mut TclObj) {
entered!("tcl_FreeInternalRep");
objtype::free_internal_rep(o);
}
unsafe extern "C" fn store_internal_rep(
o: *mut TclObj,
ty: *const TclObjType,
ir: *const TclObjInternalRep,
) {
entered!("tcl_StoreInternalRep");
objtype::store_internal_rep(o, ty, ir);
}
unsafe extern "C" fn fetch_internal_rep(
o: *mut TclObj,
ty: *const TclObjType,
) -> *mut TclObjInternalRep {
entered!("tcl_FetchInternalRep");
objtype::fetch_internal_rep(o, ty)
}
unsafe extern "C" fn convert_to_type(
interp: *mut c_void,
o: *mut TclObj,
ty: *const TclObjType,
) -> c_int {
entered!("tcl_ConvertToType");
objtype::convert_to_type(interp, o, ty)
}
unsafe extern "C" fn get_obj_type(name: *const c_char) -> *const TclObjType {
entered!("tcl_GetObjType");
if name.is_null() {
return ptr::null();
}
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("GetObjType", &text);
objtype::lookup(&text)
}
unsafe extern "C" fn append_all_obj_types(_interp: *mut c_void, o: *mut TclObj) -> c_int {
entered!("tcl_AppendAllObjTypes");
let list = objtype::list_of(o);
for name in objtype::registered_names() {
let e = obj::new_string(name.as_bytes());
obj::incr_ref(e);
list.elems.push(e);
}
objtype::invalidate(o);
TCL_OK
}
unsafe extern "C" fn get_int_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
out: *mut c_int,
) -> c_int {
entered!("tcl_GetIntFromObj");
match objtype::wide_of(o) {
Some(v) if v >= c_int::MIN as i64 && v <= c_int::MAX as i64 => {
*out = v as c_int;
TCL_OK
}
_ => TCL_ERROR,
}
}
unsafe extern "C" fn get_long_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
out: *mut std::ffi::c_long,
) -> c_int {
entered!("tcl_GetLongFromObj");
match objtype::wide_of(o) {
Some(v) => {
*out = v as std::ffi::c_long;
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn get_wide_int_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
out: *mut i64,
) -> c_int {
entered!("tcl_GetWideIntFromObj");
match objtype::wide_of(o) {
Some(v) => {
*out = v;
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn get_double_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
out: *mut f64,
) -> c_int {
entered!("tcl_GetDoubleFromObj");
match objtype::double_of(o) {
Some(v) => {
*out = v;
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn get_boolean_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
out: *mut c_int,
) -> c_int {
entered!("tcl_GetBooleanFromObj");
match objtype::bool_of(o) {
Some(v) => {
*out = c_int::from(v);
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn get_bool_from_obj(
_interp: *mut c_void,
o: *mut TclObj,
_flags: c_int,
out: *mut c_char,
) -> c_int {
entered!("tcl_GetBoolFromObj");
match objtype::bool_of(o) {
Some(v) => {
if !out.is_null() {
*out = c_char::from(v);
}
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn create_obj_command(
interp: *mut c_void,
name: *const c_char,
proc_: *mut c_void,
client_data: *mut c_void,
delete_proc: *mut c_void,
) -> *mut c_void {
entered!("tcl_CreateObjCommand");
record_command(interp, name, proc_, client_data, delete_proc, false)
}
unsafe extern "C" fn create_obj_command2(
interp: *mut c_void,
name: *const c_char,
proc_: *mut c_void,
client_data: *mut c_void,
delete_proc: *mut c_void,
) -> *mut c_void {
entered!("tcl_CreateObjCommand2");
record_command(interp, name, proc_, client_data, delete_proc, true)
}
unsafe fn record_command(
interp: *mut c_void,
name: *const c_char,
proc_: *mut c_void,
client_data: *mut c_void,
delete_proc: *mut c_void,
proc2: bool,
) -> *mut c_void {
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("CreateObjCommand", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
let fresh = HostCommand {
name: text.clone(),
proc_,
client_data,
delete_proc,
proc2,
ensemble_map: ptr::null_mut(),
dying: false,
};
match h.commands.iter().position(|c| c.name == text) {
Some(i) => {
let old = std::mem::replace(&mut *h.commands[i], fresh);
run_delete_proc(old.delete_proc, old.client_data);
&mut *h.commands[i] as *mut HostCommand as *mut c_void
}
None => {
h.commands.push(Box::new(fresh));
&mut **h.commands.last_mut().unwrap() as *mut HostCommand as *mut c_void
}
}
}
unsafe extern "C" fn delete_command_from_token(interp: *mut c_void, token: *mut c_void) -> c_int {
entered!("tcl_DeleteCommandFromToken");
let h = &mut *(*(interp as *mut HostInterp)).host;
let Some(i) = h
.commands
.iter()
.position(|c| &**c as *const HostCommand as *const c_void == token)
else {
return -1;
};
note("DeleteCommandFromToken", &h.commands[i].name.clone());
if h.commands[i].dying {
h.commands.remove(i);
return 0;
}
h.commands[i].dying = true;
let (delete_proc, client_data) = (h.commands[i].delete_proc, h.commands[i].client_data);
run_delete_proc(delete_proc, client_data);
if let Some(i) = h
.commands
.iter()
.position(|c| &**c as *const HostCommand as *const c_void == token)
{
h.commands.remove(i);
}
0
}
unsafe fn run_delete_proc(delete_proc: *mut c_void, client_data: *mut c_void) {
if delete_proc.is_null() {
return;
}
let f: unsafe extern "C" fn(*mut c_void) = std::mem::transmute(delete_proc);
f(client_data);
}
pub unsafe fn command_named(host: *mut Host, name: &str) -> Option<&'static HostCommand> {
if host.is_null() {
return None;
}
(*host)
.commands
.iter()
.find(|c| c.name == name)
.map(|c| &*(&**c as *const HostCommand))
}
unsafe extern "C" fn create_interp() -> *mut c_void {
entered!("tcl_CreateInterp");
let host = Box::into_raw(Box::new(empty_host()));
super::interp::shared_for(host);
wrap_interp(host) as *mut c_void
}
unsafe extern "C" fn delete_interp(interp: *mut c_void) {
entered!("tcl_DeleteInterp");
let h = (*(interp as *mut HostInterp)).host;
(*h).deleted = true;
if h == CURRENT.load(Ordering::Relaxed) {
return;
}
for cmd in &(*h).commands {
run_delete_proc(cmd.delete_proc, cmd.client_data);
}
super::interp::forget(h);
drop(Box::from_raw(h));
drop(Box::from_raw(interp as *mut HostInterp));
}
unsafe extern "C" fn interp_deleted(interp: *mut c_void) -> c_int {
entered!("tcl_InterpDeleted");
if interp.is_null() {
return 1;
}
let h = (*(interp as *mut HostInterp)).host;
c_int::from(h.is_null() || (*h).deleted)
}
unsafe extern "C" fn delete_hash_entry(e: *mut TclHashEntry) {
entered!("tcl_DeleteHashEntry");
super::hash::delete_entry(e)
}
unsafe extern "C" fn delete_hash_table(t: *mut TclHashTable) {
entered!("tcl_DeleteHashTable");
super::hash::delete_table(t)
}
unsafe extern "C" fn get_assoc_data(
interp: *mut c_void,
name: *const c_char,
proc_out: *mut *mut c_void,
) -> *mut c_void {
entered!("tcl_GetAssocData");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("GetAssocData", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
match h.assoc_data.iter().find(|(n, _, _)| *n == text) {
Some((_, p, d)) => {
if !proc_out.is_null() {
*proc_out = *p;
}
*d
}
None => ptr::null_mut(),
}
}
unsafe extern "C" fn set_assoc_data(
interp: *mut c_void,
name: *const c_char,
proc_: *mut c_void,
client_data: *mut c_void,
) {
entered!("tcl_SetAssocData");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("SetAssocData", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
match h.assoc_data.iter_mut().find(|(n, _, _)| *n == text) {
Some(e) => {
e.1 = proc_;
e.2 = client_data;
}
None => h.assoc_data.push((text, proc_, client_data)),
}
}
unsafe extern "C" fn get_command_info(
interp: *mut c_void,
name: *const c_char,
info: *mut TclCmdInfo,
) -> c_int {
entered!("tcl_GetCommandInfo");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("GetCommandInfo", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
let Some(cmd) = h.commands.iter().find(|c| c.name == text) else {
return 0;
};
(*info).is_native_object_proc = 1;
(*info).obj_proc = cmd.proc_;
(*info).obj_client_data = cmd.client_data;
(*info).proc = ptr::null_mut();
(*info).client_data = ptr::null_mut();
(*info).delete_proc = cmd.delete_proc;
(*info).delete_data = cmd.client_data;
(*info).namespace_ptr = ptr::null_mut();
(*info).obj_proc2 = ptr::null_mut();
(*info).obj_client_data2 = ptr::null_mut();
1
}
static STARTUP_SCRIPT: AtomicPtr<TclObj> = AtomicPtr::new(ptr::null_mut());
static STARTUP_ENCODING: AtomicPtr<TclObj> = AtomicPtr::new(ptr::null_mut());
unsafe extern "C" fn get_startup_script(encoding_out: *mut *const c_char) -> *mut TclObj {
entered!("tcl_GetStartupScript");
if !encoding_out.is_null() {
let enc = STARTUP_ENCODING.load(Ordering::Relaxed);
*encoding_out = if enc.is_null() {
ptr::null()
} else {
(*enc).bytes
};
}
STARTUP_SCRIPT.load(Ordering::Relaxed)
}
pub fn set_startup_file(path: &str) {
unsafe {
let kept = retain(obj::new_string(path.as_bytes()));
obj::release(STARTUP_SCRIPT.swap(kept, Ordering::Relaxed));
}
}
unsafe extern "C" fn set_startup_script(path: *mut TclObj, encoding: *const c_char) {
entered!("tcl_SetStartupScript");
let kept = if path.is_null() {
ptr::null_mut()
} else {
retain(path)
};
obj::release(STARTUP_SCRIPT.swap(kept, Ordering::Relaxed));
let enc = if encoding.is_null() {
ptr::null_mut()
} else {
retain(obj::new_string(CStr::from_ptr(encoding).to_bytes()))
};
obj::release(STARTUP_ENCODING.swap(enc, Ordering::Relaxed));
}
unsafe extern "C" fn canceled(_interp: *mut c_void, _flags: c_int) -> c_int {
entered!("tcl_Canceled");
TCL_OK
}
unsafe extern "C" fn find_command(
interp: *mut c_void,
name: *const c_char,
_context_ns: *mut TclNamespace,
_flags: c_int,
) -> *mut c_void {
entered!("tcl_FindCommand");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("FindCommand", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
let bare = text.strip_prefix("::").unwrap_or(&text);
let qualified = format!("::{bare}");
match h
.commands
.iter()
.find(|c| c.name == text || c.name == bare || c.name == qualified)
{
Some(c) => &**c as *const HostCommand as *mut c_void,
None => ptr::null_mut(),
}
}
unsafe extern "C" fn get_obj_result(interp: *mut c_void) -> *mut TclObj {
entered!("tcl_GetObjResult");
let h = &mut *(*(interp as *mut HostInterp)).host;
if h.result.is_null() {
h.result = retain(obj::alloc());
}
h.result
}
unsafe extern "C" fn link_var(
interp: *mut c_void,
name: *const c_char,
addr: *mut c_void,
ty: c_int,
) -> c_int {
entered!("tcl_LinkVar");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
let h = &mut *(*(interp as *mut HostInterp)).host;
h.linked_vars.push((text, ty));
super::linkvar::link_var(interp, name, addr, ty)
}
unsafe extern "C" fn first_hash_entry(
t: *mut TclHashTable,
s: *mut TclHashSearch,
) -> *mut TclHashEntry {
entered!("tcl_FirstHashEntry");
super::hash::first_entry(t, s)
}
unsafe extern "C" fn init_hash_table(t: *mut TclHashTable, key_type: c_int) {
entered!("tcl_InitHashTable");
super::hash::init(t, key_type)
}
unsafe extern "C" fn create_thread_exit_handler(proc_: *mut c_void, client_data: *mut c_void) {
entered!("tcl_CreateThreadExitHandler");
host().exit_handlers.push((proc_, client_data));
}
unsafe extern "C" fn create_exit_handler(proc_: *mut c_void, client_data: *mut c_void) {
entered!("tcl_CreateExitHandler");
host().exit_handlers.push((proc_, client_data));
}
unsafe extern "C" fn next_hash_entry(s: *mut TclHashSearch) -> *mut TclHashEntry {
entered!("tcl_NextHashEntry");
super::hash::next_entry(s)
}
unsafe extern "C" fn dstring_append(
ds: *mut TclDString,
bytes: *const c_char,
length: isize,
) -> *mut c_char {
entered!("tcl_DStringAppend");
dstring::append(ds, bytes, length)
}
unsafe extern "C" fn dstring_append_element(
ds: *mut TclDString,
element: *const c_char,
) -> *mut c_char {
entered!("tcl_DStringAppendElement");
dstring::append_element(ds, element)
}
unsafe extern "C" fn dstring_start_sublist(ds: *mut TclDString) {
entered!("tcl_DStringStartSublist");
dstring::start_sublist(ds);
}
unsafe extern "C" fn dstring_end_sublist(ds: *mut TclDString) {
entered!("tcl_DStringEndSublist");
dstring::end_sublist(ds);
}
unsafe extern "C" fn dstring_set_length(ds: *mut TclDString, length: isize) {
entered!("tcl_DStringSetLength");
dstring::set_length(ds, length);
}
unsafe extern "C" fn dstring_free(ds: *mut TclDString) {
entered!("tcl_DStringFree");
dstring::free(ds);
}
unsafe extern "C" fn dstring_init(ds: *mut TclDString) {
entered!("tcl_DStringInit");
dstring::init(ds);
}
unsafe extern "C" fn posix_error(_interp: *mut c_void) -> *const c_char {
entered!("tcl_PosixError");
let text = libc::strerror(*libc::__error());
if text.is_null() {
c"unknown error".as_ptr()
} else {
text
}
}
unsafe extern "C" fn translate_file_name(
interp: *mut c_void,
name: *const c_char,
ds: *mut TclDString,
) -> *mut c_char {
entered!("tcl_TranslateFileName");
let given = std::ffi::CStr::from_ptr(name)
.to_string_lossy()
.into_owned();
let Ok(expanded) = crate::cmd_file::expand_tilde(&given) else {
install_result(
interp,
obj::new_string(format!("couldn't expand \"{given}\"").as_bytes()),
);
return ptr::null_mut();
};
dstring::init(ds);
dstring::append(
ds,
expanded.as_ptr() as *const c_char,
expanded.len() as isize,
)
}
const TCL_DOUBLE_SPACE: usize = 27;
unsafe extern "C" fn print_double(_interp: *mut c_void, value: f64, dst: *mut c_char) {
entered!("tcl_PrintDouble");
let text = crate::runtime::format_double(value);
let mut n = text.len().min(TCL_DOUBLE_SPACE - 1);
while n > 0 && !text.is_char_boundary(n) {
n -= 1;
}
ptr::copy_nonoverlapping(text.as_ptr() as *const c_char, dst, n);
*dst.add(n) = 0;
}
unsafe extern "C" fn dstring_to_obj(ds: *mut TclDString) -> *mut TclObj {
entered!("tcl_DStringToObj");
dstring::to_obj(ds)
}
unsafe extern "C" fn dstring_result(interp: *mut c_void, ds: *mut TclDString) {
entered!("tcl_DStringResult");
let o = dstring::to_obj(ds);
install_result(interp, o);
}
unsafe extern "C" fn dstring_get_result(interp: *mut c_void, ds: *mut TclDString) {
entered!("tcl_DStringGetResult");
let h = &mut *(*(interp as *mut HostInterp)).host;
let text = if h.result.is_null() {
Vec::new()
} else {
obj::string_of(h.result).to_vec()
};
dstring::free(ds);
dstring::append(ds, text.as_ptr() as *const c_char, text.len() as isize);
clear_result(interp);
}
unsafe extern "C" fn is_safe(_interp: *mut c_void) -> c_int {
entered!("tcl_IsSafe");
0
}
unsafe extern "C" fn register_obj_type(ty: *const TclObjType) {
entered!("tcl_RegisterObjType");
note("RegisterObjType", &objtype::name_of(ty));
objtype::register(ty);
if std::env::var_os("TCLRS_TK_EXERCISE_TYPES").is_some() {
let line = objtype::exercise(ty);
let mut err = std::io::stderr().lock();
use std::io::Write;
let _ = writeln!(err, "tkobjtype {line}");
let _ = err.flush();
}
}
unsafe fn clear_result(interp: *mut c_void) {
let h = &mut *(*(interp as *mut HostInterp)).host;
let old = std::mem::replace(&mut h.result, ptr::null_mut());
obj::release(old);
}
unsafe fn install_result(interp: *mut c_void, o: *mut TclObj) {
let kept = retain(o);
let h = &mut *(*(interp as *mut HostInterp)).host;
let old = std::mem::replace(&mut h.result, kept);
obj::release(old);
}
unsafe extern "C" fn reset_result(interp: *mut c_void) {
entered!("tcl_ResetResult");
clear_result(interp);
}
unsafe extern "C" fn set_error_code(_interp: *mut c_void) {
entered!("tcl_SetErrorCode");
}
unsafe extern "C" fn set_obj_result(interp: *mut c_void, o: *mut TclObj) {
entered!("tcl_SetObjResult");
note("SetObjResult", &obj::text_of(o));
install_result(interp, o);
}
unsafe extern "C" fn get_thread_data(key: *mut c_void, size: isize) -> *mut c_void {
entered!("tcl_GetThreadData");
let h = host();
let k = key as usize;
if let Some((_, p)) = h.thread_data.iter().find(|(a, _)| *a == k) {
return *p;
}
let p = libc::calloc(1, size as usize);
assert!(!p.is_null(), "out of memory allocating thread data");
h.thread_data.push((k, p));
p
}
unsafe fn var_of(
interp: *mut c_void,
part1: *const c_char,
part2: *const c_char,
) -> Option<*mut TclObj> {
let (n, i) = var_key(part1, part2);
if i.is_empty() {
let shared = super::linkvar::shared_of(interp)?;
let value = crate::runtime::global_of(&shared, &n)?;
return Some(super::linkvar::cached_obj(interp, &n, &value));
}
let h = &mut *(*(interp as *mut HostInterp)).host;
h.vars
.iter()
.find(|(vn, vi, _)| *vn == n && *vi == i)
.map(|(_, _, v)| *v)
}
unsafe fn var_key(part1: *const c_char, part2: *const c_char) -> (String, String) {
let name = String::from_utf8_lossy(CStr::from_ptr(part1).to_bytes()).into_owned();
let index = if part2.is_null() {
String::new()
} else {
String::from_utf8_lossy(CStr::from_ptr(part2).to_bytes()).into_owned()
};
(name, index)
}
unsafe extern "C" fn get_var2(
interp: *mut c_void,
part1: *const c_char,
part2: *const c_char,
_flags: c_int,
) -> *const c_char {
entered!("tcl_GetVar2");
match var_of(interp, part1, part2) {
Some(v) => {
obj::string_of(v);
(*v).bytes
}
None => ptr::null(),
}
}
unsafe extern "C" fn set_var2(
interp: *mut c_void,
part1: *const c_char,
part2: *const c_char,
value: *const c_char,
flags: c_int,
) -> *const c_char {
entered!("tcl_SetVar2");
let (n, i) = var_key(part1, part2);
note("SetVar2", &n);
if i.is_empty() {
let text = String::from_utf8_lossy(CStr::from_ptr(value).to_bytes()).into_owned();
let value = fusevm::Value::Str(std::sync::Arc::new(text));
let o = super::linkvar::set_scalar(interp, &n, value, flags);
if o.is_null() {
return ptr::null();
}
obj::string_of(o);
return (*o).bytes;
}
let o = retain(obj::new_string(CStr::from_ptr(value).to_bytes()));
let h = &mut *(*(interp as *mut HostInterp)).host;
match h.vars.iter_mut().find(|(vn, vi, _)| *vn == n && *vi == i) {
Some(e) => e.2 = o,
None => h.vars.push((n, i, o)),
}
(*o).bytes
}
unsafe fn mutex_of(m: *mut *mut c_void) -> *mut libc::pthread_mutex_t {
if (*m).is_null() {
let p = libc::malloc(std::mem::size_of::<libc::pthread_mutex_t>())
as *mut libc::pthread_mutex_t;
assert!(!p.is_null(), "out of memory allocating a mutex");
libc::pthread_mutex_init(p, ptr::null());
*m = p as *mut c_void;
}
*m as *mut libc::pthread_mutex_t
}
unsafe extern "C" fn mutex_lock(m: *mut *mut c_void) {
entered!("tcl_MutexLock");
libc::pthread_mutex_lock(mutex_of(m));
}
unsafe extern "C" fn mutex_unlock(m: *mut *mut c_void) {
entered!("tcl_MutexUnlock");
libc::pthread_mutex_unlock(mutex_of(m));
}
unsafe extern "C" fn get_var2_ex(
interp: *mut c_void,
part1: *const c_char,
part2: *const c_char,
_flags: c_int,
) -> *mut TclObj {
entered!("tcl_GetVar2Ex");
var_of(interp, part1, part2).unwrap_or(ptr::null_mut())
}
unsafe extern "C" fn utf_to_title(src: *mut c_char) -> isize {
entered!("tcl_UtfToTitle");
let mut i = 0isize;
loop {
let c = *src.offset(i) as u8;
if c == 0 {
break;
}
if c.is_ascii() {
*src.offset(i) = if i == 0 {
c.to_ascii_uppercase() as c_char
} else {
c.to_ascii_lowercase() as c_char
};
}
i += 1;
}
i
}
unsafe extern "C" fn get_time(t: *mut TclTime) {
entered!("tcl_GetTime");
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
libc::gettimeofday(&mut tv, ptr::null_mut());
(*t).sec = tv.tv_sec;
(*t).usec = tv.tv_usec as std::ffi::c_long;
}
unsafe extern "C" fn dict_obj_put(
_interp: *mut c_void,
dict: *mut TclObj,
key: *mut TclObj,
value: *mut TclObj,
) -> c_int {
entered!("tcl_DictObjPut");
let want = obj::string_of(key).to_vec();
let kept = retain(value);
let d = objtype::dict_of(dict);
if let Some(slot) = d.pairs.iter_mut().find(|(k, _)| obj::string_of(*k) == want) {
let old = std::mem::replace(&mut slot.1, kept);
obj::release(old);
} else {
d.pairs.push((retain(key), kept));
}
objtype::invalidate(dict);
TCL_OK
}
unsafe extern "C" fn dict_obj_get(
_interp: *mut c_void,
dict: *mut TclObj,
key: *mut TclObj,
out: *mut *mut TclObj,
) -> c_int {
entered!("tcl_DictObjGet");
let want = obj::string_of(key).to_vec();
let d = objtype::dict_of(dict);
*out = d
.pairs
.iter()
.find(|(k, _)| obj::string_of(*k) == want)
.map(|(_, v)| *v)
.unwrap_or(ptr::null_mut());
TCL_OK
}
unsafe extern "C" fn dict_obj_remove(
_interp: *mut c_void,
dict: *mut TclObj,
key: *mut TclObj,
) -> c_int {
entered!("tcl_DictObjRemove");
let want = obj::string_of(key).to_vec();
let d = objtype::dict_of(dict);
if let Some(at) = d.pairs.iter().position(|(k, _)| obj::string_of(*k) == want) {
let (k, v) = d.pairs.remove(at);
obj::release(k);
obj::release(v);
objtype::invalidate(dict);
}
TCL_OK
}
unsafe extern "C" fn dict_obj_size(
_interp: *mut c_void,
dict: *mut TclObj,
out: *mut isize,
) -> c_int {
entered!("tcl_DictObjSize");
*out = objtype::dict_of(dict).pairs.len() as isize;
TCL_OK
}
unsafe extern "C" fn dict_obj_first(
_interp: *mut c_void,
dict: *mut TclObj,
search: *mut obj::TclDictSearch,
key_out: *mut *mut TclObj,
value_out: *mut *mut TclObj,
done: *mut c_int,
) -> c_int {
entered!("tcl_DictObjFirst");
objtype::dict_of(dict);
(*search).next = ptr::null_mut();
(*search).epoch = 1;
(*search).dictionary_ptr = dict as *mut c_void;
dict_search_step(search, key_out, value_out, done);
TCL_OK
}
unsafe extern "C" fn dict_obj_next(
search: *mut obj::TclDictSearch,
key_out: *mut *mut TclObj,
value_out: *mut *mut TclObj,
done: *mut c_int,
) {
entered!("tcl_DictObjNext");
dict_search_step(search, key_out, value_out, done);
}
unsafe extern "C" fn dict_obj_done(search: *mut obj::TclDictSearch) {
entered!("tcl_DictObjDone");
(*search).epoch = 0;
(*search).dictionary_ptr = ptr::null_mut();
}
unsafe fn dict_search_step(
search: *mut obj::TclDictSearch,
key_out: *mut *mut TclObj,
value_out: *mut *mut TclObj,
done: *mut c_int,
) {
let dict = (*search).dictionary_ptr as *mut TclObj;
let at = (*search).next as usize;
let finished = if (*search).epoch == 0 || dict.is_null() {
true
} else {
let d = objtype::dict_of(dict);
match d.pairs.get(at) {
Some((k, v)) => {
if !key_out.is_null() {
*key_out = *k;
}
if !value_out.is_null() {
*value_out = *v;
}
(*search).next = (at + 1) as *mut c_void;
false
}
None => true,
}
};
if finished {
(*search).epoch = 0;
if !key_out.is_null() {
*key_out = ptr::null_mut();
}
if !value_out.is_null() {
*value_out = ptr::null_mut();
}
}
if !done.is_null() {
*done = c_int::from(finished);
}
}
unsafe extern "C" fn create_namespace(
interp: *mut c_void,
name: *const c_char,
client_data: *mut c_void,
delete_proc: *const c_void,
) -> *mut TclNamespace {
entered!("tcl_CreateNamespace");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("CreateNamespace", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
if let Some((_, p)) = h.namespaces.iter().find(|(n, _)| *n == text) {
return *p;
}
let tail = text.rsplit("::").next().unwrap_or(&text).to_string();
let ns = Box::into_raw(Box::new(TclNamespace {
name: dup_cstring(&tail),
full_name: dup_cstring(&text),
client_data,
delete_proc,
parent_ptr: ptr::null_mut(),
}));
h.namespaces.push((text, ns));
ns
}
unsafe extern "C" fn export(
interp: *mut c_void,
ns: *mut TclNamespace,
pattern: *const c_char,
reset_list_first: c_int,
) -> c_int {
entered!("tcl_Export");
let text = String::from_utf8_lossy(CStr::from_ptr(pattern).to_bytes()).into_owned();
note("Export", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
let key = ns as usize;
let slot = match h.exports.iter().position(|(k, _)| *k == key) {
Some(i) => i,
None => {
h.exports.push((key, Vec::new()));
h.exports.len() - 1
}
};
if reset_list_first != 0 {
h.exports[slot].1.clear();
}
if text.contains("::") {
set_result_bytes(
interp,
format!("invalid export pattern \"{text}\": pattern can't specify a namespace")
.as_bytes(),
);
return TCL_ERROR;
}
if !h.exports[slot].1.contains(&text) {
h.exports[slot].1.push(text);
}
TCL_OK
}
unsafe extern "C" fn create_ensemble(
interp: *mut c_void,
name: *const c_char,
_ns: *mut TclNamespace,
_flags: c_int,
) -> *mut c_void {
entered!("tcl_CreateEnsemble");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("CreateEnsemble", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
h.commands.push(Box::new(HostCommand {
name: text,
proc_: ptr::null_mut(),
client_data: ptr::null_mut(),
delete_proc: ptr::null_mut(),
proc2: false,
ensemble_map: ptr::null_mut(),
dying: false,
}));
&mut **h.commands.last_mut().unwrap() as *mut HostCommand as *mut c_void
}
unsafe extern "C" fn set_ensemble_mapping_dict(
_interp: *mut c_void,
token: *mut c_void,
dict: *mut TclObj,
) -> c_int {
entered!("tcl_SetEnsembleMappingDict");
let cmd = &mut *(token as *mut HostCommand);
cmd.ensemble_map = retain(dict);
TCL_OK
}
unsafe extern "C" fn find_ensemble(
_interp: *mut c_void,
name: *mut TclObj,
_flags: c_int,
) -> *mut c_void {
entered!("tcl_FindEnsemble");
note("FindEnsemble", &obj::text_of(name));
ptr::null_mut()
}
unsafe extern "C" fn find_namespace(
interp: *mut c_void,
name: *const c_char,
_context: *mut TclNamespace,
_flags: c_int,
) -> *mut TclNamespace {
entered!("tcl_FindNamespace");
let text = String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned();
note("FindNamespace", &text);
let h = &mut *(*(interp as *mut HostInterp)).host;
h.namespaces
.iter()
.find(|(n, _)| *n == text)
.map(|(_, p)| *p)
.unwrap_or(ptr::null_mut())
}
unsafe fn dup_cstring(s: &str) -> *mut c_char {
let p = libc::malloc(s.len() + 1) as *mut c_char;
assert!(!p.is_null(), "out of memory copying a string");
ptr::copy_nonoverlapping(s.as_ptr() as *const c_char, p, s.len());
*p.add(s.len()) = 0;
p
}
unsafe extern "C" fn register_config(
_interp: *mut c_void,
_pkg: *const c_char,
_cfg: *const c_void,
_enc: *const c_char,
) {
entered!("tcl_RegisterConfig");
}
unsafe extern "C" fn get_string_from_obj(o: *mut TclObj, len: *mut isize) -> *mut c_char {
entered!("tcl_GetStringFromObj");
let b = obj::string_of(o);
if !len.is_null() {
*len = b.len() as isize;
}
(*o).bytes
}
unsafe extern "C" fn list_obj_get_elements(
_interp: *mut c_void,
list: *mut TclObj,
objc: *mut isize,
objv: *mut *mut *mut TclObj,
) -> c_int {
entered!("tcl_ListObjGetElements");
let l = objtype::list_of(list);
*objc = l.elems.len() as isize;
*objv = l.elems.as_mut_ptr();
TCL_OK
}
unsafe extern "C" fn parse_args_objv(
interp: *mut c_void,
arg_table: *const ArgvInfo,
objc_ptr: *mut isize,
objv: *const *mut TclObj,
rem_objv: *mut *mut *mut TclObj,
) -> c_int {
entered!("tcl_ParseArgsObjv");
let want_leftovers = !rem_objv.is_null();
let mut leftovers: Vec<*mut TclObj> = Vec::new();
if want_leftovers {
leftovers.push(*objv);
}
let mut src_index: isize = 1;
let mut dst_index: isize = 1;
let mut objc = *objc_ptr - 1;
let mut rest = false;
while objc > 0 {
let cur = *objv.offset(src_index);
src_index += 1;
objc -= 1;
let text = obj::text_of(cur);
let mut matched: Option<&ArgvInfo> = None;
let mut ambiguous = false;
let mut info = arg_table;
while !info.is_null() && (*info).type_ != TCL_ARGV_END {
let entry = &*info;
if entry.key.is_null() {
info = info.add(1);
continue;
}
let key = CStr::from_ptr(entry.key).to_string_lossy();
if !key.starts_with(text.as_str()) {
info = info.add(1);
continue;
}
if key.len() == text.len() {
matched = Some(entry);
ambiguous = false;
break;
}
if matched.is_some() {
ambiguous = true;
break;
}
matched = Some(entry);
info = info.add(1);
}
if ambiguous {
install_result(
interp,
obj::new_string(format!("ambiguous option \"{text}\"").as_bytes()),
);
return TCL_ERROR;
}
let Some(entry) = matched else {
if !want_leftovers {
install_result(
interp,
obj::new_string(format!("unrecognized argument \"{text}\"").as_bytes()),
);
return TCL_ERROR;
}
dst_index += 1;
leftovers.push(cur);
continue;
};
match entry.type_ {
TCL_ARGV_CONSTANT => *(entry.dst as *mut c_int) = entry.src as c_int,
TCL_ARGV_INT => {
if objc == 0 {
return missing_arg(interp, &text);
}
let next = *objv.offset(src_index);
match objtype::wide_of(next) {
Some(v) if v >= c_int::MIN as i64 && v <= c_int::MAX as i64 => {
*(entry.dst as *mut c_int) = v as c_int;
}
_ => {
let key = CStr::from_ptr(entry.key).to_string_lossy().into_owned();
install_result(
interp,
obj::new_string(
format!(
"expected integer argument for \"{key}\" but got \"{}\"",
obj::text_of(next)
)
.as_bytes(),
),
);
return TCL_ERROR;
}
}
src_index += 1;
objc -= 1;
}
TCL_ARGV_STRING => {
if objc == 0 {
return missing_arg(interp, &text);
}
*(entry.dst as *mut *const c_char) =
(**objv.offset(src_index)).bytes as *const c_char;
src_index += 1;
objc -= 1;
}
TCL_ARGV_REST => {
if !entry.dst.is_null() {
*(entry.dst as *mut c_int) = dst_index as c_int;
}
rest = true;
break;
}
TCL_ARGV_FLOAT => {
if objc == 0 {
return missing_arg(interp, &text);
}
let next = *objv.offset(src_index);
match objtype::double_of(next) {
Some(v) => *(entry.dst as *mut f64) = v,
None => {
let key = CStr::from_ptr(entry.key).to_string_lossy().into_owned();
install_result(
interp,
obj::new_string(
format!(
"expected floating-point argument for \"{key}\" but got \"{}\"",
obj::text_of(next)
)
.as_bytes(),
),
);
return TCL_ERROR;
}
}
src_index += 1;
objc -= 1;
}
TCL_ARGV_FUNC => {
let handler: ArgvFuncProc = std::mem::transmute(entry.src);
let arg = if objc == 0 {
std::ptr::null_mut()
} else {
*objv.offset(src_index)
};
if handler(entry.client_data, arg, entry.dst) != 0 {
src_index += 1;
objc -= 1;
}
}
TCL_ARGV_GENFUNC => {
let handler: ArgvGenFuncProc = std::mem::transmute(entry.src);
let taken = handler(
entry.client_data,
interp,
objc,
objv.offset(src_index),
entry.dst,
);
if taken < 0 {
return TCL_ERROR;
}
src_index += taken;
objc -= taken;
}
TCL_ARGV_HELP => {
install_result(interp, obj::new_string(usage_of(arg_table).as_bytes()));
return TCL_ERROR;
}
other => {
install_result(
interp,
obj::new_string(
format!("bad argument type {other} in Tcl_ArgvInfo").as_bytes(),
),
);
return TCL_ERROR;
}
}
}
let _ = rest;
if !want_leftovers {
return TCL_OK;
}
for i in 0..objc {
leftovers.push(*objv.offset(src_index + i));
}
*objc_ptr = leftovers.len() as isize;
let bytes = (leftovers.len() + 1) * std::mem::size_of::<*mut TclObj>();
let block = libc::malloc(bytes) as *mut *mut TclObj;
for (i, p) in leftovers.iter().enumerate() {
*block.add(i) = *p;
}
*block.add(leftovers.len()) = std::ptr::null_mut();
*rem_objv = block;
TCL_OK
}
unsafe fn missing_arg(interp: *mut c_void, text: &str) -> c_int {
install_result(
interp,
obj::new_string(format!("\"{text}\" option requires an additional argument").as_bytes()),
);
TCL_ERROR
}
unsafe fn usage_of(arg_table: *const ArgvInfo) -> String {
let mut out = String::from("Command-specific options:");
let mut info = arg_table;
while !info.is_null() && (*info).type_ != TCL_ARGV_END {
let entry = &*info;
if !entry.key.is_null() {
out.push_str("\n ");
out.push_str(&CStr::from_ptr(entry.key).to_string_lossy());
if !entry.help.is_null() {
out.push_str(": ");
out.push_str(&CStr::from_ptr(entry.help).to_string_lossy());
}
}
info = info.add(1);
}
out
}
#[repr(C)]
struct InterpState {
status: c_int,
result: *mut TclObj,
}
unsafe extern "C" fn save_interp_state(interp: *mut c_void, status: c_int) -> *mut c_void {
entered!("tcl_SaveInterpState");
let result = retain(get_obj_result(interp));
let state = libc::malloc(std::mem::size_of::<InterpState>()) as *mut InterpState;
(*state) = InterpState { status, result };
state as *mut c_void
}
unsafe extern "C" fn restore_interp_state(interp: *mut c_void, state: *mut c_void) -> c_int {
entered!("tcl_RestoreInterpState");
let state_ptr = state as *mut InterpState;
let status = (*state_ptr).status;
install_result(interp, (*state_ptr).result);
discard_interp_state(state);
status
}
unsafe extern "C" fn discard_interp_state(state: *mut c_void) {
entered!("tcl_DiscardInterpState");
let state_ptr = state as *mut InterpState;
obj::release((*state_ptr).result);
libc::free(state);
}
unsafe extern "C" fn append_obj_to_error_info(interp: *mut c_void, o: *mut TclObj) {
entered!("tcl_AppendObjToErrorInfo");
let message = obj::text_of(o);
let h = &mut *(*(interp as *mut HostInterp)).host;
if h.error_info.is_none() {
h.error_info = Some(obj::text_of(get_obj_result(interp)));
}
if let Some(info) = h.error_info.as_mut() {
info.push_str(&message);
}
obj::release(o);
}
unsafe extern "C" fn background_exception(interp: *mut c_void, code: c_int) {
entered!("tcl_BackgroundException");
if code == TCL_OK {
return;
}
let result = obj::text_of(get_obj_result(interp));
let h = &mut *(*(interp as *mut HostInterp)).host;
let info = h.error_info.clone().unwrap_or_else(|| result.clone());
h.background_errors.push((code, result, info.clone()));
eprintln!("tkbgerror {code} {info}");
h.error_info = None;
}
unsafe extern "C" fn allow_exceptions(_interp: *mut c_void) {
entered!("tcl_AllowExceptions");
}