Convertidor UTF Unicode

17

El objetivo es crear un convertidor totalmente compatible entre las codificaciones Unicode oficiales tal como se indica en las preguntas frecuentes de UTF . Dado que esto se centra en Unicode, aceptaré la respuesta con el recuento de bytes más bajo utilizando la mejor de las codificaciones posibles (que probablemente será UTF-8, a menos que tal vez la programes en APL). Pido disculpas por la larga publicación, pero gran parte explica las codificaciones a las que también se puede acceder en la especificación oficial (pdf, sección 3.9 D90 - D92) , o Wikipedia .

Especificaciones

Si en algún momento su idioma de elección no puede cumplir exactamente con un requisito, sustitúyalo por algo que mantenga el espíritu de las reglas dadas. P.ej. no todos los idiomas tienen arrays, funciones, etc. incorporados

  • No usar bibliotecas de cadenas / funciones, ni codificar bibliotecas / funciones. El objetivo de este código de golf es implementar el convertidor utilizando la manipulación bit / byte. Sin embargo, se permite el uso de cadenas en su capacidad como una matriz de caracteres o bytes. Ah, y tampoco hay llamadas del sistema operativo que realicen la conversión.

  • El convertidor es una función que tomará tres parámetros: una matriz de bytes que representa la cadena de entrada codificada y las codificaciones de "entrada" y "salida" representadas como números. Arbitrariamente asignaremos UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, and UTF32LEnúmeros del 0 al 6 en ese orden. No es necesario verificar si el número es < 0o > 6, asumiremos que estos parámetros son correctos. El convertidor devolverá una matriz de bytes válida en la codificación de salida deseada.

  • Usaremos el carácter nulo ( U+0000) como un terminador de cadena. Cualquier cosa después de esto no importa. Asumiremos que la matriz de entrada tiene el carácter nulo en alguna parte, por lo que no necesita hacer una verificación de límites.

  • Según las preguntas frecuentes , si la matriz de bytes de entrada no es válida para su codificación declarada, debemos señalar un error. Haremos esto de una de las siguientes maneras: bloquear el programa, lanzar una excepción, devolver nulo o devolver una matriz cuyos primeros cuatro bytes son todos 0 (para que pueda reconocerse como U+0000en cada codificación).

Las codificaciones

Deben seguirse las especificaciones oficiales, pero Wikipedia proporciona una buena (y hasta donde creo correcta) explicación de las codificaciones, y las resumiré aquí para completarlas. Tenga en cuenta que UTF-16 y UTF-32 tienen variantes para endianness .

UTF-32, UTF-32LE, UTF-32BE

La codificación más simple, cada punto de código simplemente se codifica en 4 bytes igual a su valor numérico. LE / BE representa endianness (little endian / big endian).

UTF-16, UTF-16LE, UTF-16BE

Los puntos de código de U+0000 - U+FFFFse codifican en 2 bytes iguales a su valor numérico. Los valores más grandes se codifican utilizando un par de sustitutos que son valores reservados U+D800 - U+DFFF. Entonces, para codificar puntos mayores que U+FFFF, se puede usar el siguiente algoritmo (copiado descaradamente de Wikipedia ):

  • 0x010000 se resta del punto de código, dejando un número de 20 bits en el rango 0..0x0FFFFF.
  • Los primeros diez bits (un número en el rango 0..0x03FF) se agregan a 0xD800 para dar la primera unidad de código o sustituto principal, que estará en el rango 0xD800..0xDBFF [...].
  • Los diez bits bajos (también en el rango 0..0x03FF) se agregan a 0xDC00 para dar la segunda unidad de código o sustituto de camino, que estará en el rango 0xDC00..0xDFFF [...].

UTF-8

Los puntos de código de U+0000 - U+007Fse codifican como 1 byte igual a su valor numérico. De U+0080 - U+07FFellos se codifican como 110xxxxx 10xxxxxx, U+0800 - U+FFFFes decir 1110xxxx 10xxxxxx 10xxxxxx, los valores más altos son 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. Los x's son los bits del valor numérico del punto de código.

BOM

La marca de orden de bytes (BOM, U+FEFF) se usa como el primer punto de código para indicar endianness. Siguiendo las pautas de preguntas frecuentes sobre las listas de materiales , la lista de materiales se utilizará de la siguiente manera: porque UTF-8, UTF-16 and UTF-32es opcional. Si la lista de materiales está ausente en UTF-16o UTF-32, se supone que es big endian. La lista de materiales no debe aparecer en UTF-16LE, UTF-16BE, UTF-32LE and UTF-32BE.

Errores comunes que causan UTF inválido

Varias cosas pueden hacer que una secuencia de bytes sea UTF no válida.

  • UTF-8 y UTF-32: codificación directa de puntos de código sustituto ( U+D800 - U+DFFF) o puntos de código mayores que U+10FFFF.
  • UTF-8: muchas secuencias de bytes no válidas.
  • UTF-16: sustitutos no emparejados o mal emparejados.
  • BOM: debe usarse como se especifica en la sección de codificación. Tenga en cuenta que al generar UTF-16o UTF-32(no se especifica endianness inherente) puede elegir, pero con little endian, debe incluir la lista de materiales.

Tenga en cuenta que los puntos de código no asignados y sin caracteres (ambos distintos de los sustitutos) deben tratarse como caracteres normales.

DPenner1
fuente
"No usar bibliotecas de cadenas / funciones, ni codificar bibliotecas / funciones". ¿Qué pasa con las incorporaciones reales? En APL, ''⎕R''⍠'InEnc' 'UTF16BE' 'OutEnc' 'UTF8-BOM'.
Adám
2
@NBZ No se permitirían, ya que el objetivo de este desafío es implementar el comportamiento que proporcionan.
DPenner1
Nota para los respondedores: había abandonado más o menos esta pregunta, pero dado el reciente interés renovado, en los próximos días, me tomaré un tiempo para analizar las respuestas.
DPenner1

Respuestas:

3

C ++, (UTF-8) 971 bytes

#include<cstdint>
using u=uint8_t;using U=uint32_t;U i,o,x,b,m;U R(u*&p){x=*p++;if(!i){m=0;while(128>>m&x)++m;if(m>1)for(x&=127>>m;--m;)x=x<<6|((*p&192)-128?~0:*p++&63);return m?x=~0:x;}else if(i<3){x<<=8;x+=*p++;}else if(i<4){x+=*p++<<8;}else if(i<6){x<<=24;x+=*p++<<16;x+=*p++<<8;x+=*p++;}else{x+=*p++<<8;x+=*p++<<16;x+=*p++<<24;}return x;}U r(u*&p){U x0=R(p);if(i&&i<4&&x>>10==54)x=R(p)>>10==55?(x0<<10)+x-56613888:~0;if(!b++){if(x==65279)if(!i||i%3==1)r(p);else x=~0;else if(x==65534&&i==1)i=3,r(p);else if(x==4294836224&&i==4)i=6,r(p);}return x>1114111||x>>11==27?x=~0:x;}void w(U x,u*&p){if(!o){if(x<128)*p++=x;else{for(m=0;~63<<m&x;m+=6);for(*p++=~127>>m/6|x>>m;m;)*p++=128|x>>(m-=6)&63;}}else if(o<4&&x>65535)x-=65536,w(55296|x>>10,p),w(56320|x&1023,p);else if(o<3)*p++=x>>8,*p++=x;else if(o<4)*p++=x,*p++=x>>8;else if(o<6)*p++=x>>24,*p++=x>>16,*p++=x>>8,*p++=x;else*p++=x,*p++=x>>8,*p++=x>>16,*p++=x>>24;}int t(u*&p,u*&q){for(b=0,x=1;U(x+x);)w(r(p),q);return x;}

El siguiente programa legible puede condensarse en el formulario anterior filtrándolo a través del siguiente comando Perl:

perl -p0 -e 's!//.*!!g;s/\s+/ /g;s/ \B|\B //g;s/0x[\da-f]+/hex($&)/ige;s/#include<[^<>]+>/\n$&\n/g;s/^\n+//mg'

El comando anterior

  • elimina comentarios
  • elimina espacios en blanco innecesarios
  • convierte literales hexadecimales a decimales
  • restablece las nuevas #includelíneas alrededor de las líneas

Código legible

#include <cstdint>
using u = uint8_t;
using U = uint32_t;

U   i,                          // input encoding
    o,                          // output encoding
    x,                          // last read value
    b,                          // char count(BOM only valid when b==0)
    m;                          // temporary variable for measuring UTF-8

//   Encodings:
// 0 UTF-8
// 1 UTF-16
// 2 UTF-16BE
// 3 UTF-16LE
// 4 UTF-32
// 5 UTF-32BE
// 6 UTF-32LE

