utils: add ascii ctype helpers
Add some constexpr ctype helpers that avoid any possible UB (i.e., handling casting to unsigned), and explicitly avoid any locale-specifics.
This commit is contained in:
@ -269,7 +269,7 @@ Mu::remove_ctrl(const std::string& str)
|
||||
result.reserve(str.length());
|
||||
|
||||
for (auto&& c : str) {
|
||||
if (::iscntrl(c) || c == ' ') {
|
||||
if (is_ascii_cntrl(c) || c == ' ') {
|
||||
if (prev != ' ')
|
||||
result += prev = ' ';
|
||||
} else
|
||||
|
||||
@ -166,6 +166,133 @@ std::tm mu_time(T t={}, bool use_utc=false) {
|
||||
return time_tm;
|
||||
}
|
||||
|
||||
/*
|
||||
* Constexpr, locale-independent ASCII versions of some <cctype> functions.
|
||||
*
|
||||
* Unlike their libc counterparts, these are well-defined for _any_
|
||||
* character value; no need for casting to unsigned char at the
|
||||
* call-site (which the libc versions require to avoid UB for negative
|
||||
* char values).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Is this an ASCII character, i.e., in [0, 0x7f]? Constexpr version
|
||||
* of ::isascii.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii(Char c) noexcept {
|
||||
return static_cast<std::make_unsigned_t<Char>>(c) < 0x80;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII control character? ASCII version of ::iscntrl.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_cntrl(Char c) noexcept {
|
||||
const auto uc{static_cast<std::make_unsigned_t<Char>>(c)};
|
||||
return uc < 0x20 || uc == 0x7f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII alphabetic character, i.e., in [a-zA-Z]?
|
||||
* ASCII version of ::isalpha.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_alpha(Char c) noexcept {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII digit, i.e., in [0-9]? ASCII version of ::isdigit.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_digit(Char c) noexcept {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII alphanumeric character, i.e., in [a-zA-Z0-9]?
|
||||
* ASCII version of ::isalnum.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_alnum(Char c) noexcept {
|
||||
return is_ascii_alpha(c) || is_ascii_digit(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII blank character, i.e., SPC or TAB? ASCII version
|
||||
* of ::isblank.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_blank(Char c) noexcept {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII white-space character, i.e., SPC, TAB, LF, VT, FF
|
||||
* or CR? ASCII version of ::isspace.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_space(Char c) noexcept {
|
||||
return c == ' ' || (c >= '\t' && c <= '\r');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an ASCII punctuation character, i.e., a printable character
|
||||
* that is neither alphanumeric nor SPC? ASCII version of ::ispunct.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return true or false
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr bool is_ascii_punct(Char c) noexcept {
|
||||
const auto uc{static_cast<std::make_unsigned_t<Char>>(c)};
|
||||
return uc > 0x20 && uc < 0x7f && !is_ascii_alnum(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lower-case version of an ASCII character in [A-Z]; any
|
||||
* other character is returned unchanged. ASCII version of ::tolower.
|
||||
*
|
||||
* @param c some character
|
||||
*
|
||||
* @return the lower-cased character
|
||||
*/
|
||||
template<std::integral Char>
|
||||
constexpr Char to_ascii_lower(Char c) noexcept {
|
||||
return (c >= 'A' && c <= 'Z') ?
|
||||
static_cast<Char>(c + ('a' - 'A')) : c;
|
||||
}
|
||||
|
||||
using StringVec = std::vector<std::string>;
|
||||
|
||||
/**
|
||||
|
||||
@ -335,6 +335,46 @@ test_summarize()
|
||||
"fortification by the Bais raja Sathna. ");
|
||||
}
|
||||
|
||||
static void
|
||||
test_ascii_ctype()
|
||||
{
|
||||
/* the ascii ctype functions are constexpr and work for any
|
||||
* integral character type... */
|
||||
static_assert(is_ascii('m'));
|
||||
static_assert(!is_ascii(static_cast<char>(0x80)));
|
||||
static_assert(is_ascii_cntrl('\n'));
|
||||
static_assert(!is_ascii_cntrl(' '));
|
||||
static_assert(is_ascii_blank('\t'));
|
||||
static_assert(!is_ascii_blank('\n'));
|
||||
static_assert(to_ascii_lower('Q') == 'q');
|
||||
static_assert(to_ascii_lower(U'Q') == U'q');
|
||||
static_assert(to_ascii_lower('8') == '8');
|
||||
static_assert(to_ascii_lower('\xc4'/* Ä */) == '\xc4');
|
||||
|
||||
/* ... and, unlike their libc cousins, are well-defined for
|
||||
* negative chars; check the full range against glib's
|
||||
* (locale-independent) g_ascii_* versions. */
|
||||
for (int i = -128; i != 128; ++i) {
|
||||
const auto c{static_cast<char>(i)};
|
||||
g_assert_cmpint(is_ascii_cntrl(c), ==, !!g_ascii_iscntrl(c));
|
||||
g_assert_cmpint(is_ascii_alpha(c), ==, !!g_ascii_isalpha(c));
|
||||
g_assert_cmpint(is_ascii_digit(c), ==, !!g_ascii_isdigit(c));
|
||||
g_assert_cmpint(is_ascii_alnum(c), ==, !!g_ascii_isalnum(c));
|
||||
/* NB: unlike isspace(3), g_ascii_isspace excludes VT */
|
||||
g_assert_cmpint(is_ascii_space(c), ==,
|
||||
!!g_ascii_isspace(c) || c == '\v');
|
||||
g_assert_cmpint(is_ascii_punct(c), ==, !!g_ascii_ispunct(c));
|
||||
g_assert_cmpint(to_ascii_lower(c), ==, g_ascii_tolower(c));
|
||||
}
|
||||
|
||||
/* values beyond the char range must not get truncated */
|
||||
const gunichar uc{0x2028/* line separator */};
|
||||
g_assert_false(is_ascii(uc));
|
||||
g_assert_false(is_ascii_cntrl(uc));
|
||||
g_assert_false(is_ascii_punct(uc));
|
||||
g_assert_true(to_ascii_lower(uc) == uc);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
@ -355,6 +395,7 @@ main(int argc, char* argv[])
|
||||
g_test_add_func("/utils/define-bitmap", test_define_bitmap);
|
||||
g_test_add_func("/utils/to-from-lexnum", test_to_from_lexnum);
|
||||
g_test_add_func("/utils/locale-workaround", test_locale_workaround);
|
||||
g_test_add_func("/utils/ascii-ctype", test_ascii_ctype);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user