Befunge compiler dengan Python

Dalam persiapan untuk kursus "Fundamentals of Compiler" untuk siswa kelas 4, saya mempelajari berbagai bahasa pemrograman esoterik. Inilah artikel bagus tentang topik ini . Bahasa Befunge (Chris Press, 1993) menurut saya paling menarik dalam artikel ini, saya terutama mencatat tiga fitur-fiturnya:

  1. Bidang program adalah torus dua dimensi, mis. secara fisik, ini adalah matriks persegi panjang dari perintah simbol yang ditutup di sepanjang batas atas (bawah) dan di sepanjang kolom kiri (kanan). Penunjuk perintah bergerak di sekitar bidang (setiap perintah adalah karakter tertentu dengan koordinat x, y), menjalankan perintah, dan melanjutkan. Pergerakan dapat di semua 4 arah (secara default, langsung dari titik 0,0), dan ketika Anda melampaui "bidang" pointer muncul di sisi yang berlawanan.
  2. Ada dua perintah (p, g) dalam bahasa yang mengubah bidang itu sendiri, yaitu program ini "menulis ulang sendiri" selama eksekusi. Kode program di awal mungkin tidak sama dengan kode di akhir. Program "123pgpg ## @" dimulai, dan program "ABC @ 1 @ 2 @ 3.14" (bukan contoh yang benar) selesai bekerja.
  3. Chris Pressy mencatat bahwa dia ingin membuat bahasa yang serumit mungkin untuk dikompilasi. Secara de facto, memang benar, membuat kompiler yang membuat file exe sangat sulit untuk program ini, saya menemukan informasi bahwa seseorang dapat melakukannya di C ... Yang terbaik adalah membuat penerjemah dari bahasa ke kode Python, yang masih saya sebut kompiler untuk kesederhanaan.


Bidang program 1993 terdiri dari 25 baris masing-masing 80 karakter. Ada 36 tim dalam bahasa tersebut, yang masing-masing merupakan karakter tabel ASCII. Untuk informasi lebih lanjut, lihat Wikipedia , saya akan memberikan deskripsi singkat dari sana:

Pindahkan perintah (9):

> Pindah ke kanan
<Pindah ke kiri
^ Pindahkan ke atas
v Pindahkan ke bawah
_ Pindah ke kanan jika bagian atas tumpukan adalah 0, jika tidak kiri.
| Pindah ke bawah jika di atas tumpukan 0, jika tidak naik.
? Bergerak ke arah acak
# Lewati sel berikutnya ("loncatan")
@ Akhir program

Perintah manipulasi tumpukan (3)

:: Tempatkan salinan simpul pada tumpukan
\ Swap vertex dan vertex
$ Delete vertex

Perintah modifikasi kode program (2):
p "PUT": koordinat sel dan kode ASCII dari karakter yang ditempatkan pada koordinat
ini diekstraksi dari stack g "GET": koordinat sel diekstraksi dari stack; Kode ASCII simbol pada koordinat ini didorong ke stack.Komando

konstan (2):

0-9 Tempatkan nomor pada
awal / akhir stack mode simbol, di mana kode ASCII dari semua karakter program saat ini

didorong ke stack.Operasi aritmatika (5):

+ Penambahan titik dan titik
- Pengurangan titik dan titik
* Perkalian titik dan titik
/ Integer divisi
% Sisa pembagian

Perintah untuk stack dan operasi logis (2)

