+/**
+ * Concatenate two strings.
+ */
+string_t concat_strings(const string_t *const s1, const string_t *const s2)
+{
+ const size_t len1 = s1->size - 1;
+ const size_t len2 = s2->size - 1;
+
+ char *const concat = obstack_alloc(&symbol_obstack, len1 + len2 + 1);
+ memcpy(concat, s1->begin, len1);
+ memcpy(concat + len1, s2->begin, len2 + 1);
+
+ return identify_string(concat, len1 + len2 + 1);
+}
+
+string_t make_string(const char *string)
+{
+ size_t len = strlen(string) + 1;
+ char *const space = obstack_alloc(&symbol_obstack, len);
+ memcpy(space, string, len);
+
+ return identify_string(space, len);
+}
+
+static void grow_symbol(utf32 const tc)
+{
+ struct obstack *const o = &symbol_obstack;
+ if (tc < 0x80U) {
+ obstack_1grow(o, tc);
+ } else if (tc < 0x800) {
+ obstack_1grow(o, 0xC0 | (tc >> 6));
+ obstack_1grow(o, 0x80 | (tc & 0x3F));
+ } else if (tc < 0x10000) {
+ obstack_1grow(o, 0xE0 | ( tc >> 12));
+ obstack_1grow(o, 0x80 | ((tc >> 6) & 0x3F));
+ obstack_1grow(o, 0x80 | ( tc & 0x3F));
+ } else {
+ obstack_1grow(o, 0xF0 | ( tc >> 18));
+ obstack_1grow(o, 0x80 | ((tc >> 12) & 0x3F));
+ obstack_1grow(o, 0x80 | ((tc >> 6) & 0x3F));
+ obstack_1grow(o, 0x80 | ( tc & 0x3F));
+ }
+}
+
+/**
+ * Parse a string literal and set lexer_token.
+ */
+static void parse_string_literal(void)
+{
+ eat('"');
+
+ while (true) {
+ switch (c) {
+ case '\\': {
+ utf32 const tc = parse_escape_sequence();
+ if (tc >= 0x100) {
+ warningf(&lexer_pos, "escape sequence out of range");
+ }
+ obstack_1grow(&symbol_obstack, tc);