🔙

Brianfuck?

A golfing experience

Mads Ohm Larsen
February 2024

This is a post from my old blog, but one that I have given a couple talks about at Ruby meetups.


Imagine an endless stream of buckets.

🗑️ 🗑️ 🗑️ 🗑️ 🗑️ 🗑️ 🗑️

We can add, subtract, read, and write to each bucket.

The entire spec for the Brainfuck language is as follows:

+ # => Add one to current bucket
- # => Subtract one from current bucket
< # => Move "bucket-pointer" to the left
> # => Move "bucket-pointer" to the right
. # => Write byte from current bucket to STDOUT
, # => Read one byte from STDIN into current bucket
[ # => If current bucket is zero, go to matching ]
] # => If current bucket is non-zero, go back to matching [

# Everything else is comments.

The Hello world!\n program is thus written:

>++++++++[-<+++++++++>]<.                  # H  ( 72)
>>+>-[+]++>++>+++[>[->+++<<+++>]<<]>-----. # e  (101)
>->+++.                                    # l  (108)
.                                          # l  (108)
+++.                                       # o  (111)
>-.                                        #    ( 32)
<<+[>[+>+]>>]<--------------.              # w  (119)
>>.                                        # o  (111)
+++.                                       # r  (114)
------.                                    # l  (108)
--------.                                  # d  (100)
>+.                                        # !  ( 33)
>+.                                        # \n ( 10)

If we wanted, we could easily write a Brainfuck interpreter in Ruby 💎.

We start, by having it load the Brainfuck program from a file:

program = File.read(ARGV.first)

Now we can run it with

$ ruby bf.rb input.bf

With this it’s easy to go on, implementing each of the instructions from the spec above. The only problem is [ and ], where we need a depth counter, to count nested loops, and then move over these in a correct manner.

program = File.read(ARGV.first)
pc = 0
bp = 0
buckets = [0]
depth = 0
direction = 1

while pc < program.size
  case program[pc]
  when ">"
    bp += 1
    buckets[bp] ||= 0
  when "<"
    bp -= 1
  when "+"
    buckets[bp] += 1
  when "-"
    buckets[bp] -= 1
  when "."
    STDOUT.putc buckets[bp].chr
  when ","
    buckets[bp] = STDIN.getc.ord
  when "["
    if buckets[bp] == 0
      depth, direction = 1, 1
    end
  when "]"
    if buckets[bp] != 0
      depth, direction = 1, -1
    end
  end

  while depth > 0
    pc += direction
    depth += direction if program[pc] == "["
    depth -= direction if program[pc] == "]"
  end

  pc += 1
end

We end up with this 40 lines (650 bytes) program.

Now lets go golfing!

Code golf is a type of recreational computer programming competition in which participants strive to achieve the shortest possible source code that implements a certain algorithm. https://en.wikipedia.org/wiki/Code_golf

DISCLAIMER: Don’t do this to your production code. This is for fun and learning only!

We can mess alot around with different stuff, learning a lot doing so. Did you know that $<.read will read the file contents of a file given as argument? Did you also know that $><<"hello" will print "hello"? Or that t[?+] will return "+" if t is "+" and nil otherwise? (Thus enabling us to short circuit our statements)

When we are done goofing around, our Brainfuck interpreter will look something like this (only 234 bytes):

a,*b=$<.read,c=d=n=0
(t,m=a[d],-1
t[?>]&&b[c-=m]||=n
t[?<]&&c+=m
t[?+]&&b[c]-=m
t[?-]&&b[c]+=m
t[?.]&&$><<b[c].chr
t[?,]&&b[c]=STDIN.getc.ord
n==b[c]?t[?[]&&n=m=1:t[?]]&&n=1
(a[d+=m][?[]&&n+=m
a[d][?]]&&n-=m)until n<1
d+=1)while a[d]

This is indeed a Brainfuck! 🤣

And it messes up the syntax highlighter… Brilliant!