3
\$\begingroup\$

Need to clear my brain.

BF interpretor in C++.

bf.h

#include <vector>

class BF
{
    std::vector<char>           memory;
    std::size_t                 dp;

    template<typename I>
    I findMatchingClose(I start);

    public:

        BF(std::size_t defaultMemorySize = 4096);
        void reset()            {dp = 0;}
        template<typename I>
        // Not sure how to specify that `I` is limited to allow random access.
        // Suppose I could re-write to allow slips back and forward.
        void run(I begin, I end);
};

bf.cpp

#include "bf.h"
#include <iostream>

BF::BF(std::size_t defaultMemorySize)
    : memory(defaultMemorySize, 0)
    , dp(0)
{}

template<typename I>
I BF::findMatchingClose(I loop)
{
    std::size_t openCount = 1;
    for (++loop; openCount != 0; ++loop) {
        if (*loop == '[') {
            ++openCount;
        }
        else if (*loop == ']') {
            --openCount;
        }
    }
    return loop;
}

template<typename I>
void BF::run(I begin, I end)
{

  /*
    >  Increment the data pointer by one (to point to the next cell to the right).
    <  Decrement the data pointer by one (to point to the next cell to the left).
    +  Increment the byte at the data pointer by one.
    -  Decrement the byte at the data pointer by one.
    .  Output the byte at the data pointer.
    ,  Accept one byte of input, storing its value in the byte at the data pointer.
    [  If the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching ] command.
    ]  If the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching [ command.
  */

    std::vector<I>    stack;
    for (I loop = begin; loop != end; ++loop) {
        switch (*loop) {
            case '>':   dp = (dp + 1) % memory.size();  break;
            case '<':   dp = std::max(memory.size() -1, dp - 1); break;
            case '+':   ++memory[dp];break;
            case '-':   --memory[dp];break;
            case '.':   std::cout << memory[dp];break;
            case ',':   std::cin  >> memory[dp];break;
            case '[':   if (memory[dp]) {
                            // Enter the loop.
                            // So remember where to jump back to.
                            stack.emplace_back(loop);
                        }
                        else {
                            // Ignore the loop.
                            // So find the end (the ++loop) will take
                            // us to the next instruction.
                            loop = findMatchingClose(loop);
                        }
            case ']':   if (memory[dp]) {
                            // Next loop iteration.
                            // Set loop to start of the inner most loop.
                            // Note ++loop will take us to the first instruction.
                            loop = stack.back();
                        }
                        else {
                            // Exit the inner most loop.
                            stack.pop_back();
                        }
                        break;
            default:
                continue;
        }
    }
}

main.cpp

#include "bf.h"
#include <iostream>
#include <fstream>
#include <iterator>

int main(int argc, char* argv[])
{
    std::cout << (-11 % 10) << "\n";

    if (argc <= 1) {
        std::cerr << "No input file\n";
        return -1;
    }

    BF  bf;

    for (int loop = 1; loop < argc; ++loop) {
        std::ifstream   file(argv[loop]);
        std::string     program(std::istream_iterator<char>{file}, std::istream_iterator<char>{});

        bf.reset();
        bf.run(std::begin(program), std::end(program));
    }

}
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$
    template<typename I>
    // Not sure how to specify that `I` is limited to allow random access.
    // Suppose I could re-write to allow slips back and forward.
    void run(I begin, I end);

I think you're looking for

#include <iterator>
    template<std::random_access_iterator I>
    void run(I begin, I end);

That said, I'd include <ranges> and write

void run(std::ranges::random_access_range auto range);

I think that the template definitions in bf.cpp should be in the header file, so that they can be instantiated by client code.


BF::BF(std::size_t defaultMemorySize)

Is that really a default size for this instance, or is it the actual size? I don't see any way to change the size after this point.


findMatchingClose() is unbounded for malformed programs - there's nothing to stop loop reaching its matching end iterator, and causing Undefined Behaviour.


Is the fallthrough from case '[' to case ']' intentional? If it is, then please add [[fallthrough]] and a comment to explain why.


        case '<':   dp = std::max(memory.size() -1, dp - 1); break;

Missing include of <algorithm>. I don't think this is a very clear wraparound implementation (I only realised that dp-1 becomes greater than memory.size() when dp is zero because of the preceding modular calculation - I'd find it clearer to have (dp > 0 ? dp : memory.size()) - 1 or similar).


                    else {
                        // Exit the inner most loop.
                        stack.pop_back();

I don't see any check for underflow here - needs a comment to explain why that can't happen.


std::cout << (-11 % 10) << "\n";

Leftover diagnostic?


if (argc <= 1) {
    std::cerr << "No input file\n";
    return -1;
}

Normally, we return small positive values from main() such as EXIT_FAILURE. Negative values are likely to be transformed by the runtime environment and OS; for example, POSIX specifies that

only the least significant 8 bits (that is, status & 0377) shall be available from wait() and waitpid();

But why are we failing in this case at all? Couldn't we read from std::cin? That would be a user's reasonable expectation. (I guess the program couldn't then use the , command, of course.)

\$\endgroup\$
3
  • \$\begingroup\$ Is the fallthrough from case '[' to case ']' intentional? No. Opps. \$\endgroup\$ Nov 11 at 17:14
  • \$\begingroup\$ Leftover diagnostic? Yes. I was checking what happened with modulo of negative values. Opps. \$\endgroup\$ Nov 11 at 17:15
  • \$\begingroup\$ Thanks. Even simple programs that seem obviously correct have issues. :-) \$\endgroup\$ Nov 11 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.