Як насправді працює привіт "Світ мозку"?


118

Хтось надіслав мені це і заявив, що це привіт світ у Brainfuck (і я сподіваюся, що так ...)

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

Я знаю основи, що це працює, переміщуючи вказівник і збільшуючи та зменшуючи речі ...

І все-таки я хочу знати, як це насправді працює? Як це друкує що-небудь на екрані в першу чергу? Як він кодує текст? Я зовсім не розумію ...


31
Повинно бути досить складно підтримувати програму, написану цією мовою ..
e2-e4

17
@ ring0: ні, це мова лише для запису.
НехайMeSOThat4U

в чому це практичне використання?
Яш Кумар Верма

10
@YashVerma це не потрібно ..
Insane

49
@YashVerma Це чітко вказано у назві мови.
Mateen Ulhaq

Відповіді:


255

1. Основи

Щоб зрозуміти Brainfuck, ви повинні уявити нескінченний масив комірок, ініціалізованих 0кожним.

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

Коли запускається програма brainfuck, вона вказує на будь-яку клітинку.

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

Якщо перемістити вказівник праворуч, >ви переміщуєте вказівник з комірки X на комірку X + 1

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

Якщо ви збільшуєте значення комірки, +ви отримуєте:

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

Якщо ви знову збільшите значення комірки, +ви отримаєте:

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

Якщо ви зменшите значення комірки, -ви отримаєте:

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

Якщо ви перемістите вказівник ліворуч, <ви переміщуєте вказівник від клітини X до комірки X-1

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

2. Введення

Для читання символів використовується кома ,. Що це робить: Прочитайте символ зі стандартного вводу та запишіть його десятковий код ASCII у фактичну комірку.

Погляньте на таблицю ASCII . Наприклад, десятковий код !є 33, поки aє 97.

Що ж, давайте уявимо, що пам'ять програми BF виглядає так:

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

Якщо припустити, що стандартний вхід означає a, якщо ви використовуєте ,оператор комами , BF - це зчитування aдесятичного коду ASCII 97в пам'ять:

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

Як правило, ви хочете думати саме так, проте правда трохи складніша. Правда в тому, що BF читає не символ, а байт (що б там не було). Дозвольте показати вам приклад:

У Linux

$ printf ł

відбитки:

ł

що є специфічним польським характером. Цей символ не кодується кодуванням ASCII. У цьому випадку це кодування UTF-8, тому він використовував більше одного байта в пам'яті комп'ютера. Ми можемо довести це, зробивши шістнадцятковий дамп:

$ printf ł | hd

який показує:

00000000  c5 82                                             |..|

Нулі компенсуються. 82є першим і c5представляє другий байт ł(для того, щоб ми їх прочитали). |..|- це графічне зображення, яке неможливо в цьому випадку.

Добре, якщо ви передасте łяк вхід до програми BF, яка читає один байт, пам'ять програми буде мати такий вигляд:

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

Чому 197? Добре 197десятковий - c5шістнадцятковий. Здається, знайоме? Звичайно. Це перший байт ł!

3. Вихід

Для друку символів ви використовуєте крапку. .Це робиться так: Припустимо, що ми обробляємо фактичне значення комірки, як десятковий код ASCII, друкуємо відповідний символ до стандартного виводу.

Що ж, давайте уявимо, що пам'ять програми BF виглядає так:

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

Якщо ви зараз використовуєте оператор dot (.), BF - це друк:

а

Тому що aдесятковий код в ASCII є 97.

Так, наприклад, така програма BF (97 плюсів по 2 крапки):

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

Збільшить значення клітинки, яку вона вказує, до 97 і роздрукує її в 2 рази.

аа

4. Петлі

У BF петля складається з циклу початку [і циклу циклу ]. Ви можете подумати, що це як у C / C ++, де умова є фактичним значенням комірки.

Подивіться програму BF нижче:

++[]

++ приріст фактичного значення комірки вдвічі:

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

