Jak właściwie działa Brainfuck Hello World?


118

Ktoś wysłał mi to i stwierdził, że to witaj świat w Brainfuck (i mam nadzieję, że tak ...)

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

Znam podstawy, że działa poprzez przesuwanie wskaźnika oraz zwiększanie i zmniejszanie wartości ...

Jednak nadal chcę wiedzieć, jak to właściwie działa? Jak w pierwszej kolejności drukuje cokolwiek na ekranie? Jak koduje tekst? W ogóle nie rozumiem ...


31
Utrzymanie aplikacji napisanej w tym języku musi być dość trudne ..
e2-e4

17
@ ring0: nie, to jest język tylko do zapisu.
LetMeSOThat4U

jakie jest to praktyczne zastosowanie?
Yash Kumar Verma

10
@YashVerma, to nie potrzebuje ...
Szalony

49
@YashVerma Jest to wyraźnie określone w nazwie języka.
Mateen Ulhaq

Odpowiedzi:


255

1. Podstawy

Aby zrozumieć Brainfuck, musisz wyobrazić sobie nieskończoną liczbę komórek inicjowanych przez 0każdą z nich.

...[0][0][0][0][0]...

Kiedy program „brainfuck” zaczyna wskazywać na dowolną komórkę.

...[0][0][*0*][0][0]...

Jeśli przesuniesz wskaźnik w prawo >, przesuniesz wskaźnik z komórki X do komórki X + 1

...[0][0][0][*0*][0]...

Jeśli zwiększysz wartość komórki +, otrzymasz:

...[0][0][0][*1*][0]...

Jeśli ponownie zwiększysz wartość komórki +, otrzymasz:

...[0][0][0][*2*][0]...

Jeśli zmniejszysz wartość komórki -, otrzymasz:

...[0][0][0][*1*][0]...

Jeśli przesuniesz wskaźnik w lewo <, przesuniesz wskaźnik z komórki X do komórki X-1

...[0][0][*0*][1][0]...

2. Wejście

Aby przeczytać znak, użyj przecinka ,. Co robi to: czyta znak ze standardowego wejścia i zapisuje jego dziesiętny kod ASCII do właściwej komórki.

Spójrz na tabelę ASCII . Na przykład kod dziesiętny !to 33, podczas gdy ajest 97.

Cóż, wyobraźmy sobie, że pamięć programu BF wygląda następująco:

...[0][0][*0*][0][0]...

Zakładając, że standardowe wejście oznacza a, jeśli używasz ,operatora przecinka , to, co robi BF, to odczyt adziesiętny kod ASCII 97do pamięci:

...[0][0][*97*][0][0]...

Na ogół chcesz tak myśleć, ale prawda jest nieco bardziej złożona. Prawda jest taka, że ​​BF nie czyta znaku, ale bajt (cokolwiek to jest). Pokażę Ci przykład:

W systemie linux

$ printf ł

wydruki:

ł

co jest specyficznym polskim charakterem. Ten znak nie jest kodowany za pomocą kodowania ASCII. W tym przypadku jest to kodowanie UTF-8, więc zajmowało więcej niż jeden bajt w pamięci komputera. Możemy to udowodnić, wykonując zrzut szesnastkowy:

$ printf ł | hd

który pokazuje:

00000000  c5 82                                             |..|

Zera są przesunięte. 82jest pierwszym i c5jest drugim bajtem reprezentującym ł(w celu ich odczytania). |..|jest reprezentacją graficzną, która w tym przypadku nie jest możliwa.

Cóż, jeśli podasz łjako dane wejściowe do programu BF, który czyta pojedynczy bajt, pamięć programu będzie wyglądać następująco:

...[0][0][*197*][0][0]...

Dlaczego 197? Cóż, 197dziesiętne to c5szesnastkowe. Wydaje się znajomy? Oczywiście. To pierwszy bajt ł!

3. Wyjście

Aby wydrukować znak, użyj kropki. .Co to robi: Zakładając, że traktujemy rzeczywistą wartość komórki jak dziesiętny kod ASCII, wypisz odpowiedni znak na standardowe wyjście.

