Many Brainfuck implementations I've come across generally are obtuse, verbose or over-the-top. But I've always seen Brainfuck as "easy to implement", especially for beginners.
My original need for this was to be able to have an actual Brainfuck "machine" that executed instructions to aid with code generation. However, this should also work fine on its own.
How can the "API" be improved?
#include <iostream>
#include <stack>
// Mini BF interpreter
struct BF
{
int tape[30000] = {0};
int *ptr = tape;
std::string program = "";
/* The index will be used to control program flow
* when encountering brackets.
*/
std::size_t index = 0;
std::stack<int> brackets;
BF(std::string program = "")
: program(program)
{
}
void addc(char c)
{
program += c;
}
void adds(std::string s)
{
program += s;
}
void run()
{
while (next());
}
void print_memory()
{
for (int i = 0; i < 100; ++i)
std::cout << tape[i] << " ";
std::cout << "\n";
}
bool next()
{
if (index < program.size())
{
token(program[index++]);
return true;
}
return false;
}
void token(char c)
{
switch (c)
{
case '+': ++*ptr; break;
case '-': --*ptr; break;
case '>': ++ptr; break;
case '<': --ptr; break;
case '[':
if (*ptr)
{
// Save position of bracket
brackets.push(index);
} else {
// Skip to the matching bracket
index = program.find(']');
}
break;
case ']':
if (*ptr)
{
// Jump to the last bracket
index = brackets.top();
} else {
// Loop is finished
brackets.pop();
}
break;
case '.':
std::cout << (char)*ptr;
break;
default:
break;
}
}
};
int main()
{
std::string program{"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."};
BF bf{program};
bf.run();
bf.print_memory();
}
-
1\$\begingroup\$ The whole point about Brainfuck being created in the first place was that it should need an as small interpreter as possible, so you are correct that Brainfuck is supposed to be "easy to implement". I guess some of us around here (erhm, me...) just like to take things to the next level ;) \$\endgroup\$Simon Forsberg– Simon Forsberg2015年09月06日 13:58:36 +00:00Commented Sep 6, 2015 at 13:58
1 Answer 1
Your program is small and well written, so there's not much else to change, IMO. My comments are mostly on code aesthetics.
Your
tape
is a fixed size array, but you are not checking if you ran out of tape/memory. A very big program could crash your interpreter. This is a simple fix that might be worth it, it shouldn't add too much complexity to the code. One simple way is to replace every raw increment/decrement ofptr
by a function that checks if you've ran out of space. Then "safely" terminate the interpreter with a friendly error message, instead of a segfault.Also, an array of
30000
int
s is a lot of memory. Declaring an instance of the interpreter as a local function-level variable might stress the program stack. Maybe replace it by a dynamically allocated container like astd::vector
.No need to explicitly initialize the string
std::string program = ""
. Astd::string
is a class, so it has a constructor that default initializes it to empty.In the constructor, use
std::move
to move the input string, instead of copying it. When you pass by value, C++ always copies the data. You'll want to avoid that for a long string such as the program text.BF(std::string program = "") : program(std::move(program)) { }
addc
andadds
are not used. Nevertheless, spell out their names:add_char
andadd_string
.print_memory
only prints the first100
values oftape
. You should comment the reason for that, otherwise, readers will assume it was a mistake, sincetape
is a lot larger. An alternative it to take the number of entries to print as a parameter of the function. Also relevant, C++ has the concept ofconst
member functions, which are functions guaranteed not to change any member data. That's the case withprint_memory
, so it should be const:void print_memory() const { /* ... */ } ^^^^^
Make your interpreter a
class
and turn the internal methods and dataprivate
. In a structure, all fields are accessible to users of the object. It is better to hide implementation details and also to prevent users from messing with the internal interpreter states. Only leave the interface methods likerun()
in the public section of your class.Very minor thing, but I'd change this:
while (next());
towhile (next()) { }
to make it more visible that that's an empty loop.Another tiny thing: I usually dislike short names or abbreviations, so I'd name the class
BFInterpreter
instead.
Lastly, there are two other very good implementations here on CR you should take a look:
Explore related questions
See similar questions with these tags.