Lisp nhỏ, thông dịch viên nhỏ


33

Các lập trình viên Lisp tự hào rằng Lisp là một ngôn ngữ mạnh mẽ có thể được xây dựng từ một tập hợp rất nhỏ các hoạt động nguyên thủy . Chúng ta hãy thực hiện ý tưởng đó bằng cách đánh golf một thông dịch viên cho một phương ngữ được gọi tinylisp.

Đặc tả ngôn ngữ

Trong thông số kỹ thuật này, bất kỳ điều kiện nào có kết quả được mô tả là "không xác định" có thể làm bất cứ điều gì trong trình thông dịch của bạn: sự cố, thất bại trong âm thầm, tạo ra gobbldegook ngẫu nhiên hoặc hoạt động như mong đợi. Một triển khai tham chiếu trong Python 3 có sẵn ở đây .

Cú pháp

Mã thông báo trong tinylisp là (, )hoặc bất kỳ chuỗi nào của một hoặc nhiều ký tự ASCII có thể in được trừ dấu ngoặc đơn hoặc dấu cách. (Tức là regex sau : [()]|[^() ]+.) Bất kỳ mã thông báo nào bao gồm hoàn toàn các chữ số là một số nguyên. (Số không hàng đầu là ổn.) Bất kỳ dấu hiệu có chứa chữ số không là một biểu tượng, thậm chí ví dụ số-looking thích 123abc, 3.14-10. Tất cả các khoảng trắng (bao gồm, ở mức tối thiểu, các ký tự ASCII 32 và 10) bị bỏ qua, ngoại trừ trong trường hợp nó tách các mã thông báo.

Một chương trình tinylisp bao gồm một loạt các biểu thức. Mỗi biểu thức là một số nguyên, ký hiệu hoặc biểu thức s (danh sách). Danh sách bao gồm 0 hoặc nhiều biểu thức được gói trong ngoặc đơn. Không có dải phân cách được sử dụng giữa các mục. Dưới đây là ví dụ về biểu thức:

4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))

Các biểu thức không được định dạng tốt (đặc biệt là các dấu ngoặc đơn chưa từng có) đưa ra hành vi không xác định. (Việc triển khai tham chiếu tự động đóng các parens mở và dừng phân tích cú pháp trên các parens đóng chưa từng có.)

Loai du lieu

Các kiểu dữ liệu của tinylisp là số nguyên, ký hiệu và danh sách. Các hàm và macro tích hợp cũng có thể được coi là một loại, mặc dù định dạng đầu ra của chúng không được xác định. Một danh sách có thể chứa bất kỳ số lượng giá trị thuộc bất kỳ loại nào và có thể được lồng sâu tùy ý. Số nguyên phải được hỗ trợ ít nhất từ ​​-2 ^ 31 đến 2 ^ 31-1.

Danh sách trống - ()cũng được gọi là nil - và số nguyên0 là các giá trị duy nhất được coi là sai về mặt logic; tất cả các số nguyên khác, danh sách không trống, nội dung và tất cả các biểu tượng đều đúng.

Đánh giá

Biểu thức trong một chương trình được đánh giá theo thứ tự và kết quả của mỗi lần gửi đến thiết bị xuất chuẩn (nhiều hơn về định dạng đầu ra sau này).

  • Một số nguyên đánh giá chính nó.
  • Danh sách trống ()đánh giá chính nó.
  • Một danh sách của một hoặc nhiều mục đánh giá mục đầu tiên của nó và coi nó là một hàm hoặc macro, gọi nó với các mục còn lại làm đối số. Nếu mục không phải là hàm / macro, hành vi không được xác định.
  • Một biểu tượng đánh giá như một tên, đưa ra giá trị ràng buộc với tên đó trong hàm hiện tại. Nếu tên không được xác định trong hàm hiện tại, nó sẽ ước lượng giá trị ràng buộc với nó ở phạm vi toàn cục. Nếu tên không được xác định ở phạm vi hiện tại hoặc toàn cầu, kết quả không được xác định (triển khai tham chiếu đưa ra thông báo lỗi và trả về nil).

Các hàm và macro tích hợp

