Viết một thông dịch viên Clem


11

Clem là một ngôn ngữ lập trình dựa trên ngăn xếp tối thiểu có các chức năng hạng nhất. Mục tiêu của bạn là viết một trình thông dịch cho ngôn ngữ Clem. Nó nên thực hiện đúng tất cả các ví dụ có trong triển khai tham chiếu, có sẵn ở đây .

Ngôn ngữ Clem

Clem là một ngôn ngữ lập trình dựa trên ngăn xếp với các chức năng hạng nhất. Cách tốt nhất để học Clem là chạy trình clemthông dịch không có đối số. Nó sẽ bắt đầu trong chế độ tương tác, cho phép bạn chơi với các lệnh có sẵn. Để chạy các chương trình ví dụ, nhập clem example.clmví dụ là tên của chương trình. Hướng dẫn ngắn gọn này là đủ để bạn bắt đầu.

Có hai lớp chức năng chính. Hàm nguyên tử và hàm ghép. Hàm hợp chất là danh sách gồm các hàm ghép và hàm nguyên tử khác. Lưu ý rằng một hàm ghép không thể chứa chính nó.

Chức năng nguyên tử

Loại đầu tiên của hàm nguyên tử là hằng số . Một hằng số chỉ đơn giản là một giá trị nguyên. Ví dụ: -10. Khi trình thông dịch gặp một hằng số , nó sẽ đẩy nó vào ngăn xếp. Chạy clemngay đi. Gõ -10tại dấu nhắc. Bạn nên thấy

> -10
001: (-10)
>

Giá trị 001mô tả vị trí của hàm trong ngăn xếp và (-10)hằng số bạn vừa nhập. Bây giờ nhập +11tại dấu nhắc. Bạn nên thấy

> +11
002: (-10)
001: (11)
>

Lưu ý rằng (-10)đã di chuyển đến vị trí thứ hai trong ngăn xếp và (11)bây giờ chiếm vị trí đầu tiên. Đây là bản chất của một chồng! Bạn sẽ nhận thấy đó -cũng là lệnh giảm dần. Bất cứ khi nào -hoặc +trước một số, họ biểu thị dấu hiệu của số đó và không phải là lệnh tương ứng. Tất cả các chức năng nguyên tử khác là các lệnh . Có tổng cộng 14 cái:

@  Rotate the top three functions on the stack
#  Pop the function on top of the stack and push it twice
$  Swap the top two functions on top of the stack
%  Pop the function on top of the stack and throw it away
/  Pop a compound function. Split off the first function, push what's left, 
   then push the first function.
.  Pop two functions, concatenate them and push the result
+  Pop a function. If its a constant then increment it. Push it
-  Pop a function. If its a constant then decrement it. Push it
<  Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
>  Pop a function and print its ASCII character if its a constant
c  Pop a function and print its value if its a constant
w  Pop a function from the stack. Peek at the top of the stack. While it is
   a non-zero constant, execute the function.

Nhập lệnh tại dấu nhắc sẽ thực thi lệnh. Gõ #tại dấu nhắc (lệnh trùng lặp). Bạn nên thấy

> #
003: (-10)
002: (11)
001: (11)
> 

Lưu ý rằng (11) đã được nhân đôi. Bây giờ gõ %tại dấu nhắc (lệnh thả). Bạn nên thấy

> %
002: (-10)
001: (11)
> 

Để đẩy một lệnh vào ngăn xếp, chỉ cần đặt nó trong ngoặc đơn. Gõ (-)tại dấu nhắc. Điều này sẽ đẩy toán tử giảm dần đến ngăn xếp. Bạn nên thấy

> (-)
003: (-10)
002: (11)
001: (-)
> 

Hàm hợp chất

Bạn cũng có thể đặt nhiều hàm nguyên tử trong ngoặc đơn để tạo thành hàm ghép. Khi bạn nhập một hàm ghép tại dấu nhắc, nó sẽ được đẩy đến ngăn xếp. Gõ ($+$)tại dấu nhắc. Bạn nên thấy

> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>

Về mặt kỹ thuật, mọi thứ trên stack là một hàm ghép. Tuy nhiên, một số hàm tổng hợp trên ngăn xếp bao gồm một hàm nguyên tử duy nhất (trong trường hợp này, chúng tôi sẽ coi chúng là các hàm nguyên tử vì mục đích thuận tiện). Khi thao tác các hàm ghép trên ngăn xếp, .lệnh (nối) thường hữu ích. Gõ .ngay. Bạn nên thấy

> . 
003: (-10)
002: (11)
001: (- $ + $)
> 

Lưu ý rằng các hàm thứ nhất và thứ hai trên ngăn xếp được nối với nhau và hàm thứ hai trên ngăn xếp đứng đầu trong danh sách kết quả. Để thực thi một chức năng trên ngăn xếp (cho dù đó là nguyên tử hay hợp chất), chúng ta phải ban hành wlệnh (while). Các wlệnh sẽ bật chức năng đầu tiên trên stack và thực hiện nó lặp đi lặp lại quá lâu như chức năng thứ hai trên stack là một hằng số khác không. Cố gắng dự đoán những gì sẽ xảy ra nếu chúng ta gõ w. Bây giờ, gõ w. Bạn nên thấy

> w
002: (1)
001: (0)
> 

Đó có phải là những gì bạn mong đợi? Hai số ngồi trên cùng của ngăn xếp đã được thêm vào và tổng của chúng vẫn còn. Hãy thử lại. Đầu tiên chúng ta sẽ bỏ số 0 và đẩy số 10 bằng cách gõ %10. Bạn nên thấy

> %10
002: (1)
001: (10)
> 

Bây giờ chúng ta sẽ nhập toàn bộ chức năng trong một lần chụp, nhưng chúng ta sẽ thêm một phần bổ sung %vào cuối để loại bỏ số không. Gõ (-$+$)w%tại dấu nhắc. Bạn nên thấy

> (-$+$)w%
001: (11)
> 

(Lưu ý thuật toán này chỉ hoạt động nếu hằng số đầu tiên trên ngăn xếp là dương).

Dây

Chuỗi cũng có mặt. Chúng chủ yếu là đường cú pháp, nhưng có thể khá hữu ích. Khi trình thông dịch gặp một chuỗi, nó sẽ đẩy từng ký tự từ cuối đến trước trên ngăn xếp. Nhập %để thả 11 từ ví dụ trước. Bây giờ, gõ 0 10 "Hi!"vào dấu nhắc. Các 0sẽ chèn một terminator NULL và 10sẽ chèn một kí tự xuống dòng. Bạn nên thấy

> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
> 

Nhập (>)wđể in các ký tự từ ngăn xếp cho đến khi chúng ta gặp bộ kết thúc NULL. Bạn nên thấy

> (>)w
Hi!
001: (0)
> 

Kết luận

Hy vọng rằng điều này là đủ để bạn bắt đầu với thông dịch viên. Thiết kế ngôn ngữ nên tương đối đơn giản. Hãy cho tôi biết nếu có bất cứ điều gì không rõ ràng khủng khiếp :) Một vài điều còn bị cố ý mơ hồ: các giá trị phải được ký và ít nhất 16 bit, ngăn xếp phải đủ lớn để chạy tất cả các chương trình tham chiếu, v.v. Nhiều chi tiết chưa được khắc ra khỏi đây bởi vì một đặc điểm kỹ thuật ngôn ngữ đầy đủ sẽ rất lớn để đăng (và tôi chưa viết một thứ nào: P). Khi nghi ngờ, bắt chước thực hiện tham chiếu.

Trang esolangs.org cho Clem

Việc thực hiện tham chiếu trong C


Bạn nói bạn chưa viết đặc tả ngôn ngữ. Tôi lấy nó sau đó bạn là người khởi tạo ngôn ngữ?
COTO

