Mengapa kode ini, ditulis mundur, mencetak "Hello World!"

261

Berikut ini beberapa kode yang saya temukan di Internet:

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

Kode ini dicetak Hello World!di layar; Anda dapat melihatnya berjalan di sini . Saya bisa melihat public static void maintulisan dengan jelas , tetapi terbalik. Bagaimana cara kerja kode ini? Bagaimana ini bisa dikompilasi?

Sunting: Saya mencoba kode ini di IntellIJ, dan berfungsi dengan baik. Namun, untuk beberapa alasan itu tidak berfungsi di notepad ++, bersama dengan cmd. Saya masih belum menemukan solusi untuk itu, jadi jika ada yang melakukannya, komentar di bawah.

Labu Imajiner
sumber
38
Yang ini lucu ... Ada hubungannya dengan dukungan RTL?
Eugene Sh.
12
Ada karakter Unicode # 8237; tepat setelah Mdan juga setelah []a: fileformat.info/info/unicode/char/202d/index.htm Ini disebut LEFT-TO-RIGHT OVERRIDE
Riiverside
45
xkcd wajib: xkcd.com/1137
Pac0
4
Anda dapat dengan mudah melihat apa yang sedang terjadi di sini hanya dengan membuat pilihan dalam cuplikan kode menggunakan mouse Anda.
Andreas Rejbrand
14
niam diov citats cilbupKedengarannya seperti pepatah Latin ..
Mick Mnemonic

Jawaban:

250

Ada karakter yang tidak terlihat di sini yang mengubah cara kode ditampilkan. Di Intellij, ini dapat ditemukan dengan menyalin-menempelkan kode ke string kosong ( ""), yang menggantikannya dengan Unicode lolos, menghapus efeknya dan mengungkapkan urutan yang dilihat kompilator.

Ini adalah output dari copy-paste itu:

"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','!'});}}   "

Karakter kode sumber disimpan dalam urutan ini, dan kompilator memperlakukannya sebagai urutan ini, tetapi mereka ditampilkan secara berbeda.

Perhatikan \u202Ekarakter, yang merupakan override kanan ke kiri, memulai blok di mana semua karakter dipaksa untuk ditampilkan kanan-ke-kiri, dan \u202D, yang merupakan override kiri-ke-kanan, memulai blok bersarang di mana semua karakter dipaksa ke kiri-ke-kanan, mengesampingkan penimpaan pertama.

Ergo, ketika menampilkan kode asli, class Mditampilkan secara normal, tetapi \u202Emembalikkan urutan tampilan segala sesuatu dari sana ke \u202D, yang membalikkan semuanya lagi. (Secara formal, segala sesuatu dari \u202Dke terminator garis akan terbalik dua kali, sekali karena \u202Ddan sekali dengan sisa teks terbalik karena \u202E, itulah sebabnya teks ini muncul di tengah-tengah baris, bukan di akhir.) Arah baris berikutnya ditangani secara independen dari yang pertama karena terminator garis, sehingga {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}ditampilkan secara normal.

Untuk algoritme dua arah Unicode penuh (sangat rumit, lusinan halaman), lihat Unicode Standard Annex # 9 .

Davis Broda
sumber
Anda tidak menjelaskan apa yang dikompilasi oleh kompiler (tidak seperti tampilan rutin) dengan karakter Unicode itu sendiri. Saya mungkin mengabaikannya secara langsung (atau memperlakukannya sebagai ruang kosong), atau mungkin menafsirkannya sebagai kontribusi nyata terhadap kode sumber. Saya tidak tahu aturan Java di sini, tetapi fakta bahwa mereka ditempatkan di akhir pengidentifikasi yang tidak digunakan menunjukkan kepada saya bahwa itu mungkin yang terakhir, dan karakter Unicode sebenarnya bagian dari nama-nama pengidentifikasi.
Marc van Leeuwen
Apakah ini akan bekerja dengan cara yang sama dalam c #, karena minat?
IanF1
14
@ IanF1 Ini akan berfungsi dalam bahasa apa pun di mana kompiler / penerjemah menghitung karakter RTL dan LTR sebagai spasi putih. Tetapi jangan pernah melakukan ini dalam kode produksi jika Anda sama sekali menghargai kewarasan orang berikutnya untuk menyentuh kode Anda, yang bisa jadi Anda.
wizzwizz4
2
Atau, dengan kata lain: "Selalu kode seolah-olah orang yang akhirnya mempertahankan kode Anda adalah seorang psikopat kejam yang tahu di mana Anda tinggal." , @ IanF1. Atau mungkin: "Selalu kode seolah-olah orang yang akhirnya mempertahankan kode Anda akan memberi nama-dan-memalukan Anda sebagai penulis asli di Stack Overflow."
Cody Gray
43

