- PVSM.RU - https://www.pvsm.ru -
Одним холодным майским днем от скуки решил я таки приступить к изучению этого удивительного языка — Brainfuck'a.
Его [1] интерпретаторы [2] публиковали [3] на [4] Хабре [5] уже [6] очень [7] много [8] раз [9].
Но мне хотолось изучить поглубже сам язык и алгоритмы на нем, а не писать очередной интерпретатор. Поэтому было решено сделать из мухи слона компилятор какого-нибудь высокоуровневого языка в brainfuck.
Однако очень быстро начался реальный brainfuck: отсутствие оператора if, отсутствие произвольного доступа к ячейкам и куча других проблем сразу свалилась на меня. Пришлось повременить с высокоуровневым языком и сделать для начала ассемблер, в который и будет компилироваться высокоуровневый язык.
О реализации ассемблера под катом.
Для удобства лента была разделена на несколько частей:
Транслятор предполагает, что выполнение программы начинается на нулевой ячейке заполненной нулями ленты. Лента должна быть закольцованной.
Это самая главная часть ассемблера. Именно с ними чаще всего придется взаимодействовать. В регистры можно помещать числа (помним, что в brainfuck'e числа ограничены 255) и символы, прибавлять к ним числа и значения других регистров, отнимать числа и значения других регистров, умножать на другие регистры, делить на другие регистры и сравнивать друг с другом.
В трансляторе используются четыре регистра: ax, bx, cx, dx — по аналогии с ассемблером x86, каждый из которых представлен одной ячейкой памяти.
Примеры:
mov ax 5 // поместили в ax 5
mov bx 6 // а в bx 6
sub ax bx // теперь в ax 255
mov cx 12
mov dx 2
mul cx dx // в cx теперь 24
mov dx 10
div cx dx // деление с остатком: в cx - 2, в dx - 4
В левую сторону от нулевой ячейки начинается стек. Он хранится примерно по принципу си-строк и может быть любого размера, однако нужно помнить, что память закольцованна и очень большое количество положенных на стек элементов могут попортить другие ваши данные.
Для того, чтобы класть и снимать значения со стека есть команды push и pop, например:
push ax
push 5
pop bx
pop cx
Однако у хранения в виде си-строк есть один недостаток: нельзя на стек положить ноль, так как ноль служит показателем вершины стека. Конечно, можно было организовать и нормальный стек, но тогда его длина была бы ограничена 256 элементами.
Временные ячейки используются для промежуточных вычислений в сложных операциях. Три флаговые ячейки используются в операции сравнения, подробнее об этом ниже.
Массивы — диапазоны ячеек, к которым можно осуществить доступ по индексу:
array test 10 // массив под именем test из десяти ячеек
set test 0 42 // установили нулевой элемент массива в значение 42
get test 0 ax // получили в ax нулевой элемент массива
Кстати, индексом может быть регистр, содержащий, например, пользовательский ввод, но при этом обратиться к элементам, с индексами больше 255 невозможно.
Строки объявляются ключевым словом string:
string hello "Hello, World!" // объявили строку hello
puts hello // вывод строки
Строки размещаются в секции массивов и при запуске программы инициализируюся.
Со строками можно обращаться как с массивами:
get hello 0 ax
put ax // выведет 'H'
Есть три операции ввода-вывода:
take ax // занесет в регистр ax код введеного символа
put ax // выведет символ, код которого в ax
puts str // выведет строку под именем str
Кстати, командой puts можно печатать и обычные массивы (до первого нуля в нём).
В принципе, ассемблерные jump'ы тоже можно организовать, но это несколько геморройно, поэтому циклы больше похожи на бейсиковские:
while ax // в while можно писать один из регистров
// do smth.
endwhile
Этот цикл будет что-то делать, пока регистр ax не станет равен нулю.
Сравнение сделано в стиле ассемблера. Чтобы что-то сравнить, нужно применить команду cmp к регистрам:
mov ax 2
mov bx 1
cmp ax bx
После этой команды изменяются значения флаговых ячеек. Их три: на равенство, на меньше и на больше. Если первый регистр равен второму, то флаг равенства устанавливается в ноль, если первый регистр меньше, то в ноль устанавливается второй флаг, а если больше, то третий.
Пользоваться результатом сравнения можно так:
cmp ax bx // флаговые ячейки установлены и не изменяются до следующего сравнения
ne // не равно
// действия на случай, если регистры не равны
end
nl // не меньше
// do
end
ng // не больше
// do
end
eq // равно
// do
end
lt // меньше
// do
end
gt // больше
// do
end
Ассемблер сделан с простым синтаксисом и предназначен больше для автоматический генерации, хотя на нем и так можно писать программы. Транслятор написан на Ruby и доступен тут [10]. Там же можно посмотреть примеры программ и написать свои.
Алгоритмы на brainfuck [11] — я использовал оттуда алгоритмы умножения и деления.
BFDev [12] — удобное brainfuck-IDE, все программы тестировались в нём.
BrainFix [13] — высокоуровневый язык, транслируемый в brainfuck. Оттуда был взят алгоритм доступа к элементу массива с неизвестным в compile time индексом.
Автор: alhimik45
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ruby/36152
Ссылки в тексте:
[1] Его: http://habrahabr.ru/post/113099/
[2] интерпретаторы: http://habrahabr.ru/post/113158/
[3] публиковали: http://habrahabr.ru/post/123034/
[4] на: http://habrahabr.ru/post/113215/
[5] Хабре: http://habrahabr.ru/post/124418/
[6] уже: http://habrahabr.ru/post/12823/
[7] очень: http://habrahabr.ru/post/105028/
[8] много: http://habrahabr.ru/post/112919/
[9] раз: http://habrahabr.ru/post/112989/
[10] тут: http://ideone.com/AGywBM
[11] Алгоритмы на brainfuck: http://esolangs.org/wiki/brainfuck_algorithms
[12] BFDev: http://www.4mhz.de/download.php?file=bfdev-1-4-7.rar
[13] BrainFix: http://brainfix.sourceforge.net/
[14] Источник: http://habrahabr.ru/post/182598/
Нажмите здесь для печати.