Code/Util/EscapeCharacters.cs
using System.Globalization;
using System.Text;

namespace WasmBox.Util.EscapeCharacters;

/// <summary>
///   Extension methods for <see cref="string"/>.
/// </summary>
public static class StringExtensions
{
    public static string Escape( this string s )
    {
        var sb = new StringBuilder();
        foreach ( var c in s )
        {
            switch ( c )
            {
                case '\n':
                    sb.Append( @"\n" );
                    break;
                case '\t':
                    sb.Append( @"\t" );
                    break;
                case '\r':
                    sb.Append( @"\r" );
                    break;
                case '\f':
                    sb.Append( @"\f" );
                    break;
                case '\b':
                    sb.Append( @"\b" );
                    break;
                case '\\':
                    sb.Append( @"\\" );
                    break;
                case '"':
                    sb.Append( @"\""" );
                    break;
                case '\0':
                    sb.Append( @"\0" );
                    break;
                case '\a':
                    sb.Append( @"\a" );
                    break;
                case '\v':
                    sb.Append( @"\v" );
                    break;
                default:
                    if ( c < ' ' || c > '~' )
                    {
                        if (c <= 0xff)
                        {
                            sb.AppendFormat(CultureInfo.InvariantCulture, @"\x{0:x2}", (int)c);
                        }
                        else
                        {
                            sb.AppendFormat(CultureInfo.InvariantCulture, @"\u{0:x4}", (int)c);
                        }
                    }
                    else
                    {
                        sb.Append( c );
                    }
                    break;
            }
        }
        return sb.ToString();
    }

    public static string UnEscape( this string s )
    {
        var sb = new StringBuilder();
        for ( var i = 0; i < s.Length; i++ )
        {
            if ( s[i] != '\\' )
            {
                sb.Append( s[i] );
                continue;
            }

            if (i + 1 >= s.Length) throw new FormatException("Invalid escape sequence: trailing backslash.");

            i++; // Consume backslash
            switch ( s[i] )
            {
                case 'n': sb.Append( '\n' ); break;
                case 't': sb.Append( '\t' ); break;
                case 'r': sb.Append( '\r' ); break;
                case 'f': sb.Append( '\f' ); break;
                case 'b': sb.Append( '\b' ); break;
                case 'a': sb.Append( '\a' ); break;
                case 'v': sb.Append( '\v' ); break;
                case '0': sb.Append( '\0' ); break;
                case '\\': sb.Append( '\\' ); break;
                case '"': sb.Append( '"' ); break;
                case 'c':
                    if (i + 1 >= s.Length) throw new FormatException("Invalid escape sequence: \\c at end of string.");
                    i++;
                    sb.Append((char)(s[i] & 0x1f));
                    break;
                case 'x':
                    var hexEnd = i + 1;
                    while (hexEnd < s.Length && hexEnd < i + 5 && "0123456789abcdefABCDEF".IndexOf(s[hexEnd]) != -1)
                    {
                        hexEnd++;
                    }
                    if (hexEnd == i + 1) throw new FormatException("Invalid escape sequence: \\x with no hex digits.");
                    
                    var hexValue = s.Substring(i + 1, hexEnd - (i + 1));
                    sb.Append((char)int.Parse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture));
                    i = hexEnd - 1;
                    break;
                case 'u':
                    if (i + 4 >= s.Length) throw new FormatException("Invalid escape sequence: \\u with less than 4 hex digits.");
                    var uhex = s.Substring(i + 1, 4);
                    for(var j=0; j<4; j++) {
                        if ("0123456789abcdefABCDEF".IndexOf(uhex[j]) == -1) {
                            throw new FormatException("Invalid escape sequence: \\u with non-hex characters.");
                        }
                    }
                    sb.Append((char)ushort.Parse(uhex, NumberStyles.HexNumber, CultureInfo.InvariantCulture));
                    i += 4;
                    break;
                default:
                    throw new FormatException($"Invalid escape character: '{s[i]}'");
            }
        }

        return sb.ToString();
    }
}