Язык программирования Форт


Символьные строки


В предыдущих главах мы употребляли термин "строка", предполагая, что его смысл совершенно очевиден. Строка (символьная строки) - это последовательность алфавитно-цифровых символов, которая запоминается в байтах памяти в виде последовательности кодов ASCII. Таким образом, строка "STRING" будет запоминаться в памяти в виде последовательности десятичных значений байтов: 83 84 82 73 78 71 STRING

Любой язык программирования в какой-то степени может обращаться со строками. Ввод и вывод (даже чисел) производится с использованием строк, листинг и исходный код программы существуют в виде строк, так же как и сообщения оператору. Даже мнемоника языка ассемблера представляется символьными строками. Когда в гл. 5 вы пользовались форматным выводом (), вы также преобразовывали числа в строки.

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

Различные версии языка Форт, поставляемые фирмами, предоставляют пользователям широкие возможности обработки строк, некоторые из них мы покажем вам на примере версии MMSFORTH.
Вы увидите, что можно определить почти любое слово, которое вам может потребоваться для работы со строками.
Для описания строки требуются две компоненты: содержимое (текст) строки и ее длина. В английском языке длина строки задается с помощью разделителей, например пробелов, кавычек, точек и запятых. Такой же способ может быть принят и для компьютера. Например, заключительная кавычка в выражении ." This is a string " (Это строка) не является словом языка Форт. Это - разделитель. Слово ." знает, что строка заканчивается там, где в тексте встретится знак кавычки. Разделителем является также закрывающая скобка в комментариях. В качестве разделителя в Форте можно использовать любое значение байта, однако чаще всего применяется пробел, которым при вводе должны быть разделены слова (или числа). Другой способ задания строки состоит в указании ее длины числом. Давайте обсудим этот способ более подробно.
Строки счетной длины, их ввод с клавиатуры
Строка, длина которой указана числом, называется в Форте счетной (реже говорят нумерованной строкой). Число символов, или счет, обычно указывается в байте-счетчике, который помещается перед текстом строки (тем самым длина строки ограничивается 256 символами). Строка предыдущего примера будет запомнена в памяти в виде счетной строки так: 06 83 84 82 73 78 71 STRING Но как же ввести строку в компьютер? Кроме слова KEY, с которым мы познакомились в гл. 5, для ввода строк предусмотрено еще несколько слов: EXPECT, WORD и QUERY.
Рассмотрим сначала слово EXPECT. (Коротко о нем говорилось в гл. 5.) Давайте создадим слово $IN (ввести_строку), которое будет печатать знак вопроса и ждать вашего ввода строки с клавиатуры. Когда вы нажмете клавишу ввода , строка будет запоминаться в виде счетной строки, а адрес ее начала (адрес байта-счетчика) будет помещен в стек. Сначала установим, имеется ли в словаре вашей версии Форта слово BLANK (пробел). Это слово не является обязательным в стандартах, но включено почти во все версии Форта.


Если же это слово отсутствует, то его можно легко определить: : BLANK ( адр n ---) 32 FILL ;


Напомним, что FILL заполняет n байтов, начиная с адреса адр, кодом ASCII, находящимся на вершине стека, в данном случае кодом пробела 32. Таким образом, слово BLANK заполняет n байтов пробелами, начиная с указанного адреса. Нам потребуется также слово 2DUP; если у вас нет его в системе, определите его так: : 2DUP ( n1 n2 - n1 n2 n1 n2) OVER OVER ;
По мере продвижения нашего определения слова $IN мы будем нуждаться для проверки в словах COUNT и TYPE (что мы уже видели в гл. 5). Напомним, что слово TYPE предполагает в стеке адрес строки (не счетной) и число символов в строке и после этого выводит указанное число символов из строки. Слово COUNT просматривает содержимое указанного адреса и возвращает в стек находящийся там байт и, кроме того, адрес первого байта строки, подготавливая стек к использованию слова TYPE. Слово COUNT можно определить следующим образом: : COUNT ( $адр - $адр+1 n) DUP 1+ SWAP C@ ; Поэтому если $адр - адрес байта-счетчика строки, то при исполнении $адр COUNT TYPE на экран будет выведена строка. Обратите внимание, что мы обозначили адрес байта-счетчика как $адр (иногда применяется обозначение $). Очевидно, что это такой же адрес, как любой другой, но, чтобы подчеркнуть, что по этому адресу хранится строка, мы используем символ $.
Будем определять $IN в несколько этапов, забывая с помощью FORGET предыдущие определения. Сначала определим : $IN PAD 1+ 255 BLANK ; и испытаем это слово, вводя $IN PAD 1+ 255 TYPE в результате вы увидите на экране 255 пробелов - это все, что содержит строка на данный момент. Обратите внимание, что мы собираемся создать строку со смещением на один байт относительно адреса буфера PAD, чтобы зарезервировать место для байта-счетчика. Теперь забудем старое определение: FORGET $IN и сделаем новое: : $IN PAD 1+ 255 2DUP BLANK ." ? " EXPECT ; Испытаем новую версию, введя слово $IN. После этого на экране мы увидим знак вопроса, после которого не будет слова "ok".