І []це як while(2) {}, значить, це нескінченна петля.

Скажімо, ми не хочемо, щоб ця петля була нескінченною. Ми можемо зробити, наприклад:

++[-]

Отже, кожен раз, коли цикл циклу, він зменшує фактичне значення комірки. Як тільки фактичне значення комірки 0закінчиться циклом:

...[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)

Розглянемо ще один приклад скінченного циклу:

++[>]

Цей приклад показує, що ми не повинні закінчувати цикл у комірці, з якої починається цикл:

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

Однак це є гарною практикою закінчитись з того, з чого ми почали. Чому? Тому що, якщо цикл закінчується іншою коміркою, яку він розпочав, ми не можемо припустити, де буде вказати комірку. Якщо чесно сказати, така практика робить мозковий епізод менш мозковим.


4
Круто, зараз я це зрозумів :)
швидкісний

25
Це було ідеальним рішенням для новачка, який намагався осмислити цю мовну ідеологію. З повагою, чудовий пост.
Кейсі

4
Найкраще представлення Brainfuck, яке я бачив. Чесно кажучи, ви трохи скасуєте BF у своєму дописі
Боянг

3
Я думаю, що якщо вам потрібен проект для вашого вільного часу, ви завжди можете додати підтримку Unicode до Brainfuck.
Альваро Гонсалес

3
Після Вашої посади, BF вже! BF!
thanos.a

52

У Вікіпедії є коментована версія коду.

+++++ +++++             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'

Щоб відповісти на ваші запитання, символи ,та .символи використовуються для вводу / виводу. Текст - ASCII.

Стаття у Вікіпедії також продовжується.

Перший рядок ініціалізується a[0] = 10, просто збільшуючи десять разів від 0. Цикл із рядка 2 ефективно встановлює початкові значення масиву: a[1] = 70(близько 72, код ASCII для символу 'H'), a[2] = 100(близький до 101 або 'e' ), a[3] = 30(близько 32, код для простору) та a[4] = 10(новий рядок). Цикл працює шляхом додавання 7, 10, 3, 1, до клітин a[1], a[2], a[3]і , a[4]відповідно , кожен раз через петлю - 10 додавань для кожного осередку в цілому (даючи і a[1]=70т.д.). Після завершення циклу a[0]дорівнює нулю. >++.потім переміщує вказівник на a[1], який містить 70, додає до нього два (утворюючи 72, що є кодом символу ASCII з великої літери Н), і виводить його.

Наступний рядок переміщує вказівник масиву на a[2]та додає до нього, створюючи 101, нижній регістр 'e', ​​який потім виводиться.

Оскільки 'l' буває сьомою літерою після 'e', ​​для виводу 'lll' додається ще сім ( +++++++) до, a[2]а результат виводиться двічі.

'o' є третьою літерою після 'l', тому a[2]збільшується ще в три рази і видає результат.

Решта програми триває так само. Для простору та великої літери вибираються різні клітини масиву та збільшуються або зменшуються за потребою.


Але ЧОМУ це друкує? чи як? Зауваження пояснюють мені намір лінії, тепер, що вона робить.
швидкісник

8
Він друкує, тому що компілятор це знає ,і .використовується для вводу / виводу, подібно до C друкує шляхом використання putchar. Це деталь реалізації, якою обробляється компілятор.
кен

1
А також тому, що він встановлює необхідні комірки на цілі значення для символів ASCII у "Hello World"
slugonamission

Я очікував більш глибокого пояснення ... але: /
speeder

1
@speeder - до відповіді я додав поглиблене пояснення коду з Вікіпедії. Ви можете ознайомитись із пов'язаною статтею для отримання додаткової інформації.
кен

9

Щоб відповісти на питання, як воно знає, що друкувати, я додав обчислення значень ASCII праворуч від коду, де відбувається друк:

> 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 те саме, що і його назва. Він використовує лише 8 символів, > [ . ] , - +що робить його найшвидшою мовою програмування для вивчення, але найважче для реалізації та розуміння. … .І змушує вас нарешті f * cking your brain.

