Lisp kecil, penerjemah kecil

33

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 0adalah 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)memberi 1.

"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 memanggil 1sebagai fungsi atau makro, tetapi (q (1 2 3))mengembalikan daftar (1 2 3). Mengevaluasi amemberi nilai yang terikat pada nama a, 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 ( 0atau 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 meneruskannya d, 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 dadalah 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 dari i.

Rekursi ekor harus bekerja baik untuk rekursi langsung (fungsi memanggil dirinya sendiri) dan rekursi tidak langsung (fungsi apanggilan fungsi byang 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 xpada 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.

DLosc
sumber
"Setiap token yang seluruhnya terdiri dari digit adalah bilangan bulat. (Leading nol tidak apa-apa.) Setiap token yang berisi non-digit adalah simbol, bahkan contoh yang tampak numerik seperti 123abc, 3.14, dan -10." tampaknya bertentangan "Integer harus didukung setidaknya dari -2 ^ 31 ke 2 ^ 31-1."
msh210
3
@ msh210 Tidak juga, karena yang pertama berbicara tentang token sedangkan yang terakhir berbicara tentang nilai . Meskipun tidak ada cara langsung untuk masuk -1, saya masih bisa menghasilkan nilai -1 dengan melakukan (s 0 1).
DLosc
1
@coredump Setelah membaca artikel Wikipedia yang bersangkutan , saya menyimpulkan bahwa implementasinya sebenarnya lebih dekat dengan dinamis, tetapi tanpa ruang lingkup bersarang. Variabel dalam fungsi Ftidak tersedia dalam fungsi Gjika Fpanggilan G(seperti dengan pelingkupan dinamis), tetapi mereka juga tidak tersedia dalam fungsi Hjika Hfungsi bersarang didefinisikan di dalam F(seperti dengan pelingkupan leksikal) - lihat uji kasus 5. Jadi menyebutnya "leksikal "Mungkin menyesatkan.
DLosc
1
Dengan kata lain: karena kurangnya ruang lingkup bersarang, suatu implementasi dapat menggunakan strategi pelingkupan dinamis atau leksikal dan menghasilkan hasil yang sama. Satu-satunya nama dalam lingkup pada waktu tertentu adalah 1) nama lokal dari fungsi yang sedang dijalankan, jika ada, dan 2) nama global. Penutupan tidak didukung. (Implementasi referensi menyimpan setumpuk binding nama yang sesuai dengan panggilan stack - pendekatan gaya dinamis, yang menurut saya akan menjadi yang paling mudah untuk diterapkan.)
DLosc
1
Wajib xkcd .
mınxomaτ

Jawaban:

11

Python 2, 685 675 660 657 646 642 640 byte

import sys,re
E=[]
G=zip("chtsle",[eval("lambda x,y=0:"+f)for f
in"[x]+y (x+[E])[0] x[1:] x-y +(x<y) +(x==y)".split()])
def V(e,L=E):
 while 1:
    try:return e and int("0%s"%e)
    except:A=e[1:]
    if""<e:return dict(G+L).get(e,e)
    f=V(e[0],L)
    if""<f:
     if f in"iv":t=V(A[0],L);e=(e[~bool(t)],t)[f>"u"];continue
     if"e">f:G[:]+=(A[0],V(A[1],L)),
     return A[0]
    if[]>f or f[0]:A=[V(a,L)for a in A]
    if[]>f:return f(*A)
    P,e=f[-2:];L=([(P,A)],zip(P,A))[P<""]
F=lambda x:V<x<""and"(%s)"%" ".join(map(F,x))or"%s"%x
for t in re.sub("([()])"," \\1 ",sys.stdin.read()).split():
 if")"==t:t=E.pop()
 if"("==t:E+=[],
 elif E:E[-1]+=t,
 else:print F(V(t))