Cóż, wyobraźmy sobie, że pamięć programu BF wygląda następująco:

...[0][0][*97*][0][0]...

Jeśli teraz używasz operatora kropki (.), BF wypisuje:

za

Ponieważ akod dziesiętny w ASCII to 97.

Na przykład program BF taki jak ten (97 plus 2 kropki):

+++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++.

Zwiększy wartość wskazywanej komórki do 97 i wydrukuje ją 2 razy.

aa

4. Pętle

W pętli BF składa się z początku [i końca pętli ]. Możesz pomyśleć, że to tak, jak w C / C ++, gdzie warunek jest rzeczywistą wartością komórki.

Spójrz na program BF poniżej:

++[]

++ zwiększa dwukrotnie rzeczywistą wartość komórki:

...[0][0][*2*][0][0]...

I []jest tak while(2) {}, więc to nieskończona pętla.

Powiedzmy, że nie chcemy, aby ta pętla była nieskończona. Możemy na przykład:

++[-]

Tak więc za każdym razem, gdy pętla się zapętla, zmniejsza rzeczywistą wartość komórki. Gdy aktualna wartość komórki się 0kończy:

...[0][0][*2*][0][0]...        loop starts
...[0][0][*1*][0][0]...        after first iteration
...[0][0][*0*][0][0]...        after second iteration (loop ends)

Rozważmy jeszcze jeden przykład skończonej pętli:

++[>]

Ten przykład pokazuje, że nie kończyliśmy pętli w komórce, w której pętla się rozpoczęła:

...[0][0][*2*][0][0]...        loop starts
...[0][0][2][*0*][0]...        after first iteration (loop ends)

Jednak dobrą praktyką jest zakończenie tam, gdzie zaczęliśmy. Czemu ? Ponieważ jeśli pętla kończy inną komórkę, to nie możemy założyć, gdzie będzie wskaźnik komórki. Szczerze mówiąc, ta praktyka sprawia, że ​​mózg jest mniejszy.


4
Fajnie, teraz to zrozumiałem :)
speeder

25
To było idealne rozwiązanie dla nowicjusza próbującego zrozumieć tę ideologię językową. Gratulacje i świetny post.
Casey

4
Najlepsze intro do Brainfuck, jakie widziałem. Szczerze mówiąc, trochę cofasz BF swoim postem
Boyang

3
Myślę, że jeśli potrzebujesz projektu w wolnym czasie, zawsze możesz dodać obsługę Unicode do Brainfuck.
Álvaro González

3
Po Twoim poście BF jest już! BF!
thanos. A

52

Wikipedia ma skomentowaną wersję kodu.

