use core::ffi::c_void;
use core::mem::ManuallyDrop;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
use crate::foundation::NSString;
use crate::rc::Id;
use crate::runtime::Class;
extern "C" {
pub static __CFConstantStringClassReference: Class;
}
#[repr(C)]
pub struct CFConstString {
isa: &'static Class,
cfinfo: u32,
#[cfg(target_pointer_width = "64")]
_rc: u32,
data: *const c_void,
len: usize,
}
unsafe impl Sync for CFConstString {}
impl CFConstString {
const FLAGS_ASCII: u32 = 0x07_C8;
const FLAGS_UTF16: u32 = 0x07_D0;
pub const unsafe fn new_ascii(isa: &'static Class, data: &'static [u8]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_ASCII,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
len: data.len() - 1,
}
}
pub const unsafe fn new_utf16(isa: &'static Class, data: &'static [u16]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_UTF16,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
len: data.len() - 1,
}
}
#[inline]
pub const fn as_nsstring_const(&self) -> &NSString {
let ptr: *const Self = self;
unsafe { &*ptr.cast::<NSString>() }
}
#[inline]
pub fn as_nsstring(&self) -> &NSString {
self.as_nsstring_const()
}
}
pub const fn is_ascii_no_nul(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
if !byte.is_ascii() || byte == b'\0' {
return false;
}
i += 1;
}
true
}
pub struct Utf16Char {
pub repr: [u16; 2],
pub len: usize,
}
impl Utf16Char {
const fn encode(ch: u32) -> Self {
if ch <= 0xffff {
Self {
repr: [ch as u16, 0],
len: 1,
}
} else {
let payload = ch - 0x10000;
let hi = (payload >> 10) | 0xd800;
let lo = (payload & 0x3ff) | 0xdc00;
Self {
repr: [hi as u16, lo as u16],
len: 2,
}
}
}
#[cfg(test)]
fn as_slice(&self) -> &[u16] {
&self.repr[..self.len]
}
}
pub struct EncodeUtf16Iter {
str: &'static [u8],
index: usize,
}
impl EncodeUtf16Iter {
pub const fn new(str: &'static [u8]) -> Self {
Self { str, index: 0 }
}
pub const fn next(self) -> Option<(Self, Utf16Char)> {
if self.index >= self.str.len() {
None
} else {
let (index, ch) = decode_utf8(self.str, self.index);
Some((Self { index, ..self }, Utf16Char::encode(ch)))
}
}
}
const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
let b0 = s[i];
match b0 {
0b0000_0000..=0b0111_1111 => {
let decoded = b0 as u32;
(i + 1, decoded)
}
0b1100_0000..=0b1101_1111 => {
let decoded = ((b0 as u32 & 0x1f) << 6) | (s[i + 1] as u32 & 0x3f);
(i + 2, decoded)
}
0b1110_0000..=0b1110_1111 => {
let decoded = ((b0 as u32 & 0x0f) << 12)
| ((s[i + 1] as u32 & 0x3f) << 6)
| (s[i + 2] as u32 & 0x3f);
(i + 3, decoded)
}
0b1111_0000..=0b1111_0111 => {
let decoded = ((b0 as u32 & 0x07) << 18)
| ((s[i + 1] as u32 & 0x3f) << 12)
| ((s[i + 2] as u32 & 0x3f) << 6)
| (s[i + 3] as u32 & 0x3f);
(i + 4, decoded)
}
0b1000_0000..=0b1011_1111 | 0b1111_1000..=0b1111_1111 => {
panic!("Encountered invalid bytes")
}
}
}
#[doc(hidden)]
pub struct CachedNSString {
ptr: AtomicPtr<NSString>,
}
impl CachedNSString {
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
#[inline]
pub fn get(&self, s: &str) -> &'static NSString {
let ptr = self.ptr.load(Ordering::SeqCst);
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
let s = ManuallyDrop::new(NSString::from_str(s));
let ptr = Id::as_ptr(&s);
self.ptr.store(ptr as *mut NSString, Ordering::SeqCst);
unsafe { ptr.as_ref().unwrap_unchecked() }
})
}
}
#[cfg(feature = "foundation")] #[macro_export]
macro_rules! ns_string {
($s:expr) => {{
const INPUT: &str = $s;
$crate::__ns_string_inner!(INPUT)
}};
}
#[doc(hidden)]
#[cfg(feature = "apple")]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
const X: &[u8] = $inp.as_bytes();
$crate::__ns_string_inner!(@inner X);
CFSTRING.as_nsstring()
}};
(@inner $inp:ident) => {
#[link_section = "__TEXT,__cstring,cstring_literals"]
static ASCII: [u8; $inp.len() + 1] = {
let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1];
let mut i = 0;
while i < $inp.len() {
res[i] = $inp[i];
i += 1;
}
res
};
const UTF16_FULL: (&[u16; $inp.len()], usize) = {
let mut out = [0u16; $inp.len()];
let mut iter = $crate::foundation::__ns_string::EncodeUtf16Iter::new($inp);
let mut written = 0;
while let Some((state, chars)) = iter.next() {
iter = state;
out[written] = chars.repr[0];
written += 1;
if chars.len > 1 {
out[written] = chars.repr[1];
written += 1;
}
}
(&{ out }, written)
};
#[link_section = "__TEXT,__ustring"]
static UTF16: [u16; UTF16_FULL.1 + 1] = {
let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1];
let mut i = 0;
while i < UTF16_FULL.1 {
res[i] = UTF16_FULL.0[i];
i += 1;
}
res
};
#[link_section = "__DATA,__cfstring"]
static CFSTRING: $crate::foundation::__ns_string::CFConstString = unsafe {
if $crate::foundation::__ns_string::is_ascii_no_nul($inp) {
$crate::foundation::__ns_string::CFConstString::new_ascii(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&ASCII,
)
} else {
$crate::foundation::__ns_string::CFConstString::new_utf16(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&UTF16,
)
}
};
};
}
#[doc(hidden)]
#[cfg(not(feature = "apple"))]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
use $crate::foundation::__ns_string::CachedNSString;
static CACHED_NSSTRING: CachedNSString = CachedNSString::new();
CACHED_NSSTRING.get($inp)
}};
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_is_ascii() {
assert!(is_ascii_no_nul(b"a"));
assert!(is_ascii_no_nul(b"abc"));
assert!(!is_ascii_no_nul(b"\xff"));
assert!(!is_ascii_no_nul(b"\0"));
assert!(!is_ascii_no_nul(b"a\0b"));
assert!(!is_ascii_no_nul(b"ab\0"));
assert!(!is_ascii_no_nul(b"a\0b\0"));
}
#[test]
fn test_decode_utf8() {
for c in '\u{0}'..=core::char::MAX {
let mut buf;
for off in 0..4 {
buf = [0xff; 8];
let len = c.encode_utf8(&mut buf[off..(off + 4)]).len();
let (end_idx, decoded) = decode_utf8(&buf, off);
assert_eq!(
(end_idx, decoded),
(off + len, c as u32),
"failed for U+{code:04X} ({ch:?}) encoded as {buf:#x?} over {range:?}",
code = c as u32,
ch = c,
buf = &buf[off..(off + len)],
range = off..(off + len),
);
}
}
}
#[test]
fn encode_utf16() {
for c in '\u{0}'..=core::char::MAX {
assert_eq!(
c.encode_utf16(&mut [0u16; 2]),
Utf16Char::encode(c as u32).as_slice(),
"failed for U+{:04X} ({:?})",
c as u32,
c
);
}
}
#[test]
fn ns_string() {
macro_rules! test {
($($s:expr,)+) => {$({
let s1 = ns_string!($s);
let s2 = NSString::from_str($s);
assert_eq!(s1, s1);
assert_eq!(s1, &*s2);
assert_eq!(s1.to_string(), $s);
assert_eq!(s2.to_string(), $s);
})+};
}
test! {
"",
"asdf",
"🦀",
"🏳️🌈",
"𝄞music",
"abcd【e】fg",
"abcd⒠fg",
"ääääh",
"lööps, bröther?",
"\u{fffd} \u{fffd} \u{fffd}",
"讓每個人都能打造出。",
"\0",
"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09",
include_str!("__ns_string.rs"),
}
}
#[test]
fn ns_string_in_unsafe() {
let s = unsafe {
let s: *const NSString = ns_string!("abc");
&*s
};
assert_eq!(s.to_string(), "abc");
}
}