diff --git a/implementations/Crafter.Review-Core.cpp b/implementations/Crafter.Review-Core.cpp new file mode 100644 index 0000000..b5b3451 --- /dev/null +++ b/implementations/Crafter.Review-Core.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module Crafter.Review:Core_impl; +import :Core; +import std; + +namespace Crafter::Review { + std::string_view Version() { + return CRAFTER_REVIEW_VERSION; + } + + std::string DriverLine() { + return std::format("crafter-review {} (prompt v{})", Version(), PromptVersion); + } +} diff --git a/implementations/Crafter.Review-Crypto.cpp b/implementations/Crafter.Review-Crypto.cpp new file mode 100644 index 0000000..d0f8d87 --- /dev/null +++ b/implementations/Crafter.Review-Crypto.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module; +#include +#include +#include +#include +#include + +module Crafter.Review:Crypto_impl; +import :Crypto; +import std; + +namespace Crafter::Review { + namespace { + std::string Digest(const EVP_MD* method, std::string_view data) { + std::array out {}; + std::uint32_t length = 0; + if (EVP_Digest(data.data(), data.size(), out.data(), &length, method, nullptr) != 1) { + throw std::runtime_error("libcrypto: digest failed"); + } + return std::string(reinterpret_cast(out.data()), length); + } + + std::string Hmac(const EVP_MD* method, std::string_view key, std::string_view data) { + std::array out {}; + std::uint32_t length = 0; + const unsigned char* result = HMAC(method, key.data(), static_cast(key.size()), + reinterpret_cast(data.data()), data.size(), + out.data(), &length); + if (result == nullptr) throw std::runtime_error("libcrypto: HMAC failed"); + return std::string(reinterpret_cast(out.data()), length); + } + + constexpr std::string_view Base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + constexpr std::string_view Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + std::int32_t Base64Value(char c) { + const std::size_t at = Base64Alphabet.find(c); + return at == std::string_view::npos ? -1 : static_cast(at); + } + } + + std::string Sha256(std::string_view data) { + return Digest(EVP_sha256(), data); + } + + std::string Sha1(std::string_view data) { + return Digest(EVP_sha1(), data); + } + + std::string Md5(std::string_view data) { + return Digest(EVP_md5(), data); + } + + std::string Sha256Hex(std::string_view data) { + return HexEncode(Sha256(data)); + } + + std::string Md5Hex(std::string_view data) { + return HexEncode(Md5(data)); + } + + std::string HmacSha256(std::string_view key, std::string_view data) { + return Hmac(EVP_sha256(), key, data); + } + + std::string HmacSha1(std::string_view key, std::string_view data) { + return Hmac(EVP_sha1(), key, data); + } + + std::string Pbkdf2Sha256(std::string_view password, std::string_view salt, std::uint32_t iterations) { + std::array out {}; + if (PKCS5_PBKDF2_HMAC(password.data(), static_cast(password.size()), + reinterpret_cast(salt.data()), static_cast(salt.size()), + static_cast(iterations), EVP_sha256(), static_cast(out.size()), out.data()) != 1) { + throw std::runtime_error("libcrypto: PBKDF2 failed"); + } + return std::string(reinterpret_cast(out.data()), out.size()); + } + + std::string HexEncode(std::string_view bytes) { + constexpr std::string_view Digits = "0123456789abcdef"; + std::string out; + out.reserve(bytes.size() * 2); + for (const char c : bytes) { + const auto byte = static_cast(c); + out.push_back(Digits[byte >> 4]); + out.push_back(Digits[byte & 0x0F]); + } + return out; + } + + std::string Base64Encode(std::string_view bytes) { + std::string out; + out.reserve((bytes.size() + 2) / 3 * 4); + std::size_t i = 0; + for (; i + 3 <= bytes.size(); i += 3) { + const std::uint32_t triple = (static_cast(bytes[i]) << 16) | (static_cast(bytes[i + 1]) << 8) | static_cast(bytes[i + 2]); + out.push_back(Base64Alphabet[(triple >> 18) & 0x3F]); + out.push_back(Base64Alphabet[(triple >> 12) & 0x3F]); + out.push_back(Base64Alphabet[(triple >> 6) & 0x3F]); + out.push_back(Base64Alphabet[triple & 0x3F]); + } + const std::size_t left = bytes.size() - i; + if (left == 1) { + const std::uint32_t one = static_cast(bytes[i]) << 16; + out.push_back(Base64Alphabet[(one >> 18) & 0x3F]); + out.push_back(Base64Alphabet[(one >> 12) & 0x3F]); + out += "=="; + } else if (left == 2) { + const std::uint32_t two = (static_cast(bytes[i]) << 16) | (static_cast(bytes[i + 1]) << 8); + out.push_back(Base64Alphabet[(two >> 18) & 0x3F]); + out.push_back(Base64Alphabet[(two >> 12) & 0x3F]); + out.push_back(Base64Alphabet[(two >> 6) & 0x3F]); + out.push_back('='); + } + return out; + } + + std::string Base64Decode(std::string_view text) { + // binascii.a2b_base64 in its default, non-strict mode, which is what + // b64decode runs: a character outside the alphabet is skipped, a '=' + // only counts once two characters of a quad are in, and a complete + // padding sequence ends the data — whatever follows it is ignored. + // What a quad has left at the end decides the error. + std::string out; + std::int32_t quadPosition = 0; + std::uint8_t leftChar = 0; + std::int32_t pads = 0; + for (const char c : text) { + if (c == '=') { + if (quadPosition >= 2 && quadPosition + ++pads >= 4) return out; + continue; + } + const std::int32_t value = Base64Value(c); + if (value < 0) continue; + const auto bits = static_cast(value); + pads = 0; + switch (quadPosition) { + case 0: + quadPosition = 1; + leftChar = bits; + break; + case 1: + quadPosition = 2; + out.push_back(static_cast(static_cast(leftChar << 2) | (bits >> 4))); + leftChar = bits & 0x0F; + break; + case 2: + quadPosition = 3; + out.push_back(static_cast(static_cast(leftChar << 4) | (bits >> 2))); + leftChar = bits & 0x03; + break; + default: + quadPosition = 0; + out.push_back(static_cast(static_cast(leftChar << 6) | bits)); + leftChar = 0; + break; + } + } + if (quadPosition == 1) { + throw std::invalid_argument(std::format("Invalid base64-encoded string: number of data characters ({}) cannot be 1 more than a multiple of 4", out.size() / 3 * 4 + 1)); + } + if (quadPosition != 0) throw std::invalid_argument("Incorrect padding"); + return out; + } + + std::string Base32Decode(std::string_view text) { + // base64.b32decode, in its order: the digits are checked while the + // quanta are decoded, the length and the padding after. + std::string_view data = text; + while (!data.empty() && data.back() == '=') data.remove_suffix(1); + const std::size_t padChars = text.size() - data.size(); + std::string out; + std::uint64_t accumulator = 0; + for (std::size_t start = 0; start < data.size(); start += 8) { + accumulator = 0; + for (const char c : data.substr(start, 8)) { + const std::size_t value = Base32Alphabet.find(c); + if (value == std::string_view::npos) throw std::invalid_argument("Non-base32 digit found"); + accumulator = (accumulator << 5) | value; + } + for (std::int32_t shift = 32; shift >= 0; shift -= 8) { + out.push_back(static_cast((accumulator >> shift) & 0xFF)); + } + } + if (text.size() % 8 != 0 || (padChars != 0 && padChars != 1 && padChars != 3 && padChars != 4 && padChars != 6)) { + throw std::invalid_argument("Incorrect padding"); + } + if (padChars != 0 && !out.empty()) { + // b32decode decoded the partial quantum as if it were 8 digits + // short of its padding: redo the last 5 bytes from the + // accumulator shifted by the padding, and keep what the data + // characters carried. + const std::uint64_t shifted = accumulator << (5 * padChars); + std::string last; + for (std::int32_t shift = 32; shift >= 0; shift -= 8) { + last.push_back(static_cast((shifted >> shift) & 0xFF)); + } + const std::size_t leftover = (43 - 5 * padChars) / 8; + out.resize(out.size() - 5); + out += last.substr(0, leftover); + } + return out; + } + + std::string RandomBytes(std::size_t count) { + std::string out(count, '\0'); + std::size_t filled = 0; + while (filled < count) { + const ssize_t got = getrandom(out.data() + filled, count - filled, 0); + if (got < 0) { + if (errno == EINTR) continue; + throw std::runtime_error(std::format("getrandom: {}", std::strerror(errno))); + } + filled += static_cast(got); + } + return out; + } + + std::string TokenHex(std::size_t count) { + return HexEncode(RandomBytes(count)); + } + + std::string Uuid4() { + std::string bytes = RandomBytes(16); + bytes[6] = static_cast((static_cast(bytes[6]) & 0x0F) | 0x40); + bytes[8] = static_cast((static_cast(bytes[8]) & 0x3F) | 0x80); + const std::string hex = HexEncode(bytes); + return std::format("{}-{}-{}-{}-{}", hex.substr(0, 8), hex.substr(8, 4), hex.substr(12, 4), hex.substr(16, 4), hex.substr(20)); + } + + bool ConstantTimeEqual(std::string_view a, std::string_view b) { + if (a.size() != b.size()) return false; + return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0; + } +} diff --git a/implementations/Crafter.Review-Driver.cpp b/implementations/Crafter.Review-Driver.cpp new file mode 100644 index 0000000..a9fae19 --- /dev/null +++ b/implementations/Crafter.Review-Driver.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module Crafter.Review:Driver_impl; +import :Driver; +import :Core; +import std; + +namespace Crafter::Review { + namespace { + constexpr std::string_view Usage = +R"(usage: crafter-review [--config FILE] {run,freeze,slots,config} ... + crafter-review {seats,inventory,sql,totp,init,doc,browser,stop} ... + crafter-review --version + +The pre-push review pipeline: adversarial lenses, one claude process per +inventory unit, against a frozen commit range.)"; + } + + std::int32_t Main(std::span args) { + const std::span rest = args.empty() ? args : args.subspan(1); + if (!rest.empty() && rest.front() == "--version") { + std::println("{}", DriverLine()); + return 0; + } + if (!rest.empty() && (rest.front() == "-h" || rest.front() == "--help")) { + std::println("{}", Usage); + return 0; + } + std::println(std::cerr, "{}", Usage); + return 3; + } +} diff --git a/implementations/Crafter.Review-Text.cpp b/implementations/Crafter.Review-Text.cpp new file mode 100644 index 0000000..37f5c39 --- /dev/null +++ b/implementations/Crafter.Review-Text.cpp @@ -0,0 +1,1061 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module; +#include +#include + +module Crafter.Review:Text_impl; +import :Text; +import :Unicode; +import :Json; +import std; + +namespace Crafter::Review::Dyn { + namespace { + template + bool InRuns(const Table& table, char32_t c) { + const auto found = std::ranges::upper_bound(table, c, {}, [](const Unicode::Run& run) { return run.first; }); + if (found == table.begin()) return false; + return c <= std::prev(found)->last; + } + + template + std::string_view Mapped(const Table& table, char32_t c) { + const auto found = std::ranges::lower_bound(table, c, {}, [](const Unicode::Mapping& mapping) { return mapping.from; }); + if (found == table.end() || found->from != c) return {}; + return found->to; + } + + void AppendUtf8(std::string& out, char32_t c) { + if (c < 0x80) { + out.push_back(static_cast(c)); + } else if (c < 0x800) { + out.push_back(static_cast(0xC0 | (c >> 6))); + out.push_back(static_cast(0x80 | (c & 0x3F))); + } else if (c < 0x10000) { + out.push_back(static_cast(0xE0 | (c >> 12))); + out.push_back(static_cast(0x80 | ((c >> 6) & 0x3F))); + out.push_back(static_cast(0x80 | (c & 0x3F))); + } else { + out.push_back(static_cast(0xF0 | (c >> 18))); + out.push_back(static_cast(0x80 | ((c >> 12) & 0x3F))); + out.push_back(static_cast(0x80 | ((c >> 6) & 0x3F))); + out.push_back(static_cast(0x80 | (c & 0x3F))); + } + } + + // One code point from `bytes` at `at`: the code point and the bytes + // it took, or U+FFFD and the length of the maximal subpart of an + // ill-formed sequence, which one U+FFFD replaces. + std::pair Next(std::string_view bytes, std::size_t at) { + const auto lead = static_cast(bytes[at]); + if (lead < 0x80) return {lead, 1}; + std::size_t need = 0; + char32_t value = 0; + std::uint8_t low = 0x80; + std::uint8_t high = 0xBF; + if (lead >= 0xC2 && lead <= 0xDF) { + need = 1; + value = lead & 0x1F; + } else if (lead >= 0xE0 && lead <= 0xEF) { + need = 2; + value = lead & 0x0F; + if (lead == 0xE0) low = 0xA0; + if (lead == 0xED) high = 0x9F; + } else if (lead >= 0xF0 && lead <= 0xF4) { + need = 3; + value = lead & 0x07; + if (lead == 0xF0) low = 0x90; + if (lead == 0xF4) high = 0x8F; + } else { + return {0xFFFD, 1}; + } + std::size_t used = 1; + for (std::size_t i = 0; i < need; ++i) { + if (at + used >= bytes.size()) return {0xFFFD, used}; + const auto next = static_cast(bytes[at + used]); + if (next < low || next > high) return {0xFFFD, used}; + value = (value << 6) | (next & 0x3F); + ++used; + low = 0x80; + high = 0xBF; + } + return {value, used}; + } + + bool IsAsciiDigit(char32_t c) { + return c >= '0' && c <= '9'; + } + + // ParseInt()'s and ParseFloat()'s first step: Unicode whitespace + // stripped, then every decimal digit made ASCII and every space a plain + // one. + std::optional AsciiNumber(std::string_view text) { + std::u32string decoded = Decode(Strip(text)); + std::string out; + for (const char32_t c : decoded) { + if (c < 0x80) { + out.push_back(static_cast(c)); + } else if (const std::int32_t digit = DecimalValue(c); digit >= 0) { + out.push_back(static_cast('0' + digit)); + } else if (IsSpace(c)) { + out.push_back(' '); + } else { + return std::nullopt; + } + } + return out; + } + + // A run of digits with single underscores between them, as ParseInt() + // and ParseFloat() take them; the digits alone. + std::optional Digits(std::string_view text) { + if (text.empty() || text.front() == '_' || text.back() == '_') return std::nullopt; + std::string digits; + char previous = '\0'; + for (const char c : text) { + if (c == '_') { + if (previous == '_') return std::nullopt; + } else if (c >= '0' && c <= '9') { + digits.push_back(c); + } else { + return std::nullopt; + } + previous = c; + } + return digits; + } + + std::string ToLowerAscii(std::string_view text) { + std::string out(text); + for (char& c : out) { + if (c >= 'A' && c <= 'Z') c = static_cast(c - 'A' + 'a'); + } + return out; + } + + // NormPath() on a string that is known to be a path. + std::vector PathComponents(std::string_view path) { + std::vector out; + for (const std::string& piece : Split(path, "/")) { + if (!piece.empty()) out.push_back(piece); + } + return out; + } + } + + std::u32string Decode(std::string_view utf8) { + std::u32string out; + out.reserve(utf8.size()); + for (std::size_t at = 0; at < utf8.size();) { + const auto [c, used] = Next(utf8, at); + out.push_back(c); + at += used; + } + return out; + } + + std::string Encode(std::u32string_view text) { + std::string out; + out.reserve(text.size()); + for (const char32_t c : text) AppendUtf8(out, c); + return out; + } + + std::string Encode(char32_t c) { + std::string out; + AppendUtf8(out, c); + return out; + } + + bool IsUtf8(std::string_view bytes) { + for (std::size_t at = 0; at < bytes.size();) { + const auto [c, used] = Next(bytes, at); + if (c == 0xFFFD) { + // A real U+FFFD in the input is three valid bytes. + if (used != 3 || bytes.substr(at, 3) != "\xEF\xBF\xBD") return false; + } + at += used; + } + return true; + } + + std::string DecodeReplace(std::string_view bytes) { + if (IsUtf8(bytes)) return std::string(bytes); + return Encode(Decode(bytes)); + } + + std::size_t Length(std::string_view text) { + std::size_t count = 0; + for (std::size_t at = 0; at < text.size(); ++count) at += Next(text, at).second; + return count; + } + + std::string Slice(std::string_view text, std::int64_t start, std::optional end) { + const std::u32string decoded = Decode(text); + const auto size = static_cast(decoded.size()); + auto clip = [size](std::int64_t index) { + if (index < 0) index += size; + return std::clamp(index, 0, size); + }; + const std::int64_t from = clip(start); + const std::int64_t to = end ? clip(*end) : size; + if (to <= from) return {}; + return Encode(std::u32string_view(decoded).substr(static_cast(from), static_cast(to - from))); + } + + bool IsSpace(char32_t c) { + return std::ranges::binary_search(Unicode::Space, c); + } + + bool IsLineBreak(char32_t c) { + return std::ranges::binary_search(Unicode::LineBreak, c); + } + + bool IsDecimal(char32_t c) { + return DecimalValue(c) >= 0; + } + + std::int32_t DecimalValue(char32_t c) { + if (IsAsciiDigit(c)) return static_cast(c - '0'); + const auto found = std::ranges::upper_bound(Unicode::Decimal, c, {}, [](const Unicode::Run& run) { return run.first; }); + if (found == Unicode::Decimal.begin()) return -1; + const Unicode::Run& run = *std::prev(found); + if (c > run.last) return -1; + return static_cast((c - run.first) % 10); + } + + bool IsPrintable(char32_t c) { + return InRuns(Unicode::Printable, c); + } + + char IgnoreCaseLetter(char32_t c) { + if (c >= 'a' && c <= 'z') return static_cast(c); + if (c >= 'A' && c <= 'Z') return static_cast(c - 'A' + 'a'); + const auto found = std::ranges::lower_bound(Unicode::IgnoreCase, c, {}, [](const Unicode::Fold& fold) { return fold.from; }); + if (found == Unicode::IgnoreCase.end() || found->from != c) return '\0'; + return found->letter; + } + + std::string LStrip(std::string_view text) { + std::size_t at = 0; + while (at < text.size()) { + const auto [c, used] = Next(text, at); + if (!IsSpace(c)) break; + at += used; + } + return std::string(text.substr(at)); + } + + std::string RStrip(std::string_view text) { + const std::u32string decoded = Decode(text); + std::size_t end = decoded.size(); + while (end > 0 && IsSpace(decoded[end - 1])) --end; + if (end == decoded.size()) return std::string(text); + return Encode(std::u32string_view(decoded).substr(0, end)); + } + + std::string Strip(std::string_view text) { + return RStrip(LStrip(text)); + } + + std::string LStrip(std::string_view text, std::string_view chars) { + const std::u32string set = Decode(chars); + const std::u32string decoded = Decode(text); + std::size_t start = 0; + while (start < decoded.size() && set.contains(decoded[start])) ++start; + return Encode(std::u32string_view(decoded).substr(start)); + } + + std::string RStrip(std::string_view text, std::string_view chars) { + const std::u32string set = Decode(chars); + const std::u32string decoded = Decode(text); + std::size_t end = decoded.size(); + while (end > 0 && set.contains(decoded[end - 1])) --end; + return Encode(std::u32string_view(decoded).substr(0, end)); + } + + std::string Strip(std::string_view text, std::string_view chars) { + return RStrip(LStrip(text, chars), chars); + } + + std::vector Split(std::string_view text) { + std::vector out; + std::string current; + bool inWord = false; + for (std::size_t at = 0; at < text.size();) { + const auto [c, used] = Next(text, at); + if (IsSpace(c)) { + if (inWord) out.push_back(std::move(current)); + current.clear(); + inWord = false; + } else { + current.append(text.substr(at, used)); + inWord = true; + } + at += used; + } + if (inWord) out.push_back(std::move(current)); + return out; + } + + std::vector Split(std::string_view text, std::string_view separator, std::int64_t maxSplit) { + if (separator.empty()) throw std::invalid_argument("empty separator"); + std::vector out; + std::size_t start = 0; + while (maxSplit < 0 || static_cast(out.size()) < maxSplit) { + const std::size_t found = text.find(separator, start); + if (found == std::string_view::npos) break; + out.emplace_back(text.substr(start, found - start)); + start = found + separator.size(); + } + out.emplace_back(text.substr(start)); + return out; + } + + std::vector RSplit(std::string_view text, std::string_view separator, std::int64_t maxSplit) { + if (separator.empty()) throw std::invalid_argument("empty separator"); + std::vector out; + std::size_t end = text.size(); + while (maxSplit < 0 || static_cast(out.size()) < maxSplit) { + if (end < separator.size()) break; + const std::size_t found = text.rfind(separator, end - separator.size()); + if (found == std::string_view::npos) break; + out.emplace_back(text.substr(found + separator.size(), end - found - separator.size())); + end = found; + } + out.emplace_back(text.substr(0, end)); + std::ranges::reverse(out); + return out; + } + + std::vector SplitLines(std::string_view text, bool keepEnds) { + std::vector out; + std::size_t start = 0; + std::size_t at = 0; + while (at < text.size()) { + const auto [c, used] = Next(text, at); + if (!IsLineBreak(c)) { + at += used; + continue; + } + std::size_t end = at + used; + if (c == '\r' && end < text.size() && text[end] == '\n') ++end; + out.emplace_back(text.substr(start, (keepEnds ? end : at) - start)); + start = end; + at = end; + } + if (start < text.size()) out.emplace_back(text.substr(start)); + return out; + } + + std::string Lower(std::string_view text) { + std::string out; + out.reserve(text.size()); + for (std::size_t at = 0; at < text.size();) { + const auto [c, used] = Next(text, at); + if (c < 0x80) { + out.push_back(static_cast(c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c)); + } else if (const std::string_view to = Mapped(Unicode::Lower, c); !to.empty()) { + out.append(to); + } else { + AppendUtf8(out, c); + } + at += used; + } + return out; + } + + std::string Upper(std::string_view text) { + std::string out; + out.reserve(text.size()); + for (std::size_t at = 0; at < text.size();) { + const auto [c, used] = Next(text, at); + if (c < 0x80) { + out.push_back(static_cast(c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c)); + } else if (const std::string_view to = Mapped(Unicode::Upper, c); !to.empty()) { + out.append(to); + } else { + AppendUtf8(out, c); + } + at += used; + } + return out; + } + + std::size_t Count(std::string_view text, std::string_view sub) { + if (sub.empty()) return Length(text) + 1; + std::size_t count = 0; + for (std::size_t at = text.find(sub); at != std::string_view::npos; at = text.find(sub, at + sub.size())) ++count; + return count; + } + + std::string Replace(std::string_view text, std::string_view from, std::string_view to) { + if (from.empty()) return std::string(text); + std::string out; + std::size_t start = 0; + for (std::size_t at = text.find(from); at != std::string_view::npos; at = text.find(from, start)) { + out.append(text.substr(start, at - start)); + out.append(to); + start = at + from.size(); + } + out.append(text.substr(start)); + return out; + } + + std::string Join(std::span parts, std::string_view separator) { + std::string out; + for (std::size_t i = 0; i < parts.size(); ++i) { + if (i > 0) out.append(separator); + out.append(parts[i]); + } + return out; + } + + std::string ReadText(const std::filesystem::path& path) { + std::ifstream in(path, std::ios::binary); + if (!in) throw std::runtime_error(std::format("[Errno 2] No such file or directory: {}", Repr(std::string_view(path.string())))); + const std::string bytes((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + for (std::size_t at = 0; at < bytes.size();) { + const auto [c, used] = Next(bytes, at); + if (c == 0xFFFD && (used != 3 || std::string_view(bytes).substr(at, 3) != "\xEF\xBF\xBD")) { + throw std::runtime_error(std::format("'utf-8' codec can't decode byte 0x{:02x} in position {}: invalid utf-8 ({})", + static_cast(bytes[at]), at, path.string())); + } + at += used; + } + return UniversalNewlines(bytes); + } + + std::string UniversalNewlines(std::string_view text) { + if (!text.contains('\r')) return std::string(text); + std::string out; + out.reserve(text.size()); + for (std::size_t i = 0; i < text.size(); ++i) { + if (text[i] == '\r') { + out.push_back('\n'); + if (i + 1 < text.size() && text[i + 1] == '\n') ++i; + } else { + out.push_back(text[i]); + } + } + return out; + } + + std::string FloatRepr(double value) { + if (std::isnan(value)) return "nan"; + if (std::isinf(value)) return value > 0 ? "inf" : "-inf"; + // The shortest round-trip digits, in to_chars' scientific form, then + // laid out: fixed from 1e-4 up to 1e16, with an exponent otherwise. + std::array buffer {}; + const auto [end, error] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value, std::chars_format::scientific); + const std::string_view scientific(buffer.data(), end); + const bool negative = scientific.starts_with('-'); + const std::string_view body = negative ? scientific.substr(1) : scientific; + const std::size_t e = body.find('e'); + std::string digits; + for (const char c : body.substr(0, e)) { + if (c != '.') digits.push_back(c); + } + std::int32_t exponent = 0; + std::from_chars(body.data() + e + 1 + (body[e + 1] == '+' ? 1 : 0), body.data() + body.size(), exponent); + while (digits.size() > 1 && digits.back() == '0') digits.pop_back(); + const std::int32_t decimalPoint = exponent + 1; + std::string out = negative ? "-" : ""; + if (decimalPoint <= -4 || decimalPoint > 16) { + out += digits.substr(0, 1); + if (digits.size() > 1) out += std::format(".{}", digits.substr(1)); + const std::int32_t shown = decimalPoint - 1; + out += std::format("e{}{:02}", shown < 0 ? '-' : '+', shown < 0 ? -shown : shown); + return out; + } + if (decimalPoint <= 0) { + out += std::format("0.{}{}", std::string(static_cast(-decimalPoint), '0'), digits); + } else if (static_cast(decimalPoint) >= digits.size()) { + out += std::format("{}{}.0", digits, std::string(static_cast(decimalPoint) - digits.size(), '0')); + } else { + out += std::format("{}.{}", digits.substr(0, static_cast(decimalPoint)), digits.substr(static_cast(decimalPoint))); + } + return out; + } + + double Round(double value, std::int32_t digits) { + if (!std::isfinite(value)) return value; + // to_chars with a precision rounds the exact binary value correctly, + // half to even on an exact tie. + std::array buffer {}; + const auto [end, error] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value, std::chars_format::fixed, digits); + if (error != std::errc()) return value; + double rounded = value; + std::from_chars(buffer.data(), end, rounded); + return rounded; + } + + std::int64_t RoundToInt(double value) { + return static_cast(std::nearbyint(value)); + } + + std::optional ParseInt(std::string_view text) { + const std::optional ascii = AsciiNumber(text); + if (!ascii || ascii->empty()) return std::nullopt; + std::string_view body = *ascii; + bool negative = false; + if (body.front() == '+' || body.front() == '-') { + negative = body.front() == '-'; + body.remove_prefix(1); + } + const std::optional digits = Digits(body); + if (!digits) return std::nullopt; + std::int64_t value = 0; + for (const char c : *digits) { + if (value > (std::numeric_limits::max() - (c - '0')) / 10) return std::nullopt; + value = value * 10 + (c - '0'); + } + return negative ? -value : value; + } + + std::optional ParseFloat(std::string_view text) { + const std::optional ascii = AsciiNumber(text); + if (!ascii || ascii->empty()) return std::nullopt; + std::string_view body = *ascii; + bool negative = false; + if (body.front() == '+' || body.front() == '-') { + negative = body.front() == '-'; + body.remove_prefix(1); + } + const std::string word = ToLowerAscii(body); + if (word == "inf" || word == "infinity") return negative ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + if (word == "nan") return std::numeric_limits::quiet_NaN(); + // digits [. digits] [e [sign] digits], one of the two digit runs + // present, underscores only between digits. + const std::size_t exponentAt = body.find_first_of("eE"); + const std::string_view mantissa = body.substr(0, exponentAt); + const std::size_t dot = mantissa.find('.'); + const std::string_view whole = mantissa.substr(0, dot); + const std::string_view fraction = dot == std::string_view::npos ? std::string_view() : mantissa.substr(dot + 1); + if (whole.empty() && fraction.empty()) return std::nullopt; + std::string normal = negative ? "-" : ""; + if (!whole.empty()) { + const std::optional digits = Digits(whole); + if (!digits) return std::nullopt; + normal += *digits; + } + if (dot != std::string_view::npos) { + normal += '.'; + if (!fraction.empty()) { + const std::optional digits = Digits(fraction); + if (!digits) return std::nullopt; + normal += *digits; + } + } + if (exponentAt != std::string_view::npos) { + std::string_view exponent = body.substr(exponentAt + 1); + normal += 'e'; + if (!exponent.empty() && (exponent.front() == '+' || exponent.front() == '-')) { + normal += exponent.front(); + exponent.remove_prefix(1); + } + const std::optional digits = Digits(exponent); + if (!digits) return std::nullopt; + normal += *digits; + } + // strtod, not from_chars: an exponent out of range is inf or 0.0, not + // an error. The process runs in the "C" locale. + return std::strtod(normal.c_str(), nullptr); + } + + std::string IntError(std::string_view text) { + return std::format("invalid literal for int() with base 10: {}", Repr(text)); + } + + std::string FloatError(std::string_view text) { + return std::format("could not convert string to float: {}", Repr(text)); + } + + std::string Repr(std::string_view text) { + const char quote = text.contains('\'') && !text.contains('"') ? '"' : '\''; + std::string out(1, quote); + for (std::size_t at = 0; at < text.size();) { + const auto [c, used] = Next(text, at); + if (c == static_cast(quote) || c == '\\') { + out.push_back('\\'); + out.push_back(static_cast(c)); + } else if (c == '\t') { + out += "\\t"; + } else if (c == '\n') { + out += "\\n"; + } else if (c == '\r') { + out += "\\r"; + } else if (c < 0x20 || c == 0x7F) { + out += std::format("\\x{:02x}", static_cast(c)); + } else if (c < 0x7F) { + out.push_back(static_cast(c)); + } else if (IsPrintable(c)) { + AppendUtf8(out, c); + } else if (c <= 0xFF) { + out += std::format("\\x{:02x}", static_cast(c)); + } else if (c <= 0xFFFF) { + out += std::format("\\u{:04x}", static_cast(c)); + } else { + out += std::format("\\U{:08x}", static_cast(c)); + } + at += used; + } + out.push_back(quote); + return out; + } + + std::string ReprValue(const Json& value) { + if (value.is_null()) return "None"; + if (value.is_boolean()) return value.get() ? "True" : "False"; + if (value.is_number_float()) return FloatRepr(value.get()); + if (value.is_number_unsigned()) return std::to_string(value.get()); + if (value.is_number_integer()) return std::to_string(value.get()); + if (value.is_string()) return Repr(std::string_view(value.get_ref())); + if (value.is_array()) { + std::string out = "["; + bool first = true; + for (const Json& item : value) { + if (!first) out += ", "; + first = false; + out += ReprValue(item); + } + return std::format("{}]", out); + } + std::string out = "{"; + bool first = true; + for (auto it = value.begin(); it != value.end(); ++it) { + if (!first) out += ", "; + first = false; + out += std::format("{}: {}", Repr(std::string_view(it.key())), ReprValue(it.value())); + } + return std::format("{}}}", out); + } + + namespace { + // One element of a translated shell pattern. + struct GlobToken { + enum class Kind { Star, Any, Literal, Class, Never }; + Kind kind; + char32_t literal = 0; + bool negate = false; + std::vector> ranges; + }; + + // A bracket expression, from the text between [ and ] — escapes and + // all — read as a regular expression reads a character class. + GlobToken ClassFrom(const std::u32string& content) { + GlobToken token { .kind = GlobToken::Kind::Class }; + std::u32string_view rest(content); + if (rest.starts_with(U'!')) { + token.negate = true; + rest.remove_prefix(1); + } + auto item = [&rest]() { + char32_t c = rest.front(); + rest.remove_prefix(1); + if (c == U'\\' && !rest.empty()) { + c = rest.front(); + rest.remove_prefix(1); + } + return c; + }; + while (!rest.empty()) { + const char32_t first = item(); + if (rest.size() >= 2 && rest.front() == U'-') { + rest.remove_prefix(1); + const char32_t last = item(); + token.ranges.emplace_back(first, last); + } else { + token.ranges.emplace_back(first, first); + } + } + return token; + } + + std::u32string EscapeClassChunk(std::u32string_view chunk, bool hyphens) { + std::u32string out; + for (const char32_t c : chunk) { + if (c == U'\\' || (hyphens && c == U'-')) out.push_back(U'\\'); + out.push_back(c); + } + return out; + } + + // A shell pattern, translated token by token. + std::vector Translate(const std::u32string& pattern) { + std::vector tokens; + const std::size_t n = pattern.size(); + std::size_t i = 0; + while (i < n) { + const char32_t c = pattern[i++]; + if (c == U'*') { + if (tokens.empty() || tokens.back().kind != GlobToken::Kind::Star) tokens.push_back({ .kind = GlobToken::Kind::Star }); + while (i < n && pattern[i] == U'*') ++i; + } else if (c == U'?') { + tokens.push_back({ .kind = GlobToken::Kind::Any }); + } else if (c == U'[') { + std::size_t j = i; + if (j < n && pattern[j] == U'!') ++j; + if (j < n && pattern[j] == U']') ++j; + while (j < n && pattern[j] != U']') ++j; + if (j >= n) { + tokens.push_back({ .kind = GlobToken::Kind::Literal, .literal = U'[' }); + continue; + } + std::u32string stuff = pattern.substr(i, j - i); + if (!stuff.contains(U'-')) { + stuff = EscapeClassChunk(stuff, false); + } else { + std::vector chunks; + std::size_t start = i; + std::size_t k = pattern[i] == U'!' ? i + 2 : i + 1; + while (true) { + const std::size_t found = k < j ? pattern.find(U'-', k) : std::u32string::npos; + if (found == std::u32string::npos || found >= j) break; + chunks.push_back(pattern.substr(start, found - start)); + start = found + 1; + k = found + 3; + } + const std::u32string chunk = pattern.substr(start, j - start); + if (!chunk.empty()) { + chunks.push_back(chunk); + } else { + chunks.back() += U'-'; + } + // A reversed range is dropped: it matches nothing. + for (std::size_t m = chunks.size() - 1; m > 0; --m) { + if (chunks[m - 1].back() > chunks[m].front()) { + chunks[m - 1] = chunks[m - 1].substr(0, chunks[m - 1].size() - 1) + chunks[m].substr(1); + chunks.erase(chunks.begin() + static_cast(m)); + } + } + stuff.clear(); + for (std::size_t m = 0; m < chunks.size(); ++m) { + if (m > 0) stuff += U'-'; + stuff += EscapeClassChunk(chunks[m], true); + } + } + i = j + 1; + if (stuff.empty()) { + tokens.push_back({ .kind = GlobToken::Kind::Never }); + } else if (stuff == U"!") { + tokens.push_back({ .kind = GlobToken::Kind::Any }); + } else { + tokens.push_back(ClassFrom(stuff)); + } + } else { + tokens.push_back({ .kind = GlobToken::Kind::Literal, .literal = c }); + } + } + return tokens; + } + + bool Matches(const GlobToken& token, char32_t c) { + switch (token.kind) { + case GlobToken::Kind::Any: + return true; + case GlobToken::Kind::Literal: + return token.literal == c; + case GlobToken::Kind::Class: { + const bool in = std::ranges::any_of(token.ranges, [c](const auto& range) { return c >= range.first && c <= range.second; }); + return in != token.negate; + } + default: + return false; + } + } + } + + bool FnMatch(std::string_view name, std::string_view pattern) { + const std::vector tokens = Translate(Decode(pattern)); + const std::u32string text = Decode(name); + // The classic two-pointer match: every token but * consumes exactly + // one character, so backtracking only ever returns to the last star. + std::size_t t = 0; + std::size_t s = 0; + std::optional starToken; + std::size_t starText = 0; + while (s < text.size()) { + if (t < tokens.size() && tokens[t].kind == GlobToken::Kind::Star) { + starToken = t++; + starText = s; + } else if (t < tokens.size() && Matches(tokens[t], text[s])) { + ++t; + ++s; + } else if (starToken) { + t = *starToken + 1; + s = ++starText; + } else { + return false; + } + } + while (t < tokens.size() && tokens[t].kind == GlobToken::Kind::Star) ++t; + return t == tokens.size(); + } + + std::optional SplitUrl::Hostname() const { + std::string hostinfo = netloc.substr(netloc.rfind('@') == std::string::npos ? 0 : netloc.rfind('@') + 1); + std::string hostname; + if (const std::size_t open = hostinfo.find('['); open != std::string::npos) { + const std::string bracketed = hostinfo.substr(open + 1); + hostname = bracketed.substr(0, bracketed.find(']')); + } else { + hostname = hostinfo.substr(0, hostinfo.find(':')); + } + if (hostname.empty()) return std::nullopt; + // A scoped IPv6 address's zone is left as written. + const std::size_t percent = hostname.find('%'); + if (percent == std::string::npos) return Lower(hostname); + return Lower(hostname.substr(0, percent)) + hostname.substr(percent); + } + + std::optional SplitUrl::Username() const { + const std::size_t at = netloc.rfind('@'); + if (at == std::string::npos) return std::nullopt; + const std::string userinfo = netloc.substr(0, at); + return userinfo.substr(0, userinfo.find(':')); + } + + std::optional SplitUrl::Password() const { + const std::size_t at = netloc.rfind('@'); + if (at == std::string::npos) return std::nullopt; + const std::string userinfo = netloc.substr(0, at); + const std::size_t colon = userinfo.find(':'); + if (colon == std::string::npos) return std::nullopt; + return userinfo.substr(colon + 1); + } + + std::optional SplitUrl::Port() const { + std::string hostinfo = netloc.substr(netloc.rfind('@') == std::string::npos ? 0 : netloc.rfind('@') + 1); + std::string port; + if (const std::size_t open = hostinfo.find('['); open != std::string::npos) { + const std::string bracketed = hostinfo.substr(open + 1); + const std::size_t close = bracketed.find(']'); + if (close != std::string::npos) { + const std::string after = bracketed.substr(close + 1); + if (const std::size_t colon = after.find(':'); colon != std::string::npos) port = after.substr(colon + 1); + } + } else if (const std::size_t colon = hostinfo.find(':'); colon != std::string::npos) { + port = hostinfo.substr(colon + 1); + } + if (port.empty()) return std::nullopt; + if (!std::ranges::all_of(port, [](char c) { return c >= '0' && c <= '9'; })) { + throw std::invalid_argument(std::format("Port could not be cast to integer value as {}", Repr(port))); + } + std::int64_t value = 0; + for (const char c : port) { + value = value * 10 + (c - '0'); + if (value > 65535) break; + } + if (value > 65535) throw std::invalid_argument("Port out of range 0-65535"); + return static_cast(value); + } + + namespace { + // ipaddress.ip_address's verdict on what sits between brackets, as + // far as urlsplit needs one: an IPv4 address is refused, anything that + // is not an IPv6 address is invalid. + void CheckBracketedHost(const std::string& host) { + if (host.starts_with('v')) { + // \Av[a-fA-F0-9]+\..+\z + std::size_t i = 1; + while (i < host.size() && std::isxdigit(static_cast(host[i]))) ++i; + if (i == 1 || i >= host.size() || host[i] != '.' || i + 1 >= host.size()) { + throw std::invalid_argument("IPvFuture address is invalid"); + } + return; + } + std::array address {}; + std::string withoutZone = host.substr(0, host.find('%')); + if (inet_pton(AF_INET, withoutZone.c_str(), address.data()) == 1) { + throw std::invalid_argument("An IPv4 address cannot be in brackets"); + } + if (inet_pton(AF_INET6, withoutZone.c_str(), address.data()) != 1) { + throw std::invalid_argument(std::format("{} does not appear to be an IPv4 or IPv6 address", Repr(host))); + } + } + } + + SplitUrl UrlSplit(std::string_view input) { + std::string url; + for (const char c : input) { + if (c != '\t' && c != '\r' && c != '\n') url.push_back(c); + } + std::size_t lead = 0; + while (lead < url.size() && static_cast(url[lead]) <= 0x20) ++lead; + url.erase(0, lead); + + SplitUrl parts; + const std::size_t colon = url.find(':'); + auto isAlpha = [](char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }; + auto isSchemeChar = [&](char c) { return isAlpha(c) || (c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.'; }; + if (colon != std::string::npos && colon > 0 && isAlpha(url[0]) + && std::ranges::all_of(std::string_view(url).substr(0, colon), isSchemeChar)) { + parts.scheme = ToLowerAscii(std::string_view(url).substr(0, colon)); + url.erase(0, colon + 1); + } + if (url.starts_with("//")) { + std::size_t end = url.find_first_of("/?#", 2); + if (end == std::string::npos) end = url.size(); + parts.netloc = url.substr(2, end - 2); + url.erase(0, end); + const bool open = parts.netloc.contains('['); + const bool close = parts.netloc.contains(']'); + if (open != close) throw std::invalid_argument("Invalid IPv6 URL"); + if (open && close) { + // _check_bracketed_netloc: the brackets may sit in the user + // part, and then the host is checked as an address all the same. + const std::string hostAndPort = parts.netloc.substr(parts.netloc.rfind('@') == std::string::npos ? 0 : parts.netloc.rfind('@') + 1); + const std::size_t bracket = hostAndPort.find('['); + if (bracket == std::string::npos) { + CheckBracketedHost(hostAndPort.substr(0, hostAndPort.find(':'))); + } else { + if (bracket != 0) throw std::invalid_argument("Invalid IPv6 URL"); + const std::string bracketed = hostAndPort.substr(1); + const std::size_t closing = bracketed.find(']'); + const std::string after = closing == std::string::npos ? std::string() : bracketed.substr(closing + 1); + if (!after.empty() && !after.starts_with(':')) throw std::invalid_argument("Invalid IPv6 URL"); + CheckBracketedHost(bracketed.substr(0, closing)); + } + } + } + if (const std::size_t hash = url.find('#'); hash != std::string::npos) { + parts.fragment = url.substr(hash + 1); + url.erase(hash); + } + if (const std::size_t question = url.find('?'); question != std::string::npos) { + parts.query = url.substr(question + 1); + url.erase(question); + } + parts.path = std::move(url); + return parts; + } + + std::string Unquote(std::string_view text) { + if (!text.contains('%')) return std::string(text); + // Percent sequences become bytes; runs of bytes are decoded as UTF-8 + // with bad bytes replaced; everything else is kept as it is. + std::string out; + std::string bytes; + auto hexValue = [](char c) -> std::int32_t { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + }; + for (std::size_t i = 0; i < text.size();) { + if (text[i] == '%' && i + 2 < text.size() && hexValue(text[i + 1]) >= 0 && hexValue(text[i + 2]) >= 0) { + bytes.push_back(static_cast(hexValue(text[i + 1]) * 16 + hexValue(text[i + 2]))); + i += 3; + continue; + } + if (!bytes.empty()) { + out += DecodeReplace(bytes); + bytes.clear(); + } + out.push_back(text[i]); + ++i; + } + if (!bytes.empty()) out += DecodeReplace(bytes); + return out; + } + + NaturalKey Natural(std::string_view name) { + NaturalKey key; + std::string text; + std::int64_t number = 0; + bool inDigits = false; + for (std::size_t at = 0; at < name.size();) { + const auto [c, used] = Next(name, at); + const std::int32_t digit = DecimalValue(c); + if (digit >= 0) { + if (!inDigits) { + key.parts.emplace_back(text); + text.clear(); + number = 0; + inDigits = true; + } + number = number * 10 + digit; + } else { + if (inDigits) { + key.parts.emplace_back(number); + inDigits = false; + } + text.append(name.substr(at, used)); + } + at += used; + } + if (inDigits) { + key.parts.emplace_back(number); + text.clear(); + } + key.parts.emplace_back(text); + return key; + } + + std::string NormPath(std::string_view path) { + if (path.empty()) return "."; + std::size_t leading = 0; + while (leading < path.size() && path[leading] == '/') ++leading; + // POSIX gives exactly two leading slashes a meaning of their own. + const std::string prefix = leading == 0 ? "" : (leading == 2 ? "//" : "/"); + std::vector kept; + for (const std::string& component : PathComponents(path)) { + if (component == ".") continue; + if (component == "..") { + if (!kept.empty() && kept.back() != "..") { + kept.pop_back(); + } else if (prefix.empty()) { + kept.push_back(component); + } + continue; + } + kept.push_back(component); + } + std::string out = prefix + Join(kept, "/"); + return out.empty() ? std::string(".") : out; + } + + std::string AbsPath(std::string_view path) { + if (path.starts_with('/')) return NormPath(path); + return NormPath(JoinPath(std::filesystem::current_path().string(), path)); + } + + std::string RelPath(std::string_view path, std::string_view start) { + if (path.empty()) throw std::invalid_argument("no path specified"); + const std::vector from = PathComponents(AbsPath(start.empty() ? "." : start)); + const std::vector to = PathComponents(AbsPath(path)); + std::size_t common = 0; + while (common < from.size() && common < to.size() && from[common] == to[common]) ++common; + std::vector relative(from.size() - common, ".."); + relative.insert(relative.end(), to.begin() + static_cast(common), to.end()); + if (relative.empty()) return "."; + return Join(relative, "/"); + } + + std::string BaseName(std::string_view path) { + return std::string(path.substr(path.rfind('/') == std::string_view::npos ? 0 : path.rfind('/') + 1)); + } + + std::string DirName(std::string_view path) { + const std::size_t slash = path.rfind('/'); + if (slash == std::string_view::npos) return ""; + std::string head(path.substr(0, slash + 1)); + if (!std::ranges::all_of(head, [](char c) { return c == '/'; })) { + while (!head.empty() && head.back() == '/') head.pop_back(); + } + return head; + } + + std::string JoinPath(std::string_view a, std::string_view b) { + if (b.starts_with('/')) return std::string(b); + if (a.empty() || a.ends_with('/')) return std::string(a) + std::string(b); + return std::format("{}/{}", std::string(a), std::string(b)); + } + + std::string ErrnoText(std::int32_t code) { + return std::format("[Errno {}] {}", code, std::strerror(code)); + } +} diff --git a/implementations/main.cpp b/implementations/main.cpp new file mode 100644 index 0000000..95bfbf3 --- /dev/null +++ b/implementations/main.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +import std; +import Crafter.Review; + +int main(int argc, char** argv) { + const std::vector args(argv, argv + argc); + return Crafter::Review::Main(args); +} diff --git a/interfaces/Crafter.Review-Core.cppm b/interfaces/Crafter.Review-Core.cppm new file mode 100644 index 0000000..b2299c6 --- /dev/null +++ b/interfaces/Crafter.Review-Core.cppm @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +export module Crafter.Review:Core; +import std; + +export namespace Crafter::Review { + // The release this binary was built from, as project.cpp stamped it: + // CRAFTER_REVIEW_VERSION, else `git describe`, else 0.0.0-dev. + std::string_view Version(); + + // Bumped only when clean results stored under the earlier number must not + // be reused: a text change a lens reads that changes what a clean pass + // proves. It is one of the inputs of every reuse key, so a bump retires + // every stored result at once. A change that leaves what a lens reads the + // same — a layout change, a reworded log line — does not bump it. + constexpr std::int32_t PromptVersion = 1; + + // `crafter-review (prompt v)`: the dump's `driver` field, + // the first log line of a run and `--version`. + std::string DriverLine(); +} diff --git a/interfaces/Crafter.Review-Crypto.cppm b/interfaces/Crafter.Review-Crypto.cppm new file mode 100644 index 0000000..18d7cae --- /dev/null +++ b/interfaces/Crafter.Review-Crypto.cppm @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +export module Crafter.Review:Crypto; +import std; + +// Byte strings are std::string throughout: a digest, a key or a nonce is a +// run of bytes, hex or base64 is text, and both travel through the same APIs +// as the text around them. OpenSSL's libcrypto does the hashing; base64 and +// base32 are written out here because what they refuse, and with which +// message, is pinned. +export namespace Crafter::Review { + std::string Sha256(std::string_view data); + std::string Sha1(std::string_view data); + std::string Md5(std::string_view data); + // Lowercase hex. + std::string Sha256Hex(std::string_view data); + std::string Md5Hex(std::string_view data); + + std::string HmacSha256(std::string_view key, std::string_view data); + std::string HmacSha1(std::string_view key, std::string_view data); + // PBKDF2-HMAC-SHA256 with a 32-byte result: SCRAM-SHA-256's Hi(). + std::string Pbkdf2Sha256(std::string_view password, std::string_view salt, std::uint32_t iterations); + + // Lowercase, two digits a byte. + std::string HexEncode(std::string_view bytes); + // Standard alphabet, padded with '=': base64.b64encode. + std::string Base64Encode(std::string_view bytes); + // base64.b64decode(text) without validate=True: characters outside the + // alphabet are dropped first, then bad padding throws + // std::invalid_argument, as it raises binascii.Error. + std::string Base64Decode(std::string_view text); + // base64.b32decode(text): the RFC 4648 alphabet in upper case, the length + // a multiple of 8 and the padding one of the five legal amounts; anything + // else throws std::invalid_argument with binascii's message. + std::string Base32Decode(std::string_view text); + + // From the kernel's CSPRNG (getrandom): secrets.token_bytes. + std::string RandomBytes(std::size_t count); + // secrets.token_hex(count): 2 * count lowercase hex digits. + std::string TokenHex(std::size_t count); + // A random (version 4) UUID in its 8-4-4-4-12 form: what `claude + // --session-id` takes. + std::string Uuid4(); + + // hmac.compare_digest: the time taken does not depend on where the two + // differ. + bool ConstantTimeEqual(std::string_view a, std::string_view b); +} diff --git a/interfaces/Crafter.Review-Driver.cppm b/interfaces/Crafter.Review-Driver.cppm new file mode 100644 index 0000000..37a6f69 --- /dev/null +++ b/interfaces/Crafter.Review-Driver.cppm @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +export module Crafter.Review:Driver; +import std; + +export namespace Crafter::Review { + // The command line: `crafter-review ...`, argv[0] first. + // Returns the process's exit code. + std::int32_t Main(std::span args); +} diff --git a/interfaces/Crafter.Review-Json.cppm b/interfaces/Crafter.Review-Json.cppm new file mode 100644 index 0000000..47cf9ec --- /dev/null +++ b/interfaces/Crafter.Review-Json.cppm @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +module; +#include "../lib/json.hpp" +export module Crafter.Review:Json; +import std; + +export namespace Crafter::Review { + // nlohmann/json, vendored unmodified at lib/json.hpp. ordered_json keeps an + // object's keys in the order they were read or inserted, and a duplicate + // key keeps its first position and its last value. That is what lets a + // driver rewrite the files every driver on the machine shares — the seat + // board, the ledger, unit-state.json — with every key in place, the ones + // it does not know about included. + using Json = nlohmann::ordered_json; + + // Indented by one space per level, one key or item per line, \uXXXX + // escapes for everything outside ASCII: every file this pipeline writes. A + // string that is not valid UTF-8 has its bad bytes replaced rather than + // throwing in the middle of a write. + // + // A float from 1e15 up to 1e16 prints with an exponent (AGENTS.md). + // Readers parse it either way, and none of the files depends on it. + inline std::string DumpFile(const Json& value) { + return value.dump(1, ' ', true, Json::error_handler_t::replace); + } + + // Keys in code-point order, no whitespace, ASCII only. The + // structured-output schema goes to claude in this form and its bytes are + // part of the prompt cache, so they must not move by a byte (the goldens + // hold them); dedupe keys use it too. + inline std::string DumpCompactSorted(const Json& value) { + // Converted to nlohmann::json, which keeps objects in a std::map: that + // sorts the keys by byte, and byte order is code-point order in UTF-8. + const nlohmann::json sorted(value); + return sorted.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace); + } + + // On one line, with ", " and ": " between items: where a reader matches + // on the bytes, such as the first line of an opencode output, `{"type": + // "review_gates_meta", …`, which salvage looks for. + inline std::string DumpSpaced(const Json& value) { + if (value.is_object()) { + std::string out = "{"; + bool first = true; + for (const auto& [key, child] : value.items()) { + if (!first) out += ", "; + first = false; + out += Json(key).dump(-1, ' ', true, Json::error_handler_t::replace); + out += ": "; + out += DumpSpaced(child); + } + return std::format("{}}}", out); + } + if (value.is_array()) { + std::string out = "["; + bool first = true; + for (const Json& child : value) { + if (!first) out += ", "; + first = false; + out += DumpSpaced(child); + } + return std::format("{}]", out); + } + return value.dump(-1, ' ', true, Json::error_handler_t::replace); + } +} diff --git a/interfaces/Crafter.Review-Text.cppm b/interfaces/Crafter.Review-Text.cppm new file mode 100644 index 0000000..dc50c54 --- /dev/null +++ b/interfaces/Crafter.Review-Text.cppm @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +export module Crafter.Review:Text; +import std; +import :Json; + +// The text, number, URL and path semantics every other partition relies on. +// Where a result reaches a prompt, a reuse key, a shared file or an exit code +// it is decided by these exact rules — Unicode whitespace in Strip(), U+2028 +// in SplitLines(), code points in Length(), half-even Round(), FnMatch()'s +// character classes — never by the nearest C++ equivalent. tests/Text checks +// every function against the reference vectors in tests/Text/fixture. +// +// Text is UTF-8 in std::string. Lengths and indices are in code points. +// +// Lower() maps Greek capital sigma to σ everywhere: it never writes the +// final form ς. +export namespace Crafter::Review::Dyn { + // ---- UTF-8 ------------------------------------------------------------- + // Each maximal subpart of an ill-formed sequence becomes one U+FFFD, the + // Unicode Standard's recommended practice. + std::u32string Decode(std::string_view utf8); + std::string Encode(std::u32string_view text); + std::string Encode(char32_t c); + // Well-formed UTF-8: no overlong forms, no surrogates, nothing above + // U+10FFFF. + bool IsUtf8(std::string_view bytes); + // bytes.decode('utf-8', 'replace'). + std::string DecodeReplace(std::string_view bytes); + // len() + std::size_t Length(std::string_view text); + // The code points from start to end: a negative index counts from the + // end, and both are clipped to the text. + std::string Slice(std::string_view text, std::int64_t start, std::optional end = std::nullopt); + + // ---- characters ---------------------------------------------------------- + bool IsSpace(char32_t c); + bool IsLineBreak(char32_t c); + bool IsDecimal(char32_t c); + // The digit's value, -1 for a character that is not a decimal digit. + std::int32_t DecimalValue(char32_t c); + bool IsPrintable(char32_t c); + // The lowercase ASCII letter `c` matches case-insensitively, or '\0': + // ASCII letters, and the four code points outside ASCII that fold onto + // one (İ ı → i, ſ → s, K → k). + char IgnoreCaseLetter(char32_t c); + + // ---- str methods ---------------------------------------------------------- + // strip()/lstrip()/rstrip(): Unicode whitespace. + std::string Strip(std::string_view text); + std::string LStrip(std::string_view text); + std::string RStrip(std::string_view text); + // strip(chars): the code points of `chars`. + std::string Strip(std::string_view text, std::string_view chars); + std::string LStrip(std::string_view text, std::string_view chars); + std::string RStrip(std::string_view text, std::string_view chars); + // split(): runs of Unicode whitespace, no empty pieces. + std::vector Split(std::string_view text); + // split(sep, maxsplit) / rsplit(sep, maxsplit): every piece, empty ones + // included; a negative maxSplit splits at every separator. + std::vector Split(std::string_view text, std::string_view separator, std::int64_t maxSplit = -1); + std::vector RSplit(std::string_view text, std::string_view separator, std::int64_t maxSplit = -1); + // splitlines(keepends): \n, \r, \r\n, \v, \f, \x1c-\x1e, \x85, U+2028, + // U+2029; no empty piece after a final line break. + std::vector SplitLines(std::string_view text, bool keepEnds = false); + std::string Lower(std::string_view text); + std::string Upper(std::string_view text); + // count(sub): non-overlapping; an empty `sub` counts len() + 1. + std::size_t Count(std::string_view text, std::string_view sub); + std::string Replace(std::string_view text, std::string_view from, std::string_view to); + std::string Join(std::span parts, std::string_view separator); + + // ---- reading text files ---------------------------------------------------- + // A text file, read whole: strict UTF-8 — an invalid byte throws + // std::runtime_error naming the file and the offset — and universal + // newlines. + std::string ReadText(const std::filesystem::path& path); + // \r\n and a lone \r become \n, as text mode reads them. + std::string UniversalNewlines(std::string_view text); + + // ---- numbers ------------------------------------------------------------------ + // A float as its shortest digits that round-trip; an exponent below 1e-4 + // and from 1e16 up; ".0" on an integral value; inf, nan. + std::string FloatRepr(double value); + // value to `digits` decimals: correctly rounded, half to even on exact + // ties. + double Round(double value, std::int32_t digits); + // value to the nearest integer, half to even. + std::int64_t RoundToInt(double value); + // An integer or a float from text: surrounding Unicode whitespace ignored, + // Unicode decimal digits accepted, single underscores between digits + // allowed. nullopt for anything else (and, for an integer, beyond 64 bits). + std::optional ParseInt(std::string_view text); + std::optional ParseFloat(std::string_view text); + // The ValueError messages for those. + std::string IntError(std::string_view text); + std::string FloatError(std::string_view text); + + // ---- repr ---------------------------------------------------------------------- + std::string Repr(std::string_view text); + // A JSON value printed for a message: objects as {'key': value}, strings + // quoted, true, false and null as True, False and None. + std::string ReprValue(const Json& value); + + // ---- shell patterns --------------------------------------------------------------- + // A shell-style pattern: case-sensitive, `*` crosses `/`, `[!...]` negates, + // an unclosed `[` is literal and a reversed range matches nothing. + bool FnMatch(std::string_view name, std::string_view pattern); + + // ---- URLs --------------------------------------------------------------------------- + struct SplitUrl { + std::string scheme; + std::string netloc; + std::string path; + std::string query; + std::string fragment; + // .hostname: lowercased, nullopt when there is none. + std::optional Hostname() const; + std::optional Username() const; + std::optional Password() const; + // .port: nullopt for none; std::invalid_argument with a ValueError + // message for one that is not a number or out of range. + std::optional Port() const; + }; + // A URL's scheme, netloc, path, query and fragment. Throws + // std::invalid_argument with a ValueError message for unbalanced or + // invalid brackets. + SplitUrl UrlSplit(std::string_view url); + // unquote(text): %XX sequences decoded as UTF-8, bad bytes replaced. + std::string Unquote(std::string_view text); + + // ---- natural sort --------------------------------------------------------------------- + // A name cut into digit runs and the text between them, compared element + // by element, a digit run by its value. + struct NaturalKey { + std::vector> parts; + std::strong_ordering operator<=>(const NaturalKey& other) const = default; + bool operator==(const NaturalKey& other) const = default; + }; + NaturalKey Natural(std::string_view name); + + // ---- POSIX paths ----------------------------------------------------------------------- + std::string NormPath(std::string_view path); + // Against the current directory, lexically: symlinks are not resolved. + std::string AbsPath(std::string_view path); + std::string RelPath(std::string_view path, std::string_view start = "."); + std::string BaseName(std::string_view path); + std::string DirName(std::string_view path); + std::string JoinPath(std::string_view a, std::string_view b); + + // ---- errno texts ------------------------------------------------------------------------- + // "[Errno 111] Connection refused": an errno as a message prints it. + std::string ErrnoText(std::int32_t code); +} diff --git a/interfaces/Crafter.Review-Unicode.cppm b/interfaces/Crafter.Review-Unicode.cppm new file mode 100644 index 0000000..ab5cc47 --- /dev/null +++ b/interfaces/Crafter.Review-Unicode.cppm @@ -0,0 +1,3889 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +// Generated tables, Unicode 16.0.0. Change them only together with +// tests/Text's vectors, and review the diff. +// +// The parts of the Unicode Character Database the driver's output depends +// on: whitespace, line breaks, decimal digits, lowercase and uppercase +// mappings outside ASCII, and which other code points match an ASCII letter +// case-insensitively. :Text builds its string functions on these, so a +// unit, an excerpt or a key cut from text with an NBSP, a U+2028 or a Kelvin +// sign in it always comes out the same. + +export module Crafter.Review:Unicode; +import std; + +export namespace Crafter::Review::Unicode { + // A code point and what a case mapping turns it into, as UTF-8 (one + // code point can become several: U+0130 lowercases to i + U+0307). + struct Mapping { + char32_t from; + std::string_view to; + }; + + struct Run { + char32_t first; + char32_t last; + }; + + struct Fold { + char32_t from; + char letter; + }; + + // Whitespace. + constexpr std::array Space = { + 0x9, 0xa, 0xb, 0xc, 0xd, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x85, 0xa0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a, 0x2028, 0x2029, 0x202f, 0x205f, 0x3000, + }; + + // Where SplitLines() ends a line (\r\n is one boundary). + constexpr std::array LineBreak = { + 0xa, 0xb, 0xc, 0xd, 0x1c, 0x1d, 0x1e, 0x85, 0x2028, 0x2029, + }; + + // Decimal digits (regex \d): runs of ten, a digit's value its + // distance from the run's start, modulo 10. + constexpr std::array Decimal = {{ + {0x30, 0x39}, + {0x660, 0x669}, + {0x6f0, 0x6f9}, + {0x7c0, 0x7c9}, + {0x966, 0x96f}, + {0x9e6, 0x9ef}, + {0xa66, 0xa6f}, + {0xae6, 0xaef}, + {0xb66, 0xb6f}, + {0xbe6, 0xbef}, + {0xc66, 0xc6f}, + {0xce6, 0xcef}, + {0xd66, 0xd6f}, + {0xde6, 0xdef}, + {0xe50, 0xe59}, + {0xed0, 0xed9}, + {0xf20, 0xf29}, + {0x1040, 0x1049}, + {0x1090, 0x1099}, + {0x17e0, 0x17e9}, + {0x1810, 0x1819}, + {0x1946, 0x194f}, + {0x19d0, 0x19d9}, + {0x1a80, 0x1a89}, + {0x1a90, 0x1a99}, + {0x1b50, 0x1b59}, + {0x1bb0, 0x1bb9}, + {0x1c40, 0x1c49}, + {0x1c50, 0x1c59}, + {0xa620, 0xa629}, + {0xa8d0, 0xa8d9}, + {0xa900, 0xa909}, + {0xa9d0, 0xa9d9}, + {0xa9f0, 0xa9f9}, + {0xaa50, 0xaa59}, + {0xabf0, 0xabf9}, + {0xff10, 0xff19}, + {0x104a0, 0x104a9}, + {0x10d30, 0x10d39}, + {0x10d40, 0x10d49}, + {0x11066, 0x1106f}, + {0x110f0, 0x110f9}, + {0x11136, 0x1113f}, + {0x111d0, 0x111d9}, + {0x112f0, 0x112f9}, + {0x11450, 0x11459}, + {0x114d0, 0x114d9}, + {0x11650, 0x11659}, + {0x116c0, 0x116c9}, + {0x116d0, 0x116e3}, + {0x11730, 0x11739}, + {0x118e0, 0x118e9}, + {0x11950, 0x11959}, + {0x11bf0, 0x11bf9}, + {0x11c50, 0x11c59}, + {0x11d50, 0x11d59}, + {0x11da0, 0x11da9}, + {0x11f50, 0x11f59}, + {0x16130, 0x16139}, + {0x16a60, 0x16a69}, + {0x16ac0, 0x16ac9}, + {0x16b50, 0x16b59}, + {0x16d70, 0x16d79}, + {0x1ccf0, 0x1ccf9}, + {0x1d7ce, 0x1d7ff}, + {0x1e140, 0x1e149}, + {0x1e2f0, 0x1e2f9}, + {0x1e4f0, 0x1e4f9}, + {0x1e5f1, 0x1e5fa}, + {0x1e950, 0x1e959}, + {0x1fbf0, 0x1fbf9}, + }}; + + // Lowercase mappings, for the code points that change, sorted. + constexpr std::array Lower = {{ + {0x41, "a"}, + {0x42, "b"}, + {0x43, "c"}, + {0x44, "d"}, + {0x45, "e"}, + {0x46, "f"}, + {0x47, "g"}, + {0x48, "h"}, + {0x49, "i"}, + {0x4a, "j"}, + {0x4b, "k"}, + {0x4c, "l"}, + {0x4d, "m"}, + {0x4e, "n"}, + {0x4f, "o"}, + {0x50, "p"}, + {0x51, "q"}, + {0x52, "r"}, + {0x53, "s"}, + {0x54, "t"}, + {0x55, "u"}, + {0x56, "v"}, + {0x57, "w"}, + {0x58, "x"}, + {0x59, "y"}, + {0x5a, "z"}, + {0xc0, "\xc3\xa0"}, + {0xc1, "\xc3\xa1"}, + {0xc2, "\xc3\xa2"}, + {0xc3, "\xc3\xa3"}, + {0xc4, "\xc3\xa4"}, + {0xc5, "\xc3\xa5"}, + {0xc6, "\xc3\xa6"}, + {0xc7, "\xc3\xa7"}, + {0xc8, "\xc3\xa8"}, + {0xc9, "\xc3\xa9"}, + {0xca, "\xc3\xaa"}, + {0xcb, "\xc3\xab"}, + {0xcc, "\xc3\xac"}, + {0xcd, "\xc3\xad"}, + {0xce, "\xc3\xae"}, + {0xcf, "\xc3\xaf"}, + {0xd0, "\xc3\xb0"}, + {0xd1, "\xc3\xb1"}, + {0xd2, "\xc3\xb2"}, + {0xd3, "\xc3\xb3"}, + {0xd4, "\xc3\xb4"}, + {0xd5, "\xc3\xb5"}, + {0xd6, "\xc3\xb6"}, + {0xd8, "\xc3\xb8"}, + {0xd9, "\xc3\xb9"}, + {0xda, "\xc3\xba"}, + {0xdb, "\xc3\xbb"}, + {0xdc, "\xc3\xbc"}, + {0xdd, "\xc3\xbd"}, + {0xde, "\xc3\xbe"}, + {0x100, "\xc4\x81"}, + {0x102, "\xc4\x83"}, + {0x104, "\xc4\x85"}, + {0x106, "\xc4\x87"}, + {0x108, "\xc4\x89"}, + {0x10a, "\xc4\x8b"}, + {0x10c, "\xc4\x8d"}, + {0x10e, "\xc4\x8f"}, + {0x110, "\xc4\x91"}, + {0x112, "\xc4\x93"}, + {0x114, "\xc4\x95"}, + {0x116, "\xc4\x97"}, + {0x118, "\xc4\x99"}, + {0x11a, "\xc4\x9b"}, + {0x11c, "\xc4\x9d"}, + {0x11e, "\xc4\x9f"}, + {0x120, "\xc4\xa1"}, + {0x122, "\xc4\xa3"}, + {0x124, "\xc4\xa5"}, + {0x126, "\xc4\xa7"}, + {0x128, "\xc4\xa9"}, + {0x12a, "\xc4\xab"}, + {0x12c, "\xc4\xad"}, + {0x12e, "\xc4\xaf"}, + {0x130, "i\xcc\x87"}, + {0x132, "\xc4\xb3"}, + {0x134, "\xc4\xb5"}, + {0x136, "\xc4\xb7"}, + {0x139, "\xc4\xba"}, + {0x13b, "\xc4\xbc"}, + {0x13d, "\xc4\xbe"}, + {0x13f, "\xc5\x80"}, + {0x141, "\xc5\x82"}, + {0x143, "\xc5\x84"}, + {0x145, "\xc5\x86"}, + {0x147, "\xc5\x88"}, + {0x14a, "\xc5\x8b"}, + {0x14c, "\xc5\x8d"}, + {0x14e, "\xc5\x8f"}, + {0x150, "\xc5\x91"}, + {0x152, "\xc5\x93"}, + {0x154, "\xc5\x95"}, + {0x156, "\xc5\x97"}, + {0x158, "\xc5\x99"}, + {0x15a, "\xc5\x9b"}, + {0x15c, "\xc5\x9d"}, + {0x15e, "\xc5\x9f"}, + {0x160, "\xc5\xa1"}, + {0x162, "\xc5\xa3"}, + {0x164, "\xc5\xa5"}, + {0x166, "\xc5\xa7"}, + {0x168, "\xc5\xa9"}, + {0x16a, "\xc5\xab"}, + {0x16c, "\xc5\xad"}, + {0x16e, "\xc5\xaf"}, + {0x170, "\xc5\xb1"}, + {0x172, "\xc5\xb3"}, + {0x174, "\xc5\xb5"}, + {0x176, "\xc5\xb7"}, + {0x178, "\xc3\xbf"}, + {0x179, "\xc5\xba"}, + {0x17b, "\xc5\xbc"}, + {0x17d, "\xc5\xbe"}, + {0x181, "\xc9\x93"}, + {0x182, "\xc6\x83"}, + {0x184, "\xc6\x85"}, + {0x186, "\xc9\x94"}, + {0x187, "\xc6\x88"}, + {0x189, "\xc9\x96"}, + {0x18a, "\xc9\x97"}, + {0x18b, "\xc6\x8c"}, + {0x18e, "\xc7\x9d"}, + {0x18f, "\xc9\x99"}, + {0x190, "\xc9\x9b"}, + {0x191, "\xc6\x92"}, + {0x193, "\xc9\xa0"}, + {0x194, "\xc9\xa3"}, + {0x196, "\xc9\xa9"}, + {0x197, "\xc9\xa8"}, + {0x198, "\xc6\x99"}, + {0x19c, "\xc9\xaf"}, + {0x19d, "\xc9\xb2"}, + {0x19f, "\xc9\xb5"}, + {0x1a0, "\xc6\xa1"}, + {0x1a2, "\xc6\xa3"}, + {0x1a4, "\xc6\xa5"}, + {0x1a6, "\xca\x80"}, + {0x1a7, "\xc6\xa8"}, + {0x1a9, "\xca\x83"}, + {0x1ac, "\xc6\xad"}, + {0x1ae, "\xca\x88"}, + {0x1af, "\xc6\xb0"}, + {0x1b1, "\xca\x8a"}, + {0x1b2, "\xca\x8b"}, + {0x1b3, "\xc6\xb4"}, + {0x1b5, "\xc6\xb6"}, + {0x1b7, "\xca\x92"}, + {0x1b8, "\xc6\xb9"}, + {0x1bc, "\xc6\xbd"}, + {0x1c4, "\xc7\x86"}, + {0x1c5, "\xc7\x86"}, + {0x1c7, "\xc7\x89"}, + {0x1c8, "\xc7\x89"}, + {0x1ca, "\xc7\x8c"}, + {0x1cb, "\xc7\x8c"}, + {0x1cd, "\xc7\x8e"}, + {0x1cf, "\xc7\x90"}, + {0x1d1, "\xc7\x92"}, + {0x1d3, "\xc7\x94"}, + {0x1d5, "\xc7\x96"}, + {0x1d7, "\xc7\x98"}, + {0x1d9, "\xc7\x9a"}, + {0x1db, "\xc7\x9c"}, + {0x1de, "\xc7\x9f"}, + {0x1e0, "\xc7\xa1"}, + {0x1e2, "\xc7\xa3"}, + {0x1e4, "\xc7\xa5"}, + {0x1e6, "\xc7\xa7"}, + {0x1e8, "\xc7\xa9"}, + {0x1ea, "\xc7\xab"}, + {0x1ec, "\xc7\xad"}, + {0x1ee, "\xc7\xaf"}, + {0x1f1, "\xc7\xb3"}, + {0x1f2, "\xc7\xb3"}, + {0x1f4, "\xc7\xb5"}, + {0x1f6, "\xc6\x95"}, + {0x1f7, "\xc6\xbf"}, + {0x1f8, "\xc7\xb9"}, + {0x1fa, "\xc7\xbb"}, + {0x1fc, "\xc7\xbd"}, + {0x1fe, "\xc7\xbf"}, + {0x200, "\xc8\x81"}, + {0x202, "\xc8\x83"}, + {0x204, "\xc8\x85"}, + {0x206, "\xc8\x87"}, + {0x208, "\xc8\x89"}, + {0x20a, "\xc8\x8b"}, + {0x20c, "\xc8\x8d"}, + {0x20e, "\xc8\x8f"}, + {0x210, "\xc8\x91"}, + {0x212, "\xc8\x93"}, + {0x214, "\xc8\x95"}, + {0x216, "\xc8\x97"}, + {0x218, "\xc8\x99"}, + {0x21a, "\xc8\x9b"}, + {0x21c, "\xc8\x9d"}, + {0x21e, "\xc8\x9f"}, + {0x220, "\xc6\x9e"}, + {0x222, "\xc8\xa3"}, + {0x224, "\xc8\xa5"}, + {0x226, "\xc8\xa7"}, + {0x228, "\xc8\xa9"}, + {0x22a, "\xc8\xab"}, + {0x22c, "\xc8\xad"}, + {0x22e, "\xc8\xaf"}, + {0x230, "\xc8\xb1"}, + {0x232, "\xc8\xb3"}, + {0x23a, "\xe2\xb1\xa5"}, + {0x23b, "\xc8\xbc"}, + {0x23d, "\xc6\x9a"}, + {0x23e, "\xe2\xb1\xa6"}, + {0x241, "\xc9\x82"}, + {0x243, "\xc6\x80"}, + {0x244, "\xca\x89"}, + {0x245, "\xca\x8c"}, + {0x246, "\xc9\x87"}, + {0x248, "\xc9\x89"}, + {0x24a, "\xc9\x8b"}, + {0x24c, "\xc9\x8d"}, + {0x24e, "\xc9\x8f"}, + {0x370, "\xcd\xb1"}, + {0x372, "\xcd\xb3"}, + {0x376, "\xcd\xb7"}, + {0x37f, "\xcf\xb3"}, + {0x386, "\xce\xac"}, + {0x388, "\xce\xad"}, + {0x389, "\xce\xae"}, + {0x38a, "\xce\xaf"}, + {0x38c, "\xcf\x8c"}, + {0x38e, "\xcf\x8d"}, + {0x38f, "\xcf\x8e"}, + {0x391, "\xce\xb1"}, + {0x392, "\xce\xb2"}, + {0x393, "\xce\xb3"}, + {0x394, "\xce\xb4"}, + {0x395, "\xce\xb5"}, + {0x396, "\xce\xb6"}, + {0x397, "\xce\xb7"}, + {0x398, "\xce\xb8"}, + {0x399, "\xce\xb9"}, + {0x39a, "\xce\xba"}, + {0x39b, "\xce\xbb"}, + {0x39c, "\xce\xbc"}, + {0x39d, "\xce\xbd"}, + {0x39e, "\xce\xbe"}, + {0x39f, "\xce\xbf"}, + {0x3a0, "\xcf\x80"}, + {0x3a1, "\xcf\x81"}, + {0x3a3, "\xcf\x83"}, + {0x3a4, "\xcf\x84"}, + {0x3a5, "\xcf\x85"}, + {0x3a6, "\xcf\x86"}, + {0x3a7, "\xcf\x87"}, + {0x3a8, "\xcf\x88"}, + {0x3a9, "\xcf\x89"}, + {0x3aa, "\xcf\x8a"}, + {0x3ab, "\xcf\x8b"}, + {0x3cf, "\xcf\x97"}, + {0x3d8, "\xcf\x99"}, + {0x3da, "\xcf\x9b"}, + {0x3dc, "\xcf\x9d"}, + {0x3de, "\xcf\x9f"}, + {0x3e0, "\xcf\xa1"}, + {0x3e2, "\xcf\xa3"}, + {0x3e4, "\xcf\xa5"}, + {0x3e6, "\xcf\xa7"}, + {0x3e8, "\xcf\xa9"}, + {0x3ea, "\xcf\xab"}, + {0x3ec, "\xcf\xad"}, + {0x3ee, "\xcf\xaf"}, + {0x3f4, "\xce\xb8"}, + {0x3f7, "\xcf\xb8"}, + {0x3f9, "\xcf\xb2"}, + {0x3fa, "\xcf\xbb"}, + {0x3fd, "\xcd\xbb"}, + {0x3fe, "\xcd\xbc"}, + {0x3ff, "\xcd\xbd"}, + {0x400, "\xd1\x90"}, + {0x401, "\xd1\x91"}, + {0x402, "\xd1\x92"}, + {0x403, "\xd1\x93"}, + {0x404, "\xd1\x94"}, + {0x405, "\xd1\x95"}, + {0x406, "\xd1\x96"}, + {0x407, "\xd1\x97"}, + {0x408, "\xd1\x98"}, + {0x409, "\xd1\x99"}, + {0x40a, "\xd1\x9a"}, + {0x40b, "\xd1\x9b"}, + {0x40c, "\xd1\x9c"}, + {0x40d, "\xd1\x9d"}, + {0x40e, "\xd1\x9e"}, + {0x40f, "\xd1\x9f"}, + {0x410, "\xd0\xb0"}, + {0x411, "\xd0\xb1"}, + {0x412, "\xd0\xb2"}, + {0x413, "\xd0\xb3"}, + {0x414, "\xd0\xb4"}, + {0x415, "\xd0\xb5"}, + {0x416, "\xd0\xb6"}, + {0x417, "\xd0\xb7"}, + {0x418, "\xd0\xb8"}, + {0x419, "\xd0\xb9"}, + {0x41a, "\xd0\xba"}, + {0x41b, "\xd0\xbb"}, + {0x41c, "\xd0\xbc"}, + {0x41d, "\xd0\xbd"}, + {0x41e, "\xd0\xbe"}, + {0x41f, "\xd0\xbf"}, + {0x420, "\xd1\x80"}, + {0x421, "\xd1\x81"}, + {0x422, "\xd1\x82"}, + {0x423, "\xd1\x83"}, + {0x424, "\xd1\x84"}, + {0x425, "\xd1\x85"}, + {0x426, "\xd1\x86"}, + {0x427, "\xd1\x87"}, + {0x428, "\xd1\x88"}, + {0x429, "\xd1\x89"}, + {0x42a, "\xd1\x8a"}, + {0x42b, "\xd1\x8b"}, + {0x42c, "\xd1\x8c"}, + {0x42d, "\xd1\x8d"}, + {0x42e, "\xd1\x8e"}, + {0x42f, "\xd1\x8f"}, + {0x460, "\xd1\xa1"}, + {0x462, "\xd1\xa3"}, + {0x464, "\xd1\xa5"}, + {0x466, "\xd1\xa7"}, + {0x468, "\xd1\xa9"}, + {0x46a, "\xd1\xab"}, + {0x46c, "\xd1\xad"}, + {0x46e, "\xd1\xaf"}, + {0x470, "\xd1\xb1"}, + {0x472, "\xd1\xb3"}, + {0x474, "\xd1\xb5"}, + {0x476, "\xd1\xb7"}, + {0x478, "\xd1\xb9"}, + {0x47a, "\xd1\xbb"}, + {0x47c, "\xd1\xbd"}, + {0x47e, "\xd1\xbf"}, + {0x480, "\xd2\x81"}, + {0x48a, "\xd2\x8b"}, + {0x48c, "\xd2\x8d"}, + {0x48e, "\xd2\x8f"}, + {0x490, "\xd2\x91"}, + {0x492, "\xd2\x93"}, + {0x494, "\xd2\x95"}, + {0x496, "\xd2\x97"}, + {0x498, "\xd2\x99"}, + {0x49a, "\xd2\x9b"}, + {0x49c, "\xd2\x9d"}, + {0x49e, "\xd2\x9f"}, + {0x4a0, "\xd2\xa1"}, + {0x4a2, "\xd2\xa3"}, + {0x4a4, "\xd2\xa5"}, + {0x4a6, "\xd2\xa7"}, + {0x4a8, "\xd2\xa9"}, + {0x4aa, "\xd2\xab"}, + {0x4ac, "\xd2\xad"}, + {0x4ae, "\xd2\xaf"}, + {0x4b0, "\xd2\xb1"}, + {0x4b2, "\xd2\xb3"}, + {0x4b4, "\xd2\xb5"}, + {0x4b6, "\xd2\xb7"}, + {0x4b8, "\xd2\xb9"}, + {0x4ba, "\xd2\xbb"}, + {0x4bc, "\xd2\xbd"}, + {0x4be, "\xd2\xbf"}, + {0x4c0, "\xd3\x8f"}, + {0x4c1, "\xd3\x82"}, + {0x4c3, "\xd3\x84"}, + {0x4c5, "\xd3\x86"}, + {0x4c7, "\xd3\x88"}, + {0x4c9, "\xd3\x8a"}, + {0x4cb, "\xd3\x8c"}, + {0x4cd, "\xd3\x8e"}, + {0x4d0, "\xd3\x91"}, + {0x4d2, "\xd3\x93"}, + {0x4d4, "\xd3\x95"}, + {0x4d6, "\xd3\x97"}, + {0x4d8, "\xd3\x99"}, + {0x4da, "\xd3\x9b"}, + {0x4dc, "\xd3\x9d"}, + {0x4de, "\xd3\x9f"}, + {0x4e0, "\xd3\xa1"}, + {0x4e2, "\xd3\xa3"}, + {0x4e4, "\xd3\xa5"}, + {0x4e6, "\xd3\xa7"}, + {0x4e8, "\xd3\xa9"}, + {0x4ea, "\xd3\xab"}, + {0x4ec, "\xd3\xad"}, + {0x4ee, "\xd3\xaf"}, + {0x4f0, "\xd3\xb1"}, + {0x4f2, "\xd3\xb3"}, + {0x4f4, "\xd3\xb5"}, + {0x4f6, "\xd3\xb7"}, + {0x4f8, "\xd3\xb9"}, + {0x4fa, "\xd3\xbb"}, + {0x4fc, "\xd3\xbd"}, + {0x4fe, "\xd3\xbf"}, + {0x500, "\xd4\x81"}, + {0x502, "\xd4\x83"}, + {0x504, "\xd4\x85"}, + {0x506, "\xd4\x87"}, + {0x508, "\xd4\x89"}, + {0x50a, "\xd4\x8b"}, + {0x50c, "\xd4\x8d"}, + {0x50e, "\xd4\x8f"}, + {0x510, "\xd4\x91"}, + {0x512, "\xd4\x93"}, + {0x514, "\xd4\x95"}, + {0x516, "\xd4\x97"}, + {0x518, "\xd4\x99"}, + {0x51a, "\xd4\x9b"}, + {0x51c, "\xd4\x9d"}, + {0x51e, "\xd4\x9f"}, + {0x520, "\xd4\xa1"}, + {0x522, "\xd4\xa3"}, + {0x524, "\xd4\xa5"}, + {0x526, "\xd4\xa7"}, + {0x528, "\xd4\xa9"}, + {0x52a, "\xd4\xab"}, + {0x52c, "\xd4\xad"}, + {0x52e, "\xd4\xaf"}, + {0x531, "\xd5\xa1"}, + {0x532, "\xd5\xa2"}, + {0x533, "\xd5\xa3"}, + {0x534, "\xd5\xa4"}, + {0x535, "\xd5\xa5"}, + {0x536, "\xd5\xa6"}, + {0x537, "\xd5\xa7"}, + {0x538, "\xd5\xa8"}, + {0x539, "\xd5\xa9"}, + {0x53a, "\xd5\xaa"}, + {0x53b, "\xd5\xab"}, + {0x53c, "\xd5\xac"}, + {0x53d, "\xd5\xad"}, + {0x53e, "\xd5\xae"}, + {0x53f, "\xd5\xaf"}, + {0x540, "\xd5\xb0"}, + {0x541, "\xd5\xb1"}, + {0x542, "\xd5\xb2"}, + {0x543, "\xd5\xb3"}, + {0x544, "\xd5\xb4"}, + {0x545, "\xd5\xb5"}, + {0x546, "\xd5\xb6"}, + {0x547, "\xd5\xb7"}, + {0x548, "\xd5\xb8"}, + {0x549, "\xd5\xb9"}, + {0x54a, "\xd5\xba"}, + {0x54b, "\xd5\xbb"}, + {0x54c, "\xd5\xbc"}, + {0x54d, "\xd5\xbd"}, + {0x54e, "\xd5\xbe"}, + {0x54f, "\xd5\xbf"}, + {0x550, "\xd6\x80"}, + {0x551, "\xd6\x81"}, + {0x552, "\xd6\x82"}, + {0x553, "\xd6\x83"}, + {0x554, "\xd6\x84"}, + {0x555, "\xd6\x85"}, + {0x556, "\xd6\x86"}, + {0x10a0, "\xe2\xb4\x80"}, + {0x10a1, "\xe2\xb4\x81"}, + {0x10a2, "\xe2\xb4\x82"}, + {0x10a3, "\xe2\xb4\x83"}, + {0x10a4, "\xe2\xb4\x84"}, + {0x10a5, "\xe2\xb4\x85"}, + {0x10a6, "\xe2\xb4\x86"}, + {0x10a7, "\xe2\xb4\x87"}, + {0x10a8, "\xe2\xb4\x88"}, + {0x10a9, "\xe2\xb4\x89"}, + {0x10aa, "\xe2\xb4\x8a"}, + {0x10ab, "\xe2\xb4\x8b"}, + {0x10ac, "\xe2\xb4\x8c"}, + {0x10ad, "\xe2\xb4\x8d"}, + {0x10ae, "\xe2\xb4\x8e"}, + {0x10af, "\xe2\xb4\x8f"}, + {0x10b0, "\xe2\xb4\x90"}, + {0x10b1, "\xe2\xb4\x91"}, + {0x10b2, "\xe2\xb4\x92"}, + {0x10b3, "\xe2\xb4\x93"}, + {0x10b4, "\xe2\xb4\x94"}, + {0x10b5, "\xe2\xb4\x95"}, + {0x10b6, "\xe2\xb4\x96"}, + {0x10b7, "\xe2\xb4\x97"}, + {0x10b8, "\xe2\xb4\x98"}, + {0x10b9, "\xe2\xb4\x99"}, + {0x10ba, "\xe2\xb4\x9a"}, + {0x10bb, "\xe2\xb4\x9b"}, + {0x10bc, "\xe2\xb4\x9c"}, + {0x10bd, "\xe2\xb4\x9d"}, + {0x10be, "\xe2\xb4\x9e"}, + {0x10bf, "\xe2\xb4\x9f"}, + {0x10c0, "\xe2\xb4\xa0"}, + {0x10c1, "\xe2\xb4\xa1"}, + {0x10c2, "\xe2\xb4\xa2"}, + {0x10c3, "\xe2\xb4\xa3"}, + {0x10c4, "\xe2\xb4\xa4"}, + {0x10c5, "\xe2\xb4\xa5"}, + {0x10c7, "\xe2\xb4\xa7"}, + {0x10cd, "\xe2\xb4\xad"}, + {0x13a0, "\xea\xad\xb0"}, + {0x13a1, "\xea\xad\xb1"}, + {0x13a2, "\xea\xad\xb2"}, + {0x13a3, "\xea\xad\xb3"}, + {0x13a4, "\xea\xad\xb4"}, + {0x13a5, "\xea\xad\xb5"}, + {0x13a6, "\xea\xad\xb6"}, + {0x13a7, "\xea\xad\xb7"}, + {0x13a8, "\xea\xad\xb8"}, + {0x13a9, "\xea\xad\xb9"}, + {0x13aa, "\xea\xad\xba"}, + {0x13ab, "\xea\xad\xbb"}, + {0x13ac, "\xea\xad\xbc"}, + {0x13ad, "\xea\xad\xbd"}, + {0x13ae, "\xea\xad\xbe"}, + {0x13af, "\xea\xad\xbf"}, + {0x13b0, "\xea\xae\x80"}, + {0x13b1, "\xea\xae\x81"}, + {0x13b2, "\xea\xae\x82"}, + {0x13b3, "\xea\xae\x83"}, + {0x13b4, "\xea\xae\x84"}, + {0x13b5, "\xea\xae\x85"}, + {0x13b6, "\xea\xae\x86"}, + {0x13b7, "\xea\xae\x87"}, + {0x13b8, "\xea\xae\x88"}, + {0x13b9, "\xea\xae\x89"}, + {0x13ba, "\xea\xae\x8a"}, + {0x13bb, "\xea\xae\x8b"}, + {0x13bc, "\xea\xae\x8c"}, + {0x13bd, "\xea\xae\x8d"}, + {0x13be, "\xea\xae\x8e"}, + {0x13bf, "\xea\xae\x8f"}, + {0x13c0, "\xea\xae\x90"}, + {0x13c1, "\xea\xae\x91"}, + {0x13c2, "\xea\xae\x92"}, + {0x13c3, "\xea\xae\x93"}, + {0x13c4, "\xea\xae\x94"}, + {0x13c5, "\xea\xae\x95"}, + {0x13c6, "\xea\xae\x96"}, + {0x13c7, "\xea\xae\x97"}, + {0x13c8, "\xea\xae\x98"}, + {0x13c9, "\xea\xae\x99"}, + {0x13ca, "\xea\xae\x9a"}, + {0x13cb, "\xea\xae\x9b"}, + {0x13cc, "\xea\xae\x9c"}, + {0x13cd, "\xea\xae\x9d"}, + {0x13ce, "\xea\xae\x9e"}, + {0x13cf, "\xea\xae\x9f"}, + {0x13d0, "\xea\xae\xa0"}, + {0x13d1, "\xea\xae\xa1"}, + {0x13d2, "\xea\xae\xa2"}, + {0x13d3, "\xea\xae\xa3"}, + {0x13d4, "\xea\xae\xa4"}, + {0x13d5, "\xea\xae\xa5"}, + {0x13d6, "\xea\xae\xa6"}, + {0x13d7, "\xea\xae\xa7"}, + {0x13d8, "\xea\xae\xa8"}, + {0x13d9, "\xea\xae\xa9"}, + {0x13da, "\xea\xae\xaa"}, + {0x13db, "\xea\xae\xab"}, + {0x13dc, "\xea\xae\xac"}, + {0x13dd, "\xea\xae\xad"}, + {0x13de, "\xea\xae\xae"}, + {0x13df, "\xea\xae\xaf"}, + {0x13e0, "\xea\xae\xb0"}, + {0x13e1, "\xea\xae\xb1"}, + {0x13e2, "\xea\xae\xb2"}, + {0x13e3, "\xea\xae\xb3"}, + {0x13e4, "\xea\xae\xb4"}, + {0x13e5, "\xea\xae\xb5"}, + {0x13e6, "\xea\xae\xb6"}, + {0x13e7, "\xea\xae\xb7"}, + {0x13e8, "\xea\xae\xb8"}, + {0x13e9, "\xea\xae\xb9"}, + {0x13ea, "\xea\xae\xba"}, + {0x13eb, "\xea\xae\xbb"}, + {0x13ec, "\xea\xae\xbc"}, + {0x13ed, "\xea\xae\xbd"}, + {0x13ee, "\xea\xae\xbe"}, + {0x13ef, "\xea\xae\xbf"}, + {0x13f0, "\xe1\x8f\xb8"}, + {0x13f1, "\xe1\x8f\xb9"}, + {0x13f2, "\xe1\x8f\xba"}, + {0x13f3, "\xe1\x8f\xbb"}, + {0x13f4, "\xe1\x8f\xbc"}, + {0x13f5, "\xe1\x8f\xbd"}, + {0x1c89, "\xe1\xb2\x8a"}, + {0x1c90, "\xe1\x83\x90"}, + {0x1c91, "\xe1\x83\x91"}, + {0x1c92, "\xe1\x83\x92"}, + {0x1c93, "\xe1\x83\x93"}, + {0x1c94, "\xe1\x83\x94"}, + {0x1c95, "\xe1\x83\x95"}, + {0x1c96, "\xe1\x83\x96"}, + {0x1c97, "\xe1\x83\x97"}, + {0x1c98, "\xe1\x83\x98"}, + {0x1c99, "\xe1\x83\x99"}, + {0x1c9a, "\xe1\x83\x9a"}, + {0x1c9b, "\xe1\x83\x9b"}, + {0x1c9c, "\xe1\x83\x9c"}, + {0x1c9d, "\xe1\x83\x9d"}, + {0x1c9e, "\xe1\x83\x9e"}, + {0x1c9f, "\xe1\x83\x9f"}, + {0x1ca0, "\xe1\x83\xa0"}, + {0x1ca1, "\xe1\x83\xa1"}, + {0x1ca2, "\xe1\x83\xa2"}, + {0x1ca3, "\xe1\x83\xa3"}, + {0x1ca4, "\xe1\x83\xa4"}, + {0x1ca5, "\xe1\x83\xa5"}, + {0x1ca6, "\xe1\x83\xa6"}, + {0x1ca7, "\xe1\x83\xa7"}, + {0x1ca8, "\xe1\x83\xa8"}, + {0x1ca9, "\xe1\x83\xa9"}, + {0x1caa, "\xe1\x83\xaa"}, + {0x1cab, "\xe1\x83\xab"}, + {0x1cac, "\xe1\x83\xac"}, + {0x1cad, "\xe1\x83\xad"}, + {0x1cae, "\xe1\x83\xae"}, + {0x1caf, "\xe1\x83\xaf"}, + {0x1cb0, "\xe1\x83\xb0"}, + {0x1cb1, "\xe1\x83\xb1"}, + {0x1cb2, "\xe1\x83\xb2"}, + {0x1cb3, "\xe1\x83\xb3"}, + {0x1cb4, "\xe1\x83\xb4"}, + {0x1cb5, "\xe1\x83\xb5"}, + {0x1cb6, "\xe1\x83\xb6"}, + {0x1cb7, "\xe1\x83\xb7"}, + {0x1cb8, "\xe1\x83\xb8"}, + {0x1cb9, "\xe1\x83\xb9"}, + {0x1cba, "\xe1\x83\xba"}, + {0x1cbd, "\xe1\x83\xbd"}, + {0x1cbe, "\xe1\x83\xbe"}, + {0x1cbf, "\xe1\x83\xbf"}, + {0x1e00, "\xe1\xb8\x81"}, + {0x1e02, "\xe1\xb8\x83"}, + {0x1e04, "\xe1\xb8\x85"}, + {0x1e06, "\xe1\xb8\x87"}, + {0x1e08, "\xe1\xb8\x89"}, + {0x1e0a, "\xe1\xb8\x8b"}, + {0x1e0c, "\xe1\xb8\x8d"}, + {0x1e0e, "\xe1\xb8\x8f"}, + {0x1e10, "\xe1\xb8\x91"}, + {0x1e12, "\xe1\xb8\x93"}, + {0x1e14, "\xe1\xb8\x95"}, + {0x1e16, "\xe1\xb8\x97"}, + {0x1e18, "\xe1\xb8\x99"}, + {0x1e1a, "\xe1\xb8\x9b"}, + {0x1e1c, "\xe1\xb8\x9d"}, + {0x1e1e, "\xe1\xb8\x9f"}, + {0x1e20, "\xe1\xb8\xa1"}, + {0x1e22, "\xe1\xb8\xa3"}, + {0x1e24, "\xe1\xb8\xa5"}, + {0x1e26, "\xe1\xb8\xa7"}, + {0x1e28, "\xe1\xb8\xa9"}, + {0x1e2a, "\xe1\xb8\xab"}, + {0x1e2c, "\xe1\xb8\xad"}, + {0x1e2e, "\xe1\xb8\xaf"}, + {0x1e30, "\xe1\xb8\xb1"}, + {0x1e32, "\xe1\xb8\xb3"}, + {0x1e34, "\xe1\xb8\xb5"}, + {0x1e36, "\xe1\xb8\xb7"}, + {0x1e38, "\xe1\xb8\xb9"}, + {0x1e3a, "\xe1\xb8\xbb"}, + {0x1e3c, "\xe1\xb8\xbd"}, + {0x1e3e, "\xe1\xb8\xbf"}, + {0x1e40, "\xe1\xb9\x81"}, + {0x1e42, "\xe1\xb9\x83"}, + {0x1e44, "\xe1\xb9\x85"}, + {0x1e46, "\xe1\xb9\x87"}, + {0x1e48, "\xe1\xb9\x89"}, + {0x1e4a, "\xe1\xb9\x8b"}, + {0x1e4c, "\xe1\xb9\x8d"}, + {0x1e4e, "\xe1\xb9\x8f"}, + {0x1e50, "\xe1\xb9\x91"}, + {0x1e52, "\xe1\xb9\x93"}, + {0x1e54, "\xe1\xb9\x95"}, + {0x1e56, "\xe1\xb9\x97"}, + {0x1e58, "\xe1\xb9\x99"}, + {0x1e5a, "\xe1\xb9\x9b"}, + {0x1e5c, "\xe1\xb9\x9d"}, + {0x1e5e, "\xe1\xb9\x9f"}, + {0x1e60, "\xe1\xb9\xa1"}, + {0x1e62, "\xe1\xb9\xa3"}, + {0x1e64, "\xe1\xb9\xa5"}, + {0x1e66, "\xe1\xb9\xa7"}, + {0x1e68, "\xe1\xb9\xa9"}, + {0x1e6a, "\xe1\xb9\xab"}, + {0x1e6c, "\xe1\xb9\xad"}, + {0x1e6e, "\xe1\xb9\xaf"}, + {0x1e70, "\xe1\xb9\xb1"}, + {0x1e72, "\xe1\xb9\xb3"}, + {0x1e74, "\xe1\xb9\xb5"}, + {0x1e76, "\xe1\xb9\xb7"}, + {0x1e78, "\xe1\xb9\xb9"}, + {0x1e7a, "\xe1\xb9\xbb"}, + {0x1e7c, "\xe1\xb9\xbd"}, + {0x1e7e, "\xe1\xb9\xbf"}, + {0x1e80, "\xe1\xba\x81"}, + {0x1e82, "\xe1\xba\x83"}, + {0x1e84, "\xe1\xba\x85"}, + {0x1e86, "\xe1\xba\x87"}, + {0x1e88, "\xe1\xba\x89"}, + {0x1e8a, "\xe1\xba\x8b"}, + {0x1e8c, "\xe1\xba\x8d"}, + {0x1e8e, "\xe1\xba\x8f"}, + {0x1e90, "\xe1\xba\x91"}, + {0x1e92, "\xe1\xba\x93"}, + {0x1e94, "\xe1\xba\x95"}, + {0x1e9e, "\xc3\x9f"}, + {0x1ea0, "\xe1\xba\xa1"}, + {0x1ea2, "\xe1\xba\xa3"}, + {0x1ea4, "\xe1\xba\xa5"}, + {0x1ea6, "\xe1\xba\xa7"}, + {0x1ea8, "\xe1\xba\xa9"}, + {0x1eaa, "\xe1\xba\xab"}, + {0x1eac, "\xe1\xba\xad"}, + {0x1eae, "\xe1\xba\xaf"}, + {0x1eb0, "\xe1\xba\xb1"}, + {0x1eb2, "\xe1\xba\xb3"}, + {0x1eb4, "\xe1\xba\xb5"}, + {0x1eb6, "\xe1\xba\xb7"}, + {0x1eb8, "\xe1\xba\xb9"}, + {0x1eba, "\xe1\xba\xbb"}, + {0x1ebc, "\xe1\xba\xbd"}, + {0x1ebe, "\xe1\xba\xbf"}, + {0x1ec0, "\xe1\xbb\x81"}, + {0x1ec2, "\xe1\xbb\x83"}, + {0x1ec4, "\xe1\xbb\x85"}, + {0x1ec6, "\xe1\xbb\x87"}, + {0x1ec8, "\xe1\xbb\x89"}, + {0x1eca, "\xe1\xbb\x8b"}, + {0x1ecc, "\xe1\xbb\x8d"}, + {0x1ece, "\xe1\xbb\x8f"}, + {0x1ed0, "\xe1\xbb\x91"}, + {0x1ed2, "\xe1\xbb\x93"}, + {0x1ed4, "\xe1\xbb\x95"}, + {0x1ed6, "\xe1\xbb\x97"}, + {0x1ed8, "\xe1\xbb\x99"}, + {0x1eda, "\xe1\xbb\x9b"}, + {0x1edc, "\xe1\xbb\x9d"}, + {0x1ede, "\xe1\xbb\x9f"}, + {0x1ee0, "\xe1\xbb\xa1"}, + {0x1ee2, "\xe1\xbb\xa3"}, + {0x1ee4, "\xe1\xbb\xa5"}, + {0x1ee6, "\xe1\xbb\xa7"}, + {0x1ee8, "\xe1\xbb\xa9"}, + {0x1eea, "\xe1\xbb\xab"}, + {0x1eec, "\xe1\xbb\xad"}, + {0x1eee, "\xe1\xbb\xaf"}, + {0x1ef0, "\xe1\xbb\xb1"}, + {0x1ef2, "\xe1\xbb\xb3"}, + {0x1ef4, "\xe1\xbb\xb5"}, + {0x1ef6, "\xe1\xbb\xb7"}, + {0x1ef8, "\xe1\xbb\xb9"}, + {0x1efa, "\xe1\xbb\xbb"}, + {0x1efc, "\xe1\xbb\xbd"}, + {0x1efe, "\xe1\xbb\xbf"}, + {0x1f08, "\xe1\xbc\x80"}, + {0x1f09, "\xe1\xbc\x81"}, + {0x1f0a, "\xe1\xbc\x82"}, + {0x1f0b, "\xe1\xbc\x83"}, + {0x1f0c, "\xe1\xbc\x84"}, + {0x1f0d, "\xe1\xbc\x85"}, + {0x1f0e, "\xe1\xbc\x86"}, + {0x1f0f, "\xe1\xbc\x87"}, + {0x1f18, "\xe1\xbc\x90"}, + {0x1f19, "\xe1\xbc\x91"}, + {0x1f1a, "\xe1\xbc\x92"}, + {0x1f1b, "\xe1\xbc\x93"}, + {0x1f1c, "\xe1\xbc\x94"}, + {0x1f1d, "\xe1\xbc\x95"}, + {0x1f28, "\xe1\xbc\xa0"}, + {0x1f29, "\xe1\xbc\xa1"}, + {0x1f2a, "\xe1\xbc\xa2"}, + {0x1f2b, "\xe1\xbc\xa3"}, + {0x1f2c, "\xe1\xbc\xa4"}, + {0x1f2d, "\xe1\xbc\xa5"}, + {0x1f2e, "\xe1\xbc\xa6"}, + {0x1f2f, "\xe1\xbc\xa7"}, + {0x1f38, "\xe1\xbc\xb0"}, + {0x1f39, "\xe1\xbc\xb1"}, + {0x1f3a, "\xe1\xbc\xb2"}, + {0x1f3b, "\xe1\xbc\xb3"}, + {0x1f3c, "\xe1\xbc\xb4"}, + {0x1f3d, "\xe1\xbc\xb5"}, + {0x1f3e, "\xe1\xbc\xb6"}, + {0x1f3f, "\xe1\xbc\xb7"}, + {0x1f48, "\xe1\xbd\x80"}, + {0x1f49, "\xe1\xbd\x81"}, + {0x1f4a, "\xe1\xbd\x82"}, + {0x1f4b, "\xe1\xbd\x83"}, + {0x1f4c, "\xe1\xbd\x84"}, + {0x1f4d, "\xe1\xbd\x85"}, + {0x1f59, "\xe1\xbd\x91"}, + {0x1f5b, "\xe1\xbd\x93"}, + {0x1f5d, "\xe1\xbd\x95"}, + {0x1f5f, "\xe1\xbd\x97"}, + {0x1f68, "\xe1\xbd\xa0"}, + {0x1f69, "\xe1\xbd\xa1"}, + {0x1f6a, "\xe1\xbd\xa2"}, + {0x1f6b, "\xe1\xbd\xa3"}, + {0x1f6c, "\xe1\xbd\xa4"}, + {0x1f6d, "\xe1\xbd\xa5"}, + {0x1f6e, "\xe1\xbd\xa6"}, + {0x1f6f, "\xe1\xbd\xa7"}, + {0x1f88, "\xe1\xbe\x80"}, + {0x1f89, "\xe1\xbe\x81"}, + {0x1f8a, "\xe1\xbe\x82"}, + {0x1f8b, "\xe1\xbe\x83"}, + {0x1f8c, "\xe1\xbe\x84"}, + {0x1f8d, "\xe1\xbe\x85"}, + {0x1f8e, "\xe1\xbe\x86"}, + {0x1f8f, "\xe1\xbe\x87"}, + {0x1f98, "\xe1\xbe\x90"}, + {0x1f99, "\xe1\xbe\x91"}, + {0x1f9a, "\xe1\xbe\x92"}, + {0x1f9b, "\xe1\xbe\x93"}, + {0x1f9c, "\xe1\xbe\x94"}, + {0x1f9d, "\xe1\xbe\x95"}, + {0x1f9e, "\xe1\xbe\x96"}, + {0x1f9f, "\xe1\xbe\x97"}, + {0x1fa8, "\xe1\xbe\xa0"}, + {0x1fa9, "\xe1\xbe\xa1"}, + {0x1faa, "\xe1\xbe\xa2"}, + {0x1fab, "\xe1\xbe\xa3"}, + {0x1fac, "\xe1\xbe\xa4"}, + {0x1fad, "\xe1\xbe\xa5"}, + {0x1fae, "\xe1\xbe\xa6"}, + {0x1faf, "\xe1\xbe\xa7"}, + {0x1fb8, "\xe1\xbe\xb0"}, + {0x1fb9, "\xe1\xbe\xb1"}, + {0x1fba, "\xe1\xbd\xb0"}, + {0x1fbb, "\xe1\xbd\xb1"}, + {0x1fbc, "\xe1\xbe\xb3"}, + {0x1fc8, "\xe1\xbd\xb2"}, + {0x1fc9, "\xe1\xbd\xb3"}, + {0x1fca, "\xe1\xbd\xb4"}, + {0x1fcb, "\xe1\xbd\xb5"}, + {0x1fcc, "\xe1\xbf\x83"}, + {0x1fd8, "\xe1\xbf\x90"}, + {0x1fd9, "\xe1\xbf\x91"}, + {0x1fda, "\xe1\xbd\xb6"}, + {0x1fdb, "\xe1\xbd\xb7"}, + {0x1fe8, "\xe1\xbf\xa0"}, + {0x1fe9, "\xe1\xbf\xa1"}, + {0x1fea, "\xe1\xbd\xba"}, + {0x1feb, "\xe1\xbd\xbb"}, + {0x1fec, "\xe1\xbf\xa5"}, + {0x1ff8, "\xe1\xbd\xb8"}, + {0x1ff9, "\xe1\xbd\xb9"}, + {0x1ffa, "\xe1\xbd\xbc"}, + {0x1ffb, "\xe1\xbd\xbd"}, + {0x1ffc, "\xe1\xbf\xb3"}, + {0x2126, "\xcf\x89"}, + {0x212a, "k"}, + {0x212b, "\xc3\xa5"}, + {0x2132, "\xe2\x85\x8e"}, + {0x2160, "\xe2\x85\xb0"}, + {0x2161, "\xe2\x85\xb1"}, + {0x2162, "\xe2\x85\xb2"}, + {0x2163, "\xe2\x85\xb3"}, + {0x2164, "\xe2\x85\xb4"}, + {0x2165, "\xe2\x85\xb5"}, + {0x2166, "\xe2\x85\xb6"}, + {0x2167, "\xe2\x85\xb7"}, + {0x2168, "\xe2\x85\xb8"}, + {0x2169, "\xe2\x85\xb9"}, + {0x216a, "\xe2\x85\xba"}, + {0x216b, "\xe2\x85\xbb"}, + {0x216c, "\xe2\x85\xbc"}, + {0x216d, "\xe2\x85\xbd"}, + {0x216e, "\xe2\x85\xbe"}, + {0x216f, "\xe2\x85\xbf"}, + {0x2183, "\xe2\x86\x84"}, + {0x24b6, "\xe2\x93\x90"}, + {0x24b7, "\xe2\x93\x91"}, + {0x24b8, "\xe2\x93\x92"}, + {0x24b9, "\xe2\x93\x93"}, + {0x24ba, "\xe2\x93\x94"}, + {0x24bb, "\xe2\x93\x95"}, + {0x24bc, "\xe2\x93\x96"}, + {0x24bd, "\xe2\x93\x97"}, + {0x24be, "\xe2\x93\x98"}, + {0x24bf, "\xe2\x93\x99"}, + {0x24c0, "\xe2\x93\x9a"}, + {0x24c1, "\xe2\x93\x9b"}, + {0x24c2, "\xe2\x93\x9c"}, + {0x24c3, "\xe2\x93\x9d"}, + {0x24c4, "\xe2\x93\x9e"}, + {0x24c5, "\xe2\x93\x9f"}, + {0x24c6, "\xe2\x93\xa0"}, + {0x24c7, "\xe2\x93\xa1"}, + {0x24c8, "\xe2\x93\xa2"}, + {0x24c9, "\xe2\x93\xa3"}, + {0x24ca, "\xe2\x93\xa4"}, + {0x24cb, "\xe2\x93\xa5"}, + {0x24cc, "\xe2\x93\xa6"}, + {0x24cd, "\xe2\x93\xa7"}, + {0x24ce, "\xe2\x93\xa8"}, + {0x24cf, "\xe2\x93\xa9"}, + {0x2c00, "\xe2\xb0\xb0"}, + {0x2c01, "\xe2\xb0\xb1"}, + {0x2c02, "\xe2\xb0\xb2"}, + {0x2c03, "\xe2\xb0\xb3"}, + {0x2c04, "\xe2\xb0\xb4"}, + {0x2c05, "\xe2\xb0\xb5"}, + {0x2c06, "\xe2\xb0\xb6"}, + {0x2c07, "\xe2\xb0\xb7"}, + {0x2c08, "\xe2\xb0\xb8"}, + {0x2c09, "\xe2\xb0\xb9"}, + {0x2c0a, "\xe2\xb0\xba"}, + {0x2c0b, "\xe2\xb0\xbb"}, + {0x2c0c, "\xe2\xb0\xbc"}, + {0x2c0d, "\xe2\xb0\xbd"}, + {0x2c0e, "\xe2\xb0\xbe"}, + {0x2c0f, "\xe2\xb0\xbf"}, + {0x2c10, "\xe2\xb1\x80"}, + {0x2c11, "\xe2\xb1\x81"}, + {0x2c12, "\xe2\xb1\x82"}, + {0x2c13, "\xe2\xb1\x83"}, + {0x2c14, "\xe2\xb1\x84"}, + {0x2c15, "\xe2\xb1\x85"}, + {0x2c16, "\xe2\xb1\x86"}, + {0x2c17, "\xe2\xb1\x87"}, + {0x2c18, "\xe2\xb1\x88"}, + {0x2c19, "\xe2\xb1\x89"}, + {0x2c1a, "\xe2\xb1\x8a"}, + {0x2c1b, "\xe2\xb1\x8b"}, + {0x2c1c, "\xe2\xb1\x8c"}, + {0x2c1d, "\xe2\xb1\x8d"}, + {0x2c1e, "\xe2\xb1\x8e"}, + {0x2c1f, "\xe2\xb1\x8f"}, + {0x2c20, "\xe2\xb1\x90"}, + {0x2c21, "\xe2\xb1\x91"}, + {0x2c22, "\xe2\xb1\x92"}, + {0x2c23, "\xe2\xb1\x93"}, + {0x2c24, "\xe2\xb1\x94"}, + {0x2c25, "\xe2\xb1\x95"}, + {0x2c26, "\xe2\xb1\x96"}, + {0x2c27, "\xe2\xb1\x97"}, + {0x2c28, "\xe2\xb1\x98"}, + {0x2c29, "\xe2\xb1\x99"}, + {0x2c2a, "\xe2\xb1\x9a"}, + {0x2c2b, "\xe2\xb1\x9b"}, + {0x2c2c, "\xe2\xb1\x9c"}, + {0x2c2d, "\xe2\xb1\x9d"}, + {0x2c2e, "\xe2\xb1\x9e"}, + {0x2c2f, "\xe2\xb1\x9f"}, + {0x2c60, "\xe2\xb1\xa1"}, + {0x2c62, "\xc9\xab"}, + {0x2c63, "\xe1\xb5\xbd"}, + {0x2c64, "\xc9\xbd"}, + {0x2c67, "\xe2\xb1\xa8"}, + {0x2c69, "\xe2\xb1\xaa"}, + {0x2c6b, "\xe2\xb1\xac"}, + {0x2c6d, "\xc9\x91"}, + {0x2c6e, "\xc9\xb1"}, + {0x2c6f, "\xc9\x90"}, + {0x2c70, "\xc9\x92"}, + {0x2c72, "\xe2\xb1\xb3"}, + {0x2c75, "\xe2\xb1\xb6"}, + {0x2c7e, "\xc8\xbf"}, + {0x2c7f, "\xc9\x80"}, + {0x2c80, "\xe2\xb2\x81"}, + {0x2c82, "\xe2\xb2\x83"}, + {0x2c84, "\xe2\xb2\x85"}, + {0x2c86, "\xe2\xb2\x87"}, + {0x2c88, "\xe2\xb2\x89"}, + {0x2c8a, "\xe2\xb2\x8b"}, + {0x2c8c, "\xe2\xb2\x8d"}, + {0x2c8e, "\xe2\xb2\x8f"}, + {0x2c90, "\xe2\xb2\x91"}, + {0x2c92, "\xe2\xb2\x93"}, + {0x2c94, "\xe2\xb2\x95"}, + {0x2c96, "\xe2\xb2\x97"}, + {0x2c98, "\xe2\xb2\x99"}, + {0x2c9a, "\xe2\xb2\x9b"}, + {0x2c9c, "\xe2\xb2\x9d"}, + {0x2c9e, "\xe2\xb2\x9f"}, + {0x2ca0, "\xe2\xb2\xa1"}, + {0x2ca2, "\xe2\xb2\xa3"}, + {0x2ca4, "\xe2\xb2\xa5"}, + {0x2ca6, "\xe2\xb2\xa7"}, + {0x2ca8, "\xe2\xb2\xa9"}, + {0x2caa, "\xe2\xb2\xab"}, + {0x2cac, "\xe2\xb2\xad"}, + {0x2cae, "\xe2\xb2\xaf"}, + {0x2cb0, "\xe2\xb2\xb1"}, + {0x2cb2, "\xe2\xb2\xb3"}, + {0x2cb4, "\xe2\xb2\xb5"}, + {0x2cb6, "\xe2\xb2\xb7"}, + {0x2cb8, "\xe2\xb2\xb9"}, + {0x2cba, "\xe2\xb2\xbb"}, + {0x2cbc, "\xe2\xb2\xbd"}, + {0x2cbe, "\xe2\xb2\xbf"}, + {0x2cc0, "\xe2\xb3\x81"}, + {0x2cc2, "\xe2\xb3\x83"}, + {0x2cc4, "\xe2\xb3\x85"}, + {0x2cc6, "\xe2\xb3\x87"}, + {0x2cc8, "\xe2\xb3\x89"}, + {0x2cca, "\xe2\xb3\x8b"}, + {0x2ccc, "\xe2\xb3\x8d"}, + {0x2cce, "\xe2\xb3\x8f"}, + {0x2cd0, "\xe2\xb3\x91"}, + {0x2cd2, "\xe2\xb3\x93"}, + {0x2cd4, "\xe2\xb3\x95"}, + {0x2cd6, "\xe2\xb3\x97"}, + {0x2cd8, "\xe2\xb3\x99"}, + {0x2cda, "\xe2\xb3\x9b"}, + {0x2cdc, "\xe2\xb3\x9d"}, + {0x2cde, "\xe2\xb3\x9f"}, + {0x2ce0, "\xe2\xb3\xa1"}, + {0x2ce2, "\xe2\xb3\xa3"}, + {0x2ceb, "\xe2\xb3\xac"}, + {0x2ced, "\xe2\xb3\xae"}, + {0x2cf2, "\xe2\xb3\xb3"}, + {0xa640, "\xea\x99\x81"}, + {0xa642, "\xea\x99\x83"}, + {0xa644, "\xea\x99\x85"}, + {0xa646, "\xea\x99\x87"}, + {0xa648, "\xea\x99\x89"}, + {0xa64a, "\xea\x99\x8b"}, + {0xa64c, "\xea\x99\x8d"}, + {0xa64e, "\xea\x99\x8f"}, + {0xa650, "\xea\x99\x91"}, + {0xa652, "\xea\x99\x93"}, + {0xa654, "\xea\x99\x95"}, + {0xa656, "\xea\x99\x97"}, + {0xa658, "\xea\x99\x99"}, + {0xa65a, "\xea\x99\x9b"}, + {0xa65c, "\xea\x99\x9d"}, + {0xa65e, "\xea\x99\x9f"}, + {0xa660, "\xea\x99\xa1"}, + {0xa662, "\xea\x99\xa3"}, + {0xa664, "\xea\x99\xa5"}, + {0xa666, "\xea\x99\xa7"}, + {0xa668, "\xea\x99\xa9"}, + {0xa66a, "\xea\x99\xab"}, + {0xa66c, "\xea\x99\xad"}, + {0xa680, "\xea\x9a\x81"}, + {0xa682, "\xea\x9a\x83"}, + {0xa684, "\xea\x9a\x85"}, + {0xa686, "\xea\x9a\x87"}, + {0xa688, "\xea\x9a\x89"}, + {0xa68a, "\xea\x9a\x8b"}, + {0xa68c, "\xea\x9a\x8d"}, + {0xa68e, "\xea\x9a\x8f"}, + {0xa690, "\xea\x9a\x91"}, + {0xa692, "\xea\x9a\x93"}, + {0xa694, "\xea\x9a\x95"}, + {0xa696, "\xea\x9a\x97"}, + {0xa698, "\xea\x9a\x99"}, + {0xa69a, "\xea\x9a\x9b"}, + {0xa722, "\xea\x9c\xa3"}, + {0xa724, "\xea\x9c\xa5"}, + {0xa726, "\xea\x9c\xa7"}, + {0xa728, "\xea\x9c\xa9"}, + {0xa72a, "\xea\x9c\xab"}, + {0xa72c, "\xea\x9c\xad"}, + {0xa72e, "\xea\x9c\xaf"}, + {0xa732, "\xea\x9c\xb3"}, + {0xa734, "\xea\x9c\xb5"}, + {0xa736, "\xea\x9c\xb7"}, + {0xa738, "\xea\x9c\xb9"}, + {0xa73a, "\xea\x9c\xbb"}, + {0xa73c, "\xea\x9c\xbd"}, + {0xa73e, "\xea\x9c\xbf"}, + {0xa740, "\xea\x9d\x81"}, + {0xa742, "\xea\x9d\x83"}, + {0xa744, "\xea\x9d\x85"}, + {0xa746, "\xea\x9d\x87"}, + {0xa748, "\xea\x9d\x89"}, + {0xa74a, "\xea\x9d\x8b"}, + {0xa74c, "\xea\x9d\x8d"}, + {0xa74e, "\xea\x9d\x8f"}, + {0xa750, "\xea\x9d\x91"}, + {0xa752, "\xea\x9d\x93"}, + {0xa754, "\xea\x9d\x95"}, + {0xa756, "\xea\x9d\x97"}, + {0xa758, "\xea\x9d\x99"}, + {0xa75a, "\xea\x9d\x9b"}, + {0xa75c, "\xea\x9d\x9d"}, + {0xa75e, "\xea\x9d\x9f"}, + {0xa760, "\xea\x9d\xa1"}, + {0xa762, "\xea\x9d\xa3"}, + {0xa764, "\xea\x9d\xa5"}, + {0xa766, "\xea\x9d\xa7"}, + {0xa768, "\xea\x9d\xa9"}, + {0xa76a, "\xea\x9d\xab"}, + {0xa76c, "\xea\x9d\xad"}, + {0xa76e, "\xea\x9d\xaf"}, + {0xa779, "\xea\x9d\xba"}, + {0xa77b, "\xea\x9d\xbc"}, + {0xa77d, "\xe1\xb5\xb9"}, + {0xa77e, "\xea\x9d\xbf"}, + {0xa780, "\xea\x9e\x81"}, + {0xa782, "\xea\x9e\x83"}, + {0xa784, "\xea\x9e\x85"}, + {0xa786, "\xea\x9e\x87"}, + {0xa78b, "\xea\x9e\x8c"}, + {0xa78d, "\xc9\xa5"}, + {0xa790, "\xea\x9e\x91"}, + {0xa792, "\xea\x9e\x93"}, + {0xa796, "\xea\x9e\x97"}, + {0xa798, "\xea\x9e\x99"}, + {0xa79a, "\xea\x9e\x9b"}, + {0xa79c, "\xea\x9e\x9d"}, + {0xa79e, "\xea\x9e\x9f"}, + {0xa7a0, "\xea\x9e\xa1"}, + {0xa7a2, "\xea\x9e\xa3"}, + {0xa7a4, "\xea\x9e\xa5"}, + {0xa7a6, "\xea\x9e\xa7"}, + {0xa7a8, "\xea\x9e\xa9"}, + {0xa7aa, "\xc9\xa6"}, + {0xa7ab, "\xc9\x9c"}, + {0xa7ac, "\xc9\xa1"}, + {0xa7ad, "\xc9\xac"}, + {0xa7ae, "\xc9\xaa"}, + {0xa7b0, "\xca\x9e"}, + {0xa7b1, "\xca\x87"}, + {0xa7b2, "\xca\x9d"}, + {0xa7b3, "\xea\xad\x93"}, + {0xa7b4, "\xea\x9e\xb5"}, + {0xa7b6, "\xea\x9e\xb7"}, + {0xa7b8, "\xea\x9e\xb9"}, + {0xa7ba, "\xea\x9e\xbb"}, + {0xa7bc, "\xea\x9e\xbd"}, + {0xa7be, "\xea\x9e\xbf"}, + {0xa7c0, "\xea\x9f\x81"}, + {0xa7c2, "\xea\x9f\x83"}, + {0xa7c4, "\xea\x9e\x94"}, + {0xa7c5, "\xca\x82"}, + {0xa7c6, "\xe1\xb6\x8e"}, + {0xa7c7, "\xea\x9f\x88"}, + {0xa7c9, "\xea\x9f\x8a"}, + {0xa7cb, "\xc9\xa4"}, + {0xa7cc, "\xea\x9f\x8d"}, + {0xa7d0, "\xea\x9f\x91"}, + {0xa7d6, "\xea\x9f\x97"}, + {0xa7d8, "\xea\x9f\x99"}, + {0xa7da, "\xea\x9f\x9b"}, + {0xa7dc, "\xc6\x9b"}, + {0xa7f5, "\xea\x9f\xb6"}, + {0xff21, "\xef\xbd\x81"}, + {0xff22, "\xef\xbd\x82"}, + {0xff23, "\xef\xbd\x83"}, + {0xff24, "\xef\xbd\x84"}, + {0xff25, "\xef\xbd\x85"}, + {0xff26, "\xef\xbd\x86"}, + {0xff27, "\xef\xbd\x87"}, + {0xff28, "\xef\xbd\x88"}, + {0xff29, "\xef\xbd\x89"}, + {0xff2a, "\xef\xbd\x8a"}, + {0xff2b, "\xef\xbd\x8b"}, + {0xff2c, "\xef\xbd\x8c"}, + {0xff2d, "\xef\xbd\x8d"}, + {0xff2e, "\xef\xbd\x8e"}, + {0xff2f, "\xef\xbd\x8f"}, + {0xff30, "\xef\xbd\x90"}, + {0xff31, "\xef\xbd\x91"}, + {0xff32, "\xef\xbd\x92"}, + {0xff33, "\xef\xbd\x93"}, + {0xff34, "\xef\xbd\x94"}, + {0xff35, "\xef\xbd\x95"}, + {0xff36, "\xef\xbd\x96"}, + {0xff37, "\xef\xbd\x97"}, + {0xff38, "\xef\xbd\x98"}, + {0xff39, "\xef\xbd\x99"}, + {0xff3a, "\xef\xbd\x9a"}, + {0x10400, "\xf0\x90\x90\xa8"}, + {0x10401, "\xf0\x90\x90\xa9"}, + {0x10402, "\xf0\x90\x90\xaa"}, + {0x10403, "\xf0\x90\x90\xab"}, + {0x10404, "\xf0\x90\x90\xac"}, + {0x10405, "\xf0\x90\x90\xad"}, + {0x10406, "\xf0\x90\x90\xae"}, + {0x10407, "\xf0\x90\x90\xaf"}, + {0x10408, "\xf0\x90\x90\xb0"}, + {0x10409, "\xf0\x90\x90\xb1"}, + {0x1040a, "\xf0\x90\x90\xb2"}, + {0x1040b, "\xf0\x90\x90\xb3"}, + {0x1040c, "\xf0\x90\x90\xb4"}, + {0x1040d, "\xf0\x90\x90\xb5"}, + {0x1040e, "\xf0\x90\x90\xb6"}, + {0x1040f, "\xf0\x90\x90\xb7"}, + {0x10410, "\xf0\x90\x90\xb8"}, + {0x10411, "\xf0\x90\x90\xb9"}, + {0x10412, "\xf0\x90\x90\xba"}, + {0x10413, "\xf0\x90\x90\xbb"}, + {0x10414, "\xf0\x90\x90\xbc"}, + {0x10415, "\xf0\x90\x90\xbd"}, + {0x10416, "\xf0\x90\x90\xbe"}, + {0x10417, "\xf0\x90\x90\xbf"}, + {0x10418, "\xf0\x90\x91\x80"}, + {0x10419, "\xf0\x90\x91\x81"}, + {0x1041a, "\xf0\x90\x91\x82"}, + {0x1041b, "\xf0\x90\x91\x83"}, + {0x1041c, "\xf0\x90\x91\x84"}, + {0x1041d, "\xf0\x90\x91\x85"}, + {0x1041e, "\xf0\x90\x91\x86"}, + {0x1041f, "\xf0\x90\x91\x87"}, + {0x10420, "\xf0\x90\x91\x88"}, + {0x10421, "\xf0\x90\x91\x89"}, + {0x10422, "\xf0\x90\x91\x8a"}, + {0x10423, "\xf0\x90\x91\x8b"}, + {0x10424, "\xf0\x90\x91\x8c"}, + {0x10425, "\xf0\x90\x91\x8d"}, + {0x10426, "\xf0\x90\x91\x8e"}, + {0x10427, "\xf0\x90\x91\x8f"}, + {0x104b0, "\xf0\x90\x93\x98"}, + {0x104b1, "\xf0\x90\x93\x99"}, + {0x104b2, "\xf0\x90\x93\x9a"}, + {0x104b3, "\xf0\x90\x93\x9b"}, + {0x104b4, "\xf0\x90\x93\x9c"}, + {0x104b5, "\xf0\x90\x93\x9d"}, + {0x104b6, "\xf0\x90\x93\x9e"}, + {0x104b7, "\xf0\x90\x93\x9f"}, + {0x104b8, "\xf0\x90\x93\xa0"}, + {0x104b9, "\xf0\x90\x93\xa1"}, + {0x104ba, "\xf0\x90\x93\xa2"}, + {0x104bb, "\xf0\x90\x93\xa3"}, + {0x104bc, "\xf0\x90\x93\xa4"}, + {0x104bd, "\xf0\x90\x93\xa5"}, + {0x104be, "\xf0\x90\x93\xa6"}, + {0x104bf, "\xf0\x90\x93\xa7"}, + {0x104c0, "\xf0\x90\x93\xa8"}, + {0x104c1, "\xf0\x90\x93\xa9"}, + {0x104c2, "\xf0\x90\x93\xaa"}, + {0x104c3, "\xf0\x90\x93\xab"}, + {0x104c4, "\xf0\x90\x93\xac"}, + {0x104c5, "\xf0\x90\x93\xad"}, + {0x104c6, "\xf0\x90\x93\xae"}, + {0x104c7, "\xf0\x90\x93\xaf"}, + {0x104c8, "\xf0\x90\x93\xb0"}, + {0x104c9, "\xf0\x90\x93\xb1"}, + {0x104ca, "\xf0\x90\x93\xb2"}, + {0x104cb, "\xf0\x90\x93\xb3"}, + {0x104cc, "\xf0\x90\x93\xb4"}, + {0x104cd, "\xf0\x90\x93\xb5"}, + {0x104ce, "\xf0\x90\x93\xb6"}, + {0x104cf, "\xf0\x90\x93\xb7"}, + {0x104d0, "\xf0\x90\x93\xb8"}, + {0x104d1, "\xf0\x90\x93\xb9"}, + {0x104d2, "\xf0\x90\x93\xba"}, + {0x104d3, "\xf0\x90\x93\xbb"}, + {0x10570, "\xf0\x90\x96\x97"}, + {0x10571, "\xf0\x90\x96\x98"}, + {0x10572, "\xf0\x90\x96\x99"}, + {0x10573, "\xf0\x90\x96\x9a"}, + {0x10574, "\xf0\x90\x96\x9b"}, + {0x10575, "\xf0\x90\x96\x9c"}, + {0x10576, "\xf0\x90\x96\x9d"}, + {0x10577, "\xf0\x90\x96\x9e"}, + {0x10578, "\xf0\x90\x96\x9f"}, + {0x10579, "\xf0\x90\x96\xa0"}, + {0x1057a, "\xf0\x90\x96\xa1"}, + {0x1057c, "\xf0\x90\x96\xa3"}, + {0x1057d, "\xf0\x90\x96\xa4"}, + {0x1057e, "\xf0\x90\x96\xa5"}, + {0x1057f, "\xf0\x90\x96\xa6"}, + {0x10580, "\xf0\x90\x96\xa7"}, + {0x10581, "\xf0\x90\x96\xa8"}, + {0x10582, "\xf0\x90\x96\xa9"}, + {0x10583, "\xf0\x90\x96\xaa"}, + {0x10584, "\xf0\x90\x96\xab"}, + {0x10585, "\xf0\x90\x96\xac"}, + {0x10586, "\xf0\x90\x96\xad"}, + {0x10587, "\xf0\x90\x96\xae"}, + {0x10588, "\xf0\x90\x96\xaf"}, + {0x10589, "\xf0\x90\x96\xb0"}, + {0x1058a, "\xf0\x90\x96\xb1"}, + {0x1058c, "\xf0\x90\x96\xb3"}, + {0x1058d, "\xf0\x90\x96\xb4"}, + {0x1058e, "\xf0\x90\x96\xb5"}, + {0x1058f, "\xf0\x90\x96\xb6"}, + {0x10590, "\xf0\x90\x96\xb7"}, + {0x10591, "\xf0\x90\x96\xb8"}, + {0x10592, "\xf0\x90\x96\xb9"}, + {0x10594, "\xf0\x90\x96\xbb"}, + {0x10595, "\xf0\x90\x96\xbc"}, + {0x10c80, "\xf0\x90\xb3\x80"}, + {0x10c81, "\xf0\x90\xb3\x81"}, + {0x10c82, "\xf0\x90\xb3\x82"}, + {0x10c83, "\xf0\x90\xb3\x83"}, + {0x10c84, "\xf0\x90\xb3\x84"}, + {0x10c85, "\xf0\x90\xb3\x85"}, + {0x10c86, "\xf0\x90\xb3\x86"}, + {0x10c87, "\xf0\x90\xb3\x87"}, + {0x10c88, "\xf0\x90\xb3\x88"}, + {0x10c89, "\xf0\x90\xb3\x89"}, + {0x10c8a, "\xf0\x90\xb3\x8a"}, + {0x10c8b, "\xf0\x90\xb3\x8b"}, + {0x10c8c, "\xf0\x90\xb3\x8c"}, + {0x10c8d, "\xf0\x90\xb3\x8d"}, + {0x10c8e, "\xf0\x90\xb3\x8e"}, + {0x10c8f, "\xf0\x90\xb3\x8f"}, + {0x10c90, "\xf0\x90\xb3\x90"}, + {0x10c91, "\xf0\x90\xb3\x91"}, + {0x10c92, "\xf0\x90\xb3\x92"}, + {0x10c93, "\xf0\x90\xb3\x93"}, + {0x10c94, "\xf0\x90\xb3\x94"}, + {0x10c95, "\xf0\x90\xb3\x95"}, + {0x10c96, "\xf0\x90\xb3\x96"}, + {0x10c97, "\xf0\x90\xb3\x97"}, + {0x10c98, "\xf0\x90\xb3\x98"}, + {0x10c99, "\xf0\x90\xb3\x99"}, + {0x10c9a, "\xf0\x90\xb3\x9a"}, + {0x10c9b, "\xf0\x90\xb3\x9b"}, + {0x10c9c, "\xf0\x90\xb3\x9c"}, + {0x10c9d, "\xf0\x90\xb3\x9d"}, + {0x10c9e, "\xf0\x90\xb3\x9e"}, + {0x10c9f, "\xf0\x90\xb3\x9f"}, + {0x10ca0, "\xf0\x90\xb3\xa0"}, + {0x10ca1, "\xf0\x90\xb3\xa1"}, + {0x10ca2, "\xf0\x90\xb3\xa2"}, + {0x10ca3, "\xf0\x90\xb3\xa3"}, + {0x10ca4, "\xf0\x90\xb3\xa4"}, + {0x10ca5, "\xf0\x90\xb3\xa5"}, + {0x10ca6, "\xf0\x90\xb3\xa6"}, + {0x10ca7, "\xf0\x90\xb3\xa7"}, + {0x10ca8, "\xf0\x90\xb3\xa8"}, + {0x10ca9, "\xf0\x90\xb3\xa9"}, + {0x10caa, "\xf0\x90\xb3\xaa"}, + {0x10cab, "\xf0\x90\xb3\xab"}, + {0x10cac, "\xf0\x90\xb3\xac"}, + {0x10cad, "\xf0\x90\xb3\xad"}, + {0x10cae, "\xf0\x90\xb3\xae"}, + {0x10caf, "\xf0\x90\xb3\xaf"}, + {0x10cb0, "\xf0\x90\xb3\xb0"}, + {0x10cb1, "\xf0\x90\xb3\xb1"}, + {0x10cb2, "\xf0\x90\xb3\xb2"}, + {0x10d50, "\xf0\x90\xb5\xb0"}, + {0x10d51, "\xf0\x90\xb5\xb1"}, + {0x10d52, "\xf0\x90\xb5\xb2"}, + {0x10d53, "\xf0\x90\xb5\xb3"}, + {0x10d54, "\xf0\x90\xb5\xb4"}, + {0x10d55, "\xf0\x90\xb5\xb5"}, + {0x10d56, "\xf0\x90\xb5\xb6"}, + {0x10d57, "\xf0\x90\xb5\xb7"}, + {0x10d58, "\xf0\x90\xb5\xb8"}, + {0x10d59, "\xf0\x90\xb5\xb9"}, + {0x10d5a, "\xf0\x90\xb5\xba"}, + {0x10d5b, "\xf0\x90\xb5\xbb"}, + {0x10d5c, "\xf0\x90\xb5\xbc"}, + {0x10d5d, "\xf0\x90\xb5\xbd"}, + {0x10d5e, "\xf0\x90\xb5\xbe"}, + {0x10d5f, "\xf0\x90\xb5\xbf"}, + {0x10d60, "\xf0\x90\xb6\x80"}, + {0x10d61, "\xf0\x90\xb6\x81"}, + {0x10d62, "\xf0\x90\xb6\x82"}, + {0x10d63, "\xf0\x90\xb6\x83"}, + {0x10d64, "\xf0\x90\xb6\x84"}, + {0x10d65, "\xf0\x90\xb6\x85"}, + {0x118a0, "\xf0\x91\xa3\x80"}, + {0x118a1, "\xf0\x91\xa3\x81"}, + {0x118a2, "\xf0\x91\xa3\x82"}, + {0x118a3, "\xf0\x91\xa3\x83"}, + {0x118a4, "\xf0\x91\xa3\x84"}, + {0x118a5, "\xf0\x91\xa3\x85"}, + {0x118a6, "\xf0\x91\xa3\x86"}, + {0x118a7, "\xf0\x91\xa3\x87"}, + {0x118a8, "\xf0\x91\xa3\x88"}, + {0x118a9, "\xf0\x91\xa3\x89"}, + {0x118aa, "\xf0\x91\xa3\x8a"}, + {0x118ab, "\xf0\x91\xa3\x8b"}, + {0x118ac, "\xf0\x91\xa3\x8c"}, + {0x118ad, "\xf0\x91\xa3\x8d"}, + {0x118ae, "\xf0\x91\xa3\x8e"}, + {0x118af, "\xf0\x91\xa3\x8f"}, + {0x118b0, "\xf0\x91\xa3\x90"}, + {0x118b1, "\xf0\x91\xa3\x91"}, + {0x118b2, "\xf0\x91\xa3\x92"}, + {0x118b3, "\xf0\x91\xa3\x93"}, + {0x118b4, "\xf0\x91\xa3\x94"}, + {0x118b5, "\xf0\x91\xa3\x95"}, + {0x118b6, "\xf0\x91\xa3\x96"}, + {0x118b7, "\xf0\x91\xa3\x97"}, + {0x118b8, "\xf0\x91\xa3\x98"}, + {0x118b9, "\xf0\x91\xa3\x99"}, + {0x118ba, "\xf0\x91\xa3\x9a"}, + {0x118bb, "\xf0\x91\xa3\x9b"}, + {0x118bc, "\xf0\x91\xa3\x9c"}, + {0x118bd, "\xf0\x91\xa3\x9d"}, + {0x118be, "\xf0\x91\xa3\x9e"}, + {0x118bf, "\xf0\x91\xa3\x9f"}, + {0x16e40, "\xf0\x96\xb9\xa0"}, + {0x16e41, "\xf0\x96\xb9\xa1"}, + {0x16e42, "\xf0\x96\xb9\xa2"}, + {0x16e43, "\xf0\x96\xb9\xa3"}, + {0x16e44, "\xf0\x96\xb9\xa4"}, + {0x16e45, "\xf0\x96\xb9\xa5"}, + {0x16e46, "\xf0\x96\xb9\xa6"}, + {0x16e47, "\xf0\x96\xb9\xa7"}, + {0x16e48, "\xf0\x96\xb9\xa8"}, + {0x16e49, "\xf0\x96\xb9\xa9"}, + {0x16e4a, "\xf0\x96\xb9\xaa"}, + {0x16e4b, "\xf0\x96\xb9\xab"}, + {0x16e4c, "\xf0\x96\xb9\xac"}, + {0x16e4d, "\xf0\x96\xb9\xad"}, + {0x16e4e, "\xf0\x96\xb9\xae"}, + {0x16e4f, "\xf0\x96\xb9\xaf"}, + {0x16e50, "\xf0\x96\xb9\xb0"}, + {0x16e51, "\xf0\x96\xb9\xb1"}, + {0x16e52, "\xf0\x96\xb9\xb2"}, + {0x16e53, "\xf0\x96\xb9\xb3"}, + {0x16e54, "\xf0\x96\xb9\xb4"}, + {0x16e55, "\xf0\x96\xb9\xb5"}, + {0x16e56, "\xf0\x96\xb9\xb6"}, + {0x16e57, "\xf0\x96\xb9\xb7"}, + {0x16e58, "\xf0\x96\xb9\xb8"}, + {0x16e59, "\xf0\x96\xb9\xb9"}, + {0x16e5a, "\xf0\x96\xb9\xba"}, + {0x16e5b, "\xf0\x96\xb9\xbb"}, + {0x16e5c, "\xf0\x96\xb9\xbc"}, + {0x16e5d, "\xf0\x96\xb9\xbd"}, + {0x16e5e, "\xf0\x96\xb9\xbe"}, + {0x16e5f, "\xf0\x96\xb9\xbf"}, + {0x1e900, "\xf0\x9e\xa4\xa2"}, + {0x1e901, "\xf0\x9e\xa4\xa3"}, + {0x1e902, "\xf0\x9e\xa4\xa4"}, + {0x1e903, "\xf0\x9e\xa4\xa5"}, + {0x1e904, "\xf0\x9e\xa4\xa6"}, + {0x1e905, "\xf0\x9e\xa4\xa7"}, + {0x1e906, "\xf0\x9e\xa4\xa8"}, + {0x1e907, "\xf0\x9e\xa4\xa9"}, + {0x1e908, "\xf0\x9e\xa4\xaa"}, + {0x1e909, "\xf0\x9e\xa4\xab"}, + {0x1e90a, "\xf0\x9e\xa4\xac"}, + {0x1e90b, "\xf0\x9e\xa4\xad"}, + {0x1e90c, "\xf0\x9e\xa4\xae"}, + {0x1e90d, "\xf0\x9e\xa4\xaf"}, + {0x1e90e, "\xf0\x9e\xa4\xb0"}, + {0x1e90f, "\xf0\x9e\xa4\xb1"}, + {0x1e910, "\xf0\x9e\xa4\xb2"}, + {0x1e911, "\xf0\x9e\xa4\xb3"}, + {0x1e912, "\xf0\x9e\xa4\xb4"}, + {0x1e913, "\xf0\x9e\xa4\xb5"}, + {0x1e914, "\xf0\x9e\xa4\xb6"}, + {0x1e915, "\xf0\x9e\xa4\xb7"}, + {0x1e916, "\xf0\x9e\xa4\xb8"}, + {0x1e917, "\xf0\x9e\xa4\xb9"}, + {0x1e918, "\xf0\x9e\xa4\xba"}, + {0x1e919, "\xf0\x9e\xa4\xbb"}, + {0x1e91a, "\xf0\x9e\xa4\xbc"}, + {0x1e91b, "\xf0\x9e\xa4\xbd"}, + {0x1e91c, "\xf0\x9e\xa4\xbe"}, + {0x1e91d, "\xf0\x9e\xa4\xbf"}, + {0x1e91e, "\xf0\x9e\xa5\x80"}, + {0x1e91f, "\xf0\x9e\xa5\x81"}, + {0x1e920, "\xf0\x9e\xa5\x82"}, + {0x1e921, "\xf0\x9e\xa5\x83"}, + }}; + + // Uppercase mappings, for the code points that change, sorted. + constexpr std::array Upper = {{ + {0x61, "A"}, + {0x62, "B"}, + {0x63, "C"}, + {0x64, "D"}, + {0x65, "E"}, + {0x66, "F"}, + {0x67, "G"}, + {0x68, "H"}, + {0x69, "I"}, + {0x6a, "J"}, + {0x6b, "K"}, + {0x6c, "L"}, + {0x6d, "M"}, + {0x6e, "N"}, + {0x6f, "O"}, + {0x70, "P"}, + {0x71, "Q"}, + {0x72, "R"}, + {0x73, "S"}, + {0x74, "T"}, + {0x75, "U"}, + {0x76, "V"}, + {0x77, "W"}, + {0x78, "X"}, + {0x79, "Y"}, + {0x7a, "Z"}, + {0xb5, "\xce\x9c"}, + {0xdf, "SS"}, + {0xe0, "\xc3\x80"}, + {0xe1, "\xc3\x81"}, + {0xe2, "\xc3\x82"}, + {0xe3, "\xc3\x83"}, + {0xe4, "\xc3\x84"}, + {0xe5, "\xc3\x85"}, + {0xe6, "\xc3\x86"}, + {0xe7, "\xc3\x87"}, + {0xe8, "\xc3\x88"}, + {0xe9, "\xc3\x89"}, + {0xea, "\xc3\x8a"}, + {0xeb, "\xc3\x8b"}, + {0xec, "\xc3\x8c"}, + {0xed, "\xc3\x8d"}, + {0xee, "\xc3\x8e"}, + {0xef, "\xc3\x8f"}, + {0xf0, "\xc3\x90"}, + {0xf1, "\xc3\x91"}, + {0xf2, "\xc3\x92"}, + {0xf3, "\xc3\x93"}, + {0xf4, "\xc3\x94"}, + {0xf5, "\xc3\x95"}, + {0xf6, "\xc3\x96"}, + {0xf8, "\xc3\x98"}, + {0xf9, "\xc3\x99"}, + {0xfa, "\xc3\x9a"}, + {0xfb, "\xc3\x9b"}, + {0xfc, "\xc3\x9c"}, + {0xfd, "\xc3\x9d"}, + {0xfe, "\xc3\x9e"}, + {0xff, "\xc5\xb8"}, + {0x101, "\xc4\x80"}, + {0x103, "\xc4\x82"}, + {0x105, "\xc4\x84"}, + {0x107, "\xc4\x86"}, + {0x109, "\xc4\x88"}, + {0x10b, "\xc4\x8a"}, + {0x10d, "\xc4\x8c"}, + {0x10f, "\xc4\x8e"}, + {0x111, "\xc4\x90"}, + {0x113, "\xc4\x92"}, + {0x115, "\xc4\x94"}, + {0x117, "\xc4\x96"}, + {0x119, "\xc4\x98"}, + {0x11b, "\xc4\x9a"}, + {0x11d, "\xc4\x9c"}, + {0x11f, "\xc4\x9e"}, + {0x121, "\xc4\xa0"}, + {0x123, "\xc4\xa2"}, + {0x125, "\xc4\xa4"}, + {0x127, "\xc4\xa6"}, + {0x129, "\xc4\xa8"}, + {0x12b, "\xc4\xaa"}, + {0x12d, "\xc4\xac"}, + {0x12f, "\xc4\xae"}, + {0x131, "I"}, + {0x133, "\xc4\xb2"}, + {0x135, "\xc4\xb4"}, + {0x137, "\xc4\xb6"}, + {0x13a, "\xc4\xb9"}, + {0x13c, "\xc4\xbb"}, + {0x13e, "\xc4\xbd"}, + {0x140, "\xc4\xbf"}, + {0x142, "\xc5\x81"}, + {0x144, "\xc5\x83"}, + {0x146, "\xc5\x85"}, + {0x148, "\xc5\x87"}, + {0x149, "\xca\xbcN"}, + {0x14b, "\xc5\x8a"}, + {0x14d, "\xc5\x8c"}, + {0x14f, "\xc5\x8e"}, + {0x151, "\xc5\x90"}, + {0x153, "\xc5\x92"}, + {0x155, "\xc5\x94"}, + {0x157, "\xc5\x96"}, + {0x159, "\xc5\x98"}, + {0x15b, "\xc5\x9a"}, + {0x15d, "\xc5\x9c"}, + {0x15f, "\xc5\x9e"}, + {0x161, "\xc5\xa0"}, + {0x163, "\xc5\xa2"}, + {0x165, "\xc5\xa4"}, + {0x167, "\xc5\xa6"}, + {0x169, "\xc5\xa8"}, + {0x16b, "\xc5\xaa"}, + {0x16d, "\xc5\xac"}, + {0x16f, "\xc5\xae"}, + {0x171, "\xc5\xb0"}, + {0x173, "\xc5\xb2"}, + {0x175, "\xc5\xb4"}, + {0x177, "\xc5\xb6"}, + {0x17a, "\xc5\xb9"}, + {0x17c, "\xc5\xbb"}, + {0x17e, "\xc5\xbd"}, + {0x17f, "S"}, + {0x180, "\xc9\x83"}, + {0x183, "\xc6\x82"}, + {0x185, "\xc6\x84"}, + {0x188, "\xc6\x87"}, + {0x18c, "\xc6\x8b"}, + {0x192, "\xc6\x91"}, + {0x195, "\xc7\xb6"}, + {0x199, "\xc6\x98"}, + {0x19a, "\xc8\xbd"}, + {0x19b, "\xea\x9f\x9c"}, + {0x19e, "\xc8\xa0"}, + {0x1a1, "\xc6\xa0"}, + {0x1a3, "\xc6\xa2"}, + {0x1a5, "\xc6\xa4"}, + {0x1a8, "\xc6\xa7"}, + {0x1ad, "\xc6\xac"}, + {0x1b0, "\xc6\xaf"}, + {0x1b4, "\xc6\xb3"}, + {0x1b6, "\xc6\xb5"}, + {0x1b9, "\xc6\xb8"}, + {0x1bd, "\xc6\xbc"}, + {0x1bf, "\xc7\xb7"}, + {0x1c5, "\xc7\x84"}, + {0x1c6, "\xc7\x84"}, + {0x1c8, "\xc7\x87"}, + {0x1c9, "\xc7\x87"}, + {0x1cb, "\xc7\x8a"}, + {0x1cc, "\xc7\x8a"}, + {0x1ce, "\xc7\x8d"}, + {0x1d0, "\xc7\x8f"}, + {0x1d2, "\xc7\x91"}, + {0x1d4, "\xc7\x93"}, + {0x1d6, "\xc7\x95"}, + {0x1d8, "\xc7\x97"}, + {0x1da, "\xc7\x99"}, + {0x1dc, "\xc7\x9b"}, + {0x1dd, "\xc6\x8e"}, + {0x1df, "\xc7\x9e"}, + {0x1e1, "\xc7\xa0"}, + {0x1e3, "\xc7\xa2"}, + {0x1e5, "\xc7\xa4"}, + {0x1e7, "\xc7\xa6"}, + {0x1e9, "\xc7\xa8"}, + {0x1eb, "\xc7\xaa"}, + {0x1ed, "\xc7\xac"}, + {0x1ef, "\xc7\xae"}, + {0x1f0, "J\xcc\x8c"}, + {0x1f2, "\xc7\xb1"}, + {0x1f3, "\xc7\xb1"}, + {0x1f5, "\xc7\xb4"}, + {0x1f9, "\xc7\xb8"}, + {0x1fb, "\xc7\xba"}, + {0x1fd, "\xc7\xbc"}, + {0x1ff, "\xc7\xbe"}, + {0x201, "\xc8\x80"}, + {0x203, "\xc8\x82"}, + {0x205, "\xc8\x84"}, + {0x207, "\xc8\x86"}, + {0x209, "\xc8\x88"}, + {0x20b, "\xc8\x8a"}, + {0x20d, "\xc8\x8c"}, + {0x20f, "\xc8\x8e"}, + {0x211, "\xc8\x90"}, + {0x213, "\xc8\x92"}, + {0x215, "\xc8\x94"}, + {0x217, "\xc8\x96"}, + {0x219, "\xc8\x98"}, + {0x21b, "\xc8\x9a"}, + {0x21d, "\xc8\x9c"}, + {0x21f, "\xc8\x9e"}, + {0x223, "\xc8\xa2"}, + {0x225, "\xc8\xa4"}, + {0x227, "\xc8\xa6"}, + {0x229, "\xc8\xa8"}, + {0x22b, "\xc8\xaa"}, + {0x22d, "\xc8\xac"}, + {0x22f, "\xc8\xae"}, + {0x231, "\xc8\xb0"}, + {0x233, "\xc8\xb2"}, + {0x23c, "\xc8\xbb"}, + {0x23f, "\xe2\xb1\xbe"}, + {0x240, "\xe2\xb1\xbf"}, + {0x242, "\xc9\x81"}, + {0x247, "\xc9\x86"}, + {0x249, "\xc9\x88"}, + {0x24b, "\xc9\x8a"}, + {0x24d, "\xc9\x8c"}, + {0x24f, "\xc9\x8e"}, + {0x250, "\xe2\xb1\xaf"}, + {0x251, "\xe2\xb1\xad"}, + {0x252, "\xe2\xb1\xb0"}, + {0x253, "\xc6\x81"}, + {0x254, "\xc6\x86"}, + {0x256, "\xc6\x89"}, + {0x257, "\xc6\x8a"}, + {0x259, "\xc6\x8f"}, + {0x25b, "\xc6\x90"}, + {0x25c, "\xea\x9e\xab"}, + {0x260, "\xc6\x93"}, + {0x261, "\xea\x9e\xac"}, + {0x263, "\xc6\x94"}, + {0x264, "\xea\x9f\x8b"}, + {0x265, "\xea\x9e\x8d"}, + {0x266, "\xea\x9e\xaa"}, + {0x268, "\xc6\x97"}, + {0x269, "\xc6\x96"}, + {0x26a, "\xea\x9e\xae"}, + {0x26b, "\xe2\xb1\xa2"}, + {0x26c, "\xea\x9e\xad"}, + {0x26f, "\xc6\x9c"}, + {0x271, "\xe2\xb1\xae"}, + {0x272, "\xc6\x9d"}, + {0x275, "\xc6\x9f"}, + {0x27d, "\xe2\xb1\xa4"}, + {0x280, "\xc6\xa6"}, + {0x282, "\xea\x9f\x85"}, + {0x283, "\xc6\xa9"}, + {0x287, "\xea\x9e\xb1"}, + {0x288, "\xc6\xae"}, + {0x289, "\xc9\x84"}, + {0x28a, "\xc6\xb1"}, + {0x28b, "\xc6\xb2"}, + {0x28c, "\xc9\x85"}, + {0x292, "\xc6\xb7"}, + {0x29d, "\xea\x9e\xb2"}, + {0x29e, "\xea\x9e\xb0"}, + {0x345, "\xce\x99"}, + {0x371, "\xcd\xb0"}, + {0x373, "\xcd\xb2"}, + {0x377, "\xcd\xb6"}, + {0x37b, "\xcf\xbd"}, + {0x37c, "\xcf\xbe"}, + {0x37d, "\xcf\xbf"}, + {0x390, "\xce\x99\xcc\x88\xcc\x81"}, + {0x3ac, "\xce\x86"}, + {0x3ad, "\xce\x88"}, + {0x3ae, "\xce\x89"}, + {0x3af, "\xce\x8a"}, + {0x3b0, "\xce\xa5\xcc\x88\xcc\x81"}, + {0x3b1, "\xce\x91"}, + {0x3b2, "\xce\x92"}, + {0x3b3, "\xce\x93"}, + {0x3b4, "\xce\x94"}, + {0x3b5, "\xce\x95"}, + {0x3b6, "\xce\x96"}, + {0x3b7, "\xce\x97"}, + {0x3b8, "\xce\x98"}, + {0x3b9, "\xce\x99"}, + {0x3ba, "\xce\x9a"}, + {0x3bb, "\xce\x9b"}, + {0x3bc, "\xce\x9c"}, + {0x3bd, "\xce\x9d"}, + {0x3be, "\xce\x9e"}, + {0x3bf, "\xce\x9f"}, + {0x3c0, "\xce\xa0"}, + {0x3c1, "\xce\xa1"}, + {0x3c2, "\xce\xa3"}, + {0x3c3, "\xce\xa3"}, + {0x3c4, "\xce\xa4"}, + {0x3c5, "\xce\xa5"}, + {0x3c6, "\xce\xa6"}, + {0x3c7, "\xce\xa7"}, + {0x3c8, "\xce\xa8"}, + {0x3c9, "\xce\xa9"}, + {0x3ca, "\xce\xaa"}, + {0x3cb, "\xce\xab"}, + {0x3cc, "\xce\x8c"}, + {0x3cd, "\xce\x8e"}, + {0x3ce, "\xce\x8f"}, + {0x3d0, "\xce\x92"}, + {0x3d1, "\xce\x98"}, + {0x3d5, "\xce\xa6"}, + {0x3d6, "\xce\xa0"}, + {0x3d7, "\xcf\x8f"}, + {0x3d9, "\xcf\x98"}, + {0x3db, "\xcf\x9a"}, + {0x3dd, "\xcf\x9c"}, + {0x3df, "\xcf\x9e"}, + {0x3e1, "\xcf\xa0"}, + {0x3e3, "\xcf\xa2"}, + {0x3e5, "\xcf\xa4"}, + {0x3e7, "\xcf\xa6"}, + {0x3e9, "\xcf\xa8"}, + {0x3eb, "\xcf\xaa"}, + {0x3ed, "\xcf\xac"}, + {0x3ef, "\xcf\xae"}, + {0x3f0, "\xce\x9a"}, + {0x3f1, "\xce\xa1"}, + {0x3f2, "\xcf\xb9"}, + {0x3f3, "\xcd\xbf"}, + {0x3f5, "\xce\x95"}, + {0x3f8, "\xcf\xb7"}, + {0x3fb, "\xcf\xba"}, + {0x430, "\xd0\x90"}, + {0x431, "\xd0\x91"}, + {0x432, "\xd0\x92"}, + {0x433, "\xd0\x93"}, + {0x434, "\xd0\x94"}, + {0x435, "\xd0\x95"}, + {0x436, "\xd0\x96"}, + {0x437, "\xd0\x97"}, + {0x438, "\xd0\x98"}, + {0x439, "\xd0\x99"}, + {0x43a, "\xd0\x9a"}, + {0x43b, "\xd0\x9b"}, + {0x43c, "\xd0\x9c"}, + {0x43d, "\xd0\x9d"}, + {0x43e, "\xd0\x9e"}, + {0x43f, "\xd0\x9f"}, + {0x440, "\xd0\xa0"}, + {0x441, "\xd0\xa1"}, + {0x442, "\xd0\xa2"}, + {0x443, "\xd0\xa3"}, + {0x444, "\xd0\xa4"}, + {0x445, "\xd0\xa5"}, + {0x446, "\xd0\xa6"}, + {0x447, "\xd0\xa7"}, + {0x448, "\xd0\xa8"}, + {0x449, "\xd0\xa9"}, + {0x44a, "\xd0\xaa"}, + {0x44b, "\xd0\xab"}, + {0x44c, "\xd0\xac"}, + {0x44d, "\xd0\xad"}, + {0x44e, "\xd0\xae"}, + {0x44f, "\xd0\xaf"}, + {0x450, "\xd0\x80"}, + {0x451, "\xd0\x81"}, + {0x452, "\xd0\x82"}, + {0x453, "\xd0\x83"}, + {0x454, "\xd0\x84"}, + {0x455, "\xd0\x85"}, + {0x456, "\xd0\x86"}, + {0x457, "\xd0\x87"}, + {0x458, "\xd0\x88"}, + {0x459, "\xd0\x89"}, + {0x45a, "\xd0\x8a"}, + {0x45b, "\xd0\x8b"}, + {0x45c, "\xd0\x8c"}, + {0x45d, "\xd0\x8d"}, + {0x45e, "\xd0\x8e"}, + {0x45f, "\xd0\x8f"}, + {0x461, "\xd1\xa0"}, + {0x463, "\xd1\xa2"}, + {0x465, "\xd1\xa4"}, + {0x467, "\xd1\xa6"}, + {0x469, "\xd1\xa8"}, + {0x46b, "\xd1\xaa"}, + {0x46d, "\xd1\xac"}, + {0x46f, "\xd1\xae"}, + {0x471, "\xd1\xb0"}, + {0x473, "\xd1\xb2"}, + {0x475, "\xd1\xb4"}, + {0x477, "\xd1\xb6"}, + {0x479, "\xd1\xb8"}, + {0x47b, "\xd1\xba"}, + {0x47d, "\xd1\xbc"}, + {0x47f, "\xd1\xbe"}, + {0x481, "\xd2\x80"}, + {0x48b, "\xd2\x8a"}, + {0x48d, "\xd2\x8c"}, + {0x48f, "\xd2\x8e"}, + {0x491, "\xd2\x90"}, + {0x493, "\xd2\x92"}, + {0x495, "\xd2\x94"}, + {0x497, "\xd2\x96"}, + {0x499, "\xd2\x98"}, + {0x49b, "\xd2\x9a"}, + {0x49d, "\xd2\x9c"}, + {0x49f, "\xd2\x9e"}, + {0x4a1, "\xd2\xa0"}, + {0x4a3, "\xd2\xa2"}, + {0x4a5, "\xd2\xa4"}, + {0x4a7, "\xd2\xa6"}, + {0x4a9, "\xd2\xa8"}, + {0x4ab, "\xd2\xaa"}, + {0x4ad, "\xd2\xac"}, + {0x4af, "\xd2\xae"}, + {0x4b1, "\xd2\xb0"}, + {0x4b3, "\xd2\xb2"}, + {0x4b5, "\xd2\xb4"}, + {0x4b7, "\xd2\xb6"}, + {0x4b9, "\xd2\xb8"}, + {0x4bb, "\xd2\xba"}, + {0x4bd, "\xd2\xbc"}, + {0x4bf, "\xd2\xbe"}, + {0x4c2, "\xd3\x81"}, + {0x4c4, "\xd3\x83"}, + {0x4c6, "\xd3\x85"}, + {0x4c8, "\xd3\x87"}, + {0x4ca, "\xd3\x89"}, + {0x4cc, "\xd3\x8b"}, + {0x4ce, "\xd3\x8d"}, + {0x4cf, "\xd3\x80"}, + {0x4d1, "\xd3\x90"}, + {0x4d3, "\xd3\x92"}, + {0x4d5, "\xd3\x94"}, + {0x4d7, "\xd3\x96"}, + {0x4d9, "\xd3\x98"}, + {0x4db, "\xd3\x9a"}, + {0x4dd, "\xd3\x9c"}, + {0x4df, "\xd3\x9e"}, + {0x4e1, "\xd3\xa0"}, + {0x4e3, "\xd3\xa2"}, + {0x4e5, "\xd3\xa4"}, + {0x4e7, "\xd3\xa6"}, + {0x4e9, "\xd3\xa8"}, + {0x4eb, "\xd3\xaa"}, + {0x4ed, "\xd3\xac"}, + {0x4ef, "\xd3\xae"}, + {0x4f1, "\xd3\xb0"}, + {0x4f3, "\xd3\xb2"}, + {0x4f5, "\xd3\xb4"}, + {0x4f7, "\xd3\xb6"}, + {0x4f9, "\xd3\xb8"}, + {0x4fb, "\xd3\xba"}, + {0x4fd, "\xd3\xbc"}, + {0x4ff, "\xd3\xbe"}, + {0x501, "\xd4\x80"}, + {0x503, "\xd4\x82"}, + {0x505, "\xd4\x84"}, + {0x507, "\xd4\x86"}, + {0x509, "\xd4\x88"}, + {0x50b, "\xd4\x8a"}, + {0x50d, "\xd4\x8c"}, + {0x50f, "\xd4\x8e"}, + {0x511, "\xd4\x90"}, + {0x513, "\xd4\x92"}, + {0x515, "\xd4\x94"}, + {0x517, "\xd4\x96"}, + {0x519, "\xd4\x98"}, + {0x51b, "\xd4\x9a"}, + {0x51d, "\xd4\x9c"}, + {0x51f, "\xd4\x9e"}, + {0x521, "\xd4\xa0"}, + {0x523, "\xd4\xa2"}, + {0x525, "\xd4\xa4"}, + {0x527, "\xd4\xa6"}, + {0x529, "\xd4\xa8"}, + {0x52b, "\xd4\xaa"}, + {0x52d, "\xd4\xac"}, + {0x52f, "\xd4\xae"}, + {0x561, "\xd4\xb1"}, + {0x562, "\xd4\xb2"}, + {0x563, "\xd4\xb3"}, + {0x564, "\xd4\xb4"}, + {0x565, "\xd4\xb5"}, + {0x566, "\xd4\xb6"}, + {0x567, "\xd4\xb7"}, + {0x568, "\xd4\xb8"}, + {0x569, "\xd4\xb9"}, + {0x56a, "\xd4\xba"}, + {0x56b, "\xd4\xbb"}, + {0x56c, "\xd4\xbc"}, + {0x56d, "\xd4\xbd"}, + {0x56e, "\xd4\xbe"}, + {0x56f, "\xd4\xbf"}, + {0x570, "\xd5\x80"}, + {0x571, "\xd5\x81"}, + {0x572, "\xd5\x82"}, + {0x573, "\xd5\x83"}, + {0x574, "\xd5\x84"}, + {0x575, "\xd5\x85"}, + {0x576, "\xd5\x86"}, + {0x577, "\xd5\x87"}, + {0x578, "\xd5\x88"}, + {0x579, "\xd5\x89"}, + {0x57a, "\xd5\x8a"}, + {0x57b, "\xd5\x8b"}, + {0x57c, "\xd5\x8c"}, + {0x57d, "\xd5\x8d"}, + {0x57e, "\xd5\x8e"}, + {0x57f, "\xd5\x8f"}, + {0x580, "\xd5\x90"}, + {0x581, "\xd5\x91"}, + {0x582, "\xd5\x92"}, + {0x583, "\xd5\x93"}, + {0x584, "\xd5\x94"}, + {0x585, "\xd5\x95"}, + {0x586, "\xd5\x96"}, + {0x587, "\xd4\xb5\xd5\x92"}, + {0x10d0, "\xe1\xb2\x90"}, + {0x10d1, "\xe1\xb2\x91"}, + {0x10d2, "\xe1\xb2\x92"}, + {0x10d3, "\xe1\xb2\x93"}, + {0x10d4, "\xe1\xb2\x94"}, + {0x10d5, "\xe1\xb2\x95"}, + {0x10d6, "\xe1\xb2\x96"}, + {0x10d7, "\xe1\xb2\x97"}, + {0x10d8, "\xe1\xb2\x98"}, + {0x10d9, "\xe1\xb2\x99"}, + {0x10da, "\xe1\xb2\x9a"}, + {0x10db, "\xe1\xb2\x9b"}, + {0x10dc, "\xe1\xb2\x9c"}, + {0x10dd, "\xe1\xb2\x9d"}, + {0x10de, "\xe1\xb2\x9e"}, + {0x10df, "\xe1\xb2\x9f"}, + {0x10e0, "\xe1\xb2\xa0"}, + {0x10e1, "\xe1\xb2\xa1"}, + {0x10e2, "\xe1\xb2\xa2"}, + {0x10e3, "\xe1\xb2\xa3"}, + {0x10e4, "\xe1\xb2\xa4"}, + {0x10e5, "\xe1\xb2\xa5"}, + {0x10e6, "\xe1\xb2\xa6"}, + {0x10e7, "\xe1\xb2\xa7"}, + {0x10e8, "\xe1\xb2\xa8"}, + {0x10e9, "\xe1\xb2\xa9"}, + {0x10ea, "\xe1\xb2\xaa"}, + {0x10eb, "\xe1\xb2\xab"}, + {0x10ec, "\xe1\xb2\xac"}, + {0x10ed, "\xe1\xb2\xad"}, + {0x10ee, "\xe1\xb2\xae"}, + {0x10ef, "\xe1\xb2\xaf"}, + {0x10f0, "\xe1\xb2\xb0"}, + {0x10f1, "\xe1\xb2\xb1"}, + {0x10f2, "\xe1\xb2\xb2"}, + {0x10f3, "\xe1\xb2\xb3"}, + {0x10f4, "\xe1\xb2\xb4"}, + {0x10f5, "\xe1\xb2\xb5"}, + {0x10f6, "\xe1\xb2\xb6"}, + {0x10f7, "\xe1\xb2\xb7"}, + {0x10f8, "\xe1\xb2\xb8"}, + {0x10f9, "\xe1\xb2\xb9"}, + {0x10fa, "\xe1\xb2\xba"}, + {0x10fd, "\xe1\xb2\xbd"}, + {0x10fe, "\xe1\xb2\xbe"}, + {0x10ff, "\xe1\xb2\xbf"}, + {0x13f8, "\xe1\x8f\xb0"}, + {0x13f9, "\xe1\x8f\xb1"}, + {0x13fa, "\xe1\x8f\xb2"}, + {0x13fb, "\xe1\x8f\xb3"}, + {0x13fc, "\xe1\x8f\xb4"}, + {0x13fd, "\xe1\x8f\xb5"}, + {0x1c80, "\xd0\x92"}, + {0x1c81, "\xd0\x94"}, + {0x1c82, "\xd0\x9e"}, + {0x1c83, "\xd0\xa1"}, + {0x1c84, "\xd0\xa2"}, + {0x1c85, "\xd0\xa2"}, + {0x1c86, "\xd0\xaa"}, + {0x1c87, "\xd1\xa2"}, + {0x1c88, "\xea\x99\x8a"}, + {0x1c8a, "\xe1\xb2\x89"}, + {0x1d79, "\xea\x9d\xbd"}, + {0x1d7d, "\xe2\xb1\xa3"}, + {0x1d8e, "\xea\x9f\x86"}, + {0x1e01, "\xe1\xb8\x80"}, + {0x1e03, "\xe1\xb8\x82"}, + {0x1e05, "\xe1\xb8\x84"}, + {0x1e07, "\xe1\xb8\x86"}, + {0x1e09, "\xe1\xb8\x88"}, + {0x1e0b, "\xe1\xb8\x8a"}, + {0x1e0d, "\xe1\xb8\x8c"}, + {0x1e0f, "\xe1\xb8\x8e"}, + {0x1e11, "\xe1\xb8\x90"}, + {0x1e13, "\xe1\xb8\x92"}, + {0x1e15, "\xe1\xb8\x94"}, + {0x1e17, "\xe1\xb8\x96"}, + {0x1e19, "\xe1\xb8\x98"}, + {0x1e1b, "\xe1\xb8\x9a"}, + {0x1e1d, "\xe1\xb8\x9c"}, + {0x1e1f, "\xe1\xb8\x9e"}, + {0x1e21, "\xe1\xb8\xa0"}, + {0x1e23, "\xe1\xb8\xa2"}, + {0x1e25, "\xe1\xb8\xa4"}, + {0x1e27, "\xe1\xb8\xa6"}, + {0x1e29, "\xe1\xb8\xa8"}, + {0x1e2b, "\xe1\xb8\xaa"}, + {0x1e2d, "\xe1\xb8\xac"}, + {0x1e2f, "\xe1\xb8\xae"}, + {0x1e31, "\xe1\xb8\xb0"}, + {0x1e33, "\xe1\xb8\xb2"}, + {0x1e35, "\xe1\xb8\xb4"}, + {0x1e37, "\xe1\xb8\xb6"}, + {0x1e39, "\xe1\xb8\xb8"}, + {0x1e3b, "\xe1\xb8\xba"}, + {0x1e3d, "\xe1\xb8\xbc"}, + {0x1e3f, "\xe1\xb8\xbe"}, + {0x1e41, "\xe1\xb9\x80"}, + {0x1e43, "\xe1\xb9\x82"}, + {0x1e45, "\xe1\xb9\x84"}, + {0x1e47, "\xe1\xb9\x86"}, + {0x1e49, "\xe1\xb9\x88"}, + {0x1e4b, "\xe1\xb9\x8a"}, + {0x1e4d, "\xe1\xb9\x8c"}, + {0x1e4f, "\xe1\xb9\x8e"}, + {0x1e51, "\xe1\xb9\x90"}, + {0x1e53, "\xe1\xb9\x92"}, + {0x1e55, "\xe1\xb9\x94"}, + {0x1e57, "\xe1\xb9\x96"}, + {0x1e59, "\xe1\xb9\x98"}, + {0x1e5b, "\xe1\xb9\x9a"}, + {0x1e5d, "\xe1\xb9\x9c"}, + {0x1e5f, "\xe1\xb9\x9e"}, + {0x1e61, "\xe1\xb9\xa0"}, + {0x1e63, "\xe1\xb9\xa2"}, + {0x1e65, "\xe1\xb9\xa4"}, + {0x1e67, "\xe1\xb9\xa6"}, + {0x1e69, "\xe1\xb9\xa8"}, + {0x1e6b, "\xe1\xb9\xaa"}, + {0x1e6d, "\xe1\xb9\xac"}, + {0x1e6f, "\xe1\xb9\xae"}, + {0x1e71, "\xe1\xb9\xb0"}, + {0x1e73, "\xe1\xb9\xb2"}, + {0x1e75, "\xe1\xb9\xb4"}, + {0x1e77, "\xe1\xb9\xb6"}, + {0x1e79, "\xe1\xb9\xb8"}, + {0x1e7b, "\xe1\xb9\xba"}, + {0x1e7d, "\xe1\xb9\xbc"}, + {0x1e7f, "\xe1\xb9\xbe"}, + {0x1e81, "\xe1\xba\x80"}, + {0x1e83, "\xe1\xba\x82"}, + {0x1e85, "\xe1\xba\x84"}, + {0x1e87, "\xe1\xba\x86"}, + {0x1e89, "\xe1\xba\x88"}, + {0x1e8b, "\xe1\xba\x8a"}, + {0x1e8d, "\xe1\xba\x8c"}, + {0x1e8f, "\xe1\xba\x8e"}, + {0x1e91, "\xe1\xba\x90"}, + {0x1e93, "\xe1\xba\x92"}, + {0x1e95, "\xe1\xba\x94"}, + {0x1e96, "H\xcc\xb1"}, + {0x1e97, "T\xcc\x88"}, + {0x1e98, "W\xcc\x8a"}, + {0x1e99, "Y\xcc\x8a"}, + {0x1e9a, "A\xca\xbe"}, + {0x1e9b, "\xe1\xb9\xa0"}, + {0x1ea1, "\xe1\xba\xa0"}, + {0x1ea3, "\xe1\xba\xa2"}, + {0x1ea5, "\xe1\xba\xa4"}, + {0x1ea7, "\xe1\xba\xa6"}, + {0x1ea9, "\xe1\xba\xa8"}, + {0x1eab, "\xe1\xba\xaa"}, + {0x1ead, "\xe1\xba\xac"}, + {0x1eaf, "\xe1\xba\xae"}, + {0x1eb1, "\xe1\xba\xb0"}, + {0x1eb3, "\xe1\xba\xb2"}, + {0x1eb5, "\xe1\xba\xb4"}, + {0x1eb7, "\xe1\xba\xb6"}, + {0x1eb9, "\xe1\xba\xb8"}, + {0x1ebb, "\xe1\xba\xba"}, + {0x1ebd, "\xe1\xba\xbc"}, + {0x1ebf, "\xe1\xba\xbe"}, + {0x1ec1, "\xe1\xbb\x80"}, + {0x1ec3, "\xe1\xbb\x82"}, + {0x1ec5, "\xe1\xbb\x84"}, + {0x1ec7, "\xe1\xbb\x86"}, + {0x1ec9, "\xe1\xbb\x88"}, + {0x1ecb, "\xe1\xbb\x8a"}, + {0x1ecd, "\xe1\xbb\x8c"}, + {0x1ecf, "\xe1\xbb\x8e"}, + {0x1ed1, "\xe1\xbb\x90"}, + {0x1ed3, "\xe1\xbb\x92"}, + {0x1ed5, "\xe1\xbb\x94"}, + {0x1ed7, "\xe1\xbb\x96"}, + {0x1ed9, "\xe1\xbb\x98"}, + {0x1edb, "\xe1\xbb\x9a"}, + {0x1edd, "\xe1\xbb\x9c"}, + {0x1edf, "\xe1\xbb\x9e"}, + {0x1ee1, "\xe1\xbb\xa0"}, + {0x1ee3, "\xe1\xbb\xa2"}, + {0x1ee5, "\xe1\xbb\xa4"}, + {0x1ee7, "\xe1\xbb\xa6"}, + {0x1ee9, "\xe1\xbb\xa8"}, + {0x1eeb, "\xe1\xbb\xaa"}, + {0x1eed, "\xe1\xbb\xac"}, + {0x1eef, "\xe1\xbb\xae"}, + {0x1ef1, "\xe1\xbb\xb0"}, + {0x1ef3, "\xe1\xbb\xb2"}, + {0x1ef5, "\xe1\xbb\xb4"}, + {0x1ef7, "\xe1\xbb\xb6"}, + {0x1ef9, "\xe1\xbb\xb8"}, + {0x1efb, "\xe1\xbb\xba"}, + {0x1efd, "\xe1\xbb\xbc"}, + {0x1eff, "\xe1\xbb\xbe"}, + {0x1f00, "\xe1\xbc\x88"}, + {0x1f01, "\xe1\xbc\x89"}, + {0x1f02, "\xe1\xbc\x8a"}, + {0x1f03, "\xe1\xbc\x8b"}, + {0x1f04, "\xe1\xbc\x8c"}, + {0x1f05, "\xe1\xbc\x8d"}, + {0x1f06, "\xe1\xbc\x8e"}, + {0x1f07, "\xe1\xbc\x8f"}, + {0x1f10, "\xe1\xbc\x98"}, + {0x1f11, "\xe1\xbc\x99"}, + {0x1f12, "\xe1\xbc\x9a"}, + {0x1f13, "\xe1\xbc\x9b"}, + {0x1f14, "\xe1\xbc\x9c"}, + {0x1f15, "\xe1\xbc\x9d"}, + {0x1f20, "\xe1\xbc\xa8"}, + {0x1f21, "\xe1\xbc\xa9"}, + {0x1f22, "\xe1\xbc\xaa"}, + {0x1f23, "\xe1\xbc\xab"}, + {0x1f24, "\xe1\xbc\xac"}, + {0x1f25, "\xe1\xbc\xad"}, + {0x1f26, "\xe1\xbc\xae"}, + {0x1f27, "\xe1\xbc\xaf"}, + {0x1f30, "\xe1\xbc\xb8"}, + {0x1f31, "\xe1\xbc\xb9"}, + {0x1f32, "\xe1\xbc\xba"}, + {0x1f33, "\xe1\xbc\xbb"}, + {0x1f34, "\xe1\xbc\xbc"}, + {0x1f35, "\xe1\xbc\xbd"}, + {0x1f36, "\xe1\xbc\xbe"}, + {0x1f37, "\xe1\xbc\xbf"}, + {0x1f40, "\xe1\xbd\x88"}, + {0x1f41, "\xe1\xbd\x89"}, + {0x1f42, "\xe1\xbd\x8a"}, + {0x1f43, "\xe1\xbd\x8b"}, + {0x1f44, "\xe1\xbd\x8c"}, + {0x1f45, "\xe1\xbd\x8d"}, + {0x1f50, "\xce\xa5\xcc\x93"}, + {0x1f51, "\xe1\xbd\x99"}, + {0x1f52, "\xce\xa5\xcc\x93\xcc\x80"}, + {0x1f53, "\xe1\xbd\x9b"}, + {0x1f54, "\xce\xa5\xcc\x93\xcc\x81"}, + {0x1f55, "\xe1\xbd\x9d"}, + {0x1f56, "\xce\xa5\xcc\x93\xcd\x82"}, + {0x1f57, "\xe1\xbd\x9f"}, + {0x1f60, "\xe1\xbd\xa8"}, + {0x1f61, "\xe1\xbd\xa9"}, + {0x1f62, "\xe1\xbd\xaa"}, + {0x1f63, "\xe1\xbd\xab"}, + {0x1f64, "\xe1\xbd\xac"}, + {0x1f65, "\xe1\xbd\xad"}, + {0x1f66, "\xe1\xbd\xae"}, + {0x1f67, "\xe1\xbd\xaf"}, + {0x1f70, "\xe1\xbe\xba"}, + {0x1f71, "\xe1\xbe\xbb"}, + {0x1f72, "\xe1\xbf\x88"}, + {0x1f73, "\xe1\xbf\x89"}, + {0x1f74, "\xe1\xbf\x8a"}, + {0x1f75, "\xe1\xbf\x8b"}, + {0x1f76, "\xe1\xbf\x9a"}, + {0x1f77, "\xe1\xbf\x9b"}, + {0x1f78, "\xe1\xbf\xb8"}, + {0x1f79, "\xe1\xbf\xb9"}, + {0x1f7a, "\xe1\xbf\xaa"}, + {0x1f7b, "\xe1\xbf\xab"}, + {0x1f7c, "\xe1\xbf\xba"}, + {0x1f7d, "\xe1\xbf\xbb"}, + {0x1f80, "\xe1\xbc\x88\xce\x99"}, + {0x1f81, "\xe1\xbc\x89\xce\x99"}, + {0x1f82, "\xe1\xbc\x8a\xce\x99"}, + {0x1f83, "\xe1\xbc\x8b\xce\x99"}, + {0x1f84, "\xe1\xbc\x8c\xce\x99"}, + {0x1f85, "\xe1\xbc\x8d\xce\x99"}, + {0x1f86, "\xe1\xbc\x8e\xce\x99"}, + {0x1f87, "\xe1\xbc\x8f\xce\x99"}, + {0x1f88, "\xe1\xbc\x88\xce\x99"}, + {0x1f89, "\xe1\xbc\x89\xce\x99"}, + {0x1f8a, "\xe1\xbc\x8a\xce\x99"}, + {0x1f8b, "\xe1\xbc\x8b\xce\x99"}, + {0x1f8c, "\xe1\xbc\x8c\xce\x99"}, + {0x1f8d, "\xe1\xbc\x8d\xce\x99"}, + {0x1f8e, "\xe1\xbc\x8e\xce\x99"}, + {0x1f8f, "\xe1\xbc\x8f\xce\x99"}, + {0x1f90, "\xe1\xbc\xa8\xce\x99"}, + {0x1f91, "\xe1\xbc\xa9\xce\x99"}, + {0x1f92, "\xe1\xbc\xaa\xce\x99"}, + {0x1f93, "\xe1\xbc\xab\xce\x99"}, + {0x1f94, "\xe1\xbc\xac\xce\x99"}, + {0x1f95, "\xe1\xbc\xad\xce\x99"}, + {0x1f96, "\xe1\xbc\xae\xce\x99"}, + {0x1f97, "\xe1\xbc\xaf\xce\x99"}, + {0x1f98, "\xe1\xbc\xa8\xce\x99"}, + {0x1f99, "\xe1\xbc\xa9\xce\x99"}, + {0x1f9a, "\xe1\xbc\xaa\xce\x99"}, + {0x1f9b, "\xe1\xbc\xab\xce\x99"}, + {0x1f9c, "\xe1\xbc\xac\xce\x99"}, + {0x1f9d, "\xe1\xbc\xad\xce\x99"}, + {0x1f9e, "\xe1\xbc\xae\xce\x99"}, + {0x1f9f, "\xe1\xbc\xaf\xce\x99"}, + {0x1fa0, "\xe1\xbd\xa8\xce\x99"}, + {0x1fa1, "\xe1\xbd\xa9\xce\x99"}, + {0x1fa2, "\xe1\xbd\xaa\xce\x99"}, + {0x1fa3, "\xe1\xbd\xab\xce\x99"}, + {0x1fa4, "\xe1\xbd\xac\xce\x99"}, + {0x1fa5, "\xe1\xbd\xad\xce\x99"}, + {0x1fa6, "\xe1\xbd\xae\xce\x99"}, + {0x1fa7, "\xe1\xbd\xaf\xce\x99"}, + {0x1fa8, "\xe1\xbd\xa8\xce\x99"}, + {0x1fa9, "\xe1\xbd\xa9\xce\x99"}, + {0x1faa, "\xe1\xbd\xaa\xce\x99"}, + {0x1fab, "\xe1\xbd\xab\xce\x99"}, + {0x1fac, "\xe1\xbd\xac\xce\x99"}, + {0x1fad, "\xe1\xbd\xad\xce\x99"}, + {0x1fae, "\xe1\xbd\xae\xce\x99"}, + {0x1faf, "\xe1\xbd\xaf\xce\x99"}, + {0x1fb0, "\xe1\xbe\xb8"}, + {0x1fb1, "\xe1\xbe\xb9"}, + {0x1fb2, "\xe1\xbe\xba\xce\x99"}, + {0x1fb3, "\xce\x91\xce\x99"}, + {0x1fb4, "\xce\x86\xce\x99"}, + {0x1fb6, "\xce\x91\xcd\x82"}, + {0x1fb7, "\xce\x91\xcd\x82\xce\x99"}, + {0x1fbc, "\xce\x91\xce\x99"}, + {0x1fbe, "\xce\x99"}, + {0x1fc2, "\xe1\xbf\x8a\xce\x99"}, + {0x1fc3, "\xce\x97\xce\x99"}, + {0x1fc4, "\xce\x89\xce\x99"}, + {0x1fc6, "\xce\x97\xcd\x82"}, + {0x1fc7, "\xce\x97\xcd\x82\xce\x99"}, + {0x1fcc, "\xce\x97\xce\x99"}, + {0x1fd0, "\xe1\xbf\x98"}, + {0x1fd1, "\xe1\xbf\x99"}, + {0x1fd2, "\xce\x99\xcc\x88\xcc\x80"}, + {0x1fd3, "\xce\x99\xcc\x88\xcc\x81"}, + {0x1fd6, "\xce\x99\xcd\x82"}, + {0x1fd7, "\xce\x99\xcc\x88\xcd\x82"}, + {0x1fe0, "\xe1\xbf\xa8"}, + {0x1fe1, "\xe1\xbf\xa9"}, + {0x1fe2, "\xce\xa5\xcc\x88\xcc\x80"}, + {0x1fe3, "\xce\xa5\xcc\x88\xcc\x81"}, + {0x1fe4, "\xce\xa1\xcc\x93"}, + {0x1fe5, "\xe1\xbf\xac"}, + {0x1fe6, "\xce\xa5\xcd\x82"}, + {0x1fe7, "\xce\xa5\xcc\x88\xcd\x82"}, + {0x1ff2, "\xe1\xbf\xba\xce\x99"}, + {0x1ff3, "\xce\xa9\xce\x99"}, + {0x1ff4, "\xce\x8f\xce\x99"}, + {0x1ff6, "\xce\xa9\xcd\x82"}, + {0x1ff7, "\xce\xa9\xcd\x82\xce\x99"}, + {0x1ffc, "\xce\xa9\xce\x99"}, + {0x214e, "\xe2\x84\xb2"}, + {0x2170, "\xe2\x85\xa0"}, + {0x2171, "\xe2\x85\xa1"}, + {0x2172, "\xe2\x85\xa2"}, + {0x2173, "\xe2\x85\xa3"}, + {0x2174, "\xe2\x85\xa4"}, + {0x2175, "\xe2\x85\xa5"}, + {0x2176, "\xe2\x85\xa6"}, + {0x2177, "\xe2\x85\xa7"}, + {0x2178, "\xe2\x85\xa8"}, + {0x2179, "\xe2\x85\xa9"}, + {0x217a, "\xe2\x85\xaa"}, + {0x217b, "\xe2\x85\xab"}, + {0x217c, "\xe2\x85\xac"}, + {0x217d, "\xe2\x85\xad"}, + {0x217e, "\xe2\x85\xae"}, + {0x217f, "\xe2\x85\xaf"}, + {0x2184, "\xe2\x86\x83"}, + {0x24d0, "\xe2\x92\xb6"}, + {0x24d1, "\xe2\x92\xb7"}, + {0x24d2, "\xe2\x92\xb8"}, + {0x24d3, "\xe2\x92\xb9"}, + {0x24d4, "\xe2\x92\xba"}, + {0x24d5, "\xe2\x92\xbb"}, + {0x24d6, "\xe2\x92\xbc"}, + {0x24d7, "\xe2\x92\xbd"}, + {0x24d8, "\xe2\x92\xbe"}, + {0x24d9, "\xe2\x92\xbf"}, + {0x24da, "\xe2\x93\x80"}, + {0x24db, "\xe2\x93\x81"}, + {0x24dc, "\xe2\x93\x82"}, + {0x24dd, "\xe2\x93\x83"}, + {0x24de, "\xe2\x93\x84"}, + {0x24df, "\xe2\x93\x85"}, + {0x24e0, "\xe2\x93\x86"}, + {0x24e1, "\xe2\x93\x87"}, + {0x24e2, "\xe2\x93\x88"}, + {0x24e3, "\xe2\x93\x89"}, + {0x24e4, "\xe2\x93\x8a"}, + {0x24e5, "\xe2\x93\x8b"}, + {0x24e6, "\xe2\x93\x8c"}, + {0x24e7, "\xe2\x93\x8d"}, + {0x24e8, "\xe2\x93\x8e"}, + {0x24e9, "\xe2\x93\x8f"}, + {0x2c30, "\xe2\xb0\x80"}, + {0x2c31, "\xe2\xb0\x81"}, + {0x2c32, "\xe2\xb0\x82"}, + {0x2c33, "\xe2\xb0\x83"}, + {0x2c34, "\xe2\xb0\x84"}, + {0x2c35, "\xe2\xb0\x85"}, + {0x2c36, "\xe2\xb0\x86"}, + {0x2c37, "\xe2\xb0\x87"}, + {0x2c38, "\xe2\xb0\x88"}, + {0x2c39, "\xe2\xb0\x89"}, + {0x2c3a, "\xe2\xb0\x8a"}, + {0x2c3b, "\xe2\xb0\x8b"}, + {0x2c3c, "\xe2\xb0\x8c"}, + {0x2c3d, "\xe2\xb0\x8d"}, + {0x2c3e, "\xe2\xb0\x8e"}, + {0x2c3f, "\xe2\xb0\x8f"}, + {0x2c40, "\xe2\xb0\x90"}, + {0x2c41, "\xe2\xb0\x91"}, + {0x2c42, "\xe2\xb0\x92"}, + {0x2c43, "\xe2\xb0\x93"}, + {0x2c44, "\xe2\xb0\x94"}, + {0x2c45, "\xe2\xb0\x95"}, + {0x2c46, "\xe2\xb0\x96"}, + {0x2c47, "\xe2\xb0\x97"}, + {0x2c48, "\xe2\xb0\x98"}, + {0x2c49, "\xe2\xb0\x99"}, + {0x2c4a, "\xe2\xb0\x9a"}, + {0x2c4b, "\xe2\xb0\x9b"}, + {0x2c4c, "\xe2\xb0\x9c"}, + {0x2c4d, "\xe2\xb0\x9d"}, + {0x2c4e, "\xe2\xb0\x9e"}, + {0x2c4f, "\xe2\xb0\x9f"}, + {0x2c50, "\xe2\xb0\xa0"}, + {0x2c51, "\xe2\xb0\xa1"}, + {0x2c52, "\xe2\xb0\xa2"}, + {0x2c53, "\xe2\xb0\xa3"}, + {0x2c54, "\xe2\xb0\xa4"}, + {0x2c55, "\xe2\xb0\xa5"}, + {0x2c56, "\xe2\xb0\xa6"}, + {0x2c57, "\xe2\xb0\xa7"}, + {0x2c58, "\xe2\xb0\xa8"}, + {0x2c59, "\xe2\xb0\xa9"}, + {0x2c5a, "\xe2\xb0\xaa"}, + {0x2c5b, "\xe2\xb0\xab"}, + {0x2c5c, "\xe2\xb0\xac"}, + {0x2c5d, "\xe2\xb0\xad"}, + {0x2c5e, "\xe2\xb0\xae"}, + {0x2c5f, "\xe2\xb0\xaf"}, + {0x2c61, "\xe2\xb1\xa0"}, + {0x2c65, "\xc8\xba"}, + {0x2c66, "\xc8\xbe"}, + {0x2c68, "\xe2\xb1\xa7"}, + {0x2c6a, "\xe2\xb1\xa9"}, + {0x2c6c, "\xe2\xb1\xab"}, + {0x2c73, "\xe2\xb1\xb2"}, + {0x2c76, "\xe2\xb1\xb5"}, + {0x2c81, "\xe2\xb2\x80"}, + {0x2c83, "\xe2\xb2\x82"}, + {0x2c85, "\xe2\xb2\x84"}, + {0x2c87, "\xe2\xb2\x86"}, + {0x2c89, "\xe2\xb2\x88"}, + {0x2c8b, "\xe2\xb2\x8a"}, + {0x2c8d, "\xe2\xb2\x8c"}, + {0x2c8f, "\xe2\xb2\x8e"}, + {0x2c91, "\xe2\xb2\x90"}, + {0x2c93, "\xe2\xb2\x92"}, + {0x2c95, "\xe2\xb2\x94"}, + {0x2c97, "\xe2\xb2\x96"}, + {0x2c99, "\xe2\xb2\x98"}, + {0x2c9b, "\xe2\xb2\x9a"}, + {0x2c9d, "\xe2\xb2\x9c"}, + {0x2c9f, "\xe2\xb2\x9e"}, + {0x2ca1, "\xe2\xb2\xa0"}, + {0x2ca3, "\xe2\xb2\xa2"}, + {0x2ca5, "\xe2\xb2\xa4"}, + {0x2ca7, "\xe2\xb2\xa6"}, + {0x2ca9, "\xe2\xb2\xa8"}, + {0x2cab, "\xe2\xb2\xaa"}, + {0x2cad, "\xe2\xb2\xac"}, + {0x2caf, "\xe2\xb2\xae"}, + {0x2cb1, "\xe2\xb2\xb0"}, + {0x2cb3, "\xe2\xb2\xb2"}, + {0x2cb5, "\xe2\xb2\xb4"}, + {0x2cb7, "\xe2\xb2\xb6"}, + {0x2cb9, "\xe2\xb2\xb8"}, + {0x2cbb, "\xe2\xb2\xba"}, + {0x2cbd, "\xe2\xb2\xbc"}, + {0x2cbf, "\xe2\xb2\xbe"}, + {0x2cc1, "\xe2\xb3\x80"}, + {0x2cc3, "\xe2\xb3\x82"}, + {0x2cc5, "\xe2\xb3\x84"}, + {0x2cc7, "\xe2\xb3\x86"}, + {0x2cc9, "\xe2\xb3\x88"}, + {0x2ccb, "\xe2\xb3\x8a"}, + {0x2ccd, "\xe2\xb3\x8c"}, + {0x2ccf, "\xe2\xb3\x8e"}, + {0x2cd1, "\xe2\xb3\x90"}, + {0x2cd3, "\xe2\xb3\x92"}, + {0x2cd5, "\xe2\xb3\x94"}, + {0x2cd7, "\xe2\xb3\x96"}, + {0x2cd9, "\xe2\xb3\x98"}, + {0x2cdb, "\xe2\xb3\x9a"}, + {0x2cdd, "\xe2\xb3\x9c"}, + {0x2cdf, "\xe2\xb3\x9e"}, + {0x2ce1, "\xe2\xb3\xa0"}, + {0x2ce3, "\xe2\xb3\xa2"}, + {0x2cec, "\xe2\xb3\xab"}, + {0x2cee, "\xe2\xb3\xad"}, + {0x2cf3, "\xe2\xb3\xb2"}, + {0x2d00, "\xe1\x82\xa0"}, + {0x2d01, "\xe1\x82\xa1"}, + {0x2d02, "\xe1\x82\xa2"}, + {0x2d03, "\xe1\x82\xa3"}, + {0x2d04, "\xe1\x82\xa4"}, + {0x2d05, "\xe1\x82\xa5"}, + {0x2d06, "\xe1\x82\xa6"}, + {0x2d07, "\xe1\x82\xa7"}, + {0x2d08, "\xe1\x82\xa8"}, + {0x2d09, "\xe1\x82\xa9"}, + {0x2d0a, "\xe1\x82\xaa"}, + {0x2d0b, "\xe1\x82\xab"}, + {0x2d0c, "\xe1\x82\xac"}, + {0x2d0d, "\xe1\x82\xad"}, + {0x2d0e, "\xe1\x82\xae"}, + {0x2d0f, "\xe1\x82\xaf"}, + {0x2d10, "\xe1\x82\xb0"}, + {0x2d11, "\xe1\x82\xb1"}, + {0x2d12, "\xe1\x82\xb2"}, + {0x2d13, "\xe1\x82\xb3"}, + {0x2d14, "\xe1\x82\xb4"}, + {0x2d15, "\xe1\x82\xb5"}, + {0x2d16, "\xe1\x82\xb6"}, + {0x2d17, "\xe1\x82\xb7"}, + {0x2d18, "\xe1\x82\xb8"}, + {0x2d19, "\xe1\x82\xb9"}, + {0x2d1a, "\xe1\x82\xba"}, + {0x2d1b, "\xe1\x82\xbb"}, + {0x2d1c, "\xe1\x82\xbc"}, + {0x2d1d, "\xe1\x82\xbd"}, + {0x2d1e, "\xe1\x82\xbe"}, + {0x2d1f, "\xe1\x82\xbf"}, + {0x2d20, "\xe1\x83\x80"}, + {0x2d21, "\xe1\x83\x81"}, + {0x2d22, "\xe1\x83\x82"}, + {0x2d23, "\xe1\x83\x83"}, + {0x2d24, "\xe1\x83\x84"}, + {0x2d25, "\xe1\x83\x85"}, + {0x2d27, "\xe1\x83\x87"}, + {0x2d2d, "\xe1\x83\x8d"}, + {0xa641, "\xea\x99\x80"}, + {0xa643, "\xea\x99\x82"}, + {0xa645, "\xea\x99\x84"}, + {0xa647, "\xea\x99\x86"}, + {0xa649, "\xea\x99\x88"}, + {0xa64b, "\xea\x99\x8a"}, + {0xa64d, "\xea\x99\x8c"}, + {0xa64f, "\xea\x99\x8e"}, + {0xa651, "\xea\x99\x90"}, + {0xa653, "\xea\x99\x92"}, + {0xa655, "\xea\x99\x94"}, + {0xa657, "\xea\x99\x96"}, + {0xa659, "\xea\x99\x98"}, + {0xa65b, "\xea\x99\x9a"}, + {0xa65d, "\xea\x99\x9c"}, + {0xa65f, "\xea\x99\x9e"}, + {0xa661, "\xea\x99\xa0"}, + {0xa663, "\xea\x99\xa2"}, + {0xa665, "\xea\x99\xa4"}, + {0xa667, "\xea\x99\xa6"}, + {0xa669, "\xea\x99\xa8"}, + {0xa66b, "\xea\x99\xaa"}, + {0xa66d, "\xea\x99\xac"}, + {0xa681, "\xea\x9a\x80"}, + {0xa683, "\xea\x9a\x82"}, + {0xa685, "\xea\x9a\x84"}, + {0xa687, "\xea\x9a\x86"}, + {0xa689, "\xea\x9a\x88"}, + {0xa68b, "\xea\x9a\x8a"}, + {0xa68d, "\xea\x9a\x8c"}, + {0xa68f, "\xea\x9a\x8e"}, + {0xa691, "\xea\x9a\x90"}, + {0xa693, "\xea\x9a\x92"}, + {0xa695, "\xea\x9a\x94"}, + {0xa697, "\xea\x9a\x96"}, + {0xa699, "\xea\x9a\x98"}, + {0xa69b, "\xea\x9a\x9a"}, + {0xa723, "\xea\x9c\xa2"}, + {0xa725, "\xea\x9c\xa4"}, + {0xa727, "\xea\x9c\xa6"}, + {0xa729, "\xea\x9c\xa8"}, + {0xa72b, "\xea\x9c\xaa"}, + {0xa72d, "\xea\x9c\xac"}, + {0xa72f, "\xea\x9c\xae"}, + {0xa733, "\xea\x9c\xb2"}, + {0xa735, "\xea\x9c\xb4"}, + {0xa737, "\xea\x9c\xb6"}, + {0xa739, "\xea\x9c\xb8"}, + {0xa73b, "\xea\x9c\xba"}, + {0xa73d, "\xea\x9c\xbc"}, + {0xa73f, "\xea\x9c\xbe"}, + {0xa741, "\xea\x9d\x80"}, + {0xa743, "\xea\x9d\x82"}, + {0xa745, "\xea\x9d\x84"}, + {0xa747, "\xea\x9d\x86"}, + {0xa749, "\xea\x9d\x88"}, + {0xa74b, "\xea\x9d\x8a"}, + {0xa74d, "\xea\x9d\x8c"}, + {0xa74f, "\xea\x9d\x8e"}, + {0xa751, "\xea\x9d\x90"}, + {0xa753, "\xea\x9d\x92"}, + {0xa755, "\xea\x9d\x94"}, + {0xa757, "\xea\x9d\x96"}, + {0xa759, "\xea\x9d\x98"}, + {0xa75b, "\xea\x9d\x9a"}, + {0xa75d, "\xea\x9d\x9c"}, + {0xa75f, "\xea\x9d\x9e"}, + {0xa761, "\xea\x9d\xa0"}, + {0xa763, "\xea\x9d\xa2"}, + {0xa765, "\xea\x9d\xa4"}, + {0xa767, "\xea\x9d\xa6"}, + {0xa769, "\xea\x9d\xa8"}, + {0xa76b, "\xea\x9d\xaa"}, + {0xa76d, "\xea\x9d\xac"}, + {0xa76f, "\xea\x9d\xae"}, + {0xa77a, "\xea\x9d\xb9"}, + {0xa77c, "\xea\x9d\xbb"}, + {0xa77f, "\xea\x9d\xbe"}, + {0xa781, "\xea\x9e\x80"}, + {0xa783, "\xea\x9e\x82"}, + {0xa785, "\xea\x9e\x84"}, + {0xa787, "\xea\x9e\x86"}, + {0xa78c, "\xea\x9e\x8b"}, + {0xa791, "\xea\x9e\x90"}, + {0xa793, "\xea\x9e\x92"}, + {0xa794, "\xea\x9f\x84"}, + {0xa797, "\xea\x9e\x96"}, + {0xa799, "\xea\x9e\x98"}, + {0xa79b, "\xea\x9e\x9a"}, + {0xa79d, "\xea\x9e\x9c"}, + {0xa79f, "\xea\x9e\x9e"}, + {0xa7a1, "\xea\x9e\xa0"}, + {0xa7a3, "\xea\x9e\xa2"}, + {0xa7a5, "\xea\x9e\xa4"}, + {0xa7a7, "\xea\x9e\xa6"}, + {0xa7a9, "\xea\x9e\xa8"}, + {0xa7b5, "\xea\x9e\xb4"}, + {0xa7b7, "\xea\x9e\xb6"}, + {0xa7b9, "\xea\x9e\xb8"}, + {0xa7bb, "\xea\x9e\xba"}, + {0xa7bd, "\xea\x9e\xbc"}, + {0xa7bf, "\xea\x9e\xbe"}, + {0xa7c1, "\xea\x9f\x80"}, + {0xa7c3, "\xea\x9f\x82"}, + {0xa7c8, "\xea\x9f\x87"}, + {0xa7ca, "\xea\x9f\x89"}, + {0xa7cd, "\xea\x9f\x8c"}, + {0xa7d1, "\xea\x9f\x90"}, + {0xa7d7, "\xea\x9f\x96"}, + {0xa7d9, "\xea\x9f\x98"}, + {0xa7db, "\xea\x9f\x9a"}, + {0xa7f6, "\xea\x9f\xb5"}, + {0xab53, "\xea\x9e\xb3"}, + {0xab70, "\xe1\x8e\xa0"}, + {0xab71, "\xe1\x8e\xa1"}, + {0xab72, "\xe1\x8e\xa2"}, + {0xab73, "\xe1\x8e\xa3"}, + {0xab74, "\xe1\x8e\xa4"}, + {0xab75, "\xe1\x8e\xa5"}, + {0xab76, "\xe1\x8e\xa6"}, + {0xab77, "\xe1\x8e\xa7"}, + {0xab78, "\xe1\x8e\xa8"}, + {0xab79, "\xe1\x8e\xa9"}, + {0xab7a, "\xe1\x8e\xaa"}, + {0xab7b, "\xe1\x8e\xab"}, + {0xab7c, "\xe1\x8e\xac"}, + {0xab7d, "\xe1\x8e\xad"}, + {0xab7e, "\xe1\x8e\xae"}, + {0xab7f, "\xe1\x8e\xaf"}, + {0xab80, "\xe1\x8e\xb0"}, + {0xab81, "\xe1\x8e\xb1"}, + {0xab82, "\xe1\x8e\xb2"}, + {0xab83, "\xe1\x8e\xb3"}, + {0xab84, "\xe1\x8e\xb4"}, + {0xab85, "\xe1\x8e\xb5"}, + {0xab86, "\xe1\x8e\xb6"}, + {0xab87, "\xe1\x8e\xb7"}, + {0xab88, "\xe1\x8e\xb8"}, + {0xab89, "\xe1\x8e\xb9"}, + {0xab8a, "\xe1\x8e\xba"}, + {0xab8b, "\xe1\x8e\xbb"}, + {0xab8c, "\xe1\x8e\xbc"}, + {0xab8d, "\xe1\x8e\xbd"}, + {0xab8e, "\xe1\x8e\xbe"}, + {0xab8f, "\xe1\x8e\xbf"}, + {0xab90, "\xe1\x8f\x80"}, + {0xab91, "\xe1\x8f\x81"}, + {0xab92, "\xe1\x8f\x82"}, + {0xab93, "\xe1\x8f\x83"}, + {0xab94, "\xe1\x8f\x84"}, + {0xab95, "\xe1\x8f\x85"}, + {0xab96, "\xe1\x8f\x86"}, + {0xab97, "\xe1\x8f\x87"}, + {0xab98, "\xe1\x8f\x88"}, + {0xab99, "\xe1\x8f\x89"}, + {0xab9a, "\xe1\x8f\x8a"}, + {0xab9b, "\xe1\x8f\x8b"}, + {0xab9c, "\xe1\x8f\x8c"}, + {0xab9d, "\xe1\x8f\x8d"}, + {0xab9e, "\xe1\x8f\x8e"}, + {0xab9f, "\xe1\x8f\x8f"}, + {0xaba0, "\xe1\x8f\x90"}, + {0xaba1, "\xe1\x8f\x91"}, + {0xaba2, "\xe1\x8f\x92"}, + {0xaba3, "\xe1\x8f\x93"}, + {0xaba4, "\xe1\x8f\x94"}, + {0xaba5, "\xe1\x8f\x95"}, + {0xaba6, "\xe1\x8f\x96"}, + {0xaba7, "\xe1\x8f\x97"}, + {0xaba8, "\xe1\x8f\x98"}, + {0xaba9, "\xe1\x8f\x99"}, + {0xabaa, "\xe1\x8f\x9a"}, + {0xabab, "\xe1\x8f\x9b"}, + {0xabac, "\xe1\x8f\x9c"}, + {0xabad, "\xe1\x8f\x9d"}, + {0xabae, "\xe1\x8f\x9e"}, + {0xabaf, "\xe1\x8f\x9f"}, + {0xabb0, "\xe1\x8f\xa0"}, + {0xabb1, "\xe1\x8f\xa1"}, + {0xabb2, "\xe1\x8f\xa2"}, + {0xabb3, "\xe1\x8f\xa3"}, + {0xabb4, "\xe1\x8f\xa4"}, + {0xabb5, "\xe1\x8f\xa5"}, + {0xabb6, "\xe1\x8f\xa6"}, + {0xabb7, "\xe1\x8f\xa7"}, + {0xabb8, "\xe1\x8f\xa8"}, + {0xabb9, "\xe1\x8f\xa9"}, + {0xabba, "\xe1\x8f\xaa"}, + {0xabbb, "\xe1\x8f\xab"}, + {0xabbc, "\xe1\x8f\xac"}, + {0xabbd, "\xe1\x8f\xad"}, + {0xabbe, "\xe1\x8f\xae"}, + {0xabbf, "\xe1\x8f\xaf"}, + {0xfb00, "FF"}, + {0xfb01, "FI"}, + {0xfb02, "FL"}, + {0xfb03, "FFI"}, + {0xfb04, "FFL"}, + {0xfb05, "ST"}, + {0xfb06, "ST"}, + {0xfb13, "\xd5\x84\xd5\x86"}, + {0xfb14, "\xd5\x84\xd4\xb5"}, + {0xfb15, "\xd5\x84\xd4\xbb"}, + {0xfb16, "\xd5\x8e\xd5\x86"}, + {0xfb17, "\xd5\x84\xd4\xbd"}, + {0xff41, "\xef\xbc\xa1"}, + {0xff42, "\xef\xbc\xa2"}, + {0xff43, "\xef\xbc\xa3"}, + {0xff44, "\xef\xbc\xa4"}, + {0xff45, "\xef\xbc\xa5"}, + {0xff46, "\xef\xbc\xa6"}, + {0xff47, "\xef\xbc\xa7"}, + {0xff48, "\xef\xbc\xa8"}, + {0xff49, "\xef\xbc\xa9"}, + {0xff4a, "\xef\xbc\xaa"}, + {0xff4b, "\xef\xbc\xab"}, + {0xff4c, "\xef\xbc\xac"}, + {0xff4d, "\xef\xbc\xad"}, + {0xff4e, "\xef\xbc\xae"}, + {0xff4f, "\xef\xbc\xaf"}, + {0xff50, "\xef\xbc\xb0"}, + {0xff51, "\xef\xbc\xb1"}, + {0xff52, "\xef\xbc\xb2"}, + {0xff53, "\xef\xbc\xb3"}, + {0xff54, "\xef\xbc\xb4"}, + {0xff55, "\xef\xbc\xb5"}, + {0xff56, "\xef\xbc\xb6"}, + {0xff57, "\xef\xbc\xb7"}, + {0xff58, "\xef\xbc\xb8"}, + {0xff59, "\xef\xbc\xb9"}, + {0xff5a, "\xef\xbc\xba"}, + {0x10428, "\xf0\x90\x90\x80"}, + {0x10429, "\xf0\x90\x90\x81"}, + {0x1042a, "\xf0\x90\x90\x82"}, + {0x1042b, "\xf0\x90\x90\x83"}, + {0x1042c, "\xf0\x90\x90\x84"}, + {0x1042d, "\xf0\x90\x90\x85"}, + {0x1042e, "\xf0\x90\x90\x86"}, + {0x1042f, "\xf0\x90\x90\x87"}, + {0x10430, "\xf0\x90\x90\x88"}, + {0x10431, "\xf0\x90\x90\x89"}, + {0x10432, "\xf0\x90\x90\x8a"}, + {0x10433, "\xf0\x90\x90\x8b"}, + {0x10434, "\xf0\x90\x90\x8c"}, + {0x10435, "\xf0\x90\x90\x8d"}, + {0x10436, "\xf0\x90\x90\x8e"}, + {0x10437, "\xf0\x90\x90\x8f"}, + {0x10438, "\xf0\x90\x90\x90"}, + {0x10439, "\xf0\x90\x90\x91"}, + {0x1043a, "\xf0\x90\x90\x92"}, + {0x1043b, "\xf0\x90\x90\x93"}, + {0x1043c, "\xf0\x90\x90\x94"}, + {0x1043d, "\xf0\x90\x90\x95"}, + {0x1043e, "\xf0\x90\x90\x96"}, + {0x1043f, "\xf0\x90\x90\x97"}, + {0x10440, "\xf0\x90\x90\x98"}, + {0x10441, "\xf0\x90\x90\x99"}, + {0x10442, "\xf0\x90\x90\x9a"}, + {0x10443, "\xf0\x90\x90\x9b"}, + {0x10444, "\xf0\x90\x90\x9c"}, + {0x10445, "\xf0\x90\x90\x9d"}, + {0x10446, "\xf0\x90\x90\x9e"}, + {0x10447, "\xf0\x90\x90\x9f"}, + {0x10448, "\xf0\x90\x90\xa0"}, + {0x10449, "\xf0\x90\x90\xa1"}, + {0x1044a, "\xf0\x90\x90\xa2"}, + {0x1044b, "\xf0\x90\x90\xa3"}, + {0x1044c, "\xf0\x90\x90\xa4"}, + {0x1044d, "\xf0\x90\x90\xa5"}, + {0x1044e, "\xf0\x90\x90\xa6"}, + {0x1044f, "\xf0\x90\x90\xa7"}, + {0x104d8, "\xf0\x90\x92\xb0"}, + {0x104d9, "\xf0\x90\x92\xb1"}, + {0x104da, "\xf0\x90\x92\xb2"}, + {0x104db, "\xf0\x90\x92\xb3"}, + {0x104dc, "\xf0\x90\x92\xb4"}, + {0x104dd, "\xf0\x90\x92\xb5"}, + {0x104de, "\xf0\x90\x92\xb6"}, + {0x104df, "\xf0\x90\x92\xb7"}, + {0x104e0, "\xf0\x90\x92\xb8"}, + {0x104e1, "\xf0\x90\x92\xb9"}, + {0x104e2, "\xf0\x90\x92\xba"}, + {0x104e3, "\xf0\x90\x92\xbb"}, + {0x104e4, "\xf0\x90\x92\xbc"}, + {0x104e5, "\xf0\x90\x92\xbd"}, + {0x104e6, "\xf0\x90\x92\xbe"}, + {0x104e7, "\xf0\x90\x92\xbf"}, + {0x104e8, "\xf0\x90\x93\x80"}, + {0x104e9, "\xf0\x90\x93\x81"}, + {0x104ea, "\xf0\x90\x93\x82"}, + {0x104eb, "\xf0\x90\x93\x83"}, + {0x104ec, "\xf0\x90\x93\x84"}, + {0x104ed, "\xf0\x90\x93\x85"}, + {0x104ee, "\xf0\x90\x93\x86"}, + {0x104ef, "\xf0\x90\x93\x87"}, + {0x104f0, "\xf0\x90\x93\x88"}, + {0x104f1, "\xf0\x90\x93\x89"}, + {0x104f2, "\xf0\x90\x93\x8a"}, + {0x104f3, "\xf0\x90\x93\x8b"}, + {0x104f4, "\xf0\x90\x93\x8c"}, + {0x104f5, "\xf0\x90\x93\x8d"}, + {0x104f6, "\xf0\x90\x93\x8e"}, + {0x104f7, "\xf0\x90\x93\x8f"}, + {0x104f8, "\xf0\x90\x93\x90"}, + {0x104f9, "\xf0\x90\x93\x91"}, + {0x104fa, "\xf0\x90\x93\x92"}, + {0x104fb, "\xf0\x90\x93\x93"}, + {0x10597, "\xf0\x90\x95\xb0"}, + {0x10598, "\xf0\x90\x95\xb1"}, + {0x10599, "\xf0\x90\x95\xb2"}, + {0x1059a, "\xf0\x90\x95\xb3"}, + {0x1059b, "\xf0\x90\x95\xb4"}, + {0x1059c, "\xf0\x90\x95\xb5"}, + {0x1059d, "\xf0\x90\x95\xb6"}, + {0x1059e, "\xf0\x90\x95\xb7"}, + {0x1059f, "\xf0\x90\x95\xb8"}, + {0x105a0, "\xf0\x90\x95\xb9"}, + {0x105a1, "\xf0\x90\x95\xba"}, + {0x105a3, "\xf0\x90\x95\xbc"}, + {0x105a4, "\xf0\x90\x95\xbd"}, + {0x105a5, "\xf0\x90\x95\xbe"}, + {0x105a6, "\xf0\x90\x95\xbf"}, + {0x105a7, "\xf0\x90\x96\x80"}, + {0x105a8, "\xf0\x90\x96\x81"}, + {0x105a9, "\xf0\x90\x96\x82"}, + {0x105aa, "\xf0\x90\x96\x83"}, + {0x105ab, "\xf0\x90\x96\x84"}, + {0x105ac, "\xf0\x90\x96\x85"}, + {0x105ad, "\xf0\x90\x96\x86"}, + {0x105ae, "\xf0\x90\x96\x87"}, + {0x105af, "\xf0\x90\x96\x88"}, + {0x105b0, "\xf0\x90\x96\x89"}, + {0x105b1, "\xf0\x90\x96\x8a"}, + {0x105b3, "\xf0\x90\x96\x8c"}, + {0x105b4, "\xf0\x90\x96\x8d"}, + {0x105b5, "\xf0\x90\x96\x8e"}, + {0x105b6, "\xf0\x90\x96\x8f"}, + {0x105b7, "\xf0\x90\x96\x90"}, + {0x105b8, "\xf0\x90\x96\x91"}, + {0x105b9, "\xf0\x90\x96\x92"}, + {0x105bb, "\xf0\x90\x96\x94"}, + {0x105bc, "\xf0\x90\x96\x95"}, + {0x10cc0, "\xf0\x90\xb2\x80"}, + {0x10cc1, "\xf0\x90\xb2\x81"}, + {0x10cc2, "\xf0\x90\xb2\x82"}, + {0x10cc3, "\xf0\x90\xb2\x83"}, + {0x10cc4, "\xf0\x90\xb2\x84"}, + {0x10cc5, "\xf0\x90\xb2\x85"}, + {0x10cc6, "\xf0\x90\xb2\x86"}, + {0x10cc7, "\xf0\x90\xb2\x87"}, + {0x10cc8, "\xf0\x90\xb2\x88"}, + {0x10cc9, "\xf0\x90\xb2\x89"}, + {0x10cca, "\xf0\x90\xb2\x8a"}, + {0x10ccb, "\xf0\x90\xb2\x8b"}, + {0x10ccc, "\xf0\x90\xb2\x8c"}, + {0x10ccd, "\xf0\x90\xb2\x8d"}, + {0x10cce, "\xf0\x90\xb2\x8e"}, + {0x10ccf, "\xf0\x90\xb2\x8f"}, + {0x10cd0, "\xf0\x90\xb2\x90"}, + {0x10cd1, "\xf0\x90\xb2\x91"}, + {0x10cd2, "\xf0\x90\xb2\x92"}, + {0x10cd3, "\xf0\x90\xb2\x93"}, + {0x10cd4, "\xf0\x90\xb2\x94"}, + {0x10cd5, "\xf0\x90\xb2\x95"}, + {0x10cd6, "\xf0\x90\xb2\x96"}, + {0x10cd7, "\xf0\x90\xb2\x97"}, + {0x10cd8, "\xf0\x90\xb2\x98"}, + {0x10cd9, "\xf0\x90\xb2\x99"}, + {0x10cda, "\xf0\x90\xb2\x9a"}, + {0x10cdb, "\xf0\x90\xb2\x9b"}, + {0x10cdc, "\xf0\x90\xb2\x9c"}, + {0x10cdd, "\xf0\x90\xb2\x9d"}, + {0x10cde, "\xf0\x90\xb2\x9e"}, + {0x10cdf, "\xf0\x90\xb2\x9f"}, + {0x10ce0, "\xf0\x90\xb2\xa0"}, + {0x10ce1, "\xf0\x90\xb2\xa1"}, + {0x10ce2, "\xf0\x90\xb2\xa2"}, + {0x10ce3, "\xf0\x90\xb2\xa3"}, + {0x10ce4, "\xf0\x90\xb2\xa4"}, + {0x10ce5, "\xf0\x90\xb2\xa5"}, + {0x10ce6, "\xf0\x90\xb2\xa6"}, + {0x10ce7, "\xf0\x90\xb2\xa7"}, + {0x10ce8, "\xf0\x90\xb2\xa8"}, + {0x10ce9, "\xf0\x90\xb2\xa9"}, + {0x10cea, "\xf0\x90\xb2\xaa"}, + {0x10ceb, "\xf0\x90\xb2\xab"}, + {0x10cec, "\xf0\x90\xb2\xac"}, + {0x10ced, "\xf0\x90\xb2\xad"}, + {0x10cee, "\xf0\x90\xb2\xae"}, + {0x10cef, "\xf0\x90\xb2\xaf"}, + {0x10cf0, "\xf0\x90\xb2\xb0"}, + {0x10cf1, "\xf0\x90\xb2\xb1"}, + {0x10cf2, "\xf0\x90\xb2\xb2"}, + {0x10d70, "\xf0\x90\xb5\x90"}, + {0x10d71, "\xf0\x90\xb5\x91"}, + {0x10d72, "\xf0\x90\xb5\x92"}, + {0x10d73, "\xf0\x90\xb5\x93"}, + {0x10d74, "\xf0\x90\xb5\x94"}, + {0x10d75, "\xf0\x90\xb5\x95"}, + {0x10d76, "\xf0\x90\xb5\x96"}, + {0x10d77, "\xf0\x90\xb5\x97"}, + {0x10d78, "\xf0\x90\xb5\x98"}, + {0x10d79, "\xf0\x90\xb5\x99"}, + {0x10d7a, "\xf0\x90\xb5\x9a"}, + {0x10d7b, "\xf0\x90\xb5\x9b"}, + {0x10d7c, "\xf0\x90\xb5\x9c"}, + {0x10d7d, "\xf0\x90\xb5\x9d"}, + {0x10d7e, "\xf0\x90\xb5\x9e"}, + {0x10d7f, "\xf0\x90\xb5\x9f"}, + {0x10d80, "\xf0\x90\xb5\xa0"}, + {0x10d81, "\xf0\x90\xb5\xa1"}, + {0x10d82, "\xf0\x90\xb5\xa2"}, + {0x10d83, "\xf0\x90\xb5\xa3"}, + {0x10d84, "\xf0\x90\xb5\xa4"}, + {0x10d85, "\xf0\x90\xb5\xa5"}, + {0x118c0, "\xf0\x91\xa2\xa0"}, + {0x118c1, "\xf0\x91\xa2\xa1"}, + {0x118c2, "\xf0\x91\xa2\xa2"}, + {0x118c3, "\xf0\x91\xa2\xa3"}, + {0x118c4, "\xf0\x91\xa2\xa4"}, + {0x118c5, "\xf0\x91\xa2\xa5"}, + {0x118c6, "\xf0\x91\xa2\xa6"}, + {0x118c7, "\xf0\x91\xa2\xa7"}, + {0x118c8, "\xf0\x91\xa2\xa8"}, + {0x118c9, "\xf0\x91\xa2\xa9"}, + {0x118ca, "\xf0\x91\xa2\xaa"}, + {0x118cb, "\xf0\x91\xa2\xab"}, + {0x118cc, "\xf0\x91\xa2\xac"}, + {0x118cd, "\xf0\x91\xa2\xad"}, + {0x118ce, "\xf0\x91\xa2\xae"}, + {0x118cf, "\xf0\x91\xa2\xaf"}, + {0x118d0, "\xf0\x91\xa2\xb0"}, + {0x118d1, "\xf0\x91\xa2\xb1"}, + {0x118d2, "\xf0\x91\xa2\xb2"}, + {0x118d3, "\xf0\x91\xa2\xb3"}, + {0x118d4, "\xf0\x91\xa2\xb4"}, + {0x118d5, "\xf0\x91\xa2\xb5"}, + {0x118d6, "\xf0\x91\xa2\xb6"}, + {0x118d7, "\xf0\x91\xa2\xb7"}, + {0x118d8, "\xf0\x91\xa2\xb8"}, + {0x118d9, "\xf0\x91\xa2\xb9"}, + {0x118da, "\xf0\x91\xa2\xba"}, + {0x118db, "\xf0\x91\xa2\xbb"}, + {0x118dc, "\xf0\x91\xa2\xbc"}, + {0x118dd, "\xf0\x91\xa2\xbd"}, + {0x118de, "\xf0\x91\xa2\xbe"}, + {0x118df, "\xf0\x91\xa2\xbf"}, + {0x16e60, "\xf0\x96\xb9\x80"}, + {0x16e61, "\xf0\x96\xb9\x81"}, + {0x16e62, "\xf0\x96\xb9\x82"}, + {0x16e63, "\xf0\x96\xb9\x83"}, + {0x16e64, "\xf0\x96\xb9\x84"}, + {0x16e65, "\xf0\x96\xb9\x85"}, + {0x16e66, "\xf0\x96\xb9\x86"}, + {0x16e67, "\xf0\x96\xb9\x87"}, + {0x16e68, "\xf0\x96\xb9\x88"}, + {0x16e69, "\xf0\x96\xb9\x89"}, + {0x16e6a, "\xf0\x96\xb9\x8a"}, + {0x16e6b, "\xf0\x96\xb9\x8b"}, + {0x16e6c, "\xf0\x96\xb9\x8c"}, + {0x16e6d, "\xf0\x96\xb9\x8d"}, + {0x16e6e, "\xf0\x96\xb9\x8e"}, + {0x16e6f, "\xf0\x96\xb9\x8f"}, + {0x16e70, "\xf0\x96\xb9\x90"}, + {0x16e71, "\xf0\x96\xb9\x91"}, + {0x16e72, "\xf0\x96\xb9\x92"}, + {0x16e73, "\xf0\x96\xb9\x93"}, + {0x16e74, "\xf0\x96\xb9\x94"}, + {0x16e75, "\xf0\x96\xb9\x95"}, + {0x16e76, "\xf0\x96\xb9\x96"}, + {0x16e77, "\xf0\x96\xb9\x97"}, + {0x16e78, "\xf0\x96\xb9\x98"}, + {0x16e79, "\xf0\x96\xb9\x99"}, + {0x16e7a, "\xf0\x96\xb9\x9a"}, + {0x16e7b, "\xf0\x96\xb9\x9b"}, + {0x16e7c, "\xf0\x96\xb9\x9c"}, + {0x16e7d, "\xf0\x96\xb9\x9d"}, + {0x16e7e, "\xf0\x96\xb9\x9e"}, + {0x16e7f, "\xf0\x96\xb9\x9f"}, + {0x1e922, "\xf0\x9e\xa4\x80"}, + {0x1e923, "\xf0\x9e\xa4\x81"}, + {0x1e924, "\xf0\x9e\xa4\x82"}, + {0x1e925, "\xf0\x9e\xa4\x83"}, + {0x1e926, "\xf0\x9e\xa4\x84"}, + {0x1e927, "\xf0\x9e\xa4\x85"}, + {0x1e928, "\xf0\x9e\xa4\x86"}, + {0x1e929, "\xf0\x9e\xa4\x87"}, + {0x1e92a, "\xf0\x9e\xa4\x88"}, + {0x1e92b, "\xf0\x9e\xa4\x89"}, + {0x1e92c, "\xf0\x9e\xa4\x8a"}, + {0x1e92d, "\xf0\x9e\xa4\x8b"}, + {0x1e92e, "\xf0\x9e\xa4\x8c"}, + {0x1e92f, "\xf0\x9e\xa4\x8d"}, + {0x1e930, "\xf0\x9e\xa4\x8e"}, + {0x1e931, "\xf0\x9e\xa4\x8f"}, + {0x1e932, "\xf0\x9e\xa4\x90"}, + {0x1e933, "\xf0\x9e\xa4\x91"}, + {0x1e934, "\xf0\x9e\xa4\x92"}, + {0x1e935, "\xf0\x9e\xa4\x93"}, + {0x1e936, "\xf0\x9e\xa4\x94"}, + {0x1e937, "\xf0\x9e\xa4\x95"}, + {0x1e938, "\xf0\x9e\xa4\x96"}, + {0x1e939, "\xf0\x9e\xa4\x97"}, + {0x1e93a, "\xf0\x9e\xa4\x98"}, + {0x1e93b, "\xf0\x9e\xa4\x99"}, + {0x1e93c, "\xf0\x9e\xa4\x9a"}, + {0x1e93d, "\xf0\x9e\xa4\x9b"}, + {0x1e93e, "\xf0\x9e\xa4\x9c"}, + {0x1e93f, "\xf0\x9e\xa4\x9d"}, + {0x1e940, "\xf0\x9e\xa4\x9e"}, + {0x1e941, "\xf0\x9e\xa4\x9f"}, + {0x1e942, "\xf0\x9e\xa4\xa0"}, + {0x1e943, "\xf0\x9e\xa4\xa1"}, + }}; + + // Printable code points: what ReprValue() leaves unescaped, as runs. + constexpr std::array Printable = {{ + {0x20, 0x7e}, + {0xa1, 0xac}, + {0xae, 0x377}, + {0x37a, 0x37f}, + {0x384, 0x38a}, + {0x38c, 0x38c}, + {0x38e, 0x3a1}, + {0x3a3, 0x52f}, + {0x531, 0x556}, + {0x559, 0x58a}, + {0x58d, 0x58f}, + {0x591, 0x5c7}, + {0x5d0, 0x5ea}, + {0x5ef, 0x5f4}, + {0x606, 0x61b}, + {0x61d, 0x6dc}, + {0x6de, 0x70d}, + {0x710, 0x74a}, + {0x74d, 0x7b1}, + {0x7c0, 0x7fa}, + {0x7fd, 0x82d}, + {0x830, 0x83e}, + {0x840, 0x85b}, + {0x85e, 0x85e}, + {0x860, 0x86a}, + {0x870, 0x88e}, + {0x897, 0x8e1}, + {0x8e3, 0x983}, + {0x985, 0x98c}, + {0x98f, 0x990}, + {0x993, 0x9a8}, + {0x9aa, 0x9b0}, + {0x9b2, 0x9b2}, + {0x9b6, 0x9b9}, + {0x9bc, 0x9c4}, + {0x9c7, 0x9c8}, + {0x9cb, 0x9ce}, + {0x9d7, 0x9d7}, + {0x9dc, 0x9dd}, + {0x9df, 0x9e3}, + {0x9e6, 0x9fe}, + {0xa01, 0xa03}, + {0xa05, 0xa0a}, + {0xa0f, 0xa10}, + {0xa13, 0xa28}, + {0xa2a, 0xa30}, + {0xa32, 0xa33}, + {0xa35, 0xa36}, + {0xa38, 0xa39}, + {0xa3c, 0xa3c}, + {0xa3e, 0xa42}, + {0xa47, 0xa48}, + {0xa4b, 0xa4d}, + {0xa51, 0xa51}, + {0xa59, 0xa5c}, + {0xa5e, 0xa5e}, + {0xa66, 0xa76}, + {0xa81, 0xa83}, + {0xa85, 0xa8d}, + {0xa8f, 0xa91}, + {0xa93, 0xaa8}, + {0xaaa, 0xab0}, + {0xab2, 0xab3}, + {0xab5, 0xab9}, + {0xabc, 0xac5}, + {0xac7, 0xac9}, + {0xacb, 0xacd}, + {0xad0, 0xad0}, + {0xae0, 0xae3}, + {0xae6, 0xaf1}, + {0xaf9, 0xaff}, + {0xb01, 0xb03}, + {0xb05, 0xb0c}, + {0xb0f, 0xb10}, + {0xb13, 0xb28}, + {0xb2a, 0xb30}, + {0xb32, 0xb33}, + {0xb35, 0xb39}, + {0xb3c, 0xb44}, + {0xb47, 0xb48}, + {0xb4b, 0xb4d}, + {0xb55, 0xb57}, + {0xb5c, 0xb5d}, + {0xb5f, 0xb63}, + {0xb66, 0xb77}, + {0xb82, 0xb83}, + {0xb85, 0xb8a}, + {0xb8e, 0xb90}, + {0xb92, 0xb95}, + {0xb99, 0xb9a}, + {0xb9c, 0xb9c}, + {0xb9e, 0xb9f}, + {0xba3, 0xba4}, + {0xba8, 0xbaa}, + {0xbae, 0xbb9}, + {0xbbe, 0xbc2}, + {0xbc6, 0xbc8}, + {0xbca, 0xbcd}, + {0xbd0, 0xbd0}, + {0xbd7, 0xbd7}, + {0xbe6, 0xbfa}, + {0xc00, 0xc0c}, + {0xc0e, 0xc10}, + {0xc12, 0xc28}, + {0xc2a, 0xc39}, + {0xc3c, 0xc44}, + {0xc46, 0xc48}, + {0xc4a, 0xc4d}, + {0xc55, 0xc56}, + {0xc58, 0xc5a}, + {0xc5d, 0xc5d}, + {0xc60, 0xc63}, + {0xc66, 0xc6f}, + {0xc77, 0xc8c}, + {0xc8e, 0xc90}, + {0xc92, 0xca8}, + {0xcaa, 0xcb3}, + {0xcb5, 0xcb9}, + {0xcbc, 0xcc4}, + {0xcc6, 0xcc8}, + {0xcca, 0xccd}, + {0xcd5, 0xcd6}, + {0xcdd, 0xcde}, + {0xce0, 0xce3}, + {0xce6, 0xcef}, + {0xcf1, 0xcf3}, + {0xd00, 0xd0c}, + {0xd0e, 0xd10}, + {0xd12, 0xd44}, + {0xd46, 0xd48}, + {0xd4a, 0xd4f}, + {0xd54, 0xd63}, + {0xd66, 0xd7f}, + {0xd81, 0xd83}, + {0xd85, 0xd96}, + {0xd9a, 0xdb1}, + {0xdb3, 0xdbb}, + {0xdbd, 0xdbd}, + {0xdc0, 0xdc6}, + {0xdca, 0xdca}, + {0xdcf, 0xdd4}, + {0xdd6, 0xdd6}, + {0xdd8, 0xddf}, + {0xde6, 0xdef}, + {0xdf2, 0xdf4}, + {0xe01, 0xe3a}, + {0xe3f, 0xe5b}, + {0xe81, 0xe82}, + {0xe84, 0xe84}, + {0xe86, 0xe8a}, + {0xe8c, 0xea3}, + {0xea5, 0xea5}, + {0xea7, 0xebd}, + {0xec0, 0xec4}, + {0xec6, 0xec6}, + {0xec8, 0xece}, + {0xed0, 0xed9}, + {0xedc, 0xedf}, + {0xf00, 0xf47}, + {0xf49, 0xf6c}, + {0xf71, 0xf97}, + {0xf99, 0xfbc}, + {0xfbe, 0xfcc}, + {0xfce, 0xfda}, + {0x1000, 0x10c5}, + {0x10c7, 0x10c7}, + {0x10cd, 0x10cd}, + {0x10d0, 0x1248}, + {0x124a, 0x124d}, + {0x1250, 0x1256}, + {0x1258, 0x1258}, + {0x125a, 0x125d}, + {0x1260, 0x1288}, + {0x128a, 0x128d}, + {0x1290, 0x12b0}, + {0x12b2, 0x12b5}, + {0x12b8, 0x12be}, + {0x12c0, 0x12c0}, + {0x12c2, 0x12c5}, + {0x12c8, 0x12d6}, + {0x12d8, 0x1310}, + {0x1312, 0x1315}, + {0x1318, 0x135a}, + {0x135d, 0x137c}, + {0x1380, 0x1399}, + {0x13a0, 0x13f5}, + {0x13f8, 0x13fd}, + {0x1400, 0x167f}, + {0x1681, 0x169c}, + {0x16a0, 0x16f8}, + {0x1700, 0x1715}, + {0x171f, 0x1736}, + {0x1740, 0x1753}, + {0x1760, 0x176c}, + {0x176e, 0x1770}, + {0x1772, 0x1773}, + {0x1780, 0x17dd}, + {0x17e0, 0x17e9}, + {0x17f0, 0x17f9}, + {0x1800, 0x180d}, + {0x180f, 0x1819}, + {0x1820, 0x1878}, + {0x1880, 0x18aa}, + {0x18b0, 0x18f5}, + {0x1900, 0x191e}, + {0x1920, 0x192b}, + {0x1930, 0x193b}, + {0x1940, 0x1940}, + {0x1944, 0x196d}, + {0x1970, 0x1974}, + {0x1980, 0x19ab}, + {0x19b0, 0x19c9}, + {0x19d0, 0x19da}, + {0x19de, 0x1a1b}, + {0x1a1e, 0x1a5e}, + {0x1a60, 0x1a7c}, + {0x1a7f, 0x1a89}, + {0x1a90, 0x1a99}, + {0x1aa0, 0x1aad}, + {0x1ab0, 0x1ace}, + {0x1b00, 0x1b4c}, + {0x1b4e, 0x1bf3}, + {0x1bfc, 0x1c37}, + {0x1c3b, 0x1c49}, + {0x1c4d, 0x1c8a}, + {0x1c90, 0x1cba}, + {0x1cbd, 0x1cc7}, + {0x1cd0, 0x1cfa}, + {0x1d00, 0x1f15}, + {0x1f18, 0x1f1d}, + {0x1f20, 0x1f45}, + {0x1f48, 0x1f4d}, + {0x1f50, 0x1f57}, + {0x1f59, 0x1f59}, + {0x1f5b, 0x1f5b}, + {0x1f5d, 0x1f5d}, + {0x1f5f, 0x1f7d}, + {0x1f80, 0x1fb4}, + {0x1fb6, 0x1fc4}, + {0x1fc6, 0x1fd3}, + {0x1fd6, 0x1fdb}, + {0x1fdd, 0x1fef}, + {0x1ff2, 0x1ff4}, + {0x1ff6, 0x1ffe}, + {0x2010, 0x2027}, + {0x2030, 0x205e}, + {0x2070, 0x2071}, + {0x2074, 0x208e}, + {0x2090, 0x209c}, + {0x20a0, 0x20c0}, + {0x20d0, 0x20f0}, + {0x2100, 0x218b}, + {0x2190, 0x2429}, + {0x2440, 0x244a}, + {0x2460, 0x2b73}, + {0x2b76, 0x2b95}, + {0x2b97, 0x2cf3}, + {0x2cf9, 0x2d25}, + {0x2d27, 0x2d27}, + {0x2d2d, 0x2d2d}, + {0x2d30, 0x2d67}, + {0x2d6f, 0x2d70}, + {0x2d7f, 0x2d96}, + {0x2da0, 0x2da6}, + {0x2da8, 0x2dae}, + {0x2db0, 0x2db6}, + {0x2db8, 0x2dbe}, + {0x2dc0, 0x2dc6}, + {0x2dc8, 0x2dce}, + {0x2dd0, 0x2dd6}, + {0x2dd8, 0x2dde}, + {0x2de0, 0x2e5d}, + {0x2e80, 0x2e99}, + {0x2e9b, 0x2ef3}, + {0x2f00, 0x2fd5}, + {0x2ff0, 0x2fff}, + {0x3001, 0x303f}, + {0x3041, 0x3096}, + {0x3099, 0x30ff}, + {0x3105, 0x312f}, + {0x3131, 0x318e}, + {0x3190, 0x31e5}, + {0x31ef, 0x321e}, + {0x3220, 0xa48c}, + {0xa490, 0xa4c6}, + {0xa4d0, 0xa62b}, + {0xa640, 0xa6f7}, + {0xa700, 0xa7cd}, + {0xa7d0, 0xa7d1}, + {0xa7d3, 0xa7d3}, + {0xa7d5, 0xa7dc}, + {0xa7f2, 0xa82c}, + {0xa830, 0xa839}, + {0xa840, 0xa877}, + {0xa880, 0xa8c5}, + {0xa8ce, 0xa8d9}, + {0xa8e0, 0xa953}, + {0xa95f, 0xa97c}, + {0xa980, 0xa9cd}, + {0xa9cf, 0xa9d9}, + {0xa9de, 0xa9fe}, + {0xaa00, 0xaa36}, + {0xaa40, 0xaa4d}, + {0xaa50, 0xaa59}, + {0xaa5c, 0xaac2}, + {0xaadb, 0xaaf6}, + {0xab01, 0xab06}, + {0xab09, 0xab0e}, + {0xab11, 0xab16}, + {0xab20, 0xab26}, + {0xab28, 0xab2e}, + {0xab30, 0xab6b}, + {0xab70, 0xabed}, + {0xabf0, 0xabf9}, + {0xac00, 0xd7a3}, + {0xd7b0, 0xd7c6}, + {0xd7cb, 0xd7fb}, + {0xf900, 0xfa6d}, + {0xfa70, 0xfad9}, + {0xfb00, 0xfb06}, + {0xfb13, 0xfb17}, + {0xfb1d, 0xfb36}, + {0xfb38, 0xfb3c}, + {0xfb3e, 0xfb3e}, + {0xfb40, 0xfb41}, + {0xfb43, 0xfb44}, + {0xfb46, 0xfbc2}, + {0xfbd3, 0xfd8f}, + {0xfd92, 0xfdc7}, + {0xfdcf, 0xfdcf}, + {0xfdf0, 0xfe19}, + {0xfe20, 0xfe52}, + {0xfe54, 0xfe66}, + {0xfe68, 0xfe6b}, + {0xfe70, 0xfe74}, + {0xfe76, 0xfefc}, + {0xff01, 0xffbe}, + {0xffc2, 0xffc7}, + {0xffca, 0xffcf}, + {0xffd2, 0xffd7}, + {0xffda, 0xffdc}, + {0xffe0, 0xffe6}, + {0xffe8, 0xffee}, + {0xfffc, 0xfffd}, + {0x10000, 0x1000b}, + {0x1000d, 0x10026}, + {0x10028, 0x1003a}, + {0x1003c, 0x1003d}, + {0x1003f, 0x1004d}, + {0x10050, 0x1005d}, + {0x10080, 0x100fa}, + {0x10100, 0x10102}, + {0x10107, 0x10133}, + {0x10137, 0x1018e}, + {0x10190, 0x1019c}, + {0x101a0, 0x101a0}, + {0x101d0, 0x101fd}, + {0x10280, 0x1029c}, + {0x102a0, 0x102d0}, + {0x102e0, 0x102fb}, + {0x10300, 0x10323}, + {0x1032d, 0x1034a}, + {0x10350, 0x1037a}, + {0x10380, 0x1039d}, + {0x1039f, 0x103c3}, + {0x103c8, 0x103d5}, + {0x10400, 0x1049d}, + {0x104a0, 0x104a9}, + {0x104b0, 0x104d3}, + {0x104d8, 0x104fb}, + {0x10500, 0x10527}, + {0x10530, 0x10563}, + {0x1056f, 0x1057a}, + {0x1057c, 0x1058a}, + {0x1058c, 0x10592}, + {0x10594, 0x10595}, + {0x10597, 0x105a1}, + {0x105a3, 0x105b1}, + {0x105b3, 0x105b9}, + {0x105bb, 0x105bc}, + {0x105c0, 0x105f3}, + {0x10600, 0x10736}, + {0x10740, 0x10755}, + {0x10760, 0x10767}, + {0x10780, 0x10785}, + {0x10787, 0x107b0}, + {0x107b2, 0x107ba}, + {0x10800, 0x10805}, + {0x10808, 0x10808}, + {0x1080a, 0x10835}, + {0x10837, 0x10838}, + {0x1083c, 0x1083c}, + {0x1083f, 0x10855}, + {0x10857, 0x1089e}, + {0x108a7, 0x108af}, + {0x108e0, 0x108f2}, + {0x108f4, 0x108f5}, + {0x108fb, 0x1091b}, + {0x1091f, 0x10939}, + {0x1093f, 0x1093f}, + {0x10980, 0x109b7}, + {0x109bc, 0x109cf}, + {0x109d2, 0x10a03}, + {0x10a05, 0x10a06}, + {0x10a0c, 0x10a13}, + {0x10a15, 0x10a17}, + {0x10a19, 0x10a35}, + {0x10a38, 0x10a3a}, + {0x10a3f, 0x10a48}, + {0x10a50, 0x10a58}, + {0x10a60, 0x10a9f}, + {0x10ac0, 0x10ae6}, + {0x10aeb, 0x10af6}, + {0x10b00, 0x10b35}, + {0x10b39, 0x10b55}, + {0x10b58, 0x10b72}, + {0x10b78, 0x10b91}, + {0x10b99, 0x10b9c}, + {0x10ba9, 0x10baf}, + {0x10c00, 0x10c48}, + {0x10c80, 0x10cb2}, + {0x10cc0, 0x10cf2}, + {0x10cfa, 0x10d27}, + {0x10d30, 0x10d39}, + {0x10d40, 0x10d65}, + {0x10d69, 0x10d85}, + {0x10d8e, 0x10d8f}, + {0x10e60, 0x10e7e}, + {0x10e80, 0x10ea9}, + {0x10eab, 0x10ead}, + {0x10eb0, 0x10eb1}, + {0x10ec2, 0x10ec4}, + {0x10efc, 0x10f27}, + {0x10f30, 0x10f59}, + {0x10f70, 0x10f89}, + {0x10fb0, 0x10fcb}, + {0x10fe0, 0x10ff6}, + {0x11000, 0x1104d}, + {0x11052, 0x11075}, + {0x1107f, 0x110bc}, + {0x110be, 0x110c2}, + {0x110d0, 0x110e8}, + {0x110f0, 0x110f9}, + {0x11100, 0x11134}, + {0x11136, 0x11147}, + {0x11150, 0x11176}, + {0x11180, 0x111df}, + {0x111e1, 0x111f4}, + {0x11200, 0x11211}, + {0x11213, 0x11241}, + {0x11280, 0x11286}, + {0x11288, 0x11288}, + {0x1128a, 0x1128d}, + {0x1128f, 0x1129d}, + {0x1129f, 0x112a9}, + {0x112b0, 0x112ea}, + {0x112f0, 0x112f9}, + {0x11300, 0x11303}, + {0x11305, 0x1130c}, + {0x1130f, 0x11310}, + {0x11313, 0x11328}, + {0x1132a, 0x11330}, + {0x11332, 0x11333}, + {0x11335, 0x11339}, + {0x1133b, 0x11344}, + {0x11347, 0x11348}, + {0x1134b, 0x1134d}, + {0x11350, 0x11350}, + {0x11357, 0x11357}, + {0x1135d, 0x11363}, + {0x11366, 0x1136c}, + {0x11370, 0x11374}, + {0x11380, 0x11389}, + {0x1138b, 0x1138b}, + {0x1138e, 0x1138e}, + {0x11390, 0x113b5}, + {0x113b7, 0x113c0}, + {0x113c2, 0x113c2}, + {0x113c5, 0x113c5}, + {0x113c7, 0x113ca}, + {0x113cc, 0x113d5}, + {0x113d7, 0x113d8}, + {0x113e1, 0x113e2}, + {0x11400, 0x1145b}, + {0x1145d, 0x11461}, + {0x11480, 0x114c7}, + {0x114d0, 0x114d9}, + {0x11580, 0x115b5}, + {0x115b8, 0x115dd}, + {0x11600, 0x11644}, + {0x11650, 0x11659}, + {0x11660, 0x1166c}, + {0x11680, 0x116b9}, + {0x116c0, 0x116c9}, + {0x116d0, 0x116e3}, + {0x11700, 0x1171a}, + {0x1171d, 0x1172b}, + {0x11730, 0x11746}, + {0x11800, 0x1183b}, + {0x118a0, 0x118f2}, + {0x118ff, 0x11906}, + {0x11909, 0x11909}, + {0x1190c, 0x11913}, + {0x11915, 0x11916}, + {0x11918, 0x11935}, + {0x11937, 0x11938}, + {0x1193b, 0x11946}, + {0x11950, 0x11959}, + {0x119a0, 0x119a7}, + {0x119aa, 0x119d7}, + {0x119da, 0x119e4}, + {0x11a00, 0x11a47}, + {0x11a50, 0x11aa2}, + {0x11ab0, 0x11af8}, + {0x11b00, 0x11b09}, + {0x11bc0, 0x11be1}, + {0x11bf0, 0x11bf9}, + {0x11c00, 0x11c08}, + {0x11c0a, 0x11c36}, + {0x11c38, 0x11c45}, + {0x11c50, 0x11c6c}, + {0x11c70, 0x11c8f}, + {0x11c92, 0x11ca7}, + {0x11ca9, 0x11cb6}, + {0x11d00, 0x11d06}, + {0x11d08, 0x11d09}, + {0x11d0b, 0x11d36}, + {0x11d3a, 0x11d3a}, + {0x11d3c, 0x11d3d}, + {0x11d3f, 0x11d47}, + {0x11d50, 0x11d59}, + {0x11d60, 0x11d65}, + {0x11d67, 0x11d68}, + {0x11d6a, 0x11d8e}, + {0x11d90, 0x11d91}, + {0x11d93, 0x11d98}, + {0x11da0, 0x11da9}, + {0x11ee0, 0x11ef8}, + {0x11f00, 0x11f10}, + {0x11f12, 0x11f3a}, + {0x11f3e, 0x11f5a}, + {0x11fb0, 0x11fb0}, + {0x11fc0, 0x11ff1}, + {0x11fff, 0x12399}, + {0x12400, 0x1246e}, + {0x12470, 0x12474}, + {0x12480, 0x12543}, + {0x12f90, 0x12ff2}, + {0x13000, 0x1342f}, + {0x13440, 0x13455}, + {0x13460, 0x143fa}, + {0x14400, 0x14646}, + {0x16100, 0x16139}, + {0x16800, 0x16a38}, + {0x16a40, 0x16a5e}, + {0x16a60, 0x16a69}, + {0x16a6e, 0x16abe}, + {0x16ac0, 0x16ac9}, + {0x16ad0, 0x16aed}, + {0x16af0, 0x16af5}, + {0x16b00, 0x16b45}, + {0x16b50, 0x16b59}, + {0x16b5b, 0x16b61}, + {0x16b63, 0x16b77}, + {0x16b7d, 0x16b8f}, + {0x16d40, 0x16d79}, + {0x16e40, 0x16e9a}, + {0x16f00, 0x16f4a}, + {0x16f4f, 0x16f87}, + {0x16f8f, 0x16f9f}, + {0x16fe0, 0x16fe4}, + {0x16ff0, 0x16ff1}, + {0x17000, 0x187f7}, + {0x18800, 0x18cd5}, + {0x18cff, 0x18d08}, + {0x1aff0, 0x1aff3}, + {0x1aff5, 0x1affb}, + {0x1affd, 0x1affe}, + {0x1b000, 0x1b122}, + {0x1b132, 0x1b132}, + {0x1b150, 0x1b152}, + {0x1b155, 0x1b155}, + {0x1b164, 0x1b167}, + {0x1b170, 0x1b2fb}, + {0x1bc00, 0x1bc6a}, + {0x1bc70, 0x1bc7c}, + {0x1bc80, 0x1bc88}, + {0x1bc90, 0x1bc99}, + {0x1bc9c, 0x1bc9f}, + {0x1cc00, 0x1ccf9}, + {0x1cd00, 0x1ceb3}, + {0x1cf00, 0x1cf2d}, + {0x1cf30, 0x1cf46}, + {0x1cf50, 0x1cfc3}, + {0x1d000, 0x1d0f5}, + {0x1d100, 0x1d126}, + {0x1d129, 0x1d172}, + {0x1d17b, 0x1d1ea}, + {0x1d200, 0x1d245}, + {0x1d2c0, 0x1d2d3}, + {0x1d2e0, 0x1d2f3}, + {0x1d300, 0x1d356}, + {0x1d360, 0x1d378}, + {0x1d400, 0x1d454}, + {0x1d456, 0x1d49c}, + {0x1d49e, 0x1d49f}, + {0x1d4a2, 0x1d4a2}, + {0x1d4a5, 0x1d4a6}, + {0x1d4a9, 0x1d4ac}, + {0x1d4ae, 0x1d4b9}, + {0x1d4bb, 0x1d4bb}, + {0x1d4bd, 0x1d4c3}, + {0x1d4c5, 0x1d505}, + {0x1d507, 0x1d50a}, + {0x1d50d, 0x1d514}, + {0x1d516, 0x1d51c}, + {0x1d51e, 0x1d539}, + {0x1d53b, 0x1d53e}, + {0x1d540, 0x1d544}, + {0x1d546, 0x1d546}, + {0x1d54a, 0x1d550}, + {0x1d552, 0x1d6a5}, + {0x1d6a8, 0x1d7cb}, + {0x1d7ce, 0x1da8b}, + {0x1da9b, 0x1da9f}, + {0x1daa1, 0x1daaf}, + {0x1df00, 0x1df1e}, + {0x1df25, 0x1df2a}, + {0x1e000, 0x1e006}, + {0x1e008, 0x1e018}, + {0x1e01b, 0x1e021}, + {0x1e023, 0x1e024}, + {0x1e026, 0x1e02a}, + {0x1e030, 0x1e06d}, + {0x1e08f, 0x1e08f}, + {0x1e100, 0x1e12c}, + {0x1e130, 0x1e13d}, + {0x1e140, 0x1e149}, + {0x1e14e, 0x1e14f}, + {0x1e290, 0x1e2ae}, + {0x1e2c0, 0x1e2f9}, + {0x1e2ff, 0x1e2ff}, + {0x1e4d0, 0x1e4f9}, + {0x1e5d0, 0x1e5fa}, + {0x1e5ff, 0x1e5ff}, + {0x1e7e0, 0x1e7e6}, + {0x1e7e8, 0x1e7eb}, + {0x1e7ed, 0x1e7ee}, + {0x1e7f0, 0x1e7fe}, + {0x1e800, 0x1e8c4}, + {0x1e8c7, 0x1e8d6}, + {0x1e900, 0x1e94b}, + {0x1e950, 0x1e959}, + {0x1e95e, 0x1e95f}, + {0x1ec71, 0x1ecb4}, + {0x1ed01, 0x1ed3d}, + {0x1ee00, 0x1ee03}, + {0x1ee05, 0x1ee1f}, + {0x1ee21, 0x1ee22}, + {0x1ee24, 0x1ee24}, + {0x1ee27, 0x1ee27}, + {0x1ee29, 0x1ee32}, + {0x1ee34, 0x1ee37}, + {0x1ee39, 0x1ee39}, + {0x1ee3b, 0x1ee3b}, + {0x1ee42, 0x1ee42}, + {0x1ee47, 0x1ee47}, + {0x1ee49, 0x1ee49}, + {0x1ee4b, 0x1ee4b}, + {0x1ee4d, 0x1ee4f}, + {0x1ee51, 0x1ee52}, + {0x1ee54, 0x1ee54}, + {0x1ee57, 0x1ee57}, + {0x1ee59, 0x1ee59}, + {0x1ee5b, 0x1ee5b}, + {0x1ee5d, 0x1ee5d}, + {0x1ee5f, 0x1ee5f}, + {0x1ee61, 0x1ee62}, + {0x1ee64, 0x1ee64}, + {0x1ee67, 0x1ee6a}, + {0x1ee6c, 0x1ee72}, + {0x1ee74, 0x1ee77}, + {0x1ee79, 0x1ee7c}, + {0x1ee7e, 0x1ee7e}, + {0x1ee80, 0x1ee89}, + {0x1ee8b, 0x1ee9b}, + {0x1eea1, 0x1eea3}, + {0x1eea5, 0x1eea9}, + {0x1eeab, 0x1eebb}, + {0x1eef0, 0x1eef1}, + {0x1f000, 0x1f02b}, + {0x1f030, 0x1f093}, + {0x1f0a0, 0x1f0ae}, + {0x1f0b1, 0x1f0bf}, + {0x1f0c1, 0x1f0cf}, + {0x1f0d1, 0x1f0f5}, + {0x1f100, 0x1f1ad}, + {0x1f1e6, 0x1f202}, + {0x1f210, 0x1f23b}, + {0x1f240, 0x1f248}, + {0x1f250, 0x1f251}, + {0x1f260, 0x1f265}, + {0x1f300, 0x1f6d7}, + {0x1f6dc, 0x1f6ec}, + {0x1f6f0, 0x1f6fc}, + {0x1f700, 0x1f776}, + {0x1f77b, 0x1f7d9}, + {0x1f7e0, 0x1f7eb}, + {0x1f7f0, 0x1f7f0}, + {0x1f800, 0x1f80b}, + {0x1f810, 0x1f847}, + {0x1f850, 0x1f859}, + {0x1f860, 0x1f887}, + {0x1f890, 0x1f8ad}, + {0x1f8b0, 0x1f8bb}, + {0x1f8c0, 0x1f8c1}, + {0x1f900, 0x1fa53}, + {0x1fa60, 0x1fa6d}, + {0x1fa70, 0x1fa7c}, + {0x1fa80, 0x1fa89}, + {0x1fa8f, 0x1fac6}, + {0x1face, 0x1fadc}, + {0x1fadf, 0x1fae9}, + {0x1faf0, 0x1faf8}, + {0x1fb00, 0x1fb92}, + {0x1fb94, 0x1fbf9}, + {0x20000, 0x2a6df}, + {0x2a700, 0x2b739}, + {0x2b740, 0x2b81d}, + {0x2b820, 0x2cea1}, + {0x2ceb0, 0x2ebe0}, + {0x2ebf0, 0x2ee5d}, + {0x2f800, 0x2fa1d}, + {0x30000, 0x3134a}, + {0x31350, 0x323af}, + {0xe0100, 0xe01ef}, + }}; + + // re.IGNORECASE: code points outside ASCII that match an ASCII + // letter of a pattern, sorted. + constexpr std::array IgnoreCase = {{ + {0x130, 'i'}, + {0x131, 'i'}, + {0x17f, 's'}, + {0x212a, 'k'}, + }}; +} diff --git a/interfaces/Crafter.Review.cppm b/interfaces/Crafter.Review.cppm new file mode 100644 index 0000000..652c381 --- /dev/null +++ b/interfaces/Crafter.Review.cppm @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +export module Crafter.Review; + +export import :Unicode; +export import :Json; +export import :Text; +export import :Crypto; +export import :Core; +export import :Driver; diff --git a/project.cpp b/project.cpp new file mode 100644 index 0000000..4ea4b5d --- /dev/null +++ b/project.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +import std; +import Crafter.Build; +// Pinned: the Flatpak builds with the base app's crafter-build, which can be +// older than the rules' master. Move the pin together with the base. +#pragma crafter dependency "https://forgejo.catcrafts.net/Catcrafts/Crafter.LintRules.git" commit "7d932892017800b4e1f1a15fe7aa49b3daee9ceb" +import Crafter.LintRules; +namespace fs = std::filesystem; +using namespace Crafter; + +namespace { + // The umbrella, then the partitions in import order: each after the ones + // it imports, which is also the order the module map in AGENTS.md uses. + constexpr std::array Interfaces = { + "interfaces/Crafter.Review", + "interfaces/Crafter.Review-Unicode", + "interfaces/Crafter.Review-Json", + "interfaces/Crafter.Review-Text", + "interfaces/Crafter.Review-Crypto", + "interfaces/Crafter.Review-Core", + "interfaces/Crafter.Review-Driver", + }; + constexpr std::array Implementations = { + "implementations/Crafter.Review-Text", + "implementations/Crafter.Review-Crypto", + "implementations/Crafter.Review-Core", + "implementations/Crafter.Review-Driver", + }; + + // One per directory under tests/, each an ordinary main() that links the + // library: exit 0 passes, nonzero fails, 77 skips. + constexpr std::array Tests = { + "Crypto", + "Text", + }; + + // The version the binary reports: CI sets it from the tag, a checkout + // describes itself, anything else is a development build. A define on + // the library, so each version is its own library variant. + std::string Version() { + if (const char* env = std::getenv("CRAFTER_REVIEW_VERSION"); env && *env) return env; + auto described = RunCommandWithTimeout("git describe --tags --always --dirty --exclude=latest 2>/dev/null", std::chrono::seconds(10)); + std::string version = described.output; + while (!version.empty() && (version.back() == '\n' || version.back() == '\r')) version.pop_back(); + if (described.exitCode != 0 || version.empty()) return "0.0.0-dev"; + return version; + } +} + +extern "C" Configuration CrafterBuildProject(std::span args) { + // Crafter.Network for its TCP and HTTP/1.1 clients and listener, without + // QUIC: msquic is most of a fresh build and nothing here speaks HTTP/3. + // From forgejo at a pinned commit, or with --local from the checkout next + // to this one (which is also how Crafter.Network then finds + // Crafter.Thread). + std::vector depArgs(args.begin(), args.end()); + depArgs.emplace_back("--no-quic"); + const bool useLocal = std::ranges::find(args, std::string_view("--local")) != args.end(); + Configuration* network = useLocal + ? LocalProject({ .projectFile = "../Crafter.Network/project.cpp", .args = depArgs }) + : GitProject({ + .source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Network.git", .commit = "f68321685006be65f6bc0436fd0175611f1e0a55" }, + .args = depArgs, + }); + + static auto ReviewLib = std::make_unique(); + ReviewLib->path = "./"; + ReviewLib->name = "crafter.review-lib"; + ReviewLib->outputName = "crafter-review"; + ApplyStandardArgs(*ReviewLib, args); + ReviewLib->type = ConfigurationType::LibraryStatic; + ReviewLib->dependencies = { network }; + // Backslash-escaped quotes: the define travels through the shell before + // it reaches clang, which must still see a string literal. + ReviewLib->defines.push_back({"CRAFTER_REVIEW_VERSION", std::format("\\\"{}\\\"", Version())}); + { + std::vector interfaces(Interfaces.begin(), Interfaces.end()); + std::vector implementations(Implementations.begin(), Implementations.end()); + ReviewLib->GetInterfacesAndImplementations(interfaces, implementations); + } + + Configuration cfg; + cfg.path = "./"; + cfg.name = "crafter.review-exe"; + cfg.outputName = "crafter-review"; + ApplyStandardArgs(cfg, args); + cfg.type = ConfigurationType::Executable; + cfg.dependencies = { ReviewLib.get() }; + { + std::array interfaces = {}; + std::array implementations = { "implementations/main" }; + cfg.GetInterfacesAndImplementations(interfaces, implementations); + } + + for (const std::string_view test : Tests) { + cfg.AddTest(test).Dependencies({ ReviewLib.get() }); + } + + LintRules::AddLiteRules(cfg); + return cfg; +} diff --git a/tests/Crypto/main.cpp b/tests/Crypto/main.cpp new file mode 100644 index 0000000..d2caa95 --- /dev/null +++ b/tests/Crypto/main.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +// The primitives the pipeline's keys, its Postgres login and its one-time +// codes rest on, against their published vectors: SHA-256, SHA-1 and MD5 +// (FIPS 180, RFC 1321), HMAC (RFC 4231, RFC 2202), PBKDF2-HMAC-SHA256 (the +// RFC 7914 vectors), base32 (RFC 4648). A reuse key that hashed differently +// would silently stop every stored result from being reused; a wrong HMAC +// would lock the lens out of the database. +// +// base64 and base32 also have to fail with their fixed messages, because +// those reach the lens: a SCRAM exchange that cannot decode the server's +// salt, a TOTP secret that is not base32. + +import std; +import Crafter.Review; +using namespace Crafter::Review; + +namespace { + std::int32_t Failures = 0; + + void Check(bool cond, std::string_view msg) { + if (!cond) { + std::println(std::cerr, "FAIL: {}", msg); + ++Failures; + } + } + + std::string Unhex(std::string_view hex) { + std::string out; + for (std::size_t i = 0; i + 1 < hex.size(); i += 2) { + out.push_back(static_cast(std::stoi(std::string(hex.substr(i, 2)), nullptr, 16))); + } + return out; + } + + // What the call threw as std::invalid_argument, or "" when it did not. + template + std::string Refusal(F call) { + try { + call(); + } catch (const std::invalid_argument& e) { + return e.what(); + } + return ""; + } +} + +int main() { + Check(Sha256Hex("") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "sha256 of the empty string"); + Check(Sha256Hex("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "sha256 of abc"); + Check(HexEncode(Sha1("abc")) == "a9993e364706816aba3e25717850c26c9cd0d89d", "sha1 of abc"); + Check(Md5Hex("abc") == "900150983cd24fb0d6963f7d28e17f72", "md5 of abc"); + + const std::string key20(20, '\x0b'); + Check(HexEncode(HmacSha256(key20, "Hi There")) == "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", "HMAC-SHA256, RFC 4231 case 1"); + Check(HexEncode(HmacSha256("Jefe", "what do ya want for nothing?")) == "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", "HMAC-SHA256, RFC 4231 case 2"); + Check(HexEncode(HmacSha1(key20, "Hi There")) == "b617318655057264e28bc0b6fb378c8ef146be00", "HMAC-SHA1, RFC 2202 case 1"); + + Check(HexEncode(Pbkdf2Sha256("password", "salt", 1)) == "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b", "PBKDF2-HMAC-SHA256, 1 iteration"); + Check(HexEncode(Pbkdf2Sha256("password", "salt", 4096)) == "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a", "PBKDF2-HMAC-SHA256, 4096 iterations"); + + Check(Base64Encode("") == "" && Base64Encode("f") == "Zg==" && Base64Encode("fo") == "Zm8=" && Base64Encode("foo") == "Zm9v" && Base64Encode("foobar") == "Zm9vYmFy", "base64 encoding, RFC 4648 vectors"); + Check(Base64Decode("Zm9vYmFy") == "foobar" && Base64Decode("Zg==") == "f" && Base64Decode("Zm8=") == "fo", "base64 decoding"); + Check(Base64Decode("Zm9v\nYmFy!") == "foobar", "characters outside the alphabet are skipped, as b64decode skips them"); + Check(Base64Decode("YQ==YmM=") == "a", "a complete padding sequence ends the data"); + Check(Refusal([] { Base64Decode("Zm9vY"); }) == "Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4", "one data character too many: binascii's message"); + Check(Refusal([] { Base64Decode("Zm9vYm"); }) == "Incorrect padding", "two left over without padding: Incorrect padding"); + const std::string salt = Base64Decode("W22ZaJ0SNY7soEsUEjb6gQ=="); + Check(salt.size() == 16 && Base64Encode(salt) == "W22ZaJ0SNY7soEsUEjb6gQ==", "the RFC 7677 salt round-trips"); + + Check(Base32Decode("") == "", "base32 of nothing"); + Check(Base32Decode("MY======") == "f" && Base32Decode("MZXQ====") == "fo" && Base32Decode("MZXW6===") == "foo", "base32, RFC 4648 vectors (1-3 bytes)"); + Check(Base32Decode("MZXW6YQ=") == "foob" && Base32Decode("MZXW6YTB") == "fooba" && Base32Decode("MZXW6YTBOI======") == "foobar", "base32, RFC 4648 vectors (4-6 bytes)"); + Check(Base32Decode("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ") == "12345678901234567890", "base32 of the RFC 6238 secret"); + Check(Refusal([] { Base32Decode("MZXW6Y=="); }) == "Incorrect padding", "two padding characters are not a legal amount"); + Check(Refusal([] { Base32Decode("MZXW6"); }) == "Incorrect padding", "a length that is not a multiple of 8"); + Check(Refusal([] { Base32Decode("mzxw6ytb"); }) == "Non-base32 digit found", "lower case is not the alphabet (b32decode without casefold)"); + Check(Refusal([] { Base32Decode("1!"); }) == "Non-base32 digit found", "a bad digit is reported before a bad length, as b32decode reports it"); + + Check(Unhex("00ff") == std::string("\x00\xff", 2) && HexEncode(std::string("\x00\xff", 2)) == "00ff", "hex both ways, lowercase"); + Check(RandomBytes(18).size() == 18 && RandomBytes(0).empty(), "random bytes of the length asked for"); + Check(RandomBytes(16) != RandomBytes(16), "two draws differ"); + const std::string token = TokenHex(16); + Check(token.size() == 32 && std::ranges::all_of(token, [](char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); }), "token_hex(16) is 32 lowercase hex digits"); + const std::string uuid = Uuid4(); + Check(uuid.size() == 36 && uuid[8] == '-' && uuid[13] == '-' && uuid[18] == '-' && uuid[23] == '-', "a uuid4 is 8-4-4-4-12"); + Check(uuid[14] == '4' && std::string_view("89ab").contains(uuid[19]), "version 4, RFC 4122 variant"); + + Check(ConstantTimeEqual("abc", "abc") && !ConstantTimeEqual("abc", "abd") && !ConstantTimeEqual("abc", "ab"), "constant-time comparison"); + + return Failures == 0 ? 0 : 1; +} diff --git a/tests/Text/fixture/vectors.json b/tests/Text/fixture/vectors.json new file mode 100644 index 0000000..eb7b0ec --- /dev/null +++ b/tests/Text/fixture/vectors.json @@ -0,0 +1,1865 @@ +{ + "unicode": "16.0.0", + "strip": [ + [ + "", + "" + ], + [ + " ", + "" + ], + [ + "a", + "a" + ], + [ + " a ", + "a" + ], + [ + "\ta b\n", + "a b" + ], + [ + "a\u00a0b", + "a\u00a0b" + ], + [ + "\u00a0x\u00a0", + "x" + ], + [ + "x\u2028y", + "x\u2028y" + ], + [ + "x\u001cy", + "x\u001cy" + ], + [ + "x\u0085y", + "x\u0085y" + ], + [ + "a\r\nb", + "a\r\nb" + ], + [ + "a\rb", + "a\rb" + ], + [ + "a\nb\n", + "a\nb" + ], + [ + "a\n\nb", + "a\n\nb" + ], + [ + "a\u000bb\fc", + "a\u000bb\fc" + ], + [ + "\u3000full\u3000width", + "full\u3000width" + ], + [ + "tail ", + "tail" + ], + [ + "\u00e9 \u00fc", + "\u00e9 \u00fc" + ], + [ + "\u200b zero width", + "\u200b zero width" + ], + [ + "one\u2029two", + "one\u2029two" + ], + [ + "line\n", + "line" + ], + [ + "\n", + "" + ], + [ + "\r\n", + "" + ], + [ + "a\u001db\u001ec", + "a\u001db\u001ec" + ] + ], + "split": [ + [ + "", + [] + ], + [ + " ", + [] + ], + [ + "a", + [ + "a" + ] + ], + [ + " a ", + [ + "a" + ] + ], + [ + "\ta b\n", + [ + "a", + "b" + ] + ], + [ + "a\u00a0b", + [ + "a", + "b" + ] + ], + [ + "\u00a0x\u00a0", + [ + "x" + ] + ], + [ + "x\u2028y", + [ + "x", + "y" + ] + ], + [ + "x\u001cy", + [ + "x", + "y" + ] + ], + [ + "x\u0085y", + [ + "x", + "y" + ] + ], + [ + "a\r\nb", + [ + "a", + "b" + ] + ], + [ + "a\rb", + [ + "a", + "b" + ] + ], + [ + "a\nb\n", + [ + "a", + "b" + ] + ], + [ + "a\n\nb", + [ + "a", + "b" + ] + ], + [ + "a\u000bb\fc", + [ + "a", + "b", + "c" + ] + ], + [ + "\u3000full\u3000width", + [ + "full", + "width" + ] + ], + [ + "tail ", + [ + "tail" + ] + ], + [ + "\u00e9 \u00fc", + [ + "\u00e9", + "\u00fc" + ] + ], + [ + "\u200b zero width", + [ + "\u200b", + "zero", + "width" + ] + ], + [ + "one\u2029two", + [ + "one", + "two" + ] + ], + [ + "line\n", + [ + "line" + ] + ], + [ + "\n", + [] + ], + [ + "\r\n", + [] + ], + [ + "a\u001db\u001ec", + [ + "a", + "b", + "c" + ] + ] + ], + "splitlines": [ + [ + "", + [] + ], + [ + " ", + [ + " " + ] + ], + [ + "a", + [ + "a" + ] + ], + [ + " a ", + [ + " a " + ] + ], + [ + "\ta b\n", + [ + "\ta b" + ] + ], + [ + "a\u00a0b", + [ + "a\u00a0b" + ] + ], + [ + "\u00a0x\u00a0", + [ + "\u00a0x\u00a0" + ] + ], + [ + "x\u2028y", + [ + "x", + "y" + ] + ], + [ + "x\u001cy", + [ + "x", + "y" + ] + ], + [ + "x\u0085y", + [ + "x", + "y" + ] + ], + [ + "a\r\nb", + [ + "a", + "b" + ] + ], + [ + "a\rb", + [ + "a", + "b" + ] + ], + [ + "a\nb\n", + [ + "a", + "b" + ] + ], + [ + "a\n\nb", + [ + "a", + "", + "b" + ] + ], + [ + "a\u000bb\fc", + [ + "a", + "b", + "c" + ] + ], + [ + "\u3000full\u3000width", + [ + "\u3000full\u3000width" + ] + ], + [ + "tail ", + [ + "tail " + ] + ], + [ + "\u00e9 \u00fc", + [ + "\u00e9 \u00fc" + ] + ], + [ + "\u200b zero width", + [ + "\u200b zero width" + ] + ], + [ + "one\u2029two", + [ + "one", + "two" + ] + ], + [ + "line\n", + [ + "line" + ] + ], + [ + "\n", + [ + "" + ] + ], + [ + "\r\n", + [ + "" + ] + ], + [ + "a\u001db\u001ec", + [ + "a", + "b", + "c" + ] + ] + ], + "splitlinesKeep": [ + [ + "", + [] + ], + [ + " ", + [ + " " + ] + ], + [ + "a", + [ + "a" + ] + ], + [ + " a ", + [ + " a " + ] + ], + [ + "\ta b\n", + [ + "\ta b\n" + ] + ], + [ + "a\u00a0b", + [ + "a\u00a0b" + ] + ], + [ + "\u00a0x\u00a0", + [ + "\u00a0x\u00a0" + ] + ], + [ + "x\u2028y", + [ + "x\u2028", + "y" + ] + ], + [ + "x\u001cy", + [ + "x\u001c", + "y" + ] + ], + [ + "x\u0085y", + [ + "x\u0085", + "y" + ] + ], + [ + "a\r\nb", + [ + "a\r\n", + "b" + ] + ], + [ + "a\rb", + [ + "a\r", + "b" + ] + ], + [ + "a\nb\n", + [ + "a\n", + "b\n" + ] + ], + [ + "a\n\nb", + [ + "a\n", + "\n", + "b" + ] + ], + [ + "a\u000bb\fc", + [ + "a\u000b", + "b\f", + "c" + ] + ], + [ + "\u3000full\u3000width", + [ + "\u3000full\u3000width" + ] + ], + [ + "tail ", + [ + "tail " + ] + ], + [ + "\u00e9 \u00fc", + [ + "\u00e9 \u00fc" + ] + ], + [ + "\u200b zero width", + [ + "\u200b zero width" + ] + ], + [ + "one\u2029two", + [ + "one\u2029", + "two" + ] + ], + [ + "line\n", + [ + "line\n" + ] + ], + [ + "\n", + [ + "\n" + ] + ], + [ + "\r\n", + [ + "\r\n" + ] + ], + [ + "a\u001db\u001ec", + [ + "a\u001d", + "b\u001e", + "c" + ] + ] + ], + "lower": [ + [ + "ABC", + "abc" + ], + [ + "abc", + "abc" + ], + [ + "\u00dcn\u00efc\u00f6d\u00e9", + "\u00fcn\u00efc\u00f6d\u00e9" + ], + [ + "\u0130stanbul", + "i\u0307stanbul" + ], + [ + "STRASSE", + "strasse" + ], + [ + "stra\u00dfe", + "stra\u00dfe" + ], + [ + "\u1e9e", + "\u00df" + ], + [ + "\u03a3\u0391", + "\u03c3\u03b1" + ], + [ + "\u212a", + "k" + ], + [ + "\u017f", + "\u017f" + ], + [ + "\u0131", + "\u0131" + ], + [ + "\u01c5", + "\u01c6" + ], + [ + "HTTP://Example.COM/Path", + "http://example.com/path" + ], + [ + "\ufb01", + "\ufb01" + ], + [ + "\ud801\udc00", + "\ud801\udc28" + ] + ], + "upper": [ + [ + "ABC", + "ABC" + ], + [ + "abc", + "ABC" + ], + [ + "\u00dcn\u00efc\u00f6d\u00e9", + "\u00dcN\u00cfC\u00d6D\u00c9" + ], + [ + "\u0130stanbul", + "\u0130STANBUL" + ], + [ + "STRASSE", + "STRASSE" + ], + [ + "stra\u00dfe", + "STRASSE" + ], + [ + "\u1e9e", + "\u1e9e" + ], + [ + "\u03a3\u0391", + "\u03a3\u0391" + ], + [ + "\u212a", + "\u212a" + ], + [ + "\u017f", + "S" + ], + [ + "\u0131", + "I" + ], + [ + "\u01c5", + "\u01c4" + ], + [ + "HTTP://Example.COM/Path", + "HTTP://EXAMPLE.COM/PATH" + ], + [ + "\ufb01", + "FI" + ], + [ + "\ud801\udc00", + "\ud801\udc00" + ] + ], + "floatRepr": [ + [ + 0.0, + "0.0" + ], + [ + -0.0, + "-0.0" + ], + [ + 1.0, + "1.0" + ], + [ + 5.0, + "5.0" + ], + [ + 0.1, + "0.1" + ], + [ + 0.25, + "0.25" + ], + [ + 1e-05, + "1e-05" + ], + [ + 0.0001, + "0.0001" + ], + [ + 1000000000000000.0, + "1000000000000000.0" + ], + [ + 1e+16, + "1e+16" + ], + [ + 123456789.0, + "123456789.0" + ], + [ + 1.5e+300, + "1.5e+300" + ], + [ + 2.675, + "2.675" + ], + [ + 0.30000000000000004, + "0.30000000000000004" + ], + [ + 1234.5678, + "1234.5678" + ], + [ + 1e+22, + "1e+22" + ], + [ + 1e+16, + "1e+16" + ], + [ + 100.0, + "100.0" + ], + [ + 0.5, + "0.5" + ], + [ + 3.14159, + "3.14159" + ], + [ + 1e-07, + "1e-07" + ] + ], + "round": [ + [ + 2.675, + 2, + "2.67" + ], + [ + 0.125, + 2, + "0.12" + ], + [ + 0.375, + 2, + "0.38" + ], + [ + 2.5, + 0, + "2" + ], + [ + 3.5, + 0, + "4" + ], + [ + 0.205, + 1, + "0.2" + ], + [ + 12.3456, + 1, + "12.3" + ], + [ + 0.0049, + 2, + "0.0" + ], + [ + 7.0, + 1, + "7.0" + ], + [ + -1.25, + 1, + "-1.2" + ], + [ + 1e+16, + 1, + "1e+16" + ], + [ + 0.15, + 1, + "0.1" + ], + [ + 0.25, + 1, + "0.2" + ], + [ + 0.35, + 1, + "0.3" + ] + ], + "int": [ + [ + " 12 ", + 12 + ], + [ + "1_000", + 1000 + ], + [ + "+5", + 5 + ], + [ + "-0", + 0 + ], + [ + "0x10", + { + "error": "ValueError", + "message": "invalid literal for int() with base 10: '0x10'" + } + ], + [ + "\uff11\uff12", + 12 + ], + [ + "", + { + "error": "ValueError", + "message": "invalid literal for int() with base 10: ''" + } + ], + [ + "12.0", + { + "error": "ValueError", + "message": "invalid literal for int() with base 10: '12.0'" + } + ], + [ + "1__0", + { + "error": "ValueError", + "message": "invalid literal for int() with base 10: '1__0'" + } + ], + [ + "_1", + { + "error": "ValueError", + "message": "invalid literal for int() with base 10: '_1'" + } + ], + [ + "\u0663", + 3 + ], + [ + "999999999999999999", + 999999999999999999 + ] + ], + "float": [ + [ + " 2.5 ", + "2.5" + ], + [ + "inf", + "inf" + ], + [ + "-Infinity", + "-inf" + ], + [ + "nan", + "nan" + ], + [ + "1e3", + "1000.0" + ], + [ + "1_0.5", + "10.5" + ], + [ + ".5", + "0.5" + ], + [ + "5.", + "5.0" + ], + [ + "", + { + "error": "ValueError", + "message": "could not convert string to float: ''" + } + ], + [ + "abc", + { + "error": "ValueError", + "message": "could not convert string to float: 'abc'" + } + ], + [ + "\u0661\u066b\u0665", + { + "error": "ValueError", + "message": "could not convert string to float: '\u0661\u066b\u0665'" + } + ], + [ + "1e400", + "inf" + ] + ], + "repr": [ + [ + "abc", + "'abc'" + ], + [ + "a'b", + "\"a'b\"" + ], + [ + "a\"b", + "'a\"b'" + ], + [ + "a'b\"c", + "'a\\'b\"c'" + ], + [ + "\n\t\r", + "'\\n\\t\\r'" + ], + [ + "\u0000\u007f", + "'\\x00\\x7f'" + ], + [ + "\u00e9", + "'\u00e9'" + ], + [ + "\u2028", + "'\\u2028'" + ], + [ + "\ud83d\ude00", + "'\ud83d\ude00'" + ], + [ + "\\", + "'\\\\'" + ], + [ + "", + "''" + ], + [ + "\u009f", + "'\\x9f'" + ], + [ + "\u00a0", + "'\\xa0'" + ], + [ + "\ud7ff", + "'\\ud7ff'" + ], + [ + "\uffff", + "'\\uffff'" + ] + ], + "reprList": [ + [ + [ + "a", + "b'c", + 1, + null, + true, + 2.5 + ], + "['a', \"b'c\", 1, None, True, 2.5]" + ] + ], + "reprDict": [ + [ + { + "x": 1, + "y": "two", + "z": [ + 1, + 2 + ] + }, + "{'x': 1, 'y': 'two', 'z': [1, 2]}" + ] + ], + "fnmatch": [ + [ + "src/a.test.ts", + "**/*.test.*", + true + ], + [ + "a.test.ts", + "**/*.test.*", + false + ], + [ + "docs/x/y.md", + "docs/**", + true + ], + [ + "README.md", + "*.md", + true + ], + [ + "dir/README.md", + "*.md", + true + ], + [ + "abc", + "[!a]*", + false + ], + [ + "bcd", + "[!a]*", + true + ], + [ + "a]", + "[]a]*", + true + ], + [ + "[x", + "[x", + true + ], + [ + "ab", + "a?", + true + ], + [ + "a/b", + "a?b", + true + ], + [ + "A.MD", + "*.md", + false + ], + [ + "x-y", + "[a-z]-[a-z]", + true + ], + [ + ".hidden", + "*", + true + ], + [ + "a\\b", + "a\\b", + true + ], + [ + "a", + "[a-", + false + ], + [ + "tsconfig.app.json", + "tsconfig*.json", + true + ], + [ + "src/test/x.ts", + "src/test/**", + true + ], + [ + "review-gates", + "review-gates", + true + ], + [ + "a!b", + "a[!]b", + false + ], + [ + "^", + "[^]", + true + ], + [ + "a", + "[^a]", + true + ], + [ + "b", + "[^a]", + false + ], + [ + "\u00e9.md", + "*.md", + true + ] + ], + "urlsplit": [ + [ + "http://localhost:8080", + { + "scheme": "http", + "netloc": "localhost:8080", + "path": "", + "query": "", + "fragment": "", + "hostname": "localhost", + "username": null, + "password": null, + "port": 8080 + } + ], + [ + "http://LOCALHOST:8080/", + { + "scheme": "http", + "netloc": "LOCALHOST:8080", + "path": "/", + "query": "", + "fragment": "", + "hostname": "localhost", + "username": null, + "password": null, + "port": 8080 + } + ], + [ + "https://user:p%40ss@Example.COM:443/a?b=1#f", + { + "scheme": "https", + "netloc": "user:p%40ss@Example.COM:443", + "path": "/a", + "query": "b=1", + "fragment": "f", + "hostname": "example.com", + "username": "user", + "password": "p%40ss", + "port": 443 + } + ], + [ + "postgresql://postgres:postgres@127.0.0.1:54322/postgres", + { + "scheme": "postgresql", + "netloc": "postgres:postgres@127.0.0.1:54322", + "path": "/postgres", + "query": "", + "fragment": "", + "hostname": "127.0.0.1", + "username": "postgres", + "password": "postgres", + "port": 54322 + } + ], + [ + "postgres://[::1]:5432/db", + { + "scheme": "postgres", + "netloc": "[::1]:5432", + "path": "/db", + "query": "", + "fragment": "", + "hostname": "::1", + "username": null, + "password": null, + "port": 5432 + } + ], + [ + "postgresql://u@localhost/", + { + "scheme": "postgresql", + "netloc": "u@localhost", + "path": "/", + "query": "", + "fragment": "", + "hostname": "localhost", + "username": "u", + "password": null, + "port": null + } + ], + [ + "http://h:0/", + { + "scheme": "http", + "netloc": "h:0", + "path": "/", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": 0 + } + ], + [ + "http://h:99999/", + { + "scheme": "http", + "netloc": "h:99999", + "path": "/", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": { + "error": "ValueError", + "message": "Port out of range 0-65535" + } + } + ], + [ + "http://h:x/", + { + "scheme": "http", + "netloc": "h:x", + "path": "/", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": { + "error": "ValueError", + "message": "Port could not be cast to integer value as 'x'" + } + } + ], + [ + "http://[::1", + { + "error": "ValueError", + "message": "Invalid IPv6 URL" + } + ], + [ + "//netloc/path", + { + "scheme": "", + "netloc": "netloc", + "path": "/path", + "query": "", + "fragment": "", + "hostname": "netloc", + "username": null, + "password": null, + "port": null + } + ], + [ + "mailto:x@y", + { + "scheme": "mailto", + "netloc": "", + "path": "x@y", + "query": "", + "fragment": "", + "hostname": null, + "username": null, + "password": null, + "port": null + } + ], + [ + "http://h/p;params?q", + { + "scheme": "http", + "netloc": "h", + "path": "/p;params", + "query": "q", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": null + } + ], + [ + " http://h/\tp\n", + { + "scheme": "http", + "netloc": "h", + "path": "/p", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": null + } + ], + [ + "HTTP://h", + { + "scheme": "http", + "netloc": "h", + "path": "", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": null + } + ], + [ + "h:8080", + { + "scheme": "h", + "netloc": "", + "path": "8080", + "query": "", + "fragment": "", + "hostname": null, + "username": null, + "password": null, + "port": null + } + ], + [ + "http://h:/", + { + "scheme": "http", + "netloc": "h:", + "path": "/", + "query": "", + "fragment": "", + "hostname": "h", + "username": null, + "password": null, + "port": null + } + ], + [ + "postgresql://%75ser:pa%3Ass@host/d%62", + { + "scheme": "postgresql", + "netloc": "%75ser:pa%3Ass@host", + "path": "/d%62", + "query": "", + "fragment": "", + "hostname": "host", + "username": "%75ser", + "password": "pa%3Ass", + "port": null + } + ], + [ + "http://[v1.fe]/", + { + "scheme": "http", + "netloc": "[v1.fe]", + "path": "/", + "query": "", + "fragment": "", + "hostname": "v1.fe", + "username": null, + "password": null, + "port": null + } + ], + [ + "http://a@b@c/", + { + "scheme": "http", + "netloc": "a@b@c", + "path": "/", + "query": "", + "fragment": "", + "hostname": "c", + "username": "a@b", + "password": null, + "port": null + } + ] + ], + "unquote": [ + [ + "a%20b", + "a b" + ], + [ + "%E2%9C%93", + "\u2713" + ], + [ + "%zz", + "%zz" + ], + [ + "%", + "%" + ], + [ + "100%25", + "100%" + ], + [ + "p%40ss", + "p@ss" + ], + [ + "%C3%28", + "\ufffd(" + ], + [ + "+", + "+" + ] + ], + "natural": [ + [ + "slot 10", + [ + "slot ", + 10, + "" + ] + ], + [ + "slot 2", + [ + "slot ", + 2, + "" + ] + ], + [ + "a1b2", + [ + "a", + 1, + "b", + 2, + "" + ] + ], + [ + "10", + [ + "", + 10, + "" + ] + ], + [ + "", + [ + "" + ] + ], + [ + "x", + [ + "x" + ] + ], + [ + "\u00e99", + [ + "\u00e9", + 9, + "" + ] + ], + [ + "v2.10", + [ + "v", + 2, + ".", + 10, + "" + ] + ] + ], + "paths": [ + [ + [ + "normpath", + "a//b/./c/.." + ], + "a/b" + ], + [ + [ + "normpath", + "/../x" + ], + "/x" + ], + [ + [ + "normpath", + "" + ], + "." + ], + [ + [ + "normpath", + "//a" + ], + "//a" + ], + [ + [ + "normpath", + "///a" + ], + "/a" + ], + [ + [ + "normpath", + "a/" + ], + "a" + ], + [ + [ + "relpath", + "/a/b/c", + "/a" + ], + "b/c" + ], + [ + [ + "relpath", + "/a", + "/a/b/c" + ], + "../.." + ], + [ + [ + "relpath", + "/a/b", + "/a/b" + ], + "." + ], + [ + [ + "relpath", + "/x/y", + "/a/b" + ], + "../../x/y" + ], + [ + [ + "abspath", + "/a/b/../c" + ], + "/a/c" + ], + [ + [ + "basename", + "/a/b/" + ], + "" + ], + [ + [ + "basename", + "a.patch" + ], + "a.patch" + ], + [ + [ + "dirname", + "/a/b/c" + ], + "/a/b" + ], + [ + [ + "dirname", + "a" + ], + "" + ], + [ + [ + "join", + "/a", + "b" + ], + "/a/b" + ], + [ + [ + "join", + "/a", + "/b" + ], + "/b" + ], + [ + [ + "join", + "a/", + "b" + ], + "a/b" + ] + ], + "jsonIndent1": [ + [ + { + "version": 1, + "drivers": {}, + "seats": { + "a": { + "windows": { + "five_hour": { + "utilization": 39, + "resetsAt": 1793421000 + } + }, + "pauseUntil": 0.0, + "state": null, + "inflight": {} + } + } + }, + "{\n \"version\": 1,\n \"drivers\": {},\n \"seats\": {\n \"a\": {\n \"windows\": {\n \"five_hour\": {\n \"utilization\": 39,\n \"resetsAt\": 1793421000\n }\n },\n \"pauseUntil\": 0.0,\n \"state\": null,\n \"inflight\": {}\n }\n }\n}" + ], + [ + [ + 1, + 2.5, + "x", + true, + null, + [], + {} + ], + "[\n 1,\n 2.5,\n \"x\",\n true,\n null,\n [],\n {}\n]" + ], + [ + { + "\u00e9": "\u00fc\u2028\u0000", + "emoji": "\ud83d\ude00" + }, + "{\n \"\\u00e9\": \"\\u00fc\\u2028\\u0000\",\n \"emoji\": \"\\ud83d\\ude00\"\n}" + ], + [ + { + "float": 1792966830.0, + "small": 1e-07, + "neg": -0.0 + }, + "{\n \"float\": 1792966830.0,\n \"small\": 1e-07,\n \"neg\": -0.0\n}" + ] + ], + "jsonDefault": [ + [ + { + "version": 1, + "drivers": {}, + "seats": { + "a": { + "windows": { + "five_hour": { + "utilization": 39, + "resetsAt": 1793421000 + } + }, + "pauseUntil": 0.0, + "state": null, + "inflight": {} + } + } + }, + "{\"version\": 1, \"drivers\": {}, \"seats\": {\"a\": {\"windows\": {\"five_hour\": {\"utilization\": 39, \"resetsAt\": 1793421000}}, \"pauseUntil\": 0.0, \"state\": null, \"inflight\": {}}}}" + ], + [ + [ + 1, + 2.5, + "x", + true, + null, + [], + {} + ], + "[1, 2.5, \"x\", true, null, [], {}]" + ], + [ + { + "\u00e9": "\u00fc\u2028\u0000", + "emoji": "\ud83d\ude00" + }, + "{\"\\u00e9\": \"\\u00fc\\u2028\\u0000\", \"emoji\": \"\\ud83d\\ude00\"}" + ], + [ + { + "float": 1792966830.0, + "small": 1e-07, + "neg": -0.0 + }, + "{\"float\": 1792966830.0, \"small\": 1e-07, \"neg\": -0.0}" + ] + ], + "jsonSorted": [ + [ + { + "version": 1, + "drivers": {}, + "seats": { + "a": { + "windows": { + "five_hour": { + "utilization": 39, + "resetsAt": 1793421000 + } + }, + "pauseUntil": 0.0, + "state": null, + "inflight": {} + } + } + }, + "{\"drivers\":{},\"seats\":{\"a\":{\"inflight\":{},\"pauseUntil\":0.0,\"state\":null,\"windows\":{\"five_hour\":{\"resetsAt\":1793421000,\"utilization\":39}}}},\"version\":1}" + ], + [ + [ + 1, + 2.5, + "x", + true, + null, + [], + {} + ], + "[1,2.5,\"x\",true,null,[],{}]" + ], + [ + { + "\u00e9": "\u00fc\u2028\u0000", + "emoji": "\ud83d\ude00" + }, + "{\"emoji\":\"\\ud83d\\ude00\",\"\\u00e9\":\"\\u00fc\\u2028\\u0000\"}" + ], + [ + { + "float": 1792966830.0, + "small": 1e-07, + "neg": -0.0 + }, + "{\"float\":1792966830.0,\"neg\":-0.0,\"small\":1e-07}" + ] + ], + "jsonDuplicate": [ + [ + "{\"a\": 1, \"b\": 2, \"a\": 3}", + [ + [ + "a", + 3 + ], + [ + "b", + 2 + ] + ] + ] + ] +} diff --git a/tests/Text/main.cpp b/tests/Text/main.cpp new file mode 100644 index 0000000..79e3b4d --- /dev/null +++ b/tests/Text/main.cpp @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +// The semantics of :Text, checked against reference vectors: every vector in +// fixture/vectors.json is a recorded answer (the file names the Unicode +// version it holds). +// +// The driver's output is decided by these rules: a unit's excerpt is cut by +// Strip() with Unicode whitespace, a key by Length() in code points, a peak +// by Round() half to even, an exclusion by FnMatch()'s character classes, a +// stack key by UrlSplit() and Lower(). The nearest C++ equivalent would agree +// on ASCII and drift exactly where nobody looks. +// +// Also :Json's three dump layouts — DumpFile(), DumpSpaced() and +// DumpCompactSorted() — and its handling of a duplicate key. + +import std; +import Crafter.Review; +namespace fs = std::filesystem; +using namespace Crafter::Review; + +namespace { + std::int32_t Failures = 0; + + void Check(bool cond, std::string_view msg) { + if (!cond) { + std::println(std::cerr, "FAIL: {}", msg); + ++Failures; + } + } + + std::vector Strings(const Json& array) { + std::vector out; + for (const Json& item : array) out.push_back(item.get()); + return out; + } + + std::string Show(const std::vector& pieces) { + Json array = Json::array(); + for (const std::string& piece : pieces) array.push_back(piece); + return Dyn::ReprValue(array); + } + + std::string ShowOptional(const std::optional& value) { + return value ? Dyn::Repr(*value) : std::string("None"); + } + + bool SameOptional(const std::optional& value, const Json& expected) { + if (expected.is_null()) return !value.has_value(); + return value && *value == expected.get(); + } + + bool IsError(const Json& value) { + return value.is_object() && value.contains("error"); + } +} + +int main() { + const fs::path path = fs::current_path() / "tests" / "Text" / "fixture" / "vectors.json"; + const Json vectors = Json::parse(Dyn::ReadText(path)); + + for (const Json& v : vectors["strip"]) { + const std::string input = v[0]; + Check(Dyn::Strip(input) == v[1].get(), std::format("strip({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Strip(input)))); + } + for (const Json& v : vectors["split"]) { + const std::string input = v[0]; + Check(Dyn::Split(input) == Strings(v[1]), std::format("split({}) = {}", Dyn::Repr(input), Show(Dyn::Split(input)))); + } + for (const Json& v : vectors["splitlines"]) { + const std::string input = v[0]; + Check(Dyn::SplitLines(input) == Strings(v[1]), std::format("splitlines({}) = {}", Dyn::Repr(input), Show(Dyn::SplitLines(input)))); + } + for (const Json& v : vectors["splitlinesKeep"]) { + const std::string input = v[0]; + Check(Dyn::SplitLines(input, true) == Strings(v[1]), std::format("splitlines({}, keepends) = {}", Dyn::Repr(input), Show(Dyn::SplitLines(input, true)))); + } + for (const Json& v : vectors["lower"]) { + const std::string input = v[0]; + Check(Dyn::Lower(input) == v[1].get(), std::format("lower({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Lower(input)))); + } + for (const Json& v : vectors["upper"]) { + const std::string input = v[0]; + Check(Dyn::Upper(input) == v[1].get(), std::format("upper({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Upper(input)))); + } + for (const Json& v : vectors["floatRepr"]) { + const double value = v[0].get(); + Check(Dyn::FloatRepr(value) == v[1].get(), std::format("repr({}) = {}", v[1].get(), Dyn::FloatRepr(value))); + } + for (const Json& v : vectors["round"]) { + const double value = v[0].get(); + const std::int32_t digits = v[1].get(); + const std::string got = digits == 0 ? std::to_string(Dyn::RoundToInt(value)) : Dyn::FloatRepr(Dyn::Round(value, digits)); + Check(got == v[2].get(), std::format("Round({}, {}) = {}, expected {}", Dyn::FloatRepr(value), digits, got, v[2].get())); + } + for (const Json& v : vectors["int"]) { + const std::string input = v[0]; + const std::optional got = Dyn::ParseInt(input); + if (IsError(v[1])) { + Check(!got && Dyn::IntError(input) == v[1]["message"].get(), std::format("ParseInt({}) fails with the recorded message ({})", Dyn::Repr(input), Dyn::IntError(input))); + } else { + Check(got && *got == v[1].get(), std::format("int({}) = {}", Dyn::Repr(input), got ? std::to_string(*got) : std::string("error"))); + } + } + for (const Json& v : vectors["float"]) { + const std::string input = v[0]; + const std::optional got = Dyn::ParseFloat(input); + if (IsError(v[1])) { + Check(!got && Dyn::FloatError(input) == v[1]["message"].get(), std::format("ParseFloat({}) fails with the recorded message", Dyn::Repr(input))); + } else { + Check(got && Dyn::FloatRepr(*got) == v[1].get(), std::format("float({}) = {}", Dyn::Repr(input), got ? Dyn::FloatRepr(*got) : std::string("error"))); + } + } + for (const Json& v : vectors["repr"]) { + const std::string input = v[0]; + Check(Dyn::Repr(input) == v[1].get(), std::format("repr of {} = {}", v[1].get(), Dyn::Repr(input))); + } + for (const char* key : { "reprList", "reprDict" }) { + for (const Json& v : vectors[key]) { + Check(Dyn::ReprValue(v[0]) == v[1].get(), std::format("ReprValue = {}, expected {}", Dyn::ReprValue(v[0]), v[1].get())); + } + } + for (const Json& v : vectors["fnmatch"]) { + const std::string name = v[0]; + const std::string pattern = v[1]; + Check(Dyn::FnMatch(name, pattern) == v[2].get(), std::format("fnmatch({}, {}) should be {}", Dyn::Repr(name), Dyn::Repr(pattern), v[2].get())); + } + for (const Json& v : vectors["urlsplit"]) { + const std::string url = v[0]; + const Json& expected = v[1]; + if (IsError(expected)) { + std::string message = "(no error)"; + try { + Dyn::UrlSplit(url); + } catch (const std::invalid_argument& e) { + message = e.what(); + } + Check(message == expected["message"].get(), std::format("UrlSplit({}) fails with {}, expected {}", Dyn::Repr(url), Dyn::Repr(message), Dyn::Repr(expected["message"].get()))); + continue; + } + const Dyn::SplitUrl got = Dyn::UrlSplit(url); + Check(got.scheme == expected["scheme"].get() && got.netloc == expected["netloc"].get() + && got.path == expected["path"].get() && got.query == expected["query"].get() + && got.fragment == expected["fragment"].get(), + std::format("urlsplit({}) parts: {} {} {} {} {}", Dyn::Repr(url), got.scheme, got.netloc, got.path, got.query, got.fragment)); + Check(SameOptional(got.Hostname(), expected["hostname"]), std::format("urlsplit({}).hostname = {}", Dyn::Repr(url), ShowOptional(got.Hostname()))); + Check(SameOptional(got.Username(), expected["username"]), std::format("urlsplit({}).username = {}", Dyn::Repr(url), ShowOptional(got.Username()))); + Check(SameOptional(got.Password(), expected["password"]), std::format("urlsplit({}).password = {}", Dyn::Repr(url), ShowOptional(got.Password()))); + const Json& port = expected["port"]; + if (IsError(port)) { + std::string message = "(no error)"; + try { + got.Port(); + } catch (const std::invalid_argument& e) { + message = e.what(); + } + Check(message == port["message"].get(), std::format("urlsplit({}).port fails with {}", Dyn::Repr(url), Dyn::Repr(message))); + } else if (port.is_null()) { + Check(!got.Port(), std::format("urlsplit({}).port is None", Dyn::Repr(url))); + } else { + Check(got.Port() == port.get(), std::format("urlsplit({}).port", Dyn::Repr(url))); + } + } + for (const Json& v : vectors["unquote"]) { + const std::string input = v[0]; + Check(Dyn::Unquote(input) == v[1].get(), std::format("unquote({}) = {}", Dyn::Repr(input), Dyn::Repr(Dyn::Unquote(input)))); + } + for (const Json& v : vectors["natural"]) { + const std::string name = v[0]; + Dyn::NaturalKey expected; + for (const Json& part : v[1]) { + if (part.is_string()) { + expected.parts.emplace_back(part.get()); + } else { + expected.parts.emplace_back(part.get()); + } + } + Check(Dyn::Natural(name) == expected, std::format("natural key of {}", Dyn::Repr(name))); + } + for (const Json& v : vectors["paths"]) { + const std::string op = v[0][0]; + const std::string a = v[0][1]; + std::string got; + if (op == "normpath") got = Dyn::NormPath(a); + if (op == "abspath") got = Dyn::AbsPath(a); + if (op == "basename") got = Dyn::BaseName(a); + if (op == "dirname") got = Dyn::DirName(a); + if (op == "relpath") got = Dyn::RelPath(a, v[0][2].get()); + if (op == "join") got = Dyn::JoinPath(a, v[0][2].get()); + Check(got == v[1].get(), std::format("{}({}) = {}, expected {}", op, Dyn::Repr(a), Dyn::Repr(got), Dyn::Repr(v[1].get()))); + } + for (const Json& v : vectors["jsonIndent1"]) { + Check(DumpFile(v[0]) == v[1].get(), std::format("DumpFile:\n{}\nexpected:\n{}", DumpFile(v[0]), v[1].get())); + } + for (const Json& v : vectors["jsonDefault"]) { + Check(DumpSpaced(v[0]) == v[1].get(), std::format("DumpSpaced: {} / expected {}", DumpSpaced(v[0]), v[1].get())); + } + for (const Json& v : vectors["jsonSorted"]) { + Check(DumpCompactSorted(v[0]) == v[1].get(), std::format("DumpCompactSorted: {} / expected {}", DumpCompactSorted(v[0]), v[1].get())); + } + for (const Json& v : vectors["jsonDuplicate"]) { + const Json parsed = Json::parse(v[0].get()); + Json pairs = Json::array(); + for (auto it = parsed.begin(); it != parsed.end(); ++it) pairs.push_back(Json::array({ it.key(), it.value() })); + Check(pairs == v[1], std::format("a duplicate key keeps its first place and its last value: {}", pairs.dump())); + } + + // Read the way open() reads: \r\n and a lone \r are \n. + Check(Dyn::UniversalNewlines("a\r\nb\rc\n") == "a\nb\nc\n", "universal newlines"); + Check(Dyn::Length("é😀a") == 3 && Dyn::Slice("é😀abc", 1, 3) == "😀a" && Dyn::Slice("abc", -2) == "bc", "len and slicing count code points"); + Check(Dyn::DecodeReplace("a\xC3(b") == "a\xEF\xBF\xBD(b", "a bad byte becomes U+FFFD, the next character survives"); + Check(!Dyn::IsUtf8("\xED\xA0\x80") && Dyn::IsUtf8("\xEF\xBF\xBD"), "a surrogate is not UTF-8; a real U+FFFD is"); + Check(Dyn::IgnoreCaseLetter(U'K') == 'k' && Dyn::IgnoreCaseLetter(U'ſ') == 's' && Dyn::IgnoreCaseLetter(U'é') == '\0', "re.IGNORECASE folds"); + Check(Dyn::ErrnoText(111) == "[Errno 111] Connection refused", "OSError text"); + Check(Dyn::Count("abab", "ab") == 2 && Dyn::Count("aaa", "aa") == 1 && Dyn::Count("ab", "") == 3, "count: non-overlapping, empty counts len+1"); + Check(Dyn::RSplit("a)b)c", ")", 1) == std::vector{ "a)b", "c" }, "rsplit with maxsplit"); + + std::println("{} vector groups, Unicode {}", vectors.size() - 1, vectors["unicode"].get()); + return Failures == 0 ? 0 : 1; +}