str: Make ss_alloc_substring_pool() null-terminate its output.
[pspp-builds.git] / src / libpspp / str.c
index b169f0d7613ee556e7a5a32e746a36ac2f06af23..08a85ad790f20fe625de485b6c22058226910e2e 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include <errno.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <unistr.h>
 
-#include <libpspp/message.h>
-#include <libpspp/pool.h>
+#include "libpspp/cast.h"
+#include "libpspp/message.h"
+#include "libpspp/pool.h"
 
-#include "minmax.h"
-#include "xalloc.h"
-#include "xsize.h"
+#include "gl/relocatable.h"
+#include "gl/minmax.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
+#include "gl/xsize.h"
 \f
 /* Reverses the order of NBYTES bytes at address P, thus converting
    between little- and big-endian byte orders.  */
@@ -47,19 +51,6 @@ buf_reverse (char *p, size_t nbytes)
     }
 }
 
-/* Finds the last NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
-   HAYSTACK_LEN.  Returns a pointer to the needle found. */
-char *
-buf_find_reverse (const char *haystack, size_t haystack_len,
-                 const char *needle, size_t needle_len)
-{
-  int i;
-  for (i = haystack_len - needle_len; i >= 0; i--)
-    if (!memcmp (needle, &haystack[i], needle_len))
-      return (char *) &haystack[i];
-  return 0;
-}
-
 /* Compares the SIZE bytes in A to those in B, disregarding case,
    and returns a strcmp()-type result. */
 int
@@ -124,9 +115,9 @@ str_compare_rpad (const char *a, const char *b)
 
 /* Copies string SRC to buffer DST, of size DST_SIZE bytes.
    DST is truncated to DST_SIZE bytes or padded on the right with
-   spaces as needed. */
+   copies of PAD as needed. */
 void
-buf_copy_str_rpad (char *dst, size_t dst_size, const char *src)
+buf_copy_str_rpad (char *dst, size_t dst_size, const char *src, char pad)
 {
   size_t src_len = strlen (src);
   if (src_len >= dst_size)
@@ -134,15 +125,15 @@ buf_copy_str_rpad (char *dst, size_t dst_size, const char *src)
   else
     {
       memcpy (dst, src, src_len);
-      memset (&dst[src_len], ' ', dst_size - src_len);
+      memset (&dst[src_len], pad, dst_size - src_len);
     }
 }
 
 /* Copies string SRC to buffer DST, of size DST_SIZE bytes.
    DST is truncated to DST_SIZE bytes or padded on the left with
-   spaces as needed. */
+   copies of PAD as needed. */
 void
-buf_copy_str_lpad (char *dst, size_t dst_size, const char *src)
+buf_copy_str_lpad (char *dst, size_t dst_size, const char *src, char pad)
 {
   size_t src_len = strlen (src);
   if (src_len >= dst_size)
@@ -150,63 +141,68 @@ buf_copy_str_lpad (char *dst, size_t dst_size, const char *src)
   else
     {
       size_t pad_cnt = dst_size - src_len;
-      memset (&dst[0], ' ', pad_cnt);
+      memset (&dst[0], pad, pad_cnt);
       memcpy (dst + pad_cnt, src, src_len);
     }
 }
 
 /* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
    DST is truncated to DST_SIZE bytes or padded on the left with
-   spaces as needed. */
+   copies of PAD as needed. */
 void
 buf_copy_lpad (char *dst, size_t dst_size,
-               const char *src, size_t src_size)
+               const char *src, size_t src_size,
+               char pad)
 {
   if (src_size >= dst_size)
     memmove (dst, src, dst_size);
   else
     {
-      memset (dst, ' ', dst_size - src_size);
+      memset (dst, pad, dst_size - src_size);
       memmove (&dst[dst_size - src_size], src, src_size);
     }
 }
 
 /* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
    DST is truncated to DST_SIZE bytes or padded on the right with
-   spaces as needed. */
+   copies of PAD as needed. */
 void
 buf_copy_rpad (char *dst, size_t dst_size,
-               const char *src, size_t src_size)
+               const char *src, size_t src_size,
+               char pad)
 {
   if (src_size >= dst_size)
     memmove (dst, src, dst_size);
   else
     {
       memmove (dst, src, src_size);
-      memset (&dst[src_size], ' ', dst_size - src_size);
+      memset (&dst[src_size], pad, dst_size - src_size);
     }
 }
 
 /* Copies string SRC to string DST, which is in a buffer DST_SIZE
    bytes long.
-   Truncates DST to DST_SIZE - 1 characters or right-pads with
-   spaces to DST_SIZE - 1 characters if necessary. */
+   Truncates DST to DST_SIZE - 1 bytes or right-pads with
+   spaces to DST_SIZE - 1 bytes if necessary. */
 void
 str_copy_rpad (char *dst, size_t dst_size, const char *src)
 {
-  size_t src_len = strlen (src);
-  if (src_len < dst_size - 1)
+  if (dst_size > 0) 
     {
-      memcpy (dst, src, src_len);
-      memset (&dst[src_len], ' ', dst_size - 1 - src_len);
+      size_t src_len = strlen (src);
+      if (src_len < dst_size - 1)
+        {
+          memcpy (dst, src, src_len);
+          memset (&dst[src_len], ' ', dst_size - 1 - src_len);
+        }
+      else
+        memcpy (dst, src, dst_size - 1);
+      dst[dst_size - 1] = 0;
     }
-  else
-    memcpy (dst, src, dst_size - 1);
-  dst[dst_size - 1] = 0;
 }
 
 /* Copies SRC to DST, which is in a buffer DST_SIZE bytes long.
-   Truncates DST to DST_SIZE - 1 characters, if necessary. */
+   Truncates DST to DST_SIZE - 1 bytes, if necessary. */
 void
 str_copy_trunc (char *dst, size_t dst_size, const char *src)
 {
@@ -223,7 +219,7 @@ str_copy_trunc (char *dst, size_t dst_size, const char *src)
 
 /* Copies buffer SRC, of SRC_LEN bytes,
    to DST, which is in a buffer DST_SIZE bytes long.
-   Truncates DST to DST_SIZE - 1 characters, if necessary. */
+   Truncates DST to DST_SIZE - 1 bytes, if necessary. */
 void
 str_copy_buf_trunc (char *dst, size_t dst_size,
                     const char *src, size_t src_size)
@@ -236,7 +232,7 @@ str_copy_buf_trunc (char *dst, size_t dst_size,
   dst[dst_len] = '\0';
 }
 
-/* Converts each character in S to uppercase. */
+/* Converts each byte in S to uppercase. */
 void
 str_uppercase (char *s)
 {
@@ -244,7 +240,7 @@ str_uppercase (char *s)
     *s = toupper ((unsigned char) *s);
 }
 
-/* Converts each character in S to lowercase. */
+/* Converts each byte in S to lowercase. */
 void
 str_lowercase (char *s)
 {
@@ -287,21 +283,6 @@ str_format_26adic (unsigned long int number, char buffer[], size_t size)
   return true;
 }
 
-/* Formats FORMAT into DST, as with sprintf(), and returns the
-   address of the terminating null written to DST. */
-char *
-spprintf (char *dst, const char *format, ...)
-{
-  va_list args;
-  int count;
-
-  va_start (args, format);
-  count = vsprintf (dst, format, args);
-  va_end (args);
-
-  return dst + count;
-}
-
 /* Sets the SIZE bytes starting at BLOCK to C,
    and returns the byte following BLOCK. */
 void *
@@ -313,36 +294,7 @@ mempset (void *block, int c, size_t size)
 \f
 /* Substrings. */
 
-/* Returns an empty substring. */
-struct substring
-ss_empty (void)
-{
-  struct substring ss;
-  ss.string = NULL;
-  ss.length = 0;
-  return ss;
-}
-
-/* Returns a substring whose contents are the given C-style
-   string CSTR. */
-struct substring
-ss_cstr (const char *cstr)
-{
-  return ss_buffer (cstr, strlen (cstr));
-}
-
-/* Returns a substring whose contents are the CNT characters in
-   BUFFER. */
-struct substring
-ss_buffer (const char *buffer, size_t cnt)
-{
-  struct substring ss;
-  ss.string = (char *) buffer;
-  ss.length = cnt;
-  return ss;
-}
-
-/* Returns a substring whose contents are the CNT characters
+/* Returns a substring whose contents are the CNT bytes
    starting at the (0-based) position START in SS. */
 struct substring
 ss_substr (struct substring ss, size_t start, size_t cnt)
@@ -354,14 +306,14 @@ ss_substr (struct substring ss, size_t start, size_t cnt)
 }
 
 /* Returns a substring whose contents are the first CNT
-   characters in SS. */
+   bytes in SS. */
 struct substring
 ss_head (struct substring ss, size_t cnt)
 {
   return ss_buffer (ss.string, MIN (cnt, ss.length));
 }
 
-/* Returns a substring whose contents are the last CNT characters
+/* Returns a substring whose contents are the last CNT bytes
    in SS. */
 struct substring
 ss_tail (struct substring ss, size_t cnt)
@@ -372,17 +324,16 @@ ss_tail (struct substring ss, size_t cnt)
     return ss;
 }
 
-/* Makes a malloc()'d copy of the contents of OLD
+/* Makes a malloc()'d, null-terminated copy of the contents of OLD
    and stores it in NEW. */
 void
 ss_alloc_substring (struct substring *new, struct substring old)
 {
-  new->string = xmalloc (old.length);
+  new->string = xmemdup0 (old.string, old.length);
   new->length = old.length;
-  memcpy (new->string, old.string, old.length);
 }
 
