FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
utf8.h
Go to the documentation of this file.
1#pragma once
2
3#include <cctype>
4
5#include <istream>
6#include <ostream>
7#include <string_view>
8
9#include "fe/assert.h"
10
11/// UTF-8 helpers for decoding byte streams, encoding `char32_t` values, and running
12/// ASCII-style character classification on `char32_t`.
13///
14/// The central entry points are @ref decode and @ref encode. Decoding returns
15/// sentinel values such as @ref EoF and @ref Invalid instead of throwing.
16namespace fe::utf8 {
17
18static constexpr size_t Max = 4; ///< Maximal number of `char8_t`s of an UTF-8 byte sequence.
19static constexpr char32_t BOM = 0xfeff; ///< [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8).
20static constexpr std::string_view Bom = "\xef\xbb\xbf"; ///< BOM as UTF-8 bytes.
21static constexpr char32_t EoF
22 = (char32_t)std::istream::traits_type::eof(); ///< End of stream sentinel returned by @ref decode.
23static constexpr char32_t Null = 0; ///< U+0000 NULL returned unchanged by @ref decode.
24static constexpr char32_t Invalid = 0x110000; ///< Sentinel returned by @ref decode for malformed UTF-8.
25
26/// Returns the expected number of bytes for an UTF-8 char sequence by inspecting the first byte.
27/// Retuns @c 0 if invalid.
28constexpr size_t num_bytes(char8_t c) noexcept {
29 if ((c & char8_t(0b10000000)) == char8_t(0b00000000)) return 1;
30 if ((c & char8_t(0b11100000)) == char8_t(0b11000000)) return 2;
31 if ((c & char8_t(0b11110000)) == char8_t(0b11100000)) return 3;
32 if ((c & char8_t(0b11111000)) == char8_t(0b11110000)) return 4;
33 return 0;
34}
35
36/// Append @p b to @p c for converting UTF-8 to UTF-32.
37constexpr char32_t append(char32_t c, char8_t b) noexcept { return (c << 6) | (b & 0b00111111); }
38
39/// Get relevant bits of first UTF-8 byte @p c of a @em multi-byte sequence consisting of @p num bytes.
40constexpr char32_t first(char32_t c, char32_t num) noexcept { return c & (0b00011111 >> (num - 2)); }
41
42/// Minimum Unicode scalar value representable in an UTF-8 sequence of @p num bytes.
43constexpr char32_t min_code_point(size_t num) noexcept {
44 switch (num) {
45 case 1: return 0x000000;
46 case 2: return 0x000080;
47 case 3: return 0x000800;
48 case 4: return 0x010000;
49 default: return 0x110000;
50 }
51}
52
53/// Is @p c a valid Unicode scalar value?
54constexpr bool is_scalar_value(char32_t c) noexcept { return c <= 0x10ffff && !(0xd800 <= c && c <= 0xdfff); }
55
56/// Is the 2nd, 3rd, or 4th byte of an UTF-8 byte sequence valid?
57/// @returns the extracted `char8_t` or `char8_t(-1)` if invalid.
58constexpr char8_t is_valid234(char8_t c) noexcept {
59 return (c & char8_t(0b11000000)) == char8_t(0b10000000) ? (c & char8_t(0b00111111)) : char8_t(-1);
60}
61
62/// Decodes the next UTF-8 sequence from @p is into a single `char32_t`.
63///
64/// Returns @ref EoF when the stream is exhausted and @ref Invalid for malformed,
65/// overlong, surrogate, or otherwise non-scalar encodings.
66inline char32_t decode(std::istream& is) {
67 char32_t result = is.get();
68 if (result == EoF) return result;
69
70 switch (auto n = utf8::num_bytes(char8_t(result))) {
71 case 0: return Invalid;
72 case 1: return result;
73 default:
74 result = utf8::first(result, n);
75
76 for (size_t i = 1; i != n; ++i)
77 if (auto x = is_valid234(is.get()); x != char8_t(-1))
78 result = utf8::append(result, x);
79 else
80 return Invalid;
81
82 if (result < utf8::min_code_point(n) || !utf8::is_scalar_value(result)) return Invalid;
83 }
84
85 return result;
86}
87
88/// Decodes the UTF-8 sequence at @p i in @p str and advances @p i past it.
89///
90/// Returns @ref EoF at the end of @p str - leaving @p i alone - and @ref Invalid for malformed,
91/// overlong, surrogate, or otherwise non-scalar encodings.
92/// @note An @ref Invalid sequence advances @p i by a *single* byte, so the next @ref decode resynchronizes
93/// instead of swallowing bytes that may well start a valid sequence themselves.
94inline char32_t decode(std::string_view str, size_t& i) noexcept {
95 if (i >= str.size()) return EoF;
96
97 char32_t result = char8_t(str[i]);
98 auto n = utf8::num_bytes(char8_t(result));
99 if (n == 0 || i + n > str.size()) return ++i, Invalid;
100 if (n == 1) return ++i, result;
101
102 result = utf8::first(result, n);
103 for (size_t j = 1; j != n; ++j) {
104 auto x = is_valid234(char8_t(str[i + j]));
105 if (x == char8_t(-1)) return ++i, Invalid;
106 result = utf8::append(result, x);
107 }
108
109 if (result < utf8::min_code_point(n) || !utf8::is_scalar_value(result)) return ++i, Invalid;
110
111 i += n;
112 return result;
113}
114
115/// Number of UTF-8 code points in @p str.
116/// @note A column is counted in code points, so this is what turns a byte offset into one.
117/// Counts via @ref decode, so a malformed sequence resynchronizes the same way the lexer does
118/// and a @p str truncated mid-sequence counts its final partial one.
119inline size_t num_code_points(std::string_view str) noexcept {
120 size_t res = 0;
121 for (size_t i = 0, e = str.size(); i < e; ++res)
122 decode(str, i);
123 return res;
124}
125
126namespace detail {
127// and, or
128inline std::ostream& ao(std::ostream& os, char32_t c32, char32_t a = 0b00111111, char32_t o = 0b10000000) {
129 return os << char((c32 & a) | o);
130}
131} // namespace detail
132
133/// Encodes @p c32 as UTF-8 and writes the resulting bytes to @p os.
134///
135/// Returns `false` when @p c32 is outside the encodable range.
136inline bool encode(std::ostream& os, char32_t c32) {
137 using detail::ao;
138 // clang-format off
139 if (c32 <= 0x00007f) { ao(os, c32 , 0b11111111, 0b00000000); return true; }
140 if (c32 <= 0x0007ff) { ao(ao(os, c32 >> 6, 0b00011111, 0b11000000), c32); return true; }
141 if (c32 <= 0x00ffff) { ao(ao(ao(os, c32 >> 12, 0b00001111, 0b11100000), c32 >> 6), c32); return true; }
142 if (c32 <= 0x10ffff) { ao(ao(ao(ao(os, c32 >> 18, 0b00000111, 0b11110000), c32 >> 12), c32 >> 6), c32); return true; }
143 // clang-format on
144 return false;
145}
146/// Wrapper for `char32_t` with an `operator<<` that writes UTF-8.
147struct Char32 {
148 Char32(char32_t c)
149 : c(c) {}
150
151 friend std::ostream& operator<<(std::ostream& os, Char32 c) {
152 auto res = utf8::encode(os, c.c);
153 assert_unused(res);
154 return os;
155 }
156
157 char32_t c;
158};
159
160/// @name Wrappers
161/// Safe `char32_t`-style wrappers for <[ctype](https://en.cppreference.com/w/cpp/header/cctype)> functions:
162/// > Like all other functions from `<cctype>`, the behavior of `std::isalnum` is undefined if the argument's value is
163/// neither representable as `unsigned char` nor equal to `EOF`.
164///@{
165// clang-format off
166inline bool isalnum (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isalnum (c) : false; }
167inline bool isalpha (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isalpha (c) : false; }
168inline bool isblank (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isblank (c) : false; }
169inline bool iscntrl (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::iscntrl (c) : false; }
170inline bool isdigit (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isdigit (c) : false; }
171inline bool isgraph (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isgraph (c) : false; }
172inline bool islower (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::islower (c) : false; }
173inline bool isprint (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isprint (c) : false; }
174inline bool ispunct (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::ispunct (c) : false; }
175inline bool isspace (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isspace (c) : false; }
176inline bool isupper (char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isupper (c) : false; }
177inline bool isxdigit(char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::isxdigit(c) : false; }
178inline bool isascii (char32_t c) noexcept { return c <= 0x7F; }
179inline char32_t tolower(char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::tolower(c) : c; }
180inline char32_t toupper(char32_t c) noexcept { return (c & ~0xFF) == 0 ? std::toupper(c) : c; }
181
182/// Is @p c within [begin, finis]?
183constexpr bool isrange(char32_t c, char32_t begin, char32_t finis) noexcept { return begin <= c && c <= finis; }
184constexpr auto isrange(char32_t begin, char32_t finis) noexcept { return [=](char32_t c) { return isrange(c, begin, finis); }; }
185
186constexpr bool isodigit(char32_t c) noexcept { return isrange(c, '0', '7'); } ///< Is octal digit?
187constexpr bool isbdigit(char32_t c) noexcept { return isrange(c, '0', '1'); } ///< Is binary digit?
188// clang-format on
189///@}
190
191/// @name any
192/// Build a predicate that checks whether a code point matches any of the given values.
193///@{
194inline bool _any(char32_t c, char32_t d) { return c == d; }
195template<class... T>
196inline bool _any(char32_t c, char32_t d, T... args) {
197 return c == d || _any(c, args...);
198}
199template<class... T>
200inline auto any(T... args) {
201 return [=](char32_t c) { return _any(c, args...); };
202}
203///@}
204
205} // namespace fe::utf8
#define assert_unused(x)
Definition assert.h:50
UTF-8 helpers for decoding byte streams, encoding char32_t values, and running ASCII-style character ...
Definition utf8.h:16
bool isalnum(char32_t c) noexcept
Definition utf8.h:166
bool isdigit(char32_t c) noexcept
Definition utf8.h:170
static constexpr char32_t Invalid
Sentinel returned by decode for malformed UTF-8.
Definition utf8.h:24
bool _any(char32_t c, char32_t d)
Definition utf8.h:194
static constexpr char32_t BOM
Byte Order Mark.
Definition utf8.h:19
char32_t tolower(char32_t c) noexcept
Definition utf8.h:179
static constexpr std::string_view Bom
BOM as UTF-8 bytes.
Definition utf8.h:20
bool isascii(char32_t c) noexcept
Definition utf8.h:178
constexpr size_t num_bytes(char8_t c) noexcept
Returns the expected number of bytes for an UTF-8 char sequence by inspecting the first byte.
Definition utf8.h:28
constexpr bool isbdigit(char32_t c) noexcept
Is binary digit?
Definition utf8.h:187
constexpr char32_t first(char32_t c, char32_t num) noexcept
Get relevant bits of first UTF-8 byte c of a multi-byte sequence consisting of num bytes.
Definition utf8.h:40
char32_t decode(std::istream &is)
Decodes the next UTF-8 sequence from is into a single char32_t.
Definition utf8.h:66
bool isxdigit(char32_t c) noexcept
Definition utf8.h:177
bool encode(std::ostream &os, char32_t c32)
Encodes c32 as UTF-8 and writes the resulting bytes to os.
Definition utf8.h:136
constexpr char8_t is_valid234(char8_t c) noexcept
Is the 2nd, 3rd, or 4th byte of an UTF-8 byte sequence valid?
Definition utf8.h:58
bool isprint(char32_t c) noexcept
Definition utf8.h:173
constexpr bool isodigit(char32_t c) noexcept
Is octal digit?
Definition utf8.h:186
constexpr char32_t min_code_point(size_t num) noexcept
Minimum Unicode scalar value representable in an UTF-8 sequence of num bytes.
Definition utf8.h:43
bool isblank(char32_t c) noexcept
Definition utf8.h:168
bool isalpha(char32_t c) noexcept
Definition utf8.h:167
static constexpr size_t Max
Maximal number of char8_ts of an UTF-8 byte sequence.
Definition utf8.h:18
bool isupper(char32_t c) noexcept
Definition utf8.h:176
char32_t toupper(char32_t c) noexcept
Definition utf8.h:180
auto any(T... args)
Definition utf8.h:200
bool iscntrl(char32_t c) noexcept
Definition utf8.h:169
bool ispunct(char32_t c) noexcept
Definition utf8.h:174
static constexpr char32_t EoF
End of stream sentinel returned by decode.
Definition utf8.h:22
constexpr char32_t append(char32_t c, char8_t b) noexcept
Append b to c for converting UTF-8 to UTF-32.
Definition utf8.h:37
constexpr bool isrange(char32_t c, char32_t begin, char32_t finis) noexcept
Is c within [begin, finis]?
Definition utf8.h:183
bool islower(char32_t c) noexcept
Definition utf8.h:172
constexpr bool is_scalar_value(char32_t c) noexcept
Is c a valid Unicode scalar value?
Definition utf8.h:54
size_t num_code_points(std::string_view str) noexcept
Number of UTF-8 code points in str.
Definition utf8.h:119
bool isspace(char32_t c) noexcept
Definition utf8.h:175
bool isgraph(char32_t c) noexcept
Definition utf8.h:171
static constexpr char32_t Null
U+0000 NULL returned unchanged by decode.
Definition utf8.h:23
friend std::ostream & operator<<(std::ostream &os, Char32 c)
Definition utf8.h:151
char32_t c
Definition utf8.h:157
Char32(char32_t c)
Definition utf8.h:148