编写Clem解释器


11

Clem是一种基于堆栈的最小编程语言,具有一流的功能。您的目标是为Clem语言编写一个解释器。它应正确执行参考实现中包含的所有示例,可从此处获得

  • 像往常一样,存在标准漏洞
  • 按字节数最小的条目获胜。

克莱姆语言

Clem是具有一流功能的基于堆栈的编程语言。学习Clem的最好方法是clem不带参数地运行解释器。它将以交互模式启动,使您可以使用可用的命令进行播放。要运行示例程序,请键入clem example.clmwhere example是程序的名称。这个简短的教程应该足以让您入门。

主要有两类功能。原子功能和复合功能。复合函数是由其他复合函数和原子函数组成的列表。请注意,复合函数不能包含自身。

原子功能

第一类原子函数是常数。甲恒定仅仅是一个整数值。例如-10。当解释器遇到常量时,它将其推入堆栈。clem现在运行。键入-10在提示符下。你应该看到

> -10
001: (-10)
>

该值001描述了函数在堆栈中的位置,并且(-10) 是您刚刚输入的常数。现在+11,在提示符下输入。你应该看到

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

请注意,它(-10)已移动到堆栈中的第二个位置,(11)现在占据了第一个位置。这就是堆栈的本质!您会注意到这-也是减量命令。无论何时-+在数字之前,它们表示该数字的符号,而不是相应的命令。所有其他原子函数都是命令。共有14个:

@  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.

在提示符下键入命令将执行该命令。键入#在提示(重复的命令)。你应该看到

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

请注意(11)已被复制。现在%,在提示符下输入(drop命令)。你应该看到

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

要将命令压入堆栈,只需将其括在括号中即可。键入(-)在提示符下。这会将递减运算符推入堆栈。你应该看到

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

复合功能

您也可以将多个原子函数括在括号中以形成复合函数。当您在提示符下输入复合函数时,它将被推入堆栈。键入($+$)在提示符下。你应该看到

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

从技术上讲,堆栈上的所有内容都是复合函数。但是,堆栈上的某些复合函数由单个原子函数组成(在这种情况下,为方便起见,我们将它们视为原子函数)。在堆栈上操作复合函数时,.命令(并置)通常很有用。键入.现在。你应该看到

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

请注意,堆栈上的第一个功能和第二个功能已连接在一起,并且堆栈上的第二个功能在结果列表中排在第一位。要执行堆栈上的函数(无论是原子函数还是复合函数),我们必须发出w命令(while)。w只要堆栈上的第二个函数为非零常数,该命令就会弹出堆栈上的第一个函数并重复执行。尝试预测如果键入会发生什么w。现在,键入w。你应该看到

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

那是您所期望的吗?坐在堆栈顶部的两个数字相加,并且它们的和仍然保留。让我们再试一次。首先,我们将键入0并将其推为10 %10。你应该看到

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

现在,我们将一次输入整个函数,但最后将添加一个额外的函数%以消除零。键入(-$+$)w%在提示符下。你应该看到

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

(请注意,该算法仅在堆栈上的第一个常数为正数时有效)。

弦乐

字符串也存在。它们主要是语法糖,但可能非常有用。当解释器遇到字符串时,它将每个字符从最后到第一个压入堆栈。键入%以删除上一个示例中的11。现在,0 10 "Hi!"在提示符下键入。在0将插入一个NULL终止和10将插入新行字符。你应该看到

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

键入(>)w以从堆栈中打印字符,直到遇到NULL终止符。你应该看到

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

结论

希望这应该足以让您开始使用口译员。语言设计应相对简单明了。让我知道是否有任何不清楚的地方:)故意遗漏了一些内容:必须对值进行签名并至少包含 16位,堆栈必须足够大以运行所有引用程序,等等。许多细节尚未确定之所以选择此处,是因为发布的完整语言规范过大(我还没有写过:P)。如有疑问,请模仿参考实现。

Clem的esolangs.org页面

C中的参考实现


您说您尚未编写语言规范。我认为您是该语言的鼻祖?
COTO 2014年