-/* Allocates room for a CNT-character string in NEW. */
+/* Allocates room for a CNT-byte string in NEW. */
 void
 ss_alloc_uninit (struct substring *new, size_t cnt)
 {
@@ -390,18 +341,25 @@ ss_alloc_uninit (struct substring *new, size_t cnt)
   new->length = cnt;
 }
 
-/* Makes a pool_alloc_unaligned()'d copy of the contents of OLD
-   in POOL, and stores it in NEW. */
+void
+ss_realloc (struct substring *ss, size_t size)
+{
+  ss->string = xrealloc (ss->string, size);
+}
+
+/* Makes a pool_alloc_unaligned()'d, null-terminated copy of the contents of
+   OLD in POOL, and stores it in NEW. */
 void
 ss_alloc_substring_pool (struct substring *new, struct substring old,
                          struct pool *pool)
 {
-  new->string = pool_alloc_unaligned (pool, old.length);
+  new->string = pool_alloc_unaligned (pool, old.length + 1);
   new->length = old.length;
   memcpy (new->string, old.string, old.length);
+  new->string[old.length] = '\0';
 }
 
-/* Allocates room for a CNT-character string in NEW in POOL. */
+/* Allocates room for a CNT-byte string in NEW in POOL. */
 void
 ss_alloc_uninit_pool (struct substring *new, size_t cnt, struct pool *pool)
 {
@@ -416,7 +374,7 @@ ss_dealloc (struct substring *ss)
   free (ss->string);
 }
 
-/* Truncates SS to at most CNT characters in length. */
+/* Truncates SS to at most CNT bytes in length. */
 void
 ss_truncate (struct substring *ss, size_t cnt)
 {
@@ -424,22 +382,22 @@ ss_truncate (struct substring *ss, size_t cnt)
     ss->length = cnt;
 }
 
