| 63 | token == u8"," || token == u8"、" || token == u8";" || token == u8":"; |
| 64 | } |
| 65 | |
| 66 | bool is_tag_open(std::string_view token) { |
| 67 | return token == "[" || token == "<"; |
| 68 | } |
| 69 | |
| 70 | bool is_tag_close(std::string_view token, std::string_view open) { |
| 71 | return (open == "[" && token == "]") || (open == "<" && token == ">"); |
| 72 | } |
| 73 | |
| 74 | std::vector<Utf8Span> split_utf8_spans(std::string_view text, std::string_view label) { |
| 75 | std::vector<Utf8Span> spans; |
| 76 | spans.reserve(utf8_codepoint_count(text, label)); |
| 77 | for (size_t pos = 0; pos < text.size();) { |
| 78 | const auto ch = static_cast<unsigned char>(text[pos]); |
| 79 | size_t width = 0; |
| 80 | if (ch <= 0x7FU) { |
| 81 | width = 1; |
| 82 | } else if ((ch & 0xE0U) == 0xC0U) { |
| 83 | width = 2; |
| 84 | } else if ((ch & 0xF0U) == 0xE0U) { |
| 85 | width = 3; |
| 86 | } else if ((ch & 0xF8U) == 0xF0U) { |
| 87 | width = 4; |
| 88 | } else { |
| 89 | throw std::runtime_error(std::string(label) + " contains invalid UTF-8"); |
| 90 | } |
| 91 | if (pos + width > text.size()) { |
| 92 | throw std::runtime_error(std::string(label) + " contains truncated UTF-8"); |
| 93 | } |
| 94 | for (size_t i = 1; i < width; ++i) { |
| 95 | if (!is_utf8_continuation(static_cast<unsigned char>(text[pos + i]))) { |
| 96 | throw std::runtime_error(std::string(label) + " contains invalid UTF-8 continuation byte"); |
| 97 | } |
no test coverage detected