1#![forbid(unsafe_code)]
2
3use crate::buffer::Buffer;
47use crate::cell::GraphemeId;
48use ahash::AHashMap;
49
50#[derive(Debug, Clone)]
52struct GraphemeSlot {
53 text: String,
55 #[allow(dead_code)]
58 width: u8,
59 refcount: u32,
61}
62
63#[derive(Debug, Clone)]
67pub struct GraphemePool {
68 slots: Vec<Option<GraphemeSlot>>,
70 lookup: AHashMap<String, GraphemeId>,
72 free_list: Vec<u32>,
74}
75
76impl GraphemePool {
77 pub fn new() -> Self {
79 Self {
80 slots: Vec::new(),
81 lookup: AHashMap::new(),
82 free_list: Vec::new(),
83 }
84 }
85
86 pub fn with_capacity(capacity: usize) -> Self {
88 Self {
89 slots: Vec::with_capacity(capacity),
90 lookup: AHashMap::with_capacity(capacity),
91 free_list: Vec::new(),
92 }
93 }
94
95 #[inline]
97 pub fn len(&self) -> usize {
98 self.slots.len().saturating_sub(self.free_list.len())
99 }
100
101 #[inline]
103 pub fn is_empty(&self) -> bool {
104 self.len() == 0
105 }
106
107 #[inline]
109 pub fn capacity(&self) -> usize {
110 self.slots.capacity()
111 }
112
113 pub fn intern(&mut self, text: &str, width: u8) -> GraphemeId {
127 assert!(width <= GraphemeId::MAX_WIDTH, "width overflow");
128
129 if let Some(&id) = self.lookup.get(text) {
131 debug_assert_eq!(
132 id.width() as u8,
133 width,
134 "intern() called with different width for the same text {:?}: existing={}, new={}",
135 text,
136 id.width(),
137 width
138 );
139 self.retain(id);
140 return id;
141 }
142
143 let slot_idx = self.alloc_slot();
145 let id = GraphemeId::new(slot_idx, width);
146
147 let slot = GraphemeSlot {
149 text: text.to_string(),
150 width,
151 refcount: 1,
152 };
153
154 if (slot_idx as usize) < self.slots.len() {
155 self.slots[slot_idx as usize] = Some(slot);
156 } else {
157 debug_assert_eq!(slot_idx as usize, self.slots.len());
158 self.slots.push(Some(slot));
159 }
160
161 self.lookup.insert(text.to_string(), id);
162 id
163 }
164
165 #[must_use]
169 pub fn get(&self, id: GraphemeId) -> Option<&str> {
170 self.slots
171 .get(id.slot())
172 .and_then(|slot| slot.as_ref())
173 .map(|slot| slot.text.as_str())
174 }
175
176 pub fn retain(&mut self, id: GraphemeId) {
180 if let Some(Some(slot)) = self.slots.get_mut(id.slot()) {
181 slot.refcount = slot.refcount.saturating_add(1);
182 }
183 }
184
185 pub fn release(&mut self, id: GraphemeId) {
190 let slot_idx = id.slot();
191 if let Some(Some(slot)) = self.slots.get_mut(slot_idx) {
192 slot.refcount = slot.refcount.saturating_sub(1);
193 if slot.refcount == 0 {
194 self.lookup.remove(&slot.text);
196 self.slots[slot_idx] = None;
198 self.free_list.push(slot_idx as u32);
200 }
201 }
202 }
203
204 pub fn refcount(&self, id: GraphemeId) -> u32 {
208 self.slots
209 .get(id.slot())
210 .and_then(|slot| slot.as_ref())
211 .map(|slot| slot.refcount)
212 .unwrap_or(0)
213 }
214
215 pub fn clear(&mut self) {
217 self.slots.clear();
218 self.lookup.clear();
219 self.free_list.clear();
220 }
221
222 fn alloc_slot(&mut self) -> u32 {
224 if let Some(idx) = self.free_list.pop() {
225 idx
226 } else {
227 let idx = self.slots.len() as u32;
228 assert!(
229 idx <= GraphemeId::MAX_SLOT,
230 "grapheme pool capacity exceeded"
231 );
232 idx
233 }
234 }
235
236 pub fn gc(&mut self, buffers: &[&Buffer]) {
246 for slot in self.slots.iter_mut().flatten() {
248 slot.refcount = 0;
249 }
250
251 for buf in buffers {
253 for cell in buf.cells() {
254 if let Some(id) = cell.content.grapheme_id() {
255 if let Some(Some(slot)) = self.slots.get_mut(id.slot()) {
258 slot.refcount = slot.refcount.saturating_add(1);
259 }
260 }
261 }
262 }
263
264 let mut keys_to_remove = Vec::new();
267
268 for (idx, slot_opt) in self.slots.iter_mut().enumerate() {
269 let should_free = slot_opt.as_ref().is_some_and(|s| s.refcount == 0);
271
272 if should_free {
273 if let Some(dead_slot) = slot_opt.take() {
275 keys_to_remove.push(dead_slot.text);
276 self.free_list.push(idx as u32);
277 }
278 }
279 }
280
281 for text in keys_to_remove {
282 self.lookup.remove(&text);
283 }
284 }
285}
286
287impl Default for GraphemePool {
288 fn default() -> Self {
289 Self::new()
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn intern_and_get() {
299 let mut pool = GraphemePool::new();
300 let id = pool.intern("๐จโ๐ฉโ๐งโ๐ฆ", 2);
301
302 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐งโ๐ฆ"));
303 assert_eq!(id.width(), 2);
304 }
305
306 #[test]
307 fn deduplication() {
308 let mut pool = GraphemePool::new();
309 let id1 = pool.intern("๐", 2);
310 let id2 = pool.intern("๐", 2);
311
312 assert_eq!(id1, id2);
314 assert_eq!(pool.refcount(id1), 2);
316 assert_eq!(pool.len(), 1);
318 }
319
320 #[test]
321 fn retain_and_release() {
322 let mut pool = GraphemePool::new();
323 let id = pool.intern("๐", 2);
324 assert_eq!(pool.refcount(id), 1);
325
326 pool.retain(id);
327 assert_eq!(pool.refcount(id), 2);
328
329 pool.release(id);
330 assert_eq!(pool.refcount(id), 1);
331
332 pool.release(id);
333 assert_eq!(pool.get(id), None);
335 assert_eq!(pool.len(), 0);
336 }
337
338 #[test]
339 fn slot_reuse() {
340 let mut pool = GraphemePool::new();
341
342 let id1 = pool.intern("A", 1);
344 pool.release(id1);
345 assert_eq!(pool.len(), 0);
346
347 let id2 = pool.intern("B", 1);
349 assert_eq!(id1.slot(), id2.slot());
350 assert_eq!(pool.get(id2), Some("B"));
351 }
352
353 #[test]
354 fn empty_pool() {
355 let pool = GraphemePool::new();
356 assert!(pool.is_empty());
357 assert_eq!(pool.len(), 0);
358 }
359
360 #[test]
361 fn multiple_graphemes() {
362 let mut pool = GraphemePool::new();
363
364 let id1 = pool.intern("๐จโ๐ป", 2);
365 let id2 = pool.intern("๐ฉโ๐ฌ", 2);
366 let id3 = pool.intern("๐ง๐ฝโ๐", 2);
367
368 assert_eq!(pool.len(), 3);
369 assert_ne!(id1, id2);
370 assert_ne!(id2, id3);
371
372 assert_eq!(pool.get(id1), Some("๐จโ๐ป"));
373 assert_eq!(pool.get(id2), Some("๐ฉโ๐ฌ"));
374 assert_eq!(pool.get(id3), Some("๐ง๐ฝโ๐"));
375 }
376
377 #[test]
378 fn width_preserved() {
379 let mut pool = GraphemePool::new();
380
381 let id1 = pool.intern("๐", 2);
383 let id2 = pool.intern("A", 1);
384 let id3 = pool.intern("ๆฅ", 2);
385
386 assert_eq!(id1.width(), 2);
387 assert_eq!(id2.width(), 1);
388 assert_eq!(id3.width(), 2);
389 }
390
391 #[test]
392 fn clear_pool() {
393 let mut pool = GraphemePool::new();
394 pool.intern("A", 1);
395 pool.intern("B", 1);
396 pool.intern("C", 1);
397
398 assert_eq!(pool.len(), 3);
399
400 pool.clear();
401 assert!(pool.is_empty());
402 }
403
404 #[test]
405 fn invalid_id_returns_none() {
406 let pool = GraphemePool::new();
407 let fake_id = GraphemeId::new(999, 1);
408 assert_eq!(pool.get(fake_id), None);
409 }
410
411 #[test]
412 fn release_invalid_id_is_safe() {
413 let mut pool = GraphemePool::new();
414 let fake_id = GraphemeId::new(999, 1);
415 pool.release(fake_id); }
417
418 #[test]
419 fn retain_invalid_id_is_safe() {
420 let mut pool = GraphemePool::new();
421 let fake_id = GraphemeId::new(999, 1);
422 pool.retain(fake_id); }
424
425 #[test]
426 #[should_panic(expected = "width overflow")]
427 fn width_overflow_panics() {
428 let mut pool = GraphemePool::new();
429 pool.intern("X", 128); }
431
432 #[test]
433 fn with_capacity() {
434 let pool = GraphemePool::with_capacity(100);
435 assert!(pool.capacity() >= 100);
436 assert!(pool.is_empty());
437 }
438
439 mod gc_tests {
440 use super::*;
441 use crate::buffer::Buffer;
442 use crate::cell::{Cell, CellContent};
443
444 fn buf_with_grapheme(id: GraphemeId) -> Buffer {
446 let mut buf = Buffer::new(4, 1);
447 let content = CellContent::from_grapheme(id);
448 buf.set(0, 0, Cell::new(content));
449 buf
450 }
451
452 #[test]
453 fn gc_retains_referenced_grapheme() {
454 let mut pool = GraphemePool::new();
455 let id = pool.intern("๐", 2);
456
457 let buf = buf_with_grapheme(id);
458 pool.gc(&[&buf]);
459
460 assert_eq!(pool.get(id), Some("๐"));
461 assert_eq!(pool.refcount(id), 1);
462 }
463
464 #[test]
465 fn gc_frees_unreferenced_grapheme() {
466 let mut pool = GraphemePool::new();
467 let id = pool.intern("๐", 2);
468
469 let buf = Buffer::new(4, 1);
471 pool.gc(&[&buf]);
472
473 assert_eq!(pool.get(id), None);
474 assert_eq!(pool.refcount(id), 0);
475 assert!(pool.is_empty());
476 }
477
478 #[test]
479 fn gc_with_multiple_buffers() {
480 let mut pool = GraphemePool::new();
481 let id1 = pool.intern("๐", 2);
482 let id2 = pool.intern("๐งช", 2);
483 let id3 = pool.intern("๐ฅ", 2);
484
485 let buf1 = buf_with_grapheme(id1);
487 let buf2 = buf_with_grapheme(id3);
488
489 pool.gc(&[&buf1, &buf2]);
490
491 assert_eq!(pool.get(id1), Some("๐"));
492 assert_eq!(pool.get(id2), None); assert_eq!(pool.get(id3), Some("๐ฅ"));
494 assert_eq!(pool.len(), 2);
495 }
496
497 #[test]
498 fn gc_with_multiple_references_in_buffer() {
499 let mut pool = GraphemePool::new();
500 let id = pool.intern("๐จโ๐ฉโ๐ง", 2);
501
502 let mut buf = Buffer::new(4, 1);
504 let content = CellContent::from_grapheme(id);
505 buf.set(0, 0, Cell::new(content));
506 buf.set(2, 0, Cell::new(content));
507
508 pool.gc(&[&buf]);
509
510 assert_eq!(pool.get(id), Some("๐จโ๐ฉโ๐ง"));
511 assert_eq!(pool.refcount(id), 2);
512 }
513
514 #[test]
515 fn gc_with_empty_pool() {
516 let mut pool = GraphemePool::new();
517 let buf = Buffer::new(4, 1);
518 pool.gc(&[&buf]); assert!(pool.is_empty());
520 }
521
522 #[test]
523 fn gc_with_no_buffers() {
524 let mut pool = GraphemePool::new();
525 let id = pool.intern("test", 1);
526 pool.gc(&[]);
527 assert_eq!(pool.get(id), None);
529 assert!(pool.is_empty());
530 }
531
532 #[test]
533 fn gc_freed_slots_are_reusable() {
534 let mut pool = GraphemePool::new();
535 let id1 = pool.intern("A", 1);
536 let _id2 = pool.intern("B", 1);
537 let slot1 = id1.slot();
538
539 let buf = buf_with_grapheme(id1);
541 pool.gc(&[&buf]);
542
543 let id3 = pool.intern("C", 1);
545 assert_eq!(pool.get(id3), Some("C"));
547 assert_eq!(pool.len(), 2); assert_eq!(pool.get(id1), Some("A"));
551 assert_eq!(id1.slot(), slot1);
552 }
553
554 #[test]
555 fn gc_resets_refcounts_accurately() {
556 let mut pool = GraphemePool::new();
557 let id = pool.intern("๐", 2);
558
559 pool.retain(id);
561 pool.retain(id);
562 assert_eq!(pool.refcount(id), 3);
563
564 let buf = buf_with_grapheme(id);
566 pool.gc(&[&buf]);
567
568 assert_eq!(pool.refcount(id), 1);
570 }
571
572 #[test]
573 fn gc_lookup_table_stays_consistent() {
574 let mut pool = GraphemePool::new();
575 let _id1 = pool.intern("A", 1);
576 let id2 = pool.intern("B", 1);
577
578 let buf = buf_with_grapheme(id2);
580 pool.gc(&[&buf]);
581
582 let id_new = pool.intern("A", 1);
584 assert_eq!(pool.get(id_new), Some("A"));
585
586 let id_b2 = pool.intern("B", 1);
588 assert_eq!(id_b2, id2);
589 }
590 }
591
592 mod property {
593 use super::*;
594 use proptest::prelude::*;
595
596 fn arb_grapheme() -> impl Strategy<Value = String> {
598 prop::string::string_regex(".{1,8}")
599 .unwrap()
600 .prop_filter("non-empty", |s| !s.is_empty())
601 }
602
603 fn arb_width() -> impl Strategy<Value = u8> {
605 0u8..=GraphemeId::MAX_WIDTH
606 }
607
608 proptest! {
609 #![proptest_config(ProptestConfig::with_cases(256))]
610
611 #[test]
613 fn intern_get_roundtrip(s in arb_grapheme(), w in arb_width()) {
614 let mut pool = GraphemePool::new();
615 let id = pool.intern(&s, w);
616 prop_assert_eq!(pool.get(id), Some(s.as_str()));
617 }
618
619 #[test]
621 fn intern_preserves_width(s in arb_grapheme(), w in arb_width()) {
622 let mut pool = GraphemePool::new();
623 let id = pool.intern(&s, w);
624 prop_assert_eq!(id.width(), w as usize);
625 }
626
627 #[test]
629 fn deduplication_same_id(s in arb_grapheme(), w in arb_width()) {
630 let mut pool = GraphemePool::new();
631 let id1 = pool.intern(&s, w);
632 let id2 = pool.intern(&s, w);
633 prop_assert_eq!(id1, id2);
634 prop_assert_eq!(pool.len(), 1);
635 }
636
637 #[test]
639 fn deduplication_refcount(s in arb_grapheme(), w in arb_width(), extra in 0u32..10) {
640 let mut pool = GraphemePool::new();
641 let id = pool.intern(&s, w);
642 for _ in 0..extra {
643 pool.intern(&s, w);
644 }
645 prop_assert_eq!(pool.refcount(id), 1 + extra);
646 }
647
648 #[test]
650 fn retain_release_refcount(
651 s in arb_grapheme(),
652 w in arb_width(),
653 retains in 0u32..10,
654 releases in 0u32..10
655 ) {
656 let mut pool = GraphemePool::new();
657 let id = pool.intern(&s, w);
658 for _ in 0..retains {
660 pool.retain(id);
661 }
662 let expected_after_retain = 1 + retains;
663 prop_assert_eq!(pool.refcount(id), expected_after_retain);
664
665 let actual_releases = releases.min(expected_after_retain - 1);
666 for _ in 0..actual_releases {
667 pool.release(id);
668 }
669 prop_assert_eq!(pool.refcount(id), expected_after_retain - actual_releases);
670 prop_assert_eq!(pool.get(id), Some(s.as_str()));
672 }
673
674 #[test]
676 fn release_to_zero_frees(s in arb_grapheme(), w in arb_width(), extra in 0u32..5) {
677 let mut pool = GraphemePool::new();
678 let id = pool.intern(&s, w);
679 for _ in 0..extra {
680 pool.retain(id);
681 }
682 for _ in 0..=extra {
684 pool.release(id);
685 }
686 prop_assert_eq!(pool.get(id), None);
687 prop_assert_eq!(pool.refcount(id), 0);
688 prop_assert!(pool.is_empty());
689 }
690
691 #[test]
693 fn slot_reuse_after_free(
694 s1 in arb_grapheme(),
695 s2 in arb_grapheme(),
696 w in arb_width()
697 ) {
698 let mut pool = GraphemePool::new();
699 let id1 = pool.intern(&s1, w);
700 let slot1 = id1.slot();
701 pool.release(id1);
702
703 let id2 = pool.intern(&s2, w);
705 prop_assert_eq!(id2.slot(), slot1);
706 prop_assert_eq!(pool.get(id2), Some(s2.as_str()));
707 }
708
709 #[test]
711 fn len_invariant(count in 1usize..20) {
712 let mut pool = GraphemePool::new();
713 let mut ids = Vec::new();
714 for i in 0..count {
715 let s = format!("g{i}");
716 ids.push(pool.intern(&s, 1));
717 }
718 prop_assert_eq!(pool.len(), count);
719
720 let release_count = count / 2;
722 for id in &ids[..release_count] {
723 pool.release(*id);
724 }
725 prop_assert_eq!(pool.len(), count - release_count);
726 }
727
728 #[test]
730 fn distinct_strings_distinct_ids(count in 2usize..15) {
731 let mut pool = GraphemePool::new();
732 let mut ids = Vec::new();
733 for i in 0..count {
734 let s = format!("unique_{i}");
735 ids.push(pool.intern(&s, 1));
736 }
737 for i in 0..ids.len() {
739 for j in (i + 1)..ids.len() {
740 prop_assert_ne!(ids[i], ids[j]);
741 }
742 }
743 }
744
745 #[test]
747 fn clear_resets_all(count in 1usize..20) {
748 let mut pool = GraphemePool::new();
749 let mut ids = Vec::new();
750 for i in 0..count {
751 let s = format!("c{i}");
752 ids.push(pool.intern(&s, 1));
753 }
754 pool.clear();
755 prop_assert!(pool.is_empty());
756 prop_assert_eq!(pool.len(), 0);
757 for id in &ids {
758 prop_assert_eq!(pool.get(*id), None);
759 }
760 }
761
762 #[test]
766 fn positive_refcount_implies_valid_slot(
767 count in 1usize..10,
768 retains in proptest::collection::vec(0u32..5, 1..10),
769 ) {
770 let mut pool = GraphemePool::new();
771 let mut ids = Vec::new();
772 for i in 0..count {
773 let s = format!("inv_{i}");
774 ids.push(pool.intern(&s, 1));
775 }
776
777 for (i, &extra) in retains.iter().enumerate() {
779 let id = ids[i % count];
780 for _ in 0..extra {
781 pool.retain(id);
782 }
783 }
784
785 for (i, &id) in ids.iter().enumerate() {
787 let rc = pool.refcount(id);
788 if rc > 0 {
789 prop_assert!(pool.get(id).is_some(),
790 "slot {} has refcount {} but get() returned None", i, rc);
791 }
792 }
793 }
794
795 #[test]
797 fn release_decrements_by_one(s in arb_grapheme(), w in arb_width(), retains in 1u32..8) {
798 let mut pool = GraphemePool::new();
799 let id = pool.intern(&s, w);
800 for _ in 0..retains {
801 pool.retain(id);
802 }
803 let rc_before = pool.refcount(id);
804 pool.release(id);
805 let rc_after = pool.refcount(id);
806 prop_assert_eq!(rc_after, rc_before - 1,
807 "release should decrement refcount by exactly 1");
808 }
809
810 #[test]
812 fn over_release_does_not_corrupt(count in 1usize..5) {
813 let mut pool = GraphemePool::new();
814 let mut ids = Vec::new();
815 for i in 0..count {
816 let s = format!("or_{i}");
817 ids.push(pool.intern(&s, 1));
818 }
819
820 let victim = ids[0];
822 pool.release(victim);
823 prop_assert_eq!(pool.refcount(victim), 0);
824 prop_assert_eq!(pool.get(victim), None);
825
826 pool.release(victim);
828 prop_assert_eq!(pool.refcount(victim), 0);
829
830 for &id in &ids[1..] {
832 prop_assert!(pool.get(id).is_some(),
833 "over-release corrupted unrelated slot");
834 prop_assert!(pool.refcount(id) > 0);
835 }
836 }
837
838 #[test]
840 fn cross_pool_id_is_invalid(s in arb_grapheme(), w in arb_width()) {
841 let mut pool_a = GraphemePool::new();
842 let pool_b = GraphemePool::new();
843 let id = pool_a.intern(&s, w);
844
845 prop_assert_eq!(pool_b.get(id), None,
847 "GraphemeId from pool A should not be valid in pool B");
848 }
849 }
850 }
851
852 #[test]
855 fn pool_debug_and_clone() {
856 let mut pool = GraphemePool::new();
857 pool.intern("๐", 2);
858 let dbg = format!("{:?}", pool);
859 assert!(dbg.contains("GraphemePool"), "Debug: {dbg}");
860 let cloned = pool.clone();
861 assert_eq!(cloned.len(), 1);
862 let id = cloned.lookup.values().next().copied().unwrap();
864 assert_eq!(cloned.get(id), Some("๐"));
865 }
866
867 #[test]
868 fn pool_default_is_new() {
869 let pool = GraphemePool::default();
870 assert!(pool.is_empty());
871 assert_eq!(pool.len(), 0);
872 }
873
874 #[test]
875 fn intern_width_zero() {
876 let mut pool = GraphemePool::new();
877 let id = pool.intern("zero-width", 0);
878 assert_eq!(id.width(), 0);
879 assert_eq!(pool.get(id), Some("zero-width"));
880 }
881
882 #[test]
883 fn intern_width_max() {
884 let mut pool = GraphemePool::new();
885 let id = pool.intern("max-width", 127);
886 assert_eq!(id.width(), 127);
887 assert_eq!(pool.get(id), Some("max-width"));
888 }
889
890 #[test]
891 fn intern_empty_string() {
892 let mut pool = GraphemePool::new();
893 let id = pool.intern("", 0);
894 assert_eq!(pool.get(id), Some(""));
895 }
896
897 #[test]
898 fn intern_long_string() {
899 let mut pool = GraphemePool::new();
900 let long = "a".repeat(1000);
901 let id = pool.intern(&long, 1);
902 assert_eq!(pool.get(id), Some(long.as_str()));
903 }
904
905 #[test]
906 fn clear_then_intern_reuses_from_scratch() {
907 let mut pool = GraphemePool::new();
908 pool.intern("A", 1);
909 pool.intern("B", 1);
910 pool.clear();
911 assert!(pool.is_empty());
912 let id = pool.intern("C", 1);
914 assert_eq!(id.slot(), 0);
915 assert_eq!(pool.get(id), Some("C"));
916 assert_eq!(pool.len(), 1);
917 }
918
919 #[test]
920 fn with_capacity_then_intern() {
921 let mut pool = GraphemePool::with_capacity(50);
922 for i in 0..50 {
923 pool.intern(&format!("g{i}"), 1);
924 }
925 assert_eq!(pool.len(), 50);
926 }
927
928 #[test]
929 fn refcount_of_freed_slot_is_zero() {
930 let mut pool = GraphemePool::new();
931 let id = pool.intern("temp", 1);
932 pool.release(id);
933 assert_eq!(pool.refcount(id), 0);
934 }
935
936 #[test]
937 fn refcount_of_invalid_id_is_zero() {
938 let pool = GraphemePool::new();
939 assert_eq!(pool.refcount(GraphemeId::new(0, 1)), 0);
940 assert_eq!(pool.refcount(GraphemeId::new(999, 1)), 0);
941 }
942
943 #[test]
944 fn retain_freed_slot_is_noop() {
945 let mut pool = GraphemePool::new();
946 let id = pool.intern("temp", 1);
947 pool.release(id);
948 pool.retain(id); assert_eq!(pool.refcount(id), 0);
951 assert_eq!(pool.get(id), None);
952 }
953
954 #[test]
955 fn double_release_is_safe() {
956 let mut pool = GraphemePool::new();
957 let id = pool.intern("temp", 1);
958 pool.release(id); pool.release(id); assert_eq!(pool.refcount(id), 0);
961 }
962
963 #[test]
964 fn multiple_slot_reuse_cycles() {
965 let mut pool = GraphemePool::new();
966 for cycle in 0..5 {
967 let id = pool.intern(&format!("cycle{cycle}"), 1);
968 assert_eq!(id.slot(), 0); assert_eq!(pool.get(id), Some(format!("cycle{cycle}").as_str()));
970 pool.release(id);
971 }
972 assert!(pool.is_empty());
973 }
974
975 #[test]
976 fn free_list_ordering() {
977 let mut pool = GraphemePool::new();
978 let id0 = pool.intern("A", 1);
979 let id1 = pool.intern("B", 1);
980 let id2 = pool.intern("C", 1);
981 assert_eq!(id0.slot(), 0);
982 assert_eq!(id1.slot(), 1);
983 assert_eq!(id2.slot(), 2);
984
985 pool.release(id0);
987 pool.release(id2);
988 assert_eq!(pool.len(), 1); let new1 = pool.intern("D", 1);
992 assert_eq!(new1.slot(), 2);
993 let new2 = pool.intern("E", 1);
994 assert_eq!(new2.slot(), 0);
995 }
996
997 #[test]
998 fn intern_after_release_deduplicates_correctly() {
999 let mut pool = GraphemePool::new();
1000 let id1 = pool.intern("X", 1);
1001 pool.release(id1);
1002 assert_eq!(pool.get(id1), None);
1004
1005 let id2 = pool.intern("X", 1);
1007 assert_eq!(pool.get(id2), Some("X"));
1008 assert_eq!(pool.refcount(id2), 1);
1009 }
1010
1011 #[test]
1012 fn clone_independence() {
1013 let mut pool = GraphemePool::new();
1014 let id = pool.intern("shared", 1);
1015
1016 let mut cloned = pool.clone();
1017 pool.release(id);
1019 assert_eq!(pool.get(id), None);
1020
1021 assert_eq!(cloned.get(id), Some("shared"));
1023 assert_eq!(cloned.refcount(id), 1);
1024
1025 cloned.retain(id);
1027 assert_eq!(cloned.refcount(id), 2);
1028 assert_eq!(pool.refcount(id), 0);
1030 }
1031
1032 #[test]
1033 fn gc_double_run_idempotent() {
1034 use crate::buffer::Buffer;
1035 use crate::cell::{Cell, CellContent};
1036
1037 let mut pool = GraphemePool::new();
1038 let id = pool.intern("keep", 1);
1039 let _id2 = pool.intern("drop", 1);
1040
1041 let mut buf = Buffer::new(4, 1);
1042 buf.set(0, 0, Cell::new(CellContent::from_grapheme(id)));
1043
1044 pool.gc(&[&buf]);
1045 assert_eq!(pool.len(), 1);
1046 assert_eq!(pool.get(id), Some("keep"));
1047
1048 pool.gc(&[&buf]);
1050 assert_eq!(pool.len(), 1);
1051 assert_eq!(pool.refcount(id), 1);
1052 }
1053
1054 #[test]
1055 fn gc_with_already_freed_slots() {
1056 use crate::buffer::Buffer;
1057
1058 let mut pool = GraphemePool::new();
1059 let id1 = pool.intern("A", 1);
1060 let id2 = pool.intern("B", 1);
1061
1062 pool.release(id1);
1064 assert_eq!(pool.len(), 1);
1065
1066 let buf = Buffer::new(4, 1);
1068 pool.gc(&[&buf]);
1069
1070 assert!(pool.is_empty());
1071 assert_eq!(pool.get(id2), None);
1072 }
1073
1074 #[test]
1075 fn stress_100_graphemes() {
1076 let mut pool = GraphemePool::new();
1077 let mut ids = Vec::new();
1078 for i in 0..100 {
1079 ids.push(pool.intern(&format!("g{i:03}"), 1));
1080 }
1081 assert_eq!(pool.len(), 100);
1082
1083 for (i, &id) in ids.iter().enumerate() {
1085 assert_eq!(pool.get(id), Some(format!("g{i:03}").as_str()));
1086 }
1087
1088 for i in (0..100).step_by(2) {
1090 pool.release(ids[i]);
1091 }
1092 assert_eq!(pool.len(), 50);
1093
1094 for i in (1..100).step_by(2) {
1096 assert_eq!(pool.get(ids[i]), Some(format!("g{i:03}").as_str()));
1097 }
1098 }
1099
1100 #[test]
1101 fn capacity_grows_with_interns() {
1102 let mut pool = GraphemePool::new();
1103 let cap_before = pool.capacity();
1104 for i in 0..20 {
1105 pool.intern(&format!("grow{i}"), 1);
1106 }
1107 assert!(pool.capacity() >= 20);
1109 assert!(pool.capacity() >= cap_before);
1110 }
1111
1112 #[test]
1113 fn len_after_mixed_operations() {
1114 let mut pool = GraphemePool::new();
1115 assert_eq!(pool.len(), 0);
1116
1117 let a = pool.intern("A", 1);
1118 assert_eq!(pool.len(), 1);
1119
1120 let b = pool.intern("B", 1);
1121 assert_eq!(pool.len(), 2);
1122
1123 pool.intern("A", 1);
1125 assert_eq!(pool.len(), 2);
1126
1127 pool.release(a);
1128 assert_eq!(pool.len(), 2);
1130
1131 pool.release(a);
1132 assert_eq!(pool.len(), 1);
1134
1135 pool.release(b);
1136 assert_eq!(pool.len(), 0);
1137 assert!(pool.is_empty());
1138 }
1139}