@COTO Đúng vậy. Tôi đã tạo ra ngôn ngữ.
Orby

5
Câu hỏi rất quan trọng: bạn phát âm từ "klem" hay "see-lem"?
Martin Ender

4
@ MartinBüttner: "klem" :)
Orby

2
Bạn có thể muốn chỉ định hướng trong đó lệnh @ xoay 3 hàm trên cùng. (001 -> 002 -> 003 -> 001 hoặc 003 -> 002 -> 001 -> 003)
kwokkie

Câu trả lời:


1

Haskell, 931 921 875

Điều này chưa hoàn toàn được đánh gôn nhưng có lẽ sẽ không bao giờ. Tuy nhiên, nó đã ngắn hơn tất cả các giải pháp khác. Tôi sẽ chơi golf này sớm hơn. Tôi không cảm thấy thích chơi golf hơn thế này.

có thể có một vài lỗi tinh vi vì tôi đã không chơi với triển khai tham chiếu C.

giải pháp này sử dụng loại StateT [String] IO ()để lưu trữ chương trình clem "runnable". hầu hết chương trình là một trình phân tích cú pháp phân tích cú pháp "chương trình có thể chạy".

để chạy sử dụng này r "<insert clem program here>".

import Text.Parsec
import Control.Monad.State
import Control.Monad.Trans.Class
import Data.Char
'#'%(x:y)=x:x:y
'%'%(x:y)=y
'@'%(x:y:z:w)=y:z:x:w
'$'%(x:y:z)=y:x:z
'/'%((a:b):s)=[a]:b:s
'+'%(a:b)=i a(show.succ)a:b
'.'%(a:b:c)=(a++b):c
_%x=x
b=concat&between(s"(")(s")")(many$many1(noneOf"()")<|>('(':)&((++")")&b))
e=choice[s"w">>c(do p<-t;let d=h>>= \x->if x=="0"then a else u p>>d in d),m&k,s"-">>(m&(' ':)&k<|>c(o(\(a:b)->i a(show.pred)a:b))),s"c">>c(do
 d<-t
 i d(j.putStr.show)a),o&(++)&map(show.ord)&between(s"\"")(s"\"")(many$noneOf"\""),(do
 s"<"
 c$j getChar>>=m.show.ord),(do
 s">"
 c$do
 g<-t
 i g(j.putChar.chr)a),m&b,o&(%)&anyChar]
k=many1 digit
i s f g|(reads s::[(Int,String)])>[]=f$(read s::Int)|0<1=g
t=h>>=(o tail>>).c
c n=return n
a=c()
h=head&get
(&)f=fmap f
m=o.(:)
o=modify
u=(\(Right r)->r).parse(sequence_&many e)""
r=(`runStateT`[]).u
s=string
j=lift

5

Con trăn, 1684 1281 ký tự

Có tất cả những thứ golf cơ bản được thực hiện. Nó chạy tất cả các chương trình ví dụ và khớp với ký tự đầu ra cho ký tự.

import sys,os,copy as C
L=len
S=[]
n=[S]
Q=lambda:S and S.pop()or 0
def P(o):
 if o:n[0].append(o)
def X():x=Q();P(x);P(C.deepcopy(x))
def W():S[-2::]=S[-1:-3:-1]
def R():a,b,c=Q(),Q(),Q();P(a);P(c);P(b)
def A(d):
 a=Q()
 if a and a[0]:a=[1,a[1]+d,lambda:P(a)]
 P(a)
def V():
 a=Q();P(a)
 if a and a[0]-1and L(a[2])>1:r=a[2].pop(0);P(r)
def T():
 b,a=Q(),Q()
 if a!=b:P([0,0,(a[2],[a])[a[0]]+(b[2],[b])[b[0]]])
 else:P(a);P(b)