Có bảy chức năng tích hợp trong tinylisp. Một hàm đánh giá từng đối số của nó trước khi áp dụng một số thao tác cho chúng và trả về kết quả.

  • c- khuyết điểm [danh sách đường dẫn]. Đưa ra hai đối số, một giá trị và một danh sách và trả về một danh sách mới thu được bằng cách thêm giá trị ở phía trước danh sách.
  • h- đầu ( xe hơi , theo thuật ngữ Lisp). Lấy một danh sách và trả về mục đầu tiên trong đó, hoặc không nếu không.
  • t- đuôi ( cdr , theo thuật ngữ Lisp). Lấy một danh sách và trả về một danh sách mới có chứa tất cả trừ mục đầu tiên hoặc không nếu không được cung cấp.
  • s- trừ đi. Lấy hai số nguyên và trả về số trừ thứ nhất.
  • l- ít hơn. Có hai số nguyên; trả về 1 nếu cái đầu tiên nhỏ hơn cái thứ hai, 0 nếu không.
  • e- công bằng. Lấy hai giá trị cùng loại (cả hai số nguyên, cả hai danh sách hoặc cả hai ký hiệu); trả về 1 nếu hai số bằng nhau (hoặc giống hệt nhau trong mọi phần tử), 0 nếu không. Các nội dung kiểm tra tính bằng nhau không được xác định (triển khai tham chiếu hoạt động như mong đợi).
  • v- đánh giá. Lấy một danh sách, số nguyên hoặc ký hiệu, biểu thị một biểu thức và đánh giá nó. Ví dụ: làm (v (q (c a b)))giống như làm (c a b); (v 1)cho 1.

"Giá trị" ở đây bao gồm mọi danh sách, số nguyên, ký hiệu hoặc nội dung, trừ khi có quy định khác. Nếu một hàm được liệt kê là lấy các loại cụ thể, thì việc chuyển các loại khác nhau đó là hành vi không xác định, như đang truyền sai số lượng đối số (việc triển khai tham chiếu thường gặp sự cố).

Có ba macro tích hợp trong tinylisp. Một macro, không giống như một hàm, không đánh giá các đối số của nó trước khi áp dụng các hoạt động cho chúng.

  • q- Trích dẫn. Có một biểu thức và trả về nó không được đánh giá. Ví dụ, việc đánh giá (1 2 3)đưa ra lỗi vì nó cố gọi 1là hàm hoặc macro, nhưng (q (1 2 3))trả về danh sách (1 2 3). Đánh giá acho giá trị ràng buộc với tên a, nhưng (q a)đưa ra tên của chính nó.
  • i- nếu. Có ba biểu thức: một điều kiện, một biểu thức iftrue và một biểu thức iffalse. Đánh giá điều kiện trước. Nếu kết quả là sai ( 0hoặc không), hãy đánh giá và trả về biểu thức iffalse. Mặt khác, đánh giá và trả về biểu thức iftrue. Lưu ý rằng biểu thức không được trả lại không bao giờ được đánh giá.
  • d- def. Có một biểu tượng và một biểu thức. Đánh giá biểu thức và liên kết nó với biểu tượng đã cho được coi là tên ở phạm vi toàn cầu , sau đó trả về biểu tượng. Cố gắng xác định lại tên sẽ thất bại (âm thầm, với một thông báo hoặc do sự cố; triển khai tham chiếu hiển thị thông báo lỗi). Lưu ý: không cần trích dẫn tên trước khi chuyển qua d, mặc dù cần phải trích dẫn biểu thức nếu đó là danh sách hoặc ký hiệu bạn không muốn đánh giá: vd (d x (q (1 2 3))).

Truyền sai số lượng đối số cho một macro là hành vi không xác định (sự cố triển khai tham chiếu). Truyền một cái gì đó không phải là một biểu tượng như là đối số đầu tiên của dhành vi không xác định (thực hiện tham chiếu không gây ra lỗi, nhưng giá trị không thể được tham chiếu sau đó).

Các hàm và macro do người dùng định nghĩa

Bắt đầu từ mười phần dựng sẵn này, ngôn ngữ có thể được mở rộng bằng cách xây dựng các hàm và macro mới. Chúng không có kiểu dữ liệu chuyên dụng; chúng chỉ đơn giản là danh sách với một cấu trúc nhất định:

  • Một chức năng là một danh sách của hai mục. Đầu tiên là danh sách một hoặc nhiều tên tham số hoặc một tên duy nhất sẽ nhận được danh sách bất kỳ đối số nào được truyền cho hàm (do đó cho phép các hàm arity biến). Thứ hai là một biểu thức là cơ thể chức năng.
  • Một macro giống như một hàm, ngoại trừ việc nó chứa nil trước (các) tên tham số, do đó làm cho nó trở thành một danh sách ba mục. (Cố gắng gọi các danh sách ba mục không bắt đầu bằng nil là hành vi không xác định; triển khai tham chiếu bỏ qua đối số đầu tiên và cũng coi chúng là macro.)

