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