Added UTF-8 helper functions
This commit is contained in:
152
src/string.c
Normal file
152
src/string.c
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#define SET_MSG(result, msg) \
|
||||||
|
do { \
|
||||||
|
snprintf((char *)(result).message, RESULT_MSG_SIZE, "%s", (const char *)msg); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
// Check if a character is a space
|
||||||
|
static inline bool is_space(unsigned char c) {
|
||||||
|
return (c == ' ' || c == '\t' ||
|
||||||
|
c == '\n' || c == '\r' ||
|
||||||
|
c == '\f' || c == '\v');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get byte length of an UTF-8 sequence
|
||||||
|
static inline int utf8_char_len(unsigned char byte) {
|
||||||
|
if ((byte & 0x80) == 0x00) return 1;
|
||||||
|
if ((byte & 0xE0) == 0xC0) return 2;
|
||||||
|
if ((byte & 0xF0) == 0xE0) return 3;
|
||||||
|
if ((byte & 0xF8) == 0xF0) return 4;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate an UTF-8 symbol
|
||||||
|
static bool utf8_is_char_valid(const char *utf8_char, int *out_len) {
|
||||||
|
if (utf8_char == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t len = utf8_char_len((unsigned char)utf8_char[0]);
|
||||||
|
if (len <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 1; idx < len; idx++) {
|
||||||
|
if ((utf8_char[idx] & 0xC0) != 0x80) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utf8_char[len] != '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_len) {
|
||||||
|
*out_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate an UTF-8 symbol and measure byte length and character count
|
||||||
|
static bool utf8_scan(const char *str, size_t *out_byte_size, size_t *out_char_count) {
|
||||||
|
size_t b_size = 0;
|
||||||
|
size_t c_count = 0;
|
||||||
|
const unsigned char *p = (const unsigned char *)str;
|
||||||
|
|
||||||
|
while (p[b_size] != '\0') {
|
||||||
|
size_t len = utf8_char_len(p[b_size]);
|
||||||
|
if (len <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx = 1; idx < len; idx++) {
|
||||||
|
if (p[b_size + idx] == '\0' || (p[b_size + idx] & 0xC0) != 0x80) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p_size += len;
|
||||||
|
c_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_byte_size = b_size;
|
||||||
|
*out_char_count = c_count;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an UTF-8 symbol to a codepoint
|
||||||
|
static uint32_t utf8_decode(const char *str, int *char_len) {
|
||||||
|
unsigned char byte = (unsigned char)*str;
|
||||||
|
*char_len = utf8_char_len(byte);
|
||||||
|
|
||||||
|
uint32_t result = 0;
|
||||||
|
|
||||||
|
switch (*char_len) {
|
||||||
|
case 1:
|
||||||
|
result = byte;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
result = ((byte & 0x1F) << 6) |
|
||||||
|
(str[1] & 0x3F);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
result = ((byte & 0x0F) << 12) |
|
||||||
|
((str[1] & 0x3F) << 6) |
|
||||||
|
(str[2] & 0x3F);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
result = ((byte & 0x07) << 18) |
|
||||||
|
((str[1] & 0x3F) << 12) |
|
||||||
|
((str[2] & 0x3F) << 6) |
|
||||||
|
(str[3] & 0x3F);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode a codepoint to an UTF-8 symbol
|
||||||
|
static int utf8_encode(uint32_t codepoint, char *out) {
|
||||||
|
if (codepoint <= 0x7F) {
|
||||||
|
out[0] = (char)codepoint;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepoint <= 0x7FF) {
|
||||||
|
out[0] = (char)(0xC0 | (codepoint >> 6));
|
||||||
|
out[1] = (char)(0x80 | (codepount & 0x3F));
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepoint <= 0xFFFF) {
|
||||||
|
out[0] = (char)(0xE0 | (codepoint >> 12));
|
||||||
|
out[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
|
||||||
|
out[2] = (char)(0x80 | (codepoint & 0x3F));
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepoint <= 0x10FFFF) {
|
||||||
|
out[0] = (char)(0xF0 | (codepoint >> 18));
|
||||||
|
out[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
|
||||||
|
out[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
|
||||||
|
out[3] = (char)(0x80 | (codepoint & 0x3F));
|
||||||
|
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
67
src/string.h
Normal file
67
src/string.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef STRING_H
|
||||||
|
#define STRING_H
|
||||||
|
|
||||||
|
#define RESULT_MSG_SIZE 64
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
STRING_OK = 0x0,
|
||||||
|
STRING_ERR_ALLOCATE,
|
||||||
|
STRING_ERR_INVALID,
|
||||||
|
STRING_ERR_INVALID_UTF8,
|
||||||
|
STRING_ERR_OVERFLOW
|
||||||
|
} string_status_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *data;
|
||||||
|
size_t byte_size; // Size in bytes minus the NULL terminator
|
||||||
|
size_t byte_cap; // Total allocated memory
|
||||||
|
size_t char_count; // Number of symbols
|
||||||
|
} string_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
string_status_t status;
|
||||||
|
uint8_t message[RESULT_MSG_SIZE];
|
||||||
|
union {
|
||||||
|
string_t *string; // For new, clone, slice, reverse, trim
|
||||||
|
char *symbol; // For get_at
|
||||||
|
bool is_eq; // For comparison
|
||||||
|
struct { // For split
|
||||||
|
string_t **strings;
|
||||||
|
size_t count;
|
||||||
|
} split;
|
||||||
|
} value;
|
||||||
|
} string_result_t;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Public APIs
|
||||||
|
string_result_t string_new(const char *c_str);
|
||||||
|
string_result_t string_close(const string_t *str);
|
||||||
|
string_result_t string_concat(const string_t *x, const string_t *y);
|
||||||
|
string_result_t string_contains(const string_t *haystack, const string_t *needle);
|
||||||
|
string_result_t string_slice(const string_t *str, size_t start, size_t end);
|
||||||
|
string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensitive);
|
||||||
|
string_result_t string_get_at(const string_t *str, size_t position);
|
||||||
|
string_result_t string_set_at(const string_t *str, size_t position, const char *utf8_char);
|
||||||
|
string_result_t string_to_lower(const string_t *str);
|
||||||
|
string_result_t string_to_upper(const string_t *str);
|
||||||
|
string_result_t string_reverse(const string_t *str);
|
||||||
|
string_result_t string_trim(const string_t *str);
|
||||||
|
string_result_t string_split(const string_t *str, const char *delim);
|
||||||
|
string_result_t string_destroy(string_t *str);
|
||||||
|
string_result_t string_split_destroy(string_t **split, size_t count);
|
||||||
|
|
||||||
|
// Inline methods
|
||||||
|
static inline size_t string_size(const string_t *str) {
|
||||||
|
return str ? str->char_count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user