simdutf 9.3.3
Unicode at GB/s.
Loading...
Searching...
No Matches
utf8_to_utf16_with_replacement.h
1#ifndef SIMDUTF_UTF8_TO_UTF16_WITH_REPLACEMENT_H
2#define SIMDUTF_UTF8_TO_UTF16_WITH_REPLACEMENT_H
3
4#include <cstring>
5
6namespace simdutf {
7namespace scalar {
8namespace {
9namespace utf8_to_utf16 {
10
11// Pointer-like view so validate_with_counts can run during constant
12// evaluation, where reinterpret_cast from char* to uint8_t* is not allowed.
13// operator+ exists only for the runtime ASCII probe inside that function.
14template <typename InputPtr> struct utf8_byte_pointer {
15 InputPtr p;
16 simdutf_constexpr23 uint8_t operator*() const noexcept {
17 return uint8_t(static_cast<unsigned char>(*p));
18 }
19 simdutf_constexpr23 uint8_t operator[](size_t i) const noexcept {
20 return uint8_t(static_cast<unsigned char>(p[i]));
21 }
22 const uint8_t *operator+(size_t i) const noexcept {
23 return reinterpret_cast<const uint8_t *>(static_cast<const void *>(p + i));
24 }
25};
26
27// Byte length of one maximal subpart starting at ptr. n must be at least 1.
28// The validator reports the first byte of the ill-formed sequence.
29template <typename InputPtr>
30#if SIMDUTF_CPLUSPLUS20
31 requires simdutf::detail::indexes_into_byte_like<InputPtr>
32#endif
33simdutf_constexpr23 size_t maximal_subpart(InputPtr ptr, size_t n) noexcept {
34 const uint8_t b = uint8_t(static_cast<unsigned char>(ptr[0]));
35 size_t need;
36 uint8_t lo = 0x80;
37 uint8_t hi = 0xBF;
38 if (b < 0xC2) {
39 return 1; // ASCII is handled by the caller; continuation, C0, C1
40 } else if (b < 0xE0) {
41 need = 1;
42 } else if (b < 0xF0) {
43 need = 2;
44 if (b == 0xE0) {
45 lo = 0xA0;
46 } else if (b == 0xED) {
47 hi = 0x9F;
48 }
49 } else if (b < 0xF5) {
50 need = 3;
51 if (b == 0xF0) {
52 lo = 0x90;
53 } else if (b == 0xF4) {
54 hi = 0x8F;
55 }
56 } else {
57 return 1; // F5..FF
58 }
59 if (n < 2 || uint8_t(static_cast<unsigned char>(ptr[1])) < lo ||
60 uint8_t(static_cast<unsigned char>(ptr[1])) > hi) {
61 return 1;
62 }
63 size_t i = 2;
64 while (i <= need && i < n &&
65 (uint8_t(static_cast<unsigned char>(ptr[i])) & 0xC0) == 0x80) {
66 i++;
67 }
68 return i;
69}
70
71// WHATWG / Unicode substitution of maximal subparts. One U+FFFD per subpart.
72// `write == false` counts UTF-16 code units and does not store.
73template <endianness endian, bool write, typename InputPtr>
74#if SIMDUTF_CPLUSPLUS20
75 requires simdutf::detail::indexes_into_byte_like<InputPtr>
76#endif
77simdutf_constexpr23 size_t transcode_with_replacement(
78 InputPtr data, size_t len, char16_t *utf16_output) noexcept {
79 size_t i = 0;
80 size_t written = 0;
81 uint32_t cp = 0;
82 size_t needed = 0;
83 size_t seen = 0;
84 uint8_t lo = 0x80;
85 uint8_t hi = 0xBF;
86 auto store = [&](uint32_t c) {
87 if (c >= 0x10000) {
88 c -= 0x10000;
89 // The mask is a no-op (c < 0x100000). It stops clang 18's SLP
90 // vectorizer on RISC-V V from narrowing c to 16 bits before
91 // the shift, which drops bits 16-19 of the code point.
92 const uint16_t high = uint16_t(0xD800 + ((c >> 10) & 0x3FF));
93 const uint16_t low = uint16_t(0xDC00 + (c & 0x3FF));
94 if constexpr (write) {
95 utf16_output[written] =
96 char16_t(scalar::utf16::swap_if_needed<endian>(high));
97 utf16_output[written + 1] =
98 char16_t(scalar::utf16::swap_if_needed<endian>(low));
99 }
100 written += 2;
101 } else {
102 if constexpr (write) {
103 utf16_output[written] =
104 char16_t(scalar::utf16::swap_if_needed<endian>(uint16_t(c)));
105 }
106 written += 1;
107 }
108 };
109 while (i < len) {
110 if (needed == 0 && i + 16 <= len) {
111#if SIMDUTF_CPLUSPLUS23
112 if !consteval
113#endif
114 {
115 uint64_t v1;
116 std::memcpy(&v1, data + i, sizeof(v1));
117 uint64_t v2;
118 std::memcpy(&v2, data + i + sizeof(v1), sizeof(v2));
119 if (((v1 | v2) & 0x8080808080808080ULL) == 0) {
120 for (size_t k = 0; k < 16; k++) {
121 store(uint8_t(static_cast<unsigned char>(data[i + k])));
122 }
123 i += 16;
124 continue;
125 }
126 }
127 }
128 const uint8_t b = uint8_t(static_cast<unsigned char>(data[i]));
129 if (needed == 0) {
130 i++;
131 lo = 0x80;
132 hi = 0xBF;
133 if (b < 0x80) {
134 store(b);
135 } else if (b <= 0xDF) {
136 if (b < 0xC2) {
137 store(0xFFFD);
138 } else {
139 needed = 1;
140 cp = b & 0x1F;
141 }
142 } else if (b <= 0xEF) {
143 if (b == 0xE0) {
144 lo = 0xA0;
145 } else if (b == 0xED) {
146 hi = 0x9F;
147 }
148 needed = 2;
149 cp = b & 0x0F;
150 } else if (b <= 0xF4) {
151 if (b == 0xF0) {
152 lo = 0x90;
153 } else if (b == 0xF4) {
154 hi = 0x8F;
155 }
156 needed = 3;
157 cp = b & 0x07;
158 } else {
159 store(0xFFFD);
160 }
161 continue;
162 }
163 if (b < lo || b > hi) {
164 needed = 0;
165 seen = 0;
166 cp = 0;
167 lo = 0x80;
168 hi = 0xBF;
169 store(0xFFFD);
170 continue;
171 }
172 i++;
173 lo = 0x80;
174 hi = 0xBF;
175 cp = (cp << 6) | uint32_t(b & 0x3F);
176 seen++;
177 if (seen == needed) {
178 store(cp);
179 needed = 0;
180 seen = 0;
181 cp = 0;
182 }
183 }
184 if (needed != 0) {
185 store(0xFFFD);
186 }
187 return written;
188}
189
190template <endianness endian, typename InputPtr>
191#if SIMDUTF_CPLUSPLUS20
192 requires simdutf::detail::indexes_into_byte_like<InputPtr>
193#endif
194simdutf_constexpr23 size_t convert_with_replacement(
195 InputPtr data, size_t len, char16_t *utf16_output) noexcept {
196 return transcode_with_replacement<endian, true>(data, len, utf16_output);
197}
198
199template <typename InputPtr>
200#if SIMDUTF_CPLUSPLUS20
201 requires simdutf::detail::indexes_into_byte_like<InputPtr>
202#endif
203simdutf_constexpr23 size_t count_with_replacement(InputPtr data,
204 size_t len) noexcept {
205 return transcode_with_replacement<endianness::LITTLE, false>(data, len,
206 nullptr);
207}
208
209template <typename InputPtr>
210#if SIMDUTF_CPLUSPLUS20
211 requires simdutf::detail::indexes_into_byte_like<InputPtr>
212#endif
213simdutf_constexpr23 utf8_to_utf16_result
214utf16_length_from_utf8_with_replacement(InputPtr data, size_t len) noexcept {
215 utf8_to_utf16_result out;
216 size_t pos = 0;
217 while (pos < len) {
218 utf8_result validation;
219#if SIMDUTF_CPLUSPLUS23
220 if consteval {
221 validation = scalar::utf8::validate_with_counts(
222 utf8_byte_pointer<InputPtr>{data + pos}, len - pos);
223 } else
224#endif
225 {
226 validation = scalar::utf8::validate_with_counts(
227 reinterpret_cast<const uint8_t *>(data + pos), len - pos);
228 }
229 if (validation.error == error_code::SUCCESS) {
230 out.count += validation.utf16_length();
231 return out;
232 }
233 if (validation.input_count >= len - pos) {
234 out.count += count_with_replacement(data + pos, len - pos);
235 out.more_errors = true;
236 if (out.error == error_code::SUCCESS) {
237 out.error = validation.error;
238 }
239 return out;
240 }
241 out.count += validation.utf16_length() + 1;
242 if (out.error == error_code::SUCCESS) {
243 out.error = validation.error;
244 }
245 const size_t err = pos + validation.input_count;
246 const size_t skip = maximal_subpart(data + err, len - err);
247 if (out.error_count < utf8_to_utf16_result::max_errors) {
248 out.error_offset[out.error_count] = err;
249 out.error_count += 1;
250 } else {
251 out.more_errors = true;
252 }
253 pos = err + skip;
254 }
255 return out;
256}
257
258} // namespace utf8_to_utf16
259} // unnamed namespace
260} // namespace scalar
261} // namespace simdutf
262
263#endif // SIMDUTF_UTF8_TO_UTF16_WITH_REPLACEMENT_H
helpers placed in namespace detail are not a part of the public API
simdutf_warn_unused utf8_to_utf16_result utf16_length_from_utf8_with_replacement(const char *input, size_t length) noexcept
Compute the number of char16_t that this UTF-8 string requires in UTF-16 when ill-formed subsequences...