// Read a character or UTF-16 surrogate
U R(u*& p) {
    x = *p++;
    if (!i) { // UTF-8
        m=0; while (128>>m&x) ++m; // how many bytes?
        if (m>1) for (x&=127>>m; --m; ) x = x<<6 | ((*p&192)-128?~0:*p++&63);
        return m ? x=~0 : x;
    } else if (i<3) { // UTF-16, UTF-16BE
        x<<=8; x+=*p++;
    } else if (i<4) { // UTF-16LE
        x+=*p++<<8;
    } else if (i<6) { // UTF-32, UTF-32BE
        x<<=24; x+=*p++<<16; x+=*p++<<8; x+=*p++;
    } else { // UTF-32LE
        x+=*p++<<8; x+=*p++<<16; x+=*p++<<24;
    }
    return x;
}

// Read a character, combining surrogates, processing BOM, and checking range
U r(u*& p) {
    U x0 = R(p);
    if (i && i<4 && x>>10==54)
        x = R(p)>>10==55 ? (x0<<10)+x-56613888: ~0; // 56613888 == 0xd800<<10 + 0xdc00 - 0x10000
    if (!b++) {                 // first char - is it BOM?
        if (x==0xFEFF)
            if (!i || i%3==1)
                r(p); // BOM in UTF-8 or UTF-16 or UTF-32 - ignore, and read next char
            else
                x = ~0; // not allowed in these modes
        else if (x==0xFFFE && i==1)
            i=3,r(p); // reversed BOM in UTF-16 - change to little-endian, and read next char
        else if (x==0xFFFE0000 && i==4)
            i=6,r(p); // reversed BOM in UTF-32 - change to little-endian, and read next char
    }
    return x>0x10ffff || x>>11==27 ? x=~0 : x;
}


// Write character(assumed in-range)
void w(U x, u*& p) {
    if (!o) { // UTF-8
        if (x<128) *p++=x;        // ASCII
        else {
            for (m=0; ~63<<m&x; m+=6); // how many bits?
            for (*p++=~127>>m/6|x>>m; m; ) *p++ = 128|x>>(m-=6)&63;
        }
    } else if (o<4 && x>65535)  // UTF-16 surrogate
        x-=65536, w(0xD800|x>>10,p), w(0xDC00|x&0x3FF,p);
    else if (o<3)  // UTF-16, UTF-16BE
        *p++=x>>8, *p++=x;
    else if (o<4)  // UTF-16LE
        *p++=x, *p++=x>>8;
    else if (o<6)  // UTF-32, UTF-32BE
        *p++=x>>24, *p++=x>>16, *p++=x>>8, *p++=x;
    else  // UTF-32LE
        *p++=x, *p++=x>>8, *p++=x>>16, *p++=x>>24;
}

// Transcode
int t(u*& p, u*& q)                  // input, output
{
    for (b=0,x=1;U(x+x);)    // exit condition is true only for x==-x, i.e. 0 and ~0
        w(r(p),q);
    return x;
}

La función a llamar es t(), con las codificaciones de entrada y salida pasadas en las variables globales iy orespectivamente, y papuntando a los bytes de entrada, que deben estar terminados en nulo. qapunta al búfer de salida, que se sobrescribirá, y debe ser lo suficientemente grande para el resultado; no hay ningún intento de evitar el desbordamiento del búfer.

Espero que los comentarios del código sean suficientemente explicativos: pregunte a continuación si uno de ellos es demasiado críptico (¡pero haga un esfuerzo primero!).

Recopilé un conjunto de pruebas sustancial mientras desarrollaba esta respuesta; Lo incluyo a continuación en beneficio de otros participantes y para documentar mi interpretación de los requisitos:

Funciones de prueba

#include <vector>
#include <iostream>

std::ostream& operator<<(std::ostream& out, const std::vector<u>& v)
{
    out << "{ ";
    for (int i: v) out << i << " ";
    out << "}";
    return out;
}

int test_read(int encoding, std::vector<u> input, U expected)
{
    b = 0;
    i = encoding;
    auto d = input.data();
    U actual = r(d);
    if (actual == expected) return 0;
    std::cerr << std::hex << "Decoding " << encoding << "; " << input << " gave " << actual
              << " instead of " << expected << std::endl;
    return 1;
}

int test_write(int encoding, U input, std::vector<u> expected)
{
    o = encoding;
    u buf[20], *p = buf;
    w(input, p);
    std::vector<u> actual(buf,p);
    if (expected == actual) return 0;
    std::cerr << std::hex << "Encoding " << encoding << "; " << input << " gave " << actual
              << " instead of " << expected << std::endl;
    return 1;
}