def r():a=os.read(0,1);F(ord(a)if a else-1)
def q(f):
 a=Q()
 if a and a[0]:os.write(1,(chr(a[1]%256),str(a[1]))[f])
def e(f,x=0):f[2]()if f[0]+f[1]else([e(z)for z in f[2]]if x else P(f))
def w():
 a=Q()
 while a and S and S[-1][0]and S[-1][1]:e(a,1)
def Y():n[:0]=[[]]
def Z():
 x=n.pop(0)
 if x:n[0]+=([[0,0,x]],x)[L(x)+L(n)==2]
D={'%':Q,'#':X,'$':W,'@':R,'+':lambda:A(1),'-':lambda:A(-1),'/':V,'.':T,'<':r,'>':lambda:q(0),'c':lambda:q(1),'w':w,'(':Y,')':Z}
def g(c):D[c]()if L(n)<2or c in'()'else P([0,1,D[c]])
N=['']
def F(x):a=[1,x,lambda:P(a)];a[2]()
def E():
 if'-'==N[0]:g('-')
 elif N[0]:F(int(N[0]))
 N[0]=''
s=j=""
for c in open(sys.argv[1]).read()+' ':
 if j:j=c!="\n"
 elif'"'==c:E();s and map(F,map(ord,s[:0:-1]));s=(c,'')[L(s)>0]
 elif s:s+=c
 elif';'==c:E();j=1
 else:
    if'-'==c:E()
    if c in'-0123456789':N[0]+=c
    else:E();c in D and g(c)

Kiểm tra :

Tập hợp clemint.py , clemtest_data.py , clemtest.py và một clemtệp nhị phân được biên dịch vào một thư mục và chạy clemtest.py.

Mở rộng :

Phiên bản vô văn hóa nhất là phiên bản này . Thực hiện theo cùng với đó.

Slà ngăn xếp chính. Mỗi mục của ngăn xếp là 3 danh sách, một trong những:

Constant: [1, value, f]
Atomic: [0, 1, f]
Compound: [0, 0, fs]

Đối với các hằng số, flà một hàm đẩy hằng số lên ngăn xếp. Đối với atmoics, flà một chức năng mà thực hiện một trong những hoạt động (ví dụ -, +). Đối với các hợp chất, fslà một danh sách các mặt hàng.

xecthực hiện một mục. Nếu đó là một hằng số hoặc một nguyên tử, nó chỉ thực hiện chức năng. Nếu đó là một hợp chất, nếu chưa có đệ quy, nó sẽ thực thi từng hàm. Vì vậy, thực hiện (10 20 - 30)sẽ thực hiện từng chức năng 10, 20, -, và 30, để lại 10 19 30trên stack. Nếu đã có đệ quy, thì nó chỉ cần đẩy hàm ghép lên ngăn xếp. Ví dụ, khi thực hiện (10 20 (3 4) 30), kết quả nên 10 20 (3 4) 30, không 10 20 3 4 30.

Làm tổ là một chút khó khăn. Bạn làm gì trong khi đọc đi (1 (2 (3 4)))? Giải pháp là có một chồng ngăn xếp. Ở mỗi cấp độ lồng nhau, một ngăn xếp mới được đẩy vào chồng các ngăn xếp và tất cả các hoạt động đẩy đều đi vào ngăn xếp này. Hơn nữa, nếu đã có lồng nhau, thì các hàm nguyên tử được đẩy thay vì thực thi. Vì vậy, nếu bạn thấy 10 20 (- 30) 40, 10được đẩy, sau đó 20, một ngăn xếp mới được tạo ra -30được đẩy lên ngăn xếp mới và )bật ra khỏi ngăn xếp mới, biến nó thành một vật phẩm và đẩy nó xuống một cấp độ. endnest()tay cầm ). Đó là một chút khó khăn vì có một trường hợp đặc biệt khi chỉ có một mục được đẩy và chúng tôi đang đẩy trở lại vào ngăn xếp chính. Đó là, (10)nên đẩy hằng số10, không phải là một hỗn hợp với một hằng số, bởi vì sau đó -+không hoạt động. Tôi không chắc liệu đây có phải là nguyên tắc hay không nhưng đó là cách nó hoạt động ...

