simdutf 9.0.0
Unicode at GB/s.
Loading...
Searching...
No Matches
utf8.h
1#ifndef SIMDUTF_UTF8_H
2#define SIMDUTF_UTF8_H
3
4#include <cstring>
5
6namespace simdutf {
7namespace scalar {
8namespace {
9namespace utf8 {
10
11// credit: based on code from Google Fuchsia (Apache Licensed)
12template <class BytePtr>
13simdutf_constexpr23 simdutf_warn_unused bool validate(BytePtr data,
14 size_t len) noexcept {
15 static_assert(
16 std::is_same<typename std::decay<decltype(*data)>::type, uint8_t>::value,
17 "dereferencing the data pointer must result in a uint8_t");
18 uint64_t pos = 0;
19 uint32_t code_point = 0;
20 while (pos < len) {
21 uint64_t next_pos;
22#if SIMDUTF_CPLUSPLUS23
23 if !consteval
24#endif
25 { // check if the next 16 bytes are ascii.
26 next_pos = pos + 16;
27 if (next_pos <= len) { // if it is safe to read 16 more bytes, check
28 // that they are ascii
29 uint64_t v1{};
30 std::memcpy(&v1, data + pos, sizeof(uint64_t));
31 uint64_t v2{};
32 std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
33 uint64_t v{v1 | v2};
34 if ((v & 0x8080808080808080) == 0) {
35 pos = next_pos;
36 continue;
37 }
38 }
39 }
40
41 unsigned char byte = data[pos];
42
43 while (byte < 0b10000000) {
44 if (++pos == len) {
45 return true;
46 }
47 byte = data[pos];
48 }
49
50 if ((byte & 0b11100000) == 0b11000000) {
51 next_pos = pos + 2;
52 if (next_pos > len) {
53 return false;
54 }
55 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
56 return false;
57 }
58 // range check
59 code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
60 if (code_point < 0x80) {
61 return false;
62 }
63 } else if ((byte & 0b11110000) == 0b11100000) {
64 next_pos = pos + 3;
65 if (next_pos > len) {
66 return false;
67 }
68 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
69 return false;
70 }
71 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
72 return false;
73 }
74 // range check
75 code_point = (byte & 0b00001111) << 12 |
76 (data[pos + 1] & 0b00111111) << 6 |
77 (data[pos + 2] & 0b00111111);
78 if ((code_point < 0x800) ||
79 (0xd7ff < code_point && code_point < 0xe000)) {
80 return false;
81 }
82 } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000
83 next_pos = pos + 4;
84 if (next_pos > len) {
85 return false;
86 }
87 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
88 return false;
89 }
90 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
91 return false;
92 }
93 if ((data[pos + 3] & 0b11000000) != 0b10000000) {
94 return false;
95 }
96 // range check
97 code_point =
98 (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
99 (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
100 if (code_point <= 0xffff || 0x10ffff < code_point) {
101 return false;
102 }
103 } else {
104 // we may have a continuation
105 return false;
106 }
107 pos = next_pos;
108 }
109 return true;
110}
111
112simdutf_really_inline simdutf_warn_unused bool validate(const char *buf,
113 size_t len) noexcept {
114 return validate(reinterpret_cast<const uint8_t *>(buf), len);
115}
116
117template <class BytePtr>
118simdutf_constexpr23 simdutf_warn_unused result
119validate_with_errors(BytePtr data, size_t len) noexcept {
120 static_assert(
121 std::is_same<typename std::decay<decltype(*data)>::type, uint8_t>::value,
122 "dereferencing the data pointer must result in a uint8_t");
123 size_t pos = 0;
124 uint32_t code_point = 0;
125 while (pos < len) {
126 // check of the next 16 bytes are ascii.
127 size_t next_pos = pos + 16;
128 if (next_pos <=
129 len) { // if it is safe to read 16 more bytes, check that they are ascii
130 uint64_t v1;
131 std::memcpy(&v1, data + pos, sizeof(uint64_t));
132 uint64_t v2;
133 std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
134 uint64_t v{v1 | v2};
135 if ((v & 0x8080808080808080) == 0) {
136 pos = next_pos;
137 continue;
138 }
139 }
140 unsigned char byte = data[pos];
141
142 while (byte < 0b10000000) {
143 if (++pos == len) {
144 return result(error_code::SUCCESS, len);
145 }
146 byte = data[pos];
147 }
148
149 if ((byte & 0b11100000) == 0b11000000) {
150 next_pos = pos + 2;
151 if (next_pos > len) {
152 return result(error_code::TOO_SHORT, pos);
153 }
154 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
155 return result(error_code::TOO_SHORT, pos);
156 }
157 // range check
158 code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
159 if (code_point < 0x80) {
160 return result(error_code::OVERLONG, pos);
161 }
162 } else if ((byte & 0b11110000) == 0b11100000) {
163 next_pos = pos + 3;
164 if (next_pos > len) {
165 return result(error_code::TOO_SHORT, pos);
166 }
167 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
168 return result(error_code::TOO_SHORT, pos);
169 }
170 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
171 return result(error_code::TOO_SHORT, pos);
172 }
173 // range check
174 code_point = (byte & 0b00001111) << 12 |
175 (data[pos + 1] & 0b00111111) << 6 |
176 (data[pos + 2] & 0b00111111);
177 if (code_point < 0x800) {
178 return result(error_code::OVERLONG, pos);
179 }
180 if (0xd7ff < code_point && code_point < 0xe000) {
181 return result(error_code::SURROGATE, pos);
182 }
183 } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000
184 next_pos = pos + 4;
185 if (next_pos > len) {
186 return result(error_code::TOO_SHORT, pos);
187 }
188 if ((data[pos + 1] & 0b11000000) != 0b10000000) {
189 return result(error_code::TOO_SHORT, pos);
190 }
191 if ((data[pos + 2] & 0b11000000) != 0b10000000) {
192 return result(error_code::TOO_SHORT, pos);
193 }
194 if ((data[pos + 3] & 0b11000000) != 0b10000000) {
195 return result(error_code::TOO_SHORT, pos);
196 }
197 // range check
198 code_point =
199 (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
200 (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
201 if (code_point <= 0xffff) {
202 return result(error_code::OVERLONG, pos);
203 }
204 if (0x10ffff < code_point) {
205 return result(error_code::TOO_LARGE, pos);
206 }
207 } else {
208 // we either have too many continuation bytes or an invalid leading byte
209 if ((byte & 0b11000000) == 0b10000000) {
210 return result(error_code::TOO_LONG, pos);
211 } else {
212 return result(error_code::HEADER_BITS, pos);
213 }
214 }
215 pos = next_pos;
216 }
217 return result(error_code::SUCCESS, len);
218}
219
220simdutf_really_inline simdutf_warn_unused result
221validate_with_errors(const char *buf, size_t len) noexcept {
222 return validate_with_errors(reinterpret_cast<const uint8_t *>(buf), len);
223}
224
225// Finds the previous leading byte starting backward from buf and validates with
226// errors from there Used to pinpoint the location of an error when an invalid
227// chunk is detected We assume that the stream starts with a leading byte, and
228// to check that it is the case, we ask that you pass a pointer to the start of
229// the stream (start). Note that the resulting count is underflowed if an error
230// is encountered in the rewinded segment.
231inline simdutf_warn_unused result rewind_and_validate_with_errors(
232 const char *start, const char *buf, size_t len) noexcept {
233 // First check that we start with a leading byte
234 if ((*start & 0b11000000) == 0b10000000) {
235 return result(error_code::TOO_LONG, 0);
236 }
237 size_t extra_len{0};
238 // A leading byte cannot be further than 4 bytes away
239 for (int i = 0; i < 5; i++) {
240 unsigned char byte = *buf;
241 if ((byte & 0b11000000) != 0b10000000) {
242 break;
243 } else {
244 buf--;
245 extra_len++;
246 }
247 }
248
249 result res = validate_with_errors(buf, len + extra_len);
250 res.count -= extra_len; // Might underflow
251 return res;
252}
253
254template <typename InputPtr>
255#if SIMDUTF_CPLUSPLUS20
256 requires simdutf::detail::indexes_into_byte_like<InputPtr>
257#endif
258simdutf_constexpr23 size_t count_code_points(InputPtr data, size_t len) {
259 size_t counter{0};
260 for (size_t i = 0; i < len; i++) {
261 // -65 is 0b10111111, anything larger in two-complement's should start a new
262 // code point.
263 if (int8_t(data[i]) > -65) {
264 counter++;
265 }
266 }
267 return counter;
268}
269
270template <typename InputPtr>
271#if SIMDUTF_CPLUSPLUS20
272 requires simdutf::detail::indexes_into_byte_like<InputPtr>
273#endif
274simdutf_constexpr23 size_t utf16_length_from_utf8(InputPtr data, size_t len) {
275 size_t counter{0};
276 for (size_t i = 0; i < len; i++) {
277 if (int8_t(data[i]) > -65) {
278 counter++;
279 }
280 if (uint8_t(data[i]) >= 240) {
281 counter++;
282 }
283 }
284 return counter;
285}
286
287template <typename InputPtr>
288#if SIMDUTF_CPLUSPLUS20
289 requires simdutf::detail::indexes_into_byte_like<InputPtr>
290#endif
291simdutf_warn_unused simdutf_constexpr23 size_t
292trim_partial_utf8(InputPtr input, size_t length) {
293 if (length < 3) {
294 switch (length) {
295 case 2:
296 if (uint8_t(input[length - 1]) >= 0xc0) {
297 return length - 1;
298 } // 2-, 3- and 4-byte characters with only 1 byte left
299 if (uint8_t(input[length - 2]) >= 0xe0) {
300 return length - 2;
301 } // 3- and 4-byte characters with only 2 bytes left
302 return length;
303 case 1:
304 if (uint8_t(input[length - 1]) >= 0xc0) {
305 return length - 1;
306 } // 2-, 3- and 4-byte characters with only 1 byte left
307 return length;
308 case 0:
309 return length;
310 }
311 }
312 if (uint8_t(input[length - 1]) >= 0xc0) {
313 return length - 1;
314 } // 2-, 3- and 4-byte characters with only 1 byte left
315 if (uint8_t(input[length - 2]) >= 0xe0) {
316 return length - 2;
317 } // 3- and 4-byte characters with only 1 byte left
318 if (uint8_t(input[length - 3]) >= 0xf0) {
319 return length - 3;
320 } // 4-byte characters with only 3 bytes left
321 return length;
322}
323
324} // namespace utf8
325} // unnamed namespace
326} // namespace scalar
327} // namespace simdutf
328
329#endif
helpers placed in namespace detail are not a part of the public API
simdutf_warn_unused size_t utf16_length_from_utf8(const char *input, size_t length) noexcept
Compute the number of 2-byte code units that this UTF-8 string would require in UTF-16LE format.
simdutf_warn_unused size_t trim_partial_utf8(const char *input, size_t length)
Given a valid UTF-8 string having a possibly truncated last character, this function checks the end o...