Register
Email: Password:
Forum » BF.

BF.

Narvius 13 years ago
So, now that Reverse Polish Notation is gutted, let's start writing brainfuck interpreters

file = IO::readlines(ARGV[0].join.gsub(/[^\+\-\<\>\[\]\.\,]/, "")
ARGV.clear
reader, ptr, data, ret_stack, skipping = 0, 0, [0], [], nil

until reader == file.size
unless skipping
case file[reader]
when ">" then ptr += 1; data.concat([0] if ptr == data.size
when "<" then ptr -= 1 unless ptr.zero?
when "+" then data[ptr] = (data[ptr].nil? ? 0 : data[ptr] + 1
when "-" then data[ptr] = (data[ptr].nil? ? 0 : data[ptr] - 1
when "[" then eval(data[ptr].zero? ? "skipping = ret_stack.size + 1" : "skipping = nil"); ret_stack.push(reader)
when "]" then eval(data[ptr].zero? ? "ret_stack.pop" : "reader = ret_stack.last")
when "." then print(data[ptr].chr)
when "," then data[ptr] = gets.getbyte(0)
end
else
case file[reader]
when "[" then ret_stack.push(reader)
when "]" then ret_stack.pop
end
skipping = nil if skipping > ret_stack.size
end
reader += 1
end


Ruby, obviously. I thought about reducing all variable names to one letter, as it is possible unambiguosly (...almost!). It's not like it makes a difference, though.
In this implementation, there are 1024 memory cells. It wouldn't have been a big problem to make it dynamic, just some nil checks or one array overload, but whatever.
It takes a filename as command line parameter, by the way. Which should be obvious.

[Edit]
Oke. Slightly edited it. Arbitrary amount of data cells.
#
E_net4 13 years ago
Huh... see, I'd love to help, but my brain isn't f'ed enough to learn the language. *poker face*
#
MageKing17 13 years ago
A quick Python version (now tested):
import sys

class _Getch: # A cross-platform method to read a single character, obtained from activestate.com
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()

class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import tty, sys, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch

class _GetchWindows:
def __init__(self):
import msvcrt

def __call__(self):
import msvcrt
return msvcrt.getch()

getch = _Getch()


if len(sys.argv) != 2: # If they didn't supply a file to parse...
file = sys.stdin #...parse standard input.
else:
file = open(sys.argv[1], "r")

stack = []
data = [0]
ptr = 0
skip = 0

while True:
cur = file.read(1)
if not cur: break # We hit the end of the file.
elif not skip:
if cur == ">":
ptr += 1
if ptr >= len(data):
data.append(0)
elif cur == "<":
if ptr > 0: ptr -= 1
else:
data.insert(0, 0)
elif cur == "+":
data[ptr] += 1
elif cur == "-":
data[ptr] -= 1
elif cur == ".":
print(chr(data[ptr], end="") # Don't add a newline after printing.
elif cur == ",":
data[ptr] = ord(getch())
elif cur == "[":
stack.append(file.tell())
if data[ptr] == 0: skip = len(stack)
elif cur == "]":
if data[ptr] == 0: stack.pop()
else: file.seek(stack[-1]
else:
if cur == "[":
stack.append(file.tell())
elif cur == "]":
stack.pop()
if skip > len(stack): skip = 0


EDIT: Fixed two bugs (the "extend data field rightwards" code needed to do a >= check, not a > check, and the file.seek() call popping the stack was causing an error when it ran into the closing bracket again) and successfully ran the following program (obtained from wikipedia):
+++++ +++++             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'
#
Pete 13 years ago
Y'all people make whatever Java knowledge I have feel embarrassingly insignificant in comparison.

Anyway, while on the subject of torturing logic and such things: http://www.minecraftforum.net/viewtopic.php?f=35&t=78889
Go.
#
Narvius 13 years ago
I extended the hello world program slightly, for testing purposes.
,
[
>
+++++ +++++ initialize counter (cell #1) to 10
[ use loop to set the next four cells to 70/100/30/10
> +++++ ++ add 7 to cell #2
> +++++ +++++ add 10 to cell #3
> +++ add 3 to cell #4
> + add 1 to cell #5
<<<< - decrement counter (cell #1)
]
> ++ . print 'H'
> + . print 'e'
+++++ ++ . print 'l'
. print 'l'
+++ . print 'o'
> ++ . print ' '
<< +++++ +++++ +++++ . print 'W'
> . print 'o'
+++ . print 'r'
----- - . print 'l'
----- --- . print 'd'
> + . print '!'
> . print '\n'
[[-]<] clear all previous cells stopping at cell #1 (which is empty)
< -
]
It also tests whether the input thingie and [[ works correctly. Basically, it loops the Hello World [input] times, for example 97 times if the input is 'a'.
#
MageKing17 13 years ago
I ran that and hit "space", here's what I got:
K:\Games\Python Tomfoolery>BF_int.py bf_prog_2.txt
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

K:\Games\Python Tomfoolery>
It works. (Yes, that's 32 lines. I counted.)
#
E_net4 13 years ago
A Minecraft CPU... it finally happened. :O
#
Vacuus 13 years ago
C++ version:
#include <iostream>
#include <deque>
#include <stack>
#include <iterator>
#include <fstream>

class Brainfuck
{
private:
typedef std::basic_istream<char>& inputStream;
typedef std::basic_ostream<char>& outputStream;

inputStream commandStream; //The command input stream
std::istream_iterator<char> it; //Input iterator.

//Brainfuck-specific streams
outputStream dataOutput; //Stream to dump output to (such as std::cout)
inputStream dataInput; //Stream to receive user input from (such as std::cin)

//Deque provides efficient insertion/deletion of elements at both ends but does not allocate memory in a singular
//contiguous chunk like a vector, hence standard pointer arithmetic cannot be used.
//Was getting some seg faults when running the 'beer.b' program; switching to unsigned char
//solved this and also prevented data from dropping below 0.
std::deque<unsigned char> data;
std::deque<unsigned char>::iterator dataIterator;

std::stack<std::streampos> jumpStack;
public:
Brainfuck(inputStream cmdStream, outputStream dataOutStream, inputStream dataInStream)
: commandStream(cmdStream), it(cmdStream), dataOutput(dataOutStream), dataInput(dataInStream)
{
dataIterator = data.begin();
}

void operator() ()
{
int skipDepth = 0;
std::istream_iterator<char> end; //Default ctor creates an 'invalid' iterator.

for (; it != end; ++it)
{
if (skipDepth != 0)
{
if (*it == '[')
{
jumpStack.push(commandStream.tellg());
}
else if (*it == ']')
{
jumpStack.pop();
}

if (jumpStack.size() < skipDepth) //Last bracket set found...
{
skipDepth=0;
}
continue;
}

switch (*it)
{
case '+':
++(*dataIterator);
break;
case '-':
--(*dataIterator);
break;
case '>':
if (++dataIterator == data.end()) //Reached the end of the deque
{
data.push_back(0);
}
break;
case '<':
if (--dataIterator == data.end())
{
data.push_front(0);
}
break;
case '.':
dataOutput<<*dataIterator;
break;
case ',':
*dataIterator = dataInput.get(); //Technically this could overflow; get() is casted to an int first but unless
//your native language is mandorin I doubt it'll be a problem.
//synchronise the rdbuf; this is incase you tried to be clever and entered more than one byte into the stream,
//syncing will discard all unread characters.
dataInput.sync();
break;
case '[':
jumpStack.push(commandStream.tellg());
if (*dataIterator == 0)
{
skipDepth = jumpStack.size();
}
break;
case ']':
if (*dataIterator == 0)
{
jumpStack.pop();
}
else
{
commandStream.seekg(jumpStack.top());
}
break;
}
}
}

};

int main(int argc, char** argv)
{
if (argc != 2)
{
std::cout<<"USAGE: brainfuck <file>\nNo file specified";
return -1;
}
std::ifstream file(argv[1];

if ( !file.good())
{
std::cout<<"USAGE: brainfuck <file>\nBad file name";
return -1;
}

//Using streams like this means you can redirect output or input to another file or another user over the network etc.
Brainfuck bf(file, std::cout, std::cin);
bf();

return 0;
}
pastebin
I've found that the 'hello world' isn't sufficient to entirely test the brainfuck language, would you believe it. I ended up using another program I found here that prints the '99 bottles of beer on the wall' song. It worked fine on MK's implementation (albeit very slowly) but mine would seg fault. Fixed it but if you wish to test your own interpreter as well, download and run this.

Anyway, I'm not entirely happy with the jump stack and I'm not overly fond of switch statements either. I think there's room for improvement with either, but I dunno, it probably doesn't matter for something as syntactically simple as BF and I don't really have the time to play with it at the moment.

Edit: Ooops, forgot that the , command was still reading in buffered input; fixed now.
#
E_net4 13 years ago
Well, I would tinker with Linoleum, but it works on N-flat addressing, which makes byte manipulation a bit harder without libraries... still...
#
Forum » BF.

Post Reply


Your email:
Your name: