Similarly in GCC it is hardcoded, with a choice of ASCII or EBCDIC (in gcc/libcpp/charset.cc):
/* Convert an escape sequence (pointed to by FROM) to its value on
the target, and to the execution character set. Do not scan past
LIMIT. Write the converted value into TBUF, if TBUF is non-NULL.
Returns an advanced pointer. Handles all relevant diagnostics.
If LOC_READER is non-NULL, then RANGES must be non-NULL: location
information is read from *LOC_READER, and *RANGES is updated
accordingly. */
static const uchar *
convert_escape (cpp_reader *pfile, const uchar *from, const uchar *limit,
struct _cpp_strbuf *tbuf, struct cset_converter cvt,
cpp_string_location_reader *loc_reader,
cpp_substring_ranges *ranges, bool uneval)
{
/\* Values of \a \b \e \f \n \r \t \v respectively. */
#if HOST_CHARSET == HOST_CHARSET_ASCII
static const uchar charconsts[] = { 7, 8, 27, 12, 10, 13, 9, 11 };
#elif HOST_CHARSET == HOST_CHARSET_EBCDIC
static const uchar charconsts[] = { 47, 22, 39, 12, 21, 13, 5, 11 };
#else
#error "unknown host character set"
#endif
uchar c;
/* Record the location of the backslash. */
source_range char_range;
if (loc_reader)
char_range = loc_reader->get_next ();
c = *from;
switch (c)
{
...
case 'n': c = charconsts[4]; break;
...