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 const MAX_COUNT: u32 = 999_999_999;
41
42 pub fn try_accumulate(&mut self, ch: char) -> bool {
51 if !ch.is_ascii_digit() {
52 return false;
53 }
54 if ch == '0' && self.buffer == 0 {
55 return false;
56 }
57 let d = (ch as u8 - b'0') as u32;
58 self.buffer = self
59 .buffer
60 .saturating_mul(10)
61 .saturating_add(d)
62 .min(Self::MAX_COUNT);
63 true
64 }
65
66 pub fn take_or(&mut self, default: u32) -> u32 {
69 let c = if self.buffer == 0 {
70 default
71 } else {
72 self.buffer
73 };
74 self.buffer = 0;
75 c
76 }
77
78 pub fn reset(&mut self) {
82 self.buffer = 0;
83 }
84
85 pub fn drain_as_digits(&mut self) -> String {
92 let s = if self.buffer == 0 {
93 String::new()
94 } else {
95 self.buffer.to_string()
96 };
97 self.buffer = 0;
98 s
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn new_is_empty() {
108 let acc = CountAccumulator::new();
109 assert!(acc.is_empty());
110 assert_eq!(acc.peek(), 0);
111 }
112
113 #[test]
114 fn try_accumulate_digit_increments() {
115 let mut acc = CountAccumulator::new();
116 assert!(acc.try_accumulate('5'));
117 assert_eq!(acc.peek(), 5);
118 assert!(!acc.is_empty());
119 }
120
121 #[test]
122 fn try_accumulate_zero_with_empty_buffer_returns_false() {
123 let mut acc = CountAccumulator::new();
125 assert!(!acc.try_accumulate('0'));
126 assert!(acc.is_empty());
127 }
128
129 #[test]
130 fn try_accumulate_zero_with_non_empty_buffer_appends() {
131 let mut acc = CountAccumulator::new();
133 assert!(acc.try_accumulate('1'));
134 assert!(acc.try_accumulate('0'));
135 assert_eq!(acc.peek(), 10);
136 }
137
138 #[test]
139 fn try_accumulate_non_digit_returns_false() {
140 let mut acc = CountAccumulator::new();
141 assert!(!acc.try_accumulate('j'));
142 assert!(!acc.try_accumulate(' '));
143 assert!(!acc.try_accumulate('g'));
144 assert!(acc.is_empty());
145 }
146
147 #[test]
148 fn take_or_drains_and_returns_count() {
149 let mut acc = CountAccumulator::new();
150 acc.try_accumulate('5');
151 assert_eq!(acc.take_or(1), 5);
152 assert!(acc.is_empty());
154 assert_eq!(acc.take_or(1), 1);
155 }
156
157 #[test]
158 fn take_or_returns_default_when_empty() {
159 let mut acc = CountAccumulator::new();
160 assert_eq!(acc.take_or(1), 1);
161 assert_eq!(acc.take_or(42), 42);
162 }
163
164 #[test]
165 fn drain_as_digits_returns_typed_chars_in_order() {
166 let mut acc = CountAccumulator::new();
167 acc.try_accumulate('1');
168 acc.try_accumulate('2');
169 acc.try_accumulate('3');
170 let s = acc.drain_as_digits();
171 assert_eq!(s, "123");
172 assert!(acc.is_empty());
173 }
174
175 #[test]
176 fn drain_as_digits_empty_returns_empty_string() {
177 let mut acc = CountAccumulator::new();
178 let s = acc.drain_as_digits();
179 assert_eq!(s, "");
180 }
181
182 #[test]
183 fn try_accumulate_clamps_at_vim_max_count() {
184 let mut acc = CountAccumulator::new();
187 for _ in 0..20 {
188 acc.try_accumulate('9');
189 }
190 assert_eq!(acc.peek(), CountAccumulator::MAX_COUNT);
191 assert_eq!(acc.drain_as_digits(), "999999999");
193 }
194
195 #[test]
196 fn reset_clears_without_returning() {
197 let mut acc = CountAccumulator::new();
198 acc.try_accumulate('7');
199 assert!(!acc.is_empty());
200 acc.reset();
201 assert!(acc.is_empty());
202 assert_eq!(acc.peek(), 0);
203 }
204}