@COTO是正确的。我创建了语言。
Orby 2014年

5
非常重要的问题:您将其发音为“ klem”还是“ see-lem”?
Martin Ender 2014年

4
@MartinBüttner:“ klem” :)
Orby

2
您可能要指定@命令旋转3个顶部功能的方向。(001->
002-

Answers:


1

哈斯克尔(931) 921 875

这还没有打完,但是可能永远不会打完。尽管如此,它已经比所有其他解决方案都短。 我会再打高尔夫。我不想再打高尔夫球了。

可能有一些细微的错误,因为我没有使用C参考实现。

该解决方案使用该类型StateT [String] IO ()来存储“可运行”的clem程序。大多数程序是解析器,用于解析“可运行程序”。

为了运行此用途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

Python,1684 1281个字符

完成了所有基本的高尔夫工作。它运行所有示例程序,并匹配每个字符的输出。

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)

测试

clemint.pyclemtest_data.pyclemtest.py和已编译的clem二进制文件收集到目录中并运行clemtest.py

扩展

最缺货的版本是这个。跟着那个。

S是主堆栈。堆栈中的每个项目都是一个3列表,其中之一:

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

对于常量,f是将常量推入堆栈的函数。对于atmoics,f是执行的操作中的一个的功能(例如-+)。对于化合物,fs是项目列表。

xec执行一个项目。如果它是常量或原子,则仅执行该函数。如果是复合的话,如果还没有递归,它将执行每个函数。因此,执行(10 20 - 30)将执行每个功能1020-,和30,留10 19 30在堆栈上。如果有递归,那么它只是将复合函数推入堆栈。例如,执行时(10 20 (3 4) 30),结果应为10 20 (3 4) 30而不是10 20 3 4 30

嵌套有点棘手。阅读时你会做什么(1 (2 (3 4)))?解决方案是拥有一堆堆栈。在每个嵌套级别,将新堆栈压入堆栈堆栈,所有推入操作都将移入该堆栈。此外,如果存在嵌套,则将推送原子函数而不是执行原子函数。因此,如果看到10 20 (- 30) 4010被推入,然后被推入,则将20创建一个新堆栈,-并将30其推到新堆栈上,然后)弹出新堆栈,将其变成一个项目,然后将其推下一层。endnest()处理)。这有点棘手,因为在特殊情况下,只有一项被推入,而我们又被推回主堆栈。也就是说(10)应推恒10,而不是一个常数的复合,因为那样的话-+则不起作用。我不确定这是否有原则,但这是它的工作方式...

我的解释器是一个逐个字符的处理器-它不创建令牌-因此数字,字符串和注释有点烦人。N对于当前正在处理的数字,有一个单独的堆栈,并且每当处理不是数字的字符时,我都必须调用endnum()以查看是否应该首先完成该数字并将其放在堆栈上。布尔变量可以跟踪我们是字符串还是注释。当一个字符串被关闭时,它将所有内脏压入堆栈。负数也需要特殊处理。

概述就是这样。其余的正在执行的所有内置插件,并在作出一定要做到深层副本+-#


荣誉!你玩的愉快吗?:)
Orby

@Orby:我当然做到了!这是一种有趣的语言,绝对是一种奇怪的语言。我希望我能得到一个<1k的口译员。不确定其他提交内容会带来什么。
Claudiu 2014年

4

C 837

感谢@ceilingcat找到了更好(或更短)的版本

这将所有内容都视为简单字符串-所有堆栈项都是字符串,即使常量也是字符串。

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

在线尝试!

我的原始版本的简化版本(与旧版本不同,该版本在不为空的情况下会在堆栈结束时打印堆栈,并带有-e参数,因此您可以在命令行上指定脚本,而不是从文件中读取):

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

真好!令人印象深刻的是,您击败了C语言中的Python解决方案。我必须上传较短的版本,我设法删除了60个字节左右。.我仍然想知道是否有其他方法可以产生少于1000个字符
Claudiu 2014年

@Claudiu我也这么认为-但我不知道怎么做。
杰里·耶利米
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.