use std::ffi::{c_char, c_void};
use std::ptr;
use super::abi::{TclDString, TclObj, TCL_DSTRING_STATIC_SIZE};
use super::obj;
pub unsafe fn init(ds: *mut TclDString) {
(*ds).string = (*ds).static_space.as_mut_ptr();
(*ds).length = 0;
(*ds).space_avl = TCL_DSTRING_STATIC_SIZE as isize;
(*ds).static_space[0] = 0;
}
unsafe fn is_static(ds: *mut TclDString) -> bool {
ptr::eq((*ds).string, (*ds).static_space.as_ptr())
}
unsafe fn reserve(ds: *mut TclDString, need: isize, alias: *const c_char) -> *const c_char {
if need <= (*ds).space_avl {
return alias;
}
let cap = (need * 2).max(TCL_DSTRING_STATIC_SIZE as isize);
let offset = if !alias.is_null()
&& alias >= (*ds).string
&& alias <= (*ds).string.offset((*ds).length)
{
Some(alias.offset_from((*ds).string))
} else {
None
};
let fresh = if is_static(ds) {
let p = libc::malloc(cap as usize) as *mut c_char;
assert!(!p.is_null(), "out of memory growing a Tcl_DString");
ptr::copy_nonoverlapping((*ds).string, p, (*ds).length as usize);
p
} else {
let p = libc::realloc((*ds).string as *mut c_void, cap as usize) as *mut c_char;
assert!(!p.is_null(), "out of memory growing a Tcl_DString");
p
};
(*ds).string = fresh;
(*ds).space_avl = cap;
match offset {
Some(n) => fresh.offset(n),
None => alias,
}
}
pub unsafe fn append(ds: *mut TclDString, bytes: *const c_char, len: isize) -> *mut c_char {
let n = if len < 0 {
if bytes.is_null() {
0
} else {
libc::strlen(bytes) as isize
}
} else {
len
};
let src = reserve(ds, (*ds).length + n + 1, bytes);
if n > 0 {
ptr::copy_nonoverlapping(src, (*ds).string.offset((*ds).length), n as usize);
}
(*ds).length += n;
*(*ds).string.offset((*ds).length) = 0;
(*ds).string
}
unsafe fn bytes_of(ds: *mut TclDString) -> &'static [u8] {
std::slice::from_raw_parts((*ds).string as *const u8, (*ds).length as usize)
}
fn need_space(text: &[u8]) -> bool {
let mut end = text.len();
while end > 0 && text[end - 1] == b'{' {
end -= 1;
}
if end == 0 {
return false;
}
let last = text[end - 1];
if crate::list::is_space(last) {
let mut i = end - 1;
let mut escaped = false;
while i > 0 && text[i - 1] == b'\\' {
escaped = !escaped;
i -= 1;
}
return escaped;
}
true
}
pub unsafe fn append_element(ds: *mut TclDString, element: *const c_char) -> *mut c_char {
let needs = need_space(bytes_of(ds));
let text = if element.is_null() {
String::new()
} else {
String::from_utf8_lossy(std::ffi::CStr::from_ptr(element).to_bytes()).into_owned()
};
let quoted = crate::list::quote(&text, !needs);
if needs {
append(ds, c" ".as_ptr(), 1);
}
append(ds, quoted.as_ptr() as *const c_char, quoted.len() as isize)
}
pub unsafe fn start_sublist(ds: *mut TclDString) {
if need_space(bytes_of(ds)) {
append(ds, c" {".as_ptr(), 2);
} else {
append(ds, c"{".as_ptr(), 1);
}
}
pub unsafe fn end_sublist(ds: *mut TclDString) {
append(ds, c"}".as_ptr(), 1);
}
pub unsafe fn set_length(ds: *mut TclDString, length: isize) {
let length = length.max(0);
reserve(ds, length + 1, ptr::null());
(*ds).length = length;
*(*ds).string.offset(length) = 0;
}
pub unsafe fn free(ds: *mut TclDString) {
if !is_static(ds) {
libc::free((*ds).string as *mut c_void);
}
init(ds);
}
pub unsafe fn to_obj(ds: *mut TclDString) -> *mut TclObj {
let result = if is_static(ds) {
obj::new_string(bytes_of(ds))
} else {
let o = obj::alloc();
obj::invalidate_string_rep(o);
(*o).bytes = (*ds).string;
(*o).length = (*ds).length;
o
};
(*ds).string = (*ds).static_space.as_mut_ptr();
(*ds).space_avl = TCL_DSTRING_STATIC_SIZE as isize;
(*ds).length = 0;
(*ds).static_space[0] = 0;
result
}