Pasiphae - интерпретатор языка Brainfuck |
Главная / ЭКВМ / Программы для ЭКВМ / Служебные программы |
Описание
Язык Brainfuck разработан Урбаном Мюллером (Urban Müller) в 1993 году. Одним из мотивов было создание полноценного языка с как можно меньшим по размеру компилятором*. Авторская реализация является фактическим стандартом языка [1]. Описание предшественника** этого языка P" - первого языка программирования без оператора GOTO - было опубликовано в 1964 году итальянским профессором Corrado Böhm. Язык Brainfuck использует модель машины, напоминающую машину Тьюринга и состоящую из следующих элементов [1]:
Структура памяти ЭКВМ хорошо приспособлена для программирования на Brainfuck: интерпретатор Pasiphae записывается в область памяти программ, в текстовой области размещаются инструкции языка Brainfuck, а область двоичных данных используется для работы интерпретатора в качестве ячеек памяти. Высокое быстродействие интерпретатора, достигаемое за счёт продуманного синтаксиса языка, позволяет применять его на ЭКВМ без лишних затруднений. Отсутствие явно указываемых адресов и меток, обозначений функций и переменных, а также минимально необходимый набор команд без каких-либо приоритетов и параметров, значительно облегчает процесс изучения языка и последующего написания программ на нём. Brainfuck с бесконечным набором ячеек имеет тьюринговскую полноту и по возможностям не уступает таким языкам, как Си, Паскаль или Форт. О его востребованности говорит то, что для данного языка существуют микрокомпьютеры со встроенным интерпретатором, а также множество диалектов и производных языков. К примеру, Brainsub — с поддержкой подпрограмм и библиотек, многопоточный Brainfork и т.д. Синтаксис BrainfuckЯзык имеет восемь команд, каждая из которых записывается одним символом. Исходный код программы на Brainfuck представляет собой последовательность этих символов. Набор команд включает команды сдвига исполнительной каретки влево-вправо, уменьшения-увеличения текущего значения ячейки на единицу, чтения-записи и организации цикла. [2] Размер ячейки — один байт. В начальном состоянии указатель находится в крайней левой позиции, все ячейки заполнены нулями. Увеличение/уменьшение значений ячеек происходит по модулю 256. Ввод-вывод происходит побайтно, с учётом кодировки ASCII. Команды языка
В классической реализации игнорируются все символы, кроме восьми командных. Комментарии, не содержащие этих символов, могут добавляться в любое место программы. [1] Особенности реализации PasiphaeКоличество ячеек с классических 30000 сокращено до 4096. Ячейки расположены в памяти двоичных данных в области регистров R1000-R5095. Размер программы ограничен областью текстовых данных. Программы на языке Brainfuck распространяются в виде файлов MKT. В Pasiphae список дополнен командами '(' с кодом 40(28h) и ')' с кодом 41(29h), которые эквивалентны '[' и ']' соответственно, - для удобства набора программ в текстовом редакторе. Дополнительно введена команда с кодом 0(00h), завершающая работу интерпретатора. При отсутствии этой команды в программе разбор будет продолжен до конца области текста. Пример программы
Разбор программы [3]: Подготовка в памяти (с ячейки 1) массива значений, близких к ASCII-кодам символов, которые необходимо вывести (70, 100, 30, 10), через повторение 10 раз приращения ячеек на 7, 10, 3 и 1, соответственно "++++++++++" - присваивание ячейке 0 (счетчику) значения 10; "[" - повторять, пока значение текущей ячейки (ячейки 0) больше нуля; ">+++++++" - приращение ячейки 1 на 7; ">++++++++++" - приращение ячейки 2 на 10; ">+++" - приращение ячейки 3 на 3; ">+" - приращение ячейки 4 на 1; "<<<<-" - возврат к ячейке 0 (счетчику), и его уменьшение на 1; "]" - вернуться к началу цикла. Получение кодов букв и их вывод">++." - вывод "Н". Получение кода 'H' (72) из 70 в ячейке 1 и вывод; ">+." - вывод "e". Получение кода 'e' (101) из 100 в ячейке 2 и вывод; "+++++++.." - вывод "ll". Получение кода 'l' (108) из 101 в ячейке 2 и вывод дважды; "+++." - вывод "o". Получение кода 'o' (111) из 108 в ячейке 2 и вывод; ">++." - вывод пробела. Получение кода пробела (32) из 30 в ячейке 3 и вывод; "<<+++++++++++++++." - вывод "W". Получение кода 'W' (87) из 72 в ячейке 1 и вывод; ">." - вывод "o". Код 'o' (111) уже находится в ячейке 2, просто его выводим; "+++." - вывод "r". Получение кода 'r' (114) из 111 в ячейке 2 и вывод; "------." - вывод "l". Получение кода 'l' (108) из 114 в ячейке 2 и вывод; "--------." - вывод "d". Получение кода 'd' (100) из 108 в ячейке 2 и вывод; ">+." - вывод "!". Получение кода '!' (33) из 32 в ячейке 3 и вывод; ">." - вывод кода перевода строки (10) из ячейки 4. Программа для ЭКВМ.CHARSET 1251 ; Pasiphae ; Интерпретатор языка Brainfuck для ЭКВМ ; НПП "СЕМИКО", 2015, GPL ; v1.1 - 01.04.2015 ; v1.2 (разбор ПС и ВК) ; Ячейки в области двоичных данных (1000-5095) ; Программа BF в области текста (5096-8167) TEK .EQU 20 ; адрес текущей ячейки ADR .EQU 23 ; текущий адрес в программе (область текста) STACK .EQU 24 ; указатель на верх стека ; (стек для команд "[" и "]" - в адресах 100-999) POS .EQU 25 ; текущая позиция вывода в строку C8168 .EQU 26 ; константа 8168 C5096 .EQU 27 ; константа 5096 C1000 .EQU 28 ; константа 1000 .ORG 0 ; Подготовка к запуску CX .NUMT TXT0 PP M 9026 ; номер версии в строку комментариев CX K SCR ; Инициализация переменных P M POS 100 P M STACK 1000 P M C1000 P M TEK 5096 P M C5096 P M ADR 8168 P M C8168 ; Очистка области ячеек перед запуском ; заполнение всех регистров двоичных данных нулевым значением 999 M4 4096 M0 CX A1: KM4 FL0 A1 ; Очистка графического экрана ; (используется для вывода в stdout) 2 PP M 9010 56 ENT CX ; позиция вывода - в нижней строке PP M 9000 ; Запись адреса таблицы команд в индексный регистр памяти программ .NUM TKOM PP M 9042 ; основной цикл интерпретатора A2: PK RM ADR PP M 9213 ; поиск адреса перехода по индексу P X<0 A5 ; команда не найдена A3: P RM ADR 1 + P M ADR P RM C8168 - P X>=0 A2 ; перейти к следующему адресу ; конец программы A40: .NUMT TXT1 A4: PP M 9026 CX ENT ENT P RM ADR R/S P GOTO 00 A5: MA ; записать адрес а RA K GOTO A ; перейти к обработке ;========================== Таблица команд Brainfuck TKOM: .DB 62 ; '>' .DA TEKI .DB 60 ; '<' .DA TEKD .DB 43 ; '+' .DA ADRI .DB 45 ; '-' .DA ADRD .DB 46 ; '.' .DA STDOUT .DB 44 ; ',' .DA STDIN .DB 91 ; '[' .DA NTC .DB 93 ; ']' .DA KTC ; дополнение для ; Pasiphae .DB 40 ; '('=='[' .DA NTC .DB 41 ; ')'==']' .DA KTC .DB 0 ; NULL - конец текста .DA A40 .END ;========================== Обработка команд ; === Перейти к следующей ячейке TEKI: P RM TEK 1 + P M TEK P RM C5096 - P X>=0 A3 ; вернуться в цикл ; выход за границы области TEKIE: .NUMT TXT2 P GOTO A4 ; === Перейти к предыдущей ячейке TEKD: P RM TEK 1 - P M TEK P X<0 A3 ; вернуться в цикл ; выход за границы области P GOTO TEKIE ; === Увеличить значение в текущей ячейке на 1 ADRI: PK RM TEK 1 + PK M TEK ; ограничение от 0 до 255 выполняется при записи P GOTO A3 ; === Уменьшить значение в текущей ячейке на 1 ADRD: PK RM TEK 1 - PK M TEK ; ограничение от 0 до 255 выполняется при записи P GOTO A3 ; === Напечатать значение из текущей ячейки (в коде ASCII) STDOUT: PK RM TEK 10 - P X=0 STDOUT0 3 - P X=0 STDOUT1 PK RM TEK PP M 9020 ; вывод символа K GRPH ; обновить экран P RM POS 1 + P M POS 24 - P X>=0 A3 ; вернуться в цикл ; перевод строки STDOUT0: CX P M POS 1 PP M 9007 ; прокрутка экрана на 1 строку 56 ENT CX PP M 9000 P GOTO A3 STDOUT1: ; возврат каретки CX P M POS 56 ENT CX PP M 9000 P GOTO A3 ; === ввести извне значение и сохранить в текущей ячейке (число) STDIN: .NUM TXT3 PP M 9026 CX ENT ENT P RM TEK R/S PK M TEK ; ограничение от 0 до 255 выполняется при записи P GOTO A3 ; === '[' ; если значение текущей ячейки ноль, ; перейти вперёд по тексту программы на ячейку, ; следующую за соответствующей ] (с учётом вложенности) NTC: PK RM TEK P X!=0 NTC1 ; не ноль P RM ADR PK M STACK ; записать адрес возврата в стек P RM STACK 1 + P M STACK P RM C1000 - P X>=0 A3 ; вернуться в цикл NTCE: .NUMT TXT4 ; ошибка P GOTO A4 NTC1: ; ноль ; стек не записывать - ибо незачем P RM ADR 1 + P M ADR P RM C8168 - P X>=0 NTC2 ; перейти к следующему адресу ; ошибка P GOTO NTCE NTC2: PK RM ADR 41 - P X!=0 NTC3 52 - P X!=0 NTC1 NTC3: ; найдена ']' P RM ADR 1 + P M ADR P RM C8168 - P X>=0 A3 ; перейти к циклу P GOTO NTCE ; === ']' ; если значение текущей ячейки не нуль, ; перейти назад по тексту программы на символ [ ; (с учётом вложенности) KTC: PK RM TEK P X!=0 KTC1 ; не ноль P RM STACK 1 - P M STACK 100 - P X>=0 NTCE ; ошибка PK RM STACK ; взять адрес возврата P M ADR ; записать его в текущий P GOTO A2 ; перейти к циклу (без инкремента) KTC1: ; ноль P RM STACK 1 - P M STACK 100 - P X>=0 NTCE ; ошибка P GOTO A3 ; перейти к циклу ; ==== TXT0: .TEXT "\rPasiphae v1.2\0" TXT1: .TEXT "\rКонец программы\0" TXT2: .TEXT "\rОшибка в < или >\0" TXT3: .TEXT "\rВведите код символа\0" TXT4: .TEXT "\rОшибка в [ или ]\0" .ENDP Интерпретатор Pasiphae v1.2 занимает 468 байт, из них 88 байт - текстовые сообщения.
Примечания** Авторский компилятор имел размер 240 байт, более поздние реализации достигают менее 200 байт [1]. Возможно, существует как минимум один компилятор размером 100 байт [5]. Приведённую здесь программу интерпретатора на ЯМК можно оптимизировать за счёт исключения избыточных проверок и текстовых сообщений. *** Предполагается, что создателя Brainfuck вдохновил язык FALSE. Этот язык был разработан Wouter van Oortmerssen в том же 1993 году. В отличие от большинства более поздних языков, FALSE предоставляет достаточно большой набор возможностей, в том числе именованные переменные, строки и лямбда-функции. Многие элементы языка, в том числе стек как основная структура данных, были заимствованы из Forth [6]. Но шесть из восьми команд Brainfuck (за исключением команд ввода-вывода) в точности совпадают с командами языка P". При этом, остается неизвестным, влиял ли последний на разработку. Литература |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
НПП "СЕМИКО" (383) 271-01-25 (многоканальный) |