+ else
+ return false;
+}
+
+/* Removes the first character from SS and returns it.
+ If SS is empty, returns EOF without modifying SS. */
+int
+ss_get_char (struct substring *ss)
+{
+ int c = ss_first (*ss);
+ if (c != EOF)
+ {
+ ss->string++;
+ ss->length--;
+ }
+ return c;
+}
+
+/* Stores the prefix of SS up to the first DELIMITER in OUT (if
+ any). Trims those same characters 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);
+}
+
+/* Stores the first CNT characters in SS in OUT (or fewer, if SS
+ is shorter than CNT characters). Trims the same characters
+ from the beginning of SS. Returns CNT. */
+size_t
+ss_get_chars (struct substring *ss, size_t cnt, struct substring *out)
+{
+ *out = ss_head (*ss, cnt);
+ ss_advance (ss, cnt);
+ return cnt;
+}
+
+/* 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
+ the integer's value into *VALUE. */
+size_t
+ss_get_long (struct substring *ss, long *value)
+{
+ char tmp[64];
+ size_t length;
+
+ length = ss_span (*ss, ss_cstr ("+-"));
+ length += ss_span (ss_substr (*ss, length, SIZE_MAX), ss_cstr (CC_DIGITS));
+ if (length > 0 && length < sizeof tmp)
+ {
+ char *tail;
+
+ memcpy (tmp, ss_data (*ss), length);
+ tmp[length] = '\0';
+
+ *value = strtol (tmp, &tail, 10);
+ if (tail - tmp == length)
+ {
+ ss_advance (ss, length);
+ return length;
+ }
+ }
+ *value = 0;
+ return 0;
+}
+
+/* Returns true if SS is empty (contains no characters),
+ false otherwise. */
+bool
+ss_is_empty (struct substring ss)
+{
+ return ss.length == 0;
+}
+
+/* Returns the number of characters in SS. */
+size_t
+ss_length (struct substring ss)
+{
+ return ss.length;
+}
+
+/* Returns a pointer to the characters in SS. */
+char *
+ss_data (struct substring ss)
+{
+ return ss.string;
+}
+
+/* Returns a pointer just past the last character 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
+ range of unsigned char. Returns EOF if IDX is out of the
+ range of indexes for SS. */
+int
+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
+ unsigned char. Returns EOF if SS is the empty string. */
+int
+ss_first (struct substring ss)
+{
+ return ss_at (ss, 0);
+}
+
+/* Returns the last character 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)
+{
+ return ss.length > 0 ? (unsigned char) ss.string[ss.length - 1] : EOF;
+}
+
+/* Returns the number of contiguous characters 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)
+ break;
+ return i;
+}
+
+/* Returns the number of contiguous characters 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)
+ 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));