This is a simple brainfuck interpreter I've written in C++. It takes a path to a brainfuck source file as a command line argument. The program uses a tape size of 30000 and cell size of one byte. Decrementing a cell's value under 0 will wrap it to 255 and vice versa.
I would really appreciate any kind of feedback, especially on how the existing code could be improved.
I'm using Apple Clang/LLVM and I've been compiling with the command:
clang++ -pedantic -O3 -std=c++17 brainfuck.cpp -o brainfuck
brainfuck.cpp
:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
/* Read the source file into a char vector, and return the vector. */
std::vector<char> read_source(std::string&& file_path)
{
std::ifstream file(file_path);
std::vector<char> source;
char c;
while (file.get(c)) {
source.push_back(c);
}
return source;
}
/* Remove all but chars, which don't have meaning in brainfuck, from the source file.
* This removes whitespace, newlines and comment chars. */
void filter_chars(std::vector<char>& source)
{
auto new_end = std::remove_if(source.begin(), source.end(),
[](char c) { return !std::strchr("<>+-[],.", c); });
source.erase(new_end, source.end());
}
/* The actual interpreter. Goes through the source char by char and
* acts according to brainfuck's rules. */
void interpret(std::vector<char>& source)
{
std::vector<unsigned char> tape(30000, 0);
int h = 0; // the tape head
int brace_count = 0; // keeps track of nested braces
int i = 0; // the index to read the source
while (i < source.size()) {
switch (source[i]) {
case '<':
--h;
break;
case '>':
++h;
break;
case '+':
++tape[h];
break;
case '-':
--tape[h];
break;
case '.':
std::cout << tape[h] << std::flush;
break;
case ',':
std::cin >> tape[h];
break;
case '[':
if (tape[h] == 0) {
++brace_count;
while (source[i] != ']' || brace_count != 0) {
++i;
if (source[i] == '[') {
++brace_count;
}
else if (source[i] == ']') {
--brace_count;
}
}
}
break;
case ']':
if (tape[h] != 0) {
++brace_count;
while (source[i] != '[' || brace_count != 0) {
--i;
if (source[i] == ']') {
++brace_count;
}
else if (source[i] == '[') {
--brace_count;
}
}
}
break;
}
++i;
}
}
int main(int argc, char* argv[])
{
if (argc > 1) {
std::vector<char> source = read_source(argv[1]);
filter_chars(source);
interpret(source);
return 0;
}
else {
std::cout << "Expected a brainfuck source file as a command line argument.\n";
std::cout << "Terminating the program." << std::endl;
return 1;
}
}