-/* Removes trailing characters in TRIM_SET from SS.
-   Returns number of characters removed. */
+/* Removes trailing bytes in TRIM_SET from SS.
+   Returns number of bytes removed. */
 size_t
 ss_rtrim (struct substring *ss, struct substring trim_set)
 {
   size_t cnt = 0;
   while (cnt < ss->length
-         && ss_find_char (trim_set,
+         && ss_find_byte (trim_set,
                           ss->string[ss->length - cnt - 1]) != SIZE_MAX)
     cnt++;
   ss->length -= cnt;
   return cnt;
 }
 
-/* Removes leading characters in TRIM_SET from SS.
-   Returns number of characters removed. */
+/* Removes leading bytes in TRIM_SET from SS.
+   Returns number of bytes removed. */
 size_t
 ss_ltrim (struct substring *ss, struct substring trim_set)
 {
@@ -448,7 +406,7 @@ ss_ltrim (struct substring *ss, struct substring trim_set)
   return cnt;
 }
 
-/* Trims leading and trailing characters in TRIM_SET from SS. */
+/* Trims leading and trailing bytes in TRIM_SET from SS. */
 void
 ss_trim (struct substring *ss, struct substring trim_set)
 {
@@ -456,10 +414,10 @@ ss_trim (struct substring *ss, struct substring trim_set)
   ss_rtrim (ss, trim_set);
 }
 
-/* If the last character in SS is C, removes it and returns true.
+/* If the last byte in SS is C, removes it and returns true.
    Otherwise, returns false without changing the string. */
 bool
-ss_chomp (struct substring *ss, char c)
+ss_chomp_byte (struct substring *ss, char c)
 {
   if (ss_last (*ss) == c)
     {
@@ -470,6 +428,20 @@ ss_chomp (struct substring *ss, char c)
     return false;
 }
 
+/* If SS ends with SUFFIX, removes it and returns true.
+   Otherwise, returns false without changing the string. */
+bool
+ss_chomp (struct substring *ss, struct substring suffix)
+{
+  if (ss_ends_with (*ss, suffix))
+    {
+      ss->length -= suffix.length;
+      return true;
+    }
+  else
+    return false;
+}
+
 /* Divides SS into tokens separated by any of the DELIMITERS.
    Each call replaces TOKEN by the next token in SS, or by an
    empty string if no tokens remain.  Returns true if a token was
@@ -516,12 +488,12 @@ ss_tokenize (struct substring ss, struct substring delimiters,
 {
   ss_advance (&ss, *save_idx);
   *save_idx += ss_ltrim (&ss, delimiters);
-  ss_get_chars (&ss, ss_cspan (ss, delimiters), token);
+  ss_get_bytes (&ss, ss_cspan (ss, delimiters), token);
   *save_idx += ss_length (*token) + 1;
   return ss_length (*token) > 0;
 }
 
-/* Removes the first CNT characters from SS. */
+/* Removes the first CNT bytes from SS. */
 void
 ss_advance (struct substring *ss, size_t cnt)
 {
@@ -531,10 +503,10 @@ ss_advance (struct substring *ss, size_t cnt)
   ss->length -= cnt;
 }
 
-/* If the first character in SS is C, removes it and returns true.
+/* If the first byte in SS is C, removes it and returns true.
    Otherwise, returns false without changing the string. */
 bool
-ss_match_char (struct substring *ss, char c)
+ss_match_byte (struct substring *ss, char c)
 {
   if (ss_first (*ss) == c)
     {
@@ -546,6 +518,23 @@ ss_match_char (struct substring *ss, char c)
     return false;
 }
 
+/* If the first byte in SS is in MATCH, removes it and
+   returns the byte that was removed.
+   Otherwise, returns EOF without changing the string. */
+int
+ss_match_byte_in (struct substring *ss, struct substring match)
+{
+  int c = EOF;
+  if (ss->length > 0
+      && memchr (match.string, ss->string[0], match.length) != NULL)
+    {
+      c = ss->string[0];
+      ss->string++;
+      ss->length--;
+    }
+  return c;
+}
+
 /* If SS begins with TARGET, removes it and returns true.
    Otherwise, returns false without changing SS. */
 bool
@@ -561,10 +550,10 @@ ss_match_string (struct substring *ss, const struct substring target)
     return false;
 }
 
-/* Removes the first character from SS and returns it.
+/* Removes the first byte from SS and returns it.
    If SS is empty, returns EOF without modifying SS. */
 int
-ss_get_char (struct substring *ss)
+ss_get_byte (struct substring *ss)
 {
   int c = ss_first (*ss);
   if (c != EOF)
@@ -576,21 +565,21 @@ ss_get_char (struct substring *ss)
 }
 
 /* Stores the prefix of SS up to the first DELIMITER in OUT (if
-   any).  Trims those same characters from SS.  DELIMITER is
+   any).  Trims those same bytes from SS.  DELIMITER is
    removed from SS but not made part of OUT.  Returns true if
    DELIMITER was found (and removed), false otherwise. */
 bool
 ss_get_until (struct substring *ss, char delimiter, struct substring *out)
 {
-  ss_get_chars (ss, ss_cspan (*ss, ss_buffer (&delimiter, 1)), out);
-  return ss_match_char (ss, delimiter);
+  ss_get_bytes (ss, ss_cspan (*ss, ss_buffer (&delimiter, 1)), out);
+  return ss_match_byte (ss, delimiter);
 }
 
-/* Stores the first CNT characters in SS in OUT (or fewer, if SS
-   is shorter than CNT characters).  Trims the same characters
+/* Stores the first CNT bytes in SS in OUT (or fewer, if SS
+   is shorter than CNT bytes).  Trims the same bytes
    from the beginning of SS.  Returns CNT. */
 size_t
-ss_get_chars (struct substring *ss, size_t cnt, struct substring *out)
+ss_get_bytes (struct substring *ss, size_t cnt, struct substring *out)
 {
   *out = ss_head (*ss, cnt);
   ss_advance (ss, cnt);
@@ -599,7 +588,7 @@ ss_get_chars (struct substring *ss, size_t cnt, struct substring *out)
 
 /* Parses and removes an optionally signed decimal integer from
    the beginning of SS.  Returns 0 if an error occurred,
-   otherwise the number of characters removed from SS.  Stores
+   otherwise the number of bytes removed from SS.  Stores
    the integer's value into *VALUE. */
 size_t
 ss_get_long (struct substring *ss, long *value)
@@ -627,7 +616,7 @@ ss_get_long (struct substring *ss, long *value)
   return 0;
 }
 
-/* Returns true if SS is empty (contains no characters),
+/* Returns true if SS is empty (has length 0 bytes),
    false otherwise. */
 bool
 ss_is_empty (struct substring ss)
@@ -635,28 +624,28 @@ ss_is_empty (struct substring ss)
   return ss.length == 0;
 }
 
-/* Returns the number of characters in SS. */
+/* Returns the number of bytes in SS. */
 size_t
 ss_length (struct substring ss)
 {
   return ss.length;
 }
 
-/* Returns a pointer to the characters in SS. */
+/* Returns a pointer to the bytes in SS. */
 char *
 ss_data (struct substring ss)
 {
   return ss.string;
 }
 
-/* Returns a pointer just past the last character in SS. */
+/* Returns a pointer just past the last byte in SS. */
 char *
 ss_end (struct substring ss)
 {
   return ss.string + ss.length;
 }
 
-/* Returns the character in position IDX in SS, as a value in the
+/* Returns the byte in position IDX in SS, as a value in the
    range of unsigned char.  Returns EOF if IDX is out of the
    range of indexes for SS. */
 int
@@ -665,7 +654,7 @@ ss_at (struct substring ss, size_t idx)
   return idx < ss.length ? (unsigned char) ss.string[idx] : EOF;
 }
 
-/* Returns the first character in SS as a value in the range of
+/* Returns the first byte in SS as a value in the range of
    unsigned char.  Returns EOF if SS is the empty string. */
 int
 ss_first (struct substring ss)
