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)
Однако рекомендуется заканчивать с того места, где мы начали. Зачем ? Поскольку, если цикл завершает другую ячейку, которую он начал, мы не можем предположить, где будет указатель ячейки. Если честно, такая практика делает мозги меньшими.