1#[derive(Debug, Clone, Copy)]
3pub struct Locale {
4 pub decimal_point: u8,
6
7 pub thousands_sep: Option<u8>,
9
10 pub grouping: [u8; 4],
16
17 pub group_repeat: bool,
20}
21
22impl Locale {
23 pub fn apply_grouping(&self, mut input: &[u8]) -> Vec<u8> {
27 debug_assert!(input.iter().all(u8::is_ascii_digit));
28 let sep = self.thousands_sep.expect("no thousands separator");
29 let mut result = Vec::with_capacity(input.len() + self.separator_count(input.len()));
30 while !input.is_empty() {
31 let group_size = self.next_group_size(input.len());
32 let (group, rest) = input.split_at(group_size);
33 result.extend_from_slice(group);
34 if !rest.is_empty() {
35 result.push(sep);
36 }
37 input = rest;
38 }
39 result
40 }
41
42 fn next_group_size(&self, digits_left: usize) -> usize {
44 let mut accum: usize = 0;
45 for group in self.grouping {
46 if digits_left <= accum + group as usize {
47 return digits_left - accum;
48 }
49 accum += group as usize;
50 }
51 debug_assert!(digits_left >= accum);
54 let repeat_group = if self.group_repeat {
55 *self.grouping.last().unwrap()
56 } else {
57 0
58 };
59
60 if repeat_group == 0 {
61 digits_left - accum
63 } else {
64 let res = (digits_left - accum) % (repeat_group as usize);
67 if res > 0 { res } else { repeat_group as usize }
68 }
69 }
70
71 pub fn separator_count(&self, digits_count: usize) -> usize {
73 if self.thousands_sep.is_none() {
74 return 0;
75 }
76 let mut sep_count = 0;
77 let mut accum = 0;
78 for group in self.grouping {
79 if digits_count <= accum + group as usize {
80 return sep_count;
81 }
82 if group > 0 {
83 sep_count += 1;
84 }
85 accum += group as usize;
86 }
87 debug_assert!(digits_count >= accum);
88 let repeat_group = if self.group_repeat {
89 *self.grouping.last().unwrap()
90 } else {
91 0
92 };
93 if repeat_group > 0 && digits_count > accum {
96 sep_count += (digits_count - accum - 1) / repeat_group as usize;
97 }
98 sep_count
99 }
100}
101
102pub const C_LOCALE: Locale = Locale {
104 decimal_point: b'.',
105 thousands_sep: None,
106 grouping: [0; 4],
107 group_repeat: false,
108};
109
110#[allow(dead_code)]
112pub const EN_US_LOCALE: Locale = Locale {
113 decimal_point: b'.',
114 thousands_sep: Some(b','),
115 grouping: [3, 3, 3, 3],
116 group_repeat: true,
117};
118
119#[cfg(test)]
120mod tests {
121 use super::{C_LOCALE, EN_US_LOCALE, Locale};
122
123 #[test]
124 fn test_apply_grouping() {
125 let input = b"123456789";
126 let mut result: Vec<u8>;
127
128 assert_eq!(EN_US_LOCALE.thousands_sep, Some(b','));
130 result = EN_US_LOCALE.apply_grouping(input);
131 assert_eq!(result, b"123,456,789");
132
133 let input = b"1234567890123456";
135 let mut locale: Locale = C_LOCALE;
136 locale.thousands_sep = Some(b'!');
137
138 locale.grouping = [5, 3, 1, 0];
139 locale.group_repeat = false;
140 result = locale.apply_grouping(input);
141 assert_eq!(result, b"1234567!8!901!23456");
142
143 locale.grouping = [5, 3, 1, 0];
145 locale.group_repeat = true;
146 result = locale.apply_grouping(input);
147 assert_eq!(result, b"1234567!8!901!23456");
148
149 locale.grouping = [5, 3, 1, 2];
150 locale.group_repeat = false;
151 result = locale.apply_grouping(input);
152 assert_eq!(result, b"12345!67!8!901!23456");
153
154 locale.grouping = [5, 3, 1, 2];
155 locale.group_repeat = true;
156 result = locale.apply_grouping(input);
157 assert_eq!(result, b"1!23!45!67!8!901!23456");
158 }
159
160 #[test]
161 #[should_panic]
162 fn test_thousands_grouping_length_panics_if_no_sep() {
163 assert_eq!(C_LOCALE.thousands_sep, None);
165 C_LOCALE.apply_grouping(b"123");
166 }
167
168 #[test]
169 fn test_thousands_grouping_length() {
170 fn validate_grouping_length_hint(locale: Locale, mut input: &[u8]) {
171 loop {
172 let expected = locale.separator_count(input.len()) + input.len();
173 let actual = locale.apply_grouping(input).len();
174 assert_eq!(expected, actual);
175 if input.is_empty() {
176 break;
177 }
178 input = &input[1..];
179 }
180 }
181
182 validate_grouping_length_hint(EN_US_LOCALE, b"123456789");
183
184 let input = b"1234567890123456";
186 let mut locale: Locale = C_LOCALE;
187 locale.thousands_sep = Some(b'!');
188
189 locale.grouping = [5, 3, 1, 0];
190 locale.group_repeat = false;
191 validate_grouping_length_hint(locale, input);
192
193 locale.grouping = [5, 3, 1, 0];
195 locale.group_repeat = true;
196 validate_grouping_length_hint(locale, input);
197
198 locale.grouping = [5, 3, 1, 2];
199 locale.group_repeat = false;
200 validate_grouping_length_hint(locale, input);
201
202 locale.grouping = [5, 3, 1, 2];
203 locale.group_repeat = true;
204 validate_grouping_length_hint(locale, input);
205 }
206}