Brainfuck

Brainfuck er et meget minimalistisk, esoterisk programmeringssprog. Sproget blev udviklet i 1993 af Urban Müller og var et forsøg på at lave et Turing-komplet programmeringssprog med en meget lille compiler.

Sammenlignet med programmeringssprog til almene formål er Brainfuck svært at skrive programmer i fordi det tilbyder meget få instruktioner. Det begrænsede instruktionssæt er lige præcis tilstrækkeligt for at gøre sprogets teoretiske udtrykskraft universelt (Turing-komplet).

Sproget er derfor ikke tiltænkt som et praktisk anvendeligt sprog, men i stedet som en form for underholdning.

Sprogets oprindelse

[redigér | rediger kildetekst]

Foruden instruktioner til håndtering af input og output er Brainfuck en variation over programmeringssproget P`` skabt af Corrado Böhm i 1964. Ved at bruge seks symboler ækvivalent med Brainfucks instruktioner viste Böhm ved eksempler hvordan de tilsammen er tilstrækkelige for at udtrykke enhver beregnelig funktion.

Sprogets design

[redigér | rediger kildetekst]

Inspireret af en compiler til programmeringsssproget FALSE som fyldte 1024 bytes, findes adskillige compilere til Brainfuck på under 200 bytes og en enkelt på under 100 bytes. Den klassiske compiler er Urban Müllers version 2 som er lavet til Amiga. Sammen med den følger en fortolker, en række programmer skrevet i Brainfuck, samt et README-dokument.

Sprogets opbygning

[redigér | rediger kildetekst]

Sproget består af 8 instruktioner som er repræsenteret ved enkelte tegn. De er hver især beskrevet nedenfor. Et Brainfuck-program er altså en serie af disse instruktioner. Hvis der forekommer andre tegn end de foruddefinerede instruktioner, vil disse tegn blive set bort fra som om de var kommentarer. (I de fleste programmeringssprog markerer man når en kommentar begynder og slutter.)

Hver af instruktionerne udføres i sekvens indtil sidste instruktion er blevet udført, hvorefter programmet afsluttes. Programmets tilstand følger en simpel model hvor en række af 30.000 celler som indeholder bytes (nedenfor kaldet data) opdateres ved at en peger på den aktive celle (nedenfor kaldet cur) hæves eller sænkes, eller den aktive celles værdi selv hæves eller sænkes. Det er desuden muligt at tilgå input, eksempelvis fra tastaturet, samt output, eksempelvis til skærmen. Input og output læses som ASCII-værdier og behandles som heltal.

Instruktioner

[redigér | rediger kildetekst]

Sproget tilbyder 8 instruktioner som er repræsenteret ved enkelte tegn.

Instruktion Betydning Tilsvarende C-kode
> forøg pegeren med én (peg på cellen til højre). cur = cur + 1;
< sænk pegeren med én (peg på cellen til venstre). cur = cur - 1;
+ forøg den nuværende celles værdi med én. data[cur] = data[cur] + 1;
- sænk den nuværende celles værdi med én. data[cur] = data[cur] - 1;
. udskriv værdien i den nuværende celle. putc(data[cur]);
, læs en værdi som input og gem værdien i nuværende celle. data[cur] = getc();
[ hvis værdien i den nuværende celle er forskellig fra 0,

så udfør løkkens indhold og gentag denne overvejelse.

while (data[cur]) { ... }
] afslut en løkke.

Omskrivning til C-kode

[redigér | rediger kildetekst]

Betragt Brainfuck-programmet ++++++++[->++++++++<]>. som udskriver tegnet @, hvilket svarer til ASCII-tegn nr. 64. Måden dette tegn konstrueres er ved først at danne værdien 8 i første celle (vha. ++++++++), hvorefter en løkke trækker én fra værdien i cellen og forøger anden celle med 8 hver gang (vha. [->++++++++<]). Efter 8 gange er værdien i første celle ikke længere forskellig fra 0, hvorefter den betragtes som falsk. Da stopper løkken, der peges på anden celle (vha. >) som udskrives (vha. .).

Det samme eksempel kunne skrives ved hjælp af følgende (ineffektive) C-kode:

int main()
{
    unsigned char data[30000] = {0};  // initialisér række af bytes
    int cur = 0;               // initialisér peger

    // forøg værdien af data[0] til 8
    data[cur]++; data[cur]++; data[cur]++; data[cur]++;
    data[cur]++; data[cur]++; data[cur]++; data[cur]++;

    // mens data[0] ikke er lig 0
    while (data[cur] != 0)
    {
        // sænk værdien i data[0] med én
        data[cur]--;
        // bevæg peger til data[1]
        cur++;
        // forøg værdien i data[1] med 8
        data[cur]++; data[cur]++; data[cur]++; data[cur]++;
        data[cur]++; data[cur]++; data[cur]++; data[cur]++;
        // bevæg peger tilbage til data[0]
        cur--;
    }

    // bevæg peger til data[1]
    cur++;
    // udskriv indholdet af data[1] som ASCII-værdi (dvs. som @)
    putc(data[cur]);
}

Det ville imidlertid svare til følgende program:

void main()
{
    unsigned char data[30000];
    int cur = 0;

    cur = 1;
    data[cur] = 64;
    putc(data[cur]);
}

Et længere eksempel er programmet som udskriver "Hello World!" efterfulgt af et linjeskift:

 
+++++ +++++             initialisér tæller (celle #0) til 10
[                       brug løkke til at sætte de næste værdier til hhv 70/100/30/10
    > +++++ ++              tilføj  7 til celle #1
    > +++++ +++++           tilføj 10 til celle #2 
    > +++                   tilføj  3 til celle #3
    > +                     tilføj  1 til celle #4
    <<<< -                  sænk tæller (celle #0)
]
> ++ .                  udskriv 'H'
> + .                   udskriv 'e'
+++++ ++ .              udskriv 'l'
.                       udskriv 'l'
+++ .                   udskriv 'o'
> ++ .                  udskriv ' '
<< +++++ +++++ +++++ .  udskriv 'W'
> .                     udskriv 'o'
+++ .                   udskriv 'r'
----- - .               udskriv 'l'
----- --- .             udskriv 'd'
> + .                   udskriv '!'
> .                     udskriv '\n'

For læsbarhedens skyld er ovenstående kildekode spredt på tværs af flere linjer og kommentarer i form af andre tegn end de 8 instruktioner er blevet tilføjet. (Det er altså ikke nødvendigt at bemærke at en kommentar starter.) Koden kunne dog lige så godt være skrevet som:

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