nstd_sys/cstring.rs
1//! A dynamically sized, null terminated, C string.
2use crate::{
3 core::{
4 alloc::{
5 NSTDAllocError::{self, NSTD_ALLOC_ERROR_NONE},
6 NSTDAllocator,
7 },
8 cstr::{
9 nstd_core_cstr_as_bytes, nstd_core_cstr_get_null, nstd_core_cstr_is_null_terminated,
10 nstd_core_cstr_new_unchecked, NSTDCStr,
11 },
12 optional::NSTDOptional,
13 slice::NSTDSlice,
14 },
15 vec::{
16 nstd_vec_allocator, nstd_vec_as_ptr, nstd_vec_as_slice, nstd_vec_cap, nstd_vec_clear,
17 nstd_vec_clone, nstd_vec_extend, nstd_vec_from_slice, nstd_vec_get_mut, nstd_vec_len,
18 nstd_vec_new_with_cap, nstd_vec_pop, nstd_vec_push, nstd_vec_stride, NSTDVec,
19 },
20 NSTDChar, NSTDUInt,
21};
22use core::ptr::addr_of;
23use nstdapi::nstdapi;
24
25/// A dynamically sized, null terminated, C string.
26///
27/// Managed C strings (`NSTDCString`) will always contain a null byte until freed.
28#[nstdapi]
29pub struct NSTDCString<'a> {
30 /// The underlying vector of `NSTDChar`s.
31 bytes: NSTDVec<'a>,
32}
33
34/// Represents an optional value of type `NSTDCString`.
35pub type NSTDOptionalCString<'a> = NSTDOptional<NSTDCString<'a>>;
36
37/// Creates a new empty `NSTDCString`.
38///
39/// # Parameters:
40///
41/// - `const NSTDAllocator *allocator` - The memory allocator.
42///
43/// # Returns
44///
45/// `NSTDOptionalCString cstring` - The new C string on success, or an uninitialized "none" variant
46/// if allocating for the C string's null terminator fails.
47///
48/// # Example
49///
50/// ```
51/// use nstd_sys::{alloc::NSTD_ALLOCATOR, cstring::nstd_cstring_new};
52///
53/// let cstring = unsafe { nstd_cstring_new(&NSTD_ALLOCATOR) };
54/// ```
55#[inline]
56#[nstdapi]
57pub fn nstd_cstring_new(allocator: &NSTDAllocator) -> NSTDOptionalCString<'_> {
58 nstd_cstring_new_with_cap(allocator, 1)
59}
60
61/// Creates a new `NSTDCString` initialized with the given capacity.
62///
63/// # Parameters:
64///
65/// - `const NSTDAllocator *allocator` - The memory allocator.
66///
67/// - `NSTDUInt cap` - The number of bytes to allocate ahead of time.
68///
69/// # Returns
70///
71/// `NSTDOptionalCString cstring` - The new C string on success, or an uninitialized "none" variant
72/// if allocating fails.
73///
74/// # Example
75///
76/// ```
77/// use nstd_sys::{alloc::NSTD_ALLOCATOR, cstring::nstd_cstring_new_with_cap};
78///
79/// let cstring = unsafe { nstd_cstring_new_with_cap(&NSTD_ALLOCATOR, 10) };
80/// ```
81#[inline]
82#[nstdapi]
83pub fn nstd_cstring_new_with_cap(
84 allocator: &NSTDAllocator,
85 cap: NSTDUInt,
86) -> NSTDOptionalCString<'_> {
87 if let NSTDOptional::Some(mut bytes) = nstd_vec_new_with_cap(allocator, 1, 1, cap) {
88 let nul: NSTDChar = 0;
89 // SAFETY: `nul` is stored on the stack.
90 if unsafe { nstd_vec_push(&mut bytes, addr_of!(nul).cast()) } == NSTD_ALLOC_ERROR_NONE {
91 return NSTDOptional::Some(NSTDCString { bytes });
92 }
93 }
94 NSTDOptional::None
95}
96
97/// Creates an owned version of an unowned C string slice.
98///
99/// # Parameters:
100///
101/// - `const NSTDAllocator *allocator` - The memory allocator.
102///
103/// - `const NSTDCStr *cstr` - The unowned C string slice.
104///
105/// # Returns
106///
107/// `NSTDOptionalCString cstring` - The new owned version of `cstr` on success, or an uninitialized
108/// "none" variant if `cstr` contains a null byte or allocating fails.
109///
110/// # Safety
111///
112/// The caller of this function must ensure that `cstr`'s data is valid for reads.
113///
114/// # Example
115///
116/// ```
117/// use nstd_sys::{
118/// alloc::NSTD_ALLOCATOR, core::cstr::nstd_core_cstr_from_raw, cstring::nstd_cstring_from_cstr,
119/// };
120///
121/// unsafe {
122/// let cstr = nstd_core_cstr_from_raw("C string\0".as_ptr().cast());
123/// let cstring = nstd_cstring_from_cstr(&NSTD_ALLOCATOR, &cstr);
124/// }
125/// ```
126#[inline]
127#[nstdapi]
128pub unsafe fn nstd_cstring_from_cstr<'a>(
129 allocator: &'a NSTDAllocator,
130 cstr: &NSTDCStr,
131) -> NSTDOptionalCString<'a> {
132 match nstd_core_cstr_get_null(cstr).is_null() {
133 true => nstd_cstring_from_cstr_unchecked(allocator, cstr),
134 false => NSTDOptional::None,
135 }
136}
137
138/// Creates an owned version of an unowned C string slice without checking if the slice contains
139/// any null bytes.
140///
141/// # Parameters:
142///
143/// - `const NSTDAllocator *allocator` - The memory allocator.
144///
145/// - `const NSTDCStr *cstr` - The unowned C string slice.
146///
147/// # Returns
148///
149/// `NSTDOptionalCString cstring` - The new owned version of `cstr` on success, or an uninitialized
150/// "none" variant if allocating fails.
151///
152/// # Safety
153///
154/// The caller of this function must ensure the following preconditions:
155///
156/// - `cstr`'s data is valid for reads.
157///
158/// - `cstr` does not contain any null (`'\0'`) bytes.
159#[nstdapi]
160pub unsafe fn nstd_cstring_from_cstr_unchecked<'a>(
161 allocator: &'a NSTDAllocator,
162 cstr: &NSTDCStr,
163) -> NSTDOptionalCString<'a> {
164 let bytes = nstd_core_cstr_as_bytes(cstr);
165 if let NSTDOptional::Some(mut bytes) = nstd_vec_from_slice(allocator, &bytes) {
166 let null: NSTDChar = 0;
167 let null = addr_of!(null).cast();
168 if nstd_vec_push(&mut bytes, null) == NSTD_ALLOC_ERROR_NONE {
169 return NSTDOptional::Some(NSTDCString { bytes });
170 }
171 }
172 NSTDOptional::None
173}
174
175/// Creates a new C string from owned data.
176///
177/// # Parameters:
178///
179/// - `NSTDVec bytes` - The bytes to take ownership of.
180///
181/// # Returns
182///
183/// `NSTDOptionalCString cstring` - The new C string with ownership of `bytes` on success, or an
184/// uninitialized "none" variant if `bytes` does not end with a null (`\0`) byte.
185///
186/// # Panics
187///
188/// This operation will panic if `bytes`'s stride is not 1.
189#[nstdapi]
190pub fn nstd_cstring_from_bytes(bytes: NSTDVec<'_>) -> NSTDOptionalCString<'_> {
191 assert!(nstd_vec_stride(&bytes) == 1);
192 let ptr = nstd_vec_as_ptr(&bytes).cast();
193 // SAFETY: `ptr` is non-null, vector length's can never be greater than `NSTDInt`'s max value.
194 let cstr = unsafe { nstd_core_cstr_new_unchecked(ptr, nstd_vec_len(&bytes)) };
195 // SAFETY: `cstr`'s data is owned by `bytes`.
196 match unsafe { nstd_core_cstr_is_null_terminated(&cstr) } {
197 true => NSTDOptional::Some(NSTDCString { bytes }),
198 false => NSTDOptional::None,
199 }
200}
201
202/// Creates a deep copy of an `NSTDCString`.
203///
204/// # Parameters:
205///
206/// - `const NSTDCString *cstring` - The C string to create a deep copy of.
207///
208/// # Returns
209///
210/// `NSTDOptionalCString cloned` - A new deep copy of `cstring` on success, or an uninitialized
211/// "none" variant if allocating fails.
212#[inline]
213#[nstdapi]
214pub fn nstd_cstring_clone<'a>(cstring: &NSTDCString<'a>) -> NSTDOptionalCString<'a> {
215 match nstd_vec_clone(&cstring.bytes) {
216 NSTDOptional::Some(bytes) => NSTDOptional::Some(NSTDCString { bytes }),
217 NSTDOptional::None => NSTDOptional::None,
218 }
219}
220
221/// Returns an immutable reference to a C string's allocator.
222///
223/// # Parameters:
224///
225/// - `const NSTDCString *cstring` - The C string.
226///
227/// # Returns
228///
229/// `const NSTDAllocator *allocator` - The C string's allocator.
230#[inline]
231#[nstdapi]
232pub const fn nstd_cstring_allocator<'a>(cstring: &NSTDCString<'a>) -> &'a NSTDAllocator {
233 nstd_vec_allocator(&cstring.bytes)
234}
235
236/// Creates a C string slice containing the contents of `cstring`.
237///
238/// # Parameters:
239///
240/// - `const NSTDCString *cstring` - The C string.
241///
242/// # Returns
243///
244/// `NSTDCStr cstr` - The new C string slice.
245#[nstdapi]
246pub const fn nstd_cstring_as_cstr(cstring: &NSTDCString<'_>) -> NSTDCStr {
247 let ptr = nstd_vec_as_ptr(&cstring.bytes);
248 let len = nstd_vec_len(&cstring.bytes);
249 // SAFETY: `ptr` is never null, owned C strings can never be longer than `NSTDInt`'s max value.
250 unsafe { nstd_core_cstr_new_unchecked(ptr.cast(), len) }
251}
252
253/// Returns an immutable byte slice of the C string's active data, including the null byte.
254///
255/// # Parameters:
256///
257/// - `const NSTDCString *cstring` - The C string.
258///
259/// # Returns
260///
261/// `NSTDSlice bytes` - The C string's active data.
262#[inline]
263#[nstdapi]
264pub const fn nstd_cstring_as_bytes(cstring: &NSTDCString<'_>) -> NSTDSlice {
265 nstd_vec_as_slice(&cstring.bytes)
266}
267
268/// Returns a raw pointer to a C string's memory.
269///
270/// # Parameters:
271///
272/// - `const NSTDCString *cstring` - The C string.
273///
274/// # Returns
275///
276/// `const NSTDChar *ptr` - A raw pointer to a C string's memory.
277#[inline]
278#[nstdapi]
279pub const fn nstd_cstring_as_ptr(cstring: &NSTDCString<'_>) -> *const NSTDChar {
280 nstd_vec_as_ptr(&cstring.bytes).cast()
281}
282
283/// Returns ownership of an `NSTDCString`'s raw data, taking ownership of said C string.
284///
285/// # Parameters:
286///
287/// - `NSTDCString cstring` - The C string.
288///
289/// # Returns
290///
291/// `NSTDVec bytes` - The C string's raw data.
292#[inline]
293#[nstdapi]
294#[allow(clippy::missing_const_for_fn)]
295pub fn nstd_cstring_into_bytes(cstring: NSTDCString<'_>) -> NSTDVec<'_> {
296 cstring.bytes
297}
298
299/// Returns the number of `char`s in a C string, excluding the null terminator.
300///
301/// # Parameters:
302///
303/// - `const NSTDCString *cstring` - The C string.
304///
305/// # Returns
306///
307/// `NSTDUInt len` - The length of the C string without it's null byte.
308#[inline]
309#[nstdapi]
310#[allow(clippy::arithmetic_side_effects)]
311pub const fn nstd_cstring_len(cstring: &NSTDCString<'_>) -> NSTDUInt {
312 nstd_vec_len(&cstring.bytes) - 1
313}
314
315/// Returns the number of `char`s in a C string, including the null terminator.
316///
317/// # Parameters:
318///
319/// - `const NSTDCString *cstring` - The C string.
320///
321/// # Returns
322///
323/// `NSTDUInt len` - The length of the C string including it's null byte.
324#[inline]
325#[nstdapi]
326pub const fn nstd_cstring_len_with_null(cstring: &NSTDCString<'_>) -> NSTDUInt {
327 nstd_vec_len(&cstring.bytes)
328}
329
330/// Returns a C string's capacity.
331///
332/// This is the max number of *bytes* the C string can contain without reallocating.
333///
334/// # Parameters:
335///
336/// - `const NSTDCString *cstring` - The C string.
337///
338/// # Returns
339///
340/// `NSTDUInt cap` - The C string's capacity.
341#[inline]
342#[nstdapi]
343pub const fn nstd_cstring_cap(cstring: &NSTDCString<'_>) -> NSTDUInt {
344 nstd_vec_cap(&cstring.bytes)
345}
346
347/// Appends an `NSTDChar` to the end of an `NSTDCString`.
348///
349/// This will have no effect if `chr` is a null byte (0).
350///
351/// # Parameters:
352///
353/// - `NSTDCString *cstring` - The C string.
354///
355/// - `NSTDChar chr` - The C char to append to the C string.
356///
357/// # Returns
358///
359/// `NSTDAllocError errc` - The allocation operation error code.
360///
361/// # Example
362///
363/// ```
364/// use nstd_sys::{
365/// alloc::NSTD_ALLOCATOR,
366/// cstring::{nstd_cstring_new, nstd_cstring_push},
367/// NSTDChar,
368/// };
369///
370/// unsafe {
371/// let mut cstring = nstd_cstring_new(&NSTD_ALLOCATOR).unwrap();
372/// nstd_cstring_push(&mut cstring, b'!' as NSTDChar);
373/// }
374/// ```
375#[nstdapi]
376pub fn nstd_cstring_push(cstring: &mut NSTDCString<'_>, chr: NSTDChar) -> NSTDAllocError {
377 if chr != 0 {
378 // SAFETY: C strings always contain an exclusive null byte at the end.
379 unsafe {
380 // Push a new null byte onto the end of the C string.
381 let nul: NSTDChar = 0;
382 let errc = nstd_vec_push(&mut cstring.bytes, addr_of!(nul).cast());
383 if errc != NSTD_ALLOC_ERROR_NONE {
384 return errc;
385 }
386 // Write `chr` over the old null byte.
387 #[allow(clippy::arithmetic_side_effects)]
388 let nulpos = nstd_vec_len(&cstring.bytes) - 2;
389 let nul = nstd_vec_get_mut(&mut cstring.bytes, nulpos).cast();
390 *nul = chr;
391 }
392 }
393 NSTD_ALLOC_ERROR_NONE
394}
395
396/// Appends a C string slice to the end of a C string.
397///
398/// # Parameters:
399///
400/// - `NSTDCString *cstring` - The C string.
401///
402/// - `const NSTDCStr *cstr` - The C string slice to append to the end of `cstring`.
403///
404/// # Returns
405///
406/// `NSTDAllocError errc` - The allocation operation error code.
407///
408/// # Panics
409///
410/// This operation will panic in the following situations:
411///
412/// - `cstr` contains a null byte.
413///
414/// - Appending the new null byte to the end of the C string fails.
415///
416/// # Safety
417///
418/// This operation can cause undefined behavior in the case that `cstr`'s data is invalid.
419///
420/// # Example
421///
422/// ```
423/// use nstd_sys::{
424/// alloc::NSTD_ALLOCATOR,
425/// core::{alloc::NSTDAllocError::NSTD_ALLOC_ERROR_NONE, cstr::nstd_core_cstr_from_raw},
426/// cstring::{nstd_cstring_new, nstd_cstring_push_cstr},
427/// NSTDChar,
428/// };
429///
430/// unsafe {
431/// let mut cstring = nstd_cstring_new(&NSTD_ALLOCATOR).unwrap();
432/// let cstr = nstd_core_cstr_from_raw("baNaNa\0".as_ptr().cast());
433/// assert!(nstd_cstring_push_cstr(&mut cstring, &cstr) == NSTD_ALLOC_ERROR_NONE);
434/// }
435/// ```
436#[nstdapi]
437pub unsafe fn nstd_cstring_push_cstr(
438 cstring: &mut NSTDCString<'_>,
439 cstr: &NSTDCStr,
440) -> NSTDAllocError {
441 // Make sure the C string slice doesn't contain a null byte.
442 assert!(nstd_core_cstr_get_null(cstr).is_null());
443 // Pop the old null byte.
444 let nul = *nstd_vec_pop(&mut cstring.bytes).cast::<NSTDChar>();
445 // Append the C string slice.
446 let bytes = nstd_core_cstr_as_bytes(cstr);
447 let errc = nstd_vec_extend(&mut cstring.bytes, &bytes);
448 // Push a new null byte.
449 let pusherrc = nstd_vec_push(&mut cstring.bytes, addr_of!(nul).cast());
450 assert!(pusherrc == NSTD_ALLOC_ERROR_NONE);
451 errc
452}
453
454/// Removes the last character from a C string and returns it.
455///
456/// # Parameters:
457///
458/// - `NSTDCString *cstring` - The C string.
459///
460/// # Returns
461///
462/// `NSTDChar chr` - The removed character, or null if the C string is empty.
463///
464/// # Example
465///
466/// ```
467/// use nstd_sys::{
468/// alloc::NSTD_ALLOCATOR,
469/// core::cstr::nstd_core_cstr_from_raw,
470/// cstring::{nstd_cstring_from_cstr, nstd_cstring_pop},
471/// NSTDChar,
472/// };
473///
474/// unsafe {
475/// let cstr = nstd_core_cstr_from_raw("123\0".as_ptr().cast());
476/// let mut cstring = nstd_cstring_from_cstr(&NSTD_ALLOCATOR, &cstr).unwrap();
477/// assert!(nstd_cstring_pop(&mut cstring) == b'3' as NSTDChar);
478/// }
479/// ```
480#[nstdapi]
481pub fn nstd_cstring_pop(cstring: &mut NSTDCString<'_>) -> NSTDChar {
482 let mut ret = 0;
483 let len = nstd_cstring_len(cstring);
484 if len > 0 {
485 // SAFETY: The C string's length is at least 1.
486 unsafe {
487 // Write the last character in the C string to the return value.
488 #[allow(clippy::arithmetic_side_effects)]
489 let last = nstd_vec_get_mut(&mut cstring.bytes, len - 1).cast::<NSTDChar>();
490 ret = *last;
491 // Set the last byte to null.
492 *last = 0;
493 // Pop the old null byte.
494 nstd_vec_pop(&mut cstring.bytes);
495 }
496 }
497 ret
498}
499
500/// Sets a C string's length to zero.
501///
502/// # Parameters:
503///
504/// - `NSTDCString *cstring` - The C string to clear.
505#[inline]
506#[nstdapi]
507pub fn nstd_cstring_clear(cstring: &mut NSTDCString<'_>) {
508 nstd_vec_clear(&mut cstring.bytes);
509}
510
511/// Frees an instance of `NSTDCString`.
512///
513/// # Parameters:
514///
515/// - `NSTDCString cstring` - The C string to free.
516#[inline]
517#[nstdapi]
518#[allow(
519 unused_variables,
520 clippy::missing_const_for_fn,
521 clippy::needless_pass_by_value
522)]
523pub fn nstd_cstring_free(cstring: NSTDCString<'_>) {}