Files
mlib/m-string.h
T
Patrick Pelissier a29682ca29 Create M_ASSERT macro to encapsulate assert within M*LIB
The goal is to enable the user to override this macro if needed so that it can enable or disable the M*LIB witout impacting its own assertions.
2020-10-29 22:46:17 +01:00

2177 lines
86 KiB
C++

/*
* M*LIB - Dynamic Size String Module
*
* Copyright (c) 2017-2020, Patrick Pelissier
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* + Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef MSTARLIB_STRING_H
#define MSTARLIB_STRING_H
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "m-core.h"
M_BEGIN_PROTECTED_CODE
/********************************** INTERNAL ************************************/
/* If within the string tests, perform additional (potentialy slow) checks */
#ifdef STRING_WITHIN_TEST
# define STRINGI_ASSERT_SLOW(n) M_ASSERT(n)
#else
# define STRINGI_ASSERT_SLOW(n) (void) 0
#endif
// This macro defines the contract of a string.
// Note: A ==> B is represented as not(A) or B
// Note: use of strlen can slow down a lot the program in some cases.
#define STRINGI_CONTRACT(v) do { \
M_ASSERT (v != NULL); \
STRINGI_ASSERT_SLOW (string_size(v) == strlen(string_get_cstr(v))); \
M_ASSERT (string_get_cstr(v)[string_size(v)] == 0); \
M_ASSERT (string_size(v) < string_capacity(v)); \
M_ASSERT (string_capacity(v) < sizeof (string_heap_ct) || !stringi_stack_p(v)); \
} while(0)
/****************************** EXTERNAL *******************************/
/***********************************************************************/
/* */
/* DYNAMIC STRING */
/* */
/***********************************************************************/
/* Index returned in case of error instead of the position within the string */
#define STRING_FAILURE ((size_t)-1)
/* This is the main structure of this module. */
// string if it is heap allocated
typedef struct string_heap_s {
size_t size;
size_t alloc;
} string_heap_ct;
// string if it is stack allocated
typedef struct string_stack_s {
char buffer[sizeof (string_heap_ct)];
} string_stack_ct;
// both cases of string are possible
typedef union string_union_u {
string_heap_ct heap;
string_stack_ct stack;
} string_union_ct;
// Dynamic string
typedef struct string_s {
string_union_ct u;
char *ptr;
} string_t[1];
// Pointer to a Dynamic string
typedef struct string_s *string_ptr;
// Constant pointer to a Dynamic string
typedef const struct string_s *string_srcptr;
/* Input option for string_fgets
STRING_READ_LINE (read line),
STRING_READ_PURE_LINE (read line and remove CR and LF)
STRING_READ_FILE (read all file)
*/
typedef enum string_fgets_s {
STRING_READ_LINE = 0, STRING_READ_PURE_LINE = 1, STRING_READ_FILE = 2
} string_fgets_t;
/* Internal method to test if the string is stack based or heap based */
static inline bool
stringi_stack_p(const string_t s)
{
// Function can be called when contract is not fullfilled
return (s->ptr == NULL);
}
/* Set the size of the string */
static inline void
stringi_set_size(string_t s, size_t size)
{
// Function can be called when contract is not fullfilled
if (stringi_stack_p(s)) {
M_ASSERT (size < sizeof (string_heap_ct) - 1);
s->u.stack.buffer[sizeof (string_heap_ct) - 1] = (char) size;
} else
s->u.heap.size = size;
}
/* Return the number of characters of the string */
static inline size_t
string_size(const string_t s)
{
// Function can be called when contract is not fullfilled
// Reading both values before calling the '?' operator allows compiler to generate branchless code
const size_t s_stack = (size_t) s->u.stack.buffer[sizeof (string_heap_ct) - 1];
const size_t s_heap = s->u.heap.size;
return stringi_stack_p(s) ? s_stack : s_heap;
}
/* Return the capacity of the string */
static inline size_t
string_capacity(const string_t s)
{
// Function can be called when contract is not fullfilled
// Reading both values before calling the '?' operator allows compiler to generate branchless code
const size_t c_stack = sizeof (string_heap_ct) - 1;
const size_t c_heap = s->u.heap.alloc;
return stringi_stack_p(s) ? c_stack : c_heap;
}
/* Return a writable pointer to the array of char of the string */
static inline char*
stringi_get_str(string_t v)
{
// Function can be called when contract is not fullfilled
char *const ptr_stack = &v->u.stack.buffer[0];
char *const ptr_heap = v->ptr;
return stringi_stack_p(v) ? ptr_stack : ptr_heap;
}
/* Return the string view a classic C string (const char *) */
static inline const char*
string_get_cstr(const string_t v)
{
// Function cannot be called when contract is not fullfilled
// but it is called by contract (so no contract check to avoid infinite recursion).
const char *const ptr_stack = &v->u.stack.buffer[0];
const char *const ptr_heap = v->ptr;
return stringi_stack_p(v) ? ptr_stack : ptr_heap;
}
/* Initialize the dynamic string (constructor)
and make it empty */
static inline void
string_init(string_t s)
{
s->ptr = NULL;
s->u.stack.buffer[0] = 0;
stringi_set_size(s, 0);
STRINGI_CONTRACT(s);
}
/* Clear the Dynamic string (destructor) */
static inline void
string_clear(string_t v)
{
STRINGI_CONTRACT(v);
if (!stringi_stack_p(v)) {
M_MEMORY_FREE(v->ptr);
v->ptr = NULL;
}
/* This is not needed but is safer to make
the string invalid so that it can be detected. */
v->u.stack.buffer[sizeof (string_heap_ct) - 1] = CHAR_MAX;
}
/* NOTE: Internaly used by STRING_DECL_INIT */
static inline void stringi_clear2(string_t *v) { string_clear(*v); }
/* Clear the Dynamic string (destructor)
and return a heap pointer to the string.
The ownership of the data is transfered back to the caller
and the returned pointer has to be released by M_MEMORY_FREE. */
static inline char *
string_clear_get_str(string_t v)
{
STRINGI_CONTRACT(v);
char *p = v->ptr;
if (stringi_stack_p(v)) {
// The string was stack allocated.
p = v->u.stack.buffer;
// Need to allocate a heap string to return the copy.
size_t alloc = string_size(v)+1;
char *ptr = M_MEMORY_REALLOC (char, NULL, alloc);
if (M_UNLIKELY (ptr == NULL)) {
M_MEMORY_FULL(sizeof (char) * alloc);
return NULL;
}
M_ASSERT(ptr != NULL && p != NULL);
memcpy(ptr, p, alloc);
p = ptr;
}
v->ptr = NULL;
v->u.stack.buffer[sizeof (string_heap_ct) - 1] = CHAR_MAX;
return p;
}
/* Make the string empty */
static inline void
string_clean(string_t v)
{
STRINGI_CONTRACT (v);
stringi_set_size(v, 0);
stringi_get_str(v)[0] = 0;
STRINGI_CONTRACT (v);
}
/* Return the selected character of the string */
static inline char
string_get_char(const string_t v, size_t index)
{
STRINGI_CONTRACT (v);
M_ASSERT_INDEX(index, string_size(v));
return string_get_cstr(v)[index];
}
/* Test if the string is empty or not */
static inline bool
string_empty_p(const string_t v)
{
STRINGI_CONTRACT (v);
return string_size(v) == 0;
}
/* Ensures that the string capacity is greater than size_alloc
(size_alloc shall include the final null char).
It may move the string from stack based to heap based.
Return a pointer to the writable string.
*/
static inline char *
stringi_fit2size (string_t v, size_t size_alloc)
{
M_ASSERT_INDEX (0, size_alloc);
// Note: this function may be called in context where the contract
// is not fullfilled.
const size_t old_alloc = string_capacity(v);
if (M_UNLIKELY (size_alloc > old_alloc)) {
size_t alloc = size_alloc + size_alloc / 2;
if (M_UNLIKELY (alloc <= old_alloc)) {
/* Overflow in alloc computation */
M_MEMORY_FULL(sizeof (char) * alloc);
// NOTE: Return is broken...
abort();
return NULL;
}
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
if (M_UNLIKELY (ptr == NULL)) {
M_MEMORY_FULL(sizeof (char) * alloc);
// NOTE: Return is currently broken.
abort();
return NULL;
}
M_ASSERT(ptr != &v->u.stack.buffer[0]);
if (stringi_stack_p(v)) {
/* Copy the stack allocation into the heap allocation */
memcpy(ptr, &v->u.stack.buffer[0],
(size_t) v->u.stack.buffer[sizeof (string_heap_ct) - 1] + 1U);
}
v->ptr = ptr;
v->u.heap.alloc = alloc;
return ptr;
}
return stringi_get_str(v);
}
/* Modify the string capacity to be able to handle at least 'alloc'
characters (including final nul char) */
static inline void
string_reserve(string_t v, size_t alloc)
{
STRINGI_CONTRACT (v);
const size_t size = string_size(v);
/* NOTE: Reserve below needed size, perform a shrink to fit */
if (size + 1 > alloc) {
alloc = size+1;
}
M_ASSERT (alloc > 0);
if (alloc < sizeof (string_heap_ct)) {
// Allocation can fit in the stack space
if (!stringi_stack_p(v)) {
/* Transform Heap Allocate to Stack Allocate */
char *ptr = &v->u.stack.buffer[0];
memcpy(ptr, v->ptr, size+1);
M_MEMORY_FREE(v->ptr);
v->ptr = NULL;
stringi_set_size(v, size);
} else {
/* Already a stack based alloc: nothing to do */
}
} else {
// Allocation cannot fit in the stack space
// Need to allocate in heap space
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
if (M_UNLIKELY (ptr == NULL) ) {
M_MEMORY_FULL(sizeof (char) * alloc);
return;
}
if (stringi_stack_p(v)) {
// Copy from stack space to heap space the string
char *ptr_stack = &v->u.stack.buffer[0];
memcpy(ptr, ptr_stack, size+1);
v->u.heap.size = size;
}
v->ptr = ptr;
v->u.heap.alloc = alloc;
}
STRINGI_CONTRACT (v);
}
/* Set the string to the C string str */
static inline void
string_set_str(string_t v, const char str[])
{
STRINGI_CONTRACT (v);
M_ASSERT(str != NULL);
size_t size = strlen(str);
char *ptr = stringi_fit2size(v, size+1);
memcpy(ptr, str, size+1);
stringi_set_size(v, size);
STRINGI_CONTRACT (v);
}
/* Set the string to the n first characters of the C string str */
static inline void
string_set_strn(string_t v, const char str[], size_t n)
{
STRINGI_CONTRACT (v);
M_ASSERT(str != NULL);
size_t len = strlen(str);
size_t size = M_MIN (len, n);
char *ptr = stringi_fit2size(v, size+1);
memcpy(ptr, str, size);
ptr[size] = 0;
stringi_set_size(v, size);
STRINGI_CONTRACT (v);
}
/* Set the string to the other one */
static inline void
string_set (string_t v1, const string_t v2)
{
STRINGI_CONTRACT (v1);
STRINGI_CONTRACT (v2);
if (M_LIKELY (v1 != v2)) {
const size_t size = string_size(v2);
char *ptr = stringi_fit2size(v1, size+1);
memcpy(ptr, string_get_cstr(v2), size+1);
stringi_set_size(v1, size);
}
STRINGI_CONTRACT (v1);
}
/* Set the string to the n first characters of other one */
static inline void
string_set_n(string_t v, const string_t ref, size_t offset, size_t length)
{
STRINGI_CONTRACT (v);
STRINGI_CONTRACT (ref);
M_ASSERT_INDEX (offset, string_size(ref) + 1);
size_t size = M_MIN (string_size(ref) - offset, length);
char *ptr = stringi_fit2size(v, size+1);
memmove(ptr, string_get_cstr(ref) + offset, size);
ptr[size] = 0;
stringi_set_size(v, size);
STRINGI_CONTRACT (v);
}
/* Initialize the string and set it to the other one
(constructor) */
static inline void
string_init_set(string_t v1, const string_t v2)
{
string_init(v1);
string_set(v1,v2);
}
/* Initialize the string and set it to the C string
(constructor) */
static inline void
string_init_set_str(string_t v1, const char str[])
{
string_init(v1);
string_set_str(v1, str);
}
/* Initialize the string, set it to the other one,
and destroy the other one.
(constructor & destructor) */
static inline void
string_init_move(string_t v1, string_t v2)
{
STRINGI_CONTRACT (v2);
memcpy(v1, v2, sizeof (string_t));
// Note: nullify v2 to be safer
v2->ptr = NULL;
STRINGI_CONTRACT (v1);
}
/* Swap the string */
static inline void
string_swap(string_t v1, string_t v2)
{
STRINGI_CONTRACT (v1);
STRINGI_CONTRACT (v2);
M_SWAP (size_t, v1->u.heap.size, v2->u.heap.size);
M_SWAP (size_t, v1->u.heap.alloc, v2->u.heap.alloc);
M_SWAP (char *, v1->ptr, v2->ptr);
STRINGI_CONTRACT (v1);
STRINGI_CONTRACT (v2);
}
/* Set the string to the other one,
and destroy the other one.
(destructor) */
static inline void
string_move(string_t v1, string_t v2)
{
string_clear(v1);
string_init_move(v1,v2);
}
/* Push a character in a string */
static inline void
string_push_back (string_t v, char c)
{
STRINGI_CONTRACT (v);
const size_t size = string_size(v);
char *ptr = stringi_fit2size(v, size+2);
ptr[size+0] = c;
ptr[size+1] = 0;
stringi_set_size(v, size+1);
STRINGI_CONTRACT (v);
}
/* Concatene the string with the C string */
static inline void
string_cat_str(string_t v, const char str[])
{
STRINGI_CONTRACT (v);
M_ASSERT (str != NULL);
const size_t old_size = string_size(v);
const size_t size = strlen(str);
char *ptr = stringi_fit2size(v, old_size + size + 1);
memcpy(&ptr[old_size], str, size + 1);
stringi_set_size(v, old_size + size);
STRINGI_CONTRACT (v);
}
/* Concatene the string with the other string */
static inline void
string_cat(string_t v, const string_t v2)
{
STRINGI_CONTRACT (v2);
STRINGI_CONTRACT (v);
const size_t size = string_size(v2);
if (M_LIKELY (size > 0)) {
const size_t old_size = string_size(v);
char *ptr = stringi_fit2size(v, old_size + size + 1);
memcpy(&ptr[old_size], string_get_cstr(v2), size);
ptr[old_size + size] = 0;
stringi_set_size(v, old_size + size);
}
STRINGI_CONTRACT (v);
}
/* Compare the string to the C string and
return the sort order (-1 if less, 0 if equal, 1 if greater) */
static inline int
string_cmp_str(const string_t v1, const char str[])
{
STRINGI_CONTRACT (v1);
M_ASSERT (str != NULL);
return strcmp(string_get_cstr(v1), str);
}
/* Compare the string to the other string and
return the sort order (-1 if less, 0 if equal, 1 if greater) */
static inline int
string_cmp(const string_t v1, const string_t v2)
{
STRINGI_CONTRACT (v1);
STRINGI_CONTRACT (v2);
return strcmp(string_get_cstr(v1), string_get_cstr(v2));
}
/* Test if the string is equal to the given C string */
static inline bool
string_equal_str_p(const string_t v1, const char str[])
{
STRINGI_CONTRACT(v1);
M_ASSERT (str != NULL);
return string_cmp_str(v1, str) == 0;
}
/* Test if the string is equal to the other string */
static inline bool
string_equal_p(const string_t v1, const string_t v2)
{
/* string_equal_p can be called with one string which is an OOR value.
In case of OOR value, .ptr is NULL and .size is maximum.
It will detect a heap based string, and read size from heap structure.
*/
M_ASSERT(v1 != NULL);
M_ASSERT(v2 != NULL);
/* Optimization: both strings shall have at least the same size */
return string_size(v1) == string_size(v2) && string_cmp(v1, v2) == 0;
}
/* Test if the string is equal to the C string
(case insentive according to the current locale)
Note: doesn't work with UTF-8 strings.
*/
static inline int
string_cmpi_str(const string_t v1, const char p2[])
{
STRINGI_CONTRACT (v1);
M_ASSERT (p2 != NULL);
// strcasecmp is POSIX only
const char *p1 = string_get_cstr(v1);
int c1, c2;
do {
// To avoid locale without 1 per 1 mapping.
c1 = toupper((unsigned char) *p1++);
c2 = toupper((unsigned char) *p2++);
c1 = tolower((unsigned char) c1);
c2 = tolower((unsigned char) c2);
} while (c1 == c2 && c1 != 0);
return c1 - c2;
}
/* Test if the string is equal to the other string
(case insentive according to the current locale)
Note: doesn't work with UTF-8 strings.
*/
static inline int
string_cmpi(const string_t v1, const string_t v2)
{
return string_cmpi_str(v1, string_get_cstr(v2));
}
/* Search for the position of the character c
from the position 'start' (include) in the string
Return STRING_FAILURE if not found.
By default, start is zero.
*/
static inline size_t
string_search_char (const string_t v, char c, size_t start)
{
STRINGI_CONTRACT (v);
M_ASSERT_INDEX (start, string_size(v) + 1);
const char *p = M_ASSIGN_CAST(const char*,
strchr(string_get_cstr(v)+start, c));
return p == NULL ? STRING_FAILURE : (size_t) (p-string_get_cstr(v));
}
/* Reverse Search for the position of the character c
from the position 'start' (include) in the string
Return STRING_FAILURE if not found.
By default, start is zero.
*/
static inline size_t
string_search_rchar (const string_t v, char c, size_t start)
{
STRINGI_CONTRACT (v);
M_ASSERT_INDEX (start, string_size(v)+1);
// NOTE: Can implement it in a faster way than the libc function
// by scanning backward from the bottom of the string (which is
// possible since we know the size)
const char *p = M_ASSIGN_CAST(const char*,
strrchr(string_get_cstr(v)+start, c));
return p == NULL ? STRING_FAILURE : (size_t) (p-string_get_cstr(v));
}
/* Search for the sub C string in the string from the position start
Return STRING_FAILURE if not found.
By default, start is zero. */
static inline size_t
string_search_str (const string_t v, const char str[], size_t start)
{
STRINGI_CONTRACT (v);
M_ASSERT_INDEX (start, string_size(v)+1);
M_ASSERT (str != NULL);
const char *p = M_ASSIGN_CAST(const char*,
strstr(string_get_cstr(v)+start, str));
return p == NULL ? STRING_FAILURE : (size_t) (p-string_get_cstr(v));
}
/* Search for the sub other string v2 in the string v1 from the position start
Return STRING_FAILURE if not found.
By default, start is zero. */
static inline size_t
string_search (const string_t v1, const string_t v2, size_t start)
{
STRINGI_CONTRACT (v2);
M_ASSERT_INDEX (start, string_size(v1)+1);
return string_search_str(v1, string_get_cstr(v2), start);
}
/* Search for the first matching character in the given C string
in the string v1 from the position start
Return STRING_FAILURE if not found.
By default, start is zero. */
static inline size_t
string_search_pbrk(const string_t v1, const char first_of[], size_t start)
{
STRINGI_CONTRACT (v1);
M_ASSERT_INDEX (start, string_size(v1)+1);
M_ASSERT (first_of != NULL);
const char *p = M_ASSIGN_CAST(const char*,
strpbrk(string_get_cstr(v1)+start, first_of));
return p == NULL ? STRING_FAILURE : (size_t) (p-string_get_cstr(v1));
}
/* Compare the string to the C string using strcoll */
static inline int
string_strcoll_str(const string_t v, const char str[])
{
STRINGI_CONTRACT (v);
return strcoll(string_get_cstr(v), str);
}
/* Compare the string to the other string using strcoll */
static inline int
string_strcoll (const string_t v1, const string_t v2)
{
STRINGI_CONTRACT (v2);
return string_strcoll_str(v1, string_get_cstr(v2));
}
/* Return the number of bytes of the segment of s
that consists entirely of bytes in accept */
static inline size_t
string_spn(const string_t v1, const char accept[])
{
STRINGI_CONTRACT (v1);
M_ASSERT (accept != NULL);
return strspn(string_get_cstr(v1), accept);
}
/* Return the number of bytes of the segment of s
that consists entirely of bytes not in reject */
static inline size_t
string_cspn(const string_t v1, const char reject[])
{
STRINGI_CONTRACT (v1);
M_ASSERT (reject != NULL);
return strcspn(string_get_cstr(v1), reject);
}
/* Return the string left truncated to the first 'index' bytes */
static inline void
string_left(string_t v, size_t index)
{
STRINGI_CONTRACT (v);
const size_t size = string_size(v);
if (index >= size)
return;
stringi_get_str(v)[index] = 0;
stringi_set_size(v,index);
STRINGI_CONTRACT (v);
}
/* Return the string right truncated from the 'index' position to the last position */
static inline void
string_right(string_t v, size_t index)
{
STRINGI_CONTRACT (v);
char *ptr = stringi_get_str(v);
const size_t size = string_size(v);
if (index >= size) {
ptr[0] = 0;
stringi_set_size(v, 0);
STRINGI_CONTRACT (v);
return;
}
size_t s2 = size - index;
memmove (&ptr[0], &ptr[index], s2+1);
stringi_set_size(v, s2);
STRINGI_CONTRACT (v);
}
/* Return the string from position index to size bytes */
static inline void
string_mid (string_t v, size_t index, size_t size)
{
string_right(v, index);
string_left(v, size);
}
/* Return in the string the C string str1 into the C string str2 from start
By default, start is zero.
*/
static inline size_t
string_replace_str (string_t v, const char str1[], const char str2[], size_t start)
{
STRINGI_CONTRACT (v);
M_ASSERT (str1 != NULL && str2 != NULL);
size_t i = string_search_str(v, str1, start);
if (i != STRING_FAILURE) {
const size_t str1_l = strlen(str1);
const size_t str2_l = strlen(str2);
const size_t size = string_size(v);
M_ASSERT(size + 1 + str2_l > str1_l);
char *ptr = stringi_fit2size (v, size + str2_l - str1_l + 1);
if (str1_l != str2_l) {
memmove(&ptr[i+str2_l], &ptr[i+str1_l], size - i - str1_l + 1);
stringi_set_size(v, size + str2_l - str1_l);
}
memcpy (&ptr[i], str2, str2_l);
STRINGI_CONTRACT (v);
}
return i;
}
/* Return in the string the string str1 into the string str2 from start
By default, start is zero.
*/
static inline size_t
string_replace (string_t v, const string_t v1, const string_t v2, size_t start)
{
STRINGI_CONTRACT (v);
STRINGI_CONTRACT (v1);
STRINGI_CONTRACT (v2);
return string_replace_str(v, string_get_cstr(v1), string_get_cstr(v2), start);
}
/* Replace in the string the sub-string at position 'pos' for 'len' bytes
into the C string str2. */
static inline void
string_replace_at (string_t v, size_t pos, size_t len, const char str2[])
{
STRINGI_CONTRACT (v);
M_ASSERT(str2 != NULL);
const size_t str1_l = len;
const size_t str2_l = strlen(str2);
const size_t size = string_size(v);
char *ptr;
if (str1_l != str2_l) {
M_ASSERT_INDEX (str1_l, size + str2_l + 1);
ptr = stringi_fit2size (v, size + str2_l - str1_l + 1);
M_ASSERT_INDEX (pos + str1_l, size + 1);
memmove(&ptr[pos+str2_l], &ptr[pos+str1_l], size - pos - str1_l + 1);
stringi_set_size(v, size + str2_l - str1_l);
} else {
ptr = stringi_get_str(v);
}
memcpy (&ptr[pos], str2, str2_l);
STRINGI_CONTRACT (v);
}
/* Format in the string the given printf format */
static inline int
string_printf (string_t v, const char format[], ...)
{
STRINGI_CONTRACT (v);
M_ASSERT (format != NULL);
va_list args;
int size;
char *ptr = stringi_get_str(v);
size_t alloc = string_capacity(v);
va_start (args, format);
size = vsnprintf (ptr, alloc, format, args);
if (size > 0 && ((size_t) size+1 >= alloc) ) {
// We have to realloc our string to fit the needed size
ptr = stringi_fit2size (v, (size_t) size + 1);
alloc = string_capacity(v);
// and redo the parsing.
va_end (args);
va_start (args, format);
size = vsnprintf (ptr, alloc, format, args);
M_ASSERT (size > 0 && (size_t)size < alloc);
}
if (size >= 0) {
stringi_set_size(v, (size_t) size);
}
va_end (args);
STRINGI_CONTRACT (v);
return size;
}
/* Append to the string the formatted string of the given printf format */
static inline int
string_cat_printf (string_t v, const char format[], ...)
{
STRINGI_CONTRACT (v);
M_ASSERT (format != NULL);
va_list args;
int size;
size_t old_size = string_size(v);
char *ptr = stringi_get_str(v);
size_t alloc = string_capacity(v);
va_start (args, format);
size = vsnprintf (&ptr[old_size], alloc - old_size, format, args);
if (size > 0 && (old_size+(size_t)size+1 >= alloc) ) {
// We have to realloc our string to fit the needed size
ptr = stringi_fit2size (v, old_size + (size_t) size + 1);
alloc = string_capacity(v);
// and redo the parsing.
va_end (args);
va_start (args, format);
size = vsnprintf (&ptr[old_size], alloc - old_size, format, args);
M_ASSERT (size >= 0);
}
if (size >= 0) {
stringi_set_size(v, old_size + (size_t) size);
} else {
// vsnprintf may have output some characters before returning an error.
// Undo this to have a clean state
ptr[old_size] = 0;
}
va_end (args);
STRINGI_CONTRACT (v);
return size;
}
/* Get a line/pureline/file from the FILE and store it in the string */
static inline bool
string_fgets(string_t v, FILE *f, string_fgets_t arg)
{
STRINGI_CONTRACT(v);
M_ASSERT (f != NULL);
char *ptr = stringi_fit2size (v, 100);
size_t size = 0;
size_t alloc = string_capacity(v);
ptr[0] = 0;
bool retcode = false; /* Nothing has been read yet */
/* alloc - size is very unlikely to be bigger than INT_MAX
but fgets accepts an int as the size argument */
while (fgets(&ptr[size], (int) M_MIN( (alloc - size), (size_t) INT_MAX ), f) != NULL) {
retcode = true; /* Something has been read */
// fgets doesn't return the number of characters read, so we need to count.
size += strlen(&ptr[size]);
if (arg != STRING_READ_FILE && ptr[size-1] == '\n') {
if (arg == STRING_READ_PURE_LINE) {
size --;
ptr[size] = 0; /* Remove EOL */
}
stringi_set_size(v, size);
STRINGI_CONTRACT(v);
return retcode; /* Normal terminaison */
} else if (ptr[size-1] != '\n' && !feof(f)) {
/* The string buffer is not big enough:
increase it and continue reading */
/* v cannot be stack alloc */
ptr = stringi_fit2size (v, alloc + alloc/2);
alloc = string_capacity(v);
}
}
stringi_set_size(v, size);
STRINGI_CONTRACT (v);
return retcode; /* Abnormal terminaison */
}
/* Get a word from the FILE and store it in the string.
Words are supposed to be separated each other by the given list of separator
separator shall be a CONSTANT C array.
*/
static inline bool
string_fget_word (string_t v, const char separator[], FILE *f)
{
char buffer[128];
char c = 0;
int d;
STRINGI_CONTRACT(v);
M_ASSERT (f != NULL);
M_ASSERT_INDEX (1+20+2+strlen(separator)+3, sizeof buffer);
size_t size = 0;
bool retcode = false;
/* Skip separator first */
do {
d = fgetc(f);
if (d == EOF) {
return false;
}
} while (strchr(separator, d) != NULL);
ungetc(d, f);
size_t alloc = string_capacity(v);
char *ptr = stringi_get_str(v);
ptr[0] = 0;
/* NOTE: We generate a buffer which we give to scanf to parse the string,
that it is to say, we generate the format dynamically!
The format is like " %49[^ \t.\n]%c"
So in this case, we parse up to 49 chars, up to the separators char,
and we read the next char. If the next char is a separator, we successful
read a word, otherwise we have to continue parsing.
The user shall give a constant string as the separator argument,
as a control over this argument may give an attacker
an opportunity for stack overflow */
while (snprintf(buffer, sizeof buffer -1, " %%%zu[^%s]%%c", (size_t) alloc-1-size, separator) > 0
/* We may read one or two argument(s) */
&& m_core_fscanf(f, buffer, m_core_arg_size(&ptr[size], alloc-size), &c) >= 1) {
retcode = true;
size += strlen(&ptr[size]);
/* If we read only one argument
or if the final read character is a separator */
if (c == 0 || strchr(separator, c) != NULL)
break;
/* Next char is not a separator: continue parsing */
stringi_set_size(v, size);
ptr = stringi_fit2size (v, alloc + alloc/2);
alloc = string_capacity(v);
M_ASSERT (alloc > size + 1);
ptr[size++] = c;
ptr[size] = 0;
// Invalid c character for next iteration
c= 0;
}
stringi_set_size(v, size);
STRINGI_CONTRACT(v);
return retcode;
}
/* Put the string in the given FILE without formatting */
static inline bool
string_fputs(FILE *f, const string_t v)
{
STRINGI_CONTRACT (v);
M_ASSERT (f != NULL);
return fputs(string_get_cstr(v), f) >= 0;
}
/* Test if the string starts with the given C string */
static inline bool
string_start_with_str_p(const string_t v, const char str[])
{
STRINGI_CONTRACT (v);
M_ASSERT (str != NULL);
const char *v_str = string_get_cstr(v);
while (*str){
if (*str != *v_str)
return false;
str++;
v_str++;
}
return true;
}
/* Test if the string starts with the other string */
static inline bool
string_start_with_string_p(const string_t v, const string_t v2)
{
STRINGI_CONTRACT (v2);
return string_start_with_str_p (v, string_get_cstr(v2));
}
/* Test if the string ends with the C string */
static inline bool
string_end_with_str_p(const string_t v, const char str[])
{
STRINGI_CONTRACT (v);
M_ASSERT (str != NULL);
const char *v_str = string_get_cstr(v);
const size_t v_l = string_size(v);
const size_t str_l = strlen(str);
if (v_l < str_l)
return false;
for(size_t i = 0; i < str_l; i++) {
if (str[i] != v_str[v_l - str_l + i])
return false;
}
return true;
}
/* Test if the string ends with the other string */
static inline bool
string_end_with_string_p(const string_t v, const string_t v2)
{
STRINGI_CONTRACT (v2);
return string_end_with_str_p (v, string_get_cstr(v2));
}
/* Compute a hash for the string */
static inline size_t
string_hash(const string_t v)
{
STRINGI_CONTRACT (v);
return m_core_hash(string_get_cstr(v), string_size(v));
}
// Return true if c is a character from charac
static bool
stringi_strim_char(char c, const char charac[])
{
for(const char *s = charac; *s; s++) {
if (c == *s)
return true;
}
return false;
}
/* Remove any characters from charac that are present
in the begining of the string and the end of the string. */
static inline void
string_strim(string_t v, const char charac[])
{
STRINGI_CONTRACT (v);
char *ptr = stringi_get_str(v);
char *b = ptr;
size_t size = string_size(v);
while (size > 0 && stringi_strim_char(b[size-1], charac))
size --;
if (size > 0) {
while (stringi_strim_char(*b, charac))
b++;
M_ASSERT (b >= ptr && size >= (size_t) (b - ptr) );
size -= (size_t) (b - ptr);
memmove (ptr, b, size);
}
ptr[size] = 0;
stringi_set_size(v, size);
STRINGI_CONTRACT (v);
}
/* Test if the string is equal to the OOR value */
static inline bool
string_oor_equal_p(const string_t s, unsigned char n)
{
return (s->ptr == NULL) & (s->u.heap.alloc == ~(size_t)n);
}
/* Set the unitialized string to the OOR value */
static inline void
string_oor_set(string_t s, unsigned char n)
{
s->ptr = NULL;
s->u.heap.alloc = ~(size_t)n;
}
/* I/O */
/* Output: "string" with quote around
Replace " by \" within the string (and \ to \\)
\n, \t & \r by their standard representation
and other not printable character with \0xx */
/* Transform the string 'v2' into a formatted string
and set it to (or append in) the string 'v'. */
static inline void
string_get_str(string_t v, const string_t v2, bool append)
{
STRINGI_CONTRACT(v2);
STRINGI_CONTRACT(v);
M_ASSERT (v != v2); // Limitation
size_t size = append ? string_size(v) : 0;
size_t v2_size = string_size(v2);
size_t targetSize = size + v2_size + 3;
char *ptr = stringi_fit2size(v, targetSize);
ptr[size ++] = '"';
for(size_t i = 0 ; i < v2_size; i++) {
const char c = string_get_char(v2,i);
switch (c) {
case '\\':
case '"':
case '\n':
case '\t':
case '\r':
// Special characters which can be displayed in a short form.
stringi_set_size(v, size);
ptr = stringi_fit2size(v, ++targetSize);
ptr[size ++] = '\\';
// This string acts as a perfect hashmap which supposes an ASCII mapping
// and (c^(c>>5)) is the hash function
ptr[size ++] = " tn\" r\\"[(c ^ (c >>5)) & 0x07];
break;
default:
if (M_UNLIKELY (!isprint(c))) {
targetSize += 3;
stringi_set_size(v, size);
ptr = stringi_fit2size(v, targetSize);
int d1 = c & 0x07, d2 = (c>>3) & 0x07, d3 = (c>>6) & 0x07;
ptr[size ++] = '\\';
ptr[size ++] = (char) ('0' + d3);
ptr[size ++] = (char) ('0' + d2);
ptr[size ++] = (char) ('0' + d1);
} else {
ptr[size ++] = c;
}
break;
}
}
ptr[size ++] = '"';
ptr[size] = 0;
stringi_set_size(v, size);
M_ASSERT (size <= targetSize);
STRINGI_CONTRACT (v);
}
/* Transform the string 'v2' into a formatted string
and output it in the given FILE */
static inline void
string_out_str(FILE *f, const string_t v)
{
STRINGI_CONTRACT(v);
M_ASSERT (f != NULL);
fputc('"', f);
size_t size = string_size(v);
for(size_t i = 0 ; i < size; i++) {
const char c = string_get_char(v, i);
switch (c) {
case '\\':
case '"':
case '\n':
case '\t':
case '\r':
fputc('\\', f);
fputc(" tn\" r\\"[(c ^ c >>5) & 0x07], f);
break;
default:
if (M_UNLIKELY (!isprint(c))) {
int d1 = c & 0x07, d2 = (c>>3) & 0x07, d3 = (c>>6) & 0x07;
fputc('\\', f);
fputc('0' + d3, f);
fputc('0' + d2, f);
fputc('0' + d1, f);
} else {
fputc(c, f);
}
break;
}
}
fputc('"', f);
}
/* Read the formatted string from the FILE
and set the converted value in the string 'v'.
Return true in case of success */
static inline bool
string_in_str(string_t v, FILE *f)
{
STRINGI_CONTRACT(v);
M_ASSERT (f != NULL);
int c = fgetc(f);
if (c != '"') return false;
string_clean(v);
c = fgetc(f);
while (c != '"' && c != EOF) {
if (M_UNLIKELY (c == '\\')) {
c = fgetc(f);
switch (c) {
case 'n':
case 't':
case 'r':
case '\\':
case '\"':
// This string acts as a perfect hashmap which supposes an ASCII mapping
// and (c^(c>>5)) is the hash function
c = " \r \" \n\\\t"[(c^(c>>5))& 0x07];
break;
default:
if (!(c >= '0' && c <= '7'))
return false;
int d1 = c - '0';
c = fgetc(f);
if (!(c >= '0' && c <= '7'))
return false;
int d2 = c - '0';
c = fgetc(f);
if (!(c >= '0' && c <= '7'))
return false;
int d3 = c - '0';
c = (d1 << 6) + (d2 << 3) + d3;
break;
}
}
string_push_back (v, (char) c);
c = fgetc(f);
}
return c == '"';
}
/* Read the formatted string from the C string
and set the converted value in the string 'v'.
Return true in case of success
If endptr is not null, update the position of the parsing.
*/
static inline bool
string_parse_str(string_t v, const char str[], const char **endptr)
{
STRINGI_CONTRACT(v);
M_ASSERT (str != NULL);
bool success = false;
int c = *str++;
if (c != '"') goto exit;
string_clean(v);
c = *str++;
while (c != '"' && c != 0) {
if (M_UNLIKELY (c == '\\')) {
c = *str++;
switch (c) {
case 'n':
case 't':
case 'r':
case '\\':
case '\"':
// This string acts as a perfect hashmap which supposes an ASCII mapping
// and (c^(c>>5)) is the hash function
c = " \r \" \n\\\t"[(c^(c>>5))& 0x07];
break;
default:
if (!(c >= '0' && c <= '7'))
goto exit;
int d1 = c - '0';
c = *str++;
if (!(c >= '0' && c <= '7'))
goto exit;
int d2 = c - '0';
c = *str++;
if (!(c >= '0' && c <= '7'))
goto exit;
int d3 = c - '0';
c = (d1 << 6) + (d2 << 3) + d3;
break;
}
}
string_push_back (v, (char) c);
c = *str++;
}
success = (c == '"');
exit:
if (endptr != NULL) *endptr = str;
return success;
}
/* Transform the string 'v2' into a formatted string
and output it in the given serializer
See serialization for return code.
*/
static inline m_serial_return_code_t
string_out_serial(m_serial_write_t serial, const string_t v)
{
M_ASSERT (serial != NULL && serial->m_interface != NULL);
return serial->m_interface->write_string(serial, string_get_cstr(v), string_size(v) );
}
/* Read the formatted string from the serializer
and set the converted value in the string 'v'.
See serialization for return code.
*/
static inline m_serial_return_code_t
string_in_serial(string_t v, m_serial_read_t serial)
{
M_ASSERT (serial != NULL && serial->m_interface != NULL);
return serial->m_interface->read_string(serial, v);
}
/* State of the UTF8 decoding machine state */
typedef enum {
STRINGI_UTF8_STARTING = 0,
STRINGI_UTF8_DECODING_1 = 8,
STRINGI_UTF8_DECODING_2 = 16,
STRINGI_UTF8_DOCODING_3 = 24,
STRINGI_UTF8_ERROR = 32
} stringi_utf8_state_e;
/* An unicode value */
typedef unsigned int string_unicode_t;
/* Error in case of decoding */
#define STRING_UNICODE_ERROR (UINT_MAX)
/* UTF8 character classification:
*
* 0* --> type 1 byte A
* 10* --> chained byte B
* 110* --> type 2 byte C
* 1110* --> type 3 byte D
* 11110* --> type 4 byte E
* 111110* --> invalid I
*/
/* UTF8 State Transition table:
* ABCDEI
* +------
* S|SI123III
* 1|ISIIIIII
* 2|I1IIIIII
* 3|I2IIIIII
* I|IIIIIIII
*/
/* The use of a string enables the compiler/linker to factorize it. */
#define STRINGI_UTF8_STATE_TAB \
"\000\040\010\020\030\040\040\040" \
"\040\000\040\040\040\040\040\040" \
"\040\010\040\040\040\040\040\040" \
"\040\020\040\040\040\040\040\040" \
"\040\040\040\040\040\040\040\040"
/* Main generic UTF8 decoder
It shall be (nearly) branchless on any CPU.
It takes a character, and the previous state and the previous value of the unicode value.
It updates the state and the decoded unicode value.
A decoded unicoded value is valid only when the state is STARTING.
*/
static inline void
stringi_utf8_decode(char c, stringi_utf8_state_e *state,
string_unicode_t *unicode)
{
const unsigned int type = m_core_clz32((unsigned char)~c) - (unsigned) (sizeof(uint32_t) - 1) * CHAR_BIT;
const string_unicode_t mask1 = (UINT_MAX - (string_unicode_t)(*state != STRINGI_UTF8_STARTING) + 1);
const string_unicode_t mask2 = (0xFFU >> type);
*unicode = ((*unicode << 6) & mask1) | ((unsigned int) c & mask2);
*state = (stringi_utf8_state_e) STRINGI_UTF8_STATE_TAB[(unsigned int) *state + type];
}
/* Check if the given array of characters is a valid UTF8 stream */
/* NOTE: Non-canonical representation are not rejected */
static inline bool
stringi_utf8_valid_str_p(const char str[])
{
stringi_utf8_state_e s = STRINGI_UTF8_STARTING;
string_unicode_t u = 0;
while (*str) {
stringi_utf8_decode(*str, &s, &u);
if ((s == STRINGI_UTF8_ERROR)
||(s == STRINGI_UTF8_STARTING
&&(u > 0x10FFFF /* out of range */
||(u >= 0xD800 && u <= 0xDFFF) /* surrogate halves */)))
return false;
str++;
}
return true;
}
/* Computer the number of unicode characters are represented in the UTF8 stream
Return SIZE_MAX (aka -1) in case of error
*/
static inline size_t
stringi_utf8_length(const char str[])
{
size_t size = 0;
stringi_utf8_state_e s = STRINGI_UTF8_STARTING;
string_unicode_t u = 0;
while (*str) {
stringi_utf8_decode(*str, &s, &u);
if (M_UNLIKELY (s == STRINGI_UTF8_ERROR)) return SIZE_MAX;
size += (s == STRINGI_UTF8_STARTING);
str++;
}
return size;
}
/* Encode an unicode into an UTF8 stream of characters */
static inline int
stringi_utf8_encode(char buffer[5], string_unicode_t u)
{
if (M_LIKELY (u <= 0x7Fu)) {
buffer[0] = (char) u;
buffer[1] = 0;
return 1;
} else if (u <= 0x7FFu) {
buffer[0] = (char) (0xC0u | (u >> 6));
buffer[1] = (char) (0x80 | (u & 0x3Fu));
buffer[2] = 0;
return 2;
} else if (u <= 0xFFFFu) {
buffer[0] = (char) (0xE0u | (u >> 12));
buffer[1] = (char) (0x80u | ((u >> 6) & 0x3Fu));
buffer[2] = (char) (0x80u | (u & 0x3Fu));
buffer[3] = 0;
return 3;
} else {
buffer[0] = (char) (0xF0u | (u >> 18));
buffer[1] = (char) (0x80u | ((u >> 12) & 0x3Fu));
buffer[2] = (char) (0x80u | ((u >> 6) & 0x3Fu));
buffer[3] = (char) (0x80u | (u & 0x3F));
buffer[4] = 0;
return 4;
}
}
/* Iterator on a string over UTF8 encoded characters */
typedef struct string_it_s {
string_unicode_t u;
const char *ptr;
const char *next_ptr;
} string_it_t[1];
/* Start iteration over the UTF8 encoded unicode value */
static inline void
string_it(string_it_t it, const string_t str)
{
STRINGI_CONTRACT(str);
M_ASSERT(it != NULL);
it->ptr = string_get_cstr(str);
it->next_ptr = it->ptr;
it->u = 0;
}
/* Set the iterator to the end of string
The iterator references therefore nothong.
*/
static inline void
string_it_end(string_it_t it, const string_t str)
{
STRINGI_CONTRACT(str);
M_ASSERT(it != NULL);
it->ptr = &string_get_cstr(str)[string_size(str)];
it->next_ptr = 0;
it->u = 0;
}
/* Set the iterator to the same position than the other one */
static inline void
string_it_set(string_it_t it, const string_it_t itsrc)
{
M_ASSERT(it != NULL && itsrc != NULL);
it->ptr = itsrc->ptr;
it->next_ptr = itsrc->next_ptr;
it->u = itsrc->u;
}
/* Test if the iterator has reached the end of the string */
static inline bool
string_end_p (string_it_t it)
{
M_ASSERT (it != NULL);
if (M_UNLIKELY (*it->ptr == 0))
return true;
stringi_utf8_state_e state = STRINGI_UTF8_STARTING;
string_unicode_t u = 0;
const char *str = it->ptr;
do {
stringi_utf8_decode(*str, &state, &u);
str++;
} while (state != STRINGI_UTF8_STARTING && state != STRINGI_UTF8_ERROR && *str != 0);
it->next_ptr = str;
it->u = M_UNLIKELY (state == STRINGI_UTF8_ERROR) ? STRING_UNICODE_ERROR : u;
return false;
}
/* Test if the iterator is equal to the other one */
static inline bool
string_it_equal_p(const string_it_t it1, const string_it_t it2)
{
M_ASSERT(it1 != NULL && it2 != NULL);
// IT1.ptr == IT2.ptr ==> IT1 == IT2 ==> All fields are equal
M_ASSERT(it1->ptr != it2->ptr || (it1->next_ptr == it2->next_ptr && it1->u == it2->u));
return it1->ptr == it2->ptr;
}
/* Advance the iterator to the next UTF8 unicode character */
static inline void
string_next (string_it_t it)
{
M_ASSERT (it != NULL);
it->ptr = it->next_ptr;
}
/* Return the unicode value associated to the iterator */
static inline string_unicode_t
string_get_cref (const string_it_t it)
{
M_ASSERT (it != NULL);
return it->u;
}
/* Return the unicode value associated to the iterator */
static inline const string_unicode_t *
string_cref (const string_it_t it)
{
M_ASSERT (it != NULL);
return &it->u;
}
/* Push unicode into string, encoding it in UTF8 */
static inline void
string_push_u (string_t str, string_unicode_t u)
{
STRINGI_CONTRACT(str);
char buffer[4+1];
stringi_utf8_encode(buffer, u);
string_cat_str(str, buffer);
}
/* Compute the length in UTF8 characters in the string */
static inline size_t
string_length_u(string_t str)
{
STRINGI_CONTRACT(str);
return stringi_utf8_length(string_get_cstr(str));
}
/* Check if a string is a valid UTF8 encoded stream */
static inline bool
string_utf8_p(string_t str)
{
STRINGI_CONTRACT(str);
return stringi_utf8_valid_str_p(string_get_cstr(str));
}
/* Define the split & the join functions
in case of usage with the algorithm module */
#define STRING_SPLIT(name, oplist, type_oplist) \
static inline void M_C(name, _split)(M_GET_TYPE oplist cont, \
const string_t str, const char sep) \
{ \
size_t begin = 0; \
string_t tmp; \
size_t size = string_size(str); \
string_init(tmp); \
M_CALL_CLEAN(oplist, cont); \
for(size_t i = 0 ; i < size; i++) { \
char c = string_get_char(str, i); \
if (c == sep) { \
string_set_strn(tmp, &string_get_cstr(str)[begin], i - begin); \
/* If push move method is available, use it */ \
M_IF_METHOD(PUSH_MOVE,oplist)( \
M_CALL_PUSH_MOVE(oplist, cont, &tmp); \
string_init(tmp); \
, \
M_CALL_PUSH(oplist, cont, tmp); \
) \
begin = i + 1; \
} \
} \
string_set_strn(tmp, &string_get_cstr(str)[begin], size - begin); \
M_CALL_PUSH(oplist, cont, tmp); \
/* HACK: if method reverse is defined, it is likely that we have */ \
/* inserted the items in the wrong order (aka for a list) */ \
M_IF_METHOD(REVERSE, oplist) (M_CALL_REVERSE(oplist, cont);, ) \
string_clear(tmp); \
} \
\
static inline void M_C(name, _join)(string_t dst, M_GET_TYPE oplist cont, \
const string_t str) \
{ \
bool init_done = false; \
string_clean (dst); \
for M_EACH(item, cont, oplist) { \
if (init_done) { \
string_cat(dst, str); \
} \
string_cat (dst, *item); \
init_done = true; \
} \
} \
/* Use of Compound Literals to init a constant string.
NOTE: The use of the additional structure layer is to ensure
that the pointer to char is properly aligned to an int (this
is a needed asumption by string_hash).
Otherwise it could have been :
#define STRING_CTE(s) \
((const string_t){{.size = sizeof(s)-1, .alloc = sizeof(s), \
.ptr = s}})
which produces faster code.
Note: This code doesn't work with C++ (use of c99 feature
of recursive struct definition and compound literral).
As such, there is a separate C++ definition.
*/
#ifndef __cplusplus
/* Initialize a constant string with the given C string */
# define STRING_CTE(s) \
((const string_t){{.u.heap = { .size = sizeof(s)-1, .alloc = sizeof(s) } , \
.ptr = ((struct { long long _n; char _d[sizeof (s)]; }){ 0, s })._d }})
#else
namespace m_string {
template <unsigned int N>
struct m_aligned_string {
string_t string;
union {
char str[N];
long long i;
};
inline m_aligned_string(const char lstr[])
{
this->string->u.heap.size = N -1;
this->string->u.heap.alloc = N;
memcpy (this->str, lstr, N);
this->string->ptr = this->str;
}
};
}
/* Initialize a constant string with the given C string (C++ mode) */
#define STRING_CTE(s) \
m_string::m_aligned_string<sizeof (s)>(s).string
#endif
/* Initialize and set a string to the given formatted value. */
#define STRINGI_INIT_PRINTF(v, ...) \
(string_init (v), string_printf (v, __VA_ARGS__) )
/* Initialize a string with the given list of arguments.
Check if it is a formatted input or not by counting the number of arguments.
If there is only one argument, it can only be a set to C string.
It is much faster in this case to call string_init_set_str.
In C11 mode, it uses the fact that string_init_set is overloaded to handle both
C string and string. */
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L)
#define STRINGI_INIT_WITH(v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(string_init_set, STRINGI_INIT_PRINTF)(v, __VA_ARGS__)
#else
#define STRINGI_INIT_WITH(v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(string_init_set_str, STRINGI_INIT_PRINTF)(v, __VA_ARGS__)
#endif
/* NOTE: Use GCC extension (OBSOLETE) */
#define STRING_DECL_INIT(v) \
string_t v __attribute__((cleanup(stringi_clear2))) = {{ 0, 0, NULL}}
/* NOTE: Use GCC extension (OBSOLETE) */
#define STRING_DECL_INIT_PRINTF(v, format, ...) \
STRING_DECL_INIT(v); \
string_printf (v, format, __VA_ARGS__)
/* Define the OPLIST of a STRING */
#define STRING_OPLIST \
(INIT(string_init),INIT_SET(string_init_set), SET(string_set), \
INIT_WITH(STRINGI_INIT_WITH), \
INIT_MOVE(string_init_move), MOVE(string_move), \
SWAP(string_swap), CLEAN(string_clean), \
TEST_EMPTY(string_empty_p), \
CLEAR(string_clear), HASH(string_hash), EQUAL(string_equal_p), \
CMP(string_cmp), TYPE(string_t), \
PARSE_STR(string_parse_str), GET_STR(string_get_str), \
OUT_STR(string_out_str), IN_STR(string_in_str), \
OUT_SERIAL(string_out_serial), IN_SERIAL(string_in_serial), \
EXT_ALGO(STRING_SPLIT), \
OOR_EQUAL(string_oor_equal_p), OOR_SET(string_oor_set) \
,SUBTYPE(string_unicode_t) \
,IT_TYPE(string_it_t) \
,IT_FIRST(string_it) \
,IT_END(string_it_end) \
,IT_SET(string_it_set) \
,IT_END_P(string_end_p) \
,IT_EQUAL_P(string_it_equal_p) \
,IT_NEXT(string_next) \
,IT_CREF(string_cref) \
)
/* Register the OPLIST as a global one */
#define M_OPL_string_t() STRING_OPLIST
/* Macro encapsulation to give a default value of 0 for start offset */
/* Search for a character in a string (string, character[, start=0]) */
#define string_search_char(v, ...) \
M_APPLY(string_search_char, v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Reverse Search for a character in a string (string, character[, start=0]) */
#define string_search_rchar(v, ...) \
M_APPLY(string_search_rchar, v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Search for a C string in a string (string, c_string[, start=0]) */
#define string_search_str(v, ...) \
M_APPLY(string_search_str, v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Search for a string in a string (string, string[, start=0]) */
#define string_search(v, ...) \
M_APPLY(string_search, v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* PBRK for a string in a string (string, string[, start=0]) */
#define string_search_pbrk(v, ...) \
M_APPLY(string_search_pbrk, v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Replace a C string to another C string in a string (string, c_src_string, c_dst_string, [, start=0]) */
#define string_replace_str(v, s1, ...) \
M_APPLY(string_replace_str, v, s1, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Replace a string to another string in a string (string, src_string, dst_string, [, start=0]) */
#define string_replace(v, s1, ...) \
M_APPLY(string_replace, v, s1, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Strim a string from the given set of characters (default is " \n\r\t") */
#define string_strim(...) \
M_APPLY(string_strim, M_IF_DEFAULT1(" \n\r\t", __VA_ARGS__))
/* Macro encapsulation for C11 */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* Select either the string function or the str function depending on
the b operade to the function.
func1 is the string function / func2 is the str function. */
# define STRINGI_SELECT2(func1,func2,a,b) \
_Generic((b)+0, \
char*: func2, \
const char *: func2, \
default : func1 \
)(a,b)
# define STRINGI_SELECT3(func1,func2,a,b,c) \
_Generic((b)+0, \
char*: func2, \
const char *: func2, \
default : func1 \
)(a,b,c)
/* Init & Set the string a to the string (or C string) b (constructor) */
#define string_init_set(a,b) STRINGI_SELECT2(string_init_set, string_init_set_str, a, b)
/* Set the string a to the string (or C string) b */
#define string_set(a,b) STRINGI_SELECT2(string_set, string_set_str, a, b)
/* Concatene the string (or C string) b to the string a */
#define string_cat(a,b) STRINGI_SELECT2(string_cat, string_cat_str, a, b)
/* Compare the string a to the string (or C string) b and return the sort order */
#define string_cmp(a,b) STRINGI_SELECT2(string_cmp, string_cmp_str, a, b)
/* Compare for equality the string a to the string (or C string) b */
#define string_equal_p(a,b) STRINGI_SELECT2(string_equal_p, string_equal_str_p, a, b)
/* strcoll the string a to the string (or C string) b */
#define string_strcoll(a,b) STRINGI_SELECT2(string_strcoll, string_strcoll_str, a, b)
#undef string_search
/* Search for a string in a string (or C string) (string, string[, start=0]) */
#define string_search(v, ...) \
M_APPLY(STRINGI_SELECT3, string_search, string_search_str, \
v, M_IF_DEFAULT1(0, __VA_ARGS__))
/* Internal Macro: Provide GET_STR method to default type */
#undef M_GET_STR_METHOD_FOR_DEFAULT_TYPE
#define M_GET_STR_METHOD_FOR_DEFAULT_TYPE GET_STR(M_GET_STRING_ARG)
#endif
/* Internal Macro: Provide GET_STR method to enum type */
#undef M_GET_STR_METHOD_FOR_ENUM_TYPE
#define M_GET_STR_METHOD_FOR_ENUM_TYPE GET_STR(M_ENUM_GET_STR)
/***********************************************************************/
/* */
/* BOUNDED STRING, aka char[N+1] */
/* */
/***********************************************************************/
/* Define a bounded (fixed) string of exactly 'max_size' characters
* (excluding the final nul char).
*/
#define BOUNDED_STRING_DEF(name, max_size) \
M_BEGIN_PROTECTED_CODE \
BOUNDED_STRING_DEF_P2(name, max_size, M_C(name, _t) ) \
M_END_PROTECTED_CODE
/* Define the OPLIST of a BOUNDED_STRING */
#define BOUNDED_STRING_OPLIST(name) \
(INIT(M_C(name,_init)), \
INIT_SET(M_C(name,_init_set)), \
SET(M_C(name,_set)), \
CLEAR(M_C(name,_clear)), \
NAME(name), \
INIT_WITH( API_1(BOUNDED_STRINGI_INIT_WITH)), \
HASH(M_C(name,_hash)), \
EQUAL(M_C(name,_equal_p)), \
CMP(M_C(name,_cmp)), \
TYPE(M_C(name,_ct)), \
OOR_EQUAL(M_C(name,_oor_equal_p)), \
OOR_SET(M_C(name, _oor_set)), \
PARSE_STR(M_C(name,_parse_str)), \
GET_STR(M_C(name,_get_str)), \
OUT_STR(M_C(name,_out_str)), \
IN_STR(M_C(name,_in_str)), \
OUT_SERIAL(M_C(name,_out_serial)), \
IN_SERIAL(M_C(name,_in_serial)), \
)
/************************** INTERNAL ***********************************/
/* Contract of a bounded string.
* A bounded string last characters is always zero. */
#define BOUNDED_STRINGI_CONTRACT(var, max_size) do { \
M_ASSERT(var != NULL); \
M_ASSERT(var->s[max_size] == 0); \
} while (0)
/* Expand the functions for a bounded string */
#define BOUNDED_STRING_DEF_P2(name, max_size, bounded_t) \
\
/* Define of an array with one more to store the final nul char */ \
typedef char M_C(name, _array_t)[max_size+1]; \
\
typedef struct M_C(name, _s) { \
char s[max_size+1]; \
} bounded_t[1]; \
\
/* Internal types for oplist */ \
typedef bounded_t M_C(name, _ct); \
\
static inline void \
M_C(name, _init)(bounded_t s) \
{ \
M_ASSERT(s != NULL); \
M_STATIC_ASSERT(max_size >= 1, M_LIB_INTERNAL, "M*LIB: max_size parameter shall be greater than 0."); \
s->s[0] = 0; \
s->s[max_size] = 0; \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
} \
\
static inline void \
M_C(name, _clear)(bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
/* nothing to do */ \
(void) s; \
} \
\
static inline void \
M_C(name, _clean)(bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
s->s[0] = 0; \
} \
\
static inline size_t \
M_C(name, _size)(const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
return strlen(s->s); \
} \
\
static inline size_t \
M_C(name, _capacity)(const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
(void)s; /* unused */ \
return max_size+1; \
} \
\
static inline char \
M_C(name, _get_char)(const bounded_t s, size_t index) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT_INDEX(index, max_size); \
return s->s[index]; \
} \
\
static inline bool \
M_C(name, _empty_p)(const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
return s->s[0] == 0; \
} \
\
static inline void \
M_C(name, _set_str)(bounded_t s, const char str[]) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
m_core_strncpy(s->s, str, max_size); \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
} \
\
static inline void \
M_C(name, _set_strn)(bounded_t s, const char str[], size_t n) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
size_t len = M_MIN(max_size, n); \
m_core_strncpy(s->s, str, len); \
s->s[len] = 0; \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
} \
\
static inline const char * \
M_C(name, _get_cstr)(const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
return s->s; \
} \
\
static inline void \
M_C(name, _set)(bounded_t s, const bounded_t str) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
BOUNDED_STRINGI_CONTRACT(str, max_size); \
M_C(name, _set_str)(s, str->s); \
} \
\
static inline void \
M_C(name, _set_n)(bounded_t s, const bounded_t str, \
size_t offset, size_t length) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
BOUNDED_STRINGI_CONTRACT(str, max_size); \
M_ASSERT_INDEX (offset, max_size+1); \
M_C(name, _set_strn)(s, str->s+offset, length); \
} \
\
static inline void \
M_C(name, _init_set)(bounded_t s, const bounded_t str) \
{ \
M_C(name,_init)(s); \
M_C(name,_set)(s, str); \
} \
\
static inline void \
M_C(name, _init_set_str)(bounded_t s, const char str[]) \
{ \
M_C(name,_init)(s); \
M_C(name,_set_str)(s, str); \
} \
\
static inline void \
M_C(name, _cat_str)(bounded_t s, const char str[]) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT (str != NULL); \
M_ASSERT_INDEX (strlen(s->s), max_size+1); \
m_core_strncat(s->s, str, max_size-strlen(s->s)); \
} \
\
static inline void \
M_C(name, _cat)(bounded_t s, const bounded_t str) \
{ \
BOUNDED_STRINGI_CONTRACT(str, max_size); \
M_C(name, _cat_str)(s, str->s); \
} \
\
static inline int \
M_C(name, _cmp_str)(const bounded_t s, const char str[]) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
return strcmp(s->s, str); \
} \
\
static inline int \
M_C(name, _cmp)(const bounded_t s, const bounded_t str) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
BOUNDED_STRINGI_CONTRACT(str, max_size); \
return strcmp(s->s, str->s); \
} \
\
static inline bool \
M_C(name, _equal_str_p)(const bounded_t s, const char str[]) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
return strcmp(s->s, str) == 0; \
} \
\
static inline bool \
M_C(name, _equal_p)(const bounded_t s, const bounded_t str) \
{ \
/* _equal_p may be called in context OOR. So contract cannot be verified */ \
return (s->s[max_size] == str->s[max_size]) & (strcmp(s->s, str->s) == 0); \
} \
\
static inline int \
M_C(name, _printf)(bounded_t s, const char format[], ...) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(format != NULL); \
va_list args; \
int ret; \
va_start (args, format); \
ret = vsnprintf (s->s, max_size+1, format, args); \
va_end (args); \
return ret; \
} \
\
static inline int \
M_C(name, _cat_printf)(bounded_t s, const char format[], ...) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(format != NULL); \
va_list args; \
int ret; \
va_start (args, format); \
size_t length = strlen(s->s); \
M_ASSERT(length <= max_size); \
ret = vsnprintf (&s->s[length], max_size+1-length, format, args); \
va_end (args); \
return ret; \
} \
\
static inline bool \
M_C(name, _fgets)(bounded_t s, FILE *f, string_fgets_t arg) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
M_ASSERT(arg != STRING_READ_FILE); \
char *ret = fgets(s->s, max_size+1, f); \
s->s[max_size] = 0; \
if (ret != NULL && arg == STRING_READ_PURE_LINE) { \
size_t length = strlen(s->s); \
if (length > 0 && s->s[length-1] == '\n') \
s->s[length-1] = 0; \
} \
return ret != NULL; \
} \
\
static inline bool \
M_C(name, _fputs)(FILE *f, const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
return fputs(s->s, f) >= 0; \
} \
\
static inline size_t \
M_C(name, _hash)(const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
/* Cannot use m_core_hash: alignment not sufficent */ \
M_HASH_DECL(hash); \
const char *str = s->s; \
while (*str) { \
size_t h = (size_t) *str++; \
M_HASH_UP(hash, h); \
} \
return M_HASH_FINAL(hash); \
} \
\
static inline bool \
M_C(name, _oor_equal_p)(const bounded_t s, unsigned char n) \
{ \
/* s may be invalid contract */ \
M_ASSERT (s != NULL); \
M_ASSERT ( (n == 0) || (n == 1)); \
return s->s[max_size] == (char) (n+1); \
} \
\
static inline void \
M_C(name, _oor_set)(bounded_t s, unsigned char n) \
{ \
/* s may be invalid contract */ \
M_ASSERT (s != NULL); \
M_ASSERT ( (n == 0) || (n == 1)); \
s->s[max_size] = (char) (n+1); \
} \
\
static inline void \
M_C(name, _get_str)(string_t v, const bounded_t s, bool append) \
{ \
STRINGI_CONTRACT(v); \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
/* Build dummy string to reuse string_get_str */ \
uintptr_t ptr = (uintptr_t) &s->s[0]; \
string_t v2; \
v2->u.heap.size = strlen(s->s); \
v2->u.heap.alloc = v2->u.heap.size + 1; \
v2->ptr = (char*)ptr; \
string_get_str(v, v2, append); \
} \
\
static inline void \
M_C(name, _out_str)(FILE *f, const bounded_t s) \
{ \
BOUNDED_STRINGI_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
/* Build dummy string to reuse string_get_str */ \
uintptr_t ptr = (uintptr_t) &s->s[0]; \
string_t v2; \
v2->u.heap.size = strlen(s->s); \
v2->u.heap.alloc = v2->u.heap.size + 1; \
v2->ptr = (char*)ptr; \
string_out_str(f, v2); \
} \
\
static inline bool \
M_C(name, _in_str)(bounded_t v, FILE *f) \
{ \
BOUNDED_STRINGI_CONTRACT(v, max_size); \
M_ASSERT(f != NULL); \
string_t v2; \
string_init(v2); \
bool ret = string_in_str(v2, f); \
m_core_strncpy(v->s, string_get_cstr(v2), max_size); \
string_clear(v2); \
return ret; \
} \
\
static inline bool \
M_C(name, _parse_str)(bounded_t v, const char str[], const char **endptr) \
{ \
BOUNDED_STRINGI_CONTRACT(v, max_size); \
M_ASSERT(str != NULL); \
string_t v2; \
string_init(v2); \
bool ret = string_parse_str(v2, str, endptr); \
m_core_strncpy(v->s, string_get_cstr(v2), max_size); \
string_clear(v2); \
return ret; \
} \
\
static inline m_serial_return_code_t \
M_C(name, _out_serial)(m_serial_write_t serial, const bounded_t v) \
{ \
BOUNDED_STRINGI_CONTRACT(v, max_size); \
M_ASSERT (serial != NULL && serial->m_interface != NULL); \
return serial->m_interface->write_string(serial, v->s, strlen(v->s) ); \
} \
\
static inline m_serial_return_code_t \
M_C(name, _in_serial)(bounded_t v, m_serial_read_t serial) \
{ \
BOUNDED_STRINGI_CONTRACT(v, max_size); \
M_ASSERT (serial != NULL && serial->m_interface != NULL); \
string_t tmp; \
/* TODO: Not optimum */ \
string_init(tmp); \
m_serial_return_code_t r = serial->m_interface->read_string(serial, tmp); \
m_core_strncpy(v->s, string_get_cstr(tmp), max_size); \
string_clear(tmp); \
BOUNDED_STRINGI_CONTRACT(v, max_size); \
return r; \
}
/* Init a constant bounded string.
Try to do a clean cast */
/* Use of Compound Literals to init a constant string.
See above */
#ifndef __cplusplus
#define BOUNDED_STRING_CTE(name, string) \
((const struct M_C(name, _s) *)((M_C(name, _array_t)){string}))
#else
namespace m_string {
template <unsigned int N>
struct m_bounded_string {
char s[N];
inline m_bounded_string(const char lstr[])
{
memset(this->s, 0, N);
m_core_strncpy(this->s, lstr, N-1);
}
};
}
#define BOUNDED_STRING_CTE(name, string) \
((const struct M_C(name, _s) *)(m_string::m_bounded_string<sizeof (M_C(name, _t))>(string).s))
#endif
/* Initialize a bounded string with the given list of arguments.
Check if it is a formatted input or not by counting the number of arguments.
If there is only one argument, it can only be a set to C string.
It is much faster in this case to call string_init_set_str.
*/
#define BOUNDED_STRINGI_INIT_WITH(oplist, v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(M_C(M_GET_NAME oplist, _init_set_str)(v, __VA_ARGS__), BOUNDED_STRINGI_INIT_PRINTF(oplist, v, __VA_ARGS__))
#define BOUNDED_STRINGI_INIT_PRINTF(oplist, v, ...) \
(M_GET_INIT oplist (v), M_C(M_GET_NAME oplist, _printf)(v, __VA_ARGS__))
M_END_PROTECTED_CODE
#endif