Ví dụ: biểu thức sau đây là hàm thêm hai số nguyên:

(q               List must be quoted to prevent evaluation
 (
  (x y)          Parameter names
  (s x (s 0 y))  Expression (in infix, x - (0 - y))
 )   
)

Và một macro lấy bất kỳ số lượng đối số và đánh giá và trả về cái đầu tiên:

(q
 (
  ()
  args
  (v (h args))
 )
)

Các hàm và macro có thể được gọi trực tiếp, ràng buộc với các tên bằng cách sử dụng dvà được truyền cho các hàm hoặc macro khác.

Vì các thân hàm không được thực thi tại thời điểm định nghĩa, nên các hàm đệ quy có thể dễ dàng xác định:

(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
  )
 ))
)

Tuy nhiên, xin lưu ý rằng ở trên không phải là một cách tốt để xác định hàm độ dài vì nó không sử dụng ...

Đệ quy cuộc gọi

Đệ quy cuộc gọi đuôi là một khái niệm quan trọng trong Lisp. Nó thực hiện một số loại đệ quy nhất định như các vòng lặp, do đó giữ cho ngăn xếp cuộc gọi nhỏ. Thông dịch viên tinylisp của bạn phải thực hiện đệ quy cuộc gọi đuôi thích hợp!

  • Nếu biểu thức trả về của hàm hoặc macro do người dùng xác định là cuộc gọi đến hàm hoặc macro do người dùng xác định khác, trình thông dịch của bạn không được sử dụng đệ quy để đánh giá cuộc gọi đó. Thay vào đó, nó phải thay thế hàm và đối số hiện tại bằng hàm mới và đối số và vòng lặp cho đến khi chuỗi cuộc gọi được giải quyết.
  • Nếu biểu thức trả về của hàm hoặc macro do người dùng xác định là lệnh gọi i, không đánh giá ngay lập tức nhánh được chọn. Thay vào đó, hãy kiểm tra xem đó có phải là một cuộc gọi đến một hàm hoặc macro do người dùng định nghĩa khác không. Nếu vậy, trao đổi chức năng và đối số như trên. Điều này áp dụng cho các lần xuất hiện lồng nhau sâu tùy ý i.

Đệ quy đuôi phải hoạt động cả cho đệ quy trực tiếp (một hàm gọi chính nó) và đệ quy gián tiếp (hàm agọi hàm bgọi [etc] gọi hàm a).

Hàm độ dài đệ quy đuôi (có chức năng trợ giúp len*):

(d len*
 (q (
  (list accum)
  (i list
   (len*
    (t list)
    (s 1 (s 0 accum))
   )
   accum
  )
 ))
)
(d len
 (q (
  (list)
  (len* list 0)
 ))
)

Việc triển khai này hoạt động đối với các danh sách lớn tùy ý, chỉ giới hạn bởi kích thước số nguyên tối đa.

Phạm vi

Các tham số hàm là các biến cục bộ (thực tế là các hằng số, vì chúng không thể được sửa đổi). Chúng nằm trong phạm vi trong khi phần thân của lệnh gọi đó đang được thực thi và nằm ngoài phạm vi trong bất kỳ lệnh gọi sâu hơn và sau khi hàm trả về. Họ có thể "phủ bóng" các tên được xác định toàn cầu, do đó làm cho tên toàn cầu tạm thời không khả dụng. Ví dụ: đoạn mã sau trả về 5, không phải 41:

(d x 42)
(d f
 (q (
  (x)
  (s x 1)
 ))
)
(f 6)

Tuy nhiên, đoạn mã sau trả về 41, vì xở cấp độ cuộc gọi 1 không thể truy cập được từ cấp độ cuộc gọi 2:

(d x 42)
(d f
 (q (
  (x)
  (g 15)
 ))
)
(d g
 (q (
  (y)
  (s x 1)
 ))
)
(f 6)

Các tên duy nhất trong phạm vi tại bất kỳ thời điểm nào là 1) tên cục bộ của hàm hiện đang thực thi, nếu có và 2) tên toàn cầu.

