use std::ffi::{c_char, c_int, c_void, CStr};
use std::ptr;
use super::abi::{RawStub, TclChannelType, TclDString, TclObj, TclStubs, TCL_CHANNEL_VERSION_5};
use super::generated::TCL_NAMES;
use super::trace::{record, Table};
use crate::cmd_channel::{self, Device, Whence, TCL_READABLE, TCL_WRITABLE};
macro_rules! entered {
($name:literal) => {
record(
Table::Tcl,
TCL_NAMES
.iter()
.position(|n| *n == $name)
.expect("no such slot"),
)
};
}
const TCL_OK: c_int = 0;
const TCL_ERROR: c_int = 1;
const TCL_INDEX_NONE: isize = -1;
struct Token {
id: usize,
}
fn token_for(id: usize) -> *mut c_void {
TOKENS.with(|t| {
let mut map = t.borrow_mut();
*map.entry(id)
.or_insert_with(|| Box::into_raw(Box::new(Token { id })) as usize)
as *mut c_void
})
}
unsafe fn id_of(chan: *mut c_void) -> Option<usize> {
if chan.is_null() {
return None;
}
Some((*(chan as *const Token)).id)
}
thread_local! {
static TOKENS: std::cell::RefCell<std::collections::HashMap<usize, usize>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
pub struct Driver {
type_ptr: *const TclChannelType,
instance_data: *mut c_void,
name: String,
closed: bool,
}
impl Driver {
unsafe fn ty(&self) -> &TclChannelType {
&*self.type_ptr
}
}
impl Device for Driver {
fn type_name(&self) -> &str {
&self.name
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize, String> {
unsafe {
let proc = self.ty().input_proc;
if proc.is_null() {
return Err("channel is not readable".to_string());
}
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(*mut c_void, *mut c_char, c_int, *mut c_int) -> c_int,
>(proc);
let mut errno: c_int = 0;
let n = f(
self.instance_data,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
&mut errno,
);
if n < 0 {
return Err(posix_message(errno));
}
Ok(n as usize)
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, String> {
unsafe {
let proc = self.ty().output_proc;
if proc.is_null() {
return Err("channel is not writable".to_string());
}
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_int) -> c_int,
>(proc);
let mut errno: c_int = 0;
let n = f(
self.instance_data,
buf.as_ptr() as *const c_char,
buf.len() as c_int,
&mut errno,
);
if n < 0 {
return Err(posix_message(errno));
}
Ok(n as usize)
}
}
fn seek(&mut self, offset: i64, whence: Whence) -> Result<i64, String> {
unsafe {
let proc = self.ty().wide_seek_proc;
if proc.is_null() {
return Err("illegal seek".to_string());
}
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(*mut c_void, i64, c_int, *mut c_int) -> i64,
>(proc);
let mut errno: c_int = 0;
let at = f(self.instance_data, offset, seek_mode(whence), &mut errno);
if at < 0 {
return Err(posix_message(errno));
}
Ok(at)
}
}
fn seekable(&self) -> bool {
unsafe { !self.ty().wide_seek_proc.is_null() }
}
fn close(&mut self) -> Result<(), String> {
if self.closed {
return Ok(());
}
self.closed = true;
unsafe {
let proc = self.ty().close2_proc;
if proc.is_null() {
return Ok(());
}
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(*mut c_void, *mut c_void, c_int) -> c_int,
>(proc);
let rc = f(
self.instance_data,
super::interp::current() as *mut c_void,
0,
);
if rc != 0 {
return Err(posix_message(rc));
}
Ok(())
}
}
fn handle(&self, direction: i32) -> Option<isize> {
unsafe {
let proc = self.ty().get_handle_proc;
if proc.is_null() {
return None;
}
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(*mut c_void, c_int, *mut *mut c_void) -> c_int,
>(proc);
let mut handle: *mut c_void = ptr::null_mut();
if f(self.instance_data, direction, &mut handle) != TCL_OK {
return None;
}
Some(handle as isize)
}
}
fn watch(&mut self, mask: i32) {
unsafe {
let proc = self.ty().watch_proc;
if proc.is_null() {
return;
}
let f = std::mem::transmute::<*const c_void, unsafe extern "C" fn(*mut c_void, c_int)>(
proc,
);
f(self.instance_data, mask);
}
}
fn set_option(&mut self, name: &str, value: &str) -> Option<Result<(), String>> {
unsafe {
let proc = self.ty().set_option_proc;
if proc.is_null() {
return None;
}
let (name, value) = (c_string(name), c_string(value));
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(
*mut c_void,
*mut c_void,
*const c_char,
*const c_char,
) -> c_int,
>(proc);
let rc = f(
self.instance_data,
super::interp::current() as *mut c_void,
name.as_ptr(),
value.as_ptr(),
);
Some(if rc == TCL_OK {
Ok(())
} else {
Err(format!("the channel driver refused {:?}", name.to_bytes()))
})
}
}
fn get_option(&mut self, name: &str) -> Option<Result<String, String>> {
unsafe {
let proc = self.ty().get_option_proc;
if proc.is_null() {
return None;
}
let name = c_string(name);
let f = std::mem::transmute::<
*const c_void,
unsafe extern "C" fn(
*mut c_void,
*mut c_void,
*const c_char,
*mut TclDString,
) -> c_int,
>(proc);
let mut ds = std::mem::zeroed::<TclDString>();
super::dstring::init(&mut ds);
let rc = f(
self.instance_data,
super::interp::current() as *mut c_void,
name.as_ptr(),
&mut ds,
);
let text = if ds.string.is_null() {
String::new()
} else {
String::from_utf8_lossy(std::slice::from_raw_parts(
ds.string as *const u8,
ds.length.max(0) as usize,
))
.into_owned()
};
super::dstring::free(&mut ds);
Some(if rc == TCL_OK { Ok(text) } else { Err(text) })
}
}
fn driver_table(&self) -> Option<usize> {
Some(self.type_ptr as usize)
}
fn instance_data(&self) -> Option<usize> {
Some(self.instance_data as usize)
}
}
fn c_string(s: &str) -> std::ffi::CString {
std::ffi::CString::new(s).unwrap_or_else(|e| {
let bytes = e.into_vec();
let at = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
std::ffi::CString::new(&bytes[..at]).unwrap_or_default()
})
}
fn seek_mode(whence: Whence) -> c_int {
match whence {
Whence::Start => libc::SEEK_SET,
Whence::Current => libc::SEEK_CUR,
Whence::End => libc::SEEK_END,
}
}
fn posix_message(errno: c_int) -> String {
let text = unsafe {
CStr::from_ptr(libc::strerror(errno))
.to_string_lossy()
.into_owned()
};
let mut chars = text.chars();
match chars.next() {
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
None => text,
}
}
unsafe extern "C" fn create_channel(
type_ptr: *const TclChannelType,
chan_name: *const c_char,
instance_data: *mut c_void,
mask: c_int,
) -> *mut c_void {
entered!("tcl_CreateChannel");
assert!(
!type_ptr.is_null() && !(*type_ptr).type_name.is_null(),
"channel does not have a type name (generic/tclIO.c:1609-1611)"
);
let ty = &*type_ptr;
let name = CStr::from_ptr(ty.type_name).to_string_lossy().into_owned();
assert!(
ty.version == TCL_CHANNEL_VERSION_5,
"channel type {name} must be version TCL_CHANNEL_VERSION_5 \
(generic/tclIO.c:1612-1614); got {}",
ty.version
);
assert!(
!ty.close2_proc.is_null(),
"channel type {name} must define close2Proc (generic/tclIO.c:1615-1617)"
);
assert!(
mask & TCL_READABLE == 0 || !ty.input_proc.is_null(),
"channel type {name} must define inputProc when used for reader channel \
(generic/tclIO.c:1618-1620)"
);
assert!(
mask & TCL_WRITABLE == 0 || !ty.output_proc.is_null(),
"channel type {name} must define outputProc when used for writer channel \
(generic/tclIO.c:1621-1623)"
);
assert!(
!ty.watch_proc.is_null(),
"channel type {name} must define watchProc (generic/tclIO.c:1624-1626)"
);
let chan_name = if chan_name.is_null() {
String::new()
} else {
CStr::from_ptr(chan_name).to_string_lossy().into_owned()
};
let id = cmd_channel::create(
&chan_name,
Box::new(Driver {
type_ptr,
instance_data,
name,
closed: false,
}),
mask,
);
cmd_channel::adopt_empty_std_slot(id);
token_for(id)
}
unsafe extern "C" fn register_channel(_interp: *mut c_void, chan: *mut c_void) {
entered!("tcl_RegisterChannel");
if let Some(id) = id_of(chan) {
cmd_channel::register(id);
}
}
unsafe extern "C" fn unregister_channel(_interp: *mut c_void, chan: *mut c_void) -> c_int {
entered!("tcl_UnregisterChannel");
match id_of(chan) {
Some(id) => match cmd_channel::unregister(id) {
Ok(()) => TCL_OK,
Err(_) => TCL_ERROR,
},
None => TCL_OK,
}
}
unsafe extern "C" fn get_channel(
_interp: *mut c_void,
chan_name: *const c_char,
mode_ptr: *mut c_int,
) -> *mut c_void {
entered!("tcl_GetChannel");
let name = CStr::from_ptr(chan_name).to_string_lossy().into_owned();
match cmd_channel::lookup(&name) {
Some(id) => {
if !mode_ptr.is_null() {
*mode_ptr = cmd_channel::mode_of(id);
}
token_for(id)
}
None => ptr::null_mut(),
}
}
unsafe extern "C" fn get_std_channel(kind: c_int) -> *mut c_void {
entered!("tcl_GetStdChannel");
match cmd_channel::std_channel(kind) {
Some(id) => token_for(id),
None => ptr::null_mut(),
}
}
unsafe extern "C" fn set_std_channel(chan: *mut c_void, kind: c_int) {
entered!("tcl_SetStdChannel");
cmd_channel::set_std(kind, id_of(chan));
}
unsafe extern "C" fn write_chars(chan: *mut c_void, src: *const c_char, len: isize) -> isize {
entered!("tcl_WriteChars");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
let bytes = super::host::c_bytes_of(src, len);
match cmd_channel::write_bytes(id, bytes) {
Ok(()) => bytes.len() as isize,
Err(_) => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn write_obj(chan: *mut c_void, obj: *mut TclObj) -> isize {
entered!("tcl_WriteObj");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
let bytes = super::host::obj_bytes_of(obj);
match cmd_channel::write_bytes(id, bytes) {
Ok(()) => bytes.len() as isize,
Err(_) => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn write_bytes_slot(chan: *mut c_void, src: *const c_char, len: isize) -> isize {
entered!("tcl_Write");
write_chars(chan, src, len)
}
unsafe extern "C" fn read_chars(
chan: *mut c_void,
obj: *mut TclObj,
chars_to_read: isize,
append_flag: c_int,
) -> isize {
entered!("tcl_ReadChars");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
let want = if chars_to_read < 0 {
None
} else {
Some(chars_to_read as i64)
};
match cmd_channel::read_chars(id, want) {
Ok(text) => {
if append_flag != 0 {
super::obj::append_bytes(obj, text.as_bytes());
} else {
super::obj::set_string(obj, text.as_bytes());
}
text.chars().count() as isize
}
Err(_) => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn read_bytes_slot(chan: *mut c_void, buf: *mut c_char, to_read: isize) -> isize {
entered!("tcl_Read");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
match cmd_channel::read_chars(id, Some(to_read as i64)) {
Ok(text) => {
let bytes = text.as_bytes();
let n = bytes.len().min(to_read.max(0) as usize);
ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, n);
n as isize
}
Err(_) => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn gets_obj(chan: *mut c_void, obj: *mut TclObj) -> isize {
entered!("tcl_GetsObj");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
match cmd_channel::gets(id) {
Ok(Some(line)) => {
super::obj::set_string(obj, line.as_bytes());
line.chars().count() as isize
}
_ => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn gets_dstring(chan: *mut c_void, ds: *mut TclDString) -> isize {
entered!("tcl_Gets");
let Some(id) = id_of(chan) else {
return TCL_INDEX_NONE;
};
match cmd_channel::gets(id) {
Ok(Some(line)) => {
super::dstring::append(ds, line.as_ptr() as *const c_char, line.len() as isize);
line.chars().count() as isize
}
_ => TCL_INDEX_NONE,
}
}
unsafe extern "C" fn flush_channel(chan: *mut c_void) -> c_int {
entered!("tcl_Flush");
match id_of(chan) {
Some(id) if cmd_channel::flush(id).is_ok() => TCL_OK,
Some(_) => TCL_ERROR,
None => TCL_ERROR,
}
}
unsafe extern "C" fn close_channel(_interp: *mut c_void, chan: *mut c_void) -> c_int {
entered!("tcl_Close");
match id_of(chan) {
Some(id) => {
TOKENS.with(|t| t.borrow_mut().remove(&id));
match cmd_channel::close(id) {
Ok(()) => TCL_OK,
Err(_) => TCL_ERROR,
}
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn close_ex(interp: *mut c_void, chan: *mut c_void, flags: c_int) -> c_int {
entered!("tcl_CloseEx");
if flags == 0 {
return close_channel(interp, chan);
}
match id_of(chan) {
Some(id) => match cmd_channel::half_close(id, flags) {
Ok(()) => TCL_OK,
Err(_) => TCL_ERROR,
},
None => TCL_ERROR,
}
}
unsafe extern "C" fn get_channel_name(chan: *mut c_void) -> *const c_char {
entered!("tcl_GetChannelName");
match id_of(chan).and_then(cmd_channel::name_of) {
Some(name) => NAMES.with(|n| {
let mut map = n.borrow_mut();
let id = id_of(chan).unwrap_or(0);
*map.entry(id).or_insert_with(|| {
let c = std::ffi::CString::new(name).unwrap_or_default();
c.into_raw() as usize
}) as *const c_char
}),
None => ptr::null(),
}
}
thread_local! {
static NAMES: std::cell::RefCell<std::collections::HashMap<usize, usize>> =
std::cell::RefCell::new(std::collections::HashMap::new());
}
unsafe extern "C" fn get_channel_type(chan: *mut c_void) -> *const TclChannelType {
entered!("tcl_GetChannelType");
id_of(chan)
.and_then(cmd_channel::driver_table)
.map_or(ptr::null(), |a| a as *const TclChannelType)
}
unsafe extern "C" fn get_channel_instance_data(chan: *mut c_void) -> *mut c_void {
entered!("tcl_GetChannelInstanceData");
id_of(chan)
.and_then(cmd_channel::instance_data)
.map_or(ptr::null_mut(), |a| a as *mut c_void)
}
unsafe extern "C" fn get_channel_mode(chan: *mut c_void) -> c_int {
entered!("tcl_GetChannelMode");
id_of(chan).map_or(0, cmd_channel::mode_of)
}
unsafe extern "C" fn get_channel_handle(
chan: *mut c_void,
direction: c_int,
handle_ptr: *mut *mut c_void,
) -> c_int {
entered!("tcl_GetChannelHandle");
match id_of(chan).and_then(|id| cmd_channel::handle_of(id, direction)) {
Some(handle) => {
if !handle_ptr.is_null() {
*handle_ptr = handle as *mut c_void;
}
TCL_OK
}
None => TCL_ERROR,
}
}
unsafe extern "C" fn set_channel_option(
_interp: *mut c_void,
chan: *mut c_void,
option_name: *const c_char,
new_value: *const c_char,
) -> c_int {
entered!("tcl_SetChannelOption");
let Some(id) = id_of(chan) else {
return TCL_ERROR;
};
let name = CStr::from_ptr(option_name).to_string_lossy().into_owned();
let value = CStr::from_ptr(new_value).to_string_lossy().into_owned();
match cmd_channel::set_channel_option(id, &name, &value) {
Ok(()) => TCL_OK,
Err(_) => TCL_ERROR,
}
}
unsafe extern "C" fn get_channel_option(
_interp: *mut c_void,
chan: *mut c_void,
option_name: *const c_char,
ds: *mut TclDString,
) -> c_int {
entered!("tcl_GetChannelOption");
let Some(id) = id_of(chan) else {
return TCL_ERROR;
};
let answer = if option_name.is_null() {
cmd_channel::all_options(id)
} else {
cmd_channel::get_channel_option(id, &CStr::from_ptr(option_name).to_string_lossy())
};
match answer {
Ok(text) => {
super::dstring::append(ds, text.as_ptr() as *const c_char, text.len() as isize);
TCL_OK
}
Err(_) => TCL_ERROR,
}
}
unsafe extern "C" fn get_channel_buffer_size(chan: *mut c_void) -> isize {
entered!("tcl_GetChannelBufferSize");
id_of(chan).map_or(0, |id| cmd_channel::buffer_size(id) as isize)
}
unsafe extern "C" fn set_channel_buffer_size(chan: *mut c_void, size: isize) {
entered!("tcl_SetChannelBufferSize");
if let Some(id) = id_of(chan) {
cmd_channel::set_buffer_size(id, size as i64);
}
}
unsafe extern "C" fn seek_channel(chan: *mut c_void, offset: i64, mode: c_int) -> i64 {
entered!("tcl_Seek");
let whence = match mode {
m if m == libc::SEEK_CUR => Whence::Current,
m if m == libc::SEEK_END => Whence::End,
_ => Whence::Start,
};
match id_of(chan) {
Some(id) => cmd_channel::seek(id, offset, whence).map_or(-1, |at| at),
None => -1,
}
}
unsafe extern "C" fn tell_channel(chan: *mut c_void) -> i64 {
entered!("tcl_Tell");
id_of(chan).map_or(-1, |id| cmd_channel::tell(id).unwrap_or(-1))
}
unsafe extern "C" fn eof_channel(chan: *mut c_void) -> c_int {
entered!("tcl_Eof");
id_of(chan).map_or(0, |id| c_int::from(cmd_channel::at_eof(id)))
}
unsafe extern "C" fn input_buffered(chan: *mut c_void) -> c_int {
entered!("tcl_InputBuffered");
id_of(chan).map_or(0, |id| cmd_channel::input_buffered(id) as c_int)
}
unsafe extern "C" fn channel_buffered(chan: *mut c_void) -> c_int {
entered!("tcl_ChannelBuffered");
id_of(chan).map_or(0, |id| cmd_channel::output_buffered(id) as c_int)
}
unsafe extern "C" fn create_channel_handler(
chan: *mut c_void,
mask: c_int,
proc: *mut c_void,
client_data: *mut c_void,
) {
entered!("tcl_CreateChannelHandler");
if let Some(id) = id_of(chan) {
cmd_channel::create_channel_handler(id, mask, proc as usize, client_data as usize);
}
}
unsafe extern "C" fn delete_channel_handler(
chan: *mut c_void,
proc: *mut c_void,
client_data: *mut c_void,
) {
entered!("tcl_DeleteChannelHandler");
if let Some(id) = id_of(chan) {
cmd_channel::delete_channel_handler(id, proc as usize, client_data as usize);
}
}
unsafe extern "C" fn notify_channel(chan: *mut c_void, mask: c_int) {
entered!("tcl_NotifyChannel");
let Some(id) = id_of(chan) else { return };
for (proc, client_data, hit) in cmd_channel::handlers_for(id, mask) {
let f = std::mem::transmute::<usize, unsafe extern "C" fn(*mut c_void, c_int)>(proc);
f(client_data as *mut c_void, hit);
}
}
unsafe extern "C" fn bad_channel_option(
interp: *mut c_void,
option_name: *const c_char,
_option_list: *const c_char,
) -> c_int {
entered!("tcl_BadChannelOption");
let name = CStr::from_ptr(option_name).to_string_lossy();
let message = format!("bad option \"{name}\": should be one of -blocking, -buffering, -buffersize, -encoding, -eofchar, -profile, -translation");
super::host::set_result_bytes(interp, message.as_bytes());
TCL_ERROR
}
unsafe extern "C" fn is_channel_shared(chan: *mut c_void) -> c_int {
entered!("tcl_IsChannelShared");
id_of(chan).map_or(0, |id| c_int::from(cmd_channel::ref_count(id) > 1))
}
unsafe extern "C" fn is_channel_registered(_interp: *mut c_void, chan: *mut c_void) -> c_int {
entered!("tcl_IsChannelRegistered");
id_of(chan).map_or(0, |id| c_int::from(cmd_channel::ref_count(id) > 0))
}
unsafe extern "C" fn open_file_channel(
_interp: *mut c_void,
file_name: *const c_char,
mode_string: *const c_char,
_permissions: c_int,
) -> *mut c_void {
entered!("tcl_OpenFileChannel");
let path = CStr::from_ptr(file_name).to_string_lossy().into_owned();
let mode = if mode_string.is_null() {
"r".to_string()
} else {
CStr::from_ptr(mode_string).to_string_lossy().into_owned()
};
match cmd_channel::open_file(&path, &mode) {
Ok(name) => match cmd_channel::lookup(&name) {
Some(id) => token_for(id),
None => ptr::null_mut(),
},
Err(_) => ptr::null_mut(),
}
}
pub unsafe fn install_impls(t: &mut TclStubs) -> Vec<usize> {
vec![
install(t, "tcl_BadChannelOption", bad_channel_option as *const ()),
install(t, "tcl_Close", close_channel as *const ()),
install(t, "tcl_CreateChannel", create_channel as *const ()),
install(
t,
"tcl_CreateChannelHandler",
create_channel_handler as *const (),
),
install(
t,
"tcl_DeleteChannelHandler",
delete_channel_handler as *const (),
),
install(t, "tcl_Eof", eof_channel as *const ()),
install(t, "tcl_Flush", flush_channel as *const ()),
install(t, "tcl_GetChannel", get_channel as *const ()),
install(
t,
"tcl_GetChannelBufferSize",
get_channel_buffer_size as *const (),
),
install(t, "tcl_GetChannelHandle", get_channel_handle as *const ()),
install(
t,
"tcl_GetChannelInstanceData",
get_channel_instance_data as *const (),
),
install(t, "tcl_GetChannelMode", get_channel_mode as *const ()),
install(t, "tcl_GetChannelName", get_channel_name as *const ()),
install(t, "tcl_GetChannelOption", get_channel_option as *const ()),
install(t, "tcl_GetChannelType", get_channel_type as *const ()),
install(t, "tcl_Gets", gets_dstring as *const ()),
install(t, "tcl_GetsObj", gets_obj as *const ()),
install(t, "tcl_GetStdChannel", get_std_channel as *const ()),
install(t, "tcl_InputBuffered", input_buffered as *const ()),
install(t, "tcl_NotifyChannel", notify_channel as *const ()),
install(t, "tcl_OpenFileChannel", open_file_channel as *const ()),
install(t, "tcl_Read", read_bytes_slot as *const ()),
install(t, "tcl_RegisterChannel", register_channel as *const ()),
install(
t,
"tcl_SetChannelBufferSize",
set_channel_buffer_size as *const (),
),
install(t, "tcl_SetChannelOption", set_channel_option as *const ()),
install(t, "tcl_SetStdChannel", set_std_channel as *const ()),
install(t, "tcl_UnregisterChannel", unregister_channel as *const ()),
install(t, "tcl_Write", write_bytes_slot as *const ()),
install(t, "tcl_ReadChars", read_chars as *const ()),
install(t, "tcl_WriteChars", write_chars as *const ()),
install(t, "tcl_WriteObj", write_obj as *const ()),
install(t, "tcl_ChannelBuffered", channel_buffered as *const ()),
install(t, "tcl_IsChannelShared", is_channel_shared as *const ()),
install(
t,
"tcl_IsChannelRegistered",
is_channel_registered as *const (),
),
install(t, "tcl_Seek", seek_channel as *const ()),
install(t, "tcl_Tell", tell_channel as *const ()),
install(t, "tcl_CloseEx", close_ex as *const ()),
]
}
unsafe fn install(t: &mut TclStubs, name: &str, f: *const ()) -> usize {
let i = TCL_NAMES
.iter()
.position(|n| *n == name)
.unwrap_or_else(|| panic!("no slot named {name} in TclStubs"));
t.slots[i] = std::mem::transmute::<*const (), RawStub>(f);
i
}