Він зберігає значення в масиві: [72] [101] [108] [111]

нехай, спочатку вказівник вказує на комірку 1 масиву:

  1. > перемістити вказівник праворуч на 1

  2. < перемістити вказівник вліво на 1

  3. + приріст значення комірки на 1

  4. - збільшують значення елемента на 1

  5. . значення друку поточної комірки.

  6. , прийняти вхід до поточної комірки.

  7. [ ] цикл, +++ [-] лічильник з 3 підрахунків bcz має перед ним 3 '+', і - декременти рахують змінну на 1 значення.

значення, що зберігаються в клітинках, мають значення ascii:

так посилаючись на вище масив: [72] [101] [108] [108] [111] , якщо збігаються зі значеннями ASCII ви виявите , що це Hello writtern

Вітаю! ви засвоїли синтаксис BF

——— Щось більше ———

давайте зробимо нашу першу програму, тобто Hello World , після якої ви зможете написати своє ім’я цією мовою.

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

розбиваючись на шматки:

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

Створює масив з 4 комірок (кількість>) і встановлює лічильник 10 приблизно так: —-psuedo код—-

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

оскільки значення лічильника зберігається у комірці 0 і> переміщення до клітинки 1 оновлює його значення на + 7> переходить до кроків 2 із кроком 10 до попереднього значення тощо….

<<< повернення до комірки 0 та зменшення її значення на 1

отже, після завершення циклу ми маємо масив: [70,100,30,10]

>++. 

переходить до першого елемента та збільшує його значення на 2 (два '+'), а потім друкує ('.') символ із цим значенням ascii. наприклад, у python: chr (70 + 2) # друкує 'H'

>+.

переміщується до приросту 1-ої комірки 1 до її значення 100 + 1 і друкує ('.') його значення, тобто chr (101) chr (101) #prints 'e', ​​в наступному фрагменті немає> або <, тому воно приймає теперішнє значення лише останній елемент та приріст лише до нього

+++++ ++..

останній елемент = 101, отже, 101 + 7 і друкує його двічі (оскільки є два '..') chr (108) # відбитків l двічі можна використовувати як

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

——— Де він використовується? ———

Це просто жартівлива мова, яка викликає виклик програмістів і не використовується практично ніде.


4

Усі відповіді ґрунтовні, але їм не вистачає однієї крихітної деталі: Друк. Будуючи перекладача brainfuck, ви також враховуєте персонаж ., це насправді те, що виглядає друкована заява у brainfuck. Отже, що повинен зробити ваш перекладач мозкових епізодів - це кожен раз, коли він стикається з .персонажем, він друкує вказаний в даний час байт.

Приклад:

Припустимо , у вас є -> char *ptr = [0] [0] [0] [97] [0]... якщо це Brainfuck заяву: >>>.покажчик повинен бути переміщений в 3 місця в правому посадки на: [97], так що тепер *ptr = 97, після того, як робити , що ваш перекладач зустрічає ., він повинен потім виклик

write(1, ptr, 1)

або будь-яке еквівалентне твердження про друк для друку поточно вказаного байта, який має значення 97, і лист aбуде надруковано на std_output.


1

Я думаю, що ти запитуєш, як Brainfuck знає, що робити з усім кодом. Існує аналізатор, написаний мовою вищого рівня, наприклад, Python для інтерпретації того, що означає крапка або що означає знак додавання в коді.

Таким чином, аналізатор прочитає ваш код рядок за рядком, і скаже добре, що є символ>, тому я повинен заздалегідь встановити місце пам'яті; код просто, якщо (вміст у цьому місці пам'яті) ==>, memlocation = + мелокація, яка є написано мовою вищого рівня, аналогічно if (вміст у пам'яті) == ".", а потім надрукувати (вміст місця в пам'яті).

Сподіваюся, це очистить це. тс

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.