Add utf-8 handling

This commit is contained in:
jun 2025-02-27 07:02:09 +01:00
parent 60e2849b01
commit d450cd8a45
8 changed files with 192 additions and 70 deletions

View File

@ -40,6 +40,9 @@ endif ()
set_target_properties(libguf_example libguf_test PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_compile_definitions(libguf_test PUBLIC TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/test/data/")
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_available)
if (ipo_available AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
@ -51,7 +54,6 @@ endif()
if (TARGET libguf_test)
message(STATUS "Configure libguf_test...")
target_compile_options(libguf_test PRIVATE ${WARNING_FLAGS_CXX} $<$<CONFIG:Debug>: ${DBG_FLAGS}>)
target_link_options(libguf_test PRIVATE ${WARNING_FLAGS_CXX} $<$<CONFIG:Debug>: ${DBG_FLAGS}> )

View File

@ -36,7 +36,7 @@ typedef struct guf_str {
typedef struct guf_str_view {
const char *str;
size_t len;
ptrdiff_t len;
} guf_str_view;
#define GUF_CSTR_TO_VIEW(CSTR) ((guf_str_view){.str = (CSTR), .len = strlen((CSTR))})

View File

@ -7,17 +7,30 @@
#ifndef GUF_UTF8_H
#define GUF_UTF8_H
#include "guf_common.h"
#include "guf_str.h"
typedef struct guf_utf8_char {
unsigned char bytes[4];
char bytes[5];
} guf_utf8_char;
typedef enum guf_utf8_stat {
GUF_UTF8_READ_DONE,
GUF_UTF8_READ_VALID,
GUF_UTF8_READ_INVALID,
GUF_UTF8_READ_TRUNCATED,
} guf_utf8_stat;
static inline bool guf_char_is_ascii(int c) {return c <= 0 && c <= 127;}
static inline bool guf_uchar_is_ascii(unsigned char c) {return c <= 127;}
GUF_FN_KEYWORDS int guf_utf8_num_bytes(unsigned char c);
GUF_FN_KEYWORDS int guf_utf8_char_num_bytes(guf_utf8_char *c);
GUF_FN_KEYWORDS guf_utf8_char guf_utf8_char_new(const char *bytes, int num_bytes);
GUF_FN_KEYWORDS bool guf_utf8_char_is_valid(const guf_utf8_char *c);
GUF_FN_KEYWORDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c);
GUF_FN_KEYWORDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str);
#endif
@ -26,6 +39,46 @@
#include "guf_common.h"
#include "guf_assert.h"
GUF_FN_KEYWORDS guf_utf8_stat guf_utf8_char_next(guf_utf8_char *res, guf_str_view *str)
{
GUF_ASSERT_RELEASE(res);
GUF_ASSERT_RELEASE(str);
if (str->len <= 0 || str->str == NULL) {
return GUF_UTF8_READ_DONE;
}
int consumed = 0;
res->bytes[consumed++] = str->str[0];
str->len--;
str->str = str->len ? str->str + 1 : NULL;
for (size_t i = 1; i < GUF_STATIC_BUF_SIZE(res->bytes); ++i) {
res->bytes[i] = '\0';
}
const int num_bytes = guf_utf8_char_num_bytes(res);
if (!num_bytes) {
return GUF_UTF8_READ_INVALID;
}
while (consumed < num_bytes && str->len > 0) {
res->bytes[consumed++] = str->str[0];
str->len--;
str->str = str->len ? str->str + 1 : NULL;
}
if (consumed < num_bytes) {
return GUF_UTF8_READ_TRUNCATED;
} else if (guf_utf8_char_is_valid(res)) {
return GUF_UTF8_READ_VALID;
} else {
return GUF_UTF8_READ_INVALID;
}
}
// cf. https://www.rfc-editor.org/rfc/rfc3629#page-4
GUF_FN_KEYWORDS int guf_utf8_num_bytes(unsigned char c)
{
@ -42,6 +95,13 @@ GUF_FN_KEYWORDS int guf_utf8_num_bytes(unsigned char c)
}
}
GUF_FN_KEYWORDS int guf_utf8_char_num_bytes(guf_utf8_char *c)
{
GUF_ASSERT(c);
return guf_utf8_num_bytes(c->bytes[0]);
}
GUF_FN_KEYWORDS bool guf_utf8_char_is_valid(const guf_utf8_char *c)
{
const int num_bytes = guf_utf8_num_bytes(c->bytes[0]);
@ -50,9 +110,11 @@ GUF_FN_KEYWORDS bool guf_utf8_char_is_valid(const guf_utf8_char *c)
return false;
}
const unsigned char *bytes = (const unsigned char*)c->bytes;
for (int i = 0; i < num_bytes; ++i) {
// "The octet values C0, C1, F5 to FF never appear.", cf. https://www.rfc-editor.org/rfc/rfc3629#page-5
if (c->bytes[i] == 0xC0 || c->bytes[i] == 0xC1 || (c->bytes[i] >= 0xF5 && c->bytes[i] <= 0xFF)) {
if (bytes[i] == 0xC0 || bytes[i] == 0xC1 || (bytes[i] >= 0xF5 && bytes[i] <= 0xFF)) {
return false;
}
}
@ -64,36 +126,34 @@ GUF_FN_KEYWORDS bool guf_utf8_char_is_valid(const guf_utf8_char *c)
switch (num_bytes)
{
case 1:
GUF_ASSERT(c->bytes[0] <= 0x7F);
return true;
case 2:
GUF_ASSERT(c->bytes[0] >= 0xC2 && c->bytes[0] <= 0xDF);
return guf_valid_tail(c->bytes[1]);
return guf_valid_tail(bytes[1]);
case 3:
if ((c->bytes[0] == 0xE0) && (c->bytes[1] >= 0xA0 && c->bytes[1] <= 0xBF) && guf_valid_tail(c->bytes[2])) {
if ((bytes[0] == 0xE0) && (bytes[1] >= 0xA0 && bytes[1] <= 0xBF) && guf_valid_tail(bytes[2])) {
return true;
}
if ((c->bytes[0] >= 0xE1 && c->bytes[0] <= 0xEC) && guf_valid_tail(c->bytes[1]) && guf_valid_tail(c->bytes[2])) {
if ((bytes[0] >= 0xE1 && bytes[0] <= 0xEC) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2])) {
return true;
}
if ((c->bytes[0] == 0xED) && (c->bytes[1] >= 0x80 && c->bytes[1] <= 0x9F) && guf_valid_tail(c->bytes[2])) {
if ((bytes[0] == 0xED) && (bytes[1] >= 0x80 && bytes[1] <= 0x9F) && guf_valid_tail(bytes[2])) {
return true;
}
if ((c->bytes[0] >= 0xEE && c->bytes[0] <= 0xEF) && guf_valid_tail(c->bytes[1]) && guf_valid_tail(c->bytes[2])) {
if ((bytes[0] >= 0xEE && bytes[0] <= 0xEF) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2])) {
return true;
}
return false;
case 4:
if ((c->bytes[0] == 0xF0) && (c->bytes[1] >= 0x90 && c->bytes[1] <= 0xBF) && guf_valid_tail(c->bytes[2]) && guf_valid_tail(c->bytes[3])) {
if ((bytes[0] == 0xF0) && (bytes[1] >= 0x90 && bytes[1] <= 0xBF) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
return true;
}
if ((c->bytes[0] >= 0xF1 && c->bytes[0] <= 0xF3) && guf_valid_tail(c->bytes[1]) && guf_valid_tail(c->bytes[2]) && guf_valid_tail(c->bytes[3])) {
if ((bytes[0] >= 0xF1 && bytes[0] <= 0xF3) && guf_valid_tail(bytes[1]) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
return true;
}
if ((c->bytes[0] == 0xF4) && (c->bytes[1] >= 0x80 && c->bytes[1] <= 0x8F) && guf_valid_tail(c->bytes[2]) && guf_valid_tail(c->bytes[3])) {
if ((bytes[0] == 0xF4) && (bytes[1] >= 0x80 && bytes[1] <= 0x8F) && guf_valid_tail(bytes[2]) && guf_valid_tail(bytes[3])) {
return true;
}
return false;
@ -101,10 +161,49 @@ GUF_FN_KEYWORDS bool guf_utf8_char_is_valid(const guf_utf8_char *c)
default:
return false;
}
#undef guf_valid_tail
}
GUF_FN_KEYWORDS bool guf_utf8_char_is_whitespace(const guf_utf8_char *c)
{
// cf. https://en.wikipedia.org/wiki/Whitespace_character#Unicode (last-retrieved 2025-02-27)
const char *ws_one_byte[] = {" ", "\n", "\t", "\t", "\v", "\f"};
const char *ws_two_bytes[] = {"\xC2\x85", "\xC2\xA0"};
const char *ws_three_bytes[] = {"\xE1\x9A\x80", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xA8", "\xE2\x80\xA9", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80"};
const int num_bytes = guf_utf8_num_bytes(c->bytes[0]);
switch (num_bytes)
{
case 1:
for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(ws_one_byte); ++i) {
if (c->bytes[0] == ws_one_byte[i][0]) {
return true;
}
}
return false;
case 2:
for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(ws_two_bytes); ++i) {
if (c->bytes[0] == ws_two_bytes[i][0] && c->bytes[1] == ws_two_bytes[i][1]) {
return true;
}
}
return false;
case 3:
for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(ws_three_bytes); ++i) {
if (c->bytes[0] == ws_three_bytes[i][0] && c->bytes[1] == ws_three_bytes[i][1] && c->bytes[2] == ws_three_bytes[i][2]) {
return true;
}
}
return false;
default:
return false;
}
}
#endif
#undef GUF_FN_KEYWORDS

