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 (многоканальный) |