Membaca 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 ekspresi E, yang awalnya kosong. Kami memindai token, dalam urutan:

  • jika kami menemukan (, kami mendorong daftar kosong di bagian atas tumpukan ekspresi;
  • jika kita menjumpai a ), kita memunculkan nilai di bagian atas tumpukan ekspresi, dan menambahkannya ke daftar yang sebelumnya di bawahnya di tumpukan;
  • jika tidak, kami menambahkan token saat ini, sebagai string, ke daftar di bagian atas tumpukan ekspresi (kami menyimpan bilangan bulat sebagai string pada tahap ini, dan menguraikannya selama evaluasi.)

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, gunakan V(), dan cetak hasilnya, diformat dengan tepat menggunakan F().

Evaluasi

Kami menjaga ruang lingkup global G,, sebagai daftar pasangan kunci / nilai. Awalnya, ini hanya berisi fungsi builtin (tetapi bukan makro, dan tidak v, yang kami perlakukan sebagai makro), yang diimplementasikan sebagai lambdas.

Evaluasi terjadi di dalam V(), yang mengambil ekspresi untuk mengevaluasi,, edan cakupan lokal L,, yang juga merupakan daftar pasangan kunci / nilai (ketika mengevaluasi ekspresi tingkat atas, cakupan lokal kosong.) Nyali V()kehidupan di dalam infinite loop, yang merupakan cara kami melakukan pengoptimalan tail-call (TCO), seperti yang dijelaskan nanti.

Kami memproses esesuai 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, eharus menjadi nama makro builtin (yaitu q, i, datau v), dan kami kembali tidak berubah. Jika tidak, jika ebukan string,

  • eadalah daftar (bukan kosong), yaitu panggilan fungsi. Kami mengevaluasi elemen pertama dari daftar, yaitu, ekspresi fungsi, dengan memanggil V()secara rekursif (menggunakan lingkup lokal saat ini); kami sebut hasilnya f. Sisa daftar A,, adalah daftar argumen. fhanya bisa berupa string, yang dalam hal ini merupakan makro builtin (atau fungsi v), lambda, dalam hal ini merupakan fungsi builtin, atau daftar, yang dalam hal ini merupakan fungsi atau makro yang ditentukan pengguna.

    Jika faa string, yaitu makro bawaan, kami menanganinya di tempat. Jika itu makro iatau v, kami mengevaluasi operan pertama, dan baik memilih operan kedua atau ketiga sesuai, dalam kasus i, atau menggunakan hasil dari operan pertama, dalam kasus v; alih-alih mengevaluasi ekspresi yang dipilih secara rekursif, yang akan mengalahkan TCO, kami cukup mengganti edengan ekspresi yang dikatakan, dan melompat ke awal loop. Jika fmakro d, kami menambahkan pasangan, yang elemen pertama adalah operan pertama, dan elemen kedua adalah hasil mengevaluasi operan kedua, ke lingkup global G,, dan mengembalikan operan pertama. Kalau tidak, fadalah makro q, dalam hal ini kami hanya mengembalikan operan secara langsung.

    Selain itu, jika fmerupakan lambda, atau daftar yang elemen pertamanya bukan (), maka itu adalah fungsi non-nullary, bukan makro, dalam hal ini kami mengevaluasi argumennya, yaitu elemen A, dan mengganti Adengan hasilnya.

    Jika fini adalah lambda, kami menyebutnya, meneruskan argumen yang sudah dibongkar A, dan mengembalikan hasilnya.

    Kalau tidak, fadalah daftar, yaitu, fungsi yang ditentukan pengguna atau makro; daftar parameternya adalah elemen kedua hingga terakhir, dan tubuhnya adalah elemen terakhir. Seperti dalam kasus makro idan v, untuk melakukan TCO, kami tidak mengevaluasi tubuh secara rekursif, melainkan mengganti edengan tubuh dan melanjutkan ke iterasi berikutnya. Berbeda dengan idan v, bagaimanapun, kami juga mengganti ruang lingkup lokal L,, 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).

