1#![expect(unsafe_code)]
6
7use std::borrow;
8use std::fmt::Write;
9use std::fmt::{self};
10use std::ops;
11use std::str;
12
13#[derive(Clone, Hash, PartialEq, Eq, Default)]
17pub struct LxString {
18 bytes: Vec<u8>,
19}
20
21impl LxString {
22 pub fn new() -> Self {
24 Self { bytes: Vec::new() }
25 }
26
27 pub fn from_vec(vec: Vec<u8>) -> Self {
29 Self { bytes: vec }
30 }
31
32 pub fn with_capacity(capacity: usize) -> Self {
34 Self {
35 bytes: Vec::with_capacity(capacity),
36 }
37 }
38
39 pub fn into_vec(self) -> Vec<u8> {
41 self.bytes
42 }
43
44 pub fn into_string(self) -> Result<String, Self> {
46 String::from_utf8(self.bytes).map_err(|e| Self {
47 bytes: e.into_bytes(),
48 })
49 }
50
51 pub fn as_lx_str(&self) -> &LxStr {
53 self
54 }
55
56 pub fn capacity(&self) -> usize {
58 self.bytes.capacity()
59 }
60
61 pub fn clear(&mut self) {
63 self.bytes.clear()
64 }
65
66 pub fn push(&mut self, s: &impl AsRef<LxStr>) {
68 self.bytes.extend_from_slice(&s.as_ref().bytes)
69 }
70
71 pub fn reserve(&mut self, additional: usize) {
74 self.bytes.reserve(additional);
75 }
76
77 pub fn reserve_exact(&mut self, additional: usize) {
80 self.bytes.reserve_exact(additional);
81 }
82
83 pub fn shrink_to_fit(&mut self) {
85 self.bytes.shrink_to_fit()
86 }
87}
88
89impl ops::Deref for LxString {
90 type Target = LxStr;
91
92 fn deref(&self) -> &Self::Target {
93 LxStr::from_bytes(&self.bytes)
94 }
95}
96
97impl borrow::Borrow<LxStr> for LxString {
98 fn borrow(&self) -> &LxStr {
99 self
100 }
101}
102
103impl<T> From<&T> for LxString
104where
105 T: ?Sized + AsRef<LxStr>,
106{
107 fn from(s: &T) -> Self {
108 s.as_ref().to_lx_string()
109 }
110}
111
112impl From<String> for LxString {
113 fn from(s: String) -> Self {
114 LxString::from_vec(s.into())
115 }
116}
117
118impl PartialEq<LxString> for &str {
119 fn eq(&self, other: &LxString) -> bool {
120 **self == **other
121 }
122}
123
124impl PartialEq<LxString> for str {
125 fn eq(&self, other: &LxString) -> bool {
126 &**other == self
127 }
128}
129
130impl PartialEq<str> for LxString {
131 fn eq(&self, other: &str) -> bool {
132 &**self == other
133 }
134}
135
136impl PartialEq<&str> for LxString {
137 fn eq(&self, other: &&str) -> bool {
138 **self == **other
139 }
140}
141
142impl fmt::Debug for LxString {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 fmt::Debug::fmt(&**self, f)
145 }
146}
147
148#[derive(PartialEq, Eq, Hash)]
152#[repr(transparent)]
153pub struct LxStr {
154 bytes: [u8],
155}
156
157impl LxStr {
158 pub fn new<T: AsRef<LxStr> + ?Sized>(s: &T) -> &LxStr {
160 s.as_ref()
161 }
162
163 pub fn from_bytes(slice: &[u8]) -> &LxStr {
165 unsafe { std::mem::transmute(slice) }
168 }
169
170 pub fn as_bytes(&self) -> &[u8] {
172 &self.bytes
173 }
174
175 pub fn to_lx_string(&self) -> LxString {
177 LxString::from_vec(self.bytes.into())
178 }
179
180 pub fn len(&self) -> usize {
182 self.bytes.len()
183 }
184
185 pub fn is_empty(&self) -> bool {
187 self.bytes.is_empty()
188 }
189
190 pub fn to_str(&self) -> Option<&str> {
192 str::from_utf8(&self.bytes).ok()
193 }
194
195 pub fn to_string_lossy(&self) -> borrow::Cow<'_, str> {
199 String::from_utf8_lossy(&self.bytes)
200 }
201}
202
203impl ToOwned for LxStr {
204 type Owned = LxString;
205
206 fn to_owned(&self) -> Self::Owned {
207 self.to_lx_string()
208 }
209}
210
211impl AsRef<LxStr> for LxStr {
212 fn as_ref(&self) -> &LxStr {
213 self
214 }
215}
216
217impl AsRef<LxStr> for LxString {
218 fn as_ref(&self) -> &LxStr {
219 self
220 }
221}
222
223impl AsRef<LxStr> for str {
224 fn as_ref(&self) -> &LxStr {
225 LxStr::from_bytes(self.as_bytes())
226 }
227}
228
229impl AsRef<LxStr> for String {
230 fn as_ref(&self) -> &LxStr {
231 (**self).as_ref()
232 }
233}
234
235impl PartialEq<LxStr> for str {
236 fn eq(&self, other: &LxStr) -> bool {
237 LxStr::new(self).eq(other)
238 }
239}
240
241impl PartialEq<str> for LxStr {
242 fn eq(&self, other: &str) -> bool {
243 self.eq(LxStr::new(other))
244 }
245}
246
247impl fmt::Debug for LxStr {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 let value = self.to_string_lossy();
252 f.write_str("\"")?;
253 for c in value.chars().flat_map(|c| c.escape_debug()) {
254 f.write_char(c)?
255 }
256
257 f.write_str("\"")
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn lx_string_new() {
267 let s = LxString::new();
268 assert_eq!(s.capacity(), 0);
269 assert_eq!(s.len(), 0);
270 assert!(s.is_empty());
271 assert_eq!(s, "");
272 assert_ne!(s, "something");
273 }
274
275 #[test]
276 fn lx_string_capacity() {
277 let s = LxString::with_capacity(100);
278 assert_eq!(s.capacity(), 100);
279 assert_eq!(s.len(), 0);
280 assert!(s.is_empty());
281 assert_eq!(s, "");
282 assert_ne!(s, "something");
283 }
284
285 #[test]
286 fn lx_string_from() {
287 let s = LxString::from("foo");
288 assert_eq!(s.capacity(), 3);
289 assert_eq!(s.len(), 3);
290 assert!(!s.is_empty());
291 assert_eq!(s, "foo");
292 assert_ne!(s, "something");
293 assert_ne!(s, "");
294
295 let s = LxString::from(String::from("foo"));
296 assert_eq!(s.capacity(), 3);
297 assert_eq!(s.len(), 3);
298 assert!(!s.is_empty());
299 assert_eq!(s, "foo");
300 assert_ne!(s, "something");
301 assert_ne!(s, "");
302 }
303
304 #[test]
305 fn lx_string_from_vec() {
306 let s = LxString::from_vec(b"foo".to_vec());
307 assert_eq!(s.capacity(), 3);
308 assert_eq!(s.len(), 3);
309 assert!(!s.is_empty());
310 assert_eq!(s, "foo");
311 assert_ne!(s, "something");
312 assert_ne!(s, "");
313 let vec = s.into_vec();
314 assert_eq!(vec, b"foo");
315 }
316
317 #[test]
318 fn lx_string_into_string() {
319 let s = LxString::from("foo");
320 let s = s.into_string().unwrap();
321 assert_eq!(s, "foo");
322
323 let s = LxString::from_vec(vec![b'a', 0xfe, 0xfe]);
324 let e = s.into_string().unwrap_err();
325 assert_eq!(e, LxString::from_vec(vec![b'a', 0xfe, 0xfe]));
326 }
327
328 #[test]
329 fn lx_string_debug() {
330 let s = LxString::from("foo\\bar");
331 let debug = format!("{:?}", s);
332 assert_eq!(debug, r#""foo\\bar""#)
333 }
334
335 #[test]
336 fn lx_str_new() {
337 let s = LxStr::new("");
338 assert_eq!(s.len(), 0);
339 assert!(s.is_empty());
340 assert_eq!(s, "");
341 assert_ne!(s, "something");
342
343 let s = LxStr::new("foo");
344 assert_eq!(s.len(), 3);
345 assert!(!s.is_empty());
346 assert_eq!(s, "foo");
347 assert_eq!(s, LxStr::new("foo"));
348 assert_ne!(s, "something");
349 assert_ne!(s, "");
350 }
351
352 #[test]
353 fn lx_str_from_bytes() {
354 let s = LxStr::from_bytes(b"foo");
355 assert_eq!(s.len(), 3);
356 assert!(!s.is_empty());
357 assert_eq!(s, "foo");
358 assert_ne!(s, "something");
359 assert_ne!(s, "");
360 }
361
362 #[test]
363 fn lx_str_from_lx_string() {
364 let s = LxString::from("foo");
365 let s = s.as_lx_str();
366 assert_eq!(s.len(), 3);
367 assert!(!s.is_empty());
368 assert_eq!(s, "foo");
369 assert_ne!(s, "something");
370 assert_ne!(s, "");
371 }
372
373 #[test]
374 fn lx_str_to_str() {
375 let s = LxStr::new("foo");
376 let s = s.to_str().unwrap();
377 assert_eq!(s, "foo");
378
379 let s = LxStr::from_bytes(&[b'a', 0xfe, 0xfe]);
380 assert!(s.to_str().is_none());
381 }
382
383 #[test]
384 fn lx_str_to_string_lossy() {
385 let s = LxStr::new("foo");
386 let s = s.to_string_lossy();
387 assert!(matches!(s, borrow::Cow::Borrowed(_)));
388 assert_eq!(s, "foo");
389 let s = LxStr::from_bytes(&[b'a', 0xfe, 0xfe, b'b']);
390
391 let s = s.to_string_lossy();
392 assert!(matches!(s, borrow::Cow::Owned(_)));
393 assert_eq!(s, "a\u{fffd}\u{fffd}b");
394 }
395
396 #[test]
397 fn lx_str_debug() {
398 let s = LxStr::new("foo\\bar");
399 let debug = format!("{:?}", s);
400 assert_eq!(debug, r#""foo\\bar""#)
401 }
402}