1use std::{borrow::Cow, cell::RefCell, fmt, rc::Rc};
3
4use libxml::tree::Node;
5
6use crate::{
7 BoxOps, NO_PROPERTIES,
8 alignment::Alignment,
9 comment::Comment,
10 common::{
11 arena::{self, SymHashMap as HashMap, SymStr},
12 dimension::Dimension,
13 error::*,
14 font::Font,
15 locator::Locator,
16 numeric_ops::NumericOps,
17 object::Object,
18 store::Stored,
19 },
20 definition::register::RegisterValue,
21 document::Document,
22 keyvals::KeyVals,
23 list::List,
24 tbox::Tbox,
25 tokens::Tokens,
26 whatsit::Whatsit,
27};
28
29#[derive(Clone)]
40pub struct Digested(Rc<DigestedData>);
41pub enum DigestedData {
51 TBox(RefCell<Tbox>),
53 Whatsit(RefCell<Whatsit>),
55 Alignment(Box<RefCell<Alignment>>),
57 List(RefCell<List>),
59 Postponed(Tokens),
61 KeyVals(Box<KeyVals>),
72 RegisterValue(RegisterValue),
74 Comment(Comment),
76}
77
78impl fmt::Debug for Digested {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", *self.0) }
81}
82impl fmt::Debug for DigestedData {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 use DigestedData::*;
85 match self {
86 TBox(v) => write!(f, "{v:?}"),
87 Whatsit(v) => write!(f, "{v:?}"),
88 Alignment(a) => write!(f, "{a:?}"),
89 List(v) => write!(f, "{v:?}"),
90 Postponed(v) => write!(f, "{v:?}"),
91 KeyVals(v) => write!(f, "{v:?}"),
92 RegisterValue(v) => write!(f, "{v:?}"),
93 Comment(v) => write!(f, "{v:?}"),
94 }
95 }
96}
97
98impl PartialEq for Digested {
99 fn eq(&self, other: &Digested) -> bool {
100 use DigestedData::*;
101 match *self.0 {
102 TBox(ref tb) => {
103 if let TBox(ref tb2) = *other.0 {
104 tb == tb2
105 } else {
106 false
107 }
108 },
109 Whatsit(ref tb) => {
110 if let Whatsit(ref tb2) = *other.0 {
111 *tb.borrow() == *tb2.borrow()
112 } else {
113 false
114 }
115 },
116 Alignment(ref tb) => {
117 if let Alignment(ref tb2) = *other.0 {
118 *tb.borrow() == *tb2.borrow()
119 } else {
120 false
121 }
122 },
123 List(ref tb) => {
124 if let List(ref tb2) = *other.0 {
125 tb == tb2
126 } else {
127 false
128 }
129 },
130 Postponed(ref tb) => {
131 if let Postponed(ref tb2) = *other.0 {
132 tb == tb2
133 } else {
134 false
135 }
136 },
137 KeyVals(ref tb) => {
138 if let KeyVals(ref tb2) = *other.0 {
139 tb == tb2
140 } else {
141 false
142 }
143 },
144 RegisterValue(ref tb) => {
145 if let RegisterValue(ref tb2) = *other.0 {
146 tb == tb2
147 } else {
148 false
149 }
150 },
151 Comment(ref tb) => {
152 if let Comment(ref tb2) = *other.0 {
153 tb == tb2
154 } else {
155 false
156 }
157 },
158 }
159 }
160}
161
162impl<'a> From<&'a String> for Digested {
165 fn from(value: &'a String) -> Digested {
166 Digested(Rc::new(DigestedData::Postponed(Tokens::new(ExplodeText!(
167 value
168 )))))
169 }
170}
171impl From<String> for Digested {
172 fn from(value: String) -> Digested {
173 Digested(Rc::new(DigestedData::Postponed(Tokens::new(ExplodeText!(
174 value
175 )))))
176 }
177}
178impl From<SymStr> for Digested {
179 fn from(sym: SymStr) -> Digested {
180 let tks = SymExplodeText!(sym);
181 Digested(Rc::new(DigestedData::Postponed(Tokens::new(tks))))
182 }
183}
184
185impl From<Tokens> for Digested {
186 fn from(value: Tokens) -> Digested { Digested(Rc::new(DigestedData::Postponed(value))) }
187}
188impl From<Tbox> for Digested {
189 fn from(value: Tbox) -> Digested { Digested(Rc::new(DigestedData::TBox(RefCell::new(value)))) }
190}
191impl From<List> for Digested {
192 fn from(value: List) -> Digested { Digested(Rc::new(DigestedData::List(RefCell::new(value)))) }
193}
194impl From<Whatsit> for Digested {
195 fn from(value: Whatsit) -> Digested {
196 Digested(Rc::new(DigestedData::Whatsit(RefCell::new(value))))
197 }
198}
199impl From<Alignment> for Digested {
200 fn from(value: Alignment) -> Digested {
201 Digested(Rc::new(DigestedData::Alignment(Box::new(RefCell::new(
202 value,
203 )))))
204 }
205}
206impl From<KeyVals> for Digested {
207 fn from(value: KeyVals) -> Digested { Digested(Rc::new(DigestedData::KeyVals(Box::new(value)))) }
208}
209impl From<RegisterValue> for Digested {
210 fn from(value: RegisterValue) -> Digested {
211 Digested(Rc::new(DigestedData::RegisterValue(value)))
212 }
213}
214impl From<Comment> for Digested {
215 fn from(value: Comment) -> Digested { Digested(Rc::new(DigestedData::Comment(value))) }
216}
217
218impl<'a> From<&'a Digested> for Option<Digested> {
219 fn from(value: &'a Digested) -> Option<Digested> { Some(value.clone()) }
220}
221
222impl From<Digested> for Result<Digested> {
229 fn from(value: Digested) -> Result<Digested> { Ok(value) }
230}
231impl From<Digested> for Result<Vec<Digested>> {
232 fn from(value: Digested) -> Result<Vec<Digested>> { Ok(vec![value]) }
233}
234impl From<Digested> for Result<Option<Digested>> {
235 fn from(value: Digested) -> Result<Option<Digested>> { Ok(Some(value)) }
236}
237
238impl Default for Digested {
239 fn default() -> Self { Digested(Rc::new(DigestedData::TBox(RefCell::new(Tbox::default())))) }
240}
241
242impl fmt::Display for Digested {
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 use DigestedData::*;
245 match *self.0 {
246 TBox(ref b) => write!(f, "{}", b.borrow()),
247 List(ref l) => write!(f, "{}", l.borrow()),
248 Whatsit(ref w) => write!(f, "{}", w.borrow()),
249 Alignment(ref a) => write!(f, "{}", a.borrow()),
250 Postponed(ref t) => write!(f, "{t}"),
251 KeyVals(ref kvs) => write!(f, "{kvs}"),
252 Comment(ref c) => write!(f, "{c}"),
253 RegisterValue(ref rv) => write!(f, "{rv}"),
254 }
255 }
256}
257impl Object for Digested {
258 fn stringify(&self) -> String {
259 use DigestedData::*;
260 match *self.0 {
261 TBox(ref b) => b.borrow().stringify(),
262 List(ref l) => l.borrow().stringify(),
263 Whatsit(ref w) => w.borrow().stringify(),
264 Alignment(ref w) => w.borrow().stringify(),
265 Postponed(ref t) => (*t).stringify(),
266 KeyVals(ref kvs) => kvs.stringify(),
267 Comment(ref c) => c.stringify(),
268 RegisterValue(ref rv) => (*rv).stringify(),
269 }
270 }
271 fn get_locator(&self) -> Option<Locator> {
272 use DigestedData::*;
273 match *self.0 {
274 TBox(ref b) => b.borrow().get_locator(),
275 List(ref l) => l.borrow().get_locator(),
276 Comment(ref c) => c.get_locator(),
277 Whatsit(ref w) => w.borrow().get_locator(),
278 Alignment(ref w) => w.borrow().get_locator(),
279 KeyVals(ref kvs) => kvs.get_locator(), RegisterValue(ref rv) => rv.get_locator(),
281 Postponed(ref _t) => None, }
283 }
284 fn revert(&self) -> Result<Tokens> {
292 use DigestedData::*;
293 match *self.0 {
294 TBox(ref b) => b.borrow().revert(),
295 List(ref l) => l.borrow().revert(),
296 Whatsit(ref w) => w.borrow().revert(),
297 Alignment(ref w) => match w.try_borrow() {
304 Ok(al) => al.revert(),
305 Err(_) => {
306 Error!(
307 "unexpected",
308 "self_referential_alignment",
309 "Reverting a re-entrant alignment to empty tokens (source text is lost)"
310 );
311 Ok(Tokens::default())
312 },
313 },
314 Postponed(ref t) => Ok(t.clone()),
315 KeyVals(ref kvs) => kvs.revert(),
316 Comment(ref c) => c.revert(),
317 RegisterValue(ref rv) => rv.revert(),
318 }
319 }
320}
321
322impl BoxOps for Digested {
323 fn unlist(&self) -> Vec<Digested> {
324 use DigestedData::*;
325 match *self.0 {
326 TBox(_) | Whatsit(_) | Alignment(_) | KeyVals(_) | Comment(_) | Postponed(_)
327 | RegisterValue(_) => {
328 vec![self.clone()]
329 },
330 List(ref l) => l.borrow().unlist(),
331 }
332 }
333 fn unlist_ref(&self) -> Vec<Cow<'_, Digested>> {
334 use DigestedData::*;
335 match *self.0 {
336 TBox(_) | Whatsit(_) | Alignment(_) | KeyVals(_) | Comment(_) | Postponed(_)
337 | RegisterValue(_) => {
338 vec![Cow::Borrowed(self)]
339 },
340 List(ref l) => l.borrow().unlist().into_iter().map(Cow::Owned).collect(),
341 }
342 }
343
344 fn be_absorbed(&self, document: &mut Document) -> Result<Vec<Node>> {
345 use DigestedData::*;
346 match &*self.0 {
347 TBox(b) => b.borrow().be_absorbed(document),
348 List(l) => l.borrow().be_absorbed(document),
349 Comment(c) => c.be_absorbed(document),
350 Whatsit(w) => w.borrow().be_absorbed(document),
351 Alignment(w) => match w.try_borrow_mut() {
358 Ok(mut al) => al.be_absorbed_mut(document),
359 Err(_) => {
360 Error!(
361 "unexpected",
362 "self_referential_alignment",
363 "Skipping absorption of a re-entrant alignment (its cells are lost)"
364 );
365 Ok(Vec::new())
366 },
367 },
368 KeyVals(kvs) => kvs.be_absorbed(document),
369 Postponed(_) => Ok(Vec::new()), RegisterValue(_rv) => Ok(Vec::new()), }
372 }
373
374 fn with_properties<R, FnR>(&self, caller: FnR) -> R
375 where FnR: FnOnce(&HashMap<Stored>) -> R {
376 use DigestedData::*;
377 match &*self.0 {
388 TBox(b) => match b.try_borrow() {
389 Ok(b) => caller(b.get_properties()),
390 Err(_) => caller(&NO_PROPERTIES),
391 },
392 List(l) => match l.try_borrow() {
393 Ok(l) => caller(l.get_properties()),
394 Err(_) => caller(&NO_PROPERTIES),
395 },
396 Comment(c) => caller(c.get_properties()),
397 Whatsit(w) => match w.try_borrow() {
398 Ok(w) => caller(w.get_properties()),
399 Err(_) => caller(&NO_PROPERTIES),
400 },
401 Alignment(w) => match w.try_borrow() {
402 Ok(w) => caller(w.get_properties()),
403 Err(_) => caller(&NO_PROPERTIES),
404 },
405 KeyVals(_) | Postponed(_) | RegisterValue(_) => caller(&NO_PROPERTIES),
406 }
407 }
408 fn set_property<T: Into<Stored>>(&mut self, key: &str, value: T) {
413 use DigestedData::*;
414 match *self.0 {
415 TBox(ref b) => b.borrow_mut().set_property(key, value),
418 List(ref l) => l.borrow_mut().set_property(key, value),
419 Whatsit(ref w) => w.borrow_mut().set_property(key, value),
420 _ => { },
421 }
422 }
423
424 fn get_property(&self, key: &str) -> Option<Cow<'_, Stored>> {
425 use DigestedData::*;
426 match *self.0 {
427 TBox(ref b) => b
428 .borrow()
429 .get_property(key)
430 .map(|v| Cow::Owned(v.into_owned())),
431 List(ref l) => l
432 .borrow()
433 .get_property(key)
434 .map(|v| Cow::Owned(v.into_owned())),
435 Whatsit(ref w) => w
436 .borrow()
437 .get_property(key)
438 .map(|v| Cow::Owned(v.into_owned())),
439 _ => None,
440 }
441 }
442 fn get_string(&self) -> Result<Cow<'_, str>> {
443 use DigestedData::*;
444 match *self.0 {
445 TBox(ref b) => b.borrow().get_string().map(|v| Cow::Owned(v.into_owned())),
446 List(ref l) => l.borrow().get_string().map(|v| Cow::Owned(v.into_owned())),
447 Whatsit(ref w) => w.borrow().get_string().map(|v| Cow::Owned(v.into_owned())),
448 _ => Ok(Cow::Borrowed("")),
449 }
450 }
451 fn has_property(&self, key: &str) -> bool {
452 use DigestedData::*;
453 match *self.0 {
454 TBox(ref b) => b.borrow().has_property(key),
455 List(ref l) => l.borrow().has_property(key),
456 Whatsit(ref w) => w.borrow().has_property(key),
457 _ => false,
458 }
459 }
460 fn get_body(&self) -> Result<Option<Digested>> {
461 use DigestedData::*;
462 match *self.0 {
463 TBox(_) | List(_) => Ok(Some(self.clone())),
465 Whatsit(ref w) => w.borrow().get_body(),
466 _ => Ok(None),
467 }
468 }
469 fn get_property_bool(&self, key: &str) -> bool {
470 use DigestedData::*;
471 match *self.0 {
472 TBox(ref b) => b.borrow().get_property_bool(key),
473 List(ref l) => l.borrow().get_property_bool(key),
474 Whatsit(ref w) => w.borrow().get_property_bool(key),
475 Alignment(_) | KeyVals(_) | Comment(_) | Postponed(_) | RegisterValue(_) => false,
476 }
477 }
478 fn get_font(&self) -> Result<Option<Rc<Font>>> {
494 use DigestedData::*;
495 match *self.0 {
496 TBox(ref b) => Ok(b.try_borrow().ok().map(|b| Rc::clone(&b.font))),
500 List(ref l) => Ok(l.try_borrow().ok().and_then(|l| l.font.clone())),
501 Whatsit(ref w) => match w.try_borrow() {
502 Ok(w) => w.get_font(),
503 Err(_) => Ok(None),
504 },
505 Postponed(ref _tks) => Ok(None),
506 _ => Ok(None),
507 }
508 }
509
510 fn compute_size(&self, options: HashMap<Stored>) -> Result<(Dimension, Dimension, Dimension)> {
514 use DigestedData::*;
515 let zero = (Dimension::new(0), Dimension::new(0), Dimension::new(0));
522 match *self.0 {
523 TBox(ref b) => match b.try_borrow_mut() {
524 Ok(mut x) => x.compute_size_and_cache(options),
525 Err(_) => Ok(zero),
526 },
527 List(ref l) => match l.try_borrow_mut() {
528 Ok(mut x) => x.compute_size_and_cache(options),
529 Err(_) => Ok(zero),
530 },
531 KeyVals(ref kvs) => kvs.compute_size(options),
532 Whatsit(ref w) => match w.try_borrow_mut() {
533 Ok(mut x) => x.compute_size_and_cache(options),
534 Err(_) => Ok(zero),
535 },
536 Alignment(ref w) => match w.try_borrow_mut() {
537 Ok(mut x) => x.compute_size_and_cache(options),
538 Err(_) => Ok(zero),
539 },
540 Postponed(_) | RegisterValue(_) | Comment(_) => Ok(zero),
541 }
542 }
543}
544
545const FP_BUDGET: u32 = 48;
549
550pub(crate) const EB_BUDGET: u32 = 256;
555
556impl Digested {
557 pub fn data(&self) -> &DigestedData { &self.0 }
559
560 pub fn cycle_fingerprint(&self) -> u64 {
575 use std::hash::Hasher;
576 let mut h = rustc_hash::FxHasher::default();
577 let mut budget: u32 = FP_BUDGET;
578 self.fingerprint_into(&mut h, &mut budget);
579 h.finish()
580 }
581
582 fn fingerprint_into<H: std::hash::Hasher>(&self, h: &mut H, budget: &mut u32) {
589 use std::hash::Hash;
590 if *budget == 0 {
591 return;
592 }
593 *budget -= 1;
594 match self.data() {
595 DigestedData::TBox(b) => {
596 0u8.hash(h);
597 if let Ok(tb) = b.try_borrow() {
598 tb.text.hash(h);
599 }
600 },
601 DigestedData::Whatsit(w) => {
602 1u8.hash(h);
603 if let Ok(wb) = w.try_borrow() {
604 (Rc::as_ptr(&wb.definition) as *const () as usize).hash(h);
608 wb.args.len().hash(h);
609 for arg in &wb.args {
610 if *budget == 0 {
611 break;
612 }
613 match arg {
614 Some(d) => d.fingerprint_into(h, budget),
615 None => {
616 *budget -= 1;
617 0xFEu8.hash(h);
618 },
619 }
620 }
621 }
622 },
623 DigestedData::Alignment(_) => 2u8.hash(h),
624 DigestedData::List(l) => {
625 3u8.hash(h);
626 if let Ok(lb) = l.try_borrow() {
627 lb.boxes.len().hash(h);
628 for child in &lb.boxes {
629 if *budget == 0 {
630 break;
631 }
632 child.fingerprint_into(h, budget);
633 }
634 }
635 },
636 DigestedData::Postponed(t) => {
637 4u8.hash(h);
638 t.len().hash(h);
639 },
640 DigestedData::KeyVals(_) => 5u8.hash(h),
641 DigestedData::RegisterValue(r) => {
642 6u8.hash(h);
643 std::mem::discriminant(r).hash(h);
644 },
645 DigestedData::Comment(c) => {
646 7u8.hash(h);
647 c.0.hash(h);
648 },
649 }
650 }
651 pub fn estimate_bytes(&self) -> usize {
661 let mut budget: u32 = EB_BUDGET;
662 self.estimate_bytes_into(&mut budget)
663 }
664
665 fn estimate_bytes_into(&self, budget: &mut u32) -> usize {
668 if *budget == 0 {
669 return 0;
670 }
671 *budget -= 1;
672 const NODE: usize = 64;
675 fn map_bytes(n: usize) -> usize { if n == 0 { 0 } else { 64 + n * 96 } }
685 match self.data() {
686 DigestedData::TBox(b) => {
687 let mut bytes = NODE + 48;
688 if let Ok(tb) = b.try_borrow() {
689 bytes += map_bytes(tb.properties.len());
690 bytes += tb.tokens.len() * 16; }
692 bytes
693 },
694 DigestedData::Whatsit(w) => {
695 let mut bytes = NODE + 64;
696 if let Ok(wb) = w.try_borrow() {
697 bytes += map_bytes(wb.properties.len());
698 bytes += wb.args.len() * 16;
699 for arg in &wb.args {
700 if *budget == 0 {
701 break;
702 }
703 if let Some(d) = arg {
704 bytes += d.estimate_bytes_into(budget);
705 }
706 }
707 }
708 bytes
709 },
710 DigestedData::List(l) => {
711 let mut bytes = NODE + 48;
712 if let Ok(lb) = l.try_borrow() {
713 bytes += map_bytes(lb.properties.len());
714 bytes += lb.boxes.len() * 8;
715 for child in &lb.boxes {
716 if *budget == 0 {
717 break;
718 }
719 bytes += child.estimate_bytes_into(budget);
720 }
721 }
722 bytes
723 },
724 DigestedData::Postponed(_) => NODE + 32,
725 DigestedData::Comment(_) => NODE + 16,
726 DigestedData::Alignment(_) | DigestedData::KeyVals(_) | DigestedData::RegisterValue(_) => {
727 NODE
728 },
729 }
730 }
731
732 pub fn value_of(&self) -> i64 {
735 match &*self.0 {
736 DigestedData::RegisterValue(rv) => rv.clone().value_of(),
737 _ => 0,
738 }
739 }
740 pub fn get_dimension(&self) -> Option<Dimension> {
742 match &*self.0 {
743 DigestedData::RegisterValue(rv) => Some(Dimension::from(rv)),
744 _ => None,
745 }
746 }
747 pub fn pt_value(&self, prec: Option<u8>) -> f64 {
749 match &*self.0 {
750 DigestedData::RegisterValue(rv) => rv.clone().pt_value(prec),
751 _ => 0.0,
752 }
753 }
754 pub fn any<F>(&self, mut check: F) -> bool
756 where F: FnMut(&Self) -> bool {
757 use DigestedData::*;
758 match &*self.0 {
759 TBox(_) | Whatsit(_) | Alignment(_) | Postponed(_) | KeyVals(_) | RegisterValue(_) => {
760 check(self)
761 },
762 Comment(_) => true,
763 List(l) => l.borrow().boxes.iter().any(check),
764 }
765 }
766
767 pub fn all<F>(&self, mut check: F) -> bool
769 where F: FnMut(&Self) -> bool {
770 use DigestedData::*;
771 match &*self.0 {
772 TBox(_) | Whatsit(_) | Alignment(_) | Postponed(_) | KeyVals(_) | RegisterValue(_) => {
773 check(self)
774 },
775 Comment(_) => true,
776 List(l) => l.borrow().boxes.iter().all(check),
777 }
778 }
779
780 pub fn is_empty(&self) -> Result<bool> {
782 use DigestedData::*;
783 Ok(match *self.0 {
784 TBox(ref b) => b.borrow().is_empty(),
785 List(ref l) => l.borrow().is_empty(),
786 Whatsit(ref w) => w.borrow().is_empty()?,
787 Postponed(ref tks) => tks.is_empty(),
788 _ => false, })
790 }
791
792 pub fn is_skippable(&self) -> bool {
795 use DigestedData::*;
796 match *self.0 {
797 Comment(_) => true,
798 TBox(ref b) => {
799 let b = b.borrow();
800 if b.get_property_bool("alignmentPreserve") {
801 false
803 } else if b.get_property_bool("isEmpty")
804 || b.get_property_bool("isSpace")
805 || b.get_property_bool("alignmentSkippable")
806 {
807 true
808 } else {
809 b.get_string()
811 .ok()
812 .map(|s| s.trim().is_empty())
813 .unwrap_or(false)
814 }
815 },
816 List(ref l) => {
817 let l = l.borrow();
818 !l.get_property_bool("alignmentPreserve") && l.boxes.iter().all(|d| d.is_skippable())
820 },
821 Whatsit(ref w) => {
822 let w = w.borrow();
823 if w.get_property_bool("alignmentPreserve") {
824 false
826 } else if w.get_property_bool("isEmpty")
827 || w.get_property_bool("isSpace")
828 || w.get_property_bool("alignmentSkippable")
829 {
830 true
831 } else {
832 match w.get_body() {
833 Ok(Some(body)) => body.is_skippable(),
834 _ => {
835 match w.get_property("content_box") {
836 Some(ref prop) => {
837 match &**prop {
839 Stored::Digested(cb) => cb.is_skippable(),
840 _ => false,
841 }
842 },
843 _ => false,
844 }
845 },
846 }
847 }
848 },
849 Postponed(ref tks) => {
850 tks.unlist_ref().iter().all(|t| {
852 let cc = t.get_catcode();
853 !matches!(
854 cc,
855 crate::token::Catcode::LETTER
856 | crate::token::Catcode::OTHER
857 | crate::token::Catcode::ACTIVE
858 | crate::token::Catcode::CS
859 )
860 })
861 },
862 _ => false,
863 }
864 }
865
866 pub fn raw_tokens(&self) -> Option<&Tokens> {
869 match *self.0 {
870 DigestedData::Postponed(ref tks) => Some(tks),
871 _ => None,
872 }
873 }
874
875 pub fn to_attribute(&self) -> String {
877 match *self.0 {
878 DigestedData::RegisterValue(ref v) => v.to_attribute(),
879 _ => self.to_string(),
880 }
881 }
882
883 pub fn untex(&self) -> Result<String> { Ok(self.revert()?.untex()) }
886
887 pub fn alignment_cell(&self) -> Option<&RefCell<Alignment>> {
888 if let DigestedData::Alignment(ref alignment) = *self.0 {
889 Some(alignment)
890 } else {
891 None
892 }
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use super::*;
899
900 #[test]
916 fn digested_data_size_budget() {
917 let size = size_of::<DigestedData>();
918 assert!(
919 size <= 128,
920 "DigestedData grew to {size} B (budget 128). A large payload re-inflated \
921 the per-box footprint — box it (cf. KeyVals, issue #361) rather than \
922 raising this budget."
923 );
924 }
925
926 #[test]
927 fn digested_from_tokens_roundtrip() {
928 let ts = Tokens::new(vec![]);
929 let d: Digested = ts.into();
930 match &*d.0 {
932 DigestedData::Postponed(_) => {},
933 other => panic!("expected Postponed, got {other:?}"),
934 }
935 }
936
937 #[test]
938 fn digested_from_string_is_postponed_tokens() {
939 let d: Digested = "abc".to_string().into();
940 match &*d.0 {
941 DigestedData::Postponed(_) => {},
942 other => panic!("expected Postponed, got {other:?}"),
943 }
944 }
945
946 #[test]
947 fn digested_from_tbox_is_tbox_variant() {
948 let tb = Tbox::default();
949 let d: Digested = tb.into();
950 match &*d.0 {
951 DigestedData::TBox(_) => {},
952 other => panic!("expected TBox, got {other:?}"),
953 }
954 }
955
956 #[test]
957 fn digested_from_list_is_list_variant() {
958 let l = List::default();
959 let d: Digested = l.into();
960 match &*d.0 {
961 DigestedData::List(_) => {},
962 other => panic!("expected List, got {other:?}"),
963 }
964 }
965
966 #[test]
967 fn digested_from_whatsit_is_whatsit_variant() {
968 let w = Whatsit::default();
969 let d: Digested = w.into();
970 match &*d.0 {
971 DigestedData::Whatsit(_) => {},
972 other => panic!("expected Whatsit, got {other:?}"),
973 }
974 }
975
976 #[test]
977 fn digested_from_keyvals_is_keyvals_variant() {
978 let kv = KeyVals::default();
979 let d: Digested = kv.into();
980 match &*d.0 {
981 DigestedData::KeyVals(_) => {},
982 other => panic!("expected KeyVals, got {other:?}"),
983 }
984 }
985
986 #[test]
987 fn digested_clone_shares_rc() {
988 let tb = Tbox::default();
991 let a: Digested = tb.into();
992 let b = a.clone();
993 assert!(Rc::strong_count(&a.0) >= 2);
995 assert!(Rc::strong_count(&b.0) >= 2);
996 }
997
998 #[test]
999 fn digested_ref_to_option_some() {
1000 let d: Digested = Tbox::default().into();
1001 let o: Option<Digested> = (&d).into();
1002 assert!(o.is_some());
1003 }
1004
1005 fn tbox_with(text: &str) -> Digested {
1006 Tbox {
1007 text: arena::pin(text),
1008 ..Default::default()
1009 }
1010 .into()
1011 }
1012 fn list_of(items: Vec<Digested>) -> Digested {
1013 List {
1014 boxes: items,
1015 ..Default::default()
1016 }
1017 .into()
1018 }
1019
1020 fn drain_nest(mut cur: Digested) {
1031 loop {
1032 let child = if let DigestedData::List(l) = &*cur.0 {
1033 let mut boxes = std::mem::take(&mut l.borrow_mut().boxes);
1034 (!boxes.is_empty()).then(|| boxes.swap_remove(0))
1035 } else {
1036 None
1037 };
1038 match child {
1039 Some(c) => cur = c,
1040 None => break,
1041 }
1042 }
1043 }
1044
1045 #[test]
1046 fn cycle_fingerprint_is_content_aware_for_lists() {
1047 let ab = list_of(vec![tbox_with("a"), tbox_with("b")]);
1051 let ac = list_of(vec![tbox_with("a"), tbox_with("c")]);
1052 assert_ne!(
1053 ab.cycle_fingerprint(),
1054 ac.cycle_fingerprint(),
1055 "same-length lists with different content must NOT share a fingerprint"
1056 );
1057 let ab2 = list_of(vec![tbox_with("a"), tbox_with("b")]);
1059 assert_eq!(ab.cycle_fingerprint(), ab2.cycle_fingerprint());
1060 }
1061
1062 #[test]
1063 fn cycle_fingerprint_distinguishes_text_and_is_bounded() {
1064 assert_ne!(
1065 tbox_with("a").cycle_fingerprint(),
1066 tbox_with("b").cycle_fingerprint()
1067 );
1068 let mut deep = tbox_with("z");
1071 for _ in 0..10_000 {
1072 deep = list_of(vec![deep, tbox_with("z")]);
1073 }
1074 let _ = deep.cycle_fingerprint(); assert_ne!(deep.cycle_fingerprint(), tbox_with("z").cycle_fingerprint());
1076 drain_nest(deep); }
1078
1079 #[test]
1080 fn estimate_bytes_is_positive_and_nesting_increases_it() {
1081 assert!(tbox_with("a").estimate_bytes() > 0);
1083 let one = list_of(vec![tbox_with("a")]);
1085 let many = list_of(vec![
1086 tbox_with("a"),
1087 tbox_with("b"),
1088 tbox_with("c"),
1089 tbox_with("d"),
1090 ]);
1091 assert!(
1092 many.estimate_bytes() > one.estimate_bytes(),
1093 "a wider list must estimate heavier than a narrow one"
1094 );
1095 }
1096
1097 #[test]
1098 fn estimate_bytes_is_cost_bounded_for_deep_nests() {
1099 let mut deep = tbox_with("z");
1102 for _ in 0..100_000 {
1103 deep = list_of(vec![deep]);
1104 }
1105 let est = deep.estimate_bytes();
1106 assert!(est > 0);
1107 assert!(
1110 est < (EB_BUDGET as usize) * 4096,
1111 "estimate must stay bounded regardless of nest depth (got {est})"
1112 );
1113 drain_nest(deep); }
1115}