Tiny Lisp,微小的口译员


33

Lisp程序员吹嘘Lisp是一种功能强大的语言,可以通过很少的原始操作集来构建。让我们通过打高尔夫一种叫做方言的口译员来付诸实践tinylisp

语言规格

在本规范中,任何结果被描述为“未定义”的条件都可能在您的解释器中做任何事情:崩溃,静默失败,产生随机的gobbldegook或按预期工作。此处提供了Python 3中的参考实现。

句法

tinylisp中的标记是()或一个或多个可打印ASCII字符的任何字符串,括号或空格除外。(即以下正则表达式:[()]|[^() ]+。)任何完全由数字组成的令牌都是整数文字。(前导零都还好。),其中包含非数字字符的任何标记是一个符号,甚至是数字好看的例子一样123abc3.14-10。所有空格(至少包括ASCII字符32和10)都将被忽略,除非它们分隔标记。

tinylisp程序由一系列表达式组成。每个表达式可以是整数,符号或s表达式(列表)。列表由括在括号中的零个或多个表达式组成。项目之间不使用分隔符。以下是表达式示例:

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

格式不正确的表达式(特别是括号不匹配的表达式)会产生不确定的行为。(参考实现自动关闭打开的parens,并停止对不匹配的关闭parens进行解析。)

资料类型

tinylisp的数据类型是整数,符号和列表。尽管内置函数和宏的输出格式未定义,但它们也可以视为一种类型。列表可以包含任意数量的任何类型的值,并且可以任意嵌套。必须至少从-2 ^ 31到2 ^ 31-1支持整数。

空列表(()也称为nil)和整数0是唯一在逻辑上被视为false的值。所有其他整数,非空列表,内建函数和所有符号在逻辑上均为真。

评价

程序中的表达式将按顺序求值,并将每个表达式的结果发送到stdout(稍后会详细介绍输出格式)。

  • 整数文字求值。
  • 空列表()将自动求值。
  • 一个或多个项目的列表将评估其第一项并将其视为函数或宏,并以其余项作为参数来调用它。如果该项目不是函数/宏,则行为未定义。
  • 符号将作为名称求值,并在当前函数中给出绑定到该名称的值。如果该名称未在当前函数中定义,它将在全局范围内求值为其绑定的值。如果未在当前或全局范围内定义名称,则结果是未定义的(参考实现给出错误消息并返回nil)。

内置函数和宏

tinylisp中有七个内置函数。函数先对每个参数求值,然后对它们应用一些运算并返回结果。

  • c-缺点[命令列表]。接受两个参数,一个值和一个列表,并返回通过将值添加到列表的前面而获得的新列表。
  • h-头(用Lisp术语表示为car)。取得一个列表并返回其中的第一项;如果给出nil,则返回nil。
  • t-tailcdr,用Lisp术语)。获取列表并返回一个新列表,其中包含除第一项外的所有列表;如果为nil,则返回nil。
  • s- 减去。取两个整数并返回第一个减第二个。
  • l- 少于。取两个整数;如果第一个小于第二个,则返回1,否则返回0。
  • e-相等 接受两个相同类型的值(两个整数,两个列表或两个符号);如果两者相等(或每个元素相同),则返回1,否则返回0。未定义测试内置函数是否相等(参考实现按预期工作)。
  • v-评估。取得一个表示表达式的列表,整数或符号,然后对其求值。例如做(v (q (c a b)))与做是一样的(c a b); (v 1)1

除非另有说明,否则此处的“值”包括任何列表,整数,符号或内置函数。如果一个函数被列为采用特定类型,则传递不同类型是未定义的行为,传递错误数量的参数也是如此(参考实现通常会崩溃)。

tinylisp中有三个内置宏。与函数不同,宏在对它们应用运算之前不会评估其参数。

  • q-报价。取一个表达式并返回未计算的值。例如,评估(1 2 3)会给出错误,因为它尝试1作为函数或宏进行调用,但(q (1 2 3))返回list (1 2 3)。评估a给出与名称绑定的值a,但(q a)给出名称本身。
  • i-如果。采用三个表达式:条件,iftrue表达式和iffalse表达式。首先评估条件。如果结果为假(0或nil),则求值并返回iffalse表达式。否则,求值并返回iftrue表达式。注意,不会返回不返回的表达式。
  • d-防御 带有符号和表达式。计算表达式并将其绑定到在全局范围内被视为名称的给定符号,然后返回该符号。尝试重新定义名称应该会失败(无提示,显示一条消息或崩溃);参考实现将显示错误消息。注意:在将名称传递给时d,不一定要用引号将其引出,但是如果表达式是您不希望对其求值的列表或符号,则必须用引号引起来:例如,(d x (q (1 2 3)))