:! Negasi: nol pada titik diganti dengan 1, nilai bukan nol diganti dengan 0
`Perbandingan" lebih besar dari ": jika titik lebih besar dari titik, 1 ditempatkan di tumpukan, jika tidak 0

perintah I / O (4):

& Minta pengguna untuk nomor dan letakkan pada stack
~ Mintalah karakter pada pengguna dan masukkan kode ASCII pada stack
. Cetak bagian atas tumpukan sebagai bilangan bulat
, Cetak karakter yang sesuai dengan kode ASCII di bagian atas tumpukan

Saya memutuskan untuk menulis kompiler Befunge (juru bahasa) dengan Python sesuai dengan aturan tahun 1993 dengan beberapa batasan: 1) bidang bukan 25x80 karakter, tetapi minimum lebar dan tinggi blok teks, 2) bidang tidak dilingkarkan ke torus, yaitu. melampaui batas dengan melompat ke sisi yang berlawanan tidak diproses. Ini bukan kemalasan (meskipun, siapa aku bercanda?), Untuk contoh kecil semuanya bekerja dengan baik, dan untuk menyelesaikan lapangan ke torus nyata cukup sederhana, akan ada keinginan.

Kode keluar di beberapa tempat tanpa perlu "di dahi", tapi ini karena fakta bahwa itu untuk siswa dan tugasnya adalah sejelas mungkin, dan tidak disingkat menjadi beberapa baris dalam bahasa Cina.

Bagian 1


Kode disediakan dari awal hingga akhir dengan satu pengecualian (yang akan ditentukan), dapat disalin ke file dan dijalankan. Teks lengkap tersedia di tautan rentry.co/ivansedov-befunge , terlalu dini bagi saya untuk memberikan yang terbaik di GitHub. Ngomong-ngomong, ada sekitar 20 implementasi bahasa Befunge di sana, tetapi kodenya baik dalam bahasa C (bukan bahasa saya) atau Python, tetapi begitu rumit sehingga saya tidak berani menyelam. Namun, di sana Anda dapat mengikuti program sampel untuk pengujian, misalnya, di sini github.com/causal-agent/befungee

from sys import *
import time
import random

Impor perpustakaan:

  1. Pustaka sys diperlukan untuk mendapatkan nama file program. Kompiler saya disebut bbb.py, contoh uji dalam direktori yang sama 1.bf, Python versi 3.7 itu sendiri, dan panggilan program di konsol tampak seperti ini: python3 bbb.py 1.bf
  2. time , , 0,5-1,0 .
  3. random «?» ( ), . -, Befunge - , « » (1 , 2 , 3 , 4 ). « » .

class Pointer:
    def __init__(self, x=0, y=0, vector=2, value=None):
        self.x = x
        self.y = y
        self.vector = vector
        self.value = value
        self.stack = []
        self.stack_sf = 0

    def __str__(self):
        return 'Point ({},{}) vektor:{} value:{} stack_sf:{} stack:{}'.format(self.x, self.y, self.vector, self.value, self.stack_sf, self.stack)

    def step(self):
        if self.vector == 1:
            self.x -= 1
        elif self.vector == 2:
            self.y += 1
        elif self.vector == 3:
            self.x += 1
        elif self.vector == 4:
            self.y -= 1

    def action(self):
	#   ,   

Kelas utama yang digunakan dalam program: Pointer (pointer), memiliki 6 properti dan 2 metode. Properti: 1) x koordinat (awalnya = 0), 2) koordinat y (awalnya = 0), 3) vektor (arah pergerakan, awalnya = 2 ke kanan), 4) nilai (nilai bidang, yang terletak di koordinat x, y, awalnya = Tidak Ada), ini, pada kenyataannya, perintah yang akan dieksekusi, 5) stack (program stack, awalnya = []) dan 6) stack_st (flag untuk memasukkan baris, awalnya = 0).

Kelas memiliki dua metode: 1) langkah () langkah penunjuk, tanpa pemeriksaan dan tes, mengubah koordinat x, y tergantung pada arah dalam vektor dan 2) tindakan () adalah jantung dari kompiler, menjalankan perintah program saat ini. Saya akan memberikan kode action () di bagian kedua, sehingga tidak mengganggu logika.

# ===============================
# =                      =
# ===============================

def open_file(name):
    data = open(name, 'r').read()
    return data

def field_print():
    for row in A:
        for elem in row:
            print(elem, end='')
        print()  

Dua fungsi bantu: 1) open_file (nama) membuka file dengan nama yang dikirimkan, membacanya dan mengembalikan konten, dan 2) field_print () mencetak konten array A, di mana karakter program berada. Membuat array A ditunjukkan di bawah ini.

# ===========================================
# =                       =
# ===========================================

numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
operators = ['+', '-', '*', '/', '%']
point = Pointer()                           #  
text = open_file(argv[1]).split("\n")       #  
n = len(text)                               # n =  
m = 0                                       # m =  

#    (   m)
for line in text:
    if len(line) > m:
        m = len(line)

#   ( n  m )
A = [' '] * n
for i in range(n):
    A[i] = [' '] * m

#    
for i in range(len(text)):
    for j in range(len(text[i])):
        A[i][j] = text[i][j]

Pengaturan program dasar. Dalam daftar angka kita meletakkan semua angka dari 0 hingga 9, yang dapat ditulis dalam program (10 tidak lagi cocok, dua karakter). Dalam daftar operator kami menempatkan operator aritmatika dasar. Buat titik = Objek penunjuk (pengaturan default), yang akan kami gunakan sepanjang waktu ...

Dalam teks variabel kita menempatkan teks baca dari program yang dapat dieksekusi, dipecah oleh simbol "baris baru". Akibatnya, teks terdiri dari beberapa baris teks dengan panjang yang berbeda, yang harus ditempatkan dalam array ruang persegi panjang di tempatnya. Mudah untuk menemukan jumlah baris n = len (teks), tetapi jumlah kolom harus dihitung berdasarkan panjang maksimum garis yang termasuk dalam teks. Saya tidak menemukan cara lain untuk melakukan ini dahi: pergi melalui semua baris dan menemukan satu dengan panjang maksimum. Memiliki di tangan n dan m (jumlah baris dan jumlah kolom dari bidang masa depan), Anda dapat membuat array dua dimensi, mengisinya dengan spasi, dan kemudian pergi teks untuk menempatkan karakter di tempat mereka.

Hasilnya adalah persegi panjang karakter antara spasi dan semua ini adalah matriks dua dimensi n oleh m. Setelah pengaturan ini, Anda dapat memanggil fungsi field_print () dan memastikan bahwa semuanya terlihat indah, tidak ada yang melayang dan tidak melanggar situasi.

# ==========================================
# =                       =
# ==========================================

# field_print()

while point.value != '@':
    try:
        point.value = A[point.x][point.y]       # 1)    point
        point.action()                          # 2)  
        # print(point)                            # 3) :  
        # time.sleep(0.5)                         # 4) :  0.5 c
    except IndexError:                          #     =  
        print('     ')
        break

# field_print()
print()

Semuanya berakhir dengan program utama (siklus), sebelum dan sesudahnya Anda dapat menampilkan bidang (kadang-kadang berguna). Siklus berputar hingga penunjuk menunjuk ke simbol “@” (ampersand, dog, akhir program). Di dalam siklus, 4 tindakan dilakukan setiap kali:

  1. Di properti point.value, karakter yang terletak di array A pada titik koordinat.x, point.y dibaca
  2. Metode point.action () dipanggil, di mana perintah saat ini (baru saja membaca) dieksekusi
  3. Pointer ditampilkan di layar (semua properti)
  4. Penundaan dibuat sebelum iterasi berikutnya (0,1 detik - 0,5 detik)

Item 3 dan 4 sepenuhnya opsional (mereka bahkan berkomentar), tetapi saya sarankan menggunakannya untuk pengujian. Semua tindakan di dalam loop terjadi dengan menangkap kesalahan IndexError (kesalahan melebihi batas indeks), ini memungkinkan Anda untuk mencegat dua kesalahan kompiler utama:

  1. Kami beralih ke tumpukan, dan tidak ada nilai
  2. Kami tidak sengaja melampaui program (array) lebar atau tinggi

Cetak kosong terakhir () diperlukan agar setelah output ditampilkan, konsol bekerja dari baris baru.

Bagian 2


Sekarang saatnya untuk kode yang paling penting - isi metode point.action () dari kelas Pointer. Segala sesuatu di bawah ini harus dimasukkan di tempat ditulisnya:

    def action(self):
	#   ,   

Perhatikan indentasi:

        if self.value == '"' and self.stack_sf == 0:                # ,  "
            self.stack_sf = 1
        elif self.value == '"' and self.stack_sf == 1:
            self.stack_sf = 0
        elif self.stack_sf == 1:                                    # "Hello"   
            self.stack.append(ord(self.value))
        elif self.value in numbers and self.stack_sf == 0:
            # 123   
            self.stack.append(int(self.value))

Sebenarnya, kode di dalam action () banyak kondisi, yang masing-masing dieksekusi ketika perintah ini berada di bawah pointer. Semuanya dimulai dengan kondisi "jika perintah saat ini = tanda kutip dan bendera awal baris stack_sf = 0", dalam hal ini bendera dinaikkan ke 1. Kami memasuki baris.

(Kalau tidak) jika perintah saat ini = tanda kutip dan bendera dinaikkan ke 1, maka ini berarti bahwa tanda kutip ditemukan untuk kedua kalinya dan Anda harus berhenti memasukkan string (bendera stack_sf diturunkan ke 0). Kami keluar jalur.

(Jika tidak) jika dua kondisi pertama tidak berfungsi dan flag stack_sf = 1, maka kita “terletak di dalam garis” dan kita perlu menambahkan kode simbol saat ini ke stack. Bukan karakter itu sendiri, tetapi kode ASCII-nya.

(Kalau tidak) jika karakter saat ini adalah di antara elemen-elemen angka dan bendera adalah stack_sf = 0, maka ini adalah, pertama, digit dan, kedua, kita tidak berada di dalam garis, kita perlu menambahkan karakter saat ini = digit ke stack. Tambahkan bukan kode karakter, tetapi karakter itu sendiri. Masih ingat bahwa ada angka 1, dan dia memiliki kode = 49. Jadi, jika kita berada di dalam baris, maka kita perlu menambahkan 49 ke stack, dan jika itu hanya dalam program, maka perintah 1 harus ditambahkan ke stack 1.

Selanjutnya semua persyaratan akan menjadi elif (jika tidak, jika ...), jadi saya hanya akan menulisnya "jika". Selain itu, semua ketentuannya berlipat ganda, Anda perlu memeriksa karakter saat ini untuk kesetaraan terhadap perintah dan fakta bahwa kita tidak berada di dalam string (di dalam string, semua karakter diproses secara berbeda). Anda bisa menulis semua ini dengan cara yang lebih optimal, tetapi solusi ini memungkinkan Anda untuk memusatkan perhatian pada dahi ini.

        elif self.value in operators and self.stack_sf == 0:
            b = self.stack.pop()
            a = self.stack.pop()
            if self.value == '+':
                res = a + b                                         # a+b  
            elif self.value == '-':
                res = a - b                                         # a-b  
            elif self.value == '*':
                res = a * b                                         # a*b  
            elif self.value == '/':
                if b == 0:
                    res = 0
                else:
                    res = a // b                                    # a//b  
            elif self.value == '%':
                res = a % b                                         # a%b  
            self.stack.append(res)

Jika karakter saat ini ada di antara operator (dan stack_sf = 0), maka itu berarti kita masuk ke operasi aritmatika. Semuanya persis sama: 1) angka b dihapus (dengan penghapusan), 2) angka a dihapus (dengan penghapusan), 3) res = nilai operasi antara a dan b, 4) res didorong ke tumpukan. Ketika membaginya dengan 0, jawabannya adalah 0, meskipun penulis bahasa menyediakan pilihan 0 atau 1.

        elif self.value == '!' and self.stack_sf == 0:
            a = self.stack.pop()
            if a == 0:
                a = 1
            else:
                a = 0
            # 0->1, 1->0
            self.stack.append(a)

        elif self.value == '`' and self.stack_sf == 0:
            a = self.stack.pop()        # 
            b = self.stack.pop()        # 
            if b > a:
                res = 1
            else:
                res = 0
            # b>a -> 1|0
            self.stack.append(res)