Dan inilah contoh sesi, menerapkan pengurutan gabungan:

Elo
sumber
Anda bisa mendapatkan sesuatu yang lebih pendek menggunakan zlib :) Kompres kode Anda yang dikonversi dalam bytes, dan ganti dengan:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
Labo
Anda bisa menyimpan dua byte dengan menugaskan A[0]beberapa variabel satu-char hanya setelah kecuali blok
Hannes Karppila
@ HannesKarppila Benar, tetapi ini akan merusak fungsi nullary (karena Akosong dalam kasus ini), dan saya tidak ingin "mundur".
Ell
4

C (GNU), 1095 byte

Banyak aksi terjadi dalam vfungsi raksasa . Alih-alih menerapkan rekursi ekor secara eksplisit, vterstruktur sehingga banyak panggilan dari vke vakan 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 beberapa scanfekstensi yang tidak tersedia di Windows, yang biasanya saya gunakan. Prospek menulis parser dengan standar scanfsangat mengerikan sehingga saya menggunakan Linux VM untuk menguji program. Parsing tanpa scanfkelas karakter mungkin akan menambahkan 100+ byte.

#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})
#define P printf
#define F(I,B)({for(I;x->c;x=x->l)B;})
#define Z return
typedef struct o{struct o*n,*l,*c;int i,t;}o;E(o a,o b){Z
a.n?!strcmp(a.n,b.n):a.c?b.c&&E(*a.c,*b.c)&E(*a.l,*b.l):!b.c&a.i==b.i;}p(o*x){x->t?P("%d ",x->i):x->n?P("%s ",x->n):F(P("("),p(x->c);P(")"));}o*x,G,N;*C(o*h,o*t){Z
O(c:h,l:t);}o*v(o*x,o*e){o*W(o*l,o*e){Z
l->c?C(v(l->c,e),W(l->l,e)):&N;}o*y,*a,*f;int t;Z
x->c?y=v(x->c,e),x=x->l,t=y->i,t?9/t?a=v(x->c,e),t>7?(t>8?a->c:a->l)?:a:t>6?v(a,e):t<6?x=v(x->l->c,e),t>4?C(a,x):O(t:1,i:t>3?E(*a,*x):t>2?a->i<x->i:a->i-x->i):v((a-&N&&!a->t|a->i?x:x->l)->l->c,e):(t&1&&d(x->c->n,v(x->l->c,e)),x->c):(y->l->l->l?y=y->l:(x=W(x,e)),a=y->c,v(y->l->c,a->n?O(n:a->n,c:x,l:&G):F(f=&G,(f=O(n:a->c->n,c:x->c,l:f),a=a->l);f))):x->n?e->n?strcmp(x->n,e->n)?v(x,e->l):e->c:e:x;}d(o*n,o*x){*v(O(n:""),&G)=(o){n:n,c:x,l:O()};}*R(h){char*z,*q;Z
scanf(" %m[^ \n()]",&q)>0?h=strtoul(q,&z,10),C(*z?O(n:q):O(t:1,i:h),R()):~getchar()&1?q=R(),C(q,R()):&N;}main(i){for(;++i<12;)d(strndup("slecivthqd"+i-2,1),O(i:i));F(x=R(),p(v(x->c,&G)));}

Semi-ungolfed

typedef struct o o;
struct o {
    char* n;
    o* l, //next in this list
     * c; 
    int i,
        t;
} ;



#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})

E(o a, o b) { //tests equality 
    return
        a.n ? !strcmp(a.n,b.n) :
        a.t ? a.i==b.i :
        a.c ? b.c && E(*a.c,*b.c)&E(*a.l,*b.l) :
        !b.c
    ;
}

#define P printf


p(o*x){
    x->t?P("%d ",x->i):x->n?P("%s ",x->n):({for(P("(");x->c;x=x->l)p(x->c);P(")");});
}


