根据乔治·爱迪生(George Edison)对这个问题的评论,编写最小的自我解释口译员。
- 您可以使用自己选择的语言。
- 空的语言不计算在内。您的程序必须至少两个字符长。
- 该程序不需要解释整个语言,只需解释图灵完备的语言功能子集(包含解释器)即可。
- 不算数。
- 请勿使用您语言的内置
eval
功能或等效功能。apply
等等也一样。
sexp
解析器。
根据乔治·爱迪生(George Edison)对这个问题的评论,编写最小的自我解释口译员。
eval
功能或等效功能。apply
等等也一样。sexp
解析器。
Answers:
,(1p0(2d())(41(2d())('#((1p0()(10()(1d,1p$)=)<)$2d,1p$)(40(1d,1c$^)(''(1d,^)('0
'9('0-(,'0'9('0-2p10*+1p$)(!1d)~)$^)(0($)'$(^)'^(&)'&(c)'c(p)'p(d)'d(=)'=(<)'<(
>)'>(~)'~(.)'.(,)',(!)'!(+)'+(-)'-(*)'*(/)'/(%)'%()38p(1p3p0(3d)((2)(3)=p1d1p$)
=)$)~)=)=,2p$&)=)=)<)$$
320→260:推送简单的字符到指令的映射,然后将其折叠。每种情况下的代码大小减半(有18种情况),但折叠需要30个字符。
这是我构造的另一种语言(基于Gist的基础解释器)。它的独特之处在于该语言可对代码片段进行编码。也就是说,使用这种基于堆栈的语言的指令字符串与使用其他语言的数据结构或闭包具有相同的效果:
1^ # Push a 1, and "lift" it to be a code literal.
(5 +) # Define a code literal.
& # Join the two code literals, forming (1 5 +)
$ # Execute the code literal.
解释器在运行之前会构建整个程序的代码片段,因此也可以将其视为编译器。因此,堆叠解释器不会导致指数级的运行时开销。
解释器直接从宿主解释器派生其所有运算符。但是,它自己进行解析,因此大多数代码只是将字符转换为各自代码文字的序列。这与using并不相同eval
,但是它揭示了任何编程语言实现如何依赖于其宿主语言/体系结构的语义。
(
... )
创建一个“块”,实际上是没有上下文的指令列表。在内部,它甚至可能是机器代码。
块 $
调用一个块。被调用者将被移交给全局堆栈,其中包括被调用的块。
值 ^
提升价值。即,将其转换为可推入该值的块。
范例:
1 ^
== (1)
block1 block2 &
连接两个块,形成一个按顺序运行的块。
范例:
(1) (2) &
== (1 2)
ñ c
复制堆栈的第n个值。
范例:
5 4 3 2 1 0 3c
== 5 4 3 2 1 0 3
ñ p
拔出堆栈的第n个值(将其删除,然后将其放在前面)。
范例:
5 4 3 2 1 0 3p
== 5 4 2 1 0 3
ñ d
从堆栈中删除n个值。 0d
是无人操作。
范例:
5 4 3 2 1 0 3d
== 5 4 3
ab(on_true)(on_false) =
测试a是否等于b。使用除第一个参数以外的所有参数,并调用on_true或on_false。如果一个参数为零,另一个为任何其他类型,则结果为false。否则,a和b必须为整数。
范例:
3 3 ('0+ .) (1d) =
== 3 '0+ .
ab(on_true)(on_false) <
测试a是否小于b。a和b必须为整数。
范例:
3 5 (1d 5) () <
== 3 1d 5
== 5
ab(on_true)(on_false) >
测试a是否大于b。a和b必须为整数。
范例:
3 5 (1d 5) () >
== 3
嗨(on_true)(on_false) ~
测试lo <= a <= hi。a,lo和hi必须为整数。
范例:
3 0 10 ('0+ .) (1d) ~
== 3 '0+ .
C .
放置字符c(从堆栈中使用)。
,
获取一个角色并将其推入堆栈。如果已到达文件末尾,则按-1。
C !
取消角色。就像C中的ungetc一样,只允许一次推回。
'c
按下字符c。
[0-9] +
推送一个十进制整数。
+
-
b *
加/减/乘两个数字。
范例:
3 5 + 7 3 + *
== 80
b /
b %
除法和模量。与C语言不同,它们向负无穷大方向舍入。
代码#
注释
该#
字符注释掉一切该行的末尾。
)
用于端块。也可以用于结束整个程序。
所有其他字符将被忽略。
0101000110100000000101011000000000011110000101111110011110000101110011110000001111000010110110111001111100001111100001011110100111010010110011100001101100001011111000011111000011100110111101111100111101110110000110010001101000011010
有关 详细信息,请参见 http://en.wikipedia.org/wiki/Binary_lambda_calculus#Lambda_encoding
我不能为这个赞不绝口,但我想我会分享这个惊人的故事:
>>>+[[-]>>[-]++>+>+++++++[<++++>>++<-]++>>+>+>+++++[>++>++++++<<-]+>>>,<++[[>[
->>]<[>>]<<-]<[<]<+>>[>]>[<+>-[[<+>-]>]<[[[-]<]++<-[<+++++++++>[<->-]>>]>>]]<<
]<]<[[<]>[[>]>>[>>]+[<<]<[<]<+>>-]>[>]+[->>]<<<<[[<<]<[<]+<<[+>+<<-[>-->+<<-[>
+<[>>+<<-]]]>[<+>-]<]++>>-->[>]>>[>>]]<<[>>+<[[<]<]>[[<<]<[<]+[-<+>>-[<<+>++>-
[<->[<<+>>-]]]<[>+<-]>]>[>]>]>[>>]>>]<<[>>+>>+>>]<<[->>>>>>>>]<<[>.>>>>>>>]<<[
>->>>>>]<<[>,>>>]<<[>+>]<<[+<<]<]
{[B':=?0:B';=?0:B'}=?0:B'{=?,A!,A!d1c&:B'?=?,A!,A!2e&:B''=?,,A!d3c&:B{[B'0<?0:B
'9>?0:1}!?B'0-{[,g!?c'0-B10*d+A!:Bd]A!d3c&}!:B'#=?{[,10=?,]A!:A!}!:,A!Bb&}{[AC[
B]DB?[AB{[Bh&hbhn!}{[B[AB]C?1-eA!:b}&[C1=?E[C]FHc&B!:C2=?{G?D:E[C}!FHcI!:C3=?E[
C]B!:C'!=?G[ABC]Hc&dbh&D?b@I!B!:b@I!:C'&=?HB!:C'@=?FGDI!:C'[=?GF&HDI!:C']=?F[A]
HDI!:C',=?,B!:C'.=?G.FHDI!:C'a'z{[DC<?0:DB>?0:1}!?Ce-HA!B!:C'A'Ze!?F[B]Cg-dA!B!
:{C'+=?{[CB+}:C'-=?{[CB-}:C'*=?{[CB*}:C'/=?{[CB/}:C'%=?{[CB%}:C'<=?{[CB<}:C'>=?
{[CB>}:C'==?{[CB=}:0}!?H[A][B]Ge!B!:FHDI!:c},c!0ac&0&0&0bho!;
BlockScript是我专门针对此挑战而创建的基于琐碎的意大利面条堆栈的语言。基本解释器是blockscript.c。
示例程序(打印前15个斐波纳契数):
{[B?B10/A!B10%d&:0}
{[B0<?'-.0B-A!:{B?Bh!{[B?B[A]A!B[B]'0+.:}!:'0.}!10.}
{[B?Dd!DC+B1-CecA!:}
0 1 15d!
;
解释器从标准输入中依次读取源代码和程序输入。这意味着要在解释器中的解释器中运行解释器,只需复制并粘贴:
# Level 1
{[B':=?0:B';=?0:B'}=?0:B'{=?,A!,A!d1c&:B'?=?,A!,A!2e&:B''=?,,A!d3c&:B{[B'0<?0:B
'9>?0:1}!?B'0-{[,g!?c'0-B10*d+A!:Bd]A!d3c&}!:B'#=?{[,10=?,]A!:A!}!:,A!Bb&}{[AC[
B]DB?[AB{[Bh&hbhn!}{[B[AB]C?1-eA!:b}&[C1=?E[C]FHc&B!:C2=?{G?D:E[C}!FHcI!:C3=?E[
C]B!:C'!=?G[ABC]Hc&dbh&D?b@I!B!:b@I!:C'&=?HB!:C'@=?FGDI!:C'[=?GF&HDI!:C']=?F[A]
HDI!:C',=?,B!:C'.=?G.FHDI!:C'a'z{[DC<?0:DB>?0:1}!?Ce-HA!B!:C'A'Ze!?F[B]Cg-dA!B!
:{C'+=?{[CB+}:C'-=?{[CB-}:C'*=?{[CB*}:C'/=?{[CB/}:C'%=?{[CB%}:C'<=?{[CB<}:C'>=?
{[CB>}:C'==?{[CB=}:0}!?H[A][B]Ge!B!:FHDI!:c},c!0ac&0&0&0bho!;
# Level 2
{[B':=?0:B';=?0:B'}=?0:B'{=?,A!,A!d1c&:B'?=?,A!,A!2e&:B''=?,,A!d3c&:B{[B'0<?0:B
'9>?0:1}!?B'0-{[,g!?c'0-B10*d+A!:Bd]A!d3c&}!:B'#=?{[,10=?,]A!:A!}!:,A!Bb&}{[AC[
B]DB?[AB{[Bh&hbhn!}{[B[AB]C?1-eA!:b}&[C1=?E[C]FHc&B!:C2=?{G?D:E[C}!FHcI!:C3=?E[
C]B!:C'!=?G[ABC]Hc&dbh&D?b@I!B!:b@I!:C'&=?HB!:C'@=?FGDI!:C'[=?GF&HDI!:C']=?F[A]
HDI!:C',=?,B!:C'.=?G.FHDI!:C'a'z{[DC<?0:DB>?0:1}!?Ce-HA!B!:C'A'Ze!?F[B]Cg-dA!B!
:{C'+=?{[CB+}:C'-=?{[CB-}:C'*=?{[CB*}:C'/=?{[CB/}:C'%=?{[CB%}:C'<=?{[CB<}:C'>=?
{[CB>}:C'==?{[CB=}:0}!?H[A][B]Ge!B!:FHDI!:c},c!0ac&0&0&0bho!;
# Level 3
{[B?B10/A!B10%d&:0}
{[B0<?'-.0B-A!:{B?Bh!{[B?B[A]A!B[B]'0+.:}!:'0.}!10.}
{[B?Dd!DC+B1-CecA!:}
0 1 15d!
;
就像电影《盗梦空间》一样,您几乎不能深入到三个层次。这不是时间问题,而是空间。BlockScript大量泄漏内存,这与语言本身的设计方式有关。
在BlockScript中,“堆栈”不是由您可能习惯的后续操作覆盖的数组。实际上,它是作为不可变链表实现的,并且堆栈在程序执行期间一直存在。同样,没有运算符(除外@
)从堆栈中删除值。但是,堆栈修改仅影响它们发生的块。
a
通过 z
从堆栈中取出第0-25个项目,并将其推入堆栈。 a
指堆栈的顶部或最近推送的项目。
A
通过 Z
提取当前帧的0-25项,并将其推入堆栈。
[
打开一个“框架”以从堆栈顶部的堆栈参考(请参见下文)中选择项目。 [
不需要match ]
,但是框架在词法范围内。在BlockScript中,“范围”由形成块的括号({
... }
)确定。因此,在块内部打开框架将不会对块外部的代码产生影响。
]
关闭当前框架,返回到上一个框架(如果有)。
{
... }
创建一个“块”,并将其推入堆栈。在块内部,堆栈将从块之前的位置开始,但调用者的堆栈将被压入顶部。堆栈在BlockScript中是持久的且不可变的,因此块是闭包。这个习惯用法{[
意味着打开一个块,然后打开一个框架以开始选择参数(使用A
到Z
)。}
到达时,块的返回值是堆栈的头部。
例:
'3 '2 '1 {[ b. d. f. B. C. D. A! } 'D 'C 'B d!;
打印123BCD123DCB123BCD123DCB…
。小写字母表示堆栈值,大写字母表示参数(因为该帧已设置为调用者的堆栈)。 A!
调用方的头(保证是被调用的块)并调用它。如果您想知道为什么BCD
每隔一次反转一次,那是因为B. C. D.
在块调用自身之前以相反的顺序推送了这些参数。
!
调用一个块。将返回值压入堆栈。
&
创建一个堆栈引用,并将其推入堆栈。可以将其视为“超级骗局”,因为它可以有效地取出堆栈中的每个项目,并从中形成一个“元组”。成语&[
意味着,无论a
,b
,c
称为前现在可以访问A
,B
,C
(对于该块的其余部分,或直到]
遇到)。
在某种程度上,由于&
捕获的值超出了通常需要的值,因此BlockScript会按设计泄漏内存。
@
切换到堆栈参考所指向的堆栈a
。这个运算符有点怪异,但是BlockScript自解释器使用了几次,以避免不得不两次重复输入相同的参数。的影响@
(或任何堆栈操作中,就此而言)被限制在调用它的块。而且,该框架不受的影响@
,因此可以使用该框架在切换堆栈后获取所需的值。
?
<在true上 :
<在false上
条件表达式,就像C中的三元运算符一样。即,如果a
为“ true”(即不等于整数零),则执行<on true>,否则执行<on false>。
注意:输入和输出在UTF-8中完成。“字符”是与Unicode索引相对应的整数。
,
获取输入的下一个字符,并将其推入堆栈。如果到达输入末尾,请按-1。
.
将字符输出到堆栈的头部。
注意:在BlockScript中,整数和字符是相同的。
'c
按下字符c。
[0-9] +
推送一个十进制整数。
这些运算符仅对整数值起作用。
+
计算b
+ a
(推结果,但不丢弃任何一个值)。-
计算b
- a
。*
计算b
* a
。/
计算b
/ a
(整数除法;向负无穷大舍入)。%
计算b
%a
(整数模;四舍五入为负无穷大)。这些运算符仅对整数值起作用。
<
如果b
小于a
,按1,否则按0。>
=
#
评论到行尾;
不需要添加换行符来获得良好的效果。
((\(E V A L)(E(r)'(())))(\(x e)(?(s x)(V x e)((\(b j)(?(= b ")(a j)(?(= b \)x
(?(= b ?)(?(E(a j)e)(E(a(d j))e)(E(a(d(d j)))e))(?(s b)(A b(E(a j)e)(E(a(d j)
)e))(E(a(d(d b)))(L(a(d b))j e)))))))(E(a x)e)(d x))))(\(x g)(? g(?(= x(a(a
g)))(d(a g))(V x(d g)))x))(\(f h v)(?(= f r)(r)(?(= f p)(p h)(?(= f s)(s h)(?
(= f a)(a h)(?(= f d)(d h)(?(= f =)(= h v)(c h v))))))))(\(k v i)(? k(L(d k)(
d v)(c(c(a k)(E(a v)i))i))i)))
从理论上讲它应该可以运行,但是由于原始解释器是BrainFuck二进制文件,而它本身是解释器,所以我只能测试每个部分。当给出自己的名字和一个简单的表达式时,(p p)
我认为它需要的时间比到目前为止我等待的40分钟要多,而且我正在使用自己的速度jitbf
来运行它(错误地)使用Perl Inline-C来即时运行C代码。
无法在Zozotez中实现整个Zozotez,因为它没有使con突变的方法,并且:
(setq / define)需要更新绑定。我也没有实现显式的begin / progn或&rest参数,宏和特殊的打印参数,因为我没有在解释器中使用它。p
即使没有使用,我也确实包含了(打印),因此程序需要像原始解释器一样显式打印其计算。
相同的取消高尔夫:
;; A stand alone Zozotez script need to be
;; contained in one expression, here with
;; all functions provided as arguments to
;; get them bound in the dynamic environment
((\ (E V A L)
(E(r)'(())))
;; E (EVAL)
(\ (x e)
(? (s x)
(V x e)
((\ (b j)
(? (= b ") (a j)
(? (= b \) x
(? (= b ?) (? (E(a j)e) (E(a(d j))e) (E(a(d(d j)))e))
(? (s b)
(A b (E(a j)e) (E (a(d j))e))
(E (a(d(d b))) (L(a(d b))j e)))))))
(E (a x) e)(d x))))
;; V (VALUE / symbol->value)
(\ (x g)
(? g
(? (= x (a (a g)))
(d (a g))
(V x (d g)))
x))
;; A (APPLY) but just for primitives
(\ (f h v)
(? (= f r) (r)
(? (= f p) (p h)
(? (= f s) (s h)
(? (= f a) (a h)
(? (= f d) (d h)
(? (= f =)
(= h v)
(c h v))))))))
;; L ( joint evLis / extend-env)
(\ (k v i)
(? k
(L (d k)
(d v)
(c (c (a k)
(E (a v) i))
i))
i)))
eval
,它是用于表达式而不是程序。
X
应该以依赖于实现的方式使图灵完整的语言(从而能够计算素数)。
X
命令随机扰动程序并执行该程序,这意味着一个人不能使用命令来确定性地计算素数。您能给我一个示例解释器,让您X
以指定的方式使用吗?
/usr/bin/cat
)图灵完备性怎么样?