int test_transcode(int ienc, std::vector<u> input, int oenc, std::vector<u> expected)
{
    b = 0;
    i = ienc; o = oenc;
    u buf[200], *p = buf, *d = input.data();
    int result = t(d, p);
    std::vector<u> actual(buf,p);
    if (result ? expected.empty() : expected == actual) return 0;
    std::cerr << std::hex << "Encoding " << ienc << " to " << oenc << "; " << input << " gave " << actual
              << " instead of " << expected << std::endl;
    return 1;
}

Banco de pruebas

static const U FAIL = ~0;
int main() {
    int e = 0;                        // error count
    // UTF-8
    e += test_read(0, { 128 }, FAIL); // unexpected continuation
    e += test_read(0, { 128, 1 }, FAIL);
    e += test_read(0, { 128, 128 }, FAIL);
    e += test_read(0, { 192, 192 }, FAIL); // start without continuation
    e += test_read(0, { 192, 0 }, FAIL);
    e += test_read(0, { 224, 0 }, FAIL);
    e += test_read(0, { 224, 192 }, FAIL);
    e += test_read(0, { 0xf4, 0x90, 128, 128 }, FAIL); // Unicode maximum+1

    e += test_read(0, { 127 }, 127);
    e += test_read(0, { 192, 129 }, 1); // We accept overlong UTF-8
    e += test_read(0, { 0xc2, 128 }, 128);
    e += test_read(0, { 224, 128, 129 }, 1);
    e += test_read(0, { 0xef, 128, 128 }, 0xF000);
    e += test_read(0, { 0xef, 191, 191 }, 0xFFFF);
    e += test_read(0, { 0xf4, 128, 128, 128 }, 0x100000);
    e += test_read(0, { 0xf4, 0x8f, 191, 191 }, 0x10FFFF); // Unicode maximum

    e += test_read(0, { 0xEF, 0xBB, 0xBF, 127 }, 127); // byte-order mark

    e += test_write(0, 0, { 0 });
    e += test_write(0, 127, { 127 });
    e += test_write(0, 128, { 0xc2, 128 });
    e += test_write(0, 255, { 0xc3, 191 });
    e += test_write(0, 0xFFFF, { 0xef, 191, 191 });
    e += test_write(0, 0x10FFFF, { 0xf4, 0x8f, 191, 191 });

    // UTF-16
    e += test_read(1, { 0, 1 }, 1);
    e += test_read(1, { 0xd8, 0, 0xdc, 1 }, 0x10001);
    e += test_read(1, { 0xdb, 0xff, 0xdf, 0xff }, 0x10ffff);

    e += test_read(1, { 0xd8, 0, 0xd8, 1 }, FAIL); // mismatched surrogate
    e += test_read(1, { 0xd8, 0, 0, 1 }, FAIL); // mismatched surrogate
    e += test_read(1, { 0xdc, 0 }, FAIL);

    e += test_write(1, 1, { 0, 1 });
    e += test_write(1, 256, { 1, 0 });
    e += test_write(1, 0xffff, { 255, 255 });
    e += test_write(1, 0x10001, { 0xd8, 0, 0xdc, 1 });
    e += test_write(1, 0x10ffff, { 0xdb, 0xff, 0xdf, 0xff });

    // UTF-16LE
    e += test_write(3, 1, { 1, 0 });
    e += test_write(3, 256, { 0, 1 });
    e += test_write(3, 0x10001, { 0, 0xd8, 1, 0xdc });
    e += test_write(3, 0x10fffe, { 0xff, 0xdb, 0xfe, 0xdf });

    // UTF-16 byte-order mark
    e += test_read(1, { 0xFE, 0xFF, 0x0, 1 }, 1); // byte-order mark
    e += test_read(1, { 0xFF, 0xFE, 1, 0x0 }, 1); // reversed byte-order mark
    // disallowed byte-order marks
    e += test_read(2, { 0xFE, 0xFF }, FAIL);
    e += test_read(3, { 0xFF, 0xFE }, FAIL);
    // reversed byte-order mark is an unassigned character - to be treated like regular character, according to question
    e += test_read(2, { 0xFF, 0xFE }, 0xfffe);
    e += test_read(3, { 0xFE, 0xFF }, 0xfffe);

    // UTF-32
    e += test_read(4, { 0, 0, 0, 1 }, 1);
    e += test_read(4, { 1, 0, 0, 0 }, FAIL);
    e += test_write(4, 1, { 0, 0, 0, 1 });
    e += test_write(4, 0x10203, { 0, 1, 2, 3 });

    // UTF-32LE
    e += test_read(6, { 0, 0, 0, 1 }, FAIL);
    e += test_read(6, { 1, 0, 0, 0 }, 1);

    // UTF-32 byte-order mark
    e += test_read(4, { 0, 0, 0xFE, 0xFF,  0, 0, 0, 1 }, 1); // byte-order mark
    e += test_read(4, { 0xFF, 0xFE, 0, 0,  1, 0, 0, 0 }, 1); // reversed byte-order mark
    // disallowed byte-order marks
    e += test_read(5, { 0, 0, 0xFE, 0xFF }, FAIL);
    e += test_read(5, { 0xFF, 0xFE, 0, 0 }, FAIL);
    e += test_read(6, { 0, 0, 0xFE, 0xFF }, FAIL);
    e += test_read(6, { 0xFF, 0xFE, 0, 0 }, FAIL);

    e += test_transcode(1, { 1, 2, 0xFE, 0xFF, 0, 0 }, // That's not a BOM; it's a zwnj when not the first char
                        1, { 1, 2, 0xFE, 0xFF, 0, 0 });
    e += test_transcode(1, { 0xFF, 0xFE, 1, 2, 0, 0 }, // reversed byte-order mark implies little-endian
                        1, { 2, 1, 0, 0 });
    e += test_transcode(4, { 0xFF, 0xFE, 0, 0, 1, 2, 0, 0, 0, 0 }, // reversed BOM means little-endian
                        4, { 0, 0, 2, 1, 0, 0, 0, 0 });
    e += test_transcode(1, { 0xdb, 0xff, 0xdf, 0xff, 0, 0 }, // U+10ffff UTF-16 to UTF-8
                        0, { 0xf4, 0x8f, 191, 191, 0 });

    return e;
}
Toby Speight
fuente
Dang .. C ++ venció a Python.
TickTock
5

