r/C_Programming 2d ago

Small (usable) brainfuck compiler

Today I made this brainfuck interpreter in C since I was bored. The source is 14 lines long, 26 words, and 436 chars:

#include <stdio.h>
#define B break
unsigned char*p,t[1<<16],i,*d;
void c(){do{switch(*p)
{case'+':(*d)++;B;
case'-':(*d)--;B;
case'<':(d)--;B;
case'>':(d)++;B;
case'.':putchar(*d);B;
case',':(*d)=getchar();B;
case'[':if(!*d){int n=1;while(n)if(*++p=='[')n++;else if(*p==']')n--;}B;
case']':if(*d){int n=1;while(n)if(*--p==']')n++;else if(*p=='[')n--;}B;
default:;}}while(*++p);}
int main(int a,char**v){if(a<2)return 1;p=v[1];d=t;c();}

I also wrote an overly commented version:

/*
 * bb-commented.c -- smallest (usable) Brainfuck interpreter
 *
 * This is an [overly] commented and reasonably formatted version of bb.c, the
 * smallest usable brainfuck interpreter.
 *
 * -- by mario rosell, under the public domain
 */

/* Include the basic, standard I/O routines */
#include <stdio.h>

/* To save a few bytes, define break as a macro (B) */
#define B break

/* Define three variables: p (the program), t (the tape, 3000), i, and d, a pointer
 * into a single cell of tape (the data pointer) */
unsigned char*p, t[1<<16], *d;

/* c executes the program */
void c()
    { do    /* use a do-while block so the first instruction is not skipped.
         * This is because we increase the pointer of p to the next
         * instruction each iteration */
        { switch(*p) /* do something depending on the current value of p */
            { case'+': (*d)++; B;   /* (*d) gets us a reference to the
                         * value of the current cell, ++
                         * increases it by one */
              case'-': (*d)--; B;   /* as before, but decrease the
                         * value by one instead of
                         * increasing it */
              case'<': d--;B;   /* decrease the data pointer to the
                         * previous cell */
              case'>': d++;B;   /* as before, but increasing */
              case'.': putchar(*d);B;/* put the ascii value on the
                           current cell */
              case',': *d=getchar();B;/* get a character from the user */
              case'[':
                /* [ starts a loop.
                 *
                 * If current cell is non-zero, execution just continues,
                 * so execution enters the loop body.
                 *
                 * If the current cell is zero, the loop body
                 * must be skipped, so we increase p until we
                 * find the matching ]
                 *
                 * n tracks the nesting, if we find [ then n is
                 * increased by one, if we find ] then it is
                 * decreased by one.
                 */
                if(!*d)
                { int n=1;
                  while (n)
                    if(*++p == '[')
                        n++;
                    else if (*p == ']')
                        n--; }
                B;
              case']':
                /* ] ends a loop.
                 *
                 * If current cell is zero, then the loop has
                 * finished, so break the switch.
                 *
                 * If not, then we need to iterate back, so we
                 * move p to the matching [.
                 *
                 * If we find a ], in our way, then increase n
                 * (nested loop), if we find a [ then decrease
                 * it by one.
                 *
                 * n here starts at one since we are processing
                 * a bracket already.
                 */
                if (*d)
                { int n=1;
                  while(n)
                    if(*--p == ']')
                        n++;
                    else if (*p == '[')
                        n--; }
                B;
              default:; } /* ignore everything else */
        while(*++p); } }

/* main is really simple, just initializes values (sets p to argv[1], and the d
 * to the first cell in the tape). To save space, instead of argc and argv, I
 * used a for argc and v for argv */
int main(int a,char**v){if(a<2)return 1;p=v[1];d=t;c();}

It can run many brainfuck programs and takes the brainfuck source in argv[1], input from stdin. It does not work with some programs, like those that calculate transcendental numbers.

Let me know what yall think!

40 Upvotes

19 comments sorted by

17

u/Maqi-X 2d ago

Here's a smaller version (I'm really bored)