Jika simbol saat ini adalah "!", Maka Anda perlu mengganti kepala (simpul) dari tumpukan: itu adalah 0 - itu akan menjadi 1, ada sesuatu yang berbeda dari 0 - itu akan menjadi 1. Jika simbol saat ini adalah "» "(apostrof), maka Anda perlu memeriksa bagian atas dan tulang belakang : 1) jika titik lebih besar dari titik, maka 1, 2) jika titik kurang dari (atau sama dengan) titik, maka 0 ditempatkan pada tumpukan. Harap dicatat bahwa ketika menghapus item untuk perbandingan, mereka dihapus (dihapus), tidak disalin.

        elif self.value == '?' and self.stack_sf == 0:
            # ? ( )
            a = random.randint(1, 4)
            self.vector = a

        elif self.value == ':' and self.stack_sf == 0:              #  
            last = self.stack.pop()
            self.stack.append(last)
            self.stack.append(last)

        elif self.value == '\\' and self.stack_sf == 0:             # ab => ba
            a = self.stack.pop()
            b = self.stack.pop()
            self.stack.append(a)
            self.stack.append(b)

Jika karakter saat ini adalah "?", Maka Anda harus memilih arah yang acak dan mengikutinya. Kami menggunakan fungsi random.randint (1, 4), yang menghasilkan angka 1,2,3,4 dan menempatkan nilai baru dalam point.vector

