Skip to main content

gc_alloc/
cstring.rs

1use std::{ffi::CStr, fmt::Write, ptr::NonNull};
2
3use crate::{GcToken, gc};
4
5pub fn from_str(_token: &impl GcToken, s: &str) -> Result<&'static CStr, NulError> {
6    if let Some(pos) = s.find('\0') {
7        return Err(NulError(pos));
8    }
9    let ptr = alloc(s.len() + 1);
10    unsafe { std::ptr::copy_nonoverlapping(s.as_ptr(), ptr.as_ptr(), s.len()) };
11    unsafe { std::ptr::write(ptr.as_ptr().add(s.len()), 0) };
12    Ok(unsafe { CStr::from_ptr(ptr.as_ptr() as *const i8) })
13}
14
15pub fn from_cstr(_token: &impl GcToken, s: &CStr) -> &'static CStr {
16    let bytes = s.to_bytes_with_nul();
17    let ptr = alloc(bytes.len());
18    unsafe { std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), bytes.len()) };
19    unsafe { CStr::from_ptr(ptr.as_ptr() as *const i8) }
20}
21
22pub fn from_iter<I: IntoIterator<Item = char>>(
23    token: &impl GcToken,
24    iter: I,
25) -> Result<&'static CStr, I::IntoIter> {
26    let mut iter = iter.into_iter();
27    let (lower, _) = iter.size_hint();
28
29    let mut formatter = Formatter::with_capacity(token, lower);
30    while let Some(c) = iter.next() {
31        if formatter.write_char(c).is_err() {
32            return Err(iter);
33        }
34    }
35    Ok(formatter.finish())
36}
37
38pub struct Formatter {
39    buf: NonNull<u8>,
40    len: usize,
41    cap: usize,
42}
43
44impl Formatter {
45    pub fn new(_token: &impl GcToken) -> Self {
46        Self {
47            buf: NonNull::dangling(),
48            len: 0,
49            cap: 0,
50        }
51    }
52
53    pub fn with_capacity(_token: &impl GcToken, cap: usize) -> Self {
54        let buf = if cap == 0 {
55            NonNull::dangling()
56        } else {
57            alloc(cap)
58        };
59        Self { buf, len: 0, cap }
60    }
61}
62
63impl Write for Formatter {
64    #[inline]
65    fn write_str(&mut self, s: &str) -> std::fmt::Result {
66        if s.find('\0').is_some() {
67            return Err(std::fmt::Error);
68        }
69
70        let mut cap = self.cap;
71        while self.len + s.len() > cap {
72            cap = cap.max(1).checked_mul(2).expect("Capacity overflow");
73        }
74        if cap != self.cap {
75            let new_buf = alloc(cap + 1);
76            unsafe { std::ptr::copy_nonoverlapping(self.buf.as_ptr(), new_buf.as_ptr(), self.len) };
77            self.buf = new_buf;
78            self.cap = cap;
79        }
80        unsafe {
81            std::ptr::copy_nonoverlapping(s.as_ptr(), self.buf.add(self.len).as_ptr(), s.len())
82        };
83        self.len += s.len();
84        Ok(())
85    }
86}
87
88impl Formatter {
89    pub fn finish(self) -> &'static CStr {
90        if self.len == 0 {
91            return c"";
92        }
93        unsafe {
94            std::ptr::write(self.buf.add(self.len).as_ptr(), 0);
95            CStr::from_ptr(self.buf.as_ptr() as *const i8)
96        }
97    }
98}
99
100#[macro_export]
101#[doc(hidden)]
102macro_rules! cformat {
103    ($token:expr, $($arg:tt)*) => {{
104        let mut formatter = $crate::cstring::Formatter::new($token);
105        std::fmt::write(&mut formatter, format_args!($($arg)*)).expect("Formatting failed");
106        formatter.finish()
107    }};
108}
109
110pub use cformat as format;
111
112#[derive(Clone, Copy, PartialEq, Eq, Debug)]
113pub struct NulError(usize);
114
115impl NulError {
116    pub fn nul_position(&self) -> usize {
117        self.0
118    }
119}
120
121impl std::fmt::Display for NulError {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        write!(f, "nul byte found in provided data at position: {}", self.0)
124    }
125}
126
127impl std::error::Error for NulError {}
128
129fn alloc(cap: usize) -> NonNull<u8> {
130    let ptr = unsafe { gc::GC_malloc_atomic(cap) as *mut u8 };
131    std::ptr::NonNull::new(ptr).expect("GC_malloc_atomic failed")
132}