Yêu cầu nộp đơn

Đầu vào và đầu ra

Trình thông dịch của bạn có thể đọc chương trình từ stdin hoặc từ một tệp được chỉ định thông qua stdin hoặc đối số dòng lệnh. Sau khi đánh giá từng biểu thức, nó sẽ xuất kết quả của biểu thức đó thành thiết bị xuất chuẩn với một dòng mới.

  • Số nguyên phải là đầu ra trong biểu diễn tự nhiên nhất của ngôn ngữ thực hiện của bạn. Số nguyên âm có thể là đầu ra, với các dấu trừ hàng đầu.
  • Các biểu tượng phải là đầu ra dưới dạng chuỗi, không có dấu ngoặc kép hoặc thoát.
  • Danh sách nên được xuất với tất cả các mục được phân tách bằng dấu cách và được gói trong ngoặc đơn. Một khoảng trắng bên trong dấu ngoặc đơn là tùy chọn: (1 2 3)( 1 2 3 )cả hai định dạng đều được chấp nhận.
  • Xuất ra các hàm và macro tích hợp là hành vi không xác định. (Giải thích tham chiếu hiển thị chúng dưới dạng <built-in function>.)

Khác

Trình thông dịch tham chiếu bao gồm môi trường REPL và khả năng tải các mô-đun tinylisp từ các tệp khác; những thứ này được cung cấp để thuận tiện và không bắt buộc cho thử thách này.

Các trường hợp thử nghiệm

Các trường hợp thử nghiệm được tách thành nhiều nhóm để bạn có thể kiểm tra các trường hợp đơn giản hơn trước khi làm việc với các nhóm phức tạp hơn. Tuy nhiên, chúng cũng sẽ hoạt động tốt nếu bạn kết hợp tất cả chúng trong một tệp lại với nhau. Chỉ cần đừng quên loại bỏ các tiêu đề và đầu ra dự kiến ​​trước khi chạy nó.

Nếu bạn đã thực hiện đệ quy cuộc gọi đuôi đúng cách, trường hợp kiểm tra cuối cùng (đa phần) sẽ trả về mà không gây ra lỗi tràn ngăn xếp. Việc thực hiện tham chiếu tính toán nó trong khoảng sáu giây trên máy tính xách tay của tôi.


"Bất kỳ mã thông báo nào bao gồm hoàn toàn các chữ số là một số nguyên. (Các số 0 đứng đầu đều ổn.) Bất kỳ mã thông báo nào không chứa các chữ số đều là ký hiệu, ngay cả các ví dụ trông giống số như 123abc, 3.14 và -10." dường như mâu thuẫn "Số nguyên phải được hỗ trợ ít nhất từ ​​-2 ^ 31 đến 2 ^ 31-1."
msh210

3
@ msh210 Không thực sự, bởi vì cái trước đang nói về mã thông báo trong khi cái sau đang nói về các giá trị . Mặc dù không có cách nào để nhập trực tiếp -1, tôi vẫn có thể tạo giá trị -1 bằng cách thực hiện (s 0 1).
DLosc

1
@coredump Sau khi đọc bài viết Wikipedia thích hợp , tôi đã kết luận rằng việc triển khai thực sự gần với động hơn, nhưng không có phạm vi lồng nhau. Các biến trong hàm Fkhông có sẵn trong hàm Gnếu Fcác lệnh gọi G(như với phạm vi động), nhưng chúng cũng không có sẵn trong hàm Hnếu Hlà một hàm lồng nhau được xác định bên trong F(như với phạm vi từ vựng) - xem trường hợp kiểm tra 5. Vì vậy, gọi nó là "từ vựng "Có thể gây hiểu nhầm.
DLosc

1
Nói một cách khác: vì thiếu phạm vi lồng nhau, việc triển khai có thể sử dụng chiến lược phạm vi động hoặc từ vựng và đưa ra kết quả tương tự. Các tên duy nhất trong phạm vi tại bất kỳ thời điểm nào là 1) tên cục bộ của hàm hiện đang thực thi, nếu có và 2) tên toàn cầu. Đóng cửa không được hỗ trợ. (Việc triển khai tham chiếu giữ một chồng các ràng buộc tên tương ứng với ngăn xếp cuộc gọi - một cách tiếp cận kiểu động, mà tôi nghĩ sẽ dễ thực hiện nhất.)
DLosc