Itu terlihat berbeda karena Algoritma Bidirectional Unicode . Ada dua karakter RLO dan LRO yang tidak terlihat yang digunakan Unicode Bidirectional Algorithm untuk mengubah tampilan visual dari karakter-karakter yang bersarang di antara dua metacharacters ini.

Hasilnya secara visual mereka terlihat dalam urutan terbalik, tetapi karakter sebenarnya dalam memori tidak terbalik. Anda dapat menganalisis hasilnya di sini . Kompiler Java akan mengabaikan RLO dan LRO, dan memperlakukannya sebagai spasi putih yang karenanya kode dikompilasi.

Catatan 1: Algoritma ini digunakan oleh editor teks dan browser untuk secara visual menampilkan karakter baik karakter LTR (Bahasa Inggris) dan karakter RTL (mis. Bahasa Arab, Bahasa Ibrani) bersamaan pada saat yang bersamaan - karenanya "bi" -directional. Anda dapat membaca lebih lanjut tentang Algoritma Bidirectional di situs web Unicode .
Catatan 2: Perilaku yang tepat dari LRO dan RLO didefinisikan dalam Bagian 2.2 dari Algoritma.

James Lawson
sumber
Apa tujuan dari kemampuan seperti itu?
Eugene Sh.
6
Karakter-karakter ini kadang-kadang diperlukan untuk membuat bahasa Arab dan Ibrani secara visual dengan benar. Bahasa-bahasa ini dibaca dan ditulis dari kanan ke kiri (RTL), karakter pertama yang dibaca / ditulis muncul di sebelah kanan . Anda dapat membaca lebih lanjut di sini .
James Lawson
Karakter Arab dan Ibrani pada dasarnya adalah RTL, meskipun - mereka akan muncul RTL bahkan tanpa override eksplisit, dan mereka bahkan akan secara otomatis membalik urutan karakter lain di dekatnya, saya pikir sebagian besar tanda baca - sehingga pengesampingan eksplisit jarang diperlukan.
user2357112 mendukung Monica
Halaman ini di sini menjelaskan kapan penggantian yang diperlukan. @ user2357112 benar, mereka jarang dibutuhkan. Memang ketika Anda memiliki tanda baca, kutipan, dan angka - karakter khusus ini dianggap "netral". Untuk komputer yang tidak dapat membaca kata-kata dan memahami konteksnya, tidak jelas apakah memperlakukannya sebagai LTR atau RTL, tetapi algoritma bidi harus memilih beberapa pemesanan. Kadang-kadang "salah" dan Anda perlu menggunakan karakter override ini untuk "memperbaikinya".
James Lawson
3
Juga, U + 202E dan U + 202D tidak dianggap sebagai spasi putih. Java hanya menganggap ruang ASCII, tab horizontal, umpan formulir, dan CR / LF / CRLF sebagai spasi putih . Mereka sebenarnya secara leksikal merupakan bagian dari pengidentifikasi M\u202Edan a\u202D, tetapi pengidentifikasi tersebut tampaknya diperlakukan setara dengan Mdan a. (JLS tidak melakukan pekerjaan dengan baik untuk menjelaskan hal ini.)
user2357112 mendukung Monica
28

Karakter U+202Emencerminkan kode dari kanan ke kiri, itu sangat pintar. Tersembunyi mulai di M,

"class M\u202E{..."

Bagaimana saya menemukan keajaiban di balik ini?

Ya, pada awalnya ketika saya melihat pertanyaan saya tegar, "ini semacam lelucon, kehilangan waktu orang lain", tetapi kemudian, saya membuka IDE saya ("IntelliJ"), membuat kelas, dan melewati kode ... dan itu dikompilasi !!! Jadi, saya melihat lebih baik dan melihat bahwa "kekosongan publik statis" mundur, jadi saya pergi ke sana dengan kursor, dan menghapus beberapa karakter ... Dan apa yang terjadi? Karakternya mulai terhapus ke belakang , jadi, saya pikir mmm .... jarang ... Saya harus menjalankannya ... Jadi saya melanjutkan untuk menjalankan program, tetapi pertama-tama saya harus menyimpannya ... dan saat itulah saya menemukannya! . Saya tidak dapat menyimpan file karena IDE saya mengatakan bahwa ada pengkodean yang berbeda untuk beberapa karakter, dan tunjukkan di mana itu, Jadi saya memulai penelitian di Google untuk karakter khusus yang dapat melakukan pekerjaan, dan hanya itu :)

Sedikit tentang

Algoritma Bidirectional Unicode, dan yang U+202Eterlibat, menjelaskan secara singkat :