+++++ +++++             initialize counter (cell #0) to 10
[                       use loop to set the next four cells to 70/100/30/10
    > +++++ ++              add  7 to cell #1
    > +++++ +++++           add 10 to cell #2 
    > +++                   add  3 to cell #3
    > +                     add  1 to cell #4
    <<<< -                  decrement counter (cell #0)
]                   
> ++ .                  print 'H'
> + .                   print 'e'
+++++ ++ .              print 'l'
.                       print 'l'
+++ .                   print 'o'
> ++ .                  print ' '
<< +++++ +++++ +++++ .  print 'W'
> .                     print 'o'
+++ .                   print 'r'
----- - .               print 'l'
----- --- .             print 'd'
> + .                   print '!'
> .                     print '\n'

Aby odpowiedzieć na twoje pytania, znaki ,i .są używane do I / O. Tekst to ASCII.

Artykuł w Wikipedii jest również bardziej szczegółowy.

Pierwsza linia jest inicjalizowana a[0] = 10przez po prostu dziesięciokrotną inkrementację od 0. Pętla z linii 2 efektywnie ustawia początkowe wartości dla tablicy: a[1] = 70(blisko 72, kod ASCII dla znaku „H”), a[2] = 100(blisko 101 lub „e” ), a[3] = 30(blisko 32, kod spacji) i a[4] = 10(nowa linia). Pętla działa dodając 7, 10, 3 i 1, do komórek a[1], a[2], a[3]i a[4], odpowiednio, za każdym razem przez pętlę - 10 dodane do każdej komórki w łącznej (co daje a[1]=70itd.). Po zakończeniu pętli a[0]wynosi zero. >++.następnie przesuwa wskaźnik do a[1], który zawiera 70, dodaje do niego dwa (tworząc 72, który jest kodem ASCII dużej litery H) i wyświetla go.

Następna linia przesuwa wskaźnik tablicy do a[2]i dodaje jeden do niej, dając 101, małą literę „e”, która jest następnie wyświetlana.

Ponieważ „l” jest siódmą literą po „e”, do wyprowadzenia „ll” dodaje się kolejne siedem ( +++++++), a[2]a wynik jest wyświetlany dwukrotnie.

„o” jest trzecią literą po „l”, więc a[2]jest zwiększane jeszcze trzy razy i wyprowadza wynik.

Reszta programu przebiega w ten sam sposób. W przypadku spacji i wielkich liter wybierane są różne komórki tablicy i zależnie od potrzeb zwiększane lub zmniejszane.


Ale DLACZEGO to się drukuje? albo jak? Komentarze wyjaśniają mi cel linii, teraz co ona robi.
ścigacz

8
Drukuje, ponieważ kompilator o tym wie ,i .jest używany do operacji we / wy, podobnie jak C drukuje przy użyciu putchar. Jest to szczegół implementacji obsługiwany przez kompilator.
ken

1
A także dlatego, że ustawia wymagane komórki na wartości całkowite dla znaków ASCII w „Hello World”
slugonamission

Spodziewałem się bardziej szczegółowego wyjaśnienia ... ale: /
speeder

1
@speeder - do odpowiedzi dodałem szczegółowe wyjaśnienie kodu z Wikipedii. Więcej informacji można znaleźć w powiązanym artykule.
ken

9

Aby odpowiedzieć na pytanie, skąd wie, co wydrukować, dodałem obliczenie wartości ASCII po prawej stronie kodu, w którym odbywa się drukowanie:

> just means move to the next cell
< just means move to the previous cell
+ and - are used for increment and decrement respectively. The value of the cell is updated when the increment/decrement happens

+++++ +++++             initialize counter (cell #0) to 10

[                       use loop to set the next four cells to 70/100/30/10

> +++++ ++              add  7 to cell #1

> +++++ +++++           add 10 to cell #2 

> +++                   add  3 to cell #3

> +                     add  1 to cell #4

<<<< -                  decrement counter (cell #0)

]            

> ++ .                  print 'H' (ascii: 70+2 = 72) //70 is value in current cell. The two +s increment the value of the current cell by 2

> + .                   print 'e' (ascii: 100+1 = 101)

+++++ ++ .              print 'l' (ascii: 101+7 = 108)

.                       print 'l' dot prints same thing again

+++ .                   print 'o' (ascii: 108+3 = 111)

> ++ .                  print ' ' (ascii: 30+2 = 32)

<< +++++ +++++ +++++ .  print 'W' (ascii: 72+15 = 87)

> .                     print 'o' (ascii: 111)

+++ .                   print 'r' (ascii: 111+3 = 114)

----- - .               print 'l' (ascii: 114-6 = 108)

----- --- .             print 'd' (ascii: 108-8 = 100)

> + .                   print '!' (ascii: 32+1 = 33)

> .                     print '\n'(ascii: 10)

9

Brainfuck to samo, co jego nazwa. Używa tylko 8 znaków, > [ . ] , - +co czyni go najszybszym do nauczenia się językiem programowania, ale najtrudniejszym do zaimplementowania i zrozumienia. … .I sprawia, że ​​w końcu skończysz z pieprzeniem mózgu.

Przechowuje wartości w tablicy: [72] [101] [108] [111]

let, początkowo wskaźnik wskazujący na komórkę 1 tablicy:

  1. > przesuń wskaźnik w prawo o 1

  2. < przesuń wskaźnik w lewo o 1

  3. + zwiększ wartość komórki o 1

  4. - zwiększ wartość elementu o 1

  5. . wypisuje wartość bieżącej komórki.

  6. , weź dane wejściowe do bieżącej komórki.

  7. [ ] pętla, +++ [-] licznik 3 zliczeń bcz ma przed sobą 3 ′ + 'i - zmniejsza licznik zmiennej o 1 wartość.

wartości przechowywane w komórkach są wartościami ascii:

więc odwołując się do powyższej tablicy: [72] [101] [108] [108] [111] jeśli dopasujesz wartości ascii, zobaczysz, że jest to Hello writtern

Gratulacje!nauczyłeś się składni BF

——- Coś więcej ———

pozwól nam stworzyć nasz pierwszy program czyli Hello World , po którym będziesz mógł napisać swoje imię w tym języku.

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

rozpadać się na kawałki:

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

Tworzy tablicę 4 komórek (liczba>) i ustawia licznik na 10, na przykład: —- pseudokod—-

array =[7,10,3,1]
i=10
while i>0:
 element +=element
 i-=1

ponieważ wartość licznika jest przechowywana w komórce 0 i> przenosi się do komórki 1 aktualizuje swoją wartość o + 7> przenosi do komórki 2 z przyrostem o 10 do poprzedniej wartości i tak dalej….

<<< powrócić do komórki 0 i zmniejszyć jej wartość o 1

stąd po zakończeniu pętli mamy tablicę: [70,100,30,10]

>++. 

przechodzi do pierwszego elementu i zwiększa jego wartość o 2 (dwa „+”), a następnie drukuje znak („.”) z tą wartością ascii. czyli na przykład w pythonie: chr (70 + 2) # wypisuje 'H'

>+.

przechodzi do drugiej komórki z przyrostem 1 do wartości 100 + 1 i drukuje ('.') jego wartość tj. chr (101) chr (101) # drukuje 'e' teraz nie ma> lub <w następnym kawałku, więc przyjmuje aktualną wartość najnowszego elementu i tylko do niego przyrost

+++++ ++..

ostatni element = 101, więc 101 + 7 i wypisuje go dwukrotnie (ponieważ są dwa '..') chr (108) #prints l dwa razy może być użyty jako

for i in array:
    for j in range(i.count(‘.’)):
           print_value

——— Gdzie jest używany?——-

Jest to tylko żartobliwy język stworzony, aby rzucić wyzwanie programistom i nie jest używany praktycznie nigdzie.


4

Wszystkie odpowiedzi są wyczerpujące, ale brakuje im jednego drobnego szczegółu: drukowania. Tworząc swojego mózgowego tłumacza, bierzesz również pod uwagę postać ., tak właściwie wygląda wypowiedź drukarska w brainfuck. Więc to, co powinien zrobić twój mózgowy tłumacz, to kiedy napotka .znak, wypisuje aktualnie wskazany bajt.

Przykład:

załóżmy, że masz -> char *ptr = [0] [0] [0] [97] [0]... jeśli to jest fikcyjne stwierdzenie: >>>.twój wskaźnik powinien zostać przesunięty o 3 spacje w prawą stronę na:, [97]więc teraz *ptr = 97, po wykonaniu tej czynności, twój tłumacz napotka a ., powinien wtedy wywołać

write(1, ptr, 1)

lub dowolna równoważna instrukcja drukowania, aby wydrukować aktualnie wskazany bajt, który ma wartość 97, a litera azostanie następnie wydrukowana na std_output.


1

Myślę, że pytasz, skąd Brainfuck wie, co zrobić z całym kodem. Istnieje parser napisany w języku wyższego poziomu, takim jak Python, aby zinterpretować, co oznacza kropka lub co oznacza znak dodania w kodzie.

Więc parser odczyta twój kod linia po linii i powie ok, jest symbol>, więc muszę przesunąć lokalizację pamięci, kod jest po prostu, jeśli (zawartość w tej lokalizacji pamięci) ==>, memlocation = + memlocation, czyli napisane w języku wyższego poziomu, podobnie jeśli (treść w lokacji pamięci) == ".", a następnie wypisz (zawartość komórki pamięci).

Mam nadzieję, że to wyjaśnia sprawę. tc

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.