There's one thing I'm not really satisfied with, regarding the 'string-desc' module:
* It makes it too easy to confuse read-only strings and read/write strings. For the classical C strings, the confusion was fixed by distinguishing the 'const char *' and 'char *' types. * I'm seeing -Wdiscarded-qualifiers warnings in a gnulib build: ../../gltests/test-string-buffer-reversed.c:113:48: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-buffer.c:112:46: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc-quotearg.c:32:39: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:125:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:126:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:127:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:128:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:186:45: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:192:49: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:193:48: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:194:48: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:195:48: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:39:39: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:73:46: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:74:47: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:82:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:83:44: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-string-desc.c:84:45: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-xstring-desc.c:33:39: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-xstring-desc.c:67:39: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-xstring-desc.c:68:38: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-xstring-desc.c:69:38: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type ../../gltests/test-xstring-desc.c:70:38: warning: passing argument 2 of 'sd_new_addr' discards 'const' qualifier from pointer target type * In code that uses sd_new_addr, I had to artificially cast the second argument from 'const char *' to 'char *', to match the parameter type. This patch fixes this problem, by distinguishing read-only strings and writable strings. Obviously, I did not want to duplicate the functions, e.g. sd_length to sd_length and rwsd_length. A single function should be applicable to both read-only strings and writable strings. Unfortunately the C type system is not helpful in this direction. But I could make it work by using a combination of - statement-expressions, - _Generic, - typeof, - and inline functions. Thus the implementation is more complex than before, but the programs that use 'string-desc' get better type safety with fewer casts. 2025-05-09 Bruno Haible <br...@clisp.org> string-desc: Distinguish writable strings from read-only strings. * lib/string-desc.h (HAVE_STATEMENT_EXPRESSIONS): New macro. (rw_string_desc_t): New type. (string_desc_t) [HAVE_STATEMENT_EXPRESSIONS]: Change field _data from 'char *' to 'const char *'. (sd_readonly, sd_readwrite): New inline functions. (sd_length): Define through a macro with _Generic. (sd_char_at): Define through a macro and an inline function. (sd_data, sd_is_empty): Define through a macro with _Generic. (sd_equals, sd_startswith, sd_endswith, sd_cmp, sd_c_casecmp, sd_index, sd_last_index, sd_contains): Define through a macro. (sd_new_addr): Define through a macro with _Generic. (sd_substring, sd_write, sd_fwrite): Define through a macro. (sd_new, sd_new_filled): Change parameter type. (sd_copy): Define through a macro. (sd_concat): Change parameter type. (sd_c): Define through a macro. (sd_set_char_at, sd_fill): Change parameter type. (sd_overwrite): Define through a macro. (sd_free): Change parameter type. * lib/string-desc.c (_sd_equals): Renamed from sd_equals. Take scalar parameters. (_sd_startswith): Renamed from sd_startswith. Take scalar parameters. (_sd_endswith): Renamed from sd_endswith. Take scalar parameters. (_sd_cmp): Renamed from sd_cmp. Take scalar parameters. (_sd_c_casecmp): Renamed from sd_c_casecmp. Take scalar parameters. (_sd_index): Renamed from sd_index. Take scalar parameters. (_sd_last_index): Renamed from sd_last_index. Take scalar parameters. (_sd_new_addr, _rwsd_new_addr): Renamed from sd_new_addr. (sd_substring): Remove function. (_sd_write): Renamed from sd_write. Take scalar parameters. (_sd_fwrite): Renamed from sd_fwrite. Take scalar parameters. (sd_new, sd_new_filled): Change parameter type. (_sd_copy): Renamed from sd_copy. Change parameter type. Take scalar parameters. (sd_concat): Change parameter type. (_sd_c): Renamed from sd_c. Take scalar parameters. (sd_set_char_at, sd_fill): Change parameter type. (_sd_overwrite): Renamed from sd_overwrite. Change parameter type. Take scalar parameters. (sd_free): Change parameter type. * lib/string-desc-contains.c (_sd_contains): Renamed from sd_contains. Take scalar parameters. * lib/xstring-desc.h (xsd_new, xsd_new_filled, xsd_copy, xsd_concat): Change return type to rw_string_desc_t. (xsd_c): Define through a macro. * lib/xstring-desc.c (xsd_concat): Change return type to rw_string_desc_t. * doc/string-desc.texi (Handling strings with NUL characters): Mention rw_string_desc_t and the sd_readonly() function. * lib/string-buffer.h (sb_dupfree, sb_xdupfree): Change return type to rw_string_desc_t. * lib/string-buffer.c (sb_contents): Add a cast to 'const char *'. (sb_dupfree): Change return type to rw_string_desc_t. * lib/xstring-buffer.c (sb_xdupfree): Change return type to rw_string_desc_t. * lib/string-buffer-reversed.h (sbr_dupfree, sbr_xdupfree): Change return type to rw_string_desc_t. * lib/string-buffer-reversed.c (sbr_contents): Add a cast to 'const char *'. (sbr_dupfree): Change return type to rw_string_desc_t. * lib/xstring-buffer-reversed.c (sbr_xdupfree): Change return type to rw_string_desc_t. * tests/test-string-desc.c (main): Use type rw_string_desc_t as appropriate. * tests/test-xstring-desc.c (main): Likewise. * tests/test-sf-istream.c (main): Remove cast in sd_new_addr argument. * tests/test-sfl-istream.c (main): Likewise. * NEWS: Mention the change. diff --git a/NEWS b/NEWS index 639760a5b9..5499535f2a 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,10 @@ User visible incompatible changes Date Modules Changes +2025-05-09 string-desc These modules now distinguish between read-only + xstring-desc string descriptors (type string_desc_t) and + writable string descriptors (type rw_string_desc_t). + 2025-05-09 copy-acl The mode_t arguments of qcopy_acl and xcopy_acl qcopy-acl should be the full st_mode of the source, not merely st_mode's permission bits. diff --git a/doc/string-desc.texi b/doc/string-desc.texi index ab5c5fced3..997c384302 100644 --- a/doc/string-desc.texi +++ b/doc/string-desc.texi @@ -53,13 +53,27 @@ same concept as @code{std::string_view} in C++, or the @code{String} type in Java. -A @code{string_desc_t} can be passed to a function as an argument, or +@code{string_desc_t} is a string descriptor to a string that cannot +be written to. There is also a type @code{rw_string_desc_t}, that is +a descriptor for a writable string. +@code{rw_string_desc_t} compares to @code{string_desc_t}, like the +pointer type @samp{char *} compares to the pointer type +@samp{const char *}. + +A @code{string_desc_t} or @code{rw_string_desc_t} +can be passed to a function as an argument, or can be the return value of a function. This is type-safe: If, by mistake, a programmer passes a @code{string_desc_t} to a function that expects a @code{char *} argument, or vice versa, or assigns a @code{string_desc_t} value to a variable of type @code{char *}, or vice versa, the compiler will report an error. +Unfortunately, @code{string_desc_t} and @code{rw_string_desc_t} +being different types, there is no implicit conversion from +@code{rw_string_desc_t} to @code{string_desc_t}. In places +where such a conversion is desired, the (inline) function +@code{sd_readonly} needs to be called. + Functions related to string descriptors are provided: @itemize @item diff --git a/lib/string-buffer-reversed.c b/lib/string-buffer-reversed.c index 4f093f66dc..8b55650bdf 100644 --- a/lib/string-buffer-reversed.c +++ b/lib/string-buffer-reversed.c @@ -148,7 +148,7 @@ string_desc_t sbr_contents (struct string_buffer_reversed *buffer) { return sd_new_addr (buffer->length - 1, - buffer->data + buffer->allocated - buffer->length); + (const char *) buffer->data + buffer->allocated - buffer->length); } const char * @@ -157,7 +157,7 @@ sbr_contents_c (struct string_buffer_reversed *buffer) return buffer->data + buffer->allocated - buffer->length; } -string_desc_t +rw_string_desc_t sbr_dupfree (struct string_buffer_reversed *buffer) { if (buffer->oom || buffer->error) @@ -185,7 +185,7 @@ sbr_dupfree (struct string_buffer_reversed *buffer) fail: sbr_free (buffer); - return sd_new_addr (0, NULL); + return sd_new_addr (0, (char *) NULL); } char * diff --git a/lib/string-buffer-reversed.h b/lib/string-buffer-reversed.h index 021508c2e9..957af427b5 100644 --- a/lib/string-buffer-reversed.h +++ b/lib/string-buffer-reversed.h @@ -131,7 +131,7 @@ extern const char * sbr_contents_c (struct string_buffer_reversed *buffer); /* Returns the contents of BUFFER and frees all other memory held by BUFFER. Returns NULL upon failure or if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ -extern string_desc_t sbr_dupfree (struct string_buffer_reversed *buffer) +extern rw_string_desc_t sbr_dupfree (struct string_buffer_reversed *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); /* Returns the contents of BUFFER (with an added trailing NUL, that is, @@ -196,7 +196,7 @@ extern int sbr_xprependf (struct string_buffer_reversed *buffer, /* Returns the contents of BUFFER and frees all other memory held by BUFFER. Returns (0, NULL) if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ -extern string_desc_t sbr_xdupfree (struct string_buffer_reversed *buffer) +extern rw_string_desc_t sbr_xdupfree (struct string_buffer_reversed *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); /* Returns the contents of BUFFER (with an added trailing NUL, that is, diff --git a/lib/string-buffer.c b/lib/string-buffer.c index ac1606f23d..50a326dd23 100644 --- a/lib/string-buffer.c +++ b/lib/string-buffer.c @@ -137,7 +137,7 @@ sb_free (struct string_buffer *buffer) string_desc_t sb_contents (struct string_buffer *buffer) { - return sd_new_addr (buffer->length, buffer->data); + return sd_new_addr (buffer->length, (const char *) buffer->data); } const char * @@ -150,7 +150,7 @@ sb_contents_c (struct string_buffer *buffer) return buffer->data; } -string_desc_t +rw_string_desc_t sb_dupfree (struct string_buffer *buffer) { if (buffer->oom || buffer->error) @@ -180,7 +180,7 @@ sb_dupfree (struct string_buffer *buffer) fail: sb_free (buffer); - return sd_new_addr (0, NULL); + return sd_new_addr (0, (char *) NULL); } char * diff --git a/lib/string-buffer.h b/lib/string-buffer.h index eefdd195d7..99f5d40bb0 100644 --- a/lib/string-buffer.h +++ b/lib/string-buffer.h @@ -129,7 +129,7 @@ extern const char * sb_contents_c (struct string_buffer *buffer); /* Returns the contents of BUFFER and frees all other memory held by BUFFER. Returns NULL upon failure or if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ -extern string_desc_t sb_dupfree (struct string_buffer *buffer) +extern rw_string_desc_t sb_dupfree (struct string_buffer *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); /* Returns the contents of BUFFER (with an added trailing NUL, that is, @@ -200,7 +200,7 @@ extern const char * sb_xcontents_c (struct string_buffer *buffer) /* Returns the contents of BUFFER and frees all other memory held by BUFFER. Returns (0, NULL) if there was an error earlier. It is the responsibility of the caller to sd_free() the result. */ -extern string_desc_t sb_xdupfree (struct string_buffer *buffer) +extern rw_string_desc_t sb_xdupfree (struct string_buffer *buffer) _GL_ATTRIBUTE_RELEASE_CAPABILITY (buffer->data); /* Returns the contents of BUFFER (with an added trailing NUL, that is, diff --git a/lib/string-desc-contains.c b/lib/string-desc-contains.c index 18657c70ff..b7943bcddd 100644 --- a/lib/string-desc-contains.c +++ b/lib/string-desc-contains.c @@ -31,16 +31,17 @@ which — depending on platforms — costs up to 2 KB of binary code. */ ptrdiff_t -sd_contains (string_desc_t haystack, string_desc_t needle) +_sd_contains (idx_t haystack_nbytes, const char *haystack_data, + idx_t needle_nbytes, const char *needle_data) { - if (needle._nbytes == 0) + if (needle_nbytes == 0) return 0; - if (haystack._nbytes == 0) + if (haystack_nbytes == 0) return -1; void *found = - memmem (haystack._data, haystack._nbytes, needle._data, needle._nbytes); + memmem (haystack_data, haystack_nbytes, needle_data, needle_nbytes); if (found != NULL) - return (char *) found - haystack._data; + return (char *) found - haystack_data; else return -1; } diff --git a/lib/string-desc.c b/lib/string-desc.c index dc13f2f150..7cabe4ed84 100644 --- a/lib/string-desc.c +++ b/lib/string-desc.c @@ -39,94 +39,95 @@ /* Return true if A and B are equal. */ bool -sd_equals (string_desc_t a, string_desc_t b) +_sd_equals (idx_t a_nbytes, const char *a_data, + idx_t b_nbytes, const char *b_data) { - return (a._nbytes == b._nbytes - && (a._nbytes == 0 || memcmp (a._data, b._data, a._nbytes) == 0)); + return (a_nbytes == b_nbytes + && (a_nbytes == 0 || memcmp (a_data, b_data, a_nbytes) == 0)); } bool -sd_startswith (string_desc_t s, string_desc_t prefix) +_sd_startswith (idx_t s_nbytes, const char *s_data, + idx_t prefix_nbytes, const char *prefix_data) { - return (s._nbytes >= prefix._nbytes - && (prefix._nbytes == 0 - || memcmp (s._data, prefix._data, prefix._nbytes) == 0)); + return (s_nbytes >= prefix_nbytes + && (prefix_nbytes == 0 + || memcmp (s_data, prefix_data, prefix_nbytes) == 0)); } bool -sd_endswith (string_desc_t s, string_desc_t suffix) +_sd_endswith (idx_t s_nbytes, const char *s_data, + idx_t suffix_nbytes, const char *suffix_data) { - return (s._nbytes >= suffix._nbytes - && (suffix._nbytes == 0 - || memcmp (s._data + (s._nbytes - suffix._nbytes), suffix._data, - suffix._nbytes) == 0)); + return (s_nbytes >= suffix_nbytes + && (suffix_nbytes == 0 + || memcmp (s_data + (s_nbytes - suffix_nbytes), suffix_data, + suffix_nbytes) == 0)); } int -sd_cmp (string_desc_t a, string_desc_t b) +_sd_cmp (idx_t a_nbytes, const char *a_data, + idx_t b_nbytes, const char *b_data) { - if (a._nbytes > b._nbytes) + if (a_nbytes > b_nbytes) { - if (b._nbytes == 0) + if (b_nbytes == 0) return 1; - return (memcmp (a._data, b._data, b._nbytes) < 0 ? -1 : 1); + return (memcmp (a_data, b_data, b_nbytes) < 0 ? -1 : 1); } - else if (a._nbytes < b._nbytes) + else if (a_nbytes < b_nbytes) { - if (a._nbytes == 0) + if (a_nbytes == 0) return -1; - return (memcmp (a._data, b._data, a._nbytes) > 0 ? 1 : -1); + return (memcmp (a_data, b_data, a_nbytes) > 0 ? 1 : -1); } - else /* a._nbytes == b._nbytes */ + else /* a_nbytes == b_nbytes */ { - if (a._nbytes == 0) + if (a_nbytes == 0) return 0; - return memcmp (a._data, b._data, a._nbytes); + return memcmp (a_data, b_data, a_nbytes); } } int -sd_c_casecmp (string_desc_t a, string_desc_t b) +_sd_c_casecmp (idx_t a_nbytes, const char *a_data, + idx_t b_nbytes, const char *b_data) { /* Don't use memcasecmp here, since it uses the current locale, not the "C" locale. */ - idx_t an = sd_length (a); - idx_t bn = sd_length (b); - const char *ap = sd_data (a); - const char *bp = sd_data (b); - idx_t n = (an < bn ? an : bn); + idx_t n = (a_nbytes < b_nbytes ? a_nbytes : b_nbytes); idx_t i; for (i = 0; i < n; i++) { - int ac = c_tolower ((unsigned char) ap[i]); - int bc = c_tolower ((unsigned char) bp[i]); + int ac = c_tolower ((unsigned char) a_data[i]); + int bc = c_tolower ((unsigned char) b_data[i]); if (ac != bc) return (UCHAR_MAX <= INT_MAX ? ac - bc : _GL_CMP (ac, bc)); } - /* Here i = n = min (an, bn). */ - return _GL_CMP (an, bn); + /* Here i = n = min (a_nbytes, b_nbytes). */ + return _GL_CMP (a_nbytes, b_nbytes); } ptrdiff_t -sd_index (string_desc_t s, char c) +_sd_index (idx_t s_nbytes, const char *s_data, char c) { - if (s._nbytes > 0) + if (s_nbytes > 0) { - void *found = memchr (s._data, (unsigned char) c, s._nbytes); + void *found = memchr (s_data, (unsigned char) c, s_nbytes); if (found != NULL) - return (char *) found - s._data; + return (char *) found - s_data; } return -1; } ptrdiff_t -sd_last_index (string_desc_t s, char c) +_sd_last_index (idx_t s_nbytes, const char *s_data, char c) { - if (s._nbytes > 0) + if (s_nbytes > 0) { - void *found = memrchr (s._data, (unsigned char) c, s._nbytes); + void *found = memrchr (s_data, (unsigned char) c, s_nbytes); if (found != NULL) - return (char *) found - s._data; + return (char *) found - s_data; } return -1; } @@ -140,11 +141,15 @@ sd_new_empty (void) result._data = NULL; return result; - } +#if __GNUC__ >= 5 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" +#endif + string_desc_t -sd_new_addr (idx_t n, char *addr) +_sd_new_addr (idx_t n, const char *addr) { string_desc_t result; @@ -152,52 +157,54 @@ sd_new_addr (idx_t n, char *addr) if (n == 0) result._data = NULL; else - result._data = addr; + result._data = (char *) addr; return result; } - -string_desc_t -sd_from_c (const char *s) +rw_string_desc_t +_rwsd_new_addr (idx_t n, /*!*/const/*!*/ char *addr) { - string_desc_t result; + rw_string_desc_t result; - result._nbytes = strlen (s); - result._data = (char *) s; + result._nbytes = n; + if (n == 0) + result._data = NULL; + else + result._data = (char *) addr; return result; } string_desc_t -sd_substring (string_desc_t s, idx_t start, idx_t end) +sd_from_c (const char *s) { string_desc_t result; - if (!(start >= 0 && start <= end && end <= s._nbytes)) - /* Invalid arguments. */ - abort (); - - result._nbytes = end - start; - result._data = s._data + start; + result._nbytes = strlen (s); + result._data = (char *) s; return result; } +#if __GNUC__ >= 5 +# pragma GCC diagnostic pop +#endif + int -sd_write (int fd, string_desc_t s) +_sd_write (int fd, idx_t s_nbytes, const char *s_data) { - if (s._nbytes > 0) - if (full_write (fd, s._data, s._nbytes) != s._nbytes) + if (s_nbytes > 0) + if (full_write (fd, s_data, s_nbytes) != s_nbytes) /* errno is set here. */ return -1; return 0; } int -sd_fwrite (FILE *fp, string_desc_t s) +_sd_fwrite (FILE *fp, idx_t s_nbytes, const char *s_data) { - if (s._nbytes > 0) - if (fwrite (s._data, 1, s._nbytes, fp) != s._nbytes) + if (s_nbytes > 0) + if (fwrite (s_data, 1, s_nbytes, fp) != s_nbytes) return -1; return 0; } @@ -206,9 +213,9 @@ sd_fwrite (FILE *fp, string_desc_t s) /* ==== Memory-allocating operations on string descriptors ==== */ int -sd_new (string_desc_t *resultp, idx_t n) +sd_new (rw_string_desc_t *resultp, idx_t n) { - string_desc_t result; + rw_string_desc_t result; if (!(n >= 0)) /* Invalid argument. */ @@ -230,9 +237,9 @@ sd_new (string_desc_t *resultp, idx_t n) } int -sd_new_filled (string_desc_t *resultp, idx_t n, char c) +sd_new_filled (rw_string_desc_t *resultp, idx_t n, char c) { - string_desc_t result; + rw_string_desc_t result; result._nbytes = n; if (n == 0) @@ -251,10 +258,10 @@ sd_new_filled (string_desc_t *resultp, idx_t n, char c) } int -sd_copy (string_desc_t *resultp, string_desc_t s) +_sd_copy (rw_string_desc_t *resultp, idx_t s_nbytes, const char *s_data) { - string_desc_t result; - idx_t n = s._nbytes; + rw_string_desc_t result; + idx_t n = s_nbytes; result._nbytes = n; if (n == 0) @@ -265,7 +272,7 @@ sd_copy (string_desc_t *resultp, string_desc_t s) if (result._data == NULL) /* errno is set here. */ return -1; - memcpy (result._data, s._data, n); + memcpy (result._data, s_data, n); } *resultp = result; @@ -273,52 +280,58 @@ sd_copy (string_desc_t *resultp, string_desc_t s) } int -sd_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...) +sd_concat (rw_string_desc_t *resultp, idx_t n, /* [rw_]string_desc_t string1, */ ...) { if (n <= 0) /* Invalid argument. */ abort (); idx_t total = 0; - total += string1._nbytes; - if (n > 1) - { - va_list other_strings; - idx_t i; - - va_start (other_strings, string1); - for (i = n - 1; i > 0; i--) - { - string_desc_t arg = va_arg (other_strings, string_desc_t); - total += arg._nbytes; - } - va_end (other_strings); - } + { + va_list strings; + va_start (strings, n); + string_desc_t string1 = va_arg (strings, string_desc_t); + total += string1._nbytes; + if (n > 1) + { + idx_t i; + + for (i = n - 1; i > 0; i--) + { + string_desc_t arg = va_arg (strings, string_desc_t); + total += arg._nbytes; + } + } + va_end (strings); + } char *combined = (char *) imalloc (total); if (combined == NULL) /* errno is set here. */ return -1; idx_t pos = 0; - memcpy (combined, string1._data, string1._nbytes); - pos += string1._nbytes; - if (n > 1) - { - va_list other_strings; - idx_t i; - - va_start (other_strings, string1); - for (i = n - 1; i > 0; i--) - { - string_desc_t arg = va_arg (other_strings, string_desc_t); - if (arg._nbytes > 0) - memcpy (combined + pos, arg._data, arg._nbytes); - pos += arg._nbytes; - } - va_end (other_strings); - } - - string_desc_t result; + { + va_list strings; + va_start (strings, n); + string_desc_t string1 = va_arg (strings, string_desc_t); + memcpy (combined, string1._data, string1._nbytes); + pos += string1._nbytes; + if (n > 1) + { + idx_t i; + + for (i = n - 1; i > 0; i--) + { + string_desc_t arg = va_arg (strings, string_desc_t); + if (arg._nbytes > 0) + memcpy (combined + pos, arg._data, arg._nbytes); + pos += arg._nbytes; + } + } + va_end (strings); + } + + rw_string_desc_t result; result._nbytes = total; result._data = combined; @@ -327,15 +340,15 @@ sd_concat (string_desc_t *resultp, idx_t n, string_desc_t string1, ...) } char * -sd_c (string_desc_t s) +_sd_c (idx_t s_nbytes, const char *s_data) { - idx_t n = s._nbytes; + idx_t n = s_nbytes; char *result = (char *) imalloc (n + 1); if (result == NULL) /* errno is set here. */ return NULL; if (n > 0) - memcpy (result, s._data, n); + memcpy (result, s_data, n); result[n] = '\0'; return result; @@ -345,7 +358,7 @@ sd_c (string_desc_t s) /* ==== Operations with side effects on string descriptors ==== */ void -sd_set_char_at (string_desc_t s, idx_t i, char c) +sd_set_char_at (rw_string_desc_t s, idx_t i, char c) { if (!(i >= 0 && i < s._nbytes)) /* Invalid argument. */ @@ -354,7 +367,7 @@ sd_set_char_at (string_desc_t s, idx_t i, char c) } void -sd_fill (string_desc_t s, idx_t start, idx_t end, char c) +sd_fill (rw_string_desc_t s, idx_t start, idx_t end, char c) { if (!(start >= 0 && start <= end)) /* Invalid arguments. */ @@ -365,18 +378,18 @@ sd_fill (string_desc_t s, idx_t start, idx_t end, char c) } void -sd_overwrite (string_desc_t s, idx_t start, string_desc_t t) +_sd_overwrite (rw_string_desc_t s, idx_t start, idx_t t_nbytes, const char *t_data) { - if (!(start >= 0 && start + t._nbytes <= s._nbytes)) + if (!(start >= 0 && start + t_nbytes <= s._nbytes)) /* Invalid arguments. */ abort (); - if (t._nbytes > 0) - memcpy (s._data + start, t._data, t._nbytes); + if (t_nbytes > 0) + memcpy (s._data + start, t_data, t_nbytes); } void -sd_free (string_desc_t s) +sd_free (rw_string_desc_t s) { free (s._data); } diff --git a/lib/string-desc.h b/lib/string-desc.h index e2de97af51..114f8f3b57 100644 --- a/lib/string-desc.h +++ b/lib/string-desc.h @@ -37,6 +37,11 @@ /* Get idx_t. */ #include "idx.h" +/* Whether the compiler supports statement-expressions. */ +#if defined __GNUC__ || defined __clang__ \ + || (defined __SUNPRO_C && __SUNPRO_C >= 0x5150) +# define HAVE_STATEMENT_EXPRESSIONS 1 +#endif _GL_INLINE_HEADER_BEGIN #ifndef GL_STRING_DESC_INLINE @@ -49,14 +54,31 @@ extern "C" { /* Type describing a string that may contain NUL bytes. - It's merely a descriptor of an array of 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_STATEMENT_EXPRESSIONS typedef struct string_desc_t string_desc_t; struct string_desc_t { /* The fields of this struct should be considered private. */ idx_t _nbytes; - char *_data; + const char *_data; }; +#else +typedef rw_string_desc_t string_desc_t; +#endif /* String descriptors can be passed and returned by value. @@ -65,67 +87,103 @@ struct string_desc_t 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 (string_desc_t s); +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 (string_desc_t s, idx_t i); +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 (string_desc_t s); +extern bool sd_is_empty ([rw_]string_desc_t s); #endif /* Return true if A and B are equal. */ -extern bool sd_equals (string_desc_t a, string_desc_t b); +#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. */ -extern bool sd_startswith (string_desc_t s, string_desc_t 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. */ -extern bool sd_endswith (string_desc_t s, string_desc_t 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'. */ -extern int sd_cmp (string_desc_t a, string_desc_t b); +#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. */ -extern int sd_c_casecmp (string_desc_t a, string_desc_t b); +#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. */ -extern ptrdiff_t sd_index (string_desc_t s, char c); +#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. */ -extern ptrdiff_t sd_last_index (string_desc_t s, char c); +#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. */ -extern ptrdiff_t sd_contains (string_desc_t haystack, string_desc_t needle); +#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. */ -extern string_desc_t sd_new_addr (idx_t n, char *addr) +#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); @@ -135,17 +193,24 @@ extern string_desc_t sd_from_c (const char *s); 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. */ -extern int sd_write (int fd, string_desc_t s); +#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. */ -extern int sd_fwrite (FILE *fp, string_desc_t s); +#if 0 /* Defined inline below. */ +extern int sd_fwrite (FILE *fp, [rw_]string_desc_t s); +#endif /* ==== Memory-allocating operations on string descriptors ==== */ @@ -154,59 +219,113 @@ extern int sd_fwrite (FILE *fp, string_desc_t s); Return 0 if successful. Upon error, return -1 with errno set. */ _GL_ATTRIBUTE_NODISCARD -extern int sd_new (string_desc_t *resultp, idx_t n); +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 (string_desc_t *resultp, idx_t n, char c); +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 (string_desc_t *resultp, string_desc_t s); +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 (string_desc_t *resultp, idx_t n, string_desc_t string1, ...); +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. */ -extern char * sd_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; +#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 (string_desc_t s, idx_t i, char c); +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 (string_desc_t s, idx_t start, idx_t end, char c); +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). */ -extern void sd_overwrite (string_desc_t s, idx_t start, string_desc_t t); +#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 (string_desc_t s); +extern void sd_free (rw_string_desc_t s); /* ==== Inline function definitions ==== */ +#if HAVE_STATEMENT_EXPRESSIONS +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_STATEMENT_EXPRESSIONS +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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_IF_NONZERO (2, 1) +{ + 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) { @@ -215,18 +334,296 @@ sd_char_at (string_desc_t s, idx_t i) abort (); return s._data[i]; } +#endif +#if HAVE_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# define sd_new_addr(n, addr) \ + _Generic (addr, \ + const char * : _sd_new_addr (n, addr), \ + char * : _rwsd_new_addr (n, addr)) +#else +# define sd_new_addr(n, addr) \ + _rwsd_new_addr (n, addr) +#endif + +#if HAVE_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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_STATEMENT_EXPRESSIONS +# 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) +{ + return _sd_overwrite (s, start, t._nbytes, t._data); +} +#endif #ifdef __cplusplus diff --git a/lib/xstring-buffer-reversed.c b/lib/xstring-buffer-reversed.c index 333ccbd8a2..51d7ffc4f7 100644 --- a/lib/xstring-buffer-reversed.c +++ b/lib/xstring-buffer-reversed.c @@ -44,15 +44,15 @@ sbr_xprepend_c (struct string_buffer_reversed *buffer, const char *str) xalloc_die (); } -string_desc_t +rw_string_desc_t sbr_xdupfree (struct string_buffer_reversed *buffer) { if (buffer->error) { sbr_free (buffer); - return sd_new_addr (0, NULL); + return sd_new_addr (0, (char *) NULL); } - string_desc_t contents = sbr_dupfree (buffer); + rw_string_desc_t contents = sbr_dupfree (buffer); if (sd_data (contents) == NULL) xalloc_die (); return contents; diff --git a/lib/xstring-buffer.c b/lib/xstring-buffer.c index 4ef1a80a80..3b2691d12e 100644 --- a/lib/xstring-buffer.c +++ b/lib/xstring-buffer.c @@ -53,15 +53,15 @@ sb_xcontents_c (struct string_buffer *buffer) return contents; } -string_desc_t +rw_string_desc_t sb_xdupfree (struct string_buffer *buffer) { if (buffer->error) { sb_free (buffer); - return sd_new_addr (0, NULL); + return sd_new_addr (0, (char *) NULL); } - string_desc_t contents = sb_dupfree (buffer); + rw_string_desc_t contents = sb_dupfree (buffer); if (sd_data (contents) == NULL) xalloc_die (); return contents; diff --git a/lib/xstring-desc.c b/lib/xstring-desc.c index 69520e2b0a..c44d8bc3b4 100644 --- a/lib/xstring-desc.c +++ b/lib/xstring-desc.c @@ -21,52 +21,58 @@ #include "ialloc.h" -string_desc_t -xsd_concat (idx_t n, string_desc_t string1, ...) +rw_string_desc_t +xsd_concat (idx_t n, /* [rw_]string_desc_t string1, */ ...) { if (n <= 0) /* Invalid argument. */ abort (); idx_t total = 0; - total += string1._nbytes; - if (n > 1) - { - va_list other_strings; - idx_t i; + { + va_list strings; + va_start (strings, n); + string_desc_t string1 = va_arg (strings, string_desc_t); + total += string1._nbytes; + if (n > 1) + { + idx_t i; - va_start (other_strings, string1); - for (i = n - 1; i > 0; i--) - { - string_desc_t arg = va_arg (other_strings, string_desc_t); - total += arg._nbytes; - } - va_end (other_strings); - } + for (i = n - 1; i > 0; i--) + { + string_desc_t arg = va_arg (strings, string_desc_t); + total += arg._nbytes; + } + } + va_end (strings); + } char *combined = (char *) imalloc (total); if (combined == NULL) xalloc_die (); idx_t pos = 0; - memcpy (combined, string1._data, string1._nbytes); - pos += string1._nbytes; - if (n > 1) - { - va_list other_strings; - idx_t i; + { + va_list strings; + va_start (strings, n); + string_desc_t string1 = va_arg (strings, string_desc_t); + memcpy (combined, string1._data, string1._nbytes); + pos += string1._nbytes; + if (n > 1) + { + idx_t i; - va_start (other_strings, string1); - for (i = n - 1; i > 0; i--) - { - string_desc_t arg = va_arg (other_strings, string_desc_t); - if (arg._nbytes > 0) - memcpy (combined + pos, arg._data, arg._nbytes); - pos += arg._nbytes; - } - va_end (other_strings); - } + for (i = n - 1; i > 0; i--) + { + string_desc_t arg = va_arg (strings, string_desc_t); + if (arg._nbytes > 0) + memcpy (combined + pos, arg._data, arg._nbytes); + pos += arg._nbytes; + } + } + va_end (strings); + } - string_desc_t result; + rw_string_desc_t result; result._nbytes = total; result._data = combined; diff --git a/lib/xstring-desc.h b/lib/xstring-desc.h index 20b3e72a18..216865a267 100644 --- a/lib/xstring-desc.h +++ b/lib/xstring-desc.h @@ -20,7 +20,8 @@ #define _XSTRING_DESC_H 1 /* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE, - _GL_ATTRIBUTE_DEALLOC_FREE, _GL_ATTRIBUTE_RETURNS_NONNULL. */ + _GL_ATTRIBUTE_DEALLOC_FREE, _GL_ATTRIBUTE_NONNULL_IF_NONZERO, + _GL_ATTRIBUTE_RETURNS_NONNULL. */ #if !_GL_CONFIG_H_INCLUDED #error "Please include config.h first." #endif @@ -44,52 +45,52 @@ extern "C" { /* Return a string of length N, with uninitialized contents. */ #if 0 /* Defined inline below. */ -extern string_desc_t xsd_new (idx_t n); +extern rw_string_desc_t xsd_new (idx_t n); #endif /* Return a string of length N, filled with C. */ #if 0 /* Defined inline below. */ -extern string_desc_t xsd_new_filled (idx_t n, char c); +extern rw_string_desc_t xsd_new_filled (idx_t n, char c); #endif /* Return a copy of string S. */ #if 0 /* Defined inline below. */ -extern string_desc_t xsd_copy (string_desc_t s); +extern rw_string_desc_t xsd_copy (string_desc_t s); #endif /* Return the concatenation of N strings. N must be > 0. */ -extern string_desc_t xsd_concat (idx_t n, string_desc_t string1, ...); +extern rw_string_desc_t xsd_concat (idx_t n, /* [rw_]string_desc_t string1, */ ...); /* Construct and return a copy of string S, as a NUL-terminated C string. */ #if 0 /* Defined inline below. */ -extern char * xsd_c (string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; +extern char * xsd_c ([rw_]string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE; #endif /* ==== Inline function definitions ==== */ -GL_XSTRING_DESC_INLINE string_desc_t +GL_XSTRING_DESC_INLINE rw_string_desc_t xsd_new (idx_t n) { - string_desc_t result; + rw_string_desc_t result; if (sd_new (&result, n) < 0) xalloc_die (); return result; } -GL_XSTRING_DESC_INLINE string_desc_t +GL_XSTRING_DESC_INLINE rw_string_desc_t xsd_new_filled (idx_t n, char c) { - string_desc_t result; + rw_string_desc_t result; if (sd_new_filled (&result, n, c) < 0) xalloc_die (); return result; } -GL_XSTRING_DESC_INLINE string_desc_t +GL_XSTRING_DESC_INLINE rw_string_desc_t xsd_copy (string_desc_t s) { - string_desc_t result; + rw_string_desc_t result; if (sd_copy (&result, s) < 0) xalloc_die (); return result; @@ -98,13 +99,28 @@ xsd_copy (string_desc_t s) GL_XSTRING_DESC_INLINE _GL_ATTRIBUTE_DEALLOC_FREE _GL_ATTRIBUTE_RETURNS_NONNULL char * -xsd_c (string_desc_t s) +_xsd_c (idx_t s_nbytes, const char *s_data) + _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1) { - char *result = sd_c (s); + char *result = _sd_c (s_nbytes, s_data); if (result == NULL) xalloc_die (); return result; } +#if HAVE_STATEMENT_EXPRESSIONS +# define xsd_c(s) \ + ({typeof (s) _xs_ = (s); \ + _xsd_c (_xs_._nbytes, _xs_._data); \ + }) +#else +GL_STRING_DESC_INLINE +_GL_ATTRIBUTE_DEALLOC_FREE _GL_ATTRIBUTE_RETURNS_NONNULL +char * +xsd_c (string_desc_t s) +{ + return _xsd_c (s._nbytes, s._data); +} +#endif #ifdef __cplusplus diff --git a/tests/test-sf-istream.c b/tests/test-sf-istream.c index 74c6203e48..0ab488fc73 100644 --- a/tests/test-sf-istream.c +++ b/tests/test-sf-istream.c @@ -91,8 +91,7 @@ main () { sf_istream_t stream; sf_istream_init_from_string_desc (&stream, - sd_new_addr (CONTENTS_LEN, - (char *) contents)); + sd_new_addr (CONTENTS_LEN, contents)); test_open_stream (&stream); sf_free (&stream); } diff --git a/tests/test-sfl-istream.c b/tests/test-sfl-istream.c index bd72952c55..43fdc14aae 100644 --- a/tests/test-sfl-istream.c +++ b/tests/test-sfl-istream.c @@ -115,8 +115,7 @@ main () { sfl_istream_t stream; sfl_istream_init_from_string_desc (&stream, - sd_new_addr (CONTENTS_LEN, - (char *) contents)); + sd_new_addr (CONTENTS_LEN, contents)); test_open_stream (&stream); sfl_free (&stream); } diff --git a/tests/test-string-desc.c b/tests/test-string-desc.c index d90b548f6a..0b2a5406a5 100644 --- a/tests/test-string-desc.c +++ b/tests/test-string-desc.c @@ -142,7 +142,7 @@ main (int argc, char *argv[]) ASSERT (sd_fwrite (stdout, s2) == 0); /* Test sd_new, sd_set_char_at, sd_fill. */ - string_desc_t s4; + rw_string_desc_t s4; ASSERT (sd_new (&s4, 5) == 0); sd_set_char_at (s4, 0, 'H'); sd_set_char_at (s4, 4, 'o'); @@ -152,7 +152,7 @@ main (int argc, char *argv[]) ASSERT (sd_startswith (s1, s4)); /* Test sd_new_filled, sd_set_char_at. */ - string_desc_t s5; + rw_string_desc_t s5; ASSERT (sd_new_filled (&s5, 5, 'l') == 0); sd_set_char_at (s5, 0, 'H'); sd_set_char_at (s5, 4, 'o'); @@ -166,13 +166,13 @@ main (int argc, char *argv[]) /* Test sd_copy, sd_free. */ { - string_desc_t s6; + rw_string_desc_t s6; ASSERT (sd_copy (&s6, s0) == 0); ASSERT (sd_is_empty (s6)); sd_free (s6); } { - string_desc_t s6; + rw_string_desc_t s6; ASSERT (sd_copy (&s6, s2) == 0); ASSERT (sd_equals (s6, s2)); sd_free (s6); @@ -180,7 +180,7 @@ main (int argc, char *argv[]) /* Test sd_overwrite. */ { - string_desc_t s7; + rw_string_desc_t s7; ASSERT (sd_copy (&s7, s2) == 0); sd_overwrite (s7, 4, s1); ASSERT (sd_equals (s7, sd_new_addr (21, "The\0Hello world!\0fox"))); @@ -188,7 +188,7 @@ main (int argc, char *argv[]) /* Test sd_concat. */ { - string_desc_t s8; + rw_string_desc_t s8; ASSERT (sd_concat (&s8, 3, sd_new_addr (10, "The\0quick"), sd_new_addr (7, "brown\0"), sd_new_addr (4, "fox"), diff --git a/tests/test-xstring-desc.c b/tests/test-xstring-desc.c index d740634049..02f22c2be5 100644 --- a/tests/test-xstring-desc.c +++ b/tests/test-xstring-desc.c @@ -33,7 +33,7 @@ main (void) string_desc_t s2 = sd_new_addr (21, "The\0quick\0brown\0\0fox"); /* Test xsd_new. */ - string_desc_t s4 = xsd_new (5); + rw_string_desc_t s4 = xsd_new (5); sd_set_char_at (s4, 0, 'H'); sd_set_char_at (s4, 4, 'o'); sd_set_char_at (s4, 1, 'e'); @@ -42,7 +42,7 @@ main (void) ASSERT (sd_startswith (s1, s4)); /* Test xsd_new_filled. */ - string_desc_t s5 = xsd_new_filled (5, 'l'); + rw_string_desc_t s5 = xsd_new_filled (5, 'l'); sd_set_char_at (s5, 0, 'H'); sd_set_char_at (s5, 4, 'o'); sd_set_char_at (s5, 1, 'e'); @@ -51,19 +51,19 @@ main (void) /* Test xsd_copy. */ { - string_desc_t s6 = xsd_copy (s0); + rw_string_desc_t s6 = xsd_copy (s0); ASSERT (sd_is_empty (s6)); sd_free (s6); } { - string_desc_t s6 = xsd_copy (s2); + rw_string_desc_t s6 = xsd_copy (s2); ASSERT (sd_equals (s6, s2)); sd_free (s6); } /* Test xsd_concat. */ { - string_desc_t s8 = + rw_string_desc_t s8 = xsd_concat (3, sd_new_addr (10, "The\0quick"), sd_new_addr (7, "brown\0"), sd_new_addr (4, "fox"),