Python - 1367 caracteres UTF-8

¡Bien! Esta fue una pregunta extremadamente difícil debido a la gran cantidad de trabajo que llevó comprender e implementar todas las especificaciones, pero creo que tengo una implementación correcta.

O,P,Q,R=65536,128,b'\xff\xfe\x00\x00',63
def A(x,y):assert x;return y
def B(x):
    o,c=[],0
    for b in x:
        if c:c,v=c-1,A(127<b<192,v<<6)|(b-P)
        else:
            c,v=(b>127)+(b>223)+(b>239),b
            if b>127:v=A(191<b<248,b&(R>>c))
        o+=[v][c:]
    return o[o[0]in(65279,O-2):]
def C(k):
    def o(x,s=None):
        for a,b in zip(x[k::2],x[1-k::2]):
            d=a|(b<<8)
            if s!=None:yield(A(56319<d<57344,d-56320)|(s<<10))+O;s=None
            elif 55295<d<57344:s=A(s<1024,d-55296)
            else:yield d
    return o
def D(x):n=(2,3,1)[[Q[:2],Q[1::-1],x[:2]].index(x[:2])];return C(n&1)(x[n&2:])
E=lambda a,b,c,d:lambda x:[L|(l<<8)|(m<<16) for L,l,m in zip(x[a::4],x[b::4],x[c::4])]
def F(x):n,m=((1,4),(-1,4),(-1,0))[[Q,Q[::-1],x[:4]].index(x[:4])];return E(*range(4)[::n])(x[m:])
S=lambda x,s=0,a=255:(x>>s)&a
G=lambda e:(e,)if e<P else(192|S(e,6),P|(e&R))if e<2048 else(224|S(e,12),P|S(e,6,R),P|(e&R))if e<O else(240|S(e,18),P|S(e,12,R),P|S(e,6,R),P|(e&R))
H=lambda e:(S(e,8),S(e))if e<O else(216|S(e-O,18),S(e-O,10),220+S((e-O)&1023,8),S(e-O))
I=lambda e:(S(e),S(e,8))if e<O else(S(e-O,10),216|S(e-O,18),S(e-O),220+S((e-O)&1023,8))
J=lambda e:(S(e,24),S(e,16),S(e,8),S(e))
K=lambda e:(S(e),S(e,8),S(e,16),S(e,24))
convert=lambda d,i,o:bytes(sum(map(L[o],N(list(M[i](d)))),()))if d else d
L,M=[G,H,H,I,J,J,K],[B,D,C(1),C(0),F,E(3,2,1,0),E(0,1,2,3)]
N=lambda d:[A(-1<x<1114112 and x&~2047!=55296,x)for x in d]

