java unicode right-to-left

java - ¿Por qué este código, escrito al revés, imprime "Hello World!"



unicode right-to-left (4)

Aquí hay un código que encontré en Internet:

class M‮{public static void main(String[]a‭){System.out.print(new char[] {''H'',''e'',''l'',''l'',''o'','' '',''W'',''o'',''r'',''l'',''d'',''!''});}}

Este código imprime Hello World! en la pantalla; Puedes verlo correr here . Puedo ver claramente public static void main escrito public static void main , pero está al revés. ¿Cómo funciona este código? ¿Cómo se compila esto?

Editar: probé este código en IntellIJ, y funciona bien. Sin embargo, por alguna razón no funciona en notepad ++, junto con cmd. Todavía no he encontrado una solución para eso, así que si alguien lo hace, comente a continuación.


Aquí hay caracteres invisibles que alteran la forma en que se muestra el código. En Intellij, estos se pueden encontrar copiando y pegando el código en una cadena vacía ( "" ), que los reemplaza con escapes Unicode, eliminando sus efectos y revelando el orden que ve el compilador.

Aquí está la salida de ese copiar y pegar:

"class M/u202E{public static void main(String[]a/u202D){System.out.print(new char[]/n"+ "{''H'',''e'',''l'',''l'',''o'','' '',''W'',''o'',''r'',''l'',''d'',''!''});}} "

Los caracteres del código fuente se almacenan en este orden, y el compilador los trata como si estuvieran en este orden, pero se muestran de manera diferente.

Tenga en cuenta que el carácter /u202E , que es una anulación de derecha a izquierda, comienza un bloque donde todos los caracteres se ven obligados a mostrarse de derecha a izquierda, y el /u202D , que es una anulación de izquierda a derecha, inicia un bloque anidado donde todos los caracteres se fuerzan en orden de izquierda a derecha, anulando la primera anulación.

Ergo, cuando muestra el código original, la class M se muestra normalmente, pero el /u202E invierte el orden de visualización de todo desde allí hasta el /u202D , que invierte todo nuevamente. (Formalmente, todo, desde /u202D hasta el terminador de línea, se invierte dos veces, una vez debido a /u202D y otra vez con el resto del texto invertido debido a /u202E , por lo que este texto aparece en el medio de la línea en lugar del final.) La direccionalidad de la línea siguiente se maneja independientemente de la primera debido al terminador de línea, por lo que {''H'',''e'',''l'',''l'',''o'','' '',''W'',''o'',''r'',''l'',''d'',''!''});}} se muestra normalmente.

Para el algoritmo bidireccional Unicode completo (extremadamente complejo, decenas de páginas de largo), consulte el Anexo 9 estándar de Unicode .


El personaje U+202E refleja el código de derecha a izquierda, aunque es muy inteligente. Está oculto a partir de la M,

"class M/u202E{..."

¿Cómo encontré la magia detrás de esto?

Bueno, al principio cuando vi la pregunta, dije: "es una especie de broma, perder el tiempo de alguien más", pero luego abrí mi IDE ("IntelliJ"), creé una clase y pasé el código ... y se compiló !!! Entonces, miré mejor y vi que el "vacío estático público" estaba al revés, así que fui allí con el cursor y borré algunos caracteres ... ¿Y qué sucede? Los caracteres comenzaron a borrarse hacia atrás , así que pensé mmm ... raro ... tengo que ejecutarlo ... Así que procedo a ejecutar el programa, pero primero necesitaba guardarlo ... y fue entonces cuando ¡Lo encontré! . No pude guardar el archivo porque mi IDE dijo que había una codificación diferente para algunos caracteres, y me indicó dónde estaba , así que empiezo una investigación en Google de caracteres especiales que podrían hacer el trabajo, y eso es todo :)

Un poco sobre

el algoritmo bidireccional Unicode, y U+202E involucrados, explican brevemente:

