КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 710658 томов
Объем библиотеки - 1389 Гб.
Всего авторов - 273979
Пользователей - 124938

Новое на форуме

Новое в блогах

Впечатления

Stix_razrushitel про Дебров: Звездный странник-2. Тропы миров (Альтернативная история)

выложено не до конца книги

Рейтинг: 0 ( 0 за, 0 против).
Михаил Самороков про Мусаниф: Физрук (Боевая фантастика)

Начал читать. Очень хорошо. Слог, юмор, сюжет вменяемый.
Четыре с плюсом.
Заканчиваю читать. Очень хорошо. И чем-то на Славу Сэ похоже.
Из недочётов - редкие!!! очепятки, и кое-где тся-ться, но некритично абсолютно.
Зачёт.

Рейтинг: +2 ( 2 за, 0 против).
Влад и мир про Д'Камертон: Странник (Приключения)

Начал читать первую книгу и увидел, что данный автор натурально гадит на чужой труд по данной теме Стикс. Если нормальные авторы уважают работу и правила создателей Стикса, то данный автор нет. Если стикс дарит один случайный навык, а следующие только раскачкой жемчугом, то данный урод вставил в наглую вписал правила игр РПГ с прокачкой любых навыков от любых действий и убийств. Качает все сразу.Не люблю паразитов гадящих на чужой

  подробнее ...

Рейтинг: +1 ( 2 за, 1 против).
Влад и мир про Коновалов: Маг имперской экспедиции (Попаданцы)

Книга из серии тупой и ещё тупей. Автор гениален в своей тупости. ГГ у него вместо узнавания прошлого тела, хотя бы что он делает на корабле и его задачи, интересуется биологией места экспедиции. Магию он изучает самым глупым образом. Методам втыка, причем резко прогрессирует без обучения от колебаний воздуха до левитации шлюпки с пассажирами. Выпавшую из рук японца катану он подхватил телекинезом, не снимая с трупа ножен, но они

  подробнее ...

Рейтинг: 0 ( 1 за, 1 против).
desertrat про Атыгаев: Юниты (Киберпанк)

Как концепция - отлично. Но с технической точки зрения использования мощностей - не продумано. Примитивная реклама не самое эфективное использование таких мощностей.

Рейтинг: +1 ( 1 за, 0 против).

Язык Форт и его реализации [Сергей Николаевич Баранов] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

С.Н.БАРАНОВ
Н.Р. НОЗДРУНОВ

ЯЗЫК
ФОРТ
И ЕГО
РЕАЛИЗАЦИИ

Ленинград
«Машиностроение»
Ленинградское отделение
1988

ББК 32.973
Б24
УДК 681.3.06
Редакционная коллегия серии:
В. М. Пономарев (ответственный редактор), В. Б. Бетелин,
А. Ф. Верещагин, Ю. С. Вишняков, Г. П. Гырдымов, А. Н. Домарацкий, Ю. В. Капитонова, В. С. Кулешов, Д. Д. Куликов,
Р. А. Кюттнер, А. А. Лескин (зам. ответственного редактора),
В. Н. Мальцев, Г. И. Новиков, Г. В. Орловский, В. М. Пашин,
О. И. Семенков, Б. Я. Советов

Р е ц е н з е н т канд. техн. наук Л. Г. Осовецкий

Б24

Баранов С. Н., Ноздрунов Н. Р.
Язык Форт и его реализации.— Л.: Машиностроение. Ленингр. отд-ние, 1988.— 157 с, ил.
(ЭВМ в производстве.)
ISBN 5-217-00324-3

Книга является первой крупной отечественной публикацией
по языку Форт. Этот язык, получивший широкое распространение
за рубежом (особенно как средство программирования для персональных ЭВМ), стал привлекать внимание и советских программистов благодаря особенностям своей методологии. Язык Форт сочетает в себе достоинства интерпретирующих и компилирующих
систем и ориентирован на диалоговый режим работы. В книге приведено большое количество примеров.
Книга рассчитана на широкий круг инженеров-программистов
и может быть полезна пользователям электронно-вычислительной
техники, не имеющим специальной программистской подготовки.
2405000000-942 КБ-42-10-87
ББК 32.973
Б
038 (О1)-88
ISBN 5-217-00324-3
© Издательство «Машиностроение», 1988

ПРЕДИСЛОВИЕ

Язык программирования Форт (англ. forth —
вперед и одновременно сокращение от fourth — четвертый), которому посвящена эта книга, появился в начале
1970-х гг. в США. Его изобретатель Чарльз Мур первоначально применил его для разработки программного
обеспечения микроЭВМ, управляющей работой радиотелескопа [27]. Преимущества работы с языком Форт
вместо применявшегося ранее Ассемблера были настолько велики, что вскоре он стал использоваться и на
других специализированных ЭВМ.
Быстрый рост популярности языка Форт начался
с середины 1970-х гг., когда появились персональные
ЭВМ. Оказалось, что этот язык позволяет обходиться
сравнительно небольшим набором возможностей первых персональных ЭВМ, превращая их в удобный и
эффективный инструмент для самой разной работы. К
середине 1980-х гг. Форт выдвинулся на третье место
после языков Бейсик и Паскаль в качестве средства программирования для персональных ЭВМ, и рост его применения продолжается [7, с. 54]. Широкое распространение получили коммерческие программные продукты,
написанные на Форте: системы обработки текстов, пакеты машинной графики, трансляторы, видеоигры
[24, 30]. Стихийно быстрое распространение Форта и
его практический успех обусловили необходимость стандартизации языка. В 1983 г. был опубликован стандарт
"Форт-83" [3, 23, 26], в соответствии с которым ведется
изложение материала в этой книге.
Едва появившись, Форт вызвал ожесточенные
споры среди профессионалов-программистов, обсуждавших, в частности, является ли Форт еще одним
языком (если языком, то какого уровня — высокого или
низкого), операционной системой, интерпретатором или
компилятором. Одни считали Форт шагом вперед в раз1*

3

витии программирования, другие — ошибочным выбросом в сторону. К настоящему времени становится
ясным, что Форт представляет собой самостоятельный
унифицированный подход к разработке программного
обеспечения, который действительно позволяет решать
практические задачи — от небольших игровых программ до больших систем программного обеспечения, работающих в реальном времени [21; 7, с.56]. Унификация состоит в том, что Форт предполагает последовательное и систематическое использование очень небольшого числа «правил». Например, «мостом» между
аппаратурой и прикладной задачей служит всего
один язык программирования (Форт), в то время как
при традиционном подходе этот разрыв заполняется
в несколько приемов разнородными инструментальными средствами (ассемблер, универсальные языки
выского уровня, проблемно-ориентированные языки,
средства операционной системы).
Система программирования на Форте (фортсистема) обычно обеспечивает полный набор средств
поддержки для разработки и исполнения программ:
операционную систему, интерпретатор для диалогового
исполнения, компилятор, ассемблер, текстовый редактор и обслуживающие программы. Все эти компоненты
являются расширениями Форта и написаны на том же
Форте. Таким образом, Форт является одновременно
и системой для пользователя, и собственной метасистемой, т. е. системой, описывающей саму себя (см. приложение 1). В соответствии с общим принципом Форта
в форт-системе используется минимум правил и накладывается минимум ограничений, т. е. она обходится
почти без синтаксиса, жестко контролируемых интерфейсов для взаимодействия модулей, закрытых для
пользователя областей памяти, имеет лишь незначительный по объему встроенный контроль ошибок. Это
обеспечивает максимум возможностей для программиста, включая возможность изменять, добавлять или
удалять любую часть системы, позволяет расширять
систему в заданном направлении и вместе с тем сохраняет ее относительную независимость от аппаратуры.
Разумеется, Форт имеет и свои недостатки. Многие программисты считают, что форт-тексты трудно
читаемы из-за применяемой в Форте обратной польской
формы и различных неочевидных манипуляций со сте4

