Как работает мир мозга Brainfuck Hello World?

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

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

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

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

Ответ 1

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, если вы используете оператор comma ,, то, что 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. Выход

Чтобы напечатать символ, который вы используете dot . Что он делает: Предполагая, что мы обрабатываем фактическое значение ячейки, как десятичный код ASCII, печатаем соответствующий символ стандартным выводам.

Хорошо, представьте себе, что ваша программная память BF выглядит так:

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

Если вы теперь используете оператор dot (.), то что BF делает, это print:

а

Поскольку a десятичный код в ASCII равен 97.

Итак, например, программа BF вроде этого (97 плюсов 2 точки):

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

Увеличит значение ячейки, на которую он указывает до 97, и распечатает его 2 раза.

аа

4. Петли

В цикле BF состоит из цикла begin [ и конца цикла ]. Вы можете думать об этом как в 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)

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

Ответ 2

Wikipedia имеет прокомментированную версию кода.

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

Wikipedia статья идет и на более глубокой основе.

Первая строка инициализирует 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, нижний регистр "e", который затем выводится.

Как "l" случается быть седьмой буквой после "е", для вывода "еще" еще семь добавлен (+++++++) в a[2], и результат выводится дважды.

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

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

Ответ 3

Чтобы ответить на вопрос о том, как он знает, что печатать, я добавил вычисление значений 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)

Ответ 4

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

Пример:

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

write(1, ptr, 1)

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