НЕСКОЛЬКО ДОПОЛНИТЕЛЬНЫХ СЛОВ УПРАВЛЕНИЯ КОМПИЛЯЦИИ
Как вы помните, число, которое появляется в определении через двоеточие, называется литералом (самоопределенным). Например, число 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> ] 6 > ROT 22 > ROT 19 > AND AND<return> ] IF ." Подходит " THEN ;<rgturn> ok