blob: a3c17b8fdd932c90501a9b752233401fdefd3b47 [file] [edit]
/* String descriptors.
Copyright (C) 2023-2026 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Bruno Haible <bruno@clisp.org>, 2023. */
#ifndef _STRING_DESC_H
#define _STRING_DESC_H 1
/* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE,
_GL_ATTRIBUTE_NODISCARD, _GL_ATTRIBUTE_NONNULL,
_GL_ATTRIBUTE_NONNULL_IF_NONZERO. */
#if !_GL_CONFIG_H_INCLUDED
#error "Please include config.h first."
#endif
/* Get ptrdiff_t. */
#include <stddef.h>
/* Get FILE. */
#include <stdio.h>
/* Get abort(), free(). */
#include <stdlib.h>
/* Get idx_t. */
#include "idx.h"
/* Whether the compiler supports statement-expressions.
Test program:
int f (int x) { return ({ x; x; }); }
*/
#if defined __GNUC__ /* both C and C++ mode */ \
|| defined __clang__ /* both C and C++ mode */ \
|| (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) /* C mode */ \
|| (defined __SUNPRO_CC && __SUNPRO_CC >= 0x5150) /* C++ mode */
# define HAVE_STATEMENT_EXPRESSIONS 1
#endif
/* Whether the compiler supports _Generic.
Test program:
int f (int x) { return _Generic (x, char *: 2, int: 3); }
*/
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 9) > 4 && !defined __cplusplus) /* C mode */ \
|| (defined __clang__ && __clang_major__ >= 3) /* both C and C++ mode */ \
|| (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) /* C mode */ \
|| (__STDC_VERSION__ >= 201112L && !defined __GNUC__) /* C mode */
# define HAVE__GENERIC 1
#endif
/* Whether the compiler supports typeof.
Test program:
int f (int x) { typeof (x) y = x; return y; }
*/
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 1) > 3) /* both C and C++ mode */ \
|| (defined __clang__ && __clang_major__ >= 3 /* both C and C++ mode */ \
&& !(defined __cplusplus && !defined __GNUC__)) /* except for clang-cl in C++ mode */ \
|| (defined __SUNPRO_C && __SUNPRO_C >= 0x5110) /* C mode */ \
|| __STDC_VERSION__ >= 202311L /* C mode */
# define HAVE_TYPEOF 1
#endif
/* Whether the compiler supports __builtin_choose_expr.
_Generic and __builtin_choose_expr are like conditional expressions,
except that the return types of the branches need not match: They avoid an
"error: type mismatch in conditional expression".
Test program:
int f (int x) { return __builtin_choose_expr (sizeof (x) == 4, 2, 3); }
*/
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 1) > 3) /* both C and C++ mode */ \
|| (defined __clang__ && __clang_major__ >= 3) /* both C and C++ mode */
# define HAVE_BUILTIN_CHOOSE_EXPR 1
#endif
/* Whether the compiler supports __builtin_constant_p and whether that built-in
returns true for string literals.
_Generic has an antiquated treatment of string literals: It treats string
literals like 'char *', not 'const char *'. To compensate for this, we need
__builtin_constant_p.
Test program:
int i, v[__builtin_constant_p ("x") > __builtin_constant_p (i) ? 1 : -1];
*/
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 1) > 3) /* both C and C++ mode */ \
|| (defined __clang__ && __clang_major__ >= 4) /* both C and C++ mode */
# define HAVE_BUILTIN_CONSTANT_P 1
#endif
/* Whether we support rw_string_desc_t as distinct from string_desc_t. */
#if HAVE_STATEMENT_EXPRESSIONS && HAVE__GENERIC && HAVE_TYPEOF \
&& HAVE_BUILTIN_CHOOSE_EXPR && HAVE_BUILTIN_CONSTANT_P
# define HAVE_RW_STRING_DESC 1
#endif
_GL_INLINE_HEADER_BEGIN
#ifndef GL_STRING_DESC_INLINE
# define GL_STRING_DESC_INLINE _GL_INLINE
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Type describing a string that may contain NUL bytes.
It's merely a descriptor of an array of bytes.
It comes in two flavours:
- string_desc_t is read-only,
- rw_string_desc_t is read-write.
When we write
[rw_]string_desc_t
it means either string_desc_t or rw_string_desc_t. */
typedef struct rw_string_desc_t rw_string_desc_t;
struct rw_string_desc_t
{
/* The fields of this struct should be considered private. */
idx_t _nbytes;
char *_data;
};
#if HAVE_RW_STRING_DESC
typedef struct string_desc_t string_desc_t;
struct string_desc_t
{
/* The fields of this struct should be considered private. */
idx_t _nbytes;
const char *_data;
};
#else
typedef rw_string_desc_t string_desc_t;
#endif
/* String descriptors can be passed and returned by value.
String descriptors and NUL-terminated 'const char *'/'char *' C strings
cannot be used interchangeably. You will get compilation errors if you
attempt to assign a string descriptor to a C string or vice versa. */
/* ==== Conversions between string descriptors ==== */
/* Return a read-only view of S. */
#if 0 /* Defined inline below. */
extern string_desc_t sd_readonly (rw_string_desc_t s);
#endif
/* Return a read-only view of S.
Attention: This function is as dangerous as a cast from 'const char *'
to 'char *'! */
#if 0 /* Defined inline below. */
extern rw_string_desc_t sd_readwrite (string_desc_t s);
#endif
/* ==== Side-effect-free operations on string descriptors ==== */
/* Return the length of the string S. */
#if 0 /* Defined inline below. */
extern idx_t sd_length ([rw_]string_desc_t s);
#endif
/* Return the byte at index I of string S.
I must be < length(S). */
#if 0 /* Defined inline below. */
extern char sd_char_at ([rw_]string_desc_t s, idx_t i);
#endif
/* Return a read-only view of the bytes of S. */
#if 0 /* Defined inline below. */
extern const char * sd_data (string_desc_t s);
extern char * sd_data (rw_string_desc_t s);
#endif
/* Return true if S is the empty string. */
#if 0 /* Defined inline below. */
extern bool sd_is_empty ([rw_]string_desc_t s);
#endif
/* Return true if A and B are equal. */
#if 0 /* Defined inline below. */
extern bool sd_equals ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif
/* Return true if S starts with PREFIX. */
#if 0 /* Defined inline below. */
extern bool sd_startswith ([rw_]string_desc_t s, [rw_]string_desc_t prefix);
#endif
/* Return true if S ends with SUFFIX. */
#if 0 /* Defined inline below. */
extern bool sd_endswith ([rw_]string_desc_t s, [rw_]string_desc_t suffix);
#endif
/* Return > 0, == 0, or < 0 if A > B, A == B, A < B.
This uses a lexicographic ordering, where the bytes are compared as
'unsigned char'. */
#if 0 /* Defined inline below. */
extern int sd_cmp ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif
/* Return > 0, == 0, or < 0 if A > B, A == B, A < B.
Either A or B must be entirely ASCII.
This uses a lexicographic ordering, where the bytes are compared as
'unsigned char', ignoring case, in the "C" locale. */
#if 0 /* Defined inline below. */
extern int sd_c_casecmp ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif
/* Return the index of the first occurrence of C in S,
or -1 if there is none. */
#if 0 /* Defined inline below. */
extern ptrdiff_t sd_index ([rw_]string_desc_t s, char c);
#endif
/* Return the index of the last occurrence of C in S,
or -1 if there is none. */
#if 0 /* Defined inline below. */
extern ptrdiff_t sd_last_index ([rw_]string_desc_t s, char c);
#endif
/* Return the index of the first occurrence of NEEDLE in HAYSTACK,
or -1 if there is none. */
#if 0 /* Defined inline below. */
extern ptrdiff_t sd_contains ([rw_]string_desc_t haystack, [rw_]string_desc_t needle);
#endif
/* Return an empty string. */
extern string_desc_t sd_new_empty (void);
/* Construct and return a string of length N, at the given memory address. */
#if 0 /* Defined inline below. */
extern string_desc_t sd_new_addr (idx_t n, const char *addr)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
extern rw_string_desc_t sd_new_addr (idx_t n, char *addr)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#endif
/* Return a string that represents the C string S, of length strlen (S). */
extern string_desc_t sd_from_c (const char *s);
/* Return the substring of S, starting at offset START and ending at offset END.
START must be <= END.
The result is of length END - START.
The result must not be freed (since its storage is part of the storage
of S). */
#if 0 /* Defined inline below. */
extern string_desc_t sd_substring (string_desc_t s, idx_t start, idx_t end);
extern rw_string_desc_t sd_substring (rw_string_desc_t s, idx_t start, idx_t end);
#endif
/* Output S to the file descriptor FD.
Return 0 if successful.
Upon error, return -1 with errno set. */
#if 0 /* Defined inline below. */
extern int sd_write (int fd, [rw_]string_desc_t s);
#endif
/* Output S to the FILE stream FP.
Return 0 if successful.
Upon error, return -1. */
#if 0 /* Defined inline below. */
extern int sd_fwrite (FILE *fp, [rw_]string_desc_t s);
#endif
/* ==== Memory-allocating operations on string descriptors ==== */
/* Construct a string of length N, with uninitialized contents.
Return 0 if successful.
Upon error, return -1 with errno set. */
_GL_ATTRIBUTE_NODISCARD
extern int sd_new (rw_string_desc_t *resultp, idx_t n);
/* Construct a string of length N, filled with C.
Return 0 if successful.
Upon error, return -1 with errno set. */
_GL_ATTRIBUTE_NODISCARD
extern int sd_new_filled (rw_string_desc_t *resultp, idx_t n, char c);
/* Construct a copy of string S.
Return 0 if successful.
Upon error, return -1 with errno set. */
#if 0 /* Defined inline below. */
_GL_ATTRIBUTE_NODISCARD
extern int sd_copy (rw_string_desc_t *resultp, [rw_]string_desc_t s);
#endif
/* Construct the concatenation of N strings. N must be > 0.
Return 0 if successful.
Upon error, return -1 with errno set. */
_GL_ATTRIBUTE_NODISCARD
extern int sd_concat (rw_string_desc_t *resultp, idx_t n, /* [rw_]string_desc_t string1, */ ...);
/* Construct a copy of string S, as a NUL-terminated C string.
Return it is successful.
Upon error, return NULL with errno set. */
#if 0 /* Defined inline below. */
extern char * sd_c ([rw_]string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE;
#endif
/* ==== Operations with side effects on string descriptors ==== */
/* Overwrite the byte at index I of string S with C.
I must be < length(S). */
extern void sd_set_char_at (rw_string_desc_t s, idx_t i, char c);
/* Fill part of S, starting at offset START and ending at offset END,
with copies of C.
START must be <= END. */
extern void sd_fill (rw_string_desc_t s, idx_t start, idx_t end, char c);
/* Overwrite part of S with T, starting at offset START.
START + length(T) must be <= length (S). */
#if 0 /* Defined inline below. */
extern void sd_overwrite (rw_string_desc_t s, idx_t start, [rw_]string_desc_t t);
#endif
/* Free S. */
extern void sd_free (rw_string_desc_t s);
/* ==== Inline function definitions ==== */
#if HAVE_RW_STRING_DESC
GL_STRING_DESC_INLINE string_desc_t
sd_readonly (rw_string_desc_t s)
{
string_desc_t result;
result._nbytes = s._nbytes;
result._data = s._data;
return result;
}
#else
# define sd_readonly(s) (s)
#endif
#if HAVE_RW_STRING_DESC
GL_STRING_DESC_INLINE rw_string_desc_t
sd_readwrite (string_desc_t s)
{
rw_string_desc_t result;
result._nbytes = s._nbytes;
result._data = (char *) s._data;
return result;
}
#else
# define sd_readwrite(s) (s)
#endif
#if HAVE_RW_STRING_DESC
# define sd_length(s) \
_Generic (s, \
string_desc_t : (s)._nbytes, \
rw_string_desc_t : (s)._nbytes)
#else
GL_STRING_DESC_INLINE idx_t
sd_length (string_desc_t s)
{
return s._nbytes;
}
#endif
#if HAVE_RW_STRING_DESC
# define sd_char_at(s, i) \
({typeof (s) _s_ = (s); \
_sd_char_at (_s_._nbytes, _s_._data, i); \
})
GL_STRING_DESC_INLINE char
_sd_char_at (idx_t s_nbytes, const char *s_data, idx_t i)
_GL_ATTRIBUTE_NONNULL ((2));
GL_STRING_DESC_INLINE char
_sd_char_at (idx_t s_nbytes, const char *s_data, idx_t i)
{
if (!(i >= 0 && i < s_nbytes))
/* Invalid argument. */
abort ();
return s_data[i];
}
#else
GL_STRING_DESC_INLINE char
sd_char_at (string_desc_t s, idx_t i)
{
if (!(i >= 0 && i < s._nbytes))
/* Invalid argument. */
abort ();
return s._data[i];
}
#endif
#if HAVE_RW_STRING_DESC
# define sd_data(s) \
_Generic (s, \
string_desc_t : (s)._data, \
rw_string_desc_t : (s)._data)
#else
GL_STRING_DESC_INLINE const char *
sd_data (string_desc_t s)
{
return s._data;
}
#endif
#if HAVE_RW_STRING_DESC
# define sd_is_empty(s) \
_Generic (s, \
string_desc_t : (s)._nbytes == 0, \
rw_string_desc_t : (s)._nbytes == 0)
#else
GL_STRING_DESC_INLINE bool
sd_is_empty (string_desc_t s)
{
return s._nbytes == 0;
}
#endif
extern bool _sd_equals (idx_t a_nbytes, const char *a_data,
idx_t b_nbytes, const char *b_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_equals(a, b) \
({typeof (a) _a_ = (a); \
typeof (b) _b_ = (b); \
_sd_equals (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
})
#else
GL_STRING_DESC_INLINE bool
sd_equals (string_desc_t a, string_desc_t b)
{
return _sd_equals (a._nbytes, a._data, b._nbytes, b._data);
}
#endif
extern bool _sd_startswith (idx_t s_nbytes, const char *s_data,
idx_t prefix_nbytes, const char *prefix_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_startswith(s, prefix) \
({typeof (s) _s_ = (s); \
typeof (prefix) _prefix_ = (prefix); \
_sd_startswith (_s_._nbytes, _s_._data, _prefix_._nbytes, _prefix_._data); \
})
#else
GL_STRING_DESC_INLINE bool
sd_startswith (string_desc_t s, string_desc_t prefix)
{
return _sd_startswith (s._nbytes, s._data, prefix._nbytes, prefix._data);
}
#endif
extern bool _sd_endswith (idx_t s_nbytes, const char *s_data,
idx_t suffix_nbytes, const char *suffix_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_endswith(s, suffix) \
({typeof (s) _s_ = (s); \
typeof (suffix) _suffix_ = (suffix); \
_sd_endswith (_s_._nbytes, _s_._data, _suffix_._nbytes, _suffix_._data); \
})
#else
GL_STRING_DESC_INLINE bool
sd_endswith (string_desc_t s, string_desc_t suffix)
{
return _sd_endswith (s._nbytes, s._data, suffix._nbytes, suffix._data);
}
#endif
extern int _sd_cmp (idx_t a_nbytes, const char *a_data,
idx_t b_nbytes, const char *b_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_cmp(a, b) \
({typeof (a) _a_ = (a); \
typeof (b) _b_ = (b); \
_sd_cmp (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
})
#else
GL_STRING_DESC_INLINE int
sd_cmp (string_desc_t a, string_desc_t b)
{
return _sd_cmp (a._nbytes, a._data, b._nbytes, b._data);
}
#endif
extern int _sd_c_casecmp (idx_t a_nbytes, const char *a_data,
idx_t b_nbytes, const char *b_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_c_casecmp(a, b) \
({typeof (a) _a_ = (a); \
typeof (b) _b_ = (b); \
_sd_c_casecmp (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
})
#else
GL_STRING_DESC_INLINE int
sd_c_casecmp (string_desc_t a, string_desc_t b)
{
return _sd_c_casecmp (a._nbytes, a._data, b._nbytes, b._data);
}
#endif
extern ptrdiff_t _sd_index (idx_t s_nbytes, const char *s_data, char c)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_index(s, c) \
({typeof (s) _s_ = (s); \
_sd_index (_s_._nbytes, _s_._data, c); \
})
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_index (string_desc_t s, char c)
{
return _sd_index (s._nbytes, s._data, c);
}
#endif
extern ptrdiff_t _sd_last_index (idx_t s_nbytes, const char *s_data, char c)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_last_index(s, c) \
({typeof (s) _s_ = (s); \
_sd_last_index (_s_._nbytes, _s_._data, c); \
})
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_last_index (string_desc_t s, char c)
{
return _sd_last_index (s._nbytes, s._data, c);
}
#endif
extern ptrdiff_t _sd_contains (idx_t haystack_nbytes, const char *haystack_data,
idx_t needle_nbytes, const char *needle_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_contains(haystack, needle) \
({typeof (haystack) _haystack_ = (haystack); \
typeof (needle) _needle_ = (needle); \
_sd_contains (_haystack_._nbytes, _haystack_._data, \
_needle_._nbytes, _needle_._data); \
})
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_contains (string_desc_t haystack, string_desc_t needle)
{
return _sd_contains (haystack._nbytes, haystack._data,
needle._nbytes, needle._data);
}
#endif
extern string_desc_t _sd_new_addr (idx_t n, const char *addr)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
extern rw_string_desc_t _rwsd_new_addr (idx_t n, /*!*/const/*!*/ char *addr)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_new_addr(n, addr) \
_Generic (addr, \
const char * : _sd_new_addr (n, addr), \
char * : /* Treat string literals like 'const char *'. */ \
__builtin_choose_expr (__builtin_constant_p (addr), \
_sd_new_addr (n, addr), \
_rwsd_new_addr (n, addr)))
#else
# define sd_new_addr(n, addr) \
_rwsd_new_addr (n, addr)
#endif
#if HAVE_RW_STRING_DESC
# define sd_substring(s, start, end) \
({typeof (s) _s_ = (s); \
idx_t _start_ = (start); \
idx_t _end_ = (end); \
if (!(_start_ >= 0 && _start_ <= _end_ && _end_ <= _s_._nbytes)) \
/* Invalid arguments. */ \
abort (); \
typeof (s) _result_; \
_result_._nbytes = _end_ - _start_; \
_result_._data = _s_._data + _start_; \
_result_; \
})
#else
GL_STRING_DESC_INLINE string_desc_t
sd_substring (string_desc_t s, idx_t start, idx_t end)
{
if (!(start >= 0 && start <= end && end <= s._nbytes))
/* Invalid arguments. */
abort ();
string_desc_t result;
result._nbytes = end - start;
result._data = s._data + start;
return result;
}
#endif
extern int _sd_write (int fd, idx_t s_nbytes, const char *s_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_write(fd, s) \
({int _fd_ = (fd); \
typeof (s) _s_ = (s); \
_sd_write (_fd_, _s_._nbytes, _s_._data); \
})
#else
GL_STRING_DESC_INLINE int
sd_write (int fd, string_desc_t s)
{
return _sd_write (fd, s._nbytes, s._data);
}
#endif
extern int _sd_fwrite (FILE *fp, idx_t s_nbytes, const char *s_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_fwrite(fp, s) \
({FILE *_fp_ = (fp); \
typeof (s) _s_ = (s); \
_sd_fwrite (_fp_, _s_._nbytes, _s_._data); \
})
#else
GL_STRING_DESC_INLINE int
sd_fwrite (FILE *fp, string_desc_t s)
{
return _sd_fwrite (fp, s._nbytes, s._data);
}
#endif
extern int _sd_copy (rw_string_desc_t *resultp,
idx_t s_nbytes, const char *s_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_copy(resultp, s) \
({rw_string_desc_t *_resultp_ = (resultp); \
typeof (s) _s_ = (s); \
_sd_copy (_resultp_, _s_._nbytes, _s_._data); \
})
#else
_GL_ATTRIBUTE_NODISCARD GL_STRING_DESC_INLINE int
sd_copy (rw_string_desc_t *resultp, string_desc_t s)
{
return _sd_copy (resultp, s._nbytes, s._data);
}
#endif
extern char *_sd_c (idx_t s_nbytes, const char *s_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_c(s) \
({typeof (s) _s_ = (s); \
_sd_c (_s_._nbytes, _s_._data); \
})
#else
GL_STRING_DESC_INLINE char *
sd_c (string_desc_t s)
{
return _sd_c (s._nbytes, s._data);
}
#endif
extern void _sd_overwrite (rw_string_desc_t s, idx_t start,
idx_t t_nbytes, const char *t_data)
_GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_overwrite(s, start, t) \
({rw_string_desc_t _s_ = (s); \
idx_t _start_ = (start); \
typeof (t) _t_ = (t); \
_sd_overwrite (_s_, _start_, _t_._nbytes, _t_._data); \
})
#else
GL_STRING_DESC_INLINE void
sd_overwrite (rw_string_desc_t s, idx_t start, string_desc_t t)
{
_sd_overwrite (s, start, t._nbytes, t._data);
}
#endif
#ifdef __cplusplus
}
#endif
_GL_INLINE_HEADER_END
#endif /* _STRING_DESC_H */