1use std::cmp::Ordering;
22use std::fmt;
23
24use crate::core::types::DynError;
25
26pub const TOKEN_WORD_CAPACITY: usize = 4;
35
36const DIGITS_PER_INT: usize = 10;
38
39const RADIX_VAL_C_REFERENCE: u32 = 0x1717_9149;
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
57pub enum Sign {
58 Negative,
60 Zero,
62 Positive,
64}
65
66impl Sign {
67 fn as_i32(self) -> i32 {
68 match self {
69 Sign::Negative => -1,
70 Sign::Zero => 0,
71 Sign::Positive => 1,
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
88pub struct DynToken {
89 sign: Sign,
90 mag: [u32; TOKEN_WORD_CAPACITY],
91 len: usize,
92}
93
94impl Default for DynToken {
95 fn default() -> Self {
96 Self {
97 sign: Sign::Zero,
98 mag: [0; TOKEN_WORD_CAPACITY],
99 len: 0,
100 }
101 }
102}
103
104impl DynToken {
105 #[must_use]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 #[must_use]
129 pub fn from_u32(value: u32) -> Self {
130 let mut t = Self::default();
131 t.size(1).expect("len of 1 fits within TOKEN_WORD_CAPACITY");
133 t.set_int(value);
134 t
135 }
136
137 pub fn size(&mut self, len: usize) -> Result<(), DynError> {
149 if len > TOKEN_WORD_CAPACITY {
150 return Err(DynError::Generic(format!(
151 "token length {len} exceeds capacity {TOKEN_WORD_CAPACITY}"
152 )));
153 }
154 self.len = len;
155 self.sign = Sign::Zero;
156 Ok(())
157 }
158
159 #[must_use]
169 pub fn len(&self) -> usize {
170 self.len
171 }
172
173 #[must_use]
183 pub fn is_empty(&self) -> bool {
184 self.len == 0
185 }
186
187 #[must_use]
198 pub fn sign(&self) -> Sign {
199 self.sign
200 }
201
202 #[must_use]
212 pub fn mag(&self) -> &[u32] {
213 &self.mag[..self.len]
214 }
215
216 pub fn mag_mut(&mut self) -> &mut [u32; TOKEN_WORD_CAPACITY] {
229 &mut self.mag
230 }
231
232 pub fn set_len_keep(&mut self, len: usize) {
245 assert!(len <= TOKEN_WORD_CAPACITY, "token length out of range");
246 self.len = len;
247 }
248
249 pub fn set_sign(&mut self, sign: Sign) {
261 self.sign = sign;
262 }
263
264 pub fn set_int(&mut self, val: u32) {
279 self.mag[0] = val;
280 self.len = 1;
281 self.sign = if val > 0 { Sign::Positive } else { Sign::Zero };
282 }
283
284 #[must_use]
294 pub fn get_int(&self) -> u32 {
295 if self.len == 0 {
296 0
297 } else {
298 self.mag[0]
299 }
300 }
301
302 #[must_use]
312 pub fn to_hex(&self) -> String {
313 use std::fmt::Write;
314 let mut out = String::with_capacity(8 * self.len);
315 for w in &self.mag[..self.len] {
316 let _ = write!(out, "{w:08x}");
317 }
318 out
319 }
320}
321
322impl fmt::Display for DynToken {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 write!(
325 f,
326 "Token(sign={}, len={}, mag={:?})",
327 self.sign.as_i32(),
328 self.len,
329 &self.mag[..self.len]
330 )
331 }
332}
333
334impl PartialEq for DynToken {
335 fn eq(&self, other: &Self) -> bool {
336 self.cmp(other) == Ordering::Equal
337 }
338}
339
340impl Eq for DynToken {}
341
342impl PartialOrd for DynToken {
343 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
344 Some(self.cmp(other))
345 }
346}
347
348impl Ord for DynToken {
349 fn cmp(&self, other: &Self) -> Ordering {
350 if self.sign == other.sign {
351 if self.sign == Sign::Zero {
352 return Ordering::Equal;
353 }
354 if self.len == other.len {
355 for i in 0..self.len {
356 let a = self.mag[i];
357 let b = other.mag[i];
358 if a != b {
359 return if a > b {
360 Ordering::Greater
361 } else {
362 Ordering::Less
363 };
364 }
365 }
366 return Ordering::Equal;
367 }
368 return if self.len > other.len {
369 Ordering::Greater
370 } else {
371 Ordering::Less
372 };
373 }
374 if self.sign.as_i32() > other.sign.as_i32() {
375 Ordering::Greater
376 } else {
377 Ordering::Less
378 }
379 }
380}
381
382impl std::hash::Hash for DynToken {
383 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
384 self.sign.as_i32().hash(state);
385 self.len.hash(state);
386 for w in &self.mag[..self.len] {
387 w.hash(state);
388 }
389 }
390}
391
392pub fn parse_token(bytes: &[u8]) -> Result<DynToken, DynError> {
410 if bytes.is_empty() {
411 return Err(DynError::Generic("empty token".into()));
412 }
413 let mut token = DynToken::default();
414
415 let (sign, payload) = if bytes[0] == b'-' {
416 if bytes.len() < 2 {
417 return Err(DynError::Generic("dangling minus sign".into()));
418 }
419 (Sign::Negative, &bytes[1..])
420 } else if bytes.len() == 1 && bytes[0] == b'0' {
421 (Sign::Zero, bytes)
422 } else {
423 (Sign::Positive, bytes)
424 };
425 token.sign = sign;
426
427 let nwords: usize = 1;
428 token.len = nwords;
429 let buf = &mut token.mag;
430
431 let digits = payload.len();
432 let mut first_group_len = digits % DIGITS_PER_INT;
433 if first_group_len == 0 {
434 first_group_len = DIGITS_PER_INT;
435 }
436
437 let mut p = 0usize;
438 if first_group_len > digits {
439 return Err(DynError::Generic("digit group overruns input".into()));
440 }
441 buf[nwords - 1] = atoui(&payload[..first_group_len])?;
442 p += first_group_len;
443
444 while p < digits {
445 let end = p + DIGITS_PER_INT;
446 if end > digits {
447 return Err(DynError::Generic("misaligned digit groups".into()));
448 }
449 let local = atoui(&payload[p..end])?;
450 add_next_word(buf, nwords, local);
451 p = end;
452 }
453
454 Ok(token)
455}
456
457fn atoui(bytes: &[u8]) -> Result<u32, DynError> {
458 let mut acc: u32 = 0;
459 for &b in bytes {
460 if !b.is_ascii_digit() {
461 return Err(DynError::Generic(format!(
462 "non-digit byte 0x{b:02x} in token"
463 )));
464 }
465 acc = acc.wrapping_mul(10).wrapping_add(u32::from(b - b'0'));
466 }
467 Ok(acc)
468}
469
470fn add_next_word(buf: &mut [u32; TOKEN_WORD_CAPACITY], len: usize, next_int: u32) {
471 let radix_val: u64 = u64::from(RADIX_VAL_C_REFERENCE);
472 let mut carry: u64 = 0;
473 for i in (0..len).rev() {
474 let product = radix_val * u64::from(buf[i]) + carry;
475 buf[i] = product as u32;
476 carry = product >> 32;
477 }
478
479 let sum = u64::from(buf[len - 1]) + u64::from(next_int);
480 buf[len - 1] = sum as u32;
481 let mut carry2 = sum >> 32;
482 if len >= 2 {
483 for i in (0..=len - 2).rev() {
484 let s = u64::from(buf[i]) + carry2;
485 buf[i] = s as u32;
486 carry2 = s >> 32;
487 }
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn default_is_empty() {
497 let t = DynToken::default();
498 assert!(t.is_empty());
499 assert_eq!(t.sign(), Sign::Zero);
500 }
501
502 #[test]
503 fn set_int_get_int_round_trip() {
504 let mut t = DynToken::default();
505 t.size(1).unwrap();
506 for v in [0u32, 1, 42, 0x7fff_ffff, 0xffff_ffff, 0x8000_0000] {
507 t.set_int(v);
508 assert_eq!(t.get_int(), v);
509 }
510 }
511
512 #[test]
513 fn set_int_zero_has_zero_sign() {
514 let mut t = DynToken::default();
515 t.size(1).unwrap();
516 t.set_int(0);
517 assert_eq!(t.sign(), Sign::Zero);
518 t.set_int(1);
519 assert_eq!(t.sign(), Sign::Positive);
520 }
521
522 #[test]
523 fn cmp_total_order_for_singletons() {
524 let mut t = vec![];
525 for v in [0u32, 1, 2, 100, 1_000_000, u32::MAX] {
526 t.push(DynToken::from_u32(v));
527 }
528 for i in 0..t.len() {
529 assert_eq!(t[i].cmp(&t[i]), Ordering::Equal);
530 for j in (i + 1)..t.len() {
531 assert_eq!(t[i].cmp(&t[j]), Ordering::Less);
532 assert_eq!(t[j].cmp(&t[i]), Ordering::Greater);
533 }
534 }
535 }
536
537 #[test]
538 fn cmp_uses_sign_first() {
539 let pos = DynToken::from_u32(1);
540 let mut neg = DynToken::default();
541 neg.size(1).unwrap();
542 neg.set_int(1);
543 neg.set_sign(Sign::Negative);
544 assert!(neg < pos);
545 }
546
547 #[test]
548 fn cmp_uses_length_when_signs_match() {
549 let mut short = DynToken::default();
550 short.size(1).unwrap();
551 short.set_int(0xffff_ffff);
552 short.set_sign(Sign::Positive);
553
554 let mut long = DynToken::default();
555 long.size(2).unwrap();
556 long.mag_mut()[0] = 1;
557 long.mag_mut()[1] = 0;
558 long.set_sign(Sign::Positive);
559
560 assert!(long > short);
561 }
562
563 #[test]
564 fn parse_zero() {
565 let t = parse_token(b"0").unwrap();
566 assert_eq!(t.sign(), Sign::Zero);
567 assert_eq!(t.get_int(), 0);
568 }
569
570 #[test]
571 fn parse_short_positive() {
572 let t = parse_token(b"42").unwrap();
573 assert_eq!(t.sign(), Sign::Positive);
574 assert_eq!(t.get_int(), 42);
575 }
576
577 #[test]
578 fn parse_negative() {
579 let t = parse_token(b"-7").unwrap();
580 assert_eq!(t.sign(), Sign::Negative);
581 assert_eq!(t.get_int(), 7);
582 }
583
584 #[test]
585 fn parse_rejects_garbage() {
586 assert!(parse_token(b"abc").is_err());
587 assert!(parse_token(b"").is_err());
588 assert!(parse_token(b"-").is_err());
589 }
590
591 #[test]
592 fn new_matches_default() {
593 let t = DynToken::new();
595 assert!(t.is_empty());
596 assert_eq!(t.len(), 0);
597 assert_eq!(t.sign(), Sign::Zero);
598 }
599
600 #[test]
601 fn from_u32_populates_single_word() {
602 let t = DynToken::from_u32(0xdead_beef);
604 assert_eq!(t.len(), 1);
605 assert_eq!(t.get_int(), 0xdead_beef);
606 assert_eq!(t.sign(), Sign::Positive);
607 assert_eq!(t.mag(), &[0xdead_beef]);
608 }
609
610 #[test]
611 fn get_int_on_empty_is_zero() {
612 let t = DynToken::default();
614 assert_eq!(t.get_int(), 0);
615 }
616
617 #[test]
618 fn set_len_keep_preserves_words() {
619 let mut t = DynToken::default();
621 t.mag_mut()[0] = 0xaa;
622 t.set_len_keep(1);
623 assert_eq!(t.len(), 1);
624 assert_eq!(t.get_int(), 0xaa);
625 }
626
627 #[test]
628 fn size_rejects_overcapacity() {
629 let mut t = DynToken::default();
632 assert!(t.size(TOKEN_WORD_CAPACITY).is_ok());
633 assert!(t.size(TOKEN_WORD_CAPACITY + 1).is_err());
634 }
635
636 #[test]
637 fn to_hex_pads_each_word() {
638 assert_eq!(DynToken::from_u32(0xdead).to_hex(), "0000dead");
640 let mut t = DynToken::default();
641 t.size(2).unwrap();
642 t.mag_mut()[0] = 1;
643 t.mag_mut()[1] = 0xff;
644 assert_eq!(t.to_hex(), "00000001000000ff");
645 assert_eq!(DynToken::default().to_hex(), "");
646 }
647
648 #[test]
649 fn display_reports_sign_len_and_mag() {
650 let s = format!("{}", DynToken::from_u32(7));
653 assert_eq!(s, "Token(sign=1, len=1, mag=[7])");
654 let mut neg = DynToken::from_u32(7);
655 neg.set_sign(Sign::Negative);
656 assert_eq!(format!("{neg}"), "Token(sign=-1, len=1, mag=[7])");
657 }
658
659 #[test]
660 fn hash_agrees_with_eq() {
661 use std::collections::hash_map::DefaultHasher;
663 use std::hash::{Hash, Hasher};
664
665 fn digest(t: &DynToken) -> u64 {
666 let mut h = DefaultHasher::new();
667 t.hash(&mut h);
668 h.finish()
669 }
670
671 let a = DynToken::from_u32(99);
672 let b = DynToken::from_u32(99);
673 assert_eq!(a, b);
674 assert_eq!(digest(&a), digest(&b));
675 assert_ne!(digest(&a), digest(&DynToken::from_u32(100)));
676 }
677
678 #[test]
679 fn cmp_equal_multiword_magnitudes() {
680 let mut a = DynToken::default();
683 a.size(2).unwrap();
684 a.mag_mut()[0] = 5;
685 a.mag_mut()[1] = 9;
686 a.set_sign(Sign::Positive);
687 let b = a.clone();
688 assert_eq!(a.cmp(&b), Ordering::Equal);
689 }
690
691 #[test]
692 fn parse_multi_group_uses_add_next_word() {
693 let t = parse_token(b"12345678901").unwrap();
697 assert_eq!(t.sign(), Sign::Positive);
698 assert!(!t.is_empty());
701 }
702
703 #[test]
704 fn parse_full_first_group_of_ten_digits() {
705 let t = parse_token(b"1000000000").unwrap();
708 assert_eq!(t.sign(), Sign::Positive);
709 assert_eq!(t.len(), 1);
710 }
711
712 #[test]
713 fn parse_negative_multi_group() {
714 let t = parse_token(b"-99999999999").unwrap();
716 assert_eq!(t.sign(), Sign::Negative);
717 }
718
719 #[test]
720 fn parse_rejects_non_digit_in_later_group() {
721 assert!(parse_token(b"1234567890x23456789").is_err());
723 }
724}