#
# bf_interpreter.py - a class implementing the brainf**k machine
#
#
# Usage: See the end of this file for an example.
#
# Tape and cells are unsigned chars which are cyclic (255 -> 0 etc.)
# so minus one never crops up, instead we loop back to 255
#
#
from sys import stdout

class BrainFeck:
    """
        A python class implementing a brainf**k machine.

        Usage:
            a = BrainFeck(TapeSize) - Init instance 'a' with tape (our cells) length TapeSize
            a.run(X)                - run, where X is a string with our instructions in.
    """
    def __init__(self, TapeSize=255):
        # Length of 'tape'
        self.tape = [0] * TapeSize
        # Figured I'd use the size of the tape in case it's different from the
        # size we requested it to be. Chances are though that should there be a
        # discrepancy, the whole thing would stop. What the hell.
        self.tapesize = len(self.tape)
        self.pointer = 0
        self.inspointer = 0
        # To do with the '[' and ']' commands
        self.stack = []

    def pointerleft(self):
        self.pointer -= 1
        self.pointer %= self.tapesize

    def pointerright(self):
        self.pointer += 1
        self.pointer %= self.tapesize

    def incCell(self):
        self.tape[self.pointer] = (self.tape[self.pointer] + 1) % 255

    def decCell(self):
        self.tape[self.pointer] = (self.tape[self.pointer] - 1) % 255

    def plot(self):
        # Plot the cell contents as long as it's not one of those
        # control character things.
        if self.tape[self.pointer] > 31:
            stdout.write(chr(self.tape[self.pointer]))
        else:
            stdout.write('.')

    def input(self):
        tmp = raw_input(',:')
        if tmp:
            self.tape[self.pointer] = ord(tmp[0])
        else:
            # Will just put in a zero if anything untoward has occured.
            self.tape[self.pointer] = 0

    def dostack(self):
        self.stack.append(self.inspointer)

    def dopop(self):
        if self.stack:
            if self.tape[self.pointer]:
                # Jump back to the matching bracket.
                # (inspointer is always incremented after every command, hence
                # the -1 part).
                self.inspointer = self.stack[-1] - 1
            else:
                # The loop has finished. Remove the jump from the stack.
                null = self.stack.pop()

    def do(self, command):
        # Instead of iterating through a load of 'if' statements,
        # functions all hang out in a dict, keyed by the corresponding
        # command char. Is it faster? Lud knows, but it's neater and
        # looks clever ;)

        cmdict =    {'<':   self.pointerleft,
                     '>':   self.pointerright,
                     '+':   self.incCell,
                     '-':   self.decCell,
                     '.':   self.plot,
                     ',':   self.input,
                     '[':   self.dostack,
                     ']':   self.dopop}

        function = cmdict.get(command, None)

        if function:
            function()
        self.inspointer += 1

    def run(self, CommandLine='.', MaxIterations=100):
        # Reset before running:
        self.__init__()
        self.inspointer = 0
        for n in range(0, MaxIterations):
            self.do(CommandLine[self.inspointer])
            # If we reach the end of the command line...
            if self.inspointer == len(CommandLine):
                break


if __name__ == '__main__':
    bf = BrainFeck()
    bf.run('''
            *
            >
           +++
         ++++[<+
          +++++
         +++++>-
       ]<.>++++[<+
        +++++>-]<
       .++++++++++
     +++..+++++++.[-
      ]>++++[<+++++
     +++>-]<.>+++++[
   <+++++++>-]<.>+++++
    +[<++++++>-]<+.++
   ++++++++.---------.
 ++++++++++.+.-------.--
  ----------.>+++[<++++
++>-]<.[-]>++++[<++++++++
          >-]<+
         .[-]+++
         +++++++.''', MaxIterations=2700)
