При переносе последней версии eForth на отечественную платформу успешно преодолены два препятствия — относительно низкое быстродействие 8-битной машинки, которая программируется на собственном входном языке, и скромный объём доступной двоичной памяти (см. 2.4.1), всего 4096 байт.
При написании 161eForth применялись готовые решения, подготовленные для Каллисто — входного языка нового поколения для отечественных ПМК. Это технология реализации форт-машины поверх десятичной и «гарвардской» архитектур, драйверы консоли и раскладка алфавитно-цифровой клавиатуры, а также основанный на них программный терминал, работающий по последовательному порту RS-232. Помимо «Электроники МК-161» и дистрибутива 161eForth вам может потребоваться самодельная накладная клавиатура, где на клавишах подписаны буквы русского и английского алфавитов. Буквы расположены по алфавиту построчно, слева направо и сверху вниз.
Доктор Чэнь-Хэнсон Тинг, автор современных версий eForth, подчёркивает в своей книге [1] важность понимания двух составляющих Форта. Это внутренний («адресный») интерпретатор, позволяющий оборудованию исполнять шитый код Форта, и внешний («текстовый») интерпретатор, отвечающий за диалог с человеком.
В двух статьях я подробно остановлюсь на наиболее радикальных решениях, использованных при реализации каждого из этих двух интерпретаторов на «Электронике». Изучение этих решений может оказаться полезным и вдохновляющим для переноса eForth на другие устройства с ограниченным объёмом памяти и производительности. Пониманию статей поможет начальное знакомство с программируемыми микрокалькуляторами (ПМК) и Фортом. Я поясню сложные моменты, уникальные для «Электроники МК» и транслятора eForth.
Начну с того, что слова eForth делятся на общеполезные и системные. Размер букв имеет значение. Имена обычных слов определены заглавными буквами, а системных — строчными. Свои нововведения в eForth я также сделал строчными буквами. Автор eForth предлагает вести основной диалог в режиме CAPS. Когда вам нужно использовать системное слово, переключитесь на время в строчные буквы (комбинация клавиш F P).
В статье все слова пишутся заглавными буквами, чтобы выделяться из текста. В нескольких ранних реализациях eForth заголовки системных слов были исключены и не выводились командой WORDS. Это помогало упростить внешний вид eForth и экономить внимание тех, кто впервые пользуется Фортом. В 161eForth заголовки этих слов сохранены в первую очередь из-за наличия декомпилятора слов двоеточия SEE, который не покажет имена системных слов, если убрать их заголовки.
Чтобы упорядочить статью и сделать её полезной, как справочник, несколько терминов мне пришлось употребить до их определения. Специалистам по Форту и ПМК эти термины должны быть знакомы. Новичкам иногда придётся заглядывать в соседние разделы (я проставил ссылки в нужных местах) или перечитать статью пару раз.
Сам 161eForth выложен здесь, вместе с исходным текстом, рисунком накладной клавиатуры и справкой words.txt с описанием всех реализованных слов: http://the-hacker.ru/2019/161eforth0.5b.zip
Также я выложил 5 маленьких видео на YouTube, иллюстрирующих работу 161eForth для тех, у кого нет МК-161: посмотреть видео.
eForth и его реализации
eForth разрабатывался, как современная замена широко известного транслятора фиг-Форта. Для переноса на МК-161 мною была выбрана 32-битная версия 5.2 транслятора 86eForth с косвенным шитым кодом, написанная в 2016 году на ассемблере MASM для операционной системы Windows. Эта версия подробно описана в третьем издании книги «eForth and Zen» [1]. Знающим английский советую разыскать и изучить эту книгу, она весьма полезна для понимания 161eForth.
В личном письме автор подтвердил, что 86eForth502.asm из этой книги — последняя версия eForth. В Интернете можно найти много англоязычной информации и по этой, и по предыдущим версиям eForth.
Развитие eForth шло по научному пути, которому учит профессор Вирт на примере своего языка программирования Оберон. Каждая последующая версия eForth была упрощением предыдущей версии. Всё, без чего можно обойтись, убиралось из языка. Остался тщательно продуманный набор сильных, выразительных языковых конструкций, чья мощь проверена на более, чем 40 реализациях eForth для разнообразных платформ. Теперь и на калькуляторе!
Будучи минималистичным диалектом Форта, eForth не ставит своей целью победить в гонке на самый крохотный Форт. Предлагаемый им набор слов вполне практичен и может легко расширяться программистом в нужную для его задач сторону.
Первая версия eForth была выпущена в 1990 году на ассемблере MASM для процессоров 8086 и работала под MS-DOS. Она содержала 31 машинно-зависимое слово ядра и 191 слово высокого уровня. Идея была проста — вы переводите на свой ассемблер всего 31 слово, и сразу получаете eForth на своём компьютере.
Такой подход повергся критике в Интернете, так как путь минимизации количества слов на ассемблере привёл к крайне низкому быстродействию для встроенных систем. Уже во второй версии eForth на ассемблере стало реализовываться максимальное количество слов, что выправило крен в сторону не только легко переносимой, но и практичной системы программирования.
Версия 5.2, выпущенная в 2016 году, содержит 71 слово «кода» и 110 слов «двоеточия». Четверть века поиска идеала привели к значительному уменьшению общего количества слов. При этом из соображений производительности увеличился процент слов, реализованных на низком уровне.
Предлагаемый 161eForth пользуется щедрыми плодами этого прогресса, но не претендует на дальнейшее развитие магистральной линии. Моя реализация предоставляет программисту все инструменты, присутствующие в версии 5.2. Когда архитектура МК-161 делает реализацию некоторых слов 86eForth невозможной или бессмысленной, вместо выкидывания лишнего я предоставляю программистам полноценную замену, взяв её из стандарта ANSI/ISO [4]. Ищущие минимализма могут самостоятельно выкинуть «лишние» слова, ведь по традиции 161eForth поставляется с исходным кодом.
При реализации eForth я придерживался авторского понимания. Например, по моему глубокому убеждению цикл FOR NEXT с начальным значением n должен выполняться ровно n раз. К такому же выводу со временем пришёл Чак Мур, автор языков Forth и colorForth. К сожалению, eForth использует устаревшее соглашение и выполняет такой цикл n+1 раз, со счётчиком от n до 0. Я не стал исправлять этот и несколько других недостатков, отдав предпочтение совместимости 161eForth с реализациями для других платформ.
Поскольку 161eForth является первой практичной системой программирования «на борту» для «Электроники МК-161», если не считать заводского языка, я проследил многолетнюю историю eForth и вернул в язык несколько слов, которые были полезными на других платформах и могут оказаться востребованными сейчас.
Например, новая-старая переменная ‘BOOT содержит токен (см. 3.1) слова, который выполняется первым после инициализации среды, но до начала диалога. По умолчанию ‘BOOT содержит токен TLOAD для интерпретации кода из «области текста» (см. 2.4.2). Это позволяет программисту подстраивать eForth под себя без перекомпиляции среды, которую пока невозможно производить на борту «Электроники».
Приоритетными задачами реализации были экономия двоичной памяти (см. 2.4.1) и повышение быстродействия. Их решение привело к драматичному уменьшению количества слов высокого уровня, ведь их код занимает эту драгоценную память, за счёт увеличения количества быстрых слов ядра, реализованных в дешёвой памяти программ (см. 2.4.3).
В итоге 161eForth содержит 129 слов кода, 78 слов высокого уровня и занимает 1816 байт двоичной памяти МК-161, то есть меньше её половины. Это даёт надежду на метакомпиляцию своей высокоуровневой части прямо на борту «Электроники».
Исходный текст eForth для МК-161 разбит на две крупные части. Написанное в системе команд МК-161 ядро содержится в файле eForth0.mkl. Слова высокого уровня определены на языке SP-Forth и размещены в файле eForth.f.
Также в дистрибутиве есть справочный файл words.txt, в котором документированы все слова 161eForth со стековой нотацией и кратким пояснением, в одну строчку.
1.1 Исходный текст ядра eForth0.mkl
Ядро eForth содержит исполняемый код, работающий в памяти программ МК-161 (см. 2.4.3), который компилируется на компьютере в файл eForth0.mkp стандартными средствами, например фирменным компилятором MKL2MKP.
Исходный текст ядра, содержащийся в файле eForth0.mkl, написан в латинской мнемонике. Например, команда ИПЕ для чтения регистра Е (он же R14) записывается в этой мнемонике, как RME. Будучи непривычной для владельцев советских ПМК, латинская мнемоника удобна для ввода с клавиатуры компьютеров. Действительно, набрать странное FX^2 проще, чем знакомое с детства Fx².
Файл eForth0.mkp является заготовкой ядра. Помимо кода примитивов он содержит заголовок ядра и таблицу имён tblNames, которую eForth.f во время компиляции переносит в десятичные регистры (см. 2.4.4). Именно на основе eForth0.mkp будет создано ядро eForth.mkp (см. 2.4.3), поэтому eForth0.mkl должен быть откомпилирован первым.
1.2 Исходный текст слов высокого уровня eForth.f
Файл eForth.f подаётся на вход замечательного отечественного компилятора SP-Forth [5]. Файл содержит определения всех слов высокого уровня. Со временем их можно будет определить на самом eForth и, возможно, компилировать прямо на борту «Электроники МК-161».
Во время компиляции eForth.f считывает заготовку ядра eForth0.mkp и с её помощью создаёт в текущем каталоге три файла для последующей загрузки в МК-161: eForth.mkp, eForth.mkd и eForth.mkb. Именно eForth.mkb содержит тела слов высокого уровня, хотя их заголовки размещены в файле eForth.mkd.
Четвёртый файл, eForth.mkt, написан на eForth вручную и может редактироваться на борту МК-161 с помощью встроенного редактора текста. Каждый из этих четырёх файлов я подробнее разберу ниже (см. 2.4).
2. Электроника МК-161
Производитель из Новосибирска называет МК-161 старинным сокращением ЭКВМ. Так назывались в СССР самые первые калькуляторы. Система команд МК-161 наследует системе команд советских калькуляторов «Электроника Б3-34» и «Электроника МК-61». Это означает, что программы, написанные для советских калькуляторов, пойдут на МК-161 без изменений или с незначительными изменениями.
Обратное неверно. eForth не пойдёт на советских ПМК, т.к. задействует множество ресурсов, появившихся впервые в МК-152/161 и отсутствовавших в предыдущих моделях серии.
Рассмотрим особенности входного языка и архитектуры МК-161, повлиявшие на 161eForth (далее просто eForth) и давшие обсуждаемой реализации eForth «русский акцент».
Первая из этих особенностей — последовательно выдерживающееся в МК-161 соглашение «старшее по младшему адресу». Например, число 1000=3×256+232 будет записано в двух последовательных байтах, как 3 и 232.
2.1 Косвенная адресация
Программировавшие советские ПМК слышали про косвенную адресацию. При прямой адресации мы явно указываем номер регистра, к которому обращаемся. Например Р ИП 44 считает содержимое регистра 44. Клавиша Р, появившаяся в МК-152, используется для обращения к регистрам с номером 15 и больше — эти регистры отсутствовали в советских ПМК.
При косвенной адресации номер нужного регистра заранее не известен. Этот номер содержится в другом регистре. Например, если регистр 8 содержит число 44, команда К ИП 8 считает содержимое регистра 44 (R44).
Клавиши К и Р можно комбинировать. Например, команда Р К БП 20 передаст управление (GOTO в латинской мнемонике) на адрес, хранящийся в R20.
Особенность, оказавшаяся важной для внутреннего интерпретатора eForth, связана с предварительным увеличением / уменьшением регистров при косвенной адресации. Эта особенность унаследована от советских ПМК.
Например, команды косвенного чтения К ИП 0, К ИП 1, К ИП 2 и К ИП 3 перед обращением к нужному регистру уменьшают на единицу содержимое регистров 0, 1, 2 или 3. Команды К ИП 4, К ИП 5 и К ИП 6 перед считыванием увеличивают на единицу содержимое регистров 4, 5 или 6.
Такая «модификация» адресного регистра позволяет обрабатывать в цикле целые группы регистров. Она похожа на ++R и --R в Си. Номер регистра-указателя важен. Именно он определяет, произойдёт его увеличение (регистры 4-6) или уменьшение (регистры 0-3) при косвенной адресации.
На архитектуру 161eForth повлияло то, что увеличение регистров 4-6 при косвенной адресации — предварительное. В результате указатель интерпретации (IP), размещённый в R6, всегда указывает на последний считанный байт шитого кода. В 86eForth IP всегда указывает на последующий байт, ещё не считанный.
Это верно и для указателя стека возвратов (RP), хранящегося в регистре 2. R2 всегда указывает на вершину стека возвратов.
Полезной особенностью МК-161 является отсутствие увеличения / уменьшения регистра, если косвенная адресация происходит с новой клавишей Р. Например, РКИП02 считает число с вершины стека возвратов, не меняя указатель. Это готовая команда Форта R@. Из написанного выше следует, что считанное значение на единицу меньше, чем адрес следующего токена, который выполнится после возврата из слова «двоеточия».
Когда вам придётся разрабатывать или изучать слова, тесно взаимодействующие с внутренним интерпретатором eForth — убедитесь, что до конца поняли этот тонкий момент, связанный с предувеличением.
2.2 Таблицы, упорядоченные и ассоциативные
Таблицы МК-161 размещаются в памяти программ (см. 2.4.3). Они появились в новосибирской «Электронике МК» и совершенно незнакомы экспертам по советским ПМК. Адрес используемой таблицы всегда хранится в регистре 9042, а вот обращение к ним различается.
Упорядоченная таблица это массив беззнаковых 16-битных целых. eForth содержит такую таблицу tblTokens с адресами примитивов (см. 3.1.1) — слов Форта, написанных в системе команд МК-161. Адресный интерпретатор (см. 3.2) использует tblTokens для быстрого исполнения шитого кода, поэтому eForth старается, чтобы в R9042 всегда содержался адрес этой таблицы.
Для обращения к упорядоченной таблице надо записать номер нужного элемента в R9210. Число n в регистре X заменится на значение элемента таблицы с номером n, отсчёт начинается с нуля.
Ассоциативные таблицы («поиск по значению») активно используются eForth, в первую очередь примитивом (FIND), ищущим слово по его имени. Также ассоциативная таблица tblCHPUT используется при выводе литеры на экран, чтобы обрабатывать перевод строки и другие управляющие коды.
Для поиска элемента n в ассоциативной таблице требуется записать n в R9212. Число n в регистре X (руководство его называет «индексом») заменится на 16-битное значение, записанное в таблице сразу после его «индекса» n.
Наличие этой быстрой, хотя и простенькой функции поиска, реализованной на ассемблере в «прошивке» МК-161, помогло eForth добиться приемлемого быстродействия при распознавании имён слов и компиляции программ. Конечно, для этого пришлось разработать не самые простые таблицы распознавания имени, «заточенные» под эту функцию. Об этом поговорим подробней во второй статье.
2.3 Прерывания и консоль
«Электроника МК» позволяет её владельцам писать программы на входном языке, реагирующие на определённые события — такие, как нажатие или отпускание кнопки, окончание счёта таймера.
eForth активно использует эту систему прерываний как для ввода с клавиатуры и отображения мигающего курсора при запросе такого ввода, так и для ввода-вывода через универсальный последовательный порт (RS-232).
Введённые с клавиатуры литеры ставятся в очередь bufKbd по мере нажатия клавиш. Это очень удобно и экономит время на системах с низким быстродействием. Переключения алфавитов и регистра обрабатываются прерыванием KeyPress и не занимают место в очереди. Долгое нажатие на клавишу вызывает автоповтор.
Когда очередь из 8 литер заполнена, а eForth ещё не готов обработать ввод (ситуация очень редкая), МК-161 издаст недовольный писк. Конечно, всю эту естественную работу клавиатуры хотелось бы не реализовывать в трансляторе, а получить «из коробки» МК-161, как сервис встроенной программы (прошивки). Но уж чем, как говорится, богаты.
После начала работы весь вывод eForth направлен на графический экран МК-161. Вывод литеры на него осуществляет относительно простая подпрограмма ChPut. Единственная сложность здесь связана с реализацией управляющего кода BS, «пробела назад». МК-161 использует пропорциональный шрифт. Поэтому в специальном буфере tblBS приходится запоминать позиции выведенных символов, откуда их позже берёт код вывода BS.
Во время диалога пользователь может с помощью слова IO> перенаправить весь ввод-вывод в последовательный порт RS-232, что даёт возможность программировать МК-161 с привычной клавиатуры компьютера или с другой МК-161. Слово CON> возвращает управление на консоль калькулятора.
2.4 Области памяти и установка eForth на МК-161
Память «Электроники МК-161» состоит из отдельно адресуемых памяти программ и регистровой памяти данных. В свою очередь, регистровая память неоднородна и делится на три большие области.
Регистры с номерами от 0 до 999 хранят «десятичные числа».
Регистры с номерами от 1000 до 8167 хранят целые от 0 до 255. Последние 3 Кбайта этой области с адресами от 5096 до 8167 называются областью текста.
Регистры с номерами от 9000 до 9999 называются регистрами функций. Эта служебная область адресного пространства напоминает порты ввода-вывода микропроцессоров. С помощью команд записи и чтения по этим адресам реализуется доступ к устройствам ввода-вывода, системе прерываний и т.п.
Чтобы установить eForth на «Электронику МК-161», достаточно передать в калькулятор четыре файла, например с помощью программы производителя MK.EXE:
- Записать eForth.mkp в память программ, начиная со страницы 0. Версия 0.5b занимает 74 страницы.
- Записать eForth.mkd в память десятичных данных
- Записать eForth.mkb в память двоичных данных
- Записать eForth.mkt в память текста
После передачи на калькулятор рекомендую тут же сохранить эти четыре файла в отдельном каталоге встроенного «электронного диска». Поскольку имя у них одинаковое, загрузить eForth можно сразу за один раз, как «пакет».
2.4.1 Двоичная память МК-161: eForth.mkb
Регистры «Электроники МК» с номерами от 1000 до 5095 используются для хранения чисел от 0 до 255. Эта область регистровой памяти калькулятора называется двоичной. К двум последовательным двоичным регистрам можно обращаться из eForth, как к одной 16-битной «ячейке», причём (как везде на МК-161) старшие 8 бит находятся в регистре с меньшим номером.
eForth использует эту крошечную «двоичную память», как свою основную. Именно с ней работают слова! и @, HERE и ALLOT, только отсюда исполняет шитый код адресный интерпретатор (см. 3.2). Здесь расположены переменные eForth, буфер ввода текста (TIB), словарь и стек откатов tblBS для реализации пробела назад.
4096 байт это очень скромно, по современным меркам. Поэтому огромные усилия были затрачены, чтобы вынести в другие области памяти всё, что только возможно.
2.4.2 Область текста: eForth.mkt
Сразу за двоичной памятью следует область текста, регистры с номерами от 5095 до 8167. Технически это такие же байтовые регистры, но возможность их записывать на диск и считывать отдельным файлом делают эту область особенной.
Для работы с «текстом» в eForth служит слово TLOAD. Оно подаёт всю эту область на вход текстового интерпретатора, как строку, длиной в 3072 литеры.
Существуют разногласия, как текст разбивать на строки. Редактор, встроенный в «Электронику МК», настаивает на длине строки в 24 литеры. Каллисто использует соглашение Форта, где строка содержит 64 литеры. eForth предоставляет выбор пользователю, считая весь текст одной длинной строкой. Можете использовать встроенный редактор МК-161. Можете написать свой, совместимый с Каллисто.
Вот начальное содержимое eForth.mkt, для удобства разбитое на три строчки:
: hi ." Привет, %user%!" CR ;
‘ hi ‘boot !
hi \
Первая строка определяет новое слово hi, приветствующее пользователя. Вторая строка берёт токен этого слова (см. 3.1) и размещает в переменной ‘BOOT (см. 1). Теперь область текста перестанет компилироваться при каждом запуске eForth. Вместо этого выполнится уже скомпилированное приветствие.
Последняя строка запускает слово hi, выводя приветствие на экран. Слово \ заканчивает интерпретацию текста, возвращая управление на пульт.
Чтобы скомпилировать произвольный файл текста, вам надо выйти в калькулятор командой BYE, выйти в главное меню и загрузить нужный файл в режиме ДОС. Также можно передать файл mkt с компьютера. Клавиша С/П вернёт вас в eForth, после чего командой TLOAD вы сможете откомпилировать файл, загруженный в область текста.
2.4.3 Память программ: eForth.mkp
Память программ МК-161 — изолированное адресное пространство. Оно тоже хранит байты, но они доступны только для чтения. Память программ содержит 10000 «шагов», что оказалось избыточным для eForth. Больше четверти памяти программ оказалась свободной, что даёт неплохой задел для развития транслятора.
Только в памяти программ могут быть реализованы «слова кода». Также сюда вынесены таблицы распознавания имени и все известные текстовые строки, что экономит двоичную память.
Некоторые слова, например C@, COUNT и TYPE, могут адресовать память программ, если адрес не является положительным числом. Например, фраза 0 C@ считает «шаг» (байт) с адреса 0 памяти программ.
2.4.4 Десятичная память: eForth.mkd
Регистры «Электроники МК» с номерами от 0 до 999 называются десятичными и содержат числа, используемые для обычных расчётов на калькуляторе — 12 десятичных знаков «мантиссы» и 2 десятичных знака «порядка». Форт рассчитан на работу с целыми числами до 4 байт длиной, такой ресурс явно избыточен для eForth.
Десятичная память использована для экономии драгоценной двоичной памяти. Сюда вынесены стеки данных и возвратов. Здесь же хранятся заголовки слов — как определяемых пользователем, так и встроенных, по одному регистру на заголовок. Такой подход позволяет переопределять даже слова, имеющие стандартные имена.
Стек в десятичной памяти приводит к ряду особенностей, характерных для Форта на МК-161. Во-первых, диапазон значений элементов стека огромен, он способных вмещать 32-битные целые. Необходимость «двойных целых» на МК-161 отпадает, хотя ради совместимости соответствующие слова eForth мною реализованы. «Двойные целые» представлены на МК-161, как два элемента стека, содержащие числа от 0 до 65535, кодирующие одно 32-битное целое со знаком в дополнительном коде. Старшие 16 бит такого числа размещается сверху, то есть по младшему адресу.
Побитовые логические операции AND, OR, XOR и NOT рассматривают свои аргументы, как 16-битные целые. Результат от 32768 до 65535 преобразуется в отрицательные числа от -32768 до -1. В eForth ложь кодируется нулём, а истина минус единицей. Также истиной считается любое значение, отличное от нуля.
Вторая особенность стека данных 161eForth — он содержит числа со знаком. Когда слово @ считывает число 65535 из 16-битной «ячейки», оно автоматически преобразовывается в -1. Предусмотрено специальное «беззнаковое» слово U@ для того, чтобы считать непосредственно 65535, со знаком «плюс».
Упомяну, что ради быстродействия два верхних элемента стека данных расположены не в десятичной памяти, а непосредственно в регистрах X и Y.
Тот факт, что десятичные регистры могут содержать дробные числа и числа с плавающей запятой, никак не используется eForth. Виртуальная машина eForth использует эти регистры для хранения 12-разрядных десятичных целых со знаком. Обращение к десятичным регистрам осуществляют слова C@ и C! — те же самые, что работают с любыми одиночными регистрами.
3. Внутренний интерпретатор
Ядро eForth это программа, написанная на входном языке МК-161. Её первая команда БП MAIN передаёт управление коду MAIN, который первым делом выясняет обстоятельства перезагрузки. Если её вызвал неправильный токен, МК-161 пискнет. При первом запуске, а также после включения МК-161, экран очищается. Далее MAIN вызывает подпрограмму Init для инициализации системы прерываний и всего, что нужно драйверам консоли МК-161.
После инициализации стеков данных и возвратов низкоуровневая часть старта завершена. Происходит невероятное для машин с гарвардской архитектурой — eForth переходит к исполнению «шитого кода» из байтовой памяти. Честь быть первым принадлежит слову, адрес заголовка которого записан в R43. Обычно это слово COLD.
Как же устроены слова высокого уровня (СВУ)? Любое слово состоит из двух частей, тела и заголовка. Заголовок хранится в десятичном регистре. Он помогает внешнему интерпретатору и декомпилятору найти имя и тело слова. Заголовок также содержит поле «лексикона» — набор флагов, помогающих внешнему интерпретатору правильно обработать найденное слово. Внутреннему интерпретатору намного важнее тело СВУ, расположенное в двоичной памяти и хранящееся в словаре. Он способен даже исполнять слова, у которых заголовок отсутствует.
Тело СВУ начинается с байта поля кода, который содержит адрес обработчика данного слова. Четыре обработчика СВУ написаны на входном языке МК-161 и начинаются на первой странице памяти программ. Мы разберём их все (см. 3.3), но главный из них называется DOLST и расположен по адресу 02, сразу после уже рассмотренной команды БП MAIN. Этот обработчик исполняет слова Форта, определённые с помощью двоеточия.
После байта поля кода идёт поле параметров, произвольной длины. В «словах двоеточия» поле параметров содержит «шитый код» — последовательность 16-битных токенов, каждый из которых обозначает одно, закреплённое за ним действие.
Сперва мы рассмотрим токен подробнее. Потом изучим внутренний интерпретатор INEXT, осуществляющий переход от одного токена к исполнению следующего. Автор eForth называет INEXT обработчиком примитивов. Завершим мы эту экскурсию по внутреннему интерпретатору разбором всех четырёх обработчиков СВУ.
3.1 Токены
Токен представляет слово в шитом коде и стеке, позволяя его быстро выполнить. Токен — указатель на тело слова, но суровая архитектура МК-161 внесла в эту простую идею свои коррективы. Разберём все виды токенов, начиная с токена примитивов.
3.1.1 Токен примитива
Всё слова, входящие в дистрибутив eForth, пронумерованы от 0 до 206. Эта нумерация сквозная, учитывающая как примитивы, так и СВУ. Так сделано, чтобы по номеру слова было легко восстановить его имя. Эти имена хранятся в памяти программ. Ссылку на нужное имя легко найти через таблицу заголовков.
Номер примитива и является его токеном. Как любой токен, примитив занимает в шитом коде два байта. Первый равен нулю. Второй содержит его номер. Таблица tblTokens позволяет быстро найти адрес кода примитива по этому номеру. Адрес tblTokens постоянно хранится в R9042 (см. 2.2), то есть для исполнения примитива всё всегда под рукой.
Слово XT> позволяет узнать адрес кода примитива по его номеру (токену). Так как код примитивов всегда располагается в памяти программ, полученный адрес всегда отрицательный (см. 2.4.3).
3.1.2 Токен СВУ
СВУ может иметь свой номер и связанное с ним стандартное имя, а может быть совершенно новым, созданным пользователем. Во всех случаях токен СВУ — адрес его поля кода (см. 3), то есть число от 1000 до 5095.
В шитом коде токен СВУ записывается весьма необычным образом. В первый байт записывается число сотен (число от 10 до 50), во второй — остаток от деления токена на 100 (число от 0 до 99).
Например, токен 1234 будет представлен двумя байтами 12 и 34. Компиляцию такого, да и любого другого токена осуществляет взятое из стандарта ANSI слово COMPILE,. Для чтения и записи токенов СВУ в шитый код служат слова XT@ и XT!.. Они же осуществляют доступ к адресам (см. 3.1.4), а слово XT@ способно также считать токен примитива.
3.1.3 Целые литералы
Целые литералы — разновидность токенов примитивов. Они достаточно необычны, чтобы рассмотреть их отдельно.
В шитом коде токены DOLIT и DOLITM занимают четыре байта. Первые два байта содержат уже рассмотренный токен примитива, то есть 0 и номер примитива. Последующие два байта содержат целое, которое данный литерал при исполнении положит на стек данных.
DOLITM отличается тем, что меняет знак числа перед тем, как положить его на стек. Он предназначен для реализации отрицательных чисел.
3.1.4 Адресные литералы
Подобно целым литералам, три адресных литерала BRANCH, ?BRANCH и DONXT занимают в шитом коде по 4 байта каждый. Первые 2 байта содержат токен примитива, последние два — адрес перехода.
Адрес записывается в том же формате, что и токен СВУ (см. 3.1.2). В первом байте идёт количество сотен, во втором — остаток от деления адреса на 100. Напомню, что из-за предувеличения (см. 2.1) адрес перехода содержит не адрес нужного токена, а число, на единицу меньшее.
Токен DONXT помогает реализовать «конечный цикл» FOR-NEXT (см. 1). Безусловный переход BRANCH нужен для реализации бесконечного цикла BEGIN-AGAIN. Условный переход ?BRANCH передаёт управление, если на вершине стека данных находится ноль («ложь»). Он служит для реализации условного оператора IF-THEN, выходов из «неопределённых циклов» BEGIN-UNTIL и BEGIN-WHILE-REPEAT.
3.1.5 Строковые литералы
Строковые литералы — разновидность токенов СВУ. В шитом коде строкового литерала после токена идёт байт с длиной строки, после которого — сама строка, от первого байта до последнего.
В eForth три строковых литерала: $"|, ."| и abort"|. Они определены в файле eForth.mkl, как токены STRQP, DOTQP и ABORQ соответственно. Основную «литеральную» работу выполняет за них слово do$, токен DOSTR.
Чтобы сделать размер статьи разумным, я не могу останавливаться слишком подробно на этой интересной теме, но неплохо знать об их наличии в eForth.
3.2 Адресный интерпретатор
Пришло время рассмотреть интерпретатор адресов, адрес которого всегда записан в регистре 9. Большинство примитивов завершают свою работу командой К БП 9, которая передаёт управление на метку INEXT.
INEXT: КИП6 Fx≠0 NPrime
NData: ВП 2 КИП6 + П7 F⟳
КИП7 П8 F⟳ КБП8
Первым делом адресный интерпретатор считывает командой КИП6 первый байт очередного токена. Если он нулевой, это примитив и обработкой токена займётся код под меткой NPrime.
Меткой NData обозначена обработка токена СВУ. Первый байт умножается на сто командой ВП 2, после чего КИП6 + прибавляет к результату второй байт токена (см. 3.1.2). Считанный токен заносится командой П7 в «рабочий регистр» WP (R7).
Мы знаем, что токен СВУ это адрес его поля кода, в котором содержится адрес обработчика. Команды КИП7 П8 считывают байт поля кода в R8, а команда КБП8 передаёт управление обработчику СВУ. Обработчик знает, что в R7 находится число, на единицу меньшее адреса поля параметров обрабатываемого слова.
Команды F⟳ с кодом 25 «прибираются» в стеке. Дело в том, что eForth хранит два верхних элемента стека данных непосредственно в регистрах X и Y стека МК-161. Такое решение ускоряет работу, но заставляет следить, чтобы эти важные данные не потерялись.
Осталось разобрать, как адресный интерпретатор исполняет примитивы.
NPrime: F⟳ КИП6 РРП9210 П8 F⟳ КБП8
Команда КИП6 считывает второй байт токена примитива. Команды РРП9210 П8 считывают из таблицы tblTokens адрес этого примитива (см. 2.2 и 3.1.1), а КБП8 передаёт этому примитиву управление.
Как и выше, F⟳ убирают лишнее из стека, восстанавливая содержимое регистров X и Y.
Адресный интерпретатор eForth такой крохотный, что он несколько раз продублирован в памяти программ. Основная копия исполняется по команде К БП 9, которой завершается большинство примитивов.
В качестве упражнения рекомендую изучить реализацию слова EXECUTE, размещённую после метки EXECU. Это вариант INEXT, который считывает токен не из шитого кода, а берёт его со стека данных.
3.3 Обработчики СВУ
Четыре разновидности СВУ имеют четыре разных обработчика: DOLST, DOVAR, DOCON и DOCONM. Выше мы уже видели, что адресный интерпретатор перед вызовом обработчика оставляет в R7 адрес поля кода обрабатываемого слова.
eForth.f узнаёт адреса этих обработчиков, считав заголовок ядра из файла eForth0.mkp. Это помогает ему правильно компилировать СВУ для «Электроники МК-161», размещая результат в файле eForth.mkb.
3.3.1 Работа слов двоеточия: DOLST и EXIT
Следующая после INEXT важная тема — что делает внутренний интерпретатор, встретив токен слова, определённого через двоеточия. Поле кода такого слова содержит число 2, поэтому INEXT передаёт управление на обработчик DOLST, который и выполняет нужную работу по началу интерпретации нового списка токенов.
DOLST: ИП6 КП2 F⟳
ИП7 П6 F⟳
INEXT:
Регистр 2, как мы уже разбирали (см. 2.1) содержит указатель стека возвратов RP. Команды ИП6 КП2 записывают в стек возвратов значение R6 — указателя интерпретации (IP). Позже это поможет вспомнить текущую позицию в старом списке токенов, где INEXT наткнулся на слово двоеточия. Теперь же ИП7 П6 переставляет IP на начало нового списка.
Сразу после кода DOLST размещён код INEXT, который исполнит первое слово нового списка токенов. Как везде, команды F⟳ помогают сохранить два верхних элемента стека данных.
Слова двоеточия обычно завершаются токеном EXITT, который делает обратную работу, в сравнении со DOLST — достаёт со стека возврата старое значение IP и возвращается к интерпретации старого списка токенов.
EXITT: РКИП02 П6 Сx 1 ИП2 + П2 F⟳
INEXT:
Команды РКИП02 П6 считывают старое значение IP с вершины стека возвратов (см. 2.1). После чего команды Сx 1 ИП2 + П2 исправляют значение RP, увеличивая его на единицу. Команда F⟳ восстанавливает стек, после чего INEXT исполняет очередное слово из старого списка токенов.
Конечно же, INEXT не может одновременно идти и после DOLST, и после EXITT. Чтобы так сделать, мною применён один древний трюк времён СССР. Вы тоже можете его освоить, изучив соответствующие строки файла eForth0.mkl
3.3.2 DOVAR, обработчик переменных и массивов
Слова, порождённые словами CREATE и VARIABLE, используют одинаковый обработчик DOVAR. Этот обработчик кладёт на стек адрес переменной, размещённой в поле параметров, которое идёт сразу после байта поля кода. Переменные VARIABLE занимают 2 байта, а созданные с помощью CREATE массивы содержат столько байт, сколько захочет программист.
DOVAR: КП3 Сx 1 ИП7 + КБП9
Команды КП3 сохраняют в стеке данных содержимое регистра Y. Одновременно в RY заносится число с вершины стека, освобождая RX под новое значение. После команд Сx 1 ИП7 + этим новым значением на вершине стека становится адрес поля параметров исполняемого слова. КБП9 передаёт управление INEXT, безо всяких трюков осуществляя переход к следующему слову.
3.3.3 Обработчики констант: DOCON и DOCONM
В отличии от DOVAR, обработчик констант обращается к полю параметров своего слова сам. DOCON считывает из него 16-битное значение константы. Это значение всегда положительно.
DOCON: КП3
ИП7 П5 Сx 256
КИП5 × КИП5 + КБП9
Команды КП3 сохраняют RY в стеке данных. Но на этот раз старая вершина стека данных возвращается в RX. Команды ИП7 П5 вытесняют её обратно в RY, одновременно готовя регистр-указатель R5 к считыванию значения константы. Далее Сx 256 заменяет мусор в регистре X на число 256.
Команды КИП5 × КИП5 + считывают константу из поля параметров на вершину стека данных, то есть в RX. Как мы помним, в МК-161 первый байт всегда старший. Он и умножается на 256, после чего к произведению прибавляется младший байт константы. Вся работа сделана, КБП9 передаёт управление следующему слову.
DOCONM работает точно также, только знак константы после считывания изменяется на противоположный. Отрицательные константы реализованы на МК-161 отдельным обработчиком ради быстродействия:
DOCONM: КП3
ИП7 П5 Сx 256
КИП5 × КИП5 + /-/ КБП9
Теперь мы полностью разобрались, как eForth исполняет свой код на «Электронике МК-161» из области данных, даже чуть затронув более глубокую тему строковых литералов (см. 3.1.5).
Во второй статье цикла я разберу внешний «текстовый» интерпретатор 161eForth, структуру таблиц заголовков и распознавания имени. Эта часть транслятора потребовала от меня разработки значительно более радикальных решений, на фоне которых разобранное выше — традиционный Форт, старый и добрый.
Счастливого программирования на Форте!
Литература
- Dr. Chen-Hanson Ting. eForth and Zen — 3rd Edition, 2017. Есть на Amazon Kindle.
- Баранов С.Н., Ноздрунов Н.Р. Язык Форт и его реализации. — Л.: Машиностроение. Ленингр. отд-ние, 1988.
- Семёнов Ю.А. Программирование на языке ФОРТ. — М.: Радио и связь, 1991.
- Стандарт ANS Forth. X3.215-1994. Перевод: http://oco.org.ua/download/forth/dpans94_ru.html
- Документация по SP-Forth: http://spf.sourceforge.net/docs/readme.ru.html
Комментариев нет:
Отправить комментарий