Хранение программ и данных
Объем памяти микрокомпьютера обычно не достаточен, чтобы хранить в ней все данные и программы, которые нам нужны. Поэтому необходимо использовать некоторое устройство массовой памяти. В микрокомпьютерах в большинстве случаев применяется либо кассетный магнитофон, либо, что более распространено, магнитный диск. Большинство языков программирования пользуется для хранения программ и данных именованными файлами, управление и обращение с которыми обеспечивается дисковой операционной системой. Как вы уже знаете. Форт сильно отличается от других языков тем, что сохраняет программы и данные в блоках.
Чтобы понять дальнейший материал этой главы, вы должны узнать, что такое файлы и как они используются в типичной дисковой операционной системе, и немного о структуре диска. Файл попросту представляет собой совокупность двоичных данных, организованных в последовательность байтов, как в памяти ЭВМ. Файл может содержать алфавитно-цифровой текст в виде кодов ASCII (например, текст этой главы был сохранен в файле после того, как его ввели с помощью процессора текста), программу, которая также часто сохраняется в виде кодов ASCII, и данные, которые могут быть записаны либо кодами ASCII, либо в двоичной форме. Файл можно загружать, если это программа, например, на Бейсике либо к нему может быть организован доступ из программы (если это данные).
Если вы введете команду "DIR" с клавиатуры, то большинство операционных систем покажет вам справочник (директорию) файлов на диске. То, как организуется хранение файла на диске, находится под контролем операционной системы.
Данные записываются на диске с помощью магнитных головок, входящих в привод диска) вдоль концентрических окружностей, называемых дорожками. Каждая дорожка разделяется на несколько дуг, называемых секторами, в каждом секторе сохраняется определенное число байтов (например, в IBM PC с DOS 2.1 на сектор приходится 512 байтов и 9 секторов на дорожку). Компьютер считывает с диска сектор за сектором. Сектор - это минимальное количество информации, которое за один раз может быть считано компьютером.
В свою очередь, файл хранится в одном или нескольких секторах. Секторы в файле могут следовать не обязательно подряд, но операционная система прослеживает, какие секторы и в какой последовательности соответствует каждому файлу. Если часть файла были изменена, то соответствующие ей секторы перезаписываются и изменяются.
В системе Форт, в противоположность описанному, данные и программы хранятся на дисках в блоках, содержащих по 1024 байта; так определяется способ хранения в стандартах. Каждый блок может состоять из одного или более секторов (Блоки также называют экранами, имея в виду, что находящаяся в блоках информация может быть очень удобно представлена на экране дисплея в виде 16 строк по 64 символа в строке. Некоторые считают, что экран означает любой блок, который преобразован так, что его можно вывести на экран видеодисплея, даже если он содержит, например, управляющие символы.) Как мы уже говорили раньше, Форт может работать либо "под операционной системой" (т.е., например. Форт вызывается в операционной системе СР/М или MS-DOS, если ввести команду FOKTH), либо может быть сам себе операционной системой; в этом случае он готов к работе сразу же после того, как машина включена и "загружена". В последнем случае блоки обычно начинаются с минимального номера на первом приводе дисковода и их последовательная нумерация ведется до самого большого номера сектора последнего привода
Хотя многие реализации Форта не работают с файлами операционной системы, имеется возможность организовать блоки Форта в виде файлов данных и программ, подобно тому как операционная система создает файлы из секторов. (В MMSFORTH имеется программа-утилита, которая позволяет передавать данные из операционной системы в Форт и обратно.) Взаимодействие Форта с операционной системой - это особая проблема, решение которой зависит от конкретного оборудования ЭВМ и операционной системы. Мы не будем обсуждать эту проблему, но покажем вам, как надо работать с программами и данными, которые хранятся в блоках Форта.
Мы также рассмотрим некоторые простейшие способы использования нескольких блоков подобно файлу. Сначала мы рассмотрим запись-чтение программы, а затем хранение и запись данных. Вы убедитесь, что отсутствие файлов не является серьезным препятствием для форта и блоки имеют даже некоторые преимущества.
Вывод листинга программы и загрузка
Вы уже видели в гл. 1, как можно вывести листинг блоков исходной программы (LIST) и загрузить блок (LOAD) и, может быть, уже редактировали их, пользуясь либо редактором вашей системы или редактором, описанным здесь в гл. 12. Сейчас мы рассмотрим более подробно сначала вывод листинга, а затем загрузку блоков. Для повторения пройденного введите 25 LIST тогда на экране вы увидите текст блока номер 25 в виде последовательности из 16 строк по 64 символа в каждой, пронумерованных от 0 до 15. Во многих версиях Форт имеется возможность вывести листинг нескольких блоков. Если в MMSFORTH ввести 25 6 PLISTS то будет выведен листинг шести блоков, начиная с 25-го по 30-й (В некоторых версиях синонимом этого слова является слово SHOW.) Конечно, все они быстро пробегут по экрану вверх. Слова типа PLISTS предусмотрены для вывода нескольких блоков на принтер. В некоторых версиях (в частности, в MMSFORTH) есть и другие слова, с помощью которых можно получить изящно оформленный листинг программы.
Переменная SCR (screen - экран) используется Форт-системой для вывода листинга блоков. Слово SCR имеется в Форт-79, в Форт-83 это слово необязательное, но в большинстве версий оно также имеется, и в нем запоминается номер последнего блока, который был выведен. Так, после 25 LIST SCR @ . выведет на экран 25. В различных версиях имеются слова для повторения вывода листинга блока, вывода предыдущего и следующего блока. Если назвать эти слова L, LL (вывести_последний) или LN (вывести_следующий), то они могут быть определены следующим образом: : L ( - ) SCR @ LIST ; : LL ( - ) SCR @ 1- LIST ; и : LN ( - ) SCR @ 1+ LIST ; Если повторять ввод LN, то будут выводиться последовательно несколько блоков.
Во многих версиях Форта есть слово INDEX, которое показывает первые строки последовательности экранов (индексные строки, в которые обычно записывают пояснения о назначении экранов). В некоторых системах перед словом INDEX должны быть указаны число просматриваемых экранов и номер начального экрана (так сделано в MMSFORTH), в других - номера начального и конечного экранов. Таким образом, в зависимости от версии 20 6 INDEX или 20 25 INDEX покажут первые строки блоков 21, 22, 23, 24 и 25.
Загрузка блоков словом LOAD так же проста, как и вывод блоков. Если в блоке содержится текст, который может быть введен с клавиатуры, то этот же текст может быть введен и из блока. Текст будет интерпретироваться точно так же, как если бы он был введен вручную. Так, если блок содержит определения слов или текст, который должен быть сразу исполнен, то 25 LOAD введет текст. Очевидно, слово LOAD можно также использовать, если оно записано внутри блока. Пусть, например, в блоке 25 содержатся определения, которые являются частью программы, продолжающейся в блоке 30. Если в блоке 25 включен текст 30 LOAD то, когда будет встречен текст 30 LOAD, то будет загружен блок 30, после чего произойдет возврат к блоку 25, чтобы продолжить ввод того, что еще могло остаться в этом блоке.
Обычно программа располагается на последовательно расположенных блоках. В большинстве версий Форта есть нестандартные слова для загрузки последовательности блоков. В MMSFORTH так же, как слово PLISTS выводит, слово LOADS загружает последовательность экранов. Так, если программа находится в блоках с 25-го по 30-й, то их можно загрузить путем ввода с клавиатуры: 25 6 LOADS Слово LOADS можно определить следующим образом: : LOADS ( п1 п2 - ) OVER + SWAP DO I LOAD LOOP ;
Вам должно быть понятно, как оно работает. (В некоторых версиях используются совершенно другие слова для загрузки, о чем будет сказано дальше.) Слово THRU можно определить так: : THRU ( п1 п2 - ) 1+ SWAP DO I LOAD LOOP ;
Очевидно, что можно в конце каждого блока последовательности из нескольких блоков помещать номер следующего загружаемого блока со словом LOAD.
Например, в конце блока 25 нужно поместить 26 LOAD, в блоке 26 - 27 LOAD и т.д. Но во многих версиях предусмотрен более простой способ. Нестандартное слово -> (следующий_блок) означает "загрузить следующий блок". Как только будет встречено это слово, будет загружаться следующий блок, даже если после него в предыдущем блоке что-либо осталось. Мы узнаем вскоре, как определить это слово.
Слово EXIT производит особое действие, когда оно встречается в блоке (вне определения через двоеточие). Оно прекращает загрузку блока. Поэтому, если вы хотите загрузить только часть программы, вы можете вставить слово EXIT перед той частью программы, которая должна быть проигнорирована.
Кроме слов LIST и LOAD вы должны познакомиться со словом COPY (оно также нестандартное, но имеется во многих версиях). Слово COPY используется в такой форме обращения: n1 n2 COPY чтобы скопировать блок n1 в блок n2. В зависимости от версии Форта после слова COPY может потребоваться слово FLUSH, которое указывает, что произведенные в блоке изменения следует сохранить. Слово FLUSH в MMSFORTH включать в программу копирования обязательно. Слово COPY удобно для перемещения программы в любое место. Мы предлагаем вам дать определение этого слова.
Слово BLK - это переменная, в которой записывается номер блока, загруженного последним (если ввод производится с клавиатуры, то в BLK записан 0, это означает, что в блоке 0 не может быть записан исходный код программы). Вы можете определить слово : .BLK ( - ) BLK @ U. : IMMEDIATE которое будет показывать на экране номер блока, если он был загружен. Слово IMMEDIATE обеспечивает немедленное исполнение слова BLK, независимо от того, включено оно или не включено в определение-двоеточие. (Мы рассмотрим слово IMMEDIATE более подробно в гл. 15.)
При загрузке блоков используется еще одна переменная >IN. В гл. 9 вы узнали, что >IN указывает на соответствующий байт во входном буфере, в который поступает входная информация (более подробно об этом слове смотрите в гл. 15).
Если содержимое BLK не равно 0, то это означает, что ввод производится с диска, тогда >IN указывает на номер байта в блоке, из которого приходит ввод. В некоторых версиях Форта имеется слово \ (обратная косая черта), которое используется для пропуска оставшейся части строки, т. е. если встречается \, то остаток строки при вводе игнорируется, поэтому этот знак можно использовать для помещения комментариев. Приведем определение этого слова: : \ ( - ) >IN @ 64 / 1+ 64 * >IN ! ; IMMEDIATE
Слово \ заставляет переменную >IN указывать на начало следующей строки, независимо от того, встречается ли оно в определении через двоеточие или самостоятельно. Теперь мы можем определить слово --> : : --> ( -- ) 0 >IN ! 1 BLK +! ; IMMEDIATE
Вам должно быть понятно, как оно работает.
Упражнения
1. Предположим, что вы определили слово : =-> ( -- ) BLK @ 1+ LOAD ; IMMEDIATE Чем его действие будет отличаться от действия слова --> , определенного выше? (Указание: рассмотрите, что произойдет после того, как будет загружен следующий блок.) 2. Дайте новое определение слова --> под именем N->, которое не просто загружает следующий блок, а, кроме того сообщает на экране "Блок n загружен", где n - номер блока, который был загружен. Не пользуйтесь для определения словом -->! 3. Определите слово LISTS, которое будет выводить блоки, сообщая в начале каждого блока его номер "Блок ххх"", где ххх - номер блока, если задан номер начального блока и число блоков, которое нужно вывести, т. е. блоки должны выводиться в виде Блок 25 1 ...... 2 ...... 3 ...... Для вывода каждого блока потребуется 17 строк. На обычной бумажной странице можно напечатать 66 строк- Определите слово LISTS так, чтобы между листингами экранов было такое количество пустых строк, чтобы три экрана занимали ровно 66 строк. 4. Иногда требуется, чтобы сообщение об ошибке выдавалось во время загрузки блока и указывало, в каком месте в блоке обнаружена ошибка. Определите слово с именем ... (уголок), которое указывало бы на местоположение ошибки, т.
е. оно должно выдавать сообщение типа Block 25 Line 3 Character 53 (Блок Строка Символ) Вы можете вставить знак " в любом месте, где вы предполагаете ошибку. Для этого вам потребуется использовать слово >IN, чтобы определить номер строки и номер символа в строке. Используйте слово IMMEDIATE. 5. При отладке программы возникает необходимость неоднократного изменения содержимого экранов. Если первое слово, которое определено в программе, : TASK, то осуществить это проще. Пусть ваша программа начинается в блоке 20, вы изменили ее и хотите удалить старую версию программы. Что вам нужно напечатать на клавиатуре, для того чтобы это сделать?
Скрытые блоки
В некоторых версиях Форта реализована идея скрытых блоков. Очевидно, возможные реализации могут быть различными, но одна из них такова: в четных блоках находится код, который нужно загрузить, в нечетные блоки записаны комментарии о содержимом следующих блоков. Если вы попытаетесь загрузить скрытый блок, то не сможете это сделать - произойдет ошибка. Кое-кому может показаться, что применение скрытых блоков - это спорный вопрос, поскольку дисковое пространство должно быть в два раза больше и, кроме того, нельзя одновременно видеть на экране блок, содержащий код программы, и скрытый блок. Но некоторые.программисты считают такую методику заслуживающей внимания и в некоторых случаях полезной, например если нужны очень подробные комментарии к программе. Вместо того чтобы рассказывать вам, как это делается, давайте реализуем скрытые блоки в следующих упражнениях.
Упражнения
1. Определите слово SLOAD, взяв за основу слово LOAD, которое, если указывается четный номер блока, дает сообщение "Не могу загружать четные блоки" и возвращает управление клавиатуре. Дайте определение, используя слова ABORT" и." (точка-кавычка). 2. Определите слово SLIST, используя слово LIST, таким образом, чтобы при запросе на выдачу блока с четным номером выводился бы следующий блок с нечетным номером. (Разумеется, при запросе на выдачу блока с нечетным номером нужно.
чтобы он выводился.) 3. Определите слово VIEW, которое должно выводить скрытый блок, связанный с загружаемым блоком, т.е. практически выводить листинг скрытого блока. Таким образом, если вводится 20 VIEW или 19 VIEW, будет выводиться скрытый блок 19. 4. Определите слово SLISTS, которое, если задан номер блока и количество блоков, которое должно быть выведено, будет выводить четные блоки, на которых записан исходный код программы. При этом первый выводимый блок должен быть такой же, как в случае слова LIST, определенного в упражнении 2. Перед каждым блоком должен быть указан номер, и если назначено вывести их на устройство печати, то нужно, чтобы в 66 строках помещалось ровно три экрана. Так, например, 19 5 LISTS должно выводить блоки 20, 22, 24, 26 и 28. 5. Определите слово VIEWS, аналогичное слову SLISTS, из упражнения 3, но выводящее скрытые блоки, т. е. если ввести 20 5 VIEWS то будут выведены блоки 19, 21, 23, 25 и 27. 6.Дайте новое определение слова --> с именем S-->, которое вызывает загрузку следующего блока, пропуская имеющиеся скрытые блоки. Если исходный код размещается у вас на четных и нечетных блоках, то реализовать идею скрытых блоков нельзя - но крайней мере, не прибегая к сложным программным ухищрениям, которые допускали бы использование "обычных" блоков с исходным кодом программы.
Загрузка экранов
Наиболее часто Форт подвергается критике за то, что невозможно загрузить Форт-программу из файлов с помощью операционной системы. Это означает, что вы не можете загрузить программу, вводя LOAD "PROG.BAS" как это делается в Бейсике. (Хотя мы уже выше упоминали о существовании некоторых версий Форта, которые обеспечивают возможность загрузки Форт-программы из файла операционной системы.) Обычно программист должен сам следить, в каких блоках записана его программа, а также за последовательностью блоков, из которых она должна быть загружена. Как правило, для этого используется так называемый загрузочный экран, или загрузочный блок.
Загрузочный экран предназначен для того, чтобы связать имя программы с блоками, в которых она размещается, с целью осуществления загрузки программы по имени. Реализация загрузки экранов может иметь различную форму.
Проще всего загрузить программу, если все относящиеся к ней блоки соединяются словом -->. Предположим, что у вас имеется несколько наборов расширяющих слов, которые можно загружать по мере необходимости. Допустим, это слова для работы со строками, слова арифметики с плавающей запятой, декомпилятор и графические слова. В этом случае вы можете скомпоновать блок, в котором содержатся следующие определения: : STRINGS 30 LOAD ; (Символьные строки) : FLTPT- 40 LOAD ; (Арифметика с плавающей запятой) : DECOMP 50 LOAD ; (Декомпиляция) : GRAPHICS 60 LOAD ; (Графика)
Если вы загрузите экран, на котором помещены эти определения, то вы сможете вызывать по выбору любой из имеющихся наборов, вводя его имя. Например, слово STRINGS загрузит слова для работы со строками. Разумеется, если блоки в STRINGS не объединены с помощью -->, то можно определить слово: : STRINGS 20 5 LOADS : или : STRINGS 20 LOAD 23 LOAD 28 LOAD : если программа находится не в последовательных блоках. Идея состоит в том, чтобы определить слово LOADS, которое сильно отличается от рассмотренного нами выше слова LOADS. Новое слово LOADS позволяет дать определение слова, которое после его ввода будет загружать блок. Вот определение этого слова: : LOADS ( n - ) CREATE , DOES> @ LOAD ;
Если это определение использовать, например, так: 30 LOADS STRINGS 40 LOADS FLTPT 50 LOADS DECOMP 60 LOADS GRAPHICS то слово LOADS определит слова STRINGS, FLTPT, DECOMP и GRAPHICS. Поэтому если ввести одно из этих слов, то будет загружен соответствующий ему блок.,Пока вы еще не знаете слово DOES>, поэтому вам может быть не вполне понятно, как работает слово LOADS, но если вы просмотрите гл. 6, где рассматривается слово CREATE, то поймете, как в данном случае работает DOES>. Вкратце, слова, заключенные между CREATE и DOES>, определяют, что происходит при исполнении слова LOADS, в то время как слова, находящиеся между DOES> и ;, описывают, что должны делать вновь определенные слова.
В данном случае они извлекают число - номер блока из слова, подобного переменной, и затем загружают соответствующий блок. Конструкция CREATE...DOES> порождает слова, которые создают новый класс слов, о чем мы более детально будем рассказывать в следующей главе. Вернемся, однако, к загрузке блоков. Мы можем создать блок-справочник, который будет напоминать о названиях программ и наборов расширяющих слов. Предположим, что вы хотите иметь справочник блока 10, причем загрузочный экран имеет номер 20. Последнее определение в блоке 20 пусть будет таким: : DIR 10 LOAD : при этом пусть в блоке 10 находится ." Расширения, определенные в блоке 20" CR CR ." STRINGS" CR (Символьные строки) ." FLTPT" CR (Арифметика с плавающей запятой) ." DECOMP" CR (Декомпиляция) ." GRAPHICS" CR (Графика)
(В Форт-83 вместо оператора." используется.( ). Если ввести DIR, то после загрузки загрузочного экрана вам будут представлены возможности выбора одного из наборов расширяющих слов. В некоторых версиях Форта, в частности в MMSFORTH, вам предоставляется возможность указать, какой из блоков Форта нужно загрузить. Если на одном блоке не хватает места для справочника, нужно указать, как связаны вместе все блоки.
Можно работать с блоками многими другими способами. Некоторые из них мы попробуем применить в упражнениях.
Упражнения
1. Пусть у вас имеется игровая программа, которая начинается в блоке 100. Каким образом можно использовать константу, чтобы можно было загрузить программу, вводя PACFORTH LOAD ? 2. Определите новое слово для загрузки GET, которое загружало бы программу с помощью GET PACFORTH. 3. Если блоки программы расположены не последовательно, то можно для хранения их номеров использовать массив, первым элементом которого будет число блоков. Так. если PACFORTH находится в блоках 30, 33, и 36, то массив можно определить так: CREATE PACFORTH 3 , 30 , 33 , 36 , Опишите слово LOADIT так, что если вы вводите PACFORTH LOADIT то будет загружена программа PACFORTH. (Указание: используйте цикл DO...LOOP.) 4.
Большим неудобством в Форте может быть слежение за тем, где находятся программы, особенно если они не находятся в последовательных блоках. Определите слово SHOWBLOCKS (показать_блоки) так. что если вы вводите PACFORTH SHOWBLOCKS то увидите 30 33 36 ok, 5. Для слежения за номерами блоков нескольких программ можно также использовать массив типа того, что мы применили в упражнении 3. Предположим, что у вас имеются массивы номеров блоков программ 1PROG. 2PROG и 3PROG. Создайте массив CREATE #PROGS 5 , 1PROG , 2 PROG , 3PROG , Теперь определите слово ?BLOCKS (в_каких_блоках?) так, что если вы напечатаете #PROGS ?BLOCKS то увидите что-нибудь вроде 1 23 25 26 27 2 31 33 39 3 55 56 57 58 60 Надеемся, что в действительности вы никогда не устроите такой беспорядок в ваших блоках. 6. Определите слово LOADEM так, что если вы напечатаете SPROGS 3 LOADEM то будет загружена программа 3PROG из предыдущего примера.
Эти упражнения могут дать вам первоначальную идею для организации блоков в файлоподобные структуры. Мы рассмотрим детальнее данный вопрос в данной главе дальше.
Работа с содержимым блоков
Очевидно, что нужно иметь способ, позволяющий манипулировать содержимым любого блока. Это должен уметь делать редактор. Возможность управления содержимым блоков требуется также для извлечения данных из блоков. Если вы выбираете блок (n), который содержит какой-то текст, например определения слов, и введете n BLOCK 1024 TYPE то увидите содержимое этого блока, правда выведенное не очень красиво. Слово BLOCK переносит содержимое блока в буфер блока область памяти размером 1024 байта, и оставляет адрес буфера в стеке. После этого 1024 TYPE печатает на экране содержимое буфера. Теперь вы можете делать всевозможные полезные действия с содержимым того блока, которое было перенесено в память словом BLOCK. Если напечатать n BLOCK CR 64 TYPE то вы увидите на экране первые 64 символа из блока n, т.е. его первую строку. Последовательность действий n BLOCK 64 + CR 64 TYPE вызовет печать второй строки.
Находящаяся в блоке информация может быть использована, если ее предварительно поместить в память словом BLOCK, с этого момента с ней можно обращаться как с данными, находящимися в памяти. Например, вот определение слова LIST: : LIST ( n - ) CR BLOCK 16 0 DO I 2 .R SPACE DUP I 64 * + 64 -TRAILING TYPE CR LOOP DROP ; Вам нетрудно понять, как оно работает. Мы должны здесь упомянуть, что в некоторых версиях Форта имеются операторы, позволяющие обмениваться содержимым блоков не в буферах, а в специально выделенной области памяти. Так, в MMSFORTH 50 PAD RBLK считает в память информацию, содержащуюся в блоке 50, помещая ее в память, начиная с адреса PAD, в то время как 50 PAD WBLK запишет 1024 байта из памяти с адреса PAD в блок 50. С помощью этих двух слов в MMSFORTH описываются слово BLOCK и некоторые другие слова для обращения с буферами блоков.
Предлагаем вам поэкспериментировать с внесением изменений содержимого блоков в нескольких упражнениях, но сначала мы должны описать, как работает блочный буфер. Выберите два блока для вывода (назовем их n1 и n2). Теперь напечатайте n1 LIST, а после этого n2 LIST. Понаблюдайте за поведением дисковода, следя за обращением к нему, и снова напечатайте n1 LIST. Вывод листинга произойдет без участия дисковода. Почему? В Форт-системе имеется по крайней мере два буфера блока (а в некоторых и больше). Один из блоков вы загрузили в буфер с помощью nl LIST, другой командой n2 LIST - в следующий буфер. Когда во второй раз вы напечатали nl LIST, Форт быстро определил, что этот блок уже находится в памяти, поэтому он не сделал попытки загрузить его снова с диска. Если блок уже помещен в память словами BLOCK, LIST, LOAD и т. п., то он будет загружаться только в том случае, если его еще нет в памяти. Подобное использование буферов блока называется иногда хранением в виртуальной памяти, поскольку в некотором смысле диск является частью памяти компьютера. Способ использования буферов диска называют также кэшированием т.е. хранением в памяти часто используемой информации с диска, не без загрузки ее с диска каждый раз, когда требуется доступ к этим данным.
Кэш-диск сокращает число обращений к дисковому устройству, значительно ускоряя исполнение программы, когда одни и те же данные требуются многократно. Во многих версиях Форта число буферов диска увеличено минимум на 2. Если вам придется работать с большим количеством часто используемых данных и вам не хватит двух буферов блоков, вы можете увеличить их число. Попробуем сделать несколько экспериментов, манипулируя с информацией из блоков, чтобы проследить, как используются буферы. Выберите три блока, содержимым которых вы не дорожите, и поэтому их можно переписывать (мы назовем их n1, n2 и n3 и будем считать, что у нас есть только два буфера блоков). Теперь попробуйте сделать 10 n1 BLOCK ! а потом n1 BLOCK @ . выведет на экран число 10. Первый элемент в блоке n1 вы изменили на 10. Теперь напечатайте 20 n2 BLOCK ! после этого 30 n3 BLOCK ! а затем n1 BLOCK @
Число 10, которое вы занесли в первый элемент блока n1, куда то пропало! Что произошло? Когда вы поместили число 30 в блок n3, то поскольку блок n3 был загружен с диска в первый блочный буфер, то его содержимое наложилось на содержимое ранее находившегося здесь блока n1. Поэтому перед извлечением первого элемента блока n1 произошла перезагрузка n1, но с его исходным содержимым, а не с числом 10 в первом элементе. Когда в блочный буфер вводится новый блок, то он попадает в последний использованный буфер. Как же в таком случае сохранить измененные данные из буфера на диске? Изменения в буферах диска могут быть сделаны постоянными, если вы используете слово UPDATE. Попробуйте проделать эксперимент заново, но после каждого изменения, внесенного оператором записи !, напечатайте UPDATE. Теперь, когда вы во второй раз введете n1 BLOCK @ . то увидите, что в первом элементе блока n1 было запомнено число 10, UPDATE помечает, что буфер нужно сохранить на диске, чтобы в противном случае на его содержимое не могло наложиться содержимое другого блока. Другими словами, оно делает произведенные изменения постоянными, так что если используемый в последний раз буфер будет переписываться, то перед этим его содержимое должно быть сохранено на диске.
Любые изменения содержимого буфера блока должны быть сделаны постоянными, прежде чем он снова будет использован, с помощью слова UPDATE. Слово UPDATE работает очень быстро, поэтому нет никаких препятствий применять его почаще, не опасаясь перестараться. Но что будет с блоком, буфер которого не был использован вторично до выключения компьютера, а изменения были объявлены постоянными? Так как блочный буфер сохраняется на диске только в том случае, когда он используется повторно, то внесенные изменения будут утрачены. Можно заставить Форт-систему записать на диск все буферы, объявленные измененными, пользуясь словами FLUSH или SAVE-BUFFERS, которые либо являются синонимами, либо очень близки по назначению в зависимости от версии Форта. Поэтому после того, как закончилась программа или процедура, которая произвела изменения в блоке, нужно использовать одно из этих слов. Между словами FLUSH и SAVE-BUFFERS имеются некоторые тонкие различия, зависящие от версии языка. В Форт-83 оба слова производят запись содержимого всех обновленных блоков на диске и снимают признак внесения изменений, но если слово FLUSH отменяет приписывание буферов конкретным номерам блоков, то слово SAVE- BUFFERS может делать или не делать это в зависимости от реализации. Поэтому если используется слово SAVE-BUFFERS, то можно изменить содержимое буфера (не используя слова BLOCK для загрузки нового блока), снова объявить изменения постоянными словом UPDATE и сохранить буферы на диске словом FLUSH. Вы можете экспериментально установить, как работает ваша версия Форта со словом SAVE-BUFFERS. В Форт-79 обязательным является только слово SAVE-BUFFERS, причем стандарт не оговаривает, должно ли оно отменять назначение буферов конкретным блокам. В большинстве версий Форт-79 имеется также и слово FLUSH, которое практически является синонимом SAVE-BUFFERS. Незначительные различия этих слов в Форт-83 в большинстве случаев не имеют значения, и ими можно пренебрегать.
Можно ли после того, как вы произвели изменения в блоках и объявили их обновленными, передумать и отменить признак обновления? Вы можете это сделать, используя слово EMPTY-BUFFERS.
Это слово понимает, что запоминать буферы на диске не нужно, и отменяет назначение буферов блокам, а в некоторых версиях оно, кроме того, производит заполнение буферов нулями (байтами, имеющими значение 0) или пробелами с кодом ASCII 32.
Слово EMPTY-BUFFERS в Форт-83 не обязательное, поскольку стандартной программе не разрешается изменять содержимое буфера блока до тех пор, пока не будут спасены на диске предыдущие изменения. Словом EMPTY-BUFFERS следует пользоваться осмотрительно, поскольку можно потерять ценную информацию, не сохранив ее предварительно на диске. Лучше всего поучиться работать с блоками и буферами блоков, проделав несколько упражнений.
Упражнения
1. Определите слово.LINE, которое будет показывать на экране строку заданного блока, если в стеке на вершине указывается номер строки, а второй элемент содержит номер блока. 2. Определите слово INDEX под именем NEWINDEX, используя слово .LINE. 3. Определите слово LIST под именем NEWUST, используя слово .LINE. 4. Определите слово BLLINE, которое будет заполнять строку 64 пробелами (код ASCII 32), если задан номер блока и номер строки. Нужно ли делать UPDATE? 5. Слово TL (напечатать строку), которое является словом MMSFORTH, выводит на экран ряд строк с номерами, так же как и LIST, выбирая их из блока, номер которого содержится в SCR. Это значит, что 5 SCR ! 9 11 TL будет выводить строки с 9-й по 11-ю из блока 5. Определите слово TL с именем NEWTL. Определите также NEWLIST пол именем NEWLIST1, используя TL. 6. Определите слово CLEAR-BLOCK, которое, если перед ним в стеке задан номер блока, заполнит его пробелами (код ASCII 32). 7. Слово РР в некоторых версиях форта позволяет изменить содержимое строки, попросту печатая ее новое содержимое. Например, 32 5 РР Это новое содержимое этой строки
изменит содержимое пятой строки в блоке 32 на текст "Это новое содержимое этой строки". Определите слово РР. (Указание: Используйте 0 WORD.) 8. Определите слово COPY, назвав его NEWCOPY. 9. Очень полезно слово, позволяющее копировать ряд блоков.
Определите слово , которое действует по аналогии с CMOVE>. 11. Можете ли вы определить новое слово, которое производит копирование "вперед" () в зависимости от того, перекрываются или не перекрываются области исходных блоков и область назначения скопированных блоков?
Хранение данных в блоках
Мы уже несколько раз говорили о том, что поскольку Форт в большинстве своих версий не использует файлы операционной системы, то хранить данные, как в файлах, хотя и трудно, но все же возможно. Практически хранение данных в блоках обладает большей гибкостью, чем хранение в файлах, хотя следует признать, что нужно приложить некоторые усилия, чтобы вести учет блоков и связей между блоками. Более того, можно сконструировать файлы, основанные на концепции блоков, и директорию (справочник), которая используется так же, как директория операционной системы. Сначала рассмотрим хранение данных. Простейший способ запомнить данные в блоках состоит в том, чтобы поместить массивы в буферы диска, откуда их можно переместить для хранения на диск. Предположим, что у вас имеется массив, который вы создали таким образом: CREATE TESTARRAY 20 , 26 , 326 , 999 , 228 ' т.е. 10-байтовый массив с пятью 16-разрядными числами. Вы можете сохранить содержимое массива на диске с помощью TESTARRAY 50 BLOCK 10 CMOVE UPDATE а для извлечения данных из массива с диска можно использовать 50 BLOCK TESTARRAY 10 CMOVE
Конечно, из всего блока мы использовали всего 10 байтов. Вы можете сохранить на диске в каждом блоке до 102 таких 5- элементных массивов с номерами 0 - 101. Для этого определим сначала две переменные: VARIABLE STORBLK VARIABLE ARRLEN и инициализируем их: 50 STORBLK ! 10 ARRLEN !
Теперь определим слова для записи массивов на диск: : ARRAYPUT ( адр n - ) (Поместить_массив) STORBLK @ BLOCK (Помещает блок в буфер) SWAP ARRLEN @ * + (Рассчитывает размер места для хранения) ARRLEN @ CMOVE UPDATE ; (Перемещает массив в блок) Для примера, при вводе TESTARRAY 5 ARRAYPUT содержимое TESTARRAY будет запомнено в массиве номер 5 блока STORBLK.
На практике необходимо предусмотреть некоторую проверку на возможность появления ошибок, как, например, попытку записи данных после конца блока. Если рассматривать блок 50 как файл, то можно назвать входящие в него 10-байтовые массивы записями. Не забывайте, что для любой записи на диск требуется исполнить слово FLUSH. В действительности чаще всего не требуется запоминать результаты в массивах перед записью их в блок. Приведем пример, в котором будем рассматривать три блока как три файла записей данных наблюдений врачом пациентов. Пациентам присвоены номера 0 - 512, в записи о каждом пациенте должны быть указаны его вес, систолическое и диастолическое давление крови. Эти данные нужно ввести в три последовательно расположенных блока, начинающихся с адреса PATBLOCK, печатая вес, систолическое давление, диастолическое давление, номер пациента и слово PD (ввести_данные_пациента), т.е. последовательность 125 132 86 92 PD должна записать вес 125 фунтов в первый блок, систолическое давление 132 во второй блок и диастолическое давление в третий блок для пациента номер 92. Эти данные должны попадать в 184-й и 185-й байты каждого блока (2х92 и 2х92+1). Мы выбрали такое загадочное короткое имя слова PD только для того, чтобы облегчить ввод для оператора. Для начала нам нужна константа PATBLOCK: 50 CONSTANT PATBLOCK Вот как можно определить слово PD : : PD ( n1 n2 n3 -- ) 2 * >R (Сохраняет смещение в блоке) PATBLOCK 2+ BLOCK R@ + !UPDATE (Записывает систол. давление) PATBLOCK 1+ BLOCK R@ + !UPDATE (Записывает диастол. давление) PATBLOCK BLOCK R> + !UPDATE ; (Записывает вес)
Как видно из описания, слово PD будет одновременно стирать существующие записи и записывать новые. Не забудьте о том, что в конце записи необходимо сделать FLUSH.
Теперь мы можем определить слова для извлечения данных. В качестве примера приводим слово SD (от Show_Data - показать^данные): : SD ( n --) 2 * >R CR R@ 2/ ."Номер пациента" . CR R@ PATBLOCK BLOCK + @ ." Вес" . CR R@ PATBLOCK 1+ BLOCK + @ ."Систолическое давление".
CR R@ PATBLOCK 2+ BLOCK + @ ."Диастолическое давление". CR ;
Если ввести с клавиатуры 18 SD то мы увидим что-нибудь вроде Номер пациента 18 Вес 192 Систолическое давление 148 Диастолическое давление 76 ok
Теперь посмотрим, как можно получить средние показатели по всем пациентам (мы только начнем решение этой задачи, предоставляя вам закончить ее в упражнениях). Прежде всего нужно убедиться, что в блоках не содержится ничего, кроме данных о пациентах, т.е. все остальные байты должны содержать нули. Это сделать просто, вводя 50 BLOCK 1024 0 FILL UPDATE й то же самое повторить с блоками 51 и 52. Теперь можно ввести данные с помощью PD. Тогда сумму содержимого каждого блока можно найти, используя слово : SUMBLOCK ( n -- ) BLOCK 0 0 ROT 512 0 DO DUP I 2 * + @ SWAP >R 0 D+ R>
LOOP DROP ;
Теперь, если вы введете PATBLOCK SUMBLOCK D. то увидите на экране сумму всех весов. Обратите внимание, что мы должны использовать числа двойной длины и сложные манипуляции в стеке, так как суммарный вес может оказаться больше 65535 фунтов (считая, что средний вес каждого пациента больше 127 фунтов). Предлагаем вам продолжить решение в следующих упражнениях.
Упражнения
1. Переделайте слово ARRAYPUT таким образом, чтобы при попытке записать какую-либо часть массива после конца блока выдавалось сообщение об ошибке и происходил уход из программы. 2. Предположим, что у вас есть два 10-байтовых массива, 1ARRAY и 2ARRAY, которые вы хотите записать в последовательных блоках точно так же, как мы записывали TESTARRAY. Определите слово PUTARRAYS, которое, если ввести: 1ARRAY 2ARRAY 5 PUTARRAYS запомнит эти два массива последовательно друг за другом, начиная с байта номер 100 (5 х 20 - 100). Зачем может потребоваться запоминание массивов попарно? 3. Определите заново слова PD и SD так, чтобы ни ввод, ни вывод не могли бы произойти после конца блока. 4. Создайте переменную CNT и модифицируйте программу SUMBLOCK так, чтобы она инкрементировала значение CNT. В CNT должно накапливаться число ненулевых записей (т.
е. фактически число записей пациентов) после исполнения слова SUMBLOCK. He забудьте в начале SUMBLOCK обнулить переменную CNT. 5. Определите слово AVE (среднее), используя CNT и SUMBLOCK так, чтобы в стек помещались средние значения каждого типа данных. 6. Можно выделить для счета пациентов первую ячейку блока вместо отдельной переменной. Каждый раз, когда данные добавляются или удаляются, должно происходить изменение счетчика. Преимущество этого состоит в том, что счет производится при вводе данных, а не при подсчете статистики, поэтому допустимо вводить и нулевые данные (в предыдущем случае слово SUMBLOCK не должно было считать их). Пусть у вас есть блок с данными о весе пациентов, причем в первой ячейке блока содержится количество пациентов, а в остальных - значения весов. Определите слово AD (Add_Data -добавить_данные) таким образом, чтобы при каждом исполнении AD в конце массива добавлялся вес, а счетчик увеличивался бы на 1. 7. Определите слово DD (удалить_данные), которое, если указана позиция, удаляет данные из нее. уменьшает па единицу счетчик пациентов, а данные, находящиеся выше, перемещаются словом CMOVE вниз. При этом, конечно, необходимо изменить нумерацию пациентов. 8. Определите вместо SUMBLOCK (SB) и AVE (AV) новые слова, которые так же, как в упражнениях 4 и 5, должны подсчитывать суммарный и средний вес, используя для счета пациентов первую ячейку блока. 9. Модифицируйте слова, определенные в упражнениях 6 - 8, назвав их ADS, DDS, SBS и AVS. таким образом, чтобы кроме записи в первой ячейке числа пациентов они записывали бы во второй и третьей ячейках суммарный вес в виде числа двойной длины. 10. Часто оказывается полезным сравнение двух блоков на идентичность их содержимого. Если суммы всех байтов первого и второго блоков равны, то, вероятно, они содержат одинаковую информацию. Такую сумму называют контрольной. Определите слово CHKSUM, которое должно подсчитывать контрольную сумму. Чтобы производить сравнение, не обязательно использовать числа двойной длины, хотя возможность переполнения не учитывать нельзя.
Почему для данной задачи можно пользоваться 16-битовыми числами? Определите также слово ?BLK=, чтобы при вводе 50 55 ?BLK= в стек помещался флаг истина, если контрольные суммы равны, и ложь в противном случае.
Хранение символьных строк в блоках
До сих пор мы рассматривали хранение в блоках только чисел, представленных в двоичной форме. Если вы попробуете вывести на экран листинг блока, на котором были записаны числа, то увидите какую-либо чушь. Однако так же успешно, как и числа, в блоках можно хранить строки
Если после загрузки программы ввести слово FETCHPHONES, то в память будет введен последний (обновленный) телефонный справочник. Больше того, если слово FETCHPHONES было последним в загруженных блоках, то все массивы окажутся автоматически инициализированными. Теперь если в справочнике производятся изменения, то, чтобы запомнить его содержимое на диске, вам нужно только ввести слово SAVEPHONES. Но не будем торопиться. В гл. 9 мы создали два слова, с помощью которых можно было изменять записи в справочнике: STOREPHONE и ERASEPHONE. Можно сделать так, что справочник будет записываться автоматически, если внести простое изменение в определения этих слов, а именно включить в конце определения слово SAVEPHONES (перед ;). Тогда если справочник изменялся, то он будет автоматически обновляться на диске. Теперь можно выключать компьютер в любой момент без потери данных. Таким образом, можно сделать справочник составной частью вашего Форта. И все же данный пример фактически не так уж много разъяснил вам, как работать со строками в блоках. Единственное, что мы сделали, - это записали в массивы в блоки. Но делать со строками можно гораздо больше.
Для разбора (разделения) слов, содержащихся в блоках на диске, можно использовать слово WORD подобно тому, как мы использовали слово QUERY в гл. 9. Рассмотрим, как это делается, на примере. Выберите блок для эксперимента (пусть это будет блок 50) и введите редактором в первую строку блока фразу "The quick brown fox".
Затем определите следующее слово: : PARSE ( блок# -- ) (Разбор) BLK @ >R >IN @ >R (Запоминает прежние указатели) BLK ! 0 >IN ! ( Готово для раэбора нового блока) BEGIN 32 WORD (Начало раэбора) COUNT ?DUP WHILE TYPE SPACE (Печатает, пока находит) REPEAT DROP R> >IN ! R> BLK ! ; (Восстанавливает указатели) Теперь если ввести 50 PARSE то вы увидите на экране The (Выделенные quick при brown разборе fox слова)
Вместо того чтобы разделять слова во входном буфере с помощью слова PARSIT из гл. 9, вы можете разделять их непосредственно в блочном буфере словом PARSE, присваивая соответствующие значения переменным BLK и >IN. Описанный прием извлечения строк из блоков является довольно эффективным для работы как со строковыми, так и с числовыми данными, запоминаемыми в форме кодов ASCII. В этом вы убедитесь, сделав несколько упражнений.
Упражнения
1. Определите слово BLOCKWORD, которое должно выделять только одно слово из блока, запоминать его в счетной строке с адресом PAD и выдавать в стек адрес PAD, В стеке должен находиться номер блока, смещение начального байта в блоке и код разделителя. Таким образом, если вы введете 50 8 32 BLOCKWORD COUNT TYPE то в PAD будет занесена счетная строка, начинающаяся с восьмого байта и завершающаяся пробелом. После этого выделенное слово будет выведено на экран. 2. Определите переменную под именем POSITION для задания смещения байта, с которого должно начинаться разделение слов. После этого дайте новое определение слова BLOCKWORD под именем BWORD. которое должно производить разбор слов, начиная с позиции, записанной в переменную POSITION. Таким образом, если ввести 8 POSITION ! 50 32 BLOCKWORD COUNT TYPE то действие должно быть таким же, как в упражнении 1, Кроме того, новое определение BLOCKWORD должно изменять значение переменной POSITION так, чтобы оно указывало на следующую после разделителя позицию выделяемого слова. Таким образом, если несколько раз подряд исполнить 50 32 BLOCKWORD COUNT TYPE то на экран будут выведены все слова из блока. 3.
Опишите слово BLOCKNUMBER, которое должно брать число из блока в форме строки символов ASCII и помещать его в стек. Для решения этой задачи вам. возможно, придется освежить в памяти слова CONVERT и NUMBER из гл. 9. 4. Кроме извлечения строк из блоков может оказаться полезным также запоминание их в блоках. Определите слово TOBLOCK, которое действует как BLOCKWORD, но запоминает строку по указанному адресу в указанном блоке в позиции, определенной в переменной POSITION. Строка должна быть представлена в счетной форме, но запоминаться она должна как строка без байта-счетчика с разделителем в конце строки. Слово POSITION нужно изменить так. чтобы оно указывало на позицию, следующую сразу после разделителя. Таким образом, если в POSITION записан 0 и строка "fox" записана, начиная с PAD. то при вводе PAD 50 32 TOBLOCK строка "fox" будет записана в блок номер 50, начиная с нулевого байта; переменная PAD будет изменена на 4, чтобы указывать на байт, следующий сразу после пробела, которым заканчивается строка "fox". Когда подобное изменение POSITION может оказаться полезным?
Использование нескольких блоков в качестве файла
Если вы пользовались файлами, работая с другими языками программирования, то вам, безусловно, понятно, что в Форте мы рассматриваем отдельный блок как короткий файл. Когда мы обсуждали передачу данных между блоками и массивами чисел и символьных строк, используя оператор CMOVE, мы показали вам так называемый метод произвольного доступа при вводе-выводе (т.е. при вводе и выводе) данных, который применяется для доступа к файлам. Мы имели в виду, что данные, находящиеся в записях (в нашем случае в массивах) запоминаются и извлекаются в блоке с указанного пользователем места. В противоположность большинству языков программирования, которые работают с записями фиксированной длины, т.е. производят обмен данными с диском фиксированными порциями информации в байтах, мы могли сами выбирать любое число байтов для обмена с буферами и, следовательно, с диском.
Применяя терминологию других языков программирования, это можно назвать произвольным доступом с произвольной длиной записи. Считается, что осуществление произвольного доступа с переменной длиной записи очень прогрессивно, но представляет собой трудную задачу. В Форте же она решается очень просто.
Метод доступа к данным, который мы применяли в последней серии упражнений, называется применительно к файлам последовательным доступом. Можете думать, что такое название объясняется тем, что текст последовательно считывается из файла (или блока) с помощью указателя (в нашем случае POSITION), который указывает на положение следующей строки, которая должна быть извлечена.
Большинство языков программирования при осуществлении последовательного доступа допускают использование очень небольшого оговоренного заранее набора разделителей. В Форте разделителем может быть любой символ. Так в упражнениях мы использовали для этой цели пробел. Кроме того, в других языках программирования последовательный доступ должен всегда производиться с начала файла, а в блоке Форта вы можете начать с любого места. Таким образом Форт позволяет очень гибко использовать отдельный блок в качестве короткого файла. Однако очевидным недостатком Форта является то, что его "файлы" не могут быть длиннее 1024 байтов. Для многих применений этого объема недостаточно. Поэтому теперь мы обсудим, каким образом можно осуществить доступ к двоичным числам и строковым данным, находящимся в настоящих файлах, состоящих из нескольких блоков. Мы можем определить новые слова Форта, которые будут подобны тем, что мы ввели в упражнениях, за исключением одного: когда будут исчерпаны данные одного блока, они будут обращаться за продолжением к другому блоку. Но это не очень просто, поскольку непросто работать со строками или числами, размещенными даже на двух блоках. Кроме того, возникает необходимость в директории блоков, чтобы знать, в каком порядке они располагаются в файле. Тем не менее мы можем создать так называемый файл в памяти, в который одновременно будут помещаться все блоки и где будут выполняться все действия с ними, а запись на диск будет производиться по завершении всех операций с данными.
В дальнейшем изложении мы будем рассматривать именно та кие файлы в памяти. Сейчас же мы обсудим, как можно использовать их для осуществления процедуры последовательного доступа при обращении как с числами, так и со строковыми данными. (Большую часть работы вам предстоит выполнить в упражнениях.)
Предположим, что имеется метеорологическая база данных, в которой хранятся записи о температуре, относительной влажности воздуха, скорости ветра и атмосферном давлении, которые производятся через каждые полчаса. База данных была собрана в блоки при помощи системы сбора данных, реализованной на Форте. Одновременно необходимо работать с данными, накопленными за четыре недели. Каждая запись содержит результаты четырех измерений, т.е. каждая запись состоит из 8 байтов. За день производится 48 записей (по одной записи за полчаса), поэтому число записей за неделю будет 48х7=336 или 336х4=1344 записей в месяц. Следовательно, для хранения записей за месяц потребуется 1344х8=10.572 байта, которые можно разместить на II блоках диска. Допустим, что нужно выводить данные за день, неделю и за месяц. Можно работать с этой информацией с помощью программы, которая обращается к очень большому массиву, размещенному в памяти, который мы назвали файлом в памяти. Сначала мы создадим массив, в котором будут храниться общее число блоков и их номера, например: CREATE DATA 11 , 50 , 51 , 52 , 53 , 60 , 61 , 62 , 63 , 64 , 65 , 66 , (Заметьте, что блоки могут идти не обязательно подряд.) Теперь резервируем место для размещения файла CREATE METFILE DATA @ 1024 * ALLOT Определим слово, которое должно перемещать данные из блоков на диске в файл METFILE: : GETFILE ( -- ) (Взять_файл) DATA @ 1+ 1 DO (Проходит по блокам) DATA I 2 * + @ BLOCK (Получает адрес каждого блока) METFILE I 1- 1024 * + 1024 CMOVE (Перемещает данные в файл) LOOP ;
При исполнении слова GETFILE файл в памяти (метафайл) будет заполнен информацией из соответствующих блоков.
Можно дать более общее определение слова GETFILE, которое не только будет заполнять метафайл, но и создавать его: : GETFILE ( адр - ) CREATE HERE OVER @ 1024 * ALLOT SWAP DUP @ 1+ 1 DO 2DUP I 2* + @ BLOCK SWAP I 1- 1024 * + 1024 CMOVE LOOP DROP DROP ;
В таком виде слово GETPILE можно использовать для создания файла из любого набора блоков, номера которых хранятся в массиве DATA. Если массив номеров блоков устроен так же, как DATA, то слово GETFILE используется в такой форме: DATA GETFILE METFILE
Теперь нужно сконструировать слова для обеспечения доступа к данным в метафайле. Вспомним, что в каждой записи имеется по четыре элемента (в 8 байтах). Поэтому запись номер 1 начинается с 0-го байта файла, запись номер 2 - с 8-го, запись номер 3 - с 16-го и т.д. Если нам нужен третий элемент данных из записи номер 200, то мы можем извлечь его из метафайла с помощью такой последовательности действий: METFILE 200 1- 8 * + 3 1- 2* @
Можно будет упростить эту операцию, если определить еще несколько слов. Причем можно сделать эти определения достаточно общими, чтобы пользоваться ими впоследствии для любого метафайла с любой длиной записей. Сначала определим переменную для хранения длины записи: VARIABLE RECLEN 8 RECLEN !
Затем определим переменную, указывающую на позицию начала записи, которая будет использоваться в такой форме: 200 METFILE RECLOC чтобы поместить в стек адрес начала 200-й записи в файле. Вот это определение: : REGLOC ( n адр -- адр ) SWAP 1- RECLEN @ * + ;
Теперь опишем слово DATALOC для вычисления адреса любого элемента заданной записи, которое, например, в случае 3 200 METFILE DATALOC будет возвращать в стек адрес начала 3-го элемента данных в записи номер 200. Вот определение этого слова: : DATALOC ( n1 n2 адр1 -- адр2) RECLOC SWAP 1- 2* + ;
С помощью этих общих слов мы можем определить слова для извлечения конкретных данных из нашего файла, т.е. для извлечения, например, данных о температуре, влажности воздуха и т.д. Мы выпишем их в последовательности, в которой они записаны в метафайле: : TEMP 1 SWAP METFILE DATALOC ; (Температура) : HUMID 2 SWAP METFILE DATALOC ; (Влажность) : WIND 3 SWAP METFILE DATALOC ; (Скор. ветра) : BAROM 4 SWAP METFILE DATALOC ; (Давление)
Тогда если вы введете 200 WIND @ то получите скорость ветра в 200-й записи. (Как вы узнаете в гл. 11, эти четыре слова можно определить еще лучше, если применить специальное слово-определитель в конструкции CREATE...DOES>.) Наконец, было бы неплохо определить слово, позволяющее по значениям номера дня относительно начала текущей серии данных, по значениям часа и минуты возвращать в стек номер записи, относящейся к этой дате.
Это может быть сделано с помощью слова : TIME->REC 30 / SWAP 2 * + SWAP -18 * + ;
Напомним, что данные собираются через каждые полчаса. Тогда, если ввести 7 10 30 TIME->REC TEMP @ то вы получите значение температуры в седьмой день от начала записей в 10 ч 30 мин. Заметьте, что слова GETFILE. RECLEN, RECLOC и DATALOC вместе со словом PUTFILE, которые вы определите в порядке упражнения, представляют собой простые, но весьма полезные и достаточно общие расширения языка для работы с файлами. Единственное ограничение на длину файла накладывает доступный объем памяти. У вас может возникнуть желание собрать эти слова вместе и оформить для применения в более общем виде (и еще некоторые слова, которые мы определим в следующем разделе). Кроме того, если вам часто приходится работать с файлами данных, вы захотите усовершенствовать обращение с файлами, чтобы добавить более мощные возможности. Мы предлагаем вам еще поработать с метеорологической базой данных в следующих упражнениях,
Упражнения
1. Определите слово DATA-AVE, которое должно выдавать среднее значение данных, если задать номер первой и последней записи и время этой записи. Так, например, если ввести 800 TIME->REC 8 23 30 TIME >REC 3 DATA-AVE то вы получите среднее значение температуры за восьмой день. считая со дня создания файла. Здесь потребуются числа двойной длины. 2. Определите слово TEMP-AVE, которое действовало бы как 1 DATA-AVE, т.е. чтобы не нужно было указывать положение элемента данных в записи. Используйте в определении ТЕМР@. 3. Предположим, что при измерении температуры была допущена ошибка в 1 градус из-за неверной калибровки термометра, в результате чего нужно добавить к каждому значению температуры в METFILE 1 градус. Определите слово CORRECT-TEMP, которое должно произвести указанную коррекцию. 4. Определите слово PUTFILE для копирования файла с изменениями в исходные блоки, из которых он был загружен в память, т.е. если ввести METFILE DATA PUTFILE то файл с измененными данными будет возвращаться на диск. 5.
Определите слово SAVEREC, которое будет заменять запись, помещая в нее число из стека. Предусмотрите уход из программы, если количество чисел в стеке меньше, чем значение переменной RECLEN.
Файлы строковых данных с последовательным доступом
В примере, который был очень подробно разобран в предыдущем разделе, мы имели дело с файлом числовых (двоичных) данных с последовательным доступом и с фиксированной длиной записи. Если вы вспомните, что еще раньше мы пользовались словом WORD для выделения строк из блока, то вам нетрудно будет представить, что мы можем работать также и с файлами, содержащими последовательности строк различной длины, отделенных друг от друга разделителями.
Мы рассмотрим здесь в общем виде строковые файлы в памяти, при этом вы определите в упражнениях некоторые полезные слова, а затем построим файл адресов и обсудим, как его можно использовать. Строковый файл (или файл строковых данных) можно описать в виде массива точно так же, как файл метеорологических данных. Предположим, что у вас имеется 10 блоков, содержащих имена и адреса людей. Можно создать массив, который описывает файл, следующим образом: CREATE ADDRESS 10 , 50 , 51 , 52 , 53 , 54 , 60 , 61 , 62 , 63 , 64 ,
Пользуясь этим массивом, можно инициализировать файл в памяти, применяя операцию GETFILE точно так же, как мы создавали в предыдущем разделе файл METFILE. т.е. чтобы сделать это, нужно написать ADDRESS GETFILE ADDFILE
Однако вместо того, чтобы описывать длину каждой записи в переменной, мы используем переменную для хранения байта-разделителя. Выберем сначала в качестве символа разделителя пробел, потому что это нам потребуется в упражнениях: VARIABLE DELIMITER 32 DELIMITER !
Теперь предположим, что нам нужно произвести разделение текста первых 64 символов в файле на строки. Мы можем определить для этого слово PARSELINE по аналогии с PARSIT из материала гл. 9: : PARSELINE ( адр -- ) (Разделить_строку) BLK @ >R >IN @ >R (Запоминает указатели при вводе из блока) TIB 80 0 FILL (Заполняет буфер ввода нулями) TIB 64 CMOVE (Вводит в буфер 64 символа) 0 BLK ! 0 >IN ! CR (Указывает на начало буфера ввода) BEGIN DELIMITER @ WORD (Начинает разделение) COUNT ?DUP WHILE TYPE SPACE (Выводит, если есть что) REPEAT (Повторяет) TIB 80 BLANK (Заполняет буфер ввода пробелами) R> >IN ! R> BLK @ ; (Восстанавливает указатели блока)
Если ввести ADDFILE PARSELINE то первые 64 символа файла будут подвергнуты разбору, т.е. разделены на слова-строки, между которыми в качестве разделителя стоит пробел, и выведены на экран. Главные различия между словами PARSIT и PARSELINE состоят в том, что в данном случае перед словом указывается не номер блока, а адрес и, кроме того, вместо конкретного кода 32 мы применили более общее DELIMITER и для выполнения разделения использовалось пространство в буфере ввода.
Теперь вам предстоит определить некоторые слова, которые являются полными аналогами слов из упражнений предыдущей серии (мы даже сохраним их имена), но они должны работать не с блоками, а с файлами.
Упражнения
1. Определите слово GETWOBD, которое будет выделять только одно слово-строку из файла и запоминать его в форме счетной строки в PAD, возвращая а стек адрес PAD. В стеке должны находиться адрес файла и смещение первого байта в файле. Тогда после ввода ADDFILE 8 GETWORD COUNT TYPE в PAD будет перемещена из файла и запомнена счетная строка символов ASCII* начинающаяся с байта номер 8 и заканчивающаяся пробелом. После этого выделенное слово-строка должно быть выведено на экран. Для распознавания конца слова-строки нужно использовать переменную DELIMITER. 2. Определите переменную POSITION, в которой должна быть записана позиция байта в файле, начиная с которой нужно произвести разбор текста и выделение слов-строк. Теперь определите слово FILEWOKD, которое должно выполнять разбор текста в файле, начиная с места, на которое указывает переменная POSITION. Например, последовательность операторов 8 POSITION ! ADDFILE FILEWORD COUNT TYPE должна приводить к тому же результату, как в примере из упражнения 1. Кроме того, слово FILEWORD должно изменять значение в POSITION так, чтобы оно указывало на байт, следующий после байта-разделителя, стоящего в конце выделенного слова-строки. Таким образом, если повторно будет исполняться ADDFILE FILEWORD COUNT TYPE то мы увидим последовательные слова строки. 3. Определите слово FILENUMBEK, используя FILEWORD, которое должно брать из файла число, представленное последовательностью кодов ASCII, и помещать его как число в стек.
Для этого вам, может быть, потребуется вспомнить, как работают слова CONVERT и NUMBER из гл. 9. 4. Кроме извлечения строк из файла весьма полезно иметь возможность записывать их в файлы. Определите слово TOFILE, которое работает аналогично слову FILEWORD, но в отличие от него запоминает строку, начиная с указанного адреса в указанный файл с позиции, указанной в переменной POSITION. Строка должна быть в исходном виде в счетной форме, а запоминаться как несчетная строка, в конце которой находится байт-разделитель. Содержимое POSITION должно быть изменено так, чтобы оно указывало на позицию, следующую после байта-разделителя. Так, если в POSITION записан 0 и текст "fox" помещен, начиная с адреса PAD, то при исполнении PAD ADDFILE TOFILE текст "fox" будет запомнен в файле, начиная с байта номер 0; содержимое переменной POSITION будет изменено на 4, т.е. будет указывать на байт, следующий после пробела, замыкающего текст "fox".
Файл адресов
В гл. 9 мы построили телефонный справочник, а в данной главе мы его модифицировали, для того чтобы запоминать в блоках диска. Несмотря на свою гибкость и быстродействие, этот справочник не лишен недостатка, состоящего в том, что для каждой записи требуется 30 байтов для имени абонента и 30 байтов для адреса. Это приводит к напрасному расходованию памяти и не позволяет записывать более длинные имена. Более гибким оказывается другой подход: будем отделять вводимые данные друг от друга с помощью байта-разделителя, в этом случае они будут занимать ровно столько места, сколько действительно требуется. Именно так мы построим наш файл адресов.
Будем считать, что каждая запись в файле должна состоять из имени абонента, его адреса и номера телефона. Каждая запись будет разделяться на четыре поля: поле имени, поле названия улицы, поле названия города, почтового индекса и страны, поле номера телефона. Для отделения полей используем знак-разделитель "'"(код ASCII 94). Мы ставим перед собой задачу определить слова для вывода имени и адреса, поиска по имени, добавления новых имен и адресов в конец файла.
После этого мы предлагаем вам дополнить список слов еще некоторыми функциями в упражнениях. выглядеть, если запросить листинг первого блока: 1 John Slmpson^2223 Second St.^Louisville, PA 234^56-225-294-678^ 2 Jean Baptist de Lamarc^23a Rue des Arbres^Parls, France^02-955 3 6^John TrenfCat's House Cottage^burton-Underhill PD56-5BC Dors 4 set England^01-35-5624^James Mathieson^238 Parkway Drive^Philade
(Помните, что файл находится в памяти, а для того, чтобы его поместить туда, нужно использовать слово GETFILE.) Для обеспечения доступа к файлу адресов, выделения полей с помощью разделителей мы воспользуемся идеями предыдущего раздела. Итак, в переменную DELIMITER нужно записать код 94, который будет помечать конец каждого поля. Теперь пусть POSITION указывает на начало имени. Проще всего вывести имя и адрес; для этого можно воспользоваться словом FILEWORD из упражнения 2 предыдущего раздела. Слово : PRINT-RECORD ( -- ) ( Напечатать_запись) CR 4 0 DO DUP FILEWORD COUNT TYPE CR LOOP DROP ; решает эту задачу, если использовать его в следующем контексте: ADDFILE PRINT-RECORD
Но как найти начало поля имени? Для этого мы должны иметь возможность искать имя, помещая указатель POSITION в начало поля имени. Поэтому нам потребуется строковая переменная, в которую мы поместим строку для поиска: 80 $VARIABLE SEARCH$
Нам нужно также иметь слово, которое возвращало бы указатель (POSITION) снова в начало поля, предполагая, что содержимое поля хранится в PAD; это слово мы будем использовать, чтобы после того, как поиск завершится успешно, переместить указатель в начало.поля имени: : POINTBACK PAD С@ 1+ NEGATE POSITION +! :
Слово POINTBACK вычитает длину поля плюс разделитель из значения, хранимого в POSITION. Нужно также распознавать, когда мы дойдем до конца файла. Мы будем делать это, используя признак конца файла в последнем поле имени, в качестве которого выберем строку "&&&", определив ее как константу: $CONSTANT EOF &&&"
Далее мы воспользуемся некоторыми словами для работы со строками из версии MMSFORTH, которые вы можете заменить собственными аналогами, если посмотрите их определения в гл. 9.) Приведенное ниже слово будет возвращать в стек флаг истина, если в строке, адрес которой находится в стеке, будет встречен признак конца файла: : ?EOF ( $адр -- флаг ) EOF INSTR 0= 0= ; (Чтобы понять, как используется слово INSTR, посмотрите пример с телефонным справочником в гл. 9.) Теперь мы можем определить слово SEARCH-NAME ( -- ) (Искать_имя) BEGIN ADDFILE FILEWQRD DUP DUP ?EOF IF POINTBACK CR ." Имя не найдено" ABORT THEN SEARCH$ INSTR DUP IF POINTBACK CR SWAP COUNT TYPE ." найдено" CR ABORT THEN UNTIL ;
Вот как будет использоваться слово SEARCH-NAME. Предположим, что в файле находится имя "John Jones", а после него "Gary Jones". Вы хотите найти адрес и телефон Gary, но вам нужно вспомнить, как его фамилия.
Введите $" Jones" SEARCH$ $! и 0 POSITION ! а после этого SEARCH-NAME
Вы увидите, что на экран будет выведено "John Jones найдено", но вы искали не это. Введите теперь PAD C@ POSITION +! чтобы пропустить John (если желаете, определите для этой цели специальное слово NEXTFIELD), а затем снова введите SEARCH-NAME, тогда вы увидите сообщение "Gary Jones найдено". Текстовая строка "Gary Jones" была запомнена в PAD в виде счетной строки, переменная POSITION указывала на начало поля имени. Теперь, если вы введете PRINT-RECORD, на экране вы увидите имя абонента и его адрес. Обратите внимание на то, что, поскольку поиск производится во всех полях, будет найдена также строка "101 Jones St."
Теперь нам нужно сконструировать слово для добавления строки к концу файла, т.е. перед началом признака конца файла): : FINDEOF ( адр - ) BEGIN DUP FILEWORD ?EOF UNTIL DROP POINTBACK ;
Форма использования этого слова такая: ADDFILE FINDEOF
Кроме того, нам нужно слово, которое должно добавлять новое поле в файле, если задан адрес счетной строки, которая должна помещаться в это поле: : PUTFIELD ( $адр - ) $" ^" $+ TOFILE ; (Слово TOFILE было определено в упражнении 4 предыдущего раздела.) Вот, например, слово для добавления нового поля в запись адреса: : ADD-ADDR ( -- ) ADDFILE FINDEOF ." Имя " IN$ PUTFIELD CR ." Адрес " IN$ PUTFIELD CR ." Город/Штат " IN$ PUTFIELD CR ." Телефон " IN$ PUTFIELD CR $" &&&" PUTFIELD ; Обратите внимание на то, что новое поле наложилось на старый признак конца файла.
Несмотря на то, что приведенный пример имеет узкоспециализированное применение, он демонстрирует большое число приемов работы с текстовыми файлами.
Вы можете развивать его дальше в последующих упражнениях.
Упражнения
1.Определите слово NEXTFIELD, которое должно помещать указатель POSITION на начало следующего поля. Это значит, что если POSITION указывает на поле имени, то NEXTFIELD должно помещать его на поле названия улицы. 2. Определите слово NEXTREC, которое, если указатель показывает на поле имени, будет помещать его на начало следующего поля имени. 3. Определите слово FINDPHONE, которое, если в PAD задано имя абонента, будет находить его в файле и печатать номер телефона. 4. Определите слово DELREC, предназначенное для удаления записи из файла и смещения оставшихся записей так, чтобы перекрыть удаленную запись. Переменная POSITION должна указывать на начало поля имени удаляемой записи.
Выводы
В этой главе мы обсудили множество вопросов, начав с простейших операций вывода листинга и загрузки программы, а закончив разбором текстовых файлов с переменной длиной записей. Мы очень подробно рассмотрели реализацию загрузки блоков, использование блоков в качестве файлов и файлов, организованных в памяти. Наш обзор затронул также примеры использования директорий файлов для организации их ввода в память блок за блоком. Причиной столь подробного изложения является то, что в большинстве версий Форта не предусмотрены организация блоков в файлы и хранение данных на диске. Даже если вам все это и не потребуется, мы надеемся, что вы имели возможность убедиться в возможностях Форта расширяться для решения задач, которые поначалу могут показаться не присущими языку. Если вам нужны средства для работы с базами данных, мы надеемся, что эта глава поможет вам решать задачи такого рода.