自解释口译员


25

根据乔治·爱迪生(George Edison)对这个问题的评论,编写最小的自我解释口译员。

  • 您可以使用自己选择的语言。
  • 空的语言不计算在内。您的程序必须至少两个字符长。
  • 该程序不需要解释整个语言,只需解释图灵完备的语言功能子集(包含解释器)即可。
  • 不算数。
  • 请勿使用您语言的内置eval功能或等效功能。apply等等也一样。

1
(嗯..我应该做些什么/usr/bin/cat)图灵完备性怎么样?
Ming-Tang

@ SHINKiROU:谢谢,我没有把它当作测试。更新。
华隆潭

相关:语言,它本身是在Stack Overflow上编写的最小的解释程序,尽管很少(实际上只有一个?)答案符合这里给出的规则。
dmckee 2011年

1
我们是否必须重写该Scheme sexp解析器,还是可以认为宿主语言还可以呢?
JB

@JB:该语言的字符串处理实用程序很好,包括sexp解析器。
华隆潭

Answers:


19

CI-260

,(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 +
  • b -
  • b *

    加/减/乘两个数字。

    范例

       3 5 + 7 3 + *
    == 80
    
  • b /

  • b %

    除法和模量。与C语言不同,它们向负无穷大方向舍入。

  • 代码#注释

    #字符注释掉一切该行的末尾。

  • )

    用于端块。也可以用于结束整个程序。

  • 所有其他字符将被忽略。


24

二进制Lambda演算,232 (29字节)

0101000110100000000101011000000000011110000101111110011110000101110011110000001111000010110110111001111100001111100001011110100111010010110011100001101100001011111000011111000011100110111101111100111101110110000110010001101000011010

有关 详细信息,请参见 http://en.wikipedia.org/wiki/Binary_lambda_calculus#Lambda_encoding


2
为什么这不是公认的答案D:BLC太神奇了!

你能解释一下吗?
PyRulez

1
不幸的是,维基百科删除了Binary Lambda Calculus页面。我的tromp.github.io/cl/cl.html页面链接到保存的副本和我写的解释解释器工作原理的论文。
John Tromp

13

我不能为这个赞不绝口,但我想我会分享这个惊人的故事:

Brainf ***(423)

>>>+[[-]>>[-]++>+>+++++++[<++++>>++<-]++>>+>+>+++++[>++>++++++<<-]+>>>,<++[[>[
->>]<[>>]<<-]<[<]<+>>[>]>[<+>-[[<+>-]>]<[[[-]<]++<-[<+++++++++>[<->-]>>]>>]]<<
]<]<[[<]>[[>]>>[>>]+[<<]<[<]<+>>-]>[>]+[->>]<<<<[[<<]<[<]+<<[+>+<<-[>-->+<<-[>
+<[>>+<<-]]]>[<+>-]<]++>>-->[>]>>[>>]]<<[>>+<[[<]<]>[[<<]<[<]+[-<+>>-[<<+>++>-
[<->[<<+>>-]]]<[>+<-]>]>[>]>]>[>>]>>]<<[>>+>>+>>]<<[->>>>>>>>]<<[>.>>>>>>>]<<[
>->>>>>]<<[>,>>>]<<[>+>]<<[+<<]<]

10

BlockScript-535

{[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中是持久的且不可变的,因此块是闭包。这个习惯用法{[意味着打开一个块,然后打开一个框架以开始选择参数(使用AZ)。}到达时,块的返回值是堆栈的头部。

    例:

    '3 '2 '1 {[ b. d. f. B. C. D. A! } 'D 'C 'B d!;
    

    打印123BCD123DCB123BCD123DCB…。小写字母表示堆栈值,大写字母表示参数(因为该帧已设置为调用者的堆栈)。 A!调用方的头(保证是被调用的块)并调用它。如果您想知道为什么BCD每隔一次反转一次,那是因为B. C. D.在块调用自身之前以相反的顺序推送了这些参数。

  • !

    调用一个块。将返回值压入堆栈。

堆栈参考

  • &

    创建一个堆栈引用,并将其推入堆栈。可以将其视为“超级骗局”,因为它可以有效地取出堆栈中的每个项目,并从中形成一个“元组”。成语&[意味着,无论abc称为前现在可以访问ABC(对于该块的其余部分,或直到]遇到)。

    在某种程度上,由于&捕获的值超出了通常需要的值,因此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(整数除法;向负无穷大舍入)。
  • %计算ba(整数模;四舍五入为负无穷大)。

关系运算符

这些运算符仅对整数值起作用。

  • <如果b小于a,按1,否则按0。
  • >
  • =

  • # 评论到行尾
  • 该程序必须以 ;
  • 所有其他字符将被忽略。

2
基督。想要像您对CI一样共享规格吗?
Casey

@Casey:添加了参考。
乔伊·亚当斯

1
您是否有兴趣知道自己在做梦?...在4级
MATEEN Ulhaq

3

Zozotez LISP:414

不需要添加换行符来获得良好的效果。

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

0

CHIQRSX9 +(可能是非竞争),2个字节

+I

如果不使用,就无法使用这种基于HQ9 +的语言编写自解释解释器I,该方法运行处理STDIN的内置解释器。


1
规则中没有任何地方说不允许使用内置自解释器。它说的是eval,它是用于表达式而不是程序。
暴民埃里克(Erik the Outgolfer)

如何计算这种语言的素数?
pppery

@ppperry X应该以依赖于实现的方式使图灵完整的语言(从而能够计算素数)。
user8397947'6

根据Esolang页面,Perl解释器的X命令随机扰动程序并执行该程序,这意味着一个人不能使用命令来确定性地计算素数。您能给我一个示例解释器,让您X以指定的方式使用吗?
pppery

@ppperry显然,用Perl编写的解释器是唯一的解释器,所以没有。另外,如果有一个程序以特定值“随机化”时会计算素数呢?
user8397947'6

0

并发文件系统Befunge 98-53 \ 18字节(几乎肯定是作弊的)

完整的53字节解释器,没有任何限制(尽管我还没有测试涉及IP拆分和包装的复杂时序交互):

v ;;;;;;;;
>]390'ai@
 t;;;;;;;;
;>zzzzz#;

从名为的文件中读取输入a并执行它。我们不能使用自修改代码的规则中未指定它。

不允许换行的18字节解释器(代码的一侧的IP移动并从另一侧开始):

]210'ai@
t
><
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.