str: New function ss_realloc().
[pspp-builds.git] / src / libpspp / str.c
index 79f3c912e9b649cf39d3ab9a0b37510e25867bf5..25e2cfd54872d2b1e232b8673cf8075fb6465bf4 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2009 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010 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/cast.h>
-#include <libpspp/message.h>
-#include <libpspp/pool.h>
+#include "libpspp/cast.h"
+#include "libpspp/message.h"
+#include "libpspp/pool.h"
 
-#include <relocatable.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.  */
@@ -49,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
@@ -193,8 +182,8 @@ buf_copy_rpad (char *dst, size_t dst_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)
 {
@@ -213,7 +202,7 @@ str_copy_rpad (char *dst, size_t dst_size, const char *src)
 }
 
 /* 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)
 {
@@ -230,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)
@@ -243,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)
 {
@@ -251,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)
 {
@@ -294,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 *
@@ -320,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)
@@ -361,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)
@@ -379,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)
 {
@@ -397,6 +341,12 @@ ss_alloc_uninit (struct substring *new, size_t cnt)
   new->length = cnt;
 }
 
+void
+ss_realloc (struct substring *ss, size_t size)
+{
+  ss->string = xrealloc (ss->string, size);
+}
+
 /* Makes a pool_alloc_unaligned()'d copy of the contents of OLD
    in POOL, and stores it in NEW. */
 void
@@ -408,7 +358,7 @@ ss_alloc_substring_pool (struct substring *new, struct substring old,
   memcpy (new->string, old.string, old.length);
 }
 
-/* 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)
 {
@@ -423,7 +373,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)
 {
@@ -431,22 +381,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)
 {
@@ -455,7 +405,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)
 {
@@ -463,7 +413,7 @@ 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)
@@ -523,12 +473,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)
 {
@@ -538,10 +488,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)
     {
@@ -553,11 +503,11 @@ ss_match_char (struct substring *ss, char c)
     return false;
 }
 
-/* If the first character in SS is in MATCH, removes it and
-   returns the character that was removed.
+/* 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_char_in (struct substring *ss, struct substring match)
+ss_match_byte_in (struct substring *ss, struct substring match)
 {
   int c = EOF;
   if (ss->length > 0
@@ -585,10 +535,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)
@@ -600,21 +550,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);
@@ -623,7 +573,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)
@@ -651,7 +601,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)
@@ -659,28 +609,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
@@ -689,7 +639,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)
@@ -697,7 +647,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)
@@ -705,26 +655,26 @@ 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 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;
 }
@@ -732,7 +682,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;
@@ -777,7 +727,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)
@@ -797,6 +747,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
@@ -916,20 +941,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
@@ -938,7 +963,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
@@ -947,7 +972,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)
@@ -973,23 +998,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)
 {
@@ -999,8 +1024,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)
 {
@@ -1008,7 +1033,7 @@ 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 (struct string *st, char c)
@@ -1051,17 +1076,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)
@@ -1072,7 +1097,7 @@ ds_set_length (struct string *st, size_t new_length, char pad)
     st->ss.length = new_length;
 }
 
-/* Removes N characters from ST starting at offset START. */
+/* Removes N bytes from ST starting at offset START. */
 void
 ds_remove (struct string *st, size_t start, size_t n)
 {
@@ -1080,12 +1105,12 @@ ds_remove (struct string *st, size_t start, size_t n)
     {
       if (st->ss.length - start <= n)
         {
-          /* All characters at or beyond START are deleted. */
+          /* All bytes at or beyond START are deleted. */
           st->ss.length = start;
         }
       else
         {
-          /* Some characters remain and must be shifted into
+          /* Some bytes remain and must be shifted into
              position. */
           memmove (st->ss.string + st->ss.length,
                    st->ss.string + st->ss.length + n,
@@ -1095,7 +1120,7 @@ ds_remove (struct string *st, size_t start, size_t n)
     }
   else
     {
-      /* There are no characters to delete or no characters at or
+      /* There are no bytes to delete or no bytes at or
          beyond START, hence deletion is a no-op. */
     }
 }
@@ -1122,7 +1147,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)
@@ -1130,7 +1155,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
@@ -1139,7 +1164,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)
@@ -1147,7 +1172,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)
@@ -1155,7 +1180,7 @@ ds_last (const struct string *st)
   return ss_last (ds_ss (st));
 }
 
-/* Returns the number of consecutive characters at the beginning
+/* 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)
@@ -1163,7 +1188,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)
@@ -1171,13 +1196,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
@@ -1188,7 +1213,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)
@@ -1233,16 +1258,15 @@ ds_steal_cstr (struct string *st)
   return s;
 }
 
-/* Reads characters from STREAM and appends them to ST, stopping
-   after MAX_LENGTH characters, after appending a newline, or
+/* 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 character was added
-   to ST, false if no characters were read before an I/O error or
+   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).
 
-   This function accepts LF, CR LF, and CR sequences as new-line,
-   and translates each of them to a single '\n' new-line
-   character in ST. */
+   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)
 {
@@ -1251,21 +1275,36 @@ ds_read_line (struct string *st, FILE *stream, size_t max_length)
   for (length = 0; length < max_length; length++)
     {
       int c = getc (stream);
-      if (c == EOF)
-        break;
-
-      if (c == '\r')
+      switch (c)
         {
+        case EOF:
+          return length > 0;
+
+        case '\n':
+          ds_put_byte (st, c);
+          return true;
+
+        case '\r':
           c = getc (stream);
-          if (c != '\n')
+          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);
-              c = '\n';
             }
+          break;
+
+        default:
+          ds_put_byte (st, c);
         }
-      ds_put_char (st, c);
-      if (c == '\n')
-        return true;
     }
 
   return length > 0;
@@ -1423,16 +1462,16 @@ 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);
 }
@@ -1450,7 +1489,9 @@ ds_relocate (struct string *st)
     {
       ds_clear (st);
       ds_put_cstr (st, rel);
-      free ((char *) rel);
+      /* The documentation for relocate says that casting away const
+       and then freeing is appropriate ... */
+      free (CONST_CAST (char *, rel));
     }
 }