比赛做题的时候刚好遇到了这个语言,感觉非常的有意思,用8种字符就可以运算出来程序,学习一下

brainfuck

Brainfuck是一种极小化的计算机语言,它是由Urban Müller在1993年创建的。由于fuck在英语中是脏话,这种语言有时被称为brainf*ck或brainf**k,甚至被简称为BF。

一,指令

BF只有8种有效字符,其实就是8种指令:

Command Explanation
+ 指针指向的字节的值加一
- 指针指向的字节的值减一
> 指针加一
< 指针减一
. 输出指针指向的单元内容(ASCⅡ码)
, 输入内容到指针指向的单元(ASCⅡ码)
[ 如果指针指向的单元值为零,向后跳转到对应的]指令的次一指令处
] 如果指针指向的单元值不为零,向前跳转到对应的[指令的次一指令处

二,代码理解

BF可以简单的翻译成C/C++语言:

img

Hello world 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
++++	将单元格0设置为8

[

>+++ 将4添加到单元格1;这将始终将单元格1设置为4

[ 由于该单元将被循环清除

>++ 将4*2添加到单元格#2

>+++ 将4*3添加到单元格#3

>+++ 将4*3添加到单元格#4

>+ 将4添加到单元格#5

<<<- 减少单元1中的循环计数器

] 循环直到单元格#1为零

> 将1添加到单元格#2

>+ 将1添加到单元格#3

> 从单元格4中减去1

>>+ 将1添加到单元格#6

[<] 移回找到的第一个零单元格;这将

被上一个循环清除的be单元#1

< -减少单元0中的循环计数器

] 循环直到单元格#0为零

其结果是:

单元格编号:01 2 3 4 5 6

目录:0 72 104 88 32 8

指针:^

>>. 单元格2的值为72,即“H”

>---. 从单元格#3中减去3得到101,即“e”

+++++ ++..+++. 第3单元的“llo”也是如此

>>. 5号单元格的空格为32

<-. 从单元格#4中减去1,得到87的“W”

<. 从“Hello”结尾起,第3单元被设置为“o”

+++.----- -.----- ---. 单元格#3表示“rl”和“d”

>>+. 将1添加到单元格#5会给我们一个感叹号

>++. 最后是第6单元的换行

推荐网站:https://fatiherikli.github.io/brainfuck-visualizer/#

1

通过指针可视化,更好的理解代码

三,Brainfuck语言 解释器

c++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
string code="...put BF code here...";
char arr[1000]={0};
char *p = arr;

void run(string s)
{
int pcode=0;
while(pcode<s.length())
{
switch(s[pcode])
{
case '>':
p++;
break;
case '<':
p--;
break;
case '+':
*p = *p + 1;
break;
case '-':
*p = *p - 1;
break;
case '.':
cout<<char(*p);
break;
case ',':
*p=getchar();
break;
case '[':
{
int num=1, pend=pcode;
while(num)
{
pend++;
if(s[pend]=='[')num++;
if(s[pend]==']')num--;
}
string ss=s.substr(pcode+1,pend-pcode-1);
while(*p)run(ss);
pcode=pend;
break;
}
case ']':
break;
}
pcode++;
}

}

int main()
{
run(code);
return 0;
}

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
global cs
global ip

global ss
#global sp

global ds
global bp

global tab
global out


cs='++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
ip = 0
print(len(cs))
ss = []
#sp = 0

ds = []
bp = 0

tab = 0
out = []

def tab_():
i = tab
stab = ''
while i > 0:
stab += '\t'
i -= 1
return stab

def push(var):
global ss
ss.append(var)

def pop():
global ss
return ss.pop()

def op_inc_val():
global ip
count = 0
while True:
op = cs[ip]
ip = ip + 1
if op == '+':
count = count + 1
else:
break
l = len(ds)
if l <= bp:
ds.append(0)
old = ds[bp]
old += count
ds[bp] = old
print (tab_()+'ds[%d] += %d (%d)'%(bp, count, old))

def op_dec_val():
global ip
count = 0
while True:
op = cs[ip]
ip = ip + 1
if op == '-':
count = count + 1
else:
break
old = ds[bp]
old -= count
ds[bp] = old
print (tab_()+'ds[%d] -= %d (%d)'%(bp, count, old))

def op_inc_dp():
global bp
bp = bp + 1

def op_dec_dp():
global bp
bp = bp - 1

def op_jmp_fwd():
global tab
global ip
print (tab_()+'while ds[%d]=%d:'%(bp, ds[bp]))
tab=tab + 1
if ds[bp] != 0:
curip = ip - 1
push(curip)
else:
c = 1;
while c > 0:
op = cs[ip]
if op == '[':
c += 1
elif op == ']':
c -= 1
ip += 1

def op_jmp_bck():
global tab
global ip
tab = tab - 1
if ds[bp] != 0:
ip = pop()

def op_out():
print (tab_()+'putchar(ds[%d]) (%d)'%(bp, ds[bp]))
out.append(ds[bp])

def op_in():
print (tab_()+'getchar')

end = len(cs)
while ip < end:
op = cs[ip]
ip = ip + 1
if op == '+':
ip = ip - 1
op_inc_val()
ip = ip - 1
elif op == '-':
ip = ip - 1
op_dec_val()
ip = ip - 1
elif op == '>':
op_inc_dp()
elif op == '<':
op_dec_dp()
elif op == '[':
op_jmp_fwd()
elif op == ']':
op_jmp_bck()
elif op == '.':
op_out()
elif op == ',':
op_in()
else:
print ('invalid opcode')
break

print (out)
str = ''
for c in out:
str += '%c'%(c)
print (str)

四,BF的操作

当前位置清零

[-] 将当前指针的值归零

之前位置清零

[[-]<] 将当前指针以及之前的指针归零

字符I/O

,. 从键盘读取一个字符并输出到屏幕上。

简单的循环

,[.,] 这是一个连续从键盘读取字符并回显到屏幕上的循环。注意,这里假定0表示输入结束,事实上有些系统并非如此。以-1和”未改变”作为判断依据的程序代码分别是”,+[-.,+]”和”,[.[-],]”。

指针维护

>,[.>,] 通过移动指针保存所有的输入,供后面的程序使用。

加法

[->+<]

把当前位置的值加到后面的单元中(破坏性的加,它导致左边的单元被归零)。

五,运算法则

条件指令

1
,----------[----------------------.,----------]

这个程序会把从键盘读来的小写字符转换成大写。按回车键退出程序。

首先,我们通过,读入第一个字符并把它减10(大多数情况下,brainfuck使用10作为换行符的值)。如果用户按的是回车键,循环命令([)就会直接跳转到程序的结尾:因为这时第一个字节已经被减到了零。如果输入的字符不是换行符(假设它是一个小写字符),程序进入循环。在这里我们再减去剩下的22,这样总共减掉32:这是ASCⅡ码中小写字符和大写字符的差值。

下面我们把它输出到屏幕。然后接收下一个输入字符,并减去10。如果它是换行符,退出循环;否则,再回到循环的开始,减去22并输出……当循环退出时,因为后面已经没有其他的指令,程序也随之终止。

加法

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

这个程序对两个一位数做加法,并输出结果(如果结果也只有一位数的话):4+3

7 (现在程序开始有点复杂了。我们要涉及到数组中单元的内容了,比如[0]、[1]、[2]之类。)

第一个输入的数字被放在在[0]中,从中减去48来把它从ASCⅡ码值48到57转换为数值0到9:这是通过在[1]中放入6,然后按照[1]中的次数让一个循环从[0]中多次减去8来完成的(当加上或减去一个大的数值时,这是常用的办法)。下一步,加号被读入[1]中;然后,第二个数字被输入,覆盖掉加号。

下面的循环[<+>-]执行最重要的工作:通过把第二个数字移动到第一个里面让它们相加,并把[1]清空。这里的每次循环都把[0]增一并从[1]中减一;最终,在[1]被置零的多次循环中,[1]中的值就被转移到了[0]中。现在,[1]中是我们输入的换行符(这个程序里,我们没有设置对输入错误的检查机制)。

然后,指针被移回到指向[0],并输出它的内容([0]里面现在是 a + (b + 48) 的值,因为我们没有修改b的值,这等于 (a + b) + 48,也就是我们想要输出的ASCⅡ值)。然后,把指针指向[1],里面保存着前面输入的换行符;输出换行符,程序结束。

乘法

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

和前一个程序类似,不过这次是乘法而不是加法。

第一个输入的数字被放入[0],星号和第二个数字被放入[1],然后两个数值都被校正:减去48。

现在,程序进入了主循环。我们的基本思想是:每次从[0]中减去一,同时把[1]的值加入到保存乘积的[2]中。在实际操作中,第一个内层循环把[1]的值同时转移到[2]和[3]中,同时[1]清零(这是我们复制数字的基该方法)。下一个内层循环把[3]中的值重新放回到[1],并清零[3]。然后从[0]中减一,结束外层循环。在退出这个循环时,[0]中为零,[1]仍然是输入的第二个数值,[2]则是这两个数值的和。(要是想保存第一个数,我们可以在外层循环中每次给[4]加一,最后把[4]移回[0]。)在结果中加48,并把换行符读入[3],输出ASCII码的乘积,然后输出刚才保存的换行符。

除法

1
>,>++++++[-<--------<-------->>]

从简单储存2个数字符到[0]和[1],并各自减去48

<<[ 这是一个主循环,在被除数,也就是[0]的值为0后循环跳出

>[->+>+<<] 从单元1中复制除数除数/485249)的值到[2]和[3],设[1]为0

>[-<<- 被除数[1]减去除数[2],结果将储存在[0],并且[2]将归0

[>]>>>[<[>>>-<<<[-]]>>]<<] 如果被除数[0]为0,跳出循环

>>>+ 加1值商到[5]

<<[-<<+>>] 从[3]复制除数到[1]

<<<] 移动指针到[0]

>[-]>>>>[-<<<<<+>>>>>] 从[5]复制商到[0] (这步不是必须的,但会更清楚)

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