+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 byte C in
+ ST at or after position OFS, or SIZE_MAX if there is no such
+ occurrence. */
+size_t
+ds_find_byte (const struct string *st, char c)
+{
+ return ss_find_byte (ds_ss (st), c);
+}
+
+/* Compares A and B and returns a strcmp()-type comparison
+ result. */
+int
+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 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)
+{
+ return ss_pointer_to_position (ds_ss (st), p);
+}
+
+/* Allocates and returns a null-terminated string that contains
+ ST. */
+char *
+ds_xstrdup (const struct string *st)
+{
+ return ss_xstrdup (ds_ss (st));
+}
+
+/* Returns the allocation size of ST. */
+size_t
+ds_capacity (const struct string *st)
+{
+ return st->capacity;
+}
+
+/* Returns the value of ST as a null-terminated string. */
+char *
+ds_cstr (const 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;
+}
+
+/* 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)
+{
+ char *s = ds_cstr (st);
+ ds_init_empty (st);
+ return s;
+}
+
+/* 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).
+
+ 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;
+
+ 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,
+ ignoring occurrences inside quoted strings. */
+static void
+remove_comment (struct string *st)
+{
+ char *cp;
+ int quote = 0;
+
+ for (cp = ds_data (st); cp < ds_end (st); cp++)
+ if (quote)
+ {
+ if (*cp == quote)
+ quote = 0;
+ else if (*cp == '\\')
+ cp++;
+ }
+ else if (*cp == '\'' || *cp == '"')
+ quote = *cp;
+ else if (*cp == '#')
+ {
+ ds_truncate (st, cp - ds_cstr (st));
+ break;
+ }
+}
+
+/* Reads a line from STREAM into ST, then preprocesses as follows:
+
+ - Splices lines terminated with `\'.
+
+ - Deletes comments introduced by `#' outside of single or double
+ quotes.
+
+ - Deletes trailing white space.
+
+ Returns true if a line was successfully read, false on
+ failure. If LINE_NUMBER is non-null, then *LINE_NUMBER is
+ incremented by the number of lines read. */
+bool
+ds_read_config_line (struct string *st, int *line_number, FILE *stream)
+{
+ ds_clear (st);
+ do
+ {
+ if (!ds_read_line (st, stream, SIZE_MAX))
+ return false;
+ (*line_number)++;
+ ds_rtrim (st, ss_cstr (CC_SPACES));
+ }
+ while (ds_chomp_byte (st, '\\'));
+
+ remove_comment (st);
+ return true;
+}
+
+/* Attempts to read SIZE * CNT bytes from STREAM and append them
+ to ST.
+ Returns true if all the requested data was read, false otherwise. */
+bool
+ds_read_stream (struct string *st, size_t size, size_t cnt, FILE *stream)
+{
+ if (size != 0)
+ {
+ size_t try_bytes = xtimes (cnt, size);
+ if (size_in_bounds_p (xsum (ds_length (st), try_bytes)))
+ {
+ char *buffer = ds_put_uninit (st, try_bytes);
+ size_t got_bytes = fread (buffer, 1, try_bytes, stream);
+ ds_truncate (st, ds_length (st) - (try_bytes - got_bytes));
+ return got_bytes == try_bytes;
+ }
+ else
+ {
+ errno = ENOMEM;
+ return false;
+ }
+ }
+ else
+ return true;
+}
+
+/* Concatenates S onto ST. */
+void
+ds_put_cstr (struct string *st, const char *s)
+{
+ if (s != NULL)
+ ds_put_substring (st, ss_cstr (s));
+}
+
+/* Concatenates SS to ST. */
+void
+ds_put_substring (struct string *st, struct substring ss)