use std::borrow::Cow;
use std::convert::TryInto;
use std::default::Default;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
use crate::support::char;
use crate::support::int;
use crate::support::size_t;
use crate::support::Opaque;
use crate::HandleScope;
use crate::Isolate;
use crate::Local;
use crate::String;
extern "C" {
fn v8__String__kMaxLength() -> size_t;
fn v8__String__Empty(isolate: *mut Isolate) -> *const String;
fn v8__String__NewFromUtf8(
isolate: *mut Isolate,
data: *const char,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__NewFromOneByte(
isolate: *mut Isolate,
data: *const u8,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__NewFromTwoByte(
isolate: *mut Isolate,
data: *const u16,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__Length(this: *const String) -> int;
fn v8__String__Utf8Length(this: *const String, isolate: *mut Isolate) -> int;
fn v8__String__Write(
this: *const String,
isolate: *mut Isolate,
buffer: *mut u16,
start: int,
length: int,
options: WriteOptions,
) -> int;
fn v8__String__WriteOneByte(
this: *const String,
isolate: *mut Isolate,
buffer: *mut u8,
start: int,
length: int,
options: WriteOptions,
) -> int;
fn v8__String__WriteUtf8(
this: *const String,
isolate: *mut Isolate,
buffer: *mut char,
length: int,
nchars_ref: *mut int,
options: WriteOptions,
) -> int;
fn v8__String__GetExternalStringResource(
this: *const String,
) -> *mut ExternalStringResource;
fn v8__String__GetExternalStringResourceBase(
this: *const String,
encoding: *mut Encoding,
) -> *mut ExternalOneByteStringResourceBase;
fn v8__String__NewExternalOneByte(
isolate: *mut Isolate,
onebyte_const: *const OneByteConst,
) -> *const String;
fn v8__String__NewExternalOneByteStatic(
isolate: *mut Isolate,
buffer: *const char,
length: int,
) -> *const String;
fn v8__String__NewExternalTwoByteStatic(
isolate: *mut Isolate,
buffer: *const u16,
length: int,
) -> *const String;
#[allow(dead_code)]
fn v8__String__IsExternal(this: *const String) -> bool;
fn v8__String__IsExternalOneByte(this: *const String) -> bool;
fn v8__String__IsExternalTwoByte(this: *const String) -> bool;
#[allow(dead_code)]
fn v8__String__IsOneByte(this: *const String) -> bool;
fn v8__String__ContainsOnlyOneByte(this: *const String) -> bool;
}
#[repr(C)]
pub enum Encoding {
Unknown = 0,
OneByte = 1,
TwoByte = 2,
}
#[repr(C)]
pub struct ExternalStringResource(Opaque);
#[repr(C)]
pub struct ExternalOneByteStringResourceBase(Opaque);
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct OneByteConst {
vtable: *const OneByteConstNoOp,
cached_data: *const char,
length: usize,
}
impl OneByteConst {
#[inline(always)]
pub const fn as_str(&self) -> &str {
if self.length == 0 {
""
} else {
unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
self.cached_data as _,
self.length,
))
}
}
}
}
impl AsRef<str> for OneByteConst {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for OneByteConst {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl std::ops::Deref for OneByteConst {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
unsafe impl Sync for OneByteConst {}
extern "C" fn one_byte_const_no_op(_this: *const OneByteConst) {}
extern "C" fn one_byte_const_is_cacheable(_this: *const OneByteConst) -> bool {
true
}
extern "C" fn one_byte_const_data(this: *const OneByteConst) -> *const char {
unsafe { (*this).cached_data }
}
extern "C" fn one_byte_const_length(this: *const OneByteConst) -> usize {
unsafe { (*this).length }
}
type OneByteConstNoOp = extern "C" fn(*const OneByteConst);
type OneByteConstIsCacheable = extern "C" fn(*const OneByteConst) -> bool;
type OneByteConstData = extern "C" fn(*const OneByteConst) -> *const char;
type OneByteConstLength = extern "C" fn(*const OneByteConst) -> usize;
#[repr(C)]
struct OneByteConstVtable {
#[cfg(target_family = "windows")]
_offset_to_top: usize,
_typeinfo: *const (),
delete1: OneByteConstNoOp,
#[cfg(not(target_family = "windows"))]
delete2: OneByteConstNoOp,
is_cacheable: OneByteConstIsCacheable,
dispose: OneByteConstNoOp,
lock: OneByteConstNoOp,
unlock: OneByteConstNoOp,
data: OneByteConstData,
length: OneByteConstLength,
}
const ONE_BYTE_CONST_VTABLE: OneByteConstVtable = OneByteConstVtable {
#[cfg(target_family = "windows")]
_offset_to_top: 0,
_typeinfo: std::ptr::null(),
delete1: one_byte_const_no_op,
#[cfg(not(target_family = "windows"))]
delete2: one_byte_const_no_op,
is_cacheable: one_byte_const_is_cacheable,
dispose: one_byte_const_no_op,
lock: one_byte_const_no_op,
unlock: one_byte_const_no_op,
data: one_byte_const_data,
length: one_byte_const_length,
};
#[repr(C)]
#[derive(Debug, Default)]
pub enum NewStringType {
#[default]
Normal,
Internalized,
}
bitflags! {
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct WriteOptions: int {
const NO_OPTIONS = 0;
const HINT_MANY_WRITES_EXPECTED = 1;
const NO_NULL_TERMINATION = 2;
const PRESERVE_ONE_BYTE_NULL = 4;
const REPLACE_INVALID_UTF8 = 8;
}
}
impl String {
#[inline(always)]
pub fn max_length() -> usize {
unsafe { v8__String__kMaxLength() }
}
#[inline(always)]
pub fn empty<'s>(scope: &mut HandleScope<'s, ()>) -> Local<'s, String> {
unsafe { scope.cast_local(|sd| v8__String__Empty(sd.get_isolate_ptr())) }
.unwrap()
}
#[inline(always)]
pub fn new_from_utf8<'s>(
scope: &mut HandleScope<'s, ()>,
buffer: &[u8],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
if buffer.is_empty() {
return Some(Self::empty(scope));
}
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromUtf8(
sd.get_isolate_ptr(),
buffer.as_ptr() as *const char,
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_from_one_byte<'s>(
scope: &mut HandleScope<'s, ()>,
buffer: &[u8],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromOneByte(
sd.get_isolate_ptr(),
buffer.as_ptr(),
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_from_two_byte<'s>(
scope: &mut HandleScope<'s, ()>,
buffer: &[u16],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromTwoByte(
sd.get_isolate_ptr(),
buffer.as_ptr(),
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn length(&self) -> usize {
unsafe { v8__String__Length(self) as usize }
}
#[inline(always)]
pub fn utf8_length(&self, scope: &mut Isolate) -> usize {
unsafe { v8__String__Utf8Length(self, scope) as usize }
}
#[inline(always)]
pub fn write(
&self,
scope: &mut Isolate,
buffer: &mut [u16],
start: usize,
options: WriteOptions,
) -> usize {
unsafe {
v8__String__Write(
self,
scope,
buffer.as_mut_ptr(),
start.try_into().unwrap_or(int::max_value()),
buffer.len().try_into().unwrap_or(int::max_value()),
options,
) as usize
}
}
#[inline(always)]
pub fn write_one_byte(
&self,
scope: &mut Isolate,
buffer: &mut [u8],
start: usize,
options: WriteOptions,
) -> usize {
unsafe {
v8__String__WriteOneByte(
self,
scope,
buffer.as_mut_ptr(),
start.try_into().unwrap_or(int::max_value()),
buffer.len().try_into().unwrap_or(int::max_value()),
options,
) as usize
}
}
#[inline(always)]
pub fn write_one_byte_uninit(
&self,
scope: &mut Isolate,
buffer: &mut [MaybeUninit<u8>],
start: usize,
options: WriteOptions,
) -> usize {
unsafe {
v8__String__WriteOneByte(
self,
scope,
buffer.as_mut_ptr() as *mut u8,
start.try_into().unwrap_or(int::max_value()),
buffer.len().try_into().unwrap_or(int::max_value()),
options,
) as usize
}
}
#[inline(always)]
pub fn write_utf8(
&self,
scope: &mut Isolate,
buffer: &mut [u8],
nchars_ref: Option<&mut usize>,
options: WriteOptions,
) -> usize {
unsafe {
let buffer = {
let len = buffer.len();
let data = buffer.as_mut_ptr().cast();
slice::from_raw_parts_mut(data, len)
};
self.write_utf8_uninit(scope, buffer, nchars_ref, options)
}
}
pub fn write_utf8_uninit(
&self,
scope: &mut Isolate,
buffer: &mut [MaybeUninit<u8>],
nchars_ref: Option<&mut usize>,
options: WriteOptions,
) -> usize {
let mut nchars_ref_int: int = 0;
let bytes = unsafe {
v8__String__WriteUtf8(
self,
scope,
buffer.as_mut_ptr() as *mut char,
buffer.len().try_into().unwrap_or(int::max_value()),
&mut nchars_ref_int,
options,
)
};
if let Some(r) = nchars_ref {
*r = nchars_ref_int as usize;
}
bytes as usize
}
#[inline(always)]
pub fn new<'s>(
scope: &mut HandleScope<'s, ()>,
value: &str,
) -> Option<Local<'s, String>> {
Self::new_from_utf8(scope, value.as_ref(), NewStringType::Normal)
}
#[inline(always)]
pub const fn create_external_onebyte_const(
buffer: &'static [u8],
) -> OneByteConst {
assert!(buffer.is_ascii() && buffer.len() <= ((1 << 29) - 24));
OneByteConst {
vtable: &ONE_BYTE_CONST_VTABLE.delete1,
cached_data: buffer.as_ptr() as *const char,
length: buffer.len(),
}
}
#[inline(always)]
pub const unsafe fn create_external_onebyte_const_unchecked(
buffer: &'static [u8],
) -> OneByteConst {
OneByteConst {
vtable: &ONE_BYTE_CONST_VTABLE.delete1,
cached_data: buffer.as_ptr() as *const char,
length: buffer.len(),
}
}
#[inline(always)]
pub fn new_from_onebyte_const<'s>(
scope: &mut HandleScope<'s, ()>,
onebyte_const: &'static OneByteConst,
) -> Option<Local<'s, String>> {
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByte(sd.get_isolate_ptr(), onebyte_const)
})
}
}
#[inline(always)]
pub fn new_external_onebyte_static<'s>(
scope: &mut HandleScope<'s, ()>,
buffer: &'static [u8],
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByteStatic(
sd.get_isolate_ptr(),
buffer.as_ptr() as *const char,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_external_twobyte_static<'s>(
scope: &mut HandleScope<'s, ()>,
buffer: &'static [u16],
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalTwoByteStatic(
sd.get_isolate_ptr(),
buffer.as_ptr(),
buffer_len,
)
})
}
}
pub fn get_external_string_resource(
&self,
) -> Option<NonNull<ExternalStringResource>> {
NonNull::new(unsafe { v8__String__GetExternalStringResource(self) })
}
pub fn get_external_string_resource_base(
&self,
) -> (Option<NonNull<ExternalOneByteStringResourceBase>>, Encoding) {
let mut encoding = Encoding::Unknown;
(
NonNull::new(unsafe {
v8__String__GetExternalStringResourceBase(self, &mut encoding)
}),
encoding,
)
}
#[inline(always)]
pub fn is_external(&self) -> bool {
self.is_external_onebyte() || self.is_external_twobyte()
}
#[inline(always)]
pub fn is_external_onebyte(&self) -> bool {
unsafe { v8__String__IsExternalOneByte(self) }
}
#[inline(always)]
pub fn is_external_twobyte(&self) -> bool {
unsafe { v8__String__IsExternalTwoByte(self) }
}
#[inline(always)]
pub fn is_onebyte(&self) -> bool {
unsafe { v8__String__IsOneByte(self) }
}
#[inline(always)]
pub fn contains_only_onebyte(&self) -> bool {
unsafe { v8__String__ContainsOnlyOneByte(self) }
}
pub fn to_rust_string_lossy(
&self,
scope: &mut Isolate,
) -> std::string::String {
let len_utf16 = self.length();
if len_utf16 == 0 {
return std::string::String::new();
}
let len_utf8 = self.utf8_length(scope);
if self.is_onebyte() && len_utf8 == len_utf16 {
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf16, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf16);
let length = self.write_one_byte_uninit(
scope,
&mut *buffer,
0,
WriteOptions::NO_NULL_TERMINATION
| WriteOptions::REPLACE_INVALID_UTF8,
);
debug_assert!(length == len_utf16);
let buffer = data as *mut u8;
return std::string::String::from_raw_parts(buffer, length, len_utf16);
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf8, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf8);
let length = self.write_utf8_uninit(
scope,
&mut *buffer,
None,
WriteOptions::NO_NULL_TERMINATION | WriteOptions::REPLACE_INVALID_UTF8,
);
debug_assert!(length == len_utf8);
let buffer = data as *mut u8;
std::string::String::from_raw_parts(buffer, length, len_utf8)
}
}
pub fn to_rust_cow_lossy<'a, const N: usize>(
&self,
scope: &mut Isolate,
buffer: &'a mut [MaybeUninit<u8>; N],
) -> Cow<'a, str> {
let len_utf16 = self.length();
if len_utf16 == 0 {
return "".into();
}
let len_utf8 = self.utf8_length(scope);
if self.is_onebyte() && len_utf8 == len_utf16 {
if len_utf16 <= N {
let length = self.write_one_byte_uninit(
scope,
buffer,
0,
WriteOptions::NO_NULL_TERMINATION,
);
debug_assert!(length == len_utf16);
unsafe {
let buffer = &mut buffer[..length];
let buffer = &mut *(buffer as *mut [_] as *mut [u8]);
return Cow::Borrowed(std::str::from_utf8_unchecked(buffer));
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf16, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf16);
let length = self.write_one_byte_uninit(
scope,
&mut *buffer,
0,
WriteOptions::NO_NULL_TERMINATION
| WriteOptions::REPLACE_INVALID_UTF8,
);
debug_assert!(length == len_utf16);
let buffer = data as *mut u8;
return Cow::Owned(std::string::String::from_raw_parts(
buffer, length, len_utf16,
));
}
}
if len_utf8 <= N {
let length = self.write_utf8_uninit(
scope,
buffer,
None,
WriteOptions::NO_NULL_TERMINATION | WriteOptions::REPLACE_INVALID_UTF8,
);
debug_assert!(length == len_utf8);
unsafe {
let buffer = &mut buffer[..length];
let buffer = &mut *(buffer as *mut [_] as *mut [u8]);
return Cow::Borrowed(std::str::from_utf8_unchecked(buffer));
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf8, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf8);
let length = self.write_utf8_uninit(
scope,
&mut *buffer,
None,
WriteOptions::NO_NULL_TERMINATION | WriteOptions::REPLACE_INVALID_UTF8,
);
debug_assert!(length == len_utf8);
let buffer = data as *mut u8;
Cow::Owned(std::string::String::from_raw_parts(
buffer, length, len_utf8,
))
}
}
}