View File

@ -1,13 +0,0 @@
„Ich weiß nicht“, rief ich ohne Klang „ich weiß ja nicht. Wenn
niemand kommt, dann kommt eben niemand. Ich habe niemandem etwas
Böses getan, niemand hat mir etwas Böses getan, niemand aber will
mir helfen. Lauter niemand. Aber so ist es doch nicht. Nur daß mir
niemand hilft —, sonst wäre lauter niemand hübsch. Ich würde ganz
gern — warum denn nicht — einen Ausflug mit einer Gesellschaft von
lauter Niemand machen. Natürlich ins Gebirge, wohin denn sonst? Wie
sich diese Niemand aneinander drängen, diese vielen quer gestreckten
und eingehängten Arme, diese vielen Füße, durch winzige Schritte
getrennt! Versteht sich, daß alle in Frack sind. Wir gehen so lala,
der Wind fährt durch die Lücken, die wir und unsere Gliedmaßen offen
lassen. Die Hälse werden im Gebirge frei! Es ist ein Wunder, daß
wir nicht singen.“

View File

@ -0,0 +1,49 @@
„Ich weiß nicht“, rief ich ohne Klang „ich weiß ja nicht. Wenn
niemand kommt, dann kommt eben niemand. Ich habe niemandem etwas
Böses getan, niemand hat mir etwas Böses getan, niemand aber will
mir helfen. Lauter niemand. Aber so ist es doch nicht. Nur daß mir
niemand hilft —, sonst wäre lauter niemand hübsch. Ich würde ganz
gern — warum denn nicht — einen Ausflug mit einer Gesellschaft von
lauter Niemand machen. Natürlich ins Gebirge, wohin denn sonst? Wie
sich diese Niemand aneinander drängen, diese vielen quer gestreckten
und eingehängten Arme, diese vielen Füße, durch winzige Schritte
getrennt! Versteht sich, daß alle in Frack sind. Wir gehen so lala,
der Wind fährt durch die Lücken, die wir und unsere Gliedmaßen offen
lassen. Die Hälse werden im Gebirge frei! Es ist ein Wunder, daß
wir nicht singen.“
Det var i den Tid, jeg gik omkring og sulted i Kristiania, denne forunderlige By,
som ingen forlader, før han har fået Mærker af den . . . .
Jeg ligger vågen på min Kvist og hører en Klokke nedenunder mig slå seks Slag; det var allerede ganske lyst,
og Folk begyndte at færdes op og ned i Trapperne. Nede ved Døren, hvor mit Rum var tapetseret med gamle Numre
af »Morgenbladet«, kunde jeg så tydelig se en Bekendtgørelse fra Fyrdirektøren, og lidt tilvenstre derfra et fedt,
bugnende Avertissement fra Bager Fabian Olsen om nybagt Brød.
The quick brown fox jumps over the lazy dog.
Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon.
Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.
Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία.
El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro.
Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en
canoë au delà des îles, près du mälström où brûlent les novæ.
D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh.
Árvíztűrő tükörfúrógép.
Pchnąć w tę łódź jeża lub ośm skrzyń fig.
Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa.
В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!
Pijamalı hasta, yağız şoföre çabucak güvendi.
ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ
ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ
ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬

