1use core::{marker::PhantomData, ptr::NonNull, str::Utf8Error};
4
5use alloc::{borrow::Cow, string::String};
6
7use crate::platform::types::{c_char, wchar_t};
8
9mod private {
10 pub trait Sealed {}
11}
12#[derive(Clone, Copy, Debug)]
13pub enum Thin {}
14
15#[derive(Clone, Copy, Debug)]
16pub enum Wide {}
17
18impl private::Sealed for Thin {}
19impl private::Sealed for Wide {}
20
21pub trait Kind: private::Sealed + Copy + 'static {
22 type C: Copy + 'static;
24 type Char: Copy + From<u8> + Into<u32> + PartialEq + 'static;
26
27 const NUL: Self::Char;
28
29 const IS_THIN_NOT_WIDE: bool;
30
31 fn r2c(c: Self::Char) -> Self::C;
32 fn c2r(c: Self::C) -> Self::Char;
33
34 fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>;
35 fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>;
36
37 unsafe fn strlen(s: *const Self::C) -> usize;
38 unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C;
39 unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C;
40}
41impl Kind for Thin {
42 type C = c_char;
43 type Char = u8;
44
45 const NUL: Self::Char = 0;
46 const IS_THIN_NOT_WIDE: bool = true;
47
48 unsafe fn strlen(s: *const c_char) -> usize {
49 unsafe { crate::header::string::strlen(s) }
50 }
51 unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char {
52 unsafe { crate::header::string::strchr(s, c.into()) }
53 }
54 unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char {
55 unsafe { crate::header::string::strchrnul(s, c.into()) }
56 }
57 fn r2c(c: u8) -> c_char {
58 c as _
59 }
60 fn c2r(c: c_char) -> u8 {
61 c as _
62 }
63 fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
64 Some(b)
65 }
66 fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
67 Some(c)
68 }
69}
70impl Kind for Wide {
71 type C = wchar_t;
72 type Char = u32;
73
74 const NUL: Self::Char = 0;
75 const IS_THIN_NOT_WIDE: bool = false;
76
77 unsafe fn strlen(s: *const Self::C) -> usize {
78 unsafe { crate::header::wchar::wcslen(s) }
79 }
80 unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C {
81 unsafe { crate::header::wchar::wcschr(s, c) }
82 }
83 unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C {
84 while unsafe { s.read() } != c && unsafe { s.read() } != 0 {
86 s = unsafe { s.add(1) };
87 }
88 s
89 }
90 fn r2c(c: Self::Char) -> Self::C {
91 c as _
92 }
93 fn c2r(c: Self::C) -> Self::Char {
94 c as _
95 }
96 fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> {
97 None
98 }
99 fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> {
100 None
101 }
102}
103
104#[derive(Clone, Copy)]
106#[repr(transparent)]
107pub struct NulStr<'a, T: Kind> {
108 ptr: NonNull<T::C>,
109 _marker: PhantomData<&'a [u8]>,
110}
111pub type CStr<'a> = NulStr<'a, Thin>;
112pub type WStr<'a> = NulStr<'a, Wide>;
113
114impl<'a, T: Kind> NulStr<'a, T> {
115 pub const unsafe fn from_ptr(ptr: *const T::C) -> Self {
119 Self {
120 ptr: unsafe { NonNull::new_unchecked(ptr.cast_mut()) },
121 _marker: PhantomData,
122 }
123 }
124 pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option<Self> {
125 if ptr.is_null() {
126 None
127 } else {
128 Some(unsafe { Self::from_ptr(ptr) })
129 }
130 }
131 #[inline]
134 #[doc(alias = "strchrnul")]
135 pub fn find_get_subslice_or_all(
136 self,
137 c: impl Into<T::Char>,
138 ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> {
139 let c = c.into();
140
141 let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) };
144
145 let found = unsafe { Self::from_ptr(found) };
148 let until = unsafe { self.slice_until_substr(found) };
149
150 if found.first() == T::NUL {
151 Err((until, found))
153 } else {
154 Ok((until, found))
155 }
156 }
157 #[inline]
161 pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] {
162 let index = unsafe {
163 substr.as_ptr().offset_from(self.as_ptr()) as usize
166 };
167 unsafe { core::slice::from_raw_parts(self.as_ptr().cast::<T::Char>(), index) }
168 }
169 #[inline]
172 pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> {
173 let rest = self.find(c)?;
174
175 Some((unsafe { self.slice_until_substr(rest) }, rest))
177 }
178 #[doc(alias = "strchr")]
181 #[doc(alias = "wcschr")]
182 #[inline]
183 pub fn find(self, c: T::Char) -> Option<Self> {
184 unsafe {
185 let ret = T::strchr(self.as_ptr(), T::r2c(c));
188 Self::from_nullable_ptr(ret)
191 }
192 }
193 #[inline]
196 pub fn contains(self, c: T::Char) -> bool {
197 self.find(c).is_some()
198 }
199 #[inline]
200 pub fn first(self) -> T::Char {
201 unsafe {
202 T::c2r(self.ptr.read())
205 }
206 }
207 #[inline]
208 pub fn first_char(self) -> Option<char> {
209 char::from_u32(self.first().into())
210 }
211 #[inline]
213 pub fn split_first_char(self) -> Option<(char, Self)> {
214 self.split_first()
215 .and_then(|(c, r)| Some((char::from_u32(c.into())?, r)))
216 }
217 #[inline]
219 pub fn split_first(self) -> Option<(T::Char, Self)> {
220 if self.first() == T::NUL {
221 return None;
222 }
223 Some((self.first(), unsafe {
224 Self::from_ptr(self.as_ptr().add(1))
225 }))
226 }
227 pub fn to_chars_with_nul(self) -> &'a [T::Char] {
228 unsafe {
229 let len = T::strlen(self.ptr.as_ptr());
231 core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1)
232 }
233 }
234 pub fn to_chars(self) -> &'a [T::Char] {
235 let s = self.to_chars_with_nul();
236 &s[..s.len() - 1]
237 }
238 pub const fn as_ptr(self) -> *const T::C {
239 self.ptr.as_ptr()
240 }
241 pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self {
242 unsafe { Self::from_ptr(chars.as_ptr().cast()) }
243 }
244 pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsWithNulError> {
245 if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) {
246 return Err(FromCharsWithNulError);
247 }
248
249 Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
250 }
251 pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result<Self, FromCharsUntilNulError> {
252 if !chars.contains(&T::NUL) {
253 return Err(FromCharsUntilNulError);
254 }
255
256 Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) })
257 }
258 #[doc(alias = "strlen")]
260 #[doc(alias = "wcslen")]
261 pub fn len(self) -> usize {
262 self.to_chars().len()
263 }
264 #[inline]
265 pub fn is_empty(&self) -> bool {
266 self.first() == T::NUL
267 }
268}
269impl<'a> CStr<'a> {
270 pub fn to_owned_cstring(self) -> CString {
271 CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) })
272 }
273 pub fn borrow(string: &'a CString) -> Self {
274 unsafe { Self::from_ptr(string.as_ptr()) }
275 }
276 #[inline]
277 pub fn to_bytes(self) -> &'a [u8] {
278 self.to_chars()
279 }
280 #[inline]
281 pub fn to_bytes_with_nul(self) -> &'a [u8] {
282 self.to_chars_with_nul()
283 }
284 pub fn to_str(self) -> Result<&'a str, Utf8Error> {
285 core::str::from_utf8(self.to_bytes())
286 }
287 pub fn to_string_lossy(self) -> Cow<'a, str> {
288 String::from_utf8_lossy(self.to_bytes())
289 }
290 #[inline]
291 pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self {
292 unsafe { Self::from_chars_with_nul_unchecked(bytes) }
293 }
294 #[inline]
295 pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result<Self, FromCharsWithNulError> {
296 Self::from_chars_with_nul(bytes)
297 }
298 #[inline]
299 pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result<Self, FromCharsUntilNulError> {
300 Self::from_chars_until_nul(bytes)
301 }
302}
303
304unsafe impl<T: Kind> Send for NulStr<'_, T> {}
305unsafe impl<T: Kind> Sync for NulStr<'_, T> {}
306
307impl From<&core::ffi::CStr> for CStr<'_> {
308 fn from(s: &core::ffi::CStr) -> Self {
309 unsafe { Self::from_ptr(s.as_ptr()) }
313 }
314}
315
316#[derive(Debug)]
317pub struct FromCharsWithNulError;
318
319#[derive(Debug)]
320pub struct FromCharsUntilNulError;
321
322pub use alloc::ffi::CString;