Векторное поле кода
В языке Форт с каждым словом-командой связано некоторое действие, определяющее семантику данного слова. В словарной статье, которая является внутренним представлением форт-слова, это действие задано через поле кода. В случае косвенного шитого кода оно содержит адрес машинной программы, исполнение которой и составляет действие данного слова. Эта программа образует исполняющую часть определяющего слова, через которое данное слово было создано, и параметризуется адресом поля параметров его словарной статьи.
Описанный механизм является чрезвычайно мощным и в то же время очень компактным в реализации. Вместе с тем и он может быть улучшен с учетом конкретной цели. Заметим, что этот механизм предписывает исполнение одной и той же программы для всех случаев применения данного слова, в то время как в каждом случае слово употребляется в определенном контексте, и его исполнение, вообще говоря, зависит от этого контекста. Разумеется, анализ контекста можно включить в программу слова, если закодировать контекст через дополнительный параметр или глобальную переменную.
Именно в этом, например, заключается действие стандартного слова LITERAL, которое анализирует текущее состояние текстового интерпретатора через значение переменной STATE. Если это значение — нуль (состояние исполнения), то это слово больше ничего не делает. При ненулевом значении (состояние компиляции) оно снимает значение с вершины стека и компилирует его как литерал на вершину словаря.
Пример другого подхода дает описанная в реализация оператора присваивания в традиционной инфиксной форме записи. В зависимости от текущего значения переменной ?ЗНАЧ, которое изменяется словами := и ;, слова, обозначающие переменные, оставляют на стеке либо адрес, либо значение данной переменной.
Предлагаемое улучшение связано прежде всего с использованием переменных. В стандартном определении при исполнении переменной на стеке будет оставлен адрес ячейки (поля параметров), в которой хранится ее значение. Однако сам по себе этот адрес редко бывает нужен, обычно он используется либо для получения текущего значения переменной с помощью слова @, либо для засылки в нее нового значения словом !.
Поэтому можно считать, что с переменной связано не одно действие, а три — получение текущего значения, засылка нового и получение адреса. Какое именно действие требуется в каждом конкретном случае, определяется контекстом.
Введем слово QUAN (от QUANTITY — величина), которое определяет новое слово с тремя описанными выше действиями. В словарной статье таких слов вместо одного поля кода создается три — по одному на каждое действие. Будем обозначать их адреса через 0CFA, 1CFA и 2CFA соответственно (рис. 3.1). За ними располагается ячейка, отведенная под текущее значение данной переменной, обозначим ее адрес через 0PFA. Если рассматривать такую структуру как обычную словарную статью, то поле 0CFA является полем кода, а поля 1CFA, 2CFA и 0PFA занимают поле параметров. Если в шитый код скомпилирован адрес 0CFA, то при исполнении соответствующего кода в качестве адреса поля параметров выступает адрес 1CFA. Аналогично для адреса 1CFA полем параметров служит 2CFA, а для 2CFA — адрес 0PFA. Поэтому описанные выше три действия можно задать так:
: ЗНАЧ DOES> ( 1CFA -> N:ЗНАЧ) 4 + @ ; : ПРИСВ DOES> ( N:ЗНАЧ,2CFA ->) 2+ ! ; : АДР DOES> ( 0PFA -> 0PFA ) ;
Рис. 3.1. Структура статьи с векторным полем кода
Нетрудно увидеть, что действие АДР совпадает со стандартным действием для переменной, состоящим в том, что адрес поля параметров кладется на стек. Определим теперь слово QUAN, используя слово ПРИСВ в качестве вспомогательного:
: QUAN ( -> ) CREATE LATEST NAME> DUP @ ( КОД 2CFA) ПРИСВ SWAP @ ( КОД 2CFA,КОД 1CFA) , , 0 , DOES> ( КОД ДЛЯ 0CFA) 4 + @ ;
Создающая часть этого определения использует поле кода создаваемой статьи как рабочую ячейку, из которой сначала извлекается значение для 2CFA, засланное туда словом CREATE, и затем значение для 1CFA, которое засылается туда словом ПРИСВ. Окончательное значение в этой ячейке устанавливается словом DOES>. Описание переменной через слово QUAN выглядит так же, как описание обычной переменной: QUAN X.
Однако теперь при исполнении слова Х на стеке будет оставлено текущее значение переменной из ее поля 0PFA. Выполнение двух других действий задают слова ТО (предлог «в») и AT (предлог «из»):
: TO ' 2+ STATE @ IF , EISE EXECUTE THEN ; IMMEDIATE : AT ' 4 + STATE @ IF , ELSE EXECUTE THEN ; IMMEDIATE
Эти слова имеют признак немедленного исполнения и в состоянии компиляции (внутри определения через двоеточие) компилируют коды 1CFA и 2CFA для следующего слова. В состоянии исполнения соответствующие действия исполняются. Теперь чтобы присвоить переменной X значение 100, нужно выполнить текст 100 TO X, а для получения значения переменной — текст AT X.
Такое усовершенствование, как и введение локальных переменных, не только упрощает программирование, но и сокращает объем скомпилированного кода. Из текста программ исчезает слово @, применявшееся для разыменования адреса переменной. В результате вместо двух ссылок — на поле CFA для переменной и статью для @ — в шитом коде присутствует только одна ссылка (на поле 0CFA). Аналогично в случае присваивания вместо двух ссылок (на поле CFA переменной и на статью для !) компилируется одна (на поле 1CFA). В практических реализациях программы для действий ЗНАЧ и ПРИСВ обычно задаются не в виде высокоуровневых определений, а непосредственно в машинном коде данной ЭВМ через встроенный ассемблер форт-системы. В этом случае описание переменных через слово QUAN не только сокращает размер программы, но и повышает ее быстродействие, поскольку отпадает необходимость в исполнении действия NEXT для интерпретации еще одной ссылки.
По аналогии со словом QUAN определим слово VECT (от VECTOR — вектор), которое также создает словарную статью с векторным полем кода из трех элементов:
: VECT ( -> ) 0 CONSTANT LATEST NAME> DUP @ , ( КОД 2CFA) ПРИСВ DUP @ ( 1CFA) SWAP >BODY ! ['] ABORT , DOES> ( КОД ДЛЯ 0CFA) 4 + @ EXECUTE ;
Код для поля 2CFA указывает на исполняющую часть из определяющего слова CONSTANT, поле 1CFA такое же, как и для слова QUAN, оно выполняет присваивание нового значения.Наконец, поле 0CFA, которое задается исполняющей частью определения VECT, исполняет слово, адрес поля кода которого является текущим значением поля 0PFA. Для получения текущего значения и засылки нового по-прежнему используются определенные ранее слова AT и TO. Вот пример на использование этих слов: VECT V ' DUP TO V. Теперь исполнение слова V равносильно исполнению DUP. Таким образом, слова, определенные через VECT, можно рассматривать как переменные, значениями которых являются другие слова. Для исполнения слова — текущего значения такой переменной — достаточно указать только имя этой переменной без дополнительных операций @ и EXECUTE. Текст AT V >NAME ID. распечатывает имя слова — текущего значения V. Разумеется, при такой распечатке предполагается, что само это слово имеет обычную структуру словарной статьи.