Jika karakter saat ini adalah ":", maka kami meletakkan pada salinan salinan bagian atas tumpukan, yaitu membacanya, dan kemudian menambahkannya ke tumpukan dua kali.

Jika karakter saat ini adalah "\\" (garis miring terbalik), maka Anda perlu menukar titik dan sub-titik. Kami mendapatkan dua angka, meletakkannya di tumpukan dengan urutan terbalik.

        elif self.value == '#' and self.stack_sf == 0:              #  ""
            self.step()

        elif self.value == ',' and self.stack_sf == 0:              # =65=A
            value = self.stack.pop()
            print(chr(value), end='')

        elif self.value == '.' and self.stack_sf == 0:              # Print 
            a = self.stack.pop()
            print(a, end='')

Jika simbol saat ini adalah "#" (pound), maka Anda harus melompati perintah (dalam arah) berikutnya. Perhatikan bahwa pada akhir tindakan () ada lompatan tanpa syarat self.step (), ini memungkinkan Anda untuk bergerak maju ke perintah berikutnya. Setelah menulis self.step () ke dalam pemrosesan "#", kita sebenarnya membuat dua lompatan dan "lewati" perintah berikutnya setelah "#".

Jika karakter saat ini adalah "," (koma), maka Anda perlu mencetak karakter yang kode ASCII-nya ada di atas tumpukan. Jika nomor 65 ada di sana, maka "A" harus ditampilkan.

