1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub struct PartialUtf8 {
25 pub value: u32,
27 pub n_remain: i32,
29}
30
31impl PartialUtf8 {
32 pub const fn new(value: u32, n_remain: i32) -> Self {
33 Self { value, n_remain }
34 }
35}
36
37const CHAR_LOOKUP: [usize; 16] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4];
41
42const PIECE_LOOKUP: [i32; 16] = [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4];
47
48#[inline]
55pub(crate) fn byte_at(src: &[u8], i: usize) -> u8 {
56 if i < src.len() {
57 src[i]
58 } else {
59 0
60 }
61}
62
63pub(crate) fn decode_char(src: &[u8], pos: usize) -> (u32, usize) {
69 let first_byte = byte_at(src, pos);
70 let highbits = (first_byte >> 4) as usize;
71 let len = CHAR_LOOKUP[highbits];
72 let mask = (1u32 << (8 - len)) - 1;
75 let mut value = first_byte as u32 & mask;
76 let end = pos + len; let mut p = pos + 1;
78 while p < end && byte_at(src, p) != 0 {
79 value = (value << 6) + (byte_at(src, p) & 0x3F) as u32;
80 p += 1;
81 }
82 (value, p)
83}
84
85pub(crate) fn decode_piece(src: &[u8], partial_start: PartialUtf8) -> (Vec<u32>, PartialUtf8) {
95 let mut pos = 0usize;
96 let mut code_points: Vec<u32> = Vec::with_capacity(src.len() + 1);
99
100 let mut value = partial_start.value;
101 let mut n_remain = partial_start.n_remain;
102
103 while byte_at(src, pos) != 0 && n_remain > 0 {
105 let next_byte = byte_at(src, pos);
106 if (next_byte >> 6) != 2 {
107 code_points.push(0);
109 return (code_points, PartialUtf8::new(0, -1));
110 }
111 value = (value << 6) + (next_byte & 0x3F) as u32;
112 pos += 1;
113 n_remain -= 1;
114 }
115
116 if partial_start.n_remain > 0 && n_remain == 0 {
117 code_points.push(value);
118 }
119
120 while byte_at(src, pos) != 0 {
122 let first_byte = byte_at(src, pos);
123 let highbits = (first_byte >> 4) as usize;
124 n_remain = PIECE_LOOKUP[highbits] - 1;
125
126 if n_remain < 0 {
127 code_points.clear();
131 code_points.push(0);
132 return (code_points, PartialUtf8::new(0, n_remain));
133 }
134
135 let mask = (1u32 << (7 - n_remain)) - 1;
136 value = first_byte as u32 & mask;
137
138 pos += 1;
139 while byte_at(src, pos) != 0 && n_remain > 0 {
140 value = (value << 6) + (byte_at(src, pos) & 0x3F) as u32;
141 pos += 1;
142 n_remain -= 1;
143 }
144 if n_remain == 0 {
145 code_points.push(value);
146 }
147 }
148 code_points.push(0);
149
150 (code_points, PartialUtf8::new(value, n_remain))
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn ascii_piece_decodes_to_its_bytes_plus_a_terminator() {
159 let (v, p) = decode_piece(b"abc", PartialUtf8::default());
160 assert_eq!(v, vec![0x61, 0x62, 0x63, 0]);
161 assert_eq!(p.n_remain, 0);
162 }
163
164 #[test]
165 fn empty_piece_is_just_the_terminator() {
166 let (v, p) = decode_piece(b"", PartialUtf8::default());
167 assert_eq!(v, vec![0]);
168 assert_eq!(p, PartialUtf8::default());
169 }
170
171 #[test]
172 fn multibyte_piece_decodes_whole() {
173 let (v, p) = decode_piece("é€𝄞".as_bytes(), PartialUtf8::default());
175 assert_eq!(v, vec![0xE9, 0x20AC, 0x1D11E, 0]);
176 assert_eq!(p.n_remain, 0);
177 }
178
179 #[test]
180 fn a_codepoint_split_across_two_pieces_is_carried_and_completed() {
181 let euro = "€".as_bytes();
184 let (v1, p1) = decode_piece(&euro[..1], PartialUtf8::default());
185 assert_eq!(v1, vec![0], "no complete code point yet");
186 assert_eq!(p1.n_remain, 2, "two continuation bytes still expected");
187
188 let (v2, p2) = decode_piece(&euro[1..2], p1);
189 assert_eq!(v2, vec![0], "still incomplete after one continuation byte");
190 assert_eq!(p2.n_remain, 1);
191
192 let (v3, p3) = decode_piece(&euro[2..], p2);
193 assert_eq!(v3, vec![0x20AC, 0], "completes on the last byte");
194 assert_eq!(p3.n_remain, 0);
195 }
196
197 #[test]
198 fn a_split_codepoint_followed_by_more_text_decodes_both() {
199 let bytes = "€!".as_bytes();
200 let (_, p1) = decode_piece(&bytes[..1], PartialUtf8::default());
201 let (v, p) = decode_piece(&bytes[1..], p1);
202 assert_eq!(v, vec![0x20AC, b'!' as u32, 0]);
203 assert_eq!(p.n_remain, 0);
204 }
205
206 #[test]
207 fn a_lead_byte_where_a_continuation_was_due_is_an_invalid_sequence() {
208 let p1 = PartialUtf8::new(0x02, 1); let (v, p) = decode_piece(b"A", p1);
210 assert_eq!(v, vec![0]);
211 assert_eq!(p, PartialUtf8::new(0, -1), "n_remain = -1 marks invalid");
212 }
213
214 #[test]
215 fn a_stray_continuation_byte_clears_everything_already_decoded() {
216 let (v, p) = decode_piece(&[b'a', b'b', 0x80], PartialUtf8::default());
220 assert_eq!(v, vec![0]);
221 assert_eq!(p.n_remain, -1);
222 }
223
224 #[test]
225 fn a_truncated_lead_byte_leaves_a_partial_not_an_error() {
226 let (v, p) = decode_piece(&[0xE2], PartialUtf8::default());
227 assert_eq!(v, vec![0]);
228 assert_eq!(p, PartialUtf8::new(0x02, 2));
229 }
230
231 #[test]
232 fn an_embedded_nul_ends_the_piece_as_it_does_in_c() {
233 let (v, _) = decode_piece(b"ab\0cd", PartialUtf8::default());
234 assert_eq!(v, vec![b'a' as u32, b'b' as u32, 0]);
235 }
236
237 #[test]
238 fn decode_char_reads_one_codepoint_and_reports_its_width() {
239 assert_eq!(decode_char(b"a", 0), (0x61, 1));
240 assert_eq!(decode_char("é".as_bytes(), 0), (0xE9, 2));
241 assert_eq!(decode_char("€".as_bytes(), 0), (0x20AC, 3));
242 assert_eq!(decode_char("𝄞".as_bytes(), 0), (0x1D11E, 4));
243 assert_eq!(decode_char("aé".as_bytes(), 1), (0xE9, 3));
245 }
246
247 #[test]
248 fn decode_char_does_not_run_past_a_truncated_sequence() {
249 let (value, end) = decode_char(&[0xE2], 0);
252 assert_eq!(end, 1, "stopped at the buffer end, not at pos+3");
253 assert_eq!(value, 0x02);
254 }
255
256 #[test]
257 fn the_two_lookup_tables_disagree_on_continuation_bytes_and_that_is_deliberate() {
258 assert_eq!(CHAR_LOOKUP[8], 1);
260 assert_eq!(decode_char(&[0x80], 0), (0x00, 1));
261 assert_eq!(PIECE_LOOKUP[8], 0);
263 }
264}