1
Bắt buộc xkcd .
mınxomaτ

Câu trả lời:


11

Con trăn 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))

Đọc đầu vào từ STDIN và ghi đầu ra vào STDOUT.

Mặc dù không được yêu cầu nghiêm ngặt, trình thông dịch hỗ trợ các hàm và macro rỗng, và tối ưu hóa các lệnh gọi được thực hiện thông qua v.

Giải trình

Phân tích cú pháp

Để phân tích cú pháp đầu vào, trước tiên chúng ta bao quanh mỗi lần xuất hiện ()với khoảng trắng và chia chuỗi kết quả thành các từ; điều này cho chúng tôi danh sách các mã thông báo. Chúng tôi duy trì một ngăn xếp biểu thức E, ban đầu là trống rỗng. Chúng tôi quét các mã thông báo, theo thứ tự:

  • nếu chúng ta gặp a (, chúng ta sẽ đẩy một danh sách trống ở đầu ngăn xếp biểu thức;
  • nếu chúng ta gặp a ), chúng ta sẽ bật giá trị ở đầu ngăn xếp biểu thức và nối nó vào danh sách trước đó nằm dưới ngăn xếp;
  • mặt khác, chúng tôi nối thêm mã thông báo hiện tại, dưới dạng một chuỗi, vào danh sách ở đầu ngăn xếp biểu thức (chúng tôi giữ các số nguyên dưới dạng chuỗi ở giai đoạn này và phân tích chúng trong quá trình đánh giá.)

Nếu, khi xử lý mã thông báo thông thường hoặc sau khi bật một biểu thức từ ngăn xếp do ), ngăn xếp biểu thức trống, chúng tôi ở biểu thức cấp cao nhất và chúng tôi đánh giá giá trị mà chúng tôi đã thêm, sử dụng V()và in kết quả của nó, định dạng thích hợp bằng cách sử dụng F().

Đánh giá

Chúng tôi duy trì phạm vi toàn cầu G, như một danh sách các cặp khóa / giá trị. Ban đầu, nó chỉ chứa các hàm dựng sẵn (nhưng không chứa các macro và không phải là macro vmà chúng ta coi là macro), được triển khai như lambdas.

Đánh giá xảy ra bên trong V(), trong đó có các biểu hiện để đánh giá, evà phạm vi địa phương, Lmà là cũng vậy, một danh sách các cặp khóa / giá trị (khi đánh giá một biểu cấp cao nhất, phạm vi địa phương có sản phẩm nào.) Các ruột của V()live bên trong một vòng lặp vô hạn, đó là cách chúng tôi thực hiện tối ưu hóa cuộc gọi đuôi (TCO), như được giải thích sau.

