Начальный курс программирования на языке Форт

         

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


Как вы помните, число, которое появляется в определении через двоеточие, называется литералом (самоопределенным). Например, число 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
Наше компилирующее слово выполняет три функции:


  • компилирует адрес слова НЕГАТИВНОЕ в ТЕСТ (так что НЕГАТИВНОЕ будет выполняться в период исполнения слова ТЕСТ);


  • инициирует слово .", которое в свою очередь компилирует dot", и компилирует фрагмент, набранный в строке;


  • компилирует адрес слова -НЕГАТИВНОЕ.


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

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

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

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

    LITERAL

    период-компиляции: ( n -- ) териод-вполнемия: ( -- n)

    Используется только внутри определения через двоеточие. Во время компиляции значение из стека компилируется как литерал в определение. Во время выполнения это значение будет помещено на стек.

    [

    ( -- )

    Переключение с режима компиляции на режим интерпретации.

    ]

    ( -- )

    Переключение на режим компиляции.

    [COMPILE] xxxx

    ( - )

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

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

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


    Содержание раздела