use std::ffi::{c_char, c_int, c_void, CStr};
use std::ptr;
use super::abi::{RawStub, TclObj, TclObjInternalRep, TclObjType, TclStubs, TCL_ERROR, TCL_OK};
use super::generated::TCL_NAMES;
use super::trace::{record, Table};
use super::{host, obj, objtype};
macro_rules! entered {
($name:literal) => {
record(
Table::Tcl,
TCL_NAMES
.iter()
.position(|n| *n == $name)
.expect("no such slot"),
)
};
}
const TCL_EXACT: c_int = 1;
const TCL_NULL_OK: c_int = 32;
const TCL_INDEX_TEMP_TABLE: c_int = 64;
const TCL_INDEX_NONE: isize = -1;
const WIDTH_MASK: c_int = 30 - ((size_of::<c_int>() as c_int) << 1);
#[repr(C)]
struct IndexRep {
table_ptr: *const c_void,
offset: isize,
index: isize,
}
static INDEX_TYPE: TclObjType = TclObjType {
name: c"index".as_ptr(),
free_internal_rep_proc: free_index as *const c_void,
dup_internal_rep_proc: dup_index as *const c_void,
update_string_proc: update_string_of_index as *const c_void,
set_from_any_proc: ptr::null(),
version: 0,
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(),
};
unsafe fn string_at(table: *const c_void, offset: isize) -> *const c_char {
*(table.byte_offset(offset) as *const *const c_char)
}
unsafe fn entry_bytes(p: *const c_char) -> &'static [u8] {
CStr::from_ptr(p).to_bytes()
}
unsafe extern "C" fn free_index(o: *mut TclObj) {
let rep = (*o).internal_rep.ptr1 as *mut IndexRep;
if !rep.is_null() {
drop(Box::from_raw(rep));
}
(*o).type_ptr = ptr::null();
}
unsafe extern "C" fn dup_index(src: *mut TclObj, dup: *mut TclObj) {
let from = (*src).internal_rep.ptr1 as *const IndexRep;
let copy = Box::into_raw(Box::new(IndexRep {
table_ptr: (*from).table_ptr,
offset: (*from).offset,
index: (*from).index,
}));
let ir = TclObjInternalRep {
ptr1: copy as *mut c_void,
ptr2: ptr::null_mut(),
};
objtype::store_internal_rep(dup, &INDEX_TYPE, &ir);
}
unsafe extern "C" fn update_string_of_index(o: *mut TclObj) {
let rep = &*((*o).internal_rep.ptr1 as *const IndexRep);
let text: &[u8] = if rep.index != TCL_INDEX_NONE {
entry_bytes(string_at(rep.table_ptr, rep.offset * rep.index))
} else {
b""
};
obj::init_string_rep(o, text.as_ptr() as *const c_char, text.len());
}
pub unsafe extern "C" fn get_index_from_obj_struct(
interp: *mut c_void,
o: *mut TclObj,
table: *const c_void,
offset: isize,
msg: *const c_char,
flags: c_int,
index_out: *mut c_void,
) -> c_int {
entered!("tcl_GetIndexFromObjStruct");
if offset < size_of::<*const c_char>() as isize {
if !interp.is_null() {
host::set_result_bytes(
interp,
format!("Invalid struct offset value {offset}.").as_bytes(),
);
}
return TCL_ERROR;
}
let cacheable = !o.is_null() && (flags & TCL_INDEX_TEMP_TABLE) == 0;
let mut index = TCL_INDEX_NONE;
let mut cached = false;
if cacheable {
let ir = objtype::fetch_internal_rep(o, &INDEX_TYPE);
if !ir.is_null() {
let rep = &*((*ir).ptr1 as *const IndexRep);
if rep.table_ptr == table && rep.offset == offset && rep.index != TCL_INDEX_NONE {
index = rep.index;
cached = true;
}
}
}
if !cached {
let key: &[u8] = if o.is_null() {
b""
} else {
let all = obj::string_of(o);
match all.iter().position(|b| *b == 0) {
Some(n) => &all[..n],
None => all,
}
};
if !(key.is_empty() && (flags & TCL_NULL_OK) != 0) {
let mut num_abbrev = 0usize;
let mut idx: isize = 0;
let mut exact = false;
loop {
let entry = string_at(table, offset * idx);
if entry.is_null() {
break;
}
let bytes = entry_bytes(entry);
if bytes == key {
index = idx;
exact = true;
break;
}
if bytes.starts_with(key) {
num_abbrev += 1;
index = idx;
}
idx += 1;
}
if !exact && ((flags & TCL_EXACT) != 0 || key.is_empty() || num_abbrev != 1) {
return error(interp, table, offset, msg, key, num_abbrev, flags);
}
}
if cacheable && index != TCL_INDEX_NONE {
let ir = objtype::fetch_internal_rep(o, &INDEX_TYPE);
if ir.is_null() {
let rep = Box::into_raw(Box::new(IndexRep {
table_ptr: table,
offset,
index,
}));
let new = TclObjInternalRep {
ptr1: rep as *mut c_void,
ptr2: ptr::null_mut(),
};
objtype::store_internal_rep(o, &INDEX_TYPE, &new);
} else {
let rep = &mut *((*ir).ptr1 as *mut IndexRep);
rep.table_ptr = table;
rep.offset = offset;
rep.index = index;
}
}
}
if !index_out.is_null() {
match flags & WIDTH_MASK {
w if w == (size_of::<u16>() as c_int) << 1 => *(index_out as *mut u16) = index as u16,
w if w == (size_of::<u8>() as c_int) << 1 => *(index_out as *mut u8) = index as u8,
w if w == (size_of::<i64>() as c_int) << 1 => *(index_out as *mut i64) = index as i64,
w if w == (size_of::<i32>() as c_int) << 1 => *(index_out as *mut i32) = index as i32,
_ => *(index_out as *mut c_int) = index as c_int,
}
}
TCL_OK
}
unsafe fn error(
interp: *mut c_void,
table: *const c_void,
offset: isize,
msg: *const c_char,
key: &[u8],
num_abbrev: usize,
flags: c_int,
) -> c_int {
if interp.is_null() {
return TCL_ERROR;
}
let what = if msg.is_null() {
Vec::new()
} else {
entry_bytes(msg).to_vec()
};
let mut idx: isize = 0;
loop {
let e = string_at(table, offset * idx);
if e.is_null() || !entry_bytes(e).is_empty() {
break;
}
idx += 1;
}
let mut out: Vec<u8> = Vec::new();
out.extend_from_slice(if num_abbrev > 1 && (flags & TCL_EXACT) == 0 {
b"ambiguous ".as_slice()
} else {
b"bad ".as_slice()
});
out.extend_from_slice(&what);
out.extend_from_slice(b" \"");
out.extend_from_slice(key);
if string_at(table, offset * idx).is_null() {
out.extend_from_slice(b"\": no valid options");
} else {
out.extend_from_slice(b"\": must be ");
out.extend_from_slice(entry_bytes(string_at(table, offset * idx)));
idx += 1;
let mut count = 0usize;
loop {
let entry = string_at(table, offset * idx);
if entry.is_null() {
break;
}
let bytes = entry_bytes(entry);
let last = string_at(table, offset * (idx + 1)).is_null();
if last && (flags & TCL_NULL_OK) == 0 {
if count > 0 {
out.extend_from_slice(b",");
}
out.extend_from_slice(b" or ");
out.extend_from_slice(bytes);
} else if !bytes.is_empty() {
out.extend_from_slice(b", ");
out.extend_from_slice(bytes);
count += 1;
}
idx += 1;
}
if (flags & TCL_NULL_OK) != 0 {
out.extend_from_slice(b", or \"\"");
}
}
host::set_result_bytes(interp, &out);
TCL_ERROR
}
pub unsafe fn install_impls(t: &mut TclStubs) -> Vec<usize> {
vec![
install(
t,
"tcl_GetIndexFromObjStruct",
get_index_from_obj_struct 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
}