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