Глава 11
РАСШИРЕНИЕ КОМПИЛЯТОРА:
ОПРЕДЕЛЯЮЩИЕ И КОМПИЛИРУЮЩИЕ СЛОВА

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

ЧТО ТАКОЕ ОПРЕДЕЛЯЮЩЕЕ СЛОВО?

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

:
VARIABLE
CONSTANT
CREATE

Все они обладают одним общим свойством: «определять» слова и добавляют новые имена к словарю. В отличие от других языков Форт позволяет создавать свои собственные определяющие слова. Для чего это нужно? Вообще говоря, определяющие слова способствуют хорошему разбиению программы. Мы уже неоднократно излагали вам концепцию разбиения (см. гл. 8). Такая программа легко читается, легко воспринимается и легко исправляется.

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

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

Слово CREATE считается родителем, а слово ПРИМЕР - ребенком. Что делает ребенок в период выполнения? Он помещает свой собственный pfa в стек. А откуда он знает, что нужно делать именно это? Мы не задавали непосредственно ему никакой программы для выполнения. Ответ прост - ПРИМЕР вообще не содержит кода периода выполнения. Его указатель кода указывает на родителя (CREATE), у которого код периода выполнения есть.

Предположим, что в Форте нет слова VARIABLE. Мы можем его определить:

: VARIABLE CREATE 0 , ;

Мы обратились к слову CREATE внутри определения через двоеточие. Что при этом произойдет? Давайте проследим за выполнением в хронологическом порядке:

: VARIABLE CREATE 0 , ;

1 Для пользователей систем фиг-Форта. Не забудьте переопределить слово CREATE следующим образом:

: CREATE <BUILDS DOES> ;

Определяется определяющее слово VARIABLE

VARIABLE АПЕЛЬСИНЫ

Исполняется слово VARIABLE, в свою очередь выполняя две функции:

CREATE. Создает с помощью CREATE заголовок в словаре с именем АПЕЛЬСИНЫ и указателем кода, который ссылается на код периода выполнения слова CREATE;

0, Засылает 16-разрядный нуль в поле параметров вновь созданной переменной и выделяет ячейку памяти.

АПЕЛЬСИНЫ

Исполняется слово АПЕЛЬСИНЫ. Так как указатель кода слова АПЕЛЬСИНЫ ссылается на код периода выполнения CREATE, pfa этого слова помещается в вершину стека. Конечно, мы могли бы обойтись без слова VARIABLE. Вполне достаточно ввести следующее:

CREATE ПРИМЕР 0 ,

Однако такая запись менее изящна, поскольку здесь разбиты на отдельные действия создание заголовка и выделение памяти. Пример с определением слова VARIABLE демонстрирует лишь половину возможностей механизма определяющих слов. Если бы мы вместо VARIABLE воспользовались словом CREATE, то нам пришлось бы подкорректировать единственное место - в фазе 1, где происходит определение слова АПЕЛЬСИНЫ. И напротив, в фазе 3 слово АПЕЛЬСИНЫ вело бы себя одинаково при определении посредством как CREATE, так и VARIABLE.

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

: CONSTANT CREATE , DOES> @ ;

Здесь «собака зарыта» в выполнении слова DOES>, которое отмечает начало кода периода выполнения для всех своих детей. Как вы знаете, константы (т.е. дети определяющего слова CONSTANT) засылают в стек свои значения, которые хранятся в их поле параметров. Поэтому слово @, которое следует за DOES>, выбирает значение константы из ее собственного pfa.

В любом определяющем слове CREATE отмечает начало действий, выполняемых в период компиляции (фаза 2), a DOES> - конец действий периода компиляции и начало операций периода выполнения (фаза 3).

Проследим еще раз все наши действия:

: CONSTANT CREATE , DOES> @ ;

Определение определяющего слова CONSTANT.

76 CONSTANT ТРОМБОНЫ

Исполнение слова CONSTANT, которое в свою очередь выполняет следующие три действия:

Создает с помощью CREATE заголовок словарной статьи с именем ТРОМБОНЫ. Выбирает из стека значение (например, 76) и заносит его в поле параметров константы. Устанавливает указатель поля кода слова ТРОМБОНЫ на код, следующий за словом DOES.

ТРОМБОНЫ

Выполнение слова ТРОМБОНЫ. Поскольку указатель поля кода слова ТРОМБОНЫ теперь указывает код, следующий за DOES>, выбирается значение (76) и помещается в вершину стека. Обратите внимание на то, что в фазе 3 слово DOES> сначала помещает в вершину стека pfa ребенка. Иными словами, определение

: VARIABLE CREATE 0 , ;

эквивалентно следующему:

: VARIABLE CREATE 0 , DOES> ;

Последнее в период выполнения помещает в вершину стека pfa, а в период компиляции ничего не выполняет.

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

: CONSTANT ( n -- ) CREATE ,
   DOES> ( -- n) @ ;

Верхняя строка стекового комментария описывает поведение родителя во время компиляции, а нижняя, после DOES>, задает поведение ребенка.
DOES>
период-выполнения : ( - a)
Используется при создании определяющих
слов. Отмечается конец участка периода
компиляции и начала участка периода выполнения.
Операции периода выполнения определены на
высокоуровневом форте. Во время выполнения на
стеке будет находиться pfa определенного слова.

ОПРЕДЕЛЯЮЩИЕ СЛОВА ВЫ МОЖЕТЕ СПЕЦИФИЦИРОВАТЬ САМИ

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

: 1ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 0 .БРЕД ;
: 2ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 1 .БРЕД ;
: СУЩЕСТВИТЕЛЬНОЕ 10 CHOOSE 2 .БРЕД ;

