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}