o*_,G,N; //N = nil



o*C(o*h,o*t){return O(c:h,l:t);}


/*
        2 3 4 5 6 7 8 9 10 11
        s l e c i v t h d  q
    */


o* v(o* x, o* e) { //takes list, int, or name
    o*W(o* l, o* e) { //eval each item in list
        return l->c ? C(v(l->c ,e), W(l->l, e)) : &N;
    }

    o*y,*a,*f;int t;
    return x->c ? //nonempty list = function/macro call
        y = v(x->c,e), //evals to function/macro
        x = x->l,   //list position of first arg (if it exists)
        (t=y->t)?   //builtin no., if any
             t>9 ?
              t&1 ? x->c // 11 = q
                : //10 = d
                (d(x->c,v(x->l->c,e)),x->c)
           : (a = v(x->c,e), //eval'd first arg
             t)>7 ? // t/h
                (t > 8 ? a->c : a->l) ?: a
           : t>6 ? //v
                v(a,e)
           : (x = x->l, //position of 2nd arg in list
             t)>5 ? //i
                v( (a->n||a->l||a->i|a->t>1 ? x : x->l)->c, e)
           : (x = v(x->c,e), //evaluated 2nd arg
             t)>4 ? // c
                C(a,x)
           : O(t:1,i:
                t>3 ? E(*a,*x) :  //e
                t>2 ? a->i<x->i : //l
                      a->i-x->i   //s
              )
        :
        (
            y->l->l->l ? //whether this is macro
                y = y->l :
                (x = W(x,e)),  //eval args
            a = y->c,  //a = arg list
            //a = a->n ? x=C(x, &N), C(a, &N) : a, //transform variadic style to normal
            v(y->l->c,
               a->n ? //variadic
                O(n:a->n,c:x,l:&G)
              : ({
                   for(f=&G; a->c; a=a->l,x=x->l)
                      f=O(n:a->c->n, c: x->c, l:f);
                   f;
                })
            )
        )
    :
    x->n ? // name
        e->n ?
            strcmp(x->n,e->n) ?
                v(x,e->l)
            : e->c
        : e
     : x; //int or nil
}

d(o*n,o*x){
    * v(O(n:""),&G) =
        (o){n:n->n,c:x,l:O()};
}


;
o*R(){
    char*z,*q;int h;
return scanf(" %m[^ \n()]",&q)>0?
    h=strtoul(q,&z,10),
    C(*z ? O(n:q) : O(t:1,i:h), R())
: getchar()&1?&N:(q=R(),C(q,R()));
}
main(i) {

    for(;++i<12;) d(O(n:strndup("slecivthdq"+i-2,1)),O(t:i));

    o *q;
    for(q=R(); q->c; q=q->l) p(v(q->c,&G));

}
feersum
sumber
Apa penggunaan executable yang dikompilasi? Apakah itu REPL? Apakah dibutuhkan nama file sebagai input?
ckjbgames
@ckjbgames Bunyinya program dari stdin.
feersum
Baik. Saya pikir Anda harus mengedit jawaban Anda dan perhatikan itu.
ckjbgames
1

Ceylon, 2422 byte

(Saya pikir ini adalah program golf terpanjang saya.)

