+/**
+ * Parse an escape sequence.
+ */
+static utf32 parse_escape_sequence(void)
+{
+ eat('\\');
+
+ utf32 const ec = c;
+ next_char();
+
+ 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;
+ /* \E is not documented, but handled, by GCC. It is acceptable according
+ * to §6.11.4, whereas \e is not. */
+ case 'E':
+ case 'e':
+ if (c_mode & _GNUC)
+ return 27; /* hopefully 27 is ALWAYS the code for ESCAPE */
+ /* FALLTHROUGH */
+ default:
+ /* §6.4.4.4:8 footnote 64 */
+ 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 (warning.traditional) {
+ warningf(&lexer_token.source_position,
+ "traditional C rejects string constant concatenation");
+ }
+#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));
+ if (warning.traditional) {
+ warningf(&lexer_token.source_position,
+ "traditional C rejects string constant concatenation");
+ }
+
+ 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));
+ if (warning.traditional) {
+ warningf(&lexer_token.source_position,
+ "traditional C rejects string constant concatenation");
+ }
+
+ 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;
+ wchar_rep_t *const dst = concat + len1;
+ for (size_t i = 0; i != len2 + 1; ++i) {
+ dst[i] = src[i];
+ }
+ if (warning.traditional) {
+ warningf(&lexer_token.source_position,
+ "traditional C rejects string constant concatenation");
+ }
+
+ return (wide_string_t){ concat, len1 + len2 + 1 };
+}
+
+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)
+{
+ const unsigned start_linenr = lexer_token.source_position.linenr;
+
+ eat('"');
+
+ while(1) {