将错误数量的参数传递给宏是未定义的行为(引用实现崩溃)。将不是符号的内容作为第一个参数传递d是未定义的行为(引用实现不会产生错误,但是该值不能随后被引用)。

用户定义的函数和宏

从这十个内置函数开始,可以通过构造新的函数和宏来扩展该语言。这些没有专用的数据类型。它们只是具有特定结构的列表:

  • 函数是两个项目的列表。第一个是一个或多个参数名称的列表,或者是一个单个名称,该名称将接收传递给该函数的所有参数的列表(因此允许使用可变参数函数)。第二个是函数主体的表达式。
  • 宏与函数相同,不同之处在于它在参数名称之前包含nil,从而使其成为三项列表。(尝试调用不以nil开头的三项列表是未定义的行为;参考实现会忽略第一个参数,并将它们也视为宏。)

例如,以下表达式是一个将两个整数相加的函数:

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

还有一个宏,它接受任意数量的参数并求值并返回第一个:

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

可以直接调用函数和宏,使用可以将其绑定到名称d,并传递给其他函数或宏。

由于函数体不在定义时执行,因此递归函数很容易定义:

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

但是请注意,以上并不是定义长度函数的好方法,因为它不使用...

尾调用递归

尾调用递归是Lisp中的重要概念。它将某些类型的递归实现为循环,从而使调用堆栈保持较小。您的tinylisp解释器必须实现适当的尾调用递归!

  • 如果用户定义的函数或宏的返回表达式是对另一个用户定义的函数或宏的调用,则您的解释器不得使用递归来评估该调用。取而代之的是,它必须用新的函数和参数替换当前的函数和参数,并循环直到调用链解决。
  • 如果用户定义的函数或宏的返回表达式是对的调用i,则不要立即求值所选的分支。而是检查它是否是对另一个用户定义的函数或宏的调用。如果是这样,换出上面的函数和参数。这适用于任意深度嵌套的i

尾递归必须对直接递归(函数调用自身)和间接递归(函数a调用b调用[etc]并调用function的函数a)都起作用。

尾递归长度函数(带有辅助函数len*):

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

此实现适用于任意大列表,仅受最大整数大小限制。

范围

函数参数是局部变量(实际上是常量,因为它们不能被修改)。它们在执行该函数的调用主体时处于范围内,而在任何更深层次的调用期间以及函数返回之后均不在范围内。他们可以“隐藏”全局定义的名称,从而使全局名称暂时不可用。例如,以下代码返回5,而不是41:

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

但是,以下代码返回41,因为x无法从呼叫级别2访问呼叫级别1:

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

在任何给定的时间范围内,唯一的名称是1)当前正在执行的函数的本地名称(如果有),以及2)全局名称。

提交要求

输入输出

您的解释器可以从stdin或通过stdin或命令行参数指定的文件中读取程序。对每个表达式求值后,应使用尾随换行符将该表达式的结果输出到stdout。

  • 整数应以实现语言最自然的表示形式输出。可以输出负整数,并带有前导负号。
  • 符号应以字符串形式输出,且没有引号或转义符。
  • 列表应以空格分隔并括在括号中的所有项目输出。括号内的空格是可选的:(1 2 3)并且( 1 2 3 )都是可接受的格式。
  • 输出内置函数和宏是未定义的行为。(参考解释将其显示为<built-in function>。)

其他

参考解释器包括REPL环境以及从其他文件加载tinylisp模块的功能。提供这些只是为了方便,并不是本挑战所必需的。

测试用例

测试用例分为几组,因此您可以先测试简单的用例,然后再处理更复杂的用例。但是,如果将它们全部转储到一个文件中,它们也可以正常工作。只是不要忘记在运行之前删除标题和预期的输出。

如果您已正确实现了尾调用递归,则最终(多部分)测试用例将返回而不会引起堆栈溢出。参考实现在我的笔记本电脑上大约在六秒钟内对其进行了计算。