Chúng tôi xử lý etheo loại của nó:

  • nếu đó là danh sách trống hoặc chuỗi có thể chuyển đổi thành int, chúng tôi sẽ trả lại ngay lập tức (có thể sau khi chuyển đổi thành int); nếu không thì,

  • nếu đó là một chuỗi, chúng tôi tra cứu nó trong một từ điển được xây dựng từ sự kết hợp của phạm vi toàn cầu và địa phương. Nếu chúng tôi tìm thấy một giá trị liên quan, chúng tôi trả lại nó; nếu không ephải là tên của một macro dựng sẵn (ví dụ q, i, dhay v), và chúng tôi gửi lại không thay đổi. Mặt khác, nếu ekhông phải là một chuỗi,

  • elà một danh sách (không trống), tức là một hàm gọi. Chúng tôi đánh giá phần tử đầu tiên của danh sách, nghĩa là biểu thức hàm, bằng cách gọi V()đệ quy (sử dụng phạm vi cục bộ hiện tại); chúng ta gọi là kết quả f. Phần còn lại của danh sách A, là danh sách các đối số. fchỉ có thể là một chuỗi, trong trường hợp đó là macro dựng sẵn (hoặc hàm v), lambda, trong trường hợp đó là hàm dựng sẵn hoặc danh sách, trong trường hợp đó là hàm hoặc macro do người dùng xác định.

    Nếu flà một chuỗi aa, tức là một macro dựng sẵn, chúng tôi xử lý nó tại chỗ. Nếu đó là macro ihoặc v, chúng tôi đánh giá toán hạng đầu tiên của nó và chọn toán hạng thứ hai hoặc thứ ba tương ứng, trong trường hợp ihoặc sử dụng kết quả của toán hạng thứ nhất, trong trường hợp v; thay vì đánh giá biểu thức được chọn một cách đệ quy, điều này sẽ đánh bại TCO, chúng ta chỉ cần thay thế ebiểu thức đã nói và nhảy đến đầu vòng lặp. Nếu flà macro d, chúng ta nối thêm một cặp, có phần tử đầu tiên là toán hạng thứ nhất và phần tử thứ hai là kết quả của việc đánh giá toán hạng thứ hai, cho phạm vi toàn cục Gvà trả về toán hạng thứ nhất. Mặt khác, flà macro q, trong trường hợp này chúng ta chỉ cần trả về toán hạng của nó trực tiếp.

    Tuy nhiên, nếu flà lambda, hoặc một danh sách có phần tử đầu tiên không phải ()thì đó là hàm không có giá trị, không phải là macro, trong trường hợp đó chúng ta đánh giá các đối số của nó, tức là các phần tử của Avà thay thế Abằng kết quả.

    Nếu flà lambda, chúng ta gọi nó, chuyển cho nó các đối số được giải nén Avà trả về kết quả.

    Mặt khác, flà một danh sách, nghĩa là một hàm hoặc macro do người dùng định nghĩa; danh sách tham số của nó là phần tử thứ hai đến cuối cùng và phần thân của nó là phần tử cuối cùng. Giống như trong trường hợp của macro iv, để thực hiện TCO, chúng tôi không đánh giá cơ thể theo cách đệ quy, mà thay vào đó ebằng cơ thể và tiếp tục lặp lại lần tiếp theo. Tuy nhiên, không giống iv, chúng tôi cũng thay thế phạm vi cục bộ L, bằng phạm vi cục bộ mới của hàm. PTrên thực tế, nếu danh sách tham số , trong thực tế, là một danh sách, phạm vi cục bộ mới được xây dựng bằng cách nén danh sách tham số P, với danh sách đối số , A; mặt khác, chúng ta đang xử lý một hàm matrixdic, trong trường hợp đó, phạm vi cục bộ mới chỉ có một phần tử là cặp (P, A).

Thay thế

Nếu bạn muốn chơi với nó, đây là phiên bản REPL của trình thông dịch. Nó hỗ trợ xác định lại các ký hiệu và nhập tệp thông qua các đối số dòng lệnh hoặc (import <filename>)macro. Để thoát trình thông dịch, hãy chấm dứt đầu vào (thông thường, Ctrl + D hoặc Ctrl + Z).

Và đây là một phiên ví dụ, thực hiện sắp xếp hợp nhất:


Bạn có thể nhận được một cái gì đó ngắn hơn bao giờ hết bằng cách sử dụng zlib :) Nén mã của bạn được chuyển đổi theo byte và thay thế nó bằng:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
Labo

Bạn có thể lưu hai byte bằng cách gán A[0]cho một số biến một char ngay sau khi ngoại trừ khối
Hannes Karppila

@HannesKarppila Điều đó đúng, nhưng điều này sẽ phá vỡ các hàm vô hiệu (vì Anó trống trong trường hợp này) và tôi không muốn "hồi quy".
Ell

4

C (GNU), 1095 byte

Phần lớn hành động diễn ra trong vchức năng khổng lồ . Thay vì thực hiện đệ quy đuôi một cách rõ ràng, vđược cấu trúc sao cho nhiều cuộc gọi từ vđến vsẽ được xử lý bằng tối ưu hóa đệ quy đuôi của gcc. Không có bộ sưu tập rác.

Điều này làm cho việc sử dụng nhiều các phần mở rộng GCC, vì vậy nó chỉ có thể được biên dịch bằng gcc (sử dụng lệnh gcc -w -Os tl.c). Nó cũng sử dụng một số scanftiện ích mở rộng không có trên Windows mà tôi thường sử dụng. Triển vọng viết trình phân tích cú pháp với tiêu chuẩn scanflà khủng khiếp đến nỗi tôi đã sử dụng máy ảo Linux để kiểm tra chương trình thay thế. Phân tích cú pháp mà không có scanfcác lớp ký tự có thể đã thêm hơn 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)));}

Không bán

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));

}

Việc sử dụng của thực thi được biên dịch là gì? Có phải là REPL không? Nó có một tên tệp làm đầu vào?
ckjbgames

