我们先来看看Brainfuck的定义:
Müller的目标是创建一种简单的、可以用最小的编译器来实现的、匹配图灵完全思想的编程语言。这种语言由八种运算符构成,为Amiga机器编写的编译器(第二版)只有240个字节大小。
就象它的名字所暗示的,brainfuck程序很难读懂。尽管如此,brainfuck图灵机一样可以完成任何计算任务。虽然brainfuck的计算方式如此与众不同,但它确实能够正确运行。
字符 | 含义 |
---|---|
> |
指针加一 |
< |
指针减一 |
+ |
指针指向的字节的值加一 |
- |
指针指向的字节的值减一 |
. |
输出指针指向的单元内容(ASCII码) |
, |
输入内容到指针指向的单元(ASCII码) |
[ |
如果指针指向的单元值为零,向后跳转到对应的] 指令的次一指令处 |
] |
如果指针指向的单元值不为零,向前跳转到对应的[ 指令的次一指令处 |
这种语言基于一个简单的机器模型,除了指令,这个机器还包括:一个以字节为单位、被初始化为零的数组、一个指向该数组的指针
(初始时指向数组的第一个字节)、以及用于输入输出的两个字节流。
首先我们要有一个以字节为单位的数组:
byte[] memory = new byte[10000];
这个就是通常计算机的内存,现在假设这个内存有10000长度那么大
然后我们需要一个指针:
int point = 0;
初始化为0,也就是指向内存的第一个地址
这个语言的源代码只有8个字符,我们假定源码为:
String source;
现在我们要读取源码,我们需要一个辅助的属性index来标示我们读到了代码的第几个字符:
int index
然后我们来处理这些指令:
switch (b) {
case '>':
point++;
break;
case '<';
point--;
break;
case '+';
memory[point]++;
break;
case '-';
memory[point]--;
break;
case '.'
System.out.print(memory[point]);
break;
case ',';
memory[point] = (byte) System.in.read();
break;
}
其实前面6个指令都很简单,关键在于后面2个指令,这个两个指令实际上就是才是逻辑和循环,这其中的核心就是跳转,就是把源代码的指针index的值进行改变,而这个改变是有条件的,下面就是我对这个逻辑的实现:
switch (b) {
case '[':
if (memory[point] == 0) {
while (source.charAt(index) != ']') {
index++;
}
}
break;
case ']';
if (memory[point] != 0) {
while (source.charAt(index) != '[') {
index--;
}
}
}
其实就是用while循环来检查代码,然后改变index的值,乍一看好像没毛病,是的,如果是下面这个输出hello, world!的代码
+++++ +++++ initialize counter (cell #0) to 10
[ use loop to set the next four cells to 70/100/30/10
> +++++ ++ add 7 to cell #1
> +++++ +++++ add 10 to cell #2
> +++ add 3 to cell #3
> + add 1 to cell #4
<<<< - decrement counter (cell #0)
]
> ++ . print 'H'
> + . print 'e'
+++++ ++ . print 'l'
. print 'l'
+++ . print 'o'
> ++ . print ' '
<< +++++ +++++ +++++ . print 'W'
> . print 'o'
+++ . print 'r'
----- - . print 'l'
----- --- . print 'd'
> + . print '!'
> . print '\n'
确实没有毛病,但是对于下面这个乘法
,>,,>++++++++[<------<------>>-]
<<[>[>+>+<<-]>>[<<+>>-]<<<-]
>>>++++++[<++++++++>-],<.>.
就有问题了,因为这里面有个多重循环,如何界定“[”对应的“]”就不准确了,这才是整个程序的难点。
我们先来看看,如何理解对应这个意思,我的理解为:如果字符[]中间有n个[和n个]字符,那么他们是一对,n>=0
按照这个理论,我们开始写代码
先定义一个计数器,出现[就加1,出现]就减1,这和上面那个理解是一个意思,只不过简化为一个变量表示
int count = 0;
然后在定义一个index移动的偏移量
int offset = 0;
然后开始跳转
// 起始偏移量为1,一直到代码结束
for (int offset = 1; index + offset < source.length(); offset++) {
// 找到跳转字符]且n相等,也就是count=0,计算index的值并跳出循环
if (source.charAt(index + offset) == ']' && count == 0) {
index += offset;
break;
}
if (source.charAt(index + offset) == '[') count++;
if (source.charAt(index + offset) == ']' && count != 0) count--;
}
下面是完整的代码:
package com.github.xiongdi.study;
public class BrainfuckInterpreter {
private byte[] memory = new byte[1000];
private int point = 0;
public void run(String source) throws Exception {
for (int index = 0; index < source.length(); index++) {
switch (source.charAt(index)) {
case '>':
point++;
break;
case '<':
point--;
break;
case '+':
memory[point]++;
break;
case '-':
memory[point]--;
break;
case '.':
System.out.print((char) memory[point]);
break;
case ',':
memory[point] = (byte) System.in.read();
break;
case '[':
if (memory[point] == 0) {
int count = 0;
for (int offset = 1; index + offset < source.length(); offset++) {
if (source.charAt(index + offset) == ']' && count == 0) {
index += offset;
break;
}
if (source.charAt(index + offset) == '[') count++;
if (source.charAt(index + offset) == ']' && count != 0) count--;
}
}
break;
case ']':
if (memory[point] != 0) {
int count = 0;
for (int offset = 1; index - offset >= 0; offset++) {
if (source.charAt(index - offset) == '[' && count == 0) {
index -= offset;
break;
}
if (source.charAt(index - offset) == ']') count++;
if (source.charAt(index - offset) == '[' && count != 0) count--;
}
}
break;
}
}
}
}