+ switch (ec) {
+ case '"': return '"';
+ case '\'': return '\'';
+ case '\\': return '\\';
+ case '?': return '\?';
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ case 'x':
+ return parse_hex_sequence();
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ return parse_octal_sequence(ec);
+ case EOF:
+ parse_error("reached end of file while parsing escape sequence");
+ return EOF;
+ case 'e':
+ if (c_mode & _GNUC)
+ return 27; /* hopefully 27 is ALWAYS the code for ESACAPE */
+ /*fallthrough*/
+ default:
+ parse_error("unknown escape sequence");
+ return EOF;
+ }
+}
+
+/**
+ * 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);
+
+#if 0 /* TODO hash */
+ const char *result = strset_insert(&stringset, concat);
+ if(result != concat) {
+ obstack_free(&symbol_obstack, concat);
+ }
+
+ return result;
+#else
+ return (string_t){ concat, len1 + len2 + 1 };
+#endif
+}
+
+/**
+ * Concatenate a string and a wide string.
+ */
+wide_string_t concat_string_wide_string(const string_t *const s1, const wide_string_t *const s2)
+{
+ const size_t len1 = s1->size - 1;
+ const size_t len2 = s2->size - 1;
+
+ wchar_rep_t *const concat = obstack_alloc(&symbol_obstack, (len1 + len2 + 1) * sizeof(*concat));
+ const char *const src = s1->begin;
+ for (size_t i = 0; i != len1; ++i) {
+ concat[i] = src[i];
+ }
+ memcpy(concat + len1, s2->begin, (len2 + 1) * sizeof(*concat));
+
+ return (wide_string_t){ concat, len1 + len2 + 1 };
+}
+
+/**
+ * Concatenate two wide strings.
+ */
+wide_string_t concat_wide_strings(const wide_string_t *const s1, const wide_string_t *const s2)
+{
+ const size_t len1 = s1->size - 1;
+ const size_t len2 = s2->size - 1;
+
+ wchar_rep_t *const concat = obstack_alloc(&symbol_obstack, (len1 + len2 + 1) * sizeof(*concat));
+ memcpy(concat, s1->begin, len1 * sizeof(*concat));
+ memcpy(concat + len1, s2->begin, (len2 + 1) * sizeof(*concat));
+
+ return (wide_string_t){ concat, len1 + len2 + 1 };
+}
+
+/**
+ * Concatenate a wide string and a string.
+ */
+wide_string_t concat_wide_string_string(const wide_string_t *const s1, const string_t *const s2)
+{
+ const size_t len1 = s1->size - 1;
+ const size_t len2 = s2->size - 1;
+
+ wchar_rep_t *const concat = obstack_alloc(&symbol_obstack, (len1 + len2 + 1) * sizeof(*concat));
+ memcpy(concat, s1->begin, len1 * sizeof(*concat));
+ const char *const src = s2->begin;
+ for (size_t i = 0; i != len2 + 1; ++i) {
+ concat[i] = src[i];
+ }
+
+ return (wide_string_t){ concat, len1 + len2 + 1 };
+}
+
+/**
+ * Parse a string literal and set lexer_token.
+ */
+static void parse_string_literal(void)
+{
+ const unsigned start_linenr = lexer_token.source_position.linenr;
+
+ eat('"');
+
+ int tc;