summaryrefslogtreecommitdiff
path: root/libmisc/include
diff options
context:
space:
mode:
Diffstat (limited to 'libmisc/include')
-rw-r--r--libmisc/include/libmisc/hash.h4
-rw-r--r--libmisc/include/libmisc/log.h4
-rw-r--r--libmisc/include/libmisc/utf8.h54
3 files changed, 58 insertions, 4 deletions
diff --git a/libmisc/include/libmisc/hash.h b/libmisc/include/libmisc/hash.h
index 91e6b10..58a895f 100644
--- a/libmisc/include/libmisc/hash.h
+++ b/libmisc/include/libmisc/hash.h
@@ -1,14 +1,14 @@
/* libmisc/hash.h - General-purpose hash utilities
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#ifndef _LIBMISC_HASH_H_
#define _LIBMISC_HASH_H_
-#include <stdint.h> /* for uint{n}_t */
#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint{n}_t */
/* djb2 hash */
typedef uint32_t hash_t;
diff --git a/libmisc/include/libmisc/log.h b/libmisc/include/libmisc/log.h
index e6dfb52..c40b642 100644
--- a/libmisc/include/libmisc/log.h
+++ b/libmisc/include/libmisc/log.h
@@ -9,9 +9,9 @@
#include <stdint.h> /* for uint8_t */
-#include <libmisc/macro.h>
-#include <libmisc/fmt.h>
#include <libmisc/_intercept.h>
+#include <libmisc/fmt.h>
+#include <libmisc/macro.h>
#ifdef NDEBUG
#define _LOG_NDEBUG 1
diff --git a/libmisc/include/libmisc/utf8.h b/libmisc/include/libmisc/utf8.h
new file mode 100644
index 0000000..b5e1b0b
--- /dev/null
+++ b/libmisc/include/libmisc/utf8.h
@@ -0,0 +1,54 @@
+/* libmisc/utf8.h - UTF-8 routines
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_UTF8_H_
+#define _LIBMISC_UTF8_H_
+
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint{n}_t */
+
+/**
+ * Decode the codepoint starting at `str` and consuming at most `len`
+ * bytes. Invalid UTF-8 is indicated with chlen=0. For valid UTF-8,
+ * chlen is always in the range [1, 4].
+ */
+static inline void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen) {
+ uint32_t ch;
+ uint8_t chlen;
+ if ((str[0] & 0b10000000) == 0b00000000) { ch = str[0] & 0b01111111; chlen = 1; }
+ else if ((str[0] & 0b11100000) == 0b11000000) { ch = str[0] & 0b00011111; chlen = 2; }
+ else if ((str[0] & 0b11110000) == 0b11100000) { ch = str[0] & 0b00001111; chlen = 3; }
+ else if ((str[0] & 0b11111000) == 0b11110000) { ch = str[0] & 0b00000111; chlen = 4; }
+ else goto invalid;
+ if ((ch == 0 && chlen != 1) || chlen > len) goto invalid;
+ for (uint8_t i = 1; i < chlen; i++) {
+ if ((str[i] & 0b11000000) != 0b10000000) goto invalid;
+ ch = (ch << 6) | (str[i] & 0b00111111);
+ }
+ if (ch > 0x10FFFF) goto invalid;
+ *ret_ch = ch;
+ *ret_chlen = chlen;
+ return;
+ invalid:
+ *ret_chlen = 0;
+}
+
+static inline bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul) {
+ for (size_t pos = 0; pos < len;) {
+ uint32_t ch;
+ uint8_t chlen;
+ utf8_decode_codepoint(&str[pos], len-pos, &ch, &chlen);
+ if (chlen == 0 || (forbid_nul && ch == 0))
+ return false;
+ pos += chlen;
+ }
+ return true;
+}
+
+#define utf8_is_valid(str, len) _utf8_is_valid(str, len, false)
+#define utf8_is_valid_without_nul(str, len) _utf8_is_valid(str, len, true)
+
+#endif /* _LIBMISC_UTF8_H_ */