@@ -673,7 +662,7 @@ ss_first (struct substring ss)
   return ss_at (ss, 0);
 }
 
-/* Returns the last character in SS as a value in the range of
+/* Returns the last byte in SS as a value in the range of
    unsigned char.  Returns EOF if SS is the empty string. */
 int
 ss_last (struct substring ss)
@@ -681,26 +670,35 @@ ss_last (struct substring ss)
   return ss.length > 0 ? (unsigned char) ss.string[ss.length - 1] : EOF;
 }
 
-/* Returns the number of contiguous characters at the beginning
+/* Returns true if SS ends with SUFFIX, false otherwise. */
+bool
+ss_ends_with (struct substring ss, struct substring suffix)
+{
+  return (ss.length >= suffix.length
+          && !memcmp (&ss.string[ss.length - suffix.length], suffix.string,
+                      suffix.length));
+}
+
+/* Returns the number of contiguous bytes at the beginning
    of SS that are in SKIP_SET. */
 size_t
 ss_span (struct substring ss, struct substring skip_set)
 {
   size_t i;
   for (i = 0; i < ss.length; i++)
-    if (ss_find_char (skip_set, ss.string[i]) == SIZE_MAX)
+    if (ss_find_byte (skip_set, ss.string[i]) == SIZE_MAX)
       break;
   return i;
 }
 
-/* Returns the number of contiguous characters at the beginning
+/* Returns the number of contiguous bytes at the beginning
    of SS that are not in SKIP_SET. */
 size_t
 ss_cspan (struct substring ss, struct substring stop_set)
 {
   size_t i;
   for (i = 0; i < ss.length; i++)
-    if (ss_find_char (stop_set, ss.string[i]) != SIZE_MAX)
+    if (ss_find_byte (stop_set, ss.string[i]) != SIZE_MAX)
       break;
   return i;
 }
@@ -708,7 +706,7 @@ ss_cspan (struct substring ss, struct substring stop_set)
 /* Returns the offset in SS of the first instance of C,
    or SIZE_MAX if C does not occur in SS. */
 size_t
-ss_find_char (struct substring ss, char c)
+ss_find_byte (struct substring ss, char c)
 {
   const char *p = memchr (ss.string, c, ss.length);
   return p != NULL ? p - ss.string : SIZE_MAX;
@@ -753,7 +751,7 @@ ss_equals_case (struct substring a, struct substring b)
   return a.length == b.length && !memcasecmp (a.string, b.string, a.length);
 }
 
-/* Returns the position in SS that the character at P occupies.
+/* Returns the position in SS that the byte at P occupies.
    P must point within SS or one past its end. */
 size_t
 ss_pointer_to_position (struct substring ss, const char *p)
@@ -773,6 +771,81 @@ ss_xstrdup (struct substring ss)
   s[ss.length] = '\0';
   return s;
 }
+/* UTF-8. */
+
+/* Returns the character represented by the UTF-8 sequence at the start of S.
+   The return value is either a Unicode code point in the range 0 to 0x10ffff,
+   or UINT32_MAX if S is empty. */
+ucs4_t
+ss_first_mb (struct substring s)
+{
+  return ss_at_mb (s, 0);
+}
+
+/* Returns the number of bytes in the UTF-8 character at the beginning of S.
+
+   The return value is 0 if S is empty, otherwise between 1 and 4. */
+int
+ss_first_mblen (struct substring s)
+{
+  return ss_at_mblen (s, 0);
+}
+
+/* Advances S past the UTF-8 character at its beginning.  Returns the Unicode
+   code point that was skipped (in the range 0 to 0x10ffff), or UINT32_MAX if S
+   was not modified because it was initially empty. */
+ucs4_t
+ss_get_mb (struct substring *s)
+{
+  if (s->length > 0)
+    {
+      ucs4_t uc;
+      int n;
+
+      n = u8_mbtouc (&uc, CHAR_CAST (const uint8_t *, s->string), s->length);
+      s->string += n;
+      s->length -= n;
+      return uc;
+    }
+  else
+    return UINT32_MAX;
+}
+
+/* Returns the character represented by the UTF-8 sequence starting OFS bytes
+   into S.  The return value is either a Unicode code point in the range 0 to
+   0x10ffff, or UINT32_MAX if OFS is past the last byte in S.
+
+   (Returns 0xfffd if OFS points into the middle, not the beginning, of a UTF-8
+   sequence.)  */
+ucs4_t
+ss_at_mb (struct substring s, size_t ofs)
+{
+  if (s.length > ofs)
+    {
+      ucs4_t uc;
+      u8_mbtouc (&uc, CHAR_CAST (const uint8_t *, s.string + ofs),
+                 s.length - ofs);
+      return uc;
+    }
+  else
+    return UINT32_MAX;
+}
+
+/* Returns the number of bytes represented by the UTF-8 sequence starting OFS
+   bytes into S.  The return value is 0 if OFS is past the last byte in S,
+   otherwise between 1 and 4. */
+int
+ss_at_mblen (struct substring s, size_t ofs)
+{
+  if (s.length > ofs)
+    {
+      ucs4_t uc;
+      return u8_mbtouc (&uc, CHAR_CAST (const uint8_t *, s.string + ofs),
+                        s.length - ofs);
+    }
+  else
+    return 0;
+}
 \f
 /* Initializes ST as an empty string. */
 void
