В этой главе мы приоткроем «щелочку» и заглянем внутрь Форт-системы. Некоторое представление о том, что там (внутри) происходит, вы уже получили ранее, но, рискуя повториться, мы приведем здесь описание Форт-машины в целом, чтобы вы поняли, как все ее механизмы взаимодействуют друг с другом.
В первой главе было показано, как текстовый интерпретатор
INTERPRET выбирает слова из входного потока и ищет их определения в нашем словаре. Если он находит слово, то исполняет его.
Посмотрим, из каких компонентов состоит текстовый интерпретатор, начнем с изучения слов поиска по словарю. Слово ' (апостроф) находит определение в словаре и помещает в стек адрес этого определения. Воспользуемся словом ВСТРЕЧА, которое мы определили в гл. 1, и запишем:
' ВСТРЕЧА U. 25520 okВ результате получим адрес слова ВСТРЕЧА (вот и все, что должно произойти).
(На самом деле и INTERPRET, и апостроф используют для поиска по словарю примитив с именем FIND (ПОИСК).)
Слово ' имеет несколько применений. Так, с помощью выражения: ' ВСТРЕЧА, вы можете узнать, было ли слово ВСТРЕЧА определено, фактически не выполняя его (при выполнении этого выражения будет либо напечатан адрес, либо выдан ответ: «?»).
Вам также может понадобиться адрес для того, чтобы вывести с помощью DUMP содержимое определения, например:
' ВСТРЕЧА 12 DUMP
Можно сочетать апостроф со словом EXECUTE. Вспомните, что текстовый интерпретатор, найдя слово, передает его адрес слову EXECUTE. To же самое можете делать и вы. Слово EXECUTE выполняет определение, адрес которого задается в стеке. Таким образом, вы можете ввести (следуя Стандарту-83):
' ВСТРЕЧА EXECUTE Привет я говорю на Форте ok
и получить тот же эффект, что и при выполнении одного слова ВСТРЕЧА, только более изощренным способом.
Слово EXECUTE не проверяет, является ли заданный адрес правильным. Вся ответственность ложится на вас. Неверный адрес почти всегда приводит к разрушению системы. Стандартом-83 определено, что апостроф оставляет в вершине стека правильный адрес для EXECUTE. К сожалению, интерфейс между апострофом и EXECUTE менялся от диалекта к диалекту на протяжении многих лет.
В приведенной ниже таблице излагаются правила вычисления адреса слова для EXECUTE. В первом столбце даются примеры использования апострофа в режиме интерпретации (что было продемонстрировано выше). Например, на фиг-Форте вы можете ввести:
' ВСТРЕЧА CFA EXECUTE
Пояснения к остальным столбцам - более краткие.
1. Получение адреса для EXECUTE в режиме интерпретации | 2. Получение адреса для EXECUTE из входного потока (внутри определения) | 3. Получение адреса следующего слова внутри определения для передачи EXECUTE | Что помещает апостроф в стек | |
Фиг-Форт | ' имя CFA | [COMPILE] ' CFA | ' имя CFA | pfa |
MMS-Форт | ' имя 2 - | [COMPILE] ' 2- | ' имя 2- | pfa |
Стандарт-79 | ' имя CFA или FIND имя | [COMPILE] ' CFA | ' имя CFA | pfa |
полиФорт | ' имя | ' | ['] имя | pfa |
Стандарт-83 | ' имя | ' | ['] имя | cfa |
Векторное вычисление, хотя и воспринимается как нечто трудоемкое, на самом деле осуществляется довольно просто. Вместо того чтобы выполнять какое-либо определение непосредственно (см. выражение ' ВСТРЕЧА EXECUTE), мы выполняем его косвенно, запоминая адрес определения в некоторой переменной, а затем выполняя содержимое этой переменной:
VARIABLE УКАЗАТЕЛЬ ( участок для хранения исполнительного вектора) ' ВСТРЕЧА УКАЗАТЕЛЬ ! ( указатель ссылается на слово ВСТРЕЧА ) УКАЗАТЕЛЬ @ EXECUTE ( выполнение фрагмента, на который ссылается УКАЗАТЕЛЬ).
(Для вычисления адреса, который засылается в УКАЗАТЕЛЬ, примените соответствующее выражение из столбца 1 приведенной выше таблицы). В ряде систем есть слово @EXECUTE, которое эквивалентно выражению "@ EXECUTE", но выполняется более эффективно.
Вы Можете попытаться сделать следующий пример самостоятельно:
1 VARIABLE 'ФРАЗА ( вектор) 2 : ФРАЗА 'ФРАЗА @ EXECUTE ; ( векторизуемое определение) 3 : ПРИВЕТ ." Привет " ; 4 : ДО-СВИДАНИЯ ." До свидания " ; 5 6 ' ПРИВЕТ 'ФРАЗА ! ( инициализация вектора)
В строке 1 вы определяете переменную с именем 'ФРАЗА. Она будет вашим указателем. В строке 2 специфицируется слово ФРАЗА для выполнения определения, адрес которого находится в указателе 'ФРАЗА. В строках 3 и 4 создаются слова, выводящие сообщения: «Привет» и «До свидания». В строке 6 вы загружаете адрес слова ПРИВЕТ в указатель 'ФРАЗА по правилам, изложенным в столбце 1 вышеприведенной таблицы.
Теперь если выполнить слово ФРАЗА, то получится следующее:
ФРАЗА Привет ok
Если вы альтернативно выполните выражение
' ДО-СВИДАНИЯ 'ФРАЗА !
тем самым запомнив адрес ДО-СВИДАНИЯ в переменной 'ФРАЗА, то результат будет таким:
ФРАЗА До свидания ok
Апостроф в имени ' ФРАЗА означает, что данное имя является указателем при векторном вычислении - таковы соглашения в Форте. Так как апостроф при выполнении выдает адрес, то префикс соответствует адресу слова ФРАЗА.
Мы можем заставить само слово ФРАЗА выполнять все, что захотим, даже слова (ПРИВЕТ и ДО-СВИДАНИЯ), которые определены после слова ФРАЗА. Следовательно, апостроф обеспечивает один из способов реализации ссылок вперед. Ссылки вперед возникают в тех случаях, когда при определении некоторого слова приходится обращаться к другому слову, еще не определенному. В Форте нет естественных средств для программирования такой ситуации, да и в большинстве ситуаций вы можете просто по-иному расположить загружаемые определения. Но иногда переупорядочивание определений невозможно и вам без ссылок вперед не обойтись. В нашем примере на строке 6 происходит завершение реализации ссылки вперед.
Более общее применение векторных вычислений - изменение выполняемых действий некоторого слова уже после того, как оно скомпилировано. Определения слов, осуществляющих интерфейс, таких, как слова управления видеодисплеем, принтером, дисководом, часто делают векторными. Векторизация позволяет хранить имена для перечисленных функций в предварительно скомпилированной части Форт-системы и в то же время организовать свои собственные варианты их исполнения. Поскольку Форт-система сама использует вектора, ваши изменения будут влиять и на нее. Вы можете заставить систему после вывода «ok» сделать возврат каретки на любом терминале или принтере, к которому есть доступ.
Согласно Стандарту-83 апостроф всегда пытается найти следующее слово во входном потоке. Что произойдет, если мы поместим апостроф внутрь какого-либо определения? При исполнении такого определения апостроф будет искать следующее слово из входного потока. Таким образом, мы можем определить:
: СКАЖИ ( имя ( -- ) ' 'ФРАЗА ! ;
(Если у вас иная система, обратитесь к столбцу 2 вышеприведенной таблицы.) Необычный стековый комментарий означает, что слово СКАЖИ будет «заглядывать» вперед по входному потоку в поисках очередного слова.
Теперь можно ввести:
СКАЖИ ПРИВЕТ ok ФРАЗА Привет ok
или
СКАЖИ ДО-СВИДАНИЯ ok ФРАЗА До свидания ok
Апостроф в слове СКАЖИ осуществляет поиск имени определенных слов ПРИВЕТ и ДО-СВИДАНИЯ во входном потоке во время выполнения слова СКАЖИ. Во время определения этого слова апостроф ничего не делает (разве что позволяет себя компилировать).
А как быть, если нужно специфицировать посредством апострофа адрес следующего слова в определении? Для этого имеется слово ['], которое применяется вместо слова ', например1:
: ПРИХОДЯ ['] ПРИВЕТ 'ФРАЗА ! ; : УХОДЯ ['] ДО-СВИДАНИЯ 'ФРАЗА ! ;
Введите следующий текст:
ПРИХОДЯ ok ФРАЗА Привет ok УХОДЯ ok ФРАЗА До свидания ok
1 Для пользователей небольших систем. Если на вашей клавиатуре нет клавиши «[» или «]», то в документации по Форт-системе должна быть указана замена.
В столбце 3 приведенной выше таблицы изложены правила выполнения этих действий на каждом из диалектов Форта. Далее дается список команд, которые мы уже рассмотрели.
' xхх |
( -- а) |
Осуществляется поиск в словаре адреса слова ххх (следующего слова во входном потоке). |
['] |
период-компиляции: ( -- ) период-выполнения: ( -- а) |
Используется только в определении через двоеточие. Компиляция адреса следующего слова в определении как литерала. |
EXECUTE |
( а -- ) |
Исполнение элемента словаря, адрес поля параметров которого находится на стеке. |
@EXECUTE |
( а -- ) |
Исполнение элемента словаря, адрес которого является содержимым а. Если по адресу a находится нуль, @ЕХЕCUТЕ ничего не выполняет. |
Все определения, независимо от того, были ли они специфицированы посредством :, VARIABLE, CREATE или любого другого определяющего слова, состоят из следующих полей1:
Точное представление перечисленных полей в памяти зависит от реализации. И хотя Стандарт-83 предписывает полю имени «естественную длину» (в словаре запоминаются все символы имени - максимально 31), в наших примерах будет использоваться вариант с тремя хранимыми символами имени, так как это легче объяснить.
Ниже показано на примере переменной ДАТА, как рассматриваемые поля располагаются в памяти. На приведенной ниже схеме каждая строка представляет ячейку словаря.
1 Для систем, функционирующих на Форт-процессорах. Приводимая структура элементов словаря и техника вызова процедур относится к виртуальным Форт-системам, реализованным на обычных процессорах с применением косвенного шитого кода, а не к системам, где Форт-процессор реализован непосредственно в кремнии. (Но, изучив материал настоящей главы, вы скорее поймете, как работает Форт-машина.)
Поле имени. В нашем примере первый байт этого поля содержит число символов полного имени определяемого слова (в слове ДАТА четыре буквы). В следующих трех байтах хранятся представления первых трех букв имени определяемого слова в коде ASCII. В системах, где сохраняются только три символа имени, - это вся информация, которая используется апострофом или ['] для сравнения имени определения со словом из входного потока.
(Заметьте, что знаковый бит байта, в котором расположен счетчик символов, используется во время компиляции и показывает, будет ли данное слово исполняться во время компиляции или просто компилироваться в новое определение. Более подробно об этом идет речь в гл. 11.)
Поле связи. Ячейка связи содержит адрес предыдущего определения в словарном списке и применяется при поиске по словарю. В несколько упрощенном виде можно представить организацию связи следующим образом. Всякий раз, добавляя в словарь новое слово, компилятор заносит в поле связи указатель адреса предыдущего определения. На рисунке он заносит в поле связи слова КУЛИНАРИЯ указатель определения АВТОМОБИЛЬ.
Свой поиск апостроф или ['] начинает с самого последнего занесенного в словарь слова и просматривает «цепочку» в обратном направлении с помощью адресов находящихся в ячейках связи.
Поле связи первого определения в словаре содержит нуль, который предписывает апострофу прекратить поиск
- искомого слова в словаре нет
Поле кода. Следующим полем является поле кода Адрес, содержащийся в этом поле, позволяет отличать переменную от константы или от определения через двоеточие. Выполнение Форт-системой некоторого слова заключается в, исполнении процедуры на машинном языке, на которую есть ссылка в данном поле Например, в случае переменной указатель, ссылается на код, который помещает адрес этой переменной в стек, в случае константы - на код, который помещает в стек ее содержимое, а в случае определения через двоеточие - на код, выполняющий оставшуюся часть этого определения.
Код, на который происходит ссылка, называется кодом периода выполнения, потому что он используется при выполнении слова данного вида (а не тогда, когда оно определяется или компилируется).
Все переменные имеют свой один и тот же указатель кода, а все константы - свой Все определения через двоеточие - то же и т. д.
Поле параметров. За полем указателя кода следует поле параметров. В случае переменных и констант роле параметров представляет собой только одну ячейку, в случае
2CONSTANT или 2VARIABLE
- две, в случае массива - столько, сколько вы пожелаете, а в случае определения через двоеточие длина поля параметров зависит от длины данного определения, о чем и пойдет речь в следующем разделе.
При изучении полей, составляющих структуру словаря, важно понять различие между адресами этих полей и их содержимым. По соглашению адрес, по которому содержится указатель кода, называется адресом поля кода (cfa). Следовательно, cfa слов содержат указатель кода периода их выполнения.
Адрес первой ячейки, в которой хранится параметр, называется адресом поля параметров (pfa).
Адрес, помещаемый в стек апострофом, в одних реализациях является cfa, в других - pfa. Стандарт предусматривает слово >BODY, осуществляющее переход от cfa к pfa.
Таким образом, имеется возможность (хотя это далеко не «стандарт» и не может быть рекомендовано к применению) изменять значение существующей константы, например:
n ' ПРЕДЕЛ >BODY !
Если ваша система отличается от той, с которой мы работаем, обращайтесь к столбцу 4 приведенной ранее таблицы, где излагаются правила вычисления адреса для EXECUTE.
Между прочим поля имени и связи часто называют заголовком элемента, а поля указателя кода и параметров - его телом.
Если формат заголовка и поля указателя кода одинаков для всех типов определений в конкретной системе, то формат поля параметров меняется от типа к типу. Рассмотрим поле параметров определения через двоеточие.
Поле параметров определения через двоеточие содержит адреса уже определенных слов, составляющих данное определение1.
Ниже приводится элемент словаря для определения слова СНИМОК, которое выглядит следующим образом:
: СНИМОК ЗАТВОР ОТКРЫТЬ ВРЕМЯ ВЫДЕРЖАТЬ ЗАТВОР ЗАКРЫТЬ ;
При выполнении слова СНИМОК определения, расположенные в последующих адресах, выполняются в порядке очереди. Механизм, который читает список адресов и выполняет определения, расположенные по каждому адресу, называется адресным интерпретатором. Слово ; в конце определения компилирует адрес слова с именем EXIT. Как видно из рисунка, адрес EXIT расположен в последней ячейке элемента словаря. Адресный интерпретатор выполнит слово EXIT тогда, когда он подойдет к его адресу, точно так же, как он выполняет остальные слова определения. EXIT завершает выполнение адресного интерпретатора, что будет показано в следующем разделе.
1 Для специалистов. Адреса, составляющие тело определения через двоеточие, как правило, указывают поле кода, а не поле параметров (т. е. cfa, а не pfa).
Функция слова EXIT заключается в том, чтобы возобновить процесс вычислений в определении более высокого уровня (по порядку вложенности), из которого была сделана ссылка на текущее определение. Рассмотрим упрощенную схему работы этого механизма. Предположим, что ОБЕД состоит из трех блюд:
: ОБЕД ПЕРВОЕ ВТОРОЕ ДЕСЕРТ ;
причем сегодня на ВТОРОЕ подается только
: ВТОРОЕ ЦЫПЛЕНОК РИС ;
Мы находимся на стадии выполнения слова ОБЕД и только что покончили с блюдом ПЕРВОЕ. Указатель, которым пользуется адресный интерпретатор, называется указателем интерпретатора (I). Так как за блюдом ПЕРВОЕ следует ВТОРОЕ, наш указатель интерпретатора указывает ячейку, содержащую адрес слова ВТОРОЕ. Прежде чем перейти к выполнению слова ВТОРОЕ, увеличим значение указателя интерпретатора настолько, чтобы при возврате он указывали бы на ДЕСЕРТ.
Теперь приступим к выполнению слова ВТОРОЕ и начнем с выполнения кода слова ВТОРОЕ, т. е. кода, на который указывает поле кода и который является общим для всех определений через двоеточие. Этот код выполняет две функции: вносит содержимое указателя интерпретатора в стек возвратов, а затем помещает адрес своего поля параметров (pfa) в указатель интерпретатора. Теперь указатель интерпретатора отсылает нас к слову ЦЫПЛЕНОК.
Итак, интерпретатор адреса готов приняться за цыпленка. Однако прежде, как и в случае со словом ВТОРОЕ, увеличим указатель настолько, чтобы он при возврате показывал на РИС. После этого код слова ЦЫПЛЕНОК заносит указатель в стек возвратов и помещает pfa слова ЦЫПЛЕНОК в указатель интерпретатора.
По мере того как мы наслаждаемся нашим аппетитным цыпленком, последовательно исполняется определение, его составляющие. Рано или поздно, обрабатывая слово ЦЫПЛЕНОК, мы подойдем к
EXIT. Слово EXIT берет число из вершины стека возвратов и вносит его в указатель интерпретатора. Далее интерпретатор адреса продолжает процесс, выполняя слово РИС, покончив с последним. Несомненно, в конце концов,
EXIT в слове ВТОРОЕ поместит значение из стека возвратов в указатель интерпретатора, и мы с вами созреем для десерта.
Итак, вы уже знаете, каким образом Форт-система хранит адреса возврата в стеке возвратов, и вам понятно, почему при хранении в этом стеке временных переменных нужна особая осторожность. При выполнении следующего определения
: ТЕСТ 3 >R CR CR CR ;
в стек возвратов помещается значение 3, затем три раза осуществляется возврат каретки и происходит возврат, но куда? По адресу 3 нет никакого слова, поэтому скорее всего вероятно разрушение системы.
В большинстве Форт-систем во время выполнения цикла DO информация об индексе и границе хранится в стеке возвратов (и
не всегда в том виде, в котором вы ожидаете). Именно поэтому в пределах цикла DO LOOP использование операций >R и R> должно быть симметричным. Другими словами, вы имеете право писать так:
... >R ... DO ... LOOP ... R> ... ;
и не должны писать иначе, например:
... >R ... DO ... R> ... LOOP ... ;
Кроме того, если вы внутри тела цикла поместите в стек возвратов временное значение, то слово выборки индекса цикла I не будет выполняться правильно:
... DO ... >R ... I ... R> ... LOOP 5
Вам, вероятно, интересно узнать, что произойдет после того, как мы исполнили последний EXIT в слове ОБЕД Чей адрес возврата находится в стеке? Куда предстоит вернуться.
Напомним, что слово ОБЕД было запущено на выполнение
словом EXECUTE, которое является компонентой слова
INTERPRET. Последнее осуществляет циклический процесс непрерывной проверки входного потока. Допустим, вы уже отобедали, и во входном потоке ничего нет для интерпретирования.
В этом случае при выполнении EXIT слова INTERPRET вы выходите на определение самого верхнего уровня с именем QUIT. В упрощенной форме QUIT выглядит следующим образом:
: QUIT BEGIN RESET QUERY INTERPRET ." ok" CR FALSE UNTIL ; где RESET очищает стек возвратов, а QUERY настраивает входной поток на буфер входного текста.
(Определение QUIT в вашей системе может отличаться от приведенной.) Как видите, после слова INTERPRET следует сообщение точки - кавычки «ok» и CR, что и должно появиться на экране после завершения интерпретации. Далее идет выражение FALSE UNTIL, которое, безусловно, обеспечивает возврат в начало цикла, где вы очищаете стек возврата и снова ожидаете ввода.
Если исполнить QUIT на любом уровне выполнения, то немедленно прекратится вычисление по нашей программе и принудительно начнется выполнение цикла QUIT. Стек возвратов очистится (независимо от того, сколько в нем находится уровней адресов возврата, после чего вы никогда больше не сможете воспользоваться ни одним из них), а система будет ожидать ввода. Теперь вам понятно, почему слово QUIT может применяться для того, чтобы сообщение «ok» не выдавалось.
Другие способы использования QUIT. Еще два важных слова обращаются к QUIT: ABORT, которое, кроме всего прочего, очищает стек данных, и ABORT". Второе слово
Слово ABORT" было введено в конце гл. 4. Как правило, оно применяется в начале определений пользовательских слов (наивысшего уровня) для того, чтобы проверить хотя бы наличие аргументов в стеке, требуемых для выполнения этих слов. Например, вы можете написать следующее определение:
: LIST ( n -- ) ГЛУБИНА-СТЕКА 1 < ABORT" Нет номера блока " LIST ;
или в более общем виде:
: АРГУМЕНТЫ ( n -- ) ГЛУБИНА-СТЕКА < NOT ABORT" Значения?" ;
что может быть использовано так:
: LIST ( n -- ) 1 АРГУМЕНТЫ LIST ;
Существует возможность опустить один уровень исполнения, просто удалив один адрес из стека возвратов. В качестве примера рассмотрим три уровня исполнения, связанных со словом ОБЕД:
Предположим, что мы изменили определение ВТОРОЕ:
: ВТОРОЕ ЦЫПЛЕНОК РИС R> DROP ;
Выражение "R> DROP" удалит из стека возвратов адрес возврата в слове ДЕСЕРТ, который был туда помещен перед выполнением слова ВТОРОЕ. Если перезагрузить эти определения и выполнить слово ОБЕД, то
EXIT третьего уровня обеспечит возврат непосредственно на первый уровень. Мы «съедим» ПЕРВОЕ, ЦЫПЛЕНКА и РИС, но останемся без ДЕСЕРТА:
Применять в прикладной программе выражения "R> DROP" нежелательно, поскольку это противоречит принципам структурного программирования. Тем не менее данное выражение иногда позволяет упростить решение задачи. Мы не будем здесь приводить аргументы «за» и «против», однако изложенное должно послужить вам предостережением.
Как недавно упоминалось, слово EXIT удаляет адрес возврата из вершины стека возвратов и помещает его в указатель интерпретатора. Интерпретатор адреса, который ориентируется по содержимому указателя интерпретатора, начинает поиск на следующем верхнем уровне. Можно включить EXIT в середину определения. Например, если бы мы переопределили слово ВТОРОЕ:
: ВТОРОЕ РИС ВЕГЕТАРИАНЕЦ IF EXIT THEN ЦЫПЛЕНОК ;
и при этом были бы вегетарианцами, то нам пришлось бы выполнить
EXIT после слова РИС, пропустить слово ЦЫПЛЕНОК и перейти сразу к ДЕСЕРТУ.
Приведенное выше определение эквивалентно следующему:
: ВТОРОЕ РИС ВЕГЕТАРИАНЕЦ NOT IF ЦЫПЛЕНОК THEN ;
Вы не имеете права использовать внутри оператора цикла DO слово EXIT, так как это слово удаляет из стека один из аргументов, занесенных в него оператором DO (вместо того, чтобы удалить из стека возвратов адрес возврата)!
Вы только что видели результат удаления адреса возврата из стека возвратов. Приведем еще один пример - занесение в стек возвратов лишнего адреса (вам, может быть, придется привести адреса cfa и pfa в соответствии с вашей системой):
: ПРИВЕТ ." Привет " ; : ДО-СВИДАНИЯ ." До свидания " ; ' ДО-СВИДАНИЯ >BODY >R ПРИВЕТ
Сначала с помощью апострофа выбираем адрес слова ДО-СВИДАНИЯ и помещаем его в стек возвратов
- пусть Форт-система «думает», что это адрес возврата. Затем инициируем слово ПРИВЕТ, которое выдает свое приветствие. В конце концов, Форт-система обращается к стеку возвратов за следующим адресом и, выбрав его, выполняет ДО-СВИДАНИЯ
- после слова привет:
>BODY |
( cfa -- pfa) |
Вычисление адреса поля параметров определения, "адрес компиляции " которого находится на стеке. |
EXIT |
( -- ) |
Удаление адреса возврата из вершины стека возвратов и занесение его в указатель адресного интерпретатора. Если слово EXIT скомпилировано в определении через двоеточие, то оно завершает выполнение этого определения в данной точке» |
QUIT |
( -- ) |
Очистка стека возвратов и передача управления терминалу, ожидающему ввода. Сообщения при этом не выдаются. |
ABORT |
( -- ) |
Очистка стека данных и выполнение функций слова QUIT. Сообщения не выдаются. |
Рекурсией называется обращение процедуры к самой себе. Обычными средствами в Форте запрещается это делать. Например, при вводе
: LOAD ( n -- ) DUP . LOAD ;
вы определяете новую версию слова LOAD, которое выдает номер загружаемого блока, после чего обращается к исходному варианту
LOAD. Чтобы выражения такого типа были возможны, Форт-система во время компиляции определения умышленно «прячет» имя данного определения, и тем самым в новое определение компилируется адрес исходного варианта.
Тем не менее на Форте реализовать рекурсию довольно просто (нехитрые средства защиты Форта легко обмануть). Слово RECURSE осуществляет компиляцию адреса текущего определяемого слова. (В некоторых системах это слово известно как MYSELF.)1 Приведем пример рекурсии:
VARIABLE СЧЕТЧИК : ПОЧЕМУ CR ." Почему вы спрашиваете? " -1 СЧЕТЧИК +! СЧЕТЧИК @ IF RECURSE THEN ; 5 СЧЕТЧИК !
Выполнив слово ПОЧЕМУ, вы получите:
ПОЧЕМУ Почему вы спрашиваете? Почему вы спрашиваете? Почему вы спрашиваете? Почему вы спрашиваете? Почему вы спрашиваете? ok
1 Для пользователей систем фиг-Форта. Определение следующее:
: RECURSE LATEST PFA CFA , ; IMMEDIATE
Для более ранних систем полифорта:
: RECURSE LAST @ @ 2+ , ; IMMEDIATE
Обратите внимание на то, что в рассматриваемом примере нет ни одного цикла. Начиная со значения 5 счетчика, слово ПОЧЕМУ будет обращаться к самому себе до тех пор, пока значение счетчика не станет равным нулю. (Не выполняйте слово ПОЧЕМУ при нулевом или слишком большом значении счетчика, поскольку ваш стек возвратов при этом переполнится.)
Вы видите на рисунке карту памяти1 типичной Форт-системы
для одного пользователя. Мультипрограммные системы, такие, как полиФорт, устроены намного сложнее, о чем пойдет речь позднее. А пока рассмотрим простой случай и последовательно изучим каждый район нашей карты.
Предварительно скомпилированное ядро Форта. В памяти с младшими адресами расположен единственный предварительно скомпилированный участок системы (уже скомпилированный в словарную форму). В одних системах коды этого участка хранятся на диске (как правило, блоки 1-8) и автоматически загружаются в память с произвольной выборкой во время запуска или восстановления вашего компьютера, в других - такой участок неизменно находится в программируемой постоянной памяти и становится доступным сразу, как только вы включаете компьютер. В предварительно скомпилированном участке обычно хранится большая часть математических операций и слов форматизации чисел одинарной длины, операции преобразования стека одинарной длины, команды редактирования, структуры управления, ассемблер, все определяющие слова, которые вам уже известны, и, конечно, интерпретаторы текста и адреса2. |
2 Для специалистов. Чтобы получить представление о том, как компактен Форт, вам достаточно знать, что весь предварительно скомпилированный участок полифорта занимает меньше 8К байтов памяти
Системные переменные. Следующий раздел памяти содержит системные переменные, которые созданы предварительно скомпилированным ядром Форта и используются всей системой. Они, как правило, не применяются пользователем.
Выборочные определения. Тот раздел Форт-системы, который не был предварительно скомпилирован, хранится на диске в виде исходного текста. Какую часть определений из этого раздела загружать, а какую - нет, вы можете решить сами, что улучшит управление использованием памяти вашего компьютера. Блок загрузки для всех выборочных определений называется блоком выбора.
Словарь пользователя. Словарь расширяется в сторону увеличения адресов памяти по мере того, как вы добавляете ваши собственные определения в область памяти, называемую словарем пользователя. Его следующая доступная ячейка в любой момент времени определяется содержимым переменной с именем Н (или DP). Во время компиляции указатель Н, по мере того как очередной элемент добавляется к словарю, переходит с ячейки на ячейку (или с байта на байт). Таким образом, для компилятора указатель Н выступает в качестве закладки; он указывает то место в словаре, куда компилятор может компилировать следующий объект. Этот указатель также используется словом ALLOT, которое передвигает его на заданное число байтов. Например, выражение 10 ALLOT добавляет к нему 10 и, следовательно, компилятор зарезервирует память в словаре для массива, состоящего из 10 байтов (или пяти ячеек).
Родственным словом является и HERE, которое определяется весьма просто:
: HERE ( -- текущий-адрес ) H @ ;
Оно помещает значение Н в стек. Слово , (запятая) помещает значение одинарной длины в следующую доступную ячейку словаря:
: , ( n -- ) HERE ! 2 ALLOT ;
т. е. запоминает некоторое значение в HERE и продвигает указатель словаря на два байта, закрепляя память под это значение. С помощью HERE вы можете определить, какой объем памяти требуется для любого фрагмента вашей программы, для чего нужно сравнить HERE до компиляции и после нее. Например, выражение
HERE 220 LOAD HERE SWAP - . 196 ok
показывает, что определения, загруженные блоком 220, заняли 196 байтов памяти словаря.
Рабочая область (PAD). На некотором удалении от HERE вы обнаружите в своем словаре небольшую область памяти, называемую рабочей областью. Наша рабочая область обычно служит для хранения строк символов в коде ASCII, которые подвергаются обработке перед выводом на терминал. Так, слова, осуществляющие форматирование чисел, используют рабочую область для обработки чисел в коде ASCII во время их перевода, прежде чем вывести эти числа с помощью TYPE,
Размер рабочей области не определен.
В большинстве систем расстояние между началом рабочей области и вершиной стека данных измеряется сотнями и даже тысячами байтов.
Поскольку адрес начала рабочей области определяется относительно последнего элемента словаря, он изменяется всякий раз при добавлении нового определения либо выполнении FORGET или EMPTY.
Подобная организация тем не менее гарантирует безопасность, так как рабочая область никогда не используется при выполнении перечисленных выше действий. Слово PAD вносит в вершину стека текущий адрес начала рабочей области. Оно имеет довольно простое определение:
: PAD ( -- a) HERE 34 + ;
т.е. помещает в стек адрес, который отстоит на фиксированное число байтов от HERE (в действительности это число может меняться).
Стек данных. Намного выше рабочей области расположен участок, зарезервированный под стек данных. У вас может создаться впечатление, что значения где-то передвигаются вверх и вниз, как будто их кто-то «вталкивает» и «выталкивает», однако на самом деле ничего подобного не происходит. Единственное, что изменяется, - это указатель вершины стека.
Как вы увидите в дальнейшем, при занесении некоторого числа в стек фактически лишь уменьшается указатель (т. е. указывает на следующий участок в направлении к младшим адресам памяти), а затем число запоминается в том месте, куда показывает указатель. Когда вы удаляете число из стека, оно выбирается из участка, на который показывает указатель, после чего последний увеличивается.
Все числа, расположенные на карте памяти выше указателя стека, не имеют смысла.
Но мере добавления в стек новых значений он «растет» в направлении младших адресов памяти.
Указатель стека выбирается посредством слова SP@1. Так как это слово доставляет адрес самого верхнего участка стека, выражение SP@ выбирает содержимое вершины стека. Такая операция, конечно, идентична операции DUP. Если у вас в стеке находится пять значений, то пятое значение можно скопировать с помощью выражения:
SP@ 8 + @
(но в большинстве случаев это не может считаться хорошим стилем программирования).
На дно стека указывает переменная S0. Она всегда содержит адрес ячейки, расположенной непосредственно под ячейкой, соответствующей «пустому» стеку.
Заметим, что при размещении чисел двойной длины как в стеке, так и в словаре старшая по порядку ячейка размещается по
1 Для пользователей систем полифорта Это слово имеет имя 'S
младшему адресу. При выполнении операций 2! и
2@ (см. рисунок). Этот порядок размещения ячеек сохраняется.
Буфер входного текста. Буфер входного текста представляет собой область памяти длиной, как правило, 80 байт, куда поступают вводимые с клавиатуры символы после нажатия клавиши «возврат каретки». Именно здесь они будут просмотрены текстовым интерпретатором.
Не путайте этот термин с термином входной поток (см. гл. 3), который означает последовательность слов, подлежащих интерпретации. Данная последовательность может быть расположена либо в буфере входного текста (в режиме интерпретации), либо в блоке, содержащем исходный текст (в режиме загрузки).
Буфер входного текста увеличивается в направлении старших адресов памяти (в том же направлении, что и рабочая область (PAD)). Слово TIB выбирает начальный адрес буфера. (На фиг-Форте вы можете ввести "TIB @", на полиФорте - "S0 @".)
Стек возвратов. Выше буфера входного текста расположен стек возвратов, функционирование которого идентично функционированию стека данных.
Пользовательские переменные. Следующий раздел памяти содержит пользовательские переменные. Эти переменные включают в себя слова BASE, S0 и многие другие, которые мы рассмотрели ниже.
Блочные буферы. В самой верхней области памяти расположены буферы блоков. Каждый буфер имеет объем 1024 байта для размещения содержимого дискового блока. Всякий раз, когда вы осуществляете доступ к какому-либо блоку (например, распечатывая или загружая его), система копирует данный блок с диска в такой буфер, где он может изменяться с помощью редактора или интерпретироваться посредством слова LOAD. Более подробно мы обсудим блочные буферы в гл. 10.
На этом наше путешествие по карте памяти типичной Форт-системы индивидуального пользования завершается. Далее приводится перечень уже знакомых вам слов, которые используются при работе с различными участками памяти.
Н или DP |
( -- а) |
Занесение в стек адреса указателя словаря. |
HERE |
( -- а) |
Занесение в стек адреса очередного доступного участка словаря. |
PAD |
( -- а) |
Занесение в стек адреса начала рабочей области, в которой хранятся строки символов в процессе промежуточной обработки. |
SP@ или 'S |
( -- а) |
Занесение в стек адреса вершины стека данных до того, как исполнено само слово SP@. |
S0 |
( -- а) |
Содержит адрес дна стека данных. |
TIB |
( -- a) |
Занесение в стек адреса начала буфера входного текста. |
Наряду с однозадачными существуют и мультизадачные Форт-системы1. Они могут работать с произвольным числом задач. Задача может быть либо терминальной, при выполнении которой вся интерактивная мощь Форта передается оператору, сидящему за терминалом, либо управляющей, которая обеспечивает управление аппаратным средством, не имеющим терминала.
Любой из задач нужна своя пользовательская область. Размер и содержимое пользовательской области зависят от вида задачи. Типовые структуры для двух видов задач показаны на рисунке.
Каждой терминальной задаче требуется собственный словарь, рабочая область (PAD), стек данных, буфер входного текста, стек возвратов и пользовательские переменные. Это означает, что все определяемые вами слова, как правило, недоступны другим задачам. Кроме того, все задачи имеют свои собственные копии пользовательских переменных, таких, как BASE.
Управляющая задача имеет пару стеков и небольшой набор пользовательских переменных. Так как при выполнении управляющей задачи не используется терминал, ей не требуются ни собственный словарь, ни рабочая область, ни буфер входного текста.
Пользовательская переменная в отличие от обычной (определяемой с помощью слова VARIABLE), значение которой хранится в поле параметров элемента словаря, состоит из двух частей. Сами данные хранятся в массиве, называемом пользовательской таблицей. Элемент словаря для каждой пользовательской переменной расположен в другом месте; он содержит смещение в пользовательской таблице. Когда вы выполняете имя пользовательской пере-
1 Для начинающих. Термин «мультизадачная» описывает систему, в которой многочисленные задачи выполняются одновременно на одном и том же компьютере, не оказывая влияния друг на друга.
менной, например Н, смещение добавляется к начальному адресу пользовательской таблицы, что дает вам адрес
Н в этом массиве и позволяет применять @ или !.
Основное достоинство пользовательских переменных состоит в том, что любое число задач может использовать одно и то же определение некоторой переменной, и каждая задача может получать свое собственное значение этой переменной. Всякая задача, которая выполняет выражение
BASE @
получает значение BASE из своей пользовательской таблицы, благодаря чему экономится большой объем памяти, и тем не менее она может выполняться независимо от остальных задач.
Порядок размещения пользовательских переменных в таблице и значения и смещений изменяются от системы к системе. Итак, существуют переменные трех видов:
В простой Форт-системе имеются три штатных контекстных словаря: словарь Форта, словарь редактора и словарь ассемблера.
Все рассмотренные выше слова принадлежат словарю Форта, за исключением команд редактора, которые относятся к словарю редактора. В словарь ассемблера включены команды, предназначенные для программирования на языке Ассемблера конкретного компьютера.
Определения добавляются в один и гот же словарь в порядке их компиляции, независимо от того, к какому контекстному словарю они принадлежат. Таким образом, контекстные словари являются не подразделами словаря как участка памяти, а связанными списками, переплетенными между собой внутри этого общего словаря1.
В качестве примера рассмотрим три контекстных словаря: «футбол», «бейсбол» и «баскетбол» (см. рисунок). Все они совместно существуют в одном и том же общем словаре, однако апостроф, проходя по цепи, имеющей отношение, скажем, к баскетболу, пере-
1 Необходимо отличать общий словарь (dictionary) как прерывный участок памяти для размещения слов от словаря (vocabulary)
- связанного списка слов. Примерами последнего могут служить словари Форта, редактора и ассемблера.
- Примеч. ред.
бирает только слова из баскетбольного словаря. Даже если в каждом контекстном словаре есть слово ЦЕНТР, апостроф подберет его вариант, требуемый для данного контекста.
Помимо замкнутости контекстных словарей, необходимо отметить еще одно преимущество такой организации данных - скорость поиска. Если в нашем примере идет речь о баскетболе, то зачем нам перебирать слова, относящиеся к футболу и бейсболу?
Вы можете переключить контекст поиска по словарю, выполнив любую из трех команд: FORTH, EDITOR или ASSEMBLER. Например, вводя слово FORTH, вы уверены, что поиск будет осуществляться в контексте словаря Форта. Но, как правило, Форт-система изменяет для вас контекст автоматически. Рассмотрим типичную схему. Система начинает работу с контекста словаря Форта. Допустим, вы заносите некоторую программу в блок. Конкретные команды редактора переключают контекст на словарь редактора. Вы работаете в контексте словаря редактора до тех пор, пока не осуществите загрузку вашего блока и не приступите к компиляции определений. Слово : автоматически восстанавливает контекст, который был ранее, а именно: Форт.
Различные версии Форт-систем имеют различные реализации контекстных словарей. Однако существуют некоторые общие положения, которые можно распространить на большинство систем.
Словарь, где должен осуществляться поиск, определяется пользовательской переменной с именем CONTEXT (КОНТЕКСТ). Как
уже упоминалось выше, команды FORTH, EDITOR и ASSEMBLER изменяют контекст поиска.
Известен еще один вид контекста: словарь, к которому должно быть присоединено новое определение. Такой словарь задается другой переменной с именем CURRENT (ТЕКУЩИЙ). Поскольку CURRENT обычно определяет словарь Форта, то новые определения, как правило, присоединяются к данному словарю.
А каким образом система компилирует слова в словари редактора и ассемблера? Это делается с помощью слова DEFINITIONS (ОПРЕДЕЛЕНИЯ), например:
EDITOR DEFINITIONS
Вы знаете, что слово EDITOR устанавливает CONTEXT для редактора. Слово DEFINITIONS копирует содержимое слова CONTEXT, каким бы оно ни было, в слово CURRENT. Слово DEFINITIONS имеет простое определение:
: DEFINITIONS CONTEXT @ CURRENT ! ;
После вывода выражения EDITOR DEFINITIONS все компилируемые с этого момента слова заносятся в словарь редактора до тех пор, пока вы не введете выражение FORTH DEFINITIONS, чтобы поместить в CURRENT Форт.
Техника работы с контекстными словарями существенно зависит от конкретной системы и в какой-то степени противоречива. В Стандарте-83 подробности опущены, а потому обойдемся без них и мы. Обращайтесь к документации по своей системе.
' ххх |
( -- а) |
Осуществляется поиск в словаре адреса слова ххх ( следующего слова из входного потока ) . |
['] |
период-компиляции: ( -- ) период-выполнения: ( -- a) |
Используется только в определении через двоеточие. Компиляция адреса следующего слова из определения как литерала. |
EXECUTE |
( a -- ) |
Выполнение элемента словаря, адрес поля параметров которого находится на стеке. |
@EXECUTE |
( a -- ) |
Выполнение элемента словаря, на pfa которого ссылается содержимое по адресу а. Если адрес содержит нуль, то @EXECUTE ничего не выполняет. |
>BODY |
( cfa -- pfa) |
Вычисление адреса поля параметров определения, адрес компиляции которого находится на стеке. |
EXIT |
( -- ) |
Удаление адреса возврата, из вершины стена возвратов и занесение его в указатель адресного интерпретатора. Если слово EXIT скомпилировано в определении через двоеточие, то оно завершает выполнение этого определения в данной точке. |
QUIT |
( -- ) |
Очистки стека возвратов и передача управлений терминалу, ожидающему ввода. Сообщения при этом ни выдается. |
ABORT |
( -- ) |
Очистке стека данных и выполнение функций слова QUIT. Сообщения не выдаются. |
H или DP |
( -- a) |
Занесение в стек адреса указателя словаря. |
HERE |
( -- a) |
Занесение в стек адреса очередного доступного участка словаря. |
PAD |
( -- a) |
Занесение в стен адреса начала рабочей области, в которой хранятся строки символов в процессе промежуточной обработки. |
SP@ или 'S |
( -- a) |
Занесение в стек адреса вершины стека данных до того, как исполнено сама слово SP@. |
S0 |
( -- a) |
Содержится адрес дна стека данных. |
TIB |
( -- a) |
Занесение в стек адреса начала буфера входного текста. |
Адресный интерпретатор. Второй из двух интерпретаторов Форта выполняет список адресов из элемента словаря, являющегося определением через двоеточие.
Буфер входного текста. Участок памяти внутри терминальной задачи, используемый для хранения текста по мере его поступления с терминала. Именно здесь интерпретируется поступивший исходный текст.
Выборочные определения. Набор определений Форта, которые входят в систему, но хранятся отдельно от предварительно сгенерированных определений. Так называемый «блок выбора» загружает блоки, содержащие выборочные определения. Этот блок по желанию пользователя может быть изменен,
Загрузка. Загрузка предварительно скомпилированного участка Форта в компьютер, после которой вы можете работать на Форте. Она происходит автоматически, когда вы включаете компьютер или нажимаете клавишу «Восстановление».
Заголовок, Поля имени и связи в элементе словаря Форта.
cfa. Адрес поля кода; адрес поля кода в элементе словаря.
Задача. Применительно к Форту это участок памяти, содержащий как минимум параметр и стек возвратов, а также множество пользовательских переменных.
Код периода выполнения. Программа, которая хранится в памяти в скомпилированном виде и определяет, что происходит во время выполнения слов заданного класса. Код периода выполнений для определения через двоеточие осуществляет переход на следующий уровень и передает управление адресному интерпретатору. В случае переменной код периода выполнения помещает содержимое pfa данной переменной в вершину стека.
Контекстный словарь. Независимо связанное подмножество словаря Форта.
Векторные вычисления. Способ вычислений, при котором выполняемый код задается не своим адресом, а адресом некоторого участка, где содержится адрес этого кода. Такой участок обычно называют «вектором». При изменении состояния системы вектор может быть переключен на некоторый другой участок кода.
Определяющee слово. Слово Форта, создающее элемент словаря, например : CONSTANT, VARIABLE и т. д.
Яоле кода. Ячейка в элементе словаря, содержащая адрес кода периода исполнения для данного конкретного типа определения.
Поле имени. Участок элемента словаря, содержащий имя (или сокращение этого имени) определенного слова, а также число символов в данном имени.
Поле связи. Ячейка элемента словаря, содержащей адрес предыдущего определения; применяется при поиске по словарю (в системах, использующих несколько цепочек, поле связи содержит адрес предыдущего определения в этой же цепочке).
Яоле параметров. Участок элемента словаря, где находится «содержимое» данного определения: в случае CONSTANT - значение константы, в случае VARIABLE - значение переменной, в случае определения через двоеточие - список адресов слов, которые должны выполняться поочередно во время выполнения этого определения. Длина поля параметров меняется в зависимости от назначения.
Пользовательская переменная. Переменная Форта, к которой в отличие от пользовательской переменной возможен доступ из любой задачи системы.
pfa. Адрес поля параметров; адрес первой ячейки поля параметров элемента словаря (или, если поле параметров представляет собой одну ячейку, адрес последней).
Рабочая область (PAD). Участок памяти внутри терминальной задачи, который служит рабочей областью для хранения строк символов в процессе промежуточной обработки.
Системная переменная. Переменная Форта, которая в отличие от пользовательской переменной доступна по всей системе (в любой задаче).
Ссылка вперед. Ссылка на слово, которое еще не определено. В Форте - это способ реализации векторных вычислений.
Тело элемента. Поля кода и параметров элемента словаря Форта.
Терминальная задача. Задача в мультизадачной системе, обеспечивающая взаимодействие с оператором через терминал, т. е. задача, имеющая текстовый интерпретатор, словарь и т. д.
Управляющая задача. В мультизадачной системе - задача, которая не допускает взаимодействия с терминалом. Управляющие задачи, как правило, обслуживают аппаратные средства.
Участок предварительно скомпилированных определений. Часть Форт-системы, которая постоянно присутствует в памяти в объектном коде сразу после выполнения операции начальной загрузки или восстановления. Эта часть обычно включает в себя текстовый и адресный интерпретаторы, определяющие слова, структуры управления, операции одинарной длины и операции над стеком, команды преобразования чисел одинарной длины и их форматизирования, редактор, ассемблер.
9.1. Напишите определение слова ПОЛУЧАЕМ так, чтобы его функции могли корректироваться словами СКЛАДЫВАЯ и УМНОЖАЯ, как в приведенном ниже примере:
СКЛАДЫВАЯ 2 3 ПОЛУЧАЕМ 5 ok УМНОЖАЯ 2 3 ПОЛУЧАЕМ 6 ok
9.2. Каков начальный адрес вашего личного словаря?
9.3. Как далеко расположена рабочая область (PAD) в чашей системе от конца вашего личного словаря?
9.4. Если слово ДАТА определено через VARIABLE, то в чем состоит различие между следующими фразами. ДАТА . и ' ДАТА >BODY ? А чем отличаются друг от друга эти две фразы: BASE и ' BASE >BODY ?
9.5. В этом упражнении вы должны создать массив для векторных (косвенных) вычислений, т.е. массив с адресами слов Форта. Определите одномерный массив ячеек, содержащих по два байта каждая, который будет возвращать адрес n-го элемента при заданном индексе n. Определите несколько слов так, чтобы они обеспечивали вывод на ваш дисплей некоторой информации и ничего не вводили. Запомните адреса этих слов в различных элементах массива, а адрес ничего не выполняющего слова - в остальных его элементах. Определите слово, вырабатывающее правильный индекс и выполняющее слово, адрес которого расположен в соответствующем индексу элементе, например:
0 ВЫПОЛНИ Привет, я ГОВОРЮ на Форте, ok 1 ВЫПОЛНИ 1 2 3 4 5 6 7 8 9 10 ok 2 ВЫПОЛНИ ********** ********** ********** ********** ********** 3 ВЫПОЛНИ ок 4 ВЫПОЛНИ ок