use std::mem;
use crate::rubysys::{
constant::{
FL_USER_1, FL_USER_17, FL_USER_2, FL_USER_3, FL_USER_4, FL_USER_5, FL_USER_6, FL_USER_7,
FL_USHIFT,
},
libc::size_t,
types::{
c_char, c_double, c_int, c_long, CallbackPtr, EncodingType, InternalValue, RBasic, Value,
},
};
pub const STR_TMPLOCK: isize = FL_USER_7;
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
pub fn rb_str_new(str: *const c_char, len: c_long) -> Value;
pub fn rb_str_new_cstr(str: *const c_char) -> Value;
pub fn rb_utf8_str_new(str: *const c_char, len: c_long) -> Value;
pub fn rb_utf8_str_new_cstr(str: *const c_char) -> Value;
pub fn rb_string_value_cstr(str: *const Value) -> *const c_char;
pub fn rb_string_value_ptr(str: *const Value) -> *const c_char;
pub fn rb_str_strlen(str: Value) -> c_long;
pub fn rb_enc_str_asciionly_p(str: Value) -> c_int;
pub fn rb_enc_str_new(str: *const c_char, len: c_long, enc: EncodingType) -> Value;
pub fn rb_str_export_locale(str: Value) -> Value;
pub fn rb_str_cat(str: Value, ptr: *const c_char, len: c_long) -> Value;
pub fn rb_check_string_type(str: Value) -> Value;
pub fn rb_str_locktmp(str: Value) -> Value;
pub fn rb_str_unlocktmp(str: Value) -> Value;
pub fn rb_str_new_frozen(orig: Value) -> Value;
pub fn rb_str_freeze(string: Value) -> Value;
pub fn rb_str_buf_append(string: Value, other: Value) -> Value;
pub fn rb_str_buf_new(capacity: c_long) -> Value;
pub fn rb_str_capacity(string: Value) -> size_t;
pub fn rb_str_cmp(string: Value, other: Value) -> c_int;
pub fn rb_str_conv_enc(string: Value, from: EncodingType, to: EncodingType) -> Value;
pub fn rb_str_dup(string: Value) -> Value;
pub fn rb_str_ellipsize(string: Value, len: c_long) -> Value;
pub fn rb_str_equal(string: Value, other: Value) -> Value;
pub fn rb_str_hash(string: Value) -> size_t;
pub fn rb_str_inspect(string: Value) -> Value;
pub fn rb_str_intern(string: Value) -> Value;
pub fn rb_str_length(string: Value) -> Value;
pub fn rb_str_modify(string: Value);
pub fn rb_str_plus(string: Value, other: Value) -> Value;
pub fn rb_str_replace(string: Value, other: Value) -> Value;
pub fn rb_str_resize(string: Value, len: c_long) -> Value;
pub fn rb_str_scrub(string: Value, replacement: Value) -> Value;
pub fn rb_str_set_len(string: Value, len: c_long);
pub fn rb_str_split(string: Value, separator: *const c_char) -> Value;
pub fn rb_str_subseq(string: Value, begin: c_long, len: c_long) -> Value;
pub fn rb_str_substr(string: Value, begin: c_long, len: c_long) -> Value;
pub fn rb_str_times(string: Value, times: Value) -> Value;
pub fn rb_str_to_dbl(string: Value, badcheck: c_int) -> c_double;
pub fn rb_str_to_inum(string: Value, base: c_int, badcheck: c_int) -> Value;
pub fn rb_debug_rstring_null_ptr(func: *const c_char);
}
#[derive(Debug, PartialEq)]
#[repr(C)]
enum RStringEmbed {
NoEmbed = FL_USER_1,
LenMask = FL_USER_2 | FL_USER_3 | FL_USER_4 | FL_USER_5 | FL_USER_6,
LenShift = FL_USHIFT + 2,
LenMax = (mem::size_of::<Value>() as isize * 3) / mem::size_of::<c_char>() as isize - 1,
Fstr = FL_USER_17,
}
#[derive(Copy, Clone)]
#[repr(C)]
union RStringAs {
heap: RStringHeap,
ary: [c_char; RStringEmbed::LenMax as usize + 1],
}
#[derive(Copy, Clone)]
#[repr(C)]
union RStringAux {
capa: c_long,
value: InternalValue,
}
#[cfg(not(ruby_gte_3_3))]
#[derive(Copy, Clone)]
#[repr(C)]
struct RStringHeap {
len: c_long,
ptr: *const c_char,
aux: RStringAux,
}
#[cfg(not(ruby_gte_3_3))]
#[repr(C)]
struct RString {
basic: RBasic,
as_: RStringAs,
}
#[cfg(ruby_gte_3_3)]
#[derive(Copy, Clone)]
#[repr(C)]
struct RStringHeap {
ptr: *const c_char,
aux: RStringAux,
}
#[cfg(ruby_gte_3_3)]
#[repr(C)]
struct RString {
basic: RBasic,
len: c_long,
as_: RStringAs,
}
unsafe fn rstring_and_flags(value: Value) -> (*const RString, InternalValue) {
let rstring: *const RString = value.value as *const RString;
let flags = (*rstring).basic.flags;
(rstring, flags)
}
unsafe fn embed_check(flags: InternalValue) -> bool {
flags & (RStringEmbed::NoEmbed as size_t) == 0
}
#[cfg(not(ruby_gte_3_2))]
pub unsafe fn rstring_embed_len(value: Value) -> c_long {
let (_rstring, flags) = rstring_and_flags(value);
((flags as i64 >> RStringEmbed::LenShift as i64)
& (RStringEmbed::LenMask as i64 >> RStringEmbed::LenShift as i64)) as c_long
}
#[cfg(ruby_3_2)]
pub unsafe fn rstring_embed_len(value: Value) -> c_long {
let (rstring, _flags) = rstring_and_flags(value);
(*rstring).as_.heap.len
}
#[cfg(ruby_gte_3_3)]
pub unsafe fn rstring_embed_len(value: Value) -> c_long {
let (rstring, _flags) = rstring_and_flags(value);
(*rstring).len
}
#[cfg(not(ruby_gte_3_2))]
unsafe fn rstring_embed_ptr(rstring: *const RString) -> *const c_char {
(*rstring).as_.ary.as_ptr()
}
#[cfg(ruby_3_2)]
unsafe fn rstring_embed_ptr(rstring: *const RString) -> *const c_char {
(&(*rstring).as_ as *const RStringAs as *const c_char).add(mem::size_of::<c_long>())
}
#[cfg(ruby_gte_3_3)]
unsafe fn rstring_embed_ptr(rstring: *const RString) -> *const c_char {
&(*rstring).as_ as *const RStringAs as *const c_char
}
#[cfg(not(ruby_gte_3_3))]
pub unsafe fn rstring_len(value: Value) -> c_long {
let (rstring, flags) = rstring_and_flags(value);
if embed_check(flags) {
rstring_embed_len(value)
} else {
(*rstring).as_.heap.len
}
}
#[cfg(ruby_gte_3_3)]
pub unsafe fn rstring_len(value: Value) -> c_long {
let (rstring, _flags) = rstring_and_flags(value);
(*rstring).len
}
pub unsafe fn rstring_ptr(value: Value) -> *const c_char {
let (rstring, flags) = rstring_and_flags(value);
if embed_check(flags) {
rstring_embed_ptr(rstring)
} else {
(*rstring).as_.heap.ptr
}
}
pub unsafe fn rstring_end(value: Value) -> *const c_char {
rstring_ptr(value).add(rstring_len(value) as usize)
}
pub unsafe fn is_lockedtmp(value: Value) -> bool {
let (_rstring, flags) = rstring_and_flags(value);
flags & STR_TMPLOCK as size_t != 0
}
#[cfg(test)]
mod tests {
use super::{rstring_end, rstring_len, rstring_ptr};
use crate::{Array, Fixnum, Object, RString, VM};
#[test]
fn test_direct_rstring_reads() {
crate::on_ruby_thread(|| {
let strings = VM::eval(
"(0..700).step(1).flat_map do |n|
s = (('a'..'z').to_a.join * 30)[0, n]
[s, s.dup, String.new(s, capacity: 1000), \"#{s}\", (\"_\" + s)[1..], s.b]
end",
)
.unwrap();
let strings = Array::from(strings.value());
for i in 0..strings.length() {
let string = strings.at(i as i64);
let value = string.value();
let bytesize = unsafe { string.send("bytesize", &[]) };
let expected = bytesize.try_convert_to::<Fixnum>().unwrap().to_i64();
let len = unsafe { rstring_len(value) };
assert_eq!(len as i64, expected, "length of string #{}", i);
let bytes = unsafe {
std::slice::from_raw_parts(rstring_ptr(value) as *const u8, len as usize)
};
let alphabet: Vec<u8> = (b'a'..=b'z').cycle().take(len as usize).collect();
assert_eq!(bytes, &alphabet[..], "bytes of string #{}", i);
assert_eq!(
unsafe { rstring_end(value).offset_from(rstring_ptr(value)) },
len as isize
);
}
let empty = RString::new_utf8("");
assert_eq!(unsafe { rstring_len(empty.value()) }, 0);
});
}
}