use std::ffi::c_char;
use super::abi::{RawStub, TclDString, TclStubs};
use super::dstring;
use super::generated::TCL_NAMES;
use super::trace::{record, Table};
macro_rules! entered {
($name:literal) => {
record(
Table::Tcl,
TCL_NAMES
.iter()
.position(|n| *n == $name)
.expect("no such slot"),
)
};
}
const CP1252: [u16; 32] = [
0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039,
0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
];
const UNICODE_SELF: u16 = 0x80;
fn complete(byte: u8) -> usize {
match byte {
0x00..=0x7F => 1,
0x80..=0xBF => 3,
0xC1 => 1,
0xC0 | 0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => 1,
}
}
fn utf_char_complete(src: &[u8]) -> bool {
!src.is_empty() && src.len() >= complete(src[0])
}
fn utf_to_char16(src: &[u8], ch: &mut u16) -> usize {
let byte = src[0];
let at = |i: usize| -> u8 { *src.get(i).unwrap_or(&0) };
if byte < 0xC0 {
if (byte & 0xC0) == 0x80
&& (at(1) & 0xC0) == 0x80
&& (at(2) & 0xC0) == 0x80
&& ((((byte as u16).wrapping_sub(0x10) << 2) & 0xFC) | 0xD800) == (*ch & 0xFCFC)
&& (at(1) & 0xF0) == (((*ch << 4) & 0x30) as u8 | 0x80)
{
*ch = (((at(1) & 0x0F) as u16) << 6) + (at(2) & 0x3F) as u16 + 0xDC00;
return 3;
}
*ch = if (byte as u16).wrapping_sub(0x80) < 0x20 {
CP1252[(byte - 0x80) as usize]
} else {
byte as u16
};
return 1;
}
if byte < 0xE0 {
if byte != 0xC1 && (at(1) & 0xC0) == 0x80 {
let v = (((byte & 0x1F) as u16) << 6) | (at(1) & 0x3F) as u16;
if v.wrapping_sub(1) >= UNICODE_SELF - 1 {
*ch = v;
return 2;
}
}
} else if byte < 0xF0 {
if (at(1) & 0xC0) == 0x80 && (at(2) & 0xC0) == 0x80 {
let v = (((byte & 0x0F) as u16) << 12)
| (((at(1) & 0x3F) as u16) << 6)
| (at(2) & 0x3F) as u16;
if v > 0x7FF {
*ch = v;
return 3;
}
}
} else if byte < 0xF5 {
if (at(1) & 0xC0) == 0x80 && (at(2) & 0xC0) == 0x80 {
let high = ((((byte & 0x07) as u16) << 8)
| (((at(1) & 0x3F) as u16) << 2)
| ((at(2) & 0x3F) >> 4) as u16)
.wrapping_sub(0x40);
if high < 0x400 {
*ch = 0xD800 + high;
return 1;
}
}
}
*ch = byte as u16;
1
}
pub unsafe extern "C" fn utf_to_char16_dstring(
src: *const c_char,
length: isize,
ds: *mut TclDString,
) -> *mut u16 {
entered!("tcl_UtfToChar16DString");
if src.is_null() {
return std::ptr::null_mut();
}
let len = if length < 0 {
libc::strlen(src)
} else {
length as usize
};
let bytes = std::slice::from_raw_parts(src as *const u8, len);
let old_length = (*ds).length as usize;
dstring::set_length(ds, (old_length + (len + 1) * size_of::<u16>()) as isize);
let w_start = (*ds).string.add(old_length) as *mut u16;
let mut w = w_start;
let mut ch: u16 = 0;
let mut p = 0usize;
let opt = len.saturating_sub(3);
while p <= opt && p < len {
p += utf_to_char16(&bytes[p..], &mut ch);
*w = ch;
w = w.add(1);
}
while p < len {
if utf_char_complete(&bytes[p..]) {
p += utf_to_char16(&bytes[p..], &mut ch);
*w = ch;
} else {
*w = bytes[p] as u16;
p += 1;
}
w = w.add(1);
}
*w = 0;
let written = w as usize - w_start as usize;
dstring::set_length(ds, (old_length + written) as isize);
w_start
}
fn utf_to_uni_char(src: &[u8], ch: &mut i32) -> usize {
let byte = src[0];
let at = |i: usize| -> u8 { *src.get(i).unwrap_or(&0) };
if byte < 0xC0 {
*ch = if (byte as u32).wrapping_sub(0x80) < 0x20 {
CP1252[(byte - 0x80) as usize] as i32
} else {
byte as i32
};
return 1;
}
if byte < 0xE0 {
if byte != 0xC1 && (at(1) & 0xC0) == 0x80 {
let v = (((byte & 0x1F) as i32) << 6) | (at(1) & 0x3F) as i32;
if (v - 1) as u32 >= (UNICODE_SELF as u32) - 1 {
*ch = v;
return 2;
}
}
} else if byte < 0xF0 {
if (at(1) & 0xC0) == 0x80 && (at(2) & 0xC0) == 0x80 {
let v = (((byte & 0x0F) as i32) << 12)
| (((at(1) & 0x3F) as i32) << 6)
| (at(2) & 0x3F) as i32;
if v > 0x7FF {
*ch = v;
return 3;
}
}
} else if byte < 0xF5 {
if (at(1) & 0xC0) == 0x80 && (at(2) & 0xC0) == 0x80 && (at(3) & 0xC0) == 0x80 {
let v = (((byte & 0x07) as i32) << 18)
| (((at(1) & 0x3F) as i32) << 12)
| (((at(2) & 0x3F) as i32) << 6)
| (at(3) & 0x3F) as i32;
if (v - 0x10000) as u32 <= 0xFFFFF {
*ch = v;
return 4;
}
}
}
*ch = byte as i32;
1
}
pub unsafe extern "C" fn utf_to_uni_char_slot(src: *const c_char, ch: *mut i32) -> isize {
entered!("tcl_UtfToUniChar");
let bytes = std::slice::from_raw_parts(src as *const u8, 4.min(libc::strlen(src) + 1));
let mut v: i32 = 0;
let n = utf_to_uni_char(bytes, &mut v);
if !ch.is_null() {
*ch = v;
}
n as isize
}
pub unsafe extern "C" fn num_utf_chars_cp(src: *const c_char, length: isize) -> isize {
entered!("tcl_NumUtfChars");
if src.is_null() {
return 0;
}
let len = if length < 0 {
libc::strlen(src)
} else {
length as usize
};
let bytes = std::slice::from_raw_parts(src as *const u8, len);
let mut ch: i32 = 0;
let mut p = 0usize;
let mut count: isize = 0;
let opt = len.saturating_sub(4);
while p <= opt && p < len {
p += utf_to_uni_char(&bytes[p..], &mut ch);
count += 1;
}
while p < len {
if utf_char_complete(&bytes[p..]) {
p += utf_to_uni_char(&bytes[p..], &mut ch);
} else {
p += 1;
}
count += 1;
}
count
}
pub unsafe extern "C" fn utf_at_index_cp(src: *const c_char, index: isize) -> *const c_char {
entered!("tcl_UtfAtIndex");
if src.is_null() {
return src;
}
let mut at = 0usize;
let mut ch: i32 = 0;
let mut remaining = index;
while remaining > 0 {
let rest =
std::slice::from_raw_parts(src.add(at) as *const u8, libc::strlen(src.add(at)) + 1);
at += utf_to_uni_char(rest, &mut ch);
remaining -= 1;
}
src.add(at)
}
pub unsafe extern "C" fn num_utf_chars(src: *const c_char, length: isize) -> isize {
entered!("tclNumUtfChars");
if src.is_null() {
return 0;
}
let len = if length < 0 {
libc::strlen(src)
} else {
length as usize
};
let bytes = std::slice::from_raw_parts(src as *const u8, len);
let mut ch: u16 = 0;
let mut p = 0usize;
let mut count: isize = 0;
let opt = len.saturating_sub(4);
while p <= opt && p < len {
p += utf_to_char16(&bytes[p..], &mut ch);
count += 1;
}
while p < len {
if utf_char_complete(&bytes[p..]) {
p += utf_to_char16(&bytes[p..], &mut ch);
} else {
p += 1;
}
count += 1;
}
count
}
pub unsafe extern "C" fn utf_at_index(src: *const c_char, index: isize) -> *const c_char {
entered!("tclUtfAtIndex");
if src.is_null() {
return src;
}
let mut at = 0usize;
let mut ch: u16 = 0;
let mut len = 0usize;
let rest = |p: usize| -> &'static [u8] {
std::slice::from_raw_parts(src.add(p) as *const u8, libc::strlen(src.add(p)) + 1)
};
let mut remaining = index;
while remaining > 0 {
len = utf_to_char16(rest(at), &mut ch);
at += len;
remaining -= 1;
}
if index > 0 && ch >= 0xD800 && len < 3 {
at += utf_to_char16(rest(at), &mut ch);
}
src.add(at)
}
pub unsafe fn install_impls(t: &mut TclStubs) -> Vec<usize> {
vec![
install(
t,
"tcl_UtfToChar16DString",
utf_to_char16_dstring as *const (),
),
install(t, "tclNumUtfChars", num_utf_chars as *const ()),
install(t, "tclUtfAtIndex", utf_at_index as *const ()),
install(t, "tcl_UtfToUniChar", utf_to_uni_char_slot as *const ()),
install(t, "tcl_NumUtfChars", num_utf_chars_cp as *const ()),
install(t, "tcl_UtfAtIndex", utf_at_index_cp 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
}