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