@ckjbgames Nó đọc một chương trình từ stdin.
frageum

Đuợc. Tôi nghĩ bạn nên chỉnh sửa câu trả lời của bạn và lưu ý rằng.
ckjbgames

1

Ceylon, 2422 byte

(Tôi nghĩ rằng đây là chương trình golf dài nhất của tôi chưa.)

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));}}

Tôi có thể đã đánh gôn thêm một số byte, vì tôi đã sử dụng một số định danh hai chữ cái ở một số nơi, nhưng tôi đã hết những chữ cái đơn có ý nghĩa cho những chữ đó. Mặc dù theo cách này, nó không giống Ceylon lắm ...

Đây là một thực hiện hướng đối tượng .

Chúng ta có một giao diện giá trị Vvới các lớp triển khai L(danh sách - chỉ là một trình bao bọc xung quanh chuỗi Ceylon của V), S(biểu tượng - trình bao quanh chuỗi), I(số nguyên - trình bao quanh số nguyên Ceylon) và B(hàm dựng sẵn hoặc macro, trình bao quanh Chức năng Ceylon).

Tôi sử dụng ký hiệu đẳng thức Ceylon tiêu chuẩn bằng cách thực hiện equalsphương thức (và cả hashthuộc tính, chỉ thực sự cần thiết cho các ký hiệu), và cũng là stringthuộc tính tiêu chuẩn cho đầu ra.

Chúng ta có một thuộc tính Boolean b(đúng theo mặc định, được ghi đè ILtrả về false cho danh sách trống) và hai phương thức l(gọi, tức là sử dụng đối tượng này làm hàm) và vO(đánh giá một bước). Cả hai đều trả về một giá trị hoặc một đối tượng Tiếp tục, sau đó cho phép đánh giá thêm một bước và vF(đánh giá đầy đủ) các vòng lặp cho đến khi kết quả không còn là sự tiếp tục nữa.

Một giao diện ngữ cảnh cho phép truy cập vào các biến. Có hai triển khai, Gcho bối cảnh toàn cầu (cho phép thêm các biến bằng cách sử dụng dnội dung) và LCcho bối cảnh cục bộ, hoạt động khi đánh giá biểu thức của hàm người dùng (nó quay lại bối cảnh toàn cầu).

Đánh giá biểu tượng truy cập vào bối cảnh, danh sách (nếu không trống) đánh giá bằng cách trước tiên đánh giá phần tử đầu tiên của chúng và sau đó gọi phương thức gọi của nó. Cuộc gọi được triển khai chỉ bằng danh sách và nội trang - trước tiên, nó đánh giá đối số (nếu là hàm, không phải là macro) và sau đó thực hiện công cụ thú vị thực sự - đối với nội dung chỉ là mã hóa cứng, đối với danh sách, nó tạo bối cảnh cục bộ mới và trả về tiếp tục với điều đó

Đối với các nội trang, tôi đã sử dụng một thủ thuật tương tự như những gì tôi đã sử dụng trong Trình thông dịch Shift , cho phép tôi xác định chúng với các loại đối số mà chúng cần, nhưng gọi chúng bằng một chuỗi chung bằng phản xạ (các loại sẽ được kiểm tra tại thời điểm cuộc gọi). Điều này tránh rắc rối chuyển đổi / xác nhận loại bên trong các hàm / macro, nhưng cần các hàm cấp cao nhất để tôi có thể có được các Functionđối tượng mô hình meta của chúng .

Hàm p(phân tích cú pháp) phân tách chuỗi tại các khoảng trắng, dòng mới và dấu ngoặc đơn, sau đó lặp qua các mã thông báo và xây dựng danh sách bằng cách sử dụng ngăn xếp và danh sách đang chạy.

Trình thông dịch (trong runphương thức, là điểm vào) sau đó lấy danh sách các biểu thức này (chỉ là giá trị), đánh giá từng biểu thức và in kết quả.


Dưới đây là một phiên bản với ý kiến ​​và chạy qua một định dạng.

Một phiên bản trước đó trước khi tôi bắt đầu chơi golf (và vẫn còn một số hiểu lầm về đánh giá danh sách) được tìm thấy tại kho Github của tôi , tôi sẽ sớm đặt phiên bản này ở đó (vì vậy hãy chắc chắn xem phiên bản đầu tiên nếu bạn muốn bản gốc).

//  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));
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.