#ifndef BFPP_H
#define BFPP_H

#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>

#define CELL_COUNT 30000

#define OP(x, y) BfppProgram& x() { s.push_back(y); return *this; }

struct BfppProgram {
  OP(operator*,  '>')
  OP(operator&,  '<')
  OP(operator+,  '+')
  OP(operator-,  '-')
  OP(operator!,  '.')
  OP(operator~,  ',')
  OP(operator--, '[')
  OP(operator++, ']')
  std::string s;
};

struct BfppInterpreter {
  BfppInterpreter(const BfppProgram& program) {
    memset(cells, 0, CELL_COUNT);
    i = 0;
    std::string::const_reverse_iterator rbegin = program.s.rbegin();
    std::string::const_reverse_iterator rend = program.s.rend();
    for (std::string::const_reverse_iterator j = rbegin; j != rend; ++j) {
      switch (*j) {
      // commands that go out of bounds are silently ignored
      case '>': if (i != (CELL_COUNT - 1)) ++i; break;
      case '<': if (i != 0) --i; break;
      case '+': ++cells[i]; break;
      case '-': --cells[i]; break;
      case '.': putchar(cells[i]); break;
      case ',':
	// EOF => no change
	int ch;
	if ((ch = getchar()) != EOF) cells[i] = ch;
	break;
      case '[':
	if (cells[i] == '\0') {
	  int depth = 0;
	  ++j;
	  while (j != rend) {
	    if (*j == '[') ++depth;
	    else if (*j == ']') {
	      if (depth == 0) break;
	      --depth;
	    }
	    ++j;
	  }
	}
	break;
      case ']':
	if (cells[i] != '\0') {
	  int depth = 0;
	  if (j != rbegin) --j;
	  while (j != rbegin) {
	    if (*j == ']') ++depth;
	    else if (*j == '[') {
	      if (depth == 0) break;
	      --depth;
	    }
	    --j;
	  }
	}
	break;
      default: break;
      }
    }
  }
  unsigned char cells[CELL_COUNT];
  size_t i;
};

#define bfpp (BfppInterpreter)
#define end_bfpp (BfppProgram());

#endif