import ceylon.language{sh=shared,va=variable,fo=formal,O=Object}import ceylon.language.meta.model{F=Function}interface X{sh fo V v(S t);sh fo G g;}class G(va Map<S,V>m)satisfies X{v(S t)=>m[t]else nV;g=>this;sh S d(S s,V v){assert(!s in m);m=map{s->v,*m};return s;}}V nV=>nothing;class LC(G c,Map<S,V>m)satisfies X{g=>c;v(S t)=>m[t]else g.v(t);}alias C=>V|Co;interface Co{sh fo C st();}interface V{sh fo C l(X c,V[]a);sh default Boolean b=>0<1;sh fo C vO(X c);sh default V vF(X c){va C v=vO(c);while(is Co n=v){v=n.st();}assert(is V r=v);return r;}}class L(sh V*i)satisfies V{vO(X c)=>if(nonempty i)then i[0].vF(c).l(c,i.rest)else this;equals(O o)=>if(is L o)then i==o.i else 1<0;b=>!i.empty;string=>"(``" ".join(i)``)";hash=>i.hash;sh actual C l(X c,V[]p){value[h,ns,x]=i.size<3then[f,i[0],i[1]]else[m,i[1],i[2]];value n=if(is L ns)then[*ns.i.narrow<S>()]else ns;assert(is S|S[]n,is V x);V[]a=h(c,p);LC lC=if(is S n)then LC(c.g,map{n->L(*a)})else LC(c.g,map(zipEntries(n,a)));return object satisfies Co{st()=>x.vO(lC);};}}class S(String n)satisfies V{vO(X c)=>c.v(this);l(X c,V[]a)=>nV;equals(O o)=>if(is S o)then n==o.n else 1<0;hash=>n.hash;string=>n;}class I(sh Integer i)satisfies V{vO(X c)=>this;l(X c,V[]a)=>nV;equals(O o)=>if(is I o)then i==o.i else 1<0;hash=>i;b=>!i.zero;string=>i.string;}V[]f(X c,V[]a)=>[for(v in a)v.vF(c)];V[]m(X c,V[]a)=>a;L c(X c,V h,L t)=>L(h,*t.i);V h(X c,L l)=>l.i[0]else L();V t(X c,L l)=>L(*l.i.rest);I s(X c,I f,I s)=>I(f.i-s.i);I l(X c,I f,I s)=>I(f.i<s.i then 1else 0);I e(X c,V v1,V v2)=>I(v1==v2then 1else 0);C v(X c,V v)=>v.vO(c);V q(X c,V a)=>a;C i(X c,V d,V t,V f)=>d.vF(c).b then t.vO(c)else f.vO(c);S d(X c,S s,V x)=>c.g.d(s,x.vF(c));class B<A>(F<C,A>nat,V[](X,V[])h=f)satisfies V given A satisfies[X,V+]{vO(X c)=>nV;string=>nat.declaration.name;l(X c,V[]a)=>nat.apply(c,*h(c,a));}{<S->V>*}b=>{S("c")->B(`c`),S("h")->B(`h`),S("t")->B(`t`),S("s")->B(`s`),S("l")->B(`l`),S("e")->B(`e`),S("v")->B(`v`),S("q")->B(`q`,m),S("i")->B(`i`,m),S("d")->B(`d`,m)};[V*]p(String inp){value ts=inp.split(" \n()".contains,1<0,1<0);va[[V*]*]s=[];va[V*]l=[];for(t in ts){if(t in" \n"){}else if(t=="("){s=[l,*s];l=[];}else if(t==")"){l=[L(*l.reversed),*(s[0]else[])];s=s.rest;}else if(exists i=parseInteger(t),i>=0){l=[I(i),*l];}else{l=[S(t),*l];}}return l.reversed;}sh void run(){va value u="";while(exists l=process.readLine()){u=u+l+"\n";}V[]e=p(u);X c=G(map(b));for(v in e){print(v.vF(c));}}

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 Vdengan kelas implementasi L(daftar - hanya pembungkus di sekitar sekuens Ceylon V), S(simbol - pembungkus di sekitar string), I(integer - pembungkus di sekitar integer Ceylon) dan B(fungsi bawaan atau makro, pembungkus di sekitar Fungsi Ceylon).

Saya menggunakan notasi kesetaraan Ceylon standar dengan menerapkan equalsmetode (dan juga hashatribut, yang benar-benar hanya diperlukan untuk simbol), dan juga stringatribut standar untuk output.

