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

         

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


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

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


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