“任何完全由数字组成的标记都是整数文字(零前导是可以的。)任何包含非数字的标记都是符号,即使是看起来像数字的示例,例如123abc,3.14和-10。” 似乎与“必须从-2 ^ 31到2 ^ 31-1至少支持整数”相矛盾。
msh210

3
@ msh210并非如此,因为前者在谈论令牌,而后者在谈论。即使没有直接输入的方法-1,我仍然可以通过执行生成值-1 (s 0 1)
DLosc

1
@coredump阅读了相关的Wikipedia文章后,我得出的结论是该实现实际上更接近于动态,但没有作用域嵌套。如果调用,则函数F中的变量在函数中不可用(如动态作用域),但如果是在内部定义的嵌套函数(如词法作用域),则它们在函数中也不可用-请参见测试用例5。因此将其称为“词法” ”可能会产生误导。GFGHHF
DLosc

1
换句话说,由于缺少范围嵌套,实现可以使用动态或词法作用域策略,并得出相同的结果。在任何给定的时间范围内,唯一的名称是1)当前正在执行的函数的本地名称(如果有),以及2)全局名称。不支持关闭。(参考实现保留了与调用栈相对应的名称绑定栈-一种动态样式的方法,我认为这是最容易实现的方法。)
DLosc 2015年

1
必填xkcd
mınxomaτ

Answers:


11

Python 2 685 675 660 657 646 642 640字节

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

从STDIN读取输入,并将输出写入STDOUT。

尽管不是严格要求,但解释器支持null函数和宏,并优化通过执行的尾部调用v

说明

解析中

来解析输入,我们首先包围的每次出现(以及)用空格,和拆分得到的字符串转换成字; 这给出了令牌列表。我们维护一个表达式堆栈E,该堆栈最初为空。我们按顺序扫描令牌:

  • 如果遇到(,则将一个空列表压入表达式堆栈的顶部;
  • 如果遇到a ),则将值弹出到表达式堆栈的顶部,然后将其附加到该堆栈之前位于其下方的列表中;
  • 否则,我们将当前标记作为字符串附加到表达式堆栈顶部的列表中(在此阶段,我们将整数保留为字符串,并在求值时解析它们。)

如果在处理普通令牌时,或者由于导致从堆栈中弹出表达式后),表达式堆栈为空,则我们处于顶级表达式中,并使用V(),和评估要附加的值打印其结果,并使用进行适当格式化F()

评价

我们维护全局范围,G作为键/值对的列表。最初,它仅包含内置函数(但不包含宏,并且不包含v,我们将其视为宏),这些函数以lambda形式实现。

评估发生内V(),这需要表达的评估,e以及局部范围内,L,这是也键/值对的列表(评估顶级表达式时,局部范围内是空的。)的胆量V()活在无限循环内,这就是我们执行尾部调用优化(TCO)的方式,如稍后所述。

我们e根据其类型进行处理:

  • 如果它是空列表,或者是可转换为int的字符串,我们将立即将其返回(可能在转换为int之后);除此以外,

  • 如果是字符串,我们将在由全局范围和局部范围的串联构成的字典中查找它。如果找到关联的值,则返回它;否则,e必须是建宏(即名称qidv),我们原样返回。否则,如果e不是字符串,

  • e是(非空)列表,即函数调用。我们通过V()递归调用(使用当前局部作用域)来评估列表的第一个元素,即函数表达式;我们称这个结果f。列表的其余部分A是参数列表。 f只能是一个字符串,在这种情况下,它是一个内置宏(或函数v),在一个lambda情况下,它是一个内置函数,或一个列表,在这种情况下,它是用户定义的函数或宏。

    如果f是一个字符串,即内置宏,我们就地处理它。如果它是宏iv,我们将评估其第一个操作数,如果是,则相应地选择第二个或第三个操作数;如果是i,则使用第一个操作数的结果v。与其递归地评估选定的表达式(这将使TCO失败),我们只需替换e为所述表达式,然后跳转到循环的开始即可。如果f是宏d,则将一对(其第一个元素是第一个操作数,而其第二个元素是对第二个操作数求值的结果)追加到全局范围内G,并返回第一个操作数。否则,f是macro q,在这种情况下,我们只需直接返回其操作数即可。

    不好意思,如果f是lambda或第一个元素不是的列表(),则它是一个非null函数,而不是宏,在这种情况下,我们将评估其参数(即的元素A)并替换A为结果。

    如果f为lambda,我们将其称为,将的解压缩参数传递给A,然后返回结果。

    否则,f是一个列表,即用户定义的函数或宏;它的参数列表是倒数第二个元素,其主体是最后一个元素。像在宏i和中一样v,为了执行TCO,我们不会递归评估主体,而是替换e为主体并继续进行下一次迭代。与i和不同v,我们还用L函数的新本地范围替换了本地范围。如果参数列表P,是的,其实,一个列表,新的本地范围由荏苒参数列表,同时施工P,有参数列表,A; 否则,我们要处理可变参数函数,在这种情况下,新的本地范围只有一个元素对(P, A)