View File

@ -6,8 +6,8 @@
#define GUF_IMPL
#include "guf_dbuf.h"
#define GUF_CNT_NAME dbuf_uchar
#define GUF_T uchar
#define GUF_CNT_NAME dbuf_char
#define GUF_T char
#define GUF_T_IS_INTEGRAL_TYPE
#define GUF_IMPL
#include "guf_dbuf.h"

View File

@ -10,8 +10,8 @@
typedef unsigned char uchar;
#define GUF_CNT_NAME dbuf_uchar
#define GUF_T uchar
#define GUF_CNT_NAME dbuf_char
#define GUF_T char
#define GUF_T_IS_INTEGRAL_TYPE
#include "guf_dbuf.h"

View File

@ -16,7 +16,7 @@ struct DictCstrToIntTest : public Test
private:
dbuf_uchar text_buf {};
dbuf_char text_buf {};
std::vector<char> text_vec {};
void insert_lookup()
@ -25,34 +25,22 @@ struct DictCstrToIntTest : public Test
dict_cstr_int word_cnt_dict {};
dict_cstr_int_init(&word_cnt_dict, &guf_allocator_libc);
ptrdiff_t len = 0;
ptrdiff_t valid_chars = 0, invalid_chars = 0, bytes = text_buf.size;
guf_str_view input_str = {.str = text_buf.data, .len = text_buf.size};
guf_utf8_char ch = {};
for (dbuf_uchar_iter it = dbuf_uchar_begin(&text_buf); !dbuf_uchar_iter_is_end(&text_buf, it); it = dbuf_uchar_iter_next(&text_buf, it, 1)) {
const unsigned char c = *it.ptr;
guf_utf8_char utf8_c = {.bytes = {c, 0, 0, 0}};
const int num_bytes = guf_utf8_num_bytes(c);
if (!num_bytes) {
continue;
}
int consumed = 1;
while (consumed < num_bytes && ((it = dbuf_uchar_iter_next(&text_buf, it, 1)), !dbuf_uchar_iter_is_end(&text_buf, it)) ) {
utf8_c.bytes[consumed++] = *it.ptr;
}
if (consumed < num_bytes) {
printf("Invalid utf-8: file is truncated\n");
break;
}
if (guf_utf8_char_is_valid(&utf8_c) && utf8_c.bytes[0] != '\0') {
char str[5] {};
memcpy(str, utf8_c.bytes, num_bytes);
printf("%s", str);
++len;
for (guf_utf8_stat stat = guf_utf8_char_next(&ch, &input_str); stat != GUF_UTF8_READ_DONE; stat = guf_utf8_char_next(&ch, &input_str)) {
if (stat == GUF_UTF8_READ_VALID) {
++valid_chars;
printf("%s", ch.bytes);
} else {
++invalid_chars;
printf("::INVALID_UTF8_CHAR::");
}
}
printf("\nread %td utf-8 characters\n", len);
TEST_CHECK(input_str.len == 0 && input_str.str == NULL);
printf("\nread %td bytes\n", bytes);
printf("read %td valid and %td invalid utf-8 characters\n", valid_chars, invalid_chars);
dict_cstr_int_free(&word_cnt_dict, NULL);
bool dbuf_null = !word_cnt_dict.kv_elems.data && !word_cnt_dict.kv_elems.allocator && !word_cnt_dict.kv_elems.capacity && !word_cnt_dict.kv_elems.size;
@ -61,29 +49,26 @@ struct DictCstrToIntTest : public Test
bool load_file()
{
#define TEST_DATA_DIR "/Users/joni/Desktop/libguf/src/test/data"
FILE *in_file {nullptr};
if (!in_file) {
in_file = fopen(TEST_DATA_DIR "/data_01.txt", "r");
in_file = fopen(TEST_DATA_DIR "/utf8-test.txt", "r");
}
if (!in_file) {
return false;
}
dbuf_uchar_init(&text_buf, 128, &guf_allocator_libc);
dbuf_char_init(&text_buf, 128, &guf_allocator_libc);
int c = EOF;
while ((c = fgetc(in_file)) != EOF) {
dbuf_uchar_push_val(&text_buf, (unsigned char)c);
text_vec.push_back((unsigned char)c);
dbuf_char_push_val(&text_buf, (char)c);
text_vec.push_back((char)c);
}
fclose(in_file);
if (*dbuf_uchar_back(&text_buf) != '\0') {
dbuf_uchar_push_val(&text_buf, '\0');
text_vec.push_back('\0');
}
// dbuf_char_insert_val(&text_buf, '\xC0', 1);
// text_vec.insert(text_vec.cbegin() + 1, '\xC0');
return TEST_CHECK(std::ssize(text_vec) == text_buf.size);
}
@ -103,7 +88,7 @@ struct DictCstrToIntTest : public Test
insert_lookup();
end:
dbuf_uchar_free(&text_buf, NULL);
dbuf_char_free(&text_buf, NULL);
text_buf = {};
passed = (num_failed_checks == 0);