Skip to main content

qanglang_core/memory/
string_interner.rs

1use rustc_hash::{FxBuildHasher, FxHashMap, FxHasher};
2use std::hash::{Hash, Hasher};
3use std::str;
4
5pub type StringHandle = u32;
6
7#[derive(Debug, Clone, Default)]
8pub struct StringInterner {
9    map: FxHashMap<u64, StringHandle>,
10    storage: Vec<u8>,
11    offsets: Vec<(u32, u32)>,
12}
13
14impl StringInterner {
15    pub fn new() -> Self {
16        Self::with_capacity(1000, 100000)
17    }
18
19    pub fn with_capacity(capacity: usize, bytes: usize) -> Self {
20        Self {
21            map: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
22            storage: Vec::with_capacity(bytes),
23            offsets: Vec::with_capacity(capacity),
24        }
25    }
26
27    pub fn intern(&mut self, s: &str) -> StringHandle {
28        let mut hasher = FxHasher::default();
29        s.hash(&mut hasher);
30        let mut hash = hasher.finish();
31
32        loop {
33            match self.map.get(&hash) {
34                None => {
35                    let storage_index = self.storage.len() as u32;
36                    let offset_index = self.offsets.len() as u32;
37                    self.map.insert(hash, offset_index);
38                    self.storage.extend_from_slice(s.as_bytes());
39                    self.offsets.push((storage_index, s.len() as u32));
40                    return offset_index;
41                }
42                Some(&index) => {
43                    if self.get(index) == s {
44                        return index;
45                    }
46                    hash = hash.wrapping_add(1);
47                }
48            }
49        }
50    }
51
52    pub fn get(&self, handle: StringHandle) -> &str {
53        let offset = &self.offsets[handle as usize];
54        let begin = offset.0 as usize;
55        let end = begin + offset.1 as usize;
56
57        unsafe { str::from_utf8_unchecked(&self.storage[begin..end]) }
58    }
59
60    pub fn concat(&mut self, handle1: StringHandle, handle2: StringHandle) -> StringHandle {
61        let (str1_idx, str1_length) = self.offsets[handle1 as usize];
62        let (str2_idx, str2_length) = self.offsets[handle2 as usize];
63        let capacity = str1_length + str2_length;
64        let str1_begin = str1_idx as usize;
65        let str1_end = str1_begin + str1_length as usize;
66        let str2_begin = str2_idx as usize;
67        let str2_end = str2_begin + str2_length as usize;
68        let mut concat_string_bytes = Vec::with_capacity(capacity as usize);
69        concat_string_bytes.extend_from_slice(&self.storage[str1_begin..str1_end]);
70        concat_string_bytes.extend_from_slice(&self.storage[str2_begin..str2_end]);
71
72        let concat_string = unsafe { str::from_utf8_unchecked(&concat_string_bytes) };
73
74        self.intern(concat_string)
75    }
76
77    pub fn starts_with(
78        &self,
79        string_handle: StringHandle,
80        starts_with_handle: StringHandle,
81    ) -> bool {
82        let (str_idx, str_len) = self.offsets[string_handle as usize];
83        let (prefix_idx, prefix_len) = self.offsets[starts_with_handle as usize];
84
85        if prefix_len > str_len {
86            return false;
87        }
88
89        let str_begin = str_idx as usize;
90        let prefix_begin = prefix_idx as usize;
91        let prefix_end = prefix_begin + prefix_len as usize;
92
93        &self.storage[str_begin..str_begin + prefix_len as usize]
94            == &self.storage[prefix_begin..prefix_end]
95    }
96
97    pub fn ends_with(&self, string_handle: StringHandle, suffix_handle: StringHandle) -> bool {
98        let (str_idx, str_len) = self.offsets[string_handle as usize];
99        let (suffix_idx, suffix_len) = self.offsets[suffix_handle as usize];
100
101        if suffix_len > str_len {
102            return false;
103        }
104
105        let str_end = str_idx as usize + str_len as usize;
106        let str_suffix_start = str_end - suffix_len as usize;
107        let suffix_begin = suffix_idx as usize;
108        let suffix_end = suffix_begin + suffix_len as usize;
109
110        &self.storage[str_suffix_start..str_end] == &self.storage[suffix_begin..suffix_end]
111    }
112
113    pub fn contains(&self, string_handle: StringHandle, needle_handle: StringHandle) -> bool {
114        let (str_idx, str_len) = self.offsets[string_handle as usize];
115        let (needle_idx, needle_len) = self.offsets[needle_handle as usize];
116
117        if needle_len > str_len {
118            return false;
119        }
120
121        if needle_len == 0 {
122            return true;
123        }
124
125        let str_begin = str_idx as usize;
126        let str_end = str_begin + str_len as usize;
127        let needle_begin = needle_idx as usize;
128        let needle_end = needle_begin + needle_len as usize;
129
130        let haystack = &self.storage[str_begin..str_end];
131        let needle = &self.storage[needle_begin..needle_end];
132
133        // Use a simple sliding window search without allocation
134        if needle_len as usize > haystack.len() {
135            return false;
136        }
137
138        for i in 0..=(haystack.len() - needle.len()) {
139            if &haystack[i..i + needle.len()] == needle {
140                return true;
141            }
142        }
143
144        false
145    }
146
147    pub fn to_uppercase(&mut self, handle: StringHandle) -> StringHandle {
148        let s = self.get(handle);
149        let uppercase = s.to_uppercase();
150        self.intern(&uppercase)
151    }
152
153    pub fn to_lowercase(&mut self, handle: StringHandle) -> StringHandle {
154        let s = self.get(handle);
155        let lowercase = s.to_lowercase();
156        self.intern(&lowercase)
157    }
158
159    pub fn length(&self, handle: StringHandle) -> usize {
160        self.get(handle).chars().count()
161    }
162
163    pub fn chars(&self, handle: StringHandle) -> impl Iterator<Item = char> + '_ {
164        self.get(handle).chars()
165    }
166
167    pub fn char_at(&mut self, handle: StringHandle, index: usize) -> Option<StringHandle> {
168        let char_opt = self.get(handle).chars().nth(index);
169        char_opt.map(|c| {
170            let mut buf = [0u8; 4];
171            let s = c.encode_utf8(&mut buf);
172            self.intern(s)
173        })
174    }
175
176    pub fn pad_start(
177        &mut self,
178        handle: StringHandle,
179        width: usize,
180        pad_char: char,
181    ) -> StringHandle {
182        let s = self.get(handle);
183        let current_len = s.chars().count();
184
185        if current_len >= width {
186            return handle;
187        }
188
189        let pad_count = width - current_len;
190        let mut padded = String::with_capacity(pad_count * pad_char.len_utf8() + s.len());
191
192        for _ in 0..pad_count {
193            padded.push(pad_char);
194        }
195        padded.push_str(s);
196
197        self.intern(&padded)
198    }
199
200    pub fn pad_end(&mut self, handle: StringHandle, width: usize, pad_char: char) -> StringHandle {
201        let s = self.get(handle);
202        let current_len = s.chars().count();
203
204        if current_len >= width {
205            return handle;
206        }
207
208        let pad_count = width - current_len;
209        let mut padded = String::with_capacity(s.len() + pad_count * pad_char.len_utf8());
210        padded.push_str(s);
211
212        for _ in 0..pad_count {
213            padded.push(pad_char);
214        }
215
216        self.intern(&padded)
217    }
218
219    pub fn trim(&mut self, handle: StringHandle) -> StringHandle {
220        let s = self.get(handle);
221        let trimmed = s.trim().to_string();
222        self.intern(&trimmed)
223    }
224
225    pub fn trim_start(&mut self, handle: StringHandle) -> StringHandle {
226        let s = self.get(handle);
227        let trimmed = s.trim_start().to_string();
228        self.intern(&trimmed)
229    }
230
231    pub fn trim_end(&mut self, handle: StringHandle) -> StringHandle {
232        let s = self.get(handle);
233        let trimmed = s.trim_end().to_string();
234        self.intern(&trimmed)
235    }
236
237    pub fn get_allocated_bytes(&self) -> usize {
238        self.storage.len() + self.offsets.len() * std::mem::size_of::<(u32, u32)>()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_basic_interning() {
248        let mut interner = StringInterner::new();
249
250        let handle1 = interner.intern("hello");
251        let handle2 = interner.intern("world");
252        let handle3 = interner.intern("hello");
253
254        assert_eq!(handle1, handle3);
255        assert_ne!(handle1, handle2);
256        assert_eq!(interner.get(handle1), "hello");
257        assert_eq!(interner.get(handle2), "world");
258    }
259
260    #[test]
261    fn test_empty_string() {
262        let mut interner = StringInterner::new();
263
264        let handle = interner.intern("");
265        assert_eq!(interner.get(handle), "");
266    }
267
268    #[test]
269    fn test_unicode_strings() {
270        let mut interner = StringInterner::new();
271
272        let handle1 = interner.intern("πŸ¦€");
273        let handle2 = interner.intern("Hello, δΈ–η•Œ");
274
275        assert_eq!(interner.get(handle1), "πŸ¦€");
276        assert_eq!(interner.get(handle2), "Hello, δΈ–η•Œ");
277    }
278
279    #[test]
280    fn test_chars_iterator() {
281        let mut interner = StringInterner::new();
282
283        let handle = interner.intern("abc");
284        let chars: Vec<char> = interner.chars(handle).collect();
285
286        assert_eq!(chars, vec!['a', 'b', 'c']);
287    }
288
289    #[test]
290    fn test_hash_collisions() {
291        let mut interner = StringInterner::new();
292
293        let mut handles = Vec::new();
294        for i in 0..1000 {
295            let s = format!("string_{}", i);
296            let handle = interner.intern(&s);
297            handles.push((handle, s));
298        }
299
300        for (handle, expected) in handles {
301            assert_eq!(interner.get(handle), expected);
302        }
303    }
304
305    #[test]
306    fn test_deduplication() {
307        let mut interner = StringInterner::new();
308
309        let handle1 = interner.intern("test");
310        let handle2 = interner.intern("test");
311        let handle3 = interner.intern("test");
312
313        assert_eq!(handle1, handle2);
314        assert_eq!(handle2, handle3);
315
316        assert_eq!(interner.offsets.len(), 1);
317    }
318
319    #[test]
320    fn test_starts_with() {
321        let mut interner = StringInterner::new();
322
323        let hello = interner.intern("hello world");
324        let prefix1 = interner.intern("hello");
325        let prefix2 = interner.intern("world");
326        let prefix3 = interner.intern("");
327        let prefix4 = interner.intern("hello world");
328        let prefix5 = interner.intern("hello world!");
329
330        assert!(interner.starts_with(hello, prefix1));
331        assert!(!interner.starts_with(hello, prefix2));
332        assert!(interner.starts_with(hello, prefix3)); // empty string
333        assert!(interner.starts_with(hello, prefix4)); // exact match
334        assert!(!interner.starts_with(hello, prefix5)); // longer than string
335    }
336
337    #[test]
338    fn test_starts_with_unicode() {
339        let mut interner = StringInterner::new();
340
341        let text = interner.intern("πŸ¦€ Rust");
342        let prefix1 = interner.intern("πŸ¦€");
343        let prefix2 = interner.intern("Rust");
344
345        assert!(interner.starts_with(text, prefix1));
346        assert!(!interner.starts_with(text, prefix2));
347    }
348
349    #[test]
350    fn test_ends_with() {
351        let mut interner = StringInterner::new();
352
353        let hello = interner.intern("hello world");
354        let suffix1 = interner.intern("world");
355        let suffix2 = interner.intern("hello");
356        let suffix3 = interner.intern("");
357        let suffix4 = interner.intern("hello world");
358        let suffix5 = interner.intern("!hello world");
359
360        assert!(interner.ends_with(hello, suffix1));
361        assert!(!interner.ends_with(hello, suffix2));
362        assert!(interner.ends_with(hello, suffix3)); // empty string
363        assert!(interner.ends_with(hello, suffix4)); // exact match
364        assert!(!interner.ends_with(hello, suffix5)); // longer than string
365    }
366
367    #[test]
368    fn test_ends_with_unicode() {
369        let mut interner = StringInterner::new();
370
371        let text = interner.intern("Rust πŸ¦€");
372        let suffix1 = interner.intern("πŸ¦€");
373        let suffix2 = interner.intern("Rust");
374
375        assert!(interner.ends_with(text, suffix1));
376        assert!(!interner.ends_with(text, suffix2));
377    }
378
379    #[test]
380    fn test_contains() {
381        let mut interner = StringInterner::new();
382
383        let text = interner.intern("hello world");
384        let needle1 = interner.intern("lo wo");
385        let needle2 = interner.intern("hello");
386        let needle3 = interner.intern("world");
387        let needle4 = interner.intern("xyz");
388        let needle5 = interner.intern("");
389        let needle6 = interner.intern("hello world");
390        let needle7 = interner.intern("hello world!");
391
392        assert!(interner.contains(text, needle1));
393        assert!(interner.contains(text, needle2));
394        assert!(interner.contains(text, needle3));
395        assert!(!interner.contains(text, needle4));
396        assert!(interner.contains(text, needle5)); // empty string
397        assert!(interner.contains(text, needle6)); // exact match
398        assert!(!interner.contains(text, needle7)); // longer than string
399    }
400
401    #[test]
402    fn test_contains_unicode() {
403        let mut interner = StringInterner::new();
404
405        let text = interner.intern("Hello, δΈ–η•Œ!");
406        let needle1 = interner.intern("δΈ–η•Œ");
407        let needle2 = interner.intern("δΈ–");
408        let needle3 = interner.intern("η•Œ");
409        let needle4 = interner.intern("πŸ¦€");
410
411        assert!(interner.contains(text, needle1));
412        assert!(interner.contains(text, needle2));
413        assert!(interner.contains(text, needle3));
414        assert!(!interner.contains(text, needle4));
415    }
416
417    #[test]
418    fn test_to_uppercase() {
419        let mut interner = StringInterner::new();
420
421        let hello = interner.intern("hello");
422        let upper = interner.to_uppercase(hello);
423
424        assert_eq!(interner.get(upper), "HELLO");
425        assert_eq!(interner.get(hello), "hello"); // original unchanged
426    }
427
428    #[test]
429    fn test_to_uppercase_unicode() {
430        let mut interner = StringInterner::new();
431
432        let text = interner.intern("straße");
433        let upper = interner.to_uppercase(text);
434
435        assert_eq!(interner.get(upper), "STRASSE");
436    }
437
438    #[test]
439    fn test_to_uppercase_already_upper() {
440        let mut interner = StringInterner::new();
441
442        let upper = interner.intern("HELLO");
443        let result = interner.to_uppercase(upper);
444
445        // Should return the same handle due to interning
446        assert_eq!(result, upper);
447        assert_eq!(interner.get(result), "HELLO");
448    }
449
450    #[test]
451    fn test_to_lowercase() {
452        let mut interner = StringInterner::new();
453
454        let hello = interner.intern("HELLO");
455        let lower = interner.to_lowercase(hello);
456
457        assert_eq!(interner.get(lower), "hello");
458        assert_eq!(interner.get(hello), "HELLO"); // original unchanged
459    }
460
461    #[test]
462    fn test_to_lowercase_unicode() {
463        let mut interner = StringInterner::new();
464
465        let text = interner.intern("ΑΒΓΔ");
466        let lower = interner.to_lowercase(text);
467
468        assert_eq!(interner.get(lower), "Ξ±Ξ²Ξ³Ξ΄");
469    }
470
471    #[test]
472    fn test_to_lowercase_already_lower() {
473        let mut interner = StringInterner::new();
474
475        let lower = interner.intern("hello");
476        let result = interner.to_lowercase(lower);
477
478        // Should return the same handle due to interning
479        assert_eq!(result, lower);
480        assert_eq!(interner.get(result), "hello");
481    }
482
483    #[test]
484    fn test_length() {
485        let mut interner = StringInterner::new();
486
487        let empty = interner.intern("");
488        let hello = interner.intern("hello");
489        let emoji = interner.intern("πŸ¦€");
490        let multi = interner.intern("Hello, δΈ–η•Œ");
491
492        assert_eq!(interner.length(empty), 0);
493        assert_eq!(interner.length(hello), 5);
494        assert_eq!(interner.length(emoji), 1); // one character, not bytes
495        assert_eq!(interner.length(multi), 9); // counts chars not bytes
496    }
497
498    #[test]
499    fn test_length_multi_byte_chars() {
500        let mut interner = StringInterner::new();
501
502        let text = interner.intern("πŸ¦€πŸ¦€πŸ¦€");
503        assert_eq!(interner.length(text), 3);
504
505        let text2 = interner.intern("δΈ–η•Œ");
506        assert_eq!(interner.length(text2), 2);
507    }
508
509    #[test]
510    fn test_operations_with_empty_strings() {
511        let mut interner = StringInterner::new();
512
513        let empty = interner.intern("");
514        let text = interner.intern("test");
515
516        assert!(interner.starts_with(empty, empty));
517        assert!(interner.ends_with(empty, empty));
518        assert!(interner.contains(empty, empty));
519        assert!(interner.starts_with(text, empty));
520        assert!(interner.ends_with(text, empty));
521        assert!(interner.contains(text, empty));
522
523        let upper = interner.to_uppercase(empty);
524        let lower = interner.to_lowercase(empty);
525
526        assert_eq!(interner.get(upper), "");
527        assert_eq!(interner.get(lower), "");
528        assert_eq!(interner.length(empty), 0);
529    }
530
531    #[test]
532    fn test_char_at() {
533        let mut interner = StringInterner::new();
534
535        let hello = interner.intern("hello");
536
537        let h = interner.char_at(hello, 0);
538        assert_eq!(h.map(|handle| interner.get(handle)), Some("h"));
539
540        let e = interner.char_at(hello, 1);
541        assert_eq!(e.map(|handle| interner.get(handle)), Some("e"));
542
543        let o = interner.char_at(hello, 4);
544        assert_eq!(o.map(|handle| interner.get(handle)), Some("o"));
545
546        assert_eq!(interner.char_at(hello, 5), None); // out of bounds
547        assert_eq!(interner.char_at(hello, 100), None); // way out of bounds
548    }
549
550    #[test]
551    fn test_char_at_unicode() {
552        let mut interner = StringInterner::new();
553
554        let text = interner.intern("Hello, δΈ–η•Œ!");
555        assert_eq!(
556            interner.char_at(text, 0).map(|h| interner.get(h)),
557            Some("H")
558        );
559        assert_eq!(
560            interner.char_at(text, 7).map(|h| interner.get(h)),
561            Some("δΈ–")
562        );
563        assert_eq!(
564            interner.char_at(text, 8).map(|h| interner.get(h)),
565            Some("η•Œ")
566        );
567        assert_eq!(
568            interner.char_at(text, 9).map(|h| interner.get(h)),
569            Some("!")
570        );
571        assert_eq!(interner.char_at(text, 10), None);
572
573        let emoji = interner.intern("πŸ¦€πŸ¦€πŸ¦€");
574        assert_eq!(
575            interner.char_at(emoji, 0).map(|h| interner.get(h)),
576            Some("πŸ¦€")
577        );
578        assert_eq!(
579            interner.char_at(emoji, 1).map(|h| interner.get(h)),
580            Some("πŸ¦€")
581        );
582        assert_eq!(
583            interner.char_at(emoji, 2).map(|h| interner.get(h)),
584            Some("πŸ¦€")
585        );
586        assert_eq!(interner.char_at(emoji, 3), None);
587    }
588
589    #[test]
590    fn test_char_at_empty_string() {
591        let mut interner = StringInterner::new();
592
593        let empty = interner.intern("");
594        assert_eq!(interner.char_at(empty, 0), None);
595    }
596
597    #[test]
598    fn test_pad_start() {
599        let mut interner = StringInterner::new();
600
601        let hello = interner.intern("hello");
602        let padded = interner.pad_start(hello, 10, ' ');
603        assert_eq!(interner.get(padded), "     hello");
604
605        let padded_stars = interner.pad_start(hello, 8, '*');
606        assert_eq!(interner.get(padded_stars), "***hello");
607
608        // Test when string is already at target width
609        let no_pad = interner.pad_start(hello, 5, ' ');
610        assert_eq!(no_pad, hello);
611        assert_eq!(interner.get(no_pad), "hello");
612
613        // Test when string is longer than target width
614        let no_pad_long = interner.pad_start(hello, 3, ' ');
615        assert_eq!(no_pad_long, hello);
616        assert_eq!(interner.get(no_pad_long), "hello");
617    }
618
619    #[test]
620    fn test_pad_start_unicode() {
621        let mut interner = StringInterner::new();
622
623        let emoji = interner.intern("πŸ¦€");
624        let padded = interner.pad_start(emoji, 5, '-');
625        assert_eq!(interner.get(padded), "----πŸ¦€");
626
627        let text = interner.intern("δΈ–η•Œ");
628        let padded_text = interner.pad_start(text, 5, '0');
629        assert_eq!(interner.get(padded_text), "000δΈ–η•Œ");
630    }
631
632    #[test]
633    fn test_pad_end() {
634        let mut interner = StringInterner::new();
635
636        let hello = interner.intern("hello");
637        let padded = interner.pad_end(hello, 10, ' ');
638        assert_eq!(interner.get(padded), "hello     ");
639
640        let padded_stars = interner.pad_end(hello, 8, '*');
641        assert_eq!(interner.get(padded_stars), "hello***");
642
643        // Test when string is already at target width
644        let no_pad = interner.pad_end(hello, 5, ' ');
645        assert_eq!(no_pad, hello);
646        assert_eq!(interner.get(no_pad), "hello");
647
648        // Test when string is longer than target width
649        let no_pad_long = interner.pad_end(hello, 3, ' ');
650        assert_eq!(no_pad_long, hello);
651        assert_eq!(interner.get(no_pad_long), "hello");
652    }
653
654    #[test]
655    fn test_pad_end_unicode() {
656        let mut interner = StringInterner::new();
657
658        let emoji = interner.intern("πŸ¦€");
659        let padded = interner.pad_end(emoji, 5, '-');
660        assert_eq!(interner.get(padded), "πŸ¦€----");
661
662        let text = interner.intern("δΈ–η•Œ");
663        let padded_text = interner.pad_end(text, 5, '0');
664        assert_eq!(interner.get(padded_text), "δΈ–η•Œ000");
665    }
666
667    #[test]
668    fn test_trim() {
669        let mut interner = StringInterner::new();
670
671        let spaced = interner.intern("  hello  ");
672        let trimmed = interner.trim(spaced);
673        assert_eq!(interner.get(trimmed), "hello");
674
675        let tab_newline = interner.intern("\t\nhello\n\t");
676        let trimmed2 = interner.trim(tab_newline);
677        assert_eq!(interner.get(trimmed2), "hello");
678
679        // Test with no whitespace
680        let no_space = interner.intern("hello");
681        let trimmed3 = interner.trim(no_space);
682        assert_eq!(interner.get(trimmed3), "hello");
683
684        // Test with only whitespace
685        let only_space = interner.intern("   ");
686        let trimmed4 = interner.trim(only_space);
687        assert_eq!(interner.get(trimmed4), "");
688
689        // Test empty string
690        let empty = interner.intern("");
691        let trimmed5 = interner.trim(empty);
692        assert_eq!(interner.get(trimmed5), "");
693    }
694
695    #[test]
696    fn test_trim_unicode() {
697        let mut interner = StringInterner::new();
698
699        let text = interner.intern("  Hello, δΈ–η•Œ!  ");
700        let trimmed = interner.trim(text);
701        assert_eq!(interner.get(trimmed), "Hello, δΈ–η•Œ!");
702
703        let emoji = interner.intern("\n\tπŸ¦€πŸ¦€\t\n");
704        let trimmed_emoji = interner.trim(emoji);
705        assert_eq!(interner.get(trimmed_emoji), "πŸ¦€πŸ¦€");
706    }
707
708    #[test]
709    fn test_trim_start() {
710        let mut interner = StringInterner::new();
711
712        let spaced = interner.intern("  hello  ");
713        let trimmed = interner.trim_start(spaced);
714        assert_eq!(interner.get(trimmed), "hello  ");
715
716        let tab_newline = interner.intern("\t\nhello");
717        let trimmed2 = interner.trim_start(tab_newline);
718        assert_eq!(interner.get(trimmed2), "hello");
719
720        // Test with no leading whitespace
721        let no_space = interner.intern("hello  ");
722        let trimmed3 = interner.trim_start(no_space);
723        assert_eq!(interner.get(trimmed3), "hello  ");
724
725        // Test with only whitespace
726        let only_space = interner.intern("   ");
727        let trimmed4 = interner.trim_start(only_space);
728        assert_eq!(interner.get(trimmed4), "");
729    }
730
731    #[test]
732    fn test_trim_start_unicode() {
733        let mut interner = StringInterner::new();
734
735        let text = interner.intern("  πŸ¦€ Rust  ");
736        let trimmed = interner.trim_start(text);
737        assert_eq!(interner.get(trimmed), "πŸ¦€ Rust  ");
738    }
739
740    #[test]
741    fn test_trim_end() {
742        let mut interner = StringInterner::new();
743
744        let spaced = interner.intern("  hello  ");
745        let trimmed = interner.trim_end(spaced);
746        assert_eq!(interner.get(trimmed), "  hello");
747
748        let tab_newline = interner.intern("hello\n\t");
749        let trimmed2 = interner.trim_end(tab_newline);
750        assert_eq!(interner.get(trimmed2), "hello");
751
752        // Test with no trailing whitespace
753        let no_space = interner.intern("  hello");
754        let trimmed3 = interner.trim_end(no_space);
755        assert_eq!(interner.get(trimmed3), "  hello");
756
757        // Test with only whitespace
758        let only_space = interner.intern("   ");
759        let trimmed4 = interner.trim_end(only_space);
760        assert_eq!(interner.get(trimmed4), "");
761    }
762
763    #[test]
764    fn test_trim_end_unicode() {
765        let mut interner = StringInterner::new();
766
767        let text = interner.intern("  Rust πŸ¦€  ");
768        let trimmed = interner.trim_end(text);
769        assert_eq!(interner.get(trimmed), "  Rust πŸ¦€");
770    }
771}