Jika karakter saat ini adalah "." (titik), maka Anda perlu mencetak nomor yang terletak di bagian atas tumpukan, seperti nomor. Jika ada 65, maka Anda perlu menampilkan "65". Dalam kedua kasus, parameter end = '' diatur selama output sehingga tidak ada jeda baris baru.

        elif self.value == '_' and self.stack_sf == 0:              #  "_"
            test = self.stack.pop()
            if test == 0:
                #  = 0, (2)
                self.vector = 2
            else:
                #  !=0, (4)
                self.vector = 4

        elif self.value == '|' and self.stack_sf == 0:              #  "|"
            test = self.stack.pop()
            if test == 0:
                self.vector = 3
            else:
                self.vector = 1

Jika karakter saat ini adalah "_" (garis bawah), lalu periksa secara horizontal. Kami menghapus dari tumpukan nomor untuk memeriksa (tes), jika itu = 0, maka kami bergerak ke kanan (vektor = 2), jika itu! = 0, maka kami pindah ke kiri (vektor = 4).

Jika karakter saat ini = "|" (bilah vertikal), maka Anda perlu melakukan pemeriksaan vertikal. Kami menghapus angka (tes) dari tumpukan, jika itu = 0, maka kami bergerak ke bawah (vektor = 3), kalau tidak kita naik (vektor = 1).

        elif self.value == '$' and self.stack_sf == 0:              #  
            self.stack.pop()

        elif self.value == '~' and self.stack_sf == 0:              # Input: A => 65
            val = input(' : ')
            self.stack.append(ord(val[0]))

        elif self.value == '&' and self.stack_sf == 0:              # Input: 65 => 65
            val = int(input(' : '))
            self.stack.append((val))

        elif self.value == 'p' and self.stack_sf == 0:              # x, y, symcode
            x = self.stack.pop()                                    # A(x,y) = symcode
            y = self.stack.pop()
            symcode = self.stack.pop()
            A[x][y] = chr(symcode)

        # x, y, value=A(x,y)
        elif self.value == 'g' and self.stack_sf == 0:
            x = self.stack.pop()                                    # ord(value) => 
            y = self.stack.pop()
            value = A[x][y]
            self.stack.append(ord(value))

Jika karakter saat ini = "$", maka Anda perlu menghapus simpul. Buat pop sederhana ().

Jika karakter saat ini = "~" (tilde), maka kami meminta karakter dari pengguna dan meletakkan kode ASCII mereka di tumpukan. Pengguna "A" (Inggris) mengirim, kita harus meletakkan 65 di tumpukan. Untuk berjaga-jaga, kita akan memasukkan val [0], jika tidak, pengguna dapat memasukkan "Apple" dan menerjemahkannya ke dalam kode tidak berfungsi.

Jika karakter saat ini = "&" (ampersand), maka kami meminta pengguna untuk nomor dan meletakkan nomor di tumpukan. Anda memasukkan 65, Anda harus meletakkan 65 di tumpukan.

Sekarang dua tim yang paling sulit.

