Brainf**k while waiting for a flight

Warning: NSFW language.

Brainfuck is a Turing-complete programming language consisting of eight commands, each of which is represented as a single character.

> Increment the pointer.
< Decrement the pointer.
+    Increment the cell at the pointer.
-    Decrement the cell at the pointer.
.    Output the ASCII value of the cell at the pointer.
,    Input a byte and store it in the cell at the pointer.
[    Jump forward past the matching ] if the cell at the current pointer is zero.
]    Jump backward to the matching [ unless the cell at the current pointer is zero.

Having arrived almost 3 hours early to JFK, flying back to Cincinnati, I spent the time coding up a Python script which inputs a string and outputs a Brainfuck source code which, when run with a Brainfuck interpreter, outputs said string. So for example:

to_brainfuck "Hello, World!"

Will output:

++++++++++[>+++>++++++>++++++++>++++++++++>+++++++++++<<<<<-]>>++++++++
++++.>>+.+++++++..>+.<<<<++++++++++++++.------------.>>>>++++++++.----
----.+++.<.--------.<<<+.-----------------------.

 

The horror above is what Brainfuck source code looks like. When you run the above code with a Brainfuck interpreter, it will print "Hello, world!".

Brainfuck interpreters and compilers can be found here. Ubuntu has a Brainfuck interpreter called bf.

Probably not the best code I wrote, could use some honing. Still, it served the purpose of killing a couple of hours.

#!/usr/bin/env python
import sys

class bf:

    def __init__(self,format_bf=True):
        """
        Initiate brainfuck code string. Pointers are initiated to the
        following values, with their ascii equivalents shown
        ptr0 = 10 ptr0 is used as a loop counter
        ptr1 = 30
        ptr2 = 60  @
        ptr3 = 80  #P
        ptr4 = 100 #d
        ptr5 = 110 #n
        """
        self.bf_code = ''
        self.bf_code += "++++++++++[>+++>++++++>++++++++>++++++++++>+++++++++++<<<<<-]"        

        # Index: cell number. Value: cell value.
        self.ptrs = {1: 30, 2: 60, 3: 80, 4: 100, 5:110}
        self.ptr_idx = 0 # which pointer is being used
        self.format_bf = format_bf # Format the bf code. Default True.

    def string_bf(self,instring):
        # Accepts a string. Outputs bf code which prints that string
        # when run with a bf interpreter
        for c in instring:
            self.bf_code += self.to_bf(c)
        # add a newline 
        self.bf_code += self.to_bf(chr(10))
        if self.format_bf:
            self.bf_code = self._format_bf_code(self.bf_code) 
        return self.bf_code

    def _format_bf_code(self,bf_code):
        # Format the bf source code to 70 chars / line
        outstr = ''
        for i,c in enumerate(bf_code):
            if i % 70 == 0 and i > 0:
                outstr += '%s\n' % c
            else:
                outstr += c
        return outstr

        
    def to_bf(self,c):
        # accept a character c, generate the bf code to print that
        # character.

        # increment / decrement the data pointer
        if c < '@':
            ptr_target = 1
        elif c >= '@' and c < 'P':
            ptr_target = 2
        elif c >= 'P' and c < 'd':
            ptr_target = 3
        elif c >= 'd' and c <'n':
            ptr_target = 4
        else:
            ptr_target = 5
        ptr_inc_str = self.increment_ptr(ptr_target)
        # Now increment / decrement the value which the pointer points
        ascii_target = ord(c)
        ascii_val = self.ptrs[self.ptr_idx]
        inc_val, inc_val_str = self.increment_val(ascii_val, ascii_target)
        self.ptrs[self.ptr_idx] += inc_val
        return ptr_inc_str+inc_val_str+'.'

    def increment_val(self,ascii_val, ascii_target):
        inc_val = ascii_target - ascii_val
        if inc_val < 0:
            inc_val_str = '-'*abs(inc_val)
        elif inc_val > 0:
            inc_val_str = '+'*abs(inc_val)
        else:
            inc_val_str = ''
        return inc_val, inc_val_str
    

    def increment_ptr(self,ptr_target):
        ptr_inc = ptr_target - self.ptr_idx
        if ptr_inc < 0:
            ptr_str = '<'*abs(ptr_inc)
        elif ptr_inc > 0:
            ptr_str = '>'*ptr_inc
        else:
            ptr_str = ''
        self.ptr_idx += ptr_inc
        return ptr_str

if __name__ == '__main__':
    my_bf = bf()
    if sys.argv[1] == '-f':
        intext = file(sys.argv[2]).read()
    else: 
        intext = sys.argv[1]
    o = my_bf.string_bf(intext)
    sys.stdout.write("%s\n" % o)
    

To run:

chmod +x bf_string.py
./bf_string.py "Brainfork is awesome!" > mycode.bf # generate Brainfuck code into mycode.bf
bf mycode.bf # The brainfuck interpreter bf
Brainfork is awesome!

And mycode.bf will contain:

++++++++++[>+++>++++++>++++++++>++++++++++>+++++++++++<<<<<-]>>++++++.>
>>++++.<<+++++++++++++++++.>+++++.>----.<---.>+.+++.<+++++.<<<++.>>>--
.>+.<<<<.>>.>>++++.<----.>----.----.<++++++++.--------.<<<+.----------
-------------.

You can also run it with the -f option, where the input string will be read from a file.
 

UPDATE: and here is a brainfuck interpreter, written in Python.
UPDATE II: Following Vincent's comment, here is a fixed version of the interpreter. This time it should work with nested loops. Thanks Vincent.

#!/usr/bin/env python
import sys
class BfInterpreter:
    def __init__(self,inpath):
        self.iptr = 0
        self.cells =[0]
        self.cmdptr = 0
        self.infile = file(inpath)
        self.bfcode = self.infile.read()
        self.cloop_stack = [] # location of current startloop in bf code
        self.ploop_stack = [] # which ptr is current loopcounter
        self.loop_ended = False # Indicates if a loop counter just
                                # reached zero
    def inc_ptr(self):
        self.iptr += 1
        if self.iptr > len(self.cells) - 1:
            self.cells.append(0)
    def dec_ptr(self):
        self.iptr -= 1
        if self.iptr <= -1:
            raise ValueError,"negative pointer"
    def inc_cell(self):
        self.cells[self.iptr] += 1
    def dec_cell(self):
        self.cells[self.iptr] -= 1
        # Check if this is a loop counter
        if self.ploop_stack:
            if self.ploop_stack[-1] == self.iptr and \
               self.cells[self.iptr] == 0:
                self.loop_ended = True
    def start_loop(self):
        self.cloop_stack.append(self.cmdptr)
        self.ploop_stack.append(self.iptr)
    def end_loop(self):
        if self.cells[self.iptr] > 0:
            if not self.cloop_stack:
                raise ValueError,"no startloop character found"
            else:
                self.cmdptr = self.cloop_stack[-1]
        elif self.cells[self.iptr] == 0 and self.loop_ended:
            self.loop_ended = False
            self.cloop_stack.pop()
            self.ploop_stack.pop()
            
    def putc(self):
        sys.stdout.write("%s" % chr(self.cells[self.iptr]))
    def getc(self):
        self.cells[self.iptr] = ord(sys.stdin.read(1))
    def run_bf(self):
        self.cmdptr = -1
        while True:
            self.cmdptr += 1
            if self.cmdptr >= len(self.bfcode):
                break
            cmd = self.bfcode[self.cmdptr]
            # print cmd,
            if cmd == '>':
                self.inc_ptr()
            elif cmd == '<':
                self.dec_ptr()
            elif cmd == '+':
                self.inc_cell()
            elif cmd == '-':
                self.dec_cell()
            elif cmd == '[':
                self.start_loop()
            elif cmd == ']':
                self.end_loop()
            elif cmd == '.':
                self.putc()
            elif cmd == ',':
                self.getc()
if __name__ == '__main__':
    bf = BfInterpreter(sys.argv[1])
    bf.run_bf()

To run, download the file, name it (say, pybf) and then:

chmod +x pybf
./pybf bf_source_code_file.bf
Share and Enjoy:
  • Fark
  • Digg
  • Technorati
  • del.icio.us
  • StumbleUpon
  • Facebook
  • Reddit
  • Twitter
  • FriendFeed
  • PDF
  • email
  • Print
  • Google Bookmarks

4 Responses to “Brainf**k while waiting for a flight”

  1. Vincent says:

    You might want to use a stack to keep track of loops, with start_loop pushing the current position to the stack and end_loop popping the latest start_loop position from the stack, otherwise your code won’t work for embedded loops.

  2. Iddo says:

    Oh wow, nice catch. Will try to fix soon.

  3. David House says:

    So why did you decide to do this more complicated thing with multiple pointers, rather than just having one cell which you incremented/decremented to the required value and then outputted? Was it just to keep the resulting brainfuck shorter?

  4. Iddo says:

    @David. Yes, to keep it shorter. I basically have 4 pointers pointing to 4 most often used area in ascii table: whitespace, numbers, lowercase and uppercase. If a character to print is in one of those areas, the value at the proper pointer is incremented and printed.