Kami memiliki atribut Boolean b(yang benar secara default, ditimpa Idan Luntuk mengembalikan false untuk daftar kosong), dan dua metode l(panggilan, yaitu menggunakan objek ini sebagai fungsi) dan vO(mengevaluasi satu langkah). Keduanya mengembalikan nilai atau objek kelanjutan yang memungkinkan kemudian evaluasi untuk satu langkah lagi, dan vF(mengevaluasi sepenuhnya) loop sampai hasilnya bukan kelanjutan lagi.

Antarmuka konteks memungkinkan akses ke variabel. Ada dua implementasi, Guntuk konteks global (yang memungkinkan penambahan variabel menggunakan dbuiltin) dan LCuntuk 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 runmetode, 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).

//  Tiny Lisp, tiny interpreter
//
// An interpreter for a tiny subset of Lisp, from which most of the
// rest of the language can be bootstrapped.
//
// Question:   https://codegolf.stackexchange.com/q/62886/2338
// My answer:  https://codegolf.stackexchange.com/a/63352/2338
//
import ceylon.language {
    sh=shared,
    va=variable,
    fo=formal,
    O=Object
}
import ceylon.language.meta.model {
    F=Function
}

// context

interface X {
    sh fo V v(S t);
    sh fo G g;
}
// global (mutable) context, with the buildins 
class G(va Map<S,V> m) satisfies X {
    // get entry throws error on undefined variables. 
    v(S t) => m[t] else nV;
    g => this;
    sh S d(S s, V v) {
        // error when already defined
        assert (!s in m);
        // building a new map is cheaper (code-golf wise) than having a mutable one.
        m = map { s->v, *m };
        return s;
    }
}

// This is simply a shorter way of writing "this is not an allowed operation".
// It will throw an exception when trying to access it.
// nV stands for "no value".
V nV => nothing;

// local context
class LC(G c, Map<S,V> m) satisfies X {
    g => c;
    v(S t) => m[t] else g.v(t);
    // sh actual String string => "[local: ``m``, global: ``g``]";
}

// continuation or value
alias C => V|Co;

// continuation
interface Co {
    sh fo C st();
}

// value
interface V {
    // use this as a function and call with arguments.
    // will not work for all types of stuff.
    sh fo C l(X c, V[] a);
    // check the truthiness. Defaults to true, as
    // only lists and integers can be falsy.
    sh default Boolean b => 0 < 1;
    // evaluate once (return either a value or a continuation).
    // will not work for all kinds of expression.
    sh fo C vO(X c);
    /// evaluate fully
    sh default V vF(X c) {
        va C v = vO(c);
        while (is Co n = v) {
            v = n.st();
        }
        assert (is V r = v);
        return r;
    }
}
class L(sh V* i) satisfies V {

    vO(X c) => if (nonempty i) then i[0].vF(c).l(c, i.rest) else this;
    equals(O o) => if (is L o) then i == o.i else 1 < 0;
    b => !i.empty;
    string => "(``" ".join(i)``)";
    hash => i.hash;

    sh actual C l(X c, V[] p) {
        value [h, ns, x] =
                i.size < 3
                then [f, i[0], i[1]]
                else [m, i[1], i[2]];
        // parameter names – either a single symbol, or a list of symbols.
        // If it is a list, we filter it to throw out any non-symbols.
        // Should throw an error if there are any, but this is harder.
        value n = if (is L ns) then [*ns.i.narrow<S>()] else ns;
        assert (is S|S[] n, is V x);
        V[] a = h(c, p);

        // local context
        LC lC = if (is S n) then
            LC(c.g, map { n -> L(*a) })
        else
            LC(c.g, map(zipEntries(n, a)));
        // return a continuation instead of actually
        // calling it here, to allow stack unwinding.
        return object satisfies Co {
            st() => x.vO(lC);
        };
    }
}