Trình thông dịch của tôi là một bộ xử lý theo từng ký tự - nó không tạo ra các mã thông báo - vì vậy các số, chuỗi và nhận xét có phần khó chịu khi xử lý. Có một ngăn xếp riêng N, đối với số hiện đang được xử lý và bất cứ khi nào một ký tự không phải là số được xử lý, tôi phải gọi endnum()để xem liệu trước tiên tôi có nên hoàn thành số đó hay không và đặt nó vào ngăn xếp. Cho dù chúng tôi trong một chuỗi hoặc một nhận xét được theo dõi bởi các biến boolean; khi một chuỗi được đóng, nó sẽ đẩy tất cả các bộ phận trên ngăn xếp. Số âm yêu cầu một số xử lý đặc biệt là tốt.

Đó là về nó cho tổng quan. Phần còn lại được thực hiện tất cả các built-in, và đảm bảo để làm bản sao sâu trong +, -#.


Thanh danh! Bạn có vui không? :)
Orby

@Orby: Tôi chắc chắn đã làm! Đó là một ngôn ngữ thú vị, chắc chắn là một ngôn ngữ lạ. Tôi hy vọng tôi có thể có được một thông dịch viên <1k. Không chắc chắn những gì mong đợi từ các đệ trình khác.
Claudiu

4

C 837

Cảm ơn @ceilingcat vì đã tìm thấy phiên bản tốt hơn (và ngắn hơn)

Điều này coi mọi thứ là các chuỗi đơn giản - tất cả các mục ngăn xếp là các chuỗi, thậm chí các hằng là các chuỗi.

#define Q strcpy
#define F(x)bcopy(b,f,p-b);f[p-b-x]=!Q(r,p);
#define C(x,y)Q(S[s-x],S[s-y]);
#define N[9999]
#define A Q(S[s++]
#define D sprintf(S[s++],"%d"
#define G(x)}if(*f==x){
#define H(x)G(x)s--;
#define R return
#define Z(x)T(t,u,v)-1||putchar(x);H(
char S N N;s;c;T(b,f,r)char*b,*f,*r;{char*p;strtol(b+=strspn(b," "),&p,0);if(p>b){F(0)R 1;}if(c=*b==40){for(p=++b;c;)c+=(*p==40)-(*p++==41);F(1)R-1;}p++;F(0)*r*=!!*b;R 0;}*P(char*p){if(*p==34)R++p;char*r=P(p+1);D,*p);R r;}E(char*x){char*p,c N,f N,r N,t N,u N,v N;for(Q(c,x);*c;Q(c,p)){Q(t,S[s-1]);if(T(c,f,p=r))A,f);else{{G(64)C(0,1)C(1,2)C(2,3)C(3,0)G(35)A,t);G(36)C(0,2)C(2,1)C(1,0)H(37)H(47)T(t,u,v);*v&&A,v);A,u);H(46)strcat(strcat(S[s-1]," "),t);H(43)D,atoi(t)+1);H(45)D,atoi(t)-1);G(60)D,getchar());H(62)Z(atoi(u))99)Z(*u)119)for(Q(u,t);atoi(S[s-1]);)E(u);G(34)p=P(p);}}}}

Hãy thử trực tuyến!

