欢迎光临散文网 会员登陆 & 注册

Esolang——1.brainfuck初探

2020-12-09 19:03 作者:水帝WaterKing  | 我要投稿

        在前言中我们提到了一种语言,brainfuck。接下来的几篇里,我们将详细介绍这种语言,并使用其进行结构化编程。

背景

        1993年,一个叫做FALSE的Esolang出现了,它是图灵完备的,而且最初的编译器只有1024个字节。这种语言影响了后来的许多Esolang,比如Befunge和我们今天要谈到的brainfuck。

        同样是在1993年,Urban Müller发现了这个编译器极小的语言后,想要更上一层楼,发明一个使用更小体积编译器的语言。于是一个只需要240字节的编译器横空出世。后来Brain Raiter更是写出了一个只需要171字节的编译器。[3]

        这种语言是图灵完备的。事实上,现在证明图灵完备的方法之一就是写出一个brainfuck解释器(就是不知道这种方法学不学术……)。

        Müller将其命名为brainfuck。然而,由于众所周知的原因,该语言的名称常被隐去,取而代之的是brainf*ck、bf、brainoof等等。如果要中文翻译的话,脑操真的挺适合的,不管是直译还是意译。

        Brainfuck,官方建议是除了句子开头以外全部用小写,不过大写问题也不大。

语言概述

        Brainfuck基于这样一台机器。它具有一列初始化为0的数组(数组长度最初要求是30,000,但这个标准不是必要的)和一个初始指向第一个元素的指针。

        该语言总共只有8个命令><+-.,[]

        以下是每一个命令的含义以及对应的C语句(p是一个char*):

解释器

        fatiherikli写了一个解释器,拥有27个上限为255的存储单元,运行代码时可以看到当前运行命令和各个单元的数值。

        网址:http://fatiherikli.github.io/brainfuck-visualizer

        这里还有一个解释器,支持负的数组下标,数组长度也不只局限于27,但是没有过程显示。

        网址:https://tio.run/#brainfuck#

        还能搜到其他一些解释器。而且由于brainfuck本身不难,写一个解释器也不是什么难事。在学Brainfuck的时候,开个解释器很有必要。与之同样必要的还有ASCII码表,除非你已经背下来了。

输出Hello, World!

        当拿到一种新的语言时,作为惯例,第一个程序永远是打印一个Helloworld。不过在那之前,我们先了解一些知识。

        首先,如何用BF换行?

        需要注意的是,BF标准规定换行符使用'\n'(10)而非'\r'(13),所以现在我们需要输出一个10。我们使用+和.两个命令

++++++++++.

        这样我们就成功换行。

        但接着问题就来了,如果我要打印一个'A'(65)怎么办?写65个+?估计这样的话键盘磨损很快吧(当然如果你用程序输出程序当我没说)

        这种时候,我们就需要使用乘法了。65=8*8+1,而乘法可以使用循环写,我们再引入其他几个命令。

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

        看这个程序,其先将当前位置加8,然后循环运行右移加8左移减1直到左侧单元为0,然后右移加1,就获得了65。这实际上就是将8加8次然后加1。用类似的方法可以大大压缩程序大小。

        有了这些知识储备,我们就可以写一个输出"Hello, World!"的程序了

++++++++[>+++++++++<-]>.>>++++++++++[<++++++++++>-]<+.+++++++..+++.>>++++[<+++++++++++>-]<.------------.<<<+++[>+++++<-]>.>.+++.------.--------.>+.(147)

        括号里就是整个程序的长度,如你所见,这是很简单的。

        但是,brainfuck之所以被称为脑操,不仅是因为它的直译,还因为这玩意儿就像智力游戏一样,极度耗脑子。用BF的人们以压缩程序长度为乐,而有时候极度困难的。比如说,下面就是一个更短的输出"Hello, World"的bf程序。

++++++++[>++++[>++>+++>+++>+>+<<<<<-]>+>+>->+[<]<-]>>.>---.+++++++..+++.>>++++.>.<<-.<.+++.------.--------.>>>+.(112)

        这程序比起上面我写的看起来就有点复杂了。没事,咱们一点一点分析

  1. >++++[>++>+++>+++>+>+<<<<<-]

    这一段话结束后从第二个元素开始会是0 8 12 12 4 4,指针在第二个元素上

  2. >+>+>->+

    这段话结束后是0 9 13 11 5 4

  3. [<]

    向左移到第一个为0的元素,即第二个元素

  4. ++++++++[>++++[>++>+++>+++>+>+<<<<<-]>+>+>->+[<]<-]

    将8乘上上述数列,结果从第一个元素开始为0 0 72 104 88 40 32

  5. >>.>---.+++++++..+++.>>++++.>.<<-.<.+++.------.--------.>>>+.

    然后分别加上或减去偏移量输出。

        实际上,如果我们放宽限制,或者考虑上BF的其他特性,我们还能得到更短的程序。

        比如,如果元素的值没有范围限制且允许负数,那么我们可以写出如下程序[4]

(>>>>>>>>>>>>>>>>)+[+[<<<+>>>>]+<-<-<<<+<++]<<.<++.<++..+++.<<++.<---.>>.>.+++.------.>-.>>--.(76(92))

--->->->>+>+>>+[++++[>+++[>++++>-->+++<<<-]<-]<+++]>>>.>-->-.>..+>++++>+++.+>-->[>-.<<](87)

        第一个程序使用了负的数组下标,长度旁边括号里的数字是加上最开始移位后程序的长度。

        再比如,如果我们规定超出0~255范围的数字需要取模255(这实际上是大部分解释器规定的),我们还有如下三个程序。[4][5]

(>>>>)--<-<<+[+[<+>--->->->-<<<]>]<<--.<++++++.<<-..<<.+.>>.>>.<<<.+++.>>.>>-.<<<+.(78(82))

(>)-[++[<++>->+++>++<<]---->+]<<<<.<<<<-.<..<<+.<<<<.>>.>>>-.<.+++.>>.>-.<<<<<+.(78(79))

(>>>>>)+[-->-[>>+>-----<<]<--<---]>-.>>>+.>>..+++[.>]<<<<.+++.------.<<-.>>>>+.(72(77))

        不过上面两个程序实在是运行时间太久了,所需的数组长度也非常长。与上面两个相比,最底下那个简直可以称之为优美。它是由KSab用C++程序暴力破解出来的。这个可以在上面提到的fatiherikli的解释器中运行,强烈建议输进去欣赏一下。

        所以BF真的挺废脑子的,你永远也不知道一个更简单的程序可能会长啥样。这可能就是BF的魅力之一吧。

参考:

[1]brainfuck - Esolang https://esolangs.org/wiki/Brainfuck

[2]The Brainfuck Programming Language http://www.muppetlabs.com/~breadbox/bf/

[3]Basics of Brainfuck https://gist.github.com/roachhd/dce54bec8ba55fb17d3a#file-readme-md

[4]code golf - "Hello, World!" - Code Golf Stack Exchange https://codegolf.stackexchange.com/questions/55422/hello-world/163590#163590

[5]code golf - "Hello, World!" - Code Golf Stack Exchange https://codegolf.stackexchange.com/questions/55422/hello-world/68494#68494

[6]Github:ksabry/bfbrute https://github.com/ksabry/bfbrute

Esolang——1.brainfuck初探的评论 (共 条)

分享到微博请遵守国家法律