Tactiliest/libs/mlib/m-string.h
2024-01-17 21:45:57 +01:00

2788 lines
108 KiB
C++

/*
* M*LIB - Dynamic Size String Module
*
* Copyright (c) 2017-2023, 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 "m-core.h"
M_BEGIN_PROTECTED_CODE
/********************************** INTERNAL ************************************/
// 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 M_STR1NG_CONTRACT(v) do { \
M_ASSERT (v != NULL); \
M_ASSERT_SLOW (m_string_size(v) == strlen(m_string_get_cstr(v))); \
M_ASSERT (m_string_get_cstr(v)[m_string_size(v)] == 0); \
M_ASSERT (m_string_size(v) < m_string_capacity(v)); \
M_ASSERT (m_string_capacity(v) < sizeof (m_str1ng_heap_ct) || !m_str1ng_stack_p(v)); \
} while(0)
// string if it is heap allocated
typedef struct m_str1ng_heap_s {
size_t size;
size_t alloc;
} m_str1ng_heap_ct;
// string if it is stack allocated
typedef struct m_str1ng_stack_s {
char buffer[sizeof (m_str1ng_heap_ct)];
} m_str1ng_stack_ct;
// both cases of string are possible
typedef union m_str1ng_union_u {
m_str1ng_heap_ct heap;
m_str1ng_stack_ct stack;
} m_str1ng_union_ct;
/* State of the UTF8 decoding machine state */
typedef enum {
M_STR1NG_UTF8_STARTING = 0,
M_STR1NG_UTF8_DECODING_1 = 8,
M_STR1NG_UTF8_DECODING_2 = 16,
M_STR1NG_UTF8_DECODING_3 = 24,
M_STR1NG_UTF8_ERROR = 32
} m_str1ng_utf8_state_e;
/* Maximum number of bytes per UTF8 code point */
#define M_STR1NG_MAX_BYTE_UTF8 4
/* Contract for a string iterator */
#define M_STR1NG_IT_CONTRACT(it) do { \
M_ASSERT( (it) != NULL); \
M_ASSERT( (it)->ptr != NULL); \
M_ASSERT( (it)->string != NULL); \
M_ASSERT( m_string_get_cstr((it)->string) <= (it)->ptr); \
M_ASSERT( (it)->ptr <= &m_string_get_cstr((it)->string)[m_string_size((it)->string)]); \
} while (0)
/****************************** EXTERNAL *******************************/
/***********************************************************************/
/* */
/* DYNAMIC STRING */
/* */
/***********************************************************************/
// The default behavior of M*LIB is to use the C library
// and not rewrite the algorithms. However, the fast code
// generates also smaller code, so we'll use it.
#ifndef M_USE_FAST_STRING_CONV
#define M_USE_FAST_STRING_CONV 1
#endif
/* Index returned in case of error instead of the position within the string */
#define M_STRING_FAILURE ((size_t)-1)
/* This is the main structure of this module, representing a dynamic string */
typedef struct m_string_s {
m_str1ng_union_ct u;
char *ptr;
} m_string_t[1];
// Pointer to a Dynamic string
typedef struct m_string_s *m_string_ptr;
// Constant pointer to a Dynamic string
typedef const struct m_string_s *m_string_srcptr;
/* Input option for m_string_fgets
M_STRING_READ_LINE (read line),
M_STRING_READ_PURE_LINE (read line and remove final CR and/or LF)
M_STRING_READ_FILE (read all file)
*/
typedef enum m_string_fgets_e {
M_STRING_READ_LINE = 0, M_STRING_READ_PURE_LINE = 1, M_STRING_READ_FILE = 2
} m_string_fgets_t;
/* An unicode code point */
typedef uint32_t m_string_unicode_t;
/* Error in case of decoding */
#define M_STRING_UNICODE_ERROR (UINT_MAX)
/* Iterator on a string over UTF8 encoded code points */
typedef struct m_string_it_s {
m_string_unicode_t u; // Decoded Unicode code point for the iterator
const char *ptr;
m_string_srcptr string;
} m_string_it_t[1];
/* PREFIX:
m_str1ng_: private methods
m_string_: public methods
*/
/* Internal method to test if the string is stack based or heap based
We test if the ptr is NULL or not.
This is not particularly efficient from a memory point of view
(as we could reuse the pointer field to store some data)
but it should be enough for most of "small" strings as most
of the strings are less than 14 characters (64 bits architecture).
Moreover it generates quite efficient code.
NOTE: We set the pointer to NULL (instead of storing a pointer to
the stack buffer for example) for stack based allocation so that
the string structure remains trivially movable (which is an important
property to have).
*/
M_INLINE bool
m_str1ng_stack_p(const m_string_t s)
{
// Function can be called when contract is not fulfilled
return (s->ptr == NULL);
}
/* Internal method to set the size of the string (excluding final nul char) */
M_INLINE void
m_str1ng_set_size(m_string_t s, size_t size)
{
// Function can be called when contract is not fulfilled
if (m_str1ng_stack_p(s)) {
M_ASSERT (size < sizeof (m_str1ng_heap_ct) - 1);
// The size of the string is stored as the last char of the buffer.
s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = (char) size;
} else {
s->u.heap.size = size;
}
}
/* Return the number of bytes of the string (excluding the final nul char) */
M_INLINE size_t
m_string_size(const m_string_t s)
{
// Function can be called when contract is not fulfilled
// Reading both values before calling the '?' operator enables compiler to generate branchless code
const size_t s_stack = (size_t) s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1];
const size_t s_heap = s->u.heap.size;
return m_str1ng_stack_p(s) ? s_stack : s_heap;
}
/* Return the capacity of the string (including the final nul char) */
M_INLINE size_t
m_string_capacity(const m_string_t s)
{
// Function can be called when contract is not fulfilled
// Reading both values before calling the '?' operator enables compiler to generate branchless code
const size_t c_stack = sizeof (m_str1ng_heap_ct) - 1;
const size_t c_heap = s->u.heap.alloc;
return m_str1ng_stack_p(s) ? c_stack : c_heap;
}
/* Return a writable pointer to the array of char of the string */
M_INLINE char*
m_str1ng_get_cstr(m_string_t v)
{
// Function can be called when contract is not fulfilled
char *const ptr_stack = &v->u.stack.buffer[0];
char *const ptr_heap = v->ptr;
return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
}
/* Return the string view a classic C string (const char *) */
M_INLINE const char*
m_string_get_cstr(const m_string_t v)
{
// Function cannot be called when contract is not fulfilled
// 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 m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
}
/* Initialize the dynamic string (constructor)
and make it empty */
M_INLINE void
m_string_init(m_string_t s)
{
s->ptr = NULL;
s->u.stack.buffer[0] = 0;
m_str1ng_set_size(s, 0);
M_STR1NG_CONTRACT(s);
}
/* Clear the Dynamic string (destructor) */
M_INLINE void
m_string_clear(m_string_t v)
{
M_STR1NG_CONTRACT(v);
if (!m_str1ng_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 (m_str1ng_heap_ct) - 1] = CHAR_MAX;
}
/* NOTE: Internaly used by M_STRING_DECL_INIT */
M_INLINE void m_str1ng_clear2(m_string_t *v) { m_string_clear(*v); }
M_INLINE m_string_ptr m_str1ng_init_ref(m_string_t v) { m_string_init(v); return 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. */
M_INLINE char *
m_string_clear_get_cstr(m_string_t v)
{
M_STR1NG_CONTRACT(v);
char *p = v->ptr;
if (m_str1ng_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 = m_string_size(v)+1;
char *ptr = M_MEMORY_REALLOC (char, NULL, alloc);
if (M_UNLIKELY_NOMEM (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 (m_str1ng_heap_ct) - 1] = CHAR_MAX;
return p;
}
/* Make the string empty */
M_INLINE void
m_string_reset(m_string_t v)
{
M_STR1NG_CONTRACT (v);
m_str1ng_set_size(v, 0);
m_str1ng_get_cstr(v)[0] = 0;
M_STR1NG_CONTRACT (v);
}
/* Return the selected byte-character of the string */
M_INLINE char
m_string_get_char(const m_string_t v, size_t index)
{
M_STR1NG_CONTRACT (v);
M_ASSERT_INDEX(index, m_string_size(v));
return m_string_get_cstr(v)[index];
}
/* Set the selected byte-character of the string */
M_INLINE void
m_string_set_char(m_string_t v, size_t index, const char c)
{
M_STR1NG_CONTRACT (v);
M_ASSERT_INDEX(index, m_string_size(v));
m_str1ng_get_cstr(v)[index] = c;
}
/* Test if the string is empty or not */
M_INLINE bool
m_string_empty_p(const m_string_t v)
{
M_STR1NG_CONTRACT (v);
return m_string_size(v) == 0;
}
/* Internal method to fit the string to the given size
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.
*/
M_INLINE char *
m_str1ng_fit2size (m_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 fulfilled.
const size_t old_alloc = m_string_capacity(v);
// This line enables the compiler to completly remove this function
// for very short constant strings.
M_ASSUME(old_alloc >= sizeof (m_str1ng_heap_ct) - 1);
if (M_UNLIKELY (size_alloc > old_alloc)) {
// Insufficient current allocation to store the new string
// Perform an allocation on the heap.
size_t alloc = size_alloc + size_alloc / 2;
if (M_UNLIKELY_NOMEM (alloc <= size_alloc)) {
/* Overflow in alloc computation */
M_MEMORY_FULL(sizeof (char) * alloc);
// NOTE: Return is currently broken.
abort();
return NULL;
}
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
M_MEMORY_FULL(sizeof (char) * alloc);
// NOTE: Return is currently broken.
abort();
return NULL;
}
// The pointer cannot be the stack buffer of the string.
// as it is heap allocated
M_ASSERT(ptr != &v->u.stack.buffer[0]);
if (m_str1ng_stack_p(v)) {
// The string was stack allocated.
/* Copy the stack allocation into the new heap allocation */
const size_t size = (size_t) v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] + 1U;
M_ASSERT( size <= alloc);
M_ASSERT( size <= sizeof (v->u.stack.buffer)-1);
memcpy(ptr, &v->u.stack.buffer[0], size);
}
// The string cannot be stack allocated anymore.
v->ptr = ptr;
v->u.heap.alloc = alloc;
return ptr;
}
return m_str1ng_get_cstr(v);
}
/* Modify the string capacity to be able to handle at least 'alloc'
characters (including final nul char).
It may reduce the allocation of the string if possible */
M_INLINE void
m_string_reserve(m_string_t v, size_t alloc)
{
M_STR1NG_CONTRACT (v);
const size_t size = m_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 (m_str1ng_heap_ct)) {
// Allocation can fit in the stack space
if (!m_str1ng_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;
m_str1ng_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
// If the string is stack allocated, v->ptr is NULL
// and it will therefore perform the initial allocation
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
M_MEMORY_FULL(sizeof (char) * alloc);
return;
}
if (m_str1ng_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;
}
M_STR1NG_CONTRACT (v);
}
/* Set the string to the C string str */
M_INLINE void
m_string_set_cstr(m_string_t v, const char str[])
{
M_STR1NG_CONTRACT (v);
M_ASSERT(str != NULL);
size_t size = strlen(str);
char *ptr = m_str1ng_fit2size(v, size+1);
// The memcpy will also copy the final null char of the string
memcpy(ptr, str, size+1);
m_str1ng_set_size(v, size);
M_STR1NG_CONTRACT (v);
}
/* Set the string to the n first characters of the C string str */
M_INLINE void
m_string_set_cstrn(m_string_t v, const char str[], size_t n)
{
M_STR1NG_CONTRACT (v);
M_ASSERT(str != NULL);
size_t len = strlen(str);
size_t size = M_MIN (len, n);
char *ptr = m_str1ng_fit2size(v, size+1);
// The memcpy will not copy the final null char of the string
memcpy(ptr, str, size);
ptr[size] = 0;
m_str1ng_set_size(v, size);
M_STR1NG_CONTRACT (v);
}
/* Set the string to the other one */
M_INLINE void
m_string_set (m_string_t v1, const m_string_t v2)
{
M_STR1NG_CONTRACT (v1);
M_STR1NG_CONTRACT (v2);
if (M_LIKELY (v1 != v2)) {
const size_t size = m_string_size(v2);
char *ptr = m_str1ng_fit2size(v1, size+1);
memcpy(ptr, m_string_get_cstr(v2), size+1);
m_str1ng_set_size(v1, size);
}
M_STR1NG_CONTRACT (v1);
}
/* Set the string to the n first characters of other one */
M_INLINE void
m_string_set_n(m_string_t v, const m_string_t ref, size_t offset, size_t length)
{
M_STR1NG_CONTRACT (v);
M_STR1NG_CONTRACT (ref);
M_ASSERT_INDEX (offset, m_string_size(ref) + 1);
size_t size = M_MIN (m_string_size(ref) - offset, length);
char *ptr = m_str1ng_fit2size(v, size+1);
// v may be equal to ref, so a memmove is needed instead of a memcpy
memmove(ptr, m_string_get_cstr(ref) + offset, size);
ptr[size] = 0;
m_str1ng_set_size(v, size);
M_STR1NG_CONTRACT (v);
}
/* Initialize the string and set it to the other one
(constructor) */
M_INLINE void
m_string_init_set(m_string_t v1, const m_string_t v2)
{
m_string_init(v1);
m_string_set(v1,v2);
}
/* Initialize the string and set it to the C string
(constructor) */
M_INLINE void
m_string_init_set_cstr(m_string_t v1, const char str[])
{
m_string_init(v1);
m_string_set_cstr(v1, str);
}
/* Initialize the string, set it to the other one,
and destroy the other one.
(constructor & destructor) */
M_INLINE void
m_string_init_move(m_string_t v1, m_string_t v2)
{
M_STR1NG_CONTRACT (v2);
memcpy(v1, v2, sizeof (m_string_t));
// Note: nullify v2 to be safer
v2->ptr = NULL;
M_STR1NG_CONTRACT (v1);
}
/* Swap the two strings v1 and v2 */
M_INLINE void
m_string_swap(m_string_t v1, m_string_t v2)
{
M_STR1NG_CONTRACT (v1);
M_STR1NG_CONTRACT (v2);
// Even if it is stack based, we swap the heap representation
// which alias the stack based
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);
M_STR1NG_CONTRACT (v1);
M_STR1NG_CONTRACT (v2);
}
/* Set the string to the other one,
and destroy the other one.
(destructor) */
M_INLINE void
m_string_move(m_string_t v1, m_string_t v2)
{
m_string_clear(v1);
m_string_init_move(v1,v2);
}
/* Push the byte-character 'c' in the string 'v' */
M_INLINE void
m_string_push_back (m_string_t v, char c)
{
M_STR1NG_CONTRACT (v);
const size_t size = m_string_size(v);
char *ptr = m_str1ng_fit2size(v, size+2);
ptr[size+0] = c;
ptr[size+1] = 0;
m_str1ng_set_size(v, size+1);
M_STR1NG_CONTRACT (v);
}
/* Concatene the string with the C string */
M_INLINE void
m_string_cat_cstr(m_string_t v, const char str[])
{
M_STR1NG_CONTRACT (v);
M_ASSERT (str != NULL);
const size_t old_size = m_string_size(v);
const size_t size = strlen(str);
char *ptr = m_str1ng_fit2size(v, old_size + size + 1);
memcpy(&ptr[old_size], str, size + 1);
m_str1ng_set_size(v, old_size + size);
M_STR1NG_CONTRACT (v);
}
/* Concatene the string with the other string */
M_INLINE void
m_string_cat(m_string_t v, const m_string_t v2)
{
M_STR1NG_CONTRACT (v2);
M_STR1NG_CONTRACT (v);
const size_t size = m_string_size(v2);
if (M_LIKELY (size > 0)) {
const size_t old_size = m_string_size(v);
char *ptr = m_str1ng_fit2size(v, old_size + size + 1);
memcpy(&ptr[old_size], m_string_get_cstr(v2), size);
ptr[old_size + size] = 0;
m_str1ng_set_size(v, old_size + size);
}
M_STR1NG_CONTRACT (v);
}
/* Compare the string to the C string and
return the sort order (negative if less, 0 if equal, positive if greater) */
M_INLINE int
m_string_cmp_cstr(const m_string_t v1, const char str[])
{
M_STR1NG_CONTRACT (v1);
M_ASSERT (str != NULL);
return strcmp(m_string_get_cstr(v1), str);
}
/* Compare the string to the other string and
return the sort order (negative if less, 0 if equal, positive if greater) */
M_INLINE int
m_string_cmp(const m_string_t v1, const m_string_t v2)
{
M_STR1NG_CONTRACT (v1);
M_STR1NG_CONTRACT (v2);
return strcmp(m_string_get_cstr(v1), m_string_get_cstr(v2));
}
/* Test if the string is equal to the given C string */
M_INLINE bool
m_string_equal_cstr_p(const m_string_t v1, const char str[])
{
M_STR1NG_CONTRACT(v1);
M_ASSERT (str != NULL);
return m_string_cmp_cstr(v1, str) == 0;
}
/* Test if the string is equal to the other string */
M_INLINE bool
m_string_equal_p(const m_string_t v1, const m_string_t v2)
{
/* m_string_equal_p can be called with (at most) one string which is an OOR value.
In case of OOR value, .ptr is NULL and .alloc is the maximum or the maximum-1.
As it will detect a stack based string, it will read the size from the alloc fied
as 0xFF or 0xFE. In both cases, the size cannot be equal to a normal string
so the test m_string_size(v1) == m_string_size(v2) is false in this case.
*/
M_ASSERT(v1 != NULL);
M_ASSERT(v2 != NULL);
/* Optimization: both strings shall have at least the same size */
return m_string_size(v1) == m_string_size(v2) && m_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.
*/
M_INLINE int
m_string_cmpi_cstr(const m_string_t v1, const char p2[])
{
M_STR1NG_CONTRACT (v1);
M_ASSERT (p2 != NULL);
// strcasecmp is POSIX only
const char *p1 = m_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.
*/
M_INLINE int
m_string_cmpi(const m_string_t v1, const m_string_t v2)
{
return m_string_cmpi_cstr(v1, m_string_get_cstr(v2));
}
/* Search for the position of the character c
from the position 'start' (include) in the string
Return M_STRING_FAILURE if not found.
By default, start is zero.
*/
M_INLINE size_t
m_string_search_char (const m_string_t v, char c, size_t start)
{
M_STR1NG_CONTRACT (v);
M_ASSERT_INDEX (start, m_string_size(v) + 1);
const char *p = M_ASSIGN_CAST(const char*,
strchr(m_string_get_cstr(v)+start, c));
return p == NULL ? M_STRING_FAILURE : (size_t) (p-m_string_get_cstr(v));
}
/* Reverse Search for the position of the character c
from the position 'start' (include) in the string
Return M_STRING_FAILURE if not found.
By default, start is zero.
*/
M_INLINE size_t
m_string_search_rchar (const m_string_t v, char c, size_t start)
{
M_STR1NG_CONTRACT (v);
M_ASSERT_INDEX (start, m_string_size(v)+1);
// NOTE: Can be implemented 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).
// However, does it worth the effort?
const char *p = M_ASSIGN_CAST(const char*,
strrchr(m_string_get_cstr(v)+start, c));
return p == NULL ? M_STRING_FAILURE : (size_t) (p-m_string_get_cstr(v));
}
/* Search for the sub C string in the string from the position start
Return M_STRING_FAILURE if not found.
By default, start is zero. */
M_INLINE size_t
m_string_search_cstr(const m_string_t v, const char str[], size_t start)
{
M_STR1NG_CONTRACT (v);
M_ASSERT_INDEX (start, m_string_size(v)+1);
M_ASSERT (str != NULL);
const char *p = M_ASSIGN_CAST(const char*,
strstr(m_string_get_cstr(v)+start, str));
return p == NULL ? M_STRING_FAILURE : (size_t) (p-m_string_get_cstr(v));
}
/* Search for the sub other string v2 in the string v1 from the position start
Return M_STRING_FAILURE if not found.
By default, start is zero. */
M_INLINE size_t
m_string_search (const m_string_t v1, const m_string_t v2, size_t start)
{
M_STR1NG_CONTRACT (v2);
M_ASSERT_INDEX (start, m_string_size(v1)+1);
return m_string_search_cstr(v1, m_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 M_STRING_FAILURE if not found.
By default, start is zero. */
M_INLINE size_t
m_string_search_pbrk(const m_string_t v1, const char first_of[], size_t start)
{
M_STR1NG_CONTRACT (v1);
M_ASSERT_INDEX (start, m_string_size(v1)+1);
M_ASSERT (first_of != NULL);
const char *p = M_ASSIGN_CAST(const char*,
strpbrk(m_string_get_cstr(v1)+start, first_of));
return p == NULL ? M_STRING_FAILURE : (size_t) (p-m_string_get_cstr(v1));
}
/* Compare the string to the C string using strcoll */
M_INLINE int
m_string_strcoll_cstr(const m_string_t v, const char str[])
{
M_STR1NG_CONTRACT (v);
return strcoll(m_string_get_cstr(v), str);
}
/* Compare the string to the other string using strcoll */
M_INLINE int
m_string_strcoll (const m_string_t v1, const m_string_t v2)
{
M_STR1NG_CONTRACT (v2);
return m_string_strcoll_cstr(v1, m_string_get_cstr(v2));
}
/* Return the number of bytes of the segment of s
that consists entirely of bytes in accept */
M_INLINE size_t
m_string_spn(const m_string_t v1, const char accept[])
{
M_STR1NG_CONTRACT (v1);
M_ASSERT (accept != NULL);
return strspn(m_string_get_cstr(v1), accept);
}
/* Return the number of bytes of the segment of s
that consists entirely of bytes not in reject */
M_INLINE size_t
m_string_cspn(const m_string_t v1, const char reject[])
{
M_STR1NG_CONTRACT (v1);
M_ASSERT (reject != NULL);
return strcspn(m_string_get_cstr(v1), reject);
}
/* Return the string left truncated to the first 'index' bytes */
M_INLINE void
m_string_left(m_string_t v, size_t index)
{
M_STR1NG_CONTRACT (v);
const size_t size = m_string_size(v);
if (index >= size)
return;
m_str1ng_get_cstr(v)[index] = 0;
m_str1ng_set_size(v,index);
M_STR1NG_CONTRACT (v);
}
/* Return the string right truncated from the 'index' position to the last position */
M_INLINE void
m_string_right(m_string_t v, size_t index)
{
M_STR1NG_CONTRACT (v);
char *ptr = m_str1ng_get_cstr(v);
const size_t size = m_string_size(v);
if (index >= size) {
ptr[0] = 0;
m_str1ng_set_size(v, 0);
M_STR1NG_CONTRACT (v);
return;
}
size_t s2 = size - index;
memmove (&ptr[0], &ptr[index], s2+1);
m_str1ng_set_size(v, s2);
M_STR1NG_CONTRACT (v);
}
/* Return the string from position index to size bytes.
See also m_string_set_n
*/
M_INLINE void
m_string_mid (m_string_t v, size_t index, size_t size)
{
m_string_right(v, index);
m_string_left(v, size);
}
/* Replace in the string the firt occurence of the C string str1
into the C string str2 from start
By default, start is zero.
*/
M_INLINE size_t
m_string_replace_cstr (m_string_t v, const char str1[], const char str2[], size_t start)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (str1 != NULL && str2 != NULL);
size_t i = m_string_search_cstr(v, str1, start);
if (i != M_STRING_FAILURE) {
const size_t str1_l = strlen(str1);
const size_t str2_l = strlen(str2);
const size_t size = m_string_size(v);
M_ASSERT(size + 1 + str2_l > str1_l);
char *ptr = m_str1ng_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);
m_str1ng_set_size(v, size + str2_l - str1_l);
}
memcpy (&ptr[i], str2, str2_l);
M_STR1NG_CONTRACT (v);
}
return i;
}
/* Replace in the string the firt occurence of the C string v1
into the C string v2 from start
By default, start is zero.
*/
M_INLINE size_t
m_string_replace (m_string_t v, const m_string_t v1, const m_string_t v2, size_t start)
{
M_STR1NG_CONTRACT (v);
M_STR1NG_CONTRACT (v1);
M_STR1NG_CONTRACT (v2);
return m_string_replace_cstr(v, m_string_get_cstr(v1), m_string_get_cstr(v2), start);
}
/* Replace in the string the sub-string at position 'pos' for 'len' bytes
into the C string str2. */
M_INLINE void
m_string_replace_at (m_string_t v, size_t pos, size_t len, const char str2[])
{
M_STR1NG_CONTRACT (v);
M_ASSERT(str2 != NULL);
const size_t str1_l = len;
const size_t str2_l = strlen(str2);
const size_t size = m_string_size(v);
char *ptr;
if (str1_l != str2_l) {
// Move bytes from the string
M_ASSERT_INDEX (str1_l, size + str2_l + 1);
ptr = m_str1ng_fit2size (v, size + str2_l - str1_l + 1);
M_ASSERT_INDEX (pos + str1_l, size + 1);
M_ASSUME (pos + str1_l < size + 1);
memmove(&ptr[pos+str2_l], &ptr[pos+str1_l], size - pos - str1_l + 1);
m_str1ng_set_size(v, size + str2_l - str1_l);
} else {
ptr = m_str1ng_get_cstr(v);
}
memcpy (&ptr[pos], str2, str2_l);
M_STR1NG_CONTRACT (v);
}
/* Replace all occurences of str1 into str2 when strlen(str1) >= strlen(str2) */
M_INLINE void
m_str1ng_replace_all_cstr_1ge2 (m_string_t v, const char str1[], size_t str1len, const char str2[], size_t str2len)
{
M_STR1NG_CONTRACT (v);
M_ASSERT(str1len >= str2len);
/* str1len >= str2len so the string doesn't need to be resized */
size_t vlen = m_string_size(v);
char *org = m_str1ng_get_cstr(v);
char *src = org;
char *dst = org;
// Go through all the characters of the string
while (*src != 0) {
// Get a new occurrence of str1 in the v string.
char *occ = strstr(src, str1);
if (occ == NULL) {
// No new occurrence
break;
}
M_ASSERT(occ >= src);
// Copy the data until the new occurrence
if (src != dst) {
memmove(dst, src, (size_t) (occ - src));
}
dst += (occ - src);
// Copy the replaced string
memcpy(dst, str2, str2len);
dst += str2len;
// Advance src pointer
src = occ + str1len;
}
// Finish copying the string until the end
M_ASSERT (src <= org + vlen );
if (src != dst) {
memmove(dst, src, (size_t) (org + vlen + 1 - src) );
}
// Update string size
m_str1ng_set_size(v, (size_t) (dst + vlen - src) );
M_STR1NG_CONTRACT (v);
}
/* Reverse strstr from the end of the string
org is the start of the string
src is the current character pointer (shall be initialized to the end of the string)
pattern / pattern_size: the pattern to search.
*/
M_INLINE char *
m_str1ng_strstr_r(char org[], char src[], const char pattern[], size_t pattern_size)
{
M_ASSERT(pattern_size >= 1);
src -= pattern_size - 1;
while (org <= src) {
if (src[0] == pattern[0]
&& src[pattern_size-1] == pattern[pattern_size-1]
&& memcmp(src, pattern, pattern_size) == 0) {
return src;
}
src --;
}
return NULL;
}
/* Replace all occurences of str1 into str2 when strlen(str1) < strlen(str2) */
M_INLINE void
m_str1ng_replace_all_cstr_1lo2 (m_string_t v, const char str1[], size_t str1len, const char str2[], size_t str2len)
{
M_STR1NG_CONTRACT (v);
M_ASSERT(str1len < str2len);
/* str1len < str2len so the string may need to be resized
Worst case if when v is composed fully of str1 substrings.
Needed size : v.size / str1len * str2len
*/
size_t vlen = m_string_size(v);
size_t alloc = 1 + vlen / str1len * str2len;
char *org = m_str1ng_fit2size(v, alloc);
char *src = org + vlen - 1;
char *end = org + m_string_capacity(v);
char *dst = end;
// Go through all the characters of the string in reverse !
while (src >= org) {
char *occ = m_str1ng_strstr_r(org, src, str1, str1len);
if (occ == NULL) {
break;
}
M_ASSERT(occ + str1len - 1 <= src);
// Copy the data until the new occurrence
dst -= (src - (occ + str1len - 1));
memmove(dst, occ+str1len, (size_t) (src - (occ + str1len - 1)));
// Copy the replaced string
dst -= str2len;
memcpy(dst, str2, str2len);
// Advance src pointer
src = occ - 1;
}
// Finish moving data back to their natural place
memmove(src + 1, dst, (size_t) (end - dst) );
// Update string size
vlen = (size_t) (src - org + end - dst + 1);
org[vlen] = 0;
m_str1ng_set_size(v, vlen );
M_STR1NG_CONTRACT (v);
}
M_INLINE void
m_string_replace_all_cstr (m_string_t v, const char str1[], const char str2[])
{
size_t str1_l = strlen(str1);
size_t str2_l = strlen(str2);
M_ASSERT(str1_l > 0);
if (str1_l >= str2_l) {
m_str1ng_replace_all_cstr_1ge2(v, str1, str1_l, str2, str2_l );
} else {
m_str1ng_replace_all_cstr_1lo2(v, str1, str1_l, str2, str2_l );
}
}
M_INLINE void
m_string_replace_all (m_string_t v, const m_string_t str1, const m_string_t str2)
{
size_t str1_l = m_string_size(str1);
size_t str2_l = m_string_size(str2);
M_ASSERT(str1_l > 0);
M_ASSERT(str2_l > 0);
if (str1_l >= str2_l) {
m_str1ng_replace_all_cstr_1ge2(v, m_string_get_cstr(str1), str1_l, m_string_get_cstr(str2), str2_l );
} else {
m_str1ng_replace_all_cstr_1lo2(v, m_string_get_cstr(str1), str1_l, m_string_get_cstr(str2), str2_l );
}
}
// Define the fast integer to string conversions if requested
// or if no support for stdarg.
#if M_USE_FAST_STRING_CONV == 1 || M_USE_STDARG == 0
// Compute the maximum number of characters needed for the buffer.
#if UINT_MAX == 4294967295U
#define M_STR1NG_INT_MAX_SIZE (10+1)
#elif UINT_MAX <= 18446744073709551615UL
#define M_STR1NG_INT_MAX_SIZE (20+1)
#else
# error Unexpected UINT_MAX value (workaround: Define M_USE_FAST_STRING_CONV to 0).
#endif
M_INLINE void
m_string_set_ui(m_string_t v, unsigned int n)
{
M_STR1NG_CONTRACT (v);
char buffer[M_STR1NG_INT_MAX_SIZE];
m_str1ng_fit2size(v, M_STR1NG_INT_MAX_SIZE);
unsigned i = 0, j = 0;
do {
// 0123456789 are mandatory in this order as characters, as per C standard.
buffer[i++] = (char) ('0' + (n % 10U));
n /= 10U;
} while (n != 0);
M_ASSERT_INDEX(i, M_STR1NG_INT_MAX_SIZE);
char *d = m_str1ng_get_cstr(v);
while (i > 0) {
d[j++] = buffer[--i];
}
d[j] = 0;
m_str1ng_set_size(v, j);
M_STR1NG_CONTRACT (v);
}
M_INLINE void
m_string_set_si(m_string_t v, int n)
{
M_STR1NG_CONTRACT (v);
// Compute the maximum number of characters needed for the buffer.
char buffer[M_STR1NG_INT_MAX_SIZE];
m_str1ng_fit2size(v, M_STR1NG_INT_MAX_SIZE);
unsigned i = 0, j = 0;
bool neg = n < 0;
unsigned n0 = neg ? 0U -(unsigned) n : (unsigned) n;
do {
// 0123456789 are mandatory in this order as characters, as per C standard.
buffer[i++] = (char) ('0' + (n0 % 10U));
n0 /= 10U;
} while (n0 != 0);
M_ASSERT_INDEX(i, M_STR1NG_INT_MAX_SIZE);
char *d = m_str1ng_get_cstr(v);
if (neg) d[j++] = '-';
while (i > 0) {
d[j++] = buffer[--i];
}
d[j] = 0;
m_str1ng_set_size(v, j);
M_STR1NG_CONTRACT (v);
}
#endif
#if M_USE_STDARG
/* Format in the string the given printf format */
M_INLINE int
m_string_vprintf (m_string_t v, const char format[], va_list args)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (format != NULL);
int size;
va_list args_org;
va_copy(args_org, args);
char *ptr = m_str1ng_get_cstr(v);
size_t alloc = m_string_capacity(v);
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 = m_str1ng_fit2size (v, (size_t) size + 1);
alloc = m_string_capacity(v);
// and redo the parsing.
size = vsnprintf (ptr, alloc, format, args_org);
M_ASSERT (size > 0 && (size_t)size < alloc);
}
if (M_LIKELY (size >= 0)) {
m_str1ng_set_size(v, (size_t) size);
} else {
// An error occured during the conversion: Assign an empty string.
m_str1ng_set_size(v, 0);
ptr[0] = 0;
}
va_end(args_org);
M_STR1NG_CONTRACT (v);
return size;
}
/* Format in the string the given printf format */
M_INLINE int
m_string_printf (m_string_t v, const char format[], ...)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (format != NULL);
va_list args;
va_start (args, format);
int ret = m_string_vprintf(v, format, args);
va_end (args);
return ret;
}
/* Append to the string the formatted string of the given printf format */
M_INLINE int
m_string_cat_vprintf (m_string_t v, const char format[], va_list args)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (format != NULL);
va_list args_org;
va_copy(args_org, args);
int size;
size_t old_size = m_string_size(v);
char *ptr = m_str1ng_get_cstr(v);
size_t alloc = m_string_capacity(v);
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 = m_str1ng_fit2size (v, old_size + (size_t) size + 1);
alloc = m_string_capacity(v);
// and redo the parsing.
size = vsnprintf (&ptr[old_size], alloc - old_size, format, args_org);
M_ASSERT (size >= 0);
}
if (size >= 0) {
m_str1ng_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_org);
M_STR1NG_CONTRACT (v);
return size;
}
/* Append to the string the formatted string of the given printf format */
M_INLINE int
m_string_cat_printf (m_string_t v, const char format[], ...)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (format != NULL);
va_list args;
va_start (args, format);
int ret = m_string_cat_vprintf(v, format, args);
va_end (args);
return ret;
}
#if M_USE_FAST_STRING_CONV == 0
M_INLINE void
m_string_set_ui(m_string_t v, unsigned int n)
{
m_string_printf(v, "%u", n);
}
M_INLINE void
m_string_set_si(m_string_t v, int n)
{
m_string_printf(v, "%d", n);
}
#endif
#endif // Have stdarg
#if M_USE_STDIO
/* Get a line/pureline/file from the FILE and store it in the string */
M_INLINE bool
m_string_fgets(m_string_t v, FILE *f, m_string_fgets_t arg)
{
M_STR1NG_CONTRACT(v);
M_ASSERT (f != NULL);
char *ptr = m_str1ng_fit2size (v, 100);
size_t size = 0;
size_t alloc = m_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]);
M_ASSERT(size >= 1);
if (arg != M_STRING_READ_FILE && ptr[size-1] == '\n') {
if (arg == M_STRING_READ_PURE_LINE) {
size --;
ptr[size] = 0; /* Remove EOL */
}
m_str1ng_set_size(v, size);
M_STR1NG_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 = m_str1ng_fit2size (v, alloc + alloc/2);
alloc = m_string_capacity(v);
}
}
m_str1ng_set_size(v, size);
M_STR1NG_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.
*/
M_INLINE bool
m_string_fget_word (m_string_t v, const char separator[], FILE *f)
{
char buffer[128];
char c = 0;
int d;
M_STR1NG_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 = m_string_capacity(v);
char *ptr = m_str1ng_get_cstr(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 */
m_str1ng_set_size(v, size);
ptr = m_str1ng_fit2size (v, alloc + alloc/2);
alloc = m_string_capacity(v);
M_ASSERT (alloc > size + 1);
ptr[size++] = c;
ptr[size] = 0;
// Invalid c character for next iteration
c= 0;
}
m_str1ng_set_size(v, size);
M_STR1NG_CONTRACT(v);
return retcode;
}
/* Put the string in the given FILE without formatting */
M_INLINE bool
m_string_fputs(FILE *f, const m_string_t v)
{
M_STR1NG_CONTRACT (v);
M_ASSERT (f != NULL);
return fputs(m_string_get_cstr(v), f) >= 0;
}
#endif // Have stdio
/* Test if the string starts with the given C string */
M_INLINE bool
m_string_start_with_str_p(const m_string_t v, const char str[])
{
M_STR1NG_CONTRACT (v);
M_ASSERT (str != NULL);
const char *v_str = m_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 */
M_INLINE bool
m_string_start_with_string_p(const m_string_t v, const m_string_t v2)
{
M_STR1NG_CONTRACT (v2);
return m_string_start_with_str_p (v, m_string_get_cstr(v2));
}
/* Test if the string ends with the C string */
M_INLINE bool
m_string_end_with_str_p(const m_string_t v, const char str[])
{
M_STR1NG_CONTRACT (v);
M_ASSERT (str != NULL);
const char *v_str = m_string_get_cstr(v);
const size_t v_l = m_string_size(v);
const size_t str_l = strlen(str);
if (v_l < str_l)
return false;
return (memcmp(str, &v_str[v_l - str_l], str_l) == 0);
}
/* Test if the string ends with the other string */
M_INLINE bool
m_string_end_with_string_p(const m_string_t v, const m_string_t v2)
{
M_STR1NG_CONTRACT (v2);
return m_string_end_with_str_p (v, m_string_get_cstr(v2));
}
/* Compute a hash for the string */
M_INLINE size_t
m_string_hash(const m_string_t v)
{
M_STR1NG_CONTRACT (v);
return m_core_hash(m_string_get_cstr(v), m_string_size(v));
}
// Return true if c is a character from charac
M_INLINE bool
m_str1ng_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. */
M_INLINE void
m_string_strim(m_string_t v, const char charac[])
{
M_STR1NG_CONTRACT (v);
char *ptr = m_str1ng_get_cstr(v);
char *b = ptr;
size_t size = m_string_size(v);
while (size > 0 && m_str1ng_strim_char(b[size-1], charac))
size --;
if (size > 0) {
while (m_str1ng_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;
m_str1ng_set_size(v, size);
M_STR1NG_CONTRACT (v);
}
/* Test if the string is equal to the OOR value */
M_INLINE bool
m_string_oor_equal_p(const m_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 */
M_INLINE void
m_string_oor_set(m_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'. */
M_INLINE void
m_string_get_str(m_string_t v, const m_string_t v2, bool append)
{
M_STR1NG_CONTRACT(v2);
M_STR1NG_CONTRACT(v);
M_ASSERT (v != v2); // Limitation
size_t size = append ? m_string_size(v) : 0;
size_t v2_size = m_string_size(v2);
size_t targetSize = size + v2_size + 3;
char *ptr = m_str1ng_fit2size(v, targetSize);
ptr[size ++] = '"';
for(size_t i = 0 ; i < v2_size; i++) {
const char c = m_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.
m_str1ng_set_size(v, size);
ptr = m_str1ng_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((unsigned char) c))) {
targetSize += 3;
m_str1ng_set_size(v, size);
ptr = m_str1ng_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;
m_str1ng_set_size(v, size);
M_ASSERT (size <= targetSize);
M_STR1NG_CONTRACT (v);
}
#if M_USE_STDIO
/* Transform the string 'v2' into a formatted string
and output it in the given FILE */
M_INLINE void
m_string_out_str(FILE *f, const m_string_t v)
{
M_STR1NG_CONTRACT(v);
M_ASSERT (f != NULL);
fputc('"', f);
size_t size = m_string_size(v);
for(size_t i = 0 ; i < size; i++) {
const char c = m_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 */
M_INLINE bool
m_string_in_str(m_string_t v, FILE *f)
{
M_STR1NG_CONTRACT(v);
M_ASSERT (f != NULL);
int c = fgetc(f);
if (c != '"') return false;
m_string_reset(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;
}
}
m_string_push_back (v, (char) c);
c = fgetc(f);
}
return c == '"';
}
#endif // Have stdio
/* 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.
*/
M_INLINE bool
m_string_parse_str(m_string_t v, const char str[], const char **endptr)
{
M_STR1NG_CONTRACT(v);
M_ASSERT (str != NULL);
bool success = false;
int c = *str++;
if (c != '"') goto exit;
m_string_reset(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;
}
}
m_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.
*/
M_INLINE m_serial_return_code_t
m_string_out_serial(m_serial_write_t serial, const m_string_t v)
{
M_ASSERT (serial != NULL && serial->m_interface != NULL);
return serial->m_interface->write_string(serial, m_string_get_cstr(v), m_string_size(v) );
}
/* Read the formatted string from the serializer
and set the converted value in the string 'v'.
See serialization for return code.
*/
M_INLINE m_serial_return_code_t
m_string_in_serial(m_string_t v, m_serial_read_t serial)
{
M_ASSERT (serial != NULL && serial->m_interface != NULL);
return serial->m_interface->read_string(serial, v);
}
/* 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 M_STR1NG_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 byte, and the previous state and the previous value of the unicode code point.
It updates the state and the decoded unicode code point.
A decoded unicoded code point is valid only when the state is STARTING.
*/
M_INLINE void
m_str1ng_utf8_decode(char c, m_str1ng_utf8_state_e *state,
m_string_unicode_t *unicode)
{
const unsigned int type = m_core_clz32((unsigned char)~c) - (unsigned) (sizeof(uint32_t) - 1) * CHAR_BIT;
const m_string_unicode_t mask1 = (UINT32_MAX - (m_string_unicode_t)(*state != M_STR1NG_UTF8_STARTING) + 1);
const m_string_unicode_t mask2 = (0xFFU >> type);
const m_string_unicode_t c1 = (m_string_unicode_t) c;
*unicode = ((*unicode << 6) & mask1) | (c1 & mask2);
*state = (m_str1ng_utf8_state_e) M_STR1NG_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 always rejected */
M_INLINE bool
m_str1ng_utf8_valid_str_p(const char str[])
{
m_str1ng_utf8_state_e s = M_STR1NG_UTF8_STARTING;
m_string_unicode_t u = 0;
while (*str) {
m_str1ng_utf8_decode(*str, &s, &u);
if ((s == M_STR1NG_UTF8_ERROR)
||(s == M_STR1NG_UTF8_STARTING
&&(u > 0x10FFFF /* out of range */
||(u >= 0xD800 && u <= 0xDFFF) /* surrogate halves */)))
return false;
str++;
}
return true;
}
/* Test if the given byte is the start of an UTF8 code point */
M_INLINE bool
m_str1ng_utf8_start_p(unsigned char val)
{
return ((val & 0xC0u) != 0x80u);
}
/* Computer the number of unicode code points are encoded in the UTF8 stream */
M_INLINE size_t
m_str1ng_utf8_length(const char str[])
{
size_t size = 0;
while (*str) {
unsigned char val = (unsigned char) *str++;
size += m_str1ng_utf8_start_p(val);
}
return size;
}
/* Encode an unicode code point into an UTF8 stream */
M_INLINE int
m_str1ng_utf8_encode(char buffer[5], m_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;
}
}
/* Start iteration over the UTF8 encoded unicode code point */
M_INLINE void
m_string_it(m_string_it_t it, const m_string_t str)
{
M_STR1NG_CONTRACT(str);
M_ASSERT(it != NULL);
it->ptr = m_string_get_cstr(str);
it->u = 0;
it->string = str;
M_STR1NG_IT_CONTRACT(it);
}
/* Set the iterator to the end of string
The iterator references therefore nothing.
*/
M_INLINE void
m_string_it_end(m_string_it_t it, const m_string_t str)
{
M_STR1NG_CONTRACT(str);
M_ASSERT(it != NULL);
it->ptr = &m_string_get_cstr(str)[m_string_size(str)];
it->u = 0;
it->string = str;
M_STR1NG_IT_CONTRACT(it);
}
/* Set the iterator to the same position than the other one */
M_INLINE void
m_string_it_set(m_string_it_t it, const m_string_it_t itsrc)
{
M_ASSERT(it != NULL && itsrc != NULL);
M_STR1NG_IT_CONTRACT(itsrc);
it->ptr = itsrc->ptr;
it->u = itsrc->u;
it->string = itsrc->string;
M_STR1NG_IT_CONTRACT(it);
}
/* Set the iterator to the given position in the string.
The given position shall reference a valide code point in the string.
*/
M_INLINE void
m_string_it_pos(m_string_it_t it, const m_string_t str, const size_t n)
{
M_ASSERT(it != NULL);
M_STR1NG_CONTRACT(str);
// The offset shall be within the string
M_ASSERT(n <= m_string_size(str));
// The offset shall reference the first Byte of an UTF 8 Code point
M_ASSERT(m_str1ng_utf8_start_p ((unsigned char)m_string_get_cstr(str)[n]));
it->ptr = &m_string_get_cstr(str)[n];
it->u = 0;
it->string = str;
M_STR1NG_IT_CONTRACT(it);
}
/* Return the current offset in the string referenced by the iterator.
This references avalid code point.
*/
M_INLINE size_t
m_string_it_get_pos(m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
return (size_t) (it->ptr - m_string_get_cstr(it->string));
}
/* Test if the iterator has reached the end of the string. */
M_INLINE bool
m_string_end_p (m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
return it->ptr[0] == 0;
}
/* Test if the iterator is equal to the other one */
M_INLINE bool
m_string_it_equal_p(const m_string_it_t it1, const m_string_it_t it2)
{
M_STR1NG_IT_CONTRACT(it1);
M_STR1NG_IT_CONTRACT(it2);
return it1->ptr == it2->ptr;
}
/* Advance the iterator to the next UTF8 unicode code point */
M_INLINE void
m_string_next (m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
const char *ptr = it->ptr;
while (*ptr != 0) {
ptr ++;
if (m_str1ng_utf8_start_p((unsigned char) *ptr) ) {
/* Start of an UTF 8 code point */
break;
}
}
it->ptr = ptr;
return;
}
/* Move the iterator to the previous code point */
M_INLINE void
m_string_previous(m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
const char *ptr = it->ptr;
const char *org = m_string_get_cstr(it->string);
while (ptr > org) {
ptr --;
if (m_str1ng_utf8_start_p((unsigned char) *ptr) ) {
/* Start of an UTF 8 code point */
it->ptr = ptr;
return;
}
}
/* We reach the start of the string: mark the iterator to the end */
it->ptr = &org[m_string_size(it->string)];
M_STR1NG_IT_CONTRACT(it);
}
/* Return the unicode code point associated to the iterator */
M_INLINE m_string_unicode_t
m_string_get_cref (const m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
M_ASSERT(*it->ptr != 0);
m_str1ng_utf8_state_e state = M_STR1NG_UTF8_STARTING;
m_string_unicode_t u = 0;
const char *str = it->ptr;
do {
m_str1ng_utf8_decode(*str, &state, &u);
str++;
} while (state != M_STR1NG_UTF8_STARTING && state != M_STR1NG_UTF8_ERROR && *str != 0);
// Save where the current unicode value ends in the UTF8 steam
M_ASSERT( (str > it->ptr) && (str - it->ptr) <= M_STR1NG_MAX_BYTE_UTF8);
// Save the decoded unicode value
return M_UNLIKELY (state == M_STR1NG_UTF8_ERROR) ? M_STRING_UNICODE_ERROR : u;
}
/* Return the unicode code point associated to the iterator */
M_INLINE const m_string_unicode_t *
m_string_cref (m_string_it_t it)
{
M_STR1NG_IT_CONTRACT(it);
it->u = m_string_get_cref(it);
return &it->u;
}
/* Update the value referenced by the iterator to the given value */
M_INLINE void
m_string_it_set_ref(m_string_it_t it, m_string_t s, m_string_unicode_t new_u)
{
M_STR1NG_IT_CONTRACT(it);
M_STR1NG_CONTRACT(s);
M_ASSERT(s == it->string);
char *ptr = m_str1ng_get_cstr(s);
M_ASSUME( it->ptr >= ptr);
size_t offset = (size_t) (it->ptr - ptr);
// Special case if the unicode codepoint is 0
if (new_u == 0) {
// Null the string
m_str1ng_set_size(s, offset);
ptr[offset] = 0;
M_STR1NG_IT_CONTRACT(it);
M_STR1NG_CONTRACT(s);
return;
}
// Encode the new unicode code point & compute its size
char buffer[4+1];
m_str1ng_utf8_encode(buffer, new_u);
size_t new_u_size = strlen(buffer);
// Compute the size of the old unicode code point
m_str1ng_utf8_state_e state = M_STR1NG_UTF8_STARTING;
m_string_unicode_t old_u = 0;
const char *str = it->ptr;
do {
m_str1ng_utf8_decode(*str, &state, &old_u);
str++;
} while (state != M_STR1NG_UTF8_STARTING && state != M_STR1NG_UTF8_ERROR && *str != 0);
M_ASSUME( str > it->ptr);
size_t old_u_size = (size_t) (str - it->ptr);
// We need to replace old_u by new_u. Both are variable length
size_t str_size = m_string_size(s);
m_str1ng_fit2size(s, str_size + new_u_size - old_u_size + 1);
ptr = m_str1ng_get_cstr(s); // ptr may be reallocated!
if (new_u_size != old_u_size) {
M_ASSUME( str_size+1 > (offset + old_u_size) );
memmove(&ptr[offset+new_u_size], &ptr[offset + old_u_size],
str_size+1 - offset - old_u_size);
}
memcpy(&ptr[offset], &buffer[0], new_u_size);
m_str1ng_set_size(s, str_size + new_u_size - old_u_size);
it->ptr = &ptr[offset];
M_STR1NG_IT_CONTRACT(it);
M_STR1NG_CONTRACT(s);
return;
}
/* Push unicode code point into string, encoding it in UTF8 */
M_INLINE void
m_string_push_u (m_string_t str, m_string_unicode_t u)
{
M_STR1NG_CONTRACT(str);
char buffer[4+1];
m_str1ng_utf8_encode(buffer, u);
m_string_cat_cstr(str, buffer);
}
/* Pop last unicode code point into string, encoding it in UTF8 */
M_INLINE bool
m_string_pop_u(m_string_unicode_t *u, m_string_t str)
{
M_STR1NG_CONTRACT(str);
char *org = m_str1ng_get_cstr(str);
size_t len = m_string_size(str);
// From the last byte in the string
while (len > 0) {
len--;
// Test if it is a start of an UTF8 code point
if (m_str1ng_utf8_start_p((unsigned char) org[len])) {
// Yes, so decode the UTF8
m_str1ng_utf8_state_e state = M_STR1NG_UTF8_STARTING;
const char *tmp = &org[len];
// Support of NULL pointer
m_string_unicode_t u_tmp;
m_string_unicode_t *u_ptr = u == NULL ? &u_tmp : u;
do {
m_str1ng_utf8_decode(*tmp++, &state, u_ptr);
} while (state != M_STR1NG_UTF8_STARTING && state != M_STR1NG_UTF8_ERROR);
// Final null char for the string
org[len] = 0;
m_str1ng_set_size(str, len);
M_STR1NG_CONTRACT(str);
return true;
}
}
// Fail to pop a unicode code
return false;
}
/* Compute the length in UTF8 code points in the string */
M_INLINE size_t
m_string_length_u(m_string_t str)
{
M_STR1NG_CONTRACT(str);
return m_str1ng_utf8_length(m_string_get_cstr(str));
}
/* Check if a string is a valid UTF8 encoded stream */
M_INLINE bool
m_string_utf8_p(m_string_t str)
{
M_STR1NG_CONTRACT(str);
return m_str1ng_utf8_valid_str_p(m_string_get_cstr(str));
}
/* Define the split & the join functions
in case of usage with the algorithm module */
#define M_STR1NG_SPLIT(name, oplist, type_oplist) \
M_INLINE void M_F(name, _split)(M_GET_TYPE oplist cont, \
const m_string_t str, const char sep) \
{ \
size_t begin = 0; \
m_string_t tmp; \
size_t size = m_string_size(str); \
m_string_init(tmp); \
M_CALL_RESET(oplist, cont); \
for(size_t i = 0 ; i < size; i++) { \
char c = m_string_get_char(str, i); \
if (c == sep) { \
m_string_set_cstrn(tmp, &m_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); \
m_string_init(tmp); \
, \
M_CALL_PUSH(oplist, cont, tmp); \
) \
begin = i + 1; \
} \
} \
m_string_set_cstrn(tmp, &m_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);, ) \
m_string_clear(tmp); \
} \
\
M_INLINE void M_F(name, _join)(m_string_t dst, M_GET_TYPE oplist cont, \
const m_string_t str) \
{ \
bool init_done = false; \
m_string_reset (dst); \
for M_EACH(item, cont, oplist) { \
if (init_done) { \
m_string_cat(dst, str); \
} \
m_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 m_string_hash).
Otherwise it could have been :
#define M_STRING_CTE(s) \
((const m_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 M_STRING_CTE(s) \
(m_string_srcptr)((const m_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_lib {
template <unsigned int N>
struct m_aligned_string {
m_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 M_STRING_CTE(s) \
m_lib::m_aligned_string<sizeof (s)>(s).string
#endif
/* Initialize and set a string to the given formatted value. */
#define m_string_init_printf(v, ...) \
(m_string_printf ( m_str1ng_init_ref(v), __VA_ARGS__) )
/* Initialize and set a string to the given formatted value. */
#define m_string_init_vprintf(v, format, args) \
(m_string_vprintf ( m_str1ng_init_ref(v), format, 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 m_string_init_set_cstr.
In C11 mode, it uses the fact that m_string_init_set is overloaded to handle both
C string and string. */
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L)
#define M_STR1NG_INIT_WITH(v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(m_string_init_set, m_string_init_printf)(v, __VA_ARGS__)
#else
#define M_STR1NG_INIT_WITH(v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(m_string_init_set_cstr, m_string_init_printf)(v, __VA_ARGS__)
#endif
/* NOTE: Use GCC extension (OBSOLETE) */
#define M_STRING_DECL_INIT(v) \
m_string_t v __attribute__((cleanup(m_str1ng_clear2))) = {{ 0, 0, NULL}}
/* NOTE: Use GCC extension (OBSOLETE) */
#define M_STRING_DECL_INIT_PRINTF(v, format, ...) \
M_STRING_DECL_INIT(v); \
m_string_printf (v, format, __VA_ARGS__)
/* Define the OPLIST of a STRING */
#define M_STRING_OPLIST \
(INIT(m_string_init),INIT_SET(m_string_init_set), SET(m_string_set), \
INIT_WITH(M_STR1NG_INIT_WITH), \
INIT_MOVE(m_string_init_move), MOVE(m_string_move), \
SWAP(m_string_swap), RESET(m_string_reset), \
EMPTY_P(m_string_empty_p), \
CLEAR(m_string_clear), HASH(m_string_hash), EQUAL(m_string_equal_p), \
CMP(m_string_cmp), TYPE(m_string_t), \
PARSE_STR(m_string_parse_str), GET_STR(m_string_get_str), \
OUT_STR(m_string_out_str), IN_STR(m_string_in_str), \
OUT_SERIAL(m_string_out_serial), IN_SERIAL(m_string_in_serial), \
EXT_ALGO(M_STR1NG_SPLIT), \
OOR_EQUAL(m_string_oor_equal_p), OOR_SET(m_string_oor_set) \
,SUBTYPE(m_string_unicode_t) \
,IT_TYPE(m_string_it_t) \
,IT_FIRST(m_string_it) \
,IT_END(m_string_it_end) \
,IT_SET(m_string_it_set) \
,IT_END_P(m_string_end_p) \
,IT_EQUAL_P(m_string_it_equal_p) \
,IT_NEXT(m_string_next) \
,IT_CREF(m_string_cref) \
,EMPLACE_TYPE(const char*) \
)
/* Register the OPLIST as a global one */
#define M_OPL_m_string_t() M_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 m_string_search_char(...) \
m_string_search_char(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/* Reverse Search for a character in a string (string, character[, start=0]) */
#define m_string_search_rchar(...) \
m_string_search_rchar(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/* Search for a C string in a string (string, c_string[, start=0]) */
#define m_string_search_cstr(...) \
m_string_search_cstr(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/* Search for a string in a string (string, string[, start=0]) */
#define m_string_search(...) \
m_string_search(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/* PBRK for a string in a string (string, string[, start=0]) */
#define m_string_search_pbrk(...) \
m_string_search_pbrk(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/* Replace a C string to another C string in a string (string, c_src_string, c_dst_string, [, start=0]) */
#define m_string_replace_cstr(...) \
m_string_replace_cstr(M_DEFAULT_ARGS(4, (0), __VA_ARGS__))
/* Replace a string to another string in a string (string, src_string, dst_string, [, start=0]) */
#define m_string_replace(...) \
m_string_replace(M_DEFAULT_ARGS(4, (0), __VA_ARGS__))
/* Strim a string from the given set of characters (default is " \n\r\t") */
#define m_string_strim(...) \
m_string_strim(M_DEFAULT_ARGS(2, (" \n\r\t"), __VA_ARGS__))
/* Concat a set strings (or const char * if C11)) into one string */
#define m_string_cats(a, ...) \
M_MAP2_C(m_string_cat, a, __VA_ARGS__)
/* Set a string to a set strings (or const char * if C11)) */
#define m_string_sets(a, ...) \
(m_string_reset(a), M_MAP2_C(m_string_cat, a, __VA_ARGS__) )
/* Macro encapsulation for C11 */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* Select either the string function or the C string function depending on
the b parameter of the function.
func1 is the string function / func2 is the str function. */
# define M_STR1NG_SELECT2(func1,func2,a,b) \
_Generic((b)+0, \
char*: func2, \
const char *: func2, \
default : func1 \
)(a,b)
# define M_STR1NG_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 m_string_init_set(a,b) M_STR1NG_SELECT2(m_string_init_set, m_string_init_set_cstr, a, b)
/* Set the string a to the string (or C string) b */
#define m_string_set(a,b) M_STR1NG_SELECT2(m_string_set, m_string_set_cstr, a, b)
/* Concatene the string (or C string) b to the string a */
#define m_string_cat(a,b) M_STR1NG_SELECT2(m_string_cat, m_string_cat_cstr, a, b)
/* Compare the string a to the string (or C string) b and return the sort order */
#define m_string_cmp(a,b) M_STR1NG_SELECT2(m_string_cmp, m_string_cmp_cstr, a, b)
/* Compare for equality the string a to the string (or C string) b */
#define m_string_equal_p(a,b) M_STR1NG_SELECT2(m_string_equal_p, m_string_equal_cstr_p, a, b)
/* strcoll the string a to the string (or C string) b */
#define m_string_strcoll(a,b) M_STR1NG_SELECT2(m_string_strcoll, m_string_strcoll_cstr, a, b)
#undef m_string_search
/* Search for a string in a string (or C string) (string, string[, start=0]) */
#define m_string_search(...) \
M_APPLY(M_STR1NG_SELECT3, m_string_search, m_string_search_cstr, \
M_DEFAULT_ARGS(3, (0), __VA_ARGS__) )
#endif
/***********************************************************************/
/* */
/* Override m-core default macros to integrate m_string_t in core */
/* */
/***********************************************************************/
/* 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)
/* Internal Macro: Provide support of m_string_t to the macro M_PRINT in C11 */
// Extend the format specifier to support m_string_t
#undef M_PRINTF_FORMAT_EXTEND
#define M_PRINTF_FORMAT_EXTEND() \
, m_string_ptr: "%s" \
, m_string_srcptr: "%s"
// Add type conversion for m_string_t (into a const char*)
#undef M_CORE_PRINTF_ARG
#define M_CORE_PRINTF_ARG(x) _Generic( ((void)0,(x)) \
, m_string_ptr: m_string_get_cstr( M_AS_TYPE(m_string_ptr, x)) \
, m_string_srcptr: m_string_get_cstr(M_AS_TYPE(m_string_srcptr,x)) \
, default: x )
/* 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 M_BOUNDED_STRING_DEF(name, max_size) \
M_BEGIN_PROTECTED_CODE \
M_BOUNDED_STR1NG_DEF_P2(name, max_size, M_F(name, _t) ) \
M_END_PROTECTED_CODE
/* Define the OPLIST of a BOUNDED_STRING */
#define M_BOUNDED_STRING_OPLIST(name) \
(INIT(M_F(name,_init)), \
INIT_SET(M_F(name,_init_set)), \
SET(M_F(name,_set)), \
CLEAR(M_F(name,_clear)), \
NAME(name), \
INIT_WITH( API_1(M_BOUNDED_STR1NG_INIT_WITH)), \
HASH(M_F(name,_hash)), \
EQUAL(M_F(name,_equal_p)), \
CMP(M_F(name,_cmp)), \
TYPE(M_F(name,_ct)), \
OOR_EQUAL(M_F(name,_oor_equal_p)), \
OOR_SET(M_F(name, _oor_set)), \
PARSE_STR(M_F(name,_parse_str)), \
GET_STR(M_F(name,_get_str)), \
OUT_STR(M_F(name,_out_str)), \
IN_STR(M_F(name,_in_str)), \
OUT_SERIAL(M_F(name,_out_serial)), \
IN_SERIAL(M_F(name,_in_serial)), \
)
/************************** INTERNAL ***********************************/
/* Contract of a bounded string.
* A bounded string last characters is always zero. */
#define M_BOUNDED_STR1NG_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 M_BOUNDED_STR1NG_DEF_P2(name, max_size, bounded_t) \
\
/* Define of an array with one more to store the final nul char */ \
typedef char M_F(name, _array_t)[max_size+1]; \
\
typedef struct M_F(name, _s) { \
char s[max_size+1]; \
} bounded_t[1]; \
\
/* Internal types for oplist */ \
typedef bounded_t M_F(name, _ct); \
\
M_INLINE void \
M_F(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; \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
} \
\
M_INLINE void \
M_F(name, _clear)(bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
/* Make the object illegal to be able to detect use after free */ \
s->s[max_size] = 0x1F; \
} \
\
M_INLINE void \
M_F(name, _reset)(bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
s->s[0] = 0; \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
} \
\
M_INLINE size_t \
M_F(name, _size)(const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
return strlen(s->s); \
} \
\
M_INLINE size_t \
M_F(name, _capacity)(const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
(void)s; /* unused */ \
return max_size+1; \
} \
\
M_INLINE char \
M_F(name, _get_char)(const bounded_t s, size_t index) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT_INDEX(index, max_size); \
return s->s[index]; \
} \
\
M_INLINE bool \
M_F(name, _empty_p)(const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
return s->s[0] == 0; \
} \
\
M_INLINE void \
M_F(name, _set_cstr)(bounded_t s, const char str[]) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
m_core_strncpy(s->s, max_size+1, str, max_size); \
s->s[max_size] = 0; \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
} \
\
M_INLINE void \
M_F(name, _set_cstrn)(bounded_t s, const char str[], size_t n) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
size_t len = M_MIN(max_size, n); \
m_core_strncpy(s->s, max_size+1, str, len); \
s->s[len] = 0; \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
} \
\
M_INLINE const char * \
M_F(name, _get_cstr)(const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
return s->s; \
} \
\
M_INLINE void \
M_F(name, _set)(bounded_t s, const bounded_t str) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_BOUNDED_STR1NG_CONTRACT(str, max_size); \
M_F(name, _set_cstr)(s, str->s); \
} \
\
M_INLINE void \
M_F(name, _set_n)(bounded_t s, const bounded_t str, \
size_t offset, size_t length) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_BOUNDED_STR1NG_CONTRACT(str, max_size); \
M_ASSERT_INDEX (offset, max_size+1); \
M_F(name, _set_cstrn)(s, str->s+offset, length); \
} \
\
M_INLINE void \
M_F(name, _init_set)(bounded_t s, const bounded_t str) \
{ \
M_F(name,_init)(s); \
M_F(name,_set)(s, str); \
} \
\
M_INLINE void \
M_F(name, _init_set_cstr)(bounded_t s, const char str[]) \
{ \
M_F(name,_init)(s); \
M_F(name,_set_cstr)(s, str); \
} \
\
M_INLINE void \
M_F(name, _cat_cstr)(bounded_t s, const char str[]) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT (str != NULL); \
M_ASSERT_INDEX (strlen(s->s), max_size+1); \
m_core_strncat(s->s, max_size+1, str, max_size-strlen(s->s)); \
} \
\
M_INLINE void \
M_F(name, _cat)(bounded_t s, const bounded_t str) \
{ \
M_BOUNDED_STR1NG_CONTRACT(str, max_size); \
M_F(name, _cat_cstr)(s, str->s); \
} \
\
M_INLINE int \
M_F(name, _cmp_cstr)(const bounded_t s, const char str[]) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
return strcmp(s->s, str); \
} \
\
M_INLINE int \
M_F(name, _cmp)(const bounded_t s, const bounded_t str) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_BOUNDED_STR1NG_CONTRACT(str, max_size); \
return strcmp(s->s, str->s); \
} \
\
M_INLINE bool \
M_F(name, _equal_cstr_p)(const bounded_t s, const char str[]) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(str != NULL); \
return strcmp(s->s, str) == 0; \
} \
\
M_INLINE bool \
M_F(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); \
} \
\
M_INLINE int \
M_F(name, _printf)(bounded_t s, const char format[], ...) \
{ \
M_BOUNDED_STR1NG_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; \
} \
\
M_INLINE int \
M_F(name, _cat_printf)(bounded_t s, const char format[], ...) \
{ \
M_BOUNDED_STR1NG_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; \
} \
\
M_INLINE bool \
M_F(name, _fgets)(bounded_t s, FILE *f, m_string_fgets_t arg) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
M_ASSERT(arg != M_STRING_READ_FILE); \
char *ret = fgets(s->s, max_size+1, f); \
s->s[max_size] = 0; \
if (ret != NULL && arg == M_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; \
} \
\
M_INLINE bool \
M_F(name, _fputs)(FILE *f, const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
return fputs(s->s, f) >= 0; \
} \
\
M_INLINE size_t \
M_F(name, _hash)(const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
/* Cannot use m_core_hash: alignment not sufficent */ \
return m_core_cstr_hash(s->s); \
} \
\
M_INLINE bool \
M_F(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); \
} \
\
M_INLINE void \
M_F(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); \
} \
\
M_INLINE void \
M_F(name, _get_str)(m_string_t v, const bounded_t s, bool append) \
{ \
M_STR1NG_CONTRACT(v); \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
/* Build dummy string to reuse m_string_get_str */ \
uintptr_t ptr = (uintptr_t) &s->s[0]; \
m_string_t v2; \
v2->u.heap.size = strlen(s->s); \
v2->u.heap.alloc = v2->u.heap.size + 1; \
v2->ptr = (char*)ptr; \
m_string_get_str(v, v2, append); \
} \
\
M_INLINE void \
M_F(name, _out_str)(FILE *f, const bounded_t s) \
{ \
M_BOUNDED_STR1NG_CONTRACT(s, max_size); \
M_ASSERT(f != NULL); \
/* Build dummy string to reuse m_string_get_str */ \
uintptr_t ptr = (uintptr_t) &s->s[0]; \
m_string_t v2; \
v2->u.heap.size = strlen(s->s); \
v2->u.heap.alloc = v2->u.heap.size + 1; \
v2->ptr = (char*)ptr; \
m_string_out_str(f, v2); \
} \
\
M_INLINE bool \
M_F(name, _in_str)(bounded_t v, FILE *f) \
{ \
M_BOUNDED_STR1NG_CONTRACT(v, max_size); \
M_ASSERT(f != NULL); \
m_string_t v2; \
m_string_init(v2); \
bool ret = m_string_in_str(v2, f); \
m_core_strncpy(v->s, max_size+1, m_string_get_cstr(v2), max_size); \
m_string_clear(v2); \
return ret; \
} \
\
M_INLINE bool \
M_F(name, _parse_str)(bounded_t v, const char str[], const char **endptr) \
{ \
M_BOUNDED_STR1NG_CONTRACT(v, max_size); \
M_ASSERT(str != NULL); \
m_string_t v2; \
m_string_init(v2); \
bool ret = m_string_parse_str(v2, str, endptr); \
m_core_strncpy(v->s, max_size+1, m_string_get_cstr(v2), max_size); \
m_string_clear(v2); \
return ret; \
} \
\
M_INLINE m_serial_return_code_t \
M_F(name, _out_serial)(m_serial_write_t serial, const bounded_t v) \
{ \
M_BOUNDED_STR1NG_CONTRACT(v, max_size); \
M_ASSERT (serial != NULL && serial->m_interface != NULL); \
return serial->m_interface->write_string(serial, v->s, strlen(v->s) ); \
} \
\
M_INLINE m_serial_return_code_t \
M_F(name, _in_serial)(bounded_t v, m_serial_read_t serial) \
{ \
M_BOUNDED_STR1NG_CONTRACT(v, max_size); \
M_ASSERT (serial != NULL && serial->m_interface != NULL); \
m_string_t tmp; \
/* TODO: Not optimum */ \
m_string_init(tmp); \
m_serial_return_code_t r = serial->m_interface->read_string(serial, tmp); \
m_core_strncpy(v->s, max_size+1, m_string_get_cstr(tmp), max_size); \
m_string_clear(tmp); \
M_BOUNDED_STR1NG_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 M_BOUNDED_STRING_CTE(name, string) \
((const struct M_F(name, _s) *)((M_F(name, _array_t)){string}))
#else
namespace m_lib {
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, N, lstr, N-1);
}
};
}
#define M_BOUNDED_STRING_CTE(name, string) \
((const struct M_F(name, _s) *)(m_lib::m_bounded_string<sizeof (M_F(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 m_string_init_set_cstr.
*/
#define M_BOUNDED_STR1NG_INIT_WITH(oplist, v, ...) \
M_IF_NARGS_EQ1(__VA_ARGS__)(M_C(M_GET_NAME oplist, _init_set_cstr)(v, __VA_ARGS__), M_BOUNDED_STR1NG_INIT_PRINTF(oplist, v, __VA_ARGS__))
#define M_BOUNDED_STR1NG_INIT_PRINTF(oplist, v, ...) \
(M_GET_INIT oplist (v), M_C(M_GET_NAME oplist, _printf)(v, __VA_ARGS__))
/********************************************************************************/
/* */
/* Define the small name (i.e. without the prefix) of the API provided by this */
/* header if it is needed */
/* */
/********************************************************************************/
#if M_USE_SMALL_NAME
#define string_t m_string_t
#define string_s m_string_s
#define STRING_FAILURE M_STRING_FAILURE
#define string_ptr m_string_ptr
#define string_srcptr m_string_srcptr
#define STRING_READ_LINE M_STRING_READ_LINE
#define STRING_READ_PURE_LINE M_STRING_READ_PURE_LINE
#define STRING_READ_FILE M_STRING_READ_FILE
#define string_fgets_t m_string_fgets_t
#define string_unicode_t m_string_unicode_t
#define STRING_UNICODE_ERROR M_STRING_UNICODE_ERROR
#define string_it_t m_string_it_t
#define string_size m_string_size
#define string_capacity m_string_capacity
#define string_get_cstr m_string_get_cstr
#define string_init m_string_init
#define string_clear m_string_clear
#define string_clear_get_str m_string_clear_get_cstr
#define string_reset m_string_reset
#define string_get_char m_string_get_char
#define string_set_char m_string_set_char
#define string_empty_p m_string_empty_p
#define string_reserve m_string_reserve
#define string_set_str m_string_set_cstr
#define string_set_strn m_string_set_cstrn
#define string_set m_string_set
#define string_set_n m_string_set_n
#define string_init_set m_string_init_set
#define string_init_set_str m_string_init_set_cstr
#define string_init_move m_string_init_move
#define string_swap m_string_swap
#define string_move m_string_move
#define string_push_back m_string_push_back
#define string_cat_str m_string_cat_cstr
#define string_cat m_string_cat
#define string_cmp_str m_string_cmp_cstr
#define string_cmp m_string_cmp
#define string_equal_str_p m_string_equal_cstr_p
#define string_equal_p m_string_equal_p
#define string_cmpi_str m_string_cmpi_cstr
#define string_cmpi m_string_cmpi
#define string_search_char m_string_search_char
#define string_search_rchar m_string_search_rchar
#define string_search_str m_string_search_cstr
#define string_search m_string_search
#define string_search_pbrk m_string_search_pbrk
#define string_strcoll_str m_string_strcoll_cstr
#define string_strcoll m_string_strcoll
#define string_spn m_string_spn
#define string_cspn m_string_cspn
#define string_left m_string_left
#define string_right m_string_right
#define string_mid m_string_mid
#define string_replace_str m_string_replace_cstr
#define string_replace m_string_replace
#define string_replace_at m_string_replace_at
#define string_replace_all_str m_string_replace_all_cstr
#define string_replace_all m_string_replace_all
#define string_vprintf m_string_vprintf
#define string_printf m_string_printf
#define string_cat_vprintf m_string_cat_vprintf
#define string_cat_printf m_string_cat_printf
#define string_fgets m_string_fgets
#define string_fget_word m_string_fget_word
#define string_fputs m_string_fputs
#define string_start_with_str_p m_string_start_with_str_p
#define string_start_with_string_p m_string_start_with_string_p
#define string_end_with_str_p m_string_end_with_str_p
#define string_end_with_string_p m_string_end_with_string_p
#define string_hash m_string_hash
#define string_strim m_string_strim
#define string_oor_equal_p m_string_oor_equal_p
#define string_oor_set m_string_oor_set
#define string_get_str m_string_get_str
#define string_out_str m_string_out_str
#define string_in_str m_string_in_str
#define string_parse_str m_string_parse_str
#define string_out_serial m_string_out_serial
#define string_in_serial m_string_in_serial
#define string_it m_string_it
#define string_it_end m_string_it_end
#define string_it_set m_string_it_set
#define string_end_p m_string_end_p
#define string_it_equal_p m_string_it_equal_p
#define string_next m_string_next
#define string_get_cref m_string_get_cref
#define string_cref m_string_cref
#define string_push_u m_string_push_u
#define string_pop_u m_string_pop_u
#define string_length_u m_string_length_u
#define string_utf8_p m_string_utf8_p
#define string_set_ui m_string_set_ui
#define string_set_si m_string_set_si
#define string_it_pos m_string_it_pos
#define string_it_get_pos m_string_it_get_pos
#define string_previous m_string_previous
#define string_it_set_ref m_string_it_set_ref
#define STRING_CTE M_STRING_CTE
#define STRING_DECL_INIT M_STRING_DECL_INIT
#define STRING_DECL_INIT_PRINTF M_STRING_DECL_INIT_PRINTF
#define STRING_OPLIST M_STRING_OPLIST
#define M_OPL_string_t M_OPL_m_string_t
#define string_sets m_string_sets
#define string_cats m_string_cats
#define string_init_printf m_string_init_printf
#define string_init_vprintf m_string_init_vprintf
#define BOUNDED_STRING_DEF M_BOUNDED_STRING_DEF
#define BOUNDED_STRING_OPLIST M_BOUNDED_STRING_OPLIST
#define BOUNDED_STRING_CTE M_BOUNDED_STRING_CTE
#endif
M_END_PROTECTED_CODE
#endif