Jika karakter saat ini = "p", maka Anda perlu mengekstrak koordinat sel dan kode ASCII karakter dari tumpukan, dan kemudian menempatkan karakter ini dalam koordinat ini di bidang A. Misalkan 1.2.65 ada di stack dan kita mendapat (1.2) dan 65, kita harus meletakkan simbol "A" di sel (1.2). Sekali lagi, saya perhatikan: kami mendapat tiga angka, dan menaruh simbol di koordinat.

Jika karakter saat ini = "g", maka koordinat sel diambil dari stack, sel dicari di lapangan, karakter diambil dari sana dan kode ASCII-nya didorong ke stack. Misalkan, simbol "B" terletak di lapangan dalam sel (2,3), tim saat ini mendapat "g" dan kami mendapat 2,3 dari tumpukan. Dalam hal ini, kita pergi sepanjang koordinat (2,3), mendapatkan simbol "B" dari sana, menerjemahkannya ke nomor 66 (kode simbol B) dan meletakkan 66 di tumpukan.

        elif self.value == '>' and self.stack_sf == 0:              # >
            self.vector = 2
        elif self.value == '<' and self.stack_sf == 0:              # <
            self.vector = 4
        elif self.value == '^' and self.stack_sf == 0:              # ^
            self.vector = 1
        elif self.value == 'v' and self.stack_sf == 0:              # v
            self.vector = 3
        self.step()                                                 #  

Nah, dan baris kode terakhir: organisasi yang memindahkan pointer melintasi bidang. Semuanya sederhana di sini: kita melihat simbol dan mengubah arah (vektor). Di akhir action () fungsi adalah self.step (), yang mengambil satu langkah ke arah saat ini. Dengan demikian, action () adalah eksekusi dari action dan langkah untuk karakter selanjutnya.

Kesimpulan


Menulis kompiler ini sangat menyenangkan. Dan betapa senangnya membawa momen ketika Anda memasukkan kode tertentu ke dalam program, dan itu dijalankan (dengan benar!) Dan Anda mengamatinya! Situs befungius.aurlien.net banyak membantu ketika bekerja , di mana penulis memposting penerjemah online Befunge (dalam JavaScript). Berikut adalah videonya dari beberapa konferensi www.youtube.com/watch?v=oCPT3L33848 , di mana ia berbicara tentang bahasa tersebut.

Untuk pengujian, Anda dapat menggunakan hampir semua program di Befunge, kecuali yang membutuhkan bidang sebagai torus, mis. memiliki transisi ke berbagai arah dunia. Untuk beberapa program, Anda perlu melihat bidang itu sendiri (misalnya, program penghitung mengubah koordinat A [0] [1]), jadi masukkan output koordinat ini di layar atau tampilkan seluruh matriks A setelah setiap langkah.

Bagaimanapun, ini bukan program yang dijilat untuk bersinar, tetapi kode pelatihan yang dapat ditambahkan dan diedit. Kesalahan ketik mungkin terjadi, meskipun saya telah mengujinya berulang kali. Kritik tidak diterima, tetapi tidak dilarang. Semoga sukses untuk semua orang dan kode yang baik.

Halo Dunia!

0"!dlrow olleH">:#,_@

Bazz

0> 1+:3%v
>^  v%5:_:5% v
,v.:_v     v0_0"zzub"v
"v         #
     >0"zzub"v
"   v"fizz"<         <
^<         $<>:#,_v
    >      #^^#   <

Fibonacci

62*1+v>01p001>+v>\:02p\:02gv
     0       ^             <
     .         :p
     "         .1
        v 0," "<0
     "  >1g12-+:|
     ,          @
     >^

Menghitung

>91+:9`v
p   v  _v
    >$0 v
^ 01+*68<

Tapi ini tidak berhasil, keluar dari lapangan

0>:00p58*`#@_0>:01p78vv$$<
@^+1g00,+55_v# !`\+*9<>4v$
@v30p20"?~^"< ^+1g10,+*8<$
@>p0\>\::*::882**02g*0v >^
`*:*" d":+*:-*"[Z"+g3 < |<
v-*"[Z"+g30*g20**288\--\<#
>2**5#>8*:*/00g"P"*58*:*v^
v*288 p20/**288:+*"[Z"+-<:
>*%03 p58*:*/01g"3"* v>::^
   \_^#!:-1\+-*2*:*85<^

Tautan dan materi tambahan:

  1. Kode lengkap
  2. Versi online
  3. Dia dalam JavaScript
  4. Dokumentasi (Bahasa Inggris)
  5. Artikel wiki (Bahasa Inggris)

All Articles