Как на самом деле работает Brainfuck Hello World?

118

Кто-то отправил мне это и заявил, что это привет, мир в Brainfuck (и я надеюсь на это ...)

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

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

И все же я все еще хочу знать, как это на самом деле работает? Как он вообще что-нибудь печатает на экране? Как он кодирует текст? Я вообще не понимаю ...

повышающее устройство
источник
31
Должно быть довольно сложно поддерживать приложение, написанное на этом языке ..
e2-e4
18
@ ring0: нет, это язык только для записи.
LetMeSOThat4U
в чем его практическая польза?
Яш Кумар Верма
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]...

Если вы сейчас используете оператор точки (.), BF печатает:

a

Поскольку 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)

Однако рекомендуется заканчивать с того места, где мы начали. Зачем ? Поскольку, если цикл завершает другую ячейку, которую он начал, мы не можем предположить, где будет указатель ячейки. Если честно, такая практика делает мозги меньшими.

Scony
источник
4
Круто, теперь я понял :)
спидер
25
Это было отличным решением для новичка, пытающегося понять идеологию этого языка. Поздравляю, и отличный пост.
Кейси
4
Лучшее вступление к Brainfuck, которое я когда-либо видел. Честно говоря, вы немного отменили BF своим постом
Boyang
3
Думаю, если вам нужен проект в свободное время, вы всегда можете добавить поддержку Unicode в Brainfuck.
Álvaro González
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 заглавной H) и выводит его.

Следующая строка перемещает указатель массива a[2]и добавляет к нему единицу, получая 101, строчную букву «е», которая затем выводится.

Поскольку 'l' оказывается седьмой буквой после 'e', ​​для вывода 'll' к нему добавляются еще семь ( +++++++), a[2]и результат выводится дважды.

'o' - третья буква после 'l', поэтому a[2]увеличивается еще три раза и выводит результат.

Остальная часть программы продолжается таким же образом. Для пробела и заглавных букв выбираются разные ячейки массива и при необходимости увеличиваются или уменьшаются.

кругозор
источник
Но ПОЧЕМУ он печатает? или как? Комментарии объясняют мне цель линии, а теперь то, что она делает.
спидер
8
Он печатает, потому что компилятор знает об этом ,и .используется для ввода-вывода, так же, как C печатает с использованием putchar. Это деталь реализации, обрабатываемая компилятором.
Кен
1
А также потому, что он устанавливает в требуемых ячейках целочисленные значения для символов ASCII в «Hello World»
slugonamission
Я ожидал более подробного объяснения ... но: /
спидер
1
@speeder - Я добавил в ответ подробное объяснение кода из Википедии. Вы можете увидеть связанную статью для получения дополнительной информации.
ken
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 символов, > [ . ] , - +что делает его самым быстрым языком программирования для изучения, но самым сложным для реализации и понимания. … .И заставляет вас, наконец, кончить тем, что вы трахаете свой мозг.

Значения хранятся в массиве: [72] [101] [108] [111]

let, изначально указатель, указывающий на ячейку 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

Congrats! вы узнали синтаксис BF

——- Что-то еще ———

давайте сделаем нашу первую программу, т.е. Hello World , после чего вы сможете написать свое имя на этом языке.

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

разбивая на части:

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

Создает массив из 4 ячеек (число>) и устанавливает счетчик 10 примерно так: —-psuedo code—-

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]

>++. 

перемещается к 1-му элементу и увеличивает его значение на 2 (два '+'), а затем печатает символ ('.') с этим значением ascii. например, в python: chr (70 + 2) # печатает 'H'

>+.

переходит на 2-ю ячейку с приращением 1 к ее значению 100 + 1 и печатает ('.') ее значение, т.е. chr (101) chr (101) # печатает 'e', ​​теперь в следующем фрагменте нет> или <, поэтому он принимает текущее значение последнего элемента и только приращение к нему

+++++ ++..

последний элемент = 101, следовательно, 101 + 7 и печатает его дважды (так как есть два '..') chr (108) #prints l дважды можно использовать как

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

——— Где это используется? ---

Это просто язык шуток, созданный для вызова программистов, который практически нигде не используется.

DARK_C0D3R
источник
4

Все ответы обстоятельны, но в них отсутствует одна крошечная деталь: печать. Создавая свой переводчик для мозгов, вы также учитываете характер ., это на самом деле то, как выглядит печатное выражение в мозговом обтекании. Итак, что должен делать ваш мозговой переводчик, так это то, что всякий раз, когда он встречает .символ, он печатает текущий указанный байт.

Пример:

предположим, что у вас есть -> char *ptr = [0] [0] [0] [97] [0]... если это утверждение для мозгов: >>>.ваш указатель должен быть перемещен на 3 пробела вправо в:, [97]так что теперь *ptr = 97, после этого ваш переводчик встречает a ., он должен затем вызвать

write(1, ptr, 1)

или любой эквивалентный оператор печати для печати текущего указанного байта, который имеет значение 97, и буква aбудет затем напечатана на std_output.

rapdean
источник
1

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

Таким образом, синтаксический анализатор будет читать ваш код построчно и сообщать, что есть символ>, поэтому мне нужно продвинуть ячейку памяти, код просто, if (содержимое в этой ячейке памяти) ==>, memlocation = + memlocation, которое является написано на языке более высокого уровня, аналогично if (содержимое в ячейке памяти) == ".", затем print (содержимое ячейки памяти).

Надеюсь, это проясняет ситуацию. дц

Рахул
источник