@@ -892,20 +965,20 @@ ds_ss (const struct string *st)
   return st->ss;
 }
 
-/* Returns a substring that contains CNT characters from ST
+/* Returns a substring that contains CNT bytes from ST
    starting at position START.
 
    If START is greater than or equal to the length of ST, then
    the substring will be the empty string.  If START + CNT
    exceeds the length of ST, then the substring will only be
-   ds_length(ST) - START characters long. */
+   ds_length(ST) - START bytes long. */
 struct substring
 ds_substr (const struct string *st, size_t start, size_t cnt)
 {
   return ss_substr (ds_ss (st), start, cnt);
 }
 
-/* Returns a substring that contains the first CNT characters in
+/* Returns a substring that contains the first CNT bytes in
    ST.  If CNT exceeds the length of ST, then the substring will
    contain all of ST. */
 struct substring
@@ -914,7 +987,7 @@ ds_head (const struct string *st, size_t cnt)
   return ss_head (ds_ss (st), cnt);
 }
 
-/* Returns a substring that contains the last CNT characters in
+/* Returns a substring that contains the last CNT bytes in
    ST.  If CNT exceeds the length of ST, then the substring will
    contain all of ST. */
 struct substring
@@ -923,7 +996,7 @@ ds_tail (const struct string *st, size_t cnt)
   return ss_tail (ds_ss (st), cnt);
 }
 
-/* Ensures that ST can hold at least MIN_CAPACITY characters plus a null
+/* Ensures that ST can hold at least MIN_CAPACITY bytes plus a null
    terminator. */
 void
 ds_extend (struct string *st, size_t min_capacity)
@@ -949,23 +1022,23 @@ ds_shrink (struct string *st)
     }
 }
 
-/* Truncates ST to at most LENGTH characters long. */
+/* Truncates ST to at most LENGTH bytes long. */
 void
 ds_truncate (struct string *st, size_t length)
 {
   ss_truncate (&st->ss, length);
 }
 
-/* Removes trailing characters in TRIM_SET from ST.
-   Returns number of characters removed. */
+/* Removes trailing bytes in TRIM_SET from ST.
+   Returns number of bytes removed. */
 size_t
 ds_rtrim (struct string *st, struct substring trim_set)
 {
   return ss_rtrim (&st->ss, trim_set);
 }
 
-/* Removes leading characters in TRIM_SET from ST.
-   Returns number of characters removed. */
+/* Removes leading bytes in TRIM_SET from ST.
+   Returns number of bytes removed. */
 size_t
 ds_ltrim (struct string *st, struct substring trim_set)
 {
@@ -975,8 +1048,8 @@ ds_ltrim (struct string *st, struct substring trim_set)
   return cnt;
 }
 
-/* Trims leading and trailing characters in TRIM_SET from ST.
-   Returns number of charactesr removed. */
+/* Trims leading and trailing bytes in TRIM_SET from ST.
+   Returns number of bytes removed. */
 size_t
 ds_trim (struct string *st, struct substring trim_set)
 {
@@ -984,12 +1057,20 @@ ds_trim (struct string *st, struct substring trim_set)
   return cnt + ds_ltrim (st, trim_set);
 }
 
-/* If the last character in ST is C, removes it and returns true.
+/* If the last byte in ST is C, removes it and returns true.
+   Otherwise, returns false without modifying ST. */
+bool
+ds_chomp_byte (struct string *st, char c)
+{
+  return ss_chomp_byte (&st->ss, c);
+}
+
+/* If ST ends with SUFFIX, removes it and returns true.
    Otherwise, returns false without modifying ST. */
 bool
-ds_chomp (struct string *st, char c)
+ds_chomp (struct string *st, struct substring suffix)
 {
-  return ss_chomp (&st->ss, c);
+  return ss_chomp (&st->ss, suffix);
 }
 
 /* Divides ST into tokens separated by any of the DELIMITERS.
@@ -1027,17 +1108,17 @@ ds_tokenize (const struct string *st, struct substring delimiters,
 }
 
 /* Pad ST on the right with copies of PAD until ST is at least
-   LENGTH characters in size.  If ST is initially LENGTH
-   characters or longer, this is a no-op. */
+   LENGTH bytes in size.  If ST is initially LENGTH
+   bytes or longer, this is a no-op. */
 void
 ds_rpad (struct string *st, size_t length, char pad)
 {
   if (length > st->ss.length)
-    ds_put_char_multiple (st, pad, length - st->ss.length);
+    ds_put_byte_multiple (st, pad, length - st->ss.length);
 }
 
 /* Sets the length of ST to exactly NEW_LENGTH,
-   either by truncating characters from the end,
+   either by truncating bytes from the end,
    or by padding on the right with PAD. */
 void
 ds_set_length (struct string *st, size_t new_length, char pad)