替换

如果您想使用它,这是解释器的REPL版本。它支持重新定义符号,以及通过命令行参数或(import <filename>)宏导入文件。要退出解释器,请终止输入(通常是Ctrl + D或Ctrl + Z)。

这是一个示例会话,实现合并排序:


您可以使用zlib缩短一些时间:)压缩以字节为单位转换的代码,并将其替换为:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
Labo 2015年

您可以通过A[0]在块之外的后面分配一个单字符变量来节省两个字节
Hannes Karppila,2015年

@HannesKarppila是的,但这会破坏null函数(因为A在这种情况下为空),所以我不想“回归”。
2015年

4

C(GNU),1095字节

许多动作发生在巨大的v功能中。而不是显式实现尾递归,v而是通过结构化的方式来实现,对vto的许多调用v将由gcc的尾递归优化处理。没有垃圾收集。

这大量使用了GCC扩展,因此只能使用gcc进行编译(使用命令gcc -w -Os tl.c)。它还使用一些scanf我通常使用的Windows上不可用的扩展名。用标准语言编写解析器的前景scanf如此糟糕,以至于我改用Linux VM来测试程序。没有scanf字符类的解析可能会增加100个以上的字节。

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

半脱胶

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

}

编译后的可执行文件有什么用?是REPL吗?是否以文件名作为输入?
ckjbgames

@ckjbgames它从stdin读取程序。
feersum

好的。我认为您应该编辑答案并注意。
ckjbgames

1

锡兰,2422字节

(我认为这是我最长的高尔夫课程。)

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

我可能会打更多的字节,因为在某些地方使用了两个字母的标识符,但是对于那些字母,我已经用完了一些有意义的单个字母。尽管就这样,它看起来也不像锡兰。

这是一个面向对象的实现。

我们有一个V实现类的值接口L(列表–只是围绕Ceylon序列的包装器V),S(符号–围绕字符串的包装器),I(整数–围绕Ceylon整数的包装器)和B(内置函数或宏,围绕a的包装器)锡兰功能)。

我通过实现equals方法(和hash属性,实际上仅是符号所需要的)以及string输出的标准属性来使用标准的锡兰等式符号。

我们有一个Boolean属性b(默认情况下为true,将被覆盖IL为空列表返回false),还有两个方法l(调用,即将此对象用作函数)和vO(评估一个步骤)。两者都返回一个值或一个Continuation对象,该对象然后允许再进行一步vF评估,并且(完全评估)循环直到结果不再是连续。

上下文接口允许访问变量。有两种实现方式,分别G是全局上下文(允许使用d内置LC函数添加变量)和局部上下文,它们在评估用户函数的表达式时有效(它会退回到全局上下文)。

符号评估访问上下文,列表(如果非空)首先评估其第一个元素,然后调用其call方法进行评估。调用仅由列表和内建函数实现–它首先评估参数(如果是函数,而不是宏),然后进行实际有趣的操作–对于内建函数,硬编码的是内建函数,对于列表则创建新的本地上下文并返回一个继续。

对于内建函数,我使用了与Shift Interpreter中类似的技巧,该技巧使我可以使用所需的参数类型来定义它们,但可以使用反射使用通用序列来调用它们(类型将在调用时进行检查)。这避免了函数/宏内部的类型转换/声明麻烦,但是需要顶级函数,因此我可以获得它们的元模型Function对象。

p(解析)函数把在空格,换行和括号的字符串,然后遍历的标记,并建立使用堆栈和运行列表清单。

然后,解释器(在run方法中,是入口)在列表中使用表达式(只是值),对每个表达式求值并打印结果。


以下是带有注释并通过格式化程序运行的版本。

我开始打高尔夫球之前的较早版本(仍然有一些关于列表评估的误解)可以在我的Github存储库中找到,我会尽快将其放在那里(因此,如果需要原始版本,请确保查看第一个版本)。

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