// symbol
class S(String n) satisfies V {
    // evaluate: resolve
    vO(X c) => c.v(this);
    // call is not allowed
    l(X c, V[] a) => nV;
    // equal if name is equal
    equals(O o) => if (is S o) then n == o.n else 1 < 0;
    hash => n.hash;
    string => n;
}

// integer
class I(sh Integer i) satisfies V {

    vO(X c) => this;
    l(X c, V[] a) => nV;
    equals(O o) => if (is I o) then i == o.i else 1 < 0;
    hash => i;
    b => !i.zero;
    string => i.string;
}

// argument handlers for functions or macros
V[] f(X c, V[] a) => [for (v in a) v.vF(c)];
V[] m(X c, V[] a) => a;

// build-in functions
// construct
L c(X c, V h, L t) => L(h, *t.i);
// head
V h(X c, L l) => l.i[0] else L();
// tail
V t(X c, L l) => L(*l.i.rest);
// subtract
I s(X c, I f, I s) => I(f.i - s.i);
// lessThan
I l(X c, I f, I s) => I(f.i < s.i then 1 else 0);
// equal
I e(X c, V v1, V v2) => I(v1 == v2 then 1 else 0);
// eval (returns potentially a continuation)
C v(X c, V v) => v.vO(c);

// build-in macros
// quote
V q(X c, V a) => a;
// if (also returns potentially a continuation)
C i(X c, V d, V t, V f) => d.vF(c).b then t.vO(c) else f.vO(c);
// define symbol in global context
S d(X c, S s, V x) => c.g.d(s, x.vF(c));

// buildin function or macro, made from a native function and an argument handler
class B<A>(F<C,A> nat, V[](X, V[]) h = f)
        satisfies V
        given A satisfies [X, V+] {
    vO(X c) => nV;
    string => nat.declaration.name;
    // this "apply" is a hack which breaks type safety ...
    // but it will be checked at runtime.
    l(X c, V[] a) => nat.apply(c, *h(c, a));
}

// define buildins
{<S->V>*} b => {
    S("c") -> B(`c`),
    S("h") -> B(`h`),
    S("t") -> B(`t`),
    S("s") -> B(`s`),
    S("l") -> B(`l`),
    S("e") -> B(`e`),
    S("v") -> B(`v`),
    S("q") -> B(`q`, m),
    S("i") -> B(`i`, m),
    S("d") -> B(`d`, m)
};

// parses a string into a list of expressions.
[V*] p(String inp) {
    // split string into tokens (retain separators, don't group them –
    // whitespace and empty strings will be sorted out later in the loop)
    value ts = inp.split(" \n()".contains, 1 < 0, 1 < 0);
    // stack of not yet finished nested lists, outer most at bottom
    va [[V*]*] s = [];
    // current list, in reverse order (because appending at the start is shorter)
    va [V*] l = [];
    for (t in ts) {
        if (t in " \n") {
            // do nothing for empty tokens
        } else if (t == "(") {
            // push the current list onto the stack, open a new list.
            s = [l, *s];
            l = [];
        } else if (t == ")") {
            // build a lisp list from the current list,
            // pop the latest list from the stack, append the created lisp list. 
            l = [L(*l.reversed), *(s[0] else [])];
            s = s.rest;
        } else if (exists i = parseInteger(t), i >= 0) {
            // append an integer to the current list.
            l = [I(i), *l];
        } else {
            // append a symbol to the current list.
            l = [S(t), *l];
        }
    }
    return l.reversed;
}

// Runs the interpreter.
// This handles input and output, calls the parser and evaluates the expressions.
sh void run() {
    va value u = "";
    while (exists l = process.readLine()) {
        u = u + l + "\n";
    }
    V[] e = p(u);
    // create global context
    X c = G(map(b));
    // iterate over the expressions, ...
    for (v in e) {
        // print("  '``v``' → ...");
        // ... evaluate each (fully) and print the result.
        print(v.vF(c));
    }
}
Paŭlo Ebermann
sumber