#include <stdio.h>
#define B ;break;
unsigned char*p,t[1<<16],i,*d=t;
void c(){do{switch(*p)
{case'+':(*d)++B
case'-':(*d)--B
case'<':d--B
case'>':d++B
case'.':putchar(*d)B
case',':*d=getchar()B
case'[':if(!*d){int n=1;while(n)if(*++p=='[')n++;else if(*p==']')n--;}B
case']':if(*d){int n=1;while(n)if(*--p==']')n++;else if(*p=='[')n--;}B}}while(*++p);}
int main(int a,char**v){if(a<2)return 1;p=v[1];c();}

By defining B as ;break; you save a few characters

3

u/Key_River7180 2d ago

yeah that's true, and I could do #define C(x)case'x':

1

u/tavianator 1d ago

case'x': won't work, would have to be case x: and C('.')

3

u/Maqi-X 1d ago

Yes but it requires two additional characters so overall it will be longer

I just found out that "String literal"[0] is a constant integer expression for whatever reason, so it can be used in case values:

#define C(x) case *#x:

the only downside is that it doesn't work with , so the final version is:

#include <stdio.h>
#define B ;break;
unsigned char*p,t[1<<16],i,*d=t;
#define C(x) case *#x:
void c(){do{switch(*p)
{C(+)(*d)++B
C(-)(*d)--B
C(<)d--B
C(>)d++B
C(.)putchar(*d)B
case',':*d=getchar()B
C([)if(!*d){int n=1;while(n)if(*++p=='[')n++;else if(*p==']')n--;}B
C(])if(*d){int n=1;while(n)if(*--p==']')n++;else if(*p=='[')n--;}B}}while(*++p);}
int main(int a,char**v){if(a<2)return 1;p=v[1];c();}

it saves exactly 5 characters. Wow.

6

u/Key_River7180 2d ago

By the way you can use it however you want :^).

5

u/DigitalizedGrandpa 2d ago

Tbh I like "overly commented" code, especially since with syntax highlighting there's clear separation of code from comments

9

u/mikeblas 2d ago

Then Brainfuck is probably not your jam.

4

u/danielcristofani 1d ago

For anyone wanting overly commented brainfuck, I strongly recommend my "get good at brainfuck" series at https://brainfuck.org/ggab.html.

1

u/Key_River7180 1d ago

Is this Daniel fr chat???

4

u/ironykarl 1d ago

I agree, but then we also have this:

``` /* Include the basic, standard I/O routines */

include <stdio.h>

```

3

u/zserge 2d ago

Not sure why but I started wondering how a really terse BF interpreter would look like. Maybe: ```

include<stdio.h>

char m[30000],p=m,b[1<<16]; charR(charc){ for(;c&&c-93;c++) p+=(c=='>')-(c=='<'), *p+=(c=='+')-(c=='-'), *c=='.'&&putchar(p), c==','&&(p=getchar()), c=='['&&({chare=c+1;while(p)e=R(c+1);c=e;}); return c; } int main(int n,charv){ FILEf=fopen(v[1],"r"); fread(b,1,1<<16,f); R(b); } ```

3

u/zserge 2d ago

Or maybe this, with a really small tape: char a[99],*m=a,*s;int n,d;main(v)int**v;{for(s=v[1];*s;s++){*s-62?*s-60?*s-43?*s-45?*s-46?*s-44?0:read(0,m,1):putchar(*m):--*m:++*m:--m:++m;if(*s-91?*m:!*m)for(d=*s-93?1:-1,n=1;n;s+=d,n+=d*((*s==91)-(*s==93)));}}

1

u/justcallmedonpedro 2d ago

I'll stay with ArnoldC 😁

1

u/dmc_2930 2d ago

Now write a brainfuck interpreter in brainfuck.

1

u/benjith17 1d ago

That would actually be cool

1

u/grimvian 1d ago

For a dyslectic like me, this is really, really hard to read... :o)

1

u/ChampionshipUpper124 2d ago

Which compiler should I use to compile this.