Standar Unicode mengatur urutan representasi memori yang dikenal sebagai urutan logis. Ketika teks disajikan dalam garis horizontal, sebagian besar skrip menampilkan karakter dari kiri ke kanan. Namun, ada beberapa skrip (seperti Arab atau Ibrani) di mana urutan alami teks horizontal yang ditampilkan adalah dari kanan ke kiri. Jika semua teks memiliki arah horizontal yang seragam, maka urutan teks tampilan tidak ambigu.

Namun, karena skrip kanan-ke-kiri ini menggunakan angka yang ditulis dari kiri ke kanan, teksnya sebenarnya dua arah: campuran teks kanan-ke-kiri dan kiri-ke-kanan. Selain digit, kata-kata yang disematkan dari bahasa Inggris dan skrip lain juga ditulis dari kiri ke kanan, juga menghasilkan teks dua arah. Tanpa spesifikasi yang jelas, ambiguitas dapat muncul dalam menentukan urutan karakter yang ditampilkan ketika arah horizontal teks tidak seragam.

Lampiran ini menjelaskan algoritma yang digunakan untuk menentukan directionality untuk teks Unicode dua arah. Algoritme memperluas model implisit yang saat ini digunakan oleh sejumlah implementasi yang ada dan menambahkan karakter pemformatan eksplisit untuk keadaan khusus. Dalam kebanyakan kasus, tidak perlu menyertakan informasi tambahan dengan teks untuk mendapatkan pemesanan tampilan yang benar.

Namun, dalam kasus teks dua arah, ada keadaan di mana pemesanan dua arah secara implisit tidak cukup untuk menghasilkan teks yang dapat dipahami. Untuk menangani kasus-kasus ini, sekumpulan karakter pemformatan terarah minimal didefinisikan untuk mengontrol urutan karakter ketika dirender. Ini memungkinkan kontrol yang tepat dari pemesanan tampilan untuk pertukaran yang dapat dibaca dan memastikan bahwa teks biasa yang digunakan untuk item sederhana seperti nama file atau label selalu dapat dipesan dengan benar untuk tampilan.

Mengapa membuat beberapa algoritma seperti ini ?

algoritma bidi dapat membuat urutan karakter Arab atau Ibrani satu demi satu dari kanan ke kiri.

Damián Rafael Lattenero
sumber
4

Bab 3 dari spesifikasi bahasa memberikan penjelasan dengan menjelaskan secara terperinci bagaimana terjemahan leksikal dilakukan untuk program Java. Yang paling penting untuk pertanyaan:

Program ditulis dalam Unicode (§3.1) , tetapi terjemahan leksikal disediakan (§3.2) sehingga Unicode lolos (§3.3) dapat digunakan untuk memasukkan karakter Unicode apa saja menggunakan hanya karakter ASCII.

Jadi suatu program ditulis dalam karakter Unicode, dan penulis dapat melarikan diri mereka menggunakan \uxxxxdalam kasus pengkodean file tidak mendukung karakter Unicode, dalam hal ini diterjemahkan ke karakter yang sesuai. Salah satu karakter Unicode yang ada dalam kasus ini adalah \u202E. Ini tidak ditampilkan secara visual dalam cuplikan, tetapi jika Anda mencoba mengalihkan penyandian browser, karakter yang tersembunyi mungkin muncul.

Oleh karena itu, terjemahan leksikal menghasilkan deklarasi kelas:

class M\u202E{

yang berarti bahwa pengidentifikasi kelas adalah M\u202E. The spesifikasi menganggap ini sebagai Pengidentifikasi valid:

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

"Java letter-or-digit" adalah karakter yang Character.isJavaIdentifierPart(int)mengembalikan metode tersebut .

M Anouti
sumber
Maaf tapi ini terbalik (pun intended). Tidak ada jalan keluar dalam kode sumber; Anda menjelaskan bagaimana itu bisa ditulis. Dan, itu mengkompilasi ke kelas bernama "M" (hanya satu karakter).
Tom Blodget
@ TomBlodget Memang tetapi intinya (yang sebenarnya saya soroti dalam kutipan spesifikasi) adalah bahwa kompiler juga dapat memproses karakter Unicode mentah. Itu benar-benar penjelasan keseluruhan. Terjemahan melarikan diri hanyalah info tambahan dan tidak terkait langsung dengan kasus ini. Adapun kelas yang dikompilasi, saya pikir itu karena karakter switch RTL entah bagaimana dibuang oleh kompiler. Saya akan mencoba untuk melihat apakah ini diharapkan, tetapi saya pikir terjadi setelah fase terjemahan leksikal.
M Anouti