1#[derive(Debug, Default, Clone, PartialEq, Eq)]
17pub struct CountAccumulator {
18 buffer: u32,
20}
21
22impl CountAccumulator {
23 pub const fn new() -> Self {
25 Self { buffer: 0 }
26 }
27
28 pub const fn is_empty(&self) -> bool {
30 self.buffer == 0
31 }
32
33 pub const fn peek(&self) -> u32 {
35 self.buffer
36 }
37
38 pub fn try_accumulate(&mut self, ch: char) -> bool {
47 if !ch.is_ascii_digit() {
48 return false;
49 }
50 if ch == '0' && self.buffer == 0 {
51 return false;
52 }
53 let d = (ch as u8 - b'0') as u32;
54 self.buffer = self.buffer.saturating_mul(10).saturating_add(d);
55 true
56 }
57
58 pub fn take_or(&mut self, default: u32) -> u32 {
61 let c = if self.buffer == 0 {
62 default
63 } else {
64 self.buffer
65 };
66 self.buffer = 0;
67 c
68 }
69
70 pub fn reset(&mut self) {
74 self.buffer = 0;
75 }
76
77 pub fn drain_as_digits(&mut self) -> String {
84 let s = if self.buffer == 0 {
85 String::new()
86 } else {
87 self.buffer.to_string()
88 };
89 self.buffer = 0;
90 s
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn new_is_empty() {
100 let acc = CountAccumulator::new();
101 assert!(acc.is_empty());
102 assert_eq!(acc.peek(), 0);
103 }
104
105 #[test]
106 fn try_accumulate_digit_increments() {
107 let mut acc = CountAccumulator::new();
108 assert!(acc.try_accumulate('5'));
109 assert_eq!(acc.peek(), 5);
110 assert!(!acc.is_empty());
111 }
112
113 #[test]
114 fn try_accumulate_zero_with_empty_buffer_returns_false() {
115 let mut acc = CountAccumulator::new();
117 assert!(!acc.try_accumulate('0'));
118 assert!(acc.is_empty());
119 }
120
121 #[test]
122 fn try_accumulate_zero_with_non_empty_buffer_appends() {
123 let mut acc = CountAccumulator::new();
125 assert!(acc.try_accumulate('1'));
126 assert!(acc.try_accumulate('0'));
127 assert_eq!(acc.peek(), 10);
128 }
129
130 #[test]
131 fn try_accumulate_non_digit_returns_false() {
132 let mut acc = CountAccumulator::new();
133 assert!(!acc.try_accumulate('j'));
134 assert!(!acc.try_accumulate(' '));
135 assert!(!acc.try_accumulate('g'));
136 assert!(acc.is_empty());
137 }
138
139 #[test]
140 fn take_or_drains_and_returns_count() {
141 let mut acc = CountAccumulator::new();
142 acc.try_accumulate('5');
143 assert_eq!(acc.take_or(1), 5);
144 assert!(acc.is_empty());
146 assert_eq!(acc.take_or(1), 1);
147 }
148
149 #[test]
150 fn take_or_returns_default_when_empty() {
151 let mut acc = CountAccumulator::new();
152 assert_eq!(acc.take_or(1), 1);
153 assert_eq!(acc.take_or(42), 42);
154 }
155
156 #[test]
157 fn drain_as_digits_returns_typed_chars_in_order() {
158 let mut acc = CountAccumulator::new();
159 acc.try_accumulate('1');
160 acc.try_accumulate('2');
161 acc.try_accumulate('3');
162 let s = acc.drain_as_digits();
163 assert_eq!(s, "123");
164 assert!(acc.is_empty());
165 }
166
167 #[test]
168 fn drain_as_digits_empty_returns_empty_string() {
169 let mut acc = CountAccumulator::new();
170 let s = acc.drain_as_digits();
171 assert_eq!(s, "");
172 }
173
174 #[test]
175 fn try_accumulate_saturates_on_overflow() {
176 let mut acc = CountAccumulator::new();
178 for _ in 0..20 {
179 acc.try_accumulate('9');
180 }
181 assert_eq!(acc.peek(), u32::MAX);
182 }
183
184 #[test]
185 fn reset_clears_without_returning() {
186 let mut acc = CountAccumulator::new();
187 acc.try_accumulate('7');
188 assert!(!acc.is_empty());
189 acc.reset();
190 assert!(acc.is_empty());
191 assert_eq!(acc.peek(), 0);
192 }
193}