/*! * Copyright (c) 2023-2025 by Contributors * \file support/encoding.cc */ #include "encoding.h" #include #include namespace mlc { namespace llm { std::string PrintAsUTF8(TCodepoint codepoint) { TVM_FFI_ICHECK(codepoint <= 0x10FFFF) << "Invalid codepoint: " << codepoint; std::string utf8; if (codepoint <= 0x7F) { // 1-byte sequence utf8 += static_cast(codepoint); } else if (codepoint <= 0x7FF) { // 2-byte sequence utf8 += static_cast(0xC0 | ((codepoint >> 6) & 0x1F)); utf8 += static_cast(0x80 | (codepoint & 0x3F)); } else if (codepoint <= 0xFFFF) { // 3-byte sequence utf8 += static_cast(0xE0 | ((codepoint >> 12) & 0x0F)); utf8 += static_cast(0x80 | ((codepoint >> 6) & 0x3F)); utf8 += static_cast(0x80 | (codepoint & 0x3F)); } else { // 4-byte sequence utf8 += static_cast(0xF0 | ((codepoint >> 18) & 0x07)); utf8 += static_cast(0x80 | ((codepoint >> 12) & 0x3F)); utf8 += static_cast(0x80 | ((codepoint >> 6) & 0x3F)); utf8 += static_cast(0x80 | (codepoint & 0x3F)); } return utf8; } std::string PrintAsEscaped( TCodepoint codepoint, const std::unordered_map& additional_escape_map) { static const std::unordered_map kCodepointToEscape = { {'\'', "\\\'"}, {'\"', "\\\""}, {'\?', "\\\?"}, {'\\', "\\\\"}, {'\a', "\\a"}, {'\b', "\\b"}, {'\f', "\\f"}, {'\n', "\\n"}, {'\r', "\\r"}, {'\t', "\\t"}, {'\v', "\\v"}, {'\0', "\\0"}, {'\x1B', "\\e"}}; if (auto it = additional_escape_map.find(codepoint); it != additional_escape_map.end()) { return it->second; } if (auto it = kCodepointToEscape.find(codepoint); it != kCodepointToEscape.end()) { return it->second; } if (codepoint >= 0x20 && codepoint <= 0x7E) { return std::string({static_cast(codepoint)}); } // convert codepoint to hex char prefix = codepoint <= 0xFF ? 'x' : codepoint <= 0xFFFF ? 'u' : 'U'; int width = codepoint <= 0xFF ? 2 : codepoint <= 0xFFFF ? 4 : 8; std::stringstream ss; ss << std::setfill('0') << std::setw(width) << std::hex << codepoint; auto hex = ss.str(); return std::string("\\") + prefix + hex; } std::string PrintAsEscaped(uint8_t raw_char) { return PrintAsEscaped(raw_char); } std::string PrintAsEscaped(std::string raw_str) { std::string res; auto codepoints = ParseUTF8(raw_str.c_str(), UTF8ErrorPolicy::kReturnByte); for (auto c : codepoints) { res += PrintAsEscaped(c); } return res; } std::tuple HandleUTF8FirstByte(uint8_t byte) { static const std::array kFirstByteMask = {0x00, 0x7F, 0x1F, 0x0F, 0x07}; // clang-format off static const std::array kUtf8Bytes = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1, }; // clang-format on auto num_bytes = kUtf8Bytes[static_cast(byte)]; if (num_bytes == -1) { return {false, 0, 0}; } return {true, num_bytes, byte & kFirstByteMask[num_bytes]}; } std::pair ParseNextUTF8(const char* utf8, UTF8ErrorPolicy error_policy) { auto [accepted, num_bytes, res] = HandleUTF8FirstByte(utf8[0]); if (accepted) { for (int i = 1; i < num_bytes; ++i) { if (utf8[i] == 0 || (static_cast(utf8[i]) & 0xC0) != 0x80) { // invalid utf8 accepted = false; break; } res = (res << 6) | (static_cast(utf8[i]) & 0x3F); } } if (!accepted) { // invalid utf8 if (error_policy == UTF8ErrorPolicy::kReturnInvalid) { return {CharHandlingError::kInvalidUTF8, utf8}; } else { return {static_cast(utf8[0]), utf8 + 1}; } } return {res, utf8 + num_bytes}; } std::vector ParseUTF8(const char* utf8, UTF8ErrorPolicy error_policy) { std::vector codepoints; while (*utf8 != 0) { TCodepoint codepoint; std::tie(codepoint, utf8) = ParseNextUTF8(utf8, error_policy); if (codepoint == CharHandlingError::kInvalidUTF8) { return {codepoint}; } codepoints.push_back(codepoint); } return codepoints; } inline int HexCharToInt(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } else { return -1; } } std::pair ParseNextUTF8OrEscaped( const char* utf8, const std::unordered_map& additional_escape_map) { static const std::unordered_map kEscapeToCodepoint = { {"\\\'", '\''}, {"\\\"", '\"'}, {"\\\?", '\?'}, {"\\\\", '\\'}, {"\\a", '\a'}, {"\\b", '\b'}, {"\\f", '\f'}, {"\\n", '\n'}, {"\\r", '\r'}, {"\\t", '\t'}, {"\\v", '\v'}, {"\\0", '\0'}, {"\\e", '\x1B'}}; if (utf8[0] != '\\') { return ParseNextUTF8(utf8, UTF8ErrorPolicy::kReturnInvalid); } auto escape_sequence = std::string(utf8, 2); if (auto it = additional_escape_map.find(escape_sequence); it != additional_escape_map.end()) { return {it->second, utf8 + 2}; } if (auto it = kEscapeToCodepoint.find(escape_sequence); it != kEscapeToCodepoint.end()) { return {it->second, utf8 + 2}; } if (utf8[1] == 'x') { // arbitrary length hex int len = 0; int32_t codepoint = 0; while (true) { auto digit = HexCharToInt(utf8[2 + len]); if (digit == -1) { break; } codepoint = codepoint * 16 + digit; ++len; } if (len == 0) { return {CharHandlingError::kInvalidEscape, utf8}; } return {codepoint, utf8 + len + 2}; } else if (utf8[1] == 'u' || utf8[1] == 'U') { // 4- or 8-digit hex int len = utf8[1] == 'u' ? 4 : 8; int32_t codepoint = 0; for (int i = 0; i < len; ++i) { auto digit = HexCharToInt(utf8[i + 2]); if (digit == -1) { return {CharHandlingError::kInvalidEscape, utf8}; } codepoint = codepoint * 16 + digit; } return {codepoint, utf8 + len + 2}; } else { return {CharHandlingError::kInvalidEscape, utf8}; } } } // namespace llm } // namespace mlc