convertes la función que toma el objeto de datos 'bytes', la ID de entrada y la ID de salida. Parece funcionar, aunque Python parece tener un uso ligeramente roto de las listas de materiales cuando no se especifica en la codificación, por lo que usar la codificación incorporada de Python para probar los modos 1 y 4 no funcionará.

Dato curioso : el tamaño también es 555 16 o 10101010101 2 .

773 caracteres para decodificar, 452 para codificar, 59 para verificación y 83 para partes misceláneas.

Cel Skeggs
fuente
@TrangOul: en general, las ediciones triviales (como el etiquetado de idiomas) están mal vistas.
Zach Gates
44
@ZachGates alguien tiene que
gato
Esa pregunta / respuesta no muestra un consenso de la comunidad. En toda la red, las ediciones triviales como estas están mal vistas. Ni los usuarios de <1000 ni> 1000 representantes deben realizar estas ediciones a menos que mejoren claramente el contenido o el formato. Es mejor esperar para editar cosas como etiquetas de idioma, correcciones / cambios de una sola palabra, etc. @cat
Zach Gates
Creo que el tamaño ya no es 0x555 :-(. Pero podría acercarse usando la punta estándar de Python-golf de usar un espacio para las sangrías.
Toby Speight
@TobySpeight es 0x557 en este momento, tienes razón. Y en realidad, utilicé pestañas, que tenían que convertirse en espacios para publicar, pero aún cuentan como un personaje. Iré a ver si puedo afeitarme un par de personajes de otra manera cuando tenga la oportunidad.
Cel Skeggs
4

Python 3, 1138 bytes (UTF-8)

Resulta que 14 horas de viaje internacional son fantásticas. oportunidad para terminar un desafío de golf ...

La función de conversión es C(). Esta llamadas u(), v()y w()para decodificar, y U(), V()y W()para codificar, UTF-8, -16 y -32, respectivamente. Ninguno de los codificadores generará una lista de materiales, pero todos los decodificadores manejarán correctamente uno. Las condiciones de error dan como resultado una excepción (generalmente una ZeroDivisionErrorcortesía de la función "morir de repente" E()).

from struct import*
l=len
j=''.join
b=lambda c:[*bin(c)[2:]]
P,Q,i,o,z,Z='HI10><'
B=65279
O,F,H,L,X=1024,65536,55296,56320,57344
E=lambda:1/0
R=lambda y,e,c,n:unpack(([[z,Z][y[:n]==pack(Z+c,B)],e][l(e)])+c*(l(y)//n),y)
S=lambda d,e:B!=d[0]and d or e and E()or d[1:]
def u(y,d=(),p=0):
 while p<l(y):
  q=b(y[p])
  if l(q)>7:
   x=q.index(o);C=1<x<5and q[x+1:]or E();X=x+p;X>l(y)>E();p+=1
   while p<X:q=b(y[p]);C=l(q)>7and(i,o==q[:2])and(*C,*q[2:])or E();p+=1
   d=*d,int(j(C),2)
  else:d=*d,y[p];p+=1
 return S(d,0)
def T(p):
 q=b(p);C=()
 while l(q)not in(7,11,16,21):q=o,*q
 while l(q)>6:C=int(i+o+j(q[-6:]),2),*C;q=q[:-6]
 return bytes(p<128and[p]or[int(i*(7-l(q))+o+j(q),2),*C])
U=lambda c:b''.join(map(T,c))
def v(y,e=''):
 c=R(y,e,P,2);d=[];n=0
 while n<l(c)-1:h,a=c[n:n+2];D=[(h,a),(F+(h-H)*O+a-L,)][H<=h<L<=a<X];M=3-l(D);n+=M;d+=D[:M]
 if n<l(c):d=*d,c[n]
 return S(d,e)
V=lambda c,e=z:W(sum(map(lambda p:([H+(p-F)//O,L+(p-F)%O],[p])[p<F],c),[]),e,P)
w=lambda y,e='':S(R(y,e,Q,4),e)
W=lambda c,e=z,C=Q:pack(e+C*l(c),*c)
K=(u,U),(v,V),(v,V,z),(v,V,Z),(w,W),(w,W,z),(w,W,Z)
def C(y,f,t):f,_,*a=K[f];_,t,*b=K[t];return t(f(y,*a),*b)
Tim Pederick
fuente