1use crate::options::{Options, Trunc, TruncCheck};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct Binary {
6 pub digits: u8,
7 pub signed: bool,
8 pub native: bool,
9}
10
11impl Binary {
12 pub const fn bytes(self) -> usize {
13 match self.digits {
14 0..=4 => 2,
15 5..=9 => 4,
16 _ => 8,
17 }
18 }
19
20 pub fn load(self, bytes: &[u8]) -> i128 {
21 assert_eq!(bytes.len(), self.bytes());
22 let raw = bytes.iter().fold(0u128, |acc, &b| acc << 8 | b as u128);
23 let bits = 8 * bytes.len() as u32;
24 if self.signed && raw >> (bits - 1) == 1 { raw as i128 - (1i128 << bits) } else { raw as i128 }
25 }
26
27 fn decimal_truncation(self, value: i128) -> i128 {
28 let kept = (value.unsigned_abs() % 10u128.pow(self.digits as u32)) as i128;
29 if value < 0 { -kept } else { kept }
30 }
31
32 fn binary_truncation(self, value: i128) -> i128 {
33 let bits = 8 * self.bytes() as u32;
34 let raw = (value as u128) & ((1u128 << bits) - 1);
35 if self.signed && raw >> (bits - 1) == 1 { raw as i128 - (1i128 << bits) } else { raw as i128 }
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct TruncOptDivergence {
43 pub value: i128,
44 pub decimal: i128,
45 pub binary: i128,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct Stored {
50 pub bytes: Vec<u8>,
51 pub value: i128,
52 pub divergence: Option<TruncOptDivergence>,
53}
54
55pub fn store(item: Binary, value: i128, options: &Options) -> Stored {
58 let value = if item.signed { value } else { value.unsigned_abs() as i128 };
59 let decimal = item.decimal_truncation(value);
60 let binary = item.binary_truncation(value);
61 let (kept, divergence) = match (item.native, options.trunc) {
62 (true, _) | (false, Trunc::Bin) => (binary, None),
63 (false, Trunc::Std) => (decimal, None),
64 (false, Trunc::Opt) => {
65 let report = decimal != binary && options.trunc_check == TruncCheck::Report;
66 (binary, report.then_some(TruncOptDivergence { value, decimal, binary }))
67 }
68 };
69 Stored { bytes: kept.to_be_bytes()[16 - item.bytes()..].to_vec(), value: kept, divergence }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::options::Options;
76
77 const HALFWORD: Binary = Binary { digits: 4, signed: false, native: false };
78 const SIGNED_HALFWORD: Binary = Binary { digits: 4, signed: true, native: false };
79
80 fn with(trunc: Trunc) -> Options {
81 Options { trunc, ..Options::default() }
82 }
83
84 #[test]
85 fn sizes_follow_the_picture() {
86 let size = |digits| Binary { digits, signed: true, native: false }.bytes();
87 assert_eq!([size(1), size(4), size(5), size(9), size(10), size(18)], [2, 2, 4, 4, 8, 8]);
88 }
89
90 #[test]
91 fn std_truncates_to_the_picture() {
92 let s = store(HALFWORD, 12345, &with(Trunc::Std));
93 assert_eq!((s.value, s.bytes.as_slice()), (2345, [0x09, 0x29].as_slice()));
94 assert_eq!(store(SIGNED_HALFWORD, -12345, &with(Trunc::Std)).value, -2345);
95 }
96
97 #[test]
98 fn bin_truncates_to_the_halfword() {
99 assert_eq!(store(HALFWORD, 12345, &with(Trunc::Bin)).value, 12345);
100 assert_eq!(store(HALFWORD, 70000, &with(Trunc::Bin)).value, 70000 - 65536);
101 let s = store(SIGNED_HALFWORD, 40000, &with(Trunc::Bin));
102 assert_eq!((s.value, s.bytes.as_slice()), (40000 - 65536, [0x9C, 0x40].as_slice()));
103 }
104
105 #[test]
106 fn comp5_ignores_trunc() {
107 let native = Binary { native: true, ..HALFWORD };
108 assert_eq!(store(native, 12345, &with(Trunc::Std)).value, 12345);
109 }
110
111 #[test]
112 fn unsigned_items_take_the_absolute_value() {
113 assert_eq!(store(HALFWORD, -42, &with(Trunc::Std)).value, 42);
114 }
115
116 #[test]
117 fn opt_reports_a_store_where_decimal_and_binary_truncation_disagree() {
118 let s = store(HALFWORD, 12345, &with(Trunc::Opt));
119 assert_eq!(s.divergence, Some(TruncOptDivergence { value: 12345, decimal: 2345, binary: 12345 }));
120 assert_eq!(store(HALFWORD, 9999, &with(Trunc::Opt)).divergence, None);
121 }
122
123 #[test]
124 fn silent_stores_the_same_value_without_a_report() {
125 let mut options = with(Trunc::Opt);
126 options.apply_flag("-silent").unwrap();
127 let s = store(HALFWORD, 12345, &options);
128 assert_eq!((s.value, s.divergence), (12345, None));
129 }
130
131 #[test]
132 fn load_reads_twos_complement() {
133 assert_eq!(SIGNED_HALFWORD.load(&[0xFF, 0xFE]), -2);
134 assert_eq!(HALFWORD.load(&[0xFF, 0xFE]), 65534);
135 }
136}