ком. В некоторых форт-системах отсутствуют средства
для получения независимого программного продукта.
Вызывает возражение отсутствие контроля типов при
взаимодействии модулей и незащищенность форт-системы от неправильных действий программиста. В то же
время становится ясно, что методология Форта находится в общем русле поисков в области технологии
программирования, хотя в настоящее время практически отсутствуют работы по методологическим и технологическим аспектам его применения, сравнимые
по значимости с исследованиями для традиционных
языков [13, 14, 19].
В целом цикл разработки программного продукта остается неизменным: анализ, проектирование, программирование, отладка. Однако лишь на первых двух этапах применяется традиционная технология «сверху —
вниз». Программирование и отладка ведутся по методу
«снизу — вверх». Благодаря этому отпадает необходимость в модулях-заглушках и в повторных тестированиях всего комплекса программ при заменах заглушек на
действительные модули, что сокращает время прохождения всего цикла и позволяет выполнить его несколько
раз за то же время. При разработке форт-программ наблюдается тенденция к вычленению относительно замкнутых групп модулей, каждая из которых проходит
свой цикл разработки. При этом обычно размер модуля
составляет от 1 до 3 строк текста, что резко контрастирует с традиционными языками. Для целей промышленного производства программ сочетание методологии
Форта с существующими [14] представляется весьма
перспективным, однако практические разработки в этой
области пока не известны.
В нашей стране также шли поиски принципов,
аналогичных тем, которые ныне определяют язык Форт,
в большей степени исходя из теоретических основ программирования [10]. Эти работы привели к созданию
интересных систем ДССП [9], КОМФОРТ [12], системы программирования на основе понятия «рабочей
смеси» [5, 17] и других. Интерес к языку Форт возрастал по мере получения сведений о нем и достижения
собственных результатов в этой области [2,6, 18,20].
Усилиями энтузиастов созданы самостоятельные реализации Форта, которые получают распространение
наряду с заимствованными реализациями (см. при5

ложение 2). Язык Форт включается в программное
обеспечение школьных компьютеров. Ведутся работы
по аппаратной реализации этого языка [12]. В 1985 г.
в рамках Рабочей группы по технологии программирования микропроцессорной техники при Комиссии по
технологии программирования при ГКНТ была создана
целевая подгруппа по языку Форт и родственным системам, задачей которой является обобщение и распространение опыта практического применения этих
средств в различных областях.
С 1978 г. в США выходит журнал "Форт Дименшнз"
(FORTH Dimensions) — основное периодическое издание для массовых пользователей языка Форт.
С 1979 г. проводятся ежегодные конференции, материалы которых, отражающие последние достижения в развитии форт-подхода, публикуются в виде
сборников. С 1983 г. издается журнал "Джорнал оф
Форт Эпликейшн энд Рисёч" (The Journal of FORTH
Application and Research, шифр ГПНТБ — V1467) —
издание для программистов-профессионалов. Журналы
«Байт» (BYTE, шифр B1841) и «Д-р Доббз Джорнал»
(Dr. Dobb's Journal, шифр W9464) посвящают
языку Форт специальные выпуски.
До сих пор знакомству широких кругов программистов нашей страны с этим языком препятствовало
отсутствие сколько-нибудь обстоятельных публикаций
о нем на русском языке. Данная книга является первой такой публикацией и написана с целью дать подробное и по возможности простое введение в язык Форт.
Мы надеемся, что знакомство с интересными принципами этого языка позволит читателям по-новому
взглянуть на свою программистскую практику и будет
полезно во всех отношениях.
Авторы
выражают
глубокую
благодарность
Г. С. Кудрявцевой, О. Н. Колесниковой и М. Б. Округину
за помощь в подготовке рукописи. Отзывы о книге
и предложения можно направлять по адресу: 191065,
Ленинград, ул. Дзержинского, 10, ЛО издательства
«Машиностроение».

Глава

1

ВВЕДЕНИЕ В ФОРТ

1.1 Основные понятия

Приступая к изучению нового для нас языка
программирования, мы прежде всего задаемся вопросами: какие конструкции есть в этом языке (какова
морфология), как они записываются (каков синтаксис)
и что означают (какова семантика). Например, в широко распространенном языке Паскаль имеется около
двадцати конструкций (идентификатор, число без
знака, присваивание, условный оператор и др.), синтаксис которых обычно задают с помощью граф-схем
или порождающих правил, а семантику объясняют на
основе той или иной машиннонезависимой модели вычислений. Часть языка ассемблера любой ЭВМ, предназначенная для записи машинных команд, содержит
по одной конструкции на каждую команду. Запись такой
конструкции обычно представляет отдельную строку
и состоит из мнемонического обозначения команды
и размещения операндов, а семантика определяется
в терминах реальных действий, которые данная команда
выполняет над ячейками памяти, регистрами и другими
компонентами архитектуры ЭВМ.
Язык Форт больше всего похож на язык ассемблера. Его синтаксис также максимально прост. Запись
каждой конструкции (команды) состоит из одного
слова — мнемонического обозначения, в качестве которого может выступать последовательность любых литер,
не содержащая пробела. Простота синтаксиса является
следствием того, что в качестве вычислительной модели
используется стековая машина. Слова-команды этой
машины снимают необходимые операнды со стека
и оставляют свои результаты (если они есть) также
на стеке. Таким образом, программа, написанная на
языке Форт, выглядит как последовательность слов,
каждое из которых подразумевает выполнение тех или
иных действий. Слова разделяются любым числом пробелов и переходов на новую строку; ограничение накла7

дывается только на длину слова — оно должно содержать не более 31 литеры. Стандарт языка определяет
сравнительно небольшой набор из 132 «обязательных»
слов. Среди них есть слова, позволяющие определять
новые через уже имеющиеся и тем самым расширять
исходный набор слов-команд в нужном для данной
задачи направлении. Некоторые часто требующиеся
расширения включены в стандарт в качестве «стандартных расширений» обязательного набора слов.
Вычислительная модель, лежащая в основе языка Форт, состоит из адресного пространства оперативной памяти объемом до 64 К байт, терминала и поля
внешней памяти на магнитных дисках объемом до 32 К
блоков по 1 К байт каждый. В пределах имеющегося
адресного пространства располагаются стек данных
и стек возвратов, словарь, буфер для ввода с терминала
и буфера для обмена с внешней памятью.
Стек данных обычно располагается в старших
адресах оперативной памяти и используется для передачи параметров и результатов между исполняемыми
словами. Его элементами являются двухбайтные
значения, которые в зависимости от ситуации могут
рассматриваться различным образом: как целые числа
со знаком в диапазоне от —32768 до +32767, как адреса оперативной памяти в диапазоне от 0 до 65535 (отсюда ограничение 64 К на размер адресного пространства), как коды литер (диапазон зависит от принятой
кодировки) для обмена с терминалом, как номера
блоков внешней памяти в диапазоне от 0 до 32767 или
просто как 16-разрядные двоичные значения. В процессе
исполнения слов значения помещаются на стек и снимаются с него. Переполнение и исчерпание стека, как
правило, не проверяется; его максимальный объем
устанавливается реализацией. Стандарт предусматривает, что стек растет в сторону убывания адресов; это
согласуется с аппаратной реализацией стека в большинстве ЭВМ, которые ее имеют.
Стек возвратов по своей структуре аналогичен
стеку данных, но используется особым образом некоторыми стандартными словами (подробнее об этом см.
в гл. 2).
Начальную часть адресного пространства обычно
занимает словарь (иначе «кодофайл») — хранилище
слов и данных. По мере расширения исходного набора
8

слов словарь растет в сторону увеличения адресов. Специальные слова из обязательного набора позволяют
управлять вершиной словаря — поднимать и опускать ее.
Наряду со стеком данных и стеком возвратов в
старших адресах оперативной памяти обычно размещается буфер на 64-100 байт для построчного ввода
форт-текста с терминала и буферный пул для обмена
с внешней дисковой памятью размером от 1 до 3 и более К байт. Доступ к этим буферам и фактический обмен
осуществляют специальные слова из обязательного
набора.
1.2. Работа в диалоговом режиме
Программирование на языке Форт является
существенно диалоговым. Работая за терминалом,
программист вводит слова-команды, а загруженная
в память ЭВМ форт-система, т. е. реализация языка
Форт на данной ЭВМ, немедленно выполняет обозначаемые этими словами действия. О своей готовности к обработке очередной строки текста форт-система обычно
сообщает программисту специальным приглашением
(например, знаком < , который печатается на терминале). Получив такое приглашение, программист набирает на терминале очередную порцию форт-текста,
заканчивая ее специальным управляющим символом
(например, нажимая клавишу "Ввод" или «Перевод
строки»). Получив сигнал о завершении ввода, фортсистема начинает обработку введенного текста (он размещается в буфере для ввода с терминала), выделяя
в нем слова-команды и исполняя их. Успешно обработав
весь введенный текст, форт-система вновь приглашает
программиста к вводу, и описанный цикл диалога повторяется. Многие форт-системы после успешного завершения обработки выводят на терминал подтверждающее сообщение (обычно ОК — сокращение от английского o'kay — все в порядке). Если во время обработки
введенного текста выявляется какая-либо ошибка
(например, встретилось неизвестное форт-системе
слово), то на терминал выводится поясняющее сообщение, обработка введенного текста прекращается
и форт-система приглашает программиста к вводу
нового текста.
9

Для завершения работы обычно предусматриваются
специальные слова-команды.
Непосредственно вводить с терминала большие
форт-тексты неудобно, поэтому их хранят во внешней
памяти на дисках, а с терминала программист вводит
только слова-команды, отсылающие форт-систему
к обработке тех или иных блоков из внешней памяти.
Для создания и исправления форт-текстов во внешней
памяти используется текстовый редактор, который является специализированным расширением форт-системы.
1.3. Стек данных и вычисления

Как уже говорилось, в основе вычислительной
модели для языка Форт лежит стековая машина. Ее
команды (слова в языке Форт) обычно используют
в качестве своих операндов верхние элементы стека,
убирая их со стека и возвращая результаты (если они
есть) на место операндов. Как правило, слова используют одно-два верхних значения на стеке. Для их описания будем применять следующую диаграмму:

При этом считаем, что самое верхнее значение в стеке
(последнее добавленное) находится справа.
Для работы с собственно вершиной стека имеются следующие слова:

Слово DUP (от DUPLICATE — дублировать) дублирует вершину стека, добавляя в стек еще одно значение,
равное тому, которое было до этого верхним. Слово
DROP (сбросить) убирает верхнее значение. Слово
OVER (через) дублирует значение, лежащее на стеке
непосредственно под верхним. Слово ROT (от ROTATE — вращать) циклически переставляет по часовой
стрелке три верхних значения в стеке. Наконец, слово
SWAP (обменять) меняет местами два верхних значения.
10

Можно работать с любым элементом стека с помощью слов

Слово PICK (взять) дублирует n-й элемент стека
(считая от нуля), так что О PICK равносильно DUP,
а 1 PICK равносильно OVER. Слово ROLL (повернуть) циклически переставляет п верхних элементов
стека (тоже считая от нуля) по часовой стрелке, так
что 2 ROLL равносильно ROT, 1 ROLL равносильно
SWAP, a 0 ROLL является пустой операцией.
Чтобы «увидеть» верхнее значение на стеке, используется слово . (точка)
, которое снимает
значение с вершины стека и печатает его на терминале
как целое число в свободном формате (т. е. без ведущих
нулей и со знаком минус, если число отрицательно).
Вслед за последней цифрой числа слово-точка выводит
один пробел, чтобы выводимые подряд числа не сливались в сплошной ряд цифр. Если программист хочет,
чтобы напечатанное значение осталось на стеке, он
должен исполнить текст DUP .. Слово DUP создаст
копию верхнего значения, а точка ее распечатает и
уберет со стека.
Перечисленные выше слова работают со значениями, уже находящимися в стеке. А как занести значение в стек? Язык Форт имеет следующее замечательное правило умолчания: если введенное слово фортсистеме не известно, то прежде чем сообщать программисту об ошибке, форт-система пытается понять это
слово как запись числа. Если слово состоит из одних
цифр с возможным начальным знаком минус, то ошибки нет: слово считается известным и его действие
состоит в том, что данное число кладется на вершину стека.
Теперь у нас достаточно средств, чтобы привести
примеры диалога. Рассмотрим следующий протокол
работы:

11

В ответ на приглашение к вводу (знак > , печатаемый
системой) программист вводит три числа: 5, 6 и 7.
Обрабатывая введенный текст, форт-система кладет эти
числа в указанном порядке на стек и по завершении
обработки выводит подтверждающее сообщение ОК
и вновь приглашает программиста к вводу. Далее программист вводит текст из четырех слов: SWAP и три
точки. Исполняя эти слова-команды, форт-система меняет местами два верхних элемента стека (5, б, 7
5, 7, 6,) и затем поочередно три раза снимает верхнее
значение со стека и печатает его. В результате на терминале появляется текст 6 7 5 и сообщение OK, указывающее на завершение обработки, после чего система
вновь выдает программисту приглашение на ввод.
Для внешнего представления чисел используется
система счисления, задаваемая программистом. Стандарт языка предусматривает следующие слова для
переключения в наиболее общеупотребительные системы:

Первоначально устанавливается десятичная система.
Если в процессе работы будет исполнено, например,
слово HEX (от HEXADECIMAL — шестнадцатиричная), то при дальнейшем вводе и выводе чисел будет
использоваться шестнадцатиричная система с цифрами
от 0 до 9 и от А до F до тех пор, пока основание системы
счисления не будет вновь изменено. Внутренним же
представлением чисел является обычный двоичный
дополнительный код, применяемый в большинстве
существующих ЭВМ.
Слова-команды, выполняющие арифметические
операции над числами, являются общепринятыми математическими обозначениями:

12

При сложении, вычитании и умножении не учитывается
возможность переполнения, в случае его возникновения
используются младшие 16 разрядов результата. Такая
арифметика называется арифметикой по модулю 65536
(2 в степени 16); ее основное достоинство состоит в том,
что она дает одинаковые в двоичном представлении
результаты независимо от того, как понимаются операнды: как числа со знаком в диапазоне от —32768
до +32767 или как числа без знака в диапазоне от О
до 65535.
Операции деления / , MOD и /MOD рассматривают свои операнды как числа со знаком. Из нескольких
известных математических определений деления с остатком (которые по-разному трактуют случаи, когда
операнды имеют разные знаки или оба отрицательны)
язык Форт использует так называемое деление с нижней
границей: остаток имеет знак делителя или равен
нулю, а частное округляется до его арифметической
нижней границы («пола») [11]. Во многих ЭВМ, имеющих аппаратную реализацию деления, применяются
другие правила для определения частного и остатка,
однако это обычно не вызывает трудностей при программировании, поскольку самый важный случай с
неотрицательными операндами все определения трактуют одинаково.
При выполнении деления возможно возникновение
ошибочной ситуации, если делитель — нуль или
частное не умещается в 16 разрядов (переполнение,
возникающее при делении —32768 на — 1 ) .
Одноместные операции ABS и NEGATE игнорируют
переполнение, возникающее в том единственном случае, когда операндом является число —32768, возвращая в качестве результата 0.
Наконец, одноместные операции 1+ , 1- , 2+ ,
2- выполняют действие, отраженное в их мнемонике:
увеличение или уменьшение значения на вершине стека
на 1 или 2; аналогично слово 2/ возвращает частное
13

от деления своего параметра на 2. Эти слова включены
в стандарт ввиду частного использования соответствующих действий.
Использование стека для хранения промежуточных значений естественным образом приводит к так
называемой "обратной польской форме" — одному из
способов бесскобочной записи арифметических выражений, подразумевающему постановку знака операции
после операндов. Например, выражение ( A / B + C ) *
(D*E — F*(G — Н)) записывается следующим образом:
A B / C + D E * F G H — * — *. Очевидно, что этот
текст выполним для Форта, если А, В и т. д.— слова,
которые кладут на стек по одному числу. Таким образом, форт-систему можно использовать как калькулятор. Чтобы вычислить, например, значение ( 2 5 + 1 8 +
+ 32) * 5, достаточно ввести такой текст: 25 18 +
+ 32 + 5 * . . В ответ система напечатает (исполняя
точку) ответ —375.
Чтобы повысить точность вычислений в последовательности умножение — деление, стандарт предусматривает два необычных слова:

Особенность этих слов состоит в том, что промежуточный результат А*В вычисляется с двойной точностью
и в качестве делимого для С используются все его 32
разряда. Окончательные же результаты являются
16-разрядными числами.
Наряду с описанной выше 16-разрядной арифметикой, язык Форт имеет полный набор средств для
работы с 32-разрядными целыми числами через стандартное расширение двойной точности. Внутренним
представлением таких чисел является 32-разрядный
двоичный дополнительный код, представляющий их как
числа со знаком в диапазоне от —2147483648 до
+ 2147483647 или как числа без знака в диапазоне
от 0 до 4294967295. При размещении в стеке число
двойной точности занимает два элемента: верхний —
старшая половина, предыдущий — младшая. Такое расположение делает простым переход от двойной точности
к обычной через слово DROP . Расширение двойных
чисел включает следующие слова:
14

Здесь обозначение типа АА подчеркивает, что значение
занимает два элемента стека. Хотя стандартное расширение этого не предусматривает, во многих реализациях языка Форт этот ряд продолжают операции
умножения и деления:

Операндами и результатами перечисленных слов
являются значения двойной длины, занимающие по два
элемента стека каждый; это обстоятельство отражено
в мнемонике, начинающейся с цифры 2, когда два
элемента стека выступают как одно целое, или с буквы
D (от слова DOUBLE — двойной), когда речь идет
об арифметическом значении двойной длины.
Следующие два слова являются переходными между арифметическими операциями одинарной и
двойной точности; их мнемоника включает букву М
(от слова MIX — смесь) и U (от слова UNSIGNED —
беззнаковый):

Слово UM* перемножает операнды А и В как 16-разрядные числа без знака, возвращая все 32 разряда получившегося произведения. Слово UM/MOD рассматривает 32-разрядное делимое АА и 16-разрядный
делитель В как числа без знака и возвращает получающиеся 16-разрядные остаток С и частное D. Если делитель — нуль или частное превышает 65535, то это
рассматривается как ошибка. Для перехода к двойной
точности с учетом знака многие реализации имеют слово
15

которое расширяет исходное число А
до числа двойной точности распространением знакового
разряда.
Чтобы при вводе различать числа двойной и одинарной точности, в сформулированное выше правило
умолчания внесена оговорка: если не найденное в словаре слово состоит только из цифр с возможным начальным знаком минус, то это число одинарной точности,
а если в слово входят точки (в любом месте), то это
число двойной длины. Пример использования фортсистемы как калькулятора для работы с числами двойной длины представляет следующий протокол работы:

В ответ на приглашение (знак > ) программист вводит
два числа двойной длины, операцию сложения этих
чисел и операцию печати результата. Выполняя указанные действия, форт-система печатает ответ (заметьте, что он уже не содержит точки) и подтверждающее
сообщение ОК.
1.4. Введение новых слов
Замечательное свойство языка Форт — это возможность вводить в него новые слова, расширяя тем
самым набор его команд в нужном программисту
направлении. Для введения новых слов чаще всего
используется определение через двоеточие — определение нового слова через уже известные. Такое определение начинается словом : (двоеточие) и заканчивается словом ; (точка с запятой). Сразу после двоеточия идет определяемое слово, а за ним — последовательность слов, через которые оно определяется. Например, текст : S2 DUP * SWAP DUP * + ; определяет
слово S2 , вычисляющее сумму квадратов двух чисел,
снимаемых с вершины стека S2
После ввода данного описания слово S2 можно исполнять и включать в описания других слов. При создании
таких определений рекомендуется тщательно комментировать все изменения стека. Слово ( (открывающая
круглая скобка) отмечает начало комментария; все
следующие литеры до первой ) (закрывающей скобки)
16

считаются комментарием и при обработке вводимого
форт-текста пропускаются.
Перепишем приведенное выше определение слова S2, показывая состояние вершины стека после
исполнения каждой строки:

По-видимому, минимальным требованием к документированности определения следует считать задание начального и конечного состояний вершины
стека при работе слова.
Рассмотрим подробнее работу форт-системы во время определения новых слов. Мы уже знаем, что получив
от программиста очередную порцию входного текста,
форт-система выделяет в ней отдельные слова и ищет их
в своем словаре. Эту работу выполняет текстовый интерпретатор форт-системы. Если слово в словаре не найдено, то текстовый интерпретатор пытается понять его
как число, используя описанное выше правило умолчания. Если слово найдено или оказалось записью числа,
то дальнейшие действия интерпретатора зависят от его
текущего состояния. В каждый момент времени текстовый интерпретатор находится в одном из двух состояний: в состоянии исполнения или в состоянии компиляции. В состоянии исполнения найденное слово исполняется (т. е. выполняется действие, составляющее его
семантику), а число кладется на стек. Если же интерпретатор находится в состоянии компиляции, то найденное
слово не исполняется, а компилируется, т. е. включается
в создаваемую последовательность действий для определяемого в данный момент слова. Найденное и скомпилированное таким образом слово будет исполнено
наряду с другими такими словами во время исполнения
определенного через них слова. Если требуется скомпилировать число, то текстовый интерпретатор компилирует особый литеральный код, который во время
исполнения положит значение данного числа на стек.
Проследим за работой текстового интерпретатора
по обработке уже рассмотренного определения слова
17

: S2 DUP * SWAP DUP * + ; . Предположим, что
перед началом обработки введенной строки интерпретатор находится в состоянии исполнения. Первым
словом является: (двоеточие), которое исполняется.
Его семантика состоит в том, что из входной строки
выбирается очередное слово и запоминается в качестве
определяемого, а интерпретатор переключается в состояние компиляции. Следующие слова, которые интерпретатор будет извлекать из входной строки ( DUP , * ,
SWAP и т. д.), будут компилироваться, а не исполняться, так как интерпретатор находится в состоянии компиляции. В результате с определяемым словом S2
связывается последовательность действий, отвечающая
этим словам. Процесс выделения и компиляции слов
будет продолжаться до тех пор, пока не встретится ;
(точка с запятой). Это слово особенное, оно имеет так
называемый «признак немедленного исполнения».
Слова с таким признаком исполняются независимо от
текущего состояния текстового интерпретатора, поэтому точка с запятой будет вторым исполненным словом
после двоеточия. Семантика точки с запятой заключается в том, что построение определения, начатого двоеточием, завершается и интерпретатор вновь переключается в состояние исполнения. Поэтому после ввода
определения слова S2 мы тут же можем проверить,
как оно работает на конкретных значениях:

Слова с признаком немедленного исполнения (другим
примером такого слова помимо точки с запятой является
открывающая круглая скобка — начало комментария)
выполняются по-разному в зависимости от состояния
текстового интерпретатора. Некоторые из них даже
используются только в одном из этих состояний. Поэтому на диаграмме для таких слов будем отмечать эти
случаи, специально указывая состояние компиляции

Слово ( в обоих состояниях действует одинаково:
пропускает все следующие вводимые литеры до закрывающей круглой скобки включительно или до конца
18

введенной строки, если закрывающая скобка отсутствует.
Слово ; допустимо применять только в состоянии
компиляции: ;
(компиляция). Оно завершает построение нового определения и переключает текстовый интерпретатор в состояние исполнения.
Слово IMMEDIATE (немедленный)
устанавливает признак немедленного исполнения для последнего
определенного слова. (Подробнее использование этого
признака рассматривается в п. 1.7.)
Введенные слова можно исключить из словаря с помощью слова FORGET (забыть)
, которое выбирает
из входной строки следующее слово и исключает его из
словаря вместе со всеми словами, определенными
позже.
Разберем следующий протокол диалога:

Сначала программист вычисляет произведение от умножения 2 на 2 и получает ответ 4. Введя затем определение слова 2 как числа 3, он в дальнейшем получает
уже другой ответ. Исключив это определение слова
2 через FORGET, он возвращается к прежней семантике слова 2.
В процессе работы текстового интерпретатора программист может переключать его из состояния компиляции в состояние исполнения и обратно с помощью
слов [ (открывающая квадратная скобка)
и ] (закрывающая квадратная скобка)
. Слово [ имеет
признак немедленного исполнения и переключает интерпретатор в состояние исполнения, а слово ] переключает
его в состояние компиляции. Обычно эти слова используются внутри определения через двоеточие, чтобы
вызвать исполнение слова или группы слов, не имеющих
признака немедленного исполнения. Например, если
19

в тексте определения понадобилась константа FF00
(в шестнадцатиричной системе), а текущей используемой системой является десятичная, то было бы неправильно включить в текст определения фрагмент HEX
FFOO DECIMAL, поскольку слово HEX будет не выполнено, а скомпилировано и число не будет воспринято
правильно. Вместо этого следует писать: [ HEX ]
FFOO [ DECIMAL ] . В языке есть и другие способы,
чтобы выразить это же более точно и красиво.
Еще один пример дает использование слова LITERAL (литерал), имеющего признак немедленного исполнения, внутри определения через двоеточие. Оно
используется в виде: [ (значение) ] LITERAL, где
(значение) — слова, вычисляющие на вершине стека
значение, которое словом LITERAL будет скомпилировано в код как число. Во время исполнения определения
это значение будет положено на стек. Таким образом,
текст
[22*]
LITERAL внутри определения через
двоеточие эквивалентен употреблению слова-числа 4.
Это дает большие возможности для использования
констант, «вычисляемых» во время компиляции определения. Аналогичное соответствие имеет место и вне
определения через двоеточие, поскольку в состоянии
исполнения слово LITERAL не выполняет никаких
действий, и поэтому на стеке остается вычисленное
перед этим значение.
1.5. Константы и переменные, работа с памятью

Программисту часто бывает удобно работать
не с «анонимными» значениями, а с именованными. По
аналогии со средствами других языков эти средства
языка Форт называются константами и переменными.
Впоследствии мы увидим, что они являются не «изначальными», а (наряду с определениями через двоеточие) частными случаями более общего понятия «определяющие слова».
Слово CONSTANT (константа) А
работает следующим образом. Со стека снимается верхнее значение, а из входного текста выбирается очередное слово
и запоминается в словаре как новая команда. Ее действие состоит в следующем: поместить на стек значение А, снятое со стека в момент ее определения. Например, 4 CONSTANT ХОР . В дальнейшем при испол20

нении слова ХОР число 4 будет положено на стек.
Слово VARIABLE (переменная) А
резервирует
в словаре два байта, а из входного потока выбирает
очередное слово и вносит его в словарь как новую команду, которая кладет на стек адрес зарезервированной двухбайтной области. Можно сказать, что переменная работает, как константа, значением которой
является адрес зарезервированной двухбайтной области.
Работа с переменной помимо получения ее адреса
состоит в получении ее текущего значения и присваивании нового. Для этого язык Форт имеет следующие
слова:

Слово @ (читается «разыменовать») снимает со стека
значение и, рассматривая его как адрес области оперативной памяти, кладет на стек двухбайтное значение, находящееся по этому адресу. Обратное действие выполняет слово ! (восклицательный знак, читается «присвоить»), которое снимает со стека два
значения и, рассматривая верхнее как адрес области
оперативной памяти, засылает по нему второе снятое
значение. Эти слова можно использовать не только для
переменных, но и для любых адресов оперативной памяти. Следующий протокол работы показывает порядок использования переменной в сочетании с этими
словами:

В первой строке определяется переменная X, и ей присваивается начальное значение 1. Затем текущее значение переменной X распечатывается. После этого текущее значение меняется на противоположное по знаку
и вновь распечатывается.
Полезным вариантом слова ! является слово +!
(плюс-присвоить) N,A
, которое увеличивает на N
21

значение, находящееся в памяти по адресу А. Несмотря
на то, что это-слово легко выразить через + и ! :

оно включено в обязательный набор слов.
Слова, определенные через CONSTANT и VARIABLE ,— такие же равноправные слова форт-системы,
как и определенные через двоеточие. Их также можно
использовать в определениях других слов и исключать
из словаря словом FORGET.
Для работы со значениями двойной длины имеются
слова

из стандартного расширения двойных чисел. При размещении в памяти такие значения рассматриваются
как пары смежных двухбайтных значений одинарной
длины: сперва (в меньших адресах) располагается
старшая половина, затем (в больших адресах) младшая. Адресом значения двойной длины считается адрес
его старшей половины.
Константы и переменные позволяют программисту
использовать память в словаре вполне определенным
образом. А как быть, если требуется что-то иное? Общий принцип языка Форт состоит в том, чтобы не закрывать от программиста даже самые элементарные
единицы, из которых строятся его более сложные слова, а предоставлять их наравне с другими словами.
Элементарными словами для работы с памятью в словаре, помимо приведенных выше @ и ! , являются следующие:

Предполагается, что словарь занимает некоторую начальную часть адресного форт-пространства и у него
имеется указатель текущей вершины (словарь растет
в сторону увеличения адресов). Слово HERE (здесь)
возвращает текущее значение указателя (адрес первого
22

свободного байта, следующего за последним занятым
байтом словаря).
Слово ALLOT (распределить) резервирует в словаре область памяти, размер которой (в байтах) снимает со стека. Резервирование состоит в том, что текущее значение указателя изменяется на заданную величину, поэтому при положительном значении запроса
память отводится вплотную от вершины словаря, а при
отрицательном значении запроса происходит освобождение соответствующего участка памяти. Наконец,
слово , (запятая) выполняет так называемую «компиляцию» значения, снимаемого со стека: значение переносится на вершину словаря, после чего указатель
вершины продвигается на 2 (размер в байтах скомпилированного значения). Некоторые из приведенных
слов легко выражаются через другие:

Такая избыточность позволяет программисту быстрее
находить нужные ему решения и делает программы
более удобочитаемыми.
А как создать в словаре поименованную область
памяти? Можно завести область и связать ее адрес
с именем через описание константы: HERE 10 ALLOT
CONSTANT X10 . Слово HERE оставляет на стеке адрес текущей вершины словаря, затем при исполнении
текста 10 ALLOT от этой вершины резервируется
10 байт, после чего слово CONSTANT связывает адрес
зарезервированной области с именем Х10. В дальнейшем при исполнении слова Х10 этот адрес будет положен на стек.
Другой возможный путь состоит в использовании
слова CREATE (создать)
в таком контексте: CREAТE X10 10 ALLOT . Слово CREATE, подобно слову
VARIABLE , выбирает из входной строки очередное
слово и определяет его как новую команду с таким
действием: положить на стек адрес вершины словаря
на момент создания этого слова. Поскольку следующие
действия в приведенном примере резервируют память,
то слово Х10 будет класть на стек адрес зарезервированной области. Очевидно, что слово VARIABLE можно выразить через CREATE
23

или иначе (если мы хотим инициализировать нулем
значение создаваемой переменной):

(Другой аспект использования слова CREATE рассматривается в п. 1.10).
В стандарте определен ряд системных переменных,
к которым программист может свободно обращаться,
в том числе STATE (состояние)
А и BASE (осноА . Исполнение каждого из этих слов завание)
ключается в том, что на стеке оставляется адрес ячейки, в которой хранится значение данной переменной.
Переменная STATE представляет текущее состояние текстового интерпретатора: нуль для исполнения
и не нуль (обычно —1) для компиляции. Поэтому вся
реализация слов [ и ] , переключающих интерпретатор
из одного состояния в другое, сводится к одному присваиванию:

Переменная BASE хранит текущее основание системы счисления для ввода — вывода чисел, поэтому
реализация слов для установки стандартных систем
выглядит так:

Отсюда следует более изящный способ кратковременной смены системы счисления во время компиляции
определения: [ BASE @ HEX [ FF00 [ BASE ! ] . Сначала на стеке запоминается текущее основание, и система счисления переключается на основание 16, в котором и воспринимается следующее число FF00, после
чего восстанавливается прежнее основание. А как узнать текущее основание системы счисления? Исполнение текста BASE @ не поможет, поскольку ответом
всегда будет 10 (почему?). Правильный ответ даст
исполнение текста BASE @ DECIMAL . , в результате
чего значение основания будет напечатано как число
в десятичной системе. Еще более правильным было бы
24

использовать текст BASE @ DUP DECIMAL . BASE
!, который после печати основания в десятичной системе восстанавливает его прежнее значение.
1.6. Логические операции

В языке Форт имеется только один тип значений — 16-разрядные двоичные числа, которые, как мы
видели, рассматриваются в зависимости от ситуации
как целые числа со знаком или как адреса и т. д. Точно
так же подходят и к проблеме представления логических значений ИСТИНА и ЛОЖЬ: число 0, в двоичном
представлении которого все разряды нули, представляет значение ЛОЖЬ, а любое другое 16-разрядное
значение понимается как ИСТИНА. Вместе с тем стандартные слова, которые должны возвращать в качетве результата логическое значение, из всех возможных представлений значения ИСТИНА используют
только одно: число — 1 (или, что то же самое, 65535),
в двоичном представлении которого все разряды единицы. Такое соглашение связано с тем, что традиционные логические операции конъюнкции, дизъюнкции
и отрицания выполняются в Форте поразрядно над
всеми шестнадцатью разрядами операндов:

Как и в предыдущих случаях, эти операции не являются независимыми: операция отрицания (поразрядное инвертирование) легко выражается через
исключающее ИЛИ (поразрядное сложение по модулю два):

Нетрудно увидеть, что для принятого в Форте стандартного представления значений ИСТИНА и ЛОЖЬ
все эти слова работают, как обычные логические
операции.
Логические значения возникают в операциях сравнения, которые входят в обязательный набор слов и имеют общепринятую программистскую мнемонику:
25

Эти операции снимают со стека два врехних значения,
сравнивают их как числа со знаком (операция«равно»
выполняет поразрядное сравнение) и возвращают результат сравнения как значение ИСТИНА и ЛОЖЬ
в описанном выше стандартном представлении. Из-за
стремления к минимизации обязательного набора операций в него не включены слова для операций смешанного сравнения, поскольку их легко выразить через
уже имеющиеся:

Для сравнения 16-разрядных чисел без знака имеется
слово
. Эта операция обычно используется для сравнения адресов, которые лежат
в диапазоне от 0 до 65535. Буква U (от UNSIGNED —
беззнаковый) в ее мнемонике говорит о том, что операнды рассматриваются как числа без знака.
Ввиду частого использования и возможности непосредственной реализации на многих существующих
ЭВМ в обязательный набор слов включены одноместные операции сравнения с нулем:

При этом слово 0= можно использовать вместо NOT
как операцию логического отрицания, и в отличие от
NOT оно будет правильно работать при любых представлениях логического значения ИСТИНА.
Описанные выше двухместные операции сравнения
естественным образом выражаются через сравнения
с нулем:

26

Стандартное расширение двойных чисел имеет
аналогичные слова для сравнения 32-разрядных значений:

Слова D< и DU< различаются тем, что первое рассматривает свои операнды как числа со знаком, а второе — как числа без знака. Для слов D 0 = и D= такое
различие несущественно, их, например, можно определить так:

Слово OR (логическое ИЛИ) в определении слова
D 0 = логически складывает старшую и младшие половины исходного 32-разрядного значения. Нулевой результат будет получен тогда и только тогда, когда
исходное значение было нулевым. Следующее слово
0= преобразует этот результат к логическому значению
в стандартном представлении. Исполнение слова D=
состоит в вычислении разности его операндов в сравнении этой разности с нулем.
1.7. Структуры управления

Во всех приводившихся выше определениях слов
тело определения записывалось как последовательность уже известных слов-команд; семантика определяемого таким образом слова состоит в последовательном выполнении слов-команд тела. Помимо последовательного исполнения традиционными приемами
в программировании стали ветвление (выбор между
разными последовательностями действий) и цикл (многократное повторение одной последовательности действий).
В языке Форт тоже имеются условные операторы
и циклы, реализованные с помощью специальных словкоманд, которые легко выражаются через другие, более
элементарные слова (см. гл. 2). Это открывает путь
к реализации таких вариантов структур управления,
которые больше всего подходят для данной задачи,
27

что дает программисту широкие возможности для создания новых структур управления.
Стандарт языка предусматривает ряд слов для
построения условных операторов и циклов в некотором
конкретном виде. Эти слова используются внутри определений через двоеточие и разделяют тело определения
на отрезки. Действия, соответствующие словам из этих
отрезков, выполняются, не выполняются или выполняются многократно в зависимости от условий, проверяемых во время выполнения данного определения. Условные операторы и циклы могут свободно вкладываться друг в друга.
Условный оператор строится с помощью слов:

Внутри определения через двоеточие отрезок текста
IF (часть-то) ELSE (часть-иначе) THEN задает следующую последовательность действий. Слово IF (если)
снимает значение с вершины стека и рассматривает
его как логическое. Если это ИСТИНА (любое нулевое
значение), то выполняется часть «то» — слова, находящиеся между IF и ELSE , а если ЛОЖЬ (равно
нулю), то исполняется часть «иначе» — слова между
ELSE и THEN . Сами слова ELSE (иначе) и THEN (то)
играют роль ограничителей для слова IF и самостоятельной семантики не имеют. Часть «иначе» вместе со
словом ELSE может отсутствовать, и тогда условный
оператор имеет сокращенную форму IF (часть-то)
THEN . Если логическое значение, снимаемое со стека
словом IF, ИСТИНА, то выполняются слова, составляющие часть «то», а если ЛОЖЬ, то данный оператор
не выполняет никаких действий. Обратите внимание,
что условие для слова IF вычисляется предшествующими словами.
Для примера рассмотрим определение слова ABS,
вычисляющего абсолютное значение числа:
Слово DUP дублирует исходное значение, следующее
слово 0< проверяет его знак, заменяя копию на логическое значение — результат проверки. Слово IF
снимает со стека этот результат, и если это ИСТИНА,
28

то лежащее на стеке исходное отрицательное значение
словом NEGATE заменяется на противоположное.
Еще пример: стандартное слово ?DUP дублирует
верхнее значение, если это не ноль, и оставляет стек
в исходном состоянии, если на вершине ноль:
В спецификации данного слова косая черта разделяет
два варианта результата, который это слово может
оставить на стеке. Примером использования полного
условного оператора может служить определение слова
S > D , расширяющего исходное значение до значения двойной длины распространением знакового
разряда:

Это слово добавляет в стек — 1 , если число на вершине
стека отрицательно, или 0 в противном случае. Таким
образом, выполняется распространение знакового разряда на старшую половину значения двойной точности.
В стандарт языка Форт включены циклы с условием
и циклы со счетчиком. Циклы первой группы образуются с помощью слов

и имеют две формы:

В обоих случаях цикл начинается словом BEGIN (начать), которое служит открывающей скобкой, отмечающей начало цикла, и во время счета не выполняет
никаких действий.
Цикл BEGIN—UNTIL называется циклом с проверкой в конце. После исполнения слов, составляющих
его тело, на стеке остается логическое значение — условие завершения цикла. Слово UNTIL (пока не) снимает
это значение со стека и анализирует его. Если это ИСТИНА (не нуль), то исполнение цикла завершается, т. е.
далее будут исполняться слова, следующие за UNTIL,
29

а если это ЛОЖЬ (нуль), то управление возвращается
к началу цикла от слова BEGIN . Например, определение слова, вычисляющего факториал, может выглядеть так:

Как и ранее, в комментариях, сопровождающих каждую строчку текста, мы указываем значения, которые
остаются на вершине стека после ее исполнения. Такое
документирование облегчает понимание программы
и помогает при ее отладке.
Цикл с проверкой в начале BEGIN — WHILE —
REPEAT используется, когда в дикле есть действия,
которые не надо выполнять в заключительной итерации.
Исполнение слов, составляющих его тело-1, оставляет
на стеке логическое значение — условие продолжения
цикла. Слово WHILE (пока) снимает это значение со
стека и анализирует его. Если это ИСТИНА (не нуль),
то исполняются слова, составляющие тело-2 данного
цикла до ограничивающего слова REPEAT (повторять), после чего вновь исполняется тело-1 от слова
BEGIN. Если же значение условия ЛОЖЬ (нуль),
то исполнение данного цикла завершается и начинают
выполняться слова, следующие за REPEAT . Заметьте,
что в отличие от цикла BEGIN-UNTIL, значение ИСТИНА соответствует продолжению цикла. Для примера
рассмотрим программу вычисления наибольшего общего делителя по алгоритму Евклида:

30

Для организации циклов с целочисленной переменной — счетчиком цикла — используются слова

Такие циклы записываются в одной из следующих двух
форм: DO LOOP или DО + L O O P .
В обоих случаях цикл начинается словом DO (делать),
которое снимает со стека два значения: начальное (на
вершине стека) и конечное (второе сверху) — и запоминает их. Текущее значение счетчика полагается равным начальному значению, после чего исполняются
слова, составляющие тело цикла. Слово LOOP увеличивает текущее значение счетчика на единицу и проверяет условие завершения цикла. В отличие от него,
слово + LOOP прибавляет к текущему значению счетчика значение шага, которое вычисляется на стеке телом цикла и рассматривается как число со знаком.
В обоих случаях условием завершения цикла является
пересечение границы между A—1 и А при переходе от
прежнего значения счетчика к новому (направление
перехода определяется знаком шага), где А — конечное значение, снятое со стека словом DO.
Если пересечения не произошло, то тело цикла
исполняется вновь с новым значением счетчика в качестве текущего.
Такое определение позволяет рассматривать исходные параметры цикла (начальное и конечное значения)
и как числа со знаком, и как числа без знака (адреса).
Например, текст 10 О DO (тело) LOOP предписывает
выполнять тело цикла 10 раз со значениями счетчика
0, 1, 2, ..., 9, а в случае 0 10 DO (тело) LOOP тело цикла будет исполнено 65 526 раз со значением счетчика
10, 11, ..., 32 767, - 3 2 768, - 3 2 767, ..., - 1 или (что
то же самое) со значениями счетчика 10, 11, ..., 65 535.
31

В то же время цикл 0 10 DO (тело) — 1 + L O O P будет
исполняться 11 раз (а не 10) со значениями счетчика
10, 9, ..., 0, поскольку пересечение границы между —1
и 0 произойдет при переходе от значения счетчика 0
к следующему значению — 1. Цикл 10 0 DO (тело) — 1
+ LOOP будет исполняться 65 527 раз со значениями
счетчика 0, —1, — 2, ..., —32 768, 32 767, .... 10 или
(что то же самое) 0, 65 535, 65 534
10.
Таким образом, цикл со счетчиком всегда выполняется
хотя бы один раз. Некоторые реализации предусматривают слово ?DO , которое не исполняет тело цикла
ни разу, если начальное и граничное значения оказались одинаковыми.
Внутри тела цикла слово I кладет на стек текущее
значение счетчика. Например, следующее определение
вычисляет сумму квадратов первых п натуральных
чисел:

Слово LEAVE (уйти), употребленное внутри цикла,
вызывает прекращение исполнения его тела; управление переходит к словам следующим за словом LOOP
или + LOOP .
В программировании часто применяется конструкция цикл в цикле. Чтобы во внутреннем цикле получить
текущее значение счетчика объемлющего цикла, используется слово J: DO ... I ... DO ... I ... J ... LOOP ... I
... LOOP . Первое вхождение слова I дает текущее значение счетчика внешнего цикла. Следующее вхождение I дает уже значение счетчика внутреннего цикла.
Чтобы получить счетчик внешнего цикла, надо использовать слово J.
По выходе из внутреннего цикла доступ к этому
значению вновь дает слово I. Этим слова I и J отличаются от переменных цикла в традиционных языках
программирования. Некоторые реализации предусматривают еще и слово К для досступа к счетчику
третьего объемлющего цикла.
32

1.8. Литеры и строки, форматный вывод чисел

В современном программировании важное место
занимает обработка текстовых данных. С каждой литерой, которую можно ввести с внешнего устройства
или вывести на него, связывается некоторое число —
код этой литеры, так что в памяти ЭВМ литеры представлены своими кодами. Стандарт языка Форт предусматривает использование таблицы кодов ASCII, в которой задействованы все числа в диапазоне от 0 до 127.
Каждый код занимает один 8-разрядный байт, в котором для представления литеры используются младшие 7 разрядов.
Такая привязка к одной конкретной кодировке не
является существенным препятствием к использованию
других, если сохраняется условие, что код литеры занимает один байт. В применяемых в нашей стране фортсистемах помимо ASCII применяются коды КОИ-7,
КОИ-8, ДКОИ и другие.
Для доступа к однобайтным значениям, расположенным в памяти, используются слова С@ А
и С! В,А
, которые аналогичны словам @ и ! . Префикс С (от слова CHARACTER — литера) говорит
о том, что эти слова работают с литерными кодами (однобайтными значениями).
Слово С@ возвращает содержимое байта по адресу
А, дополняя его до 16-разрядного значения нулевыми
разрядами. Обратное действие выполняет слово С! ,
которое засылает младшие 8 разрядов значения В
в память по адресу А.
Для политерного обмена с терминалом стандарт
предусматривает такие слова:

в

Слово KEY (клавиша) возвращает в младших разрядах значения А — код очередной литеры, введенной
с терминала. В отличие от слова С@ старшие разряды
зависят от реализации и могут быть ненулевыми. Обратное действие выполняет слово EMIT (испустить),
которое снимает значение со стека и, рассматривая его
2 Зак. № 966

33

младшие разряды как код литеры, выводит эту литеру
на терминал. Специальное слово CR (сокращение от
CARRIAGE RETURN — возврат каретки) выполняет
перевод строки при выводе на терминал.
Расположенные в памяти побайтно коды литер
образуют строку, которую можно напечатать на терминале словом TYPE (напечатать). Это слово снимает со
стека число — количество литер (оно должно быть
неотрицательно) и адрес начала строки (ее первого
байта):

Если число литер равно нулю, то ничего не печатается.
Обратное действие — ввод строки литер — выполняет слово EXPECT (ожидать), которое снимает
со стека длину и адрес области памяти для размещения
вводимых литер. Коды литер, последовательно вводимых с терминала, помещаются в указанную область
до тех пор, пока не будет введено заданное число литер
или не будет введена управляющая литера «возврат
каретки» (код этой литеры в память не заносится).
Фактическое число введенных литер сообщается в
стандартной переменной SPAN (размер), эти литеры
к тому же отображаются на терминале.
Ввиду его особой важности для кодирования пробела выделена специальная константа BL
(от
BLANK — пробел), которую для кода ASGII можно
задать так: 32 CONSTANT BL. При исполнении слова
BL на стеке остается код пробела. Чтобы вывести
пробел на терминал, имеются следующие стандартные
слова:

Слово SPACE (пробел) выводит на терминал один
пробел, а слово SPACES (пробелы) — несколько,
снимая их количество со стека (это значение, как и
длина строки в слове TYPE, должно быть неотрицательным).
Внутри определений через двоеточие можно использовать явно заданные тексты для вывода их на
терминал. При исполнении слова ." (точка и кавычка),
34

употребленного в контексте ." текст ", следующие за
ним литеры до закрывающей кавычки исключительно
печатаются на терминале. Пробел, отделяющий слово .", в число печатаемых литер не входит. Другое
слово .( (точка и скобка) отличается от ." тем, что
ограничителем текста является закрывающая правая
скобка, и это слово можно использовать как внутри
определений, так и вне их.
Помимо строки — поля байт, длина которого задается отдельно — язык Форт использует строки со счетчиком. Строка со счетчиком представляется полем байт,
причем в первом байте записана длина строки. Стандарт не определяет форму представления счетчика
длины, оставляя решение этого вопроса на усмотрение
разработчиков конкретной реализации. Для перехода
от строки со счетчиком к строке с явно заданной длиной
имеется слово COUNT (счетчик) А
B,N, которое
преобразует адрес А строки со счетчиком в адрес В
ее первой литеры (обычно это А+ 1) и значение счетчика. Строки со счетчиком используются при вводе слов
из входной строки. Стандартное слово WORD (слово)
снимает со стека код литеры-ограничителя и выделяет из входной строки подстроку, ограниченную
этой литерой (начальные вхождения литеры-ограничителя пропускаются). Из выделенной подстроки формируется строка со счетчиком, адрес которой возвращается в качестве результата. Слова-команды
языка Форт вводятся исполнением текста BL WORD,
а текстовая строка в слове ." — исполнением текста
QUOTE WORD , где слово QUOTE — константа,
обозначающая код кавычки. Литеры введенной строки
обычно располагаются вслед за вершиной словаря,
т. е. в незащищенном месте, и поэтому их нужно както защитить, если предполагается их дальнейшее использование. Некоторые реализации предусматривают
слово" (кавычка), которое используется внутри определения через двоеточие и во время его исполнения
кладет на стек адрес следующей строки как строки
со счетчиком. Это позволяет работать с явно заданными
текстами.
Чтобы программист мог задавать коды литер, не
связывая себя конкретной кодировкой, во многие
реализации введено слово С" , которое кладет на стек
код первой литеры следующего слова и может исполь2*

35

зоваться как внутри определения через двоеточие, так
и вне его:

Исполнение текста BL WORD COUNT DROP C@ оставляет на стеке код первой литеры следующего слова.
Далее этот код нужно либо скомпилировать как число,
либо оставить на стеке в зависимости от текущего
состояния текстового интерпретатора. Для этого используется уже известное нам слово LITERAL . Однако
включить его непосредственно в текст нельзя, так как
это слово имеет признак немедленного исполнения и будет исполняться во время компиляции данного определения. Чтобы скомпилировать слово с признаком немедленного исполнения, используется слово [COMPILE] (от COMPILE — компилировать)
(компиляция), которое само имеет такой признак. Оно принудительным образом компилирует следующее за ним
слово независимо от наличия у него признака немедленного исполнения. Таким образом, ввод строки, ограниченной кавычкой, с помощью слова С" можно задать
так: С" " WORD . Такой текст более нагляден, чем
тот, в котором используется конкретный код или обозначающая его константа.
Важной областью применения строковых значений
являются форматные преобразования, позволяющие
переводить число из машинного двоичного представления в строку литер. Эти преобразования выполняются
над числами двойной длины, результирующая строка
размещается во временном буфере PAD (прокладка),
который заполняется с конца. Такое название буфера
связано с тем, что он располагается в незащищенной
части адресного пространства между словарем и стеком. Слово PAD кладет на стек адрес конца этого
буфера и обычно определяется так:

В данном случае предполагается, что размер буфера
не будет превышать 100 байт.
Собственно форматное преобразование начинается
словом завершает
форматное
преобразование,
возвращая адрес и длину получившейся текстовой строки:
Для вывода знака «минус» имеется слово SIGN (знак):
которое добавляет в.буфер PAD знак «минус», если
параметр на вершине стека (число одинарной точности)
отрицателен.
С помощью перечисленных средств легко определить
стандартные слова D. и . для печати чисел и в свободном (минимальном) формате:

Слово D. сначала переводит абсолютное значение исходного числа в строку литер, потом добавляет к ней
возможный знак «минус», анализируя для этого стар37

шую половину первоначального значения, и затем
печатает получившуюся строку, выводя после нее еще
один пробел. Слово . дополняет свой параметр до значения двойной длины распространением знакового
разряда и обращается к слову D. для печати получившегося числа. Аналогичным образом реализуются
стандартные слова D.R и .R , которые печатают число
в поле заданного размера вплотную к его правому краю
(отсюда в их мнемонике присутствует буква R от
RIGHT — правый), добавляя при необходимости начальные пробелы:

В заключение рассмотрим программу шестнадцатиричной распечатки областей памяти словом DUMP
(дамп), которое получает на стеке адрес области и ее
длину:

Внешний цикл с шагом 16 формирует и печатает текстовую строку. Первый внутренний цикл с шагом —1 засылает в буфер PAD литерные значения, соответствующие распечатываемым байтам. Здесь слово DECODE С
C/С1 заменяет код С на некоторый код С1
(например, код литеры «точка»), если он не является
кодом литеры, которую можно напечатать на данном
терминале. Второй внутренний цикл с шагом —2 засылает в буфер четыре шестнадцатиричные цифры —
38

представления двухбайтных значений, разделяя их
одним пробелом. Далее в буфере PAD (т. е. в начале
строки) формируется адрес тоже в виде четырехзначного числа. Перед началом работы устанавливается
шестнадцатиричная система счисления, а в конце восстанавливается первоначальная. Следующий протокол
работы показывает результат исполнения слова DUMP
в конкретном случае:

Обратите внимание, что адреса и значения байтов напечатаны в шестнадцатиричной системе. Точки в литерном представлении байтов заменяют литеры, которые
не могут быть напечатаны.
1.9. Определяющие слова
Конструкции языка Форт, которые мы рассматривали до сих пор, имеют аналоги в других известных языках программирования. Например, определению
через двоеточие очевидным образом соответствует
описание процедуры в языках Фортран и Паскаль.
Теперь мы рассмотрим свойство языка Форт, которое
существенно отличает его от других и в значительной
степени определяет его как нечто новое.
Программируя какую-либо задачу, мы моделируем
понятия, в которых эта задача формулируется и решается, через понятия данного языка программирования.
Традиционные языки имеют определенный набор понятий (процедуры, переменные, массивы, типы данных,
исключительные ситуации и т.д.), с помощью которых
программист должен выразить решение исходной задачи. Этот набор выразительных средств фиксирован
в каждом языке, но поскольку он содержит такие
универсальные средства, как процедуры и типы данных, то опытный программист может смоделировать
любые необходимые ему понятия программирования.
Язык Форт предлагает вместо фиксированного набора порождающих понятий единый механизм порождения таких порожающих понятий. Таким образом, приступая к программированию задачи на языке Форт,
39

программист определяет необходимые ему понятия
и с их помощью решает поставленную задачу. Такое
прямое введение в язык нужных для данной задачи
средств вместо их моделирования (часто весьма сложного) через имеющиеся универсальные средства уменьшает разрыв между постановкой задачи и ее программной реализацией. Это упрощает понимание и отладку
программы, так как в ней более наглядно проступают
объекты и отношения исходной задачи, и вместе с тем
повышает ее эффективность, так как вместо сложного
моделирования понятий задачи через универсальные
используется их непосредственная реализация через
элементарные.
Новые понятия вводятся посредством определения
через двоеточие, в теле которого используются слова

Рассмотрим уже известное нам слово CONSTANT
(константа), которое используется для определения
констант. Его определение можно задать так:
Часть определения от слова CREATE до DOES> называется создающей (CREATE — создать), остальная
часть от слова D O E S > и до конца называется исполняющей (DOES — исполняет). В данном случае
создающая часть состоит из одного слова , (запятая),
а исполняющая часть — из слова @ (разыменование).
Рассмотрим исполнение данного определения на
примере 4 CONSTANT ХОР. Слово 4 кладет число 4 на
стек. Далее исполняется слово CONSTANT. Слово
CREATE , с которого начинается его определение, выбирает из входной строки очередное слово (в данном
случае ХОР) и добавляет его в словарь как новую команду. Создающая часть, состоящая из слова «запятая», переносит число 4 в память, компилируя его на
вершину словаря. Слово D O E S > , отмечающее конец
создающей части, завершает исполнение данного определения, при этом семантикой созданного слова ХОР
будет последовательность действий исполняющей
части, начиная от слова DOES> . В дальнейшем
исполнение слова ХОР начнется с того, что слово
40

D O E S > положит на стек адрес вершины словаря,
какой она была на момент начала работы создающей
части, после чего будет работать исполняющая часть
определения. Поскольку по данному адресу создающая
часть скомпилировала число 4, то исполняющая
часть — разыменование — заменит на стеке этот адрес
его содержимым, т. е. числом 4, что и требуется по смыслу данного понятия.
Рассмотрим другой пример. Введем понятие вектора.
При создании вектора будем указывать размер (число
элементов), а при обращении к нему — индекс (номер)
элемента, в результате чего получается адрес данного
элемента. Этот адрес можно разыменовать и получить
значение элемента или можно заслать по этому адресу
новое значение. Если желательно контролировать правильность индекса при обращении к вектору, то определение может выглядеть так:

Разберем, как работает данное определение при создании вектора 10 вектор X.
Создающая часть компилирует размер вектора
и вслед за этим отводит память на 10*2, т. е. 20 байт.
Таким образом, для вектора X в словаре отводится
область размером 22 байта, в первых двух байтах
которой хранится число 10 — размер вектора. При
обращении к вектору X на стеке должно находиться
значение индекса. Слово DOES> кладет сверху адрес
области, сформированной создающей частью, после
чего работает исполняющая часть определения. Проверив, что индекс I лежит в диапазоне от 1 до 10, она
оставляет на стеке адрес, равный начальному адресу
области плюс 1*2, т. е. адрес I-го элемента вектора, если
считать, что элементы располагаются в зарезервированной области подряд. Слово EXIT (выход) завершает
исполнение определения, что позволяет обойтись без
части «иначе» в условном операторе. Если окажется,
что индекс не положителен или больше числа элементов,
то будет напечатано сообщение "ошибка в индексе"
словом ." , и исполнение закончится через слово ABORT
41

(выброс). Если по каким-либо причинам контроль индексов не нужен, можно дать более краткое определение:

Если мы условимся считать индексы не от единицы,
а то нуля, то исполняющая часть еще более сократится
за счет исключения слова I — для уменьшения значения
индекса на единицу.
Таким образом, программист может реализовывать
варианты понятий, наиболее подходящие для его задачи. Исходный небольшой набор слов-команд фортсистемы он может избирательно наращивать в нужном
направлении, постоянно совершенствуя свой инструментарий.
Используемый в языке Форт способ введения определяющих слов связан с очень важным понятием —
частичной параметризацией. Определяющее слово задает целый класс слов со сходным действием, которое
описывается исполняющей частью определяющего слова. Каждое отдельное слово из данного класса характеризуется результатом исполнения создающей части —
тем или иным содержимым связанной с ним области
памяти, адрес которой передается исполняющей части
как параметр. Таким образом, исполняющая часть —
то общее, что характеризует данный класс слов,— во
время ее исполнения частично параметризуется результатом исполнения создающей части для данного отдельного представителя этого класса. Как создающая часть,
так и частично параметризованная исполняющая часть,
могут требовать дополнительных параметров для своего
исполнения (в примере для вектора это размер вектора
и индекс). Все это представляет программисту практически неограниченную свободу в создании новых понятий и удобных инструментальных средств.

Глава 2. РЕАЛИЗАЦИЯ И РАСШИРЕНИЯ

2.1. Шитый код и его разновидности
Логически можно выделить два подхода к реализации языков программирования — трансляцию и интерпретацию [10]. Транслятор преобразует входной
текст программы в машинный код данной ЭВМ; впоследствии этот код, объединяясь с другими машинными
модулями, образует рабочую программу, которую
можно загрузить в оперативную память и исполнить.
Интерпретатор непосредственно исполняет программу
на языке высокого уровня, рассматривая входной текст
как последовательность кодов операций, управляющих
его работой. Между этими полюсами располагается
целый спектр промежуточных подходов, состоящих
в предварительном преобразовании входного текста
программы в некоторой промежуточный язык с последующей интерпретацией получившегося кода. Чем
ближе промежуточный язык к машинному языку, тем
ближе данный подход к классической трансляции, а чем
ближе он к исходному языку программирования, тем
ближе этот подход к интерпретации.
Оптимальный вариант промежуточного языка должен существенно отличаться от исходного языка программирования и быть удобным для создания простых
и надежных интерпретаторов. Примером одного из
таких промежуточных языков является известный Пкод, используемый во многих реализациях языка Паскаль. Рассматриваемые нами варианты шитого кода
образуют специальный класс представлений промежуточных языков, особенно удобных для интерпретации
[22, 25, 29].
В общем случае промежуточный язык состоит из
набора элементарных операций, средств управления
локальной памятью для хранения и передачи данных
и средств управления процессом вычисления. Однако
если по своей сложности промежуточный язык при43

ближается к исходному машиннонезависимому языку
программирования, то целесообразность его использования снижается. Одним из приемов, направленных
на упрощение набора команд промежуточного языка,
является применение стековой архитектуры (или нульадресных команд). В этом случае для работы с памятью
требуется всего две операции: перенести значение со
стека в память и наоборот из памяти в стек. Кроме
того, стековая архитектура естественным образом приводит к стековому механизму передачи параметров,
который таким образом становится общей частью всех
реализаций данного промежуточного языка.
Шитый код особенно удобен для реализации виртуальных машин со стековой архитектурой: П-кода, Лиспа
и, конечно, Форта. Вместе с тем в каждом случае следует
различать качества реализации, которые обеспечиваются применением шитого кода, и качества, которые
присущи самому языку.
Известны четыре разновидности шитого кода: подпрограммная, косвенная и свернутая. Во всех четырех
случаях операции промежуточного языка представляются ссылками на подпрограммы, соответствующие
этим операциям. Перечисленные разновидности различаются способом представления этих ссылок и соответственно интерпретатором такой последовательности.
Подпрограммы, реализующие операции языка, разделяются на два класса: верхнего уровня, представленные в том же шитом коде, и нижнего уровня, реализованные непосредственно в машинном коде данной ЭВМ.
Подпрограммы нижнего уровня взаимодействуют
с интерпретатором шитого кода, используя выделенные
ему машинные ресурсы. Вместе с тем конкретный вид
этих ресурсов может быть самым разным для разных
архитектур ЭВМ.
Во всех разновидностях шитого кода его интерпретатор должен обеспечивать выполнение трех действий,
которые традиционно обозначаются так:
NEXT (следующий) — переход к интерпретации
следующей ссылки в данной последовательности ссылок;
CALL (вызов) — переход в подпрограмму верхнего
уровня, представленную в шитом коде;
RETURN (возврат) — возврат из подпрограммы
верхнего уровня на продолжение интерпретации.
44

Рис. 2.1. Подпрограммный шитый код

В силу его очевидной рекурсивности интерпретатор
использует специальный стек в качестве собственной
структуры данных. Этот стек называется стеком возвратов, чтобы отличать его от стека данных, обычно
используемого операциями промежуточного языка.
В качестве еще одной собственной структуры данных
интерпретатора выступает указатель на текущее место
в интерпретируемой последовательности ссылок, представляющих операции промежуточного языка.
Из всех разновидностей шитого кода подпрограммный максимально эффективен по времени исполнения.
Он удобен в том случае, когда архитектура данной
ЭВМ включает аппаратные стеки, команду перехода
на подпрограмму (перехода с возвратом), в которой
адрес возврата запоминается на вершине стека, и
команду возврата по адресу, находящемуся на вершине
стека. Для ЭВМ СМ-4 и «Электроника-60» это команды
JSR и RST, для микропроцессора К580 — команды
CALL и RET.
Структура подпрограммного шитого кода приведена
на рис. 2.1. Каждая ссылка на операцию промежуточного языка представляется в виде машинной команды
перехода на соответствующую подпрограмму. Стек
возвратов используется для сохранения адреса возврата этими командами, а в качестве указателя текущего места в интерпретируемой последовательности
ссылок выступает внутренний регистр счетчика адреса.
Высокоуровневая подпрограмма на промежуточном
45

языке представляет собой последовательность таких
же команд перехода с возвратом, которая заканчивается командой возврата по адресу, снимаемому с вершины
стека возвратов. Подпрограмма нижнего уровня должна заканчиваться такой же командой возврата для
продолжения обработки. Таким образом, в подпрограммном шитом коде интерпретатор реализуется непосредственным образом в структуре самого кода.
Действие NEXT состоит в исполнении пары команд
JSR/RST, действие CALL как таковое отсутствует, действие RETURN состоит в команде RST. Подпрограммный
шитый код в отличие от всех остальных разновидностей
допускает большую оптимизацию по времени счета за
счет непосредственной вставки подпрограмм нижнего
уровня в место их вызова.
Прямой шитый код (рис.2.2.) уступает подпрограммному по скорости исполнения, но дает выигрыш по
объему памяти, необходимой для его размещения.
В качестве последовательности операций промежуточного языка выступает последовательность адресов
соответствующих подпрограмм. Ее можно рассматривать как последовательность вызовов подпрограммного
шитого кода с удаленным кодом команды JSR (именно
это и дает экономию объема памяти примерно на
1/3 по сравнению с подпрограммным кодом). Поскольку
код команды отсутствует, требуется специальный интер-

Рис. 2.2. Прямой шитый код
46

Рис. 2.3. Косвенный шитый код

претатор последовательности ссылок. Подпрограммы
верхнего уровня должны начинаться машинными
командами, выполняющими действие CALL (положить
текущее значение указателя на стек возвратов, перевести указатель на начало последовательности адресов
данной подпрограммы, исполнить NEXT) и заканчиваться адресом подпрограммы RETURN (снять значение со стека возвратов и заслать его в указатель, исполнить NEXT). Подпрограммы в машинном коде должны
заканчиваться исполнением действия NEXT (скопировать адрес подпрограммы по текущему значению
указателя в рабочую ячейку, перевести указатель на
следующий элемент кода, передать управление по адресу в рабочей ячейке). В тех случаях, когда архитектура
ЭВМ позволяет выразить действия CALL и NEXT
одной-двумя машинными командами (например, для
ЭВМ СМ-4), эти команды вставляются непосредственно
в подпрограммы; если же требуется более длинная последовательность команд, то в подпрограммы вставляются команды перехода на соответствующие точки,
которые выносятся в отдельное подпрограммное ядро.
Косвенный шитый код уступает прямому по скорости
исполнения, но имеет то преимущество, что его высокоуровневые подпрограммы не зависят от машины, поскольку не содержат машинных кодов. Как и в случае
прямого кода, последовательность операций промежуточного языка состоит из последовательности адресов
подпрограмм, разница заключается в организации этих
41

подпрограмм и действиях интерпретатора (рис. 2.3.).
Теперь чтобы передать управление на машинный код,
в действии NEXT требуется выполнить еще одно разыменование. Подпрограмма верхнего уровня начинается
не машинными командами для действия CALL, а ячейкой, где записан адрес этой точки, и заканчивается
ячейкой, где записан адрес ячейки с адресом точки
RETURN. Подпрограмму на машинном языке представляет ячейка, где записан адрес начала соответствующего кода. Завершающим действием такой
подпрограммы, как и раньше, является исполнение
действия NEXT. Для этого обычно используется безусловный переход на соответствующую точку интерпретатора.
Наконец, свернутый шитый код, который может
быть как прямым, так и косвенным, отличается тем,
что вместо прямых адресов подпрограмм и кодов в нем
используются их свертки, которые, вообще говоря,
короче этих адресов. Таким путем за счет усложнения
доступа к подпрограммам в действии NEXT (через
использование таблицы сверток или специальной
функции, преобразующей свертку в соответствующий
адрес) можно добиться экономии памяти или возможности использовать больший диапазон адресов дляразмещения кода. Пусть, например, архитектура ЭВМ
требует, чтобы адреса подпрограмм и команд были
четными. Тогда, используя в качестве свертки, представляющей такой адрес, его значение, деленное на 2,
мы получаем возможность использовать вдвое большее
адресное пространство.
Таким образом, разновидности шитого кода предлагают широкий диапазон характеристик по скорости
исполнения и требованиям к памяти. Поскольку все
они логически эквивалентны, то выбор конкретного
варианта определяется конкретными требованиями.
В реализациях языка Форт встречаются все эти варианты.
2.2. Структура словарной статьи

Каждое слово-команда, известное форт-системе,
хранится в оперативной памяти (в словаре) в виде
специальной структуры данных — словарной статьи,
состоящей из поля имени, поля связи, поля кода и поля
48

параметров. Стандарт не фиксирует порядок взаимного расположения этих полей, оставляя его на усмотрение разработчиков реализации. Как правило, поля располагаются подряд в том порядке, как они перечислены
выше.
Поля имени и связи вместе образуют заголовок
словарной статьи, который нужен для поиска статьи
по имени слова. Результатом поиска являются поля
кода и параметров, которые вместе образуют собственно тело словарной статьи, определяющее действие,
связанное с данным словом, т. е. его семантику. Согласно стандарту словарную статью представляет адрес
ее поля кода, исходя из которого можно получить
адреса всех других ее полей. Поскольку именно этот
адрес компилируется в шитый код, то он также называется адресом компиляции.
Поле имени содержит имя слова в виде строки со
счетчиком. Стандарт ограничивает максимальную длину имени 31 литерой (5 двоичными разрядами), чтобы
остающиеся разряды в байте счетчика можно было
использовать под специальные признаки. Важнейший
из них — признак немедленного исполнения, который
обычно занимает старший разряд байта-счетчика. Таким образом, поле имени имеет переменную длину,
которая определяется значением счетчика.
Поле связи занимает 2 байта и содержит адрес
заголовка предыдущей словарной статьи. Через это
поле словарные статьи связаны в цепные списки, в которых можно вести поиск статьи по имени слова. Поле
связи первой статьи в списке содержит некоторое специальное значение, обычно нуль. Новые слова добавляются в конец списка, и поиск слов ведется от
конца списка к началу.
Тело словарной статьи реализуется через шитый
код в одной из его разновидностей. Для определенности примем за основу косвенный шитый код. В этом
случае интерпретатор шитого кода называется адресным интерпретатором форт-системы, поскольку интерпретирует последовательность адресов, каждый из
которых является адресом компиляции некоторой словарной статьи (адресом ее поля кода). В качестве
собственных данных адресный интерпретатор использует стек возвратов и указатель на текущее место
в интерпретируемой последовательности адресов.
49

Поле кода словарной статьи в случае косвенного
шитого кода занимает 2 байта и содержит адрес машинной программы, которая и выполняет действие, связанное с данным словом. Точка NEXT адресного интерпретатора обеспечивает этой программе доступ к полю
параметров данной словарной статьи: Для статей, соответствующих определению через двоеточие, поле кода
содержит адрес точки CALL адресного интерпретатора,
а поле параметров представляет собой последовательность адресов словарных статей, входящих в данное
определение. Завершается такая последовательность
адресом словарной статьи EXIT (выход), который
компилируется завершающей данное определение точкой с запятой. Для словарных статей нижнего уровня,
т. е. реализованных непосредственно в машинном коде,
поле кода содержит адрес соответствующей машинной
программы, которая обычно располагается в поле параметров этой статьи. Таким образом, можно сказать,
что поле кода словарной статьи содержит адрес машинной программы — интерпретатора поля параметров.
Все слова, определенные через двоеточие, имеют в качестве интерпретатора действие CALL адресного интерпретатора, слова нижнего уровня наоборот имеют
каждое свой отдельный интерпретатор в виде машинной
программы, размещенной в поле параметров. В частности, такой программой для слова EXIT является действие RETURN адресного интерпретатора.
На рис. 2.4. приведены адресный интерпретатор и
структура словарной статьи для определения
вычисляющего сумму натуральных чисел от I до А по
известной формуле. В качестве языка нижнего уровня
для записи действий адресного интерпретатора применяется очевидная нотация. Переменная RI обозначает указатель текущего места в интерпретируемом
коде, процедура MEM дает значение, находящееся по
заданному адресу в памяти форт-системы. Процедуры
RPUSH и RPOP используются для обращения к стеку
возвратов с тем, чтобы положить значение на его вершину и снять верхнее значение.
На рис. 2.4 изображены две словарные статьи:
верхнего уровня для слова F, определенного через
двоеточие, и нижнего уровня для слова EXIT, являю50

Рис. 2.4. Адресный интерпретатор и структура словарной статьи для косвенного шитого кода

щаяся к тому же частью адресного интерпретатора.
Поле имени каждой статьи начинается байтомсчетчиком, в котором записано число литер в имени
слова. Далее располагаются коды самих этих литер.
Вслед за полем имени идет поле связи, содержащее
адрес поля имени предыдущей словарной статьи. В тех
реализациях, где двухбайтные значения должны располагаться с четного адреса, поле имени тоже располагают с четного адреса, дополняя его (как в случае
слова EXIT) до четного числа байт специальным кодом
(обычно пробелом или нулем).
Поле кода словарной статьи содержит адрес
машинной программы. Для слова F это адрес точки
CALL адресного интерпретатора, для слова EXIT —
адрес его поля параметров, где и располагается соответствующая программа.
Наконец, в соответствии с техникой шитого кода
в поле параметров статьи для слова F располагается
последовательность адресов полей кода словарных
статей тех слов, из которых составлено его определение.
51

В этих адресах перед именем слова стоит знак
. Скомпилированное число занимает две ячейки: в первой
находится адрес специального служебного слова LIT
(от LITERAL — литерал), а во второй —значение
данного числа. Действие слова LIT состоит в том, что
значение числа кладется на стек данных, а указатель
интерпретации передвигается дальше, обходя это значение в шитом коде:

Здесь используется тот факт, что в момент перехода на
очередную машинную программу в действие NEXT указатель текущего места RI уже установлен на следующий
элемент интерпретируемой последовательности адресов.
Адреса полей словарной статьи имеют традиционные
обозначения, которые представляют аббревиатуры их
английских названий:

Представлением словарной статьи считается адрес ее
поля кода. Стандартное слово > BODY (от body тело) CFA
PFA преобразует его в адрес поля параметров. По аналогии во многих реализациях введен
следующий ряд слов для переходов между остальными
полями статьи:

Их реализация определяется принятой структурой
словарной статьи.
Стандарт предусматривает слово EXECUTE (исполнить) CFA
которое снимает со стека адрес поля
кода словарной статьи и исполняет ее. Непосредственно
под этим значением в стеке должны находиться параметры, необходимые данному слову. Такой механизм
открывает широкие возможности для передачи словкоманд в качестве параметров.
52

2.3. Стек возвратов
и реализация структур управления
Один из важных принципов языка Форт состоит
в том, чтобы предоставить программисту максимальный
доступ ко всем средствам его реализации. В соответствии с этим принципом собственные данные адресного
интерпретатора — стек возвратов — были сделаны доступными, для чего введены специальные слова:
А. Буква R (от RETURN —
и
возврат) в имени этих слов напоминает о том, что они
работают со стеком возвратов. Все эти слова можно
использовать только внутри компилируемых определений. Во время исполнения любого такого определения, представленного в шитом коде как подпрограмма
верхнего уровня, на вершине стека возвратов находится адрес того места, откуда данное определение было
вызвано (адрес возврата). Это значение было помещено туда действием CALL при входе в данное определение. Адрес возврата будет снят со стека возвратов
и использован для продолжения интерпретации действием RETURN, составляющим семантику слова
EXIT.
Перечисленные слова позволяют программисту
вмешиваться в описанный механизм вызова, реализуя
свои собственные схемы передач управления, и, кроме
того, использовать стек возвратов для хранения временных данных в пределах одного определения (все
положенные на стек возвратов значения должны быть
сняты с него перед выходом из определения). Однако
неосторожное манипулирование стеком возврата может
вызвать непредсказуемые последствия, вплоть до разрушения форт-системы. Слово >R (читается «на эр»)
переносит значения с вершины стека данных на стек
возвратов. Обратное действие выполняет слово R>
(читается «с эр»). Слово R@ (читается «эр разыменовать») копирует вершину стека возвратов на стек данных, сохраняя это значение на стеке возвратов.
С помощью описанных слов легко определить, например, приведенное выше слово LIT , компилируемое
в шитый код вместе со следующим за ним значением,
чтобы со время счета положить это значение на стек
данных:
53

Во время работы данного определения слово R@ кладет
на стек адрес возврата, указывающий в этот момент
на следующий после адреса статьи LIT элемент в интерпретируемой последовательности адресов
(см.
рис. 2.4). Слово @ разыменовывает этот адрес, таким
образом на стеке оказывается скомпилированное
в шитый код число. Слова R> 2+ > R увеличивают
адрес возврата на 2, чтобы обойти значение числа, т. е.
чтобы оно не было воспринято как адрес некоторой
статьи. Таким образом, в приведенном определении
слова LIT реализован интересный способ передачи
параметра через адрес возврата, невозможный в традиционных языках программирования.
Рассмотренные ранее структуры управления —
условный оператор и циклы — также легко выражаются
через эти понятия. По их образцу программист может
создавать собственные структуры управления, совершенствуя свой инструментарий. Реализация условного
оператора и циклов с проверкой опирается на следующие слова, аналогичные рассмотренному выше слову
LIT:

Слово COMPILE (компилировать) компилирует (т. е.
добавляет) на вершину словаря значение, находящееся
в шитом коде непосредственно за данной ссылкой на
статью COMPILE. Слово BRANCH (переход) переустанавливает указатель интерпретации по адресу,
скомпилированному вслед за данной ссылкой на статью
BRANCH . Наконец, слово PBRANCH снимает значение
со стека и анализирует его: если это ЛОЖЬ (нуль),
то оно работает, как BRANCH ,— указатель интерпретации устанавливается по адресу, скомпилированному вслед за данной ссылкой на статью PBRANCH ,
а если это ИСТИНА (не нуль), то при интерпретации
данной последовательности скомпилированный адрес
перехода будет обойден. То обстоятельство, что в определении слова PBRANCH используется
условный
оператор, для реализации условного оператора несущественно, потому что на практике слова BRANCH
и PBRANCH реализуются в форт-системах как подпрограммы нижнего уровня. Приведенные определения
54

лишь иллюстрируют выразительные возможности
языка, показывая, что даже такие, самые элементарные
действия можно задать машиннонезависимым способом
в виде высокоуровневых определений.
Вот еще пример определения важного стандартного
слова:

Слово LITERAL анализирует текущее состояние текстового интерпретатора: если это компиляция, то компилирует значение, находящееся на вершине стека, как
литерал в шитый код; в противном случае это значение
остается на стеке.
Теперь у нас достаточно средств, чтобы выразить
стандартные структуры управления, как обычные определения, через двоеточие. Например, слова для условного оператора можно определить так:

Все эти слова имеют признак немедленного исполнения,
который придается им словом IMMEDIATE , исполненным сразу после завершения каждого определения. Это
означает, что данные слова будут исполняться, а не
компилироваться, как остальные, во время обработки
тела определений текстовым интерпретатором фортсистемы.
На рис. 2.5 показана последовательность состояний
словаря и стека в разные моменты обработки фрагмента
А В IF С D THEN E F в теле определения через двоеточие. Эту обработку выполняет текстовый интерпретатор, находящийся в состоянии компиляции. В результате строится последовательность адресов словарных
статей в соответствии с принятой техникой шитого кода.
Пусть слова А, В, ..., F являются обычными фортсловами, не имеющими признака немедленного исполнения. Тогда соответствующие им адреса будут компилироваться на вершину словаря.
Состояние / на рис. 2.5. соответствует моменту
обработки непосредственно перед вводом слова IF.
55

Рис. 2.5. Компиляция условного оператора

На вершине словаря скомпилированы адреса статей
А и В и указатель вершины HERE указывает на следующий адрес. Слово IF имеет признак немедленного
исполнения, поэтому будет не скомпилировано, а исполнено.
Состояние // показывает результат исполнения слова IF . На вершину словаря скомпилирована ссылка
на статью ?BRANCH, вслед за которой отведено еще
2байта под адрес перехода, и адрес зарезервированного
места сохранен на стеке данных.
Дальнейшие слова С и D будут опять компилироваться. Состояние /// соответствует моменту перед
вводом слова THEN .
Слово THEN , как и IF, имеет признак немедленного исполнения, поэтому будет исполняться; в результате возникнет состояние IV. В этот момент определяется адрес перехода для скомпилированной словом IF
ссылки на статью ?BRANCH ; это текущее значение
указателя вершины словаря HERE , которое и вписывается в зарезервированные ранее 2 байта. Результат
дальнейшей компиляции приводит к состоянию V.
56

Аналогичным образом исполняется и определение
слова ELSE , которое компилирует обход части «иначе»
и вписывает адрес ее начала в качестве адреса перехода для ссылки ?BRANCH . В свою очередь, адрес
перехода для скомпилированного обхода будет вписан
завершающим условный оператор словом THEN.
Можно повысить надежность программирования
условного оператора введением контроля за соответствием слов IF , THEN и ELSE с помощью вспомогательного слова ?PAIRS :
которое снимает два значения со стека и, если они не
равны между собой (их разность не нуль), выдает
сообщение об ошибке с пояснительным текстом «Непарные скобки». Стандартное слово ABORT" (выброс
(исполнение) по своему употреблению
и кавычка) А
аналогично слову ." (точка и кавычка): оно снимает
значение со стека и, рассматривая его как логическое,
сигнализирует об ошибке, печатая следующий за ним
текст до кавычки, если это значение ИСТИНА (не нуль).
Усиленные таким контролем определения слов условного оператора выглядят следующим образом:

В этих определениях для контроля вместе с адресом
зарезервированного места передается число 1, которое
проверяется с помощью слова ?PAIRS в словах, использующих переданный адрес. Такой простой способ
контроля на практике оказывается вполне достаточным.
При этом программист может встроить любой другой
контроль по своему желанию.
Приведенное определение условного оператора
связано с реализацией стандартных слов BRANCH
и ?BRANCH , выполняющих переходы в шитом коде.
Из соображений эффективности эти слова обычно
задают как подпрограммы нижнего уровня в машинном
языке. Тогда в зависимости от архитектуры ЭВМ
может оказаться предпочтительней ле абсолютный, как
57

в приведенной реализации, а относительный адрес
перехода, компилируемый в шитый код сразу после
ссылки на статью BRANCH или ?BRANCH . Чтобы
сделать определения, использующие эти слова, машиннонезависимыми, стандарт предусматривает следующие
слова для организации таких ссылок:

Слово > MARK (от MARK — отметить) резервирует
место для ссылки вперед и оставляет адрес зарезервированного места на стеке. Слово > RESOLVE (от
RESOLVE — разрешить) снимает этот адрес со стека
и вписывает в него ссылку на текущую вершину словаря
в соответствии с принятой реализацией переходов
в шитом коде, согласованной с реализацией слов BRANCH
и ?BRANCH . Аналогично слова ,— это как раз адрес команды JSR в определении
слова CONST . Слово LATEST кладет на стек адрес
заголовка последней созданной статьи, т. е. статьи ХОР,
а слово NAME> преобразует этот адрес в адрес поля
кода. Поскольку со стека возвратов было снято одно
значение, то по завершении данного определения управление вернется в точку после вызова определения, вызвавшего данное, т. е. на продолжение работы после
вызова слова CONST. Аналогичные действия будут
исполнены при обработке текста 5 CONST ОТЛ . Если
теперь слово ХОР будет исполняться, то из точки NEXT
адресного интерпретатора управление будет передано
на машинную программу по адресу из поля кода, т. е.
на команду JSR в теле определения CONST . Эта команда перехода с возвратом передает управление на
точку DOES, сообщив ей в качестве своего адреса возврата адрес следующей последовательности ссылок —
исполняющей части определения CONST . Точка DOES
кладет на стек адрес поля параметров статьи ХОР
(в этот момент в рабочей ячейке W еще находится адрес
поля кода статьи ХОР , загруженный туда действием
NEXT) и исполняет действие CALL для исполняющей
68

части определения CONST . Следующее исполняемое
слово @ заменит на стеке адрес поля параметров статьи
ХОР числом 4, скомпилированным по этому адресу,
и затем слово EXIT завершит исполнение данного вызова слова ХОР .
Слово : тоже является определяющим, и его словарная статья имеет такую же структуру. Рассмотрим его
работу на примере трансляции определения CONST.
Создающая часть слова : состоит из слов CREATE
и ] . Первое выбирает из входной строки следующее
за двоеточием слово (в данном случае CONST ) и создает для него начало словарной статьи (заголовок
и поле кода), а второе переключает текстовый интерпретатор в состояние компиляции. Последнее в создающей части слово (;CODE) вписывает в поле кода создаваемой новой статьи текущее значение указателя интерпретации, т. е. адрес точки CALL, которая располагается в теле данного определения, после чего исполнение
двоеточия заканчивается. Поскольку теперь интерпретатор находится в состоянии компиляции, то следующие
вводимые слова будут компилироваться, заполняя поле
параметров статьи CONST последовательностью ссылок. Так будет продолжаться до тех пор, пока слово —
точка с запятой, отмечающее конец определения и имеющее признак немедленного исполнения, не переключит
интерпретатор обратно в состояние исполнения:

Определяющие слова являются механизмом для
создания классов слов со сходным «поведением»,
которое определяется исполняющей частью и включается в словарную статью слова через поле кода. Разница
между словами внутри одного класса состоит в значении
поля параметров, которое строится при определении
слова. Слова, определенные через двоеточие, составляют один из классов наряду с константами, переменными, списками слов и другими, которые программист
может вводить по своему усмотрению. При этом достигается существенная экономия памяти за счет того, что
общая часть всех слов одного • класса присутствует
в памяти в единственном экземпляре в статье определяющего слова.
69

Таким образом, в основе языка Форт лежат две
структуры:
внешняя — простейшая
синтаксическая
структура слова, представленного как последовательность литер, ограниченная пробелом, и внутренняя —
структура словарной статьи, в которой поле кода задает
интерпретатор поля параметров. Одним из частных
случаев такого интерпретатора является адресный
интерпретатор, и тогда поле параметров содержит
последовательность интерпретируемых адресов в шитом
коде. Этому случаю отвечает определение через двоеточие. Другие определяющие слова задают другие
интерпретаторы и соответственно другие структуры
поля параметров.
2.6 Встроенный ассемблер
Встроенный ассемблер позволяет программисту
задавать реализацию слов непосредственно в машинном
коде данной ЭВМ. Это дает ему возможность, с одной
стороны, повышать быстродействие программы до
максимально возможного уровня за счет перевода
интенсивно используемых слов непосредственно в машинный код, а с другой — использовать особенности архитектуры данной ЭВМ и «напрямик» связываться с операционной системой и внешним окружением.
Примеры обоих случаев дает сама форт-система.
Очевидно, что слова, выполняющие арифметические
операции или действия с вершиной стека, естественно
реализовывать непосредственно в машинном коде, поскольку скорость их исполнения в значительной степени
определяет быстродействие системы. Вместе с тем слово
1+ , увеличивающее на единицу значение на вершине
стека, можно определить как в машинном коде (особенно если в данной ЭВМ есть специальная команда увеличения значения на единицу), так и через двоеточие:
В последнем случае, если к тому же слово 1 реализовано
в виде отдельной словарной статьи 1 CONSTANT 1 ,
шитый код займет всего три ячейки, что может быть
короче аналогичной программы в машинном коде. Разумеется, при этом исполнение будет дольше за счет интерпретации последовательности из трех ссылок вместо
70

прямого исполнения соответствующих машинных команд. Также очевидно, что, например, форт-слова для
обмена с терминалом естественно задать на машинном
уровне через обращения к соответствующим функциям
операционной системы или непосредственно к аппаратуре.
Для определения слов непосредственно в машинном
коде стандарт языка Форт предусматривает следующие
слова:

составляющие стандартное ассемблерное расширение
обязательного набора слов.
Слово ASSEMBLER (ассемблер) является именем
списка слов, содержащего слова, с помощью которых
строится машинный код (мнемоники машинных команд,
обозначения регистров, способы адресации и т. д.).
Слово CODE (код) является определяющим для
слов нижнего уровня и обычно определяется так:

Оно используется в сочетании со словом END-CODE
(конец кода): C O D E
END-CODE, где «имя» является именем определяемого слова, а «машинный-код» — записью его реализации в машинном коде в соответствии с принятыми
соглашениями.
Поле кода такой словарной статьи содержит адрес
ее поля параметров, в котором располагается данный
машинный код.
Наконец, слово ;CODE , имеющее признак немедленного исполнения, позволяет задавать исполняющую
часть определяющих слов непосредственно в машинном
коде:

Оно используется внутри определения через двоеточие
для определяющего слова аналогично слову DOES>
71

и отделяет высокоуровневую создающую часть от исполняющей части, заданной в машинном коде. Во
время исполнения скомпилированного словом ;CODE
слова (;CODE) адрес машинной программы, составляющей исполняющую часть, будет заслан в поле кода
определяемого слова, которое таким образом получит
интерпретатор, реализованный в машинном коде. На
практике именно таким способом задают стандартные
определяющие слова : , CONSTANT и VARIABLE .
Конкретный вид машинной программы зависит от
архитектуры данной ЭВМ. Общим правилом является
то, что этот текст представляет собой последовательность слов, которые исполняются текстовым интерпретатором, в результате чего на вершине словаря формируется соответствующий двоичный машинный код.
Машинные команды записываются в естественной
для Форта обратной польской форме: сначала операнды, а затем слово, обозначающее мнемонику
команды.
Операнды — это слова, вычисляющие на стеке размещения операндов: номера регистров, адреса в памяти
и их модификации, значения непосредственных операндов и т. д.
Мнемоника команды, обычно состоящая из традиционного обозначения данной команды и запятой, снимает со стека размещения своих операндов и компилирует соответствующий двоичный код.
Включение запятой в имя слова для кода команды,
с одной стороны, подчеркивает тот факт, что данное
слово компилирует команду, а с другой стороны, позволяет отличать, например, часто встречающиеся мнемоники ADD , CН и т. д. от чисел, заданных в шестнадцатиричной системе.
В табл. 2.1 приведена запись одних и тех же машинных команд в традиционном ассемблере и встроенном
ассемблере разных форт-систем для нескольких распространенных типов ЭВМ. Как и в случае реализации
структур управления, во встроенный ассемблер можно
включить дополнительные средства контроля, которые,
учитывая формат машинных команд, проверяют правильность задания их операндов.
72

Т а б л и ц а 2.1. Сравнительная запись машинных команд в традиционном ассемблере и встроенном ассемблере форт-системы
Тип ЭВМ
СМ-4

ЕС ЭВМ

К580

БЭСМ-6

Традиционный ассемблер

Встроенный ассемблер
форт-системы

СМРВ # 1 2 , (Rl) +
JMP
NEXT
RTS

12 # Rl ) + СМРВ,
NEXT
JMP,
RTS,

STM
BALR
В

14 12 12 (, 13 STM,
15 0 BALR,
NEXT
B,

14,12,12(13)
15, 0
NEXT

MOV
А. В
LXI H, 15
POP
H

А В MOV,
H 15 LXI,
H
POP,

, UTC, =15
, XTA,
3, UTC, 777

0 0 5 # # UTC,
0 0 XTA,
3 777 UTC,

В гл. 3 рассмотрена реализация полного встроенного
ассемблера для микропроцессора К580, занимающая
100 строк текста на языке Форт.
Встроенный ассемблер форт-систем часто делают
«структурным», т. е. включают в него операторы ветвления и циклы, выполняющие переходы по значению
управляющих разрядов в специальном регистре. По
аналогии с такими же средствами языка Форт эти структуры задают с помощью тех же слов с добавлением,
запятой: IF, THEN, ELSE, BEGIN, UNTIL и т. д. При
этом вводят слова, обозначающие различные состояния
управляющих сигналов, а слова, реализующие структурные операторы компилируют команды переходов,
соответствующие указанным состояниям. Такой подход
позволяет во многих случаях избежать введения сложного механизма переходов на метку, поскольку ассемблерные вставки, для трансляции которых и существует
встроенный ассемблер, состоят, как правило, из нескольких команд.
Программа в машинном коде, заданная через слова
CODE или ;CODE , получает управление от адресного
интерпретатора и после ее завершения возвращает
управление интерпретатору на точку NEXT. Для связи
73

с адресным интерпретатором в списке слов ASSEMBLER
обычно предусматривается ряд констант, обозначающих точки входа, номера регистров и другие ресурсы
адресного интерпретатора. Аналогичным образом предусматривается связь с операционной системой, встроенным постоянным программным обеспечением и аппаратурой.
В общем объеме больших программ на Форте ассемблерные коды составляют незначительную часть ( в реализации самой форт-системы, например, 10—30 %),
но вместе с тем такая возможность делает программиста
максимально свободным в выборе средств для реализации своих идей.
2.7 Работа с внешней памятью

В настоящее время в каждом языке программирования тем или иным способом решаются вопросы
обмена с внешней памятью. Из-за огромного разнообразия внешних устройств и способов хранения информации на внешних носителях единый универсальный механизм обмена отсутствует. В каждом языке
определяется своя, в известной мере универсальная
файловая система, которая при реализации моделируется на реальной файловой системе конкретной
операционной системы.
Поскольку в язык Форт легко включать новые
слова-команды, то надобность в стандартных универсальных развитых средствах обмена отпадает.
В каждой реализации можно ввести такие средства
исходя из особенностей и возможностей применяемых
для данной ЭВМ файловой системы и конкретных внешних устройств. Вместе с тем желательно иметь механизм, обеспечивающий по крайней мере перенос и хранение на машинном носителе программных текстов на
языке Форт. Исходя из этого стандарт предусматривает
элементарнейшую файловую систему, которая легко
реализуется в любой конкретной файловой системе или
непосредственно через драйверы внешних устройств
(магнитных дисков).
В языке Форт применяется механизм виртуальной
внешней памяти, состоящей из блоков по 1 К байт каждый. Блок идентифицируется номером — числом в диапазоне от 1 до 32767. В адресном пространстве форт74

системы выделяется память под буферный пул, рассчитанный на одновременное хранение одного или более
блоков. Каждый буфер помимо памяти для хранения
собственно блока данных содержит номер блока
и признак его измененности.
Слово BUFFER (буфер), получив номер блока,
ищет свободный буфер в пуле и возвращает его адрес,
записывая в служебную ячейку буфера переданный
номер блока. Тем самым найденный буфер приписывается данному блоку, однако содержимое буферной памяти пока остается прежним. Если свободного буфера
не оказалось, то аналогичным образом используется
один из занятых. Если при этом в служебной ячейке
буфера был установлен признак измененности, то
данные из буферной памяти переписываются во внешнюю, они заменяют там прежнее содержимое соответствующего блока.
Слово BLOCK (блок) аналогично по использованию
слову BUFFER за исключением того, что перепись
данных из внешней памяти производится в приписанный данному блоку буфер. Разумеется, если данный
блок уже находится в буферном пуле, то переписи данных не происходит, и слово BLOCK возвращает адрес
этого буфера.
Наконец, слово UPDATE (изменить) устанавливает
признак измененности последнего блока, к которому
было адресовано обращение через слово BLOCK или
BUFFER . Таким образом, впоследствии этот блок будет
автоматически переписан во внешнюю память.
При реализации обмена с внешней памятью в качестве буферного пула обычно используется связный
участок оперативный памяти. Пусть его начало задается константой FIRST , а конец — адрес байта, следующего за последним,— константой LIMIT. (Если
пул располагается вплотную к концу адресного пространства, то этот следующий адрес равен нулю!) Буфера
в пуле располагаются подряд, каждый начинается двухбайтной ячейкой, в которой записывается номер приписанного блока, причем старший разряд используется
под признак измененности. Далее идет буферная память
для блока размером 1024 байта, завершается буфер
еще одной служебной ячейкой, в которой записан ноль
(ее назначение указано в п.2.8). Пусть переменные
PREV и USE указывают на текущий используемый
75

буфер и следующий, который будет выдан при запросе
на свободный буфер. Определим слово + B U F , которое
вычисляет адрес буфера, следующего в пуле за переданным, и возвращает признак несовпадения его с текущим:

Пусть служебные слова RBLK A,N
и WBLK A,N
выполняют чтение блока с указанным номером в заданную область оперативной памяти и запись из нее. Тогда
с учетом принятых условий слова, выполняющие работу
с внешней памятью, можно задать так:

Для записи всех измененных буферов во внешнюю
память служит слово SAVE-BUFFERS (сохранить
буфера):

При исполнении слова SAVE-BUFFERS все буфера
остаются приписанными прежним блокам. Слово FLUSH
(очистить) переписывает все исправленные блоки во
внешнюю память и освобождает буфера. Многие реализации имеют слово EMPTY-BUFFERS (опустошить
76

буфера), которое освобождает буферный пул, не переписывая исправленные блоки:

Внешняя память форт-системы в основном используется для хранения форт-текстов. Блок внешней
памяти с форт-текстом называется экраном и условно
разбивается на 16 строк по 64 литеры в каждой. Такой
формат экрана сложился традиционно и закреплен
в стандарте.
Программист создает и исправляет экраны во внешней памяти, используя встроенный редактор форт-системы. Как и в случае встроенного ассемблера, стандарт
не определяет конкретный вид и состав его команд,
поскольку это в значительной степени определяется
функциональными возможностями и характеристиками
конкретных терминалов и средствами работы с ними.
Обычно слова-команды текстового редактора составляют отдельный список слов с именем EDITOR (редактор), базирующийся на списке FORTH. Для распечатки
редактируемого экрана на терминале чаще всего используют слово LIST (распечатать), которое, кроме
того, сохраняет номер экрана в служебной переменной
SCR:

Вначале печатается номер данного экрана, затем его
строки. Перед каждой строкой печатается ее номер —
число в диапазоне от 0 до 15. По завершении редактирования исправленные экраны можно записать во внешнюю память словом FLUSH .
2.8. Интерпретация входного потока
Собственно работа форт-системы заключается
в распознавании и исполнении слов-команд, которые
программист вводит с терминала. Ввод осуществляется
построчно: набрав нужный текст, программист нажимает специальную управляющую клавишу, сообщая тем
77

самым о завершении ввода. Форт-система размещает
введенный текст в специальном буфере для ввода с терминала, который располагается в адресном пространстве оперативной памяти. Адрес начала этого буфера
дает стандартное слово TIB (сокращение от TEXT
INPUT BUFFER — буфер для ввода текста), его длина
хранится в стандартной переменной #ТIВ . Данный
буфер представляет собой входной поток, из которого
слово WORD выбирает слова. По исчерпании входного
потока форт-система вводит в этот буфер новый текст,
получаемый от программиста. При возникновении
какой-либо ошибочной ситуации форт-система прекращает дальнейшую интерпретацию текущего содержимого этого буфера и, выдав соответствующее сообщение
программисту, заполняет буфер новым текстом, который
после этого вводит программист.
Альтернативой вводу текста с терминала является
ввод из внешней памяти форт-системы. Переключением
входного потока на внешнюю память и обратно на
терминал управляет стандартная переменная BLK
(сокращение от BLOCK — блок), значение которой
проверяется каждый раз в слове WORD . Если это нуль,
то в качестве входного потока служит буфер TIB , в противном случае это значение рассматривается как номер
блока внешней памяти, который используется как входной поток (этот блок переносится в оперативную память
словом BLOCK ). Текущая позиция во входном потоке
хранится в стандартной переменной >IN (от IN —
вход) и в случае ввода с терминала изменяется в пределах от 0 до значения #ТIВ , а при вводе из внешней
памяти — в диапазоне от 0 до 1024.
Обычно конец входного потока в оперативной памяти (отмечается нулевым кодом (именно для этого
в буферном пуле после памяти для данных блока резервируется еще одна ячейка). Слово WORD , «натыкаясь»
на нулевой код, возвращает в качестве результата
пустую строку с нулевым значением счетчика литер,
при этом в список FORTH включается словарная статья
для такого «пустого» слова. Оно имеет признак немедленного исполнения и поэтому всегда исполняется
назависимо от текущего состояния текстового интерпретатора. Его исполнение состоит в прекращении
интерпретации данного входного потока и тем самым
позволяет избежать дополнительных проверок на ис78

черпание. Обычно в реализациях языка Форт определяется слово INTERPRET (интерпретировать), выполняющее интерпретацию текущего входного потока.
Оно представляет собой бесконечный цикл по вводу
и исполнению (или компиляции) слов:

В приведенном примере конструкция BEGIN-AGAIN
определяет бесконечный цикл.
Слово AGAIN выполняет безусловный переход на
начало цикла.
В случае если очередное введенное слово не найдено
в словаре, исполняется слово NUMBER, которое
пытается воспринять его как запись числа в соответствии с текущим основанием системы счисления — значением переменной BASE . Если это удалось, то слово
NUMBER возвращает значение числа как значение
двойной длины и дополнительно в переменной DPL
сообщает позицию десятичной точки в нем ( - 1 , если
точки в записи числа не было). В противном случае
возникает ошибочная ситуация «Слово не найдено»,
и интерпретация прекращается. Если же введенное
слово оказалось числом, то в зависимости от наличия
в нем точки оно рассматривается как число двойной
или одинарной точности. Таким образом, пустое слово — ограничитель
входного
потока — прекращает
исполнение слова INTERPRET и возобновляет исполнение слова, его вызвавшего:

Здесь X обозначает пустое слово.
Таким образом, работу форт-системы можно задать
таким бесконечным циклом:
79

Для переключения входного потока на внешнюю
память стандарт предусматривает слово LOAD (загрузить) :

Параметром слова LOAD является номер экрана (блока) для интерпретации. Очевидно, что приведенное
нами выше определение допускает рекурсивное использование слова LOAD внутри экранов во внешней
памяти.
Некоторые реализации предусматривают слово THRU
(сквозь) для последовательной интерпретации диапазона экранов:

Для продолжения интерпретации следующего экрана
часто используется слово
, логически сцепляющее
следующей экран с данным:

В этом случае интерпретации сцепленных экранов
нужно «загрузить» словом LOAD только первый из этих
экранов.
80

2.9. Целевая компиляция
и модель форт-системы
Решая задачу на языке Форт, программист расширяет исходный набор слов-команд форт-системы,
добавляя к нему новые определения. Его конечной
целью является определение некоторого «главного»
слова, исполнение которого и решает поставленную
задачу. Главное слово можно запустить на счет через
текстовый интерпретатор форт-системы, если ввести
имя слова в качестве очередной форт-команды. Во
время исполнения этого слова используются ранее
введенные определения вспомогательных слов и (прямо
или косвенно) ряд слов исходной форт-системы.
Во многих форт-системах предусмотрена возможность сохранения текущего состояния словаря во внешней памяти в качестве двоичного форт-модуля. Это
позволяет при последующих обращениях к данной задаче сразу начинать счет, запуская на исполнение ее
главное слово. При таком подходе естественно возникает задача минимизации используемых средств
и построения конечного программного продукта, уже
не зависящего от форт-системы. Такую задачу позволяет решать пакет целевой компиляции, который является ценным инструментальным средством при создании прикладного программного обеспечения на языке
Форт.
Целевая компиляция позволяет создавать единый
независимый рабочий модуль исходя из комплекса
взаимосвязанных определений на языке Форт. Составной частью этого комплекса является запись определений всех стандартных слов, которая образует модель
форт-системы и над которой «надстраиваются» определения данной задачи. В тексте модели обычно выделяется запускающая часть, которая обеспечивает инициирование работы и переход к исполнению главного
слова в данном комплексе определений. Если в реализации есть пакет целевой компиляции, его часто используют для раскрутки самой форт-системы исходя из
некоторой ее начальной версии. В этом случае главным
является приводившееся выше слово ФОРТ-СИСТЕМА,
которое выполняет бесконечный цикл по вводу входного потока с терминала и его текстовой интерпретации.
81

Входной текст для целевой компиляции помимо
собственно текста на языке Форт содержит еще ряд
слов-директив, позволяющих выполнять компиляцию
за один проход по тексту. Эти директивы включают,
в частности, средства предописания слов для того,
чтобы некоторые слова можно было использовать раньше их определения (например, чтобы в запускающей
части, с которой обычно начинается входной текст,
указать имя главного слова, которое будет определено
только в конце). Важной возможностью является управление построением целевой словарной статьи: некоторые или даже все статьи можно создать без заголовка, значительно сократив за счет этого размер
рабочего модуля. Кроме того, язык директив обычно
содержит средства для диагностики, распечатки карты
создаваемого модуля, построения таблицы перекрестных ссылок, сбора и распечатки статистической информации об использовании тех или иных слов.
Пакет целевой компиляции представляет собой
отдельно загружаемый пакет, написанный на языке
Форт. В начале работы он резервирует область памяти
для целевого адресного пространства, в которой строит
двоичный код конечного программного продукта по
мере обработки входного текста. После завершения
целевой компиляции построенный модуль может быть
выгружен во внешнюю память для дальнейшего использования в качестве независимого модуля в соответствии с заложенными в него соглашениями о связях.
В состав пакета целевой компиляции входят целевой ассемблер и целевой компилятор. Целевой ассемблер отличается от обычного встроенного ассемблера
форт-системы только тем, что строит машинный код
не в инструментальном адресном пространстве от
текущей вершины словаря, которая дается словом
HERE (здесь), а в целевом. Указатель в целевом адресном пространстве обычно обозначается словом THERE
(там) внутри самого пакета, который пользуется обоими
этими указателями. Для того чтобы получить целевой
ассемблер из исходного инструментального, как правило, достаточно оттранслировать его в контексте
других определений компилирующих слов ( , , С, ,
ALLOT и т. д.), которые выполняют те же действия,
но в целевом адресном пространстве, а не в инструментальном.
82

Задача целевого компилятора состоит в построении
высокоуровневого шитого кода в целевом адресном
пространстве, причем все ссылки на статьи, включая
и ссылки на все стандартные форт-слова, должны также
относиться к этому пространству. Для решения этой
задачи обычно поступают следующим образом. В списке
слов целевой компиляции переопределяют все стандартные определяющие слова (:, CONSTANT, VARIABLE, CODE и т. д.) таким образом, чтобы они строили
две статьи: одну стандартным образом, но в целевом
адресном пространстве, а другую — специальным
образом — в инструментальном. Исполнение инструментальной статьи компилирует ссылку, соответствующую целевой статье данного слова, как очередной
элемент шитого кода на вершину целевого словаря. Обрабатывая стандартным текстовым интерпретатором
высокоуровневое определение в контексте таких инструментальных определений, мы получаем в целевом
пространстве соответствующий шитый код. Аналогичным
образом должны быть переопределены и все слова
с признаком немедленного исполнения, реализующие
структуры управления и выполняющие какую-либо
компиляцию во время обработки определений ( IF,
THEN ,; и т. д.). Эти слова в отличие от их стандартных
прототипов должны компилировать статьи и переходы
для целевого адресного пространства, а не инструментального. Совокупность перечисленных определений
и образует целевой компилятор.
При построении конечного программного продукта
путем целевой компиляции возможно существенное
уменьшение его размера. Кроме уже упоминавшегося
исключения из словарных статей ненужных заголовков целевая компиляция позволяет вести учет всех
используемых данной задачей слов и автоматически
собрать для нее специальное ядро поддержки, состоящее только из тех слов, которые в ней действительно
используются. При этом возможна еще большая оптимизация за счет непосредственной подстановки определений в место их вызова для тех слов, которые
оказались использованными только один раз. Некоторые пакеты целевой компиляции дают возможность
довести такую подстановку до уровня машинного кода,
при этом высокоуровневые определения рассматриваются как своеобразные макроопределения. После
83

выполнения такой макрогенерации результирующий
машинный код уже не требует адресного интерпретатора и представляет собой линейную последовательность непосредственно исполняемых машинных команд.
Именно этот прием был применен, например, при
создании известного пакета машинной графики ГРАФОРТ для персонального компьютера фирмы ИБМ
и компьютеров фирмы «Эппл» (Apple, США). Сравнительно большой объем получившегося машинного
кода компенсировался чрезвычайно высоким быстродействием, недостижимым для традиционных языков
программирования. В то же время совершенно очевидно, что разработать и отладить программу такого
объема на языке ассемблера вручную практически
невозможно.
Описанный подход к целевой компиляции применим
и в том случае, когда целевая ЭВМ, для которой строится конечный программный продукт, отличается по своей
архитектуре и системе команд от инструментальной.
В этом случае изменения касаются только целевого
ассемблера и определений в машинном коде, которые
должны отвечать системе команд целевой ЭВМ. Такое
использование целевой компиляции в качестве инструментального кросс-средства все чаще имеет место
при создании программного обеспечения встроенных
микропроцессоров и специализированных микропроцессорных устройств на их основе.

Глава
3
Примеры программных разработок

В этой главе приведены примеры реализации
отдельных программных средств — небольших удобных
расширений (инфиксная форма записи, локальные
переменные, конструкция «переключатель», векторное
поле кода) — и целых инструментальных систем на
базе языка Форт. Основное внимание уделено методологическим аспектам использования Форта как расширяемого языка. Все приведенные примеры отражают
опыт использования Форта в конкретных разработках.
3.1. Средства отладки форт-программ

Программы на языке Форт — определения
слов — кодируются и вводятся в ЭВМ методом «снизу
вверх», т. е. начиная с элементарных, использующих
только стандартные слова форт-системы, и кончая
определением главного слова, использующего уже
введенные. Такой порядок естественным образом предполагает немедленную отладку вводимых определений,
поскольку определение готово к исполнению сразу же
после ввода. Отладка облегчается тем, что механизм
взаимодействия модулей упрощен до предела: через
стек данных, который программист может сам заполнить значениями параметров перед вызовом отлаживаемого слова.
Для распечатки текущего состояния стека данных
и стека возвратов многие реализации имеют слова
S. и R. . В сочетании со словом DUMP , которое распечатывает область памяти, и словом ?
которое печатает значение, находящееся по указанному
адресу, эти слова создают богатые возможности для
диалоговой отладки форт-слов.
85

То обстоятельство, что в основе языка Форт лежит
интерпретатор и при исполнении каждого слова управление проходит через точку NEXT адресного интерпретатора форт-системы, дает дополнительные возможности для организации автоматического слежения за
исполнением скомпилированных слов. Программист
может организовать динамическую подмену точки
NEXT на такую, которая, выполняя те же действия по
переходу к интерпретации очередной ссылки, выполняет и заданные отладочные действия. Поскольку
в момент исполнения точки NEXT через адрес поля кода
имеется доступ к адресу поля имени словарной статьи,
то отладочные действия можно формулировать, задавая имена интересующих программиста слов.
Например, по специальному слову ON-NEXT (ПРИСЛЕД) строится список слов (в виде списка адресов
компиляции), подлежащих отслеживанию. В частности, его можно задать, как все слова, определения
которых расположены в словаре выше некоторого адреса. Для всех этих слов указываются отладочные действия, состоящие, как правило, из распечатки имени
слова и стека данных в момент перехода к его исполнению. В некоторых случаях можно проводить анализ
стека возвратов и отслеживать возврат из определений,
распечатывая результаты из стека данных на момент
возврата. Специальные слова TRACE-ON (СЛЕЖ-ВК.Л)
и TRACE-OFF (СЛЕЖ-ВЫКЛ) включает и выключают механизм отладочного слежения, подменяя
или восстанавливая точку NEXT адресного интерпретатора.
С помощью этого же механизма можно ввести защиту от зацикливания, например подсчитывая в точке
NEXT число исполненных слов и возобновляя диалог
при достижении некоторого заданного значения этого
счетчика. Если данная ЭВМ имеет встроенный таймер
и операционная система позволяет обрабатывать асинхронные выходы по истечении заданного интервала
времени, то этот механизм можно также использовать
для предохранения от зацикливания. В процедуре
обработки выхода по исчерпанию временного интервала
нужноподменить точку NEXT на такую, которая выполнит возврат к диалогу с программистом. При этом
программист может получить исчерпывающую информацию о месте такого «прерывания от таймера».
86

Другой полезный механизм отладочного слежения
позволяет переключаться на диалог с программистом
в момент исполнения заданного слова и реализуется
через подмену поля кода в заданной словарной статье.
Для выполнения такой подмены нужно определить
специальное слово, например, STOP (стойте!), которое
выбирает имя слова из входного потока и засылает
в поле кода его словарной статьи адрес специального
кода. Прежнее значение поля кода сохраняется в специально резервируемой для этого ячейке словаря.
Новый код для подмененного таким образом слова
состоит в переключении на текстовую интерпретацию входного потока, так что все необходимые отладочные распечатки и установки значений программист
задает непосредственно в диалоге. Слово GO (идите!)
завершает диалог и продолжает исполнение программы,
при этом отлаживаемое слово, вызвавшее останов,
исполняется в прежнем виде (для этого используется
сохраненное «настоящее» значение из его поля кода).
Можно предусмотреть специальное слово для продолжения работы без исполнения рассматриваемого слова.
Описанные механизмы, являясь достаточно простыми, вместе с тем существенно облегчают отладку сложных программ. Их сильная зависимость от реализации
не позволяет сделать эти определения переносимыми.
Вместе с тем именно эта черта позволяет реализовать
такие отладочные средства максимально удобным для
программиста способом с учетом конкретных особенностей данной реализации и ее операционного окружения.
Удобным вспомогательным средством, позволяющим
быстро находить место останова в терминах входного
текста на языке Форт, является символьная распечатка
шитого кода. Поскольку скомпилированная ссылка
представляет собой адрес поля кода словарной статьи,
то по ней через слово > NAME можно вычислить адрес
поля имени и напечатать это имя словом ID. :
Слово COUNT в качестве счетчика длины выдает значение первого байта поля имени; поскольку старшие
разряды этого байта используются под специальные
признаки, то необходимо специальное преобразование,
определяемое представлением счетчика, чтобы получить
87

истинную длину поля имени. Константа 31 как ограничение на длину имени слова дается стандартом языка.
В программе распечатки последовательности ссылок
надо предусмотреть специальную обработку некоторых
адресов, вслед за которыми скомпилирована не ссылка
на очередную статью, а некоторое другое значение.
Такими «особыми» ссылками в стандарте языка являются слова для выполнения переходов BRANCH и
?BRANCH, для реализации циклов (DO) , (LOOP) и
( +LOOP) , для исполнения литералов LIT и 2LIT и
некоторые другие.
3.2. Инфиксная запись формул

Для многих программистов трудным барьером
на пути к овладению языком Форт оказывается используемая в нем обратная польская форма для записи
выражений. Опишем простую надстройку над языком
Форт, которая позволяет записывать формулы в обычной инфиксной форме с использованием скобок. Будем
по-прежнему считать все элементы такого выражения
(скобки, знаки операций и элементарные термы) отдельными форт-словами и разделять их при записи пробелами. Задача состоит в том, чтобы вычисления на стеке
автоматически перегруппировывались исходя из инфиксной формы записи. Например, чтобы вместо текста
2 5 + 7 3 - * можно было писать ( ( 2 + 5 ) * ( 7 +
+ 3 ) ) . Внешние скобки нужны для того, чтобы отмечать конец выражения. При желании это можно
задавать и каким-нибудь другим способом.
Для операций в инфиксной записи вводится понятие
приоритета (старшинства), которое определяет порядок
вычислений при отсутствии скобок. Приоритет обозначается целым числом, и операции с меньшим приоритетом выполняются после операций с большим приоритетом. Например, в выражении А + В / С подразумевается
следующая расстановка скобок: ( А + ( В / С ) ) , т. е.
сначала выполняется деление и только потом сложение, потому что приоритет деления выше приоритета
сложения. В случае равных приоритетов, например
в выражении А + В + С, будем выполнять операции
слева направо: ( ( A + B ) + C ) . Традиционно используемые приоритеты двухместных операций показаны
в табл. 3.1. Все одноместные операции (ABS, NEGATE,
88

Таблица
Приоритет
Операция

3. 1. Приоритеты двухместных операций
2

3

4

5

6

OR
XOR

AND

=

<
>

+

7

/
MOD

8

**

NOT и др.) имеют максимальный приоритет (обычно 9).
Опишем вспомогательную структуру данных — стек
ОПРЦ , элементами которого являются пары значений:
приоритет операции и адрес кода, который ее вычисляет.
Размер стека определяется максимальной глубиной
вложенности формул, которую мы допускаем: CREATE
ОПРЦ HERE 2+ , 40 ALLOT. Первым элементом
в поле параметров слова ОПРЦ является указатель
вершины этого стека (адрес первого свободного байта),
далее зарезервирована память на 5 элементов по 4 байта (два значения) каждый. По аналогии со словами
> R , R@ и R> определим слова для работы со стеком
ОПРЦ:

Обработка операций в инфиксной форме состоит в том,
что операция не выполняется, а помещается вместе со
своим приоритетом на стек операций, выталкивая из
него на исполнение все операции с меньшим или равным
приоритетом. В состоянии исполнения вытолкнутая
операция исполняется, а в состоянии компиляции —
компилируется. Предполагая, что в стеке ОПРЦ сначала размещается адрес поля кода для исполнения
операции, а за ним приоритет, определим слово для
такого выталкивания операций:

Теперь введем определяющие слова для двухместных
и одноместных операций и переопределим через них
стандартные арифметические форт-слова:
89

В определении слова 2-ОП используется то обстоятельство, что код для исполнения данной операции обозначается словом, которое этим определяющим словом
переопределяется. Поэтому перед тем как его имя будет
выбрано из входного потока словом CREATE , текущая
позиция во входном потоке запоминается на стеке возвратов и после исполнения слова ' (штрих) вновь
устанавливается в то же положение для исполнения
слова CREATE.
Переопределенные слова получают признак немедленного исполнения, чтобы описанные действия по переупорядочиванию вычислений выполнялись и во время
компиляции, компилируя нужную последовательность
операций. В заключение осталось переопределить круглые скобки, явно задающие порядок вычислений:

Открывающая скобка кладет на стек значение 0 —
ограничитель для выталкивания операций. Закрывающая скобка выталкивает все операции до ближайшего
ограничителя и удаляет его из стека. Переопределение
открывающей скобки делает невозможным ее использование в прежнем смысле — как знака комментария.
Поэтому программисту, вводящему такую надстройку,
следует подумать и о решении этого вопроса.
Развивая описанную надстройку дальше, определим
простой входной язык с описаниями переменных и присваиваниями, которые записываются обычным образом.
В качестве знака присваивания пусть используется
слово : = , а в качестве разделителя операторов —
слово ; . Если переменная использована как получатель
присваивания (слева от знака : = ), то ее исполнение
оставляет на стеке адрес значения; а если данная пере90

менная входит в правую часть присваивания, то ее
исполнение кладет на стек само значение данной переменной. Для управления поведением переменных нашего языка введем рабочую переменную ?ЗНАЧ,
которая имеет значение 0 при обработке левой части
присваивания и значение — 1 при обработке правой,
и определим слово ПЕРЕМ для описания переменных
нашего языка:

Для записи присваиваний определим слова : — и ;
через уже определенные скобки:

Слово : = кладет на стек ОПРЦ ограничитель для
выталкивания операций и устанавливает переменную
?ЗНАЧ на обработку правой части присваивания. Слово
; выталкивает со стека ОПРЦ все накопившиеся там
операции, в результате на вершине стека данных оказывается значение правой части. Непосредственно под
ним располагается адрес переменной, оставленный
левой частью данного присваивания. Слова SWAP
и ! выполняют присваивание, причем в состоянии компиляции они компилируются, а состоянии исполнения
исполняются. В заключение переменная ?ЗНАЧ переустанавливается на режим обработки левой части
следующего присваивания.
Благодаря словам := и ; отпадает необходимость
в дополнительных внешних скобках для всего выражения. Входной текст в описанном расширении выглядит вполне традиционно:

и вместе с тем это текст на языке Форт! Такие операторы
присваивания можно исполнять непосредственно или
91

включать в тело определений через двоеточие. Однако
переопределение точки с запятой усложняет написание
новых определений в таком расширении. Чтобы решить
эту проблему, можно предусмотреть специальные средства для определения новых слов, как и слова для включения комментариев. Описанный пример еще раз
показывает, что средства, которые язык Форт предоставляет программисту, позволяют ему реализовать
практически любые необходимые инструментальные
надстройки, исходя из конкретных требований и особенностей данной задачи и пожеланий пользователей,
с ней работающих.
3.3. Локальные переменные

Стек данных как универсальное средство для
передачи параметров и результатов между фортсловами имеет неоспоримые преимущества. Вместе с тем
внутри определения он используется для промежуточных вычислений и размещения значений, которые в них
участвуют. Это вызывает определенные трудности для
доступа к такому локальному значению, поскольку его
положение относительно вершины стека постоянно
меняется.
Для упрощения работы желательно закрепить за
локальным объектами внутри определения некоторые
постоянные имена, через которые и осуществлять
доступ к ним.
Имеющийся в языке механизм описания переменных
в данном случае не подходит, поскольку создает глобальные имена, тогда как требуется именовать локальные объекты, учитывая при этом возможность рекурсивных вызовов. Поставленную задачу решает включение
в работу дополнительного стека, отличного от стека
данных. Локальные значения размещаются в этом
стеке при входе в определение и убираются из него при
выходе.
На все время исполнения определения их положение
относительно вершины стека остается постоянным,
это позволяет организовать очень простой доступ к
таким значениям.
Простейшая надстройка над языком Форт, которая
позволяет работать с локальными переменными, выглядит так:
92

Вначале отводится область объемом 100 байт и адрес
ее конца запоминается как константа LP0. Эта область
будет использоваться как локальный стек, растущий
в сторону убывания адресов. Переменная LP хранит
указатель на текущую вершину локального стека, ее
инициализацию выполняет слово INIT, которое присваивает ей значение LP0. Слово LOC резервирует
в этом стеке память на N двухбайтных значений, дополнительно отводя еще одну ячейку, в которую засылает
значение N — длину всей области. Обратное действие — освобождение памяти — выполняет слово UNLOC ,
которое использует сохраненное значение N. Слова
@@ и !! являются определяющими для двух рядов
слов, выполняющих разыменование локальных объектов и присваивание им нового значения. Каждый
локальный объект в пределах одного определения идентифицируется своим номером, который включен в имя
операций разыменования и присваивания для работы
с ним. В качестве примера рассмотрим определение
слова 5INV, переставляющего 5 верхних элементов
стека данных в обратном порядке:

Приведенная реализация локальных переменных
предельно упрощена. Ее очевидным усовершенствованием является, например, введение контроля за переполнением и исчерпанием локального стека и включение действий LOC и UNLOC в семантику входа в высокоуровневое определение и выхода из него; при этом подсчет числа локальных объектов может выполняться
автоматически, аналогично определению адреса перехода в структурах управления.
Можно пойти еще дальше и вместо прямой нумерации локальных объектов ввести их настоящее описание
через соответствующее слово внутри определения через
двоеточие с автоматическим переносом входных пара93

метров на локальный стек при входе в такое определение. Словарная статья для имени локального объекта,
которая строится по такому описанию, должна располагаться несколько выше текущей вершины словаря
и иметь признак немедленного исполнения. Ее действие
состоит в компиляции обращения к соответствующему
элементу локального стека. По завершении компиляции
данного определения все такие временные статьи
автоматически исключаются из словаря.
Введение локальных переменных не только упрощает
программирование, освобождая программиста от необходимости тщательно отслеживать все изменения
на стеке, но и сокращает размер скомпилированной
программы, поскольку из нее исчезают сложные последовательности из слов OVER , DUP , ROT , PICK и
других, обычно используемых для доступа к локальным
значениям. Такое неожиданное сочетание приятного
с полезным — одна из многих удивительных сторон
языка Форт, которые открываются программисту при
знакомстве с ним.
3.4. Векторное поле кода

В языке Форт с каждым словом-командой связано некоторое действие, определяющее семантику
данного слова. В словарной статье, которая является
внутренним представлением форт-слова, это действие
задано через поле кода. В случае косвенного шитого
кода оно содержит адрес машинной программы, исполнение которой и составляет действие данного слова.
Эта программа образует исполняющую часть определяющего слова, через которое данное слово было
создано, и параметризуется адресом поля параметров
его словарной статьи.
Описанный механизм является чрезвычайно мощным и в то же время очень компактным в реализации.
Вместе с тем и он может быть улучшен с учетом конкретной цели. Заметим, что этот механизм предписывает
исполнение одной и той же программы для всех случаев
применения данного слова, в то время как в каждом
случае слово употребляется в определенном контексте,
и его исполнение, вообще говоря, зависит от этого
контекста. Разумеется, анализ контекста можно включить в программу слова, если закодировать контекст
94

через дополнительный параметр или глабальную переменную.
Именно в этом, например, заключается действие
стандартного слова LITERAL, которое анализирует
текущее состояние текстового интерпретатора через
значение переменной STATE . Если это значение —•
нуль (состояние исполнения), то это слово больше ничего не делает. При ненулевом значении (состояние
компиляции) оно снимает значение с вершины стека и
компилирует его как литерал на вершину словаря.
Пример другого подхода дает описанная в п. 3.2
реализация оператора присваивания в традиционной
инфиксной форме записи. В зависимости от текущего
значения переменной ?ЗНАЧ, которое изменяется словами : = и ;, слова, обозначающие переменные, оставляют на стеке либо адрес, либо значение данной переменной.
Предлагаемое улучшение связано прежде всего
с использованием переменных. В стандартном определении при исполнении переменной на стеке будет оставлен адрес ячейки (поля параметров), в которой хранится ее значение. Однако сам по себе этот адрес редко бывает нужен, обычно он используется либо для получения
текущего значения переменной с помощью слова @ ,
либо для засылки в нее нового значения словом ! .
Поэтому можно считать, что с переменной связано не
одно действие, а три — получение текущего значения,
засылка нового и получение адреса. Какое именно действие требуется в каждом конкретном случае, определяется контекстом.
Введем слово QUAN (от QUANTITY — величина),
которое определяет новое слово с тремя описанными
выше действиями. В словарной статье таких слов
вместо одного поля кода создается три — по одному
на каждое действие. Будем обозначать их адреса через
0CFA, 1CFA и 2CFA соответственно (рис. 3.1). За
ними располагается ячейка, отведенная под текущее
значение данной переменной, обозначим ее адрес через
0 P F A . Если рассматривать такую структуру как обычную словарную статью, то поле 0CFA является полем
кода, а поля 1CFA,2CFA и 0PFA занимают поле параметров. Если в шитый код скомпилирован адрес 0CFA ,
то при исполнении соответствующего кода в качестве
адреса поля параметров выступает адрес 1CFA . Анало95

Рис. 3.1. Структура статьи
с векторным полем кода

гично для адреса 1CFA полем параметров служит
2CFA , а для 2CFA — адрес 0PFA . Поэтому описанные
выше три действия можно задать так:

Нетрудно увидеть, что действие АДР совпадает со
стандартным действием для переменной, состоящим
в том, что адрес поля параметров кладется на стек.
Определим теперь слово QUAN , используя слово
ПРИСВ в качестве вспомогательного:

Создающая часть этого определения использует поле
кода создаваемой статьи как рабочую ячейку, из которой сначала извлекается значение для 2CFA , засланное туда словом CREATE, и затем значение для 1CFA ,
которое засылается туда словом ПРИСВ . Окончательное значение в этой ячейке устанавливается словом
DOES> . Описание переменной через слово QUAN
выглядит так же, как описание обычной переменной:
QUAN X . Однако теперь при исполнении слова X на
стеке будет оставлено текущее значение переменной
из ее поля 0PFA . Выполнение двух других действий
задают слова ТО (предлог «в») и AT (предлог «из»)
96

Эти слова имеют признак немедленного исполнения
и в состоянии компиляции (внутри определения через
двоеточие) компилируют коды 1CFA и 2CFA для следующего слова. В состоянии исполнения соответствующие действия исполняются. Теперь, чтобы присвоить
переменной X значение 100, нужно выполнить текст
100 ТО X, а для получения адреса — текст AT X.
Такое усовершенствование, как и введение локальных переменных, не только упрощает программирование, но и сокращает объем скомпилированного кода.
Из текста программ исчезает слово (а>, применявшееся
для разыменования адреса переменной. В результате
вместо двух ссылок — на поле CFA для переменной
и статью для @ — в шитом коде присутствует только
одна ссылка (на поле 0CFA). Аналогично в случае
присваивания вместо двух ссылок (на поле CFA переменной и на статью для !) компилируется одна (на поле
1CFA). В практических реализациях программы для
действий ЗНАЧ и ПРИСВ обычно задаются не в виде
высокоуровневых определений, а непосредственно в
машинном коде данной ЭВМ через встроенный ассемблер форт-системы. В этом случае описание переменных
через слово QUAN не только сокращает размер программы, но и повышает ее быстродействие, поскольку
отпадает необходимость в исполнении действия NEXT
для интерпретации еще одной ссылки.
По аналогии со словом QUAN определим слово
VECT (от VECTOR — вектор), которое также создает
словарную статью с векторным полем кода из трех
элементов:

Код для поля 2CFA указывает на исполняющую часть
из определяющего слова CONSTANT , поле 1CFA такое
же, как и для слова QUAN, оно выполняет присваивание нового значения. Наконец, поле 0CFA,
4 Зак. № 966

97

которое задается исполняющей частью определения
VECT, исполняет слово, адрес поля кода которого
является текущим значением поля 0PFA . Для получения текущего значения и засылки нового по-прежнему
используются определенные ранее слова AT и ТO.
Вот пример на использование этих слов: VECT V ' DUP
ТО V . Теперь исполнение слова V равносильно исполнению DUP. Таким образом, слова, определенные через
VECT , можно рассматривать как переменные, значениями которых являются другие слова. Для исполнения
слова — текущего значения такой переменной — достаточно указать только имя этой переменной без дополнительных операций @ и EXECUTE. Текст AT V> NAME
ID. распечатывает имя слова — текущего значения V .
Разумеется, при такой распечатке предполагается, что
само это слово имеет обычную структуру словарной
статьи.
3.5. Выбор по целому
Выбор по целому — распространенная конструкция в языках программирования. Она является обобщением условного оператора, который осуществляет
выбор между двумя последовательностями операторов — частью «то» и частью «иначе» — по логическому
значению (ИСТИНА или ЛОЖЬ) условия. В конструкции выбора по целому в качестве значения условия
выступает целое число, и выбор осуществляется между
несколькими альтернативными ветвями, каждая из
которых соответствует определенному значению условия или некоторому множеству таких значений. Как
правило, множества значений условия для разных ветвей не должны пересекаться. Обычно эти множества
задают явным перечислением отдельных значений или
указанием диапазона для них. Разберем два варианта
реализации выбора по целому. В первом используется
переключатель, во втором — вложенные условные операторы.
В случае реализации через переключатель каждая
ветвь должна задаваться отдельным форт-словом.
Пусть имеется несколько ветвей, каждая из которых
задается своим порядковым номером. Из адресов компиляции этих ветвей в соответствии с их порядковыми
номерами строится вектор-переключатель, и нужная
98

ветвь выбирается по порядковому номеру. Определяющее слово SWITCH (переключатель) компилирует
такой вектор в поле параметров определяемого слова
и задает выбор альтернативы в исполняющей части:

Создающая часть строит новую словарную статью и
переключает текстовый интерпретатор в состояние компиляции. Таким образом, следующие слова будут компилироваться в поле параметров определяемого слова.
Слово SMUDGE выставляет разряд «Не готов» в байтесчетчике поля имени, делая данную статью «невидимой»
при поиске слов в словаре. Этот разряд будет сброшен
словом ; (точка с запятой), которое завершает компиляцию такого переключателя. Например:

Порядок использования переключателя ДЕНЬНЕДЕЛИ иллюстрирует следующий протокол работы:

Описанный механизм аналогичен определению вектора,
рассмотренному в п. 1.10. Его также можно задавать
по-разному в зависимости от конкретных требований.
В приведенной реализации неправильное значение выбирающего условия (выходящее за диапазон от 1 до 7)
приведет к непредсказуемому результату, поскольку
соответствующий контроль отсутствует. Чтобы ввести
его в реализацию, нужно каким-то образом подсчитать
число скомпилированных ссылок при завершении компиляции. Для этого можно применить способ, аналогичный компиляции переходов в шитом коде, введя
вместо точки с запятой специальное слово, отмечающее конец переключателя.
4*

99

Для реализации конструкции выбора через вложенные условные операторы создаются специальные слова,
которые обрамляют всю конструкцию и каждую ее
ветвь. В этом случае ветвь является обычной последовательностью форт-слов, аналогичной части «то» или
части «иначе» условного оператора. Как и в случае
условного оператора, конструкцию выбора можно использовать только внутри определений через двоеточие,
т. е. в состоянии компиляции текстового интерпретатора. При этом все ее специальные слова имеют признак
немедленного исполнения и компилируют необходимые
проверки и переходы.

Слова CASE (выбор) и ENDCASE (конец выбора)
ограничивают конструкцию и обеспечивают правильную компиляцию вложенных операторов. Слово CASE ,
проверив, что текстовый интерпретатор находится в состоянии компиляции, сменяет глобальную переменную
CSP (сокращение от CURRENT STACK POINTER —
текущий указатель стека), сохраняя на стеке ее прежнее значение (слово !CSP засылает в переменную CSP
адрес текущей вершины стека). Слова OF (из) и ENDOF (конец из) ограничивают отдельную ветвь. Во время работы скомпилированного определения перед началом исполнения каждой ветви на стеке лежат два
значения: число, представляющее условие выбора, и номер данной ветви. Слово OF компилирует текст OVER =
100

= IF DROP , который обеспечивает передачу управления на данную ветвь, если эти два значения совпали,
причем в этом случае они оба снимаются со стека. Если
же значения оказались разными, то управление передается на текст, следующий за словом ENDOF для
данной ветви, которое эквивалентно слову ELSE . Наконец, слово ENDCASE компилирует операцию DROP ,
чтобы снять со стека оставшееся там значение условия,
и разрешает все накопившиеся на стеке выходы из ветвей на текущую точку, исполняя для этих ветвей слово
THEN . Его последним действием является восстановление прежнего значения переменной CSP , которое
к этому моменту оказывается на вершине стека.
В приведенных определениях можно несколько
уменьшить объем кода, компилируемого для входа
в каждую ветвь, если ввести для этого специальное
слово, вслед за которым в шитый код компилируется
адрес перехода на следующую ветвь:

Исполнение слова (OF) аналогично ?BRANCH , в зависимости от условия оно переустанавливает указатель
интерпретации, либо обходя скомпилированную за ним
ссылку, либо устанавливая указатель по значению этой
ссылки. Аналогичным образом наряду со словами (OF)
и ОФ можно определить пары ( < O F ) и < O F , (> OF)
и > OF , ( < O F < ) и