+ size_t i;
+ for (i = 0; i < ss.length; i++)
+ if (ss_find_char (stop_set, ss.string[i]) != SIZE_MAX)
+ break;
+ return i;
+}
+
+/* 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)
+{
+ const char *p = memchr (ss.string, c, ss.length);
+ return p != NULL ? p - ss.string : SIZE_MAX;
+}
+
+/* Compares A and B and returns a strcmp()-type comparison
+ result. */
+int
+ss_compare (struct substring a, struct substring b)
+{
+ int retval = memcmp (a.string, b.string, MIN (a.length, b.length));
+ if (retval == 0)
+ retval = a.length < b.length ? -1 : a.length > b.length;
+ return retval;
+}
+
+/* Compares A and B case-insensitively and returns a
+ strcmp()-type comparison result. */
+int
+ss_compare_case (struct substring a, struct substring b)
+{
+ int retval = memcasecmp (a.string, b.string, MIN (a.length, b.length));
+ if (retval == 0)
+ retval = a.length < b.length ? -1 : a.length > b.length;
+ return retval;
+}
+
+/* Compares A and B and returns true if their contents are
+ identical, false otherwise. */
+int
+ss_equals (struct substring a, struct substring b)
+{
+ return a.length == b.length && !memcmp (a.string, b.string, a.length);
+}
+
+/* Compares A and B and returns true if their contents are
+ identical except possibly for case differences, false
+ otherwise. */
+int
+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.
+ P must point within SS or one past its end. */
+size_t
+ss_pointer_to_position (struct substring ss, const char *p)
+{
+ size_t pos = p - ss.string;
+ assert (pos <= ss.length);
+ return pos;
+}
+
+/* Allocates and returns a null-terminated string that contains
+ SS. */
+char *
+ss_xstrdup (struct substring ss)
+{
+ char *s = xmalloc (ss.length + 1);
+ memcpy (s, ss.string, ss.length);
+ s[ss.length] = '\0';
+ return s;
+}
+\f
+/* Initializes ST as an empty string. */
+void
+ds_init_empty (struct string *st)
+{
+ st->ss = ss_empty ();
+ st->capacity = 0;
+}
+
+/* Initializes ST with initial contents S. */
+void
+ds_init_string (struct string *st, const struct string *s)
+{
+ ds_init_substring (st, ds_ss (s));
+}
+
+/* Initializes ST with initial contents SS. */
+void
+ds_init_substring (struct string *st, struct substring ss)
+{
+ st->capacity = MAX (8, ss.length * 2);
+ st->ss.string = xmalloc (st->capacity + 1);
+ memcpy (st->ss.string, ss.string, ss.length);
+ st->ss.length = ss.length;
+}
+
+/* Initializes ST with initial contents S. */
+void
+ds_init_cstr (struct string *st, const char *s)
+{
+ ds_init_substring (st, ss_cstr (s));
+}
+
+/* Frees ST. */
+void
+ds_destroy (struct string *st)
+{
+ if (st != NULL)