Форт ожидает, что вы будете вводить строку, и, когда вы нажмете клавишу ввода , эта строка будет записана в память. Попробуйте ввести $IN ? THIS IS A TEST (ЭТО ТЕСТ) (Знак вопроса "?" был напечатан компьютером: вы ввели "THIS IS A TEST" ("Это тест".) Теперь введем PAD 1+ 14 TYPE Тогда вы увидите на экране THIS IS A TEST Вы приказали компьютеру, чтобы он вывел 14 символов, начиная с адреса PAD+1, на экран, и убедились, что это были символы, введенные словом EXPECT. Слово EXPECT ищет адрес и указанное в стеке число символов. Мы указали адрес и PAD плюс 1, так как именно этот адрес и число 255 были указаны в определении слова $IN и остались в стеке благодаря слову 2DUP. Слово EXPECT ожидает, когда будут введены символы строки. Если ввод прекращается с помощью клавиши , то словом EXPECT будут запомнены только те символы, которые были введены до нажатия клавиши . Если при вводе будет превышено указанное число символов (в данном случае 255), то слово EXPECT будет действовать так же, как если бы вы нажали клавишу ввода . Но наше слово еще не закончено, так как не было запомнено число введенных символов. Снова забудем предыдущее определение FORGET $IN и дадим, наконец, полное определение: : $IN ( - $адр) PAD 1+ 255 2DUP BLANK ." ? " 2DUP EXPECT -TRAILING PAD C! DROP PAD; Здесь ключевое слово "TRAILING. Этому слову нужен в стеке начальный адрес и число символов, так же как словам BLANK и EXPECT; в данном случае указанный адрес и число были запомнены в стеке с помощью слова 2DUP, непосредственно предшествующего слову EXPECT. Слово -TRAILING заменяет затем байт длины строки числом символов до первого пробела "в хвосте" строки символов. В действительности слово -TRAILING просматривает байты, отступая от указанного адреса на указанное в стеке число байтов вперед, и вычитает единицу, если обнаруживает пробел. Это повторяется до тех пор, пока не встретится первый символ, не являющийся пробелом. Поэтому число, которое -TRAILING возвращает в стек, является длиной (счетом) строки.


Фактически - TRAILING отнимает пробелы, следующие после строки. Длина строки затем запоминается словом $IN по адресу ячейки PAD (с помощью PAD С!). На этом заканчивается формирование счетной строки. Остается с помощью DROP удалить адрес PAD плюс 1, который кладется в стек словом -TRAILING, и заменить его на PAD, т.е. адрес байта-счетчика.
Заслуживают упоминания некоторые детали. Стандарт Форт-79 допускает, чтобы слово EXPECT записывало до двух нулей (байтов со значением 0) в конце строки символов. Если ваша версия Форта делает это (как, например, версия MMSFORTH), то необходимо в слове $IN сразу же после -TRAILING вставить операцию 2 -. В Форт-83 нулевые байты не разрешаются. Вам нужно проверить, как устроен ваш Форт. Другая деталь: число символов, собираемых словом EXPECT в Форт-83 и других версиях, запоминается в переменной SPAN. Это позволяет упростить определение слова $IN на Форт-83: : $IN ( - $адр) PAD 1+ 255 ." ? " EXPECT SPAN @ PAD С! PAD ;
Другими словами, на Форт-83 здесь слово -TRAILING не нужно для подсчета длины строки.
Разумеется, слова EXPECT и -TRAILING имеют много других применений. Мы можем увидеть некоторые их применения из нескольких упражнений.
Упражнения
1. Вспомните слово EMIT (см. гл. 5), а потом дайте определение слова NEWTYPE, используя цикл DO-LOOP. 2. Вспомните слово KEY из гл. 5 и дайте определение слова EXPECT, используя цикл BEGIN...WHILE...BEPEAT. Сделайте так, чтобы ваша версия слова EXPECT запоминала число символов строки в переменной SPAN. Понимаете ли вы, почему здесь нельзя использовать счетный цикл? Не забудьте предусмотреть возможность появления символа стирания влево (код 8). 3. Предположим, что слово EXPECT завершает несчетную строку по крайней мере одним нулевым байтом. Определите слово, которое будет выводить строку на экран, если известен адрес первого символа. Используйте цикл BEGIN...WHILE...REPEAT. 4. Зарезервируйте 258 байтов в массиве с именем $SPACE, используя слова CREATE и ALLOT. Определите слово GET$.


которое работало бы так же, как $IN, но запоминало бы строку в переменной $SPACE. 5. Определите слово ADD$, которое будет ожидать ввода строки, а затем присоединять ее к концу строки, находящейся в массиве $SPACE. He забудьте скорректировать байт-счетчик с помощью слова - TRAILING после того, как строка будет помещена в переменную. Очевидно, что суммарная длина обеих строк не должна превышать 255 байтов, поэтому, если строка получится слишком длинной, ее необходимо урезать. 6. Дайте новое определение слова ADD$ под именем ADD$1 такое,чтобы вторая строка игнорировалась, если суммарная длина обеих строк превышает 255 байтов, т.е. строка в $SPACE не должна изменяться. 7. Просмотрите материал о форматном выводе в гл. 5 и обратите внимание на то, что слово #> оставляет в стеке адрес несчетной строки и байт-счетчик. Теперь определите слово, которое должно брать из стека число двойной длины, представляющее число центов, преобразовывать его в счетную строку вида $123.45 и запоминать строку в массиве $SPACE. Используйте слово CMOVE. 8. Определите слово LEFT$, которое предполагает наличие в стеке адреса строки, поверх которого находится байт-счетчик. Заданное в стеке число символов должно быть затем перемещено из указанного адреса по адресу PAD + 1, а новая длина строки должна быть записана в PAD. Слово LEFT$ должно оставлять в стеке адрес новой строки, т.е. PAD. Таким образом, если в $SPACE находится строка "The quick brown fox", тогда после операций $SPACE 9 LEFT$ в стеке будет оставлен адрес счетной строки "The quick".
Ввод с помощью слова WORD
WORD - это слово, преобразующее в счетную строку все, что во входном потоке следует после него, и возвращающее в стек адрес начала этой строки (байт-счетчик). Слову WORD нужен в стеке код ASCII байта-разделителя (например, 32 - пробел или 34 - кавычка "), оно запоминает все, что встречается, вплоть до этого разделителя. Попробуйте ввести следующее определение : SHOW-SILLY 34 (") WORD CR COUNT TYPE ; а затем введите предложение SHOW-SILLY This is a silly example"


( Это глупый пример) Тогда вы увидите на экране текст "This is a silly example", повторенный строкой ниже введенного. Строка "This is a silly example" была запомнена словом WORD, а затем распечатана с помощью фрагмента COUNT TYPE. Слову WORD известно, что нужно прекратить запоминание строки, когда оно встретит в стеке разделитель-кавычку (так как 34 - это код ASCII для кавычки). Если вы введете SHOW-SILLY silly example" xyz
то будет выдано сообщение об ошибке, потому что после того, как будет исполнено слово SHOW-SILLY, Форт будет пытаться интерпретировать xyz как слово языка Форт. Слово WORD прекращает ввод также при нажатии клавиши . Поэтому SHOW-SILLY This is a test
произведет такое же действие. Слово Форт также игнорирует ограничитель в начале строки, т.е. если ввести SHOW-SILLY """"This is a silly example"
то вы увидите то же самое, что и в первом примере. А вот пример уже далеко не глупый: : .( 41 WORD COUNT TYPE ; IMMEDIATE
Он представляет собой определение слова Форт-83.( (точка-скобка). Вам должно быть понятно в нем все, за исключением слова IMMEDIATE (немедленно), которое просто помечает, что последнее определяемое слово должно быть исполнено сразу, как только оно встретится, независимо от того, находится ли оно внутри другого определения через двоеточие или нет. Таким образом, слово.( будет выводить на экран все, что следует после него, вплоть до ) (скобки, код ASCII которой равен 41) даже внутри определения-двоеточия.
В большинстве версий строка, которая запоминается словом WORD, помещается в верхней части словаря. Ее адрес возвращается в стек словом HERE (здесь), и в соответствии с общепринятым в Форте жаргоном мы будем обращаться к адресу вершины словаря с помощью слова HERE. Новые слова добавляются в словарь с ячейки HERE, а слова , (запятая), ALLOT (зарезервировать) и др. резервируют место, начиная с адреса HERE.
Кроме того, слово WORD используется Форт-системой для разбора или разделения слов во входном потоке, который помещает выделенные слова в HERE.


Поэтому если после ввода строки словом SHOW-SILLY было введено что-либо еще, то вновь введенная строка будет искажена. Практически это означает, что нужно сразу использовать то, что введено и запомнено словом WORD, или защитить до дальнейшего ввода. Попробуйте ввести следующую последовательность операторов: 32 WORD TESTING COUNT TYPE
Почти наверняка в ответ вы увидите на экране текст "TYPE", а не "TESTING". Причина здесь в том, что, хотя строка "TESTING" была записана словом WORD в ячейку HERE, слово WORD использовалось Форт-системой для интерпретации операторов COUNT TYPE и так как последним, что встретилось во входном потоке, была строка "TYPE", она и была выведена на экран. На самом деле, если вы введете HERE COUNT TYPE то также увидите на экране текст "TYPE". Строка "TYPE" была преобразована в счетную строку и напечатана потому, что она была запомнена с адреса HERE, когда интерпретатор пользовался словом WORD. Если вы этого не увидели на экране, значит, ваша версия Форта - одна из немногих, которая не запоминает строку, введенную словом WORD, в ячейке HERE. Если после ввода строки словом WORD происходит ее искажение последующим вводом, то что же хорошего в этом слове? Можно ответить так: строка должна быть либо защищена, либо перемещена в памяти, прежде чем будет сделан новый ввод. Например, строку можно было бы переместить по адресу PAD, как в следующем слове: : SAVE-STRING 34 WORD DUP C@ 1+ PAD SWAP CMOVE ; Теперь введите SAVE-STRING This is a test of SAVE-STRING" PAD COUNT TYPE
Тогда вы увидите на экране текст "This is a test of SAVE-STRING" (это тест слова SAVE-STRING). Операция 1 + нужна здесь потому, что нужно переместить не только символы строки, но также и байт-счетчик. Строка, которая теперь находится в PAD, может быть перемещена куда угодно, и с ней можно обращаться так же, как со строкой, запомненной словом EXPECT в предыдущих примерах. Но еще можно очень просто защитить строку, которая вводится словом WORD, особенно если в вашей версии Форта, как в большинстве других, строка помещается на верху словаря.


Можно создать строковую константу, т.е. константу, которая вместо числа содержит строку. Вот как можно это сделать: ; $CONSTANT CREATE 34 WORD C@ 1+ ALLOT ; Действие $CONSTANT аналогично действию SAVE-STRING, за исключением того, что строка не перемещается в памяти, защищается в словаре с помощью слова ALLOT - так же, как в гл. 6 делается защита массива. Теперь можно использовать слово $CONSTANT следующим образом: $CONSTANT HOWDYDO Comment allez vous? "
(Как дела? /франц./) Если вы введете HOWDYDO COUNT TYPE на экране появится текст "Cominent allez vous?" ("Как дела?" (франц.)) Не написать ли нам разговорник, пользуясь языком Форт?
Слова SAVE-STRING, $CONSTANT, HOWDYDO и подобные им слова можно, очевидно, использовать не только для ввода с клавиатуры, но также и с диска. Заметьте, однако, что нельзя просто ввести CREATE HOWOYDO 34 WORD Comment allez vous? " C@ 1+ ALLOT так как С@ 1+ ALLOT наложится на фразу, которую вы собирались сохранить. Почти во всех случаях слово WORD должно использоваться в определениях через двоеточие.
Если в вашей версии Форт строка, вводимая словом WORD, запоминается не в ячейке HERE, то слово $CONSTANT можно определить следующим образом: : $CONSTANT CREATE 34 WORD COUNT DUP 1+ ALLOT HERE SWAP CMOVE ;
Т. е. строка должна быть перемещена оператором CMOVE в HERE, чтобы защитить ее с помощью ALLOT.
В сочетании со словом WORD часто используется слово QUERY. В Форт83 это слово не обязательное (оно входит в Форт-79), тем не менее имеется почти во всех версиях. Слово QUERY работает почти так же, как EXPECT (оно и определяется через EXPECT), но в отличие от последнего оно запоминает строку не с того адреса, который вы указываете, или используя указанную длину строки, а в текстовом входном буфере (TIB) длиной 80 байтов - специально отведенной области памяти. Этот буфер используется для хранения входного потока информации с клавиатуры, и слово QUERY используется Форт-системой как раз для приема входного потока.


В Форт-83 и большинстве диалектов адрес начала текстового входного буфера возвращается словом TIB (от Text Inpul Buffer). Попробуйте ввести TIB 11 TYPE и вы увидите, что на экран выводится "TIB 11 TYPE". Вы просто вывели 11 символов из текстового входного буфера, т.е. то, что вы в него ввели. Теперь попробуйте ввести определение : SILLY-QUERY TIB 80 BLANK 0 >IN ! ." ? " QUERY CR TIB 80 TYPE ; Как и $IN, слово SILLY-QUERY делает паузу, чтобы вы что-либо ввели, например фразу "The quick brown", а затем печатает то, что вы ввели. Но, кроме того, оно еще печатает сообщение об ошибке. Это происходит потому, что после того, как SILLY-QUERY выведет то, что было введено, Форт пытается интерпретировать все находящееся во входном буфере как Форт-слова и, естественно, он не может распознать текст "The". Вы можете избежать этого, если сразу же после TYPE введете в определение TIB 80 BLANK, чтобы очистить буфер. Возможно, единственная загадочная часть определения - это О >IN !. Слово >IN - это переменная пользователя, в которой содержится число, показывающее, на какую глубину использован буфер ввода IN в 0 для того, чтобы после QUERY либо интерпретатор, либо слово WORD выполняли свои действия с начала буфера. Слово QUERY само по себе мало что дает пользователю (хотя, конечно, оно очень важно для осуществления ввода в Форт), вместо него обычно используется слово EXPECT. Слово QUERY почти всегда используется в сочетании со словом WORD. Слово QUERY заполняет текстовый входной буфер, после чего слово WORD производит разбор текста, т.е. оно разделяет входной поток на меньшие строки с учетом указанного разделителя. Следовательно, WORD можно использовать для разделения входного потока на более мелкие части. Попробуйте ввести следующее определение: : SILLY-ТОО TIB 80 BLANK 0 >IN ! ." ? " QUERY 32 WORD CR COUNT TYPE TIB 80 BLANK ;
Введите SILLY-ТОО и в ответ на вопрос "?" введите текст "The quick brown fox".


Тогда в следующей строке вы увидите, что выводится текст "The". Код 32 указывает слову WORD, что разделителем считается пробел, поэтому слово WORD преобразовало текст "The" в счетную строку, а COUNT TYPE вывело эту строку. Конечно, этот пример глупый также, поскольку он не делает ничего, кроме вывода части того, что вы ввели, на экран. Как и при других применениях слова WORD, входной поток должен быть запомнен либо защищен раньше, чем определение будет исполнено. Вот два более полезных слова: : PARSIT TIB 80 BLANK 0 >IN ! ." ? " QUERY 4 0 DO 44 WORD DUP C@ 1+ PAD 10 I * + SWAP CMOVE LOOP TIB 80 BLANK ; : SHOWIT 4 0 DO PAD I 10 * + COUNT TYPE LOOP ;
Слово PARSIT разделяет четыре строки, каждая из которых может содержать до 10 символов, которые отделены друг от друга запятой (код ASCII 44). Вам должно быть понятно, как оно работает, по аналогии с другими словами, которые мы определили в этой главе. (Слово PARSIT работает, только если ввести его с клавиатуры, поскольку слово QUERY работает только с клавиатурой, но не работает с диском.) В гл. 10 мы увидим, что разбор используется для выделения строковых данных из блоков.
Упражнения
1. Определите слово $VARIABLE, которое, если ему предшествует число, будет резервировать указанное им количество байтов в памяти для запоминания именованной строки, т. е. 256 $VARIABLE $SPACE резервирует 256 байтов для счетной строки в переменной $SPACE. 2. Определите слово $!, которое, если в стеке имеются два адреса $адр1 и $адр2, будет перемещать счетную строку из адреса $адр1 в $адр2. Тогда если строка запомнена в PAD, то : PAD $SPACE $! переместит строку в переменную, определенную в упражнении 1. 3. Определите слово $GET, которое будет перемещать вводимую после него строку в ячейку с адресом, указанным в стеке. Так, например, PAD $GET The quick brown fox..." будет помещать счетную строку "The quick brown fox..." в память, начиная с PAD. 4. Определите слово $", которое должно помещать следующую после него строку в память, начиная с PAD, и возвращать в стек адрес, где сохраняется строка.


Таким образом , $" This is a string," будет выводить на экран "This is a string" и помещать в стек адрес PAD. 5. Как бы вы запомнили строку, вводимую словом S" в переменную $SPACE, пользуясь словами, определенными в этих упражнениях? 6. 10 строк запомнены в строковых константах с именами $$1, $$2,..., $S10. Определите слово $CHOICE, которое, если указывается номер, будет печатать соответствующую строку. Например, 4 $CHOISE будет печатать строку, хранящуюся в $4. Может быть, вам потребуется вспомнить векторное исполнение из гл. 6. 7. Определите четыре строковые переменные с именами 1STRING, 2STRING и т.д. Теперь определите слово, аналогичное PARSIT, которое будет помещать четыре строки с разделителем # в эти переменные, а не в разные места после PAD, как это делало слово PARSIT. (.Указание: используйте вектор и $!.) 8. Как вы думаете, что происходит со значением переменной >IN, когда исполняется слово WORD? Модифицируйте слово, которое вы определили в упражнении 7, чтобы оно печатало значения переменной >IN каждый раз при исполнении слова WORD. 9. Определите слово, эквивалентное (, под именем ((. Оно должно работать как внутри, так и вне определения-двоеточия.
Расширенный набор строковых операций в MMSFORTH
К настоящему времени мы уяснили, что, хотя Форт сам по себе не обладает широкими возможностями работы со строками, в нем есть несколько слов, необходимых для определения практически любого слова для работы со строками, какое вам только потребуется.
Как вы уже видели, многие слова, нужные всем, можно определить и включить в любую версию Форта. К сожалению, только в некоторых версиях предусмотрены такие слова, расширяющие возможности языка. Одной из версий является MMSFORTH. Она имитирует большинство операторов для обработки строк, имеющихся у Бейсика, представляя собой превосходный пример, каким должно быть хорошее программное обеспечение для работы со строками. Если даже вы не располагаете версией MMSFORTH, вам следует ознакомиться с его словами; знать о них очень полезно, и они могут дать вам некоторые идеи для создания слов, которые могут потребоваться.


Мы дадим описание каждого слова MMSFORTH для работы со строками и некоторые примеры их использования. В некоторых случаях определения слов будут достаточно очевидны, и вы можете попробовать сами дать их определения. Но мы не будем приводить определения этих слов, потому что для некоторых слов требуются приемы, которые вы еще не изучали (в частности, программирование на ассемблере), а также вследствие того, что они защищены авторским правом на MMSFORTH. В конце концов, авторы MMSFORTH имеют право получать вознаграждение за свою работу. Сводка слов MMSFORTH для работы со строками приведена в табл.9.1. В некоторых случаях их действие настолько очевидно, что мы не будем описывать их подробнее. В других случаях мы дадим примеры применения некоторых слов.
Таблица 9.1. Слова MMSFORTH для работы с символьными строками (Все строки принимаются счетными.)
$CONSTANT ( --) ( строковая константа) Используется в форме $CONSTANT NAME "This will be a constant" (Это должна быть константа) Во время исполнения слова NAME (имя) в стек кладется адрес строки. Действие слова такое же, как слова $CONSTANT, ранее описанного в этой главе.
$VARIABLE ( n --) (строковая переменная) Резервирует N байтов для запоминания строки в массиве. Используется в формате 256 $VARIABLE NAME Действие слова NAME такое же, как в случае константы $CONSTANT.
$. ( $адр --) Выводит на экран строку, начинающуюся с адреса $адр. Эквивалентно по своему действию группе операторов COUNT TYPE,
$! ( $адр1 $адр2 --) Перемещает строку, начинающуюся с адреса $адр1, в область памяти с начальным адресом $адр2. Начиная с $адр2, должно быть зарезервировано место, достаточное для помещения строки, иначе результат будет искажен.
$" ( -- $адр) Принимает следующую за собой символьную строку (используя в качестве разделителя кавычку '" '), возвращает в стек адрес, начиная с которого запоминается строка. Используется в форме
$" This is a string" $. Будет выведено "This is a string" (Это строка).


То же самое, что и слово $", которое вы определили в упражнениях. Строка запоминается с адреса PAD.
IN$ ( -- $адр) Печатает знак вопроса "?" и приостанавливается, ожидая ввод строки с кавычкой в качестве ограничителя ('" '). Пример использования этого слова при вводе строки с клавиатуры
IN$ ? This is a string"
$. Будет выведено "This is a string", Применение идентично применений слова $IN, определенного ранее в данной главе.
LEFT$ ($адр1 n - $адр2) Помещает n левых символов строки, находящейся по адресу $адр1, в счетную строку, начинающуюся с адреса PAD и возвращает в стек адрес PAD. Если в строке Окажется меньше п символов, то будет помещена вся строка. Если по адресу FOXY запомнена строка "The quick brown fox", то последовательность операторов FOXY 9 LEFT$ $. напечатает "The quick",
RIGHT$ ( $адр1 n - $адр2) Помещает п правых символов строки, находящейся по адресу $адр1, в счетную строку, начинающуюся с адреса PAD, и возвращает в стек адрес PAD. Если в строке меньше чем n символов, то будет помещена вся строка. Если по адресу FOXY запомнена строка "The quick brown fox", то последовательность операторов FOXY 10 RIGHT$ $. напечатает текст "brown fox".
MID$ ( $адр1 n1 n2 -- $адр) Помещает n2 символов из середины строки, находящейся по адресу $адр1, начиная с символа номер n1, в счетную строку, начинающуюся с адреса PAD. Возвращает в стек адрес PAD, Если между позицией n1 и концом строки окажется меньше n2 символов, то будут помещены все символы, начиная с номера n1 до конца строки. Если по адресу FOXY запомнена строка 'The Quick brown fox", то последовательность операторов FOXY 5 11 MID$ $. напечатает текст "quick brown".
$XCHG ( $адр1 $адр2 --) Переставляет содержимое с адреса $адр1 с содержимым с адреса $адр2, используя для промежуточного хранения строки, начинающейся с адреса $адр2, адрес PAD. Начиная с адреса $адр1 и адреса $адр2, должно быть зарезервировано место, достаточное для размещения большей из двух строк, иначе могут произойти непредвиденные последствия.


$+ ( $адр1 $адр2 -- $адр3) Содержимое адреса $адр2 добавляется (соединяется) к концу содержимого адреса $адр1, результирующая строка помещается, начиная с адреса PAD, при этом в стек помещается адрес PAD. Если с адреса FOXYSTART начинается строка "The quick", а с адреса FOXYEND находится строка "brown fox", то последовательность операторов FOXYSTART FOXYEND $+ $. напечатает текст "The quickbrown fox". Обратите внимание, что между словами quick и brown нет пробела .
$COMPARE ( $адр1 $адр2 -- флаг) Две строки сравниваются посимвольно с учетом алфавитного порядка символов, в стек возвращается флаг. Если строки равны, то возвращается флаг 0; если строка с адресом $адр1 находится по алфавиту дальше, чем строка с адресом $адр2, то возвращается +1; если строка с адресом $адр2 находится по алфавиту дальше, чем строка с адресом $адр1, то возвращается -1. Если по адресу ALPHA находится Строка "abc". а по адресу BETA "abd", то последовательность операторов ALPHA BETA $COMPARE . выведет на экран -1, в то время как BETA ALPHA $COMPARE . выдаст 1. Символы, не входящие в алфавит, сравнивается по значению кодов ASCII.
INSTR ( $адр1 $адр2 -- n) Содержимое строки по адресу $адр1 ищется в тексте строки, начинающейся с адреса $адр2. Если оно не находится, в стек возвращается 0. Если находится в стек возвращается номер позиции первого символа строки, хранящейся по адресу $адр2. Если по адресу FOXY содержится строка "The quick Orown fox", a no адресу FOXYPART строка "brown", то последовательность операторов FOXY FOXYPART INSTR . выведет на экран 11.
ASC ( $адр - n) Возвращает в стек значение кода ASCII первого символа строки, хранящейся по адресу $адр1. Если по адресу FOXY помещена строка "The quick brown fox", то после FOXY ASC . будет выведено 84, т.е. код ASCII буквы Т.
CHR$ ( n -- $адр) Преобразует число п в односимвольную счетную строку, которая содержит символ, код ASCII которого равен n. 84 CHR$ $.


напечатает букву Т. Слово CHR$ можно определить следующим образом: : CHR$ I PAD С! PAD 1+ С! PAD ;
INKEY$ ( -- $адр) Опрашивает клавиатуру, и если была нажата клавиша, то по адресу PAD запоминается односимвольная счетная строка. Если ни одна клавиша не нажата, то в PAD помещается 0. Возвращает в стек адрес PAD, Обычно слово INKEY$ используется в цикле, пример его применения вы найдете в тексте ниже.
STRING$ ( n1 $адр1 -- $адр2) Создает счетную строку в PAD, состоящую из n1 символов, таких же, как первый символ строки с адресом $адр1, В стек помещается адрес PAD, 10 $" S" STRINGS $. выведет строку "SSSSSSSSSS". Приведенное ниже слово очистит содержимое счетной строки, заместив его пробелами: : ERASE-$ ( $addr --) DUP C@ 32 CHR$ STRING$ SWAP $! ;
STR$ ( n -- $адр) Преобразует число n в строку, запоминаемую в PAD. и возвращает в стек адрес PAD. 12345 STR$ $. выводит "12345",
LEN ( $адр - n) возвращает в стек длину строки, запоминаемой по адресу $адр. Определение этого слова очень простое : : LEN С@ ; VAL ( $адр - -n или dn) Если начальные символы строки по адресу $адр распознаются как число в текущей системе счисления (BASE), то в стек помещается это число. Если в числе обнаруживается десятичная точка, то в стек помещается число двойной длины, Если первый символ не является цифрой, то в стек возвращается 0. Таким образом, при шестнадцатеричной системе счисления $" 123AXYZ" VAL . выводит 123А $" 123.ABC" VAL D. выводит 123ABC и $" xyz" VAL . выводит 0
$ARRAY ( n1 n2 --) Слово-определитель для создания строковых массивов. если ввести 29 19 $ARRAY 20STRINGS то последовательность операторов 2 20STRINGS возвратит в стек адрес, где помещается строка длиной до 30 символов (n1) и, кроме того. будет запомнен байт-счетчик. Таким образом. $" The quick brown fox" 2 20STRINGS $! запомнит строку и 2 20STR1NGS $. в свою очередь, выведет на экран "The quick brown fox". Максимальное значение n1 равно 254.


Элементы нумеруются, начиная с нуля. поэтому значение n2 = 19 в вышеприведенном примере резервировало место для 20 строк.
2$ARRAY ( n1 n2 n3 --) Слово-определитель для создания строковых матриц из п2+1 рядов и п3+1 столбцов, причем каждая строка содержит до п1 символов плюс байт-счетчик. Если было введено 19 9 4 2$ARRAY 50STRINGS то $" The quick brown fox" 5 2 50STRINGS $! запомнит текст строки. Тогда 5 2 50STRINGS 8 О 50STRINGS $! создаст ее копию в элементе матрицы в 6-м ряду. 0-м столбце, так что 8 О 50STRINGS $. выведет строку "The quick brown fox".
$-TB ( $адр -- $адр) Удаляет пробелы в конце счетной строки, при этом уменьшает содержимое счетчика числа символов строки на число обнаруженных пробелов. Таким образом, последовательность операторов $" The quick " DUP $. $-ТВ DUP $. $. напечатает The quick The quickThe quick Завершающие пробелы были удалены перед вторым словом $. .
Приведенных объяснений должно быть достаточно, чтобы понять действие большинства описанных слов для работы со строками. Однако некоторые примеры помогут подчеркнуть их полезность. Возможно, наиболее трудным среди этих слов является INKEY$, поэтому лучше всего посмотреть его действие на примере. Действие слова INKEY$ аналогично действию ?KEY, которое является в его определении самым главным. Мы определим слово SHOWKEYS, которое просто выводит на экран все, что было введено с клавиатуры (в том числе символы возврата каретки влево не один шаг и перевода каретки), до тех пор, пока на будет нажата клавиша точки "."; мы определим это слово, используя как ?KEY, так и INKEY$.
: SHOWKEYS ( --) BEGIN (Начало условного цикла) ?KEY DUP (Помещает в стек код ASCII символа или 0, если клавиша не нажата) ?DUP IF EMIT THEN (Печатает символ, если не 0) 46 = UNTIL ; (Повторяет исполнение, начиная с ?KEY. если не была нажата клавиша ".", код ASCII равен 46) А вот определение через INKEY$: : SHOWKEYS ( --) BEGIN (Начало условного цикла) INKEY$ DUP $. (Считывает код клавиши в счетную строку печатает ее.


Длина строки равна 0, если не была нажата клавиша, поэтому ничего не печатается) 1+ С@ (Извлекает в стек символ из строки) 46 = UNTIL ; (Повторяет исполнение с INKEY$, если не была нажата ".", код ASCII равен 46)
Второе определение более медленное, чем первое, но поскольку любое из них работает быстрее, чем кто-либо сможет сделать ввод с клавиатуры, то скорость не является определяющим фактором, Второе определение короче и понятнее. Но главное преимущество второго определения состоит в том, что введенный символ запоминается как счетная строка, которая готова для работы с другими словами, предназначенными для обработки строковых данных.
Приведем несколько более полезный пример. Мы определим слово BUILDTEST, которое обеспечит возможность непосредственно записывать счетную строку в строковую переменную TEST$ (аналогичное слово вы определили в упражнении, пользуясь словами EXPECT и -TRAILING). В качестве разделителя мы используем символ #. Сначала определим строковую переменную 255 $VARIABLE TEST$ и установим ее длину равной нулю: 0 TEST$ С! Теперь мы можем дать определение слова BUILDTEST: : BUILDTEST ( -- ) BEGIN (Начало условного цикла) INKEY$ DUP DUP $. (Принимает строку- символ с клавиатуры, копирует ее адрес и распечатывает ее) $" #" $COMPARE (Сравнивает строку-символ с "#" и возвращает 0, если есть "#") WHILE (Если "#" не обнаружен, продолжает цикл, иначе переходит на исполнение слов после REPEAT и заканчивается) TEST$ SWAP $+ (Добавляет строку-Символ к TEST$, результат - в PAD) TEST$ $! (Запоминает слитую строку в ТЕSТ$) REPEAT ; (Переходит к повторению с INKEY$)
Хотя в данном случае можно описать слово BUILDTEST "обычными" словами Форта, такое определение было бы труднее и для написания, и для понимания. И кроме того, заметьте, что в отличие от тех слов, которые вы определили со словами EXPECT и -TRAILING, если вы используете слово BUILDTEST без предварительного "обнуления" переменной TEST$, то оно будет присоединять новую строку к тому, что уже было в TEST$.


Чтобы так же действовало определение с "обычными" словами, нужно было бы использовать слово ?KEY, при этом определение самого слова ?KEY было бы очень замысловатым. Конечно, вы можете и прямо ввести строку с клавиатуры в TEST$, если наберете $" I am filling the String" TESTI $! (Я заполняю строку) В упражнениях мы предложим вам описать более общее и более полезное слово BUILD$.
Телефонный справочник
Чтобы практически оценить полезность приведенных слов для работы со строками, мы составим небольшую программу, которая будет создавать телефонный справочник. Для этого нужно определить три главных слова: STOREPHONE (записать_телефон), которое будет запрашивать имя абонента и номер телефона и запоминать эти данные в массиве, GETPHONE (выдать телефон), которое будет запрашивать имя абонента и выдавать номер его телефона, если абонент найден, и ERASEPHONE (стереть_телефон) для удаления абонента и номера телефона, чтобы освободить место в справочнике.
Мы используем два массива: 10-байтовый массив AVAILABLE (доступность), в котором каждый байт будет содержать 0 или 1 в зависимости от того, свободна (0) или занята (1) позиция в справочнике; второй массив PHONES (телефоны), представляющий собой строковую матрицу размерности 10х2 (10 строчек х 2 столбца), в каждой строчке содержится имя абонента (нулевой столбец) и телефон (первый столбец). Вначале создадим и инициализируем массивы: CREATE AVAILABLE 10 ALLOT AVAILABLE 10 0 FILL создается массив AVAILABLE и инициализируется заполнением его нулями (все элементы справочника, или позиции, свободны). При вводе 29 9 1 2$ARRAY PHONES создается массив PHONES, в котором на каждую запись отводится 30 символов для имени абонента и номера телефона. Если требуется справочник на большее число абонентов, то нужно резервировать большее число байтов в массиве AVAILABLE и большее число строчек в массиве PHONES.
Теперь можно приступить к определению трех слов, нужных нам для работы со справочником:
: STOREPHONE ( -- ) (Запомнить_телефон) ." Имя абонента?" IN$ (Вводит имя абонента) PAD 31 + $! PAD 31 + (Запоминает его выше PAD; выдает адрес) SPACE ." Номер телефона?" IN$ (Вводит номер телефона PAD) 10 0 DO (Начинает цикл просмотра справочника) AVAILABLE I + С@ (Определяет, свободна [0] или занята [1] запись) 0= IF I 1 PHONES $! (Запоминает номер в элементе I,1) I 0 PHONES $! (Запоминает имя в элементе I,0.) 1 I AVAILA8LE + C! (Запоминает 1, чтобы отметить, что I-я запись занята) LEAVE (Запись произведена, поэтому вы THEN ходит из цикла и из слова) I 9 = IF (Если дошли до конца записей) CR ." Справочник заполнен" (Не обнаружено свободного места в справочнике) THEN LOOP ;


После того как с клавиатуры введено имя абонента, его необходимо убрать из PAD в PAD плюс 31, чтобы освободить место для ввода номера в PAD. Затем с помощью счетного цикла DO-LOOP просматриваются все позиции справочника для поиска незанятой позиции. Если позиция (i) найдена, то имя абонента и его телефон запоминаются в элементах 0 и 1 i-й строчки массива PHONES и исполнение заканчивается. Если свободная позиция не найдена, т.е. ни в одной позиции массива AVAILABLE не встретился 0, то выдается сообщение "Справочник заполнен". Можно было бы определить слово STOREPHONE и без применения слов, обращающихся со строковыми данными, но это не доставило бы вам никакого удовольствия.
Теперь нам нужно описать слово GETPHONE для вывода обнаруженных в справочнике абонентов и телефонов: : GETPHONE ( --) (Вывести_телефон) ." Имя абонента?" IN$ CR (Вводит строку для поиска) 10 0 DO (Цикл просмотра справочника) DUP I 0 PHONES SWAP (Берет имя в 1-й строке и переставляет его с искомым именем) INSTR IF (Возвращает 0, если не обнаружено в этой позиции, или число, номер позиции, если имя найдено) I 0 PHONES $. 2 SPACES (Печатает полностью найденное имя) I 1 PHONES $. CR (Печатает номер телефона абонента) THEN LOOP DROP ; (Снимает имя и заканчивает программу)
Обратите внимание, что мы используем оператор INSTR не для определения положения символа в строке, а только для того, чтобы отметить, найдена ли искомая строка или нет. Кроме того, заметьте, что поиск возможен по любой части имени абонента. Например, если в справочнике имеются абоненты "Godfrey James" и "Eugene Godfrey", то при поиске по фрагменту "frey" будут выведены на экран оба абонента. И наконец, нам нужно еще слово для уничтожения ненужных записей и освобождения места для новых: : ERASEPHONE ( --) (Стереть_телефон) ." Имя абонента?" IN$ (Вводит строку для поиска) 10 0 DO DUP I 0 PHONES SWAP (Поиск, как в GETPHONE,) INSTR IF (Если найдено -...) 0 I AVAILABLE + С! (Разрешает ввод в эту позицию) 0 I 0 PHONES С! (Стирает имя, делая длину записи равной 0) 0 I 1 PHONES С! (Стирает номер, делая длину записи равной 0) THEN LOOP DROP ; (Снимает имя и заканчивает программу)


Действия слов GETPHONE и ERASEPHONE очень похожи, но в последнем случае найденное имя и телефон не выводятся, а стираются. Это производится с помощью записи 0 в длину строки. Аналогично для освобождения найденной позиции в справочнике в массиве AVAILABLE также записывается 0. Чтобы использовать эту программу, нужно набрать на клавиатуре имя требуемой функции. Но если вы хотите, чтобы программой мог пользоваться любой непосвященный человек, необходимо предусмотреть в ней встроенный контроль ошибок, например, чтобы предотвратить ввод слишком длинного имени или номера телефона. Еще лучше, если программой можно будет пользоваться, вводя всего одно слово - имя программы, а затем одно из трех возможных слов можно будет исполнить по выбору из меню. Поскольку такие улучшения программы дают возможность показать пример использования слов для операций со строками, посмотрим, как они могут быть сделаны.
Вначале мы определим еще одно слово QUITPHONE ( закончить_телефоны), которое нужно для того, чтобы выйти из программы: : QUITPHONE ." ok" CR QUIT : Для исполнения одной из функций программы: трех определенных ранее и QUITPHONE - мы применим вызов по вектору. Итак, нам нужен вектор CREATE CHOICE FIND STOREPHONE , FIND GETPHONE , FIND ERASEPHONE , FIND QUITPHONE ,
(На Форт-83 вместо FIND нужно использовать '.) Ну а как мы будем узнавать, какое слово следует исполнить? Для этого создадим меню, которое будет подсказывать, что нужно ввести "S", чтобы записать номер телефона, "G" - чтобы его выдать, и т.д. Для приема ответа оператора с клавиатуры мы используем слово INKEY$, и тогда, если принят ответ "G", это будет означать, что мы хотим вывести номер телефона. Как же теперь можно исполнить соответствующий элемент вектора? Это можно сделать путем определения позиции буквы ответа (например, "G") в строке, по которой мы будем определять положение функции GETPHONE в векторе. Поэтому нам потребуется строка CHOICES (выбор): $CONSTANT CHOICE$ SGEQ" которая показывает относительное положение слов в векторе.


Еще нам потребуется слово для представления меню: : MENU CR ." НАЖМИТЕ S, чтобы ЗАПИСАТЬ телефон" CR ." НАЖМИТЕ G, чтобы ВЫВЕСТИ телефон" CR ." НАЖМИТЕ Е, чтобы СТЕРЕТЬ телефон" CR ." НАЖМИТЕ Q, чтобы выйти из программы" CR ;
Обратите внимание, как легко составляется меню на Форте. Вот теперь мы можем дать описание главной программы: : FINDPHONE ( --) ( Найти_телефон) MENU BEGIN (Печатает меню и входит в цикл) CHOICES INKEY$ (Вводит адрес строки выбора вариантов; опрашивает клавишу) INSTR (Находит позицию символа выбора в строке CHOICE$ или кладет в стек 0, если не обнаруживает) ?DUP IF (Если найден один из 4 символов...) 1- 2* CHOISE + @ (Находит слово в векторе и исполняет) EXECUTE MENU THEN (Печатает меню и продолжается) 0 UNTIL ; ( Бесконечный цикл; прекращается по QUITPHONE)
Снова заметим, что можно определить PINDPHONE и не пользуясь словами для строковых операций, - через ?KEY, хотя и значительно более длинным и запутанным исходным текстом, но с помощью специально сконструированных слов $CONSTANT, INKEY$ и INSTR это сделано значительно проще. Вы можете вспомнить, что в MMSFORTH есть слово ACASE (гл.7), которое так же успешно можно использовать в данном случае, но в других версиях оно не имеет эквивалентов, между тем слова $CONSTANT, INKEY$ и INSTR относительно несложно определить. Если вы не располагаете версией MMSFORTH, мы советуем вам написать набор необходимых слов для работы со строковыми данными.
Еще один способ написать слово, эквивалентное FINDPHONE, состоит в использовании конструкции IF...THEN, но исходный текст программы получился бы ужасно большим. Поэтому векторное исполнение значительно предпочтительнее. Написание такой же программы на Бейсике и других языках программирования, конечно, возможно, но представляет собой значительно более трудную задачу.
У этой программы есть одно существенное ограничение: вы потеряете справочник, как только выключите машину. В гл.10 мы разовьем эту программу с той целью, чтобы справочник сохранялся на диске.


Упражнения
1. Определите слова NEWLEFT$ и NEWRIGHT$, используя MID$. 2. Определите слово $CHAR. которое должно брать из стека код ASCII и создавать счетную строку из одного символа в PAD, оставляя адрес PAD в стеке. 3. Определите слово 1STCHAR, которое должно возвращать код ASCII первого символа счетной строки, адрес которой находится в стеке. 4. Определите слово $SWAP, которое должно переставлять содержимое двух счетных строк, адреса которых находятся в стеке. Определите это слово на обычном Форте и на MMSFORTH, пользуясь его словами для работы со строками. 5. Найдите слова MMSFORTH, эквивалентные тем, которые были вами определены в упражнениях 1-4. 6. Определите слово с именем BUILD$, которое работает, как BUILDTEST, но, в отличие от него, требует перед собой указания имени (или $адр) строковой переменной, т.е. TESTS BUILDS должно делать то же самое, что делает одно слово BUILDTEST. (Совет: Запомните адрес в стеке возвратов при первом вводе слова.) 7. Опишите слово, которое работает так же, как BUILD$, используя IN$ и $! (Это очень просто.) 8. Модифицируйте программу для создания телефонного справочника, расширив его до 30 записей. Измените программу так, чтобы число записей в справочнике можно было просто изменять, изменяя значение одной константы. Почему это лучше? 9. Определите слово SHOWPHONES. которое должно выводить весь телефонный справочник. 10. Определите слово ERASEALL для стирания всех записей в телефонном справочнике. Оно должно выдавать подсказку: "Вы уверены, что хотите стереть все? (Y/N)", прежде чем перейти к исполнению программы. Для распознавания ответа (Y - да или N - нет) примените функцию INSTR.
Преобразование символьных строк в числа
В гл.5 мы обещали вам, что опишем в данном разделе, как можно вводить числа с клавиатуры по ходу исполнения программы (функция, которая должна быть стандартной, но тем не менее в стандартном Форте отсутствует). Весь фокус в том, чтобы вводить числа в виде символьных строк, а затем преобразовывать их в стеке в числа.


Вот как это делается. Наиболее важное слово для этой операции - CONVERT. Слово CONVERT предполагает наличие двойного (32-битного) слова во втором и третьем элементах стека и адреса на вершине стека. Адрес должен быть на один байт ниже текста символьной строки, представляющей цифры числа (т.е. адрес должен указывать на байт-счетчик строки, хотя слово CONVERT игнорирует байт-счетчик). Слово CONVERT затем преобразует все цифровые символы с начала строки в число (указание знака недопустимо) и добавляет результат к числу двойной длины - "зародышу", находящемуся в стеке. После этого оно оставляет в стеке адрес первого байта, который не является цифрой, и преобразованное число (в виде числа двойной длины) ниже этого адреса. Обычно в качестве "зародыша" для слова CONVERT используется 0 и, кроме того, возвращаемый словом адрес первого нецифрового байта убирается. Слово CONVERT выполняет преобразование в соответствии с текущим значением основания системы счисления, т.е. оно будет делать преобразование также и алфавитных символов, если основание больше 10.
Мы рассмотрим несколько примеров постепенно увеличивающейся сложности, но сначала вы должны ввести слова $IN и $CONSTANT, как мы их определили выше в данной главе. Теперь определим $CONSTANT $NUM 1234X" и введем последовательность операторов 0 0 $NUM CONVERT ... Если мы представим для определенности, что $NUM хранится по адресу 1000, то вы должны увидеть на экране: 1005 0 1234
Адрес байта-счетчика (1000) игнорируется, и преобразование начинается с адреса 1001; после того, как оно завершится, адрес символа "X" (1005) кладется на вершину стека, а число двойной длины 1234, старшая часть которого равно 0, находится ниже этого адреса. Теперь введите определение : SILLY 0 0 32 WORD CONVERT DROP DROP . ; и после этого SILLY 1596 Тогда вы увидите на экране число 1596, выведенное из стека. Если вы попробуете SILLY -1256 на выходе будет получен 0, потому что слово CONVERT не воспринимает знак "-" и не делает преобразование.


Слово SILLY (глупо) и в самом деле глупое, поскольку оно вводит числа прямо в стек. Вот несколько более полезное слово: : #IN 0 0 $IN CONVERT DROP DROP ;
Слово #IN (число_ввод), которое определено в MMSFORTH, приостанавливает исполнение и, как и $IN, позволяет вам ввести символьную строку, а затем оставляет в стеке "величину" строки в виде числа одинарной длины. Вы должны самостоятельно определить слово D#IN, которое позволит вводить числа двойной длины.
Но есть одно затруднение в приведенном определении слова #IN; если вы делаете ошибку при вводе и введете первый символ, например "X", то слово #IN возвратит 0, чего вы, конечно, не хотели- В связи с этим нужно в слове #IN предусмотреть проверку на наличие ошибки. Вот как это можно сделать. Забудем слово #IN: FORGET #IN и введем новое определение: : #IN ( -- n) (число_ввод) BEGIN (Начинает условный цикл) $IN DUP 0 0 ROT (Вводит строку, делает ее копий и подготавливает ее для CONVERT.) CONVERT (Выполняет преобразование) SWAP DROP (Удаляет старшую часть числа) ROT 1+ = (Если возвращаемый адрес такой же, как адрес первого символа, то этот символ - не цифра) WHILE ." REDO " DROP (Убирает ввод и запрашивает новый) REPEAT ; ( Если цифра, то продолжает преобразование) Теперь введите HEX #IN и ответьте на предложение сделать ввод (?) строкой "XYZ". Вы увидите сообщение "REDO" (введите снова) и новый знак вопроса. На этот раз напечатайте в ответ "12AB", а затем выведите результат, вводя. (точку). Попробуйте использовать это определение #IN с другими числами. Не забывайте возвращаться к десятичной системе счисления после каждого исполнения слова.
Остается еще одна нерешенная проблема: слово #IN не понимает знак "-", помещенный слева от строки символов, если преобразуется отрицательное число. Забудем старое определение #IN: FORGET #IN и рассмотрим определение, которое будет распознавать отрицательные числа: : #IN ( -- n) (ввод_числа) BEGIN (Начинает условный цикл) $IN DUP 1+ С@ 45 = (Не является ли первый символ знаком "-"?) DUP >R (Запоминает флаг в стеке возвратов) IF 1+ THEN (Добавляет 1 к $адр, пропуская "-", если он есть) DUP 0 0 ROT CONVERT (Выполняет преобразование) ROT ROT (Помещает число двойной длины на вершину) R> IF DNEGATE THEN (Если найден знак "-", делает отрицательным) DROP (Удаляет старшую часть числа) SWAP ROT 1+ = (Если возвращаемый адрес такой же, как адрес первого символа, то строка не цифровая) WHILE ." REDO " DROP (Убирает ввод и запрашивает новый) REPEAT ; (Если цифра, то продолжает преобразование)


В этой версии слова # IN сначала просматривается, есть ли в самом начале строки знак "-" (минус); если есть, то адрес начала строки увеличивается на 1, т.е. пропускается символ "-" и в стек возвратов помещается флаг истина. После преобразования число двойной длины превращается в отрицательное, если флаг в стеке возвратов - истина. За исключением той части, где делается проверка на знак "-", определение совпадает с предыдущим. Вы понимаете, что это определение важное и, возможно, включите его в вашу версию Форта. И кроме того, теперь вы лучше понимаете, как работает слово CONVERT.
В большинстве версий Форта есть еще одно слово, которое по своему действию похоже на CONVERT. Это слово NUMBER.
Слово NUMBER не стандартное, но оно входит в контролируемый список, т.е. определено, как оно должно действовать, если имеется. Но беда в том, что некоторые реализации Форта не следуют стандартам, хотя объявляется, что следуют. Согласно стандарту слово NUMBER предполагает в стеке наличие адреса строки, оно преобразует эту строку в число двойной длины так же, как и CONVERT, но, во-первых, не требует в стеке "числа-зародыша", во-вторых, не возвращает в стек адрес первого не цифрового символа и, в-третьих, в преобразуемой строке может находиться знак "-", в таком случае в стек помещается отрицательное число. Вот определение слова NUMBER, соответствующее его описанию в стандарте: : NUMBER ( $адр - d или n) 0 0 ROT DUP 1+ С@ 45 = DUP >R + CONVERT DROP R> IF DNEGATE THEN ; Вы должны сами понять, как это определение работает, сравнивая его с последним описанием слова #IN.
Некоторые версии не соответствуют стандарту, в частности они проверяют символ; следующий после цифровой подстроки, не является ли он десятичной точкой, и если является, то оставляют в стеке результат в форме двойного числа. В противном случае они выдают результат преобразования в виде числа одинарной длины. Определением такого слова NUMBER может быть : NUMBER ( $адр - d или n) 0 0 ROT DUP 1+ С@ 45 = DUP >R + CONVERT С@ 46 = 0= IF DROP THEN ( Если не ".", то убрать) DROP R> IF DNEGATE THEN ;


Нетрудно видеть, что последние два определения очень похожи, но в последнем случае результат, возвращаемый словом CONVERT, проверяется: не содержится ли в нем десятичная точка. Если не содержится, то старшая часть числа убирается из стека, результат выдается как одинарное число. В некоторых версиях Форта десятичная точка допускается в любом месте цифровой строки. При этом они могут принимать или не принимать ее во внимание для того, чтобы выдавать одинарное или двойное число. Кроме этого, можно устанавливать переменную пользователя (т.е. записывать "1"), чтобы отметить, что обнаружена десятичная точка, тогда значение этой переменной дает возможность пользователю, если он желает, убирать из стека старшую часть 32-битового числа (как это делает MMSFORTH).
Если версия содержит слово NUMBER, то оно является важной частью интерпретатора Форт-системы. Вспомните, что анализ входного потока производится словом WORD, которое оставляет в результате разбора символьные строки. При этом именно слово NUMBER используется для того, чтобы цифровые строки превращать в числа, которые кладутся в стек. Эта проблема обсуждается в гл.15. Чтобы еще лучше оценить слова CONVERT и NUMBER, проделайте несколько упражнений.
Упражнения
1. Опишите три варианта слова D#IN, которые аналогичны трем вариантам слова #IN, данным выше, но оставляют в стеке двойное число. 2. Определите версию слова #IN, которая оставляет в стеке либо двойное, либо одинарное число, в зависимости от того, заканчивается или не заканчивается строка десятичной точкой. Определение должно допускать ввод отрицательных чисел " давать сообщение "Введите снова", если строка не содержит цифр. 3. Определите слово #IN, используя слово NUMBER. Это определение не должно делать проверки строки на нецифровые символы. 4. Это и последующие упражнения должны убедить вас в том, что и в Форте можно пользоваться инфиксной нотацией. Если вы определите слово : SILLY 32 WORD NUMBER DROP . ; то в результате SILLY 526 будет выведено число 526.


Здесь слово WORD ввело цифровую строку. Слово NUMBER преобразовало ее в число и, естественно, напечатало. Учитывая этот пример, напишите слово PLUS, которое будет брать из входного потока число, которое следует после него, преобразовывать его в число одинарной длины и складывать с числом, которое уже находится в стеке. Таким образом, 5 PLUS 6 . должно вывести на экран 11. Может быть. вы определите слово INFIX для упрощения определения слова PLUS, а также слов, которые встретятся в следующих упражнениях. 5. Теперь определите еще четыре слова: MINUS (минус), TIMES (умножить), DIVIDEDBY (разделить_на) и EQUALS (равно), которые позволят использовать Форт в качестве калькулятора (правда, только для целых чисел), т.е. в результате операций 3 PLUS 9 TIMES 2 DIVIDEDBY 4 EQUALS (3 плюс 9 умножить_на 2 делить на 4 равно), вы должны получить на экране 6.
Применяя слова WORD, NUMBER, и др., вы можете полностью переделать саму природу языка. На практике применение подобных приемов позволяет написать на языке Форт интерпретатор другого языка, например, Бейсика.
Выводы
Одна из распространенных претензий к языку Форт состоит в том, что он якобы имеет очень слабые возможности для работы с символьными строками. Не воздерживаются от критики даже те программисты, которые работают на языке Форт, когда они пишут что-либо о языке. Мы надеемся, что вы уже убедились в несостоятельности подобной критики, форт может быть столь же эффективен в обработке строковых данных, как и другие языки. С подобной критикой мы уже встречались в связи с работой с числами с плавающей запятой. Важная цель стандартов языка Форт состоит в том, чтобы не навязывать жесткие ограничения, которые могут препятствовать его развитию и гибкости. С другой стороны, отсюда вытекает, что каждый пользователь должен приспособить язык, чтобы он соответствовал более общим требованиям, и это задает программисту дополнительную работу. Что касается авторов, то мы считаем недостатком стандарта то, что он не определяет лучшие возможности работать с числами с плавающей запятой и символьными строками.Можно с этим соглашаться или не соглашаться, но расширением словаря различных версий Форта может значительно увеличить производительность программистов. Почти все работающие на Форте быстро создают свои пакеты программ и слова, которыми можно воспользоваться при необходимости. Одна из целей данной книги - помочь вам создать такую коллекцию слов и программ; вы сами можете включить некоторые слова из этой главы в свою коллекцию.
Вы уже, вероятно, осознали, что ввод данных с клавиатуры, как числовых, так и текстовых, связан с неудобствами. Например, составленный вами выше телефонный справочник стирается, когда вы выключаете компьютер. В связи с этим нам нужен какой-либо способ запоминать строки и числа на диске. Выход из положения состоит в записи в блоки на диске, которые являются предметом рассмотрения следующей главы.

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