КОМАНДЫ РЕДАКТИРОВАНИЯ СТРОК
Итак, вы уже знаете способы изменения символов и слов и можете приступить к корректированию целых строк. Р
Слово Р, которое мы уже ввели ранее, использует тот же буфер вставок, что и команда I. Предположим, что в вашем буфере вставок все еще находится фрагмент ВНОВЬ из предыдущего примера, а строка 14 все еще является текущей. Наберите на клавиатуре следующий текст: P<return>
В результате прежнее содержимое строки 14 заменяется содержимым буфера вставок и в ней теперь находится только одно слово ВНОВЬ.
Чтобы получить представление об этой команде, посмотрите три примера ее применения:1. Р ВЕСЬ ЭТОТ TEKCT<return> 2. Pbb<return> 3. P<return>
В первом примере указанная строка помещается в буфер вставок, а затем в текущую строку, во втором заполняется пробелами буфер вставок, а затем и текущая строка, и, наконец, в третьем содержимое буфера вставок вносится в текущую строку.
U
Подобную функцию выполняет и слово U, Оно помещает содержимое буфера вставок ниже текущей строки. К примеру, допустим, что ваш блок имеет вид:
Если вы передвинете курсор ко второй строке: 2 Т
а затем наберете U KAPЛИH<return> ok
U KУПEP<return> ok
то пoлучитe следующее:
Вместо того чтобы заменить текущую строку, команда U «втискивает» содержимое буфера вставок между текущей и последующими строками, передвигая их ближе к концу. Если бы в строке 15 находилась какая-то информация, она была бы вытеснена за пределы экрана и потеряна. Когда вы добавляете последовательность строк, проще иметь дело с командой U, а не Р, например: 1 Т P AДAMC<rеturn> ok
U БPAУH<return> ok
U KЬЮДАХИ<return> ok
U ДЭВИС<return> ok
Перечисленные выше три способа использования команды Р применимы также и к команде U.
X
Команда X по своему действию противоположна команде U. Она извлекает текущую строку. Если в рассмотренном выше примере мы сделаем строку 3 текущей (с помощью предложения 3 Т), а затем введем X<return>
то строка 3 будет удалена, а нижние строки передвинутся вверх:
Как видите, по команде \ извлеченная строка тоже помещается в буфер вставок. Это облегчает ее перемещение в дальнейшем. Например, последовательно вводя два предложения 9 T<return>
и P<return>
вы можете поместить КАРЛИН в строку 9.
Для того чтобы вставить новую нулевую строку, а прежнюю нулевую строку опустить ниже, нужно сначала поместить эту новую строку под нулевой: 0 T U ЭТО НОВАЯ НУЛЕВАЯ СТРОКА.<return>
а затем первые две строки поменять местами: 0 Т X U<return>
КОМАНДЫ СИМВОЛЬНОГО РЕДАКТИРОВАНИЯ
Здесь мы рассмотрим, как вставлять и удалять текст в пределах строки.
F
Прежде чем вставлять или удалять текст, вы должны суметь подвести курсор редактора (не путать с курсором Форта) к месту вставки или удаления. Наш редактор обозначает позицию курсора тем, что дает следующий за ним текст до конца строки в негативном изображении. Предположим, что текущее содержимое строки 3 таково:
и вам нужно вставить недостающую букву М в слово ПАМЯТЬ. Курсор находится в начале строки. Для того чтобы переместить его за ПА, введите команду F (FIND — НАЙТИ) с фрагментом ПА: F ПA<return>
Слово F будет просматривать текст, начиная с текущей позиции курсора, до тех пор, пока не найдет заданный фрагмент (в нашем случае ПА), после чего переместит курсор за найденный фрагмент.
I
Так как курсор подведен к требуемому месту, достаточно ввести-
и I вставит (INSERT —ВСТАВИТЬ) символ М за курсором. ЧЕМ ЛУЧШЕ КОМПЬЮТЕР, ТЕМ БОЛЬШЕ ПАМЯТЬ
Е
Чтобы удалить фрагмент с помощью команды Е (ERASE — СТЕРЕТЬ), вы должны его сначала найти, послав команду F. Например, если вы хотите удалить слово ЛУЧШЕ, в первую очередь восстановите положение курсора:
после чего введите
и далее
Слово Е удалит фрагмент, который вы только что задали в команде F:
После этого Е выведет исправленную строку:
Курсор указывает место, куда вы можете вставить другое слово:
D
По команде D (DELETE — УДАЛИТЬ) находится и удаляется заданный фрагмент. В ней фактически сочетаются две команды:
F и Е. Например, если ваш курсор находится в таком положении:
то вы можете удалить слово КОМПЬЮТЕР, набрав
Однако вы снова можете вставить фрагмент текста в то место строки, на которое указывает сейчас курсор:
Применение команды D чревато ошибками более, чем последовательности команд F и D, так как при двухшаговом способе вы сначала четко указываете, что нужно удалить, а затем удаляете.
R
По команде R (REPLACE — ЗАМЕНИТЬ) заменяется фрагмент текста, который вы только что нашли. Эта команда объединяет в себе команды Е и I. Например, если курсор показывает на фрагмент
и вы ввели F У НУЖЕН<return> R CAM<return>
то получите следующее:
Команду R нужно применять в тех случаях, когда требуется сделать вставку перед определенным фрагментом текста. Например, если вы в нулевой строке пропустили символ E:
то не так просто с помощью F найти для этой буквы место. Вы должны провести курсор через множество пробелов к требуемому (перед MPTY). В данной ситуации лучше воспользоваться таким приемом: F MPTY<return>
затем R EMPTY<return>
TILL (ДО)
Самой мощной командой удаления является TILL. Она удаляет все, начиная с текущего положения курсора до указанного фрагмента включительно. Например, после применения к строке
(заметьте, где находится курсор) команды TILL
TILL HA<return>
останется лишь текст:
Эта фраза звучит приятнее, не правда ли? С помощью TILL осуществляется поиск в пределах текущей строки, а не по всей оставшейся части блока.
КОМБИНИРОВАННЫЕ КОМАНДЫ РЕДАКТИРОВАНИЯ
N и В
Вводя слово N, вы тем самым добавляете единицу к номеру текущего блока. Таким образом, очередная набранная комбинация символов N L
вызовет печать следующего блока. Аналогично слово В
уменьшит на единицу номер текущего блока. Иными словами, комбинация В L
позволяет вам вывести на печать предыдущий блок.
В некоторых системах N объединена с командой L, так что, применяя N, мы переходим к следующему блоку и выводим его содержимое. Вы можете при необходимости переопределить команды N и В с тем, чтобы они выполнялись так же — Форт допускает переопределение команд применительно к вашим потребностям.
COPY
Слово COPY позволяет копировать содержимое одного блока в другой. При этом вытесняются любые данные, находившиеся в приемном блоке до копирования. Эта команда имеет следующий формат: откуда куда COPY
Если, к примеру, вы введете 153 200 COPY
то информация, находящаяся в блоке 153, будет скопирована в блок 200. Пусть у вас войдет в привычку вводить FLUSH после каждого выполнения COPY.
S
Команда S представляет собой расширенную версию команды F. Она дает возможность осуществлять поиск указанного текста в пределах текущего блока, а затем и в пределах последующих блоков вплоть до того блока, который вы указали в команде. Например, если текущим является блок 180 и вы ввели предложение 185 S СОКРОВИЩА
то S инициирует поиск «сокровища» в блоках начиная с 180-го и заканчивая 184-м. Если искомый фрагмент обнаруживается, то на экран выводится блок, содержащий данный фрагмент, а курсор устанавливается в его начале.
Слово S оставляет в стеке номер блока, на котором закончился поиск, так что если вам требуется продолжить поиск того же самого фрагмента, просто введите команду S.
G и BRING (ПЕРЕНОС)
Слово G берет одну строку из другого блока и вставляет ее перед текущей строкой (перемещая вни? текущую и все последующие строки).
Пусть курсор указывает на строку 3 следующего блока: 1 2 Куда ж ты, милая
3 , девалась? И где искать тебя теперь?
Пропущенный текст находится в строке 10 блока 38. Наберите на клавиатуре 38 10 8<return>
и вы получите следующее: 1 2 Куда ж ты, милая
3 пропущенная строка 4 , девалась? И где искать тебя теперь?
Слово BRING перемещает сразу группу строк. Выражение 3 S 10 14 BRING
переместит из блока 38 строки 10—14.
М
Некоторые разработчики Форта вместо команд G и BRING используют команду М, которая инициирует действия, противоположные вызываемым командой G. Она перемещает текущую строку в заданную строку экрана. 190 2 M<return>
Слово К меняет местами содержимое буферов поиска и вставок. Это полезно в тех ситуациях, когда вы случайно командой D удалили нужный вам текст. Так как удаленный текст находится в буфере поиска, вам достаточно ввести команду К, которая переместит его в буфер вставок, а затем команду I.
Можно выполнить и обратное действие. Если вы ошибочно вставили некоторый фрагмент не на свое место, переместите его посредством К в буфер поиска, а затем удалите с помощью Е.
Попытайтесь самостоятельно поменять местами два слова в одной и той же строке, используя слово К.
В ряде систем вместо того, чтобы нажимать клавишу RETURN, вы можете с помощью символа возврата каретки обозначать конец фрагмента текста и таким образом в одной строке вводить более одной команды. Например, вы можете ввести D ФРУКТЫ^ I ОРЕХИ<return>
Весь текст разместился в одной строке, а результат вы получите такой же, как если бы вы набрали на клавиатуре следующее: D ФРУКТЫ<return>
И I ОРЕХИ<return>
Итак, мы рассмотрели команды редактора. Поскольку Форт по своей природе — язык гибкий, а пользователи при необходимости могут определять собственные команды редактирования, набор команд редактора в вашей системе может отличаться от набора, описываемого в настоящей книге. В конце раздела приводятся все команды, о которых здесь шла речь.
В заключение следует отметить, что редактор применительно к Форту это не программа, как, возможно, принято в других языках, а, скорее, набор слов.
Его часто называют словарем, но к вопросу о словарях мы вернемся позднее.
Определение местоположения исходного текста (полезный прием).
В некоторых Форт-системах имеется слово LOCATE (ОПРЕДЕЛИТЬ-МЕСТОПОЛОЖЕНИЕ) или VIEW. Если вы введете следующее предложение:
LOCATE РАЗМЕР-ЯИЦ
то получите распечатку текста блока, содержащего определение РАЗМЕР-ЯИЦ. При этом указанное слово должно быть загруженным, т. е. находиться в словаре з данный момент. (В отдельных системах вы можете находить местоположение выборочных системных определений и слов вашей прикладной программы, но вы не имеете права определять местоположение слов из предварительно скомпилированного участка.)
Ниже следует перечень слов Форта, приведенных в настоящей главе:
FORGET имя ( -- ) | С помощью этого слова мы забываем ( удаляем иа словаря ) указанное слово и все слова, внесенные в словарь после него. |
LIST ( n -- ) | Вывод на экран дискового блока. |
LOAD ( n -- ) | Загрузка дискового блока ( компиляция или выполнение ) . Блок 0 обычно загружен быть не может. |
FLUSH ( -- ) | Запись всех обновленных дисковых буферов на диск, после чего освобождение этих буферов. |
\ ( -- ) | Пропуск оставшегося текста данной строки. |
\S ( -- ) | Пропуск оставшегося текста экрана. |
THRU ( мач ком — ) | Загрузка всех блоков с номерами из диапазона от нач до кон |
.( текст) ( -- ) | Вывод текста сообщения, ограниченного правой круглой сковкой. Используется, как правило, за пределами определения через двоеточие. |
THRIAD ( n -- ) | Вывод трех блоков с номерами, включающими n, начиная с номера, делящегося вез остатка на 3. |
SHOW ( нам кон -- ) | Вывод блоков с номерами из диапазона от нач до кон по три блока. |
INDEX ( нам кон --) | Вывод комментария только для блоков , номера которых входят в диапазон от н а ч до кон. |
LOCATE xxx ( -- ) или VIEW | Вывод содержимого блока, иэ которого было загружено определение слова ххх. |
Команды редактирования | - работа со строками |
Т ( n -- ) | Вывод заданной строки. |
Р ( — ) Рьь или Р XXX | Копирование заданного фрагмента, еcли есть, в буфер вставок, после чего помещение копии буфера вставок в текущую строку. |
U ( -- ) Uьь или U ххх |
Копирование заданной строки, если есть, в буфер вставок после чего помещение копии буфера вставок в строку, следующую за текущей. |
G ( блок строка ) | Копирование заданной строки и помещение ее в строку перед текущей, со сдвигом текщей и всех последующих строк вниз. |
BRING ( блок нам кон ) | Получение строк в указанном диапазоне. |
X ( — ) | Копирование текщей строки в буфер вставок и извлечение этой строки из блока. |
F или ( — ) F ххх | Копирование указанной строки, если заданы, в буфер поиска, после чего поиск данной строки в текущем блоке. |
S или S xxx | ( n - ) или ( n - n ) | Копирование указанной строки, если задана, в буфер поиска, после .чего просмотр блоков от текущего до n-ного в поисках указанной строки. Если строка найдена, на стек помещается номер последнего просмотренного блока. |
E | ( - ) | Используется следом за F. Удаляется столько символов перед курсором, сколько их в данный момент находится в буфере поиска. |
I> или Б ххх | ( - ) | Копирование указанной строки, если задана, в буфер поиска, поиск очередного вхождения этого фрагмента в текущей строке и удаление его. |
TILL или TILL xxx | ( - ) | Копирование указанной строки, если задана, в буфер поиска, после чего удаление всех символов, начиная от курсора и заканчивая последним символом заданной строки. |
I или I XXX |
( - ) | Копирование умазанного фрагмента, если задан, в буфер вставок, после чего помещение содержимого буфера вставок сразу же после курсора. |
R или R xxx | ( - ) | Объединяются команды Е и I. Замещение найденного фрагмента заданным фрагментом или содержимым буфер вставок. |
^ | ( - ) | Отметка конца текста, помещаемого в буфер. |
WIPE | ( -- ) | Заполнение текущего блока пробелами. |
L | ( -- ) | Вывод содержимого текущего блока. |
N | ( -- ) | Делается текучим следующий блок. |
В | ( -- ) | Делается текущим предыдущий блок. |
COPY | ( откуда куда -- ) | Копирование содержимое одного блока в другой. |
К | ( -- ) | Меняются местами содержимое буфера поиска и буфера вставок. |
КОМПЬЮТЕР «ПРИНИМАЕТ РЕШЕНИЯ»
В этой главе вы научитесь писать программы таким образом, чтобы компьютер мог «принимать решения». Теперь вы будете воспринимать свой компьютер как довольно сложное устройство, а не просто как обычный калькулятор.
КОНСТАНТЫ
Если в переменных обычно хранятся значения, которые могут изменяться, то константы используются для хранения значений, которые изменению не подлежат. На Форте мы создаем константу и тут же устанавливаем ее значение, например:
220 CONSTANT ПРЕДЕЛ
Здесь определена константа с именем ПРЕДЕЛ, которой присвоено значение 220. Теперь мы имеем право подставлять слово ПРЕДЕЛ вместо значения
: ?ЖАРКО ( температура -- ) ПРЕДЕЛ > IF ." Опасно — Уменьшите нагрев ! " THEN ;
В том случае, когда число в стеке больше 220, выдается предупреждающее сообщение. Заметьте, что, говоря ПРЕДЕЛ, мы получаем значение, а не адрес. Нам здесь не требуется «выборка». В этом и состоит основное отличие переменных от констант. Дело в том, что при работе с переменной нам нужен адрес, чтобы иметь возможность как выборки значения, так и его запоминания. При использовании же константы всегда требуется значение (мы ни-когда в нее ничего не запоминаем).
Одним из примеров применения констант может служить именование аппаратного адреса. Допустим, что программа для управления фотокамерой с помощью микропроцессора содержит следующее определение:
: СНИМОК ЗАТВОР ОТКРЫТЬ БРЕМЯ ВЫДЕРЖАТЬ ЗАТВОР ЗАКРЫТЬ ;
Здесь слово ЗАТВОР определено как константа при условии, что его выполнение обеспечивает аппаратный адрес затвора фотокамеры. Оно может быть определено так:
HEX ЗЕ27 CONSTANT ЗАТВОР DECIMAL
Слова ОТКРЫТЬ и ЗАКРЫТЬ могут быть просто определены:
: ОТКРЫТЬ ( а -- ) 1 SWAP ! ; : ЗАКРЫТЬ ( а -- ) 0 SWAP ! ;
так что выражение ЗАТВОР ОТКРЫТЬ запишет единицу по адресу затвора, и он откроется.
Использование в определениях констант, а не изображений самих чисел является важным элементом хорошего стиля программирования. Прежде всего наличие констант делает вашу программу более читабельной. Все определения Форта должны быть так же самодокументированы, как определение СНИМОК.
Не менее существенно и то, что значения могут изменяться (могут изменяться, к примеру, аппаратные средства).
Если в такой ситуации вам достаточно внести изменения в один фрагмент программы — в определение константы, — то все остальное будет сделано автоматически, без вероятных пропусков, как это имело бы место при корректировке вручную.
Третье преимущество заключается в том, что в компилируемой форме определение, содержащее константу, занимает меньший объем памяти, чем то же определение, но с изображением числа вместо константы. Если некоторое число применяется неоднократно, то получаемый при этом выигрыш перекрывает расходы на описание константы. Поэтому во многих Форт-системах часто повторяющиеся числа определены как константы:
0 CONSTANT 0 1 CONSTANT 1 и т.д.
В дальнейшем будем считать, что в вашей системе имеются следующие определения констант FALSE (ЛОЖЬ) и TRUE (ИСТИНА):
0 CONSTANT FALSE -1 CONSTANT TRUE
CONSTANT xxx ( n -- ) Создание константы с именем xxx и xxx: ( -- n) значением n. Слово xxx при своем выполнении заносит в стек n. FALSE ( -- f) Занесение в стек логического значения ложь ( 0 ). TRUE ( -- t) Занесение в стек логического значения истина ( —1 ).
КОНТЕКСТНЫЕ СЛОВАРИ (СПИСКИ СЛОВ)
В простой Форт-системе имеются три штатных контекстных словаря: словарь Форта, словарь редактора и словарь ассемблера.
Все рассмотренные выше слова принадлежат словарю Форта, за исключением команд редактора, которые относятся к словарю редактора. В словарь ассемблера включены команды, предназначенные для программирования на языке Ассемблера конкретного компьютера.
Определения добавляются в один и гот же словарь в порядке их компиляции, независимо от того, к какому контекстному словарю они принадлежат. Таким образом, контекстные словари являются не подразделами словаря как участка памяти, а связанными списками, переплетенными между собой внутри этого общего словаря1.
В качестве примера рассмотрим три контекстных словаря: «футбол», «бейсбол» и «баскетбол» (см. рисунок). Все они совместно существуют в одном и том же общем словаре, однако апостроф, проходя по цепи, имеющей отношение, скажем, к баскетболу, пере-
1 Необходимо отличать общий словарь (dictionary) как прерывный участок памяти для размещения слов от словаря (vocabulary) - связанного списка слов. Примерами последнего могут служить словари Форта, редактора и ассемблера. - Примеч. ред.
бирает только слова из баскетбольного словаря. Даже если в каждом контекстном словаре есть слово ЦЕНТР, апостроф подберет его вариант, требуемый для данного контекста.
Помимо замкнутости контекстных словарей, необходимо отметить еще одно преимущество такой организации данных - скорость поиска. Если в нашем примере идет речь о баскетболе, то зачем нам перебирать слова, относящиеся к футболу и бейсболу?
Вы можете переключить контекст поиска по словарю, выполнив любую из трех команд: FORTH, EDITOR или ASSEMBLER. Например, вводя слово FORTH, вы уверены, что поиск будет осуществляться в контексте словаря Форта. Но, как правило, Форт-система изменяет для вас контекст автоматически. Рассмотрим типичную схему. Система начинает работу с контекста словаря Форта. Допустим, вы заносите некоторую программу в блок.
Конкретные команды редактора переключают контекст на словарь редактора. Вы работаете в контексте словаря редактора до тех пор, пока не осуществите загрузку вашего блока и не приступите к компиляции определений. Слово : автоматически восстанавливает контекст, который был ранее, а именно: Форт.
Различные версии Форт-систем имеют различные реализации контекстных словарей. Однако существуют некоторые общие положения, которые можно распространить на большинство систем.
Словарь, где должен осуществляться поиск, определяется пользовательской переменной с именем CONTEXT (КОНТЕКСТ). Как
уже упоминалось выше, команды FORTH, EDITOR и ASSEMBLER изменяют контекст поиска.
Известен еще один вид контекста: словарь, к которому должно быть присоединено новое определение. Такой словарь задается другой переменной с именем CURRENT (ТЕКУЩИЙ). Поскольку CURRENT обычно определяет словарь Форта, то новые определения, как правило, присоединяются к данному словарю.
А каким образом система компилирует слова в словари редактора и ассемблера? Это делается с помощью слова DEFINITIONS (ОПРЕДЕЛЕНИЯ), например: EDITOR DEFINITIONS
Вы знаете, что слово EDITOR устанавливает CONTEXT для редактора. Слово DEFINITIONS копирует содержимое слова CONTEXT, каким бы оно ни было, в слово CURRENT. Слово DEFINITIONS имеет простое определение:: DEFINITIONS CONTEXT @ CURRENT ! ;
После вывода выражения EDITOR DEFINITIONS все компилируемые с этого момента слова заносятся в словарь редактора до тех пор, пока вы не введете выражение FORTH DEFINITIONS, чтобы поместить в CURRENT Форт.
Техника работы с контекстными словарями существенно зависит от конкретной системы и в какой-то степени противоречива. В Стандарте-83 подробности опущены, а потому обойдемся без них и мы. Обращайтесь к документации по своей системе.
' ххх | ( -- а) | Осуществляется поиск в словаре адреса слова ххх ( следующего слова из входного потока ) . |
['] | период-компиляции: ( -- ) период-выполнения: ( -- a) | Используется только в определении через двоеточие. Компиляция адреса следующего слова из определения как литерала. |
EXECUTE | ( a -- ) | Выполнение элемента словаря, адрес поля параметров которого находится на стеке. |
@EXECUTE | ( a -- ) | Выполнение элемента словаря, на pfa которого ссылается содержимое по адресу а. Если адрес содержит нуль, то @EXECUTE ничего не выполняет. |
>BODY | ( cfa -- pfa) | Вычисление адреса поля параметров определения, адрес компиляции которого находится на стеке. |
EXIT | ( -- ) | Удаление адреса возврата, из вершины стена возвратов и занесение его в указатель адресного интерпретатора. Если слово EXIT скомпилировано в определении через двоеточие, то оно завершает выполнение этого определения в данной точке. |
QUIT | ( -- ) | Очистки стека возвратов и передача управлений терминалу, ожидающему ввода. Сообщения при этом ни выдается. |
ABORT | ( -- ) | Очистке стека данных и выполнение функций слова QUIT. Сообщения не выдаются. |
H или DP | ( -- a) | Занесение в стек адреса указателя словаря. |
HERE | ( -- a) | Занесение в стек адреса очередного доступного участка словаря. |
PAD | ( -- a) | Занесение в стен адреса начала рабочей области, в которой хранятся строки символов в процессе промежуточной обработки. |
SP@ или 'S | ( -- a) | Занесение в стек адреса вершины стека данных до того, как исполнено сама слово SP@. |
S0 | ( -- a) | Содержится адрес дна стека данных. |
TIB | ( -- a) | Занесение в стек адреса начала буфера входного текста. |
ЛИТЕРАТУРА
1. Ham, Michael, "Think Like a User, Write Like a Fox," Forth Dimensions, VI/3, p. 23.
Про ошибки на сайте обязательно сообщите .
1. "Add a Break Point Tool," Forth Dimensions, Vol. V, No. 1, p. 19.
Про ошибки на сайте обязательно сообщите .
1. Cassady. John J , "8080 Assembler," Forth Dimensions, III/6, p. 180.
2. Duncan, Ray. "Forth 8086 Assembler," Dr. Dobb's Journal, 09/05, pp. 28-35, May 1984
3. Perry, Michael A., "A 68000 FORTH Assembler," Dr. Dobb's Journal, 08/09, pp. 28-43, September 1983.
4. Ragsdale, William F.. "A FORTH Assembler for the 6502," Dr. Dobb's Journal, 06/09, pp. 12-24, September 1981; reprinted in Forth Dimensions, III/5, pp 143-50, January/February 1982
Про ошибки на сайте обязательно сообщите .
[1] Brodie, Leo, Thinking Forth (Englewood Cliffs, N.J.: Prentice-Hall, 1984).
Про ошибки на сайте обязательно сообщите .
1. Bowhill, Sidney A., "A Variable-Precision Floatmg-Point System for Forth," 1983 FORML Conference Proceedings, Asilomar, California.
2. Bumgarner, John O., and Jonathan R. Sand, "Dysan IEEE P-754 Binary Floating Point Architecture," 1983 Rochester Forth Conference Proceedings, pp. 185-94.
3. Jesch, Michael, "Floating Point in FORTH?" 1981 FORML Conference Proceedings, pp. 61-78; reprinted as "Floating Point FORTH?", Forth Dimensions, 4/1, May-June 1982, pp. 23-25.
4. Harwood, James V., "FORTH Floating Point," 1981 Rochester Forth Standards Conference Proceedings, p. 189,
5. Monroe, Alfred J., "Forth Floating-Point Package," Dr. Dobb's Journal, 7/9, September 1982, pp. 16-29.
6. Petersen, Joel V., and Michael Lennon, "NIC-FORTH and Floating Point Arithmetic," 1981 Rochester Forth Standards Conference Proceedings, pp. 213-17.
7. Duncan. Ray, and Martin Tracy, 'The FVG Standard Floating-Point Extension," Dr. Dobb's Journal, 9/9, September 1984, pp. 110-15.
8. Redington, Dana, "Forth and Numeric Co-processors: An Extensible Way to Floatingpoint Computation," Conference Proceedings of the Eighth WCCF, 3, pp. 368-73, March 18-20, 1983.
9. Redington, Dana, "Stack-Oriented Co-Processors and Forth," Forth Dimensions, 5/3 September-October 1983, pp. 20-22.
Про ошибки на сайте обязательно сообщите .
МАНИПУЛЯЦИИ СО СТЕКОМ
Если вы пытались решить задачу 6 в последнем упражнении, то вам уже ясно, что выражение в инфиксной форме a - b ----- c
нельзя преобразовать в форму определения Форта без того, чтобы каким-то образом не поменять местами значения в стеке. «Каким-то образом» — это значит, что вы должны выполнить операцию преобразования стека, а именно перестановку SWAP.
1 Для тех, кто имеет склонность к математике. Хотите верьте, хотите нет, но в информатике мы сталкиваемся с противоречиями при решении даже такой простой задачи, как «—32 разделить на 7». Результатом может быть либо —4 при -остатке -3 (-4 х 7 = -28; -28 + — 3 = 31), либо -5 при остатке 4 (-5 х 7 = -35; -35 + 4 = 31). Группа по разработке стандарта Форт-83 приняла решение о том, что при выполнении операций деления частное не должно округляться. Иными словами, дзух целых чисел, между которыми находится дробное частное, выбирается меньшее. В нашем примере -5 меньше, чем -4, поэтому выбирается -5. При делении без округления частного знак остатка совпадает со знаком делителя. Таким образом, если мы делим -31 на 7 в среде Форт-83, то получаем частное -5 и остаток 4, Это правило относится к делению чисел со знаком и не приводит к противоречиям в окрестности нуля.
МАССИВЫ
Как вам уже известно, выражение VARIABLE ДАТА создает определение и выделяет память для значения одинарной длины:
А если необходимо выделить память для 10 или 20 значений одинарной длины под одним и тем же именем? Такая структура называется массивом. На форте массив строится следующим образом.
В первую очередь мы создаем массив с помощью непривычного определяющего слова CREATE (СОЗДАТЬ)1: CREATE МОЙ-МАССИВ
1 для пользователей систем фиг-Форта. В вашей системе имеется слово CREATE, но оно отличается от упоминаемого здесь и редко используется. Чтобы вы могли и далее следить за ходом событий, переопределите его следующим образом:
: CREATE <BUILD DOES> ;
Как и VARIABLE, CREATE компилирует в словарь новое имя (вашего массива) вместе с кодами, которые специфицируют его действия. Но при этом память под данные не выделяется.
Каким образом мы впоследствии отведем память под созданный массив? Это делается посредством слова ALLOT (ВЫДЕЛИТЬ), которое выбирает из стека в качестве аргумента число байтов, резервируемое для массива.
Если требуется выделить память под 10 значений одинарной длины, то нужно ввести 20 ALLOT
(Значение одинарной длины занимает два байта.)
Когда вы исполняете слово, определенное как переменная Форт-система помещает в вершину стека адрес значения. Таким же образом, когда вы исполняете слово, созданное с помощью CREATE, в вершину стека заносится адрес начала массива (первого значения).
Проиллюстрируем применение массива на следующем примере Предположим, что в нашей лаборатории имеется не одна, а пять горелок, на которых нагреваются различные жидкости.
Мы можем с помощью слова ?ЖАРКО проверять, не превышает ли температура нагрева каждой из пяти горелок установленного для нее максимального значения, если определим ПРЕДЕЛ не как константу, а как массив. Присвоим этому массиву имя ПРЕДЕЛЫ:
Допустим, что мы устанавливаем предельное значение температуры для горелки 0 220°. Запомним это значение посредством следующей фразы: 220 ПРЕДЕЛЫ !
МАССИВЫ БАЙТОВ
Форт позволяет создать массив, каждый элемент которого содержит не полную ячейку, а один байт. Это полезно в тех случаях, когда вы запоминаете ряд чисел, представляемых восемью битами.
Диапазон значений 8-разрядного числа без знака — от 0 до 255. Байтовые массивы могут также служить для хранения строк символов в коде ASCII. Преимущество массива байтов перед массивом ячеек заключается в том, что при его применении вы можете иметь тот же объем данных при половинном объеме памяти.
Механизм использования байтового массива тот же, что и массива ячеек, за исключением двух положений:
1) вы не должны удваивать смещение, так как каждый элемент соответствует одному адресу;
2) слова! и Р нужно заменить словами С! и С@ . Этим, словам, которые функционируют только с байтовыми значениями, дан префикс С, потому что обычно они обеспечивают доступ к символам в коде ASCII.С! ( b a -- ) Занесение 8-разрядного числа по заданному адресу. С@ ( а -- b ) Выборка 8-разрядного числа по заданному адресу.
так как ПРЕДЕЛЫ доставляет адрес первой ячейки нашего массива. Установим предельное значение температуры для горелки
1 равным 340° и запомним его, добавив два байта к адресу исходной ячейки:
Мы можем запомнить предельные значения для горелок 2, 3 и 4, добавляя к исходному адресу «смещения» 4, 6 и 8. Так как смещение всегда равно удвоенному номеру горелки, определим полезное слово: : ПРЕДЕЛ ( номер-горелки -- адрес-предельного-значения) 2* ПРЕДЕЛЫ + ;
чтобы по номеру соответствующей горелки, находящемуся в стеке, вычислять адрес, который отстоит от начала на величину соответствующего смещения1.
После всех преобразований полезность слова ПРЕДЕЛ возросла в такой степени, что мы можем переопределить слово ?ЖАРКО: : ?ЖАРКО ( температура номер-горелки — ) ПРЕДЕЛ @ > IF , " Опасно — Уменьшите нагрев! " THEN ;
1 1 Для начинающих. В этом случае номер горелки называется индексом массива. Индекс — это относительный указатель элемента памяти Умножая индекс на 2, мы получаем смещение относительно начала массива Смещение равно фактическому числу байтов между началом массива и искомым элементом.
2. Мы нумеруем горелки с 0 до 4, вместо того чтобы нумеровать их с I до 5. по той причине, что хотим использовать сами номера горелок в качестве индексов 1о, что большинство людей называют «первым» в какой-то последовательности, программисты называют «нулевым» При желании вы можете пронумеровать горелки с 1 по 5, но тогда вам придется добавлять в определении слов самого высокого уровня корректирующий фрагмент (простое выражение «1-»), как это всегда делается.
Примеры выполнения нового варианта этого слова приведены ниже:210 0 ?ЖАРКО ok
230 0 ?ЖАРКО Опасно — Уменьшите нагрев! ok
З00 1 ?ЖАРКО ok З50 1 ?ЖАРКО Опасно — Уменьшите нагрев! ok
и т.д.CREATE xxx ( -- ) Создание заголовка в словаре с именем xxx. xxx: ( -- a) Слово xxx при выполнении заносит в стек свой адрес
ALLOT ( n -- ) Резервирование в поле параметров слова, определенного последним, n дополнительных байт.
МУЛЬТИЗАДАЧНЫЕ ФОРТ-СИСТЕМЫ
Наряду с однозадачными существуют и мультизадачные Форт-системы1. Они могут работать с произвольным числом задач. Задача может быть либо терминальной, при выполнении которой вся интерактивная мощь Форта передается оператору, сидящему за терминалом, либо управляющей, которая обеспечивает управление аппаратным средством, не имеющим терминала.
Любой из задач нужна своя пользовательская область. Размер и содержимое пользовательской области зависят от вида задачи. Типовые структуры для двух видов задач показаны на рисунке.
Каждой терминальной задаче требуется собственный словарь, рабочая область (PAD), стек данных, буфер входного текста, стек возвратов и пользовательские переменные. Это означает, что все определяемые вами слова, как правило, недоступны другим задачам. Кроме того, все задачи имеют свои собственные копии пользовательских переменных, таких, как BASE.
Управляющая задача имеет пару стеков и небольшой набор пользовательских переменных. Так как при выполнении управляющей задачи не используется терминал, ей не требуются ни собственный словарь, ни рабочая область, ни буфер входного текста.
НЕМНОГО ЛОГИКИ
Форт (как и большинство языков программирования) дает вам возможность комбинировать флаги. Возьмем, к примеру, комбинацию по принципу «или». Даны значения двух флагов. Если хотя бы одно из них истинно, то Форт-система выполнит действие, если оба значения ложны — не выполнит.
Поясним изложенное на следующем примере/Допустим, вы хотите вывести имя АРТИШОК в том случае, если исходное число либо отрицательно, либо кратно 10. Как это сделать на Форте? Рассмотрим выражение:
Ниже приводятся значения, которые получаются при исходном значении, скажем равном 30:
Флаги логически складываются? Что же происходит при таком сложении флагов? Вы получаете в результате истину, если хотя бы один из флагов (или сразу оба) истинны. Ниже приводятся четыре возможных сочетания двух флагов и результаты операции OR (ИЛИ) над ними.
Определение в нашем простом примере, следовательно, будет выглядеть так: : ОВОЩ ( n) DUP 0< SWAP 10 MOD 0= OR IF ." Артишок " THEN ;
Ниже приводится улучшенный вариант приведенного выше оп-ределения слова ?ДЕНЬ. Предыдущее определение браковало только элементы, значение которых превышало 31, а отрицатель-ные значения не допускались вовсе. : ?ДЕНЬ ( день) DUP 1 < SWAP 31 > OR IF ." Ошибка " ELSE ." Спасибо " THEN ;
(Во многих Форт-системах для таких случаев предусмотрено слово WITHING (В ИНТЕРВАЛЕ). См. вопросы в конце главы.)
Другой комбинацией флагов является комбинация по принципу И. Здесь для получения истины оба флага должны быть истинны. Например, чтобы сделать сквозняк, нужно открыть обе двери: входную И выходную. Сравните с комбинацией ИЛИ: если входная ИЛИ выходная дверь (или сразу обе) будут открыты, то налетят мухи1.
Форт включает слово AND (И), Ниже приводится таблица результатов операции AND над двумя флагами:
1 Для любознательных и начинающих. Применение слов, аналогичных «или»' и «и», при создании некоторых фрагментов прикладных программ называется логикой. Нотация для логических операций была разработана в XIX в. Дж. Булем.
Этот раздел математики теперь называют булевой алгеброй. Таким образом, термин «булевский флаг» (или даже «булевская переменная») просто обозначает флаг, который будет использован в логической операции.
Иными словами, только пара значений «истина» дает в результате истину.
Допустим далее, что мы подбираем картонный ящик для дисковода, имеющего следующие параметры: высота — 6 дюймов, ширина — 19 дюймов, длина — 22 дюйма. Для того чтобы дисковод поместился в ящик, должны быть выдержаны все параметры: и высота, и ширина, и длина. Если параметры ящика заданы в стеке, то можно написать следующее определение: : ОБЪЕМ ( длина ширина высота -- ) 6 > ROT 22 > ROT 19 > AND AND IF ." Подходит " THEN ;
Проверить слово ОБЪЕМ можно следующей фразой:23 20 7 ОБЪЕМ Подходит ок
Третий вид комбинации флагов называется исключающим ИЛИ. В Форте эта операция выполняется словом XOR. Результат получается истинным только в тех случаях, когда хотя бы один из флагов истинен, но не оба сразу.
Ниже приводится пример применения такой операции. Слово ?ЗНАКИ выбирает из стека два числа. Если их знаки совпадают (оба числа положительны или оба отрицательны), то выдается сообщение «Знаки совпадают», в противном случае выдается сообщение «Знаки разные»:: ?ЗНАКИ ( nl n2 — ) 0< SWAP 0< XOR IF ." Знаки разные " ELSE ." Знаки совпадают • THEN ;
По мере усложнения ваших прикладных программ вы можете писать операторы Форта в виде текста на естественном языке в постфиксной форме, что очень легко читается. Только определите где-нибудь отдельные слова, находящиеся внутри определения, которые будут проверять некоторое условие и оставлять в стеке флаг. Следующий пример: вам предстоит выполнить фотосъемку, но прежде необходимо проверить освещенность И наличие пленки в фотоаппарате:: ФОТОСЪЕМКА ?СВЕТ ?ПЛEHKA AND IF СНИМОК THEN ;
Другой фрагмент, который мажет быть использован в программе обработки данных:: СЛУЖБА-ЗНАКОМСТВ C-ЮМОPOM ОТЗЫВЧИВЫЙ AND ЛЮБИТ-ИСКУССТВО ЛЮБИТ-МУЗЫКУ OR AND КУРИТ NOT AND IF ." У нас имеется подходящая для вас кандидатура " THEN ;
Здесь такие слова, как С-ЮМОРОМ и ОТЗЫВЧИВЫЙ, предназначены для выполнения проверки записи из дискового файла, содержащего информацию о возможных партнерах.
Обращаем ваше внимание на то. что до тех пор, пока мы не oбясним некоторые тонкости использования слов NOT, OR, AND, и XOR, их следует применять только с аргументами, являющимися логическими флагами, т.е. с нулем или отрицательной единицей. Все рассмотренные выше операции сравнения (за исключением операции « — », применяемой вместо «не равно») оставляют в стеке логические флаги Поэкспериментируйте, чтобы посмотреть, как выполняются команды NOT, OR, AND и XOR с числами, отличными от 0 и -1
НЕСКОЛЬКО ДОПОЛНИТЕЛЬНЫХ СЛОВ УПРАВЛЕНИЯ КОМПИЛЯЦИИ
Как вы помните, число, которое появляется в определении через двоеточие, называется литералом (самоопределенным). Например, число 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
ОГРАНИЧЕНИЯ НА ВЫПОЛНЕНИЕ ЦИКЛА
Есть несколько важных правил, которых нужно придерживаться, программируя циклы DO. Прежде всего в операторе DO начальный индекс никогда не должен совпадать с границей. Если вы хотите, чтобы цикл не был выполнен ни разу, и определили его следующим образом: : ОШИБКА 10 10 DO I . LOOP ;
то не получите ожидаемого результата. Результат будет таков:
К сожалению, LOOP уже переступил финишную черту и не попадет под электронный глаз в течение длительного времени (до тех пор, пока не вернется назад через 65535 шагов)1.
Если вы не уверены в том, что граница и индекс в каком-то вашем определении не совпадут, используйте вместо DO слово ?DO, которое, как и первое, берет из стека (границу индекс --), но сразу передает управление LOOP в том случае, когда граница и индекс совпадают.
Возвращаясь к слову STARS из гл. 1, напишем его правильное определение: : STARS ( число-звездочек) 0 ?DO 42 EMIT LOOP ;
При отсутствии в вашей системе слова ?DO используйте следующее определение:: STARS ( число-звездочек) ?DUP IF О DO 42 EMIT LOOP THEN ;
С какими неприятностями вы еще можете столкнуться? В большинстве Форт-систем при выполнении цикла DO текущие значения счетчика хранятся в стеке возвратов (введенном в гл. 5). Это при-
1 Для пользователей систем, созданных до принятия Стандарта-83. В более старых системах цикл выполнился бы один раз (вместо 64К раз), но и это не то, что вам нужно.
водит к некоторым ограничениям. В частности, слово I можно употреблять только в том определении, в котором используются слова DO и LOOP. Вы не имеете права ввести такой текст: : ТЕСТ I . ; : ДЕКАДА 10 0 DО ТЕСТ LOOP ;
Если вспомогательное определение (ТЕСТ) требует значения индекса, вы должны передать его через стек, например: : ТЕСТ ( индекс -- ) . ; : ДЕКАДА 10 0 DO I ТЕСТ LOOP ;
Отметим еще несколько ограничений. Вы не должны перед словом DO помещать в стек возвратов с помощью слова >R промежуточные значения с тем, чтобы использовать их внутри цикла. Если же вы запрограммировали внесение некоторого значения в стек возвратов внутри цикла, то обязаны перед выходом из цикла, до начала выполнения LOOP (или слова LEAVE, которое мы рассмотрим ниже), запрограммировать его удаление словом R>.
ОКРУГЛЕНИЕ
Из предыдущего примера вытекает другой вопрос: как производить округление? Решим такую задачу: сколько бананов нужно завезти в школьный буфет, если он рассчитан на 225 человек, и 32% из них обычно покупают бананы? Естественно, нас интересуют только целые бананы, так что в случае получения нецелого результата его нужно округлить. Сейчс слово % определено так, что все цифры справа от десятичной точки просто отбрасываются. Иными словами, результат «усекается»: 32% ОТ РЕЗУЛЬТАТ 225 = 72.00 72 - абсолютно правильный 226 = 72.32 72 - правильный, округлен в меньшую сторону (усечен) 227 = 72.64 72 - усечен, но не округлен
Существует, однако, способ, при котором любое десятичное значение от 0,5 и выше, полученное в остатке, округляется до следующего целого. Для того чтобы найти «округленные проценты», мы должны определить слово R%: : R% ( n % -- результат ) 10 */ 5 + 10 / ;
так что теперь выражение 227 32 R% .
даст в результате 73, т. е. правильно округленное до следующего целого числа.
Заметьте, что сначала мы делим на 10, а не на 100. При этом у нас появляется возможность добавить цифру 5 в позицию единиц числа, полученного в результате выполнения операции */. ОПЕРАЦИЯ СОДЕРЖИМОЕ СТЕКА
227 32 10 */ 726 5 + 731 10 / 73
Окончательное деление на 10 приводит число к надлежащему виду, в чем вы можете убедиться самостоятельно1.
Как недостаток такого способа округления необходимо отметить, что вы теряете одну значащую цифру в конечном результате, а именно вместо 32,767 мы можем получить только 3,276 Но если для вас это важно, вы всегда можете воспользоваться числами двойной длины (они будут рассмотрены позднее), которые также можно округлять.
ОПЕРАЦИИ ДЕЛЕНИЯ
Слово / (слэш) отображает самую простую операцию деления в Форте. Слэш обеспечивает только частное; если в результате деления образуется остаток, он теряется. Набрав на клавиатуре 22 4 / . 5 ok
вы получите только частное (пять) и не получите остаток (два). Если же вы хотите выполнить операцию деления, аналогичную предусмотренной в карманном калькуляторе, такой результат вас не удовлетворит, тем более что Форт не позволяет даже округлить полученное целое число.
Но / — всего лишь одна из нескольких операций деления в Форте. Такое разнообразие операций дает пользователю широкие возможности. Во многих задачах округленный результат или результат двойной точности вам и не требуется. Допустим, вам нужно решить такую задачу: сколько банкнот достоинством в один доллар получается при размене 22 четвертей доллара? Ответ очевиден: пять (а не 5.5). Машинный меняла, к примеру, не будет знать, как выдать вам 5.5 дол. Здесь необходимы операции, подобные операции /, но вычленяющие целые частное и остаток: MOD ( n1 n2 -- n-остаток ) Деление. В стек помещается остаток от деления.
/MOD ( n1 n2 -- Деление. В стек помещаются n—остаток n-частное ) остаток и частное.
Итак, если вам требуется только частное, применяйте операцию /, если вам требуется только остаток — операцию MOD1, а если и остаток, и частное — операцию /MOD.
Выполним в качестве примера операцию /MOD:22 4 /MOD . . 5 2 ok
1Для любознательных. MOD — сокращение от modulo, что означает "остаток".
Полученных вами знаний уже достаточно для того, чтобы легко написать следующий набор определений: : ДОЛ-ЧЕТВЕРТИ ( четверти -- четверти доллары ) 4 /MOD ; : .ДОЛЛАРЫ ( доллары -- ) . ." долларов * ; : .ЧЕТВЕРТИ ( четверти — ) . ." четверти " ; : ЧЕТВЕРТИ ( четверти — ) ДОЛ-ЧЕТВЕРТИ ." Получается" .ДОЛЛАРЫ ." и " .ЧЕТВЕРТИ ;
Далее вы можете ввести 22 ЧЕТВЕРТИ
и получить 22 ЧЕТВЕРТИ Получается 5 долларов и 2 четверти ок
В Стандарте Форт-83 во всех операциях деления частное
Что же делать, если в прикладных программах требуется округление? Не беспокойтесь — нужные средства легко создаются, как вы увидите в разд. «Округление» гл. 5, путем комбинирования и расширения элементарных арифметических операций.
ОПЕРАЦИИ НАД ЧИСЛАМИ ДВОЙНОЙ ДЛИНЫ
Слова для выполнения операций над числами двойной длины имеются не во всех Форт-системах. В некоторые системы они включены на правах выборочных, т. е. прежде чем ими пользоваться, вы должны их загрузить.
Ниже приводится перечень слов двойной длины для выполнении математических операций. D+ ( dl d2 -- d-сумма) Сложение двух 32-разрядных чисел.
D- ( dl d2 -- d-разность) Вычитание одного 32-разрядного числа ив другого (dl-d2).
DNEGATE ( d -- -d) Изменение знака 32-разрядного числа на противоположный.
DABS ( d1 -- |d|) Занесение в стек абсолютного значения 32-разрядного числа.
DMAX ( d1 d2 -- d-max) Занесение в стек максимального из двух 32-разрядных чисел.
DMIN ( d1 d2 -- d-min) Занесение в стек минимального из двух 32-разрядных чисел.
D= ( d1 d2 -- ?) Занесение в стек истины в случае равенства d1 и d2.
D0= ( d -- ?) Занесение в стек истины, если d равно нулю.
D< ( d1 d2 -- ?) Занесение в стек истины, если dl меньше d2.
DU< ( ud1 ud2 -- ?) Занесение в стек истины, если ud1 меньше ud2. Оба числа без знака.
D.R ( d ширина -- ) Вывод 32-разрядного числа со знаком. Число выравнивается справа внутри поля заданной ширины.
Буква D в начале каждого выражения означает, что указанная операция может выполняться только над числами двойной длины, а цифра 2 в начале слова, в частности 2SWAP и 2DUP, - что данная операция может выполняться как над числами двойной длины, так и над парами чисел одинарной длины. Пример выполнения операции D+ :200000. 300000. D+ D. 500000 ok
ОПЕРАЦИИ НАД ЧИСЛАМИ РАЗЛИЧНОЙ ДЛИНЫ
В следующей таблице перечислены часто используемые слова Форта, которые выполняются над комбинацией чисел одинарной и двойной длины1:UM* ( ul u2 -- ud) Перемножение двух 16-разрядных чисел. Все значения без знака. (В предыдущих версиях данная операция называлась U*)
UM/MOD ( ud u1 -- u2 u3) Деление 32-разрядного числа на 16-разрядное. В стек заносятся 16-разрядные остаток и частное ( честное в вершину). Частное округлено до ближайшего меньшего целого! Все значения без знака. (В предыдущих версиях данная операция называлась U/MOD).
M* ( n1 n2 -- d- Перемножение двух 16-разрядных чи- произведение) сел. Все знамени» со знаком.
М+ ( d n -- d-сумма) Сложение 32-разрядного и 16-раэрядного чисел. Результат 32-разрядный.
М/ ( d n -- n-частное) Деление 32-разрядного числа на 16-разрядное. Результат 16-разрядный. Все значения со знаком.
М*/ ( d n u -- d) Умножение 32-разрядного на 16-разрядное и деление промежуточного результата тройной длины на 16-разрядное число (d*n/u). Результат 32-разрядный.
Слово UM* является командой быстрого умножения, где под результат отводится 32 разряда. Эта команда может быть использована как базовая при определении других операций умножения. Аналогичным образом все команды деления могут быть определены через базовую операцию UM/MOD.
Ниже приводится пример выполнения операции М+: 200,000 7 М+ D. 200007 ok
С помощью операции М*/ мы можем переопределить наш прежний вариант слова % так, что оно будет выполняться с аргументом двойной длины:: % ( d n% -- d) 100 M*/ ;
1 См. сноску в разд. «Операции деления» (гл. 2).
Например:200.50 15 % D. 3007 ok
Если вы загрузили определение „J6 (которое мы приводили ранее), то можете ввести следующий текст: 200.50 15 % .$ $30.07
Можно переопределить данное выше определение R% таким образом, чтобы получать округленный результат двойной длины : R% ( d n% -- d) 10 M*/ 5 M+ 1 10 M*/ ;
и тогда 200.50 15 R% .$ $30.08
Заметим, что М*/ является единственным «штатным» словом Форта, которое осуществляет умножение аргумента двойной длины. Для того чтобы умножить, скажем, 200000 на 3, мы должны подставить единицу в качестве фиктивного делителя:200,000 3 1 M*/ D. 600000 ok
так как 3/1 эквивалентно 3.
М*/ также является единственным словом Форта, которое осуществляет деление с результатом двойной длины. Так, при делении 200000 на 4 мы должны подставить единицу в качестве фиктивного числителя: 200,000 1 4 M*/ D. 50000 ok
ОПЕРАЦИИ НАД ДРОБНЫМИ ЧИСЛАМИ
Как вы видели, для масштабирования нецелые величины можно выразить в виде пары целых чисел. В некоторых программах вещественные числа могут понадобиться не только для маштабирования. Например, как сложить две дроби, не используя представление с плавающей точкой?
7 . 23 Ж"Н9 =
На Форте вы можете сделать это с помощью команд так называемой дробной арифметики, реализующих операции над числами с фиксированной точкой.
При использовании дробной арифметики мы применяем масштабирование с подразумеваемым положением десятичной точки. Но вместо масштабирования посредством умножения на степень 10 (как принято при вычислениях вручную) мы будем масштабировать путем умножения на степень 2 (как принято при вычислении на компьютере). Таким образом, выражение «десятичная точка» уместно заменить выражением «двоичная точка»"
1 Для истинных математиков Ниже приводится небольшая таблица вещественной аппроксимации различных констант:
2 Для начинающих Этот вопрос подробнее рассматривается в гл 7
Допустим, вы ввели следующее определение: : +1 ( -- масштабная-единица ) 16364 ;
Здесь выбран масштаб» при котором число 16384 представляет положительную единицу (константы будут введены в гл. 8). В двоичной системе счисления число 16384 выглядит следующим образом: 0100000000000000
т. е. это единица в соответствующем масштабе с подразумевае-мой двоичной точкой.
Теперь добавьте к Форту две новые арифметические операции: дробное умножение и дробное деление, выполняемые с учетом выбранного нами масштаба. : *. ( n n -- n) +1 */ ;
: /. ( n n -- n) +1 SWAP */ ;
Что же у вас получилось? При делении единицы на единицу должна получиться единица.1 1 /. . 16384 ok
(Напомним, что в выбранном масштабе 16384 это единица.) Теперь разделите 1 на 2: 1 2 /. . 8192 ok
Здесь 8192 представляет половину единицы (половина от 16384). Следовательно, вы можете решить поставленную задачу таким образом: 7 34 /. 23 99 /. +
Обратите внимание на последнюю операцию.
Для сложения двух дробей мы применяем хорошо знакомый вам знак + Конечно, пока это не совсем удобно, так как вы еще не умеете получать ответы в прежней форме. Для того чтобы представить результат снова в десятичной системе, необходимо ввести 10000 *. . 4381 ok
Ответ составит 4381/10000, или в более привычном виде 0.4381.
С помощью дробной арифметики, используя быстрые операции над целыми числами, вы можете складывать, вычитать, умножать
и делить даже вещественные числа. В вычислительных задачах (в которых выполняется множество арифметических действий), скорость вычислений считается самым важным параметром Перевод же чисел в удобочитаемый вид (в десятичную систему) не столь существен, поскольку выводить нужно лишь конечный результат. Более того, в таких приложениях, как графика и робототехника, вообще не требуется перевод результата в десятичную систему - получаемая информация должна быть понятна , графическому устройству, плоттеру, руке робота или чему-то еще
Имеет смысл решать на компьютере задачи теми средствами, к которым он приспособлен, а не навязывать ему образ действий, являющийся следствием нашего школьного образования
Нам все-таки хотелось бы вводить и выводить дроби с помощью привычных обозначений. Вы были бы не против, если бы Форт-система выводила результат с десятичной точкой в соответствующем месте? А для этого нужно всего лишь одно слово, лред-ставляющее число в традиционном формате. Воспользуемся средствами, описанными в гл. 71:: #.#### DUP ABS 0 <# # # # # 46 HOLD # ROT SIGH #> TYPE SPACE ; : .F ( дробь -- ) 10000 *. #.#### ;
Теперь наше выражение будет иметь вид 7 34 /. 23 99 /. + .F 0.4381 ok
Это не представление с плавающей точкой, но результат в принципе такой же и получен гораздо быстрее.
Допустим, вы хотите, чтобы вводимые аргументы выглядели как вещественные числа, например: .1250 + .3750 = ?
Для того чтобы выполнить такую операцию на Форте, в первую очередь вам потребуется слово, преобразующее числа с десятичной точкой в масштабированную дробь.
Детали мы объясним вам в гл. 7, а для своих текущих потребностей вы можете определить, например, слово : D>F ( d -- дробь) DROP 10000 /. ;
(D>F означает перевод из числа двойной длины в дробное. DOUBLE - двойной, FRACTION - дробь). Теперь можно вводить следующие выражения:.1250 D>F .3750 D>F + .F 0.5000 ok
1 Для пользователей фиг-Форта, систем полиФорт, созданных бо введения Стандарта-83 и других старых систем Перед SIGN слово ROT должно быть опущено
Обратите внимание на то, что необходимо дополнять исходные данные до четырех десятичных знаков.
Вы можете выполнять умножение двух дробей посредством *..7500 D>F .5000 D>F *. .F .3750 ok
Интересно отметить, что если вы выполняете операцию умножения *. дроби на целое, то результат будет целым, например:28 .5000 D>F *. . 14 ok
Применяя операцию /., можно разделить, скажем, -0.3 на 0.95:-.3000 D>F .9500 D>F /. .F -0.3160 ok
Выполняя ту же операцию над двумя целыми, вы получите дробный результат:22 44 /. .F .5000 ok
Если же вы делите посредством /. целое на дробь, то в результате получите целое. Обозначив символом f дробь, а символом i целое, мы можем построить следующую таблицу выполнения операций:ОПЕРАЦИЯ РЕЗУЛЬТАТ f f + f f f - f f i * f i f * f f i / f f f *. f f i *. i i f *. i f f /. f i i /. i i f /. i
Использование двоично-ориентированного масштаба, например числа 16384, а не десятично-ориентированного, например 10000, позволяет обеспечить в пределах 16 разрядов большую точность (точность повышается в отношении 16 к 10). При этом операции */ и /. кодируются на ассемблере чрезвычайно эффективно. Но и число 16384 мы выбрали в качестве единицы произвольно. Если бы было выбрано число 256, то получилось бы восемь разрядов (включая знак) слева от двоичной точки и восемь разрядов справа. Этот метод при необходимости можно применить и к 32-разрядным числам.
Операции *. и /. можно выполнять над тригонометрическими функциями, поскольку значение угла представляется как функция длины окружности, выраженная дробью (в интервале от 0 до 1).Перевод радиан в градусы и обратное преобразование осуществляются весьма просто.
ОПЕРАЦИИ НАД СТРОКАМИ В ОПЕРАТИВНОЙ ПАМЯТИ
Команды для пересылки строк литер или массивов данных весьма просты. Каждой команде требуются три аргумента: исходный адрес, конечный адрес и значение счетчика.
MOVE | ( a1 a2 u -- ) | По ячеечное копирование участка памяти длиной u байтов, начинающегося с адреса a1, в участок памяти, начинающийся с а2. Пересылка идет с al В сторону увеличения адресов. |
CMOVE | ( al a2 u -- ) | Побайтное копирование участка памяти длиной u байтов, начинающегося с a1, в участок памяти, начинающийся с а2. Пересылка идет с а1 в сторону увеличения адресов. |
СМОVE> | ( a1 a2 u -- ) | Копирование участка памяти длиной u байтов, начинающегося с адреса a1, в участок памяти, начинающийся с адреса а2. Копирование начинается с КОНЦА строки и продвигается в сторону уменьшения адресов. |
Заметим, что для этих команд существуют определенные соглашения (рассмотренные ранее):
если среди аргументов находятся источник и адресат, как это имело место в случае с COPY, то источник предшествует адресату;
если среди аргументов находятся адрес и счетчик, как это имело место в случае с TYPE, то адрес предшествует счетчику.
Поэтому для перечисленных трех слов аргументы следуют в таком порядке: (источник адресат счетчик --). Для того чтобы переслать содержимое всего буфера в рабочую область, вы могли бы, к примеру, написать:210 BLOCK PAD 1024 CMOVE
хотя на машинах с поячеечной адресацией памяти намного быстрее выполнялась бы последовательная пересылка ячеек (ячейка за ячейкой):210 BLOCK PAD 1024 MOVE
Слово CMOVE> позволяет пересылать некоторую строку в область, которая расположена в памяти с большими адресами, но пересекается с исходной областью1.
Для заполнения некоторого массива пробелами вы можете воспользоваться уже знакомым вам словом BLANK2. Например, внесите пробелы в 1024 байта рабочей области:PAD 1024 BLANK
Это равносильно выражению: PAD 1024 BL FILL
В большинстве систем слово BL определено как константа со значением 32.
1 для начинающих Предположим, вы хотите передвинуть некоторую строку в памяти на один байт вправо (например, когда текстовый редактор включает в текст некоторый символ) Применив команду CMOVE, вы скопировали бы первую букву строки во второй байт, но при этом вторая литера строки оказалась бы «испорченной» В результате получилась бы строка, составленная из одного и того же символа Чтобы сохранить исходную строку, в данной ситуации нужно воспользовался словом CMOVE>
2 для пользователей систем фиг-Форта. В вашей системе это слово BLANKS (ПРОБЕЛЫ)
BLANK | ( a u -- ) | Заполнение участка памяти длиной u байт символам пробела в коде ASCII. |
Остроумный, но не согласующийся со стандартом прием: для заполнения участка памяти многобайтным шаблоном примените слово CMOVE. При выполнении следующего выражения 20-байт-ный массив будет заполнен 10-ю копиями 16-разрядного адреса слова НОП:CREATE ТАБЛИЦА ' НОП , 18 ALLOT \ 10 ячеек ТАБЛИЦА DUP 2+ 18 CMOVE \ инициализация выделенной памяти \ адресом МОП
ОПЕРАЦИИ СРАВНЕНИЯ
Ниже приводится неполный список операций сравнения, которые вы можете применять перед выполнением оператора IF ... THEN1= равно <> не равно < меньше > больше 0= равно-нулю 0< меньше—нуля 0> больше-нуля
Слова < и > требуют такого же расположения аргументов в стеке, как и арифметические операции:
Рассмотрим другой пример. Проверим, не превышает ли температура лабораторного бойлера допустимого значения. Значение температуры нужно получить в стеке: ?ЖАРКО ( температура — )
220 > IF ." ОПАСНО! Уменьшите нагрев" THEN ;
1 Для тех, у кого нет <>. Используйте— (минус). См. разд. «Секреты оператора IF» в данной главе.
Если значение, находящееся в стеке больше, чем 220, то на терминал будет выведено сообщение об опасности. Вы можете выполнить это слово автономно, наорав на клавиатуре сначала определение, а затем и само слово. Перед словом вы должны набрать значение температуры: 290 ?ЖАРКО ОПАСНО! Уменьшите нагрев ok 130 ?ЖАРКО ок
Вы можете проверить, является ли некоторое число нулем, отрицательным или положительным, с помощью трех следующих слов: 0=, 0<, 0>.. Они эквивалентны выражениям 0 = , 0 <, 0 > и отличаются лишь эффективностью предлагаемых операций.
ОПЕРАЦИЯ МАСШТАБИРОВАНИЯ */
Приведенная ниже арифметическая операция весьма полезна, хотя и выглядит несколько необычно.*/ ( n1 n2 n3 -- Умножение, затем деление (n1*n2/nЗ). результат ) Промежуточный результат 32-разрядный.
Например, допустим, что в стеке содержатся три числа* ( 225 32 100 -- )
*/ сначала перемножит 225 на 32, а затем разделит полученный результат на 100. Эта операция особенно удобна для решения таких задач, как вычисление процентов. В частности, вы можете определить слово % следующим образом: : % ( n % - n') 100 */ ;
Введя число 225, а затем выражение 32 %
вы получите в стеке 32% от 225, т. е. 721.
Команда */ это не простое сочетание команд * и /. Она использует для промежуточного результата число удвоенной длины. «Что это значит?», - спросите вы. Предположим, вам нужно вычислить 34% от 2000. Вспомним, что операции над числами одинар-
1 Для любознательных Способ, при котором сначала перемножаются два чи ела. а затем полученное произведение делится на 100, применяется и при вы-пошении подобных вычислений вручную
ной точности, такие, как * и /, дают результаты в диапазоне от -32768 до +32767. Если вы введете выражение2000 34 * 100 /
(то получите неверный результат, поскольку промежуточное выражение (в нашем случае произведение) превышает 32767:
При выполнении же сдвоенной операции для промежуточного выражения применяется слово двойной длины, так что его разрядность позволяет помещать произведение любых двух чисел одинарной длины. Выражение 2800 34 100 */
дает правильный результат, так как конечное выражение находится в пределах диапазона одинарного представления чисел.
ОПЕРАТОРЫ ВЫВОДА
Слово EMIT берет из стека одно значение в коде ASCII, используя только младший байт, и выводит этот символ. Например, при текущей десятичной системе счисления вы получите такой результат:65 EMIT A ok 66 EMIT B ok
Слово TYPE выводит всю строку символов при ее заданном начальном адресе и счетчике в следующей форме:( a количество -- )
Вам уже встречалось слово TYPE в определениях, связанных с форматированием чисел, но тогда не нужно было заботиться ни об адресе, ни о счетчике, так как их значения обеспечивались словом #> автоматически.
Зададим слову TYPE адрес, по которому, как нам известно, находится строка символов. Напомним, что начальный адрес буфера входного текста задается словом TIB (см. гл. 9 о вариантах диалектов). Допустим, вы вводите команду:TIB 11 TYPE
В результате будет выведено 11 символов из буфера входного текста, который содержит только что введенную команду:TIB 11 TYPE<return>TIB 11 TYPEok
TYPE | ( а количество - ) | Происходит выдача заданного количества символов, начиная с заданного адреса, на текущее внешнее устройство |
ОПРЕДЕЛЯЮЩИЕ СЛОВА ВЫ МОЖЕТЕ СПЕЦИФИЦИРОВАТЬ САМИ
Рассмотрим три класса определяющих слов. Слова первого класса дают возможность выделять фрагменты с похожими свойствами из серии определений через двоеточие. В предыдущей главе вы имели дело со следующими определениями: : 1ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 0 .БРЕД ; : 2ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 1 .БРЕД ; : СУЩЕСТВИТЕЛЬНОЕ 10 CHOOSE 2 .БРЕД ;
Если бы вам пришлось определять и другие части речи, то можно было бы их выделить в отдельное слово «часть-речи»:: ЧАСТЬ ( столбец# -- ) 10 CHOOSE SWAP .БРЕД ; : 1ПРИЛАГАТЕЛЬНОЕ 0 ЧАСТЬ ; : 2ПРИЛАГАТЕЛЬНОЕ 1 ЧАСТЬ ; : СУЩЕСТВИТЕЛЬНОЕ 2 ЧАСТЬ ;
Однако при этом слишком расточительно расходуется память. Поскольку различие между показанными тремя словами заключено только в одном числе, их незачем определять через двоеточие. Целесообразнее воспользоваться таким приемом:: ЧАСТЬ ( столбец# -- ) CREATE , DOES> @ 10 CHOOSE SWAP .БРЕД ; 0 ЧАСТЬ 1ПРИЛАГАТЕЛЬНОЕ 1 ЧАСТЬ 2ПРИЛАГАТЕЛЬНОЕ 2 ЧАСТЬ СУЩЕСТВИТЕЛЬНОЕ
Приведенное определение слова ЧАСТЬ аналогично определению слова CONSTANT1, с той лишь разницей, что в период выполнения помимо занесения в стек номера столбца оно также определяет номер строки посредством датчика случайных чисел и обращается к слову .БРЕД.
Следующий довольно большой класс определяющих слов позволяет выделять повторяющиеся фрагменты кодов периода компиляции. Допустим, требуется назначить ряд блоков для группы студентов начиная с блока 360, причем каждому студенту нужно выделить по 25 блоков. Для этого понадобится несколько кон-
1 Для пуристов. Иногда имеет смысл объединять существующие определяющие слова, если они выполняют большую часть тех функций (или все), которые должны выполняться во время компиляции. Так, выражение CREATE, с тем же эффектом можно заменить словом CONSTANT. Стандарт, однако, не рекомендует пользоваться такими приемами и не во всех системах это получится.
стант, обозначающих номер начального блока для каждого студента. Константы можно определить так:360 CONSTANT ВАСИЛЬЕВ 385 CONSTANT СИМОНЧИК 410 CONSTANT РЯБИНИНА 455 CONSTANT ИВАНОВА 460 CONSTANT ПЕТРОВА 485 CONSTANT ДЕМИН
Если у вас со сложением дела обстоят плохо, то вы обязательно ошибетесь. Кроме того, вдруг вы измените свое решение и выделите каждому студенту по 30 блоков? Есть более изящный прием. Он состоит в том, чтобы выделить вычисления во время компиляции в отдельное слово:: +СТУДЕНТ ( n -- n+25 n ) DUP 23 + SWAP ; 360 \ начало участка памяти, отведенной студентам +СТУДЕНТ CONSTANT ВАСИЛЬЕВ +СТУДЕНТ CONSTANT СИМОНЧИК +СТУДЕНТ CONSTANT РЯБИНИНА +СТУДЕНТ CONSTANT ИВАНОВА +СТУДЕНТ CONSTANT ПЕТРОВА +СТУДЕНТ CONSTANT ДЕМИН . .( Конец участка памяти, отведанного студентам ) CR
Последняя точка выбирает из стека номер блока. Но и этот прием блекнет перед методом использования определяющих слов. Убедитесь сами: : СТУДЕНТ ( n -- n+23) CREATE DUP , 23 + DOES> ( -- n) @ ; 360 \ Начало участка, отваленного студентам СТУДЕНТ ВАСИЛЬЕВ СТУДЕНТ СИМОНЧИК СТУДЕНТ РЯБИНИНА СТУДЕНТ ИВАНОВА СТУДЕНТ ПЕТРОВА СТУДЕНТ ДЕМИН . .( Конец участка, отведенного студентам ) CR
Определяющее слово СТУДЕНТ и создает «константы», и управляет всеми вычислениями во время компиляции.
Определяющие слова третьего класса дают возможность создавать новые структуры данных, например многомерные массивы. Нас иногда упрекают в том, что структуры данных Форта бедны. Однако редко бывает так, чтобы какой-то набор структур данных удовлетворял всем прикладным областям. Форт же предоставляет удобные инструменты для создания определяющих слов привычных нам структур данных. Мы можем образовать структуры данных, которые во время выполнения осуществляют, помимо всего прочего, присущие только им операции. В информатике такие структуры называются абстрактными типами данных.
Рассмотрим два примера. Первый из них, более простой, демонстрирует определяющее слово, организующее одномерный массив. Можно, конечно, создать такой массив и без применения определяющего слова:CREATE КЛАПАНЫ 30 ALLOT \ байтовый массив установки клапанов : КЛАПАН ( i -- а) \ преобр. номера клапана в абсолютный адрес КЛАПАНЫ + ;
Здесь слово КЛАПАН вычисляет индекс массива КЛАПАНЫ.
Например, при выполнении выражения6 КЛАПАН С@
будет включен гидравлический клапан 6.
Приведенное выше решение приемлемо только в тех случаях, когда в программе требуется один или два таких массива. Если же массивов должно быть больше, использование определяющего слова упростит программирование.: МАССИВ ( #байтов -- ) \ определение одномерного массива байтов CREATE ALLOT DOES> ( i -- a) + ; 30 МАССИВ КЛАПАН 6 КЛАПАН С@
Рассмотрим выполнение этого определения. В фазе 1 определяется слово МАССИВ. В фазе 2 исполняется МАССИВ, который в свою очередь обращается к слову CREATE (чтобы определить КЛАПАН) и слову ALLOT (для резервирования 30 байтов под массив). В фазе 3 исполняется слово КЛАПАН, инициируя код периода выполнения слова МАССИВ с добавлением индекса (6) к начальному адресу массива.
Если внести изменения в определение определяющего слова перед его повторной компиляцией, то тем самым можно изменить характеристики всех слов, входящих в данное семейство. Такая возможность значительно упрощает разработку программ. Например, когда нужно при описании массива заполнить его нулями, вы должны соответствующим образом создать определение МАССИВ. Сначала вы определяете слово, которое аналогично ALLOT, но «обнуляет» выделенный участок памяти:: 0ALLOT ( #байтов -- ) HERE OVER ERASE ALLOT ;
Затем подставляете в определение МАССИВ вместо ALLOT слово 0ALLOT: : МАССИВ ( #байтов -- ) \ определение одномерного массива байтов CREATE 0ALLOT DOES> ( i -- a) + ;
Можно также выделить в отдельный фрагмент некоторые отладочные процедуры, необходимые при разработке программы, а затем удалить их, предварительно убедившись в том, что она работает правильно. Ниже приводится вариант слова МАССИВ, где во время выполнения проверяется, не вышел ли индекс массива за установленные границы. : МАССИВ ( #байтов) CREATE DUP , ALLOT DOES> ( i -- a) 2DUP @ U< NOT ABORT" Выход эа границу " + 2+ ;
Это происходит следующим образом:DUP , ALLOT Компиляция счетчика и выделение заданного количества байтов,
DOES> 2DUP @ По заданному на стеке индексу во время выполнения вычисляется: ( i pfa i # )
U< NOT Проверка того, что индекс не меньше максимального значения, а именно: запомненного счетчика. Так как U< является операцией сравнения над значениями без знака, то отрицательные аргументы будут трактоваться как числа, выходящие за границу, и приводить к аварийному сообщению.
ABORT" Выход за Аварийное завершение при выходе числа за диапазон. границу "
+ 2+ В противном случае сложение индекса с pfa и добавление числа 2 для пропуска ячейки, содержащей счетчик.
Существует еще один способ использования определяющих слов, который помогает при создании программ. Допустим, вы вдруг решаете, что все ваши массивы, определенные с помощью слова МАССИВ, слишком велики, чтобы хранить их в памяти компьютера, и должны быть помещены на диск. Единственное, что вы должны в таком случае сделать, переопределить фрагмент периода выполнения в слове МАССИВ. Это новое определение вычислит номер блока, в котором содержится заданный байт, считает блок посредством BLOCK в некоторый буфер и оставит в вершине стека адрес требуемого байта относительно начала буфера. Массив, определенный подобным образом, может храниться в нескольких последовательных блоках (с использованием тех же средств, что и в упр. 10.7).
Ниже приведен пример определяющего слова, которое создает двумерный массив байтов заданного размера1: : МАТРИЦА ( #строк #столбцов -- ) CREATE OVER , * ALLOT DOES> ( строка столбец -- a) DUP @ ROT * + + 2+ ;
1 Для любителей оптимизации Этот вариант будет выполняться еще быстрее: МАТРИЦА ( #строк #столбцов -- ) OVER CONSTANT HERE 2+ , * ALLOT DOES> ( строка столбец -- а) 2@ ROT * + + ;
Для того чтобы создать массив размером 4х4 байта, вы должны написать:
Выбрать же, к примеру, байт в строке 2 и в столбце 1 можно следующим образом:2 1 ТАБЛИЦА С@
Вот так кратко можно описать выполнение слова МАТРИЦА. Поскольку аппаратные средства компьютера позволяют хранить только одномерные массивы, второе измерение необходимо моделировать.
Мы представляем себе, что наш массив выглядит следующим образом:
а на самом деле в памяти машины он хранится в виде
Если вам требуется адрес байта, расположенного в строке 2 столбца 1, то вы можете умножить номер столбца (1) на число строк в каждом столбце (4), а затем прибавить номер строки (2). В результате получается, что вам нужен шестой байт машинного представления массива. Примерно такие вычисления делают в период выполнения элементы, составляющие слово МАТРИЦА. Но для того чтобы их производить, как вы можете заметить, любое составляющее слово должно «знать» число строк в каждом столбце конкретного массива. Для этих целей слово МАТРИЦА во время компиляции вносит число строк в столбце в начало массива. Для любознательных приведем стековые эффекты фрагмента периода выполнения слова МАТРИЦА:ОПЕРАЦИЯ СОДЕРЖИМОЕ СТЕКА строка столбец pfa PUP @ строка столбец рfa #строк ROT строка pfa #строк столбец * строка pfа индекс-столбца + + адрес 2+ скорректированный-адрес
К вычисленному адресу необходимо добавить двойку, потому что первая ячейка нашего массива содержит число строк.
Предлагаем вашему вниманию еще один пример, который, может быть, не очень полезен, но весьма нагляден:\ Шаблоны с использованием определяющих слов : STAR 42 EMIT ; : .РЯД ( b -- ) \ вывод звездочки на каждый бит из байта CR 8 0 DO DUP 128 AND IF STAR ELSE SPACE THEN 2* LOOP DROP ; : ФОРМА ( b1 b2 b3 b4 b5 b6 b7 b8 -- ) \ определение формы из 8-строк CREATE 8 0 DO С, LOOP DOES> DUP 7 + DO I С@ .РЯД -1 +LOOP CR ; \ формы: HEX 18 18 3С 5А 99 24 24 24 ФОРМА ЧЕЛОВЕК 81 42 24 18 18 24 42 81 ФОРМА КОНЬ АА АА FE FE 38 38 38 FE ФОРМА ЗАМОК DECIMAL
Слово .РЯД выводит строку, состоящую из звездочек и пробелов, где звездочка соответствует единице, а пробел - нулю в восьмиразрядном двоичном представлении числа, находящегося в вершине стека, например:2 BASE ! ok
00111001 .РЯД *** * ok DECIMAL ok
Наше определяющее слово ФОРМА берет из стека восемь аргументов и определяет шаблон, который при своем выполнении выводит решетку 8х8 элементов, соответствующую этим восьми аргументам:ЧЕЛОВЕК ** ** **** * ** * * ** * * * * * * * ok
Итак, определяющие слова могут быть чрезвычайно полезным инструментом. Создавая новое определяющее слово, вы тем самым расширяете свой компилятор. Традиционные языки не обеспечивают такой гибкости, потому что они представляют собой жесткие готовые программы, которые предлагают вам в обязательном порядке конкретный набор операторов. Реальная помощь определяющих слов заключается в том, что их применение позволяет упростить вашу программу. При правильном их употреблении вы можете сократить время программирования, уменьшить размер программы и повысить ее читабельность. В следующем разделе мы приведем еще один способ расширения средств компилятора Форта.
ОРГАНИЗАЦИЯ ЦИКЛА ПО МАССИВУ
Предлагаем вашему вниманию один из приемов работы с массивами. Лучше всего проиллюстрировать этот прием, написав на Форте наше собственное определение слова с именем DUMP. Оно применяется для вывода содержимого ряда адресов памяти. Форма использования этого слова следующая: адрес длина DUMP
Например, мы можем ввести СЧЕТЧИКИ 12 DUMP
и на печать будет выведено содержимое нашего массива СЧЕТЧИКИ, обеспечивающего нам подсчет яиц. Так как слово DUMP изначально было создано как средство программиста для вывода на печать содержимого участков памяти, то мы и получаем это содержимое либо байт за байтом, либо ячейку за ячейкой, в зависимости от вида адресации, применяемой в данном компьютере. В нашем варианте DUMP будет выводить ячейку за ячейкой. Очевидно, что в рассматриваемом здесь примере слово DUMP содержит цикл DO. Вопрос заключается в том, что должно использоваться в качестве индекса. Хотя мы можем задействовать как индекс цикла непосредственно сам счетчик (0—6), все же для скорости лучше взять за индекс АДРЕС. Адрес массива СЧЕТЧИКИ будет начальным индексом для нашего цикла, а адрес плюс счетчик — верхней границей, например: : DUMP ( а длина -- ) OVER + SWAP DO CR I @ 5 U.R 2 +LOOP ;
Ключевым выражением здесь является фраза OVER + SWAP
которая непосредственно предшествует DO.
Теперь начальный и конечный адреса находятся в стеке, готовые к использованию в качестве верхней границы и индекса цикла DO.
Выражение типа OVER + SWAP
называется клише, поскольку оно выполняет некоторое единое действие с точки зрения более высокого уровня. Данное конкретное клише преобразует адрес и счетчик в форму аргументов оператора DO. Так как индексирование осуществляется по адресам, для тою чтобы напечатать содержимое каждого элемента массива, поскольку мы находимся внутри цикла, достаточно просто ввести I @ 5 U.R
Проверка байтов выполняется попарно (из-за того, что @ выбирает 16-разрядное значение), поэтому мы всякий раз увеличиваем индекс на два с помощью выражения 2 +LOOP
ОСНОВНЫЕ ТЕРМИНЫ
Выполнение. Применительно к слову —- это выполнение операций, заданных в скомпилированном определении данного слова.
Входной поток Текст, который должен быть прочитан текстовым интерпретатором Это может быть текст, только что набранный на терминале или ранее записанный на диск.
Глоссарий Список слов, определенных в Форте, с их стековыми нотациями (какого рода информация помещается в стек перед выполнением слова и что остается в стеке в качестве результата).
Интерпретация. Чтение из входного потока при обращении к интерпретатору текста и поиск каждого встретившегося слова в словаре. В случае неудачи это слово трактуется как число.
Инфиксная запись. Метод записи: знак операции располагается между операндами, над которыми она выполняется, например 2 + 5.
Компиляция. Генерация по исходному тексту элемента словаря (внутренняя форма определения). Следует отличать от EXECUTE.
Переполнение стека. Аварийная ситуация, которая создается в том случае, когда вся область памяти, отведенная под стек, заполнена данными.
Постфиксная запись. Метод записи: знак операции следует за операндами, над которыми эта операция выполняется, например 2 5 + Также известен как обратная польская запись.
Потеря элемента стека. Аварийная ситуация, которая возникает в том случае, когда для выполнения какой-либо операции требуется элемент из стека, а стек пуст.
Расширяемость. Характеристика языка, означающая, что программист может добавлять новые средства или модифицировать существующие.
Словарь. В Форте это перечень слов и определений, включающий в себя как «системные» определения (созданные при генерации системы), так и «пользовательские» (которые создаете вы сами). Словарь размещается в памяти компьютера в компилируемой форме.
Слово. В Форте это имя определения.
Стек. Участок памяти,» в который данные помещаются и из которого они удаляются по принципу «последним пришел — первым обслужен» (LIFO).
LIFO. Принцип функционирования стека (последним пришел — первым обслужен). Так, коробка для хранения теннисных мячей имеет структуру LIFO: последний помещенный в нее мяч вы выбираете первым.
Виртуальная память. Использование внешней памяти (например, диска), так как если бы она была оперативной, включая те средства операционной системы, которые обеспечивают эту возможность.
Ожидание. Приостановка исполнения программы для ожидания ввода с клавиатуры (в противоположность сканированию)
Относительный указатель. Переменная, определяющая абсолютный адрес некоторого участка, а его расположение относительно начала массива или строки.
Сканирование. «Заглядывание» вперед по входному потоку с целью просмотра оставшегося текста или поиска фрагмента, ограниченного заданным символом.
Суперстрока. В Форте - это массив символов, содержащий ряд строк. Доступ к любой из них может быть получен посредством индексирования этого массива.
Упрятывание информации. Прием, используемый программистом: он локализует информацию о том, как работает конкретная конструкция (особенно в тех случаях, когда что-то может измениться в последующих версиях или, будет повторно применяться) в пределах некоторой программы или комплекса программ.
Бит использования во время компиляции. Бит элемента словаря, который определяет, будет ли слово во время компиляции выполняться, а не компилироваться.
D-схема. Графическое представление логической структуры некоторой программы, а в случае Форта - некоторого определения.
Компилирующее слово. Слово, используемое внутри определения через двоеточие для выполнения действий во время компиляции.
Немедленно исполняемое слово. Словарная статья, где установлен бит непосредственного использования, что вызывает во время компиляции не компилирование данного слова, а его выполнение.
Определяющее слово. Слово, которое при своем выполнении создает новый элемент словаря. Определяющее слово задает функции периода-компиляции и периода-выполнения для каждого члена семейства слов, определяемых этим словом.
Функции периода выполнения. По отношению к определяющим словам: последовательность команд, которые будут выполняться при выполнении любого слова из некоторого семейства. По отношению к компилирующим словам: некоторая программа, которая будет выполняться при выполнении определения-родителя. Не у всех компилирующих слов имеются функции периода выполнения.
Функции периода компиляции. Применительно к определяющим словам: последовательность команд, которая будет выполняться при выполнении этого определяющего слова - команды осуществляют компиляцию слов, принадлежащих к конкретному семейству. Применительно к компилирующим словам: функции слова, содержащегося в определении через двоеточие, которые выполняются во время компиляции данного определения.
ОСОБЕННОСТИ ПРОГРАММИРОВАНИЯ НА ФОРТЕ
Программист пишет программу на Форте обычно в несколько этапов:
С помощью редактора Форта он делает доступным некоторый блок.
Набирает определение.
Завершает работу с редактором (при необходимости).
Загружает данный блок.
Проверяет новое слово.
Если слово выполняется неправильно, то программист забывает это определение посредством FORGET, редактирует его и повторно загружает. Если же слово выполняется правильно, программист возвращается к п. 1 и принимается за следующее определение.
Основной причиной, по которой программирование на Форте происходит быстрее, чем на других языках, является быстрая оборачиваемость цикла «кодирование—загрузка—тестирование». Загрузка блока занимает менее секунды.
Поэтому программирование на Форте требует несколько иной стратегии, чем на большинстве известных языков программирования, например Си. После того как вы представили себе программу в целом и решили, что будет выполнять каждое слово в отдельности, можете приступать к кодированию. У вас есть возможность проверять ваши определения по мере написания и модифицировать их с целью удаления ошибок, дальнейшего совершенствования и наведения «глянца». Таким образом, все последующие слова создаются на проверенном фундаменте и вы управляете программой с самого начала ее создания. (В книге автора «Думаем на Форте» такой подход называется итеративной разработкой.)
ОТКАЧКА ФАЙЛА
Итак, наш первый пример - простая файловая система1. Это серьезная и полезная программа, которая к тому же является неплохим пособием для изучения хорошего стиля программирования на Форте. Мы разделили этот раздел на три части:
рекомендации конечному пользователю по работе с файловой системой; описание структуры программы и выполнения некоторых определений; листинг программы с блоками, содержащими документацию.
Как пользоваться простой файловой системой. Рассматриваемая здесь файловая система позволяет быстро запоминать и восстанавливать информацию. Она мгновенно запоминает (для последующего применения) фамилии людей, их адреса и телефоны2. Вы можете не только вводить, изменять и удалять записи, но и находить файл с любой информацией. Например, по номеру телефона легко установить фамилию абонента, по известной фамилии определить место работы и т. д.
Для каждого человека отводится некоторая запись, которая состоит из четырех полей с именами: фамилия, имя, работа, телефон.
Поиск информации. Вы можете просматривать файл в поисках содержимого какого-либо поля, используя слово НАЙТИ, за которым должны следовать имя поля и его содержимое:найти работа диктор<return> Дан Рэйвер ok
Если в поле «работа» содержится строка «диктор», то система выведет фамилию диктора. При отсутствии файла с такими атрибутами система выдаст сообщение: «Сведений нет». В том случае, когда поле с искомыми атрибутами найдено, запись с соответствующей информацией становится текущей. Вы можете вывести содержимое любого поля текущей записи с помощью слова «дать». Например, если вы ввели упомянутую выше строку, то теперь можете написать:дать телефон<return> 555-9876 ок
1 Для пользователей файловой системы. Версии Форта, поставляемые профессиональным программистам, включают намного больше средств для работы с базами данных.
2 Для программистов. Вы легко можете изменить имена или увеличить число полей, обрабатываемых системой.
Команда «найти» применяется только для поиска первого поля с указанными атрибутами.
Для того чтобы добраться до следующего такого поля, воспользуйтесь командой «еще». В частности, чтобы найти еще одного диктора, нужно ввести еще<return> Конни Чанг ok
а затем еще<return> Франк Рейнольда ok
Если в данном файле больше нет сведений о дикторах, то при посылке команды «еще» вы получите сообщение: «Больше нет», т.е. полей с такими атрибутами в файле не осталось.
Для получения списка лиц, у которых в соответствующем поле информация совпадает с атрибутами, применявшимися при последнем поиске, введите команду «все»: все
Дэн Рэйэер Конни Чанг Фрэнк Рейнольда ok
Так как фамилия и имя хранятся порознь, вы можете организовать поиск с помощью команды «найти» по одному из этих атрибутов. Но если вам известны и имя, и фамилия человека, которого вы ищете, то для экономии времени можно осуществлять поиск сразу по двум полям, используя слово «фио». С этой целью вы должны задать слову «фио» имя и фамилию, причем фамилию нужно задать первым операндом и отделить его от второго операнда запятой, например:фио Уандэр,Стив<return> Стив Уандэр ok
(после запятой не должно быть пробела, поскольку запятая отмечает конец первого поля и начало второго). Подобно командам «найти» и «еще» слово «фио» выводит найденное имя.
С помощью слова «пара» вы можете осуществить поиск любой пары полей, для чего необходимо задать имена обоих полей и их содержимое, разделив операнды запятой. В частности, чтобы найти некоего комментатора по имени Дан, вы должны ввести: пара работа диктор,имя Дэн<return> Дэн Рэйвер ok
Сопровождение файлов. Если вам требуется ввести новую запись, вы должны применить команду «внести», разместив за ней операнды: фамилия, имя, место работы, телефон, причем операнды отделяются только запятой, например:внести Нуреев,Рудольф,танцовщик балета,355-1234<return> ok
Изменить содержимое единственного поля внутри текущей записи вы можете с помощью команды «изменить», расположив за ней имя этого поля, а затем новое содержимое последнего:изменить работа хореограф<return> ok
Для удаления текущей записи вы должны ввести команду «удалить»:удалитъ<return> ок
После того как вы что-то добавляли к записям, модифицировали их либо удаляли, обязательно введите слово FLUSH, если собираетесь отключить компьютер или сменить диск: FLUSH ok
OVER (ЧЕРЕЗ)
Теперь допустим, что кто-то попросил вас вычислить выражение а * (а + b)
при следующем содержимом стека: ( a b — )
Вы скажете, что для этого потребуется новая операция со стеком, так как вам нужно два экземпляра а, и а должно находиться под b. OVER и есть та самая «новая» операция. OVER создает еще один экземпляр а, который «перепрыгивает», как при игре в чехарду, через b:
Теперь исходное выражение а * (а + b)
может быть легко записано в виде OVER + *
При этом происходит следующее:
Прежде чем записывать выражения на Форте, их нужно разложить на множители. Например, вычислить выражение а2 + ab
с помощью Форта, применяя только описанные выше средства, довольно сложно (если вообще возможно) до тех пор, пока вы не разложите это выражение на множители, т. е. не приведете его к виду а * (а + b)
А такое выражение вы уже умеете вычислять
ПЕЧАТЬ БЕЗ ИЗМЕНЕНИЯ СОДЕРЖИМОГО СТЕКА
При отладке программ программисту часто необходимо знать, что в данный момент находится в стеке. Конечно, набрав серию точек, мы выведем на терминал содержимое стека, но при этом выведенные значения будут потеряны для последующих операций В большинстве Форт-систем имеется слово с именем .S, которое для своего выполнения не требует аргументов в стеке и выводит содержимое стека, не разрушая его, т. е. оставляя после завершения функционирования прежнее состояние стека. При отладке программы вы можете ввести несколько слов, выполнить слово .S, чтобы убедиться в том, что содержимое стека соответствует результату функционирования введенных слов, ввести еще несколько слов, снова выполнить .S и т. д.
Давайте проверим: 1 2 3 .S 1 2 3 ok ROT .S 2 3 1 ok
При изучении слов манипулирования со стеком начинающие иногда используют следующий прием: : SWAP SWAP .S ; : DUP DUP .S ; : OVER OVER .S ; : ROT ROT .S ; : DROP DROP .S ;
Таким образом осуществляется немедленная обратная связь по каждой выполняемой команде. (Это возможно потому, что интерпретатор из двух слов с одинаковыми именами выполняет занесенное в словарь последним.)
ПЕРЕМЕННЫЕ И КОНСТАНТЫ ДВОЙНОЙ ДЛИНЫ
Вы можете определить переменную двойной длины с помощью слова 2VARIABLE, например:
2VARIABLE ДАТА
После этого вы можете использовать слова ФОРТА 2! (ЗАПОМНИ ДВА) и 2@ (ВЫБЕРИ ДВА) для доступа к переменной двойной длины. Можно записать число двойной длины в такую переменную, просто записав
800000. ДАТА 2!
и выбрать значение из нее, введя
ДАТА 2@ D. 800000 ok
Вы можете также запомнить полностью дату (месяц, число, год) в такой переменной:
7/16/86 ДАТА 2!
и выбрать их назад:
ДАТА 2@ .ДАТА 7/16/81 ok
в предположении, что у вас загружен вариант определения .ДАТА, приводимый в последней главе.
Можно определить константу двойной длины с помощью слова 2CONSTANT, например
200000. 2CONSTANT ЯБЛОКИ
после чего слово ЯБЛОКИ будет помещать в стек число двойной длины:
ЯБЛОКИ D. 200000 ok
Судя по префиксу 2, мы можем также использовать слово 2CONSTANT при определении пары чисел одинарной длины. Можно помещать два числа под одним именем просто из соображений удобства, а также для резервирования памяти в словаре.
Вспомните (см. гл. 5), что вы можете с помощью выражения 355 113 */ умножить некоторое число на аппроксимацию pi. Слово 2CONSTANT позволяет запомнить эти два целых:
355 113 2CONSTANT PI
а впоследствии применить выражение PI */, например:
10000 PI */ . 31415 ok
Ниже приводится перечень слов применительно к структурам данных двойной длины.2VARIABLE xxx ( -- ) Создание переменной двойной длины ххх: ( -- a) с именем ххх. Слово ххх при выполнении помещяет на стек свой адрес. 2CONSTANT ххх ( d -- ) Создает константу двойной длины с именем ххх и значением d. ххх: ( -- d) Слово ххх при выполнении помещает в стек значение d. 2! ( d а -- ) Запоминание числа двойной длины по заданному адресу. 2@ ( а -- d) Занесение в стек числа двойной длины, расположенного по заданному адресу.
ПЕРЕМЕННЫЕ (ОБЩИЕ СВЕДЕНИЯ)
Для начала приведем пример ситуации, когда вам понадобилось бы воспользоваться переменной, например для хранения даты1. В первую очередь создадим переменную (VARIABLE) с именем ДАТА2
VARIABLE ДАТА
Если сегодня 12-е число, то мы вводим: 12 ДАТА !, т. е. помещаем 12 в стек, затем указываем имя переменной и, наконец, выполняем слово ! (ЗАПОМНИ). Это выражение записывает число 12 в переменную ДАТА. Для обратного действия нужно ввести:
1 Для начинающих. Предположим, ваш компьютер выдает банковские счета на протяжении дня, и каждый такой счет должен включать дату. Вам не хотелось бы хранить дату все время в стеке и, кроме того, дата не должна быть частью определения, иначе его пришлось бы переопределять каждый день. Вам нужна переменная.
2 Для пользователей систем фиг-Форт. Чтобы вариант VARIABLE вашей системы совпадал с описанным в книге, введите определение : VARIABLE 0 VARIABLE ;
ДАТА @, иными словами, назвать переменную, а затем выполнить слово @ (ВЫБРАТЬ) Указанное выражение выбирает число 12 и помещает его в стек. Таким образом, выражение
ДАТА @ . 12 ok
выведет на печать дату
На Форте это делается еще проще с помощью слова, определение которого приводится ниже:
: ? @ . ;
Поэтому вместо «ДАТА-выборка-точка» мы можем просто набрать ДАТА ? 15 ok
ДАТА будет иметь значение 12 до тех пор, пока вы не измените его. Для того чтобы изменить значение переменной, необходимо записать в нее новое число. 13 ДАТА ! ok
ДАТА ? 13 оk
Понятно, что можно определить и дополнительные переменные для месяца и года:
VARIABLE ДАТА VARIABLE МЕСЯЦ VARIABLE ГОД
а затем слово с именем !ДАТА (для «запоминания-даты»), например:
: !ДАТА ( месяц день год -- ) ГОД ! ДАТА ! МЕСЯЦ ! ;
и использовать его следующим образом: 7 31 88 !ДATA ok
После этого нужно определить слово с именем .ДАТА (для «вывода-даты») :
: .ДАТА МЕСЯЦ ? ДАТА ? ГОД ? ;
Ваша Форт-система уже имеет ряд определенных переменных, одна из которых — BASE Она содержит основание текущей системы счисления. На самом деле определения систем счисления
HEX, DECIMAL (и OCTAL, если в вашей системе таковая имеется) весьма просты:
: DECIMAL 10 BASE ! ; : HEX 16 BASE ! ; : OCTAL 8 BASE ! ;
Вы можете работать с любой системой счисления, просто загрузив ее основание в BASE1:
Если где-нибудь в определении системных слов, осуществляющих преобразования входных и выходных чисел, вы найдете выражение BASE @ значит, текущее значение BASE используется в процессе такого преобразования. Следовательно, одна и та же программа может работать с числами в любой системе счисления. Отсюда мы можем сделать следующее формальное заключение об использовании переменных: в Форте уместно создавать переменные для тех значений, используемых внутри определения, которые могут быть изменены в любой момент после того, как это определение уже скомпилировано.
ПЕРЕМЕННЫЕ В КАЧЕСТВЕ СЧЕТЧИКА
В Форте переменная представляет собой идеальное средство для счетчика. Вернемся к примеру с машиной для упаковки яиц и предположим, что нам нужна информация о том, сколько яиц проходит по ленте конвейера за один день. (Этот пример вы должны выполнить за своим терминалом, так что по ходу изложения набирайте на клавиатуре текст и вводите его.)
Сначала мы определяем
в которой будем вести подсчет. Каждое утро мы будем начинать подсчет с нуля, поэтому нам придется загружать в переменную ЯЙЦА нуль, используя слово, определение которого выглядит так:
: УСТАНОВИТЬ 0 ЯЙЦА ! ;
После этого где-нибудь в нашей программе по упаковке яиц нужно определить слово, которое всякий раз, когда яйцо минует электрический «глазок» на конвейере, выполняет следующее выра-жение:
1 ЯЙЦА +!
Слово +!1 добавляет заданное значение к содержи^мому (любому) по данному адресу. Таким образом, выражение 1 ЯЙЦА + ! увеличивает счетчик яиц на единицу. Для иллюстрации изложенного поместим это выражение внутрь некоторого определения:
: ЯЙЦО 1 ЯЙЦА +! ;
А в конце дня выясним, сколько яиц прошло через конвейер, набрав на клавиатуре ЯЙЦА?. Теперь проверим:
УСТАНОВИТЬ ok
ЯЙЦО ok
ЯЙЦО ok
ЯЙЦО ok
ЯЙЦА ? 3 ok
Ниже приводится перечень слов, которые мы уже рассмотрели в настоящей главе.
VARIABLE ххх ( -- ) Создание переменной с именем ххх. ххх ( -- а) Слово ххх при выполнении помещает в стек свой адрес. ! ( n а --) Запоминание числа одинарной длины по заданному адресу. @ ( a -- n) Замещение адреса его содержимым. ? ( а --) Вывод значения по заданному адресу с последующим пробелом. +! ( n а --) Сложение числа одинарной длины с содержимым заданного адреса.
1Для любознательных. Это слово обычно определяют на уровне языка Ассемблера, определение же на языке высокого уровня имеет вид:
: +! ( приращение а --) DUP @ ROT + SWAP ! ;
ПЕРИОД ИСПОЛНЕНИЯ И ПЕРИОД КОМПИЛЯЦИИ
При изучении Форта вам встретятся два термина: период исполнения и период компиляции. Смысл их достаточно прост. Если вы компилируете определение некоторого слова, то речь идет о периоде его компиляции, а если вы выполняете слово — о периоде исполнения.
Например, в только что приведенном определении ВСТРЕЧА компьютер во время компиляции выдал вам просто «ok», а во время выполнения указанного определения вывел на экран следующее: «Привет, я говорю на Форте». В случае предварительно определенных слов, таких, как CR, период компиляции приходится на время создания Форт-системы, а период выполнения наступает всякий раз, когда вы обращаетесь к этим словам (или к словам, которые в свою очередь обращаются к ним).
Посмотрим на эту ситуацию с другой стороны. Когда мы компилируем слово ВСТРЕЧА, компилятор Форта (или набор так называемых «компилирующих слов», осуществляющих компиляцию) на самом деле исполняется. Поэтому для слова ВСТРЕЧА это период компиляции, а для компилирующих слов Форта — период исполнения.
Термины период компиляции и период исполнения будут вам особенно полезны при рассмотрении более сложных слов.
ПОЧЕМУ ПРОГРАММИСТЫ ПРЕДПОЧИТАЮТ МАСШТАБИРОВАНИЕ
Многие опытные программисты, использующие традиционные языки программирования, воспринимают представление с плавающей точкой как нечто само собой разумеющееся. Их мнение можно выразить примерно так: «Почему я должен следить за перемещением десятичной точки? Для чего же тогда нужны компьютеры?». Вопрос поставлен правильно - он отражает основное преимущество реализации арифметических операций над числами с плавающей точкой. При переводе математических уравнений в машинный код такое представление чисел существенно облегчает жизнь программисту.
Однако многие прикладные Форт-программы должны работать в реальном масштабе времени. При этом компьютер используется для управления некоторым устройством или организации функционирования ряда дисплеев и клавиатур. Такие программы, для того чтобы «выжимать» из устройств максимальную производительность, нужно делать по возможности быстродействующими Следовательно, программист зачастую заинтересован в максимизации производительности аппаратных средств в большей степени, чем в повышении эффективности программирования. Во многих случаях (например, при использовании карманных компьютеров) к тому же приходится экономить память.
Если в вашей программе некоторые вычисления должны повторяться миллионы раз, то требуемую скорость вы получите, выполняя арифметические операции над числами с фиксированной точкой. Действительно ли велик получаемый при этом выигрыш? Бесспорно. Время выполнения операций деления или умножения чисел с плавающей точкой намного превосходит время выполнения аналогичных операций над числами с фиксированной точкой. А при сложении или вычитании на подготовку и преобразование аргументов уходит столько же времени, сколько и на саму операцию. Большинство мини- и микрокомпьютеров «не думает» в терминах представлений с плавающей точкой и накладные расходы по организации выполнения соответствующих операций на них очень велики.
Свыше десяти лет программисты создают сложные прикладные Форт-программы на базе арифметики с фиксированной точкой При этом им приходится решать самые изощренные задачи с использованием дифференциальных уравнений, быстрого преобразования Фурье, метода наименьших квадратов, линейной регрессии и т.
д. То, что другие программисты делают на мощных универсальных ЭВМ, Форт-программисты выполняют на мини- и микрокомпьютерах, получая иногда общий выигрыш в скорости вычислений
Неверно, что Форт не имеет возможности поддерживать арифметику с плавающей точкой. Программистами созданы на Форте функции с плавающей точкой [1] - [7], а в ряде Форт-систем применяются сопроцессоры с плавающей точкой (отдельный чип, единственной функцией которого является реализация высокоскоростных операций над числами с плавающей точкой) [8], [9].
Большинство программистов считают, что операции над числа ми с плавающей точкой им необходимы, но это не так При решении многих физических задач значения обрабатываемых и получаемых величин не выходят за пределы диапазона от единицы до нескольких тысяч и, следовательно, умещаются в 16-разрядное целое слово. (Некоторые вычисления могут потребовать 32-разрядного представления, что Форт в состоянии обеспечить.) К таким задачам относится моделирование погоды, восстановление изображения, автоматическое измерение электрических цепей и т. п. Масштабирование дает вам возможность работать с данными в 16-разрядном целочисленном диапазоне и поэтому отпадает надобность в расточительных операциях над числами с плавающей точкой. Не подумайте, что мы вынуждаем вас нарушать общепринятые правила и оставляем наедине со своими проблемами. Форт представляет в ваше распоряжение уникальный набор команд, так называемых операций масштабирования, которые поддерживают аппроксимацию вещественных величин и выполнение операций с дробными значениями. В следующем разделе мы познакомим вас с основными возможностями целочисленной арифметики
ПОИСК ПО СЛОВАРЮ
В первой главе было показано, как текстовый интерпретатор INTERPRET выбирает слова из входного потока и ищет их определения в нашем словаре. Если он находит слово, то исполняет его.
Посмотрим, из каких компонентов состоит текстовый интерпретатор, начнем с изучения слов поиска по словарю. Слово ' (апостроф) находит определение в словаре и помещает в стек адрес этого определения. Воспользуемся словом ВСТРЕЧА, которое мы определили в гл. 1, и запишем:
' ВСТРЕЧА U. 25520 ok
В результате получим адрес слова ВСТРЕЧА (вот и все, что должно произойти).
(На самом деле и INTERPRET, и апостроф используют для поиска по словарю примитив с именем FIND (ПОИСК).)
Слово ' имеет несколько применений. Так, с помощью выражения: ' ВСТРЕЧА, вы можете узнать, было ли слово ВСТРЕЧА определено, фактически не выполняя его (при выполнении этого выражения будет либо напечатан адрес, либо выдан ответ: «?»).
Вам также может понадобиться адрес для того, чтобы вывести с помощью DUMP содержимое определения, например: ' ВСТРЕЧА 12 DUMP
Можно сочетать апостроф со словом EXECUTE. Вспомните, что текстовый интерпретатор, найдя слово, передает его адрес слову EXECUTE. To же самое можете делать и вы. Слово EXECUTE выполняет определение, адрес которого задается в стеке. Таким образом, вы можете ввести (следуя Стандарту-83): ' ВСТРЕЧА EXECUTE Привет я говорю на Форте ok
и получить тот же эффект, что и при выполнении одного слова ВСТРЕЧА, только более изощренным способом.
Слово EXECUTE не проверяет, является ли заданный адрес правильным. Вся ответственность ложится на вас. Неверный адрес почти всегда приводит к разрушению системы. Стандартом-83 определено, что апостроф оставляет в вершине стека правильный адрес для EXECUTE. К сожалению, интерфейс между апострофом и EXECUTE менялся от диалекта к диалекту на протяжении многих лет.
В приведенной ниже таблице излагаются правила вычисления адреса слова для EXECUTE. В первом столбце даются примеры использования апострофа в режиме интерпретации (что было продемонстрировано выше). Например, на фиг-Форте вы можете ввести: ' ВСТРЕЧА CFA EXECUTE
Пояснения к остальным столбцам - более краткие.
1. Получение адреса для EXECUTE в режиме интерпретации | 2. Получение адреса для EXECUTE из входного потока (внутри определения) | 3. Получение адреса следующего слова внутри определения для передачи EXECUTE | Что помещает апостроф в стек | |
Фиг-Форт | ' имя CFA | [COMPILE] ' CFA | ' имя CFA | pfa |
MMS-Форт | ' имя 2 - | [COMPILE] ' 2- | ' имя 2- | pfa |
Стандарт-79 | ' имя CFA или FIND имя | [COMPILE] ' CFA | ' имя CFA | pfa |
полиФорт | ' имя | ' | ['] имя | pfa |
Стандарт-83 | ' имя | ' | ['] имя | cfa |
ПОЛЬЗОВАТЕЛЬСКИЕ ПЕРЕМЕННЫЕ
Пользовательская переменная в отличие от обычной (определяемой с помощью слова VARIABLE), значение которой хранится в поле параметров элемента словаря, состоит из двух частей. Сами данные хранятся в массиве, называемом пользовательской таблицей. Элемент словаря для каждой пользовательской переменной расположен в другом месте; он содержит смещение в пользовательской таблице. Когда вы выполняете имя пользовательской пере-
1 Для начинающих. Термин «мультизадачная» описывает систему, в которой многочисленные задачи выполняются одновременно на одном и том же компьютере, не оказывая влияния друг на друга.
менной, например Н, смещение добавляется к начальному адресу пользовательской таблицы, что дает вам адрес Н в этом массиве и позволяет применять @ или !.
Основное достоинство пользовательских переменных состоит в том, что любое число задач может использовать одно и то же определение некоторой переменной, и каждая задача может получать свое собственное значение этой переменной. Всякая задача, которая выполняет выражение BASE @
получает значение BASE из своей пользовательской таблицы, благодаря чему экономится большой объем памяти, и тем не менее она может выполняться независимо от остальных задач.
Порядок размещения пользовательских переменных в таблице и значения и смещений изменяются от системы к системе. Итак, существуют переменные трех видов:
системные, применяемые всей Форт-системой;
пользовательские, являющиеся уникальными для каждой задачи, несмотря на то что их определения могут быть использованы всеми задачами системы;
обычные, которые могут быть доступными либо во всей системе, либо в пределах единственной задачи (в зависимости от того как они определены: внутри слова OPERATOR или внутри частной задачи).
ПОСТФИКСНАЯ ЗАПИСЬ
«Но, позвольте, — скажете вы, — почему на Форте нужно писать 3 4 +
а не 3 + 4
как это принято?» Дело в том, что в Форте применяется постфиксная запись (называемая так потому, что знак операции располагается после чисел) вместо инфиксной (где знак операции располагается между числами), поэтому все слова, которым «требуются» числа, могут их взять из стека. Например, слово + берет из стека два числа и складывает их, слово . берет одно число и выводит его, SPACES берет одно число и выводит соответствующее ему количество пробелов, EMIT берет число, которое изображает какой-либо символ, и выводит этот символ. Даже слово STARS, которое мы определили сами, берет число из стека и выводит заданное этим числом количество звездочек.
Когда все операции, которые должны быть выполнены над числами, уже находящимися в стеке, определены, обеспечить взаимодействие между ними достаточно просто, даже если программа становится сложной.
Ранее отмечалось, что Форт позволяет использовать слово любым из двух способов: либо называя его, либо применяя в определении другого слова, тем самым именуя то другое слово. Постфиксная запись является частью механизма, который предоставляет вам такую возможность. Например, предположим, что вам требуется слово, которое всегда прибавляет число 4 к любому числу, находящемуся в стеке. Назовем это слово ПЛЮС-ЧЕТЫРЕ
что означает «больше на четыре», и определим его следующим образом: : ПЛЮС-ЧЕТЫРЕ 4 + ;<return>
Выполним проверки 3 ПЛЮС-ЧЕТЫРЕ .<return> 7 ok
и еще раз -10 ПЛЮС-ЧЕТЫРЕ .<return> -6 ok
Число 4, находящееся внутри определения, помещается в стек таким же образом, как если бы оно находилось вне определения. Затем слово + складывает два числа, хранящихся в стеке. Так как операция сложения всегда выполняется над содержимым стека, тот факт, что число 4 пришло из определения, а 3 нет, не имеет значения.
В дальнейшем мы перейдем к более сложным примерам, и тогда процесс занесения значений в стек и арифметика в постфиксной записи будут вам намного понятнее. Чем больше операций вовлекается в процесс, тем важнее становится вопрос взаимодействия между ними. Применение стека упрощает это взаимодействие.
ПОСТРОЕНИЕ ПРОГРАММЫ ВВОДА ЧИСЕЛ С ПОМОЩЬЮ СЛОВА KEY
В данном разделе мы покажем вам, как слово KEY может быть использовано при разработке специализированного интерпретатора ввода с клавиатуры. Допустим, вы хотите создать слово, подобное EXPECT, которое ожидало бы ввода с клавиатуры, но воспринимало бы только числовые символы. Остальные символы не должны восприниматься. Исключение составляют сигналы, посылаемые при нажатии клавиш ЗАБОЙ (в обычном значении) и RETURN (конец ввода). В отличие от EXPECT ваше слово не будет завершать свое выполнение при вводе заданного числа цифр, так как пользователь может, нажав клавишу ЗАБОЙ, исправить последнюю цифру до нажатия клавиш RETURN. Назовем это слово EXPECT# и зададим ему следующий порядок аргументов:( a макс-длина -- факт-длина)
где а - поле для размещения строки, а макс-длина - предельное число вводимых цифр. Фактическая длина может понадобиться в том случае, если вы захотите проверить, ввел ли пользователь хотя бы одну цифру.
Вам придется осуществлять такие действия, как возврат курсора на одну позицию, что программируется в разных системах по-разному. Принято вычленять подобные фрагменты в слова Форта и определять их отдельно от остальной части программы. Это делает программу более мобильной, поскольку при смене системы достаточно будет заменить лишь соответствующие слова. Такой прием называется локализацией или упрятыванием информации, не существенной для пользователя. Для большинства компьютеров мы можем написать определение: : ЗАБОЙ 8 EMIT ;
но в некоторых системах, где применяются устройства вывода с распределенной памятью, могут потребоваться другие определения. Кроме того, символы, получаемые словом KEY при нажатии клавиш ЗАБОЙ и RETURN, в различных системах различны. Упрятывание информации происходит следующим образом:: ЗБ? ( с -- t=клавиша-забоя) 8 = ; \ в некот.сист. 12 : ВК? ( с -- t=клавиша-RETURN) 13 = ; \ в некот.сист. 0
Основной конструкцией слова EXPECT# является цикл BEGIN WHILE REPEAT. Здесь WHILE выполняет проверку на нажатие клавиши RETURN.
Цикл начинается со слова KEY. Если введена правильная цифра, вы обрабатываете ее с помощью слова ПОЛУЧЕНИЕ (посылаете в буфер и увеличиваете соответствующий указатель). В случае «ЗАБОЯ» вы уничтожаете последний символ посредством слова НАЗАД (заполняете пробелом позицию последнего введенного символа и уменьшаете значение указателя). Используя таким образом слово KEY в цикле, можно создавать любые интерпретаторы клавиатуры или редакторы. Последнее определение, ЦИФРЫ, демонстрирует использование слова EXPECT#. Итак, в листинге не осталось больше ничего, что было бы вам не известно, поэтому никаких причин для того, чтобы отложить рассмотрение этой программы, у вас нет. (Более подробную информацию вы найдете в [1].)Block # 350 0 \ Ввод чисел часть 1 1 2 : НАЗАД 8 EMIT ; 3 : ЗБ? ( с -- t=клавиша-забоя) 8 = ; \ в некот.сист. 12 4 : ВК? ( с -- t=клавиша-RETURN) 13 = ; \ в некот.сист. 0 5 : #? ( с -- t=правильная-цифра) ASCII 0 ASCII 9 1+ WITHIN ; 6 7 8 9 10 11 12 13 14 15
Block # 351 0 \ Ввод чисел часть 2 1 : ПОЛУЧЕНИЕ ( 1-й-адр посл-а+1 текущ-адр с - 1-й-адр 2 посл текущ') 3 >R 2DUP > IF R@ DUP EMIT OVER C! 1+ THEN R> DROP ; 4 : НАОБОРОТ ( 1-й-адр посл-а+1 текущ-адр с -- 1-й-адр поcл 5 текущ' ) 6 DROP SWAP >R 2DUP < IF НАЗАД SPACE НАЗАД 1- DUP 1 BLANK 7 THEN R> SWAP ; 8 : EXPECT# ( а макс-длина -- факт.-длина) 9 OVER + OVER BEGIN KEY DUP ВК? NOT WHILE 10 DUP #? IF ПОЛУЧЕНИЕ ELSE 11 DUP ЗБ? IF НАОБОРОТ ELSE DROP 12 THEN THEN REPEAT ROT 2DROP SWAP - ; 13 14 : ЦИФРЫ ( #цифр -- d) 15 PAD SWAP 2DUP 1+ BLANK EXPECT# DROP PAD 1- NUMBER ;
ПРАКТИЧЕСКИЕ ЗАДАЧИ НА ПРИМЕНЕНИЕ ПОСТФИКСНОЙ ЗАПИСИ (УПРАЖНЕНИЕ 2-А]
Для начала представьте выражения из левого столбца в постфиксной записи, используя только карандаш и бумагу. Например, если дано ab + c 2 * 3 + 4 (10)
У вас должно получиться следующее: a b * c +
Затем проверьте полученные выражения, подставляя в них числа из среднего столбца и применяя Форт в режиме калькулятора. Правильный результат приведен в правом столбце. В нашем случае 2 3 * 4 + . 10 ok
1. c(a + b) 3 ( 4 + 5 ) (27)
ab 80 * 90 2. ---- --------- (72) 100 100
3a - b ( 3 * 9 ) - 7 3. ------ + c ------------- + 2 (7) 4 4
a + 1 7 + 1 4. ----- ----- (2) 4 4
5. x(7x + 5) 10 ((7 * 10) + 5) (750)
Преобразуйте следующие выражения из постфиксной формы в инфиксную: 6. a b - b a + /
7. a b 10 * /
Ответы к упражнению 2-A
1. a b + c * или c a b + * 2. a b * 100 / 3. 3 a * b - 4 / c + 4. a 1 + 4 / 5. 7 x * 5 + x * a - b 6. ----- b + a a 7. --- 100
ПРАВИЛА ЗАПИСИ ФОРТ-ПРОГРАММ
Вернемся к нашему учебному блоку из предыдущего раздела. Хороший стиль программирования на Форте требует отводить строку 0 любого блока под комментарий — краткое описание функционального назначения определений, содержащихся в данном блоке, В комментарий часто включают дату внесения последнего изменения и инициалы программиста.
1 Относительно Форта, функционирующего под управлением других операционных систем. Многие Форт-системы работают под управлением других операционных систем, например, СР/М или MS-DOS. В них блоки Форта представляют собой участки размером в 1К в специально зарезервированном для этих целик файле (или файлах). Как открывать и закрывать такие файлы, объясняется в документации по вашей системе.
Мы использовали в приведенном выше примере для выделения комментария круглые скобки. В некоторых системах имеется слово \ (пропуск строки), которое предписывает интерпретатору пропустить остаток строки (все что находится справа от этого слова). По аналогии со словом ( слово \ начинает комментарий, но в отличие от него не требует ограничителя. Вы можете помещать \ в середину любой строки: ее содержимое слева от этого слова будет обработано интерпретатором, справа — нет.
Похожее слово \S (пропуск экрана) предписывает интерпретатору игнорировать дальнейший текст в данном блоке. В отдельных Форт-системах для таких целей применяется слово EXIT.
Ниже приводятся еще несколько правил, позволяющих сделать текст в блоке удобочитаемым.
Отделяйте комментарий с обеих сторон двумя пробелами. Если стековый комментарий отсутствует, отделяйте имя определения от содержательной части тремя пробелами.
Разбивайте определения на фрагменты, разделяемые двумя пробелами.
Если определение занимает более одной строки, делайте отступ во всех строках, кроме первой.
Не помещайте на одной строке более одного определения.
Определения должны быть краткими! В среднем определение должно занимать две строки.
В книге автора «Думаем на Форте» [1] хорошему стилю программирования на Форте посвящена целая глава.
Итак, запомните две команды, введенные в этом разделе: \ ( — ) Пропуск оставшегося текста данной строки. \S ( — ) Пропуск оставшегося текста экрана.
ПРЕИМУЩЕСТВА ШЕСТНАДЦАТЕРИЧНОЙ СИСТЕМЫ СЧИСЛЕНИЯ (И ДРУГИЕ СИСТЕМЫ)
Как только вы начнете серьезно программировать, вам, кроме двоичной и десятичной систем счисления, понадобятся другие системы, особенно шестнадцатиричная (основание 16) и восьмиричная (основание 8). Позднее мы их подробно рассмотрим, а пока вам не помешает небольшое введение.
Программисты начали использовать шестнадцатиричные и восьмиричные числа потому, что компьютеры «думают» в двоичной системе, а человеку очень трудно воспринимать числа, состоящие из длинного ряда двоичных цифр. Намного проще перевести двоичное число в шестнадцатиричное, чем двоичное в десятичное, поскольку 16 является степенью числа 2, в то время как 10 - нет. То же самое справедливо и для восьмиричной системы. Поэтому программисты обычно применяют для записи двоичных чисел, которыми компьютер обозначает адреса и машинные коды, шестнадцатиричную или восьмиричную системы. Шестнадцатиричная система выглядит на первый взгляд необычно, так как в ней задействованы буквы от А до F.ДЕСЯТИЧНАЯ ДВОИЧНАЯ ШЕСТНАДЦАТИРИЧНАЯ
0 0000 О 1 0001 1 2 0010 2 3 0011 3 4 0100 4 5 0101 5 6 О11О 6 7 0111 7 8 1000 8 9 1001 9 10 1010 А 11 1011 В 12 1100 С 13 1101 D 14 1110 Е 15 1111 F
Возьмем двоичное число одинарной длины: 0111101110100001. Для того чтобы перевести это число в шестнадцатиричную систему, мы сначала должны разбить его на тетрады: 0111 1011 1010 0001
затем заменить каждую тетраду на ее шестнадцатиричный эквивалент: 7 В А 1 или просто 7ВА1.
В восьмиричной системе используются только цифры от 0 до 7. Большинство современных компьютеров применяет шестнадцатиричную систему, поэтому мы не станем приводить пример на перевод в эту систему. Перевод чисел из одной системы в другую рассматривается более детально в настоящей главе позднее.
ПРЕОБРАЗОВАНИЕ ВВОДИМЫХ ЧИСЕЛ
Как было показано в гл. 7, с помощью слов <# и #> можно преобразовать число, находящееся в стеке, в строку символов в коде ASCII. Слово CONVERT (ПРЕОБРАЗОВАТЬ) выполняет обратную функцию: оно переводит строку символов ASCII, представляющих некоторое число, в двоичную форму и заносит его в стек.
CONVERT |
( ud1 a1 -- ud2 а2) | Начиная с адреса a1+1 (байт, содержащий длину, пропускается), CONVERT преобразует строку в двоичное значение, которое зависит от текущей системы счисления (значения BASE). Полученное значение накапливается в ud1, и остается на стеке как ud2. Процесс продолжается до тех пор, пока не встретится символ, который не может выть истолкован как цифра в текучей системе счисления. Адрес этого символа заносится на стек как а2. |
Приведем несложный пример:: ПЛЮС \ n2 ( n1 -- сумма ) 0 0 BL WORD CONVERT 2DROP + ;
Это определение можно использовать следующим образом: 12 ПЛЮС 23 . 35 ок
Слово ПЛЮС является лучшим доказательством (для скептиков) того, что в Форте можно применять инфиксную запись выражений. Это слово начинает выполняться при одном занесенном в стек в двоичной форме аргументе 12. В первую очередь, занося в стек нуль двойной длины, мы очищаем место для накопления, после чего слово WORD сканирует входной поток для чтения второго аргумента n2 и оставляет адрес прочитанного фрагмента в вершине стека. Слово CONVERT преобразует строку по заданному адресу (пропуская первый байт со счетчиком) в число двойной длины и заносит его в стек на место нуля. 2DROP удаляет из стека последний адрес, помещенный в него словом CONVERT вместе со старшей ячейкой преобразованного числа, превращая последнее тем самым в число одинарной длины. Наконец, + складывает два верхних числа в стеке.
Если бы слово CONVERT функционировало с такими аргументами: ( а -- ud)
то его можно было бы применять повторно, выполняя преобразования строки, содержащей различные нецифровые символы. Например, строку 6/20/85
можно преобразовать в три числа одинарной длины, трижды подряд обращаясь к CONVERT.
Адрес, оставленный в вершине стека при первом применении CONVERT передавался бы аргументом второму CONVERT и т. д.
В большинстве систем имеется слово NUMBER (число), которое выполняет те же функции, но зачастую проще в употреблении. В Стандарте-83 (слова несогласованного набора) это слово определено следующим образом:
NUMBER |
( a -- d) |
Преобразование текста, начинающегося с адреса а+1, в двоичное значение с учетом текущей системы счисления (значения BASE). Строка может предваряться знаком минус, что делает полученное значение отрицательным. |
Слово NUMBER используется и самой ФОРТ-системой. Это «обработчик чисел», к которому обращается текстовый интерпретатор, если искомое слово не найдено в словаре. NUMBER пытается преобразовать полученный фрагмент в число и в случае удачи заносит его значение в стек, при неудаче же осуществляется ABORT.
В каждой Форт-системе процесс преобразования чисел происходит по-своему, так как способов их введения существует очень много. Ниже будет показано одно из возможных определений слова NUMBER, которое воспринимает символы : , - . /
как правильные пунктуационные знаки, указывающие, что данный фрагмент нужно считать числом двойной длины. Если внутри какого-либо числа появился один из перечисленных символов, то в переменную DPL (положение десятичной точки) заносится количество цифр в числе справа от точки. Например, при вводе фрагмента 200.2 DPL содержит единицу. Если в числе нет знаков пунктуации, то значение DPL окажется равным -1.\ Определение слова NUMBER : NUMBER? ( адр - d t-успешное-завершение) DUP 1+ С@ Получение первой цифры ASCII - = Это знак минус? DUP >R Запоминать флага в стеке возвратов - Если первым символом является "-" , то к адр добавляется 1, чтобы тот указывал на первую цифру ( вычитание -1 равносильно прибавлению 1) -1 DPL ! Отметка того, что знаков пунктуации пока нет. 0 0 ROT В качества первоначального накапливаемого значения берется 0 двойной длины.
BEGIN CONVERT Преобразование до первого символа, не являющегося цифрой DUP C@ DUP ASCII : = SWAP ASCII . ASCII / 1+ Это запятая, дефис, точка или слэш? WITHIN OR WHILE 0 DPL ! REPEATE Если да, то переустановить DPL и продолжать -ROT R> IF DNEGATE THEN Перемещение d в вершину. Если значение в стеке возвратов указывает минус, то число делается отрицательным. ROT С@ BL = ; Является ли последний, непреобраэованный, символ пробелом, как это и должно быть?
: NUMBER ( адр -- d) NUMBER? NOT ABORT" ?" ; Если преобразование завершилось неудачей, то аварийный выход посредством ABORT.
В приведенном определении учитывается, что CONVERT вычитает из DPL по единице при обработке каждой цифры до тех пор, пока значение переменной не станет равным -1. Кроме того, в определении используется слово WITHIN, аналогичное слову ВНУТРИ (см. упражнение к гл. 4).
В качестве «истины» принято арифметическое значение -1, как это определено Стандартом-83. Для более ранних систем, где значением истины является единица, в строке 4 нужно заменить «-» на «+».
В Форте число, вводимое без знаков пунктуации, заносится в стек как число одинарной длины. При рассмотренном здесь определении слова NUMBER текстовый интерпретатор должен обращаться к нему примерно так: ... NUMBER DPL @ -1 = IF DROP THEN ...
А.ОТВЕТЫ К УПРАЖНЕНИЯМ
ГЛАВА 1 1. : ДАР ." подставку для книг" ; : ДАРИТЕЛЬ ." Маша" ; : БЛАГОДАРНОСТЬ CR ." Дорогая " ДАРИТЕЛЬ ." ," CR 5 SPACES ." спасибо за " ДАР ." . " ;
2. : МЕНЬШЕ-НА-ДЕСЯТЬ ( n - n-10 ) -10 + ; или : МЕНЬШЕ-НА-ДЕСЯТЬ ( n - n-10 ) 10 - ;
3. При компиляции определения БЛАГОДАРНОСТЬ компилятор включает в него определение слова ДАРИТЕЛЬ, существующее на момент компиляции. Если вы после компиляции слова БЛАГОДАРНОСТЬ добавите в словарь новый вариант слова ДАРИТЕЛЬ, то этот никак не отразится на уже скомпилированном слове БЛАГОДАРНОСТЬ. ( Но вы можете переопределить ( перекомпилировать ) и слово БЛАГОДАРНОСТЬ. В этом случае в его определение войдет новый вариант слова ДАРИТЕЛЬ.)
ГЛАВА 2
1. DUP DUP: (1 2 -- 1 2 2 2) 2DUP: (1 2 -- 1 2 1 2)
2. : NIP ( а b - b ) SWAP DROP ;
3. : TUCK ( а Ь - Ь a b) SWAP OVER ;
4. : -ROT ( а Ь с - cab) ROT ROT ;
5. SWAP 2SWAP SWAP
6. : 3DUP ( n1 n2 n3 - n1 n2 n3 n1 n2 n3) DUP 2OVER ROT ; 7. : 2-7 ( c a b - n) OVER + * + ;
8. : 2-8 ( a b - n) 2DUP - ROT ROT + / ;
9. : УПАКОВКА ( #яиц - ) 12 /MOD . ." коробок и " . ." не упаковано " ;
ГЛАВА 4
1. -1 0= NOT . -1 ok
0 0= NOT . 0 ok
200 0= NOT . -1 ok
2. Не спросит ничего.
3. ( употреблять спиртные напитки можно только с 21 года, тогда:) : РАЗРЕШЕНИЕ ( возраст - ) 20 > IF ." Употребление алкоголя разрешено " ELSE ." Вы еще молоды " THEN ;
4. : ЗНАКИ ( n) DUP 0= IF ." Нуль " ELSE DUP 0< IF ." Отрицательное " ELSE ." Положительное " THEN THEN DROP ; ( или как-нибудь иначе - лишь бы работало)
5. : <> ( n1 n2 - ?) = NOT ;
6. : XOR ( х у - ?) 2DUP NOT AND SWAP ROT NOT AND OR ;
7. : STARS ( n - ) ?DUP IF STARS THEN ;
8. : NEGATE ( n - -n) 0 SWAP - ; : ABS ( n - |n| ) DUP 0< IF NEGATE THEN ;
9. : /UP ( делимое делитель - частное) /MOD SWAP IF 1+ THEN ;
10. : -ROT ( а Ь с - c a b) ROT ROT ; : WITHIN ( n l h - ?) -ROT OVER > NOT -ROT > AND ; Ниже приводится более эффективный вариант, в котором используются приемы, рассмотренные в следующих главах: : WITHIN ( n l h - ?) OVER - >R - R> U< ;
11. : УГАДАЙ ( ответ попытка - ответ ) 2DUP = IF ." Вы угадали! " 2DROP ELSE 2DUP < IF ." Слишком много " ELSE ." Слишком мало " THEN DROP THEN ;
12. : .ОТРИЦАТЕЛЬНОЕ ( n - |n|) 0< IF ." Отрицательное " ABS THEN : ПРОПИСЬ ( n - ) DUP ABS 4 > IF ." Выходит за границу " ELSE DUP .ОТРИЦАТЕЛЬНОЕ DUP 0= IF ." Нуль " ELSE DUP 1 = IF ." Один " ELSE DUP 2 = IF ." Два " ELSE DUP 3 = IF ." Три " ELSE ." Четыре " THEN THEN THEN THEN THEN DROP ;
13. в предположении,что -ROT и WITHIN уже загружены: : 3DUP ( a b c - a b c a b c) DUP 2OVER ROT ; : ЛОВУШКА ( ответ -меньш-число -большее-число -- ответ | -- ) 3DUP OVER = -ROT = AND IF ."Вы угадали! " 2DROP DROP ELSE 3DUP SWAP 1 + SWAP WITHIN IF ." Между " ELSE ." Вне " THEN 2DROP THEN ;
ГЛАВА 5
1. -1 интерпретируется как число "отрицательная единица" ; 1- является словом форта, которое вычитает единицу из значения на стеке.
2. */ NEGATE
3. МАХ МАХ МАХ .
4. а) : 2ЗНАЧ ( n1 n2 - n? n?) \ помещение большего значения на вершину 2DUP > IF SWAP THEN ;
б) : 3ЗНАЧ ( n1 n2 nЗ - n? n? n?) \ большее значение на вершину 2ЗНАЧ >R 2ЗНАЧ R> 2ЗНАЧ ; ( Вы можете продолжать в том же духе и далее ... ) : 4ЗНАЧ \ иэ четырех элементов стека больший поместить в вершину 3ЗНАЧ >R 3ЗНАЧ R> 2ЗНАЧ ; ...последним оператором во всем случаях должен быть 2ЗНАЧ.)
в) : ?ОБЪЕМ ( длина ширина высота - ) \ в любом порядке 3ЗНАЧ 22 > ROT 6 > ROT 19 >
AND AND IF ." Подходит " THEN ; ( Автор благодарит за пример Микаэла Хэма.)
5. : РИСУЙ ( n - ) CR 80 100 */ STARS ;
6. а) 0 32 - 10 18 */ . -17 ok
б) 212 32 - 10 18 */ . 100 ok
в) -32 32 - 10 18 */ . -35 ok
г) 16 18 10 */ 32 + . 60 ok
д) 233 273 - . -40 ok
7. : Ф>Ц ( фаренг - цельс) 32 - 10 18 */ ; : Ц>Ф ( цельс - фаренг) 18 10 */ 32 + ; : К>Ц ( кельв - цельс) 273 - ; : Ц>К ( цельс - кельв) 273 + ; : Ф>К ( фаренг - кельв) Ф>Ц Ц>К ; : К>Ф ( кельв - фаренг) К>Ц Ц>Ф ;
Block# 270 0 \ Ответы к упражнениям: глава 6 1 \ Упражнения 1-6 2 : STARS ( n) 0 ?DO 42 EMIT LOOP ; 3 : КЛЕТКА ( ширина высота - ) 0 DO CR DUP STARS LOOP DROP ; 4 : \STARS ( #строк - ) 0 DO CR I SPACES 10 STARS LOOP ; 5 : /STARS ( #строк - ) 6 1- 0 SWAP DO CR I SPACES 10 STARS -1 +LOOP ; 7 \ Определение /STARS с использованием конструкции BEGIN ... UNTIL: 8 : A/STARS ( #строк) 9 BEGIN 1- CR DUP SPACES 10 STARS DUP 0= UNTIL DROP ; 10 11 \ РОМБЫ определены в два этапа: 12 : ТРЕУГОЛЬНИК ( приращение граница индекс - ) 13 DO CR 9 I - SPACES I 2* 1+ STARS DUP +LOOP DROP ; 14 : РОМБЫ ( #ром6ов - ) 15 0 DO 1 10 0 ТРЕУГОЛЬНИК -1 0 9 ТРЕУГОЛЬНИК LOOP CR :
Block# 271 0 \ Ответы к упражнениям; глава 6; продолжение 1 \ Упражнение 7: 2 : THRU ( от до - ) 1+ SWAP DO I DUP . LOAD LOOP ; 3 \ Упражнение 8 4 : R%, ( n1 % - n2) 10 */ 5 + 10 / ; 5 : УДВОЕНО ( вклад процент - ) 6 OVER 2* SWAP ROT 21 1 DO 7 CR ." Год " I 2 .R 3 SPACES 8 2DUP R% + DUP ." Сумма " ? DUP 2OVER DROP > IF 10 CR CR ." Более чем удвоено через " I . ." лет " LEAVE 11 THEN LOOP 2DROP DROP ; 12 \ Упражнение 9 13 : ** (n1 n2 - n1 -в-степени-n2) 14 1 SWAP ?DUP IF 0 DO OVER * LOOP THEN SWAP DROP ; 15 \ Спасибо за упражнение Дж.И.Андересну, Эдинбург, Шотландия
Block# 272 0 \ Ответы к упражнениям; глава 7 1 \ Упражнение 1: 2 : N-MAX 0 BEGIN 1+ DUP 0< UNTIL 1- . ; 3 ( Начиная с нуля, увеличиваем значение на стеке до тех пор, пока оно 4 не станет отрицательным - это означает, что достигнута граница пред- 5 ставления целых чисел. Последний оператор 1- возвращает значение 6 перед достижением границы.) 7 8 \ Упражнение 2 (а-е): 9 : BYNARY 2 BASE ! ; 10 : БИТОВЫЙ ( #бита - позиция-бита) 1 SWAP 0 ?DO 2* LOOP ; 11 : УСТАНОВИТЬ-БИТ ( битовый1 #бита - битовый2) БИТОВЫЙ OR ; 12 : ОЧИСТИТЬ-БИТ ( битовый1 #бита - битовый2) БИТОВЫЙ -1 XOR AND ; 13 : ДАЙ-БИТ ( битовый #бита - бит ) БИТОВЫЙ AND ; 14 : ПЕРЕКЛЮЧИТЬ-БИТ ( битовый1 #бита - битовый2) БИТОВЫЙ XOR ; 15 : ИЗМЕНЕНИЕ ( (битовый1 битовый2 - битовый3 ) XOR ;
Block# 273 0 \ Ответы к упражнениям; глава 7, продолжение 1 \ Упражнение 3: 2 : БИП ." Бип " 7 EMIT ; 3 : ЗАДЕРЖКА 20000 0 DO LOOP ; 4 : 3ЗВОНКА БИП ЗАДЕРЖКА БИП ЗАДЕРЖКА БИП ; 5 6 \ Упражнение 4а: 7 : Ф>Ц -320 М+ 10 18 М*/ ; 8 : Ц>Ф 18 10 М*/ 320 М+ ; 9 : К>Ц -2732 М+ ; 10 : Ц>К 2732 М+ ; 11 : Ф>К Ф>Ц Ц>К ; 12 : К>Ф К>Ц Ц>Ф 5 13 \ Упражнение 4б: 14 : .ГРАДУС ( d - ) DUP >R DABS 15 <# # 46 HOLD #S R> SIGN #> TYPE SPACE ;
Block# 274 0 \ Ответы к упражнениям; глава 7, продолжение 1 \ Упражнение 5: ( в результате получается 17513;считается довольно долго.) 2 : ВЫЧИСЛ ( х - dv) 3 DUP 7 М* 20 М+ ROT 1 М*/ 5 M+ ; 4 : ?DMAX 0 BEGIN 1+ DUP ВЫЧИСЛ 0 в D< UNTIL 1- . ; 5 6 \ Упражнение 6: 7 В 16-чной системе десятичная цифра имеет такое же значение. 8 9 \ Упражнение 7: 10 : BINARY 2 BASE ! ; 11 : 3-СИСТЕМЫ 12 17 0 DO CR ." Десятичная" DECIMAL I 4 .R 8 SPACES 13 .' 16-ричная" HEX I 3 .R 8 SPACES 14 ." Двоичная" BINARY I S .R 8 SPACES 15 LOOP DECIMAL ;
Block# 275 0 \ Ответы к упражнениям глава 7, продолжение 1 \ Упражнение 8: 2 ( 3.7 интерпретируется как число двойной длины, поскольку содержит 3 десятичную точку, и поэтому занимает два элемента стека, 4 Так как 37 является небольшим числом, то его старшая часть 5 состоит из нулей. "." является оператором над значением одинарной 6 длины; две же точки подряд выводят обе части значения двойной 7 длины. Старшая часть располагается на вершине стека, поэтому 8 ее выводит первая точка; Вторая точка выводит младшую часть - 9 37.) 10 ( Число 65536 в точности на единицу превышает число, которое уме- 11 щается в 16 разрядах. Поэтому 17-й разряд становится равным "1", 12 а все остальные биты превращаются в нули. 17-й бит числа двойной 13 длины является крайним правым битом старшей части. Она выводится 14 как "1". Младшая часть выводится как нули.) 15 ( Число 65538 больше на два, поэтому младшая часть выглядит как "2".)
Block# 276 0 \ Ответы к упражнениям; глава 7, продолжение 1 \ Упражнение 9: 2 ( Поскольку данный фрагмент не является словом, форт интерпретирует 3 его как число. Так как NUMBER интерпретирует точку как разделитель 4 целой и дробной части числа, что для него является признаком числа 5 двойной длины, то он на стек поместит ноль двойной длины.) 6 7 \ Упражнение 10: 8 : .ТЕЛЕФОН ( d - ) <# # # # # ASCII - HOLD # # # 9 OVER IF ASCII / HOLD #S THEN #> TYPE SPACE ; 10 11 12 13 14 15 Block# 277 0 \ Ответы к упражнениям; глава 8 1 \ Упражнение 1-а 2 VARIABLE ПИРОЖКИ 0 ПИРОЖКИ ! 3 : ИСПЕКИ-ПИРОЖОК 1 ПИРОЖКИ +! ; 4 : СЪЕШЬ-ПИРОЖОК ПИРОЖКИ @ IF -1 ПИРОЖКИ +! ." Спасибо " 5 ELSE ." Какой пирожок?" THEN ; 6 \ Упражнение 1-б 7 VARIABLE ЗАМОРОЖЕННЫЕ-ПИРОЖКИ 0 ЗАМОРОЖЕННЫЕ-ПИРОЖКИ ! 8 : ЗАМОРОЗЬ-ПИРОЖКИ ПИРОЖКИ @ ЗАМОРОЖЕННЫЕ-ПИРОЖКИ +! 0 ПИРОЖКИ ! ; 9 \ Упражнение 2: 10 : .БАЗА BASE @ DUP DECIMAL . BASE ! ; 11 \ Упражнение 3 ( сверх-надежный вариант): 12 : S>D ( п - d) DUP 0< ; \ из одинарной в двойную длину 13 : М. ( d - ) TUCK DABS 14 <# DPL @ DUP -1 <> IF В ?DO # LOOP ASCII . HOLD ELSE 15 DROP S>D THEN #S ROT SIGN #> TYPE SPACE ;
Block# 278 0 \ Ответы к упражнениям; глава 8, Упражнение 4 1 2 CREATE #КАРАНДАШЕЙ 8 ALLOT \ карандаши четырех цветов 3 0 CONSTANT КРАСНЫХ 2 CONSTANT ГОЛУБЫХ 4 4 CONSTANT ЗЕЛЕНЫХ 6 CONSTANT ОРАНЖЕВЫХ 5 6 : КАРАНДАШЕЙ ( смещение - а) #КАРАНДАШЕЙ + ; 7 8 23 КРАСНЫХ КАРАНДАШЕЙ . 9 15 ГОЛУБЫХ КАРАНДАШЕЙ ! 10 12 ЗЕЛЕНЫХ КАРАНДАШЕЙ ! 11 0 ОРАНЖЕВЫХ КАРАНДАШЕЙ I 12 13 \ Для проверки мы можем ввести, например, следующий текст: 14 \ ГОЛУБЫХ КАРАНДАШЕЙ ? 15 ok
15
Block# 279 0 \ Ответы к упражнениям; глава 8, Упражнение 5 1 2 CREATE 'ШАБЛОНЫ 20 ALLOT ( 10 ячеек) 3 : ШАБЛОНЫ ( i - а ) 2* 'ШАБЛОНЫ + ; 4 : STARS ?DUP IF 0 DO 42 EMIT LOOP THEN ; 5 : ИНИЦ-ШАБЛОНОВ 10 0 DO 16 MOD I ШАБЛОНЫ ! LOOP ; 6 7 : РИСУЙ ( - ) 8 100 DO CR I 2 .R SPACE I ШАБЛОНЫ @ STARS LOOP CR ; 9 10 ИНИЦ-ШАБЛОНОВ 11 12 13 14 15
Block# 280 0 \ Ответы к упражнениям; глава 8, упражнение 6 1 1 CONSTANT ЖЕНЩИНА 0 CONSTANT МУЖЧИНА 2 2 CONSTANT СЕМЕЙНЫЙ 0 CONSTANT ОДИНОКИЙ 3 4 CONSTANT РАБОТАЕТ 0 CONSTANT HE-РАБОТАЕТ 4 8 CONSTANT ГОРОДСКОЙ 0 CONSTANT HE-ГОРОДСКОЙ 5 VARIABLE ВАСЯ 6 VARIABLE ИРА 7 : ОПИСАНИЯ ( состояние состояние состояние состояние имярек -- ) 8 >R OR OR OR R> ! ; 9 МУЖЧИНА СЕМЕЙНЫЙ HE-РАБОТАЕТ HE-ГОРОДСКОЙ ВАСЯ ОПИСАНИЯ 10 ЖЕНЩИНА ОДИНОКИЙ РАБОТАЕТ ГОРОДСКОЙ ИРА ОПИСАНИЯ 11 12 13 14 15
Block# 281 0 \ Ответы к упражнениям; глава 8, упражнение 6, продолжение 1 : .ПОЛ ( битовый - ) женщина AND IF ." жен" THEN ." муж " ; 2 : .СЕМ-ПОЛ ( битовый - ) 3 СЕМЕЙНЫЙ AND IF ." семейный " ELSE ." одинокий " THEN ; 4 : .РАБОТА ( битовый - ) 5 РАБОТАЕТ AND 0= IF ." не " THEN ." работает " ; 6 : .ЖИТЕЛЬСТВО ( битовый - ) 7 ГОРОДСКОЙ AND 0= IF ." не " THEN ." городской " ; 8 : СВЕДЕНИЯ ( имярек - ) 9 @ DUP -ПОЛ DUP .СЕМ-ПОЛ DUP .РАБОТА .ЖИТЕЛЬСТВО ; 113 11 12 13 14 15
Block# 282 0 \ Ответы к упражнениям; глава 3, упражнение 7 1 CREATE ПОЛЕ 9 ALLOT 2 : КВАДРАТ ( #квадрата - а) ПОЛЕ + ; 3 : ОЧИСТИТЬ ПОЛЕ 9 0 FILL ; ОЧИСТИТЬ 4 : ЛИНИЯ ." | " ; 5 : ПОДЧЕРКИВАНИЕ CR 9 0 DO ASCII - EMIT LOOP CR ; 6 : .КЛЕТКА ( #квадрата - ) КВАДРАТ С@ DUP 0= IF 2 SPACES ELSE 7 DUP 1 = IF ." X " ELSE ." 0 " THEN THEN DROP ; 8 : КАРТИНКА CR 9 0 DO I IF I 3 MOD 0= IF 9 ПОДЧЕРКИВАНИЕ ELSE ЛИНИЯ THEN THEN I .КЛЕТКА LOOP CR QUIT ; 10 : ХОД ( игрок #квадрата - ) 11 1- 0 MAX 8 MIN КВАДРАТ С! ; 12 : X! ( #квадрата - ) 1 SWAP ХОД КАРТИНКА ; 13 : 0! ( #квадрата - ) -1 SWAP ХОД КАРТИНКА ; 14
15 Block# 283 0 \ Ответы к упражнениям; глава 9 1 2 \ Упражнение 1: 3 VARIABLE 'ПОЛУЧАЕМ 4 : ПОЛУЧАЕМ ( n n - ) 'ПОЛУЧАЕМ @ EXECUTE . : 5 : СКЛАДЫВАЯ ['] + 'ПОЛУЧАЕМ ! ; 6 : УМНОЖАЯ ['] * 'ПОЛУЧАЕМ ! ; 7 8 \ Упражнение 2: 9 \ Вы можете узнать это, введя 1в \ HERE U. 11 \ в начале работы или после применения системных команд, очи- 12 \ щающих словарь, таких как COLD или EMPTY. 13 14 15
Block # 284 0 \ Ответы к упражнениям; глава 7, продолжение 1 2 \ Упражнение 3: 3 \ Вы можете узнать это, введя 4 \ PAD HERE - U. 5 6 \ Упражнение 4: 7 \ а) Разницы нет. Переменная оставляет на стеке собственный pfa. 8 \ б) Пользовательская переменная оставляет на стеке адрес ячейки 9 \ из пользовательской таблицы, Элемент словаря, который ищется 10 словом, может находиться где угодно, 11 \ 12 13 14 15
Block # 285 0 \ Ответы к упражнениям; глава 9, продолжение 1 \ Упражнение 5, Решение 1: 2 CREATE 'ЧТО-ДЕЛАТЬ 12 ALLOT \ 6 ячеек 3 : ЧТО-ДЕЛАТЬ ( i -- а) 0 МАХ 5 MIN 2* 'ЧТО-ДЕЛАТЬ + ; 4 5 : ВСТРЕЧА ." Привет, я говорю на форте. " ; 6 : ПОСЛЕДОВАТЕЛЬНОСТЬ 11 1 DO I . LOOP ; 7 : ПЛИТКА 10 5 КЛЕТКА ; \ См. ответы к главе 6 6 : НИЧЕГО ; 9 10 ' ВСТРЕЧА 0 ЧТО-ДЕЛАТЬ ! ' ПОСЛЕДОВАТЕЛЬНОСТЬ 1 ЧТО-ДЕЛАТЬ ! 11 ' ПЛИТКА 2 ЧТО-ДЕЛАТЬ ! ' НИЧЕГО 3 ЧТО-ДЕЛАТЬ ! 12 ' НИЧЕГО 4 ЧТО-ДЕЛАТЬ ! ' НИЧЕГО 5 ЧТО-ДЕЛАТЬ ! 13 14 : ЧТО-НИБУДЬ ( индекс --) ЧТО-ДЕЛАТЬ @EXECUTE ; 15
Block # 286 0 \ Ответы к упражнениям; глава 9, продолжение 1 \ Упражнение 5, Решение 2; 2 CREATE 'ЧТО-ДЕЛАТЬ 12 ALLOT \ 6 ячеек 3 : ЧТО-ДЕЛАТЬ ( i -- а) 0 MАХ 5 MIN 2* 'ЧТО-ДЕЛАТЬ + ; 4 5 : ВСТРЕЧА ." Привет, я говорю на форте. " ; 6 : ПОСЛЕДОВАТЕЛЬНОСТЬ 11 1 DO I . LOOP ; 7 : ПЛИТКА 13 5 КЛЕТКА 5 \ см. ответы к главе 6 8 : НИЧЕГО ; 9 10 : ИНИЦИАЛИЗАЦИЯ ( - ) 11 6 0 DO ['] НИЧЕГО I ЧТО-ДЕЛАТЬ ! LOOP 12 ['] ВСТРЕЧА 0 ЧТО-ДЕЛАТЬ ! ['] ПОСЛЕДОВАТЕЛЬНОСТЬ 1 ЧТО-ДЕЛАТЬ ! 13 ['] ПЛИТКА 2 ЧТО-ДЕЛАТЬ ! ; 14 ИНИЦИАЛИЗАЦИЯ 15 : ЧТО-НИБУДЬ ( индекс - ) ЧТО-ДЕЛАТЬ @EXECUTE ;
Block # 287 0 \ Ответы к упражнениям; глава 10 1 \ Упражнение 1: 2 : СИМВОЛ ( i - а) 3 228 BLOCK + ; 4 : ЗАМЕНА ( c1 с2 - ) \ замена c1 на с2 5 1024 0 DO OVER I СИМВОЛ С@ = IF DUP I СИМВОЛ С! 6 UPDATE THEN LOOP 2DROP ; 7 8 \ Упражнение 2: 9 181 LOAD \ Случайные числа 10 \ ??? CONSTANT ПРЕДСКАЗАНИЯ \ номер блока под сообщения 11 : ПРЕДСКАЗАНИЕ CR 16 CHOOSE 64 * ПРЕДСКАЗАНИЯ BLOCK + 12 64 -TRAILING TYPE SPACE ; 13 \ Вы можете обращаться к своим собственным "предсказаниям".Занесите 14 \ их по одному на строку в свободный блок, л затем занесите номер 15 \ этого блока в приведенное выше определение константы ПРЕДСКАЗАНИЯ
Block# 238 0 \ Ответы к упражнениям; глава 10, продолжение 1 \ часть а: 2 : ДА/НЕТ? ( - t=Y | f=прочее) KEY DUP EMIT ASCII Y = ; 3 \ часть б: 4 : ДА/НЕТ? ( - t=Y | f=прочее) 5 KEY 95 AND DUP EMIT ASCII Y = ; 6 \ часть в; два возможных решения: 7 : ДА/НЕТ? ( - t=Y | f=N ) 8 BEGIN KEY 95 AND DUP ASCII Y = IF DROP TRUE EXIT ELSE 9 DUP ASCII N = IF 0= EXIT THEN THEN 10 DROP FALSE UNTIL ; 11 12 : ДА/НЕТ? ( - t=Y | f=N ) 13 BEGIN KEY 95 AND DUP ASCII Y = OVER ASCII N = OR NOT 14 WHILE DROP REPEAT ASCII Y = ; 15 Block# 289 0 \ Ответы к упражнениям; глав* 10, упражнение 4 1 1 : ЖИВОТНЫЕ LIT" КРЫСЫ БЫКА ТИГРА КРОЛИКА ДРАКОНА ЗМЕИ ЛОШАДИ 2 БАРАНА ОБЕЗЬЯНЫПЕТУХА СОБАКИ СВИНЬИ " ; 3 : .ЖИВОТНОЕ ( u - ) \ и изменяется от 0 до 11 4 8 * ЖИВОТНЫЕ 1+ + 8 -TRAILING TYPE ; 5 : (ГОРОСКОП) ( год - ) 6 1900 - 12 MOD 7 ." Вы родились в год " .ЖИВОТНОЕ 8 ASCII . EMIT CR ; 9 350 351 THRU \ загрузка определения EXPECT# 10 : ЦИФРЫ ( #цифр - d ) 11 DUP 0 DO ASCII EMIT LOOP DUP 0 DO ЗАБОИ LOOP 12 PAD SWAP 2DUP 1+ BLANK EXPECT# DROP PAD 1- NUMBER : 13 : ГОРОСКОП 14 CR ." В каком году вы родились? " 4 ЦИФРЫ 15 CR DROP (ГОРОСКОП) ;
Block# 290 0 \ Ответы к упражнениям; глава 10, упражнение 5 1 VARIABLE СТРОКА 2 : начало 0 строка ' ; 3 : добавить \ 1прилагательное, 2прилагательное, 3прилагательное, 4 \ существительное ( - ) 5 СТРОКА @ 0 БРЕД 60 BLANK UPDATE 6 3 0 DO 7 ASCII , WORD COUNT СТРОКА @ I БРЕД SWAP CMOVE UPDATE 8 LOOP 1 СТРОКА +! ; 9 \ или, используя TEXT: 10 : добавить \ 1прилагательное, 2прилагательное 3прилагательное, 11 \ существительное ( - ) 12 3 0 DO 13 ASCII , TEXT PAD СТРОКА @ I БРЕД 28 CMOVE UPDATE 14 LOOP 1 СТРОКА +! ;
15 Block# 291 0 \ Ответы к упражнениям; глава 10 1 \ Упражнение 6 2 : >ДАТА ( а -- n n ) 3 0 0 ROT CONVERT ROT >R 0 SWAP CONVERT ROT >R 4 0 SWAP CONVERT 2DROP 1900 + R> R> 256 * + SWAP ; 5 : СКАНИРОВАНИЕ BL WORD >ДАТА ; 6 7 \ Упражнение 7 8 VARIABLE STUFF \ первым блоком файла 9 300 STUFF \ является блок 300 10 : ЭЛЕМЕНТ ( i - а) 11 2* 1024 /MOD STUFF @ + BLOCK + UPDATE ; 12 \ Проверка виртуального массива: 13 : ИНИЦИАЛ-МАССИВ 600 0 ВО I I ЭЛЕМЕНТ ! LOOP ; 14 : .МАССИВ 600 0 DO I . SPACE I ЭЛЕМЕНТ ? LOOP ; 15
Block# 292 0 \ Ответы к упражнениям; глава 10, упражнение 7, продолжение 1 2 \ Теперь преобразуем виртуальный массив в файл: 3 : ИСПОЛЬЗОВАНО ( -- а) СВОБ @ BLOCK UPDATE ; 4 \ Переопределим ЭЛЕМЕНТ так, чтобы ИСПОЛЬЗОВАННЫЕ пропускались: 5 : ЭЛЕМЕНТ ( i - а) 6 1+ 2* 1024 /MOD СВОБ @ + BLOCK + UPDATE ; 7 8 : НЕТ-ИСП 0 ИСПОЛЬЗОВАНО ! ; 9 НЕТ-ИСП 10 : ПОМЕСТИТЬ ( n - ) ИСПОЛЬЗОВАНО @ ЭЛЕМЕНТ ! 1 ИСПОЛЬЗОВАНО +! ; 11 s ВНЕСТИ ( n1 n2 - ) SWAP ПОМЕСТИТЬ ПОМЕСТИТЬ ; 12 13 : ТАБЛИЦА CR ИСПОЛЬЗОВАНО @ 0 ?DO I 8 МOD 0= IF CR THEN 14 I ЭЛЕМЕНТ @ 8 .R LOOP CR ; 15
Block# 293 0 \ Ответы к упражнениям; глава 11 1 \ Упражнение 1: 2 : ЗАГРУЗКА ( n - ) CREATE , DOES> ( - ) @ LOAD ; 4 \ Упражнение 2: 5 : ОСНОВАНИЕ. ( n - ) CREATE , 6 DOES> ( n -- ) @ BASE @ SWAP BASE ' SWAP . BASE ! ; 7 8 \ Упражнение 3: 9 : МНОГО ( a -- ) CREATE , 10 DOES> ( -- ) @ SWAP 0 ?DO DUP EXECUTE LOOP DROP ; 11 ' CR МНОГО CRS 12 4 CRS 13 14 15
Block# 294 0 \ Ответы к упражнениям; глава 11, продолжение 1 \ Упражнение 4: 2 : TURNE [COMPILE] DO ; IMMEDIATE 3 : RETURNE [COMPILE] LOOP ; IMMEDIATE 4 : ПОПЫТКА 10 0 TURNE I . RETURNE ; 5 6 \ Упражнение 5: 7 : ЦИКЛЫ ( #раз - ) 8 >IN @ SWAP 0 DO DUP >IN ! INTERPRET LOOP DROP ; 9 10 11 12 13 14 15
Block# 295 0 \ Ответы к упражнениям; глава 11, упражнение 6: 1 : STAR * 42 EMIT ; 2 : .РЯД ( b - ) \ вывод звездочки на каждый бит из байта 3 CR 8 0 DO DUP 128 AND IF STAR ELSE SPACE THEN 4 2* LOOP DROP ; 5 VARIABLE ШАБЛОН 6 : БИТ ( t=не-пробел - ) 7 1 AND ШАБЛОН @ 2* + ШАБЛОН ! ; 8 : ЗВЕЗДЫ>БИТЫ ( а - b) 9 0 ШАБЛОН ! 8 OVER + SWAP DO I С@ BL <> БИТ LOOP 10 ШАБЛОН @ ; 11 : ЧТ-РЯД ( - b) 12 ASCII | WORD COUNT + 8 - ЗВЕЗДЫ>БИТЫ ; 13 : ФОРМА CREATE 8 0 DO ЧТ-РЯД С, . LOOP 14 DOES> 8 OVER + SWAP DO I С@ .РЯД LOOP CR ; 15
Block# 296 0 ФОРМА .L XXX | 1 X | 2 X | 3 X | 4 X | 5 X | 6 X X| 7 XXXXXXXX| 8 ФОРМА .В ХХХХХХХ | 9 X X| 10 X X| 11 XXXXXX | 12 X X| 13 X X| 14 X X| 15 XXXXXXX|
Block# 297 0 \ Ответы к упражнениям; глава 12, упражнение 1: 1 : ширина ( смещение длина - нов-смещение ) 2 CREATE OVER , DUP , + ; 3 13 \ начальная позиция внутри записи 4 16 ширина фамилия 5 12 ширина имя 6 24 ширина работа 7 12 ширина телефон 8 CONSTANT /ЗАПИСЬ ( количество байт на запись ) EXIT 9 По приведенным выше правилам компилируются одинаковые структуры. 10 "ширина" является определяющим словом, которое хранит на стеке 11 текущую позицию внутри поля. Для каждого паля "ширина" компили- 12 рует смещение и длину, а затем их складывает, чтобы получить 13 следующее смещение. После того, как определится "телефон", по- 14 лученное а результате значение является длиной всей записи и 15 превращается в константу /ЗАПИСЬ
Block# 296 0 \ Ответы к упражнениям; глава 12, упражнение 2: 1 : вызвать ( имя ( -- ) 2 имя запомнить ВВЕРХ -НАЙТИ IF ОТСУТСТВУЕТ 3 ELSE CR .ИМЯ телефон .ПОЛЕ THEN ; 4 5 6 7 8 9 10 11 12 13 14 15
Про ошибки на сайте обязательно сообщите .
ПРИМЕНЕНИЕ СЛОВА WORD
Помимо текстового интерпретатора многие слова Форта используют WORD. Так, слово CREATE выбирает из входного потока имя создаваемого слова, a FORGET - имя слова, которое должно быть забыто.
Вернемся к приведенному выше примеру с генератором бессмысленных сообщений. Хотелось бы иметь при таком генераторе удобные средства введения в базу данных бессмысленных фраз (текстовый редактор Форта не показывает нам, где начинается 20-й или 40-й столбец). Допустим, нам нужно определить для введения в базу данных очередного слова из входного потока слово «добавить»: начало добавить высокий добавить культурный добавить уровень добавить общий и т.д.
Это можно сделать следующим образом:\ загрузчик в базу данных бессмысленных сообщений VARIABLE РЯД VARIABLE СТОЛБЕЦ : начало 0 РЯД ! 0 СТОЛБЕЦ ! ; : +РЯД 1 РЯД +! ; : +СТОЛБЕЦ СТОЛБЕЦ @ 1+ 3 /МОD РЯД +! СТОЛБЕЦ ! ;
: добавить \ бессмысленная фраза ( -- ) 1 WORD COUNT РЯД @ СТОЛБЕЦ @ БРЕД DUP 20 BLANK SWAP CMOVE UPDATE +СТОЛБЕЦ ;
Обратите внимание на то, как приведенные выше программы вычисляют соответствующие строку и столбец, а также на удачное выделение слова БРЕД, которое оставляет в вершине стека адрес в блоке пересечения заданных строки и столбца.
Кроме того, необходимо отметить, что выражение 1 WORD удачнее выражения BL WORD, поскольку некоторые фразы состоят из двух слов, разделенных пробелами, а мы не хотим считывать только первое слово. Наша цель - считать все, что пользователь ввел до конца строки. В коде ASCII символ 1 является управляющим. Обычно он не может быть введен с клавиатуры и, значит, не может появиться среди символов входного буфера. Поэтому выражение 1 WORD применяется для чтения содержимого входного буфера до того момента, пока пользователь не нажмет клавишу RETURN.
Со словом WORD могут сочетаться и другие ограничители, например кавычки и круглые скобки. Слово Форта ." использует выражение ASCII " WORD
для чтения из входного потока выводимой строки. Слово ( использует выражение ASCII ) WORD
для выборки из входного потока фрагмента, который должен быть пропущен. Считывая из входного потока запятые или иные разделители, можно даже обрабатывать несколько фрагментов из одной строки, но из разных полей.
Уже знакомое вам слово TEXT упрощает определение слова «добавить»: : добавить \ бессмысленную фразу ( -- ) 1 TEXT PAD РЯД @ СТОЛБЕЦ @ БРЕД 20 CMOVE UPDATE +СТОЛБЕЦ ;
ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ ОПЕРАТОРА ЦИКЛА DO
Вы можете оставить в стеке какое-нибудь число, которое будет служить аргументом для некоторого объекта внутри оператора цикла. Например,: ПРОИЗВЕДЕНИЯ ( n ) CR 11 1 DO DUP I * . LOOP DROP ;
даст вам следующий результат: 7 ПРОИЗВЕДЕНИЯ 7 14 21 28 35 42 49 56 A3 70 ок
или 8 ПРОИЗВЕДЕНИЯ 8 16 24 32 40 48 56 64 72 80 ok
Здесь вы просто умножаете текущее значение индекса на n на каждом шаге цикла. Заметьте, что необходимо размножить число n с помощью DUP внутри цикла, чтобы его копия была всегда доступна и убрать это число посредством DROP посае выхода из цикла.
Существует несколько «хитрых» примеров работы со стеком внутри цикла DO. Рассмотрим их на примере начисления сложных процентов. При заданном начальном остатке, скажем 1000 дол. и норме процента, допустим 6%, требуется написать определение для создания и вывода на печать таблицы, приведенной ниже: 1000 6 СЛОЖНЫЕ-ПРОЦЕНТЫ Год 1 Сумма 1060 Год 2 Сумма 1124 Год 3 Сумма 1191 и т.д.
и т. д. на двадцать лет. Сначала мы загружаем слово R%, специфицированное в гл. 5, а затем создаем определение : СЛОЖНЫЕ-ПРОЦЕНТЫ ( вклад процент -- ) SWAP 21 1 DO CR ." Год " I . 3 SPACES 2DUP R% + DUP ." Сумма " . LOOP 2DROP ;
На каждом шаге выполнения цикла мы применяем операцию 2DUP и таким образом обеспечиваем значение текущего остатка и неизменной нормы процента для следующего шага. После выхода из цикла по окончании вычислений мы убираем эти значения с помощью операции 2DROP.
Индекс может выступать в качестве некоторого условия для оператора IF. С учетом этой возможности вы можете предпринимать конкретные действия только на определенных шагах цикла, например: : ПРЯМОУГОЛЬНИК 256 0 DО I 16 MOD 0= IF CR THEN ." *" LOOP ;
Слово ПРЯМОУГОЛЬНИК выведет на печать 256 звездочек, причем после каждой 16-й звездочки он будет осуществлять возврат каретки на вашем терминале. В результате вы получите **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** **************** ****************
ПРОГРАММА-РЕДАКТОР
Для начала найдите пустой блок (если нужно, используйте INDEX) и распечатайте его следующим образом: 180 LIST
При распечатке пустого блока вы увидите слева на экране 16 строк (0—15), пронумерованных сверху вниз, без какой-либо информации. Приглашение ok в последней строке означает, что текстовый интерпретатор выполнил вашу команду на распечатку данного блока.
Распечатывая блок, вы тем самым выбираете его для дальнейшей работы:
Сделав какой-то блок «текущим», вы можете распечатывать его, просто набирая слово L
В отличие от LIST для L не нужно указывать номер блока, так как это слово распечатывает текущий блок.
Подготовив блок с помощью LIST к редактированию, неплохо было бы выполнить команду WIPE (СТЕРЕТЬ). Иногда никем не используемый блок, на первый взгляд пустой, содержит разного
рода символы, что может воспрепятствовать его загрузке, Слово WIPE заполняет текущий блок пробелами, после чего тот становится действительно пустым.
Поскольку у нас есть текущий блок, выберем и текущую строку с помощью слова Т. Предположим, вы должны записать какую-то информацию в строку 31. Наберите на клавиатуре
Слово Т выделяет выбранную строку, заменяя ее изображение на негативное. Теперь, после того, как вы зафиксировали место, куда будете вносить исправления, можно в текущую строку занести некоторый текст с помощью команды Р (PUT — ВСТАВИТЬ): Р МЫ НАХОДИМСЯ ЗДЕСЬ<return>
Р — вставляет текст, следующий за этой командой (вплоть до символа возврата каретки), в текущую строку.
1 Для любознательных. На самом деле в качестве указателя выступает не номер строки, а позиция курсора. Более подробно это будет показано в следующих сносках.
Помните, что текущая позиция, установленная вами, остается прежней, так что если вы сейчас наберете Р А ТЕПЕРЬ МЫ ЗДECb<return>
то увидите, что в строке 3 прежний текст заменен на более поздний. Аналогично если вы введете Р, а затем по меньшей мере два пробела (один — чтобы отделить Р от текста, а другой — в качестве вводимого текста), то на месте прежней строки будет строка из одних пробелов. Другими словами, в этом случае очищается строка. В данной главе символ b означает пробел, так что для заполнения строки пробелами нужно набрать на клавиатуре Pbb<return>
ПРОГРАММИСТУ О СТРУКТУРЕ ПРИКЛАДНОЙ ПРОГРАММЫ
В настоящем разделе мы дадим рекомендации начинающему Форт-программисту по работе с листингом программы, который приводится ниже. Вы познакомитесь со структурой этой программы и некоторыми из наиболее сложных определений.
Итак, обратимся к листингу. Обратите внимание на то, что вид данной программы несколько отличается от всех остальных, показанных в книге. Здесь использованы соглашения, принятые в фирме FORTH, Inc.
Блоки, расположенные на левой стороне разворота, называются блоками сопровождения. Они содержат комментарий к исходному тексту блоков на правой стороне разворота. Например, определение слова HELP располагается в строке 1 блока 240, а комментарий к этому слову - в строке 1 блока 561. Наличие блоков сопровождения имеет очевидные преимущества. Прежде всего документация всегда находится вместе с программой. В большинстве систем есть слово, которое обеспечивает связь между исходным текстом блока и комментарием к блоку в обе стороны (в полиФорте Q). Программист получает возможность в ходе разработки программы создавать документацию и вносить в нее изменения. С помощью слов LOCATE и Q можно сделать доступным описание, относящееся к любому слову.
Если группа слов описана в одном блоке, то и комментарий к ним должен размещаться в одном блоке, что спасает от повторных описаний и многословия, свойственного глоссариям, где материал расположен в алфавитном порядке.
Слово HELP определено в блоке 240. Оно выводит текст блока подсказок (блок 561), который содержит все команды уровня пользователя. Блок подсказок появляется при загрузке блока 240 (так что он доступен пользователю при компиляции программы) или при вводе пользователем слова HELP
Выражение 241 243 THRU в строке 2 блока 240 загружает фрагмент программы более низкого уровня. Остальные операторы блока загружают слова самого высокого уровня. Помещая группу команд в блок загрузки, а не в последний блок, пользователь тем самым помещает комментарий к этим командам в блок сопровождения блока загрузки.
Обратите внимание на девять команд пользователя в блоке 240. Как просты их определения, несмотря на то, что они реализуют очень мощные функции.
Рассматриваемая программа является образцом хорошо выполненной разработки на Форте. Слово -НАЙТИ (примитивное слово для работы с файлами) выделено таким образом, что его можно включать в определения таких слов, как «найти», «еще», «все* внутреннего слова (Г1АРА), которое используется словами «пара» и «фио». Мы кратко разберем эти определения, но сначала обсудим общую структуру программы, Одной из основных ее особенностей является то, что каждое из четырех полей имеет имя, которое необходимо ввести, чтобы задать соответствующее поле. Например, выражение «фамилия ПОМЕСТИТЬ» поместит строку символов из входного потока в поле «фамилия» текущей записи, выражение «фамилия .ПОЛЕ» распечатает содержимое данного поля и т. д.
Существует информация двух типов для обозначения полей: начальный адрес поля относительно начала записи и его длина. В рассматриваемой программе структура записи такова:
Например, поле «работа» начинается с 28-го байта каждой записи, а его длина составляет 24 байта. Мы вправе сделать длину записи равной в точности 64 байтам, чтобы при распечатке файла посредством LIST столбцы были бы выровнены по вертикали. Такая структура записи выбрана из соображений удобства при программировании именно нашей задачи. Если вы внесете изменения
в программу, то можете сделать записи нужной вам длины и состоящими из требуемого числа полей1.
Две величины, характеризующие каждое поле, мы помещаем в таблицу двойной длины, связанную с именем этого поля. Следовательно, наше определение «работа» будет иметь вид: CREATE работа 28 , 24 ,
Таким образом, при вводе имени поля в вершину стека вносится адрес таблицы, описывающей поле «работа», по которому мы можем выбрать любую из двух характеризующих его величин.
Назовем каждый такой элемент таблицей спецификации поля, или для краткости таблицей полей.
1 Для тех, кто хочет модифицировать нашу файловую систему. При изменении параметров полей убедитесь, что начальный байт каждого последующего поля правильно стыкуется с предыдущим полем. Например, если первое поле имеет длину 30 байт после такого выражения
CREATE 1ПОЛЕ 0 , 30 ,
то начальный адрес следующего поля должен быть 30-м и определяться выражением
CREATE 2ПОЛЕ 30 , 12 ,
и т. д.
Установите значение /ЗАПИСЬ равным длине всей записи (начальный байт последнего поля плюс длина поля). Используя /ЗАПИСЬ, система автоматически подсчитывает число записей, которые она может разместить в одном блоке (1024 /ЗАПИСЬ/), и определяет соответствующую константу ЗАП/БЛК. (см также упражнение в конце главы).
Вы можете изменить расположение вновь созданного файла (например, создать несколько различных файлов) путем изменения в строке 5 значения константы ФАЙЛ. Можно изменить и максимальное число блоков, отводимых под ваш файл, заменив число 2 на другое в той же строке. Это значение будет переведено в максимальное число записей путем умножения его на значение, содержащееся в ЗАП/БЛК, и храниться в виде константы МАКС-ЗАП.
Часть функций нашей программы обусловлена требованиями, предъявляемыми командами «найти», «еще» и «все», т. е. по команде «найти» должен не только осуществляться поиск заданной строки в содержимом полей данного типа, но и «запоминаться» и сама строка, и тип поля, чтобы команды «все» и «еще» смогли бы воспользоваться этой информацией. Можно указать тип поля только одним значением - адресом таблицы полей данного типа. Это означает, что мы можем «запомнить» тип поля, послав его адрес в переменную с помощью слова ЗАПОМНИТЬ. Переменная ТИП служит для обозначения типа поля.
Для того чтобы запомнить строку, мы определили буфер с именем ЧТО, куда строка может быть помещена. (Память для буфера определяется в рабочей области PAD, где она может повторно использоваться, и при этом не расходуется память, выделенная под словарь.)
Слово ЗАПОМНИТЬ имеет два назначения: запоминать тип заданного поля в ТИП и заданную строку символов в ЧТО. Если вы посмотрите на слово «найти», обращенное к конечному пользователю, то заметите, что оно в первую очередь посредством слова ЗАПОМНИТЬ запоминает информацию, по которой должен осуществляться поиск, после чего исполняет внутреннее слово -НАЙТИ, осуществляющее по информации, хранимой в ТИП и ЧТО, поиск аналогичной строки. Слова «еще» и «все» тоже используют слово -НАЙТИ, но без слова ЗАПОМНИТЬ. Они осуществляют поиск полей по содержимому, которое было запомнено командой ЗАПОМНИТЬ при последнем применении команды «найти».
Так как с помощью слова «дать» можно получить любую информацию из записи, которую мы уже нашли, применив команду «найти», нам нужен указатель текущей записи. Таким указателем служит переменная ЗАПИСЬ#. Операции, выполняемые в блоке 242 словами ВВЕРХ и ВНИЗ, должны показаться вам тривиальными.
Слово ЗАПИСЬ использует переменную ЗАПИСЬ# для вычисления абсолютного адреса (машинного адреса в буфере на каком-то диске) начала текущей записи. Поскольку ЗАПИСЬ применяет слово BLOCK, оно гарантирует, что данная запись действительно существует в буфере.
Обратите внимание на то, что ЗАПИСЬ допускает расположение одного файла в нескольких смежных блоках. /MOD делит значение, находящееся в переменной ЗАПИСЬ#, на число записей в одном блоке (в нашем случае 16, так как каждая запись имеет длину в 64 байта). Частное указывает тот блок относительно первого блока, где должна находиться обрабатываемая запись, а остаток определяет смещение этой записи относительно начала вычисленного блока.
В таблице полей содержатся относительные значения адреса поля и длины. Однако нам для таких слов, как TYPE, MOVE и -TEXT, часто требуется знать абсолютные адрес и длину. Поэтому покажем, как в определении слова ПОЛЕ адрес таблицы полей преобразуется в абсолютные адрес и длину, а затем как в определении слова .ПОЛЕ используется слово ПОЛЕ.
Слово ПОМЕСТИТЬ с помощью слова ПОЛЕ вычисляет адрес и счетчик поля назначения для слова ЧТЕНИЕ. Последнее выбирает из входного потока строку, ограниченную запятой, и помещает эту строку в поле назначения. Заметьте, что ЧТЕНИЕ инициирует слово ПОДРОВНЯТЬ, которое введено для урезания числе символов перекачиваемой строки в том случае, если источник превышает поле назначения/
В определении слова ПУСТАЯ в блоке 243 обращают на себя внимание два обстоятельства. Первое из них - это способ обнаружения пустой записи. Если первый байт какой-либо записи не содержит информации, то можно считать, что вся запись пуста (в этом заключается принцип выполнения слова «внести»). Если в первом байте содержится некоторый символ, значение которого в коде ASCII меньше 33 (код 32 соответствует пробелу), значит, в данном байте находится невидимый символ и, следовательно, строка пуста. В пустом блоке могут находиться нули или он может быть полностью заполнен пробелами, но в любом случае такие записи будут рассматриваться как пустые. По обнаружении пустой записи LEAVE завершает цикл. В переменной ЗАПИСЬ# находится номер свободной записи.
Второе обстоятельство, связанное со словом ПУСТАЯ, заключается в том, что это слово прерывает выполнение посредством АBORT при заполнении файла, т. е. если оно прошлось по всем записям и не обнаружило среди них ни одной пустой. Для того чтобы обойти все записи, можно воспользоваться циклом DO, но как узнать по окончании выполнения цикла, была ли обнаружена во время просмотра хотя бы одна пустая запись?
Прежде чем приступить к выполнению цикла, целесообразно оставить в вершине стека значение истины, которое будет служить флагом. При обнаружении пустой записи мы можем сменить значение флага на нуль (посредством слова NOT) перед тем, как выйти из цикла. Если после выхода из цикла в вершине стека по-прежнему находится единица, значит, пустая запись не обнаружена, а если нуль, то обнаружена.
Мы применяем аналогичный прием в определении слова -НАЙТИ.
Это слово оставляет в вершине стека флаг для слова, которое его выполняет, а именно: «найти», «еще», «все» или (ПАРА). Флаг показывает, была ли найдена заданная строка до конца файла. Каждое из перечисленных слов внешнего уровня в зависимости от состояния флага должно принимать соответствующее решение. Если заданная строка не найдена, то в вершине стека будет значение истины (отсюда и название слова -НАЙТИ).
С учетом контекста использования слова -НАЙТИ изменим значения флага на обратные. Так как значение флага должно быть истинным в том случае, когда поиск заданной строки окончился неудачей, проще всего определить это слово таким образом, чтобы до начала поиска в вершине стека была единица, которая заменялась бы нулем лишь при успешном завершении поиска. Обращаем ваше внимание на то, что во время выполнения цикла в вершине стека находятся два значения: только что рассмотренный флаг и адрес таблицы полей, определяющей поле, по которому ведется поиск. Поскольку адрес нам требуется на каждом шаге выполнения цикла, а значение флага, возможно, понадобится всего один раз, мы решили хранить адрес в вершине стека, а флаг - под ним. Для этого мы и использовали выражение SWAP NOT SWAP
Между прочим мы могли бы избежать возникновения такой ситуации, если бы вместо выражений ТИП @ DUP ПОЛЕ
перед циклом и внутри его для того, чтобы обеспечить оба этих значения в вершине стека, мы применили бы выражение ТИП @ ПОЛЕ
внутри цикла. Мы отказались от этого потому, что всегда стремимся минимизировать число операций, выполняемых внутри цикла.: операции внутри цикла повторяются многократно и занимают очень много времени.
На этом мы завершаем описание программы в целом и надеемся, что вы без труда разберетесь в деталях ее работы самостоятельно по приводимому ниже листингу1.
1 Для пользователей систем фиг-Форта. Прежде чем загрузить программу с файловой системой, убедитесь в том, что приведенные ниже определения скомпилированы первыми.
: VARIABLE 0 VARIABLE ; : CREATE <BUILDS DOES> ; : BLANK ( a # -- ) BLANKS ; : WORD ( -- a) WORD HERE ; : >IN ( -- a) IN ;
Затем замените выражение блока 243
ABORT" Переполнение файла"
на следующее:
IF ." Переполнение файла" QUIT THEN
а в блоке 240 замените каждое вхождение выражения ' >BODY на [COMPILE] '. Наконец, загрузите необходимые дополнительные команды, указанные в примечании 3.
2. Для пользователей систем полифорта. Перед загрузкой программы работы с файлами убедитесь, что вы сначала скомпилировали следующее определение:
: >BODY ;
и загрузили все необходимые определения, указанные в примечании 3.
3. Для пользователей систем, в которых нет следующих слов. Загрузите, если нужно, приведенные ниже определения (после того, как осуществится загрузка определений, указанных в примечаниях 1,2):-1 CONSTANT TRUE : ASCII ( -- с) BL WORD 1+ С@ COMPILE LITERAL ; IMMEDIATE : \ IN @ 64 / 1+ 64 * IN ! ; : -TEXT ( a1 # a2 - ?) 2DUP + SWAP DO DROP 2+ DUP 2- @ I @ - DUP IF DUP ABS / LEAVE THEN 2 +LOOP SWAP DROP ; : TEXT ( c) PAD 80 BLANK WORD СОUNT PAD SWAP CMOVE ;
561 LIST 0 HELP описание директив ФАЙЛОВОЙ СИСТЕМЫ. 1 внести - пополнит базу данных. Данные вводятся в четыре поля текущей записи 2 следите за правильным использованием запятых и пробелов. 3 Использование: внести Рэйзер,Дан,диктор,555-1212 4 удалить - заполнение пробелами текущей записи и обновление дискового буфера 5 изменить - размещение входного текста в заданном поле текущей записи. 6 Использование: изменить работа программист 7 найти - поиск входного фрагмента в памяти и индикация его наличия или отсут 8 ствия. Использование: найти имя Дан 9 дать - выдача данных из указанного поля текущей записи. 10 Использование: дать телефон 11 еще - выдача следующего найденного фрагмента за ЗАПИСЬ# 12 все - выдача всех искомых полей вазы данных 13 пара - поиск записи по содержимому двух полей. 14 Использование: пара работа диктор,телефон 535-9876 13 фио - поиск по имени и фамилии. Использование: фио Рэйзер,Дан
562 LIST 0 Текст в коде ASCII запоминается на диск в очередную запись длиной в одну 1 строку, на которую указывает ЗАПИСЬ», с возможность» доступа к 2 четырем полям этой записи по именам полей.
Аля поиска в базе 3 данных по содержимому входного буфера применяется слово -TEXT. 4 Удачный поиск завершается выходом из цикла посредством EXIT и установкой 5 ЗАПИСЬ# на вывод содержимого поля, определяемого переменной ТИП. 6 Имя каждого поля содержит смещение относительно начала текущей 7 записи и счетчик (длину) в байтах. С помощью этих имен мы можем 8 в слове ПОЛЕ для доступа к данным выдавать виртуальные адреса. 9 Мы можем осуществлять доступ к полям заданной текущей записи «ЗАПИСЬ») 10 по имени поля, а затем работать в окрестности полученного адреса. 11 Значение ЗАПИСЬ# последовательно увеличивается. 12 ТИП содержит адрес интересуемого нас поля; используется для 13 входа в запись, на которую указывает ЗАПИСЬ# . 14 ЧТО является буфером, содержащим входной тест, по которому 15 осуществляется поиск в вазе данных.
563 LIST 0 ВВЕРХ устанавливает указатель записи в начало вазы данных. 1 ВНИЗ устанавливает указатель на следующую запись. 2 3 ПОДРОВНЯТЬ устанавливает счетчик для CMOVE в пределах длины 4 поля. 5 ЧТЕНИЕ заполняет буфер пробелами и заносит в него указанный 6 счетчик байтов. Ограничителем поля является знак "," я коде ASCII. 7 8 ЗАПИСЬ оператор, работающий с виртуальной памятью и 9 вырабатывавший адрес внутри дискового елочного буфера. Этот 10 адрес является началом текущей записи. 11 ПОЛЕ выбирает смешение и длину в одной из четырех переменных 12 для получения адреса и счетчика, необходимых при выводе информации. 13 14 ПОМЕСТИТЬ помечает символы, введенные с клавиатуры, в 15 обновляемый дисковый елочный буфер.
240 LIST 0 ( Простая файловая система ) DECIMAL 1 : HELP SCR @ 561 LIST SCR ! ; HELP 2 241 243 THRU 3 : внести ПУСТАЯ фамилия ПОМЕСТИТЬ имя ПОМЕСТИТЬ работа ПОМЕСТИТЬ 4 телефон ПОМЕСТИТЬ ; 5 : удалить ЗАПИСЬ /ЗАПИСЬ BLANK UPDATE ; 6 : изменить ' >BODY ПОМЕСТИТЬ ; 7 : найти ( поле текст) ' >BODY ЗАПОМНИТЬ ВВЕРХ -НАЙТИ IF
8 ОТСУТСТВУЕТ ELSE -ИМЯ THEN ; 9 10 : дать ( поле) ' >BODY .ПОЛЕ ; 11 : еще ВНИЗ -НАЙТИ IF . " Больше нет " ELSE .ИМЯ THEN 12 : все ВВЕРХ BEGIN CR -НАЙТИ NOT WHILE .ИМЯ ВНИЗ REPEAT ; 13 14 : пара ' >BODY ЗАПОМНИТЬ ' >BODY PAD 80 ЧТЕНИЕ имя (ПАРА) ; 15 : фио фамилия ЗАПОМНИТЬ PAD 80 ЧТЕНИЕ имя (ПАРА) ;
241 LIST 0 ( Поля) 1 VARIABLE ЗАПИСЬ# ( текущая запись)
2 VARIABLE ТИП ( указатель • таблицу полей на последнее используемое поле) 3 : ЧТО ( -- a) PAD 100 + ; 4 ( смешение) ( длина) 5 CREATE Фамилии 0 , 16 , 6 CREATE имя 16 , 12 , 7 CREATE работа 28 , 24 , 8 CREATE телефон 52 , 12 , 9 10 64 CONSTANT /ЗАПИСЬ ( число вайт в одной записи) 11 1024 CONSTANT /БЛОК ( число байт в одном блоке) 12 /БЛОК /ЗАПИСЬ / CONSTANT ЗАП/БЛК ( число записей в блоке) 13 244 CONSTANT ФАЙЛЫ ( с данного блока начинаютcя файлы) 14 2 ( блоки) ЗАП/БЛК * CONSTANT МАКС-ЗАП ( максимальное число записей) 15
242 LIST 0 ( Записи) 1 : ВВЕРХ 0 ЗАПИСЬ# ! ; 2 : ВНИЗ 1 ЗАПИСЬ# +! ; 3 : ПОДРОВНЯТЬ ( а # а #) >R SWAP R> MIN CMOVE ; 4 : ЧТЕНИЕ ( a #) 2DUP BLANK ASCII , WORD COUNT 5 2SWAP ПОДРОВНЯТЬ ; 6 : ЗАПИСЬ ( -- а) ЗАПИСЬ# @ ЗАП/БЛК /MOD ФАЙЛЫ + BLOCK 7 8 SWAP /ЗАПИСЬ * + ; 9 10 : ПОЛЕ ( a -- a' n) 2@ ЗАПИСЬ + SWAP ; 11 12 : ПОМЕСТИТЬ ( а) ПОЛЕ ЧТЕНИЕ UPDATE ; 13 14 15
364 LIST 0 .ПОЛЕ вывод информации из заданного поля записи. 1 .ИМЯ вывод имени и фамилии из заданной записи 2 3 ЗАПОМНИТЬ устанавливает, ЧТО собой представляет текстовый 4 аргумент и ТИП поля, по которому будет осуществляться поиск. 5 ПУСТАЯ установка указателя записи на следующую доступную 6 (свободную) запись; если достигнуто значение МАКС-ЗАП,то ABORT. 7 Запись, первый байт которой равен 32 (пробел) или 0, считается пустой. 8 -НАЙТИ побайтное сравнение фрагмента из ЧТО с содержимым выбранного 9 поля каждой записи. Если сравнение успешное или цикл поиска завершен, 10 выдается флаг "истина". Это логическое значение используется 11 словами (ПАРА), найти, все и еще 12 (ПАРА) по адресам двух полей выбираются два текстовых фрагмента и 13 сравниваются с содержимым выбранных полей каждой записи. 14 15
243 LIST 0 ( Выдача информации) 1 : .ПОЛЕ ( a) ПОЛЕ -TRAILING TYPE SPACE ; 2 : .ИМЯ имя .ПОЛЕ фамилия .ПОЛЕ ; 3 4 : ЗАПОМНИТЬ ( a) DUP ТИП ! 2+ @ ASCII , TEXT 5 PAD ЧТО ROT CMOVE ; 6 7 : ПУСТАЯ TRUE МАКС-ЗАП 0 DO I ЭАПИСЬ# ! ЗАПИСЬ C@ 33 < IF 8 NOT LEAVE THEN LOOP ABORT" Переполнение файла" ; 9 : -НАЙТИ ( - t) TRUE ТИП @ МАКС-ЗАП ЗАПИСЬ# @ DO I0 I ЗАПИСЬ# ! DUP ПОЛЕ ЧТО -TEXT 0= IF 11 SWAP NOT SWAP LEAVE THEN LOOP DROP ; 12 : ОТСУТСТВУЕТ ." Сведения отсутствуют " ; 13 : (ПАРА) ( a) МАКС-ЗАП 0 DO I ЗАПИСЬ# ! -НАЙТИ IF 14 ОТСУТСТВУЕТ LEAVE ELSE DUP ПОЛЕ PAD -TEXT 0= IF 15 .ИМЯ LEAVE THEN THEN LOOP DROP ;
244 LIST 0 Филлмор Миллард президент нет телефона 1 Линкольн Авраам президент нет телефона 2 Бронте Эмилия писатель нет телефона ... 245 LIST 0 Ван Еарен Абигейл обозреватель 555-2233 ...
ПРОИЗВОЛЬНОЕ ИЗМЕНЕНИЕ ПОСЛЕДОВАТЕЛЬНОСТИ ВЫПОЛНЕНИЯ СЛОВ
Существует возможность опустить один уровень исполнения, просто удалив один адрес из стека возвратов. В качестве примера рассмотрим три уровня исполнения, связанных со словом ОБЕД:
Предположим, что мы изменили определение ВТОРОЕ: : ВТОРОЕ ЦЫПЛЕНОК РИС R> DROP ;
Выражение "R> DROP" удалит из стека возвратов адрес возврата в слове ДЕСЕРТ, который был туда помещен перед выполнением слова ВТОРОЕ. Если перезагрузить эти определения и выполнить слово ОБЕД, то EXIT третьего уровня обеспечит возврат непосредственно на первый уровень. Мы «съедим» ПЕРВОЕ, ЦЫПЛЕНКА и РИС, но останемся без ДЕСЕРТА:
Применять в прикладной программе выражения "R> DROP" нежелательно, поскольку это противоречит принципам структурного программирования. Тем не менее данное выражение иногда позволяет упростить решение задачи. Мы не будем здесь приводить аргументы «за» и «против», однако изложенное должно послужить вам предостережением.
Как недавно упоминалось, слово EXIT удаляет адрес возврата из вершины стека возвратов и помещает его в указатель интерпретатора. Интерпретатор адреса, который ориентируется по содержимому указателя интерпретатора, начинает поиск на следующем верхнем уровне. Можно включить EXIT в середину определения. Например, если бы мы переопределили слово ВТОРОЕ: : ВТОРОЕ РИС ВЕГЕТАРИАНЕЦ IF EXIT THEN ЦЫПЛЕНОК ;
и при этом были бы вегетарианцами, то нам пришлось бы выполнить EXIT после слова РИС, пропустить слово ЦЫПЛЕНОК и перейти сразу к ДЕСЕРТУ.
Приведенное выше определение эквивалентно следующему: : ВТОРОЕ РИС ВЕГЕТАРИАНЕЦ NOT IF ЦЫПЛЕНОК THEN ;
Вы не имеете права использовать внутри оператора цикла DO слово EXIT, так как это слово удаляет из стека один из аргументов, занесенных в него оператором DO (вместо того, чтобы удалить из стека возвратов адрес возврата)!
Вы только что видели результат удаления адреса возврата из стека возвратов. Приведем еще один пример - занесение в стек возвратов лишнего адреса (вам, может быть, придется привести адреса cfa и pfa в соответствии с вашей системой): : ПРИВЕТ ." Привет " ; : ДО-СВИДАНИЯ ." До свидания " ; ' ДО-СВИДАНИЯ >BODY >R ПРИВЕТ
Сначала с помощью апострофа выбираем адрес слова ДО-СВИДАНИЯ и помещаем его в стек возвратов - пусть Форт-система «думает», что это адрес возврата. Затем инициируем слово ПРИВЕТ, которое выдает свое приветствие. В конце концов, Форт-система обращается к стеку возвратов за следующим адресом и, выбрав его, выполняет ДО-СВИДАНИЯ - после слова привет:
>BODY |
( cfa -- pfa) |
Вычисление адреса поля параметров определения, "адрес компиляции " которого находится на стеке. |
EXIT |
( -- ) |
Удаление адреса возврата из вершины стека возвратов и занесение его в указатель адресного интерпретатора. Если слово EXIT скомпилировано в определении через двоеточие, то оно завершает выполнение этого определения в данной точке» |
QUIT |
( -- ) |
Очистка стека возвратов и передача управления терминалу, ожидающему ввода. Сообщения при этом не выдаются. |
ABORT |
( -- ) |
Очистка стека данных и выполнение функций слова QUIT. Сообщения не выдаются. |
РАБОТА СО СТЕКОМ
Мы уже продемонстрировали некоторые методы работы со стеком и постфиксную запись. Теперь рассмотрим этот вопрос более детально.
Стек Форта функционирует по принципу «последним занесен первым выбран» (LIFO). Вы это видели в приведенном выше при мере. Число 3 было занесено в стек первым, а затем в него занесли число 4, «протолкнув» вглубь стека тройку. Позднее при выполнении операции сложения машина выбрала первым число 4, так как оно находилось в вершине стека.
В качестве еще одного примера выполним другую операцию. Вспомните, что каждое слово . берет одно значение из стека и выводит его на экран. Четыре точки, следовательно, возьмут четыре числа: 2 4 6 8 . . . .<return> 8 6 4 2 ok
Система читает символы из входного потока слева направо. При вводе крайнее правое значение на экране дисплея будет находиться последним в стеке, т. е. в его вершине. При выводе крайнее правое значение поступает на экран с самого дна стека.
Будьте внимательны, чтобы не допустить ошибки. Наберите на клавиатуре 10 20 30 . . . .
(четыре точки) и нажмите клавишу RETURN. В результате вы получите : 10 20 30 . . . .<return> З0 20 10 . Стек пуст
Каждая точка удаляет из стека одно значение. Четвертая точка «обнаружит», что в стеке нечего взять для вывода на дисплей, о чем вы и получите сообщение.
Такая ошибка называется потерей элемента стека. (Заметьте, что потеря элемента стека вовсе не «ok».) В противном случае, когда вы исчерпали емкость стека, возникает ситуация его переполнения. Однако стек настолько глубок, что такое событие практически нереально, если только вы не совершите что-то из ряда вон выходящее.
Как правило, вас не должно интересовать содержимое всего стека — вам нужны только те числа, с которыми вы работаете в данный момент. Остальные же значения, если таковые были занесены в стек ранее, должны в сохранности находиться в стеке до того момента, пока они не понадобятся. Если вы, к примеру, захотите нарисовать прямоугольник, применяя ту же технику, что и
1 Для любознательных. На самом деле на экран выводится всегда то значение, которое находится в вершине стека. Поэтому если в стеке ничего нет, выводится следующее значение, находящееся глубже последнего, а именно нуль. И только тогда обнаруживается ошибка. Ошибочное слово (в нашем случае точка) выводится на экран, а за ним — сообщение об ошибке.
при создании буквы F, то для начертания сторон можно написать слово : СТОРОНЫ STAR 5 SPACES STAR 5 ;
которое при выполнении дает вам следующее: * *
Предположим, вы хотите научиться рисовать прямоугольник любого размера. Тогда вы должны не заключать число 5 в определении, а передавать его слову СТОРОНЫ как аргумент. В этом случае ваше определение будет выглядеть так: : СТОРОНЫ STAR SPACES STAR ;
Теперь нужно обращаться к слову СТОРОНЫ, скажем, таким образом: 5 СТОРОНЫ
При этом несмотря на то, что при первом употреблении слова STAR в стек заносится число 42, предназначенное для EMIT, заданное вами число 5 будет благополучно дожидаться в стеке выполнения своего слова SPACES.