@@ -1048,6 +1129,34 @@ ds_set_length (struct string *st, size_t new_length, char pad)
     st->ss.length = new_length;
 }
 
+/* Removes N bytes from ST starting at offset START. */
+void
+ds_remove (struct string *st, size_t start, size_t n)
+{
+  if (n > 0 && start < st->ss.length)
+    {
+      if (st->ss.length - start <= n)
+        {
+          /* All bytes at or beyond START are deleted. */
+          st->ss.length = start;
+        }
+      else
+        {
+          /* Some bytes remain and must be shifted into
+             position. */
+          memmove (st->ss.string + st->ss.length,
+                   st->ss.string + st->ss.length + n,
+                   st->ss.length - start - n);
+          st->ss.length -= n;
+        }
+    }
+  else
+    {
+      /* There are no bytes to delete or no bytes at or
+         beyond START, hence deletion is a no-op. */
+    }
+}
+
 /* Returns true if ST is empty, false otherwise. */
 bool
 ds_is_empty (const struct string *st)
@@ -1070,7 +1179,7 @@ ds_data (const struct string *st)
 }
 
 /* Returns a pointer to the null terminator ST.
-   This might not be an actual null character unless ds_c_str() has
+   This might not be an actual null byte unless ds_c_str() has
    been called since the last modification to ST. */
 char *
 ds_end (const struct string *st)
@@ -1078,7 +1187,7 @@ ds_end (const struct string *st)
   return ss_end (ds_ss (st));
 }
 
-/* Returns the character in position IDX in ST, as a value in the
+/* Returns the byte in position IDX in ST, as a value in the
    range of unsigned char.  Returns EOF if IDX is out of the
    range of indexes for ST. */
 int
@@ -1087,7 +1196,7 @@ ds_at (const struct string *st, size_t idx)
   return ss_at (ds_ss (st), idx);
 }
 
-/* Returns the first character in ST as a value in the range of
+/* Returns the first byte in ST as a value in the range of
    unsigned char.  Returns EOF if ST is the empty string. */
 int
 ds_first (const struct string *st)
@@ -1095,7 +1204,7 @@ ds_first (const struct string *st)
   return ss_first (ds_ss (st));
 }
 
-/* Returns the last character in ST as a value in the range of
+/* Returns the last byte in ST as a value in the range of
    unsigned char.  Returns EOF if ST is the empty string. */
 int
 ds_last (const struct string *st)
@@ -1103,7 +1212,14 @@ ds_last (const struct string *st)
   return ss_last (ds_ss (st));
 }
 
-/* Returns the number of consecutive characters at the beginning
+/* Returns true if ST ends with SUFFIX, false otherwise. */
+bool
+ds_ends_with (const struct string *st, struct substring suffix)
+{
+  return ss_ends_with (st->ss, suffix);
+}
+
+/* Returns the number of consecutive bytes at the beginning
    of ST that are in SKIP_SET. */
 size_t
 ds_span (const struct string *st, struct substring skip_set)
@@ -1111,7 +1227,7 @@ ds_span (const struct string *st, struct substring skip_set)
   return ss_span (ds_ss (st), skip_set);
 }
 
-/* Returns the number of consecutive characters at the beginning
+/* Returns the number of consecutive bytes at the beginning
    of ST that are not in STOP_SET.  */
 size_t
 ds_cspan (const struct string *st, struct substring stop_set)
@@ -1119,13 +1235,13 @@ ds_cspan (const struct string *st, struct substring stop_set)
   return ss_cspan (ds_ss (st), stop_set);
 }
 
-/* Returns the position of the first occurrence of character C in
+/* Returns the position of the first occurrence of byte C in
    ST at or after position OFS, or SIZE_MAX if there is no such
    occurrence. */
 size_t
-ds_find_char (const struct string *st, char c)
+ds_find_byte (const struct string *st, char c)
 {
-  return ss_find_char (ds_ss (st), c);
+  return ss_find_byte (ds_ss (st), c);
 }
 
 /* Compares A and B and returns a strcmp()-type comparison
@@ -1136,7 +1252,7 @@ ds_compare (const struct string *a, const struct string *b)
   return ss_compare (ds_ss (a), ds_ss (b));
 }
 
-/* Returns the position in ST that the character at P occupies.
+/* Returns the position in ST that the byte at P occupies.
    P must point within ST or one past its end. */
 size_t
 ds_pointer_to_position (const struct string *st, const char *p)