Если бы вам пришлось определять и другие части речи, то можно было бы их выделить в отдельное слово «часть-речи»:

: ЧАСТЬ ( столбец# -- ) 10 CHOOSE SWAP .БРЕД ;
: 1ПРИЛАГАТЕЛЬНОЕ 0 ЧАСТЬ ;
: 2ПРИЛАГАТЕЛЬНОЕ 1 ЧАСТЬ ;
: СУЩЕСТВИТЕЛЬНОЕ 2 ЧАСТЬ ;

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

: ЧАСТЬ ( столбец# -- ) CREATE ,
    DOES> @ 10 CHOOSE SWAP .БРЕД ;
0 ЧАСТЬ 1ПРИЛАГАТЕЛЬНОЕ
1 ЧАСТЬ 2ПРИЛАГАТЕЛЬНОЕ
2 ЧАСТЬ СУЩЕСТВИТЕЛЬНОЕ

Приведенное определение слова ЧАСТЬ аналогично определению слова CONSTANT1, с той лишь разницей, что в период выполнения помимо занесения в стек номера столбца оно также определяет номер строки посредством датчика случайных чисел и обращается к слову .БРЕД.

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

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

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

360 CONSTANT ВАСИЛЬЕВ
385 CONSTANT СИМОНЧИК
410 CONSTANT РЯБИНИНА
455 CONSTANT ИВАНОВА
460 CONSTANT ПЕТРОВА
485 CONSTANT ДЕМИН

Если у вас со сложением дела обстоят плохо, то вы обязательно ошибетесь. Кроме того, вдруг вы измените свое решение и выделите каждому студенту по 30 блоков? Есть более изящный прием. Он состоит в том, чтобы выделить вычисления во время компиляции в отдельное слово:

: +СТУДЕНТ ( n -- n+25 n ) DUP 23 + SWAP ;
    360 \ начало участка памяти, отведенной студентам
+СТУДЕНТ CONSTANT ВАСИЛЬЕВ
+СТУДЕНТ CONSTANT СИМОНЧИК
+СТУДЕНТ CONSTANT РЯБИНИНА
+СТУДЕНТ CONSTANT ИВАНОВА
+СТУДЕНТ CONSTANT ПЕТРОВА
+СТУДЕНТ CONSTANT ДЕМИН
. .( Конец участка памяти, отведанного студентам ) CR

Последняя точка выбирает из стека номер блока. Но и этот прием блекнет перед методом использования определяющих слов. Убедитесь сами:

: СТУДЕНТ ( n -- n+23) CREATE DUP , 23 +
    DOES> ( -- n) @ ;
360 \ Начало участка, отваленного студентам
СТУДЕНТ ВАСИЛЬЕВ
СТУДЕНТ СИМОНЧИК
СТУДЕНТ РЯБИНИНА
СТУДЕНТ ИВАНОВА
СТУДЕНТ ПЕТРОВА
СТУДЕНТ ДЕМИН
. .( Конец участка, отведенного студентам ) CR

Определяющее слово СТУДЕНТ и создает «константы», и управляет всеми вычислениями во время компиляции.

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

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

CREATE КЛАПАНЫ 30 ALLOT \ байтовый массив установки клапанов
: КЛАПАН ( i -- а) \ преобр. номера клапана в абсолютный адрес
   КЛАПАНЫ + ;

Здесь слово КЛАПАН вычисляет индекс массива КЛАПАНЫ. Например, при выполнении выражения

6 КЛАПАН С@

будет включен гидравлический клапан 6.

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

: МАССИВ ( #байтов -- ) \ определение одномерного массива байтов
    CREATE ALLOT
    DOES> ( i -- a) + ;
30 МАССИВ КЛАПАН
6 КЛАПАН С@

Рассмотрим выполнение этого определения. В фазе 1 определяется слово МАССИВ. В фазе 2 исполняется МАССИВ, который в свою очередь обращается к слову CREATE (чтобы определить КЛАПАН) и слову ALLOT (для резервирования 30 байтов под массив). В фазе 3 исполняется слово КЛАПАН, инициируя код периода выполнения слова МАССИВ с добавлением индекса (6) к начальному адресу массива.

Если внести изменения в определение определяющего слова перед его повторной компиляцией, то тем самым можно изменить характеристики всех слов, входящих в данное семейство. Такая возможность значительно упрощает разработку программ. Например, когда нужно при описании массива заполнить его нулями, вы должны соответствующим образом создать определение МАССИВ. Сначала вы определяете слово, которое аналогично ALLOT, но «обнуляет» выделенный участок памяти:

: 0ALLOT ( #байтов -- ) HERE OVER ERASE ALLOT ;

Затем подставляете в определение МАССИВ вместо ALLOT слово 0ALLOT:

: МАССИВ ( #байтов -- ) \ определение одномерного массива байтов
    CREATE 0ALLOT
    DOES> ( i -- a) + ;

Можно также выделить в отдельный фрагмент некоторые отладочные процедуры, необходимые при разработке программы, а затем удалить их, предварительно убедившись в том, что она работает правильно. Ниже приводится вариант слова МАССИВ, где во время выполнения проверяется, не вышел ли индекс массива за установленные границы.

: МАССИВ ( #байтов) CREATE DUP , ALLOT
     DOES> ( i -- a) 2DUP @ U< NOT ABORT" Выход эа границу " + 2+ ;

Это происходит следующим образом:

DUP , ALLOT               Компиляция счетчика и выделение заданного
                          количества байтов,
DOES> 2DUP @              По заданному на стеке индексу во время
                          выполнения вычисляется: ( i pfa i # )
U< NOT                    Проверка того, что индекс не меньше
                          максимального значения, а именно:
                          запомненного счетчика. Так как U<
                          является операцией сравнения над значениями
                          без знака, то отрицательные аргументы
                          будут трактоваться как числа, выходящие за
                          границу, и приводить к аварийному сообщению.
ABORT" Выход за           Аварийное завершение при выходе числа за диапазон.
границу "
+ 2+                      В противном случае сложение индекса с pfa и
                          добавление числа 2 для пропуска ячейки, содержащей
                          счетчик.

Существует еще один способ использования определяющих слов, который помогает при создании программ. Допустим, вы вдруг решаете, что все ваши массивы, определенные с помощью слова МАССИВ, слишком велики, чтобы хранить их в памяти компьютера, и должны быть помещены на диск. Единственное, что вы должны в таком случае сделать, переопределить фрагмент периода выполнения в слове МАССИВ. Это новое определение вычислит номер блока, в котором содержится заданный байт, считает блок посредством BLOCK в некоторый буфер и оставит в вершине стека адрес требуемого байта относительно начала буфера. Массив, определенный подобным образом, может храниться в нескольких последовательных блоках (с использованием тех же средств, что и в упр. 10.7).

Ниже приведен пример определяющего слова, которое создает двумерный массив байтов заданного размера1:

: МАТРИЦА ( #строк #столбцов -- ) CREATE OVER , * ALLOT
    DOES> ( строка столбец -- a) DUP @ ROT * + + 2+ ;

1 Для любителей оптимизации Этот вариант будет выполняться еще быстрее

: МАТРИЦА ( #строк #столбцов -- )
    OVER CONSTANT HERE 2+ , * ALLOT
    DOES> ( строка столбец -- а) 2@ ROT * + + ;

Для того чтобы создать массив размером 4х4 байта, вы должны написать:

Выбрать же, к примеру, байт в строке 2 и в столбце 1 можно следующим образом:

2 1 ТАБЛИЦА С@

Вот так кратко можно описать выполнение слова МАТРИЦА. Поскольку аппаратные средства компьютера позволяют хранить только одномерные массивы, второе измерение необходимо моделировать. Мы представляем себе, что наш массив выглядит следующим образом:

а на самом деле в памяти машины он хранится в виде

Если вам требуется адрес байта, расположенного в строке 2 столбца 1, то вы можете умножить номер столбца (1) на число строк в каждом столбце (4), а затем прибавить номер строки (2). В результате получается, что вам нужен шестой байт машинного представления массива. Примерно такие вычисления делают в период выполнения элементы, составляющие слово МАТРИЦА. Но для того чтобы их производить, как вы можете заметить, любое составляющее слово должно «знать» число строк в каждом столбце конкретного массива. Для этих целей слово МАТРИЦА во время компиляции вносит число строк в столбце в начало массива. Для любознательных приведем стековые эффекты фрагмента периода выполнения слова МАТРИЦА:

ОПЕРАЦИЯ     СОДЕРЖИМОЕ СТЕКА
             строка столбец pfa
PUP @        строка столбец рfa #строк
ROT          строка pfa #строк столбец
*            строка pfа индекс-столбца
+ +          адрес
2+           скорректированный-адрес

К вычисленному адресу необходимо добавить двойку, потому что первая ячейка нашего массива содержит число строк.

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

\ Шаблоны с использованием определяющих слов
: STAR 42 EMIT ;
: .РЯД ( b -- ) \ вывод звездочки на каждый бит из байта
   CR 8 0 DO DUP 128 AND IF STAR ELSE SPACE THEN
     2* LOOP DROP ;
: ФОРМА ( b1 b2 b3 b4 b5 b6 b7 b8 -- ) \ определение формы из 8-строк
    CREATE 8 0 DO С, LOOP
    DOES> DUP 7 + DO I С@ .РЯД -1 +LOOP CR ;
\ формы:
HEX
18 18 3С 5А 99 24 24 24 ФОРМА ЧЕЛОВЕК
81 42 24 18 18 24 42 81 ФОРМА КОНЬ
АА АА FE FE 38 38 38 FE ФОРМА ЗАМОК
DECIMAL

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

2 BASE ! ok
00111001 .РЯД
  ***  * ok
DECIMAL ok

Наше определяющее слово ФОРМА берет из стека восемь аргументов и определяет шаблон, который при своем выполнении выводит решетку 8х8 элементов, соответствующую этим восьми аргументам:

ЧЕЛОВЕК
   **
   **
  ****
 * ** *
*  **  *
  *  *
  *  *
  *  *
ok

Итак, определяющие слова могут быть чрезвычайно полезным инструментом. Создавая новое определяющее слово, вы тем самым расширяете свой компилятор. Традиционные языки не обеспечивают такой гибкости, потому что они представляют собой жесткие готовые программы, которые предлагают вам в обязательном порядке конкретный набор операторов. Реальная помощь определяющих слов заключается в том, что их применение позволяет упростить вашу программу. При правильном их употреблении вы можете сократить время программирования, уменьшить размер программы и повысить ее читабельность. В следующем разделе мы приведем еще один способ расширения средств компилятора Форта.

ЧТО ТАКОЕ КОМПИЛИРУЮЩЕЕ СЛОВО?

Если обычные слова Форта появляются внутри определения через двоеточие, то они компилируются в словарь. Эти слова во время компиляции ведут себя пассивно. Компилирующие слова также появляются внутри определения через двоеточие, но в противоположность первым активно влияют на процесс компиляции. Такие слова, как IF, THEN, BEGIN, REPEAT и ." являются компилирующими.

Создатели Форта проявили последовательность и в данном случае. Коль уж существует тенденция не включать в язык все возможные определяющие слова, то компилирующих операторов в самом языке немного. Возможность управлять компиляцией путем образования собственных компилирующих слов предоставляет вам такую свободу, какую не может обеспечить ни один из известных языков программирования. Это средство позволяет локализовать информацию внутри соответствующих определений (не рассредоточивая ее по всей программе), что упрощает написание программы, облегчает ее чтение, восприятие и сопровождение. Вероятно, ваша программа будет выглядеть более привлекательной, если в язык включить оператор выбора вариантов, который сравнивает текстовые фрагменты. А, может быть, вы хотели бы добавить к вашим операторам управления средства жесткого аварийного контроля? Не исключено, что вам захочется иметь оператор цикла DO, использующий 32-разрядный индекс. Все это в ваших силах.

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

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

Компилятор двоеточия функционирует аналогично текстовому интерпретатору. Он выбирает из входного потока слова и пытается отыскать их в словаре. Однако, вместо того чтобы (как ИНТЕРПРЕТАТОР) исполнять эти слова немедленно, он, как правило, компилирует их адреса в словарь. Но компилятор распознает компилирующие слова и только их исполняет сразу, подобно текстовому интерпретатору.

Каким образом компилятор двоеточия отличает компилирующие слова? По биту немедленного исполнения данного определения (гл. 9 «Структура словарной статьи»): если бит сброшен, то компилируется адрес слова, если установлен, что слово немедленно исполняется. Такие слова называются словами немедленного исполнения (immediate).

Слово IMMEDIATE делает слово немедленно исполняемым. Его формат:

: имя определение ; IMMEDIATE

т. е. это слово выполняется сразу после компиляции определения. Допустим, у нас есть определение:

: ТЕСТ ; IMMEDIATE

Это слово немедленного исполнения которое ничего не выполняет. Если мы обратимся к нему из определения другого слова, например:

: 2CRS CR ТЕСТ CR ;

то будет скомпилирован следующий фрагмент словаря:

Как видите, определение скомпилировано без слова ТЕСТ. На самом деле оно выполнено во время компиляции слова 2CRS. Поскольку слово ТЕСТ ничего не выполняет, оно бесполезно. Приведем другой пример. Предположим, что у нас есть слово с именем ТЮЛЬПАН и определение:

: ТЕСТ COMPILE ТЮЛЬПАН ; IMMEDIATE

Теперь переопределим слово 2CRS точно так же, как и ранее:

: 2CRS CR ТЕСТ CR ;

и получим следующий результат:

На сей раз слово ТЕСТ во время компиляции определения 2CRS скомпилировало адрес слова ТЮЛЬПАН. На самом деле мы нашим определением как бы сказали:

: 2CRS CR ТЮЛЬПАН CR ;

и что компилировать, а что нет, определяет ТЕСТ, потому что это слово немедленного исполнения.

Обратите внимание на слово COMPILE (КОМПИЛЯЦИЯ). Мы ввели его как бы между прочим, поскольку его функции проще понять в контексте.

: ТЕСТ COMPILE ТЮЛЬПАН ; IMMEDIATE

COMPILE вычисляет адрес следующего слова определения и запоминает его в виде числа:

: 2CRS CR ТЕСТ CR ;

Когда исполняется слово немедленного выполнения, в котором появилось COMPILE, оно компилирует запомненный адрес в создаваемое определение. Этот процесс можно трактовать как отсроченную компиляцию.

Приведем теперь очень полезный пример. Допустим, вы написали отладочное средство с именем ОТЛАДКА, которое хотите использовать в любом месте своей программы. Так как желательно иметь возможность при необходимости включать и отключать это средство, воспользуемся словом ТЕСТ, определенным следующим образом:

: ТЕСТ ПРОВЕРКА? IF ОТЛАДКА THEN ;

Слово ТЕСТ при выполнении проверяет флаг и определяет, обращаться к слову ОТЛАДКА или нет. Казалось бы, все хорошо, однако много времени уходит на остановку для проверки флага на каждом шаге цикла. Если вы не работаете в отладочном режиме, вам вряд ли нужно всякий раз проверять режим. Проблема решается путем переопределения слова ТЕСТ:

: ТЕСТ ПРОВЕРКА? IF COMPILE ОТЛАДКА THEN ; IMMEDIATE

В такой ситуации для включения отладочного средства придется перекомпилировать программу, но это займет немного времени. В отладочном режиме слово ТЕСТ скомпилирует ОТЛАДКА в соответствующие места программы. В противном случае оно вообще ничего компилировать не будет. Проверка IF будет осуществляться в период компиляции.

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

: dot" R> COUNT 2DUP + >R TYPE ;
: ." COMPILE dot" ASCII " STRING ; IMMEDIATE

Определение dot" и ."

: ВСТРЕЧА ." Эй, ты " ;

Исполнение .", которое является словом немедленного выполнения (и поэтому исполняется во время компиляции слова ВСТРЕЧА). Оно в свою очередь осуществляет компиляцию:

 

ВСТРЕЧА

Выполнение слова ВСТРЕЧА, которое вызывает слово dot", а оно уже выводит строку на экран1.

Следующие два слова применяются при создании новых компилирующих слов:
IMMEDIATE
( - )
Последнее определенное слово становится немедленно
исполняемым, то есть во время компиляции оно будет
не компилироваться, а выполняться.
C0MPILE xxx
( - )
Применяется при определении компилируйте-то слова.
Когда это компилируйте* слово будет в свою очередь
использоваться в исходном определении, адрес поля
кода ххх будет скомпилирован в словарную статью,
так что когда вновь созданное определение
выполняется, выполняется и ххх.

НЕСКОЛЬКО ДОПОЛНИТЕЛЬНЫХ СЛОВ УПРАВЛЕНИЯ КОМПИЛЯЦИИ

Как вы помните, число, которое появляется в определении через двоеточие, называется литералом (самоопределенным). Например, число 4 в следующем определении является литералом:

: ПЛЮС-ЧЕТЫРЕ 4 + ;

1 Для очень любознательных. Слово dot" начинает свое выполнение с того, что получает адрес стека возвратов (в нашем случае - адрес строки со счетчиком). COUNT преобразует этот адрес отдельно в адрес и значение счетчика для TYPE. Но мы должны привести в порядок указатель стека возвратов, чтобы он при возврате показывал бы за строку. Адрес мы вычисляем путем сложения копий адреса и счетчика, полученных с помощью 2DUP, а затем подкорректированный адрес засылаем в стек возвратов. (Если у вас есть листинги исходных текстов, то, прежде чем экспериментировать, проверьте определение слова .".)

Использование литерала в определении через двоеточие требует двух ячеек. В первой содержится адрес некоторой программы, которая, отработав, поместит содержимое второй ячейки (само число) в вершину стека1. Имя этой процедуры может меняться. Назовем ее кодом периода выполнения для литерала, или просто (LITERAL). Компилятор, встречая некоторое число, сначала компилирует код периода выполнения для литерала, а затем само число.

Слово LITERAL (ЛИТЕРАЛ) вы наиболее часто будете употреблять при компиляции литерала. Это слово компилирует как код периода выполнения, так и само значение, например2:

4
: ПЛЮС-ЧЕТЫРЕ ( n -- n+4) LITERAL + ;

Здесь слово LITERAL занесет число 4, помещенное в вершину стека перед компиляцией, в элемент словаря как литерал. В результате мы получим элемент словаря, идентичный показанному на приведенном выше рисунке.

Можно привести пример более интересного применения слова LITERAL. Вспомните, что в гл. 8 был образован массив с именем ПРЕДЕЛЫ, состоящий из пяти ячеек, в которых хранятся значения предельной температуры для соответствующих горелок. Чтобы упростить доступ к массиву, мы создали слово с именем ПРЕДЕЛ. Эти два определения выглядели следующим образом:

CREATE ПРЕДЕЛЫ 10 ALLOT \ массив из 5 ячеек, содерж.предел.знач.
: ПРЕДЕЛ ( #горелки -- адрес-пред-знач) 2* ПРЕДЕЛЫ + ;

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

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

: ИЗВНЕ ( литерал -- мусор) 0 SWAP ; IMMEDIATE
4
: ПЛЮС-ЧЕТЫРЕ ИЗВНЕ LITERAL + ; DROP

Допустим теперь, что доступ к массиву осуществляется только через слово ПРЕДЕЛ. Мы сможем уничтожить заголовок нашего массива (восемь байтов), сделав такую замену:

HERE 10 ALLOT \ массив из 5 предельных значений
: ПРЕДЕЛ ( #горелки -- адр-пред-знач) 2* LITERAL + ;

В первой строке мы помещаем в вершину стека адрес начала массива (HERE), а во второй - заносим этот адрес как литерал в определение слова ПРЕДЕЛ. Таким образом, мы ликвидировали заголовок слова ПРЕДЕЛЫ и сэкономили память словаря.

Существуют еще два слова управления компиляцией, которые вы должны знать, - [ и ]. Они могут использоваться внутри определения через двоеточие соответственно для прекращения компиляции и ее возобновления. Любые слова, появляющиеся между ними, будут исполнены немедленно, т. е. во время компиляции.

Представьте себе, например, что в некотором определении вы должны вывести строку 3 из блока 180. Чтобы получить адрес третьей строки, вы могли бы воспользоваться выражением

180 BLOCK 3 64 * +

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

180 BLOCK 192 +

однако трудно сразу сообразить, что означает здесь 192. Лучшим решением является такое выражение:

180 BLOCK [ 3 64 * ] LITERAL +

Арифметические операции выполняются только один раз, во время компиляции, а результат заносится в словарь как литерал.

Выше упоминалось о том, что слово ] повторно запускает процесс компиляции. На самом деле оно инициируется словом : и во многих системах является компилятором.

Приведем простой пример на применение литерала. Это определение может быть загружено с блока на диске.

: НАПЕЧАТАЙ-ЭТО [ BLK @ ] LITERAL LIST ;

При исполнении слова ПЕЧАТЬ выводится тот блок, в котором оно определено. (Во время компиляции в BLK содержится номер последнего загруженного блока, LITERAL заносит этот номер в определение как литерал, так что во время выполнения последней будет служить аргументом для LIST.)

Здесь уместно дать определение LITERAL:

: LITERAL ( n -- ) COMPILE (LITERAL) , ; IMMEDIATE

Сначала оно компилирует адрес кода периода выполнения, затем - само выражение (используя запятую).

Следующее слово для управления компиляцией - [COMPILE]. Допустим, вы хотите переименовать слово IF, но делать это так, как показано ниже:

: если IF ; IMMEDIATE

не имеете права, поскольку слово IF само является немедленно исполняемым. Его код осуществляет переход, если условие не выполняется, на соответствующий оператор THEN. Вы должны каким-то образом обойти это препятствие (бит немедленного исполнения) и заставить IF компилироваться, как если бы оно было

обычным словом. В такой ситуации вам поможет слово [COMPILE] . Если определить

: если [COMPILE] IF ; IMMEDIATE
: иначе [COMPILE] ELSE ; IMMEDIATE
: то [COMPILE] THEN ; IMMEDIATE

то у вас появится возможность по-новому записывать условия:

: ветвление ( ?) если ." Истина " иначе ." Ложь " то ." флаг" ;

Вас может удивить, почему нельзя использовать слово COMPILE непосредственно:

: если COMPILE IF ; IMMEDIATE

Вспомните, что COMPILE осуществляет так называемую отсроченную компиляцию, т. е. компилирует IF не в то слово, которое мы имеем в виду, а в то, которое инициирует это слово (например, «ветвление»). С другой стороны, слово [COMPILE] компилирует слова немедленного выполнения в традиционном смысле, иначе они бы исполнялись. Между прочим квадратные скобки в слове [COMPILE] означают, что данное слово «выполняется во время компиляции» - таково еще одно соглашение об именовании в Форте. Вы можете смоделировать слово [COMPILE] следующим образом:

: если [ ' IF , ] ; IMMEDIATE

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

Теперь наступает для вас час испытаний. Если вы его переживете, можете считать себя специалистом по компилирующим словам. Предположим, у нас имеются слова НЕГАТИВНОЕ и -НЕГАТИВНОЕ, которые изменяют обычное изображение на экране негативным и, наоборот, негативное обычным соответственно для любого правильного текста. Наша цель - создать слово Н." для автоматического изменения режима экрана на негативный, вывода строки и возвращения к нормальному режиму.

Существуют два решения этой задачи и оба они представляют интерес. Для начала условимся, что изменение режима должно осуществляться тогда, когда строка выводится, а не компилируется. Первое решение заключается в создании слова с именем «нточка"», аналогично слову dot", и слово Н.", которое имитирует .", но вместо слова dot" компилирует слово «нточка"»:

: dot" R> COUNT 2DUP + >R TYPE ;
: ." COMPILE dot" ASCII " WORD C@ 1+ ALLOT ; IMMEDIATE
: нточка" НЕГАТИВНОЕ R> COUNT 2DUP + >R TYPE -НЕГАТИВНОЕ ;
: H." COMPILE нточка" ASCII " WORD С@ l+ ALLOT ; IMMEDIATE

Но это решение далеко не изящно и зависит от реализации. Другой вариант - вызов слова .":

: H." COMPILE НЕГАТИВНОЕ [СОМРILE] ." COMPILE -НЕГАТИВНОЕ ; IMMEDIATE

Перед вами определение компилирующего слова. Посмотрим, что оно компилирует. Если использовать его в определении

: ТЕСТ H." Ура!" ;

то компилируется следующий фрагмент:
ТЕСТ Поле связи Поле кода НЕГАТИВНОЕ dot" 4 У Р А ! -НЕГАТИВНОЕ EXIT

Наше компилирующее слово выполняет три функции:

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

Если вам трудно сразу «переварить» все вышеизложенное, то будем надеяться, что по мере освоения этих слов в процессе практической работы вы испытаете радость познания. Возможно, другие языки и проще в изучении, но скажите, какой иной язык, кроме Форта, позволит вам расширить компилятор?

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

Ниже приведены все дополнительные слова управления компиляцией, введенные в данном разделе.
LITERAL
период-компиляции:
( n -- )
териод-вполнемия:
( -- n)
Используется только внутри определения
через двоеточие. Во время компиляции
значение из стека компилируется как литерал
в определение. Во время выполнения это
значение будет помещено на стек.
[
( -- )
Переключение с режима компиляции на
режим интерпретации.
]
( -- )
Переключение на режим компиляции.
[COMPILE] xxxx
( - )
При использовании внутри определения через
двоеточие вызывает компиляции слова немедленного
исполнения ххх , как если бы оно не было словом
немедленного исполнения, ххх будет выполняться
при выполнении данного определения.

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

: ?ОБЪЕМ ( длина ширина высота -- )<return>
] 6 > ROT 22 > ROT 19 > AND AND<return>
] IF ." Подходит " THEN ;<rgturn> ok

ФЛАГ СОСТОЯНИЯ

Введем последний термин, имеющий отношение к процессу компиляции, - состояние. В большинстве Форт-систем есть переменная с именем STATE (СОСТОЯНИЕ), в которой содержится «истина», если вы работаете в режиме компиляции, и «ложь», если вы работаете в режиме интерпретации. Покажем способ вывода значения переменной STATE:

: .СОСТОЯНИЕ STATE ? ; IMMEDIATE

Введите:

.СОСТОЯНИЕ 0 ok

В момент вызова .СОСТОЯНИЕ Форт-система находилась в режиме интерпретации (то, что .СОСТОЯНИЕ является словом немедленного исполнения, не играет роли. Интерпретатор не проверяет бит немедленного исполнения).

Теперь вызовите слово .СОСТОЯНИЕ из нового определения:

: ТЕСТ .СОСТОЯНИЕ ; -1 ok

На сей раз значение STATE равно «истине», поскольку .СОСТОЯНИЕ было инициировано компилятором.

В каких случаях возникает необходимость знать состояние? Всегда, когда вы хотите создать слово, которое должно «делать вид», что его поведение одинаково как внутри определения, так и вне его, а на самом деле оно проявляет себя по-разному. В качестве примера можно привести слово ASCII. При обычном использовании это слово появляется внутри определения через двоеточие:

: ТЕСТ ( -- ascii-a) ASCII A ;

При исполнении слова ТЕСТ в вершину стека вносится число 65. ASCII осуществляет преобразование во время компиляции, Оно должно быть компилирующим словом: выбирать из входного потока символ, компилировать его как литерал, чтобы последний мог быть занесен в стек во время выполнения слова ТЕСТ. Вы можете создать компилирующий вариант слова ASCII следующим образом:

: ASCII ( -- с )
\ Компиляция: с ( -- )
   BL WORD 1+ С@ [COMPILE] LITERAL ; IMMEDIATE

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

В приведенном выше определении слово WORD выбирает из входного потока текст, ограниченный пробелом, и вносит в вершину стека адрес участка памяти, где будет храниться строка со счетчиком. Мы считаем, что такой текст содержит только один символ, поэтому пропускаем счетчик байтов посредством 1+ и выбираем значение с помощью С@ . Затем инициируем слово LITERAL, компилирующее код периода выполнения для литерала, за которым следует само значение. Это все, что нам требуется.

Попытаемся заставить ASCII выполняться вне определения. Например, выражение

ASCII A

внесет в вершину стека значение 65 (что может оказаться полезным при составлении таблиц и т. д.). Далее «попросим» ASCII выполнить что-нибудь такое, что оно еще не делало. Создадим следующий вариант определения:

: ASCII ( -- с) BL WORD 1+ C@ ;

Для того чтобы одно и то же слово ASCII могло выполняться в обоих вариантах, оно должно реагировать на состояние

: ASCII ( -- c)
\ КОМПИЛЯЦИЯ: c ( -- )
\ Интерпретация: с ( -- с)
   BL WORD 1+ С@
   STATE @ IF [COMPILE] LITERAL THEN ; IMMEDIATE

Такое слово называется зависимым от состояния. На первый взгляд зависимость от состояния кажется свойством разумным и даже желательным, особенно для слова ASCII, которое используется только при компиляции. Однако когда программист пытается повторно применять зависимые слова в другом определении и хочет, чтобы они выполнялись обычным образом, возникают трудности. Приведем пример использования компилирующего слова, зависимого от состояния, в определении другого компилирующего слова. По мере того как растет глубина вложенности слова, зависимого от состояния, программисту приходится затрачивать все больше усилий на то, чтобы держать под контролем, что и где должно выполняться.

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

Слово ' постигла участь описанных выше слов и теперь это простое слово. Его функции внутри определения аналогичны функциям, выполняемым во время интерпретации: оно выявляет определение, имя которого находится во входном потоке во время исполнения. Вариант апострофа, функционирующего как компилирующее слово, называется ['].

Некоторые разработчики Форта считают, что зависимость от состояния не имеет права на существование. Применительно к ASCII возможны два решения: 1) разрешить его использование только внутри определения и, если возникает необходимость, создать слово с другими функциями, скажем ascii (на нижнем регистре), для режима интерпретации; 2) сделать его пригодным только для интерпретации:

: ТЕСТ [ ASCII A ] LITERAL ;

Можно договориться и ввести другое слово:

: [ASCII] ASCII [COMPILE] LITERAL ; IMMEDIATE

которое рекомендуется применять следующим образом:

: ТЕСТ [ASCII] A ;

ВВЕДЕНИЕ В БЛОК-СХЕМЫ ФОРТА

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

Рассматриваемые здесь диаграммы основаны на так называемых D-схемах. Последовательные операторы записывают один под другим, не соединяя их линиями и не заключая в рамки:

      оператор
 следующий оператор
 следующий оператор

Линии же служат для того, чтобы показать, что действия выполняются не в порядке очередности, а либо в зависимости от некоторого условия (условные операторы), либо неоднократно (операторы цикла). Условный оператор Форта

условие IF истина ELSE ложь THEN оператор

изображается следующей диаграммой:

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

Структура BEGIN ... UNTIL изображается так:

Циклическая структура располагается справа от основной линии вычислений и соединяется с ней горизонтальной линией в верхней своей части. Если нужно показать вложенные циклы, то они должны быть расположены правее. Черная точка является признаком конца цикла. Она означает, что управление

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

Цикл вида BEGIN ... WHILE ... REPEAT имеет аналогичную диаграмму:

Итак, мы кратко осветили вопрос о применении блок-схем при программировании на Форте и теперь можем наглядно представить вам структуру двух очень важных слов.

ЗАКЛЮЧЕНИЕ

Мы завершаем рассмотрение текстового интерпретатора и компилятора и в конце этого раздела, возможно, увидим их несколько в ином свете.

В процессе изложения мы неоднократно упоминали слово INTERPRET, имея в виду текстовый интерпретатор. Его строгое описание выглядит так:
INTERPRET
( -- )
Интерпретация текста из входного потока по
указателю >IN до исчерпания входного потока.

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

Создадим следующее определение:

: ТЕСТ 0 BEGIN DUP . 1+ QUERY INTERPRET 0 UNTIL ;

Введите слово ТЕСТ. Оно выведет нуль, остановится и будет ждать. Если вы нажмете клавишу RETURN, цикл продолжит свое выполнение, и на экране высветится единица. До нажатия клавиши RETURN вы можете ввести любую команду. Прежде чем продолжить выполнение цикла, INTERPRET ее выполнит. Если вы хотите завершить цикл, введите QUIT или сделайте ошибку, вызывающую ABORT. Любое из этих действий очистит стек возвратов и тем самым приведет к выходу как из INTERPRET, так и из ТЕСТ. (Это средство можно использовать гораздо шире [1].)

В разных диалектах Форта слово INTERPRET определено по-разному, но суть этого слова можно передать, изобразив алгоритм его выполнения с помощью D-схемы.

Алгоритм выполнения слова INTERPRET можно описать следующим образом. Начинаем цикл. В теле цикла выбираем очередное слово из входного потока и осуществляем поиск его определения в словаре. Если определение найдено, исполняем слово. Затем проверяем, не исчерпан ли стек.

(В том случае, когда стек исчерпан, завершаем цикл посредством EXIT и выдаем аварийное сообщение.) Если слово в словаре не найдено, пытаемся преобразовать введенный фрагмент в число и внести его значение в вершину стека. Далее повторяем цикл входного потока.

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

(Если вам доступен исходный текст системы, мы рекомендуем вам изучить настоящие определения слов INTERPRET и ].)

Сравните обе диаграммы, и вы увидите, что интерпретатор может вызывать ] для того, чтобы определить, будет данное слово выполняться или компилироваться. Такая простота в организации системы позволит вам легко добавлять новые компилирующие слова.

Итак, существуют два способа расширения Форт-компилятора:

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

Ниже приводится перечень слов Форта, рассмотренных в настоящей главе.

DOES>
период-выполнения:
( - a)
Используется при создании определяющих
слов. Отмечается конец участка периода
компиляции и начала участка периода выполнения.
Операции периода выполнения определены на
высокоуровневом форте. Во время выполнения на
стеке будет находиться pfa определенного слова.
IMMEDIATE
( - )
Последнее определенное слово становится немедленно
исполняемым, то есть во время компиляции оно будет
не компилироваться, а выполняться.
COMPILE xxx
( - )
Применяется при определении компилируйте-то слова.
Когда это компилируйте* слово будет в свою очередь
использоваться в исходном определении, адрес поля
кода ххх будет скомпилирован в словарную статью,
так что когда вновь созданное определение
выполняется, выполняется и ххх.
LITERAL
период-компиляции:
( n -- )
териод-вполнемия:
( -- n)
Используется только внутри определения
через двоеточие. Во время компиляции
значение из стека компилируется как литерал
в определение. Во время выполнения это
значение будет помещено на стек.
[
( -- )
Переключение с режима компиляции на
режим интерпретации.
]
( -- )
Переключение на режим компиляции.
[COMPILE] xxxx
( - )
При использовании внутри определения через
двоеточие вызывает компиляции слова немедленного
исполнения ххх , как если бы оно не было словом
немедленного исполнения, ххх будет выполняться
при выполнении данного определения.
INTERPRET
( -- )
Интерпретация текста из входного потока по
указателю >IN до исчерпания входного потока.

ОСНОВНЫЕ ТЕРМИНЫ

Бит использования во время компиляции. Бит элемента словаря, который определяет, будет ли слово во время компиляции выполняться, а не компилироваться.

D-схема. Графическое представление логической структуры некоторой программы, а в случае Форта - некоторого определения.

Компилирующее слово. Слово, используемое внутри определения через двоеточие для выполнения действий во время компиляции.

Немедленно исполняемое слово. Словарная статья, где установлен бит непосредственного использования, что вызывает во время компиляции не компилирование данного слова, а его выполнение.

Определяющее слово. Слово, которое при своем выполнении создает новый элемент словаря. Определяющее слово задает функции периода-компиляции и периода-выполнения для каждого члена семейства слов, определяемых этим словом.

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

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

УПРАЖНЕНИЯ

11.1. Введите определяющее слово с именем ЗАГРУЗКА, которое будет определять слова, загружающие при своем исполнении некоторый блок. Например, выражение

690 ЗАГРУЗКА КОРРЕСПОНДЕНЦИЯ

должно определить слово КОРРЕСПОНДЕНЦИЯ. В результате выполнения последнего должен быть загружен блок 600.

11.2. Определите слово с именем СИСТЕМА., создающее слова для вывода чисел в конкретных системах счисления. Например, выражение

16 СИСТЕМА. Н.

должно определить слово Н., которое выводило бы значение из вершины стека в шестнадцатиричной системе, но саму систему счисления (BASE) не меняло бы:

DECIMAL
17 DUP Н. .<return> 11 17 ok

11.3. Определите слово с именем МНОГО, чтобы оно получало адрес некоторого слова, например CR или STAR, и создавало множественное число этого слова, в нашем случае CRS или STARS. Вы будете задавать адрес слова в единственном числе посредством апострофа. Например, фраза

' CR МНОГО CRS

определит слово CRS так же, как и выражение

: CRS ?DUP IF 0 DO CR LOOP THEN ;

11.4. На французский язык слова DO и LOOP переводятся как TOURNE и RETOURNE соответственно. Определите TOURNE и RETOURNE как французские имена слов управления циклом, используя слова DO и LOOP, после чего проверьте их, написав цикл на французском языке.

11.5. Напишите слово с именем РАЗ, которое вызовет исполнение оставшихся во входном потоке символов до возврата каретки. Число исполнений задается числом, находящимся в вершине стека, например:

7 РАЗ 42 EMIT SPACE<return> * * * * * * * ok

11.6. В настоящей главе демонстрировался пример использования определяющего слова с именем ФОРМА. Чтобы задать конкретное изображение, нужно было ввести восемь чисел, каждое из которых представляло собой битовый шаблон отдельной строки. Придумайте более изящный способ задания описания рисунка, содержащего 8х8 элементов изображения. Можно, например, вместо шестнадцатиричных цифр задавать некоторую схему.

Теперь реализуйте то, что вы придумали (с привлечением существующего определения слова .РЯД). Проверьте свой способ формирования рисунка.

ЛИТЕРАТУРА

1. "Add a Break Point Tool," Forth Dimensions, Vol. V, No. 1, p. 19.