Глава 12
ТРИ С ПОЛОВИНОЙ ПРИМЕРА

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

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

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

ОТКАЧКА ФАЙЛА

Итак, наш первый пример - простая файловая система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

ПРОГРАММИСТУ О СТРУКТУРЕ ПРИКЛАДНОЙ ПРОГРАММЫ

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

Итак, обратимся к листингу. Обратите внимание на то, что вид данной программы несколько отличается от всех остальных, показанных в книге. Здесь использованы соглашения, принятые в фирме 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
...

БЕЗ ВЗВЕШИВАНИЯ

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

Вычислим вес конусообразной «кучи» некоторого материала, зная ее высоту и угол откоса, а также плотность материала. Чтобы сделать нашу задачу более конкретной, давайте взвесим кучи песка, гравия и цемента. Крутизна каждой кучи, называется углом естественного откоса, зависит от вида материала. Например, песчаные кучи более крутые, чем из гравия.

(На самом деле эти величины колеблются в большом диапазоне в зависимости от разных факторов. Для иллюстрации мы выбрали приблизительные значения угла откоса и плотности.)

Существует формула вычисления массы конусообразной кучи высотой h (в футах), углом естественного откоса 9 (в градусах) и плотностью материала D (в фунтах на кубический фут)1:

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

1 Для скептиков. Объем конуса V вычисляется по формуле

где b - радиус основания; h - высота. Мы можем найти радиус основания, зная угол откоса, или, более точно, тангенс этого угла. Тангенсом некоторого угла называется отношение катета противолежащего (на рисунке h) к катету прилежащему (на рисунке b):

Если обозначить этот угол через , то . Следовательно, Подставив это выражение в формулу для V и затем умножив результат на плотность D (в фунтах на кубический фут), получим приведенную выше формулу.

ность и угол естественного откоса остаются неизменными. Поэтому можно записать указанные две величины для каждого вида материала в некоторую таблицу. Так как в конечном итоге нам потребуется тангенс угла, а не его величина в градусах, будем хранить значение тангенса. Например, угол естественного откоса для цемента составляет 35°, а его тангенс равен .700. Мы будем хранить это значение в виде целого 700.

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

Начнем решение задачи с установления порядка величин. Высота всех трех куч лежит в пределах от 5 до 50 футов. Вычислив массу кучи цемента, мы получим 35 000 000 фунтов. Но так как кучи не имеют форму правильного конуса, а мы берем средние зна чения величин, точность вычислений не может превышать четырех или пяти порядков1. Если перевести наш результат в тонны, то получим 17 500 т. Это значение вполне удовлетворяет представлению числа одинарной длины, так что в нашей программе условимся применять арифметические операции только над значениями одинарной длины.

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

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

15 ФУТОВ 2 ДЮЙМОВ КУЧА

где слова ФУТОВ и ДЮЙМОВ представляли бы футы и дюймы с точностью до десятых долей дюйма, а слово КУЧА производило бы необходимые вычисления. Слова ФУТОВ и ДЮЙМОВ можно было бы определить так:

: ФУТОВ ( футы -- вес-в-масштабе) 10 * ;
: ДЮЙМОВ { вес-в-масштайе -- вес-в-масштабе' )
   100 12 */ 5 + 10 / + ;

использование слова ДЮЙМОВ не обязательно. Таким образом, выражение 23 ФУТОВ поместит в вершину стека число 230, выражение 15 ФУТОВ 4 ДЮЙМОВ - число 153 и т. д. (Между прочим довольно легко организовать ввод данных и в десятых долях дюйма с десятичной точкой, например: 15.2. В таком случае слово NUMBER переводит вводимое число в значение двойной длины. Так как мы имеем дело только с числами одинарной длины, для того чтобы удалить старший байт, достаточно просто применить слово DROP.)

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

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

значение высоты вплоть до 50 футов, т. е. при выбранном масштабе 500. Даже если мы возведем это значение лишь в квадрат, то получим 250 000, что уже превышает возможности представления одинарной точности. Но для того чтобы выразить ответ в тоннах, рано или поздно в ходе вычислений нам придется выполнить деление на 2000, поэтому выражение

DUP DUP 2000 */

будет одновременно возводить аргумент в квадрат и переводить в тонны, так как под промежуточный результат в операции */ отводится слово двойной длины. Если наш аргумент равен 500, то в результате получится 125.

Высота кучи может оказаться равной всего лишь пяти футам. Возведение этого значения в квадрат дает 25, а при делении последнего числа на 2000 средствами целочисленной арифметики мы получим нуль, что свидетельствует о неудачном выборе масштаба с небольшими значениями аргумента. Для обеспечения максимальной точности мы не должны при масштабировании получать меньшие значения, чем требуется. Число 250 000 вполне представимо средствами арифметики одинарных чисел, если его предварительно разделить на 10, поэтому мы начнем наше определение КУЧА следующим выражением:

DUP DUP 10 */

На данном этапе результат с учетом масштабирования окажется в десять раз большим (25000 вместо 2500.0), чем требуется.

Далее необходимо возвести аргумент в куб. Непосредственное умножение опять приведет к результату двойной длины и, значит, масштабирование должно производиться с применением операции */. Как видите, выбрав в качестве делителя 1000, мы продолжаем оставаться в пределах одинарной длины. Теперь наш результат будет в десять раз меньшим (12 500 вместо 125 000), чем требуется, и все еще сохраняется точность в пять знаков. Согласно приведенной выше формуле нужно умножить аргумент на pi. Вы знаете, что на Форте это можно сделать с помощью следующего выражения:

355 113 */

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

355 339 */

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

1000 ТЕТА @ */

Такое действие мы должны произвести дважды, поэтому оформим его в виде определения с именем /TAN (для деления-на-тангенс) и применим это определение дважды в определении слова КУЧА. Наш результат пока еще будет в десять раз меньше, чем на самом деле (26711 вместо 267110 при максимальных значениях аргументов).

Нам остается лишь умножить полученное значение на величину плотности материала, максимальное значение которой равно 131 фунт на кубический фут. Во избежание переполнения уменьшаем плотность в 100 раз, применяя выражение

ПЛОТНОСТЬ @ 100 */

Проверив выполнение программы в этой точке с данными о куче цемента высотой 50 футов, получим число 34991, что превышает 15 разрядов. Теперь пора принять во внимание значение 2000. Вместо

ПЛОТНОСТЬ @ 100 */

мы можем написать:

ПЛОТНОСТЬ @ 200 */

и наш ответ будет приведен к целому числу тонн.

Этот вариант программы вы найдете на распечатке блока 246, которая приводится ниже. Как уже упоминалось ранее, в блоке 248 находится вариант той же программы с использованием арифметических операций над данными двойной длины. Здесь вы вводите высоту в виде числа двойной точности с одним знаком после точки, представляющим десятые доли фута, например 50.0 футов, а далее следует слово ФУТЫ.

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

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

                      В ФУНТАХ      В ТОННАХ
---------------------------------------------
калькулятор         34.995.634     17,497.817
16-разрядный форт        -         17.495
32-разрядный форт   34.995.634     17.497.817
---------------------------------------------

Результаты же работы нашей программы таковы:

246 LOAD ok          ( компиляция варианта с одинарной длиной)
ЦЕМЕНТ ok
10 ФУТОВ КУЧА = 138 тонн цемента ok
10 ФУТОВ 3 ДЮЙМОВ КУЧA = 1S1 тонн цемента ok
СУХОЙ-ПЕСОК
10 ФУТОВ КУЧА = 81 тонн песка ok
248 LOAD ok          ( компиляция варианта с двойной длиной)
ЦЕМЕНТ ok
10.0 ФУТОВ = 279939 фунтов цемента или 139.969 тонн ok

Замечание по поводу компиляции строк. Определение слова МАТЕРИАЛ требует трех аргументов для каждого материала, одним из которых является адрес некоторой строки. Слово .МАТЕРИАЛ с помощью этого адреса выводит название рассматриваемого материала. Для помещения этой строки в словарь и передачи адреса слову МАТЕРИАЛ мы определили слово с именем ," (запятая-кавычки).

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

: STRING ( с) WORD С@ 1 + ALLOT ;
Block # 246
 0 \ Определение массы конической кучи - одинарная длина
 1 VARIABLE ПЛОТНОСТЬ VARIABLE ТЕТА VARIABLE Н.М.
 2 : ," ( --a) HERE ASCII " STRING ;
 3 : .МАТЕРИАЛ Н.М. @ COUNT TYPE SPACE ;
 4 : МАТЕРИАЛ ( 'строка плотность тета -- ) CREATE , , ,
 5    DOES> DUP @ ТЕТА ! 2+ DUP @ ПЛОТНОСТЬ ! 2+ @ Н.М. ! ;
 6
 7 : ФУТОВ ( футы -- вec-в-масштабе) 10 * ;
 8 : ДЮЙМОВ ( вес-в-масштабе -- вес-в-масштабе')
 9     100 12 */ 5 + 10 / + ;
10
11 : /TAN ( n - n') 1000 TETA @ */ ;
12 : КУЧА ( вес-в-масштабе -- )
13     DUP DUP 10 */ 1000 */ 355 339 */ /TAN /TAN
14     ПЛОТНОСТЬ @ 200 */ ." = " . ." тонн " .МАТЕРИАЛ ;
15 247 LOAD
Block # 247
 0 \ Таблица материалов
 1 \ адрес-строки        плотность     тета
 2 ," цемента"             131         700  МАТЕРИАЛ ЦЕМЕНТ
 3 ," рыхлого гравия"      93          649  МАТЕРИАЛ РЫХЛЫЙ-ГРАВИЙ
 4 ," плотного гравия"     100         700  МАТЕРИАЛ ПЛОТНЫЙ-ГРАВИЯ
 5 ," сухого песка"        90          734  МАТЕРИАЛ СУХОЙ-ПЕСОК
 6 ," мокрого песка"       118         900  МАТЕРИАЛ МОКРЫЙ-ПЕСОК
 7 ," глины"               120         727  МАТЕРИАЛ ГЛИНА
 8
 9
10
11
12 ЦЕМЕНТ
13
14
15
Block # 248
 0 \ Масса конической кучи - двойная длина
 1 VARIABLE ПЛОТНОСТЬ VARIABLE TETA VARIABLE H.М.
 2 : ," ( -- a) HERE ASCII " STRING ;
 3 : .МАТЕРИАЛ Н.М. @ COUNT TYPE SPACE ;
 4 : DU.3 ( du -- ) <# # # # ASCII . HOLD #S #> TYPE SPACE ;
 5 : МАТЕРИАЛ ( 'строка плотность тета -- ) CREATE
 6    DOES> DUP @ TETA ! 2+ DUP @ ПЛОТНОСТЬ ! 2+ @ Н.М. ! ;
 7 : КУБ ( d -- d') 2DUP OVER 10 M*/ DROP 10 M*/
 8 : /TAN ( d -- d') 1000 TETA @ М*/ ;
 9 : ФУТОВ ( d -- ) КУБ 355 339 M*/ ПЛОТНОСТЬ @ 1 М*/
10    /TAN /TAN 5 M+ 1 I0 M*/
11    2DUP ." = " D. ." фунтов " .МАТЕРИАЛ
12    1 2 M*/ ." или " DU.3 ." тонн " ;
13 247 LOAD
14
15

ФОРТ-АССЕМБЛЕР

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

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

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

: НОВОЕ-ИМЯ ( код высокого уровня . . .) ;

то тело ассемблерного определения включает команды на языке Ассемблера:

CODE НОВОЕ-ИМЯ ( код на языке ассемблера . . .) END-CODE

(Слово завершения ассемблерного кода варьируется от системы к системе; Стандарт-83 рекомендует END-CODE.)

Слово, которое определяется посредством CODE, хранится в словаре так же, как и все остальные, и выполняется или вызывается подобно любому другому слову. Определенное соответствующим образом это слово будет выполняться со значениями из стека, поэтому вы можете передавать ему аргументы так, как если бы оно было определено через двоеточие. На самом деле, выполняя некоторое слово, вы не в состоянии установить, определено ли оно через двоеточие или через CODE (разве что по скорости выполнения).

Лучше всего писать программу на языке высокого уровня. После того как она начала правильно работать, вы можете выявить те участки, на выполнение которых тратится основное время и переписать их в машинных кодах. Повторно откомпилируйте программу, и она будет работать намного быстрее. Альтернативный способ - заранее фиксировать критичные по времени участки - не столь эффективен.

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

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

Таким образом, простое определение слова CODE может иметь вид:

: CODE CREATE HERE HERE 2- ! ;

Теперь нам нужен набор слов, позволяющий осуществлять трансляцию машинных команд в словарь. Для начала выберем несложное слово. Команда процессора 8080 СМА вычисляет дополнение содержимого регистра А. Код этой операции в двоичной системе счисления выглядит так: 00101111. Чтобы транслировать команду, введем следующее определение:

HEX
: CMA 2F C, ;

Еще одна простая команда XCHG обеспечивает обмен содержимым между парами регистров D-Е и Н-L. Код такой операции: 11101011. Далее мы можем ввести определение:

: XCHG ЕВ С, ;

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

CODE ТЕСТ СМА ХСНG . . .

Получено слово ТЕСТ, выполняющее машинные команды СМА и XCHG. Вы можете для проверки этого слова воспользоваться словом DUMP (но ни в коем случае не инициируйте слово ТЕСТ!).

Как заканчивается CODE-определение, мы покажем позднее, а пока вернемся к определению машинных команд. У нас уже определены две команды, состоящие из восьмиразрядного кода операции. Процессор 8080 имеет довольно много команд такого типа. Поэтому нам необходимо слово для определения всех подобных команд (назовем их командами типа!) 1MI.

: 1MI ( код-операции - ) CREATE С, DOES> С@ С, ;

Определим с помощью введенного слова следующие команды (первые два определения по-новому создают уже имеющиеся у нас команды):

HEX
2F 1MI СМA      ЕВ 1MI XCHG     00 1MI NOP       76 1MI HLT
F3 1MI DI       FB 1MI EI       07 IMI RLC       0F 1MI RRC
17 1MI RAL      1F IMI RAR      Е9 1MI PCHL      F9 1MI SPHL
E3 1MI XTHL     27 1MI DAA      37 1MI STC       3F 1MI CMC
C0 1MI RNZ      C8 1MI RZ       D0 1MI RNC       D8 1MI RC
Е0 1MI RPO      E8 1MI RPE      F0 1MI RP        F8 1MI RM
C9 1MI RET

Определяющее слово 1MI создает семейство команд, каждую из которых отличает уникальный код операции, но при компиляции все они ведут себя одинаково: их код заносится в словарь. Вновь образованное определение СМА функционально почти не отличается от прежнего определения через двоеточие. Единственное отличие состоит в том, что слово С, заносящее код операции в словарь, находится в части DOES> слова 1MI, а сам код (2F) - в поле параметров слова СМА.

Мы уже определили большую группу команд процессора 8080. Но остальные его команды не так просты. Например, команда ADD дополнительно вносит содержимое заданного регистра в регистр А (сумматор). Для того чтобы на обычном ассемблере 8080 добавить

содержимое регистра В к содержимому регистра А, нужно ввести

ADD В

Код операции ADD в двоичной системе имеет вид 10000SSS, где SSS - три бита, используемые для указания задаваемого регистра (S означает источник). Регистр В задается как 000, отсюда "ADD В" в двоичном коде будет выглядеть следующим образом: 10000000.

Аналогично если регистр L задается как 101 (S), то выражение "ADD L" превратится в 10000101. Иными словами, нужный код операции получается при выполнении команды OR над двоичным значением 10000000 (шестнадцатиричное 80) и числом, обозначающим регистр. Определим операцию ADD так:

: ADD ( регистр# - ) 80 OR С, ;

Самый простой способ занесения номера нужного регистра в вершину стека - задать номера регистров в виде констант:

0 CONSTANT В
1 CONSTANT С
2 CONSTANT D
3 CONSTANT E
4 CONSTANT H
5 CONSTANT L
7 CONSTANT A

(Здесь перечислены все регистры, которые можно использовать в команде ADD.) Теперь для занесения в словарь кода операции сложения значений регистра В и сумматора можно написать:

В ADD

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

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

: 2MI CREATE C, DOES> ( регистр# - ) С@ OR С, ;

С помощью этого слова можно ввести следующие определения:

80 2MI ADD    88 2MI ADC    90 2MI SUB     98 2MI SBB
А0 2MI ANA    AB 2MI XRA    В0 2MI ORA     B8 2MI CMP

(2MI функционирует аналогично 1MI, т. е. запоминает уникальный код операции определяемой команды (ребенка) в поле параметров последней. Отличие же заключается в том, что 2MI заставляет команду-ребенка при выполнении логически складывать посредством OR код операции ребенка с номером регистра из стека.) Существует еще один класс машинных команд, содержащих номера регистров в коде операции, но в другом месте. Например, код команды 1NR (приращение) имеет вид OODDD100 (шестнадцатиричное число 04), где DDD - регистр, подлежащий приращению (D означает «получатель»). Мы можем воспользоваться константами, обозначающими номера регистров, но при этом необходимо осуществить сдвиг на три бита влево, прежде чем команда OR сложит их с кодом операции (сдвиг на три позиции влево эквивалентен умножению на восемь):

: INR ( регистр# -- ) 8 * 04 OR С, ;

Как и в предыдущем случае, введем определяющее слово:

: 3MI CREATE С,
  DOES> ( регистр# -- ) С@ SWAP 8 * OR С, ;

04 3МI INR

Теперь выражение "С INR" занесет в словарь код операции: 00001100.

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

Для создания команд остальных типов нам достаточно ввести всего два определяющих слова - 4MI и 5М1. Первое применяется для образования кодов тех операций, которые требуют дополнительно восьмиразрядного литерала, например ADI (непосредственное сложение с А). Второе слово определяет коды операций, требующих дополнительно 16-разрядного литерала. В качестве примера можно привести команды CALL, JMP и подобные им. Команды MOV, MVI и LX1 уникальны и поэтому специфицируются индивидуально посредством двоеточия без использования определяющего слова.

Изучая листинг, обратите внимание на то, что в него включены операторы управления, такие, как IF, ELSE,THEN,BEGIN, UNTIL, WHILE и REPEATE. Это не совсем те слова, с определениями которых вы уже познакомились ранее (где передача управления компилируется посредством высокоуровневых слов Форта), а их версии, созданные только для ассемблера, где передача управления и разрешение адресов, как и в традиционном ассемблере, осуществляются на уровне машинных команд. Однако они обеспечивают вам возможность программирования с использованием формата структур высокого уровня.

Но можно ли компилировать в словарь различные варианты слов IF, THEN и т. д., они ведь в нем смешаются? Конечно, так как команды ассемблера хранятся в контекстном словаре ASSEMBLER, а не в словаре FORTH. Определение CODE в нашем листинге инициирует слово ASSEMBLER, что делает этот контекстный словарь текущим всякий раз, когда мы начинаем CODE-определение.

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

: SHIFT+ RLC В ADD ;

Заметьте, что, появившись внутри ассемблерного определения, слово SHIFT+ помещает в словарь две команды, составляющие определение этого слова так, как если бы вместо него были введены сами команды:

RLC В ADD

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

Слово NEXT представляет собой одну из макрокоманд, написанных на языке Ассемблера. В нашей системе она определена так:

: NEXT (NEXT) JMP ;

Иными словами, NEXT - это машинная команда, передающая управление по адресу, который оставляет в вершине стека слово (NEXT). По данному адресу расположен код адресного интерпретатора (о котором речь шла в гл. 9). Адресный интерпретатор является ядром Форта и выполняет поочередно все адреса в скомпилированном Форт-определении. Каждое определение через CODE должно заканчиваться инициированием адресного интерпретатора. Следовательно, любое ассемблерное определение должно завершаться словом NEXT. В нашем ассемблере определения также должны иметь в конце слово END-CODE, которое дополнительно восстанавливает контекст.

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

HEX
CODE X ( n -- n') \ Меняются местами старший и младший байты n
  Н POP   L A MOV   H L MOV   A H MOV  H PUSH
  NEXT END-CODE

(Мы пересылаем п из стека в пару регистров HL, регистр L (младшие байты) в регистр А, регистр Н (старшие байты) в L, а А в Н, помешаем содержимое пары регистров HL в стек, передаем управление NEXT).

CODE BP ( a # -- ) \ Перевод из нижнего регистра в верхний
  D POP H POP
  BEGIN   D A MOV   Е ORA 0= NOT WHILE
    M A MOV   60 CPI   CS NOT IF
      20 SUI   A M MOV THEN
   D DCX    H INX   REPEAT NEXT END-CODE

(Пересылаем счетчик в пару регистров DE, а адрес - в пару регистров HL, начинаем цикл, проверяем, выполняя команду OR над содержимым регистров D и Е, не равно ли значение счетчика нулю. Пока его значение не равно нулю, перемещаем символ из памяти, на которую ссылается указатель, в сумматор. Если код обрабатываемого символа больше 60 (строчная «а» и выше), вычитаем десятичное число 32, преобразуя символ в прописной, и записываем в память. Уменьшаем счетчик и увеличиваем адрес. Повторяем цикл. Передаем управление NEXT).

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

В основу описанного здесь ассемблера положен ассемблер 8080, разработанный Дж. Кассэди. Мы внесли в него изменения в соответствии со Стандартом-83 и для простоты изучения убрали некоторые зависимые от системы фрагменты. С оригиналом вы можете познакомиться в [1 ]. Ассемблер для других процессоров описан в [2], [3], [4].

\ Ассемблер 8080
\ учебная версия ассемблера 8080,основанная на фигФорте;
\ разработана Джоном Кэсседи
HEX
VOCABULARY ASSEMBLER
: CODE CREATE HERE HERE 2- ! ASSEMBLER ;
ASSEMBLER DEFINITIONS
: END-CODE CURRENT @ CONTEXT ! ;
0 CONSTANT В   1 CONSTANT С   2 CONSTANT D    3 CONSTANT E
4 CONSTANT H   5 CONSTANT L   6 CONSTANT PSW  6 CONSTANT M
6 CONSTANT SP  7 CONSTANT A
: 1MI CREATE C, DOES> C@ C, ;
: 2MI CREATE C, DOES> C@ OR C, ;
: 3MI CREATE C, DOES> С@ SWAP 8 * OR C, ;
: 4MI CREATE C, DOES> С@ С, С, ;
: 5MI CREATE С, DOES> С@ С, , ;

\ Ассемблер 8080
HEX
00 1MI NOP   76 1MI HLT    F3 1MI DI    FB 1MI ED
07 1MI RLC   0F 1MI RRC    17 1MI RAL   1F 1MI RAR
E9 1MI PCHL  F9 1MI SPHL   E3 1MI XTHL  EB 1MI XCHG
27 1MI DAA   2F 1MI CMA    37 1MI STC   3F 1MI CMC
80 2MI ADD   88 2MI ADC    90 2MI SUB   98 2MI SВВ
А0 2MI ANA   A8 2MI XRA    B0 2MI ORA   B8 2MI CMP
B9 3MI DAD   C1 3MI POP    C3 3MI PUSH  B2 3MI STAX
0А 3MI LDAX  04 3MI INR    05 3MI DCR   03 3MI INX
0B 3MI DCX   C7 3MI RST    D3 4MI OUT   DB 4MI SBI
E6 4MI ANI   ЕЕ 4MI XRI    F6 4MI ORI   FE 4MI CPI
22 5MI SHLD  2A 5MI LHLD   32 5MI STA   3А 5МI LDA
CD 5MI CALL

\ Ассемблер 8880
HEX
C9 1MI RET   C3 5MI JMP    C2 CONSTANT 0=   D2 CONSTANT CS
E2 CONSTANT PE    F2 CONSTANT 0<
: NOT 8 OR ;
: MOV 8 * 40 + + C, ;
: MVI 8 * 6 + C, C, ;
: LXI 8 * 1+ C, , ;
: THEN HERE SWAP ! ;
: IF C, HERE 0, ;
: ELSE C3 IF SWAP THEN ;
: BEGIN HERE ;
: UNTIL C, , ;
: WHILE IF ;
: REPEAT SWAP JMP THEN ;
: NEXT (NEXT) JMP ;

УСОВЕРШЕНСТВОВАННЫЙ ГЕНЕРАТОР БЕССМЫСЛЕННЫХ СООБЩЕНИЙ

Генератор бессмысленных сообщений, о котором шла речь в гл. 10, имеет существенный недостаток: необходимо осуществлять возврат каретки в определении слова СООБЩЕНИЕ. Это ведет к мозаичному выводу фраз.

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

комментариев. Приобретайте навыки чтения программ на Форте. Попробуйте сделать определения более ясными. Результат работы нашей программы выглядит так:

В ДАННОМ СООБЩЕНИИ МЫ РАССКАЖЕМ ВАМ О ТОМ, ЧТО ВКЛАДЫВАЯ ИМЕЮЩИЕСЯ В НАЛИЧИИ
СРЕДСТВА В ИНТЕГРИРОВАННЫЙ ЦИФРОВОЙ КОМПЛЕКС ПРИМЕНЯЯ АВТОНОМНЫЙ КУЛЬТУРНЫЙ
ПРОДУКТ ПРЕДСТАВЛЯЕТСЯ ВОЗМОЖНЫМ ДАЖЕ НЕСМОТРЯ НА КВАЛИФИЦИРОВАННЫЙ ЦИФРОВОЙ
ПРОЕКТ ЕЦЕ БОЛЬШЕ КРЕПИТЬ УНИКАЛЬНЫЙ ПРОГРАММНЫЙ ОБЪЕМ.
С ОДНОЙ СТОРОНЫ, ИССЛЕДОВАНИЯ ПОКАЗАЛИ, ЧТО СТРУКТУРИРОВАННО ПРИМЕНЯЯ
ОБЩИЙ ТРУДОВОЙ ОБЪЕМ УЧИТЫВАЯ АВТОМАТИЗИРОВАННЫЙ ПРОГРАММНЫЙ АВТОМАТ
СТАНОВИТСЯ НЕОСУЩЕСТВИМЫМ ПОДНИМАЯ СЛУЧАЙНЫЙ ПРОИЗВОДСТВЕННЫЙ ПРОЦЕСС
ЕЩЕ БОЛЬШЕ КРЕПИТЬ КВАЛИФИЦИРОВАННЫЙ МНОГООТРАСЛЕВОЙ ПРИНЦИП.
С ДРУГОЙ СТОРОНЫ, ТЕМ НЕ МЕНЕЕ, ПРАКТИЧЕСКИЙ ОПЫТ ПОКАЗЫВАЕТ, ЧТО
СТРУКТУРИРОВАННО ПРИМЕНЯЯ ЦИФРОВОЙ ХИМИЧЕСКИЙ КОМПЛЕКС УЧИТЫВАЯ НЕОБЫЧАЙНЫЙ
МНОГООТРАСЛЕВОЙ АВТОМАТ НЕОБХОДИМО РАССМАТРИВАЯ НЕОБЫЧАЙНЫЙ ПРОГРАММНЫЙ
КРИТЕРИИ ФУНКЦИОНИРОВАТЬ КАК ВРЕМЕННЫЙ ЦИФРОВОЙ ПРОЕКТ.
В РЕЗУЛЬТАТЕ НАШЕ ПРЕДЛОЖЕНИЕ ЗАКЛЮЧАЕТСЯ В ТОМ, ЧТО СТРУКТУРНО ПРИМЕНЯЯ
СЛУЧАЙНЫЙ КУЛЬТУРНЫЙ ИНТЕРЕС УЧИТЫВАЯ СИСТЕМАТИЗИРОВАННЫЙ ЦИФРОВОЙ УРОВЕНЬ
НЕОБХОДИМО РАССМАТРИВАЯ ЦИФРОВОЙ ТРУДОВОЙ ПРОЕКТ ЕЩЕ БОЛЬШЕ КРЕПИТЬ
СИНХРОНИЗИРОВАННЫЙ КОРОТКОВОЛНОВЫЙ КРИТЕРИИ.
Block # 156
 0 ( Генератор бессмысленных выражений, вариант с самоформатированием )
 1 VARIABLE ОСТАТОК \ количество оставшихся символов, подлежащих сканировании
 2 VARIABLE ПО-ГОРИЗОНТАЛИ \ текущее положение курсора для вывода по
 3 \ горизонтали
 4 70 CONSTANT ПРАВГРАН \ правая граница
 5 : СКАНЕР ( длина-поиска -- адр-пробела | конец-поля)
 6   2DUP + ROT ROT OVER + SWAP ВО 1 С@ BL =
 7     IF DROP I LEAVE THEN LOOP ;
 8 : ДАЙСЛОВО ( a -- a слово-счетчик) \ получ. очереди, слова для форматирования
 9     DUP ОСТАТОК @ BUP 0> IF СКАНЕР ELSE DROP THEN OVER -
10     DUP 1+ NEGATE ОСТАТОК +! ;
11 : СЮДА? ( счетчик -- 1=подходит для данной строки)
12     ПО-ГОРИЭОНТАЛИ @ + ПРАВГРАН < ;
13 : SPACE' ПО-ГОРИЗОНТАЛИ @ IF SPACE 1 ПО-ГОРИЭОНТАЛИ +! THEN ;
14 : CR' CR 0 ПО-ГОРИЗОНТАЛИ ! ;
15 157 LOAD 158 LOAD
Block # 157
 0 ( Генератор бессмысленных выражений, вариант с самоформатированием )
 1 : .СЛОВО ( а # -- ) \ вывод слова, если необходимо, выполнение CR
 2   DUP СЮДА? IF SPACE' ELSE CR' THEN
 3   DUP ПО-ГОРИЗОНТАЛИ +! TYPE ;
 4 : СЛЕДУЮЩЕЕ ( a # -- след.адр.)
 5    + 1+ ;
 6 : ВЫВОД ( а #символов -- ) \ вывод сформатированного текста
 7    ОСТАТОК !
 8    BEGIN ДАЙСЛОВО DUP WHILE 2DUP .СЛОВО СЛЕДУЮЩЕЕ REPEAT
 9    2DROP ;
10 : АБРЕД ( -- а) 161 BLOCK ; \ случайные слова
11 : ОБОРОТЫ ( -- а) 160 BLOCK ; \ связывающие обороты
12 : ВВЕДЕНИЯ ( - a) 1S9 BLOCK ; \ начала фраз
13 : БРЕД ( #строки #столбца -- а) \ получение адреса слова
14    20 * SWAP 64 * + АБРЕД + ;
15
Block # 158
 0 ( Генератор бессмысленных выражений, вариант с самоформатированием )
 1 : .БРЕД ( #строки #столбца -- ) ВРЕД 20 ВЫВОД ;
 2 : ЧАСТЬ-РЕЧИ ( #строки -- ) CREATE , \ определение частей речи
 3    DOES> @ 10 CHOOSE SWAP .БРЕД ;
 4 0 ЧАСТЬ-РЕЧИ 1ПРИЛАГАТЕЛЬНОЕ
 5 1 ЧАСТЬ-РЕЧИ 2ПРИЛДГАТЕЛЬНОЕ
 6 2 ЧАСТЬ-РЕЧИ СУЩЕСТВИТЕЛЬНОЕ
 7 : ФРАЗА 1ПРИЛАГАТЕЛЬНОЕ 2ПРИЛАГАТЕЛЬНОЕ СУЩЕСТВИТЕЛЬНОЕ ;
 8 : ОБОРОТ ( #группы -- ) [ 4 64 * ] LITERAL *
 9     3 CHOOSE 64 * + ОБОРОТЫ + 64 ВЫВОД ;
10 : ПОВЕСТВОВАНИЕ 4 0 DO I ОБОРОТ ФРАЗА LOOP ." ." CR' ;
11 : ВВЕДЕНИЕ ( #абзаца -- )
12    CR' 64 * ВВЕДЕНИЯ + 64 ВЫВОД ;
13 : СООБЩЕНИЕ CR' CR' 4 0 DO I ВВЕДЕНИЕ ПОВЕСТВОВАНИЕ LOOP ;
14
15
Block # 159
 0 В ДАННОМ СООБЩЕНИИ МЫ РАССКАЖЕМ ВАМ О ТОМ, ЧТО
 1 С ОДНОЙ СТОРОНЫ, ИССЛЕДОВАНИЯ ПОКАЗАЛИ, ЧТО
 2 С ДРУГОЕ СТОРОНЫ, ТЕМ НЕ МЕНЕЕ, ПРАКТИЧЕСКИЙ ОПЫТ ПОКАЗЫВАЕТ, ЧТО
 3 В РЕЗУЛЬТАТЕ НАШЕ ПРЕДЛОЖЕНИЕ ЗАКЛЮЧАЕТСЯ В ТОМ, ЧТО
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Block # 160
 0 ПРИМЕНЯЯ
 1 ВКЛАДЫВАЯ ИМЕЮЩИЕСЯ В НАЛИЧИИ СРЕДСТВА В
 2 СТРУКТУРИРОВАННО ПРИМЕНЯЯ
 3
 4 ИМЕЯ В ВИДУ
 5 ЧТОБЫ КОМПЕНСИРОВАТЬ
 6 УЧИТЫВАЯ
 7
 8 ПРЕДСТАВЛЯЕТСЯ ВОЗМОЖНЫМ ДАЖЕ НЕСМОТРЯ НА
 9 СТАНОВИТСЯ НЕОСУЩЕСТВИМЫМ ПОДНИМАЯ
10 НЕОБХОДИМО РАССМАТРИВАЯ
11
12 ФУНКЦИОНИРОВАТЬ КАК
13 СОЗДАТЬ
14 ЕЩЕ БОЛЬШЕ КРЕПИТЬ
15
Block # 161
 0 ВЫСОКИЙ             КУЛЬТУРНЫЙ          УРОВЕНЬ
 1 ОБЩИЙ               ПРОИЗВОДСТВЕННЫЙ    ИНТЕРЕС
 2 АВТОМАТИЗИРОВАННЫЙ  НАУКОЕМКИЙ          КОМПЛЕКС
 3 ЗАПЛАНИРОВАННЫЙ     ВАЛОВОЙ             ОБЪЕМ
 4 ИНТЕГРИРОВАННЫЙ     ЦИФРОВОЙ            КОЭФФИЦИЕНТ
 5 КВАЛИФИЦИРОВАННЫЙ   МНОГООТРАСЛЕВОЙ     ПРИНЦИП
 6 ПРЕДСТАВИТЕЛЬНЫЙ    ХИМИЧЕСКИЙ          ГЕНЕРАТОР
 7 ТЕХНОЛОГИЧЕСКИЙ     НЕПРЕРЫВНЫЙ         ПРОЦЕСС
 8 АВТОНОМНЫЙ          АППАРАТНЫЙ          ИНТЕРФЕЙС
 9 ЦИФРОВОЙ            НЕЗАВИСИМЫЙ         АВТОМАТ
10 СИНХРОНИЗИРОВАННЫЙ  ФУНКЦИОНАЛЬНЫЙ      КРИТЕРИЙ
11 СИСТЕМАТИЗИРОВАННЫЙ КОРОТКОВОЛНОВОЙ     ПРОЕКТ
12 СЛУЧАЙНЫЙ           ОТРИЦАТЕЛЬНЫЙ       ИМПУЛЬС
13 НЕОБЫЧАЙНЫЙ         ТРУДОВОЙ            ПОДЪЕМ
14 ВРЕМЕННЫЙ           НЕХАРАКТЕРНЫЙ       СПАД
15 УНИКАЛЬНЫЙ          ПРОГРАММНЫЙ         ПРОДУКТ

УПРАЖНЕНИЯ

12.1. Для описания четырех полей в простой файловой системе мы применяли следующие определения:

                ( смещение) ( длина)
CREATE фамилия      0 ,        16 ,
CREATE имя         16 ,        12 ,
CREATE работа      28 ,        24 ,
CREATE телефон     52 ,        12 ,

а затем добавили еще одно

64 CONSTANT /ЗАПИСЬ

чтобы определить длину всей записи. Изменяя длину поля с фамилией, мы должны также подправлять начальный адрес остальных трех полей, а также значение переменной /ЗАПИСЬ Найдите программный способ изменения этих значений. Определите синтаксис и напишите программу.

12.2 Используя язык базы данных, реализованной в простой файловой системе, определите новое слово «вызвать», которое осуществляло бы поиск имени и помещало в вершину стека ФИО и номер телефона, например:

Вызвать Конни
Конни Чанг 555-9653 ok

ЛИТЕРАТУРА

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