Ada 3.4.0
Fast spec-compliant URL parser
Loading...
Searching...
No Matches
unicode.cpp
Go to the documentation of this file.
1#include "ada/unicode.h"
2
5#include "ada/common_defs.h"
6#include "ada/log.h"
7
9#include "ada_idna.cpp"
11
12#include <algorithm>
13#if ADA_SSSE3
14#include <tmmintrin.h>
15#elif ADA_NEON
16#include <arm_neon.h>
17#elif ADA_SSE2
18#include <emmintrin.h>
19#elif ADA_LSX
20#include <lsxintrin.h>
21#elif ADA_RVV
22#include <riscv_vector.h>
23#endif
24
25#include <ranges>
26
27namespace ada::unicode {
28
29constexpr bool is_tabs_or_newline(char c) noexcept {
30 return c == '\r' || c == '\n' || c == '\t';
31}
32
33constexpr uint64_t broadcast(uint8_t v) noexcept {
34 return 0x101010101010101ull * v;
35}
36
37constexpr bool to_lower_ascii(char* input, size_t length) noexcept {
38 uint64_t broadcast_80 = broadcast(0x80);
39 uint64_t broadcast_Ap = broadcast(128 - 'A');
40 uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1);
41 uint64_t non_ascii = 0;
42 size_t i = 0;
43
44 for (; i + 7 < length; i += 8) {
45 uint64_t word{};
46 memcpy(&word, input + i, sizeof(word));
47 non_ascii |= (word & broadcast_80);
48 word ^=
49 (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
50 memcpy(input + i, &word, sizeof(word));
51 }
52 if (i < length) {
53 uint64_t word{};
54 memcpy(&word, input + i, length - i);
55 non_ascii |= (word & broadcast_80);
56 word ^=
57 (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
58 memcpy(input + i, &word, length - i);
59 }
60 return non_ascii == 0;
61}
62#if ADA_SSSE3
63ada_really_inline bool has_tabs_or_newline(
64 std::string_view user_input) noexcept {
65 // first check for short strings in which case we do it naively.
66 if (user_input.size() < 16) { // slow path
67 return std::ranges::any_of(user_input, is_tabs_or_newline);
68 }
69 // fast path for long strings (expected to be common)
70 // Using SSSE3's _mm_shuffle_epi8 for table lookup (same approach as NEON)
71 size_t i = 0;
72 // Lookup table where positions 9, 10, 13 contain their own values
73 // Everything else is set to 1 so it won't match
74 const __m128i rnt =
75 _mm_setr_epi8(1, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 0, 0, 13, 0, 0);
76 __m128i running = _mm_setzero_si128();
77 for (; i + 15 < user_input.size(); i += 16) {
78 __m128i word = _mm_loadu_si128((const __m128i*)(user_input.data() + i));
79 // Shuffle the lookup table using input bytes as indices
80 __m128i shuffled = _mm_shuffle_epi8(rnt, word);
81 // Compare: if shuffled value matches input, we found \t, \n, or \r
82 __m128i matches = _mm_cmpeq_epi8(shuffled, word);
83 running = _mm_or_si128(running, matches);
84 }
85 if (i < user_input.size()) {
86 __m128i word = _mm_loadu_si128(
87 (const __m128i*)(user_input.data() + user_input.length() - 16));
88 __m128i shuffled = _mm_shuffle_epi8(rnt, word);
89 __m128i matches = _mm_cmpeq_epi8(shuffled, word);
90 running = _mm_or_si128(running, matches);
91 }
92 return _mm_movemask_epi8(running) != 0;
93}
94#elif ADA_NEON
95ada_really_inline bool has_tabs_or_newline(
96 std::string_view user_input) noexcept {
97 // first check for short strings in which case we do it naively.
98 if (user_input.size() < 16) { // slow path
99 return std::ranges::any_of(user_input, is_tabs_or_newline);
100 }
101 // fast path for long strings (expected to be common)
102 size_t i = 0;
115 static uint8_t rnt_array[16] = {1, 0, 0, 0, 0, 0, 0, 0,
116 0, 9, 10, 0, 0, 13, 0, 0};
117 const uint8x16_t rnt = vld1q_u8(rnt_array);
118 // m['0xd', '0xa', '0x9']
119 uint8x16_t running{0};
120 for (; i + 15 < user_input.size(); i += 16) {
121 uint8x16_t word = vld1q_u8((const uint8_t*)user_input.data() + i);
122
123 running = vorrq_u8(running, vceqq_u8(vqtbl1q_u8(rnt, word), word));
124 }
125 if (i < user_input.size()) {
126 uint8x16_t word =
127 vld1q_u8((const uint8_t*)user_input.data() + user_input.length() - 16);
128 running = vorrq_u8(running, vceqq_u8(vqtbl1q_u8(rnt, word), word));
129 }
130 return vmaxvq_u32(vreinterpretq_u32_u8(running)) != 0;
131}
132#elif ADA_SSE2
133ada_really_inline bool has_tabs_or_newline(
134 std::string_view user_input) noexcept {
135 // first check for short strings in which case we do it naively.
136 if (user_input.size() < 16) { // slow path
137 return std::ranges::any_of(user_input, is_tabs_or_newline);
138 }
139 // fast path for long strings (expected to be common)
140 size_t i = 0;
141 const __m128i mask1 = _mm_set1_epi8('\r');
142 const __m128i mask2 = _mm_set1_epi8('\n');
143 const __m128i mask3 = _mm_set1_epi8('\t');
144 // If we supported SSSE3, we could use the algorithm that we use for NEON.
145 __m128i running{0};
146 for (; i + 15 < user_input.size(); i += 16) {
147 __m128i word = _mm_loadu_si128((const __m128i*)(user_input.data() + i));
148 running = _mm_or_si128(
149 _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1),
150 _mm_cmpeq_epi8(word, mask2))),
151 _mm_cmpeq_epi8(word, mask3));
152 }
153 if (i < user_input.size()) {
154 __m128i word = _mm_loadu_si128(
155 (const __m128i*)(user_input.data() + user_input.length() - 16));
156 running = _mm_or_si128(
157 _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1),
158 _mm_cmpeq_epi8(word, mask2))),
159 _mm_cmpeq_epi8(word, mask3));
160 }
161 return _mm_movemask_epi8(running) != 0;
162}
163#elif ADA_LSX
164ada_really_inline bool has_tabs_or_newline(
165 std::string_view user_input) noexcept {
166 // first check for short strings in which case we do it naively.
167 if (user_input.size() < 16) { // slow path
168 return std::ranges::any_of(user_input, is_tabs_or_newline);
169 }
170 // fast path for long strings (expected to be common)
171 size_t i = 0;
172 const __m128i mask1 = __lsx_vrepli_b('\r');
173 const __m128i mask2 = __lsx_vrepli_b('\n');
174 const __m128i mask3 = __lsx_vrepli_b('\t');
175 // If we supported SSSE3, we could use the algorithm that we use for NEON.
176 __m128i running{0};
177 for (; i + 15 < user_input.size(); i += 16) {
178 __m128i word = __lsx_vld((const __m128i*)(user_input.data() + i), 0);
179 running = __lsx_vor_v(
180 __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1),
181 __lsx_vseq_b(word, mask2))),
182 __lsx_vseq_b(word, mask3));
183 }
184 if (i < user_input.size()) {
185 __m128i word = __lsx_vld(
186 (const __m128i*)(user_input.data() + user_input.length() - 16), 0);
187 running = __lsx_vor_v(
188 __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1),
189 __lsx_vseq_b(word, mask2))),
190 __lsx_vseq_b(word, mask3));
191 }
192 if (__lsx_bz_v(running)) return false;
193 return true;
194}
195#elif ADA_RVV
196ada_really_inline bool has_tabs_or_newline(
197 std::string_view user_input) noexcept {
198 uint8_t* src = (uint8_t*)user_input.data();
199 for (size_t vl, n = user_input.size(); n > 0; n -= vl, src += vl) {
200 vl = __riscv_vsetvl_e8m1(n);
201 vuint8m1_t v = __riscv_vle8_v_u8m1(src, vl);
202 vbool8_t m1 = __riscv_vmseq(v, '\r', vl);
203 vbool8_t m2 = __riscv_vmseq(v, '\n', vl);
204 vbool8_t m3 = __riscv_vmseq(v, '\t', vl);
205 vbool8_t m = __riscv_vmor(__riscv_vmor(m1, m2, vl), m3, vl);
206 long idx = __riscv_vfirst(m, vl);
207 if (idx >= 0) return true;
208 }
209 return false;
210}
211#else
212ada_really_inline bool has_tabs_or_newline(
213 std::string_view user_input) noexcept {
214 auto has_zero_byte = [](uint64_t v) {
215 return ((v - 0x0101010101010101) & ~(v) & 0x8080808080808080);
216 };
217 size_t i = 0;
218 uint64_t mask1 = broadcast('\r');
219 uint64_t mask2 = broadcast('\n');
220 uint64_t mask3 = broadcast('\t');
221 uint64_t running{0};
222 for (; i + 7 < user_input.size(); i += 8) {
223 uint64_t word{};
224 memcpy(&word, user_input.data() + i, sizeof(word));
225 uint64_t xor1 = word ^ mask1;
226 uint64_t xor2 = word ^ mask2;
227 uint64_t xor3 = word ^ mask3;
228 running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
229 }
230 if (i < user_input.size()) {
231 uint64_t word{};
232 memcpy(&word, user_input.data() + i, user_input.size() - i);
233 uint64_t xor1 = word ^ mask1;
234 uint64_t xor2 = word ^ mask2;
235 uint64_t xor3 = word ^ mask3;
236 running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
237 }
238 return running;
239}
240#endif
241
242// A forbidden host code point is U+0000 NULL, U+0009 TAB, U+000A LF, U+000D CR,
243// U+0020 SPACE, U+0023 (#), U+002F (/), U+003A (:), U+003C (<), U+003E (>),
244// U+003F (?), U+0040 (@), U+005B ([), U+005C (\‍), U+005D (]), U+005E (^), or
245// U+007C (|).
246constexpr static std::array<uint8_t, 256> is_forbidden_host_code_point_table =
247 []() consteval {
248 std::array<uint8_t, 256> result{};
249 for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
250 '>', '?', '@', '[', '\\', ']', '^', '|'}) {
251 result[c] = true;
252 }
253 return result;
254 }();
255
256ada_really_inline constexpr bool is_forbidden_host_code_point(
257 const char c) noexcept {
258 return is_forbidden_host_code_point_table[uint8_t(c)];
259}
260
261constexpr static std::array<uint8_t, 256> is_forbidden_domain_code_point_table =
262 []() consteval {
263 std::array<uint8_t, 256> result{};
264 for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
265 '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) {
266 result[c] = true;
267 }
268 for (uint8_t c = 0; c <= 32; c++) {
269 result[c] = true;
270 }
271 for (size_t c = 127; c < 255; c++) {
272 result[c] = true;
273 }
274 return result;
275 }();
276
277static_assert(sizeof(is_forbidden_domain_code_point_table) == 256);
278
279ada_really_inline constexpr bool is_forbidden_domain_code_point(
280 const char c) noexcept {
281 return is_forbidden_domain_code_point_table[uint8_t(c)];
282}
283
284ada_really_inline constexpr bool contains_forbidden_domain_code_point(
285 const char* input, size_t length) noexcept {
286 size_t i = 0;
287 uint8_t accumulator{};
288 for (; i + 4 <= length; i += 4) {
289 accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])];
290 accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 1])];
291 accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 2])];
292 accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 3])];
293 }
294 for (; i < length; i++) {
295 accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])];
296 }
297 return accumulator;
298}
299
300constexpr static std::array<uint8_t, 256>
302 std::array<uint8_t, 256> result{};
303 for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
304 '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) {
305 result[c] = 1;
306 }
307 for (uint8_t c = 'A'; c <= 'Z'; c++) {
308 result[c] = 2;
309 }
310 for (uint8_t c = 0; c <= 32; c++) {
311 result[c] = 1;
312 }
313 for (size_t c = 127; c < 255; c++) {
314 result[c] = 1;
315 }
316 return result;
317 }();
318
319ada_really_inline constexpr uint8_t
320contains_forbidden_domain_code_point_or_upper(const char* input,
321 size_t length) noexcept {
322 size_t i = 0;
323 uint8_t accumulator{};
324 for (; i + 4 <= length; i += 4) {
325 accumulator |=
327 accumulator |=
329 accumulator |=
331 accumulator |=
333 }
334 for (; i < length; i++) {
335 accumulator |=
337 }
338 return accumulator;
339}
340
341// std::isalnum(c) || c == '+' || c == '-' || c == '.') is true for
342constexpr static std::array<bool, 256> is_alnum_plus_table = []() consteval {
343 std::array<bool, 256> result{};
344 for (size_t c = 0; c < 256; c++) {
345 result[c] = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
346 (c >= 'A' && c <= 'Z') || c == '+' || c == '-' || c == '.';
347 }
348 return result;
349}();
350
351ada_really_inline constexpr bool is_alnum_plus(const char c) noexcept {
352 return is_alnum_plus_table[uint8_t(c)];
353 // A table is almost surely much faster than the
354 // following under most compilers: return
355 // return (std::isalnum(c) || c == '+' || c == '-' || c == '.');
356}
357
358ada_really_inline constexpr bool is_ascii_hex_digit(const char c) noexcept {
359 return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') ||
360 (c >= 'a' && c <= 'f');
361}
362
363ada_really_inline constexpr bool is_ascii_digit(const char c) noexcept {
364 // An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9),
365 // inclusive.
366 return (c >= '0' && c <= '9');
367}
368
369ada_really_inline constexpr bool is_ascii(const char32_t c) noexcept {
370 // If code point is between U+0000 and U+007F inclusive, then return true.
371 return c <= 0x7F;
372}
373
374ada_really_inline constexpr bool is_c0_control_or_space(const char c) noexcept {
375 return (unsigned char)c <= ' ';
376}
377
378ada_really_inline constexpr bool is_ascii_tab_or_newline(
379 const char c) noexcept {
380 return c == '\t' || c == '\n' || c == '\r';
381}
382
383constexpr std::string_view table_is_double_dot_path_segment[] = {
384 "..", "%2e.", ".%2e", "%2e%2e"};
385
386ada_really_inline constexpr bool is_double_dot_path_segment(
387 std::string_view input) noexcept {
388 // This will catch most cases:
389 // The length must be 2,4 or 6.
390 // We divide by two and require
391 // that the result be between 1 and 3 inclusively.
392 uint64_t half_length = uint64_t(input.size()) / 2;
393 if (half_length - 1 > 2) {
394 return false;
395 }
396 // We have a string of length 2, 4 or 6.
397 // We now check the first character:
398 if ((input[0] != '.') && (input[0] != '%')) {
399 return false;
400 }
401 // We are unlikely the get beyond this point.
402 int hash_value = (input.size() + (unsigned)(input[0])) & 3;
403 const std::string_view target = table_is_double_dot_path_segment[hash_value];
404 if (target.size() != input.size()) {
405 return false;
406 }
407 // We almost never get here.
408 // Optimizing the rest is relatively unimportant.
409 auto prefix_equal_unsafe = [](std::string_view a, std::string_view b) {
410 uint16_t A, B;
411 memcpy(&A, a.data(), sizeof(A));
412 memcpy(&B, b.data(), sizeof(B));
413 return A == B;
414 };
415 if (!prefix_equal_unsafe(input, target)) {
416 return false;
417 }
418 for (size_t i = 2; i < input.size(); i++) {
419 char c = input[i];
420 if ((uint8_t((c | 0x20) - 0x61) <= 25 ? (c | 0x20) : c) != target[i]) {
421 return false;
422 }
423 }
424 return true;
425 // The above code might be a bit better than the code below. Compilers
426 // are not stupid and may use the fact that these strings have length 2,4 and
427 // 6 and other tricks.
428 // return input == ".." ||
429 // input == ".%2e" || input == ".%2E" ||
430 // input == "%2e." || input == "%2E." ||
431 // input == "%2e%2e" || input == "%2E%2E" || input == "%2E%2e" || input ==
432 // "%2e%2E";
433}
434
435ada_really_inline constexpr bool is_single_dot_path_segment(
436 std::string_view input) noexcept {
437 return input == "." || input == "%2e" || input == "%2E";
438}
439
440ada_really_inline constexpr bool is_lowercase_hex(const char c) noexcept {
441 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
442}
443
444constexpr static char hex_to_binary_table[] = {
445 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11,
446 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
447 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15};
448unsigned constexpr convert_hex_to_binary(const char c) noexcept {
449 return hex_to_binary_table[c - '0'];
450}
451
452std::string percent_decode(const std::string_view input, size_t first_percent) {
453 // next line is for safety only, we expect users to avoid calling
454 // percent_decode when first_percent is outside the range.
455 if (first_percent == std::string_view::npos) {
456 return std::string(input);
457 }
458 std::string dest;
459 dest.reserve(input.length());
460 dest.append(input.substr(0, first_percent));
461 const char* pointer = input.data() + first_percent;
462 const char* end = input.data() + input.size();
463 // Optimization opportunity: if the following code gets
464 // called often, it can be optimized quite a bit.
465 while (pointer < end) {
466 const char ch = pointer[0];
467 size_t remaining = end - pointer - 1;
468 if (ch != '%' || remaining < 2 ||
469 ( // ch == '%' && // It is unnecessary to check that ch == '%'.
470 (!is_ascii_hex_digit(pointer[1]) ||
471 !is_ascii_hex_digit(pointer[2])))) {
472 dest += ch;
473 pointer++;
474 } else {
475 unsigned a = convert_hex_to_binary(pointer[1]);
476 unsigned b = convert_hex_to_binary(pointer[2]);
477 char c = static_cast<char>(a * 16 + b);
478 dest += c;
479 pointer += 3;
480 }
481 }
482 return dest;
483}
484
485std::string percent_encode(const std::string_view input,
486 const uint8_t character_set[]) {
487 auto pointer = std::ranges::find_if(input, [character_set](const char c) {
488 return character_sets::bit_at(character_set, c);
489 });
490 // Optimization: Don't iterate if percent encode is not required
491 if (pointer == input.end()) {
492 return std::string(input);
493 }
494
495 std::string result;
496 result.reserve(input.length()); // in the worst case, percent encoding might
497 // produce 3 characters.
498 result.append(input.substr(0, std::distance(input.begin(), pointer)));
499
500 for (; pointer != input.end(); pointer++) {
501 if (character_sets::bit_at(character_set, *pointer)) {
502 result.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
503 } else {
504 result += *pointer;
505 }
506 }
507
508 return result;
509}
510
511template <bool append>
512bool percent_encode(const std::string_view input, const uint8_t character_set[],
513 std::string& out) {
514 ada_log("percent_encode ", input, " to output string while ",
515 append ? "appending" : "overwriting");
516 auto pointer = std::ranges::find_if(input, [character_set](const char c) {
517 return character_sets::bit_at(character_set, c);
518 });
519 ada_log("percent_encode done checking, moved to ",
520 std::distance(input.begin(), pointer));
521
522 // Optimization: Don't iterate if percent encode is not required
523 if (pointer == input.end()) {
524 ada_log("percent_encode encoding not needed.");
525 return false;
526 }
527 if constexpr (!append) {
528 out.clear();
529 }
530 ada_log("percent_encode appending ", std::distance(input.begin(), pointer),
531 " bytes");
532 // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
533 out.append(input.data(), std::distance(input.begin(), pointer));
534 ada_log("percent_encode processing ", std::distance(pointer, input.end()),
535 " bytes");
536 for (; pointer != input.end(); pointer++) {
537 if (character_sets::bit_at(character_set, *pointer)) {
538 out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
539 } else {
540 out += *pointer;
541 }
542 }
543 return true;
544}
545
546bool to_ascii(std::optional<std::string>& out, const std::string_view plain,
547 size_t first_percent) {
548 std::string percent_decoded_buffer;
549 std::string_view input = plain;
550 if (first_percent != std::string_view::npos) {
551 percent_decoded_buffer = unicode::percent_decode(plain, first_percent);
552 input = percent_decoded_buffer;
553 }
554 // input is a non-empty UTF-8 string, must be percent decoded
555 std::string idna_ascii = ada::idna::to_ascii(input);
556 if (idna_ascii.empty() || contains_forbidden_domain_code_point(
557 idna_ascii.data(), idna_ascii.size())) {
558 return false;
559 }
560 out = std::move(idna_ascii);
561 return true;
562}
563
564std::string percent_encode(const std::string_view input,
565 const uint8_t character_set[], size_t index) {
566 std::string out;
567 // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
568 out.append(input.data(), index);
569 auto pointer = input.begin() + index;
570 for (; pointer != input.end(); pointer++) {
571 if (character_sets::bit_at(character_set, *pointer)) {
572 out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
573 } else {
574 out += *pointer;
575 }
576 }
577 return out;
578}
579
580} // namespace ada::unicode
Definitions of the character sets used by unicode functions.
Declaration of the character sets used by unicode functions.
Cross-platform compiler macros and common definitions.
#define ADA_PUSH_DISABLE_ALL_WARNINGS
Definition common_defs.h:94
#define ADA_POP_DISABLE_WARNINGS
#define ada_really_inline
Definition common_defs.h:85
ada_really_inline constexpr bool bit_at(const uint8_t a[], const uint8_t i)
constexpr char hex[1024]
std::string to_ascii(std::string_view ut8_string)
Includes the declarations for unicode operations.
static constexpr std::array< uint8_t, 256 > is_forbidden_domain_code_point_table
Definition unicode.cpp:261
static constexpr std::array< uint8_t, 256 > is_forbidden_domain_code_point_table_or_upper
Definition unicode.cpp:301
static constexpr char hex_to_binary_table[]
Definition unicode.cpp:444
constexpr uint64_t broadcast(uint8_t v) noexcept
Definition unicode.cpp:33
constexpr std::string_view table_is_double_dot_path_segment[]
Definition unicode.cpp:383
constexpr bool is_tabs_or_newline(char c) noexcept
Definition unicode.cpp:29
static constexpr std::array< uint8_t, 256 > is_forbidden_host_code_point_table
Definition unicode.cpp:246
static constexpr std::array< bool, 256 > is_alnum_plus_table
Definition unicode.cpp:342
tl::expected< result_type, ada::errors > result
Definitions for all unicode specific functions.