+ 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
+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)
+ {
+ ss_dealloc (&st->ss);
+ st->ss.string = NULL;
+ st->ss.length = 0;
+ st->capacity = 0;
+ }
+}
+
+/* Swaps the contents of strings A and B. */
+void
+ds_swap (struct string *a, struct string *b)
+{
+ struct string tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+/* Helper function for ds_register_pool. */
+static void
+free_string (void *st_)
+{
+ struct string *st = st_;
+ ds_destroy (st);
+}
+
+/* Arranges for ST to be destroyed automatically as part of
+ POOL. */
+void
+ds_register_pool (struct string *st, struct pool *pool)
+{
+ pool_register (pool, free_string, st);
+}
+
+/* Cancels the arrangement for ST to be destroyed automatically
+ as part of POOL. */
+void
+ds_unregister_pool (struct string *st, struct pool *pool)
+{
+ pool_unregister (pool, st);
+}
+
+/* Copies SRC into DST.
+ DST and SRC may be the same string. */
+void
+ds_assign_string (struct string *dst, const struct string *src)
+{
+ ds_assign_substring (dst, ds_ss (src));
+}
+
+/* Replaces DST by SS.
+ SS may be a substring of DST. */
+void
+ds_assign_substring (struct string *dst, struct substring ss)
+{
+ dst->ss.length = ss.length;
+ ds_extend (dst, ss.length);
+ memmove (dst->ss.string, ss.string, ss.length);
+}
+
+/* Replaces DST by null-terminated string SRC. SRC may overlap
+ with DST. */
+void
+ds_assign_cstr (struct string *dst, const char *src)
+{
+ ds_assign_substring (dst, ss_cstr (src));
+}
+
+/* Truncates ST to zero length. */
+void
+ds_clear (struct string *st)
+{
+ st->ss.length = 0;
+}
+
+/* Returns a substring that contains ST. */
+struct substring
+ds_ss (const struct string *st)
+{
+ return st->ss;
+}
+
+/* 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 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 bytes in
+ ST. If CNT exceeds the length of ST, then the substring will
+ contain all of ST. */
+struct substring
+ds_head (const struct string *st, size_t cnt)
+{
+ return ss_head (ds_ss (st), cnt);
+}
+
+/* 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
+ds_tail (const struct string *st, size_t cnt)
+{
+ return ss_tail (ds_ss (st), cnt);