@@ -1163,38 +1279,74 @@ ds_capacity (const struct string *st)
 char *
 ds_cstr (const struct string *st_)
 {
-  struct string *st = (struct string *) st_;
+  struct string *st = CONST_CAST (struct string *, st_);
   if (st->ss.string == NULL)
     ds_extend (st, 1);
   st->ss.string[st->ss.length] = '\0';
   return st->ss.string;
 }
 
-/* Appends to ST a newline-terminated line read from STREAM.
-   Newline is the last character of ST on return, unless an I/O error
-   or end of file is encountered after reading some characters.
-   Returns true if a line is successfully read, false if no characters at
-   all were read before an I/O error or end of file was
-   encountered. */
-bool
-ds_read_line (struct string *st, FILE *stream)
+/* Returns the value of ST as a null-terminated string and then
+   reinitialized ST as an empty string.  The caller must free the
+   returned string with free(). */
+char *
+ds_steal_cstr (struct string *st)
 {
-  int c;
+  char *s = ds_cstr (st);
+  ds_init_empty (st);
+  return s;
+}
 
-  c = getc (stream);
-  if (c == EOF)
-    return false;
+/* Reads bytes from STREAM and appends them to ST, stopping
+   after MAX_LENGTH bytes, after appending a newline, or
+   after an I/O error or end of file was encountered, whichever
+   comes first.  Returns true if at least one byte was added
+   to ST, false if no bytes were read before an I/O error or
+   end of file (or if MAX_LENGTH was 0).
 
-  for (;;)
-    {
-      ds_put_char (st, c);
-      if (c == '\n')
-       return true;
+   This function treats LF and CR LF sequences as new-line,
+   translating each of them to a single '\n' in ST. */
+bool
+ds_read_line (struct string *st, FILE *stream, size_t max_length)
+{
+  size_t length;
 
-      c = getc (stream);
-      if (c == EOF)
-       return true;
+  for (length = 0; length < max_length; length++)
+    {
+      int c = getc (stream);
+      switch (c)
+        {
+        case EOF:
+          return length > 0;
+
+        case '\n':
+          ds_put_byte (st, c);
+          return true;
+
+        case '\r':
+          c = getc (stream);
+          if (c == '\n')
+            {
+              /* CR followed by LF is special: translate to \n. */
+              ds_put_byte (st, '\n');
+              return true;
+            }
+          else
+            {
+              /* CR followed by anything else is just CR. */
+              ds_put_byte (st, '\r');
+              if (c == EOF)
+                return true;
+              ungetc (c, stream);
+            }
+          break;
+
+        default:
+          ds_put_byte (st, c);
+        }
     }
+
+  return length > 0;
 }
 
 /* Removes a comment introduced by `#' from ST,
@@ -1240,12 +1392,12 @@ ds_read_config_line (struct string *st, int *line_number, FILE *stream)
   ds_clear (st);
   do
     {
-      if (!ds_read_line (st, stream))
+      if (!ds_read_line (st, stream, SIZE_MAX))
         return false;
       (*line_number)++;
       ds_rtrim (st, ss_cstr (CC_SPACES));
     }
-  while (ds_chomp (st, '\\'));
+  while (ds_chomp_byte (st, '\\'));
 
   remove_comment (st);
   return true;
@@ -1349,16 +1501,57 @@ ds_put_vformat (struct string *st, const char *format, va_list args_)
     }
 }
 
-/* Appends character CH to ST. */
+/* Appends byte CH to ST. */
 void
-ds_put_char (struct string *st, int ch)
+ds_put_byte (struct string *st, int ch)
 {
   ds_put_uninit (st, 1)[0] = ch;
 }
 
-/* Appends CNT copies of character CH to ST. */
+/* Appends CNT copies of byte CH to ST. */
 void
-ds_put_char_multiple (struct string *st, int ch, size_t cnt)
+ds_put_byte_multiple (struct string *st, int ch, size_t cnt)
 {
   memset (ds_put_uninit (st, cnt), ch, cnt);
 }
+
+
+/* If relocation has been enabled, replace ST,
+   with its relocated version */
+void
+ds_relocate (struct string *st)
+{
+  const char *orig = ds_cstr (st);
+  const char *rel = relocate (orig);
+
+  if ( orig != rel)
+    {
+      ds_clear (st);
+      ds_put_cstr (st, rel);
+      /* The documentation for relocate says that casting away const
+       and then freeing is appropriate ... */
+      free (CONST_CAST (char *, rel));
+    }
+}
+
+
+\f
+
+/* Operations on uint8_t "strings" */
+
+/* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
+   DST is truncated to DST_SIZE bytes or padded on the right with
+   copies of PAD as needed. */
+void
+u8_buf_copy_rpad (uint8_t *dst, size_t dst_size,
+                 const uint8_t *src, size_t src_size,
+                 char pad)
+{
+  if (src_size >= dst_size)
+    memmove (dst, src, dst_size);
+  else
+    {
+      memmove (dst, src, src_size);
+      memset (&dst[src_size], pad, dst_size - src_size);
+    }
+}