Một phiên bản ít chơi hơn bản gốc của tôi (không giống như phiên bản được chơi golf, bản này sẽ in ngăn xếp khi nó kết thúc nếu nó không trống và lấy tham số -e để bạn có thể chỉ định tập lệnh trên dòng lệnh thay vì đọc từ tệp):

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define FIRST_REST(x) memcpy(first, b, p - b); first[p - b - x] = '\0'; strcpy(rest, p);
#define COPY(dest,src) strcpy(stack[size + dest], stack[size + src]);
char stack[9999][9999]; int size = 0;
int token(char *b, char *first, char *rest)
{
    while (*b == 32) b++;
    char *p; int x = strtol(b, &p, 0);
    if (p > b) { FIRST_REST(0) return 1; }
    if (*b == '(') { int c = 1; for (p = ++b; c; ++p) c += (*p == '(') - (*p == ')'); FIRST_REST(1) return -1; }
    p++; FIRST_REST(0) if (!*b) *rest = '\0'; return 0;
}
char *push(char *pointer)
{
    if (*pointer == '\"') return pointer+1;
    char *result = push(pointer+1);
    sprintf(stack[size++], "%d", *pointer);
    return result;
}
void eval(char *x)
{
    char program[9999], first[9999], rest[9999], tos[9999], tmp1[9999], tmp2[9999];
    char *pointer;
    for (strcpy(program, x); *program; strcpy(program, pointer))
    {
        *stack[size] = '\0';
        strcpy(tos, stack[size-1]);
        if (token(program, first, rest))
        {
            pointer = rest;
            strcpy(stack[size++], first);
        }
        else
        {
            pointer = rest;
            if (*first == '@'){
                COPY(0, -1) COPY(-1, -2) COPY(-2, -3) COPY(-3, 0) }
            if (*first == '#')
                strcpy(stack[size++], tos);
            if (*first == '$'){
                COPY(0, -2) COPY(-2, -1) COPY(-1, 0) }
            if (*first == '%')
                size--;
            if (*first == '/'){
                size--; token(tos, tmp1, tmp2); if (*tmp2) strcpy(stack[size++], tmp2); strcpy(stack[size++], tmp1); }
            if (*first == '.'){
                size--; strcat(stack[size - 1], " "); strcat(stack[size - 1], tos); }
            if (*first == '+'){
                size--; sprintf(stack[size++], "%d", atoi(tos) + 1); }
            if (*first == '-'){
                size--; sprintf(stack[size++], "%d", atoi(tos) - 1); }
            if (*first == '<')
                sprintf(stack[size++], "%d", getchar());
            if (*first == '>'){
                size--; if (token(tos, tmp1, tmp2) == 1) putchar(atoi(tmp1)); }
            if (*first == 'c'){
                size--; if (token(tos, tmp1, tmp2) == 1) printf("%s", tmp1); }
            if (*first == 'w'){
                size--; strcpy(tmp1, tos); while (atoi(stack[size - 1])) eval(tmp1); }
            if (*first == '\"')
                pointer=push(pointer);
        }
    }
}
int main(int argc, char **argv)
{
    char program[9999] = "";
    int i = 0, comment = 0, quote = 0, space = 0;
    if (!strcmp(argv[1], "-e"))
        strcpy(program, argv[2]);
    else
    {
        FILE* f = fopen(argv[1], "r");
        for (;;) {
            char ch = fgetc(f);
            if (ch < 0) break;
            if (!quote) {
                if (ch == '\n') comment = 0;
                if (ch == ';') comment = 1;
                if (comment) continue;
                if (ch <= ' ') { ch = ' '; if (space++) continue; }
                else space = 0;
            }
            if (ch == '\"') quote = 1 - quote;
            program[i++] = ch;
        }
        fclose(f);
    }
    eval(program);
    for (int i = 0; i < size; i++) printf("%03d: (%s)\r\n",size-i,stack[i]);
    return 0;
}

Đẹp! Thật ấn tượng khi bạn đánh bại giải pháp Python trong C. Tôi phải tải lên phiên bản ngắn hơn của mình, tôi đã quản lý để tắt 60 byte hoặc hơn .. Tôi vẫn tự hỏi liệu có một cách tiếp cận khác sẽ mang lại ít hơn 1000 ký tự
Claudiu

@Claudiu Tôi cũng nghĩ vậy - nhưng tôi không thể tìm ra cách.
Jerry Jeremiah
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.