El estándar Unicode prescribe un orden de representación de memoria conocido como orden lógico. Cuando el texto se presenta en líneas horizontales, la mayoría de los scripts muestran caracteres de izquierda a derecha. Sin embargo, hay varios guiones (como el árabe o el hebreo) donde el orden natural del texto horizontal en la pantalla es de derecha a izquierda. Si todo el texto tiene una dirección horizontal uniforme, entonces el orden del texto de la pantalla no es ambiguo.

Sin embargo, debido a que estos scripts de derecha a izquierda usan dígitos que se escriben de izquierda a derecha, el texto es en realidad bidireccional: una mezcla de texto de derecha a izquierda y de izquierda a derecha. Además de los dígitos, las palabras incrustadas del inglés y otros scripts también se escriben de izquierda a derecha, produciendo también texto bidireccional. Sin una especificación clara, pueden surgir ambigüedades para determinar el orden de los caracteres mostrados cuando la dirección horizontal del texto no es uniforme.

Este anexo describe el algoritmo utilizado para determinar la direccionalidad del texto bidireccional Unicode. El algoritmo extiende el modelo implícito actualmente empleado por varias implementaciones existentes y agrega caracteres de formato explícito para circunstancias especiales. En la mayoría de los casos, no es necesario incluir información adicional con el texto para obtener un orden de visualización correcto.

Sin embargo, en el caso del texto bidireccional, hay circunstancias en las que un orden bidireccional implícito no es suficiente para producir un texto comprensible. Para tratar estos casos, se define un conjunto mínimo de caracteres de formato direccional para controlar el orden de los caracteres cuando se representan. Esto permite un control exacto del orden de visualización para el intercambio legible y garantiza que el texto sin formato utilizado para elementos simples como nombres de archivo o etiquetas siempre se pueda ordenar correctamente para su visualización.

¿Por qué crear un algoritmo como website ?

El algoritmo bidi puede representar una secuencia de caracteres árabes o hebreos uno tras otro de derecha a izquierda.

PD: Sé que no es la mejor respuesta, pero fue divertido resolver el problema primero: P


Se ve diferente debido al Algoritmo bidireccional Unicode . Hay dos caracteres invisibles de RLO y LRO que el Algoritmo bidireccional Unicode usa para cambiar la apariencia visual de los caracteres anidados entre estos dos metacaracteres.

El resultado es que visualmente se ven en orden inverso, pero los caracteres reales en la memoria no se invierten. Puedes analizar los resultados here . El compilador de Java ignorará RLO y LRO, y los tratará como espacios en blanco, razón por la cual el código se compila.

Nota 1: los editores de texto y los navegadores utilizan este algoritmo para mostrar visualmente los caracteres, tanto los caracteres LTR (inglés) como los caracteres RTL (p. Ej., Árabe, hebreo) al mismo tiempo, por lo tanto, "bi" -direccional. Puede leer más sobre el algoritmo bidireccional en el website de Unicode.
Nota 2: El comportamiento exacto de LRO y RLO se define en la Sección 2.2 del Algoritmo.


El Capítulo 3 de la especificación del lenguaje proporciona una explicación al describir en detalle cómo se realiza la traducción léxica para un programa Java. Lo más importante para la pregunta:

Los programas están escritos en Unicode (§3.1) , pero se proporcionan traducciones léxicas (§3.2) para que los escapes de Unicode (§3.3) se puedan usar para incluir cualquier carácter Unicode utilizando solo caracteres ASCII.

Entonces, un programa está escrito en caracteres Unicode, y el autor puede escapar de ellos usando /uxxxx en caso de que la codificación del archivo no sea compatible con el carácter Unicode, en cuyo caso se traduce al carácter apropiado. Uno de los caracteres Unicode presentes en este caso es /u202E . No se muestra visualmente en el fragmento, pero si intenta cambiar la codificación del navegador, pueden aparecer los caracteres ocultos.

Por lo tanto, la traducción léxica da como resultado la declaración de clase:

class M/u202E{

lo que significa que el identificador de clase es M/u202E . La specification considera esto como un identificador válido:

Identifier: IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral IdentifierChars: JavaLetter {JavaLetterOrDigit}

Una "letra o dígito Java" es un carácter para el que el método Character.isJavaIdentifierPart(int) devuelve verdadero.