Pemrogram Lisp membanggakan bahwa Lisp adalah bahasa yang kuat yang dapat dibangun dari sekumpulan kecil operasi primitif . Mari kita mempraktekkan gagasan itu dengan bermain golf juru bahasa untuk dialek yang disebut tinylisp
.
Spesifikasi bahasa
Dalam spesifikasi ini, kondisi apa pun yang hasilnya digambarkan sebagai "tidak terdefinisi" dapat melakukan apa saja pada penerjemah Anda: crash, gagal diam-diam, menghasilkan gobbldegook acak, atau bekerja seperti yang diharapkan. Implementasi referensi di Python 3 tersedia di sini .
Sintaksis
Token dalam tinylisp adalah (
,, )
atau string apa pun dari satu atau lebih karakter ASCII yang dapat dicetak kecuali tanda kurung atau spasi. (Yaitu regex berikut:. [()]|[^() ]+
) Setiap token yang seluruhnya terdiri dari digit adalah bilangan bulat integer. (Memimpin nol baik-baik saja.) Setiap token yang mengandung non-digit adalah simbol, bahkan contoh numerik yang tampak seperti 123abc
, 3.14
, dan -10
. Semua spasi putih (termasuk, setidaknya, karakter ASCII 32 dan 10) diabaikan, kecuali sejauh itu memisahkan token.
Program tinylisp terdiri dari serangkaian ekspresi. Setiap ekspresi adalah bilangan bulat, simbol, atau ekspresi-s (daftar). Daftar terdiri dari nol atau lebih ekspresi yang dibungkus dengan tanda kurung. Tidak ada pemisah yang digunakan di antara item. Berikut adalah contoh ungkapan:
4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))
Ekspresi yang tidak terbentuk dengan baik (khususnya, yang memiliki tanda kurung yang tidak cocok) memberikan perilaku yang tidak terdefinisi. (Implementasi referensi menutup secara otomatis parens terbuka dan berhenti mengurai pada parens dekat yang tak tertandingi.)
Tipe data
Tipe data tinylisp adalah bilangan bulat, simbol, dan daftar. Fungsi bawaan dan makro juga dapat dianggap tipe, meskipun format outputnya tidak ditentukan. Daftar dapat berisi sejumlah nilai dari jenis apa pun dan dapat disarangkan secara mendalam. Integer harus didukung setidaknya dari -2 ^ 31 hingga 2 ^ 31-1.
Daftar kosong ()
- juga disebut sebagai nil - dan integer 0
adalah satu-satunya nilai yang dianggap salah secara logis; semua bilangan bulat lainnya, daftar kosong, bawaan, dan semua simbol secara logis benar.
Evaluasi
Ekspresi dalam suatu program dievaluasi secara berurutan dan hasil masing-masing dikirim ke stdout (lebih lanjut tentang pemformatan output nanti).
- Integer literal mengevaluasi dirinya sendiri.
- Daftar kosong
()
mengevaluasi dirinya sendiri. - Daftar satu atau lebih item mengevaluasi item pertama dan memperlakukannya sebagai fungsi atau makro, menyebutnya dengan item yang tersisa sebagai argumen. Jika item tersebut bukan fungsi / makro, perilaku tidak terdefinisi.
- Simbol mengevaluasi sebagai nama, memberikan nilai yang terikat pada nama itu dalam fungsi saat ini. Jika nama tidak didefinisikan dalam fungsi saat ini, ia mengevaluasi nilai yang terikat padanya pada lingkup global. Jika nama tidak didefinisikan pada lingkup saat ini atau global, hasilnya tidak terdefinisi (implementasi referensi memberikan pesan kesalahan dan mengembalikan nihil).
Fungsi dan makro bawaan
Ada tujuh fungsi bawaan di tinylisp. Suatu fungsi mengevaluasi setiap argumennya sebelum menerapkan beberapa operasi padanya dan mengembalikan hasilnya.
c
- kontra [daftar saluran]. Membawa dua argumen, nilai dan daftar, dan mengembalikan daftar baru yang diperoleh dengan menambahkan nilai di bagian depan daftar.h
- head ( mobil , dalam terminologi Lisp). Mengambil daftar dan mengembalikan item pertama di dalamnya, atau nihil jika diberikan nihil.t
- tail ( cdr , dalam terminologi Lisp). Mengambil daftar dan mengembalikan daftar baru yang berisi semua kecuali item pertama, atau nihil jika diberikan nihil.s
- kurangi. Membawa dua bilangan bulat dan mengembalikan yang pertama minus yang kedua.l
- kurang dari. Membawa dua bilangan bulat; mengembalikan 1 jika yang pertama kurang dari yang kedua, 0 sebaliknya.e
- sama. Mengambil dua nilai dari jenis yang sama (bilangan bulat, daftar, atau simbol); mengembalikan 1 jika keduanya sama (atau identik di setiap elemen), 0 sebaliknya. Pengujian bawaan untuk kesetaraan tidak terdefinisi (implementasi referensi berfungsi seperti yang diharapkan).v
- eval. Mengambil satu daftar, integer, atau simbol, mewakili ekspresi, dan mengevaluasinya. Misalnya melakukan(v (q (c a b)))
sama dengan melakukan(c a b)
;(v 1)
memberi1
.
"Nilai" di sini mencakup daftar, bilangan bulat, simbol, atau bawaan apa pun, kecuali ditentukan lain. Jika suatu fungsi didaftarkan sebagai mengambil tipe-tipe spesifik, meneruskannya tipe-tipe yang berbeda adalah perilaku yang tidak terdefinisi, seperti halnya melewati jumlah argumen yang salah (implementasi referensi umumnya macet).
Ada tiga makro bawaan di tinylisp. Makro, tidak seperti fungsi, tidak mengevaluasi argumennya sebelum menerapkan operasi padanya.
q
- kutipan. Mengambil satu ekspresi dan mengembalikannya tidak dievaluasi. Misalnya, mengevaluasi(1 2 3)
memberikan kesalahan karena mencoba memanggil1
sebagai fungsi atau makro, tetapi(q (1 2 3))
mengembalikan daftar(1 2 3)
. Mengevaluasia
memberi nilai yang terikat pada namaa
, tetapi(q a)
memberikan nama itu sendiri.i
- jika. Membawa tiga ekspresi: suatu kondisi, ekspresi iftrue, dan ekspresi iffalse. Mengevaluasi kondisi terlebih dahulu. Jika hasilnya falsy (0
atau nil), evaluasi dan kembalikan ekspresi iffalse. Kalau tidak, evaluasi dan kembalikan ekspresi iftrue. Perhatikan bahwa ekspresi yang tidak dikembalikan tidak pernah dievaluasi.d
- def. Mengambil simbol dan ekspresi. Mengevaluasi ekspresi dan mengikatnya ke simbol yang diberikan diperlakukan sebagai nama di lingkup global , lalu mengembalikan simbol. Mencoba mendefinisikan ulang nama harus gagal (diam-diam, dengan pesan, atau dengan crash; implementasi referensi menampilkan pesan kesalahan). Catatan: tidak perlu mengutip nama sebelum meneruskannyad
, meskipun perlu mengutip kutipan jika daftar atau simbol yang tidak ingin Anda evaluasi: misalnya(d x (q (1 2 3)))
,.
Melewati jumlah argumen yang salah ke makro adalah perilaku yang tidak terdefinisi (implementasi referensi lumpuh). Melewati sesuatu yang bukan simbol sebagai argumen pertama d
adalah perilaku tidak terdefinisi (implementasi referensi tidak memberikan kesalahan, tetapi nilainya tidak dapat direferensikan selanjutnya).
Fungsi dan makro yang ditentukan pengguna
Mulai dari sepuluh built-in ini, bahasa dapat diperluas dengan membangun fungsi dan makro baru. Ini tidak memiliki tipe data khusus; mereka hanya daftar dengan struktur tertentu:
- Fungsi adalah daftar dua item. Yang pertama adalah daftar satu atau lebih nama parameter, atau satu nama yang akan menerima daftar argumen apa pun yang diteruskan ke fungsi (sehingga memungkinkan untuk fungsi variabel-arity). Yang kedua adalah ekspresi yang merupakan fungsi tubuh.
- Makro sama dengan fungsi, kecuali makro berisi nil sebelum nama parameter, sehingga menjadikannya daftar tiga item. (Mencoba memanggil daftar tiga item yang tidak dimulai dengan nihil adalah perilaku yang tidak terdefinisi; implementasi referensi mengabaikan argumen pertama dan memperlakukannya sebagai makro juga.)
Misalnya, ekspresi berikut adalah fungsi yang menambahkan dua bilangan bulat:
(q List must be quoted to prevent evaluation
(
(x y) Parameter names
(s x (s 0 y)) Expression (in infix, x - (0 - y))
)
)
Dan makro yang mengambil sejumlah argumen dan mengevaluasi dan mengembalikan yang pertama:
(q
(
()
args
(v (h args))
)
)
Fungsi dan makro dapat dipanggil langsung, terikat dengan nama menggunakan d
, dan diteruskan ke fungsi atau makro lain.
Karena badan fungsi tidak dieksekusi pada waktu definisi, fungsi rekursif mudah didefinisikan:
(d len
(q (
(list)
(i list If list is nonempty
(s 1 (s 0 (len (t list)))) 1 - (0 - len(tail(list)))
0 else 0
)
))
)
Perhatikan, bagaimanapun, bahwa di atas bukan cara yang baik untuk mendefinisikan fungsi panjang karena tidak menggunakan ...
Rekursi ekor-panggilan
Rekursi ekor-panggilan adalah konsep penting dalam Lisp. Ini mengimplementasikan beberapa jenis rekursi sebagai loop, sehingga menjaga stack panggilan kecil. Penerjemah tinylisp Anda harus menerapkan rekursi ekor-panggilan yang tepat!
- Jika ekspresi balik dari fungsi atau makro yang ditentukan pengguna adalah panggilan ke fungsi atau makro yang ditentukan pengguna lain, juru bahasa Anda tidak boleh menggunakan rekursi untuk mengevaluasi panggilan itu. Sebagai gantinya, ia harus mengganti fungsi dan argumen saat ini dengan fungsi dan argumen baru dan loop sampai rantai panggilan diselesaikan.
- Jika ekspresi balik dari fungsi atau makro yang ditentukan pengguna adalah panggilan untuk
i
, jangan segera mengevaluasi cabang yang dipilih. Alih-alih, periksa apakah itu panggilan ke fungsi lain yang ditentukan pengguna atau makro. Jika demikian, tukar fungsi dan argumen seperti di atas. Ini berlaku untuk kejadian yang sangat bersarang darii
.
Rekursi ekor harus bekerja baik untuk rekursi langsung (fungsi memanggil dirinya sendiri) dan rekursi tidak langsung (fungsi a
panggilan fungsi b
yang memanggil [dll] yang memanggil fungsi a
).
Fungsi panjang rekursif ekor (dengan fungsi pembantu len*
):
(d len*
(q (
(list accum)
(i list
(len*
(t list)
(s 1 (s 0 accum))
)
accum
)
))
)
(d len
(q (
(list)
(len* list 0)
))
)
Implementasi ini berfungsi untuk daftar besar yang sewenang-wenang, hanya dibatasi oleh ukuran integer maks.
Cakupan
Parameter fungsi adalah variabel lokal (sebenarnya konstanta, karena tidak dapat dimodifikasi). Mereka berada dalam ruang lingkup sementara tubuh panggilan fungsi itu dieksekusi, dan di luar ruang selama panggilan yang lebih dalam dan setelah fungsi kembali. Mereka dapat "membayangi" nama yang ditentukan secara global, sehingga membuat nama global tersebut untuk sementara tidak tersedia. Misalnya, kode berikut mengembalikan 5, bukan 41:
(d x 42)
(d f
(q (
(x)
(s x 1)
))
)
(f 6)
Namun, kode berikut mengembalikan 41, karena x
pada panggilan tingkat 1 tidak dapat diakses dari panggilan tingkat 2:
(d x 42)
(d f
(q (
(x)
(g 15)
))
)
(d g
(q (
(y)
(s x 1)
))
)
(f 6)
Satu-satunya nama dalam lingkup pada waktu tertentu adalah 1) nama lokal dari fungsi yang sedang dijalankan, jika ada, dan 2) nama global.
Persyaratan pengiriman
Masukan dan keluaran
Penerjemah Anda dapat membaca program dari stdin atau dari file yang ditentukan melalui stdin atau argumen baris perintah. Setelah mengevaluasi setiap ekspresi, itu harus menampilkan hasil dari ekspresi itu ke stdout dengan baris baru yang tertinggal.
- Integer harus menjadi output dalam representasi paling alami bahasa implementasi Anda. Bilangan bulat negatif dapat berupa output, dengan tanda minus terkemuka.
- Simbol harus berupa output sebagai string, tanpa tanda kutip di sekitarnya atau lolos.
- Daftar harus berupa output dengan semua item dipisahkan dengan ruang dan dibungkus dengan tanda kurung. Ruang di dalam tanda kurung adalah opsional:
(1 2 3)
dan( 1 2 3 )
keduanya merupakan format yang dapat diterima. - Mengeluarkan fungsi dan makro bawaan adalah perilaku yang tidak terdefinisi. (Interpretasi referensi menampilkannya sebagai
<built-in function>
.)
Lain
Interpreter referensi termasuk lingkungan REPL dan kemampuan untuk memuat modul tinylisp dari file lain; ini disediakan untuk kenyamanan dan tidak diperlukan untuk tantangan ini.
Uji kasus
Kasing uji dipisahkan menjadi beberapa kelompok sehingga Anda dapat menguji yang lebih sederhana sebelum mengerjakan yang lebih rumit. Namun, mereka juga akan berfungsi dengan baik jika Anda membuang semuanya dalam satu file bersama. Hanya saja, jangan lupa untuk menghapus judul dan output yang diharapkan sebelum menjalankannya.
Jika Anda telah menerapkan rekursi panggilan-tailing dengan benar, test case akhir (multi-bagian) akan kembali tanpa menyebabkan stack overflow. Implementasi referensi menghitungnya dalam waktu sekitar enam detik di laptop saya.
sumber
-1
, saya masih bisa menghasilkan nilai -1 dengan melakukan(s 0 1)
.F
tidak tersedia dalam fungsiG
jikaF
panggilanG
(seperti dengan pelingkupan dinamis), tetapi mereka juga tidak tersedia dalam fungsiH
jikaH
fungsi bersarang didefinisikan di dalamF
(seperti dengan pelingkupan leksikal) - lihat uji kasus 5. Jadi menyebutnya "leksikal "Mungkin menyesatkan.Jawaban:
Python 2,
685675660657646642640 byteMembaca input dari STDIN dan menulis output ke STDOUT.
Meskipun tidak sepenuhnya diperlukan, interpreter mendukung fungsi dan makro nullary, dan mengoptimalkan panggilan ekor yang dieksekusi melalui
v
.Penjelasan
Parsing
Untuk mengurai input, pertama-tama kita mengelilingi setiap kemunculan dari
(
dan)
dengan spasi, dan membagi string yang dihasilkan menjadi kata-kata; ini memberi kita daftar token. Kami mempertahankan tumpukan ekspresiE
, yang awalnya kosong. Kami memindai token, dalam urutan:(
, kami mendorong daftar kosong di bagian atas tumpukan ekspresi;)
, kita memunculkan nilai di bagian atas tumpukan ekspresi, dan menambahkannya ke daftar yang sebelumnya di bawahnya di tumpukan;Jika, saat memproses token biasa, atau setelah memunculkan ekspresi dari tumpukan karena
)
, tumpukan ekspresi kosong, kami berada di ekspresi tingkat atas, dan kami mengevaluasi nilai yang seharusnya kami tambahkan, gunakanV()
, dan cetak hasilnya, diformat dengan tepat menggunakanF()
.Evaluasi
Kami menjaga ruang lingkup global
G
,, sebagai daftar pasangan kunci / nilai. Awalnya, ini hanya berisi fungsi builtin (tetapi bukan makro, dan tidakv
, yang kami perlakukan sebagai makro), yang diimplementasikan sebagai lambdas.Evaluasi terjadi di dalam
V()
, yang mengambil ekspresi untuk mengevaluasi,,e
dan cakupan lokalL
,, yang juga merupakan daftar pasangan kunci / nilai (ketika mengevaluasi ekspresi tingkat atas, cakupan lokal kosong.) NyaliV()
kehidupan di dalam infinite loop, yang merupakan cara kami melakukan pengoptimalan tail-call (TCO), seperti yang dijelaskan nanti.Kami memproses
e
sesuai dengan jenisnya:jika daftar kosong, atau string yang dapat dikonversi ke int, kami segera mengembalikannya (mungkin setelah konversi ke int); jika tidak,
jika itu sebuah string, kita mencarinya di kamus yang dibuat dari gabungan lingkup global dan lokal. Jika kami menemukan nilai terkait, kami mengembalikannya; jika tidak,
e
harus menjadi nama makro builtin (yaituq
,i
,d
atauv
), dan kami kembali tidak berubah. Jika tidak, jikae
bukan string,e
adalah daftar (bukan kosong), yaitu panggilan fungsi. Kami mengevaluasi elemen pertama dari daftar, yaitu, ekspresi fungsi, dengan memanggilV()
secara rekursif (menggunakan lingkup lokal saat ini); kami sebut hasilnyaf
. Sisa daftarA
,, adalah daftar argumen.f
hanya bisa berupa string, yang dalam hal ini merupakan makro builtin (atau fungsiv
), lambda, dalam hal ini merupakan fungsi builtin, atau daftar, yang dalam hal ini merupakan fungsi atau makro yang ditentukan pengguna.Jika
f
aa string, yaitu makro bawaan, kami menanganinya di tempat. Jika itu makroi
atauv
, kami mengevaluasi operan pertama, dan baik memilih operan kedua atau ketiga sesuai, dalam kasusi
, atau menggunakan hasil dari operan pertama, dalam kasusv
; alih-alih mengevaluasi ekspresi yang dipilih secara rekursif, yang akan mengalahkan TCO, kami cukup menggantie
dengan ekspresi yang dikatakan, dan melompat ke awal loop. Jikaf
makrod
, kami menambahkan pasangan, yang elemen pertama adalah operan pertama, dan elemen kedua adalah hasil mengevaluasi operan kedua, ke lingkup globalG
,, dan mengembalikan operan pertama. Kalau tidak,f
adalah makroq
, dalam hal ini kami hanya mengembalikan operan secara langsung.Selain itu, jika
f
merupakan lambda, atau daftar yang elemen pertamanya bukan()
, maka itu adalah fungsi non-nullary, bukan makro, dalam hal ini kami mengevaluasi argumennya, yaitu elemenA
, dan menggantiA
dengan hasilnya.Jika
f
ini adalah lambda, kami menyebutnya, meneruskan argumen yang sudah dibongkarA
, dan mengembalikan hasilnya.Kalau tidak,
f
adalah daftar, yaitu, fungsi yang ditentukan pengguna atau makro; daftar parameternya adalah elemen kedua hingga terakhir, dan tubuhnya adalah elemen terakhir. Seperti dalam kasus makroi
danv
, untuk melakukan TCO, kami tidak mengevaluasi tubuh secara rekursif, melainkan menggantie
dengan tubuh dan melanjutkan ke iterasi berikutnya. Berbeda dengani
danv
, bagaimanapun, kami juga mengganti ruang lingkup lokalL
,, dengan ruang lingkup fungsi lokal yang baru. Jika daftar parameter,P
, adalah, pada kenyataannya, daftar, ruang lingkup lokal baru dibangun oleh zipping daftar parameter,P
, dengan daftar argumen,A
; jika tidak, kita berhadapan dengan fungsi variadik, dalam hal ini lingkup lokal baru hanya memiliki satu elemen, pasangan(P, A)
.REPL
Jika Anda ingin bermain dengannya, inilah versi penerjemah REPL. Ini mendukung pendefinisian ulang simbol, dan mengimpor file melalui argumen baris perintah, atau
(import <filename>)
makro. Untuk keluar dari juru bahasa, hentikan input (biasanya, Ctrl + D atau Ctrl + Z).Tampilkan cuplikan kode
Dan inilah contoh sesi, menerapkan pengurutan gabungan:
Tampilkan cuplikan kode
sumber
import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
A[0]
beberapa variabel satu-char hanya setelah kecuali blokA
kosong dalam kasus ini), dan saya tidak ingin "mundur".C (GNU), 1095 byte
Banyak aksi terjadi dalam
v
fungsi raksasa . Alih-alih menerapkan rekursi ekor secara eksplisit,v
terstruktur sehingga banyak panggilan dariv
kev
akan ditangani oleh optimasi rekursi ekor gcc. Tidak ada pengumpulan sampah.Ini membuat penggunaan ekstensi GCC sangat berat, sehingga hanya bisa dikompilasi dengan gcc (gunakan perintah
gcc -w -Os tl.c
). Itu juga menggunakan beberapascanf
ekstensi yang tidak tersedia di Windows, yang biasanya saya gunakan. Prospek menulis parser dengan standarscanf
sangat mengerikan sehingga saya menggunakan Linux VM untuk menguji program. Parsing tanpascanf
kelas karakter mungkin akan menambahkan 100+ byte.Semi-ungolfed
sumber
Ceylon, 2422 byte
(Saya pikir ini adalah program golf terpanjang saya.)
Saya bisa bermain golf beberapa byte lebih banyak, karena saya menggunakan beberapa pengenal dua huruf di beberapa tempat, tapi saya sudah kehabisan huruf tunggal yang agak bermakna bagi mereka. Meskipun cara ini tidak terlihat seperti Ceylon sangat banyak ...
Ini adalah berorientasi objek implementasi .
Kami memiliki antarmuka nilai
V
dengan kelas implementasiL
(daftar - hanya pembungkus di sekitar sekuens CeylonV
),S
(simbol - pembungkus di sekitar string),I
(integer - pembungkus di sekitar integer Ceylon) danB
(fungsi bawaan atau makro, pembungkus di sekitar Fungsi Ceylon).Saya menggunakan notasi kesetaraan Ceylon standar dengan menerapkan
equals
metode (dan jugahash
atribut, yang benar-benar hanya diperlukan untuk simbol), dan jugastring
atribut standar untuk output.Kami memiliki atribut Boolean
b
(yang benar secara default, ditimpaI
danL
untuk mengembalikan false untuk daftar kosong), dan dua metodel
(panggilan, yaitu menggunakan objek ini sebagai fungsi) danvO
(mengevaluasi satu langkah). Keduanya mengembalikan nilai atau objek kelanjutan yang memungkinkan kemudian evaluasi untuk satu langkah lagi, danvF
(mengevaluasi sepenuhnya) loop sampai hasilnya bukan kelanjutan lagi.Antarmuka konteks memungkinkan akses ke variabel. Ada dua implementasi,
G
untuk konteks global (yang memungkinkan penambahan variabel menggunakand
builtin) danLC
untuk konteks lokal, yang aktif ketika mengevaluasi ekspresi fungsi pengguna (kembali ke konteks global).Evaluasi simbol mengakses konteks, daftar (jika tidak kosong) evaluasi dengan terlebih dahulu mengevaluasi elemen pertama mereka dan kemudian memanggil metode panggilannya. Panggilan diimplementasikan hanya dengan daftar dan builtin - pertama mengevaluasi argumen (jika suatu fungsi, bukan jika makro) dan kemudian melakukan hal-hal menarik yang sebenarnya - untuk builtin hanya apa yang hardcode, untuk daftar itu menciptakan konteks lokal baru dan mengembalikan kelanjutan dengan itu.
Untuk builtin saya menggunakan trik yang mirip dengan yang saya gunakan di blog saya Shift Interpreter , yang memungkinkan saya untuk mendefinisikannya dengan tipe argumen yang mereka butuhkan, tetapi memanggil mereka dengan urutan umum menggunakan refleksi (jenis akan diperiksa pada waktu panggilan). Ini menghindari konversi tipe / pernyataan tegas di dalam fungsi / makro, tetapi membutuhkan fungsi tingkat atas sehingga saya bisa mendapatkan objek meta-model mereka
Function
.Itu
p
(parse) membagi string pada spasi, baris baru dan tanda kurung, kemudian loop di atas token dan membangun daftar menggunakan tumpukan dan daftar berjalan.Penerjemah (dalam
run
metode, yang merupakan titik masuk) kemudian mengambil daftar ekspresi ini (yang hanya nilai), mengevaluasi masing-masing, dan mencetak hasilnya.Di bawah ini adalah versi dengan komentar dan dijalankan melalui formatter.
Versi sebelumnya sebelum saya mulai bermain golf (dan masih dengan beberapa kesalahpahaman tentang evaluasi daftar) ditemukan di repositori Github saya , saya akan meletakkan yang ini di sana segera (jadi pastikan untuk melihat versi pertama jika Anda ingin yang asli).
sumber