1use crate::{Arena, Vec};
2use allocator_api2::alloc::Allocator;
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::io;
6use std::ops::Deref;
7
8#[repr(C)]
19pub struct String<'a, A: Allocator = &'a Arena> {
20 bytes: Vec<'a, u8, A>,
21}
22
23impl<'a, A: Allocator> String<'a, A> {
24 #[inline]
26 pub fn new_in(alloc: A) -> Self {
27 Self { bytes: Vec::new_in(alloc) }
28 }
29
30 #[inline]
32 pub fn with_capacity_in(cap: usize, alloc: A) -> Self {
33 Self { bytes: Vec::with_capacity_in(cap, alloc) }
34 }
35
36 #[inline]
38 pub fn from_str_in(str: &str, alloc: A) -> Self {
39 let mut out = Self::with_capacity_in(str.len(), alloc);
40 out.push_str(str);
41 out
42 }
43
44 pub fn from_reader_in<R: io::Read>(mut reader: R, alloc: A) -> io::Result<Self> {
49 const CHUNK: usize = 8 * 1024;
51 let mut bytes = Vec::new_in(alloc);
52 let mut filled = 0;
53 loop {
54 if filled == bytes.len() {
55 bytes.extend_from_slice(&[0; CHUNK]);
56 }
57 match reader.read(&mut bytes[filled..]) {
58 Ok(0) => break,
59 Ok(read) => filled += read,
60 Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
61 Err(err) => return Err(err),
62 }
63 }
64 bytes.truncate(filled);
65 match std::str::from_utf8(&bytes) {
66 Ok(_) => Ok(Self { bytes }),
67 Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err)),
68 }
69 }
70
71 #[inline]
73 pub fn len(&self) -> usize {
74 self.bytes.len()
75 }
76
77 #[inline]
78 pub fn is_empty(&self) -> bool {
79 self.bytes.is_empty()
80 }
81
82 #[inline]
84 pub fn capacity(&self) -> usize {
85 self.bytes.capacity()
86 }
87
88 #[inline]
90 pub fn push_str(&mut self, str: &str) {
91 self.bytes.extend_from_slice(str.as_bytes());
92 }
93
94 #[inline]
96 pub fn push(&mut self, char: char) {
97 let mut buf = [0; 4];
98 self.push_str(char.encode_utf8(&mut buf));
99 }
100
101 #[inline]
103 pub fn as_str(&self) -> &str {
104 Self::as_utf8(&self.bytes)
105 }
106
107 #[inline]
112 pub fn into_str(self) -> &'a str {
113 Self::as_utf8(self.bytes.into_slice())
114 }
115
116 #[inline]
117 fn as_utf8(bytes: &[u8]) -> &str {
118 debug_assert!(std::str::from_utf8(bytes).is_ok(), "arena String must always hold valid UTF-8");
119 unsafe { std::str::from_utf8_unchecked(bytes) }
123 }
124}
125
126impl<'a, A: Allocator> Extend<char> for String<'a, A> {
127 fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
128 for char in iter {
129 self.push(char);
130 }
131 }
132}
133
134impl<'a, A: Allocator> Deref for String<'a, A> {
135 type Target = str;
136
137 #[inline]
138 fn deref(&self) -> &str {
139 self.as_str()
140 }
141}
142
143impl<'a, A: Allocator> AsRef<str> for String<'a, A> {
144 #[inline]
145 fn as_ref(&self) -> &str {
146 self.as_str()
147 }
148}
149
150impl<'a, A: Allocator> fmt::Write for String<'a, A> {
151 #[inline]
152 fn write_str(&mut self, str: &str) -> fmt::Result {
153 self.push_str(str);
154 Ok(())
155 }
156}
157
158impl<'a, A: Allocator> fmt::Display for String<'a, A> {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 fmt::Display::fmt(self.as_str(), f)
161 }
162}
163
164impl<'a, A: Allocator> fmt::Debug for String<'a, A> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 fmt::Debug::fmt(self.as_str(), f)
167 }
168}
169
170impl<'a, A: Allocator> PartialEq for String<'a, A> {
171 fn eq(&self, other: &Self) -> bool {
172 **self == **other
173 }
174}
175
176impl<'a, A: Allocator> Eq for String<'a, A> {}
177
178impl<'a, A: Allocator> PartialEq<str> for String<'a, A> {
179 fn eq(&self, other: &str) -> bool {
180 &**self == other
181 }
182}
183
184impl<'a, A: Allocator> PartialEq<&str> for String<'a, A> {
185 fn eq(&self, other: &&str) -> bool {
186 &**self == *other
187 }
188}
189
190impl<'a, A: Allocator> Hash for String<'a, A> {
191 fn hash<H: Hasher>(&self, state: &mut H) {
192 (**self).hash(state);
193 }
194}
195
196#[macro_export]
205macro_rules! format_in {
206 (in $alloc:expr, $($arg:tt)*) => {{
207 let mut str = $crate::String::new_in($alloc);
208 ::core::fmt::Write::write_fmt(&mut str, ::core::format_args!($($arg)*))
209 .expect("formatting into an arena String cannot fail");
210 str
211 }};
212}
213
214#[cfg(test)]
215mod test {
216 use super::String;
217 use crate::Arena;
218 use std::fmt::Write;
219 use std::io::{self, Read};
220
221 struct DribbleReader<'r> {
223 bytes: &'r [u8],
224 interrupt_at: usize,
225 reads: usize,
226 }
227
228 impl<'r> Read for DribbleReader<'r> {
229 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
230 self.reads += 1;
231 if self.reads == self.interrupt_at {
232 return Err(io::Error::from(io::ErrorKind::Interrupted));
233 }
234 let Some((first, rest)) = self.bytes.split_first() else {
235 return Ok(0);
236 };
237 buf[0] = *first;
238 self.bytes = rest;
239 Ok(1)
240 }
241 }
242
243 #[test]
244 fn new_is_empty_and_allocates_nothing() {
245 let alloc = Arena::default();
246 let str = String::new_in(&alloc);
247 assert!(str.is_empty());
248 assert_eq!(str.len(), 0);
249 assert_eq!(str.as_str(), "");
250 }
251
252 #[test]
253 fn push_str_appends_in_order() {
254 let alloc = Arena::default();
255 let mut str = String::new_in(&alloc);
256 str.push_str("foo");
257 str.push_str("bar");
258 assert_eq!(str.as_str(), "foobar");
259 assert_eq!(str.len(), 6);
260 }
261
262 #[test]
263 fn push_encodes_multibyte_chars() {
264 let alloc = Arena::default();
265 let mut str = String::new_in(&alloc);
266 str.push('a');
267 str.push('£');
268 str.push('😀');
269 assert_eq!(str.as_str(), "a£😀");
270 assert_eq!(str.len(), 7);
272 }
273
274 #[test]
275 fn from_str_in_copies_the_source() {
276 let alloc = Arena::default();
277 let str = String::from_str_in("hello", &alloc);
278 assert_eq!(str.as_str(), "hello");
279 assert_eq!(str.len(), 5);
280 }
281
282 #[test]
283 fn extend_appends_each_char() {
284 let alloc = Arena::default();
285 let mut str = String::from_str_in("hello ", &alloc);
286 str.extend("wörld".chars());
287 assert_eq!(str.as_str(), "hello wörld");
288 assert_eq!(str.len(), 12);
289 }
290
291 #[test]
292 fn write_macro_formats_through_fmt_write() {
293 let alloc = Arena::default();
294 let mut str = String::new_in(&alloc);
295 write!(&mut str, "{}px", 12).unwrap();
296 assert_eq!(str.as_str(), "12px");
297 }
298
299 #[test]
300 fn format_in_builds_a_string() {
301 let alloc = Arena::default();
302 let str = format_in!(in &alloc, "{}{}", 12, "px");
303 assert_eq!(str, "12px");
304 }
305
306 #[test]
307 fn into_str_outlives_the_string() {
308 let alloc = Arena::default();
309 let borrowed: &str = {
310 let mut str = String::new_in(&alloc);
311 str.push_str("outlives");
312 str.into_str()
313 };
314 assert_eq!(borrowed, "outlives");
315 }
316
317 #[test]
318 fn survives_reallocation() {
319 let alloc = Arena::default();
320 let mut str = String::new_in(&alloc);
321 for i in 0..512 {
322 write!(&mut str, "{}", i % 10).unwrap();
323 }
324 let expected: std::string::String = (0..512).map(|i| char::from(b'0' + (i % 10) as u8)).collect();
326 assert_eq!(str.len(), 512);
327 assert_eq!(str.as_str(), expected);
328 }
329
330 #[test]
331 fn with_capacity_reserves_without_writing() {
332 let alloc = Arena::default();
333 let mut str = String::with_capacity_in(16, &alloc);
334 assert!(str.is_empty());
335 assert!(str.capacity() >= 16);
336 str.push_str("fits");
337 assert_eq!(str.as_str(), "fits");
338 }
339
340 #[test]
341 fn deref_exposes_str_methods() {
342 let alloc = Arena::default();
343 let mut str = String::new_in(&alloc);
344 str.push_str(" padded ");
345 assert_eq!(str.trim(), "padded");
346 assert!(str.contains("padded"));
347 }
348
349 #[test]
350 fn equality_against_str_and_self() {
351 let alloc = Arena::default();
352 let mut a = String::new_in(&alloc);
353 a.push_str("same");
354 let mut b = String::new_in(&alloc);
355 b.push_str("same");
356 assert_eq!(a, b);
357 assert_eq!(a, "same");
358 assert_eq!(a, *"same");
359 }
360
361 #[test]
362 fn from_reader_in_reads_to_end() {
363 let alloc = Arena::default();
364 let str = String::from_reader_in("body{color:blue}".as_bytes(), &alloc).unwrap();
365 assert_eq!(str.as_str(), "body{color:blue}");
366 assert_eq!(str.len(), 16);
367 }
368
369 #[test]
370 fn from_reader_in_rejects_invalid_utf8() {
371 let alloc = Arena::default();
372 let err = String::from_reader_in(&[b'a', 0xff, b'b'][..], &alloc).unwrap_err();
373 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
374 }
375
376 #[test]
377 fn from_reader_in_grows_past_one_chunk() {
378 let alloc = Arena::default();
379 let mut expected = "x".repeat(8 * 1024 - 2);
381 expected.push('😀');
382 expected.push_str(&"y".repeat(4096));
383 let str = String::from_reader_in(expected.as_bytes(), &alloc).unwrap();
384 assert_eq!(str.len(), expected.len());
385 assert_eq!(str.as_str(), expected);
386 }
387
388 #[test]
389 fn from_reader_in_handles_partial_reads_and_interruptions() {
390 let alloc = Arena::default();
391 let reader = DribbleReader { bytes: "a£😀b".as_bytes(), interrupt_at: 3, reads: 0 };
392 let str = String::from_reader_in(reader, &alloc).unwrap();
393 assert_eq!(str.as_str(), "a£😀b");
394 }
395}