Интерпретация, компиляция и исполнение
В гл. 14 мы рассматривали структуру Форта и организацию памяти. Теперь мы должны посмотреть, как он работает, т. е, как интерпретирует, компилирует и исполняет слова и числа. Стандарты Форта благоразумно не делают попыток определить, как должен быть устроен язык изнутри. Задача стандартов - обеспечение функциональной совместимости программ, а не подавление оригинальных путей адаптации Форта к новой технике. Но это означает, что мы можем только описать, как работает типовая версия Форта. Несмотря на это, мы полагаем, что, предлагая типовой способ функционирования Форта, мы дадим лучшее представление, как работает любой Форт.
Мы, пожалуй, напомним вам, что в форте термины "интерпретация", "компиляция" и (в меньшей степени) "исполнение" имеют иное значение, чем в теории вычислительной технике вообще. В Форте интерпретация состоит из приема слов и чисел из входного потока через дисковые блоки или с клавиатуры, последующей компиляции введенного в словарь или непосредственного исполнения. С другой стороны, в Бейсике и других интерпретивных языках интерпретация означает загрузку исходного текста в память, трансляцию его в машинные коды и выполнение строка за строкой каждый раз. когда программа запускается. Другими словами, интерпретация в Форте происходит до компиляции и исполнения, в то время как в других языках за интерпретацией всегда следует исполнение и не генерируется постоянная исполняемая программа. В Форте компиляция состоит из формирования новых элементов словаря путем построения списка CFA слов, описанных ранее. С другой стороны, компиляция в Фортране и других языках представляет собой трансляцию исходного текста программы в программу а машинных кодах путем просмотра библиотеки процедур, хранящихся на диске, и укладку процедур в виде программ в машинных кодах в исполнительный файл. Таким образом, компиляция в других языках представляет собой создание программ в машинных кодах, в то время как в Форте это означает объединение существовавших ранее программ с помощью списка CFA в поле параметров слов.
Эдмундс (1985) дает более полное описание обычного использования слов "интерпретатор" и "компилятор". Исполнение во всех языках состоит из работы программ в машинных кодах, но а Форте, в противоположность другим языкам, это делается с помощью нескольких коротких системных программ и адресов, скомпилированных в Описание каждого слова, чтобы сформировать цепочку заранее написанных машинных кодов, которые и выполняют работу программы. Благодаря формированию этой цепочки или нити, на которую "нанизываются" машинные коды Форта, он за служил название "цепной интерпретивный язык" (или TIL - ThreadedInterpretive Language).
Интерпретация
Привлекательность Форта происходит от простоты ввода последовательности чисел и слов с клавиатуры, последовательности действий Форта после того, как вы нажали клавишу . Вы делаете это с самого начала каждый раз, когда сделали короткое вычисление или описание с клавиатуры нового слова. Исходный текст на дисковых блоках воспринимается или интерпретируется Фортом точно так же, как ввод с клавиатуры; фактически все вводы для Форта обрабатываются одним и тем же способом. Все вводы в Форте приходят через входной поток, последовательность слов и чисел, разделенных пробелами, вне зависимости от происхождения ввода.
Но как узнает Форт, где найти данные? Как Форт узнает слова и отличит их от чисел? Как узнает Форт, что делать с введенной информацией? В табл. 15.1 дана сводка того, что делает Форт после инициализации. Подробное рассмотрение операций в табл. 15.1 ответит на некоторые наши вопросы. Функции, заключенные в скобки в табл. 15.1, могут выполняться, а могут и не выполняться в раз личных версиях Форта.
Вы рассмотрели ABORT и QUIT в гл. 7, но в другом контексте. ABORT и QUIT возвращают управление клавиатуре, так как они представляют собой, как показано в табл. 15.1, бесконечные циклы, присущие интерпретатору Форта. Задачей ABORT является приведение системы в исходное состояние и передача управления оператору QUIT, который исполняется до тех пор, пока не встретится ошибка.
QUIT очищает стек возвратов, устанавливает режим исполнения и ожидает нового ввода с клавиатуры. Как только что-то поступило на вход, производится интерпретация до тех пор, пока не будет обнаружено ошибки (табл. 15.2). В случае ошибки QUIT прерывает цикл с помощью ABORT, чтобы вернуть систему в исходное состояние, прежде чем продолжить работу дальше. (Когда вы применяли ABORT и QUIT в гл. 7, вы в действительности использовали их циклы, как здесь и показано.)
Таблица 15.1. Обзор работы Форт
Инициализация системы ABORT BEGIN Очистка стека параметров (FORTH делается текущим контекстным словарем) (Установка десятичной системы счисления) QUIT Установка режима исполнения BEGIN Очистка стека возвратов Ожидание ввода с терминала BEGIN Если входной поток не иссяк и не обнаружено ошибки при интерпретации входной информации (продолжение в таб.15.2). то - REPEAT UNTIL ошибка UNTIL всегда
Форт проводит большую часть времени внутри цикла QUIT. Здесь входной поток воспринимается, делится на отдельные слова и числа (называемые лексемами), которые в зависимости от обстоятельств, рассмотренных в табл. 15.2 компилируются, исполняются или заносятся в стек. Когда входной поток иссяк, управление передается снова клавиатуре, готовой для нового ввода.
Как работает интерпретация в форте, показано в табл. 15.2. Входной поток воспринимается с терминала, если переменная пользователя BLK равна 0, или из блока, номер которого записан в BLK. Входной поток подвергается разбору или разбивается на группы символов (или лексемы). Так как разбор'производится командой 32 WORD, лексемы должны отделяться друг от друга по крайней мере одним пробелом (ASCII 32), что объясняет, почему пробел не может никогда быть частью слова Форта. Лексема является, таким образом, просто группой ASCII символов, которая может быть, а может и не быть действительным словом Форта или числом. Для каждой лексемы
Таблица 15.2. Интерпретация входного потока
Если входной поток не иссяк Если BLK @ = 0, входной поток поступает с терминала В противном случае входной поток поступает из блока, номер которого лежит в BLK Для каждой лексемы из входного потока...
Если лексема оказалась словом из словаря... Если режим компиляции (STATE =1) Если слово немедленного исполнения, то оно исполняется В противном случае компилируется CFA слова в словарь В противном случае (STATE=0) слово исполняется Если лексема не слово Если режим компиляции (STATE=1)... Если число одинарной длины, компилируем с помощью LITERAL В противном случае (число двойной длины) компилируем с помощью DLITERAL В противном случае (STATE=0) заносит число в стек В противном случае имеет место ошибка и выполняется ABORT Повторять, пока не иссякнет входной поток или не возникнет ошибка
проверяется, не является ли она словом Форта, путем кодирования (если необходимо) и поиска в словаре, как описано в гл. 14, чтобы определить, соответствует ли она имени какого-либо уже запомненного слова.
Если лексема найдена в словаре, она является словом форта и исполняется или компилируется в зависимости от значения переменной пользователя STATE. Если STATE содержит не нулевое значение, Форт находится в режиме компиляциии, если только лексема не описана как слово немедленного исполнения, ее CFA компилируется в словарь оператором , (запятая) по адресу HERE. Ес ли значение переменной STATE равно 0, Форт находится в режиме исполнения и слово выполняется путем передачи его CFA оператору EXECUTE.
Если лексема не найдена в словаре, интерпретатор пытается преобразовать ее в двоичное число для записи в память с помощью слова NUMBER. Если каждый символ лексемы является цифрой с величиной меньше текущего значения BASE, преобразование оказывается успешным. Например, наибольшая цифра в десятичной системе (основание 10) равна 9, но наибольшая цифра в шестнадцатеричной системе (основание 16) равна F шестнадцатеричному или 15 - десятичному; тогда символ G приведет к ошибке преобразования, когда система шестнадцатеричная. Десятичная точка указывает Форту, что лексема должна рассматриваться как число двойной длины (См. описание слова NUMBER в гл. 9). Если лексема является действительно числом, величина STATE определит, следует ли это число компилировать или исполнять, передав его в стек.
Мы позднее расскажем вам подробнее о том, как компилировать числа.
Если ошибки не случилось, Форт выдает на экран "ОК", чтобы сообщить, что все, что нужно, сделано и что он готов для очередного ввода с клавиатуры. Но если лексема - не слово Форта и не число или если при интерпретации или исполнении произошла ошибка, система, прежде чем продолжить процесс с самого начала, возвращается в исходное состояние оператором ABORT.
Является ли источником входного потока блок или клавиатура, смещение относительно его начала в любой момент во время интерпретации задается содержимым пользовательской переменной >IN ("to-in"). Переменная >IN содержит число байтов от начала входного потока, где происходит синтаксический разбор. Эта переменная всегда указывает на байт после пробела, следующего за последней лексемой. Переменная >IN постоянно корректируется во время интерпретации. Переменные BLK и >1N описаны в гл. 10, но мы покажем так же некоторые их приложения в упражнениях.
Как уже обсуждалось в гл. 9, когда входной поток воспринят с клавиатуры (содержимое BLK равно 0), символы уложены в зарезервированную область памяти, называемую текстовым входным буфером, одновременно они отображаются на экране терминала. (Стандарты требуют, чтобы текстовый входной буфер вмещал по меньшей мере 80 символов.) В Форт-83 (и в большинстве других версий) адрес начала этого буфера хранится в переменной TIB.
В гл. 9 вы видели, как использовать QUERY для ввода строк перед их разбором. Главная функция QUERY - работа в интерпретаторе. В Форт-79 и большинстве других версий Форта ввод происходит через QUERY. Описание в Форт-79 будет выглядеть : QUERY TIB 80 EXPECT 0 >IN ! ;
Поскольку описание не устанавливает >IN на начало входного буфера или BLK равным 0, для пользы дела мы должны описать что-то вроде : GETINPUT 0 BLK ! 0 >IN ! QUERY ;
Теперь, если вы напечатаете GETINPUT
курсор будет установлен после GETINPUT, ожидая вашего ввода. Теперь, если вы напечатаете, скажем, 5 12 + .
на экране появится 17. Другими словами, GETINPUT использует QUERY, которое, в свою очередь, использует EXPECT, чтобы передать в процессе исполнения введенную информацию во входной буфер. Оператор QUERY не стандартизован в Форт-83, но если он включен (а так обычно и есть), он должен быть определен иначе: : QUERY 0 BLK ! 0 >IN ! TIB 80 EXPECT 0 >IN ! ;
В этом случае QUERY действует как GETINPUT.
Чтобы посмотреть, что содержится в текстовом буфере, нажатием соответствующей клавиши введите семь пробелов и затем напечатайте TIB 2 DUMP
Результат после DUMP будет выглядеть как (адр) 20 20 20 20 20 20 20 54 49 42 20 40 20 32 20 44 Т I В @ 2 D 55 4D 50 00 00 xx xx xx xx xx xx xx xx xx xx xx U M P
где "(адр)" будет зависеть от вашей системы, а "xx" - неизвестные байты. Вы можете убедиться, что входной буфер является точной копией строки, которую вы напечатали, дополненной пробелами в начале и, возможно, оставшимися символами от предшествующего ввода в конце (отмеченные здесь "хх"). Это связано с тем, что входной буфер заполняется оператором QUERY. Мы знаем, что Форт интерпретировал входную строку, так как он исполнил DUMP, чтобы пропечатать две первые строки текстового входного буфера,
Но как Форт знает, где остановить интерпретацию строки, введенной с клавиатуры? Это варьируется от версии к версии. Мы опишем, что происходит в MMSFORTH. Напечатайте ту же команду, но на сей раз без предшествующих пробелов, и увидите
(адр) 54 49 42 20 40 20 32 20 44 55 4D 50 00 00 20 44 T I B @ 2 D U M P D 55 4D 50 00 00 хх хх хх хх хх хх хх хх хх хх хх U M P
Оператор DUMP еще раз интерпретирован. Но почему MMSFORTH не пытается интерпретировать "DUMP", который вы видите в конце, оставшийся от предшествующего ввода? Ввод с клавиатуры выполняется оператором EXPECT в QUERY, который в данном случае мы завершили нажатием клавиши возврата каретки. Почему нет ASCII-кода возврата каретки (0D HEX) в конце введенного текста во входном буфере? Потому что вместо возврата каретки EXPECT в MMSFORTH вводит два ASCII-кода, равных 0 (нулевые байты), чтобы пометить конец ввода.
Более ранний ввод игнорируется, так как в MMSFORTH (и большинстве других версий) интерпретация текста завершается, когда во входном потоке встречаются символы 0, вставленные туда оператором EXPECT. Это прерывание с помощью'"нулевых символов применяется не только для ввода с клавиатуры, но и для блочных буферов; хотя дисковые буферы должны содержать 1024 байта, каждый из них занимает 1026 байтов памяти, так как нужны еще два ASCII-символа 0, чтобы пометить конец каждого из буферов. В противном случае интерпретатор не будет знать, что конец буфера уже достигнут, и может попытаться интерпретировать другие части памяти, что будет чревато неприятностями.
Другой способ контроля конца входного потока заключается в подсчете числа введенных символов до нажатия возврата каретки. В Форт-83 это число для пультового буфера хранится в #Т1В. Еще один способ решения проблемы - это контроль того, что число, записанное в >IN, не превышает размера блочного или текстового входного буфера. В этом случае для каждого блочного буфера требуется только 1024 байта, какие-либо разграничители для прерывания интерпретации не требуются.
Но как производится синтаксический анализ текста во входном буфере, чтобы слова могли компилироваться или исполняться? Мы уже упомянули, что это делается словом WORD, и, если вы помните, как мы использовали WORD в PARSIT в гл. 9, вы сможете ответить на этот вопрос. Слово WORD производит разбор буфера, выделяя последовательности символов, отделенные друг от друга пробелами, и помещает их в виде счетных строк по адресу HERE. Слова затем используются для' поиска в словаре, и они либо исполняются, либо компилируются в соответствии с величиной STATE, как мы уже описали.
Теперь мы можем видеть, как разграничитель 0 прерывает интерпретацию во многих версиях Форта, ASCII-символ (0) воспринимается интерпретатором как имя слова (непечатное имя) Форта. которое исполняется немедленно, чтобы остановить интерпретацию. Это слово иногда называется х, а иногда "null".
Все эти процессы, собранные в табл. 15.2, объединяются вместе и соответствует термину текстовый интерпретатор. Текстовый интерпретатор часто несколько произвольно называется внешним интерпретатором в противоположность внутреннему или адресному интерпретатору, который является чем-то совсем другим, он обслуживается позднее в этой главе.
Упражнения
1. Во время интерпретации слово в зависимости от величины STATE либо компилируется, либо исполняется. Предположим, что словарь был просмотрен и что CFA слова было извлечено. Опишите слово ?COMPILE, которое компилирует или исполняет слово. (Подсказка: используйте в вашем описании , (запятую) и EXECUTE.) Это очень близко к тому, что действительно делается после успешного поиска в словаре. 2. Изучите слово FIND из Форт-83 (стр.155). Пусть FIND использовался и результат его работы записан в стек. Предположим также, что найдено искомое слово. Переопределите ?COMPILE как 83?COMPILE, чтобы при нахождении слова немедленного исполнения оно выполнялось во время компиляции, в противном же случае исполнялось бы или компилировалось в зависимости от переменной STATE. Почему оператор FIND из Форт-83 неотъемлемая часть интерпретатора? 3. Опишите Форт-83 версию '(апостроф) как N', используя FIND из версии Форт-83. Если слово не найдено, должно высвечиваться "Word not found" ("слово не найдено"). 4. Модифицируйте ?COMPILE из упражнения 1 так, чтобы при компиляции их CFA отображалось на экране дисплея. 5. Попытайтесь напечатать >IN @ с клавиатуры. Теперь сделайте это, введя предварительно несколько пробелов. Что означают полученные числа? Как используется >IN, когда входная информация поступает от клавиатуры? 6. Напечатав 0 >IN ! QUERY вы вызовете "зависание" ЭВМ. Почему? 7. Предположим, что вы описали : FAKE-LOOP 10 = IF QUIT THEN ; далее напечатали 0
а затем 1+ DUP DUP FAKE-LOOP ) >IN !
Что, вы полагаете, произойдет? 8. Пусть 0, или X, или null останавливает интерпретацию в вашем Форте. Опишите : STOP 0 TIB >IN @ + ! ; Теперь, если вы напечатаете 5.
STOP 6 что вы увидите? Почему?
Компиляция
Как вы видели, когда текстовый интерпретатор встречает слово Форта, происходит одно из двух: слово либо исполняется, либо компилируется в словарь Форта. Но как осуществляется компиляция или исполнение? Рассмотрим сначала компиляцию.
Компиляция представляет собой процесс добавления в словарь новых слов, чисел или строк. Обычно это делается в верхней части словаря (т.е. по адресу HERE). Мы видели в гл. 6, что 8- и 16-битовые величины могут компилироваться непосредственно с помощью С, и , (запятой). Слова, такие как С, , которые расширяют словарь, не создавая новых описаний, называются компилирующими. Слова же, которые компилируют новые описания, такие как : (двоеточие) и CREATE, называются словами-описателями. В этом разделе мы рассмотрим, как осуществляют компиляцию наиболее часто используемые слова-описатели.
Теперь покажем, как работает : . Рассмотрим наш старый пример: : BASE? BASE @ DUP DECIMAL . BASE ! : : (двоеточие) кодирует имя "BASE?" и запоминает результат (по адресу HERE) как поле имени в элементе словаря BASE?. Если имя BASE? уже имеется в словаре, то будет выдано сообщение "duplicate name" (повторное описание). Слово : затем находит NFA предшествующего слова в словаре и запоминает в поле связи BASE?, чтобы обеспечить управление поиском. (Способ того, как это делается, зависит от оговоренных условий связи контекстного словаря вашего Форта; см. гл. 14.) Этим завершается оформление заголовка BASE?. Следующим шагом будет компиляция адреса программы в машинных кодах, используемая при выполнении слов типа двоеточие (называемая исполнительной программой типа двоеточие), в поле программы BASE?. Слово : может также выполнить самые разные процедуры, например разрешить слову ; детектировать ошибки компиляции. После этого слово : устанавливает Форт в режим компиляции путем записи ненулевого кода в STATE. На этом работа : завершится. Следующий шаг весьма прост: каждое последующее слово из входного потока компилируется путем нахождения в словаре и записи его CFA (с помощью ,) в новое описание.
Это выполняется автоматически по мере разбора входного потока текстовым интерпретатором. Каждый раз, когда адрес (или что-то еще) компилируется в словарь по адресу HERE, верх словаря смещается выше на соответствующее число (переменная DP увеличивается), так что HERE всегда указывает на первый свободный байт памяти, находящийся сразу после словаря. Результирующий список адресов в поле параметров BASE? выглядит, как показано в гл. 14.
Слово ; (точка с запятой) завершает описание, начатое :, поместив CFA оператора EXIT в последнюю позицию тела оператора BASE?, возвратив Форт в режим исполнения и сделав значение переменной STATE равным 0, Слово ; (точка с запятой) проверяет также, не было ли ошибки при компиляции. Один из способов, каким ; может выявить ошибку, - это проверить, изменился ли указатель стека в процессе компиляции. Другая схема предполагает, что : заносит в стек число, которое должно там сохраняться к моменту выполнения слова ;, последнее его и удаляет. Если ошибка произошла, ; возвращает переменной DP и, следовательно, HERE то значение, которое оно имело до :, предотвращая какие-либо изменения словаря. (Некоторые версии Форта оставляют в словаре заголовок и частично компилированное тело, просто устанавливая бит-метку в такое состояние (см. гл. 14), что слово не может быть найдено. Это, однако, позволяет ошибкам использовать пространство словаря.)
Откомпилированный в оператор BASE? представляет собой заголовок, состоящий из поля имени и поля связи, и тело, составленное из указателя на исполнительную программу "двоеточие", за которым следует список CFA слов, используемых в описании BASE?, завершающийся CFA слова EXIT. Вы можете убедиться, что это как раз то, что было скомпилировано в поле параметров BASE?, с помощью пропечатки и записи каждого адреса, содержащегося там. Затем, если вы отдельно проверите каждое слово, используемое в описании BASE?, с помощью FIND (СЛОВО) U. или в Форт-83 ' (слово) U. вы убедитесь, что список адресов тот же, что и в поле параметров.
Программа, которая выполняет аналогичную функцию автоматически, отображая имена слов, использованных при описании конкретного слова, называется Форт-декомпилятором. Декомпилятор находит каждое слово, использованное в описании по его CFA, которое, в свою очередь, используется для нахождения его имени. Декомпилятор не может работать с MMSPORTH и другими версиями, которые используют хэш-коды для имен.
Упражнения
1. Скомпилируйте следующие слова: : 1DUMMY ; ; 2DUMMY 1DUMMY ; : 3DUMMY 2DUMMY ; пропечатайте каждое из них с помощью DUMP. определите адреса поля связи, поля программы и поля параметров каждого слова и запишите их. Затем запишите содержимое каждого из полей. 2. Адреса, записанные в поле программы всех слов, одни и те же. Почему? В каком соотношении находится этот адрес с адресом поля программы BASE? ? 3. Маршрут исполнения слова 3DUMMY проходит через шесть CFA (если вы включите в список три исполняемых EXIT). Просмотрите поля параметров и выясните, что это за адреса. 4. Как бы вы могли изменить поле связи 2DUMMY. чтобы 1DUMMY было нельзя обнаружить в словаре ? Это должно быть сделано аккуратно и может оказаться невозможным, если в вашем словаре используется большое число путей поиска. Проверьте ваши результаты. 5. Как вы можете изменить 3DUMMY, чтобы оно исполняло 1DUMMY вместо 3DUMMY без повторной компиляции? (Подсказка: измените что-то в PFA) Как бы вы могли изменить его так, чтобы 3DUMMY не делало ничего (подумайте об EXIT). 6. Найдите CFA операторов + и - и запишите их. Опишите следующее: : PLUS/MINUS/TIMES * ; Когда исполняется PLUS/MINUS/TIMES, перемножаются два числа. Измените это слово путем изменения его поля параметров так, чтобы оно выполняло операцию вычитания одного числа из другого. А теперь сделайте так, чтобы оно производило сложение двух чисел. Хотя в норме этого делать в программе не следует, это демонстрирует, что функция слова может динамически меняться путем изменения содержимого поля параметров. (Это, конечно, может быть сделано более изящно, если использовать исполнительный вектор, как в гл. 6.).
Исполнение при компиляции
Бывают случаи, когда хочется исполнить какое-то слово во время компиляции описания типа двоеточие. Рассмотрим примеры: : SAY-IT ." I am compiling." ; IMMEDIATE : COMPILE-IT SAY-IT ." I am already compiled." ;
Когда вы компилируете COMPILE-IT, будет выдано на экран сообщение "I am compiling.". Когда же COMPILE-IT исполняется, вы увидите "I am already compiled.". Но, когда вы исполните SAY-IT с клавиатуры, будет выдано сообщение "I am compiling.", что будет, конечно, неверным. Так как за описанием SAY-IT следует слово IMMEDIATE, оно становится словом немедленного исполнения. Вспомните из гл. 9, что ( и.( являются также примерами слов немедленного исполнения.
Слово IMMEDIATE сообщает Форту, что нужно пометить только что описанное слово, чтобы оно исполнялось немедленно там, где встретится, вне зависимости от того, находится ли Форт в режиме компиляции или исполнения. Действие слова IMMEDIATE заключается в том, чтобы устанавливать первый бит поля имени только что описанного слова в единичное состояние. Этот бит воспринимается внешним интерпретатором как флаг, указывающий, что слово должно быть исполнено, где бы не встретилось. Первый бит - это часто старший бит байта длины в словарях с кодированными именами, в таком случае имя в словаре, чей первый байт больше шестнадцатеричного 7F, представляет собой слово немедленного исполнения. Все слова, которые выполняют свою функцию при компиляции, являются словами немедленного исполнения.
Необходимо соблюдать предосторожность при работе со словами немедленного исполнения. Запомните, что большинство версий Форта проверяет тем или иным способом, был ли изменен указатель стека во время компиляции (хотя это и не регламентируется ни одним стандартом). Таким образом, если слово немедленного исполнения изменит стек, это вызовет ошибку, когда ; обнаружит, что стек в процессе компиляции изменился. Вы можете проверить это, описав : .IMM . ; IMMEDIATE и затем : TRY-IT .
IMM ;
Вы почти наверняка получите сообщение об ошибке. Но, если вы опишите.IMM как : .IMM DUP . ; IMMEDIATE и опишите TRY-IT, как и раньше, с 5 в стеке, цифра 5 может быть пропечатана или нет в зависимости от того, как проверяется стек во время компиляции. Это показывает, что вы должны описывать слова немедленного исполнения очень тщательно, так чтобы они не взаимодействовали с Форт-компиляцией неожиданным образом.
Мы рассмотрим некоторые практические примеры слов немедленного исполнения в упражнениях, но сначала давайте посмотрим, как слово немедленного исполнения может быть скомпилировано в описание. Это делается с помощью слова [COMPILE]. Попробуйте этот вариант COMPILE-IT : LIAR [COMPILE] SAY-IT ." I am already compiled." ; При компиляции на экране ничего не появится, но, когда слово будет исполняться, вы увидите I am compiling, I am already compiled,
Слово [COMPILE] заставляет скомпилировать SAY-IT, несмотря на то, что оно является словом немедленного исполнения. Другими словами, [COMPILE] заставляет игнорировать старший бит в SAY-IT и компилировать SAY-IT вместо того, чтобы исполнять во время компиляции.
Способом исполнения слова или слов, которые не относятся к числу слов немедленного исполнения, во время компиляции является использование слов [ (левая скобка) и ] (правая скобка). По пробуйте написать : SAY-IT-NOW [ ." I am compiling." ] " I am already compiled. " ;
В точности так же, как для COMPILE-IT, при компиляции SAY-IT-NOW на экране появится "I am compiling.", но при исполнении вы получите "I am already compiled.". Слова между [ и ] исполняются, а не компилируются. Описания [ и ] просты : [ 0 STATE ! ; IMMEDIATE и : ] 1 STATE ! ;
Вы должны понимать, как они работают и почему ] не должно быть словом немедленного исполнения, в то время как [ должно.
Слова [ и ] могут использоваться как в пределах описаний типа двоеточие, так и вне их. Вспомните, что при работе текстового интерпретатора в режиме компиляции, т.
е. когда значение STATE не равно 0, CFA любых слов из словаря, встречающихся во входном потоке, заносится по адресу HERE, значение HERE соответствующим образом смещается. Попробуйте исполнить HERE U. ] SWAP DROP [ HERE U. и вы увидите, что значение слова HERE сместилось вверх на 4 байта, которые были добавлены к словарю, что они содержат CFA слов SWAR и DROP, но не существует простого способа использовать их, так как они не могут быть найдены при просмотре словаря.
Но теперь предположим, что вы хотите скомпилировать исполнительный вектор с именем CHOICES с вариантами 1CHOICE, 2CHOICE и 3CHOICE. Вы уже знаете из гл. 6, что нужно сформировать массив с помощью слова CREATE и, найдя CFA вариантов, записать их в массив с по мощью слова , (запятая). Теперь посмотрим на это: CREATE CHOICES ] 1CHOICE 2CHOICE 3CHOICE [
Эта процедура создает идентичный массив с меньшими хлопотами и с много большей наглядностью. Данная техника была использована для массива KEYVECTORS в редакторе в гл. 12. Позже мы рассмотрим еще некоторые приложения скобок, но сначала несколько упражнений.
Упражнения
1. Для целей отладки иногда удобно знать, в каком блоке и где происходит компиляция. Опишите слово с именем LOC1, используя IMMEDIATE, которое, будучи введенным где-либо в блоке, отмечает позицию, выразив ее через номер блока и относительный адрес в блоке. Модифицируйте это слово в LOC2 так , чтобы оно печатало номер блока и номер строки в блоке (в предположении, что строка имеет 64 символа). 2. Предположим, что слово из упражнения 1 не было словом немедленного исполнения, но вы хотите его использовать внутри описания типа двоеточие. Как вы можете это сделать? 3. Как можно отобразить содержимое стека, используя.S, когда компилируется описание типа двоеточие? 4. Что случится, если вы вставите [ без парной скобки ] в описание типа двоеточие? 5. Могли ли вы использовать [COMPILE], чтобы скомпилировать слово, которое не является словом немедленного исполнения в описание типа двоеточие? 6. Что произойдет, если вы введете [ 239 ] в описание типа двоеточие? Результат будет зависеть от того, как контролируется состояние стека в вашей версии.
Попробуйте это с вашей версией. Если вы не получите сообщение об ошибке, проверьте состояние стека после этого. 7. Без отыскания CFA для +, -, * и / создайте массив с именем OPERATOR, содержащий CFA этих слов в указанном порядке. Теперь опишите слово с именем MATH, которое в соответствии с числом в стеке будет складывать, вычитать, умножать и делить два числа, занимающие вторую и третью позиции в стеке. Единице должно соответствовать сложение, двойке - вычитание и т.д. Таким образом 5 2 1 MATH должно дать 7 5 2 3 MATH должно дать 10 и т.д.
Компиляция чисел и текста
Существует слово LITERAL, задачей которого является взять число из стека и использовать его в описании типа двоеточие так, что при его исполнении это число будет уложено в стек. Попробуйте : SILLY [ 2991 ] LITERAL ; Если вы напечатаете SILLY . вы увидите на экране число 2991, которое было занесено в стек: словом SILLY. Но мы дали этому слову такое имя (silly - глупый) потому, что оно было описано глупым образом. Вы могли бы точно так же описать : SILLY 2991 ; (Даже это глупо, так как следовало бы использовать константу; это бы заняло меньше места в словаре и обеспечило большее быстродействие.)
Для чего же пригодно слово LITERAL ? Давайте рассмотрим некоторые примеры. Вы помните, что всегда полезно описать : TASK ; в начале программы, чтобы можно было ее удалить, написав FORGET TASK
Давайте заставим TASK делать что-то полезное. Если вы опишите : TASK ." Loaded from block" [ BLK @ ] LITERAL. ; всякий раз, когда вы напечатаете TASK с клавиатуры, вам будет сообщен номер блока, где началась программа. [ BLK @ ] кладет номер блока в стек, a LITERAL компилирует его на стадии компиляции TASK. Всякий раз, когда используется TASK, отображается номер блока, где размещен исходный текст программы.
Может быть, наиболее важная функция LITERAL- ускорение исполнения программы. Предположим, что вы имеете константу 55 CONSTANT A и вы должны многократно в процессе выполнения программы вычислять квадрат числа A.
Вы можете тогда описать : ASQRD A DUP * ; но произведение будет вычисляться каждый раз, когда нужен квадрат. Если A действительно константа, т. е. если она не изменяется в процессе исполнения программы, то : ASORD [ A DUP * ] LITERAL ; будет выполняться быстрее, так как произведение будет определено на стадии компиляции, а не исполнения. Следует всегда стараться сделать все, что можно, во время компиляции, избегая потерь времени при исполнении. Слова-скобки часто используются, чтобы сделать программу более читаемой. : RUGS ( n -- sq-ft ) [ 9 12 * ] LITERAL * ; вычисляет и компилирует 108 (квадратных футов), слово создано для умножения на число ковриков, лежащее в стеке. Таким образом, 10 RUGS положит в стек 1080. Конечно, слово может быть описано как : RUGS ( n -- sg-ft ) 9 12 * * ; или проще: : RUGS ( п - sg-ft ) 108 * ; но первый пример более удобочитаемый, чем другие, и работает так же быстро, как и последний; 9 12 * вычисляется только раз, когда RUGS компилируется. Сохранение длины и ширины в явном виде делает более легким просмотр исходного текста программы и понятным размеры ковра, которые имелись в ввиду. (Замечание: другим способом решения проблемы было бы описание 9 12 * CONSTANT 9BY12 использующие константу двойной длины для запоминания длины и ширины. Если вы затем опишите ; RUGS * ; последовательность 12 9BY12 RUGS будет однозначной, весьма удобочитаемой и столь же быстродействующей, хотя и требует большего места в словаре из-за описания константы.)
Другой подход может позволить оператору выбрать размер ковра во время компиляции. Предположим что вы описали : WHATSIZE? ." Length" #IN ." Width" #IN . ; и затем опишем заново : CARPETS [ WHATSIZE? ] LITERAL * ; Пользователь мог бы задать размер ковра, который нужно использовать при специальном прогоне программы без изменения исходного текста. Обратите внимание, что если бы WHATSIZE было описано как слово немедленного исполнения, то при использовании в CARPETS, его не следовало бы помещать в скобки.
Мы предупреждали, что слова немедленного исполнения могут приводить к ошибкам, если они изменяют состояние стека, когда используются в описании, начинающемся с двоеточия. Таким образом, хотя LITERAL компилирует число из стека, некорректно писать 555 : TAKENUM LITERAL ; и ожидать, что TAKENUM оставит 555 в стеке. Почти наверняка будет выдано сообщение об ошибке, так как LITERAL удаляет число из стека при компиляции. Это приведет к тому, что TAKENUM будет помечено так, что его нельзя будет найти в словаре.
Теперь мы в состоянии понять, как в действительности компилируются числа в описании типа двоеточие. LITERAL - слово немедленного исполнения, поэтому конечно, само не компилируется. Вместо этого оно компилирует CFA другого слова (иногда называемого LIT), за которым следует число из стека. Когда слово LIT исполняется, оно просто берет число, скомпилированное после него, и кладет его в стек. Числа, скомпилированные непосредственно (без LITERAL) в слово типа двоеточие, также используют LIT, причем тем же самым способом.
LITERAL представляет собой пример слова с двумя совершенно разными функциями: первая используется на фазе компиляции слова, вторая - при его исполнении. Так должно быть потому, что компиляция числа из стека и укладка его в стек при исполнении - совершенно разные операции, поэтому эти функции поделены между двумя словами LITERAL и LIT. Так как поведение LITERAL при исполнении реализовано с помощью LIT, LIT называется исполнительной программой LITERAL.
Как LITERAL компилирует CFA LIT в словарь? Ответ дает слово COMPILE. Хотя COMPILE выглядит подобно [ COMPILE ], его действие совершенно другое. В отличие от [ COMPILE ] при использовании COMPILE в описании типа двоеточие оно компилируется как любое другое слово, не относящееся к "немедленному" типу. COMPILE что-то делает, только когда слово, в котором оно использовано, исполняется. В этот момент оно берет CFA, следующее за его собственным CFA, и компилирует в словарь по адресу HERE. Действие COMPILE называется отсроченной компиляцией, так как оно не делает действительно ничего до тех пор, пока слово, в котором оно применено, не будет исполнено.
COMPILE выполняет компиляцию, даже если слово, в котором оно использовано, является словом немедленного исполнения.
Таким образом, возможное описание LITERAL выглядит как : LITERAL STATE @ IF COMPILE LIT , THEN ; IMMEDIATE Когда LITERAL исполняется внутри описания типа двоеточие, отсроченное действие COMPILE" заключается в компиляции CFA слова LIT по адресу HERE. Запятая затем компилирует число из стека, которое будет нужно LIT, когда описываемое слово будет исполняться. Три фазы функционирования компилирующих слов сходны с этапами работы слов-описателей, как видно из гл. 11. В случае LITERAL этапами являются: 1 - компиляция LITERAL; 2 - исполнение LITERAL, когда слово, в котором оно использовано, компилирует LIT, и 3 - исполнение LIT, когда слово, где оно скомпилировано, исполняется.
Мы можем теперь понять, как работает слово .". Подобно LITERAL, слово ." является словом немедленного исполнения, которое имеет разные функции на фазе компиляции и исполнения. Когда." встречается в описании типа двоеточие, оно компилирует CFA своей исполнительной программы (иногда называемой (.")), а также следующий за ним текст, ограниченный." и " (двойная кавычка). Это делается командой 34 WORD, которая заносит счетную строку по адресу HERE как раз туда, где ее следует скомпилировать (см. главу 9). Слово." использует байт-счетчик счетной строки, так же как ALLOT, чтобы выделить в словаре место, достаточное для данной строки и ее счетного байта. Когда слово, куда скомпилирована строка, исполняется, (.") отображает строку, после чего исполняются слова, следующие за строкой. Итак, описание." может иметь вид : ." COMPILE (.") 34 WORD C@ 1+ ALLOT ; IMMEDIATE
Это описание." используется в Форт-83, где оно может работать только внутри описания типа Двоеточие. Но в Форт-79." может работать как внутри описания, так в вне его и оно описано несколько иначе. Описание." В Форт-79 могло бы выглядеть как : ." STATE @ IF COMPILE (.") 34 WORD C@ 1+ ALLOT ELSE 34 WORD COUNT TYPE THEN ; IMMEDIATE
Версия слова." в Форт- 79 является примером так называемых слов, зависящих от состояний, эти слова делают разные вещи в зависимости от того, в каком состоянии находится система: в режиме компиляции или исполнения. Вы можете вспомнить, что слово .( ("dop-paren" =" точка-скобка) стандарта Форт-83 используется для немедленного вывода текста, следующего за ним вплоть до разграничителя) (правая скобка). Его описание имеет вид : .( 41 WORD COUNT TYPE ; IMMEDIATE Форт-83 не использует стандартных слов, зависящих от состояния, хотя ваши собственные слова и могут быть такими.
Теперь должно быть ясно, что Форт имеет очень мощный компилятор, но в следующем разделе мы покажем, как еще более мощные компилирующие слова IF, ELSE, THEN, BEGIN и UNTIL могут компилировать Форт-структуры, которые реализуют при использовании ветвления и зацикливания. Понимание их работы при компиляции позволит вам сконструировать свои собственные компилирующие слова.
Упражнения
1. Опишите слова START-WHERE? так, чтобы, если описано : TASK START-WHERE? : при исполнении TASK было бы сообщено, из какого блока было скомпилировано TASK. 2. Написана программа для перевода долларов в фунты стерлингов. Курс варьируется от дня ко дню, так что. когда про грамма компилируется в начале каждого дня. необходим новый коэффициент пересчета. Опишите слово немедленного исполнения с именем ?RATE, которое при компиляции программы будет делать запрос текущего значения курса, а затем выдаст его значение в стек. (Разместите слово в свободном блоке для загрузки.) 3. Используя слово из упражнения 2, опишите слово с именем CONVERTS, которое при компиляции запрашивает текущее значение курса, а когда исполняется, преобразует доллары в фунты согласно объявленному курсу. Подберите соответствующие масштабы коэффициента для входных и выходных величин (запишите слово в тот же блок. что и в упражнении 2). 4. Теперь опишите три слова с именами ENGLAND, DENMARK и GERMANY, которые при исполнении будут переводить доллары в фунты , кроны и марки.
Слова должны использовать PRATE, и они должны воспринимать ежедневное значение курса для каждого вида валюты при компиляции, делая запрос "Dollars to kroner's, current rate?". (Снова используйте тот же блок.) 5. Опишите слово ?СОМР, которое будет печатать "Compile Only!" и выполнять ABORT, если слово, в котором оно применено, используется в режиме исполнения (?СОМР часто используется в описаниях компилирующих слов, таких как LITERAL, чтобы предотвратить их неправильное применение вне описаний типа двоеточие). 6. Что отобразит на дисплее следующая текстовая последовательность? : TEST COMPILE DUP ; IMMEDIATE : -TEST1 5 TEST ; TEST1 . . Почему TEST должно быть словом немедленного исполнения? Что бы произошло, если вы напечатали TEST ? (Подсказка: COMPILE содержит слово ?СОМР.)
Компиляция условных операторов Форт
Теперь нам следует посмотреть, как организуют работу IF...ELSE...THEN. Существуют две причины, почему мы этого хотим: познакомить вас подробнее с условными переходами и привести еще несколько примеров применения COMPILE. Мы рассмотрим сначала IF....THEN. Вот один из способов, каким их можно описать: : IF ?COMP COMPILE ?BRANCH HERE 0 , ; IMMEDIATE : THEN ?COMP HERE OVER - SWAP ! ; IMMEDIATE ?COMP предотвращает их использование вне описаний типа двоеточие. Как вы видели в предшествующих упражнениях, его описание могло бы иметь вид ; : ?COMP STATE @ 0= ABORT" Compile only!" ; Когда во входном потоке при компиляции встретится оператор IF, он первым делом с помощью COMPILE PBRANCH скомпилирует CFA слова ?BRANCH (?BRANCH иногда называют OBRANCH) в верхнюю ячейку словаря, затем с помощью HERE занесет адрес следующей свободной ячейки словаря в стек и, наконец, запишет 0 по этому адресу (0 будет заменен другим числом, сформированным оператором THEN). Слова между IF и THEN компилируются обычным порядком.
К моменту исполнения THEN, адрес кода 0, скомпилированного оператором IF, все еще лежит в стеке. Слово THEN вычисляет смещение адреса,- оставленного оператором IF, по отношению к верху словаря.
Это осуществляется командой HERE OVER -. Данное смещение затем запоминанием в ячейке, зарезервированной IF (посредством 0), с помощью команды SWAP !. Конечно, ни слово IF, ни слово THEN сами не компилируются, так как являются словами немедленного исполнения.
Мы можем понять работу IF... THEN яснее, рассматривая поле параметров слова. Как вы знаете из гл. 8, IF выполняет передачу управления слову, следующему после THEN, если в стеке лежит 0, в противном случае исполняются слова, расположенные между IF и THEN. Если мы опишем слово : NON-ZERO? IF ." Non-" THEN ." Zero" ; мы можем убедиться, что если при исполнении NON-ZERO? в стеке окажется ненулевое число,. дисплей отобразит "Non-Zero", в противном случае будет отпечатано "zero". Теперь, когда мы знаем, как работает IF...THEN, мы можем изобразить карту поля параметров слова NON-ZERO?
Поле параметров Содержимое 7BRANCH смещение (.") (4Non-) (.") (4Zеrо) (EXIT) Байты 2 2 2 5 2 5 2
где (4Non-) и (4Zero) представляют собой счетные строки, скомпилированные оператором.". Смещение будет равно 7 и указывает на CFA второго (.").
Теперь о том, как работает то, что скомпилировано IF...THEN. PBRANCH является исполнительной программой для IF, и, если в стеке 0, управление передается вперед на число байтов, заданное величиной смещения. Это делается путем добавления этого смещения к собственному адресу в PFA NON-ZERO?, Форту предписывается продолжить исполнение, начиная с указанного адреса. В случае NON- ZERO? исполнение возобновится со слова." , предшествующего "Zero". Если 7BRANCH обнаруживает в стеке ненулевое число, передача управления осуществляется со смещением в два байта, в результате выполняется " Non-" и." Zero".
Заметьте, что некоторые версии Форта используют абсолютный адрес, а не смещение к адресу для передачи управления оператором ?BRANCH. В этом случае описание IF будет тем же, a THEN следует описать как : THEN ?COMP HERE SWAP ! ; IMMEDIATE Таким образом, после ?BRANCH будет записан адрес передачи управления, а не смещение. ?BRANCH, конечно, использует тогда запомненный адрес передачи управления, а не вычисляет его, используя смещение.
Применение адреса вместо смещения обеспечивает большее быстродействие, так как не требует вычислений, прежде чем выполнить переход. Вы можете описать и распечатать описание NON- ZERO?, чтобы понять, какой метод реализован в вашем Форте. MMSFORTH использует абсолютные адреса переходов, а не смещения.
ELSE работает в какой-то мере аналогично комбинации IF и THEN. Его описание может выглядеть как : ELSE ?COMP COMPILE BRANCH HERE 0 , SWAP HERE OVER - SWAP ! ; IMMEDIATE
Когда ELSE встречается в тексте, последовательность COMPILE BRANCH HERE 0 делает то же, что и IF, но вместо CFA ?BRANCH компилируется CFA BRANCH. Последнее слово является исполнительной программой ELSE. После BRANCH компилируется 0, адрес которого остается в стеке для использования оператором THEN. Но вспомним, что в стеке лежит адрес, оставленный IF, поэтому необходима команда SWAP, прежде чем выполнять HERE OVER - SWAP ! для вычисления и запоминания смещения в ячейку, зарезервированную оператором IF. Это смещение указывает на CFA, которое следует сразу за 0, оставленным оператором ELSE. Адрес 0, который скомпилирован ELSE, теперь лежит в стеке, и THEN заменит его, так же как THEN заменяет 0, следующий за ?BRANCH в предшествующем примере.
Что делает ELSE, может быть прояснено на следующем примере. Если мы опишем слово : FLAG? ( n - ) IF ." True " ELSE ." False " THEN ." Flag" ; поле параметров FLAG? будет содержать ?BRANCH (смещение 1) (.") (5True) BRANCH (смещение 2) (.") (6False) (.") (4Flag) (EXIT)
При исполнении, если в стеке 0, ?BRANCH осуществляет переход к позиции непосредственно за (смещением 2), т.е. к оператору (.") для "False", скомпилированному." , следующим за ELSE в исходном тексте. В противном случае 7BRANCH "перепрыгивает" через (смещение 1) и исполняет (."), предшествующий "True", а затем переходит к BRANCH. BRANCH вычисляет адрес продолжения выполнения программы, используя (смещение 2), и переходит к фразе ."Flag", следующей за THEN в исходном тексте программы, BRANCH делает то же самое, что и ?BRANCH, но осуществляет переход вне зависимости от состояния стека.
Необходимость помнить об отличиях компилирующих слов во время компиляции и при исполнении может показаться утомительной. Следует лишь помнить, что любое компилирующее слово выполняет две функции: одну - на фазе компиляции, когда производится компиляция исполнительной программы данного слова и следующих за ней числа, смещения, адреса или текста, и вторую - на фазе исполнения, т.е. когда скомпилированное слово исполняется- Теперь вы знаете, как разобраться в работе большинства слов путем анализа их поля параметров. Давайте попытаемся это сделать на ряде примеров.
Упражнения
1. Опишем : LOOK-AT-IF ( flag -) IF 1 ELSE 2 THEN 3 ; С помощью пропечатки поля параметров как бы вы нашли положение LIT (или его эквивалента) и положение 1, 2 и 3 (запомните, что некоторые малые числа заносятся в стек словами Форта)? Используя эти позиции в качестве контрольных точек, как бы вы нашли откомпилированное CFA операторов 7BRANCH и BRANCH (или их эквивалентов) и адреса или смещения, следующие за ними? Как можно определить, используют ли команды ветвления смещение или адрес? 2. Опишите .СМР для отображения величины HERE и содержимого стека во время компиляции (используя.S). 3. Введите .СМР перед IF, ELSE и THEN и после 3 в LOOK-AT-IF. Объясните полученные значения в стеке и изменение величины HERE. He забудьте, что : (двоеточие) может положить что-то в стек. 4. Для иллюстрации предположим, что CFA LIT равна 174. Если вы опишете : TEST [ 174 , 5 , ] . ; Что будет сделано? 5. Предположим, что LIT или его эквивалент не существуют в качестве поименованного слова. Используя только его CFA, опишите NEWLITERAL. Опишите NEWLITERAL так, чтобы оно работало как LITERAL, но с числами двойной длины в стеке. 6. Предположим, что ?BRANCH и BRANCH не описаны в вашей системе. Используя CFA эквивалентов, дайте описание для NEWIF и NEWELSE. Предполагается, что осуществляется абсолютное, а не относительное ветвление.
Слова без заголовков
Слова без заголовков в соответствии с определением лишены имени и поля связи, а имеют только тело.
Конечно, они не могут быть найдены в словаре, но, поскольку они имеют тело, т.е. поле программы и поле параметров, они могут быть исполнены, В действительности, когда вы в предшествующем наборе упражнений описали LITERAL, не имея слова LIT, вы использовали LIT как слово без заголовка. Векторное исполнение (смотрите гл. 6) так же игнорирует заголовки слов (так как EXECUTE требует для исполнения только CFA слов). Слова без заголовков могут использоваться для того, чтобы их нельзя было найти в словаре. Иначе они являются "спрятанными" словами (хотя, как вы знаете, по слову LIT умный программист может, вероятно, их найти).
Стратегия создания слов без заголовков заключается в создании в словаре структур, которые имеют все необходимое слову для исполнения, кроме имени и поля связи. Конечно, слова без заголовков нуждаются в поле программы, содержимое которого должно указывать на исполнительную программу, соответствующую типу слова, которое вы создаете. Мы приведем здесь пример, как создать слова типа двоеточие без заголовка, хотя константы, переменные и другие слова без заголовков могут создаваться согласно тем же правилам.
Давайте создадим слово без заголовка, эквивалентное нашему старому другу BASE?. Сначала нам нужно знать адрес исполнительной программы описаний типа двоеточие. Для этого достаточно воспользоваться оператором FIND (в Форт-79) или ' (в Форт-83) для любого слова типа двоеточие, с тем чтобы получить CFA и занести его содержимое в стек. Если вы оставите этот адрес в стеке, приведенная ниже программа сформирует описание BASE? без заголовка, оставив его CFA в стеке для последующего использования описанного слова: HERE SWAP , ] BASE @ DUP DECIMAL - BASE ! EXIT [ Слово HERE заносит в стек верхний адрес словаря, который станет адресом поля программы, нового слова без заголовка.
Последовательность SWAP , компилирует адрес исполнительной программы слов типа двоеточие, который вы оставили в стеке. Слово ] осуществляет переход в режим компиляции и слова, начиная с BASE по ! будут скомпилированы в поле параметров слова без заголовка точно так же, как это было в обычном описании.
CFA слова EXIT компилируется аналогично тому, как это делает ; в стандартном описании. Затем [ возвращает систему в режим исполнения. CFA слова без заголовка (записанное по адресу HERE) - все еще в стеке; для того чтобы предохранить его от потери, мы можем спасти его с помощью описания CONSTANT BASE? теперь введение BASE? EXECUTE исполнит "беззаголовочная" версию слова BASE?. Конечно, намного проще описать BASE?, как обычно, - через двоеточие. Зачем нам все эти дополнительные хлопоты? Версия BASE? без заголовка не может быть найдена в словаре и не может быть исполнена или скомпилирована в другие слова без дополнительных усилий. Но эти недостатки слов без заголовка являются и их достоинствами, так как они недоступны и не могут чему-либо по мешать. Это важно для некоторых программ, которые при неправильном использовании могут разрушить систему. Многие внутренние программы Форта откомпилированы в виде слов без заголовков, потому что они практически бесполезны для чего бы то ни было, кроме внутренних задач Форта. (MMSFORTH использует массив MMS, где содержится набор системных слов без заголовка. На пример, MMSFORTH-эквивалеит PBRANCH является третьим элементом MMS.) Слова без заголовков полезны также, чтобы предотвратить несанкционированные манипуляции с откомпилированной программой, - действительно, целые прикладные Форт-программы компилируются с использованием слов без заголовков, хотя и без использования этого метода. (В вашем Форте, возможно, предусмотрена компиляция беззаголовочных программ. MMSFORTH использует для этой цели системную программу TEMP-HEAD)
Это дает представление о том, насколько гибкой может быть Форт-компиляция. Хотя в отличие от других языков в Форте компиляция и не генерирует программу в машинных кодах, она и не должна это делать; каждое слово Форта в конце концов указывает на программу,в машинных кодах и исполняет ее. Форт - один из немногих языков программирования, который позволяет модифицировать собственный компилятор, используя программы высокого уровня.
Упражнения
1. Создайте два слова' без заголовков, одно. чтобы пропечатать "Word#0". и второе, чтобы пропечатать "Word#1". Исполните оба слова, используя константы с именами 0WORD и 1WORD. (Запоминание адреса исполнительной программы для операторов типа двоеточие в константе COLON-ADDR может упростить решение задачи.) 2. Как бы вы откомпилировали слова без заголовков 0WORD и 1WORD в описание типа двоеточие? (Подсказка: используйте скобки и , (запятая).) 3. Используйте CREATE...DOESE> для создания слова-описателя, производные слова которого будут исполнять 0WORD, если в стеке лежит 0, и 1WORD, если в стеке лежит 1. (Этот метод может быть использован для создания очень сложных для понимания исходных текстов.) 4. Напишите слово-описатель GIVE-NAME, которое, формируя производные слова, требует наличия в стеке CFA описанного перед этим слова без заголовка. Когда производные слова исполняются, они должны исполнять соответствующее слово без заголовка. 5. Опишите слово без заголовка (может быть, 2WORD) и оставьте его CFA в стеке. Затем опишите слово типа двоеточие и скомпилируйте CFA слова без заголовка так, что оно будет использовано словом типа двоеточие. (Будьте осмотрительны при компиляции слова типа двоеточие, следите за состоянием стека.) 6. Если в вашем Форте 7BRANCH и BRANCH не имеют имени, как бы вы описали IF и ELSE согласно приведенному нами ранее алгоритму? 7. Сделайте то же, что и в упражнении 6, но опишите LITERAL, используя безымянное LIT.
Исполнение
Мы видели, как происходит компиляция; ну а теперь, что вы скажете об исполнении? То есть, что случится после того, как мы откомпилировали слово и затем напечатали его имя или использовали EXECUTE при наличии его CFA в стеке? Давайте сначала посмотрим весь процесс в целом, затем перейдем к рассмотрению деталей механизма.
Мы можем увидеть весь процесс исполнения в Форте, описав следующие слова: : 1LEVEL ." The lowest level " CR ; : 2LEVEL ." Begin 2LEVEL ". CR 1LEVEL . " End 2LEVEL " CR ; ; 3LEVEL ." Begin 3LEVEL " CR 2LEVEL " End 3LEVEL " CR : Когда исполняется 3LEVEL, на экране отображается Begin 3LEVEL Begin 2LEVEL The lowest level End 2LEVEL End 3LEVEL ok
Это показывает, что 3LEVEL использует слово 2LEVEL, которое, в свою очередь, использует 1LEVEL, в соответствии с тем, что мы потребовали в описании. Когда исполнение 1LEVEL завершено, управление передается назад к 2LEVEL, а затем к 3LEVEL, и в конце концов появится отклик "ok", сообщающий о готовности к новому вводу. Заметьте, что, когда начинает исполняться 3LEVEL, используя 2LEVEL (и когда стартует 2LEVEL, используя 1LEVEL), Форт должен запомнить, куда следует вернуться по завершении задания низкого уровня. Итак, как же Форт отслеживает порядок исполнения?.
Давайте проследим исполнение 3LEVEL, просмотрев описание. Необходимо несколько указателей. Один мы назовем указателем инструкции или IP, он отслеживает слово, которое должно быть исполнено следующим , и первоначально указывает на последовательность." Begin 3LEVEL". Этот указатель переходит от слова к слову, пока не встретит 2LEVEL внутри описания слова 3LEVEL. Теперь IP должен двинуться внутрь 2LEVEL, указывая на ." Begin 2LEVEL", но сначала нужен другой указатель, чтобы хранить информацию о том, куда вернуться в описании 3LEVEL. Второй указатель должен указывать на последовательность ." End 3LEVEL". После того как 2LEVEL отобразит " Begin 2LEVEL ", та же проблема возникает вновь. Третий указатель нужен, чтобы отслеживать место, куда возвращаться в описании 2LEVEL, в то время как IP отслеживает последовательность слов внутри 1LEVEL. Итак, теперь имеется два ожидаемых возврата: конкретно к сообщению "End" в 2LEVEL и к сообщению "End" в 3LEVEL. Эти указатели, конечно, содержат адреса CFA в поле параметров слов. Указатель инструкций указывает на следующий CFA слова, которое должно быть исполнено. Другие указатели, второй и третий в нашем примере, указывают на слова, подлежащие исполнению после возвращения из слова на более низкий уровень. Эти указатели хранятся в стеке возвратов, что является, конечно, причиной его наименования. На верху стека возвратов хранится адрес программы в интерпретаторе, которая должна быть исполнена по завершении выполнения слова, а под ним хранятся адреса, управляющие указателем инструкций и через него возвратом с одного уровня на другой.
Из этого должно быть очевидно, почему ничего нельзя положить в стек возвратов и оставить там при выходе из слова.
Это рассмотрение процесса исполнения показывает общий подход к тому, как происходит выполнение программы, но остается еще много вопросов без ответа. Как осуществляется управление IP и стеком возвратов? Как выполняется переход между словами с уровня на уровень? Как производится передача управления машинной программе и уход из нее? Существуют и другие вопросы, на которые почти невозможно ответить, используя только стандартные слова. Мы должны тщательно проследить на примере, как это делается. Объяснения, которые мы дадим, являются типовыми для большинства версий Форта, но существуют и другие способы выполнения программ. Даже если ваш Форт отличается от рассматриваемого, мы полагаем, что, если вы поняли метод исполнения, описанный здесь, вы оцените многие аспекты структуры и функций любой версии Форта. Объяснение, данное ниже, является сложным и потребует очень тщательного изучения и, вероятно, повторного чтения. Но мы обсуждаем самое ядро Форта и надеемся, что вы сочтете эту работу важной.
Прежде чем мы рассмотрим пример, мы должны ввести еще несколько указателей и некоторые программы, написанные в машинных кодах. Если их назначение и функции не будут очевидны при первом прочтении, вернитесь к их описанию позднее, когда вы будете отслеживать процесс исполнения на примере. Кроме указателя инструкций и стека возвратов мы будем пользоваться двумя другими указателями, чтобы объяснить, как Форт исполняет откомпилированные слова. Один из них - указатель слов (который мы будем обозначать WP); он содержит адрес поля параметров слова в момент начала его исполнения (почему это так, будет понятно позднее). Другой указатель мы будем называть регистром передачи управления (сокращенно - JP). Он служит для хранения адреса, куда будет передано управление для исполнения программы в машинных кодах. Запомните, что IP, WP и JP характеризуют лишь один способ представления того, как осуществляется исполнение слов в Форте.
Эти указатели обычно представляют собой регистры центрального процессора, но существует много реализации на разных ЭВМ. Сокращения, которые будут использоваться в дальнейшем, представлены в табл. 15.3.
Таблица 15.3. Указатели и стек возвратов, используемые при выполнении программы Форта
IP = Указатель инструкции Адрес CFA текущего слова, подлежащего исполнению WP = Указатель слов PFA слова в начале обращения JP = Указатель передачи управления Адрес, куда должен передать управление процессор RS = Стек возвратов
Стек адресов возврата
Исполнение в Форте представляет собой последовательность переходов между двумя типами программ в машинных кодах. Это: 1. Исполнительная программа слова Форта (т.е. программа, адрес которой указан в поле программы). 2. Программы в машинных кодах, размещенные в поле параметров слов-примитивов,
Исполнительная программа определяет то, как выполняется слово Форт, а программы в машинных кодах слов-примитивов выполняют полезную работу Форт-программы. Различие между исполнительной программой и программами в машинных кодах несколько запутанно, так как исполнительная программа слова-примитива - это и есть программа в машинных кодах, размещенная в его поле параметров. Выполнение слов типа двоеточие и примитивов контролируется тремя короткими программами в машинных кодах, каждая из которых требует только около дюжины байтов на 8-битовом процессоре, NEXT является наиболее фундаментальной программой, так как она используется для завершения всех программ в машинных кодах используемых Фортом.
NEXT является не словом словаря Форта, а словом, используемым Форт-ассемблером для перехода к внутреннему или адресному интерпретатору. Машинная программа, к которой происходит переход при NEXT, осуществляет переход к выполнению следующего слова Форта.
Слова типа двоеточие используют еще две программы, которые мы рассмотрим. Первая - это исполнительная программа описаний типа двоеточие (иногда называется DOCOL), ее функции заключаются в управлении исполнением таких описаний.
Другим словом, используемым при исполнении слов типа двоеточие, является EXIT, CFA которого компилируется в конце каждого описания оператором ;. Слово EXIT извлекает из стека возвратов и заносит в указатель инструкции код, управляющий тем, что будет исполняться следующим. Как исполнительная программа, так и слово EXIT передают управление слову NEXT. чтобы перейти к исполнению следующего слова. Функционирование этих программ станет понятным из нашего примера. Давайте опишем слово : SQUARE DUP * ; и, используя произвольные адреса, рассмотрим его скомпилированную структуру; это позволит нам исследовать детально механику исполнения программы Форта. Тело слова SQUARE может быть представлено как.
CFA PFA Тело слова SQUARE 20000 20002 20004 20006 Содержит 13000 18000 17000 12000 (CFA слов) DUP * EXIT
CFA слова SQUARE равно 20000, содержимое которого (13000) является произвольным адресом, который мы выбрали для исполнительной программы слов типа двоеточие. Другие три адреса, скомпилированные в PFA слова SQUARE (18000, 17000 и 12000), являются CFA слов DUP, * и EXIT. Но так как DUP и * являются примитивами (CFA которых указывают на их PFA), мы можем дополнить нашу диаграмму дополнительным списком адресов:
CFA PFA Тело слова SQUARE 20000 20002 20004 20006 Содержит... 13000 18000 17000 12000 (CFA слова) DUP * EXIT Содержит ... 18002 17002 12002
Чтобы получить более интересный пример, давайте опишем слово : CUBE DUP SQUARE * ; теперь мы можем исследовать, как слова типа двоеточие обращаются к другим словам аналогичного типа. Используя адреса, которые были приняты для слова SQUARE, мы можем получить карту компиляции слова CUBE:
CFA PFA Тело слова CUBE 21000 21002 21004 21006 21008 Содержит... 13000 18000 20000 17000 12000 (CFA слов) DUP SQUARE * EXIT Содержит... - 18002 13000 17002 12002
Чтобы детально проследить исполнение слов SQUARE и CUBE, основывайтесь на этих картах.
Когда во входном потоке встретится 3 CUBE, интерпретатор текста положит в стек 3, затем найдет в словаре CUBE и оставит в стеке его CFA (21000).
Так как Форт находится в режиме исполнения, это CFA передается оператору EXECUTE, чтобы начать выполнение слова CUBE. Слово EXECUTE берет CFA из стека и использует его для получения адреса исполнительной программы CUBE (13000), который заносится в регистр передачи управления (JP). В то же самое время в указатель слов WP записывается PFA слова CUBE. Исполнение начинается путем передачи управления по адресу, лежащему в JP, или 13000 (адрес исполнительной программы описаний типа двоеточие). То, что мы только что объяснили, может быть отображено на диаграмме:
До Операция После
Стек - ? Засылка 3 в стек - 3 Стек - 3 Обнаружение CFA слова CUBE - 3 21000 EXECUTE, который выполняет: Стек - 3 21000 Используя CFA CUBE, - 3 определяет адрес исполнительной программы описаний типа двоеточие JP = ? и кладет в JP JP = 13000 WP = ? Укладку PFA CUBE в WP WP = 21002 JP = 13000 Переход к исполнительной программе типа двоеточие
Прежде чем вы увидите, что делает исполнительная программа, мы должны заметить, что IP содержит адрес во внешнем интерпретаторе
До Операция После
DOCOL, которая выполняет: IP = OUTER Засылку IP в стек возвратов, чтобы сохранить адрес, куда возвращаться RS -- OUTER WP = 21002 Занесение PFA CUBE в IP IP = 21002 NEXT. чтобы закончить исполнение программы : IP @ @ Извлечение адреса исполнительной программы слова DUP и загрузку его в JP JP = 18002 WP = 21002 Занесение PFA DUP в WP WP = 16002 IP = 21002 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP = 21004 JP = 18002 Передачу управления исполни- тельной программе DUP (машин- мой программе дублирования кода в стеке) Стек - 3 Работу DUP - 3 3
DUP (конкретно 21002), команда IP @ @ является символическим отражением того, как определяется адрес исполнительной программы DUP. (Поскольку 21002 @ выдает 18000, содержимое 18000, т.е. 18002, может быть занесено в JP с помощью команды 21002 @ @. Конечно, @, как это использовано здесь, является символическим и не означает занесения чего-либо в стек.
Если что- то не ясно, смотрите диаграмму CUBE.) Адрес исполнительной программы DUP (ее PFA, поскольку программа в машинных кодах лежит в ее
До Операция После
IP = 21004 (Словом DUP не изменен) IP = 21004 NEXT, который выполняет: IP @ @ Вычисление адреса исполнительной программы SQUARE и загрузку ее в JP JP = 13000 WP = 18002 Занесение PFA SQUARE в WP WP = 20002 IP = 21004 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP = 21006 JP = 18002 Передачу управления исполнительной программе SQUARE (машин- ной программе дублирования кода в стеке)
поле параметров) загружается в JP, так как процессор должен исполнить ее непосредственно. Но прежде чем это сделано, IP дается приращение, чтобы он указывал на следующую ячейку (21004) в PFA слова CUBE. Затем управление передается программе DUP, которая дублирует в стек число 3.
После того как DUP выполнит свою работу, исполнение переходит к оператору NEXT, который является всегда последней командой в поле параметров примитива.
Еще раз исполняется программа DOCOL, на этот раз для слова SQUARE.
Программа DUP дублирует число 3, хранящееся в стеке. Заметьте, что хотя в WP загружено PFA слова DUP, оно не использовано. Исполнение продолжается путем передачи управления оператору NEXT, так как DUP является примитивом.
До Операция После
IP = 20004 (Не изменен словом DUP) IP = 20004 NEXT, который выполняет: IP @ @ Определение адреса исполнительной программы слова * и загрузку его в JP JP = 17002 WP = 18002 Занесение PFA * в WP WP = 17002 IP = 20004 Приращение IP, теперь он указывает на следующее слово, IP = 20006 подготовлено его исполнение JP = 17002 Передачу управления исполнительной программе * (машинной программе перемножения двух чисел) Стек = -3 3 Выполнение * - 3 9
* перемножает два верхних числа в стеке, и мы еще раз обращаемся к NEXT, так как * является примитивом.
До Операция После
IP = 20006 (Не изменен словом *) IP = 20006 NEXT, который выполняет: IP @ @ Получение адреса исполнительной программы слова EXIT и загрузку его в JP JP = 12002 WP = 17002 Занесение PFA слова EXIT в WP WP = 12002 IP = 20006 Приращение IP, теперь он указывает на следующее слово IР = 20008 (после конца SQUARE) IP = 20008 IP = 12002 Передачу управления исполнительной программе EXIT (машинной программе перехода на более высокий уровень)
Заметьте, что хотя, IP указывает на ячейку после конца SQUARE, это не имеет никакого значения, когда мы завершаем исполнение SQUARE с помощью EXIT, так как в случае исполнительной программы типа двоеточие EXIT завершается переходом к NEXT.
До Операция После
EXIT, который выполняет RS -- OUTER 21006 Засылку в IP кода из стека RS -- OUTER возвратов IP = 21006 NEXT,который выполняет; IP @ @ Определение адреса исполнительной программы слова * и загрузку его JP JP = 17002 WP=12002 Занесение PFA слова * в WP WP = 17002 IР=20006 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP=20008 JP=17002 Передачу управления исполнительной программе * (машинной программе умножения двух чисел). Стек - 3 9 Работу * - 27
* умножает два числа в стеке, и мы снова сталкиваемся с командой NEXT в конце программы, написанной в машинных кодах.
До Операция После
IР=21008 (Не изменен словом *) IP=21008 NEXT. который выполняет: IP @ @ Получение адреса исполнительной программы слова EXIT и загрузку его в JP JP=12002 WP=17002 Занесение PFA слова EXIT в WP WP=12002 IР=21008 Приращение IP, теперь он IР=21010 указывает на следующее слово, после конца слова CUBE IP=21010 JP=12002 Передачу управления исполнительной программе EXIT (машинной программе перехода на более высокий уровень)
Здесь снова не имеет значения то, что IP содержит адрес, который следует после описания CUBE. Теперь мы завершаем исполнение CUBE оператором EXIT.
До Операция после
Оператор EXIT, который RS=OUTER выполняет: Засылку в IP кода RS -- ? из стека возвратов IP = OUTER NEXT, который выполняет: IP @ @ Определение адреса исполнительной программы слова OUTER и загрузку его в JP JP = ? WP = 12002 Укладку PFA ? в WP WP = ? IP = OUTER Приращение IP, теперь он указывает на следующее слово. подготовлено его исполнение IP=OUTER+2 JP = ? Передачу управления исполнительной программе слова во внешнем интерпретаторе Стек = 27 Стек содержит три в кубе
Это завершает исполнение CUBE и управление передается назад к исполнительной программе следующего слова, которое исполняется внешним интерпретатором.
Когда входной поток иссякает, управление возвращается клавиатуре и печатается отклик "ok" при коде 27 в стеке.
Исполнение слова CUBE можно в общем виде охарактеризовать как выполнение последовательности программ в машинных кодах, представленных в табл. 15.4.
Исполнение всех слов Форта организовано одним и тем же основным способом. Единственной вариацией является действие исполнительных программ, на которые указывает содержимое полей программы каждого слова. Исполнительная программа для константы занесет в стек содержимое ее поля параметров и передаст управление слову NEXT. Исполнительная программа слова + (программа в машинных кодах лежит в его поле параметров) сложит два числа из стека и перейдет к исполнению слова NEXT. Программы в машинных кодах для слов BRANCH и 7BRANCH должны вычислять адрес передачи управления и изменять соответственно IP, прежде чем исполнить NEXT. Различные другие слова (такие как исполнительные программы для DO и LOOP) изменяют порядок исполнения программы Форта путем изменения содержимого стека возвратов. Исполнение программы в форте является простым и весьма гибким.
Хотя слово NEXT, различные исполнительные программы и слово EXIT все являются очень короткими (и, следовательно, быстрыми). Форт имеет встроенные "накладные расходы", так как переход от одной программы к другой через слова NEXT и EXIT требует времени. Несмотря на это, используя CUBE в качестве примера, в табл. 15.4 показано, что только около половины байтов, исполняемых словом CUBE, составляют "накладные расходы". Половина потрачена в DUP и *, которые были бы нужны, даже если слово CUBE было написано в машинных кодах. Поскольку скорость исполнения является примерно пропорциональной числу исполняемых байтов (в действительности * медленнее, так как эта программа включает в себя циклы), слово CUBE, как написано в Форте, не более чем вдвое медленнее варианта, написанного на ассемблере. Заметьте также, что Форт очень
Таблица 15.4. Программы, выполняемые при исполнении слова CUBE ( 1)
Имя Требуемое число байт EXECUTE 7 DOCOL. для CUBE 14 NEXT 12 DUP 5 NEXT 12 DOCOL для SQUARE 14 NEXT 12 DUP 5 NEXT 12 * 70 NEXT 12 EXIT 12 NEXT 12 * 70 NEXT 12 EXIT 12 NEXT 12 (внешний интерпретатор) ________________________ Всего 305
эффективно использует память. Хотя CUBE использует всего 305 байтов, описание CUBE и SQUARE добавляет к словарю только 30 байтов. Более того, описание CUBE в машинных кодах потребовало бы 75 байтов при оптимальном описании * и DUP. Форт-программы могут действительно потребовать меньше памяти, чем программа в машинных кодах. Даже если не учитывать быстродействие и использование памяти, трудно себе представить более простой, элегантный или удобный способ связать фрагменты машинных программ, чем метод, реализуемый в Форте.
Одни микропроцессоры подходят лучше для работы Форта, другие меньше, в зависимости от их стеков и возможностей косвенной адресации. Существуют несколько Форт-систем со словарями, записанными в ПЗУ (постоянное запоминающее устройство), но разработаны и более впечатляющие варианты. Так как машинные программы, используемые Фортом, при исполнении весьма коротки, имеется возможность применения микропрограмм для непосредственной реализации Форт-команд вместо стандартного набора инструкций. Если NEXT, различные исполнительные программы и EXIT можно было бы исполнять за один машинный цикл, то оценка показывает, что скорость исполнения Форта увеличилась бы более чем в 100 раз по сравнению с написанием Форт-примитивов на ассемблере для процессора той же серии. Это бы позволило микроЭВМ, ориентированным на форт, работать со скоростью больших вычислительных машин.
Не важно, каким процессором вы располагаете, в любом случае вы можете ускорить исполнение Форт-программ, переписав слова с использованием Форт-ассемблера. С помощью ассемблерного слова CODE можно описать слова со структурой, идентичной примитивам, с той же легкостью, что и в случае описания типа двоеточие (предполагается, что вы владеете программированием на ассемблере).
Этой темы мы касаемся в гл. 16.
Упражнения
1. Чтобы отображать содержимое стека возвратов (используя слово : XX R@ U. ;), опишите новые версии 1LEVEL, 2LEVEL и 3LEVEL. Что в действительности означают отображенные адреса? 2. Что случится, если вы используете команду R> DROP после сообщения в 1LEVEL ? ========================== 1 В таблице представлены исполняемые байты памяти для типов 8- битовой реализации Форта. Подчеркнутые программы выполняют полезную работу. ========================== 3. Испробуйте команду R> DROP в других точках ILEVEL и 2LEVEL, чтобы познакомиться с ее действием. Можете вы объяснить, почему было бы неразумно использовать R> DBOP в 3LEVEL? 4. Как может быть использован указатель WP в исполнительной программе для констант ? 5. Опишите CUBE? как : CUBE?? DUP SQUARE EXIT * ; и отследите состояние стека, стека возвратов, IP и WP, используя программу CUBE в качестве путеводителя. Является ли слово EXIT, когда оно используется, в точности таким же. что и в случае, когда оно встречается в конце описания типа двоеточие? 6. Вспомните, что LITERAL компилирует CFA своей исполнительной программы (часто называемой LIT) и следующее за ним число, лежащее в стеке, в любое описание, где оно использовано. Как вы полагаете, избегает Форт "исполнения" числа, скомпилированного после LIT, когда слова, где они использованы, исполняются?
Рекурсия
В гл. 8 мы обещали рассказать вам о другом типе циклов - рекурсивных циклах. Рекурсия в вычислительной терминологии означает возможность подпрограмме обращаться к самой себе. В Форте это значит, что слово обращается к самому себе в пределах своего описания. Поскольку рекурсия весьма опасна, если вы не понимаете точно, что происходит, мы отложили эту тему до тех пор, пока не поняли процесса исполнения. Рекурсия в Форте весьма проста в реализации. Все, что требуется сделать, - это скомпилировать CFA слова внутри его собственного поля параметров. Когда это CFA встретится, исполнение переключается назад к началу описания, т.е.
к началу поля параметров. В MMSFORTH и многих других версиях слово, которое это реализует, имеет имя MYSELF.
Описание в MMSFORTH имеет вид : MYSELF ?COMP LAST-CFA , ; IMMEDIATE
LAST-CFA заносит в стек CFA заголовка слова, описанного последним, в данном случае CFA самого слова. Некоторые версии используют RECURSE вместо MYSELF, другие позволяют использовать просто имя самого слова.
Причина, почему рекурсия опасна, связана с тем, что очень легко создать бесконечные циклы и довольно легко переполнить стек возвратов. Рассмотрим это: : DESTROY! MYSELF ;
Когда DESTROY! исполняется, каждый раз при обращении слова к самому себе происходят приращение указателя стека возвратов и запись в него адреса слова, следующего за словом MYSELF, в данном случае EXIT. Здесь не только нет способа выхода из цикла, но приращение указателя стека возвратов очень скоро исчерпает ресурс места в памяти, выделенной для этого. Слово MYSELF в действительности всегда используется внутри структур управления, например в конструкциях IF...ELSE...THEN... Если вы опишете : MYLOOP DUP 10 U< IF DUP . 1+ MYSELF THEN ; и затем напечатаете 5 MYLOOP вы увидите 5 6 7 8 9 С другой стороны, если вы опишете : ODDLOOP DUP 10 U< IF DUP 1+ MYSELF THEN . ; его исполнение выдаст на экран 10 9 8 7 6 5
Что случилось? Каждый раз, когда ODDLOOP обращается к самому себе, новое число на 1 больше, чем предшествующее, заносится в стек. И каждый раз увеличивается указатель стека возвратов, указывая на. .Когда достигается предел, равный 10, стек возвратов очищается последовательным исполнением слова. , при этом печатаются числа, записанные в стеке параметров. Вот более полезный пример. Факториал числа - это результат умножения числа на число меньше этого на 1 с последующим умножением произведения на число, еще раз уменьшенное на 1, и т.д. То есть факториал 5 должен быть равен 5х4х3х2х1 Теперь, если вы опишете : FACTORIAL DUP 1- DUP 2 > IP MYSELF THEN * ; Тогда 7 FACTORIAL отобразит 5040, факториал 7. Если это вас смущает, рассмотрим слово : SHOWFACT DUP .
DUP 1- DUP 2 > IF MYSELF THEN DUP . * ; которое при исполнении с 7 в стеке отобразит на дисплее 7 6 5 4 3 26 24 120 720 и, если вы затем напечатаете. , вы увидите значение факториала 5040. Небольшое исследование SHOWFACT должно прояснить вам, как работает FACTORIAL. Вы можете описать аналогичное слово с использованием do-loop, но оно будет более неуклюжим. В упражнениях вы найдете еще несколько приложений MYSELF.
Упражнения
1. Опишите FACTORIAL, используя DO-LOOP. 2. Опишите SHOWASCII так, что, когда вы напечатаете 50 80 SHOWASCII вы увидите числа и эквивалентные им ASCII-символы. Сделайте это с использованием рекурсии и do-loop. 3. Опишите слово GENERATIONS для вычисления числа поколений, необходимых одной бактерии, чтобы произвести более 2000 бактерий путем простого деления. Сделайте это, используя рекурсию и конструкцию BEGIN... . Эти упражнения показывают, что в большинстве случаев циклы предпочтительнее рекурсии.
Выводы
Не существует другого такого языка, в котором введение могло бы поднять ваш уровень понимания того, как в действительности он работает. И нет другого языка, который бы позволил вам настолько вмешиваться во внутреннюю работу его интерпретатора, компилятора и в способ исполнения программы. Если вы поняли содержание главы, а также то, как работает ваш микропроцессор, то вы уже почти в состоянии создать свою версию Форта. Может быть, эта способность языка создавать и модифицировать самого себя объясняет, почему существует так много коммерчески доступных версий Форта. Но, конечно, власть, которую предоставляет вам Форт над самим собой, несет в себе и определенную ответственность. Очень легко сделать что-то, что разрушит систему. Забудьте лишь использовать EXIT в слове без заголовка или не обеспечьте выход из рекурсивного слова и вы будете вынуждены перезагрузить ЭВМ.
Но существует даже более элементарный уровень Форта. Это использование программ на ассемблере в качестве части Форт-программы. Программирование на ассемблере в Форте так же просто и интерактивно, как и программирование на Форте, если вы знаете, как работает ассемблер.В гл. 16 мы покажем вам, как вы можете описать такие фундаментальные слова, как SWAP и DROP, описывая их в рамках Форта. Вы увидите, что можете использовать Форт для того, чтобы реализовать все возможности вашей ЭВМ.