Язык программирования Форт
Американский стандартный код для обмена информацией (ASCII)
Кроме представления чисел разряды в памяти могут представлять алфавитно-цифровые символы. Символы просто кодируются числами, при этом наиболее распространенным является код ASCII, полное наименование которого приведено в заголовке. Первоначально код ASCII предназначался не для компьютеров, а для телекоммуникаций. Это привело к тому, что некоторые коды имеют странные названия. Например, код 7 (называемый BEL -- звонок) предназначался для звонка в момент возврата каретки с целью привлечения внимания оператора. Код 5 называется WRU (от Who aRe yoU -- кто там?), предназначался для запроса служебного позывного оператора на другом конце линии, код 25 -- ЕМ (End of Media -- конец носителя) может быть использован для индикации конца телеграфной ленты. Хотя мы еще увидим в , как Форт обращается со строками символов сейчас мы рассмотрим, как он воспринимает отдельные символы. Несмотря на то, что код ASCII не предназначался для ЭВМ, символы в нем все же упорядочены логично с точки зрения ЭВМ т.е. в порядке расположения букв алфавита. Полный набор кодов ASCII приведен в . В показано только, как он организован.Таблица 3.3, Сводка кодов ASCII в десятичной и двоичной форме
От До Десятичный Двоичный Десятичный Двоичный Назначение
000 00000000 031 00011111 Коды управления ЭВМ 032 00100000 064 01000000 Цифры и знаки пунктуации 065 01000001 090 01011010 Буквы A-Z 091 01011011 096 01100000 Разные знаки 097 01100001 122 01111010 Буквы a-z 123 01111011 127 01111111 Разные символы 128 10000000 255 11111111 Кодами ASCII не являются
Вы можете заметить, что в стандартном коде фактически используется только 7 разрядов из байта. Это объясняется тем, что для телетайпов использовалась семидорожечная бумажная лента с семью отверстиями поперек ленты. (Она же использовалась для хранения программ и данных до сравнительно недавнего времени.) Числа с восьмым разрядом, установленным в 1 (128 - 255), не являются кодами ASCII как в Форт-79, так и в Форт-83, но в некоторых версиях Форт и большинстве микрокомпьютеров частично используются.
Например, в MMSFORTH с ЭВМ TRS-80 и IBM PC они используются для графических и специальных символов. Первые 31 код называются управляющими кодами. Хотя их назначение стандартизовано для передачи данных, но в микрокомпьютерах они применяются для разных целей, т.е. стандарт нарушается. Тем не менее некоторые из них все же стандартизованы, как, например, 8 -- код возврата на позицию влево, 13 -- возврат каретки, 10 -- перевод строки.
Управляющие коды посылаются с клавиатуры путем нажатия клавиши "Control" (управление, иногда обозначается "Ctrl") и одновременно какой-то литерной клавиши, например для возврата влево на позицию -- это Ctrl-H. Если на клавиатуре нет клавиши "Ctrl", то в вашей версии Форта должна быть предусмотрена какая-либо другая клавиша, которая действует как "Ctrl", например в MMSFORTH с ранними моделями ЭВМ TRS-80 использовалась клавиша "Clear" (очистка). Вам нужно посмотреть по своей документации, какие управляющие коды используются в вашей версии форта и какую клавишу нужно использовать, если у вас нет клавиши "Ctrl". Чтобы узнать как в вашей версии Форта используются коды ASCII, воспользуйтесь двумя словами EMIT и KEY.
Введите
65 EMIT
и на экране должно появиться
A ok.
Слово EMIT берет число с вершины стека и посылает его ASCII-эквивалент на экран. Испытайте со словом EMIT другие числа, сравнивая получаемые результаты с таблицей в , чтобы получше прочувствовать, как ваше оборудование обращается с кодами ASCII. Некоторые особые символы, такие как тильда "~", "стрелка вверх" (^), квадратные скобки ([]) на различных компьютерах могут выглядеть по-разному, и то, что печатает ваш принтер, также может отличаться от того, что выводится на экране. (Предупреждение: использование чисел меньше 32 может привести к неожиданным результатам, так как это управляющие коды.)
Если слово EMIT позволяет вывести символы ASCII, соответствующие числу, находящемуся в стеке, то слово KEY позволяет выдать в стек код символа ASCII, вводимого с клавиатуры.Введите слово
KEY
Ничего заметного при этом не случится, даже сообщение "ok" не появится. Теперь нажмите клавишу "В", а потом . ; после этого вы увидите, что в стек было положено число 66. Слово KEY приказало Форту приостановить то, что он делал, и подождать, когда будет нажата какая-либо клавиша, тогда код, соответствующий символу клавиши, кладется в стек. Когда вы нажали клавишу "В", вы поместили значение кода ASCII в стек и увидели этот код, когда ввели . (точку). Что вы увидите, если введете с клавиатуры
KEY EMIT
Попробуйте после этого нажать какую-либо клавишу. Можете ли вы объяснить результат ? Если у вас нет компьютера под рукой, то вот, что вы должны увидеть, если, скажем, нажмете Z после :
Z ok
Полезно уметь делать преобразования кодов ASCII, например, для представления вместо букв нижнего регистра букв верхнего регистра и наоборот. Посмотрим, как это делается в упражнениях.
Буфер ввода
В действительности числа, которые вы вводите, не попадают в стек сразу. Все, что вы вводите с клавиатуры, сначала запоминается в небольшой области памяти, которая называется буфером ввода, в том же виде, как вы потом видите на экране. Когда вы нажимаете клавишу , буфер ввода интерпретируется, т.е. сканируется слева направо. Интерпретатор просматривает каждую строчку символов, разделенных пробелами. Если он опознает символ(ы) как слово, он принимает его и просматривает следующий набор символов. Если символы не опознаются как слово, интерпретатор пробует, не являются ли они числом, и, если это так, помещает в стек число. Если же символы не воспринимаются ни как слова, ни как числа, Форт выдает сообщение об ошибке. Попробуйте ввести159 ZZZZZ
(либо другое бессмысленное слово) вы увидите:
zzzzz ?
или какое-либо сообщение, свидетельствующее о том, что Форт вас не понял. Если теперь вы введете . (точку), Форт напечатает сообщение "Stack empty !" (стек пуст).
Когда Форт не может интерпретировать слово, он производит очистку стека. Это необходимо помнить потому, что иначе вы будете обескуражены результатом, а отчасти потому, что таким путем удобно очищать стек. Помните также, что, если Форт встречает неизвестное ему слово, он игнорирует все, что следует за ним в строке ввода.
Буфер ввода имеет определенную длину, которая зависит от используемой версии Форта. В соответствии с требованиями стандартов буфер должен принимать не менее 80 символов.
Что такое Форт ?
Из всего сказанного может показаться, что у нас есть только два выбора среди языков программирования: либо быстрые, но громоздкие компиляторы, либо медленные, но зато общительные интерпретаторы. К счастью, Форт является еще одной альтернативой. Программы, написанные на языке Форт, по быстродействию не уступают, а то и превосходят программы, написанные на компилирующих языках. Форт-программы очень легко изменяются и отлаживаются. Еще одним их достоинством является то, что они обычно занимают в памяти меньше места, чем программы на других, как компилирующих, так и интерпретирующих языках. Дополнительное преимущество языка заключается в том, что он дает возможность программисту определить некоторые слова на языке ассемблера, когда требуется максимальное быстродействие, т.е. в нем совмещаются преимущества языка высокого уровня и языка ассемблера. Чем объясняются все эти достоинства языка Форт ? Тем, что он является интерпретирующим языком с шитым кодом. Чтобы разобраться, что это значит, нужно рассмотреть, что происходит при определении Форт-слова. В поставляемом потребителям виде базовая Форт-система содержит множество слов, которые были определены не с помощью других слов, а непосредственно на машинном языке (они составляют часть, называемую ядром языка). Эти слова используются для описания других слов, и их называют примитивами. Примерами примитивов являются слова *, +, DUP, SWAP и т.д. Если вы компилируете слово SQUARE, вводя: SQUARE DUP * ;
в компьютер не вводится никакого нового машинного кода, вы только предлагаете ему соединить вместе коды слова DUP и * под именем SQUARE. Точно также и CUBE:
: CUBE DUP SQUARE * :
не вводит нового машинного кода. Аналогично и слово POWER4, которое вы определили в одном из упражнений. Компиляция состоит как бы в протаскивании одной нити через различные определения с исполнением встречающихся по пути примитивов и машинных команд. Когда исполняется слово POWER4, "нить" проходит так, как показано на . Все необходимые действия, кроме ввода в память, были выполнены с помощью двух примитивов DUP и *, входящих в ядро и определенных на машинном коде.
Если вы знакомы с компиляцией в других языках программирования, то заметите, что форт осуществляет ее совершенно по-другому. В и мы подробно рассмотрим, как работает компилятор шитого кода. В книге Лулигера (1981) рассматриваются некоторые теоретические вопросы разработки интерпретирующих языков, использующих шитый код. Теперь мы можем понять причину многих достоинств языка Форт: программирование в диалоговом режиме, гибкость, большая скорость, минимальные потребности памяти и такое же полное управление оборудованием компьютера, какое доступно ассемблеру или машинному языку. Вы уже убедились в больших возможностях общения с языком на примерах и упражнениях этой главы. Большое преимущество в скорости работы перед другими интерактивными языками объясняется тем, что последние, как правило, интерпретирующие.

рис. 1.3
Поскольку интерпретация включает в себя преобразование кода программы в машинный код строчка за строчкой во время исполнения программы, то и Форт, и любой другой компилирующий язык должны работать намного быстрее. Скорость их работы складывается из нескольких компонент. Во-первых, использование стека позволяет сэкономить время на извлечение переменных из памяти, если выполняются операции с числами. Во-вторых, переход от примитива к примитиву и от слова к слову также занимает очень мало времени. В-третьих, в процессе исполнения программы Форт делает только минимальную проверку ошибок, поэтому не подключаются никакие "скрытые" программы для их обнаружения, например, если вы пытаетесь разделить число на 0. Проверка ошибок целиком возлагается на программиста. Поскольку Форт-программа составляется и отлаживается слово за словом, это может при отладке сильно мешать, поэтому проверку ошибок можно предусматривать "по обстоятельствам". Наконец, программируя на Форте, приходится отчетливо представлять себе и учитывать, что и как делает компьютер, поэтому программа получается более эффективной.
Очень малая потребность в памяти, присущая Форту, вызывается применением шитого кода.
В отличие от других интерпретирующих языков не требуется, чтобы код исходной программы всегда находился в памяти. И в отличие от любого компилирующего языка каждый примитив и процедуры, определенные на машинном языке, хранятся в памяти в единственном экземпляре. Большинство же компиляторов добавляют к программе модули машинного кода при каждом обращении к этому модулю. Когда компилятор встречает в исходном коде вызов функции, он отыскивает в библиотеке определенные на машинном коде процедуры и размещает их в памяти, поэтому, если функция вызывается несколько раз, то в память будет помещено несколько копий процедуры. Интерпретирующий язык с шитым кодом записывает процедуру в память только один раз и делает переход к ней из любого места, где она потребуется. Поэтому Форт-программа может занимать в памяти значительно меньше места, чем эквивалентная программа, написанная, скажем, на Фортране.
Наконец, Форт обладает потрясающей способностью управлять компьютером, поскольку на нем довольно легко определить новые примитивы, т.е. процедуры в машинных кодах. Если определение слова начинается с CODE, то все, что следует после этого слова, описывается на языке ассемблера и при ассемблировании превращается в машинный код, который становится частью определения этого слова. Описание вы найдете в . Поскольку слова Форта можно легко комбинировать с мнемоникой ассемблера, он обладает мощью ассемблера и удобством языка высокого уровня. Очевидно, что программирование на Форт-ассемблере не является обязательным, но приятно сознавать, что такая возможность имеется при необходимости.
Что такое машинный язык ?
Чтобы задать вопрос, почему Форт работает не так, как другие языки программирования, надо сначала спросить, а что такое язык ЭВМ ? Компьютер -- это машина, которая может производить включение и выключение переключающих устройств с очень большой скоростью (миллион или более раз в секунду). Каждое переключающее устройство может представлять "1" (включено) или "0" (выключено), большая часть этих переключающих устройств находится в памяти ЭВМ. Компьютер может хранить числа, буквы и другие данные в памяти, поскольку он обладает способностью переводить их в последовательность из единиц и нулей (включенных и выключенных состояний переключающих устройств). Так, например, буква R обычно хранится в ЭВМ в виде 01010010, буква S -- в виде 0101011, а буква r -- как 01110010. Но еще важнее, что и инструкции, которые указывают компьютеру, что он должен делать, также хранятся в памяти в виде последовательности из единиц и нулей. Центральное процессорное устройство (ЦПУ) считывает эти последовательности, определяя, что делать. Эта последовательность единиц и нулей, на которую отзывается центральный процессор, представляет собой программу самого низкого уровня, и фактически только такую программу процессор может непосредственно исполнять.В отличие от компьютеров человек не обладает способностью мыслить категориями единиц и нулей. Поэтому он нуждается в языке для общения с компьютером. Самый простой язык, который называется машинным языком, представляет собой попросту процессорные инструкции в виде последовательностей из многоразрядных чисел, которые хранятся в компьютере представленными в виде единиц и нулей. Однако и такое представление трудно для человеческого восприятия, поэтому пользуются языком более высокого уровня, в котором каждая инструкция представляется некоторой аббревиатурой (или, как говорят, мнемоникой), которая, к примеру может указывать, что компьютер должен переслать число из памяти в регистр ЦПУ. Каждая мнемоническая инструкция, в свою очередь, ассемблируется (размещается) с помощью программы, которая написана на машинном языке так, чтобы сформировать в памяти последовательность из единиц и нулей.
Программа, предназначенная для этой цели, называется ассемблером, а язык этого уровня также называется ассемблером, или языком ассемблера.
Язык ассемблера обладает тем достоинством, что он может управлять всеми доступными данному процессору операциями и, кроме того, создает очень компактные программы, которые эффективно используют запоминающее устройство и обеспечивают максимально возможную скорость работы ЭВМ. Недостатком же его является то, что программист должен описать действия ЭВМ вплоть до самых мелких деталей. Например, нельзя на языке ассемблера дать команду компьютеру перемножить два числа, необходимо расчленить процесс умножения на ряд более простых шагов. Программа умножения двух чисел, записанная на языке ассемблера, может вылиться в некоторое количество строчек. Поэтому на языке ассемблера обычно пишут те программы, которые должны работать с максимально возможным быстродействием с учетом мельчайших подробностей работы компьютера, например программы для управления запоминающим устройством на магнитных дисках.
Язык ассемблера все же неудобен для решения большинства практических задач, поэтому в основном его используют для написания других языков программирования, чтобы еще на одну ступень подняться над машинным языком ЦПУ. Языки такого рода называют языками программирования высокого уровня. К ним относятся Фортран, Бейсик, Кобол, АПЛ, Паскаль и тот, который нас здесь больше всего интересует -- Форт. Они преобразуют понятные человеку символы (например, * -- обозначение операции умножения двух чисел) в последовательность понятных компьютеру инструкций из единиц и нулей. Следовательно, языки высокого уровня выполняют роль переводчиков между человеком и ЭВМ.
Языки высокого уровня традиционно разделяются на два класса: интерпретирующие и компилирующие. Компилирующим языком называют такой язык, который целиком преобразует исходную программу в машинный язык, исполняющуюся так же, как программа, написанная непосредственно на машинном языке. Текст программы, написанный на языке близком к обычному английскому, называется исходным кодом, инструкции на машинном языке -- скомпилированным кодом.
Часто текст программы называют просто кодом, будь то язык машины, ассемблера или язык высокого уровня. Компилирующие языки появились первыми, самыми старыми среди них являются Фортран, Алгол и Кобол, которые еще и сейчас применяются преимущественно на больших ЭВМ.
Достоинство компилирующих языков состоит в том, что громадный исходный код программы не нужно размещать в машине, она транслируется в машинный код один раз, а исполняемая программа работает обычно очень быстро и для ее размещения требуется меньше места в памяти. Недостаток же состоит в том, что сам процесс компиляции очень трудоемок, трудоемок также процесс внесения исправлений и изменений в программу (отладки программы), потому что при внесении любого изменения ее приходится заново компилировать целиком.
Интерпретирующий язык транслирует исходный код программы (интерпретирует) строчку за строчкой при каждом исполнении программы. Интерпретирующим языком является Бейсик, хотя также существуют и компилирующие версии этого языка. Очевидно что при исполнении программы на интерпретацию расходуется время, поэтому интерпретирующие языки по своей природе работают медленнее, чем компилирующие. С другой стороны, исходный текст программы можно легко изменить, потому что он всегда находится в компьютере, а скорректированную программу можно быстро и просто проверить. Обычно интерпретирующие языки поощряют при их изучении и программировании к применению метода проб и ошибок. Кстати, язык Бейсик был первоначально разработан в Дартмутском колледже как раз для изучения компьютерных языков. Интерпретирующие языки обычно общительны, в том смысле, что они делают общение между программистом и программой (и, следовательно, компьютером) относительно несложным.
Что такое память ?
Представьте себе, что имеется 8 переключателей, каждый из которых может быть включен или выключен. Можно их закодировать так, чтобы состояние переключателей представлялось числами. Будем считать, что единице соответствует включенный переключатель, нулю --- выключенный. В показаны числа, построенные таким образом. Восемь переключателей могут представлять до 256 чисел (от 0 до 255). Если немного подумать, вы сможете доказать, что с помощью n переключателей можно запомнить 2^n чисел (2^8=256).С помощью 16 переключателей или двух банков по 8 переключателей могут быть представлены 65536 чисел (от 0 до 65535).
Предполагается, что для чисел в стеке имеется два банка по 8 переключателей. Вследствие этого наибольшее число, которое можно записать в стек и которое мы называем числом одинарной длины, равно 65535. Для представления чисел двойной длины нужно уже 32 переключателя, и диапазон представляемых чисел увеличивается до 65535 х 65535 = 4271406736.
Итак, мы видим, что память микрокомпьютера -- это не что иное, как большое количество переключателей, которые могут включаться и выключаться с огромной скоростью. Вот так. И все, и ничего больше. Сложность здесь состоит в том, чтобы организовать переключатели для хранения чисел и, следовательно, данных. (Алфавитно-цифровой текст также можно представить в виде закодированных чисел. Например, буква А представляется числом 65.) Каждый переключатель в памяти называется битом, от английского Binary digiT (двоичный разряд, а почему -- мы вскоре узнаем).
А теперь можете забыть о всяких переключателях, будем говорить только о битах. Если значение содержимого бита равно 1, то говорят, что он взведен или установлен; если значение равно 0, то говорят, что он сброшен или выключен. Многие недорогие ЭВМ, например Commodore 84, TRS-80 модель 4, Apple II, используют ЦПУ, которое работает с 8-разрядными числами; их называют 8-разрядными машинами, они обеспечивают доступ к 65536 8-разрядным ячейкам памяти, т.е. к 524288 битам. Более дорогие 16-разрядные машины, например IBM PC и Tandy 2000, работают с 16 разрядами одновременно, обеспечивают доступ к большему числу ячеек памяти, объем памяти здесь, скорее, ограничивается потребностями пользователя и его финансовыми возможностями. 8-разрядный элемент памяти называют байтом, в байтах принято оценивать емкость памяти.
В ЭВМ ранних моделей за единицу емкости принималось четыре разряда.
16 разрядов иногда называют словом, однако, так как слово на больших ЭВМ может быть 32-разрядным или даже 64-разрядным и, кроме того, понятие "слово" в языке Форт имеет совсем другой смысл, будем называть 16-разрядное число ячейкой либо просто числом. Это число -- целое, ибо оно используется для хранения в стеке чисел одинарной длины. Для обозначения больших объемов памяти применяются слова с префиксами кило и Мега-, которые в метрической системе означают соответственно 1000 и 1 миллион.
Например, километр -- это 1000 метров, 1 МегаГерц -- это 1000000 Гц, или 1 млн. колебаний/с. В вычислительной технике применяются другие единицы (не столь строгие). Килобайт (сокращается как Кбайт) -- это приблизительно 1000 байтов, на самом деле он равен 210 = 1024 байта. Мегабайт (сокращенно Мбайт) используется еще более нечетко. Иногда считают его равным 1000 Кбайтам, т.е. 1024000 байтам, а иногда 1024 Кбайтам, т.е. 1048576 байтам. Последнее число представляется как 220 байтов. Это полезная единица для обозначения объема памяти, и мы будем использовать именно это определение. Так, например, модели ЭВМ TRS-80 Modell III и Apple II обычно называют машинами с объемом памяти 64 Кбайта или б4К- машинами; они могут работать с памятью объемом 64 х 1034 = 65536 байтов; IBM PC может работать с памятью объемом до 1 Мбайта, или 1048576 байтов.
Таблица 3.1 Двоичная запись и основание системы счисления
| Состояния переключателей |
число | Состояния переключателей |
число |
| 00000000 | 0 | 00000110 | 6 |
| 00000001 | 1 | 00000111 | 7 |
| 00000010 | 2 | 00001000 | 8 |
| 00000011 | 3 | 00001001 | 9 |
| 00000100 | 4 | .... | ..... |
| 00000101 | 5 | 11111111 | 255 |
с двоичной системой. Как мы вскоре увидим, иногда (но не очень часто) удобно работать с числами, использующими для записи 8 цифр (от 0 до 7), т.е. восьмеричную форму, а очень часто удобнее использовать 16 чисел (от 0 до 9 и от А до F), или так называемую шестнадцатеричную форму представления. В шестнадцатеричной форме число 10 обозначается буквой А, 11 -- буквой В и т.д., число 15 -- буквой F, число 16 обозначается как 10. Давайте снова рассмотрим таблицу двоичных чисел. Посмотрите, сможете ли вы объяснить, почему для обращения с байтом оказывается удобной шестнадцатеричная система (для этого продолжите таблицу до 16). Если вы не догадались, то через некоторое время это станет понятнее. Хорошее практическое обсуждение оснований систем счисления и других аспектов представления чисел вы можете найти в книге Липшуца "Арифметические основы компьютеров" (1982).
Количество различных цифр, используемых для представления чисел, называется основанием системы счисления или просто основанием. При двоичной записи основание равно 2, при восьмеричной -- 8, при десятичной -- 10, при шестнадцатеричной -- 16 (обозначение ее в Форте HEX происходит от английского названия hexadecimal). Прелесть Форта состоит в том, что, хотя все числа он хранит в двоичной форме (впрочем, как и многие другие языки, что определяется требованиями компьютера), он может принимать их и отображать с любым основанием вплоть до 72. Нам потребуются некоторые слова Форта, которые позволяли бы отображать числа из стека в различных системах счисления, но прежде чем мы с ними познакомимся, необходимо немного отвлечься, чтобы рассмотреть, как мы можем извлекать байты и числа, которые хранятся в памяти. С каждым байтом сопоставляется число, начиная с нуля и больше, которое соответствует его положению в памяти и называется адресом. Таким образом, первый байт имеет 0-й адрес в памяти, в то время как для 8-разрядной ЭВМ адрес самого верхнего байта равен 65535. Для осуществления доступа к содержимому указанного адреса в Форте предусмотрено несколько слов.
Наиболее важными из них являются @ (извлечь) и ! (занести, запомнить). Если в стеке на вершине находится адрес, то слово @ замещает адрес 16-разрядным (двухбайтовым) числом, которое хранится по этому адресу. Если так же на вершине находится адрес, а вторым элементом стека является число, то оператор ! производит запоминание этого числа по указанному адресу. Слово ! следует употреблять осмотрительно, так как можно изменить содержимое важной части памяти, если вы запишете не в то место памяти. Можно использовать слова @ и ! для того, чтобы узнать систему счисления или изменить ее при вводе и выводе чисел. В памяти имеется ячейка (адрес), в которой хранится основание системы счисления, которое действует в настоящий момент при вводе-выводе чисел. Этот адрес выдается в стек когда вы вводите слово BASE. Давайте его испытаем. Вначале дадим компьютеру задание принимать и выводить числа в десятичной системе счисления с помощью слова DECIMAL (десятичный). Слово DECIMAL изменяет число, которое хранится по адресу, возвращаемому в стек словом BASE, на 10 (десятичная система). Теперь основание может быть сделано шестнадцатеричным (основание 16), если ввести
16 BASE !
Слово BASE помещает на вершину стека соответствующий адрес, затем в этот адрес записывается число 16. Заметим, кстати, что слово BASE -- это особая переменная, так называемая переменная пользователя. Переменная -- это слово, которое выдает адрес, где может храниться число; мы обсудим ее более детально в . Форт не нуждается в большом количестве переменных, как другие языки программирования, так как он может хранить числа в стеке. Теперь, когда компьютер использует шестнадцатеричную систему счисления, вы можете ввести
1В DECIMAL .
и получите
12 Ok
Число 12 в десятичной записи -- это то же самое, что 1В в шестнадцатеричной. Следующий пример произведет обратное действие:
13 16 BASE ! .
выдаст на экране 1С, т.е. шестнадцатеричный эквивалент числа 13. Слово HEX, которое не является стандартным, включено в большинство версий Форта.
Оно устанавливает шестнадцатеричную систему счисления так же, как DECIMAL устанавливает десятичную. Во многих версиях есть слова OCTAL и BINARY, которые устанавливают систему счисления 8 и 2 соответственно. Можно проиллюстрировать один важный момент с помощью следующих экспериментов. Попробуем ввести
DECIMAL 2 BASE ! BASE @ .
потом
DECIMAL 8 BАSЕ ! BASE @ .
и
DECIMAL 16 BASE BASE @ .
Во всех случаях вы увидите 10. Почему ? Основание числа во всех случаях представляется как 10, так как это -- два (в двоичной системе), 8 (десятичное) -- это 10 в восьмеричной системе и 16 (десятичное) представляется так же, как 10 в шестнадцатеричной системе. Как же тогда узнать, в какой системе мы находимся ? Вот слово, которое поможет это сделать:
: BASE? BASE @ DUP DECIMAL . BASE ! ;
Вы должны понимать, как оно работает. Если вы введете
16 BASE ! BASE?
то увидите, что на экране будет число 16, и после этого мы попрежнему останемся в шестнадцатеричной системе счисления. Вместе с тем, когда вы вводите
n BASE !
нужно быть внимательным и твердо знать, от какого основания мы переходим. Например, если мы находимся в двоичной системе, то при вводе
10 BASE !
ничего не произойдет. 10 в двоичной системе -- это десятичное число 2, но мы уже находимся в двоичной системе. А что произойдет, если ввести
10 BASE !
в шестнадцатеричной системе ? Как снова вернуться к основанию 10 ? Если вы были в шестнадцатеричной системе, можете ввести
A BASE !
А -- это шестнадцатеричное число, которое равно 10 (десятичное). Если вы забыли текущее основание, то слово DECIMAL всегда возвратит вас к основанию 10, независимо от того, в какой системе вы были до этого.
Приведем слово, которое показывает в двоичной системе счисления число, находящееся на вершине стека. Обратите внимание, что при этом оно не изменяет содержимое стека:
: .BIN (n - n) DUP BASE @ 2 BASE ! SWAP . BASE ! ;
Вы должны догадаться, как оно работает.
В качестве упражнения (и для использования впоследствии) опишите три слова: .ОСТ, .DEC и .HEX, которые будут печатать число из стека в восьмеричной, десятичной и шестнадцатеричной системе соответственно.Для этого вам надо изменить в слове .BIN всего один символ. Теперь мы сможем написать еще одно слово, которое даст возможность представить число в стеке одновременно в двоичной, восьмеричной, десятичной и шестнадцатеричной системах :
: .NUMS (n -) .BIN .ОСТ .DEC .HEX DROP ;
Слово .NUMS можно использовать для того, чтобы посмотреть, как различные числа представляются в различных системах счисления. Но давайте проделаем это в следующих упражнениях. Если у вас нет компьютера, проверьте ваши ответы по .
Что такое стек ?
Можно представить стек попросту как стопку чисел, положенных друг на друга, либо, если хотите, как колоду карт, описанную в . Первое число, которое было введено в стек, будет находиться на дне, последнее введенное число -- на вершине стека. Числа из стека можно брать только с вершины. В тексте мы будем показывать стек в виде столбика чисел, положенного горизонтально, т.е. в строчку, причем дно стека находится слева, а его вершина -- справа. Загрузите в компьютер Форт и введите1 2 3
(обязательно отделите числа друг от друга пробелами, как показано).
Теперь в стеке находятся числа
З (вершина ) 2 1 (дно)
аналогия с картами представлена на . Можно представить этот стек таким образом:

рис. 2.1
Теперь, если ввести
. . .
компьютер выдаст на экран
3 2 1 ok
Первым из стека было взято и напечатано число 3, затем то же самое было сделано с числом 2, а потом с числом 1. Теперь стек пуст. Обратите внимание, что числа на экране расположены в обратном порядке по сравнению с тем, как они представлены в стеке. Вы поняли, почему ? Стек языка Форт называется стеком с организацией "последним пришел -- первым вышел" (по-английски - LIFO). Когда стек пуст, попробуйте ввести еще раз . (точка).
Тогда вы увидите
хххх . ? Stack empty ! (стек пуст !)
Форт отреагировал сообщением об ошибке; вы попросили его что-то напечатать, а он не знает, что напечатать (ххххх обычно бессмысленное число, а вид сообщения зависит от версии Форта, с которой вы работаете, мы, привели сообщение, которое выдает MMSFORTH). Обратите внимание, что Форт не выдает сообщение "ok", потому что выполнение вашей последней команды не было завершено (а значит, не все в порядке).
Еще раз нажмите , чтобы получить на экране новое приглашение "ok", хотя в этом нет особой необходимости.
Фактически Форт готов к вводу новой информации, даже если он не дал сообщение "ok".
Форт ... Почему он такой необычный ?
В оставшейся части этой главы мы хотели бы познакомить вас с тем, что такое язык ЭВМ и с некоторыми абстрактными, теоретическими и концептуальными особенностями языка Форт. Если даже вы знакомы с компьютерами и языками программирования, вам следует прочитать это, чтобы узнать о некоторых уникальных свойствах языка Форт.Что такое Форт ?
Язык Форт сильно отличается от других языков программирования для ЭВМ. Но он дает возможность программисту очень просто общаться с компьютером, и это облегчает его изучение. Форт практически мгновенно реагирует на все, что вы вводите с клавиатуры, и изучать его лучше всего, непосредственно работая на компьютере. Чаще всего считают его сложным опытные программисты, которые имеют сложившиеся представления о том, как должен работать язык программирования, они привыкли, что сначала надо написать длинную программу, прежде чем проверить ее работу. Поэтому постарайтесь забыть то, что вы знаете о других языках, садитесь за клавиатуру и давайте начнем.Хранение программ и данных
Объем памяти микрокомпьютера обычно не достаточен, чтобы хранить в ней все данные и программы, которые нам нужны. Поэтому необходимо использовать некоторое устройство массовой памяти. В микрокомпьютерах в большинстве случаев применяется либо кассетный магнитофон, либо, что более распространено, магнитный диск. Большинство языков программирования пользуется для хранения программ и данных именованными файлами, управление и обращение с которыми обеспечивается дисковой операционной системой. Как вы уже знаете. Форт сильно отличается от других языков тем, что сохраняет программы и данные в блоках.Чтобы понять дальнейший материал этой главы, вы должны узнать, что такое файлы и как они используются в типичной дисковой операционной системе, и немного о структуре диска. Файл попросту представляет собой совокупность двоичных данных, организованных в последовательность байтов, как в памяти ЭВМ. Файл может содержать алфавитно-цифровой текст в виде кодов 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 должна указывать на начало поля имени удаляемой записи.
Выводы
В этой главе мы обсудили множество вопросов, начав с простейших операций вывода листинга и загрузки программы, а закончив разбором текстовых файлов с переменной длиной записей. Мы очень подробно рассмотрели реализацию загрузки блоков, использование блоков в качестве файлов и файлов, организованных в памяти. Наш обзор затронул также примеры использования директорий файлов для организации их ввода в память блок за блоком. Причиной столь подробного изложения является то, что в большинстве версий Форта не предусмотрены организация блоков в файлы и хранение данных на диске. Даже если вам все это и не потребуется, мы надеемся, что вы имели возможность убедиться в возможностях Форта расширяться для решения задач, которые поначалу могут показаться не присущими языку. Если вам нужны средства для работы с базами данных, мы надеемся, что эта глава поможет вам решать задачи такого рода.
Создание слов-определителей
Замечательная способность языка Форт к расширению является следствием наличия в нем так называемых определяющих слов. Единственное назначение этих слов состоит в компиляции (определении или создании) других слов. Наиболее важным из определяющих слов (слов-описателей) является : (двоеточие). Из других слов, с которыми вы к настоящему времени познакомились, это CREATE, VARIABLE. CONSTANT и т. п. Во время исполнения определяющего слова оно создает новое слово, помещая а словарь заголовок создаваемого слова, после которого следует все остальное, что необходимо для исполнения нового слова. В заголовке содержатся имя слова и некоторая дополнительная информация. На любом этапе программирования на Форте определяющие слова используются для соединения между собой простых программ в более сложные. Исключительной особенностью Форта, которую мы рассмотрим в этой главе, является то, что вы сами можете создать новые определяющие слова, не ограничиваясь теми, которые предусмотрены в базовом языке Форт. И при этом создание новых определяющих слов производится так же просто, как и создание "обычных" слов языка. Это открывает неограниченные возможности для создания новых типов слов и новых типов данных, которые могут сделать ваши программы более эффективными и в то же время облегчить их написание.Порождающие и порождаемые слова
Каждое определяющее слово, входящее в ядро языка Форт, способно породить определенный класс слов. Например, хотя каждое слово, которое определяется с помощью : (слова-двоеточия), выполняет отличные от других слов действия, но они сходны между собой по способу определения, компиляции и исполнения. Все слова, определенные через : (двоеточие), принадлежат к одному классу, поскольку они составляются из более простых слов для объединения функций этих слов. Аналогично все слова, создаваемые с помощью слова-определителя CONSTANT, относятся к классу "констант", потому что все они одинаково компилируются и исполняются. Следовательно, каждое слово можно отнести к какому-либо классу в соответствии с порождающим его словом.
Отношение между словами можно сделать более ясным, если назвать определяющие слова словами-родителями, а порожденные ими слова словами-детьми. Все слова-дети общего слова-родителя ведут себя сходным образом, но все же отлично от других слов-детей, родных им "по крови". Общее в поведении всех "единокровных" слов-детей является следствием того, что они происходят от одного "родителя". Различия между "детьми" определяются тем, что при создании в них были скомпилированы различные значения. Чтобы понять, почему слова-дети ведут себя так или иначе, вы должны проанализировать определение слов-родителей и их собственные определения.
Если описать "генеалогию" слов, то мы сможем выделить три стадии, которые в литературе по языку Форт называют ходом событий. Ход событий - это то, что происходит, когда: 1. Рождается слово-родитель (компиляция определяющего слова). 2. Родитель активен и порождает (исполнение определяющего слова слово-ребенка и компиляция слова-ребенка) 3. Ребенок действует самостоятельно (исполнение слова-ребенка)
Причина того, что мы выделяем три, а не четыре стадии, очень простая: когда определяющее слово исполняется, оно компилирует слово-ребенок. Поэтому то, что кажется двумя стадиями, на самом деле это одно действие. Общее в поведении слов-детей, происходящих от одного "родителя", предписывается им на первой стадии при определении слова-родителя. Слова, определенные с помощью, похожи по своему поведению из-за того способа, которым определено само это слово, а одинаково они исполняются потому, что одно из действий определяющего слова - сделать так, чтобы "дети" вели себя одинаково. Это легче всего проследить на примере слов-детей, происходящих от VARIABLE и CONSTANT.
Все переменные кладут в стек адрес, по которому записано их содержимое, в то время как константы выдают в стек свои значения. Различие их действия обусловлено способом определения слов VARIABLE и CONSTANT. Различия в поведении слов-детей закладываются на стадии 2 при исполнении определяющего слова и компиляции слова-ребенка.
Слова-дети действуют по-разному, потому что они различаются по своему содержимому. Каждое слово, определенное через двоеточие, отличается от других, потому что в его определении используются другие комбинации слов. Константы отличаются друг от друга значениями величин, с которыми они были созданы. Поэтому мы можем сказать, что слова-дети общего порождающего слова-родителя одинаковы, поскольку они одинаково исполняются, но различны потому, что содержат различные значения величин или адресов (помимо того, что они имеют разные имена и расположены в словаре в разных местах). Применяя терминологию указанных трех стадий или хода событий, еще раз посмотрим на их последовательность:
Событие 1: Создается определяющее слово для компиляции слов-детей с определенным типом поведения.
Событие 2: Исполняется определяющее слово для того, чтобы создать слово-ребенок со своим содержимым и поведением.
Событие 3: Исполняется слово-ребенок в соответствии с тем, что слово-родитель научило слово-ребенка делать со своим содержимым.
Может показаться мистическим, что одно слово способно определить, как будет исполняться другое слово, но в самом деле это совсем просто. Когда определяющее слово порождает слово-ребенка, то кроме записи его содержимого слово-родитель записывает в него также адрес машинного кода стадии исполнения. Код стадии исполнения - это программа на машинном языке, которая описывает, как должно вести себя слово-ребенок, т.е. что оно должно делать со своим содержимым. Так как каждое определяющее слово записывает адрес специфического кода стадии исполнения во вес свои слова-дети, они и исполняются одинаково. (В гл.15 мы более детально рассмотрим действие кода стадии исполнения в словах, определенных через двоеточие.)
Определяющие слова
Мы рассмотрели, как используются определяющие слова (слова-определители) для порождения своих "отпрысков", но как же создаются сами определяющие слова? С первого взгляда может показаться, что можно ответить на этот вопрос, просматривая содержимое этих слов в словаре.
Но это не так уж просто. Определение слова : в ядре языка выглядит так, как будто оно само определено через двоеточие, но это, очевидно, абсурд. Еще более странно то, что слова, которые, казалось бы, определены через :, находятся в словаре раньше, чем само :. Как разрешить проблему первичности курицы и яйца?
Конечно, ядро Форта было создано без использования языка Форт (по крайней мере, в обычном смысле), хотя оно выглядит так, как будто бы,с использованием. Ядро должно было быть написано на другом языке - либо на ассемблере, либо на языке высокого уровня. В этом все дело. Практически можно написать Форт-систему, используя другую Форт-систему. Создание нового Форта с использованием Форта производится программой, которая называется метакомпилятором, и о нем уместно сказать несколько слов.
Метакомпиляция - это разновидность обычной компиляции Форта (т.е. процесса добавления новых слов в словарь). Но вместо того, чтобы строить словарь, начиная с адреса, на который указывает слово HERE, "метасловарь" размещается в некотором другом месте памяти. В памяти создается "образ" всего кода, который потребуется новому Форту; этот код затем выгружается на диск таким образом, чтобы его можно было запустить на исполнение с помощью операционной системы компьютера. Метакомпиляция может породить копию Форта или новый Форт, специализированный для определенных задач, даже для выполнения на ЭВМ другого типа. Независимо от того, был ли Форт создан на языке ассемблера, метакомпилятора или другом языке высокого уровня, кажущийся парадокс появления в словаре слов, определенных через двоеточие, раньше определения двоеточия, объясняется тем, что при создании самого ядра использовался совершенно другой способ, отличающийся от добавления к словарю новых слов. Теперь мы выяснили, как в Форте создаются определяющие слова и "обычные" слова, входящие в ядро Форт-системы. Ну а может ли пользователь создать новые определяющие слова и как?
Ответом на этот вопрос является слово CREATE, которое может использоваться самостоятельно или совместно с другим словом DOES>.
Слово CREATE дает само по себе наиболее общий способ определения новых слов в Форте. Как мы уже видели в гл.6, слово CREATE используется в форме CREATE CHILD-WORD где CHILD-WORD - это определяемое слово. Напомним вкратце: действие CREATE состоит в том, что оно помещает в словарь заголовок для слова CHILD-WORD, а когда это слово исполняется, то в стек кладется адрес его содержимого. Слово CREATE не резервирует никакого пространства после заголовка определяемого слова; резервирование места выполняется отдельной операцией, обычно словами С,, , (запятая) или ALLOT. Вы знаете также, что определение : VARIABLE CREATE 0 , ; создает VARIABLE как определяющее слово, которое может использоваться для определения произвольного числа переменных, каждая из которых оказывается инициализирована нулем. Определение слова VARIABLE является действием первой стадии. Действие второй стадии происходит, когда мы используем слово так, как, например, в данном случае: VARIABLE DISCOUNT Ее можно разложить на отдельные события: VARIABLE Начинает исполнение определяющего слова CREATE Делает имя "DISCOUNT" словом в словаре Запоминает адрес кода стадии исполнения в DISCOUNT 0 , Компилирует в DISCOUNT два байта нулей
Действие VARIABLE на третьей стадии происходит тогда, когда исполняется слово DISCOUNT, т.е. когда исполняется код стадии исполнения, который был записан в содержимое DISCOUNT словом CREATE, при этом в стек помещается адрес содержимого DISCOUNT.
Любое слово, определенное через двоеточие, которое содержит как часть своего определения слово CREATE, является новым определяющим словом. Как можно было бы определить CONSTANT ? Казалось бы, это можно сделать следующим образом: : BAD-CONSTANT CREATE , @ ; (Плохая_константа) но мы сразу же замечаем, что слово BAD-CONSTANT работать не может, так как операция @ будет совершаться на второй стадии, когда создается слово-ребенок, а не тогда, когда это слово должно исполняться. В действительности нам нужно определить слово CONSTANT так, чтобы содержимое слова-ребенка извлекалось на третьей стадии.
Это достигается с помощью слова DOES>. Но прежде чем рассматривать, как это осуществить, проделаем несколько упражнений, в которых мы дополнительно познакомимся с применением слова CREATE.
Упражнения
1. Опишите определяющее слово 2VARIABLE. которое должно создать переменную для хранения чисел двойной длины. Определите его таким образом, чтобы переменная инициализировалась нулем, и так, чтобы оно компилировало двойное число в стек, когда исполняется 2VARIABLE (последний метод используется в нескольких нестандартных версиях форта для всех переменных). 2. Опишите определяющее слово $CONSTANT, которое при исполнении в форме $CONSTANT строка" будет запоминать в словаре строку как счетную. При исполнении COUNT TYPE строка должна выводиться на экран. 3. Опишите слово-определитель RESERVE, которое должно создавать слова, для которых в словаре резервируется n байтов. Таким образом, с помощью 10 RESERVE 10BYTES определяется слово 10BYTES, которое, в свою очередь, резервирует 10 байтов. Напишите слово RESERVE, которое должно инициализировать все зарезервированные байты нулями. 4. Опишите слово-определитель BLOCKARRAY, которое должно запомнить число, взятое из стека, и после этого зарезервировать еще 1024 байта для содержимого блока, т.е. 213 BLOCKARRAY BLK1 должно создать слово BLK1, в которое должно быть занесено число 213, после которого должны следовать 1024 свободных байта. 5. Теперь напишите слово GETBLOCK. которое должно заполнять содержимое блочного массива содержимым указанного блока. Таким образом, BLK1 GETBLOCK должно поместить содержимое блока 213 в зарезервированное пространство в BLK1 (используйте слово nBLOCK). Напишите слово PUTBLOCK, которое должно пересылать содержимое блочного массива в блок с указанным номером. (Подсказка: вспомните действие слов UPDATE и SAVE-BUFFERS или FLUSH.) 6. Напишите два слова В@ и В!, которые должны соответственно извлекать и запоминать числа в указанном блочном массиве, т.е. 5 BLK1 В@ должно извлекать пятый элемент из блочного массива, который должен прийти из блока 213.
Эти упражнения показывают, что для хранения-извлечения и манипулирования с данными на диске могут быть полезными специальные определяющие слова и специализированные массивы. Такого рода специализированные слова особенно полезны при создании новых версий языка Форт для разработки программ обработки данных. Например, можно определить типы массивов для хранения данных в полях различных размеров, в частности для файлов инвентаризации, файла медицинских наблюдений и т.д. Имея специализированные типы массивов и специализированные определяющие слова, проще организовать слежение за тем, где и в каком формате хранятся данные. Например, массив BP-BLK (блок_давления_крови) может содержать записи о кровяном давлении пациентов, и если он организован по вышеописанной схеме, то вам не надо помнить, в каком блоке он записан. Эту идею можно распространить на соответствующие блоки для веса, роста и других показателей пациентов. Возможности здесь не ограничены.
Создание новых определяющих слов
Каждый раз, когда слово CREATE используется внутри определения через двоеточие, мы создаем новые определяющие слова. В упражнениях вы имели дело с разнообразными определяющими словами, которые по-разному действовали при компиляции слов-детей, но все порожденные слова-дети действовали одинаково при исполнении: они оставляли адрес своего содержимого в стеке. Слово DOES> нужно для того, чтобы определяющее слово задало способ поведения слова-ребенка на стадии исполнения. Теперь мы можем определить константу следующим образом: : CONSTANT CREATE , DOES> @ ;
На первой стадии деятельность слова CONSTANT проявляется во время его компиляции. Если слово CONSTANT исполняется, например, для компиляции слова 1024 CONSTANT 1K то на второй стадии действия слова CONSTANT можно расчленить следующим образом: CONSTANT Начинает исполнение определяющего слова CREATE Заносит в словарь имя "1K" Запоминает адрес кода стадии исполнения в слове 1K , Компилирует число 1024, взятое из стека
Из присутствия в определении DOES> @ мы узнаем, что на третьей стадии действие слова CONSTANT (при исполнении 1K) более сложное, чем в случае VARIABLE.
При исполнении 1K вначале в стек кладется адрес содержимого 1K (потому что слово CREATE помещает код стадии исполнения в 1K, чтобы работать таким образом) и после этого @ извлекает содержимое из этого адреса, помещая в стек число 1024. Другими словами, оператор @, следующий после DOES>, исполняется тогда, когда слово-ребенок исполняется, а не тогда, когда оно определяется.
Определение слова CONSTANT является отличным примером создания новых определяющих слов. Слова, которые находятся между CREATE и DOES>, исполняются на второй стадии, т.е. тогда, когда исполняется слово-родитель и компилируется слово-ребенок. Когда исполняется само слово-ребенок, то вначале оно помещает в стек адрес его содержимого, а потом исполняются слова, которые находятся в определяемом слове после DOES>, описывающие, что должно делать слово-ребенок,
Приведем пример использования определяющих слов, с которыми мы вновь встретимся в гл. 12 и 13, когда будем обсуждать разработку программы-редактора. Как вы уже знаете, многие терминалы и принтеры управляются кодами ASCII со значениями 0 - 31 (их называют управляющими). Значения управляющих кодов должны быть записаны в константах и выводиться на терминал словом EMIT, но лучше для этого определить специальное слово IS-CONTROL - это не что иное, как CONSTANT, в которое добавлено слово EMIT, описывающее поведение слова IS-CONTROL при исполнении. Слово IS-CONTROL. можно использовать для создания целого семейства родственных слов, например: 7 IS-CONTROL BELL (эвуковой_сигнал) 8 IS-CONTROL BACKSPACE (возврат_влево) 12 IS-CONTROL FORMFEED (подача_страницы) 13 IS-CONTROL CR (возврат_каретки) где каждое слово будет задавать терминалу определенное действие. Одно из достоинств определяющих слов уже очевидно: они способствуют улучшению читабельности программ. Например, при использовании слова IS-CONTROL нужно только конкретизировать данные, которыми отличается новое слово от других слов-детей, порождаемых словом IS-CONTROL, а именно управляющим кодом и именем.
Каждое слово-ребенок в семействе определений имеет свою индивидуальность. Определяющие слова дают вам возможность разграничить общее поведение слов, имеющих общее происхождение, и их индивидуальное поведение. Общее поведение слов-детей запрограммировано в исполнительной части определения слова, следующей после DOES>. Индивидуальное поведение слов-детей определяется значением (или значениями), которое находилось в стеке, когда оно создавалось, и, конечно, его уникальным именем.
Задачи, для которых требуется некоторое количество слов, имеющих сходные определения, лучше всего решаются при помощи нового определяющего слова. Вот еще один пример. В гл. 4 мы показали вам способ представления математических функций на Форте. С помощью определяющего слова можно создать любое количество линейных уравнений вида у = ax + b путем создания слов-детей с коэффициентами а и Ь, находящимися в стеке. Если затем будет исполняться слово-ребенок и в стеке находится значение х, то оно будет оставлять значение решения - у. Приведем определение этого определяющего слова: : LINEAR ( а b --) (линейная функция) CREATE SWAP , , DOES> DUP >R @ * R>
2+ @ + ;
Обратите внимание, что величины а и b переставляются в стеке при создании слова LINEAR, чтобы избавиться от стековых манипуляций на стадии исполнения. Если мы определили линейное уравнение у=3х+17 при помощи 3 17 LINEAR ALINE то, когда оно будет исполняться в форме 2 ALINE мы увидим решение 23. Исполнение слова ALINE можно описать следующим образом: DUP ( -- 2 адр адр) Адрес числа 3 in ALINE >R ( -- 2 адр) Помещает адрес в стек возвратов @ ( -- 2 3) Извлекает число 3 (а) * ( -- 6 ) 6 = ax, первый член R> ( -- 6 адр) Адрес числа 3 в ALINE 3+ ( -- 6 адр+2) Адрес числа 17 в ALINE @ ( -- 6 17) Извлекает 17 (b), второй член + ( -- 23) 23 = у = ax + b, решение
Пример показывает общую методику, используемую в сложных определяющих словах. Так как адрес первого элемента ALINE потребуется для извлечения двух чисел, его запоминают в стеке возвратов.
После извлечения адреса из стека возвратов его нужно увеличить на 2, чтобы указать на следующий элемент ALINE, при этом в него будет скомпилировано число 17, которое надо извлечь. Хотя в этом примере мы обошлись обычными словами для стековых манипуляций, в случае, если в словах-детях нужно запомнить несколько чисел или байтов, могут потребоваться некоторые изменения технических приемов.
Лучше всего можно оценить мощь определяющих слов из практических примеров. Из приведенных ниже упражнений вы сможете извлечь еще некоторые идеи.
Упражнения
1. Определите слово 2CONSTANT, которое должно работать так же, как и CONSTANT, но с двойными числами. 2. Определите слово MAKEDATE (создать_дату), для которого в стеке должны находиться числа: месяц, день и год, чтобы оно при исполнении выдавало дату с косой чертой в качестве разделителя. Например, 12 7 41 MAKEDATE PEARLHARBOR должно создать слово PEARLHARBOR, которое при исполнении должно выдавать дату в виде 12/07/41. (Вспомните вывод по шаблону из гл.5.) 3. Определите определяющее слово COUNTEB (счетчик), которое использует число из стека, чтобы инициализировать порождаемые им слова. Когда исполняются слова-дети COUNTER, то при очередном исполнении их содержимое должно изменяться таким образом, что 0 COUNTER COUNTIT должно создать COUNTIT, которое при исполнении будет последовательно изменять свое содержимое: 1, 2, 3 и т.д. Как можно извлечь содержимое COUNTIT ? 4. Как можно использовать слова, производные от COUNTER, для подсчета частоты использования определенных слов в программе? 5. Для регистрации цвета фотографических красителей экспериментально определяют насыщенность составляющих цветов : голубого, желтого и пурпурного, значения каждой из которых могут изменяться в диапазоне от 0 до 255. Определите слово COLOR, которое берет из стека величины насыщенности цветов, а порождаемые им слова должны выдавать насыщенность каждого из трех цветов в процентах. Так, например, 128 128 128 COLOR 1DYE будет создавать слово IDYE, которое при исполнении должно вывести "50% голубого, 50% желтого, 50% пурпурного".
Возможно, что прежде, чем определить само слово COLOR, вы захотите определить три слова, которые будут выводить процентное содержание цвета. 6. Определите слово QUADRATIC, которое работает подобно LINEAR, но определяет порождаемые им слова, выдающие в стек решение уравнения у = ах2 + bх + с, если вначале в стеке находится х. 7. Уравнение Михаэлиса-Ментена широко применяется в биологии и биохимии для определения скорости энзиматических реакций. Его общий вид такой: Q = QмаксS / (KM + S), где Q - скорость реакции, S - концентрация субстрата, Qмакс - максимальная скорость реакции и KM - константа полунасыщения, т.е. концентрация субстрата, при которой скорость реакции составляет половину от максимального значения. Напишите определяющие слова для решения этого уравнения, причем в фазе компиляции в стеке находятся значения Quarr и КМ, а значение S задается в стеке во время исполнения. 8. Многочлен n-й степени является очень полезной функцией ввиду того, что он может "имитировать" почти любую другую функцию. Многочлен имеет вид у = а1 + а2x +а3x^2 +... + аnx^(n-1) Напишите порождающее слово POLYNOM, для которого в стеке заданы некоторое количество коэффициентов (т.е. значений аi), и производные от него слова-дети будут рассчитывать значение полинома (у), используя эти коэффициенты. Вам потребуется использовать в определяющем слове циклы DO-LOOP после CREATE и после DOES>. Понятно ли вы вам, как POLYNOM может заменить слова QUADRATIC и LINEAR ? 9. Определите слово , которое должно использоваться в форме мин макс n A-CONSTANT где A-CONSTANT будет иметь начальное значение п, но возвращать число мин, если его содержимое меньше, чем мин. или число макс, если его содержимое больше, чем макс. (Содержимое A-CONSTANT должно быть изменено при помощи ' или ' >BODY и !.)
Определение массивов
В гл.6 мы ознакомились с созданием одномерных массивов с помощью слов CREATE и ALLOT, однако, чтобы извлекать или записывать данные, нам приходилось вычислять величину смещения адреса.
Слова- определители находят превосходное применение при создании массивов, поскольку с их помощью можно создать описание массива с определенной размерностью, которое при исполнении будет выдавать адрес нужного элемента в стек. Приведем пример слова-определителя для создания массива из символов, или байтов, с именем CARRAY. Один из возможных вариантов слова-определителя такой: : CARRAY CREATE ALLOT DOES> + ;
Если его использовать в форме 30 CARRAY NOVEMBER то будет создан массив из 30 байтов, элементы которого нумеруются числами от 0 до 29. На стадии исполнения слова-ребенка требуется наличие в стеке номера элемента массива, чтобы вычислить его адрес. Таким образом, при исполнении 1 NOVEMBER C@ . будет рассчитан адрес второго байта в массиве NOVEMBER, извлечено его содержимое и после этого выведено на экран.
Существуют два способа нумерации элементов массивов, начиная либо с нулевого элемента (как в вышеприведенном случае), либо с элемента 1. Если вы хотите, чтобы выражение 1NOVEMBER выдало адрес первого (а не второго) байта в массиве NOVEMBER, определите CARRAY таким образом: : CARRAY CREATE ALLOT OOES> 1- + ; Несмотря на то, что нумерация с первого элемента приводит к небольшому проигрышу в скорости, вы убедитесь в том, что она удобнее для работы. Для создания массивов чисел одинарной длины можно использовать похожее определение (принимая также, что нумерация элементов начинается с нуля) : ARRAY CREATE 2 * ALLOT DOES> SWAP 2 * + ;
Для регистрации цвета фотографических красителей уверены, что вы сможете самостоятельно проанализировать, что делает это определение. Можете ли вы предложить определение, которое создает массив, начинающийся с первого элемента ?
В упражнениях мы предложим вам написать эквивалентное слово для создания массивов двойных чисел. Обратите внимание на то, что массивы, порождаемые этими простыми определениями, будут безропотно возвращать в стек адреса элементов, находящихся вне резервированного адресного пространства, если номер элемента выходит из заданного диапазона.
Запись данных за пределами резервированной области может привести к катастрофическим последствиям. Поэтому корректное решение задачи состоит в том, чтобы написать слово, которое производит проверку значения номера элемента массива, прежде, чем его использовать для работы с определенным массивом, на допустимость, т.е. попадание его в область разрешенных значений. Однако включение проверки в опреде ление массивов ARRAY и CARRAY приведет к снижению быстродействия, независимо от того, будет обнаружена ошибка или нет. Если скорость не очень важна, то можно переписать определения массивов, включив в них проверку ошибок. В таком случае в слово, определяющее массив, наряду с размером резервированной для массива области должно быть скомпилировано число элементов (чтобы использовать для проверки). Если принять, что номер младшего элемента массива равен 0, то одним из возможных определений, выполняющих проверку, может быть : ECARRAY ( n --) CREATE DUP , 2+ ALLOT DOES> DUP @ 3 PICK SWAP U< ( 2 PICK для Форт-83) IF + 2+ ELSE ." Ошибка индекса" ABORT THEN :
Приведенное определение можно расчленить на отдельные действия следующим образом: : ECARRAY (Имя определяющего слова) CREATE (Создает заголовок слова-ребенка) DUP (Копирует число элементов массива в стеке) (Помещает в слово-ребенок адрес кода стадии исполнения) , (Компилирует максимальный номер элемента) 2+ (Устанавливает число байтов, необходимых для компиляции массива) ALLOT (Резервирует в словаре место для массива) DOES> (Определяет поведение слова-ребенка при исполнении) (Номер элемента находится в стеке) (Оставляет в стеке адрес содержимого слова-ребенка) DUP ( -- n адр адр ) @ ( -- n адр макс ) (Максимальная размерность массива) 3 PICK ( -- n адр макс n ) (Номер элемента) (2 PICK для Форт-83) SWAP ( - n адр n макс ) U< (Номер элемента меньше допустимого?) IF (Если номер меньше допустимого...) + (Вычисляет адрес элемента и) 2+ (смещение для обхода максимального значения) ELSE (Но, если номер элемента слишком большой) ." Ошибка индекса" (то выдает сообщение об ошибке) ABORT (очищает стек и прекращает работу) THEN ; (Заканчивает определение)
Не могли бы вы попытаться несколько повысить эффективность этого определения, используя стек возвратов?
Слово для проверки недопустимого значения индекса массива в определении чисел одинарной длины будет иметь такую же форму, однако в нем должна быть предусмотрена возможность использования для каждого числа двух байтов. Чтобы обнаружить, что индекс слишком велик или попадает в область отрицательных значений, используется оператор сравнения U
Упражнения
1. Создайте слово для определения массивов чисел двойной длины DARRAY, которое работает так же. как CARRAY и ARRAY. 2. Перепишите определения ARRAY и EARRAY с именами 1ARRAY и 1EARRAY, принимая номер начального элемента равным I. (Помните о необходимости ввести проверку ошибки в EARRAY.) 3. Напишите новые версии слов CARRAY и ARRAY, назвав их OCARRAY и OARRAY, которые инициализируют все элементы массива-ребенка нулями. 4. Напишите определяющее слово PRESERVE, которое должно скомпилировать все слова, находящиеся в массиве, в именованный массив. 5. Модифицируйте слово, определенное в упражнении 4 (назовите его SAVE-TO-RETURN), которое при исполнении возвращало бы в стек числа в первоначальной последовательности. 6. Это полезное, хотя и не очень приятное упражнение. Создайте слово .WORD, производные слова от которого при исполнении просто печатают свое имя. Таким образом, в результате исполнения .WORD .Nasty будет создано слово .Nasty, которое будет печатать на экране "Nasty".
Отвлечение: реализация игры "Жизнь"
Большинство из тех, кто был связан с работой на-компьютерах в 70-х гг., по крайней мере, слышали что-либо об игре "Жизнь", придуманной английским математиком Джоном Конвейем. Статья Мартина Гарднера в рубрике "Математические игры" в журнале Scientific American вызвала повальное увлечение, которое привело, как говорят, к такому расточению машинного времени на ЭВМ, как ни одна другая проблема. В наше время вследствие возросшего искусства программистов эта задача не представляется более бросающей вызов. (Например, с MMSFORTH поставляется программа, занимающая всего 5 блоков.)
Принципы игры "Жизнь" очень простые: перед началом игры с помощью простого редактора на экране дисплея изображаются колонии "клеточных бактерий" (представленные простыми графическими образами или буквами). После того как введена картина их исходного расположения, начинается жизнь первого "поколения". У каждой клетки имеется восемь соседних позиций
Для программирования игры "жизнь" нужно создать два массива. Первый массив - это массив клеток, изображаемых на экране. Второй - массив числа соседей каждой клетки. После завершения подсчета числа соседей значения элементов массива используются для определения мест, где клетки умирают, продолжают жить или возникают. Эта информация используется для обновления первого массива, который будет представлять следующее поколение. Вас может заинтересовать программирование игры "Жизнь" на Форте, однако мы ограничимся здесь составлением программы только для одномерного случая игры LINELIFE, в которой каждая клетка может иметь только двух соседей: слева и справа. В этом случае умирает клетка, не имеющая соседей или имеющая двух соседей, но те, которые имеют только одного соседа, будут выживать. Если соседи находятся по обе стороны от незанятой позиции, то в ней возникает новая клетка. Задача составления этой программы для нас состоит не столько в том, чтобы сделать интересную игру, сколько в том, чтобы рассмотреть еще один пример программирования с использованием конструкции слов-определителей CREATE...DOES.
Программа начинается с объявления констант 42 CONSTANT CHAR 66 CONSTANT LLEN где CHAR - это символ "*", используемый для изображения клетки, а LLEN - это увеличенная на 2 длина строки, выводимой на экране. Слово LLEN делается на два больше длины выводимой строки для того, чтобы программа подсчета числа соседей могла работать и с крайними позициями строки. Следующим шагом будет резервирование места для двух массивов с помощью CREATE IMAGE LLEN ALLOT IMAGE 1+ CONSTANT *IMAGE CREATE CALCS LLEN ALLOT CALCS 1+ CONSTANT *CALCS где IMAGE - это строка, которая должна быть выведена на экран, а CALCS - массив информации о числе соседей каждой клетки.
Первые позиции в IMAGE и CALCS названы соответственно #IMAGE и #CALC. Теперь мы определим слова : CLEAR- IMAGE IMAGE LLEN 32 FILL ; (очистка_изображения) : CLEAR-CALCS CALCS LLEN 0 FILL ; (очистка__счетчика_соседей) для того, чтобы производить очистку массивов, заполняя IMAGE пробелами и CALCS нулями. Для каждой клетки, обнаруженной в массиве IMAGE, мы должны в соответствующих элементах справа и слева от этой клетки массива CALCS увеличить их содержимое на единицу. Эта задача решается двумя словами: : INCS ( адр --) >R R@ C@ 1+ R@ С! (Слева от клетки) R@ 1+ С@ 1+ R@ 1+ С! (На месте клетки) R@ 2+ С@ 2 R> 2+ С! (Справа от клетки) ; : INC-CALC ( п -) DUP *IMAGE + С@ CHAR = IF *CALCS + 1- INCS ELSE DROP THEN ; где слово INCS инкрементирует байты по трем адресам: слева, справа и в текущей позиции, а слово INC-CALC анализирует n-й символ массива IMAGE, не равен ли он CHAR (т.е. символу, изображающему клетку), и если это так, то добавляет 1 к соответствующим элементам в массиве CALCS. Подсчет соседей в каждой позиции для всей строки производится при помощи слова: : CALCULATE CLEAR-CALC LLEN 0 DO I INC-CALC LOOP :
Оно занимается тем, что рассчитывает изменяющиеся значения элементов массива CALC для текущего расположения клеток в массиве IMAGE, затем информация, имеющаяся в массиве CALC, должна быть переведена в новую картину, соответствующую новому поколению клеток. Последнее выполняется процедурой : MAKE-IMAGE CLEAR-IMAGE LLEN 0 DO *CALCS I + C@ DUP 1 = SWAP 2 = OR IF CHAR *IMAGE I + C! THEN LOOP ; которая очищает массив IMAGE, затем с помощью цикла проходит по всем позициям массива CALCS, определяя, равно ли значение элемента 1 или 2, и если это так, то в массив IMAGE, выводимый на экран, помещается клетка (т.е. символ CHAR). Это все, что касается собственно логики программы. Остальная часть программы служит для того, чтобы загрузить ее, записать начальные значения в массивы и установить начальные условия, т.е. инициализировать эволюцию LINELIFE.
Рассмотрим слово-определитель MAKEDO.
Оно может порождать другие слова, которые будут забирать из стека различное число параметров, компилировать их и потом использовать для инициализации начала игры LINELIFE. Каждое число, которое берет из стека MAKEDO, соответствует позиции, куда должна быть помещена клетка в массив IMAGE перед началом игры; это дает вам возможность связать описание любого количества исходных картин расположения клеток с именем. Такой подход представляет собой простую альтернативу созданию специального редактора для ввода картины начала эволюции : MAKEDO CREATE DEPTH DUP С, 0 DO С, LOOP DOES> CLEAR-IMAGE DUP C@ 1+ 1 DO DUP I + C@ * IMAGE + CHAR SWAP C! LOOP DROP ;
Слово MAKEDO заслуживает более внимательного рассмотрения: : MAKEDO (Имя слова-определителя) CREATE (Компилирует заголовок слова-ребенка) (Помещает в слово-ребенок адрес кода стадии исполнения) DEPTH (Помещает в стек значение его глубины) DUP С, (DUP и компилирует глубину стека) 0 DO (Начинает просмотр стека DEPTH раз...) , (Компилирует каждый элемент из стека) LOOP (пока они не кончатся) DOES> (Начинает определение стадии исполнения слова-ребенка) (Помещает в стек адрес содержимого слова-ребенка) CLEAR-IMAGE (Перед началом очищает массив IMAGE) DUP ( -- адр адр) С@ ( -- адр глубина) (Количество чисел в стеке) 1+ ( - адр глубина+1) 1 DO (Начинает цикл извлечения скомпилированных позиций в стек) DUP ( -- адр адр) I ( Индекс цикла, начинает извлечение с 1-й позиции) + ( -- адр адр+i) (Адрес байта) С@ ( Извлекает элемент i-й позиции из слова-ребенка) *IMAGE ( -- адр адр+i адр) ( Показывает первую клетку в IMAGE) + (Адрес, по которому клетка запоминается в IMAGE) CHAR ( -- адр адр_клетки сммв) ( симв - символ клетки) SWAP ( -- адр симв адр_клетки) С! ( Записывает клетку в массив IMAGE) LOOP ( - адр) ( Проходит по всем позициям) DROP ( -- ) ( После завершения удаляет ненужный адрес) ; (Конец определения) Определение слова MAKEDO позволяет скомпилировать произвольное число клеток в LINELIFE, не указывая в явном виде, сколько их есть.
Таким образом, как 1 17 32 MAKEDO 1DO так и 2 3 4 15 16 22 33 40 MAKEDO 2DO являются одинаково правомерными определениями. При исполнении слова, определенного с помощью MAKEDO, вначале осуществляется очистка массива IMAGE, после чего производится произвольное размещение клеток в соответствии с заданным при компиляции слова-ребенка. Это первоначальное расположение клеток будет использовано для начала игры LINELIFE. Чтобы показать текущее расположение клеток, мы используем слово : SHOW-IMAGE *IMAGE LLEN 2 - -TRALING TYPE CR ;
Оно выводит LLEN байтов и делает возврат каретки. Теперь мы можем написать главное слово LINELIFE, которое показывает текущее состояние массива IMAGE (которое также порождено словом MAKEDO) и производит расчеты для неопределенного количества поколений, основываясь на начальном размещении клеток и правилах игры. Это слово определяется следующим образом: : LINELIFE CR BEGIN CALCULATE MAKE-IMAGE SHOW-IMAGE 0 UNTIL ; и может быть использовано в форме 1DO LINELIFE, 2DO LINELIFE и т.д. Несмотря на то, что программа LINELIFE сравнительно простая (и не столь интересная, как настоящая программа игры "Жизнь"), она дает нам возможность необычного использования определяющих слов в практических целях. Благодаря тому, что слово MAKEDO может обращаться с любым числом позиций в стеке при компиляции производных слов, нам удалось обойтись без редактора, необходимого для задания первоначального расположения клеток, которое требуется для начала игры LINELIFE. Слова, порождаемые MAKEDO, позволяют очень просто запоминать исходное состояние клеток для последующих проходов игры.
Прикладная программа на языке ФОРТ для сбора данных
Одной из наиболее важных областей применения Форта является взаимодействие с внешними устройствами компьютера. В нашем заключительном примере мы покажем использование конструкции CREATE... DOES> для облегчения сбора и анализа данных в реальном масштабе времени с помощью компьютера. Этот пример был взят из практического применения Форта одним из авторов в его исследовательской работе.
Собственно говоря, он изучил Форт именно потому, что ни один другой язык не позволял ему так просто добиться того, что он хотел. Задача состояла в том, чтобы снять значения разнообразных показателей свойств воды в озере с помощью датчиков, ввести значения в компьютер, а затем обработать данные, чтобы понять их изменения и взаимосвязь.
Общее число датчиков может доходить до 48, они включают в себя фотоэлементы, электроды для измерения концентрации ионов водорода (рН), приборы для измерения прозрачности воды и измерения потоков, а также электроды для измерения растворенного в воде кислорода. Сборки с датчиками погружались в воду озера. Перед нами стояла задача иметь возможность наблюдать, что измерено в данный момент, а также результаты измерений за последние 24 ч. Датчики через кабели соединялись с компьютером, который находился на берегу. Каждый прибор выдавал на выходе напряжение от 0 до 1000 мВ, пропорциональное измеряемой физической величине. Выход его был соединен с аналого-цифровым преобразователем, чтобы получить цифровую величину для ввода в компьютер через порты ввода. Для нашего примера мы будем предполагать, что написана программа на языке Форт, и с помощью нее величины, выраженные в милливольтах, вводятся в массив PORT-DATA так, что, например, с помощью 1 PORT-DATA @ можно положить в стек текущее значение из порта номер 1 в милливольтах. Программу для сбора данных написать несложно, но она будет сильно зависеть от применяемого компьютера и версии языка Форт.
Задача для нас состоит в том, чтобы преобразовать напряжение на входе в фактические значения рН, температуры и т.д. перед тем, как в дальнейшем представить их в виде таблиц, графиков и записей на диске; при этом для каждого датчика имеется своя функция преобразования. Мы ограничимся здесь только температурными измерениями, поскольку процедуры измерения других параметров во многом похожи. Любой датчик температуры дает на выходе напряжение, которое связано с температурой линейной пропорциональностью, т.е.
Т = aV + b, где Т - температура, V - напряжение, а и b - константы, которые определяются при калибровке и для каждого датчика имеют свои значения. Нам нужно для каждого датчика создать слово, которое будет брать из стека значение напряжения, рассчитывать значение температуры (на практике умноженное на 1000) и запоминать результат в массиве RESULT. Для создания слов, например, 1ТЕМР, 2ТЕМР и т.д. можно использовать слово-определитель port# a b VOLT-TO-TEMP nTEMP (#порта) где nТЕМР - слово-ребенок. Например, если для датчика температуры с номером 8, подключенного к порту номер 32, уравнение имеет вид: Т == 26v + 1200, тогда соответствующее ему слово для преобразования милливольт в значение температуры выглядит так: 32 26 1200 VOLT-TO-TEMP 8TEMP а слово для преобразования напряжение-температура VOLT-TO-TEMP можно определить следующим образом: : VOLT-TO-TEMP (Имя определяющего слова) CREATE (Компилирует заголовок слова-ребенка) (Помещает в слово-ребенок адрес кода стадии исполнения) ROT , SWAP , , (Компилирует #порта, а, b) DOES> (Начинает определение стадии исполнения слова-ребенка) (Помещает в стек адрес содержимого слова-ребенка) >R ( -- ) (Помещает адрес в стек возвратов) R@ @ ( -- #порта ) (Помещает в стек номер порта) PORT-DATA @ ( -- данные ) (Извлекает значение напряжения из порта n) R@ 2+ @ ( -- V а ) ( Извлекает а ) * ( -- V*a ) (Вычисляет значение переменной компонента) R@ 4 + @ ( -- V*a b ) (Извлекает b) + ( -- результат ) (Рассчитывает температуру) R> @ ( -- результат #порта ) (Помещает в стек номер порта) RESULT ! ( -- ) (Запоминает результат в n-й позиции, массива) ; (Конец определения) где начальные значения компилируются в слово-ребенок в такой же последовательности, в какой они появляются в стеке. При исполнении слова-ребенка, например 8TEMP, значение берется из соответствующего порта (в данном случае 32-го), преобразуется в температуру благодаря исполнению слов, находящихся в его определении после слова DOES>. В итоге результаты запоминаются в ранее определенном массиве RESULT.
Аналогичные определяющие слова существуют для каждого типа датчика, например, рН-измерителя, датчика содержания кислорода и т.д. Они будут отличаться после слова DOES>, поскольку отличаются уравнения для преобразования измеренных величин для каждого типа датчика.
Адреса производных слов помещаются в массив для векторного исполнения, который называется CONVERT- VECT, в порядке следования номеров портов, относящихся к датчикам портов.
Главное слово, которое помещает в массив RESULT результаты измерений, может быть определено довольно просто: : CONVERT-VOLTS 48 0 DO CONVERT-VECT I 2 * + @ EXECUTE ;
Хотя мы показали вам только часть программы (сбор данных, их хранение и представление гораздо сложнее), вы убедились, что слова-дети, порождаемые определяющими словами, можно легко приспосабливать к различным типам датчиков с учетом индивидуальных калибровочных характеристик. С помощью определяющих слов калибровочные параметры компилируются непосредственно в индивидуальное слово для каждого датчика. Это пример того, что с помощью определяющих слов программа на исходном языке становится более удобочитаемой, структурированной и компактной.
Выводы
Возможность создавать новые определяющие слова - пожалуй, единственное наиболее мощное средство программирования на языке Форт. Если использование конструкции CREATE.. .DOES> кажется вам странным или даже отпугивающим, не поддавайтесь искушению обойтись без них. Лучше попытайтесь поработать над словами собственного изобретения, чтобы развить первоначальное интуитивное представления о процессах, происходящих при определении новых слов. Любую программу, в которой требуются повторяющиеся определения, можно сделать более изящной, если применить слово-определитель для создания класса слов, выполняющих сходную работу. Такие программы будет легко разрабатывать, они станут более компактными и удобочитаемыми.
В гл.16 мы обсудим программирование на Форт-ассемблере, который в двух существенных моментах имеет прямое отношение к словам-определителям.
Во-первых, Форт- ассемблер представляет пример использования всей мощи слов-определителей. Так, ассемблер для микропроцессора 8080 описывается с помощью всего трех блоков исходного кода, при этом в него включены слова для организации ветвлений и циклов, а для его реализации требуется всего пять слов-определителей, чтобы создать 70 слов мнемоники ассемблера. Во-вторых, если слова, порождаемые словами-определителями, исполняются недопустимо медленно, то вы можете определить их действие на стадии исполнения с помощью слова ;CODE (если вы располагаете Форт-ассемблером). Это слово используется в определениях вместо DOES>, что позволяет применить ассемблерную мнемонику для описания действий порождаемого слова на стадии исполнения. В гл.16 мы используем слово ;CODE для создания версии слова ARRAY на ассемблере. Применение слов-определителей может коренным образом изменить ваш стиль программирования. Через некоторое время вы будете удивляться, как вы могли раньше обходиться без них.
Редакторы Форта
Одной из важнейших частей любого языка программирования для ЭВМ является редактор, который служит для ввода и видоизменения программы, в этом отношении Форт не исключение. Удобной особенностью Форта является то, что редактор написан на Форте и может, как и всякая другая программа, легко изменяться с целью адаптации к вашим нуждам и вкусам. Так как редактор - это программа, которую вы будете использовать при программировании на форте наиболее часто, имеет смысл изменить его, если вы не удовлетворены редактором, который вам достался вместе с системой.Попыток стандартизировать Форт-редактор не предпринималось, за исключением того, что слово EDITOR используется в качестве имени словаря редактирующих операторов (словари рассмотрены в гл.14). При таком разнообразии на рынке аппаратуры (в дополнение к разнообразию мнений о том, что такое хороший редактор), достаточно бессмысленна попытка навязать редактор, приемлемый для всех.
Существует два типа редакторов, поставляемых обычно с Фортом (часто с одной и той же системой). Первый из них строчный редактор, который в соответствии с названием дает возможность модифицировать содержимое блоков форта строчка за строчкой (предполагается, что блок Форта содержит 16 строк по 64 символа). Хотя строчный редактор занимает мало места в памяти и обладает высоким быстродействием, ему свойственны ограничения, связанные с тем, что вы не видите изменений, которые вносите в текст блока в целом. Вторым типом Форт-редактора является экранный редактор, где модификации происходят в месте текста, отмеченном курсором, который можно переместить куда угодно в пределах блока, отображенного на экране. Экранный редактор проще использовать, чем строчный, хотя и за счет большего объема занимаемой памяти.
В этой главе мы рассмотрим типичный, но довольно простой строчный редактор, а затем предложим исходный текст экранного редактора. Если у вас имеется только строчный редактор, вы сможете его использовать для написания предложенного здесь экранного редактора.
Владея работающим экранным редактором, вы можете его использовать для упражнений, предлагаемых в книге, так же как и для совершенствования самого редактора. Даже если вы вполне удовлетворены вашим редактором (многие версии Форта снабжены редакторами, более совершенными, чем приведенный здесь нами). вам следует рассмотреть некоторые наши идеи, прежде чем переходить к гл.13, где описана структура редактора.
Основы редактирования для Форта
Как было показано в гл.10, ввод-вывод для диска осуществляется через резервные зоны памяти, называемые блочными буферами. Как вы знаете, ключевым словом для ввода с диска служит BLOCK, которое загружает содержимое блока с диска в свободный блочный буфер и заносит в стек адрес первого байта этого буфера. Напомним также, что ключевыми словами для спасения блоков информации на диске являются UPDATE, которое помечает буфер блока, чтобы он был спасен, и FLUSH (или SAVE-BUFFERS), которое записывает на диск содержимое буферов, помеченных UPDATE.
Редактирование производится путем переноса содержимого блока с диска в буфер, изменения содержимого этого буфера и пометки буфера таким образом, что его измененное содержимое будет снова записано на диск. В действительности при работе с экранным редактором текст из буфера переносится в другую область памяти, где производится редакторская правка до возвращения текста обратно в буфер. В этом случае измененный текст из буфера редактора переносится в блочный буфер только при условии, что сделанные редактором изменения следует занести на диск. Если текст всегда редактируется непосредственно в блочном буфере, вы будете вынуждены использовать EMPTY-BUFFERS каждый раз , когда вы решите отменить некоторые редакционные исправления. Измененный текст в буфере редактора должен быть перенесен в блочный буфер прежде, чем он сможет быть записан на диск, таким образом уменьшится вероятность занесения на диск нежелательных изменений.
Строчное редактирование
Строчный редактор Форта состоит из набора слов, которые используются для изменения содержимого блоков Форта строчка за строчкой.
Строчный редактор не является "унифицированной" программой, в которой каждое слово, предназначенное для редактирования строки, применяется независимо для изменения отдельной строки текста. Команда n LIST в начале процедуры редактирования присваивает номер блока переменной SCR и отображает содержимое блока, который будет редактироваться (SCR не нужно для системы Форт-83, но большинство диалектов ее используют).
Слова строчного редактора используют число в SCR для идентификации блока, который должен редактироваться, и, кроме того, многие из них требуют при исполнении наличия в стеке номера строки. В табл. 12.1 представлены некоторые типичные команды строчного редактора, которые использовались в устаревшей версии MMSFORTH V 1.9 (MMSFORTH в настоящее время поставляется с мощным экранным редактором). Вы сами можете описать эти слова, основываясь на знаниях, приобретенных в гл.10.
Таблица 12.1. Команды строчного редактора MSFORTHV 1.9.
n A текст Добавить строку, сдвинуть вниз остальные строки: строка помещается в PAD-буфер n D Стереть строку, сдвинуть вверх остальные строки; строка помещается в PAD-буфер n I Ввести строку, сдвинув остальные строки вниз; строка извлекается из PAD-буфера n P текст Поместить текст, следующий за "Р", в строку n n R Заменить строку n строкой из PAD-буфера S Выйти из редактора и спасти все изменения n Т Отобразить на экране строку n; поместить строку в PAD-буфер Q Выйти из редактора без спасения внесенных изменений
При выполнении команды LIST номер блока запоминается в SCR, а его содержимое заносится в буфер. Большинство операторов строчного редактора используют последовательность SCR @ BLOCK для получения адреса памяти, куда был загружен блок с диска (блочный буфер). Редактирование производится непосредственно в блочном буфере, и дополнительно требуется только 64 байта памяти сразу за словом PAD. Эта память служит для размещения строки, которая стерта или введена, так что она может быть позднее использована для ввода или замещения фрагмента текста в блочном буфере.
Простота этого редактора привлекательна, но его возможности, очевидно, весьма ограничены.
Более мощные строчные редакторы снабжены командами для поиска, добавления или замещения фрагмента текста в пределах строки, блока или серии блоков. Эти или другие улучшения могут сделать строчное редактирование более гибким и эффективным. Простой строчной редактор занимает очень мало места в памяти и работает на любом дисплее. Но даже после улучшения строчный редактор не сможет сравниться в простоте использования с экранным редактором.
Экранное редактирование
Экранный редактор представляет собой программу (в противоположность совокупности слов, составляющих строчный редактор), которая загружает содержимое блока в буфер, отображает текст и позволяет изменить текст в соответствии с положением курсора на экране. Все вводы с клавиатуры перехватываются редактором, который распознает вводимый код и выделяет коды, генерируемые командными клавишами, в то время как все прочие "печатные" символы используются для изменения текста. Экранный редактор просто использовать, так как измененный текст всегда на экране, а применение курсора для отметки места, где надлежит что-то изменить, является вполне естественным. Экранный редактор может быть дополнен многими новыми возможностями, такими как дополнительные строчные буферы, так называемые "теневые блоки" для обширных комментариев (см.гл.10), автоматическая простановка даты (дата ставится на верхней строке), средства для чтения и ввода двоичных величин при редактировании блоков данных и многое другое.
Экранный редактор
Наш экранный редактор требует, чтобы ваша версия Форта и (или) ваш видеотерминал имели некоторый контроль над тем, что отображается на экране. Как минимум, вы должны быть способны очистить экран и поместить курсор в верхний левый угол, а также установить курсор в указанную точку экрана. Способы реализации этих функций сильно варьируются от ЭВМ к ЭВМ и от терминала к терминалу. Большинство удаленных терминалов воспринимают последовательности управляющих символов, посланных оператором EMIT, и за счет этого управляют изображением на экране.
Многие ЭВМ со встроенными дисплеями также воспринимают управляющие символы, и кроме того, работают со словами Форта, предназначенными для перемещения курсора и управления экраном. Например, MMSFORTH использует слово PAGE для очистки экрана и РТС для перемещения курсора в позицию, заданную числами в стеке. Мы написали этот редактор для работы с терминалом Lear-SiegeI ADM-31, и если вы используете другой терминал, вам надо модифицировать программу в блоке 2.
Кроме минимальных возможностей по очистке экрана и перемещению курсора многие терминалы (встроенные дисплеи) имеют много других функций. Например, часто бывает предусмотрена возможность стирания символов в интервале от места, отмеченного курсором, до края строки перемещения курсора вверх или вниз на одну строку и т.д. Если ваш дисплей позволяет делать такие вещи, то вы можете сделать работу редактора более удобной, хотя эти усовершенствования, строго говоря, и не являются обязательными. Мы дадим вам советы, как выполнить эти модификации. Наконец, некоторые встроенные дисплеи имеют ЗУ, в которых изображение хранится в виде ASCII символов и которые непрерывно опрашиваются с помощью схемотехнических средств, а их содержимое отображается на экране. Если ваша ЭВМ работает таким образом, можно сделать намного более эффективный редактор. Мы дадим вам некоторые предложения по поводу такого редактора в гл.13, где обсудим структуру этого редактора.
Приведенный ниже текст программы редактора содержит различные советы по модификациям, которые могут сделать его использование более удобным и эффективным. Программа составлена, однако, с тем расчетом, чтобы максимально упростить ее применение и сделать это возможным на максимальном числе ЭВМ. Мы полагаем, что вы используете ее сначала только с совершенно необходимыми, которые совершенно необходимыми, т.е. изменив лишь
0 ( 20 июля 85 Экранный редактор NS 01 из 10) 1 : TASK ; DECIMAL 2 3 32 CONSTANT BL 102/1 CONSTANT 1K 4 64 CONSTANT 64 63 CONSTANT 63 5 10000 CONSTANT PDELAY 5000 CONSTANT SDELAY 6 VARIABLE SCR VARIABLE ROW 7 VARIABLE COL VARIABLE I/R 8 VARIABLE LOWBLK VARIABLE HIGHBLK 9 ( запоминание первого и последнего блоков, которые можно редактировать на вашей ЭВМ) 10 1 LOWBLK ! 169 HIGHBIK ! 11 ; --- ; ( слово, не выполняющее никакой работы) 12 : MODE ( -- ) I/R @ 0= I/R ! ; ( выбор ввод-замещение ) 13 : DELAYS ( n -- ) 0 DO LOOP ; ( задержка на n циклов ) 14 : PAUSE ( -- ) PDELAY DELAYS ; ( длинная задержка) 15 : SPAUSE ( -- ) SDELAY DELAYS ; ( короткая задержка )
0 ( 20 июля 85 Экранный редактор NS 02 из 10) 1 : PADDR ( n--) CREATE , DOES> @ PAD + ; 2 1K 2 * PADDR BLINE ( последняя строка кольцевого буфера) 3 1K 2 * 64 + PADDR LBUFF ( адрес строки для копирования-замещения) 4 : CONTROL ( с--) CREATE DEPTH DUP С, 0 DO DEPTH ROLL С, 5 LOOP DOES> DUP DUP C@ + SWAP DO I 1+ C@ EMIT LOOP ; 6 27 42 CONTROL ( очистка экрана, курсор вверх влево) 7 : 27 EMIT 61 EMIT ROW @ 32 + EMIT COL @ 32 + EMIT ; 8 ( положение курсора в строке Х и столбце Y) 9 ( : PAGE ; : ROW @ COL @ PTC ;)
10 ( Чтобы использовать далее , , , и ) 11 ( необходимо описать их здесь, используя лишь в случае 12 отсутствия прямого позиционирования курсора) 13 ( 28 CONTROL ( перемещение курсора вверх влево) 14 ( : ROW @ ?DUP IF 0 DO LOOP THEN ) 15 ( COL @ ?DUP IF 0 DO LOOP THEN ;)
описания и в блоке 2. Вы можете позднее использовать сам редактор для внесения последующих модификаций, которые допускает ваша ЭВМ, ваша версия Форта и ваш дисплей.
Хотя это весьма простой редактор, он включает в себя некоторые функции, которые полезны и в то же время необычны. Даже если вы удовлетворены редактором, поставленным с вашей версией Форта, для вас будет, вероятно, полезно изучить эту программу, особенно в связи с тем, что мы используем ее в качестве примера того, как следует укладывать и комментировать исходный текст программы.
Если ваша версия Форта содержит пользовательскую переменную SCR, вам следует удалить описание SCR со строки 6. Вы должны также заменить числа 1 и 169 в строке 10 на номера первого и последнего блоков, которые может модифицировать редактор в вашей системе. CONTROL представляет собой слово-описатель, которое формирует слова, предназначенные для генерации последовательностей управляющих символов, число которых задается числом в стеке. Хотя это слово используется здесь только один раз (для в строке 9), оно включено в текст, чтобы позволить вам описать другие управляющие последовательности. Слово определено так, что оно посылает на терминал символ 27, за которым следует код 42.
Описания и в строках б, 7 и 8 выполнены для терминала ADM- 31 и, вероятно, должны быть изменены для вашего оборудования. Описание в строке 7 показывает типовой способ прямого позиционирования курсора. ADM-31 требует ESC- последовательности из четырех символов вида
ESC "=" (строка) + 32 (столбец) + 32,
где строка (row) и столбец (column) равны номерам строки и столбца, куда должен быть установлен курсор (0 - 23 и 0 - 79).
Когда ESC и "=" (ASCII коды 27 и 61) переданы, к кодам строки и столбца добавляется 32, после чего они посылаются на терминал. Редактор использует переменные ROW и COL для отслеживания положения курсора.
Если ваша версия Форта содержит слова для очистки экрана и перемещения курсора, вы захотите использовать описания на строке 9 (не забывайте поместить в скобки прежние описания). В MMSFORTH слово PAGE очищает экран и устанавливает курсор в верхний левый угол, в то время как PTC устанавливает курсор в положение, заданное кодами в стеке. Вам следует подставить эквивалентные слова из вашей версии Форта. Если у вас нет средств для непосредственного управления положением курсора с помощью слова типа PTC или управляющих кодовых последовательностей, вы можете использовать строки 13 и 14,
0 ( 20 июля 85 Экранный редактор NS 03 из 10) 1 : @CURSOR ( --r с ) ROW @ COL @ ; ( вызов строки и столбца ) 2 : ! CURSOR ( r c--) COL ! ROW ! ; ( запоминание столбца и строки) 3 ( Опишите здесь, если возможно, и и используйте в блоках 3 и 4) 4 : 0 COL ! ; ( перемещение курсора в начало строки ) 5 : 0 0 !CURSOR ; ( перемещение курсора вверх влево ) 6 : BABOVE ( --n ) ROW @ 64 * ; ( число байтов в рядах выше курсора ) 7 : BBELOW ( --n ) 16 ROW @ - 64 * 1К + ; ( байт ниже курсора ) 8 : OFFSET ( --n ) BABOVE COL @ + ; ( число байт в буфере перед курсором ) 9 10 : CPOS ( --адр ) PAD OFFSET + : ( адрес курсора ) 11 : LSTART ( --адр ) PAD BABOVE + ; ( адрес начала строки ) 12 : LEND ( --адр ) LSTART 63 + ; ( адрес конца строки ) 13 14 : BLEFT ( --n ) LEND CPOS - ; ( число байтов в строке слева от курсора) 15
но вам нужно сначала описать слова для перемещения курсора в верхний левый угол (без стирания чего-либо), для сдвига курсора вниз на одну строку и для перемещения курсора на одну позицию. Эти слова могут быть описаны с помощью управляющих кодовых последовательностей, которые описаны с привлечением CONTROL. Эти модификации программы для и - единственное, что необходимо, чтобы заставить редактор работать на любом дисплее, который отображает по крайней мере 16 строк по 64 символа в каждой. Изменения, предлагаемые для других блоков, являются чисто рекомендательными, но сделают работу с редактором
0 ( 20 июля 85 Экранный редактор NS 04 из 10 ) 1 ( Если возможно, Опишите ,,, здесь ) 2 ( для того, чтобы использовать вместо в описаниях ) 3 ( в строках 4, 5, 6 и 7. ) 4 : LEFT COL @ 0 > IF -1 COL +! ( или ) THEN ; 5 : RIGHT COL @ 63 < IF 1 COL +! ( или ) THEN ; 6 : UP ROW @ 0 > IF -1 ROW +! ( или ) THEN ; 7 : DOWN ROW @ 15 > IF 1 ROW +! ( или ) THEN ; 8 : ( -- ) BLEFT SPACES : ( стереть конец строки ) 9 : NEWLINE ( -- ) DOWN ; ( курсор в начало следующей строки ) 10 : SHOWLINES ( -- ) @CURSOR 16 ROW @ ( отображение строк ) 11 DO I ROW ! LSTART 64 -TRAILING TYPE LOOP 12 ! CURSOR ; 13 : SHOWBLK ( -- ) @CURSOR SHOWLINES ! CURSOR ; 14 : TYPELINE ( -- ) CPOS BLEFT 1+ OVER OVER TYPE -TRAILING 15 SWAP DROP BLEFT 1+ - SPACES ;
удобнее и производительнее.
Ваш видеодисплей или терминал может воспроизводить управляющие кодовые последовательности для перемещения курсора в начало строки или в верхний угол экрана. Если это так, то вам следует использовать CONTROL для описания так, чтобы перемещать курсор в начало строки, и для установки его вверх влево. Эти слова могут затем использоваться вместо в описаниях и в строках 4 и 5. Это сделает редактирование более удобным и быстрым.
Редактор будет более быстрым и удобным, если ваше оборудование позволяет и вы можете заменить в строках 4-7 словами, описанными в строках с 1-й по 3-ю или ранее. должно перемещать курсор на предшествующую позицию, - на одну позицию вперед.
Аналогично слово должно перемещать курсор вверх на одну строку. Эти слова могут, вероятно, быть описаны с помощью CONTROL. Ваше оборудование может также позволять замену описания в строке 8 на управляющую последовательность, описанную через CONTROL.
0 ( 20 июля 85 Экранный редактор NS 5 из 10 ) 1 2 ; BCLEAR ( -- ) PAD 1K + 1152 BL FILL ; ( очистка буфера ) 3 : SCLEAR ( -- ) PAD 1K BL FILL SHOWBLK ; ( Очистка экрана ) 4 5 6 : LOADBLK ( -- ) SCR @ BLOCK PAD 1K CMOVE ; ( загрузка блока ) 7 : RESTORE ( -- ) LOADBLK SHOWBLK ; 8 9 : (UPDATE) ( -- ) PAD SCR @ BLOCK 1K CMOVE UPDATE ; ( пометка для спасения ) 10 11 : +BLK ( -- ) SCR @ HIGHBLK @ < IF 1 SCR +! RESTORE THEN ; 12 : -BLK ( -- ) SCR @ LOWBLK @ > IF -1 SCR +! RESTORE THEN ; 13 14 : CLRTOP ( -- r c ) @CURSOR ; 15 : WRITETOP ( r c--) TYPELINE !CURSOR ;
0 ( 20 июля 85 Экранный редактор NS 06 из 10 ) 1 : ?BLK# ( -- ) CLRTOP ." * * * BLOCK#Ж " SCR @ . ( блок# ) 2 PAUSE WRITETOP ; 3 4 : UPDATES ( -- ) (UPDATE) ( пометить блок и показать это ) 5 CLRTOP , " * * * UPDATED Block#: " SCR @ . SPAUSE WRITETOP ; 6 7 : ?CLEAR ( --) CLRTOP ." * * *" X-OUT: (B)uffer, (S)creen ?" 8 KEY DUP 66 = IF DROP BCLEAR 9 ELSE 83 = IF SCLEAR !CURSOR EXIT THEN 10 THEN WRITETOP ; 11 : ?EXIT ( f--) CLRTOP ." * * * EXIT:(S)ave. (Q)uit ?" 12 KEY DUP 83 = 13 IF DROP DROP DROP UPDATE FLUSH 1+ EXIT 14 ELSE 81 = IF DROP DROP EMPTY-BUFFERS 1+ EXIT THEN 15 THEN WRITETOP ;
0 ( 20 июля 85 Экранный редактор NS 07 из 10) 1 : OPENUP ( -- ) ( раздвинуть текст в месте буфера, куда указывает курсор) 2 COL @ 64 < IF CPOS DUP 3 1+ BLEFT ) BL CPOS C! THEN ; 4 5 : OPEN ( -- ) OPENUP TYPELINE ; ( раздвинуть текст в месте, указанном курсором) 6 : TRUNC ( - ) CPOS BLEFT BL FILL ; ( укоротить строку) 7 8 : OVERTYPE ( СИМВ-- ) COL @ 64 < ( заместить символ, на который указывает курсор) 9 IF DUP EMIT CPOS С! COL +! ELSE DROP THEN ; 10 11 : INSERT ( СИМВ-- ) OPENUP OVERTYPE TYPELINE ; ( ввести символ ) 12 : DELETE ( -- ) COL @ 64 < ( стереть символ ) 13 IF CPOS 1+ CPOS BLEFT CMOVE BL LEND C! 14 TYPELINE 15 THEN ;
0 ( 20 июля 85 Экранный редактор NS 08 из 10) 1 : CLINE ( -- ) LSTART LBUFF 64 CMOVE ; ( копирование строки в буфер) 2 3 : PLINE ( -- ) LBUFF LSTART 64 CMOVE ; ( выложить строку буфера) 4 @CURSOR TYPELINE !CURSOR ; 5 6 : KLINE ( -- ) ( стереть строку, занеся ее в кольцевой буфер) 7 LSTART BLINE 64 CMOVE ( перенос текущей строки ) 8 LSTART 64 + LSTART BBELOW CMOVE ( сдвинуть буфер вверх ) 9 SHOWLINES ; ( отображение измененных строк ) 10 11 : ILINE ( -- ) ( ввести строку из кольцевого буфера ) 12 LSTART DUP 64 + BBELOW ) ( сдвинуть буфер ) 13 BLINE LSTART 64 CMOVE ( перенести строку с низа буфера ) 14 SHOWLINES ; ( отображение измененных строк ) 15
0 ( 20 июля 85 Экранный редактор NS 09 из 10) 1 CREATE KEYVECTORS ] ( исполнительный вектор команд редактора) 2 LEFT ( А курсор влево ) BLK# ( в номер блока ) 3 CLINE ( С копирует строку ) DELETE ( D стирает символ ) 4 ?ЕХIТ ( Е уход из редактора ) --- ( F ) 5 MODE ( G перевод в новый режим ) --- ( H ) 6 ILINE ( I ввод строки ) --- ( J ) 7 KLINE ( К стирание строки ) -BLK ( L последний блок ) 8 NEWLINE ( М или ENTER, CR+LF ) +BLK ( N следующий блок ) 9 OPEN ( O раздвинуть текст ) PLINE ( Р вставить строку ) 10 ( Q курсор на место ) RESTORE ( R восстановить экран ) 11 RIGHT ( S курсор вправо ) TRUNC ( Т укоротить строку ) 12 UPDATES ( U пометить буфер )----( V ) 13 UP ( W курсор вверх ) ?CLEAR ( X очистить буфер экрана ) 14 --- ( Y ) DOWN ( Z курсор вниз ) [ 15 : KEYDO ( n -- ) 1- 2* KEYVECTORS + @ EXECUTE ;
0 ( 20 июля 85 Экранный редактор NS 10 из 10) 1 : EDITCASE ( флаг симв -- флаг ) 2 DUP 27 < OVER 0 > AND ( легальный управляющий символ? ) 3 IF KEYDO ( если так. то исполняем команду ) 4 ELSE DUP 31 > OVER 127 < AND ( если нет, печатный символ ?) 5 IF I/R @ ( если да - смотрим, каков режим ) 6 IF INSERT ELSE OVERTYPE THEN ( и вводим или замещаем ) 7 ELSE DROP --- ( но, если символ непечатный, игнорируем ) 8 THEN 9 THEN ; 10 11 : EDINIT ( blk -- ) SCR ! EMPTY-BUFFERS LOADBLK SHOWBLK ; 12 : EDITLOOP ( -- ) edinit 0 BEGIN KEY EDITCASE DUP UNTIL 13 DROP ; 14 : EDIT ( blk- ) EDITLOOP ; 15 : E SCR @ EDIT :
Многие терминалы и ЭВМ позволяют описать слово, которое генерирует звуковой сигнал. Это часто делается путем выдачи кода CTRL G или ASCII 7, называемого BEL. Вы можете описать как 7 CONTROL
или что-нибудь эквивалентное. Если это так, вы можете заменить "---" в блоках 9 и 10 словом , и ваша ЭВМ выдаст вам звуковой сигнал, если вы напечатаете неописанный управляющий или другой символ, который нельзя использовать.
Использование экранного редактора
Теперь поговорим об использовании редактора. Загрузите блоки редактора и, чтобы отредактировать блок с номером "n", выдайте команду n EDIT. Оформленный соответствующим образом блок появится на экране, курсор будет помещен в верхнем левом углу. Раз SCR содержит номер нужного блока (а так должно быть после выполнения вами процедур EDIT или LIST для этого блока), вы просто нажимаете "Е" для редактирования данного блока. Используя команды, перечисленные в табл. 12.2, вы сможете изменить содержимое блока различными способами, пока не получите удовлетворительного результата путем спасения этих изменений или игнорируя их. Экспериментируйте с редактором, чтобы привыкнуть к нему.
Таблица 12.2. Команды экранного редактора, клавиши и их функции
Команды позиционирования курсора Q Курсор на место - в верхний левый угол. W Курсор вверх на одну строку. S Курсор вправо. Z Курсор вниз. А Курсор влево. М Курсор в начало следующей строки (клавиша делает то же самое).
Команды редактирования символов
О Раздвижка текста, смещение остальной строки на один символ. D Стирание, смещение остальной строки на один символ влево). Т Укорачивание, стирание строки справа от курсора. G Установка режима переключения между режимами ввода и замещения. В режиме замещения буква, на которую указывает курсор, заменяется напечатанной буквой. В режиме ввода напечатанная буква вводится в текст, остальная часть строки смещается вправо.
Команды редактирования строк
С Копирование текущей строки в буфер строки. Р Извлечение строки из буфера и укладка ее на место текущей строки.
К Стирание строки и помещение ее в кольцевой буфер (строка не теряется). I Вывод строки из кольцевого буфера.
Команды редактирования блока
U Перемещение буфера редактора в блочный буфер и пометка его для записи на диск (UPDATE), R Восстановление буфера редактора (стирание изменений, восстановление последней записанной версии). L Переход к редактирований последнего (предшествующего) блока. N Переход к редактированию следующего блока. В Отображение номера блока, редактирование которого производится. Х Заполнение пробелами (X-out) кольцевого буфера или экранного или никакого. Е Уход из редактора с (без) записи на диск текста (вам будет предоставлен выбор).
Необычной особенностью редактора является наличие так называемого "кольцевого буфера", который позволяет не терять строки текста, подлежащего уничтожению (стиранию). Когда очередная строка удаляется, она копируется в область "ниже" экрана, и, если стерто достаточно много строк, они могут снова появиться на экране снизу. Если вы поупражняетесь с процедурами стирания и восстановления строк, станет понятно, что на экране отображается только верхняя половина главного буфера, используемого редактором. Часть ниже экрана никогда не отображается непосредственно, но ее содержимое может быть просмотрено путем стирания 16 строк (при курсоре на верхней строке), чтобы содержимое буфера попало на экран. Теперь исходное содержимое экрана находится в буфере. Стирание или ввод 16 новых строк "вращает" буфер в прямом или обратном направлении, что позволяет восстановить исходное состояние буфера. Буфер редактора имеет кольцевую структуру, так что строки не могут быть потеряны. Эта мера облегчает стирание до 16 строк, чтобы избавиться от них или чтобы переместить их в другой блок, введя их туда.
Существуют два способа, чтобы подавить заполнение "скрытого" буфера стертыми строками, которые вы не желаете хранить. Команда X-out заполнит все 16 строк скрытого буфера пробелами, если в ответ на запрос вы нажмете клавишу "В". (Если же вы откликнетесь, нажав клавишу "S", пробелами будет заполнен экран.
При нажатии любых других клавиш ничего не произойдет.)
Если вы хотите избавиться от одной строки, укоротите ее перед стиранием, что исключит загромождение буфера ненужными строками. Использование кольцевого буфера станет вполне простым после того, как вы поупражняетесь с ним.
Существует много способов улучшить редактор, но за счет усложнения текста программы и увеличения требуемой памяти. Некоторые модификации будут предложены в гл.13, но кое-что следует упомянуть здесь. Как уже было сказано, чисто косметической является замена "---" в блоках 9 и 10 на , после чего редактор будет сигнализировать вам, если нажата неописанная управляющая клавиша или поступил символ, который не может быть отображен на дисплее. Вы можете использовать управляющие символы из нижнего регистра путем добавления нескольких битов в маску (см. гл.З).
Если вы можете заменить символ курсора на вашем терминале, вам следует изменить описание MODE в блоке 1, чтобы использовать курсор для индикации действующего режима ввод-замещение. А если вы хотите сменить функции командных клавиш редактора, поменяйте местами слова в массиве KEYVECTORS.
Существуют и другие улучшения, которые можно добавить, если ваш дисплей может отображать 24 строки по 80 символов. Например, блок, подлежащий редактированию, может размещаться в центре экрана. Границы блока могут быть выделены, а неиспользуемая область вокруг блока может быть использована для отображения номера блока, режима ввода и даже меню команд редактирования. Редактор может быть модифицирован (с определенным трудом) для использования на терминалах, которые отображают менее 1024 символов, но многие преимущества экранного редактора при этом будут утрачены, так как весь блок не будет отображен на экране.
Ясно, что используемый вами редактор будет сильно влиять на ваше отношение к Форту. В равной мере верно и то, что свойства редактора, кажущиеся одним важными, могут рассматриваться другими как излишние (и наоборот), так как редактирование в высшей степени индивидуальный процесс.В гл.13 исследуются причины, почему редактор написан так, а не иначе, даются комментарии и оригинальные тексты программы. После прочтения главы вы должны знать достаточно о написании редактора, для того чтобы модифицировать то, что мы предложим, или чтобы добавить функции к редактору, поставленному вместе с вашим Фортом. Если вы почувствовали, что некоторые свойства вашего редактора вас не устраивают, вы не должны проклинать Форт - в ваших руках средства, чтобы написать редактор, который удовлетворит вас в полной мере.
Программирование на Форт. Стиль
Форт может изменить ваше представление о программировании. Помимо очевидного отличия Форта от других языков (постфиксная, а не прямая нотация, расширяемость, а не фиксированный набор команд), вы можете почувствовать, что форт открывает новые подходы к решению проблем. В этой главе мы рассмотрим стиль программирования форта как с точки зрения решения проблем с помощью программирования, так и с позиции привычек, делающих программирование легче и продуктивнее.В этой главе подробно рассматривается редактор, представленный в гл. 12. Вместо того чтобы просто анализировать, как работает редактор в целом, мы проведем вас через программу и опишем цепочку решений, которые вынудили нас написать редактор именно так. Вы узнаете больше, если мы скажем вам, как мотивировалось наше решение при написании программы. чем если бы мы только объяснили, каким образом работает редактор. Программирование - в равной мере искусство и наука, и невозможно просто дать правила, которым можно следовать слепо. Каждый программист решит проблему по-разному, и гибкость форта поощряет эту индивидуальность. Итак, раз вы изучаете редактор, попытайтесь подумать о других возможных путях решения проблемы.
Сначала проблема должна быть рассмотрена в целом. Затем следует разработать слова низкого уровня, которые обеспечат связь между оборудованием ЭВМ и словами высокого уровня, выполняющими многие программные функции. (Конечно, многие из таких слов низкого уровня являются уже частью словаря Форта.) Полезная работа Форт-программы большей частью выполняется словами, которые занимают положение между примитивами и словами высокого уровня, которые связывают все воедино. Создание этих слов,среднего уровня всегда предполагает некоторое число проб и ошибок как в отладочных программах, так и в корректировке неправильного представления об исходной проблеме и о том, как подогнать отдельные части друг к другу, чтобы решить эту проблему. Работа этого метода проб и ошибок станет ясна, если вы посмотрите, как мы написали редактор.
Задание на программу
Если вам рассказали в деталях, что программа должна делать и как она должна это делать, обычно довольно просто ее написать. В действительности вы не пишите программу, а только транслируете набор инструкций из одной формы (словесное описание) в другую (программа Форт). Хотя процесс трансляции может потребовать сосредоточенности, простое написание программы согласно спецификациям - занятие менее увлекательное и менее творческое, чем выполнение всей работы с самого начала. Если вы отвечаете за задание и за написание программы, вы имеете возможность изменить функцию программы в процессе ее написания. Это то место, где начинается творчество, и Форт этому поможет. Так как вы описали слова, то лучше представляете пути решения ваших проблем и возможности вашей программы.
Программирование является упражнением в решении проблем. Нужно не только понять задачу, но и заставить ЭВМ выполнять процедуру, необходимую для решения задачи. Это включает в себя несколько довольно расплывчатых этапов: описание всей проблемы и цели; разделение проблемы на меньшие задачи, решаемые независимо, и, может быть, последующее деление таких задач; написание программы для этих мелких задач; отладка программы, возможно изменение алгоритма для решения главной проблемы или составляющей ее части и снова отладка; сборка составных частей; окончательная отладка всего пакета. Многие языки маскируют этот естественный процесс: описывается набор алгоритмов, определяющих все, что должна делать программа, а затем пишется программа с минимальными по возможности переделками и отладкой.
Форт допускает другой подход; внутренняя природа решения проблемы при этом более прозрачна. Мелкие проблемы могут решаться одна за другой, проверяться, корректироваться и, может быть, если надо, при этом модифицируются другие части программы как следствие обстоятельств, выявленных в процессе отладки. Это возможно в Форте, так как в нем весьма быстро и легко можно изменить и проверить описание слова или нескольких слов.
Мы использовали эту итеративную модификацию слов несколько раз в предшествующих главах частично как способ показа того, как строить слова на базе основополагающих идей, частично для того, чтобы привести примеры процесса написания Форт-программ. Составление программы на Форте включает в себя написание некоторого числа коротких описаний, использование их в других описаниях и, наконец, сборку всей системы в целом обычно в виде одного слова, которое исполняет программу. Но прежде чем что-либо из этого можно было сделать, вы должны иметь очень точное представление о том, что должна делать программа, т. е. что является главной задачей. Проблема должна быть сформулирована таким образом, чтобы ее можно было поделить на задачи, которые решаются путем описания слов Форта.
Это возвращает нас снова к заданию на программу к описанию того, что должна делать программа. Описание высшего уровня представляет ваши интуитивные цели: что бы вы хотели, чтобы делала программа? Точное описание ваших намерений является наиболее важным шагом в написании программы, так как диапазон ваших целей определяет то, насколько будет полезна ваша программа или даже следует ли ее вообще писать. Этот этап постановки задачи и ошибка здесь будет усиливаться по мере вашей работы и может сделать вашу проблему неразрешимой.
Мы решили написать экранный редактор для этой книги не потому, что было бы полезно для читателей его иметь. Много важнее то, что сам процесс написания редактора является хорошим примером процесса написания любой программы средней сложности. Мы написали редактор для работы по возможности с простейшим терминалом, так как мы хотели сделать его как можно более универсальным, имея в виду, что это сильно упростит его использование читателями с другими типами ЭВМ. Но это ограничение сделало также алгоритм более строгим, а процесс программирования и саму программу более интересными. Таким образом мы не могли полагаться на известные характеристики ЭВМ или терминала, что сделало бы работу легче.
Какова же задача экранного редактора?
Конечно, концепция экранного редактора уже включает многое из того, что должна делать программа. Основной функцией редактора являются вход в режим и модификация текста программы, но существует много способов выполнения этого. Фактическим стандартом для входа в экранный редактор Форта является n EDIT. Эта команда должна отображать содержимое блока n, обеспечивать модификацию содержимого различными путями и последующую запись на диск. Текст изменяется путем перемещения курсора в любом направлении и ввода или стирания символов, на которые указывает курсор. Мы бы также хотели, чтобы редактор позволял нам копировать, замещать, вставлять и стирать строки текста, на которые указывает курсор, а также выполнять некоторые другие операции. Мы можем теперь установить определенное число точно заданных, но достаточно общих спецификаций того, что должен делать редактор.
1. Позволять редактировать блок путем выдачи команды EDIT, отображающей содержимое блока в виде 16 строк по 64 символа и установку курсора в верхний левый угол экрана. 2. Позволять перемещать курсор вправо, влево, вверх и вниз путем нажатия определенных клавиш. Позволять стирание символов нажатием другой клавиши. 3. Позволять замещать символы, на которые указывает курсор, если включен соответствующий режим. 4. Позволять переходить в режим ввода, когда символ не печатается поверх существующего, отмеченного курсором, а вводится так, что остальные символы в строке смещаются, освобождая место для нового. Позволять также переход в режим "замещение" (предположительно переключение из режима в режим производится с помощью командного символа). 5. Позволять стирать строки текста и заносить их в буфер (используя другой управляющий символ) так, чтобы имелась возможность ввести где-то еще. 6. Позволять установить флаг записи на диск (опять же посредством управляющего символа), используя слово UPDATE, чтобы редактируемый блок мог быть записан. 7. Позволять уходить из редактора (посредством еще одного управляющего символа) с последующим уходом в Форт-интерпретатор.
Хотя многие программисты, может быть, и не записывают такие формальные спецификации, они, несмотря на это, держат в уме достаточно точно определенные цели. Мы теперь имеем хорошо описанные цели, но еще не знаем, как разбить программу на слова Форта. Общая задача должна быть сначала разделена на меньшие части. Существует много способов, как это сделать, а для редактора это может быть сделано в уме. Например, мы знаем, что должны разделять текстовые и управляющие символы, выполнять определенные процедуры согласно тому, что требуют управляющие символы, и осуществлять переключение между режимами ввода и замещения. Часто полезно представить проблему на бумаге. Это можно сделать разными способами. Традиционно используется блок-схема. На рис. 13.1 представлена блок-схема редактора. Но блок-схема занимает много места на бумаге и ее неудобно создавать и изменять. Словесное описание функций редактора, как это показано в табл. 13.1, может быть лучше. (Для подготовки таких описаний особенно удобен текстовый редактор, который предоставляет широкие возможности для внесения изменений.)

Так как вы программируете на Форте, то можете предпочесть запись описания в форме псевдофорт, как в табл. 13.2. Слова псевдофорт могут отличаться от тех, что будут использоваться в програме (хотя они могут и предвосхитить имена реальных слов), они имеют то преимущество, что делят всю проблему наилучшим образом.
Таблица 13.1. Словесное описание редактора
Загрузить редактируемый блок в блочный буфер. Отобразить блок, поместить курсор влево вверх, Начать цикл. Получить символ с клавиатуры, Это управляющий символ? Если да, то выполнить команду редактора. Если нет, проверить, является ли он отображаемым, Если да, то проверить - мы в режиме ввода? Если да, то ввести символ в текст. Если нет, то произвести замещение символа. Если нет, то символ игнорируется. Продолжить цикл, пока придет команда выхода из системы
Как мы увидим, существует весьма яркая параллель между табл. 13.2 и словами, которые будут использоваться для выполнения основной работы редактора.
Существует много методов деления программных заданий, начиная от формализма блок-схемы и вплоть до идей, которые вы держите в уме. Вы можете считать полезной комбинацию слов и диаграмм. Важно, чтобы деление на вторичные задачи было выполнено до начала написания программы. Форт требует меньше формализма при разделении проблемы на части, чем многие другие языки, но решение любой задачи (безразлично - программной или нет) выиграет, если она сначала обдумана. Хотя ни одно из этих описаний редактора не является полным, все они задают базовую стратегию решения проблемы. Каждое нажатие клавиши должно обрабатываться индивидуально, для того, чтобы отделить команды от букв, вводимых в текст. Хотя команды редактора не стандартизованы, мы имеем отдельный механизм для их ввода (используя управляющие клавиши). Мы также решили включить два режима (ввод и замещение), позволяющие вводить символы двумя различными способами. Редактор будет работать в бесконечном цикле до тех пор, пока не будет выдана команда EXIT ("выход"), по которой измененный текст будет записан (или нет) на диск. Мы можем теперь рассказать вам, как мы подходили к написанию программы.
Мы начали с уточнения некоторых деталей нашего общего плана. Редактирующие команды, выбранные кодом управляющей клавиши, было бы легко описать, если бы использовался исполнительный вектор. Это делает команды легко задаваемыми, а для пользователя легко изменяемыми по желанию. Простейший путь выполнения выбора ввод-замещение открывает введение флага, который будет устанавливаться одной из команд редактора. Редактор выполняет большинство своих операций в пределах цикла BEGIN... UNTIL отчасти потому, что нужен именно бесконечный цикл (позволяющий выполнять любое число
Таблица 13.2. Описание редактора на псевдофорт
: EDIT ( n -- ) BLOCK-TO-BUFFER BLOCK-TO-SCREEN CURSOR-TO-START BEGIN FETCH-CHARACTER CONTROL-CHAR? IF DO-EDIT ELSE PRINTABLE-CHAR? IF INSERT-MODE? IF INSERT ELSE OVERTYPE THEN ELSE DROP THEN THEN END-FLAG UNTIL ;
редактирующих операций), отчасти из- за того, что EXIT только меняет флаг, лежащий в стеке, для того чтобы выйти из цикла при UNTIL. В нашей концепции необходимо еще принять решение о многих других редактирующих командах и об использовании памяти ЭВМ.
Мы имеем выбор позволить редактору непосредственно модифицировать содержимое блочного буфера или перенести его содержимое в другую часть памяти (PAD и далее) и редактировать его там. Был выбран последний метод, так как он позволяет "отменять" изменения и таким образом обеспечивать некоторую защиту против изменений на диске, внесенных случайно. Карта распределения памяти показана в табл. 13.3. Слово BLOCK используется для загрузки блока, номер
Таблица 13.3. Предварительные соображения о распределении памяти для редактора
Блочный Переносится PAD Отображается Терминал буфер в на
Первая строка --> Первая строка --> Первая строка Вторая строка --> Вторая строка --> Вторая строка ............. --> ............. --> ........... Последняя строка --> Последняя строка --> Последняя строка
которого хранится, в стеке, в блочный буфер, откуда 1024 символа переносятся командой CMOVE в PAD, где будет проводиться редактирование. Когда нужно сохранить изменения, 1024 символа из PAD переносятся в блочный буфер, который помечается для сохранения оператором UPDATE. Блок из 1024 символов отображается на экране терминала. Положение курсора на терминале и положение соответствующего символа в PAD оказываются связанными так, что изменение положения курсора означает изменение указания на символ в тексте, лежащем в массиве выше адреса PAD. Положение курсора логически характеризуется кодами строки и столбца, номера которых отсчитываются от левого верхнего угла дисплея. Эти коды могут быть затем использованы для получения любой другой информации, связанной с положением курсора.
Одной из особенностей редактора, в которой мы нуждались, была возможность вводить и стирать строки текста, на которые указывает курсор.
Память сразу за последней отображенной строкой (начиная с PAD+1024) может использоваться для хранения бесконечного числа стертых строк, которые могут быть вставлены в отображаемый текст, если это желательно.
Таблица 13.4. Использование памяти редактора
Блочный Перено- PAD Отобража- Терминал буфер сится в ется на
Первая строка --> Первая строка --> Первая строка Вторая строка --> Вторая строка --> Вторая строка .......... --> ............. --> ............. Последняя строка --> Последняя строка --> Последняя строка 1-я скрытая строка 2-я скрытая строка 16-я скрытая строка Дополнительная строка Строка для операций копирования-замещения
Представляется разумным включить 16 строк в скрытый буфер так, чтобы содержимое всего блока могло быть стерто и затем восстановлено где-то еще. Если процедуры ввода и стирания строки были спроектированы для работы в кольцевом режиме, ни одна строка не будет потеряна. Так как отображаемый и скрытый буфера смежны, программа для реализации этого кольцевого буфера будет весьма проста. Окончательное распределение памяти в редакторе, выбранное нами, включая дополнительную строку для процедур копирования-замещения, показано в табл. 13.4.
Представляется полезным добавить кольцевой буфер к редактору, и это можно рассматривать в качестве примера для модификации других редакторов, не имеющих таких возможностей. (Как это работает, будет показано в деталях позднее.)
Этого обзора было бы достаточно, чтобы начать писать редактор, но мы добавили еще два ограничения. Мы знали, что надо свести число терминальных операций к минимуму, чтобы редактор работал с наиболее широким спектром ЭВМ. После некоторого размышления мы решили спроектировать редактор, который работает, используя только две терминальные команды: 1 - очистка экрана и 2 - установка курсора на заданную строку и столбец. Было ли это практичным, стало ясным после того, как редактор был частично написан, но это была цель, за которую мы боролись. Позднее мы ограничили нашу задачу написанием редактора для минимального размера экрана 16 строк по 64 столбца, что упростило текст программы.
Это завершило спецификацию " высокого уровня" для редактора; теперь мы в основном поняли, как должен работать редактор. Продолжать более общее описание функций редактора без реального решения некоторых проблем "нижнего" уровня было бы в заметной мере пустой тратой времени. Например, вдруг число слов управляющих терминалом, нельзя сократить до 2? Теперь мы были готовы начинать писать Форт-программу. Для большинства языков программирования требуется много, больше планирования, так как модульная структура, присущая словам Форта, им недоступна. Если вы изучаете Фортран, Кобол или большинство других языков, в ваших интересах спланировать все как можно подробнее, прежде чем написать строчку программы. Например, вам надо выбрать, какой управляющей клавише присвоить какую функцию, и вы должны мысленно спроектировать все управляющие функции терминала. Процесс разработки при этом будет дольше и конечный продукт, вероятно, не будет столь уж хорош. Форт позволяет писать и отлаживать программы методом проб и ошибок или на интерактивной основе. Это выявляет многие ошибки прежде, чем они получат шанс распространиться непредсказуемым путем через программу. Мы не будем описывать все пробы и ошибки, которые были совершены при написании редактора, но попытаемся представить вам идеи некоторых шагов, которые предпринимались.
Закладка фундамента
До сих пор мы строили замок грез, лишенный фундамента. Наши размышления привели к ясному пониманию того, какой редактор мы хотим создать, и к конкретным идеям построения некоторых его структур высокого уровня. Но прежде чем мы сможем проверить какие-либо наши идеи, мы должны определить слова, на которых будет базироваться все остальное. Должны быть описаны переменные и константы (хотя некоторые из них могут быть добавлены позднее). Должны быть зафиксированы некоторые адреса памяти. Нужно описать слово или слова, управляющие функциями терминала. Должна быть решена проблема, как изменить и запомнить положение курсора на экране терминала и соответствующие позиции в буфере редактора.
Эти и другие аспекты работы редактора должны быть рассмотрены на ранних этапах.
Хотя мы опишем окончательный текст программы блок за блоком (чтобы сделать ее более понятной), сам редактор пишется не так. Мы сначала написали редактор, который может делать очень немногое, чтобы кое-что проверить. Базовые слова дописывались позднее в процессе написания программы и были помещены в первые два блока, чтобы сохранить иерархию функций слов от наименее к наиболее сложным и сделать программу простой для понимания. Мы группировали слова по их функциям настолько, насколько возможно, в некоторых случаях проводя переукладку после того, как программа была функционально завершена.
Кто-то может сказать, что текст программы написан слишком плотно, но мы должны были экономить место в книге. Нужно оставить достаточно свободного места в программе, чтобы можно было выполнить переукладку слов согласно их функциям, а также чтобы осталось место для изменений и добавлений. Если вы делаете много изменений в тексте программы, вам нужно место для ее расширения. Первые два блока редактора содержат описания констант, переменных, флагов и адресов некоторых буферов и базовые слова для управления курсором и терминалом. Переменные ROW и COL (строка и столбец, где находится курсор) и I/R (флаг ввод-замещение) были действительно описаны первыми. Остальные были добавлены по мере необходимости. Позднее в процессе программирования с целью экономии памяти были описаны некоторые числа (BL, 1K, 64 и т.д.). PDELAY и SDELAY представляют собой два цикла, организующие задержки для отображения информационных сообщений.
0 ( 20 июля 85 Экранный редактор NS 01 из 10) 1: TASK ; DECIMAL 2 3 32 CONSTANT BL 1024 CONSTANT 1K 4 64 CONSTANT 64 63 CONSTANT 63 5 10000 CONSTANT PDELAY 5000 CONSTANT SDELAY 6 VARIABLE SCR VARIABLE ROW 7 VARIABLE COL VARIABLE I/R 8 VARIABLE LOWBLK VARIABLE HIGHBLK 9 ( запоминание первого и последнего блоков, которые можно редактировать на вашей ЭВМ) 10 1 LOWBLK ! 169 HIGHBLK !
Они являются константами, чтобы можно было изменить задержки, не изменяя текста программы.
Константы и переменные должны быть собраны в начале программы (или в начале секций большой программы) для того, чтобы их было легко найти и изменить. Переменная SCR была описана для тех версий Форта, где этого слова нет. Переменные LOWBLK и HIGHBLK были добавлены для того, чтобы редактор не имел доступа к тем блокам, к которым не следует. Они могут быть изменены в тексте программы или с клавиатуры после того, как редактор загружен. 11 : --- ; ( слово, не выполняющее никакой работы для исполнительного вектора) Это слово (названное так, чтобы бросаться в глаза в тексте программы) позволяет использовать неописанные управляющие клавиши в исполнительном векторе KEYVECTORS (блок 9). Это слово также было описано, когда был описан KEYVECTORS, но помещено здесь, так как это слово низкого уровня. 12 : MODE (-) I/R @ 0= I/R ! ; ( выбор ввод-замещение) Каждый раз, когда используется MODE, оно переводит значение I/R из состояния истинно в состояние ложно или наоборот. MODE действует как переключатель, который выбирает режим ввода символов. 13 : DELAY ( n - ) 0 DO LOOP ; ( Задержка на n циклов) 14 : PAUSE ( - ) PDELAY DELAYS ; ( Длинная задержка) 15 : SPAUSE ( - ) SDELAY DELAYS ; ( Короткая задержка) Эти циклы задержки были описаны позднее, но перемещены в начало текста программы. PAUSE и SPAUSE были описаны как отдельные слова, чтобы в дальнейшем сэкономить место в программе. Основополагающие слова размещены в следующем блоке. 0 ( 20 июля 85 Экранный редактор NS 02 из 10) 1 : PADDR ( n - ) CREATE , DOES> @ PAD + ; 2 : 1K 2 * PADDR BLINE ( Последняя строка кольцевого буфера) 3 : 1K 3 * 64 + PADDR LBUFF ( Адрес строки для копирования-эамещения)
Слово-описатель PADDR ("PAD ADDRESS" = адрес PAD) используется для формирования двух адресов, необходимых при работе с буферами редактора. Для симметрии мы могли бы описать также О ADDR OLINE, чтобы получить адрес PAD (начало буфера редактора), но в тексте программы использовался PAD для того, чтобы подчеркнуть, какой именно адрес памяти используется.
Команды 1K 2 * и 1K 2 * 64 + применены для вычисления значений 2048 и 2112, так как это легче понять в терминах блоков и строк, которые, в свою очередь, дают лучшие представление о структуре буфера редактора. Заметьте, что мы не заботимся о времени вычислений, так как они выполняются только раз, во время компиляции. 4 : CONTROL ( с --) CREATE DEPTH DUP С, 0 DO DEPTH ROLL С, 5 LOOP DOES> DUP DUP C@ + SWAP DO I 1+ C@ EMIT LOOP ; 6 27 42 CONTROL ( Очистка экрана, курсор вверх влево)
CONTROL - слово-описатель, сформированное для описания слов, которые генерируют управляющие коды и ESC-последовательности. На терминале ADM 31 работает 27 42 CONTROL
так как ESC-последовательность ESC * (ASCII 27 и последующий ASCII 42) используется для очистки экрана. Хотя CONTROL имеет встроенное описание, это слово упрощает описание слов управления терминалом и делает текст программы более читаемым. Описание : 27 EMIT 42 EMIT ; также будет работать, но применение слова-описателя CONTROL при наличии нескольких описаний сэкономит память. Слова, имеющие отношение к функциям терминала, снабжены именами, заключенными в треугольные скобки, чтобы они выделялись в тексте программы. 7 : 27 EMIT 61 EMIT ROW @ 32 + EMIT COL @ 32 + EMIT ; 8 ( Положение курсора в строке Х и столбце Y) Это описание демонстрирует типовой образец слова, осуществляющего прямое позиционирование курсора. Оно работает с ADM 31 и, конечно, будет переделано для других терминалов или ЭВМ. 9 ( : PAGE ; : ROW @ COL @ PTC ; ) Это показывает, как можно описать и , применяя слова Форта, а не ESС-последовательности (эти примеры используют слова MMSFORTH). 10 ( Чтобы использовать далее . , , и ) 11 ( необходимо описать их здесь. Используется лишь в 12 случае отсутствия прямого перемещения курсора) 13 ( 28 CONTROL (перемещение курсора вверх влево) 14 ( : ROW @ ?DUP IF 0 DO LOOP THEN ) 15 ( COL @ ?DUP IF 0 DO LOOP THEN ; )
Требуется только одно описание ("cursor-X-Y") и ("clear"); это альтернативное описание медленнее и основано на
("downward-line-feed" = переход на одну строку вниз) и ("advance" = сдвиг курсора на одну позицию), которые описываются оператором CONTROL. Это слово следует использовать, только если терминал позволяет очень примитивное управление курсором. Чтобы сделать программу легко читаемой, все слова, описанные CONTROL, нужно сгруппировать вслед за описанием самого слова CONTROL.
В блоке 3 продолжается описание слов, управляющих курсором. 0 ( 20 июля 85 Экранный редактор NS 03 из 10) 1 : @CURSOR (- r c ) ROW @ COL @ ; ( Вызов строки и столбца) 2 ; !CURSOR ( r c -) COL ! ROW ! ; ( Запоминание строки и столбца)
@CURSOR и 1CORSOR были описаны, чтобы упростить вычисления и запоминание положения курсора. Это было сделано после того, как мы выяснили, что комбинации, которые они представляют, появляются в нескольких местах текста разрабатываемой программы. Эти слова довольно часто встречаются попарно в одном и том же описании и делают программу легко читаемой. Представление положения курсора через номера колонок и столбцов (а не только через номера байта) упрощает перемещение курсора и описание некоторых последующих слов, 3 ( опишите здесь, если возможно, и и используйте в блоках 3 и 4) 4 : 0 COL ! ; ( Перемещение курсора в начало строки) 5 : 0 0 !CURSOR ; ( Перемещение курсора вверх влево) Слова ("start-line" - начало строки) и ("home-cursor" - курсор на место) могут быть описаны с помощью CONTROL (если ваш терминал поддерживает эти функции) и использованы вместо в этих определениях. Заметьте, что положение курсора должно отслеживаться в двух местах: его реальное положение запоминается в ROW и COL, но курсор должен быть также помещен в соответствующую точку на экране. 6 : BABOVE ( - n ) ROW @ 64 * ( Число байт в рядах выше курсора) 7 : BBELOW ( - n ) 16 ROW @ - 64 * 1К + ; ( Число байт ниже курсора) 8 : OFFSET ( - n ) BABOVE COL @ + ; ( Число байт в буфере перед курсором) 9 Неудачно названные BABOVE ("bytes-above" - число байтов до) и BBELOW ("bytes-below" - число байтов после) - слова, которые пригодятся позднее.
Они вычисляют число байтов (в буфере) в строках выше курсора и число байтов (также в буфере) в строке, где находится курсор, а также в строках ниже его (включая весь кольцевой буфер). Слово OFFSET кладет в стек число байтов, отсчитанное от начального положения курсора (верхний левый угол). Так как это слово используется только в CPOS, OFFSET является излишним, оно лишь делает описание CPOS более легким для понимания. Обратите внимание, что любое изменение величины ROW или COL поменяет число, выдаваемое в стек оператором OFFSET (и CPOS). 10 : CPOS ( - адр) PAD OFFSET +: ( адрес курсора) 11 : LSTART ( - адр) PAD BABOVE +: ( адрес начала строки) 12 : LEND ( - адр) LSTART 63 +: ( адрес конца строки) Слова CPOS ("cursor-position" - положение курсора), LSTART ("linestart - начало строки) и LEND ("line-end" - конец строки) выдают три адреса в буфере, которые широко используются в последующем тексте редактора. 14 : BLEFT ( -n ) LEND CPOS - ; ( Число байтов в строке слева от курсора) 15 BLEFT ("bytes-left" - байтов осталось) необходимо для последующих слов, которым нужна информация о числе байтов (символов) между курсором и концом строки.
Заметьте, что положение курсора в буфере задается несколькими способами. Мы знаем его строку и столбец, число байтов до и после него в PAD-буфере, число байтов от начала буфера, его адрес в памяти, адрес начала и конца строки, где он находится, и число байтов, лежащих в строке после курсора. Мы теперь имеем полный набор слов, позволяющий нам описать большое число слов высокого уровня в блоке 4 0 ( 20 июля 85 Экранный редактор NS 04 из 10) 1 ( Если возможно, опишите , , и здесь для того, чтобы использовать их) 2 ( вместо в описаниях на) 3 ( строках 4, 5, 6 и 7. ) 4 : LEFT COL @ 0 > IF -1 COL +! (или ) THEN : 5 : RIGHT COL @ 63 < IF 1 COL +! (или ) THEN ; 6 : UP ROW @ 0 > IF -1 ROW +! (или ) THEN ; 7 : DOWN ROW @ 15 < IF 1 ROW +! (или ) THEN ;
Первая и наиболее важная вещь, которую мы хотели бы сделать, это двигать курсор в четырех направлениях, но не покидая экрана.
Слова и т.д. должны быть описаны с помощью CONTROL, если ваш терминал или ЭВМ позволяют это, и затем использованы вместо в этих словах. Заметьте, что ROW и COL должны так изменяться, чтобы отслеживать соответствие положения курсора и адреса в буфере редактора. 8 : ( --) BLEFT SPACES ; ( Стереть конец строки) Было бы лучше описать оператор ("erase-end-line" - стереть конец строки) с помощью оператора CONTROL при условии, что ваш терминал позволяет это. 9 : NEWLINE ( -- ) DOWN ; ( Курсор в начало следующей строки)
Имени NEWLINE будет поставлена в соответствие клавиша (которая формирует код, эквивалентный CTRL-M), чтобы установить курсор в начало следующей строки. Это слово имитирует функцию "возврат каретки + перевод строки" (). Однако в редакторе ВК только перемещает курсор; это не должно вызывать перемещение текста на экране. 10 : SHOWLINES ( - ) @CURSOR 16 ROW @ ( отображение строк) 11 DO I ROW ! LSTART 64 -TRAILING TYPE LOOP 12 !CURSOR ; 13 : SHOWBLK ( -- ) @CURSOR SHOWLINES ICURSOR ;
Оператор SHOWLINES отображает строку, где находится курсор и все последующие строки до конца блока. Когда изменена только нижняя часть экрана, это экономит время по сравнению с отображением заново всего экрана. Оператор SHOWBLK использует оператор SHOWLINES для отображения всего блока после перемещения курсора влево вверх. Обратите внимание на то, как @CURSOR и ICURSOR используются в паре для занесения в стек кодов исходного положения курсора и для последующего в конце работы слова восстановления его позиции. 14 : TYPELINE ( -- ) CPOS BLEFT 1+ OVER OVER TYPE -TRAILING 15 SWAP DROP BLEFT 1+ -SPACES ;
Оператор TYPELINE пропечатывает все символы, начиная* от курсора и до конца строки. -TRAILING используется для определения числа пробелов, необходимых для того, чтобы избавиться от лишних символов, которые в противном случае появятся за пределами 64- символьной строки.
Мы имеем теперь к концу четвертого блока описания почти всех слов низкого уровня, в частности слов, оперирующих адресами памяти, перемещающих курсор и отображающих текст на экране.
Только некоторые из действительно редактирующих команд были описаны в первых четырех блоках, но в следующих четырех блоках мы опишем остальные.
Упражнения
1. Почему строка 1 блока 1 записана следующим образом : TASK ; DECIMAL 2. Почему лучше описать LOWBLK и HIGHBLK как переменные, а не как константы ? 3. Почему PDELAY и SDELAY описаны как константы, а не использованы непосредственно числа в описаниях PAUSE и SPAUSE? 4. Приведите две причины для выделения DELAYS из описаний PAUSE и SPAYSE. 5. Предположим, что вы используете терминал ADM 31 и хотите добавить слова PAGE и РТС из MMSFORTH в ваш Форт. Сформулируйте их описание. 6. Предположим, что вы работаете с терминалом, на котором работают ESC-последовательности: 27 13 Сместить курсор на одну строку вниз, 27 14 Сместить курсор вперед на один символ, 27 15 Сместить курсор вверх влево (HOME) Опишите , и в строках 10, 11 и 12 блока 2. Что бы бы теперь сделали, чтобы использовать описание , приведенное в строках 14 и 15 ? 7. Почему модификации, предложенные в строках 1, 2 и 3 блока 4, были бы предпочтительнее, если они возможны?
Основные положения
Теперь, когда фундамент заложен, в следующих четырех блоках может быть описано большинство редактирующих команд. Эти операторы основаны большей частью на словах, которые мы уже описали, а не на словах, описанных в Форте. Наибольшее удовольствие доставляет написание программ Форта среднего и наивысшего уровней, так как вы при этом используете в основном язык, созданный вами самими. Но на этой фазе проявляются противоречия и пропуски, допущенные в написанных ранее программах. Это как раз то время, когда вы оцените оставленное вами свободное место в предшествующих блоках для расширения возможностей и внесения изменений. Пока мы писали редактор, блоки 1-4 занимали на самом деле 7 блоков. Порядок, в котором описываются слова промежуточного уровня, до некоторой степени произволен, поскольку большинство из них являются независимыми. Разумно помещать связанные слова в одном и том же блоке хотя бы для того, чтобы легче было их найти и отредактировать.
0 ( 20 июля 85 Экранный редактор NS 05 из 10) 1 2 Ж BCLEAR ( -- ) PAD 1K + 1152 BL FILL : ( Очистка буфера) 3 : SCLEAR ( -- ) PAD 1K BL FILL SHOWBLK ; ( Очистка экрана) 4 Имеются слова для очистки содержимого кольцевого буфера и дисплея. 5 6 : LOADBLK ( - ) SCR @ BLOCK PAD 1K CMOVE ; ( Загрузка блока) 7 : RESTORE ( - ) LOADBLK SHOWBLK ; 8 Слово LOADBLK ("load-block" - загрузить блок) производит загрузку в блочный буфер, а затем и в буфер редактора. RESTORE загружает буфер редактора и отображает его содержимое. RESTORE аннулирует редактирование, проведенное с момента, когда блок был последний раз отредактирован и сохранен. Функция RESTORE оказалась полезной сама по себе при написании программ (она отсутствовала в исходной спецификации) и оказалась практичной, так как блочный буфер не используется непосредственно при редактировании. 9 : (UPDATE) ( - ) PAD SCR @ BLOCK 1K CMOVE UPDATE ; ( Пометка для сохранения)
Это слово копирует отображаемую часть буфера редактора в блочный буфер и помечает для сохранения. Его имя в скобках предполагает, что оно тесно связано с функцией UPDATE, описанной ниже. 10 11 : +BLK ( - ) SCR @ HIGHBLK @ < IF 1 SCR +! RESTORE THEN ; 12 : -BLK ( - ) SCR @ LOWBLK @ > IF -1 SCR +! RESTORE THEN ;
Эти слова используются для начала редактирования следующего или предшествовавшего блока. Заметьте, что блок, который вы покидаете, должен быть помечен для сохранения (UPDATE), если изменения, сделанные в нем, нужно записать на диске, прежде чем вы продолжите работу. HIGHBLK и LOWBLK отсутствовали в исходной спецификации; они были добавлены позднее для того, чтобы предотвратить доступ редактора к запретным или несуществующим блокам. 13 14 : CLRTOP ( - r c ) @CURSOR ; 15 : WRITETOP ( r с -) TYPELINE !CURSOR ;
Слова CLRTOP ("clear-top" - очистить верх) и WRITETOP были описаны для упрощения написания подсказок, которые используются в четырех словах следующего блока. При совместном использовании они запоминают положение курсора в стеке, заполняют верхнюю строку на терминале пробелами, восстанавливают верхнюю строку и устанавливают курсор туда, где он был.
Четыре слова в блоке 6 выдают информацию оператору и (или) обеспечивают выбор из меню. 0 ( 20 июля 85 Экранный редактор NS 06 из 10 ) 1 : ?BLKS ( -- ) CLRTOP ." * * * BLOCK#: " SCR @ . ( блок# ) 2 PAUSE WRITETOP ;
?BLK# ("block-number" - номер блока) отображает текущий номер блока на верхней строке, ждет некоторое время, а затем восстанавливает верхнюю строку на дисплее. Это слово просто напоминает оператору номер блока, который редактируется. Длительность паузы задается константой PDELAY. 4 : UPDATES ( -- ) (UPDATE) ( пометить блок и показать это) 5 CLRTOP ." * * * UPDATED Block#: " SCR @ . SPAUSE WRITETOP ;
Оператор UPDATES позволяет вам пометить блок для сохранения и затем подтвердить эту операцию, используя более короткую паузу, чем в ?BLK#. Отображение для этого необязательно, но без него у вас не будет средств убедиться, что пометка (UPDATE) действительно произошла. 6 7 : ?CLEAR ( - ) CLRTOP ." * * * X-OUT; (B)uffer, (S)creen ?" 8 KEY DUP 66 = IF DROP BCLEAR 9 ELSE 83 = IF SCLEAR !CURSOR EXIT THEN 10 THEN WRITETOP;
Это и следующее слова представляют субменю, которое позволяет присвоить заданные функции определенным управляющим клавишам в основной программе. В 7CLEAR ("очистить?") предполагается три варианта: очистить буфер, экран или ничего (ничего, если вы нажмете любую клавишу, кроме "В" или "S"). Верхняя строка восстанавливается, если информация на экране не была стерта (что показывает, насколько полезной может быть операция EXIT). В любом случае курсор будет установлен туда, где он был ранее. 11 : ?EXIT ( f - ) CLRTOP ." * * * EXIT; (S)ave, (Q)uit?" 12 KEY DUP 83 = 13 IF DROP DROP DROP UPDATE FLUSH 1+ EXIT 14 ELSE 81 = IF DROP DROP EMPTY-BUFFERS 1+ EXIT THEN 15 THEN WRITETOP ;
Это слово, используемое для ухода из редактора, работает во многом так же, как и ?CLEAR, представляя три варианта, прежде чем делать что-либо. Слово 1+ становится понятным, когда вы заглянете в EDITLOOP в последнем блоке, оно устанавливает флаг, позволяющий выйти из редактора.
EXIT используется здесь так же, как и ?CLEAR, для предотвращения в WRITETOP восстановления верхней строки при выходе из редактора.
Следующий блок содержит слова, необходимые для ввода текста и изменения его различными путями. Существуют два основных способа или режима, используемые для ввода символов: замещение символа, на который указывает курсор, или раздвижка текста (перемещение остальной части строки на одну позицию вправо) перед вводом нового символа. 0 ( 20 июля 85 Экранный редактор NS 07 из 10) 1 : OPENUP ( - ) ( Раздвинуть текст в месте буфера, куда указывает курсор) 2 COL @ 64 < IF CLOS DUP 3 1+ BLEFT ) BL CPOS C! THEN ; 4 : OPEN ( -- ) OPENUP TYPELINE ; ( Раздвинуть текст в месте, указанном курсором)
OPENUP необходимо для ввода пробела в позицию, указанную курсором, перед вводом символа. Это слово воздействует только на буфер, ничего не отображая на экране. Если вы не хотите терять конец строки, который выдвигается в правую сторону дисплея, проверяйте последний символ в строке (используя LEND @), чтобы убедиться, что это пробел, прежде чем позволить слову OPENUP делать чтобы-то ни было. Слово OPEN позволяет вам вводить пробел в позицию, отмеченную курсором, смещая текст на один символ вправо. Это побочный продукт слова OPENUP, которое все равно нужно было написать для INSERT. 6 : TRUNC ( -) CPOS BLEFT BL FILL ; ( Укоротить строку)
TRUNC ("отбросить") заполняет пробелами часть строки, лежащую между курсором и концом строки. 7 8 : OVERTYPE ( Симв - ) COL @ 64 < ( Заместить символ, на который указывает курсор) 9 IF DUP EMIT CPOS С! 1 COL +! ELSE DROP THEN ; 10
Это основное слово для замещения символа, на который указывает курсор, символом, лежащим в стеке. Если курсор не находится за краем строки, символ, лежащий в стеке, отображается на терминале и записывается в соответствующем месте буфера редактора. Положение курсора в буфере (COL) увеличивается на 1, чтобы обеспечить соответствие с положением терминального курсора, который при отображении символа перемещается на одну позицию вправо. 11 : INSERT ( симв - ) OPENUP OVERTYPE TYPELINE ; ( Ввести символ )
Слово INSERT сначала раздвигает текст, затем замещает пробел, возникший на месте курсора, символом, лежащим в стеке, после чего отображает строку. 12 : DELETE ( - ) COL @ 64 < ( Стереть символ) 13 IF CPOS 1+ CPOS BLEFT CMOVE BL LEND C! 14 TYPELINE 15 THEN ;
Стирание символа выполняется путем перемещений текста между курсором и концом строки (в буфере редактора) на одну позицию влево, записи пробела в конец строки (в буфере), стирания строки на экране и отображения измененной строки.
Следующий блок работает с невидимыми на экране буферами (строчный и кольцевой буферы). Строчный буфер используется для получения копии строки, которая заменит строку где-либо еще. Он особенно полезен для запоминания строки заголовка, которая будет использоваться в нескольких блоках. Кольцевой буфер воспринимает одну или более строк, которые были стерты. Эти строки затем могут быть вставлены где-либо еще в том же или другом блоке. Если вы хотите избавиться от некоторых строк совсем, то укоротите их с помощью оператора TRUNC при положении курсора на левом поле, прежде чем стирать и помещать их в кольцевой буфер. Слово BCLEAR очистит все строки в кольцевом буфере (см. его описание в блоке 5). То, что вы видите на экране, если блок помечен (UPDATE), то и будет записано на диск. 0 ( 20 июля Экранный редактор NS 08 из 10) 1 : CLINE ( --- ) LSTART LBUFF 64 CMOVE ; ( Копирование строки в буфер) 2 3 : PLINE ( - ) LBUFF LSTART 64 CMOVE ( Извлечь строку из буфера ) 4 CURSOR TYPELINE !CURSOR ;
CLINE ("copy-line" - скопировать строку) и PLINE ("put-line" - вставить строку) работают с буфером строки (по адресу LBUFF), предназначенным для копирования и замещения отдельной строки. Полезной модификацией редактора было бы постоянное отображение содержимого строчного буфера сразу под текстом блока на экране, если ваш экран имеет достаточно места. 5 6 : KLINE ( -- ) ( Стереть строку, занеся ее в кольцевой буфер) 7 LSTART BLINE 64 CMOVE ( Перенос текущей строки) 8 LSTART 64 + LSTART BBELOW CMOVE ( Сдвинуть буфер вверх) 9 SHOWLINES ; ( Отображение измененных строк)
Слово KLINE ("kill-line" - стереть строку) стирает строку, на которую указывает курсор, из отображаемой части буфера редактора и переносит ее в кольцевой буфер по адресу BLINE. Затем весь буфер редактора, начиная со строки ниже курсора и кончая BLINE, сдвигается на одну строку (64 символа), так что перекрывает стертую строку. Слово SHOWLINES отображает строки, начиная с помеченной курсором до конца экрана. Последней строкой экрана станет верхняя строка кольцевого буфера, в котором строки при работе перемещаются по ротационной схеме. 11 : ILINE ( --) ( Ввести строку из кольцевого буфера) 12 LSTART DUP 64 + BBELOW ) ( сдвинуть буфер) 13 BLINE LSTART 64 CMOVE ( Перенести строку с низа буфера) 14 SHOWLINES ; ( Отображение измененных строк)
Слово ILINE ("insert-line" - ввести строку) сдвигает кольцевой буфер на одну позицию. Весь буфер редактора, начиная со строки с курсором и кончая строкой перед BLINE. смещаются вниз на 64 символа, а последняя строка (теперь по адресу BLINE) переносится, чтобы заместить строку, где был раньше курсор; изменения отображаются оператором SHOWLINES. Это выглядит как вращение кольцевого буфера вниз на одну строку. Здесь описаны все функции, используемые редактором. Остается только соединить эти функции вместе, чтобы редактор выполнял то, что было вначале задумано.
Соединение частей в единое целое
Наиболее интересная фаза написания любой программы - это внесение последних поправок и наблюдение за тем, как она, наконец, работает. В Форте последняя фаза доставляет даже большое удовольствие, так как вы проверяете слова промежуточного уровня и теперь видите, как они собраны и как работают совместно. Если вы хорошо поставили задачу и выразительно назвали ваши слова, сборка всего вместе - обычно наиболее простая часть написания программы на Форте.
Простейший способ доступа ко всем 23 командам редактора - это создать исполнительный вектор для командных слов. Если используется клавиша CTRL в комбинации с буквой от "А" до "Z", то это дает 26 возможных команд.
Слово -- было создано в блоке 1, чтобы поставить в соответствие неиспользуемым управляющим символам пустое слово, не выполняющее никакой работы.
0 ( 20 июля 85 Экранный редактор NS 09 ИЗ 10) 1 CREATE KEYVECTORS ] ( Исполнительный вектор команд редактора) 2 LEFT ( А Курсор влево ) ?BLK# ( В Номер блока ) 3 GLINE ( С Копирует строку ) DELETE ( D Стирает символ ) 4 ?EXIT ( Е Уход из редактора ) --- ( F ) 5 MODE ( G Переход в новый режим ) --- ( Н ) 6 ILINE ( I Ввод строки ) --- ( J ) 7 KLINE ( К Стирание строки ) -BLK ( L Последний блок ) 8 NEWLINE ( М или ENTER, CR+LF ) +BLK ( N Следующий блок ) 9 OPEN ( O Раздвинуть текст ) PLINE ( Р Вставить строку ) 10 ( Q Курсор на место ) RESTORE ( R Восстановить экран ) 11 RIGHT ( S Курсор вправо ) TRUNC ( Т Укоротить строку ) 12 UPDATES ( U Поместить буфер ) --- ( V ) 13 UP ( W Курсор вверх ) ?CLEAR ( X Очистить буфер/экран ) 14 --- ( Y ) DOWN ( Z Курсор вниз ) [
Дополнительные приложения слов ] и [ описаны в гл.15, но здесь достаточно знать, что они помещают адреса слов, записанных между ними, в тело описания KEYVECTORS, как это требуется для исполнительных векторов. Функции, присваиваемые клавишам, произвольны, и мы действительно меняли местами команды в процессе написания программы; вы можете делать то же самое. Оператор KEYVECTORS может быть написан так, чтобы занимать намного меньше места на экране. Но тогда для документирования функций клавиш потребовалась бы отдельная таблица команд; более эффективно было бы позволить описанию слова документировать само себя. 15 : KEYDO ( n -) 1- 2* KEYVECTORS + @ EXECUTE ;
Слово KEYDO воспринимает число (от 1 до 26, от CTRL-A до CTRL-Z) и исполняет n-ю команду в KEYVECTORS. Теперь любая редактирующая команда может быть использована нажатием одной клавиши с пульта. Блок 10 как раз то место, где все соединяется в единое целое. 0 ( 20 июля 85 Экранный редактор NS 10 из 10) 1 : EDITCASE ( флаг симв - флаг') 2 : DUP 27 < OVER 0 > AND ( Легальный управляющий символ ?) 3 IF KEYDO ( Если так, то исполняем команду) 4 ELSE DUP 31 > OVER 127
EDITCASE - ключевое слово редактора; это место, где анализируется то, что вводится с клавиатуры, и производятся соответствующие действия. Слово EDITCASE предполагает наличие флага и кода символа в стеке. Сначала анализируется символ и проверяется, лежит ли он в интервале между CTRL-A и CTRL-Z включительно, и если да, то выполняется соответствующая команда. Если это не управляющий символ, его код проверяется еще раз, при этом выясняется возможность отображения через ASCII-код. Если это так, то в зависимости от статуса ввод-замещение производится одно из двух: символ либо вставляется в текст, либо впечатывается поверх того, который был в буфере редактора. Если символ непечатный, он игнорируется. Флаг в EDITCASE служит для управления выходом из редактора. Флаг (0) заносится в стек словом EDITLOOP (описанным ниже) и заменяется на 1, если в ?EXIT (в блоке 6) был сделан выбор "exit" (выход). Слово --- может быть заменено как здесь, так и в KEYVECTORS словом, описанным с помощью CONTROL, чтобы выдать звуковой сигнал, если ваша ЭВМ это позволяет.
Слово EDITCASE суммирует основные операции редактора и является Форт-интерпретацией части, связанной со спецификацией клавиш, приведенной в первой части этой главы. Слово ts ( так было в оригинале -прим. OCR-man)
EDITCASE может быть записано так, чтобы читаться почти так же легко, как словесное описание, если бы оно было написано с использованием трех слов, описанных для того, чтобы исключить три предложения, как это видно из нижеприведенного примера.
ПсевдоФорт из табл 13.2 Действительный текст программы
: EDIT ( n-) BLOCK-TO-BUFFER BLOCK-TO-SCREEN CURSOR-TO-START BEGIN : EDITCASE ( флаг симв-флаг') FETCH-CHARACTER CONTROL-CHAR? DUP CONTROL-CHAR? IF KEYDO IF KEYDO ELSE PRINTABLE-CHAR? ELSE DUP PRINTABLE-CHAR? IF INSERT-MODE? IF INSERT-MODE? IF INSERT IF INSERT ELSE OVERTYPE ELSE OVERTYPE THEN THEN ELSE DROP ELSE DROP --- THEN THEN THEN THEN ; END-FLAG UNTIL ;
Обратите внимание на сходство между EDITCASE и псевдо Форт-спецификацией для редактора в табл.13.2.
Все части оказались взаимно согласованными.
Хотя EDITCASE содержит базовую логику редактора, как показано в табл.13.2, нужно еще много сделать, чтобы редактор был вполне функционирующим. Блок, который нужно отредактировать, должен быть загружен в буфер редактора и должен быть запущен бесконечный цикл приема кодов от клавиатуры, которые нужны для работы EDITCASE. 11 : EDINIT ( blk -) SCR ! EMPTY-BUFFERS LOADBLK SHOWBLK ; Слово EDINIT ("edit-initialize" - вход в редактирование) запоминает номер блока в SCR, очищает блочный буфер с помощью EMPTY-BUFFERS, загружает содержимое блока в буфер редактора и отображает его на экране терминала. 12 : EDITLOOP ( -- ) EDINIT 0 BEGIN KEY EDITCASE DUP UNTIL 13 DROP ;
Слово EDITLOOP запускает редактор с помощью EDINIT и помещает флаг 0 в стек для того, чтобы редактор оставался в бесконечном цикле, пока флаг не будет заменен на 1 в ?EXIT (что заставит UNTIL прервать цикл), после чего этот флаг убирается. 14 : EDIT ( blk -) EDITLOOP ; 15 : E SCR @ EDIT ;
Описание закончено. Чтобы использовать редактор, следует напечатать слова EDIT и E. EDIT устанавливает курсор в исходное положение, очищает экран и входит в EDITLOOP с номером блока в стеке, по завершении редактирования экран вновь очищается. (Если вы хотите, измените EDIT так, чтобы курсор появился под текстом блока, редактирование которого вы прервали по команде QUIT, без стирания экрана.) Слово Е представляет удобный способ возврата к работе с только что редактированным блоком, чей номер хранится в SCR. Если SCR - часть вашей Форт-системы, слово Е перейдет к редактированию блока, для которого последней выполнялась команда LIST. Редактор завершен, если вы, конечно, не хотите его модифицировать. В гл.14 мы увидим,как для редактора можно организовать отдельный словарь, но это сейчас необязательно. Даже если вы найдете этот редактор проще того, который имеется в вашем Форте, наш детальный анализ может помочь вам понять его и, может быть, даже улучшить.
Но наше основное соображение, почему мы дали столь детальное описание редактора, было не просто документирование программы или оказание помощи для модификации вашего собственного редактора: мы надеемся, что вы научились разрабатывать и писать сложные программы.
Этот процесс обсуждается в следующем разделе.
Упражнение
1. Как бы вы отлаживали слова, описанные в блоках 9 и 10? 2. Мы дали два описания EDITCASE, последнее требует описания слов CONTROL-CHAR?. PRINTABLE-CHAR? и INSERT-MODE?. Определите эти слова. Этот процесс выделения части программы и развертывания ее в виде отдельных слов называется факторизацией описания. Подробнее вы прочтете об этом в следующем разделе.
Комментарии
Теперь, когда вы знакомы с текстом редактора и некоторыми причинами того, что она написана именно так, посмотрим на процесс программирования с более общих позиций. Наш первый шаг заключается в формировании четкой идеи основных функций редактора. Это может быть сделано многими путями: с помощью функциональной схемы (рис.13.1), словесного описания (табл.13.1) и псевдо форт-программы (табл.13.2). Для простой программы эти планы можно было бы держать в голове. Но план нужен для того, чтобы направлять усилия при программировании.
Мы не можем чрезмерно подчеркивать важность процесса планирования. Соблазн сесть и написать программу немедленно очень силен, особенно если пользуетесь языком Форт. Лучше обдумать проблему в течение часов, дней или иногда недель. Если вам не терпится сесть за терминал, примите решение по части проекта, которая вам ясна, и приступайте к работе над ней. Например, мы упражняемся с описаниями CONTROL, чтобы проще описать ESC- последовательность. То же самое было сделано, когда мы присваивали функции клавишам в KEYVECTORS. Упражнение с частью программы может быть очень полезным, например, для прояснения общего плана.
Упражняясь с частью программы, вы часто обнаруживаете, что вам нужно отладить слово, которое использует еще неописанные слова. В этом случае полезно использовать слова, называемые подставками. Подставки не выполняют никакой работы, но сообщают вам, что слово исполнено. Например, KEYVECTORS может быть проверено путем замены слов между ] и [ на CRTLA, CTRLB и т.д. до CTRLZ. CTRLA будет подставкой: : CTRLA ." Control A typed" ; KEYVECTORS и KEYDO и даже примитивная версия EDITCASE и EDITLOOP могут быть таким образом протестированы.
Такого рода упражнения могут быть весьма полезны при формировании вашего плана. Мы выполнили это отчасти еще до начала программирования и еще больше в процессе написания редактора. Так как наши основные планы были сформулированы, мы приступили к написанию настоящей программы.
Мы сначала написали редактор с минимальным числом функций для того, чтобы иметь базовую версию, в пределах которой не могли отлаживать новые команды. Таким образом, EDITCASE и EDITLOOP были написаны прежде, чем многие более ранние слова. По мере формирования редактора связанные слова группировались в некоторых блоках, а слова низкого уровня были помещены в первую пару блоков, если необходимость в них становилась очевидной. Когда редактор начал работать, были добавлены и отлажены некоторые новые команды. Процедура программирования состояла из создания слов низкого уровня, от которых все зависит, слов высокого уровня для обеспечения тестовых задач, слов среднего уровня для большинства редактирующих команд (добавления слов низкого уровня по мере необходимости), и в заключение проводились отладка и доводка всего в целом.
Благодаря возможностям, которые предоставляются в сфере тестирования и модификации, Форт идеально подходит для внедрения новых идей в работающие программы. Ключом решения проблемы является разработка базового набора слов. Другими словами, следует разработать удобную номенклатуру слов. Это ключевая концепция программирования на Форте. Номенклатура - это "система наименований, используемых в конкретной области знаний или искусства какой-либо школой или личностью, в частности названий, служащих при классификации для достижения различия с другими техническими терминами (Webster's New Collegiate Dictionary G. & С. Merriam Co., Springfield, Mass., 1959). Спецификация программ - первый шаг в процессе конкретизации проблемы. Следующий шаг - сокращение спецификации до номенклатуры, приемлемой для данной задачи. Если номенклатура зафиксирована, тогда известны слова среднего уровня и остается написать программу, используя эту номенклатуру.
Этот процесс разделения проблемы на более мелкие, которые решить легче, называется факторизацией. Хотя факторизация могла бы быть выполнена на любом универсальном языке ЭВМ с тем, чтобы расщепить проблему на части, с которыми можно работать на этом языке, эта процедура особенно проста и интуитивна на Форте. В Бейсике, Паскале и большинстве других языков факторизация состоит в разработке подпрограмм или процедур, которые вызываются при необходимости. Но в этих языках (в особенности в Бейсике) факторизация часто очень трудна для начинающих. В Форте подход к решению проблем является более интуитивным (и в действительности диктуется самим языком), так как каждое слово Форта является фактором. Каждое слово - это часть номенклатуры, которая разработана в процессе решения проблемы и имеет большое значение в описании решения. В идеале каждое слово воплощает лишь одну идею, так что его использование при построении более сложных концепций в последующих словах довольно легко понять. Слова высокого уровня, хорошо написанной Форт-программы должны читаться почти так же, как словесное описание проблемы. В действительности программа Форта в заметной мере самодокументируется. (Вы видели это в упражнении 2 предшествующего набора задач.)
Существуют различные пути, какими можно факторизовать задачу. В редакторе позиция курсора может быть выражена через число байтов от начала буфера или номера строчки и столбца. Эти два метода представления положения курсора эквивалентны потому, что номера строки и столбца могут быть вычислены на номера байта с помощью операции 64/MOD. Так какой же способ предпочтительнее? Имеются два соображения; положение курсора легче воспринять, зная строку и столбец, да и программировать так легче: строка и столбец используются независимо более часто, чем номер байта в буфере (который можно легко вычислить с помощью ROW и COL). Часто .способ, которым произведена факторизация, сильно влияет на то, как пишется остальная программа. Хотя имена слов в стандартном Форте могут быть весьма информативны (до 31 символа), длина немногих из них достигает даже трети от этой величины.
Почему? По одной причине: длинные имена долго печатать. Может быть, более важно, что они занимают больше место на диске, которого всегда не хватает. Часто сокращения - наилучший путь сохранить информативность имени. Слова базового Форта (такие как @ и !) могут использоваться в именах слов, обозначая извлечение и запоминание чисел (как в @CURSOR и !CLIRSOR), Если слово заносит флаг, включает в себя процедуру выбора или ответа на вопрос, его имя может содержать "?" (как ?BLK# и ?EXIT). И конечно, может использоваться любое число индивидуальных сокращений. Очевидно, баланс должен лежать между чрезмерно длинными словами - описателями и короткими крайне непонятными. Ясно, также, что все сокращения и личные системы кодирования имен слов должны применяться согласованно, что-бы иметь какую-то ценность. Хорошим тестом является проверка можете ли вы читать имена слов вслух, что скажет вам, разумны ли ваши сокращения и, таким образом, будут ли они понятны спустя какое-то время. Другой полезной идеей является присвоение словам имен, которые говорят о том, что они делают, а не как. Важно рассматривать слова Форта как концепцию и часть решения проблемы, а не как часть программы, которая что-то делает. Слова Форта часто проще назвать, если вы отслеживаете их функцию в контексте всей программы. Тезаурус, кстати, может быть таким же ценным инструментом в Форте, каким он является при письме.
Комментарии идут "рука об руку" с хорошими именами слов и имеют целью сделать ваш текст программы понятным как при написании, так и в дальнейшем. Комментарии и состояние стека следует вводить щедро в процессе программирования, чтобы вы могли отслеживать то, что вы ожидаете от ваших слов. Лучше всего вычислить и записать эволюцию стека в слове сразу при его описании, даже если никакого изменения стека не происходит. Эта привычка может весьма упростить задачу ознакомления с вашими новыми словами, так как вы не должны каждый раз просматривать их описания, когда вы их используете.
Конечно, после того, как программа написана, вы должны потратить столько времени, сколько нужно на тщательное оформление вашей программы и блоков, чтобы они были как можно более читаемыми.
Другой полезной привычкой является использование первой строки каждого блока для информации о содержании блока. Информация, обычно включаемая в эту индексную строку, содержит дату последней модификации, заголовок программы, инициалы автора, относительный номер блока и общее число блоков в программе. Некоторые программисты любят перечислять в индексной строке имена слов, описанных в блоке, чтобы облегчить поиск описаний. Порядок размещения этих данных не играет роли, если использованная очередность позволяет выделить важную информацию.
Выводы
Намного легче ремонтировать автомобиль, который на ходу, чем тот, который даже не поставлен на колеса. То же справедливо и для программирования на Форте: как только программа готова, чтобы что-то делать, появляются идеи по ее улучшению и совершенствованию. Если окончательная отладка программы доставляет удовольствие, то полная переделка, когда вы думали, что почти все завершено, - вряд ли. Единственный путь избежать больших задержек при программировании - попытаться предвидеть трудности до того, как они случились. Это делается путем формирования ясной идеи относительно проблемы в целом, прежде чем писать программу.
В любом случае вы должны иметь ясное представление о вашей задаче и целях, прежде чем вы приступите к программированию (на любом языке). Программирование на Форте более творческое, интерактивное и итеративное, чем на других языках. Чарльз Мур создал Форт для своих собственных приложений с целью увеличения личной производительности как программиста. Мы надеемся, что эта глава прояснила, почему форт увеличивает производительность. Форт называют усилителем идей. Существует несколько причин этого. Расширяемость Форта предлагает вам большой выбор. Применение слов Форта разделяет проблему на части, стимулируя логическое мышление.Интерактивная природа Форта способствует быстрой проверке, позволяет вам оттачивать ваши идеи, а использование в Форте длинных имен, аналогичных словам естественного языка, позволяет писать легко читаемые программы. Наконец, Форт дает вам больше власти над ЭВМ, чем может дать какой-либо другой язык. Но было также сказано, что Форт может сделать хорошего программиста великим, а плохого - ужасным. Если вы не будете дисциплинированны в определении вашей задачи, в факторизации и субфакторизации проблем, в определении слов с функциональными именами, в написании хороших комментариев и в расположении вашей программы и блоков разумным образом, вы попадете в категорию ужасных. Надеемся, что эта глава поможет вам двигаться в другом направлении.
Память Форта. Словари и контекстные словари
Теперь мы знаем, что Форт предоставляет вам большую власть над ЭВМ, чем большинство других языков. Целью данной главы и двух последующих является предоставление вам возможности еще большего контроля. Форт обеспечивает значительную гибкость там, где это возможно, за счет создания новых слов-описателей или, например, написания редактора. Но для использования всех преимуществ Форта, его мощи и максимальной гибкости вы должны иметь хорошее понимание внутреннего устройства языка. Вы должны понять, например, как Форт использует память ЭВМ, как устроен словарь и как он работает. Мы раскроем эти темы в данной главе. Вы должны также понять, как Форт интерпретирует входной поток информации, как он интерпретирует слова и как происходит исполнение слов. Это темы гл.15. Наконец, для достижения полного контроля над ЭВМ некоторые вещи должны выполняться на ассемблере. Мы обсуждаем Форт-ассемблер в гл.16.Теперь вы уже сформировавшийся программист и можете писать сложные программы на Форте, но внутреннее устройство языка является для вас, вероятно, таинственным. Эти три главы раскроют тайну, фактически мы расскажем вам кое-что, что действительно необходимо знать, чтобы создать версию Форта. Существует несколько тем. такие как метакомпиляторы и целевые компиляторы, декомпиляторы и дисассемблеры, использование в операционных системах, связь с аппаратурой и различные пути реализации цепных программ ("шитых кодов"), которые мы не рассмотрим здесь, - это тема другой книги, Но когда вы закончите последние три главы, у вас будет все, что нужно практически для любых приложений языка.
Об использовании памяти в Форте
Мы немало написали о словаре, стеке, словах PAD, HERE, блочных буферах и других частях Форта безотносительно к их действительному положению в памяти. Хотя детальное знание распределения памяти в Форте не нужно для использования языка, полезно иметь карту памяти, чтобы сделать наглядным то, как организован Форт. Карта памяти (табл.14.1) является последовательным списком позиций в памяти и их функций.
Одни адреса показаны с их Форт-именами, в то время как другие отмечают границы областей, используемых для
Таблица 14.1. Типичная карта памяти (MMSFORTH)
Адрес Шестнадцатеричный Десятичный функция
0 0 BLOCK ; блочный буфер 1 402 1026 BLOCK ; блочный буфер 2 44Е 1102 TIB ; текстовый входной буфер Недокуметировано Различные системные величины и программы 804 2052 ' FORTH ; начало словаря: ядро программ в машинных кодах 9С4 2500 Словарь, исходные тексты не поставляются 2008 8200 ' HEX : Словарь, исходные тексты поставляются 1А38 19000 Словарь, Скомпилированные программы пользователя Разное HERE, DP @; верх словаря: место для приема данных от WORD Разное PAD ; временный буфер Свободная память (переменного размера) Разное SP@ или 'S: верх стека параметров 79E0 31200 S0 @ или S0: начало стека параметров Разное Верх стека возвратов Разное Начало стека возвратов Разное Начало дополнительного блочного буфера 7D00 32000 Конец дополнительного блочного буфера
Адреса памяти неточны, правильные значения смотрите в вашем руководстве по программированию.
других целей. Таблица 14.1 представляет собой карту распределения для конкретной реализации MMSFORTH; карта вашей системы будет другой. Она может отличаться только специфическими адресами или может быть фундаментально другой, но описание, использующее MMSFORTH в качестве примера, поможет вам понять функциональные элементы почти любого Форта.
Если карта памяти не поставлена вместе с документацией вашей версии Форта, вы можете подготовить ее сами на примере таблицы 14.1. Начните с пометки первой и последней ячеек памяти вашей ЭВМ вверху и внизу листа бумаги и, заполнив столько позиций, сколько вы сможете, используйте слова Форта, как в нашей модели, или их эквиваленты из вашего Форта. Вы можете выполнить некоторое исследование и изучить вашу документацию, и, если только вы не имеете версию Форта с очень необычной организацией (HS/FORTH, например), вы сможете все разрисовать.
Хотя карты памяти и отличаются, благодаря особенностям работы Форта должны быть и сходства.
Все версии Форта должны иметь блочные буферы, программы в машинных кодах, словарь, свободную память, а также стек параметров и стек возвратов; они составляют основу конструкции языка. Но стандарты определяют только способ поведения Форта, а не то, как это поведение реализуется. Таким образом реализации отличаются. Многозадачные и многопользовательские системы будут особенно различными, и мы не будем здесь их обсуждать. Несмотря на это, последующее обсуждение карты для MMSFORTH применимо к большинству других версий Форта, даже если конкретные адреса памяти и будут отличаться.
В MMSFORTH область младших адресов памяти содержит два блочных буфера, сразу за ними размещен текстовый входной буфер. Многие версии имеют блочные буферы в верхней части памяти. Эти адреса вы можете найти с помощью слов BLOCK и TIB. Вслед за входным буфером, но до словаря, лежит область системных величин и программ в машинных кодах, которые имеют отношение к функциям примитивов языка. Сюда относятся данные о числе и типах дисковых драйверов, значения по умолчанию и текущие значения системных переменных, таких как указатель стека возвратов, а также программы в машинных кодах, используемые всеми словами Форта. Значения переменных пользователя (такие как BASE, STATE и BLK) также запоминаются в массиве перед словарем, указания на их адреса обеспечиваются соответствующими словами. Это позволяет установить все переменные пользователя (например, при инициализации системы) простой засылкой таблицы в память.
Первым словом словаря является FORTH, которое представляет собой действительное имя контекстного словаря Форта (об этом подробнее в следующей главе). Словарь, конечно, включает в себя все слова Форта и их откомпилированные описания. Младшая часть словаря в MMSFORTH имеет предкомпилированную форму, загружается непосредственно с системного диска и не может быть легко изменена пользователем. Эта младшая часть состоит из описаний системных слов и собственных программ, что отличает MMSFORTH от других версий.
HEX - первое слово MMSFORTH, текст которого доступен для программиста. Тексты форт-описаний,начиная с HEX и далее, хранятся в виде блоков на диске и могут компилироваться по выбору или модифицироваться с целью получения версии MMSFORTH, отвечающей вашим собственным требованиям. Этот текст включает в себя расширение компилятора, ассемблер, программы управления печатающим устройствам, экранный редактор и многое другое. Когда первая часть этого "репертуара" блоков загружена, MMSFORTH становится согласованным со стандартом Форт-79. Некоторые версии Форта предоставляют текст почти всего словаря, для других это предоставляется за дополнительную плату. Некоторые используют мета компилятор для трансляции ядра словаря, а некоторые как MMSFORTH, рассматривают ядро словаря как собственность. Не важно, каков размер словаря, адрес первого доступного байта после словаря засылается в стек оператором HERE. HERE берет значение указателя словаря (переменная пользователя с именем DP в MMSFORTH и большинстве других версий, но ни в одном из стандартов она не упомянута) и заносит его в стек. Слово HERE можно описать как : HERE DP @ ;
В целях ускорения его можно описать в машинных кодах. Хотя мы часто обращаемся к HERE так, как если бы это была константа, которая засылает в стек адрес конца словаря, нужно помнить, что это в действительности слово, которое выдает значение переменной - пользователя. То есть вы не можете ее изменить, дав команду HERE !. Конечно, значение, выдаваемое HERE, изменяется по мере пополнения словаря при компиляции новых слов или в результате работы оператора FORGET, удаляющего слова из словаря. Действительно, : ALLOT DP + ! : является описанием, используемым в некоторых версиях Форта для изменения значения HERE.
Вспомним из гл.9, что память, начиная с HERE и далее, используется оператором WORD в качестве временного буфера или области для запоминания, а "плавающая" зона, отстоящая от HERE на фиксированное число байтов, является временным буфером, адрес которого сообщает PAD.
Слово PAD можно описать как : PAD HERE n + ; . где n- фиксированное число (по крайней мере 65 в большинстве версий Форта и по меньшей мере 85 в Форт-83). Как и в случае HERE, легко подумать о PAD как о константе, а не как о слове, которое вычисляет адрес. Как вы видели в предшествующих главах, PAD весьма полезен для временного запоминания данных, так как он никогда не может быть в конфликте со словарем. PAD не пригоден для длительного запоминания, потому что некоторые слова Форта используют его и из-за того, что он смещается при изменении размера словаря.
В большинстве версий Форта стек параметров размещается в области старших адресов памяти и в конце первых 64К байт (далее могут размещаться блочные буферы). Между PAD и стеком параметров - переменное число свободных ячеек памяти (сам временный буфер), это число зависит от нескольких факторов, включая размер памяти ЭВМ, от конфигурации Форта, размера словаря и числа кодов в стеке параметров. Свободная память может лежать в интервале от нескольких до многих тысяч байтов и может быть использована для системных процедур, таких как форматирование или создание копий содержимого диска. Главным образом область свободной памяти предназначена для использования словарем или стеком. Хотя словарь увеличивается от малых адресов к большим, стек параметров в действительности растет от своего начала в сторону меньших адресов. Это позволяет использовать свободную память как для словаря, так и для стека по необходимости. Вы можете понять, почему переполнение стека имеет катастрофические последствия. Переполненный стек может наложиться на PAD, HERE и на верхнюю часть словаря, разрушив его. Это может, вероятно, случиться при беззаботном описании слов, которые оставляют в стеке что-то при зацикливании. И конечно, размер словаря ограничен, так как нельзя двигать PAD дальше, чем на верх стека параметров.
Стеки
Как контролируется содержимое стека? С помощью указателя. Если мы произвольно установим начало стека по адресу 1000, мы сможем представить шесть байтов области стека как
Адрес Содержимое
995 х 996 х Указатель стека 997 2 LSB Верх стека (SP@ или 'S) 997 998 0 MSB 999 1 LSB 1000 0 MSB Начало стека (S0 или S0 @)
где каждый адрес содержит старший (MSB) или младший (LSB) байт числа. В этом случае стек содержит 1 и 2, а каждый символ "х" проставлен для неопределенного байта. Число 2 находится на верху стека, так как на адрес его младшего байта указывает указатель стека. Если мы положим в стек 3 с помощью 3 таблица измениться:
Адрес Содержимое
Указатель стека 995 3 LSB Верх стека (SP@ или 'S) 995 996 0 MSB 997 2 LSB 998 0 MSB 999 1 LSB 1000 0 MSB Начало стека (S0 или S0 @)
Два байта, представляющие число 3, занесены в ячейку с адресами 996 и 995, а указатель стека уменьшен на 2. Если вы теперь напечатаете DROP
результатом будет
Адрес Содержимое 995 3 996 0 Указатель стека 997 2 LSB Верх стека (SP@ или 'S) 997 998 0 MSB 999 1 LSB 1000 0 MSB Начало стека (S0 или S0 @)
Снова указатель стека просто увеличен на 2. Форт может стирать или не стирать 3, которая "удалена" из стека. Число 3 может остаться, так как любое новое число, положенное в стек, просто заместит его там. Стек по существу - массив с полуавтоматической укладкой и извлечением чисел. Если вы немного знакомы с тем, как работает процессор, мы можем сказать, что обычно указатель стека параметров совпадает с указателем стека процессора. Ото обсуждается подробнее в гл.16.) Большинство версий Форта имеет нестандартные слова, позволяющие вам найти адреса начала и верха стека. Наиболее часто адрес начала стека хранится в переменной пользователя, так что SO @ заносит в стек этот адрес (MMSFORTH делает это), хотя в некоторых версиях SO само засылает в стек этот адрес, т.е. @ не нужно. Адрес верхней ячейки стека засылается в стек оператором SP@ (засылка указателя стека - Stack Pointer Fetch), хотя некоторые модификации Форта используют слово 'S (MMSFORTH использует SP@). Таким образом, если стек содержит
3 9 99 тогда S0 @ 2 - @ . S0 @ 4 - @ . S0 @ 6 - @ .
отобразит на экране 3 6 99 в то время как SP@ @ . SP@ 2+ @ . SP@ 4 + @ . выдаст на экран 99 6 3
С помощью этих слов можно описать другие полезные слова. Например: : DEPTH ( nl п2, - nl n2..,nn) SP@ SO @ SWAP - 2 / ;
Мы попросим вас описать некоторые другие слова в качестве упражнений. Как вы знаете, с целью экономии времени Форт не контролирует переполнение стека, так что только внимательное программирование предотвращает разрушение словаря ниже стека. Извлечение кодов из пустого стека намного более вероятно, так как довольно легко напечатать лишнюю точку с клавиатуры или неправильно оценить число аргументов, необходимых слову Форта. В такой ситуации указатель стека имеет значение больше, чем нижний адрес стека, т.е. если SP@ выдает число большее, чем SO @. Фактически все варианты Форта проверяют, не случилось ли такое событие при переходе к пультовому режиму и при многих операциях вывода. Обычно, если зарегистрировано извлечение кода из пустого стека, выдается сообщение об ошибке, а указатель стека устанавливается в начальное положение. Но возможно и разрушение системы без сообщения об ошибке. Например, если стек пуст, следующая программа сделает это: : BOMBIT 500 0 DO DROP LOOP ;
Слово BOMBIT почти наверняка разрушит Форт, и потребуется перегрузка ЭВМ. Форт возлагает больше ответственности за ошибки на программиста. Это одна из причин, почему Форт обладает высоким быстродействием. Потребовалось бы очень много времени, чтобы контролировать переполнение стека каждый раз, когда изменяется его указатель. Хотя в MMSFORTH входной текстовый буфер размещен за блочным буфером в области младших адресов, в некоторых вариантах Форта он помещен выше стека параметров. Во всяком случае, стек возвратов, который обсуждается в гл.8, обычно находится сразу за входным буфером или стеком параметров. Вы видели, что >R, R> и R@ позволяют использовать стек возвратов для запоминания значений из стека параметров и что стек возвратов используется для запоминания индексов цикла, но это не его главная функция.
Стек возвратов используется для записи адресов так, что Форт знает, куда вернуться, когда выполнение слова завершилось. Это описано в деталях в разделе об исполнении слов Форта в гл.15. Область между стеком возвратов и верхней границей памяти может использоваться для различных целей в разных версиях. В MMSFORTH эта область может быть зарезервирована для дополнительных блочных буферов, которые расширяют число буферов, размещенных ниже словаря, но это, конечно, соответственно сокращает количество свободной памяти. Организация памяти, которую мы описывали до сих пор, является типичной для ЭВМ, которые могут непосредственно адресоваться до 64К байт. Но 16-битовые ЭВМ, так же как IBM PC, могут работать с 1М байтом памяти и более, в то время как числа одинарной длины не могут описать адрес более 65535. Существуют различные пути решения проблемы, чтобы работать с большими массивами памяти в Форте. Может быть, простейшим и наиболее общим является предоставление основной части Форта младших 64К байт (для программ редко требуется больше памяти) и использование адресов двойной длины для адресации к данным, лежащим выше. Например, MMSFORTH использует организацию памяти, которую мы описали, но имеет слова для извлечения, запоминания, пересылки, резервирования и прочего использования памяти с адресами более 64К. Очень часто в этой области памяти организуется псевдодиск. MMSFORTH использует сходную схему, но предусматривает возможность воспользоваться метакомпилятором для другого Форта в области старших сегментов памяти и работать с ним в дальнейшем как с совершенно независимым языком (это делается, когда подготовлена новая версия Форта). В тех случаях, когда программа со словарем больше, чем позволяет адресоваться 16-разрядное слово, могут использоваться оверлеи, когда части словаря замещаются с диска, по мере необходимости (за счет потери быстродействия). По крайней мере, одна версия Форта, PC/FORTH+, программно использует 32-разрядные, а не 16-битовые числа как для стека, так и для адресуемой памяти (хотя и за счет места и скорости).
Таким образом, память может адресоваться через обычные коды и, хотя карта памяти организована так же, как было описано, программа может работать со всей доступной памятью. Может быть, наиболее сложное использование памяти большей емкости реализовано в HS/FORTH, где применены различные сегменты для различных частей описаний слов, для различных контекстных словарей и для стеков, буферов и т.д. Таким образом, дополнительная память используется многими компонентами Форта, в то время как 16- разрядные числа могут использоваться так же, как в Форте, в пространстве, ограниченном 64К байтами. Различные схемы использования памяти в 16-разрядных ЭВМ является темой для другой книги. Лучший путь понять, что где лежит, - это пропечатать на терминале большой кусок памяти. Мы предлагаем вам сделать это в упражнениях следующего раздела этой главы. Даже если ваш Форт имеет оператор DUMP, вы можете найти следующее описание полезным, так как оно выдает как ASCII, так и цифровое представление байтов. : DUMP ( начальный адрес, число строк --) CR BASE @ >R HEX ( спасение BASE, выбор шестнадцатеричной системы) 16 * OVER + SWAP ( вычисление индексов цикла) DO I 0 TYPE 2 SPACES ( печать номера строки) 16 0 DO ( Начало цикла по байтам) I 4 MOD 0= IF SPACE THEN ( группируем байты по 4) I J + C@ ( получаем байт) 0 TYPE SPACE ( печать байта) LOOP ( цикл для 16 байтов) CR 7 SPACES ( новая строка) 16 0 DO ( начало цикла для символов) I 4 MOD 0= IF SPACE THEN ( группируем символы по 4) I J + C@ DUP ( извлечение байта) 31 > OVER 127 < AND ( печатный ascii-символ?) IF EMIT 2 SPACES ( если так. печатаем символ) ELSE DROP 3 SPACES ( если нет. удаляем байт) THEN ( конец ветвления) LOOP CR ( цикл для 16 байтов) 16 +LOOP ( шаг в 16 байтов на каждой строке) R> BASE ! ; ( восстановление BASE пo завершении)
Слово DUMP предполагает, что в стеке лежит начальный адрес и число 16- байтовых строк, которые вы хотите отобразить. При выводе строки из 16 байтов пропечатывается шестнадцатеричный начальный адрес, за которым следует сами байты.
В следующей строке представляются ASCII- символы для печатных байтов, отображенных в предшествующей строке. Выводимые символы во многих случаях бессмысленны, но могут помочь вам найти откомпилированный текст или имена слов Форта (если они записаны в незакодированном виде).
Вы можете захотеть пропечатать часть или весь ваш словарь с помощью печатающего устройства (что потребует много страниц). Многие вещи, которые кажутся абстрактными и труднозапоминаемыми, становятся ясными, когда у вас есть четкая запись, в которую можно заглянуть. Это может быть непрактично, если ваш Форт запоминает слова в нетипичном формате (например, различные части слов записаны в разных сегментах памяти). Если вы действительно пропечатали вашу систему, будет полезным использовать цветные фломастеры для выделения каждого класса слов. Как можно найти и идентифицировать слова, будет ясно из следующего раздела.
Упражнения
1. Переведите ЭВМ в шестнадцатеричную систему счисления и введите числа 0, 10. FF, F00 и FFFF в стек. Что вы ожидаете получить, выдав команду SP@ I DUMP? Используйте DUMP, чтобы проверить ваш ответ. 2. Дайте ответ для упражнения 1 в случае чисел двойной длины 0. 10., FF., FFFF. и FFFFFFFF.. 3. Как вы можете определить, какое самое длинное число может быть интерпретировано при вводе с пульта в вашем Форте? Куда кладет Форт слово, которое компилируется? 4. Опишите С, и , (запятая), не используя ALLOT. 5. Если свободная память в вашем Форте лежит между PAD и верхом стека параметров, опишите слово.MEM, которое выдает на дисплей величину емкости этой памяти. 6. Опишите PICK как NEWPICK. используя SP@ или 'S в зависимости от того, что приемлемо в вашей системе. 7. Опишите NEW.S используя S0. He используйте ROLL. 8. Опишите ZERO-STACK, которое заполняет стек нулями, не меняя указателя стека. Не используйте DO-LOOP, примените FILL.
Как слова Форта записаны в словаре?
Ключом к пониманию того, как работает Форт и почему он легко расширяем, является структура слов Форта и то, как они записываются в словаре. (Помните, что существует много способов сделать это и что мы описываем наиболее общий из них с целью пояснения.) Термины Форта "слово" и "словарь" действительно весьма близки к их исходному смыслу, так как (как и в русском языке) слова Форта определены через другие слова, которые, сгруппированные вместе, образуют словарь.
Аналогия на этом не кончается: слова Форта могут быть организованы в контекстные словари и одно и то же слово может даже быть использовано в нескольких разных контекстах. Давайте рассмотрим один элемент словаря. Если мы введем: : BASE? BASE @ DUP DECIMAL . BASE ! ; почти все версии Форта скомпилируют его в словарь в формате, содержащем поле имени и поле связи (в совокупности называемые заголовком), а также поле программы и поле параметров (называемые телом слова). Заголовок используется при поиске в словаре, в то время как тело управляет тем, что слово должно делать. Четыре поля могут быть представлены в виде:
Заголовок Тело (поле имени)(поле связи) (поле программы)(поле параметров)
Адресам первого байта имени, ячейки связи, программы и поля параметров слова часто присваиваются сокращения NFA, LFA, CFA и PFA. Так, если BASE? было скомпилировано, начиная с шестнадцатеричного адреса 7000, его представление в словаре может быть описано как
Заголовок Тело Поле ИМЕНИ СВЯЗИ ПРОГРАММЫ ПАРАМЕТРОВ Длина 4 байта 2 байт 2 байта (варьируется) Адрес NFA=7000 LFA=7004 CFA=7006 PFA=7008
К несчастью, терминология Форта стала неоднозначной и путаной при использовании терминов NFA, CFA, LFA и PFA. Они часто используются не только как адреса первого байта поля, но и как содержимое поле. Так как все поля, кроме поля имени, содержат адреса (например, поле связи содержит адрес или указатель, используемый при просмотре словаря), это сильно все путает. Мы используем эти термины для обозначения только адресов полей и советуем вам поступать так же. Мы будем ссылаться на содержимое поля связи, например, называя его именно так или, может быть, как адрес или указатель, лежащий в поле связи. (Некоторые считают, что следует использовать сокращения ANF, ACF, ALF и APF, чтобы избежать путаницы. Мы же чувствуем, что, введя больше терминов, можно в действительности внести еще больший разнобой.) Давайте посмотрим, что из себя представляет каждое поле и что оно делает. Поле имени содержит имя слова, оно служит для того, чтобы было можно найти слово Форта в словаре, например, позволяя Форту исполнить слово, имя которого было введено с последующим нажатием клавиши "возврат каретки".
Имя (такое как "BASE?") кодируется каким-то способом (в зависимости от версии), прежде чем быть запомненным. Мы скоро увидим подробнее, как кодируются имена. Следующая часть элемента словаря - поле связи содержит просто указатель на другое слово, уже описанное в словаре. Этот адрес используется, чтобы направить поиск на слово, описанное перед этим, обычно на его NFA. Слово, на которое указано, может лежать непосредственно перед данным словом или заметно раньше, в зависимости от структуры связи в конкретной версии. Полезность поля станет яснее, когда мы позднее в этой главе обсудим контекстные словари. Поле программы содержит указатель на программу в машинных кодах, которая исполняется при использовании слова. Эта программа в машинных кодах определяет, wrn делает слово и к какому типу оно относится. Все слова определенного типа (описания, начинающиеся с двоеточия, константы, переменные и т.д.) имеют один и тот же адрес в своем поле программы. Таким образом, константа (созданная оператором CONSTANT) имеет адрес в ее поле программы, указывающий на программу в машинных кодах, которая копирует в стек число из его поля параметров. Эта программа для каждого типа слов называется исполнительной. Адрес поля программы всех описаний, начинающихся с двоеточия, указывает на исполнительную программу, которая управляет выполнением слова в соответствии со списком адресов, хранящихся в поле параметров. Исполнение слов типа : и других слов рассмотрено подробнее в гл. 15. (Действительно использование поля программы, которое мы описали, представляет собой то, что называется косвенно цепной программой. Существуют другие возможности, но они используются нечасто.) Длина поля параметров слова варьируется значительно в соответствии с типом слова и способом его описания. Она может в диапазоне от одного байта (как в словах, описанных CCONSTANT) до многих тысяч байт (скажем, в массивах). Конкретное содержание поля параметров зависит от типа слова. Например, поле параметров константы, переменной и массива содержит последовательность байтов, или данные, в то время как строка-константа будет иметь в поле параметров счетную строку. (Адрес, выдаваемый любым словом, созданным с помощью CREATE, равен PFA этого слова.) Поле параметров слова типа двоеточие содержит адреса, обычно CFA слов, используемых в описании слова.
Если мы выделим CFA слов, используемых при описании BASE?, скобками ({...}), содержимое поля параметров BASE можно будет представить как
Поле параметров {BASE} {@} {DUP} {DECIMAL} {.} {BASE} {!} {EXIT}
В последней ячейке поля параметров любого описания типа двоеточие лежит адрес слова, названного EXIT, которое кладется туда оператором ; (точка с запятой). Слово EXIT необходимо, чтобы за вершить исполнение слова и передать управление слову, из которого произошло обращение. (Это исчерпывающе объяснено в гл.15.)
Как запоминаются имена?
Простейший способ запомнить имя слова BASE? в словаре заключается в том, чтобы записать число букв в слове, после чего занести буквы в ASCII представлении, т.е. оформить как счетную строку. Если записано имя полностью, тогда имеется возможность получить полный список слов в словаре. Это делается посредством слов WORDS, VLIST или CATALOG, в зависимости от версии. Данное преимущество компенсируется потерями в памяти и увеличением времени, необходимого для поиска слова. Форт-79 и Форт-83 специфицируют только то, что различные слова должны иметь до 31 символа и быть уникально закодированы и узнаваемы. Как это сделать, оставлено на усмотрение разработчику языка. Общим нестандартным методом является запоминание числа символов в исходном имени, за которым следуют только первые три символа имени.
В некоторых версиях пользователь может определить максимальное число запоминаемых символов, изменяя величину системной переменной с именем WIDTH, прежде чем компилировать текст программы. Опасность укорочения исходного имени до заданного числа символов заключается в том, что длинные имена с равным числом символов могут оказаться не уникальными. Таким образом, BASE? и BASE! следует переименовать на ?BASE и !BASE для того, чтобы они были различимыми. MMSFORTH использует необычную схему. Он кодирует (хэширует) слова так, чтобы вставить как можно больше информации в четыре байта поля имени. Таким образом, никакого дополнительного места в словаре не используется, так как имена укорочены, но вероятность неуникальности имени сведена к минимуму.
Это в принципе соответствует регламентации стандартов, но не буквально. Преимуществом является экономия места и сокращение времени поиска. К недостаткам относится невозможность декодирования имен слов и, как следствие, недоступность списка слов в словаре. Аналогичный метод кодирования имен вы можете применить и в вашем Форте. Так как только 5 битов первого байта поля имени необходимо для кодирования максимального числа символов (31), три остальных бита байта длины используются для других целей. В MMSFORTH (и некоторых других версиях Форта) первый бит поля имени равен 1 для слов немедленного исполнения (например, если оно исполняется даже при компиляции слова типа двоеточие подобно.( ). В Форте, где запоминается все имя, старший бит первого и последнего символов (включая байт длины) устанавливаются в единичное состояние, для того чтобы отметить начало и конец имени. (Так как все символы - имени ASCII, их старшие биты равны 0.) Эта информация может быть использована нестандартным словом TRAVERSE (см. стр.195). Другие свободные биты могут быть равны 0 или 1 в зависимости от того, откомпилировано ли слово без ошибок или допустимо ли, чтобы оно было найдено в словаре при поиске. Бит, который используется для того, чтобы слово могло быть найдено, называется бит-метка, для переключения этого бита используется нестандартное слово SMUDGE. Имеет смысл упомянуть еще раз, что, хотя структура словаря, которую мы описали, является наиболее распространенной, она не является единственно используемой. Могут применяться другие варианты для языка в целом или только для отдельных слов. Например, можно компилировать слова с более чем одним полем программы, как это сделано в слове QUAN MMSFORTH, которое использует три поля программы. Каждое CFA содержит указатель на исполняемую программу, который придает словам QUAN три различных образа поведения, когда они используются с IS, AT и сами по себе. Слово F-83 Лаксена и Перри компилирует номер блока, откуда извлечена программа, в заголовок слова, так что оператор VIEW может сообщить, в каком блоке лежит описание данного слова.
Возможны и более радикальные варианты. Функциональные элементы слов Форта (имя, ячейка связи, поля программы и параметров) могут храниться даже в различных частях или сегментах памяти, как в HSFORTH и (немного иначе) в MacFORTH.
Нахождение частей слов
Вы уже знаете наиболее часто используемые слова Форта, предназначенные для поиска адресов других слов в словаре ' (апостроф), ['] и FIND, которые находят CFA или PFA в зависимости от того, используете вы Форт-79 или Форт-83. И вы так же знаете о слове >BODY в Форт-83, которое находит PFA для данного CFA, лежащего в стеке. Слово FIND в Форт-83 имеет совершенно другой смысл по отношению к Форт-79. В стандарте 83 слово FIND предполагает наличие в стеке адреса счетной строки. Затем FIND ищет эту строку в словаре и, если находит, заносит в стек CFA и 1 или -1 на верх стека. Если слово в описании типа двоеточие помечено как слово "немедленного исполнения", в стек заносится 1, если же оно работает только в режиме исполнения - то -1. Если строка в словаре не найдена, в стек заносится адрес исходной строки, а на верх стека - 0. В Форт-83 слово FIND может использоваться не только подобно ' с EXECUTE, но решение может быть принято в зависимости от того, является ли слово оператором немедленного исполнения и найдено ли слово вообще. В действительности FIND используется главным образом для внутренней работы языка при компиляции, а не для целей программирования. Многие версии снабжены дополнительными словами для вычисления адресов различных полей, выдавая PFA или NFA в стек. Их работа пояснена ниже:
NFA "n-f-a" (PFA - NFA) LFA "l-f-a" (PFA - LFA) CFA "c-f-a" (PFA - CFA) PFA "p-f-a" (NFA - PFA)
NFA использует нестандартное слово TRAVERSE, которое просматривает поле имени произвольной длины из конца в конец (в любом направлении), находя начальный или конечный байт, это позволяет работать с именем любой длины. Хотя Форт-79 и Форт-83 категорически запрещают изменение содержимого скомпилированных слов в стандартной программе, экспериментальный стандарт Форт-83 признает полезность нахождения адресов полей слова.
Предлагаются следующие слова:
>BODY "to-body" (CFA - PFA) (стандарт Форт-83) >NAME "to-name" (CFA - NFA) (к полю имени) >LINK "to-link" (CFA - LPA) (к полю связи) B0DY> "from-body" (PFA - CFA) (от PFA) NAME? "from-name" (NFA - CFA) (от NFA) LINK> "from-link" (LFA - CFA) (от LFA) N>LINK "name-to-link" (NFA - LFA) (от имени к LPA) L>NAME "link-to-name" (LFA - NFA) (от LFA к имени)
Если эти или аналогичные слова отсутствуют в вашем Форте, вы можете сами описать их. Например, используя структуру словаря, которую мы описали, с именами длиной в четыре байта, будет довольно просто составить их описание: : >BODY ( cfa - pfa) 2 + ; или : L>NAME ( lfa - nfa) 4 - ; Мы попросим вас попытаться описать такие слова в качестве упражнения.
Упражнения
1. Предположим, что версия форта имеет поле имени длиной 4 байта, поле связи и поле программы по 2 байта. Опишите остальные предлагаемые Форт-83 слова, приведенные выше ( >NAME, >LINK и т.д.). 2. Запишите положение слово HERE, введите описание BASE?, которое мы использовали, пропечатайте эту часть словаря с помощью DUMP, для того чтобы посмотреть, можете ли вы идентифицировать его имя. поля связи, программы и параметров, а также их содержимое. Используйте ' (Форт-79) или ' >BODY (Форт-83) с BASE? и выполните команду DUMP для полученного адреса. 3. Опишите по крайней мере пять слов в форме : 1DUMMY ;, : 2DUMMY ; и т.д. и пропечатайте посредством DUMP каждый из этих элементов словаря, чтобы посмотреть, куда в предшествующем слове указывает содержимое поля связи. Указывает ли каждое поле связи на предшествующее слово или какое-либо другое? 4. Найдите адрес исполняемой программы для слов типа двоеточие. Зная этот адрес, опишите слово COLONWORDS, которое воспринимает два адреса со стека и находит CFA всех слов типа двоеточие, которые лежат между указанными адресами.
Контекстные словари
Концепция контекстных словарей расширяет буквально смысл метафоры, примененной в отношении определений слова и словаря.
Контекстный словарь не используется в смысле "ее разговорный словарь велик", это не означает просто набор слов. Пожалуй, фразы "словарь джаза", "медицинский словарь" или "инженерный словарь" ближе по смыслу, вкладываемому Форт(1). В Форте контекстный словарь - это совокупность родственных слов, которые ищутся как целое, т.е. он представляет собой секцию базового словаря. Другим подходом к этому могут быть контекстные словари - это наборы слов, описания которых в словаре связаны друг с другом. Контекстные словари Форта предоставляют вам контроль за тем, как производится поиск слов в словаре- Все программы, которые мы составили до сих пор, использовали только один контекстный словарь - словарь FORTH, начинающийся словом с именем FORTH. He удивительно, что первичный контекстный словарь имеет имя FORTH, часто поставляются два других контекстных словаря (называемые EDITOR и ASSEMBLER). Первый содержит слова, используемые для редактирования текстов программ, а второй включает слова для компиляции программ, написанных на ассемблере (см.гл.16). Когда введено одно из слов EDITOR или ASSEMBLER, слова, принадлежащие к этому контекстному словарю, просматриваются первыми вне зависимости от того, когда они описаны или когда загружен словарь. Поиск в пределах любого контекстного словаря производится сверху вниз, т.е. начиная со слов, описанных только что, в направлении слов, загруженных раньше. Когда поиск в специализированном контекстном словаре завершен, он продолжается в словаре FORTH (снова сверху вниз). FORTH является словарем по умолчанию и всегда просматривается вне зависимости от того, какой словарь или комбинация словарей просматривались первыми, С другой стороны, если исполнено слово FORTH, тогда просматривается только контекстный словарь FORTH, а слова из других словарей игнорируются.
Легко понять, что просмотр словаря не может происходить непосредственно от слова HERE до слова FORTH, скорее, он следует структуре логического дерева, где контекстный словарь FORTH
1 В русском языке трудно подобрать отличные друг от друга слова для DICTIONARY и VOCABULARY. Последнее переведено - контекстный словарь. - Прим. перев.
является стволом, а другие контекстные словари - ветвями, ведущими к стволу. Эта структура позволяет вам просмотреть или добавить любой контекстный словарь вне зависимости от того, где он размещен в словаре. Предположим,что вы определили слово, использующее мнемонику ассемблера со словом CODE (о программировании на Форт-ассемблере см.гл.16). Одним из действий CODE будет то, что контекстный словарь ASSEMBLER просматривается первым, так что мнемоника ассемблера, которая компилирует машинные коды, будет находиться в словаре прежде, чем распознаются любые другие слова с тем же именем. Это не только несколько ускоряет компиляцию программы на ассемблере, но имеет важное следствие, позволяющее словам с идентичными именами иметь разные функции в различных контекстных словарях. Какое "значение" имеет слово, когда определены несколько слов с одним и тем же именем, зависит от того, какое из них будет найдено первым; а это задается порядком, в котором просматриваются контекстные словари. Имеется несколько способов введения контекстных словарей, и фактически внутренние детали в различных реализациях могут варьироваться значительно. Все контекстные словари в конце концов связаны со словарем FORTH, но они могут быть связаны также друг с другом. Контекстные словари пользователя могут быть связаны непосредственно со стволом FORTH или образовывать цепочку веточка-ветка-ствол, согласно которой и происходит поиск. Порядок просмотра для простой структуры будет выглядеть так:
(1) Контекстный словарь -> : (2) Контекстный словарь -> : -> (4) Контекстный словарь FORTH (3) Контекстный словарь -> :
где числами (1) - (4) помечены альтернативы начальных точек просмотра словаря. Этот метод связи словарей встречается в некоторых реализациях Форта, но более сложные методы позволяют контекстным словарям образовывать цепи, как показано ниже: (1) Контекстный -->: словарь :-> (3) Контекстный --->: (2) Контекстный -->: :->(5) Контекстный словарь словарь FORTH (4) Контекстный словарь------------------> : где снова просмотр может начинаться в одной из точек, помеченных цифрами (1) - (5), но если первым просматривается контекстный словарь (1), то следующим перед FORTH будет словарь (3).
Эта иерархия возможных путей поиска является гибкой, но потенциально не однозначной. Хорошей идеей является максимальное упрощение использования словарей, что обеспечит простоту чтения и отладки вашей программы.
Контекстный словарь создается словом-описателем VOCABULARY (name) и, когда (name) исполнено, поиск будет начат именно с этого словаря. Адрес контекстного словаря, который должен просматриваться первым, хранится в переменной пользователя CONTEXT, названной так в соответствии с ее функцией (как, например, в предложении "класс в образовательном контексте означает...но в контексте спортивной квалификации смысл его...)(1). Любое число слов с идентичными именами могут быть доступны, если все они определены в разных контекстных словарях. Конечно, если одно и то же имя присутствует в одном и том же контексте словаре, использоваться сможет лишь то, которое найдено первым.
Если вы хотите добавить описание в словарь с именем (name), напишите (name) DEFINITIONS где слово DEFINITIONS имеет описание ; DEFINITIONS CONTEXT @ CURRENT ! ; и где CURRENT является еще одной переменной пользователя, которая указывает на контекстный словарь, который только что подключен к основному. Вы, вероятно, будете не так часто непосредственно пользоваться словами CONTEXT или CURRENT, в то время как VOCABULARY и DEFINITIONS являются более удобочитаемыми и обеспечивают работу с контекстными словарями. (CONTEXT и CURRENT включены в Форт-79, но удалены из базового набора слов Форт-83 предположительно из-за того, что они редко, если вообще когда-либо, используются программистами.)
Следует еще раз подчеркнуть, что последовательность, в которой слова обнаруживаются при поиске в контекстном словаре, определяется не их расположением в словаре, а лишь содержанием по лей связи. Слова контекстного словаря ASSEMBLER, например, будут связаны с другими ассемблерными словами вплоть до самого последнего (т.е. слова ASSEMBLER). Это последнее слово будет связано с последним описанным словом из контекстного словаря FORTH.
Порядок просмотра словаря
1 В оригинале не класс, a school в значениях школа и косяк рыбы.- Прим. перев.
задается при компиляции слова. Когда с помощью слова VOCABULARY создан новый контекстный словарь (пусть он имеет имя STATISTICS), в поле параметров слова STATISTICS зарезервировано место для адреса слова, описанного в контекстном словаре STATISTICS последним. Каждый раз, когда добавляется новое слово, адрес предшествующего заносится в поле связи слова, а адрес нового слова укладывается в STATISTICS для использования при описании следующих слов. Когда просмотр достигает конца контекстного словаря, начинается просмотр сверху вниз словаря (FORTH или какого-то другого), в котором был описан предшествующий.
Мы описали два способа работы с контекстными словарями (связь только с FORTH или друг с другом и с FORTH), и в обоих случаях мы предполагаем, что в словаре существует только один ПУТЬ ПОИСКА. Пути поиска - это пути от конца словаря к его началу согласно указателям в ячейках связи слов, входящих в контекстный словарь. Используя более чем один путь поиска, можно заметно сократить время компиляции исходного текста программы. Заметьте, то, что мы называем путем поиска, другие называют "нитью". Мы избегаем слова "нить" в этом контексте, чтобы не путать путь поиска с цепочкой программ в машинных кодах, которая работает при исполнении скомпилированных слов и также называется "нитью" ("thread").
MMSFORTH предоставляет пример того, как можно использовать несколько путей поиска. Мы видели, что MMSFORTH запоминает слова в словаре, используя закодированные имена, чтобы различать слова со сходными именами. Если длина хэш-кода задана (0 - 31 в MMSFORTH), он может быть использован для управления поиском в словаре. В этом случае каждое имя контекстного словаря (например, ASSEMBLER или FORTH) вместо того, чтобы иметь один указатель на слово, описанное последним, имеет 32 таких указателя, по одному на каждый путь поиска. Когда слово компилируется, его хэш-код определяет, с каким из путей просмотра контекстного словаря его связать и где будет запомнен его адрес (в качестве будущего объекта связи для последующих слов того же маршрута поиска).
Когда слово ищется, его имя снова кодируется (формируется тот же хэшкод) и определяется маршрут поиска, где оно должно быть найдено. Таким образом, только 1/32 словаря должна быть просмотрена, чтобы найти слово, что значительно ускоряет компиляцию, так как поиск - процесс времяемкий. Если слово связано с маршрутом поиска на фазе компиляции, то, конечно, оно может быть найдено позднее на том же самом пути поиска. Этот метод требует большего по размеру описания слова и так же зависит от способа хэш-кодирования, который определяет номера маршрутов, которые (в среднем) распределяются равномерно. Правильное использование большого числа маршрутов просмотра всегда ускоряет поиск в словаре, независимо от того, используются ли контекстные словари.
Вы помните, что слово FORGET служит для удаления слов из словаря. Ниже показано, как оно работает. Формат обращения FORGET (name) если (name) отсутствует в текущем словаре, будет дано сообщение об ошибке. Слово FORGET устанавливает верхнюю границу словаря (указанную словом HERE и содержащуюся в переменной пользователя DP) равной первому байту слова (name) (т.е. его NFA), эффективно удаляя все слова и контекстные словари, описанные позднее. Может показаться, что FORGET не обращает внимания на контекстные словари и просто укорачивает словарь до имени (name), но FORGET делает намного больше. Все маршруты просмотра во всех затронутых словарях должны быть поправлены FORGET так, чтобы последующий поиск начинался с самого последнего оставшегося слова в контекстном словаре, из которого были удалены слова. Очевидно, что делается много больше, чем просто изменяется значение переменной DP. В некоторых, версиях слово FORGET, прежде чем что-либо делать, проверяет адрес, хранящийся в переменной пользователя FENCE, чтобы убедиться, что FENCE содержит адрес меньше, чем слово, которое следует забыть. Переменная FENCE, которая может быть задана как HERE FENCE ! защищает все, что лежит в словаре ниже адреса, который в нее записан, от случайного стирания.
Хотя стандарты и не требуют наличия FENCE, эта переменная удобна для предотвращения потенциально катастрофических ошибок. Хотя мы описали большинство наиболее употребляемых структур словарей, могут использоваться и другие структуры. Применение контекстных словарей в Форте находится в процессе изменения (как и некоторые другие части языка). Существует несколько хороших способов определить выборочный поиск в секции словаря, каждый со своими преимуществами и недостатками. Например, Форт-83 имеет экспериментальное предложение разрешить управление порядком поиска путем декларации, а не во время компиляции. Манипулирование контекстными словарями в основном весьма просто; это делается тремя словами: VOCABULARY, DEFINITIONS и FORGET, а так же именами индивидуальных контекстных словарей. В заключение: 1. VOCABULARY (имя) создает контекстный словарь с именем (имя). 2. (имя) делает (имя) контекстным словарем (т.е. поиск начнется с него). 3. (имя) DEFINITIONS делает (имя) текущим словарем, в который добавляются новые описания. 4. FORGET (имя) удаляет (имя) и все слова, описанные после (имя), если (имя) принадлежит к текущему словарю.
Главные ограничения контекстных словарей связаны с тем, что они могут приводить к путанице (в особенности когда связаны друг с другом) и к тому, что порядок поиска не может быть изменен, если исходный текст откомпилирован. Большинство программистов старается как можно меньше использовать контекстные словари, но, как мы увидим в следующем разделе, даже с учетом перечисленных ограничений словари могут быть вполне полезными.
Упражнения
1. Ваш Форт, вероятно, имеет слово (называемое иногда VLIST, CATALOG или WORDS) для выдачи списка слов в контекстном словаре. Как вы можете просмотреть слова всех контекстных словарей в вашем Форте? Как вы думаете указанные выше операторы находят слова, которые отображают? 2. Опишите новый контекстный словарь с именем A-VOC. Опишите четыре слова в словаре A-VOC, чтобы печатать некоторые фразы, и присвойте имена с префиксом А-, последнее слово должно иметь имя A-LAST. 3.Отпечатайте список слов в A-VOC, которые вы описали.
Сделайте FORTH контекстным словарем и повторите выдачу, Что получилось? 4. Попытайтесь выполнить A-LAST при контекстном словаре FORTH. Что получилось? Сделайте контекстным A-VOC и попытайтесь исполнить A-LAST снова. 5. Сделайте A-VOC контекстным словарем, опишите другой словарь B- VOC и добавьте несколько новых описаний, завершающихся словом B- LAST. Теперь посмотрите, можете ли вы исполнить A-LAST, находясь в B-VOC. Что говорит вам это о структуре контекстных словарей в вашем Форте? 6. Опишите еще одно слово с именем A-LAST в контекстном словаре B-VOC (используя B-VOC DEFINITIONS, чтобы быть уверенным, что вы пользуетесь нужным контекстным словарем). Как вы можете воспользоваться обеими версиями A-LAST?
Использование контекстных словарей
Существует несколько причин использования различных контекстных словарей, а не только FORTH. Во-первых, вы можете описать набор слов с идентичными именами, но с различными функциями, так что они не будут мешать друг другу. Это весьма удобно при написании Форт-ассемблера, так как вы можете описать слова, управляющие программой, для ассемблера, используя те же имена, что и в Форте, даже если их функции совершенно различны, т. е. при наличии отдельного контекстного словаря ASSEMBLER слова IP, ELSE, THEN и т.д. могут без каких-либо неопределенностей сосуществовать в общем словаре, имея те же имена, что и слова FORTH. Это позволяет сделать программирование на Форт-ассемблере во многом идентичным программированию на Форте. При использовании в определенных контекстных словарях слова, имеющие одинаковые имена, могут выполнять различную работу.
Другой причиной использования контекстного словаря является ускорение компиляции. Это становится особенно важным, если ваша программа настолько велика, что вы вынуждены использовать оверлеи различных блоков для различных функций в верхней области очень большого словаря. Каждый раз, когда загружается набор блоков, вы можете сэкономить на времени компиляции, используя отдельный контекстный словарь для каждой функции.
С точки зрения приоритета поиска здесь создается эффект помещения каждой функции "в верхней части словаря". Это существенно менее важно для Форта, который при компиляции использует много маршрутов поиска. Заметим, что исполнение не ускорится, так как оно не включает в себя поисков в словаре (за исключением нахождения первого слова, чтобы начать исполнение программы).
Еще одним преимуществом нескольких контекстных словарей является то, что не все слова в конкретном словаре должны быть описаны сразу. Вы можете использовать (имя) DEFINITIONS поочередно в различных местах программы, чтобы описать порядок обхода родственных слов (объединив их в одном контекстном словаре). Это может быть удобно для последующего расширения набора команд в словаре.
Но чрезмерное увлечение контекстными словарями может привести к вредным привычкам просто потому, что добавление слов в словарь когда попало не представляется разумным. Намного лучше держать контекстный словарь в одном месте (непрерывный массив блоков), так чтобы с ним можно было работать как с единым логическим блоком. Вашу программу будет легче читать и поддерживать.
Вот пример удобного использования контекстного словаря при создании процедуры "help", которая переопределяет слова Форта в словаре с именем HELPS. Новые операторы могут отобразить краткое описание функций слов словаря Форта. Это может показаться теперь тривиальным, но представьте, что вы имели бы это, когда мы только начали изучать Форт.
Сначала создадим словарь HELPS: VOCABULARY HELPS HELPS DEFINITIONS Теперь введем несколько слов в словарь "HELPS": : DUP ." = stack (n - n n) " CR ; : DROP ." = stack (n - ) " CR ; и т.д. Когда вы кончили, опишите слово HELP, которое используется следующим образом: HELP (name) . что выдает описание, содержащееся в слове (name). Для того чтобы слово HELP было найдено в любом случае, оно должно быть описано, как слово словаря FORTH. Таким образом, его описание должно начинаться с FORTH DEFINITIONS HELP могло бы быть описано как : HELP [COMPILE] HELPS FIND EXECUTE [COMPILE] FORTH ;
Слово HELP делает HELPS контекстным словарем для того, чтобы найти ваши полезные описания. Слово FIND находит (имя) и исполняет его, а затем делает контекстным словарем FORTH. Слово [COMPILE] необходимо, если ваши контекстные словари являются словами немедленного исполнения (для дальнейшего обсуждения см. гл.15). Кроме того, пользователи Форт-83 должны использовать ' вместо FIND. Это описание опасно: в такой системе может произойти катастрофа, если HELP будет использовано с (имя), которое не было описано в контекстном словаре "HELPS". Несколько лучше описание HELP, приведенное ниже: : HELP [COMPILE] HELPS FIND ?DUP IF [ FIND HELPS ] LITERAL OVER U< ( IF ['] HELPS OVER U< ) (Форт-83) IF EXECUTE ELSE DROP . " not in HELP list." THEN ELSE ." not found." THEN [COMPILE] FORTH ;
Когда исполняется HELP (name), HELPS делается контекстным словарем, так что новые описания будут найдены раньше, чем слова FORTH с тем же именем получат шанс быть исполненным. Слово FIND выдает CFA (имя) или 0, если поиск не имел успеха. Если (name) найдено, контролируется его CFA, для того чтобы выяснить, было ли (name) описано позже. чем HELPS (функция [ FIND HELPS ] LITERAL будет прояснена в гл.15); если это так, слово из словаря HELPS исполняется, если же нет, имеется две возможности: или (name) не описано в словаре HELPS, или оно нигде не найдено, включая словарь FORTH. При реализации этих вариантов выдаются соответствующие сообщения. В конце концов FORTH делается контекстным словарем, так чтобы вы после выполнения HELP не остались в словаре HELPS. Этот пример должен показать вам, как отдельные контекстные словари могут выполнять работу просто и понятно там, где другие методы трудноприменимы.
Упражнения
1. Опишите HELP так, чтобы вы возвращались в контекстный словарь, из которого произошло обращение (а не всегда в FORTH). 2. Опишите контекстный словарь "HELPS", который печатает описание слов из блоков на диске, а не из словаря, лежащего в памяти.
Ограничьте каждое описание 32 или 64 символами (так чтобы 32 или 16 описаний помещалось в одном блоке). Каждое слово из HELPS должно использовать BLOCK для загрузки блока, где лежит нужный текст, вычислять адрес начала текста и отображать его. Для формирования слов из набора HELPS, которые вам нужны, используйте слово-описатель HELPER (сосредоточьте ваши усилия вначале на первом блоке).
Выводы
В первой части главы мы обсудили организацию памяти и словаря Форта без практических примеров применения. Вы. возможно, почувствовали, что материал, предложенный там. имел главным образом академический интерес. Но только благодаря пониманию организации памяти и структуры слов Форта вы сможете вполне контролировать работу системы. В гл.15 мы обсудим, как работает интерпретация, компиляция и осуществляется выполнение программ. Вы убедитесь, что все, что вы узнали об организации памяти и структуре словаря, весьма существенно.
Интерпретация, компиляция и исполнение
В гл. 14 мы рассматривали структуру Форта и организацию памяти. Теперь мы должны посмотреть, как он работает, т. е, как интерпретирует, компилирует и исполняет слова и числа. Стандарты Форта благоразумно не делают попыток определить, как должен быть устроен язык изнутри. Задача стандартов - обеспечение функциональной совместимости программ, а не подавление оригинальных путей адаптации Форта к новой технике. Но это означает, что мы можем только описать, как работает типовая версия Форта. Несмотря на это, мы полагаем, что, предлагая типовой способ функционирования Форта, мы дадим лучшее представление, как работает любой Форт.Мы, пожалуй, напомним вам, что в форте термины "интерпретация", "компиляция" и (в меньшей степени) "исполнение" имеют иное значение, чем в теории вычислительной технике вообще. В Форте интерпретация состоит из приема слов и чисел из входного потока через дисковые блоки или с клавиатуры, последующей компиляции введенного в словарь или непосредственного исполнения. С другой стороны, в Бейсике и других интерпретивных языках интерпретация означает загрузку исходного текста в память, трансляцию его в машинные коды и выполнение строка за строкой каждый раз. когда программа запускается. Другими словами, интерпретация в Форте происходит до компиляции и исполнения, в то время как в других языках за интерпретацией всегда следует исполнение и не генерируется постоянная исполняемая программа. В Форте компиляция состоит из формирования новых элементов словаря путем построения списка CFA слов, описанных ранее. С другой стороны, компиляция в Фортране и других языках представляет собой трансляцию исходного текста программы в программу а машинных кодах путем просмотра библиотеки процедур, хранящихся на диске, и укладку процедур в виде программ в машинных кодах в исполнительный файл. Таким образом, компиляция в других языках представляет собой создание программ в машинных кодах, в то время как в Форте это означает объединение существовавших ранее программ с помощью списка CFA в поле параметров слов.
Эдмундс (1985) дает более полное описание обычного использования слов "интерпретатор" и "компилятор". Исполнение во всех языках состоит из работы программ в машинных кодах, но а Форте, в противоположность другим языкам, это делается с помощью нескольких коротких системных программ и адресов, скомпилированных в Описание каждого слова, чтобы сформировать цепочку заранее написанных машинных кодов, которые и выполняют работу программы. Благодаря формированию этой цепочки или нити, на которую "нанизываются" машинные коды Форта, он за служил название "цепной интерпретивный язык" (или TIL - ThreadedInterpretive Language).
Интерпретация
Привлекательность Форта происходит от простоты ввода последовательности чисел и слов с клавиатуры, последовательности действий Форта после того, как вы нажали клавишу . Вы делаете это с самого начала каждый раз, когда сделали короткое вычисление или описание с клавиатуры нового слова. Исходный текст на дисковых блоках воспринимается или интерпретируется Фортом точно так же, как ввод с клавиатуры; фактически все вводы для Форта обрабатываются одним и тем же способом. Все вводы в Форте приходят через входной поток, последовательность слов и чисел, разделенных пробелами, вне зависимости от происхождения ввода.
Но как узнает Форт, где найти данные? Как Форт узнает слова и отличит их от чисел? Как узнает Форт, что делать с введенной информацией? В табл. 15.1 дана сводка того, что делает Форт после инициализации. Подробное рассмотрение операций в табл. 15.1 ответит на некоторые наши вопросы. Функции, заключенные в скобки в табл. 15.1, могут выполняться, а могут и не выполняться в раз личных версиях Форта.
Вы рассмотрели ABORT и QUIT в гл. 7, но в другом контексте. ABORT и QUIT возвращают управление клавиатуре, так как они представляют собой, как показано в табл. 15.1, бесконечные циклы, присущие интерпретатору Форта. Задачей ABORT является приведение системы в исходное состояние и передача управления оператору QUIT, который исполняется до тех пор, пока не встретится ошибка.
QUIT очищает стек возвратов, устанавливает режим исполнения и ожидает нового ввода с клавиатуры. Как только что-то поступило на вход, производится интерпретация до тех пор, пока не будет обнаружено ошибки (табл. 15.2). В случае ошибки QUIT прерывает цикл с помощью ABORT, чтобы вернуть систему в исходное состояние, прежде чем продолжить работу дальше. (Когда вы применяли ABORT и QUIT в гл. 7, вы в действительности использовали их циклы, как здесь и показано.)
Таблица 15.1. Обзор работы Форт
Инициализация системы ABORT BEGIN Очистка стека параметров (FORTH делается текущим контекстным словарем) (Установка десятичной системы счисления) QUIT Установка режима исполнения BEGIN Очистка стека возвратов Ожидание ввода с терминала BEGIN Если входной поток не иссяк и не обнаружено ошибки при интерпретации входной информации (продолжение в таб.15.2). то - REPEAT UNTIL ошибка UNTIL всегда
Форт проводит большую часть времени внутри цикла QUIT. Здесь входной поток воспринимается, делится на отдельные слова и числа (называемые лексемами), которые в зависимости от обстоятельств, рассмотренных в табл. 15.2 компилируются, исполняются или заносятся в стек. Когда входной поток иссяк, управление передается снова клавиатуре, готовой для нового ввода.
Как работает интерпретация в форте, показано в табл. 15.2. Входной поток воспринимается с терминала, если переменная пользователя BLK равна 0, или из блока, номер которого записан в BLK. Входной поток подвергается разбору или разбивается на группы символов (или лексемы). Так как разбор'производится командой 32 WORD, лексемы должны отделяться друг от друга по крайней мере одним пробелом (ASCII 32), что объясняет, почему пробел не может никогда быть частью слова Форта. Лексема является, таким образом, просто группой ASCII символов, которая может быть, а может и не быть действительным словом Форта или числом. Для каждой лексемы
Таблица 15.2. Интерпретация входного потока
Если входной поток не иссяк Если BLK @ = 0, входной поток поступает с терминала В противном случае входной поток поступает из блока, номер которого лежит в BLK Для каждой лексемы из входного потока...
Если лексема оказалась словом из словаря... Если режим компиляции (STATE =1) Если слово немедленного исполнения, то оно исполняется В противном случае компилируется CFA слова в словарь В противном случае (STATE=0) слово исполняется Если лексема не слово Если режим компиляции (STATE=1)... Если число одинарной длины, компилируем с помощью LITERAL В противном случае (число двойной длины) компилируем с помощью DLITERAL В противном случае (STATE=0) заносит число в стек В противном случае имеет место ошибка и выполняется ABORT Повторять, пока не иссякнет входной поток или не возникнет ошибка
проверяется, не является ли она словом Форта, путем кодирования (если необходимо) и поиска в словаре, как описано в гл. 14, чтобы определить, соответствует ли она имени какого-либо уже запомненного слова.
Если лексема найдена в словаре, она является словом форта и исполняется или компилируется в зависимости от значения переменной пользователя STATE. Если STATE содержит не нулевое значение, Форт находится в режиме компиляциии, если только лексема не описана как слово немедленного исполнения, ее CFA компилируется в словарь оператором , (запятая) по адресу HERE. Ес ли значение переменной STATE равно 0, Форт находится в режиме исполнения и слово выполняется путем передачи его CFA оператору EXECUTE.
Если лексема не найдена в словаре, интерпретатор пытается преобразовать ее в двоичное число для записи в память с помощью слова NUMBER. Если каждый символ лексемы является цифрой с величиной меньше текущего значения BASE, преобразование оказывается успешным. Например, наибольшая цифра в десятичной системе (основание 10) равна 9, но наибольшая цифра в шестнадцатеричной системе (основание 16) равна F шестнадцатеричному или 15 - десятичному; тогда символ G приведет к ошибке преобразования, когда система шестнадцатеричная. Десятичная точка указывает Форту, что лексема должна рассматриваться как число двойной длины (См. описание слова NUMBER в гл. 9). Если лексема является действительно числом, величина STATE определит, следует ли это число компилировать или исполнять, передав его в стек.
Мы позднее расскажем вам подробнее о том, как компилировать числа.
Если ошибки не случилось, Форт выдает на экран "ОК", чтобы сообщить, что все, что нужно, сделано и что он готов для очередного ввода с клавиатуры. Но если лексема - не слово Форта и не число или если при интерпретации или исполнении произошла ошибка, система, прежде чем продолжить процесс с самого начала, возвращается в исходное состояние оператором ABORT.
Является ли источником входного потока блок или клавиатура, смещение относительно его начала в любой момент во время интерпретации задается содержимым пользовательской переменной >IN ("to-in"). Переменная >IN содержит число байтов от начала входного потока, где происходит синтаксический разбор. Эта переменная всегда указывает на байт после пробела, следующего за последней лексемой. Переменная >IN постоянно корректируется во время интерпретации. Переменные BLK и >1N описаны в гл. 10, но мы покажем так же некоторые их приложения в упражнениях.
Как уже обсуждалось в гл. 9, когда входной поток воспринят с клавиатуры (содержимое BLK равно 0), символы уложены в зарезервированную область памяти, называемую текстовым входным буфером, одновременно они отображаются на экране терминала. (Стандарты требуют, чтобы текстовый входной буфер вмещал по меньшей мере 80 символов.) В Форт-83 (и в большинстве других версий) адрес начала этого буфера хранится в переменной TIB.
В гл. 9 вы видели, как использовать QUERY для ввода строк перед их разбором. Главная функция QUERY - работа в интерпретаторе. В Форт-79 и большинстве других версий Форта ввод происходит через QUERY. Описание в Форт-79 будет выглядеть : QUERY TIB 80 EXPECT 0 >IN ! ;
Поскольку описание не устанавливает >IN на начало входного буфера или BLK равным 0, для пользы дела мы должны описать что-то вроде : GETINPUT 0 BLK ! 0 >IN ! QUERY ;
Теперь, если вы напечатаете GETINPUT
курсор будет установлен после GETINPUT, ожидая вашего ввода. Теперь, если вы напечатаете, скажем, 5 12 + .
на экране появится 17. Другими словами, GETINPUT использует QUERY, которое, в свою очередь, использует EXPECT, чтобы передать в процессе исполнения введенную информацию во входной буфер. Оператор QUERY не стандартизован в Форт-83, но если он включен (а так обычно и есть), он должен быть определен иначе: : QUERY 0 BLK ! 0 >IN ! TIB 80 EXPECT 0 >IN ! ;
В этом случае QUERY действует как GETINPUT.
Чтобы посмотреть, что содержится в текстовом буфере, нажатием соответствующей клавиши введите семь пробелов и затем напечатайте TIB 2 DUMP
Результат после DUMP будет выглядеть как (адр) 20 20 20 20 20 20 20 54 49 42 20 40 20 32 20 44 Т I В @ 2 D 55 4D 50 00 00 xx xx xx xx xx xx xx xx xx xx xx U M P
где "(адр)" будет зависеть от вашей системы, а "xx" - неизвестные байты. Вы можете убедиться, что входной буфер является точной копией строки, которую вы напечатали, дополненной пробелами в начале и, возможно, оставшимися символами от предшествующего ввода в конце (отмеченные здесь "хх"). Это связано с тем, что входной буфер заполняется оператором QUERY. Мы знаем, что Форт интерпретировал входную строку, так как он исполнил DUMP, чтобы пропечатать две первые строки текстового входного буфера,
Но как Форт знает, где остановить интерпретацию строки, введенной с клавиатуры? Это варьируется от версии к версии. Мы опишем, что происходит в MMSFORTH. Напечатайте ту же команду, но на сей раз без предшествующих пробелов, и увидите
(адр) 54 49 42 20 40 20 32 20 44 55 4D 50 00 00 20 44 T I B @ 2 D U M P D 55 4D 50 00 00 хх хх хх хх хх хх хх хх хх хх хх U M P
Оператор DUMP еще раз интерпретирован. Но почему MMSFORTH не пытается интерпретировать "DUMP", который вы видите в конце, оставшийся от предшествующего ввода? Ввод с клавиатуры выполняется оператором EXPECT в QUERY, который в данном случае мы завершили нажатием клавиши возврата каретки. Почему нет ASCII-кода возврата каретки (0D HEX) в конце введенного текста во входном буфере? Потому что вместо возврата каретки EXPECT в MMSFORTH вводит два ASCII-кода, равных 0 (нулевые байты), чтобы пометить конец ввода.
Более ранний ввод игнорируется, так как в MMSFORTH (и большинстве других версий) интерпретация текста завершается, когда во входном потоке встречаются символы 0, вставленные туда оператором EXPECT. Это прерывание с помощью'"нулевых символов применяется не только для ввода с клавиатуры, но и для блочных буферов; хотя дисковые буферы должны содержать 1024 байта, каждый из них занимает 1026 байтов памяти, так как нужны еще два ASCII-символа 0, чтобы пометить конец каждого из буферов. В противном случае интерпретатор не будет знать, что конец буфера уже достигнут, и может попытаться интерпретировать другие части памяти, что будет чревато неприятностями.
Другой способ контроля конца входного потока заключается в подсчете числа введенных символов до нажатия возврата каретки. В Форт-83 это число для пультового буфера хранится в #Т1В. Еще один способ решения проблемы - это контроль того, что число, записанное в >IN, не превышает размера блочного или текстового входного буфера. В этом случае для каждого блочного буфера требуется только 1024 байта, какие-либо разграничители для прерывания интерпретации не требуются.
Но как производится синтаксический анализ текста во входном буфере, чтобы слова могли компилироваться или исполняться? Мы уже упомянули, что это делается словом WORD, и, если вы помните, как мы использовали WORD в PARSIT в гл. 9, вы сможете ответить на этот вопрос. Слово WORD производит разбор буфера, выделяя последовательности символов, отделенные друг от друга пробелами, и помещает их в виде счетных строк по адресу HERE. Слова затем используются для' поиска в словаре, и они либо исполняются, либо компилируются в соответствии с величиной STATE, как мы уже описали.
Теперь мы можем видеть, как разграничитель 0 прерывает интерпретацию во многих версиях Форта, ASCII-символ (0) воспринимается интерпретатором как имя слова (непечатное имя) Форта. которое исполняется немедленно, чтобы остановить интерпретацию. Это слово иногда называется х, а иногда "null".
Все эти процессы, собранные в табл. 15.2, объединяются вместе и соответствует термину текстовый интерпретатор. Текстовый интерпретатор часто несколько произвольно называется внешним интерпретатором в противоположность внутреннему или адресному интерпретатору, который является чем-то совсем другим, он обслуживается позднее в этой главе.
Упражнения
1. Во время интерпретации слово в зависимости от величины STATE либо компилируется, либо исполняется. Предположим, что словарь был просмотрен и что CFA слова было извлечено. Опишите слово ?COMPILE, которое компилирует или исполняет слово. (Подсказка: используйте в вашем описании , (запятую) и EXECUTE.) Это очень близко к тому, что действительно делается после успешного поиска в словаре. 2. Изучите слово FIND из Форт-83 (стр.155). Пусть FIND использовался и результат его работы записан в стек. Предположим также, что найдено искомое слово. Переопределите ?COMPILE как 83?COMPILE, чтобы при нахождении слова немедленного исполнения оно выполнялось во время компиляции, в противном же случае исполнялось бы или компилировалось в зависимости от переменной STATE. Почему оператор FIND из Форт-83 неотъемлемая часть интерпретатора? 3. Опишите Форт-83 версию '(апостроф) как N', используя FIND из версии Форт-83. Если слово не найдено, должно высвечиваться "Word not found" ("слово не найдено"). 4. Модифицируйте ?COMPILE из упражнения 1 так, чтобы при компиляции их CFA отображалось на экране дисплея. 5. Попытайтесь напечатать >IN @ с клавиатуры. Теперь сделайте это, введя предварительно несколько пробелов. Что означают полученные числа? Как используется >IN, когда входная информация поступает от клавиатуры? 6. Напечатав 0 >IN ! QUERY вы вызовете "зависание" ЭВМ. Почему? 7. Предположим, что вы описали : FAKE-LOOP 10 = IF QUIT THEN ; далее напечатали 0
а затем 1+ DUP DUP FAKE-LOOP ) >IN !
Что, вы полагаете, произойдет? 8. Пусть 0, или X, или null останавливает интерпретацию в вашем Форте. Опишите : STOP 0 TIB >IN @ + ! ; Теперь, если вы напечатаете 5.
STOP 6 что вы увидите? Почему?
Компиляция
Как вы видели, когда текстовый интерпретатор встречает слово Форта, происходит одно из двух: слово либо исполняется, либо компилируется в словарь Форта. Но как осуществляется компиляция или исполнение? Рассмотрим сначала компиляцию.
Компиляция представляет собой процесс добавления в словарь новых слов, чисел или строк. Обычно это делается в верхней части словаря (т.е. по адресу HERE). Мы видели в гл. 6, что 8- и 16-битовые величины могут компилироваться непосредственно с помощью С, и , (запятой). Слова, такие как С, , которые расширяют словарь, не создавая новых описаний, называются компилирующими. Слова же, которые компилируют новые описания, такие как : (двоеточие) и CREATE, называются словами-описателями. В этом разделе мы рассмотрим, как осуществляют компиляцию наиболее часто используемые слова-описатели.
Теперь покажем, как работает : . Рассмотрим наш старый пример: : BASE? BASE @ DUP DECIMAL . BASE ! : : (двоеточие) кодирует имя "BASE?" и запоминает результат (по адресу HERE) как поле имени в элементе словаря BASE?. Если имя BASE? уже имеется в словаре, то будет выдано сообщение "duplicate name" (повторное описание). Слово : затем находит NFA предшествующего слова в словаре и запоминает в поле связи BASE?, чтобы обеспечить управление поиском. (Способ того, как это делается, зависит от оговоренных условий связи контекстного словаря вашего Форта; см. гл. 14.) Этим завершается оформление заголовка BASE?. Следующим шагом будет компиляция адреса программы в машинных кодах, используемая при выполнении слов типа двоеточие (называемая исполнительной программой типа двоеточие), в поле программы BASE?. Слово : может также выполнить самые разные процедуры, например разрешить слову ; детектировать ошибки компиляции. После этого слово : устанавливает Форт в режим компиляции путем записи ненулевого кода в STATE. На этом работа : завершится. Следующий шаг весьма прост: каждое последующее слово из входного потока компилируется путем нахождения в словаре и записи его CFA (с помощью ,) в новое описание.
Это выполняется автоматически по мере разбора входного потока текстовым интерпретатором. Каждый раз, когда адрес (или что-то еще) компилируется в словарь по адресу HERE, верх словаря смещается выше на соответствующее число (переменная DP увеличивается), так что HERE всегда указывает на первый свободный байт памяти, находящийся сразу после словаря. Результирующий список адресов в поле параметров BASE? выглядит, как показано в гл. 14.
Слово ; (точка с запятой) завершает описание, начатое :, поместив CFA оператора EXIT в последнюю позицию тела оператора BASE?, возвратив Форт в режим исполнения и сделав значение переменной STATE равным 0, Слово ; (точка с запятой) проверяет также, не было ли ошибки при компиляции. Один из способов, каким ; может выявить ошибку, - это проверить, изменился ли указатель стека в процессе компиляции. Другая схема предполагает, что : заносит в стек число, которое должно там сохраняться к моменту выполнения слова ;, последнее его и удаляет. Если ошибка произошла, ; возвращает переменной DP и, следовательно, HERE то значение, которое оно имело до :, предотвращая какие-либо изменения словаря. (Некоторые версии Форта оставляют в словаре заголовок и частично компилированное тело, просто устанавливая бит-метку в такое состояние (см. гл. 14), что слово не может быть найдено. Это, однако, позволяет ошибкам использовать пространство словаря.)
Откомпилированный в оператор BASE? представляет собой заголовок, состоящий из поля имени и поля связи, и тело, составленное из указателя на исполнительную программу "двоеточие", за которым следует список CFA слов, используемых в описании BASE?, завершающийся CFA слова EXIT. Вы можете убедиться, что это как раз то, что было скомпилировано в поле параметров BASE?, с помощью пропечатки и записи каждого адреса, содержащегося там. Затем, если вы отдельно проверите каждое слово, используемое в описании BASE?, с помощью FIND (СЛОВО) U. или в Форт-83 ' (слово) U. вы убедитесь, что список адресов тот же, что и в поле параметров.
Программа, которая выполняет аналогичную функцию автоматически, отображая имена слов, использованных при описании конкретного слова, называется Форт-декомпилятором. Декомпилятор находит каждое слово, использованное в описании по его CFA, которое, в свою очередь, используется для нахождения его имени. Декомпилятор не может работать с MMSPORTH и другими версиями, которые используют хэш-коды для имен.
Упражнения
1. Скомпилируйте следующие слова: : 1DUMMY ; ; 2DUMMY 1DUMMY ; : 3DUMMY 2DUMMY ; пропечатайте каждое из них с помощью DUMP. определите адреса поля связи, поля программы и поля параметров каждого слова и запишите их. Затем запишите содержимое каждого из полей. 2. Адреса, записанные в поле программы всех слов, одни и те же. Почему? В каком соотношении находится этот адрес с адресом поля программы BASE? ? 3. Маршрут исполнения слова 3DUMMY проходит через шесть CFA (если вы включите в список три исполняемых EXIT). Просмотрите поля параметров и выясните, что это за адреса. 4. Как бы вы могли изменить поле связи 2DUMMY. чтобы 1DUMMY было нельзя обнаружить в словаре ? Это должно быть сделано аккуратно и может оказаться невозможным, если в вашем словаре используется большое число путей поиска. Проверьте ваши результаты. 5. Как вы можете изменить 3DUMMY, чтобы оно исполняло 1DUMMY вместо 3DUMMY без повторной компиляции? (Подсказка: измените что-то в PFA) Как бы вы могли изменить его так, чтобы 3DUMMY не делало ничего (подумайте об EXIT). 6. Найдите CFA операторов + и - и запишите их. Опишите следующее: : PLUS/MINUS/TIMES * ; Когда исполняется PLUS/MINUS/TIMES, перемножаются два числа. Измените это слово путем изменения его поля параметров так, чтобы оно выполняло операцию вычитания одного числа из другого. А теперь сделайте так, чтобы оно производило сложение двух чисел. Хотя в норме этого делать в программе не следует, это демонстрирует, что функция слова может динамически меняться путем изменения содержимого поля параметров. (Это, конечно, может быть сделано более изящно, если использовать исполнительный вектор, как в гл. 6.).
Исполнение при компиляции
Бывают случаи, когда хочется исполнить какое-то слово во время компиляции описания типа двоеточие. Рассмотрим примеры: : SAY-IT ." I am compiling." ; IMMEDIATE : COMPILE-IT SAY-IT ." I am already compiled." ;
Когда вы компилируете COMPILE-IT, будет выдано на экран сообщение "I am compiling.". Когда же COMPILE-IT исполняется, вы увидите "I am already compiled.". Но, когда вы исполните SAY-IT с клавиатуры, будет выдано сообщение "I am compiling.", что будет, конечно, неверным. Так как за описанием SAY-IT следует слово IMMEDIATE, оно становится словом немедленного исполнения. Вспомните из гл. 9, что ( и.( являются также примерами слов немедленного исполнения.
Слово IMMEDIATE сообщает Форту, что нужно пометить только что описанное слово, чтобы оно исполнялось немедленно там, где встретится, вне зависимости от того, находится ли Форт в режиме компиляции или исполнения. Действие слова IMMEDIATE заключается в том, чтобы устанавливать первый бит поля имени только что описанного слова в единичное состояние. Этот бит воспринимается внешним интерпретатором как флаг, указывающий, что слово должно быть исполнено, где бы не встретилось. Первый бит - это часто старший бит байта длины в словарях с кодированными именами, в таком случае имя в словаре, чей первый байт больше шестнадцатеричного 7F, представляет собой слово немедленного исполнения. Все слова, которые выполняют свою функцию при компиляции, являются словами немедленного исполнения.
Необходимо соблюдать предосторожность при работе со словами немедленного исполнения. Запомните, что большинство версий Форта проверяет тем или иным способом, был ли изменен указатель стека во время компиляции (хотя это и не регламентируется ни одним стандартом). Таким образом, если слово немедленного исполнения изменит стек, это вызовет ошибку, когда ; обнаружит, что стек в процессе компиляции изменился. Вы можете проверить это, описав : .IMM . ; IMMEDIATE и затем : TRY-IT .
IMM ;
Вы почти наверняка получите сообщение об ошибке. Но, если вы опишите.IMM как : .IMM DUP . ; IMMEDIATE и опишите TRY-IT, как и раньше, с 5 в стеке, цифра 5 может быть пропечатана или нет в зависимости от того, как проверяется стек во время компиляции. Это показывает, что вы должны описывать слова немедленного исполнения очень тщательно, так чтобы они не взаимодействовали с Форт-компиляцией неожиданным образом.
Мы рассмотрим некоторые практические примеры слов немедленного исполнения в упражнениях, но сначала давайте посмотрим, как слово немедленного исполнения может быть скомпилировано в описание. Это делается с помощью слова [COMPILE]. Попробуйте этот вариант COMPILE-IT : LIAR [COMPILE] SAY-IT ." I am already compiled." ; При компиляции на экране ничего не появится, но, когда слово будет исполняться, вы увидите I am compiling, I am already compiled,
Слово [COMPILE] заставляет скомпилировать SAY-IT, несмотря на то, что оно является словом немедленного исполнения. Другими словами, [COMPILE] заставляет игнорировать старший бит в SAY-IT и компилировать SAY-IT вместо того, чтобы исполнять во время компиляции.
Способом исполнения слова или слов, которые не относятся к числу слов немедленного исполнения, во время компиляции является использование слов [ (левая скобка) и ] (правая скобка). По пробуйте написать : SAY-IT-NOW [ ." I am compiling." ] " I am already compiled. " ;
В точности так же, как для COMPILE-IT, при компиляции SAY-IT-NOW на экране появится "I am compiling.", но при исполнении вы получите "I am already compiled.". Слова между [ и ] исполняются, а не компилируются. Описания [ и ] просты : [ 0 STATE ! ; IMMEDIATE и : ] 1 STATE ! ;
Вы должны понимать, как они работают и почему ] не должно быть словом немедленного исполнения, в то время как [ должно.
Слова [ и ] могут использоваться как в пределах описаний типа двоеточие, так и вне их. Вспомните, что при работе текстового интерпретатора в режиме компиляции, т.
е. когда значение STATE не равно 0, CFA любых слов из словаря, встречающихся во входном потоке, заносится по адресу HERE, значение HERE соответствующим образом смещается. Попробуйте исполнить HERE U. ] SWAP DROP [ HERE U. и вы увидите, что значение слова HERE сместилось вверх на 4 байта, которые были добавлены к словарю, что они содержат CFA слов SWAR и DROP, но не существует простого способа использовать их, так как они не могут быть найдены при просмотре словаря.
Но теперь предположим, что вы хотите скомпилировать исполнительный вектор с именем CHOICES с вариантами 1CHOICE, 2CHOICE и 3CHOICE. Вы уже знаете из гл. 6, что нужно сформировать массив с помощью слова CREATE и, найдя CFA вариантов, записать их в массив с по мощью слова , (запятая). Теперь посмотрим на это: CREATE CHOICES ] 1CHOICE 2CHOICE 3CHOICE [
Эта процедура создает идентичный массив с меньшими хлопотами и с много большей наглядностью. Данная техника была использована для массива KEYVECTORS в редакторе в гл. 12. Позже мы рассмотрим еще некоторые приложения скобок, но сначала несколько упражнений.
Упражнения
1. Для целей отладки иногда удобно знать, в каком блоке и где происходит компиляция. Опишите слово с именем LOC1, используя IMMEDIATE, которое, будучи введенным где-либо в блоке, отмечает позицию, выразив ее через номер блока и относительный адрес в блоке. Модифицируйте это слово в LOC2 так , чтобы оно печатало номер блока и номер строки в блоке (в предположении, что строка имеет 64 символа). 2. Предположим, что слово из упражнения 1 не было словом немедленного исполнения, но вы хотите его использовать внутри описания типа двоеточие. Как вы можете это сделать? 3. Как можно отобразить содержимое стека, используя.S, когда компилируется описание типа двоеточие? 4. Что случится, если вы вставите [ без парной скобки ] в описание типа двоеточие? 5. Могли ли вы использовать [COMPILE], чтобы скомпилировать слово, которое не является словом немедленного исполнения в описание типа двоеточие? 6. Что произойдет, если вы введете [ 239 ] в описание типа двоеточие? Результат будет зависеть от того, как контролируется состояние стека в вашей версии.
Попробуйте это с вашей версией. Если вы не получите сообщение об ошибке, проверьте состояние стека после этого. 7. Без отыскания CFA для +, -, * и / создайте массив с именем OPERATOR, содержащий CFA этих слов в указанном порядке. Теперь опишите слово с именем MATH, которое в соответствии с числом в стеке будет складывать, вычитать, умножать и делить два числа, занимающие вторую и третью позиции в стеке. Единице должно соответствовать сложение, двойке - вычитание и т.д. Таким образом 5 2 1 MATH должно дать 7 5 2 3 MATH должно дать 10 и т.д.
Компиляция чисел и текста
Существует слово LITERAL, задачей которого является взять число из стека и использовать его в описании типа двоеточие так, что при его исполнении это число будет уложено в стек. Попробуйте : SILLY [ 2991 ] LITERAL ; Если вы напечатаете SILLY . вы увидите на экране число 2991, которое было занесено в стек: словом SILLY. Но мы дали этому слову такое имя (silly - глупый) потому, что оно было описано глупым образом. Вы могли бы точно так же описать : SILLY 2991 ; (Даже это глупо, так как следовало бы использовать константу; это бы заняло меньше места в словаре и обеспечило большее быстродействие.)
Для чего же пригодно слово LITERAL ? Давайте рассмотрим некоторые примеры. Вы помните, что всегда полезно описать : TASK ; в начале программы, чтобы можно было ее удалить, написав FORGET TASK
Давайте заставим TASK делать что-то полезное. Если вы опишите : TASK ." Loaded from block" [ BLK @ ] LITERAL. ; всякий раз, когда вы напечатаете TASK с клавиатуры, вам будет сообщен номер блока, где началась программа. [ BLK @ ] кладет номер блока в стек, a LITERAL компилирует его на стадии компиляции TASK. Всякий раз, когда используется TASK, отображается номер блока, где размещен исходный текст программы.
Может быть, наиболее важная функция LITERAL- ускорение исполнения программы. Предположим, что вы имеете константу 55 CONSTANT A и вы должны многократно в процессе выполнения программы вычислять квадрат числа A.
Вы можете тогда описать : ASQRD A DUP * ; но произведение будет вычисляться каждый раз, когда нужен квадрат. Если A действительно константа, т. е. если она не изменяется в процессе исполнения программы, то : ASORD [ A DUP * ] LITERAL ; будет выполняться быстрее, так как произведение будет определено на стадии компиляции, а не исполнения. Следует всегда стараться сделать все, что можно, во время компиляции, избегая потерь времени при исполнении. Слова-скобки часто используются, чтобы сделать программу более читаемой. : RUGS ( n -- sq-ft ) [ 9 12 * ] LITERAL * ; вычисляет и компилирует 108 (квадратных футов), слово создано для умножения на число ковриков, лежащее в стеке. Таким образом, 10 RUGS положит в стек 1080. Конечно, слово может быть описано как : RUGS ( n -- sg-ft ) 9 12 * * ; или проще: : RUGS ( п - sg-ft ) 108 * ; но первый пример более удобочитаемый, чем другие, и работает так же быстро, как и последний; 9 12 * вычисляется только раз, когда RUGS компилируется. Сохранение длины и ширины в явном виде делает более легким просмотр исходного текста программы и понятным размеры ковра, которые имелись в ввиду. (Замечание: другим способом решения проблемы было бы описание 9 12 * CONSTANT 9BY12 использующие константу двойной длины для запоминания длины и ширины. Если вы затем опишите ; RUGS * ; последовательность 12 9BY12 RUGS будет однозначной, весьма удобочитаемой и столь же быстродействующей, хотя и требует большего места в словаре из-за описания константы.)
Другой подход может позволить оператору выбрать размер ковра во время компиляции. Предположим что вы описали : WHATSIZE? ." Length" #IN ." Width" #IN . ; и затем опишем заново : CARPETS [ WHATSIZE? ] LITERAL * ; Пользователь мог бы задать размер ковра, который нужно использовать при специальном прогоне программы без изменения исходного текста. Обратите внимание, что если бы WHATSIZE было описано как слово немедленного исполнения, то при использовании в CARPETS, его не следовало бы помещать в скобки.
Мы предупреждали, что слова немедленного исполнения могут приводить к ошибкам, если они изменяют состояние стека, когда используются в описании, начинающемся с двоеточия. Таким образом, хотя LITERAL компилирует число из стека, некорректно писать 555 : TAKENUM LITERAL ; и ожидать, что TAKENUM оставит 555 в стеке. Почти наверняка будет выдано сообщение об ошибке, так как LITERAL удаляет число из стека при компиляции. Это приведет к тому, что TAKENUM будет помечено так, что его нельзя будет найти в словаре.
Теперь мы в состоянии понять, как в действительности компилируются числа в описании типа двоеточие. LITERAL - слово немедленного исполнения, поэтому конечно, само не компилируется. Вместо этого оно компилирует CFA другого слова (иногда называемого LIT), за которым следует число из стека. Когда слово LIT исполняется, оно просто берет число, скомпилированное после него, и кладет его в стек. Числа, скомпилированные непосредственно (без LITERAL) в слово типа двоеточие, также используют LIT, причем тем же самым способом.
LITERAL представляет собой пример слова с двумя совершенно разными функциями: первая используется на фазе компиляции слова, вторая - при его исполнении. Так должно быть потому, что компиляция числа из стека и укладка его в стек при исполнении - совершенно разные операции, поэтому эти функции поделены между двумя словами LITERAL и LIT. Так как поведение LITERAL при исполнении реализовано с помощью LIT, LIT называется исполнительной программой LITERAL.
Как LITERAL компилирует CFA LIT в словарь? Ответ дает слово COMPILE. Хотя COMPILE выглядит подобно [ COMPILE ], его действие совершенно другое. В отличие от [ COMPILE ] при использовании COMPILE в описании типа двоеточие оно компилируется как любое другое слово, не относящееся к "немедленному" типу. COMPILE что-то делает, только когда слово, в котором оно использовано, исполняется. В этот момент оно берет CFA, следующее за его собственным CFA, и компилирует в словарь по адресу HERE. Действие COMPILE называется отсроченной компиляцией, так как оно не делает действительно ничего до тех пор, пока слово, в котором оно применено, не будет исполнено.
COMPILE выполняет компиляцию, даже если слово, в котором оно использовано, является словом немедленного исполнения.
Таким образом, возможное описание LITERAL выглядит как : LITERAL STATE @ IF COMPILE LIT , THEN ; IMMEDIATE Когда LITERAL исполняется внутри описания типа двоеточие, отсроченное действие COMPILE" заключается в компиляции CFA слова LIT по адресу HERE. Запятая затем компилирует число из стека, которое будет нужно LIT, когда описываемое слово будет исполняться. Три фазы функционирования компилирующих слов сходны с этапами работы слов-описателей, как видно из гл. 11. В случае LITERAL этапами являются: 1 - компиляция LITERAL; 2 - исполнение LITERAL, когда слово, в котором оно использовано, компилирует LIT, и 3 - исполнение LIT, когда слово, где оно скомпилировано, исполняется.
Мы можем теперь понять, как работает слово .". Подобно LITERAL, слово ." является словом немедленного исполнения, которое имеет разные функции на фазе компиляции и исполнения. Когда." встречается в описании типа двоеточие, оно компилирует CFA своей исполнительной программы (иногда называемой (.")), а также следующий за ним текст, ограниченный." и " (двойная кавычка). Это делается командой 34 WORD, которая заносит счетную строку по адресу HERE как раз туда, где ее следует скомпилировать (см. главу 9). Слово." использует байт-счетчик счетной строки, так же как ALLOT, чтобы выделить в словаре место, достаточное для данной строки и ее счетного байта. Когда слово, куда скомпилирована строка, исполняется, (.") отображает строку, после чего исполняются слова, следующие за строкой. Итак, описание." может иметь вид : ." COMPILE (.") 34 WORD C@ 1+ ALLOT ; IMMEDIATE
Это описание." используется в Форт-83, где оно может работать только внутри описания типа Двоеточие. Но в Форт-79." может работать как внутри описания, так в вне его и оно описано несколько иначе. Описание." В Форт-79 могло бы выглядеть как : ." STATE @ IF COMPILE (.") 34 WORD C@ 1+ ALLOT ELSE 34 WORD COUNT TYPE THEN ; IMMEDIATE
Версия слова." в Форт- 79 является примером так называемых слов, зависящих от состояний, эти слова делают разные вещи в зависимости от того, в каком состоянии находится система: в режиме компиляции или исполнения. Вы можете вспомнить, что слово .( ("dop-paren" =" точка-скобка) стандарта Форт-83 используется для немедленного вывода текста, следующего за ним вплоть до разграничителя) (правая скобка). Его описание имеет вид : .( 41 WORD COUNT TYPE ; IMMEDIATE Форт-83 не использует стандартных слов, зависящих от состояния, хотя ваши собственные слова и могут быть такими.
Теперь должно быть ясно, что Форт имеет очень мощный компилятор, но в следующем разделе мы покажем, как еще более мощные компилирующие слова IF, ELSE, THEN, BEGIN и UNTIL могут компилировать Форт-структуры, которые реализуют при использовании ветвления и зацикливания. Понимание их работы при компиляции позволит вам сконструировать свои собственные компилирующие слова.
Упражнения
1. Опишите слова START-WHERE? так, чтобы, если описано : TASK START-WHERE? : при исполнении TASK было бы сообщено, из какого блока было скомпилировано TASK. 2. Написана программа для перевода долларов в фунты стерлингов. Курс варьируется от дня ко дню, так что. когда про грамма компилируется в начале каждого дня. необходим новый коэффициент пересчета. Опишите слово немедленного исполнения с именем ?RATE, которое при компиляции программы будет делать запрос текущего значения курса, а затем выдаст его значение в стек. (Разместите слово в свободном блоке для загрузки.) 3. Используя слово из упражнения 2, опишите слово с именем CONVERTS, которое при компиляции запрашивает текущее значение курса, а когда исполняется, преобразует доллары в фунты согласно объявленному курсу. Подберите соответствующие масштабы коэффициента для входных и выходных величин (запишите слово в тот же блок. что и в упражнении 2). 4. Теперь опишите три слова с именами ENGLAND, DENMARK и GERMANY, которые при исполнении будут переводить доллары в фунты , кроны и марки.
Слова должны использовать PRATE, и они должны воспринимать ежедневное значение курса для каждого вида валюты при компиляции, делая запрос "Dollars to kroner's, current rate?". (Снова используйте тот же блок.) 5. Опишите слово ?СОМР, которое будет печатать "Compile Only!" и выполнять ABORT, если слово, в котором оно применено, используется в режиме исполнения (?СОМР часто используется в описаниях компилирующих слов, таких как LITERAL, чтобы предотвратить их неправильное применение вне описаний типа двоеточие). 6. Что отобразит на дисплее следующая текстовая последовательность? : TEST COMPILE DUP ; IMMEDIATE : -TEST1 5 TEST ; TEST1 . . Почему TEST должно быть словом немедленного исполнения? Что бы произошло, если вы напечатали TEST ? (Подсказка: COMPILE содержит слово ?СОМР.)
Компиляция условных операторов Форт
Теперь нам следует посмотреть, как организуют работу IF...ELSE...THEN. Существуют две причины, почему мы этого хотим: познакомить вас подробнее с условными переходами и привести еще несколько примеров применения COMPILE. Мы рассмотрим сначала IF....THEN. Вот один из способов, каким их можно описать: : IF ?COMP COMPILE ?BRANCH HERE 0 , ; IMMEDIATE : THEN ?COMP HERE OVER - SWAP ! ; IMMEDIATE ?COMP предотвращает их использование вне описаний типа двоеточие. Как вы видели в предшествующих упражнениях, его описание могло бы иметь вид ; : ?COMP STATE @ 0= ABORT" Compile only!" ; Когда во входном потоке при компиляции встретится оператор IF, он первым делом с помощью COMPILE PBRANCH скомпилирует CFA слова ?BRANCH (?BRANCH иногда называют OBRANCH) в верхнюю ячейку словаря, затем с помощью HERE занесет адрес следующей свободной ячейки словаря в стек и, наконец, запишет 0 по этому адресу (0 будет заменен другим числом, сформированным оператором THEN). Слова между IF и THEN компилируются обычным порядком.
К моменту исполнения THEN, адрес кода 0, скомпилированного оператором IF, все еще лежит в стеке. Слово THEN вычисляет смещение адреса,- оставленного оператором IF, по отношению к верху словаря.
Это осуществляется командой HERE OVER -. Данное смещение затем запоминанием в ячейке, зарезервированной IF (посредством 0), с помощью команды SWAP !. Конечно, ни слово IF, ни слово THEN сами не компилируются, так как являются словами немедленного исполнения.
Мы можем понять работу IF... THEN яснее, рассматривая поле параметров слова. Как вы знаете из гл. 8, IF выполняет передачу управления слову, следующему после THEN, если в стеке лежит 0, в противном случае исполняются слова, расположенные между IF и THEN. Если мы опишем слово : NON-ZERO? IF ." Non-" THEN ." Zero" ; мы можем убедиться, что если при исполнении NON-ZERO? в стеке окажется ненулевое число,. дисплей отобразит "Non-Zero", в противном случае будет отпечатано "zero". Теперь, когда мы знаем, как работает IF...THEN, мы можем изобразить карту поля параметров слова NON-ZERO?
Поле параметров Содержимое 7BRANCH смещение (.") (4Non-) (.") (4Zеrо) (EXIT) Байты 2 2 2 5 2 5 2
где (4Non-) и (4Zero) представляют собой счетные строки, скомпилированные оператором.". Смещение будет равно 7 и указывает на CFA второго (.").
Теперь о том, как работает то, что скомпилировано IF...THEN. PBRANCH является исполнительной программой для IF, и, если в стеке 0, управление передается вперед на число байтов, заданное величиной смещения. Это делается путем добавления этого смещения к собственному адресу в PFA NON-ZERO?, Форту предписывается продолжить исполнение, начиная с указанного адреса. В случае NON- ZERO? исполнение возобновится со слова." , предшествующего "Zero". Если 7BRANCH обнаруживает в стеке ненулевое число, передача управления осуществляется со смещением в два байта, в результате выполняется " Non-" и." Zero".
Заметьте, что некоторые версии Форта используют абсолютный адрес, а не смещение к адресу для передачи управления оператором ?BRANCH. В этом случае описание IF будет тем же, a THEN следует описать как : THEN ?COMP HERE SWAP ! ; IMMEDIATE Таким образом, после ?BRANCH будет записан адрес передачи управления, а не смещение. ?BRANCH, конечно, использует тогда запомненный адрес передачи управления, а не вычисляет его, используя смещение.
Применение адреса вместо смещения обеспечивает большее быстродействие, так как не требует вычислений, прежде чем выполнить переход. Вы можете описать и распечатать описание NON- ZERO?, чтобы понять, какой метод реализован в вашем Форте. MMSFORTH использует абсолютные адреса переходов, а не смещения.
ELSE работает в какой-то мере аналогично комбинации IF и THEN. Его описание может выглядеть как : ELSE ?COMP COMPILE BRANCH HERE 0 , SWAP HERE OVER - SWAP ! ; IMMEDIATE
Когда ELSE встречается в тексте, последовательность COMPILE BRANCH HERE 0 делает то же, что и IF, но вместо CFA ?BRANCH компилируется CFA BRANCH. Последнее слово является исполнительной программой ELSE. После BRANCH компилируется 0, адрес которого остается в стеке для использования оператором THEN. Но вспомним, что в стеке лежит адрес, оставленный IF, поэтому необходима команда SWAP, прежде чем выполнять HERE OVER - SWAP ! для вычисления и запоминания смещения в ячейку, зарезервированную оператором IF. Это смещение указывает на CFA, которое следует сразу за 0, оставленным оператором ELSE. Адрес 0, который скомпилирован ELSE, теперь лежит в стеке, и THEN заменит его, так же как THEN заменяет 0, следующий за ?BRANCH в предшествующем примере.
Что делает ELSE, может быть прояснено на следующем примере. Если мы опишем слово : FLAG? ( n - ) IF ." True " ELSE ." False " THEN ." Flag" ; поле параметров FLAG? будет содержать ?BRANCH (смещение 1) (.") (5True) BRANCH (смещение 2) (.") (6False) (.") (4Flag) (EXIT)
При исполнении, если в стеке 0, ?BRANCH осуществляет переход к позиции непосредственно за (смещением 2), т.е. к оператору (.") для "False", скомпилированному." , следующим за ELSE в исходном тексте. В противном случае 7BRANCH "перепрыгивает" через (смещение 1) и исполняет (."), предшествующий "True", а затем переходит к BRANCH. BRANCH вычисляет адрес продолжения выполнения программы, используя (смещение 2), и переходит к фразе ."Flag", следующей за THEN в исходном тексте программы, BRANCH делает то же самое, что и ?BRANCH, но осуществляет переход вне зависимости от состояния стека.
Необходимость помнить об отличиях компилирующих слов во время компиляции и при исполнении может показаться утомительной. Следует лишь помнить, что любое компилирующее слово выполняет две функции: одну - на фазе компиляции, когда производится компиляция исполнительной программы данного слова и следующих за ней числа, смещения, адреса или текста, и вторую - на фазе исполнения, т.е. когда скомпилированное слово исполняется- Теперь вы знаете, как разобраться в работе большинства слов путем анализа их поля параметров. Давайте попытаемся это сделать на ряде примеров.
Упражнения
1. Опишем : LOOK-AT-IF ( flag -) IF 1 ELSE 2 THEN 3 ; С помощью пропечатки поля параметров как бы вы нашли положение LIT (или его эквивалента) и положение 1, 2 и 3 (запомните, что некоторые малые числа заносятся в стек словами Форта)? Используя эти позиции в качестве контрольных точек, как бы вы нашли откомпилированное CFA операторов 7BRANCH и BRANCH (или их эквивалентов) и адреса или смещения, следующие за ними? Как можно определить, используют ли команды ветвления смещение или адрес? 2. Опишите .СМР для отображения величины HERE и содержимого стека во время компиляции (используя.S). 3. Введите .СМР перед IF, ELSE и THEN и после 3 в LOOK-AT-IF. Объясните полученные значения в стеке и изменение величины HERE. He забудьте, что : (двоеточие) может положить что-то в стек. 4. Для иллюстрации предположим, что CFA LIT равна 174. Если вы опишете : TEST [ 174 , 5 , ] . ; Что будет сделано? 5. Предположим, что LIT или его эквивалент не существуют в качестве поименованного слова. Используя только его CFA, опишите NEWLITERAL. Опишите NEWLITERAL так, чтобы оно работало как LITERAL, но с числами двойной длины в стеке. 6. Предположим, что ?BRANCH и BRANCH не описаны в вашей системе. Используя CFA эквивалентов, дайте описание для NEWIF и NEWELSE. Предполагается, что осуществляется абсолютное, а не относительное ветвление.
Слова без заголовков
Слова без заголовков в соответствии с определением лишены имени и поля связи, а имеют только тело.
Конечно, они не могут быть найдены в словаре, но, поскольку они имеют тело, т.е. поле программы и поле параметров, они могут быть исполнены, В действительности, когда вы в предшествующем наборе упражнений описали LITERAL, не имея слова LIT, вы использовали LIT как слово без заголовка. Векторное исполнение (смотрите гл. 6) так же игнорирует заголовки слов (так как EXECUTE требует для исполнения только CFA слов). Слова без заголовков могут использоваться для того, чтобы их нельзя было найти в словаре. Иначе они являются "спрятанными" словами (хотя, как вы знаете, по слову LIT умный программист может, вероятно, их найти).
Стратегия создания слов без заголовков заключается в создании в словаре структур, которые имеют все необходимое слову для исполнения, кроме имени и поля связи. Конечно, слова без заголовков нуждаются в поле программы, содержимое которого должно указывать на исполнительную программу, соответствующую типу слова, которое вы создаете. Мы приведем здесь пример, как создать слова типа двоеточие без заголовка, хотя константы, переменные и другие слова без заголовков могут создаваться согласно тем же правилам.
Давайте создадим слово без заголовка, эквивалентное нашему старому другу BASE?. Сначала нам нужно знать адрес исполнительной программы описаний типа двоеточие. Для этого достаточно воспользоваться оператором FIND (в Форт-79) или ' (в Форт-83) для любого слова типа двоеточие, с тем чтобы получить CFA и занести его содержимое в стек. Если вы оставите этот адрес в стеке, приведенная ниже программа сформирует описание BASE? без заголовка, оставив его CFA в стеке для последующего использования описанного слова: HERE SWAP , ] BASE @ DUP DECIMAL - BASE ! EXIT [ Слово HERE заносит в стек верхний адрес словаря, который станет адресом поля программы, нового слова без заголовка.
Последовательность SWAP , компилирует адрес исполнительной программы слов типа двоеточие, который вы оставили в стеке. Слово ] осуществляет переход в режим компиляции и слова, начиная с BASE по ! будут скомпилированы в поле параметров слова без заголовка точно так же, как это было в обычном описании.
CFA слова EXIT компилируется аналогично тому, как это делает ; в стандартном описании. Затем [ возвращает систему в режим исполнения. CFA слова без заголовка (записанное по адресу HERE) - все еще в стеке; для того чтобы предохранить его от потери, мы можем спасти его с помощью описания CONSTANT BASE? теперь введение BASE? EXECUTE исполнит "беззаголовочная" версию слова BASE?. Конечно, намного проще описать BASE?, как обычно, - через двоеточие. Зачем нам все эти дополнительные хлопоты? Версия BASE? без заголовка не может быть найдена в словаре и не может быть исполнена или скомпилирована в другие слова без дополнительных усилий. Но эти недостатки слов без заголовка являются и их достоинствами, так как они недоступны и не могут чему-либо по мешать. Это важно для некоторых программ, которые при неправильном использовании могут разрушить систему. Многие внутренние программы Форта откомпилированы в виде слов без заголовков, потому что они практически бесполезны для чего бы то ни было, кроме внутренних задач Форта. (MMSFORTH использует массив MMS, где содержится набор системных слов без заголовка. На пример, MMSFORTH-эквивалеит PBRANCH является третьим элементом MMS.) Слова без заголовков полезны также, чтобы предотвратить несанкционированные манипуляции с откомпилированной программой, - действительно, целые прикладные Форт-программы компилируются с использованием слов без заголовков, хотя и без использования этого метода. (В вашем Форте, возможно, предусмотрена компиляция беззаголовочных программ. MMSFORTH использует для этой цели системную программу TEMP-HEAD)
Это дает представление о том, насколько гибкой может быть Форт-компиляция. Хотя в отличие от других языков в Форте компиляция и не генерирует программу в машинных кодах, она и не должна это делать; каждое слово Форта в конце концов указывает на программу,в машинных кодах и исполняет ее. Форт - один из немногих языков программирования, который позволяет модифицировать собственный компилятор, используя программы высокого уровня.
Упражнения
1. Создайте два слова' без заголовков, одно. чтобы пропечатать "Word#0". и второе, чтобы пропечатать "Word#1". Исполните оба слова, используя константы с именами 0WORD и 1WORD. (Запоминание адреса исполнительной программы для операторов типа двоеточие в константе COLON-ADDR может упростить решение задачи.) 2. Как бы вы откомпилировали слова без заголовков 0WORD и 1WORD в описание типа двоеточие? (Подсказка: используйте скобки и , (запятая).) 3. Используйте CREATE...DOESE> для создания слова-описателя, производные слова которого будут исполнять 0WORD, если в стеке лежит 0, и 1WORD, если в стеке лежит 1. (Этот метод может быть использован для создания очень сложных для понимания исходных текстов.) 4. Напишите слово-описатель GIVE-NAME, которое, формируя производные слова, требует наличия в стеке CFA описанного перед этим слова без заголовка. Когда производные слова исполняются, они должны исполнять соответствующее слово без заголовка. 5. Опишите слово без заголовка (может быть, 2WORD) и оставьте его CFA в стеке. Затем опишите слово типа двоеточие и скомпилируйте CFA слова без заголовка так, что оно будет использовано словом типа двоеточие. (Будьте осмотрительны при компиляции слова типа двоеточие, следите за состоянием стека.) 6. Если в вашем Форте 7BRANCH и BRANCH не имеют имени, как бы вы описали IF и ELSE согласно приведенному нами ранее алгоритму? 7. Сделайте то же, что и в упражнении 6, но опишите LITERAL, используя безымянное LIT.
Исполнение
Мы видели, как происходит компиляция; ну а теперь, что вы скажете об исполнении? То есть, что случится после того, как мы откомпилировали слово и затем напечатали его имя или использовали EXECUTE при наличии его CFA в стеке? Давайте сначала посмотрим весь процесс в целом, затем перейдем к рассмотрению деталей механизма.
Мы можем увидеть весь процесс исполнения в Форте, описав следующие слова: : 1LEVEL ." The lowest level " CR ; : 2LEVEL ." Begin 2LEVEL ". CR 1LEVEL . " End 2LEVEL " CR ; ; 3LEVEL ." Begin 3LEVEL " CR 2LEVEL " End 3LEVEL " CR : Когда исполняется 3LEVEL, на экране отображается Begin 3LEVEL Begin 2LEVEL The lowest level End 2LEVEL End 3LEVEL ok
Это показывает, что 3LEVEL использует слово 2LEVEL, которое, в свою очередь, использует 1LEVEL, в соответствии с тем, что мы потребовали в описании. Когда исполнение 1LEVEL завершено, управление передается назад к 2LEVEL, а затем к 3LEVEL, и в конце концов появится отклик "ok", сообщающий о готовности к новому вводу. Заметьте, что, когда начинает исполняться 3LEVEL, используя 2LEVEL (и когда стартует 2LEVEL, используя 1LEVEL), Форт должен запомнить, куда следует вернуться по завершении задания низкого уровня. Итак, как же Форт отслеживает порядок исполнения?.
Давайте проследим исполнение 3LEVEL, просмотрев описание. Необходимо несколько указателей. Один мы назовем указателем инструкции или IP, он отслеживает слово, которое должно быть исполнено следующим , и первоначально указывает на последовательность." Begin 3LEVEL". Этот указатель переходит от слова к слову, пока не встретит 2LEVEL внутри описания слова 3LEVEL. Теперь IP должен двинуться внутрь 2LEVEL, указывая на ." Begin 2LEVEL", но сначала нужен другой указатель, чтобы хранить информацию о том, куда вернуться в описании 3LEVEL. Второй указатель должен указывать на последовательность ." End 3LEVEL". После того как 2LEVEL отобразит " Begin 2LEVEL ", та же проблема возникает вновь. Третий указатель нужен, чтобы отслеживать место, куда возвращаться в описании 2LEVEL, в то время как IP отслеживает последовательность слов внутри 1LEVEL. Итак, теперь имеется два ожидаемых возврата: конкретно к сообщению "End" в 2LEVEL и к сообщению "End" в 3LEVEL. Эти указатели, конечно, содержат адреса CFA в поле параметров слов. Указатель инструкций указывает на следующий CFA слова, которое должно быть исполнено. Другие указатели, второй и третий в нашем примере, указывают на слова, подлежащие исполнению после возвращения из слова на более низкий уровень. Эти указатели хранятся в стеке возвратов, что является, конечно, причиной его наименования. На верху стека возвратов хранится адрес программы в интерпретаторе, которая должна быть исполнена по завершении выполнения слова, а под ним хранятся адреса, управляющие указателем инструкций и через него возвратом с одного уровня на другой.
Из этого должно быть очевидно, почему ничего нельзя положить в стек возвратов и оставить там при выходе из слова.
Это рассмотрение процесса исполнения показывает общий подход к тому, как происходит выполнение программы, но остается еще много вопросов без ответа. Как осуществляется управление IP и стеком возвратов? Как выполняется переход между словами с уровня на уровень? Как производится передача управления машинной программе и уход из нее? Существуют и другие вопросы, на которые почти невозможно ответить, используя только стандартные слова. Мы должны тщательно проследить на примере, как это делается. Объяснения, которые мы дадим, являются типовыми для большинства версий Форта, но существуют и другие способы выполнения программ. Даже если ваш Форт отличается от рассматриваемого, мы полагаем, что, если вы поняли метод исполнения, описанный здесь, вы оцените многие аспекты структуры и функций любой версии Форта. Объяснение, данное ниже, является сложным и потребует очень тщательного изучения и, вероятно, повторного чтения. Но мы обсуждаем самое ядро Форта и надеемся, что вы сочтете эту работу важной.
Прежде чем мы рассмотрим пример, мы должны ввести еще несколько указателей и некоторые программы, написанные в машинных кодах. Если их назначение и функции не будут очевидны при первом прочтении, вернитесь к их описанию позднее, когда вы будете отслеживать процесс исполнения на примере. Кроме указателя инструкций и стека возвратов мы будем пользоваться двумя другими указателями, чтобы объяснить, как Форт исполняет откомпилированные слова. Один из них - указатель слов (который мы будем обозначать WP); он содержит адрес поля параметров слова в момент начала его исполнения (почему это так, будет понятно позднее). Другой указатель мы будем называть регистром передачи управления (сокращенно - JP). Он служит для хранения адреса, куда будет передано управление для исполнения программы в машинных кодах. Запомните, что IP, WP и JP характеризуют лишь один способ представления того, как осуществляется исполнение слов в Форте.
Эти указатели обычно представляют собой регистры центрального процессора, но существует много реализации на разных ЭВМ. Сокращения, которые будут использоваться в дальнейшем, представлены в табл. 15.3.
Таблица 15.3. Указатели и стек возвратов, используемые при выполнении программы Форта
IP = Указатель инструкции Адрес CFA текущего слова, подлежащего исполнению WP = Указатель слов PFA слова в начале обращения JP = Указатель передачи управления Адрес, куда должен передать управление процессор RS = Стек возвратов
Стек адресов возврата
Исполнение в Форте представляет собой последовательность переходов между двумя типами программ в машинных кодах. Это: 1. Исполнительная программа слова Форта (т.е. программа, адрес которой указан в поле программы). 2. Программы в машинных кодах, размещенные в поле параметров слов-примитивов,
Исполнительная программа определяет то, как выполняется слово Форт, а программы в машинных кодах слов-примитивов выполняют полезную работу Форт-программы. Различие между исполнительной программой и программами в машинных кодах несколько запутанно, так как исполнительная программа слова-примитива - это и есть программа в машинных кодах, размещенная в его поле параметров. Выполнение слов типа двоеточие и примитивов контролируется тремя короткими программами в машинных кодах, каждая из которых требует только около дюжины байтов на 8-битовом процессоре, NEXT является наиболее фундаментальной программой, так как она используется для завершения всех программ в машинных кодах используемых Фортом.
NEXT является не словом словаря Форта, а словом, используемым Форт-ассемблером для перехода к внутреннему или адресному интерпретатору. Машинная программа, к которой происходит переход при NEXT, осуществляет переход к выполнению следующего слова Форта.
Слова типа двоеточие используют еще две программы, которые мы рассмотрим. Первая - это исполнительная программа описаний типа двоеточие (иногда называется DOCOL), ее функции заключаются в управлении исполнением таких описаний.
Другим словом, используемым при исполнении слов типа двоеточие, является EXIT, CFA которого компилируется в конце каждого описания оператором ;. Слово EXIT извлекает из стека возвратов и заносит в указатель инструкции код, управляющий тем, что будет исполняться следующим. Как исполнительная программа, так и слово EXIT передают управление слову NEXT. чтобы перейти к исполнению следующего слова. Функционирование этих программ станет понятным из нашего примера. Давайте опишем слово : SQUARE DUP * ; и, используя произвольные адреса, рассмотрим его скомпилированную структуру; это позволит нам исследовать детально механику исполнения программы Форта. Тело слова SQUARE может быть представлено как.
CFA PFA Тело слова SQUARE 20000 20002 20004 20006 Содержит 13000 18000 17000 12000 (CFA слов) DUP * EXIT
CFA слова SQUARE равно 20000, содержимое которого (13000) является произвольным адресом, который мы выбрали для исполнительной программы слов типа двоеточие. Другие три адреса, скомпилированные в PFA слова SQUARE (18000, 17000 и 12000), являются CFA слов DUP, * и EXIT. Но так как DUP и * являются примитивами (CFA которых указывают на их PFA), мы можем дополнить нашу диаграмму дополнительным списком адресов:
CFA PFA Тело слова SQUARE 20000 20002 20004 20006 Содержит... 13000 18000 17000 12000 (CFA слова) DUP * EXIT Содержит ... 18002 17002 12002
Чтобы получить более интересный пример, давайте опишем слово : CUBE DUP SQUARE * ; теперь мы можем исследовать, как слова типа двоеточие обращаются к другим словам аналогичного типа. Используя адреса, которые были приняты для слова SQUARE, мы можем получить карту компиляции слова CUBE:
CFA PFA Тело слова CUBE 21000 21002 21004 21006 21008 Содержит... 13000 18000 20000 17000 12000 (CFA слов) DUP SQUARE * EXIT Содержит... - 18002 13000 17002 12002
Чтобы детально проследить исполнение слов SQUARE и CUBE, основывайтесь на этих картах.
Когда во входном потоке встретится 3 CUBE, интерпретатор текста положит в стек 3, затем найдет в словаре CUBE и оставит в стеке его CFA (21000).
Так как Форт находится в режиме исполнения, это CFA передается оператору EXECUTE, чтобы начать выполнение слова CUBE. Слово EXECUTE берет CFA из стека и использует его для получения адреса исполнительной программы CUBE (13000), который заносится в регистр передачи управления (JP). В то же самое время в указатель слов WP записывается PFA слова CUBE. Исполнение начинается путем передачи управления по адресу, лежащему в JP, или 13000 (адрес исполнительной программы описаний типа двоеточие). То, что мы только что объяснили, может быть отображено на диаграмме:
До Операция После
Стек - ? Засылка 3 в стек - 3 Стек - 3 Обнаружение CFA слова CUBE - 3 21000 EXECUTE, который выполняет: Стек - 3 21000 Используя CFA CUBE, - 3 определяет адрес исполнительной программы описаний типа двоеточие JP = ? и кладет в JP JP = 13000 WP = ? Укладку PFA CUBE в WP WP = 21002 JP = 13000 Переход к исполнительной программе типа двоеточие
Прежде чем вы увидите, что делает исполнительная программа, мы должны заметить, что IP содержит адрес во внешнем интерпретаторе
До Операция После
DOCOL, которая выполняет: IP = OUTER Засылку IP в стек возвратов, чтобы сохранить адрес, куда возвращаться RS -- OUTER WP = 21002 Занесение PFA CUBE в IP IP = 21002 NEXT. чтобы закончить исполнение программы : IP @ @ Извлечение адреса исполнительной программы слова DUP и загрузку его в JP JP = 18002 WP = 21002 Занесение PFA DUP в WP WP = 16002 IP = 21002 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP = 21004 JP = 18002 Передачу управления исполни- тельной программе DUP (машин- мой программе дублирования кода в стеке) Стек - 3 Работу DUP - 3 3
DUP (конкретно 21002), команда IP @ @ является символическим отражением того, как определяется адрес исполнительной программы DUP. (Поскольку 21002 @ выдает 18000, содержимое 18000, т.е. 18002, может быть занесено в JP с помощью команды 21002 @ @. Конечно, @, как это использовано здесь, является символическим и не означает занесения чего-либо в стек.
Если что- то не ясно, смотрите диаграмму CUBE.) Адрес исполнительной программы DUP (ее PFA, поскольку программа в машинных кодах лежит в ее
До Операция После
IP = 21004 (Словом DUP не изменен) IP = 21004 NEXT, который выполняет: IP @ @ Вычисление адреса исполнительной программы SQUARE и загрузку ее в JP JP = 13000 WP = 18002 Занесение PFA SQUARE в WP WP = 20002 IP = 21004 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP = 21006 JP = 18002 Передачу управления исполнительной программе SQUARE (машин- ной программе дублирования кода в стеке)
поле параметров) загружается в JP, так как процессор должен исполнить ее непосредственно. Но прежде чем это сделано, IP дается приращение, чтобы он указывал на следующую ячейку (21004) в PFA слова CUBE. Затем управление передается программе DUP, которая дублирует в стек число 3.
После того как DUP выполнит свою работу, исполнение переходит к оператору NEXT, который является всегда последней командой в поле параметров примитива.
Еще раз исполняется программа DOCOL, на этот раз для слова SQUARE.
Программа DUP дублирует число 3, хранящееся в стеке. Заметьте, что хотя в WP загружено PFA слова DUP, оно не использовано. Исполнение продолжается путем передачи управления оператору NEXT, так как DUP является примитивом.
До Операция После
IP = 20004 (Не изменен словом DUP) IP = 20004 NEXT, который выполняет: IP @ @ Определение адреса исполнительной программы слова * и загрузку его в JP JP = 17002 WP = 18002 Занесение PFA * в WP WP = 17002 IP = 20004 Приращение IP, теперь он указывает на следующее слово, IP = 20006 подготовлено его исполнение JP = 17002 Передачу управления исполнительной программе * (машинной программе перемножения двух чисел) Стек = -3 3 Выполнение * - 3 9
* перемножает два верхних числа в стеке, и мы еще раз обращаемся к NEXT, так как * является примитивом.
До Операция После
IP = 20006 (Не изменен словом *) IP = 20006 NEXT, который выполняет: IP @ @ Получение адреса исполнительной программы слова EXIT и загрузку его в JP JP = 12002 WP = 17002 Занесение PFA слова EXIT в WP WP = 12002 IP = 20006 Приращение IP, теперь он указывает на следующее слово IР = 20008 (после конца SQUARE) IP = 20008 IP = 12002 Передачу управления исполнительной программе EXIT (машинной программе перехода на более высокий уровень)
Заметьте, что хотя, IP указывает на ячейку после конца SQUARE, это не имеет никакого значения, когда мы завершаем исполнение SQUARE с помощью EXIT, так как в случае исполнительной программы типа двоеточие EXIT завершается переходом к NEXT.
До Операция После
EXIT, который выполняет RS -- OUTER 21006 Засылку в IP кода из стека RS -- OUTER возвратов IP = 21006 NEXT,который выполняет; IP @ @ Определение адреса исполнительной программы слова * и загрузку его JP JP = 17002 WP=12002 Занесение PFA слова * в WP WP = 17002 IР=20006 Приращение IP, теперь он указывает на следующее слово, подготовлено его исполнение IP=20008 JP=17002 Передачу управления исполнительной программе * (машинной программе умножения двух чисел). Стек - 3 9 Работу * - 27
* умножает два числа в стеке, и мы снова сталкиваемся с командой NEXT в конце программы, написанной в машинных кодах.
До Операция После
IР=21008 (Не изменен словом *) IP=21008 NEXT. который выполняет: IP @ @ Получение адреса исполнительной программы слова EXIT и загрузку его в JP JP=12002 WP=17002 Занесение PFA слова EXIT в WP WP=12002 IР=21008 Приращение IP, теперь он IР=21010 указывает на следующее слово, после конца слова CUBE IP=21010 JP=12002 Передачу управления исполнительной программе EXIT (машинной программе перехода на более высокий уровень)
Здесь снова не имеет значения то, что IP содержит адрес, который следует после описания CUBE. Теперь мы завершаем исполнение CUBE оператором EXIT.
До Операция после
Оператор EXIT, который RS=OUTER выполняет: Засылку в IP кода RS -- ? из стека возвратов IP = OUTER NEXT, который выполняет: IP @ @ Определение адреса исполнительной программы слова OUTER и загрузку его в JP JP = ? WP = 12002 Укладку PFA ? в WP WP = ? IP = OUTER Приращение IP, теперь он указывает на следующее слово. подготовлено его исполнение IP=OUTER+2 JP = ? Передачу управления исполнительной программе слова во внешнем интерпретаторе Стек = 27 Стек содержит три в кубе
Это завершает исполнение CUBE и управление передается назад к исполнительной программе следующего слова, которое исполняется внешним интерпретатором.
Когда входной поток иссякает, управление возвращается клавиатуре и печатается отклик "ok" при коде 27 в стеке.
Исполнение слова CUBE можно в общем виде охарактеризовать как выполнение последовательности программ в машинных кодах, представленных в табл. 15.4.
Исполнение всех слов Форта организовано одним и тем же основным способом. Единственной вариацией является действие исполнительных программ, на которые указывает содержимое полей программы каждого слова. Исполнительная программа для константы занесет в стек содержимое ее поля параметров и передаст управление слову NEXT. Исполнительная программа слова + (программа в машинных кодах лежит в его поле параметров) сложит два числа из стека и перейдет к исполнению слова NEXT. Программы в машинных кодах для слов BRANCH и 7BRANCH должны вычислять адрес передачи управления и изменять соответственно IP, прежде чем исполнить NEXT. Различные другие слова (такие как исполнительные программы для DO и LOOP) изменяют порядок исполнения программы Форта путем изменения содержимого стека возвратов. Исполнение программы в форте является простым и весьма гибким.
Хотя слово NEXT, различные исполнительные программы и слово EXIT все являются очень короткими (и, следовательно, быстрыми). Форт имеет встроенные "накладные расходы", так как переход от одной программы к другой через слова NEXT и EXIT требует времени. Несмотря на это, используя CUBE в качестве примера, в табл. 15.4 показано, что только около половины байтов, исполняемых словом CUBE, составляют "накладные расходы". Половина потрачена в DUP и *, которые были бы нужны, даже если слово CUBE было написано в машинных кодах. Поскольку скорость исполнения является примерно пропорциональной числу исполняемых байтов (в действительности * медленнее, так как эта программа включает в себя циклы), слово CUBE, как написано в Форте, не более чем вдвое медленнее варианта, написанного на ассемблере. Заметьте также, что Форт очень
Таблица 15.4. Программы, выполняемые при исполнении слова CUBE ( 1)
Имя Требуемое число байт EXECUTE 7 DOCOL. для CUBE 14 NEXT 12 DUP 5 NEXT 12 DOCOL для SQUARE 14 NEXT 12 DUP 5 NEXT 12 * 70 NEXT 12 EXIT 12 NEXT 12 * 70 NEXT 12 EXIT 12 NEXT 12 (внешний интерпретатор) ________________________ Всего 305
эффективно использует память. Хотя CUBE использует всего 305 байтов, описание CUBE и SQUARE добавляет к словарю только 30 байтов. Более того, описание CUBE в машинных кодах потребовало бы 75 байтов при оптимальном описании * и DUP. Форт-программы могут действительно потребовать меньше памяти, чем программа в машинных кодах. Даже если не учитывать быстродействие и использование памяти, трудно себе представить более простой, элегантный или удобный способ связать фрагменты машинных программ, чем метод, реализуемый в Форте.
Одни микропроцессоры подходят лучше для работы Форта, другие меньше, в зависимости от их стеков и возможностей косвенной адресации. Существуют несколько Форт-систем со словарями, записанными в ПЗУ (постоянное запоминающее устройство), но разработаны и более впечатляющие варианты. Так как машинные программы, используемые Фортом, при исполнении весьма коротки, имеется возможность применения микропрограмм для непосредственной реализации Форт-команд вместо стандартного набора инструкций. Если NEXT, различные исполнительные программы и EXIT можно было бы исполнять за один машинный цикл, то оценка показывает, что скорость исполнения Форта увеличилась бы более чем в 100 раз по сравнению с написанием Форт-примитивов на ассемблере для процессора той же серии. Это бы позволило микроЭВМ, ориентированным на форт, работать со скоростью больших вычислительных машин.
Не важно, каким процессором вы располагаете, в любом случае вы можете ускорить исполнение Форт-программ, переписав слова с использованием Форт-ассемблера. С помощью ассемблерного слова CODE можно описать слова со структурой, идентичной примитивам, с той же легкостью, что и в случае описания типа двоеточие (предполагается, что вы владеете программированием на ассемблере).
Этой темы мы касаемся в гл. 16.
Упражнения
1. Чтобы отображать содержимое стека возвратов (используя слово : XX R@ U. ;), опишите новые версии 1LEVEL, 2LEVEL и 3LEVEL. Что в действительности означают отображенные адреса? 2. Что случится, если вы используете команду R> DROP после сообщения в 1LEVEL ? ========================== 1 В таблице представлены исполняемые байты памяти для типов 8- битовой реализации Форта. Подчеркнутые программы выполняют полезную работу. ========================== 3. Испробуйте команду R> DROP в других точках ILEVEL и 2LEVEL, чтобы познакомиться с ее действием. Можете вы объяснить, почему было бы неразумно использовать R> DBOP в 3LEVEL? 4. Как может быть использован указатель WP в исполнительной программе для констант ? 5. Опишите CUBE? как : CUBE?? DUP SQUARE EXIT * ; и отследите состояние стека, стека возвратов, IP и WP, используя программу CUBE в качестве путеводителя. Является ли слово EXIT, когда оно используется, в точности таким же. что и в случае, когда оно встречается в конце описания типа двоеточие? 6. Вспомните, что LITERAL компилирует CFA своей исполнительной программы (часто называемой LIT) и следующее за ним число, лежащее в стеке, в любое описание, где оно использовано. Как вы полагаете, избегает Форт "исполнения" числа, скомпилированного после LIT, когда слова, где они использованы, исполняются?
Рекурсия
В гл. 8 мы обещали рассказать вам о другом типе циклов - рекурсивных циклах. Рекурсия в вычислительной терминологии означает возможность подпрограмме обращаться к самой себе. В Форте это значит, что слово обращается к самому себе в пределах своего описания. Поскольку рекурсия весьма опасна, если вы не понимаете точно, что происходит, мы отложили эту тему до тех пор, пока не поняли процесса исполнения. Рекурсия в Форте весьма проста в реализации. Все, что требуется сделать, - это скомпилировать CFA слова внутри его собственного поля параметров. Когда это CFA встретится, исполнение переключается назад к началу описания, т.е.
к началу поля параметров. В MMSFORTH и многих других версиях слово, которое это реализует, имеет имя MYSELF.
Описание в MMSFORTH имеет вид : MYSELF ?COMP LAST-CFA , ; IMMEDIATE
LAST-CFA заносит в стек CFA заголовка слова, описанного последним, в данном случае CFA самого слова. Некоторые версии используют RECURSE вместо MYSELF, другие позволяют использовать просто имя самого слова.
Причина, почему рекурсия опасна, связана с тем, что очень легко создать бесконечные циклы и довольно легко переполнить стек возвратов. Рассмотрим это: : DESTROY! MYSELF ;
Когда DESTROY! исполняется, каждый раз при обращении слова к самому себе происходят приращение указателя стека возвратов и запись в него адреса слова, следующего за словом MYSELF, в данном случае EXIT. Здесь не только нет способа выхода из цикла, но приращение указателя стека возвратов очень скоро исчерпает ресурс места в памяти, выделенной для этого. Слово MYSELF в действительности всегда используется внутри структур управления, например в конструкциях IF...ELSE...THEN... Если вы опишете : MYLOOP DUP 10 U< IF DUP . 1+ MYSELF THEN ; и затем напечатаете 5 MYLOOP вы увидите 5 6 7 8 9 С другой стороны, если вы опишете : ODDLOOP DUP 10 U< IF DUP 1+ MYSELF THEN . ; его исполнение выдаст на экран 10 9 8 7 6 5
Что случилось? Каждый раз, когда ODDLOOP обращается к самому себе, новое число на 1 больше, чем предшествующее, заносится в стек. И каждый раз увеличивается указатель стека возвратов, указывая на. .Когда достигается предел, равный 10, стек возвратов очищается последовательным исполнением слова. , при этом печатаются числа, записанные в стеке параметров. Вот более полезный пример. Факториал числа - это результат умножения числа на число меньше этого на 1 с последующим умножением произведения на число, еще раз уменьшенное на 1, и т.д. То есть факториал 5 должен быть равен 5х4х3х2х1 Теперь, если вы опишете : FACTORIAL DUP 1- DUP 2 > IP MYSELF THEN * ; Тогда 7 FACTORIAL отобразит 5040, факториал 7. Если это вас смущает, рассмотрим слово : SHOWFACT DUP .
DUP 1- DUP 2 > IF MYSELF THEN DUP . * ; которое при исполнении с 7 в стеке отобразит на дисплее 7 6 5 4 3 26 24 120 720 и, если вы затем напечатаете. , вы увидите значение факториала 5040. Небольшое исследование SHOWFACT должно прояснить вам, как работает FACTORIAL. Вы можете описать аналогичное слово с использованием do-loop, но оно будет более неуклюжим. В упражнениях вы найдете еще несколько приложений MYSELF.
Упражнения
1. Опишите FACTORIAL, используя DO-LOOP. 2. Опишите SHOWASCII так, что, когда вы напечатаете 50 80 SHOWASCII вы увидите числа и эквивалентные им ASCII-символы. Сделайте это с использованием рекурсии и do-loop. 3. Опишите слово GENERATIONS для вычисления числа поколений, необходимых одной бактерии, чтобы произвести более 2000 бактерий путем простого деления. Сделайте это, используя рекурсию и конструкцию BEGIN... . Эти упражнения показывают, что в большинстве случаев циклы предпочтительнее рекурсии.
Выводы
Не существует другого такого языка, в котором введение могло бы поднять ваш уровень понимания того, как в действительности он работает. И нет другого языка, который бы позволил вам настолько вмешиваться во внутреннюю работу его интерпретатора, компилятора и в способ исполнения программы. Если вы поняли содержание главы, а также то, как работает ваш микропроцессор, то вы уже почти в состоянии создать свою версию Форта. Может быть, эта способность языка создавать и модифицировать самого себя объясняет, почему существует так много коммерчески доступных версий Форта. Но, конечно, власть, которую предоставляет вам Форт над самим собой, несет в себе и определенную ответственность. Очень легко сделать что-то, что разрушит систему. Забудьте лишь использовать EXIT в слове без заголовка или не обеспечьте выход из рекурсивного слова и вы будете вынуждены перезагрузить ЭВМ.
Но существует даже более элементарный уровень Форта. Это использование программ на ассемблере в качестве части Форт-программы. Программирование на ассемблере в Форте так же просто и интерактивно, как и программирование на Форте, если вы знаете, как работает ассемблер.В гл. 16 мы покажем вам, как вы можете описать такие фундаментальные слова, как SWAP и DROP, описывая их в рамках Форта. Вы увидите, что можете использовать Форт для того, чтобы реализовать все возможности вашей ЭВМ.
Программирование на форт-ассемблере
Как описано в гл. 3, память ЭВМ представляет собой серию ключей, которые могут использоваться для представления " 1" и "О" в двоичных числах. Программы и данные запоминаются в двоичной форме, таким образом представляя числа, текст или машинные коды. В гл. 3 рассмотрены различные способы представления чисел, а в гл. 9 обсуждается запоминание и манипулирование строками текста. В этой главе мы обсудим, как писать слова, чтобы исполнять программы в машинных кодах. Машинный язык (называемый также машинным кодом) - это набор двоичных чисел, которые ЭВМ может интерпретировать как инструкции и непосредственно исполнять. Это наиболее фундаментальный способ программирования ЭВМ. Преимущество использования программы в машинных кодах, в противоположность программе на языке высокого уровня, заключается в том, что это дает возможность полностью управлять всеми аспектами работы ЭВМ и позволяет написать программу, которая будет исполняться так быстро, как только может работать машина. Наиболее общепринятый способ написания программ в машинных кодах заключается в использовании программы, называемой ассемблером.. Каждая машинная инструкция имеет имя, называемое мнемоническим, чтобы облегчить ее запоминание. "Типичный" (не форт) ассемблер воспринимает файл мнемоники, написанный с использованием редактора, и преобразует мнемонику в машинные коды, которые либо запоминаются в другом файле, либо загружаются в память, чтобы быть исполненными. После нескольких последовательных, требующих времени и памяти шагов (таких как редактирование связей и загрузка) программа в машинных кодах может быть исполнена либо сама, либо совместно с другой программой, написанной на другом языке. Форт-ассемблер использовать много проще, так как не требуется редактирования связей и загрузки, а мнемоника может быть использована как част), описаний слов Форта в Форт-программе. Мнемоника форт-ассемблера - это слова, которые компилируют машинные коды и слово, описанное с помощью слова CODE.За подобными справками вам следует обращаться к руководству по вашему процессору. В 8-битовом процессоре 8 бит (байт) рассматриваются в качестве команд или данных. Хотя существует только 256 (2^8) возможных комбинаций из 8 битов, реализуемо более 256 типов команд, так как некоторые байты будут заставлять ЦП воспринимать еще один или несколько байтов для формирования команды. Например, 8-битовый микропроцессор Z-80 имеет более 700 типов команд, содержащих от одного до 4 байтов каждая. Шестнадцатибитовые микропроцессоры могут использовать 8- или 16-битовые команды, 8- или 16-битовые регистры и могут передавать данные по 16 или 32 битов за такт в зависимости от модели. Конечно, микроЭВМ имеют даже больше вариаций. Мы приведем примеры для микропроцессора Z-80, так как он используется в TRS-80 и в семействе СР/М машин, а также для 8086 и 8088, так как они применяются в IBM PC и семействе оборудования, использующем MS-DOS. Самый простой способ рассмотрения машинных программ на вашей ЭВМ - это пропечатка поля параметров примитивов. Используя описание DUMP из гл. 14, ' SWAP 1 DUMP в Форт-79 или ' SWAP >BODY 1 DUMP в Форт-83, отобразим машинную программу, которая меняет местами два верхних элемента в стеке. Для Форта Z-80, который использует регистр указателя стека параметров Форта, эта программа, вероятно, отпечатает (адр) D1 Е1 D5 Е5 FD E9 хх хх хх xх хх хх хх хх хх хх где мы использовали "хх" для обозначения байтов, величина которых не определена. (Это пример для MMSFORTH на TRS80.) Но командные байты ЭВМ (известные так же как коды операций) ничего не значат, если у вас нет книги по программированию на ассемблере для Z-80, с помощью которой можно декодировать их. Если бы вы рассмотрели каждый байт, то поняли бы, что D1 = POP DE Е1 = POP HL D5 = PUSH DE Е5 = PUSH HL FD E9 = JP (IY) но это вряд ли прояснит, что здесь происходит, если вы не знаете ассемблера Z-80. Различные команды (POP DE и т.д.) являются мнемоникой, о которой мы говорили. Если вы знаете смысл этой мнемоники, вы можете оценить, как работает слово SWAP.
D1 = POP DE Перенести число из стека в регистр DE Е1 = POP HL Перенести число из стека в регистр HL D5 = PUSH DE Перенести число из регистра DE в стек E5 = PUSH HL Перенести число из регистра HL в стек FD E9 = JP (IY) Передать управление по адресу, лежащему в регистре IY Программисту, работающему на Форте, довольно легко понять команды PUSH и POP, так как "аппаратный стек", реализованный в Z-80 с помощью SP-регистра (указателя стека), является в большинстве версий Форта как раз тем самым стеком, который непосредственно использует Форт. Применены здесь два регистра общего назначения с именами DE и HL, а также команда записи чисел в стек, названная PUSH, и извлечения кода из стека, называемая POP. Машинная программа для SWAP просто говорит ЦП Z-80 убрать два числа из стека и записать их в два регистра ЦП, а затем положить их в стек в обратном порядке. Это показывает, насколько легко переслать любое число кодов в регистры микропроцессора и обратно, используя стек. Команда JP (IY) (которая может быть другой в вашем Форте, даже если у вас применена система Z-80) заставляет Z-80 передать управление по адресу, записанному в регистре IY. Это инструкция, названная нами NEXT в гл. 15. Инструкция NEXT используется для завершения исполнения Форт-программы в машинных кодах (примитива) и передачи управления внутреннему интерпретатору Форта. Коды операций для NEXT в вашем Форте могут быть другими, даже если вы используете Z-80. NEXT в действительности слово MMSFORTH и не является стандартным словом, но почти все версии Форта содержат его эквивалент. Чтобы проиллюстрировать, как одно и то же задание реализуется на разных микропроцессорах (так вы не будете разочарованы, если не имеете оборудования, которое мы обсуждаем), ниже представлены программы для SWAP в двух различных Фортах для 16-битовой IBM PC, базирующейся на микропроцессоре 8088. MMSFORTH 5A = POP DX 58 = POP AX 52 = PUSH DX 50 = PUSH AX AD = LODS AX, SI Загрузка в регистр AX содержимого регистра SI 93 = XCHG AX, BX Пересылка содержимого ВХ в AX и наоборот FF 27 = JMP (ВХ) Передача управления по адресу, лежащему в регистре ВХ MVPFORTH 5В = POP ВХ 58 = POP AX 53 = PUSH ВХ 50 = PUSH AX AD = LODS AX, SI Загрузка в регистр AX содержимого регистра SI AB D8 = MOV ВХ, AX Пересылка содержимого AX в регистр ВХ FF 27 = JMP (ВХ) Передача управления по адресу, лежащему в регистре ВХ
Вы можете видеть, что так же, как в версии MMSFORTH на Z-80, оба эти описания SWAP извлекают и укладывают коды из стека и в стек с использованием регистров. MMSFORTH применяет AX- и DX-регистры, a MVPFORTH - AX и BX. Следующие три байта в MMSFORTH и следующие четыре в MVPFORTH являются двумя версиями NEXT. Обе загружают содержимое регистра SI в ВХ-регистр и затем для продолжения работы осуществляют передачу управления по адресу, хранящемуся в ВХ., В обеих версиях Форта SI-регистр используется для хранения указателя инструкций. который мы в гл. 15 назвали IP. Существуют ли какие-то причины для различий между MMSFORTH и MVPFORTH? Версия MVPFORTH немного быстрее, в то время как NEXT MMSFORTH требует на один байт меньше памяти; различие тривиально. (Но быстродействие очень важно в определении слова NEXT, поскольку оно завершает все примитивы.) Версия SWAP MVPFORTH может точно так же использоваться в MMSFORTH, и наоборот, с точно таким же результатом. Существует обычно несколько различных путей реализации одной и той же функции в машинных кодах. Прежде чем создавать слово, которое выполняет машинную программу, давайте рассмотрим откомпилированную форму программы-примитива. Поле программы слова-примитива указывает на его поле параметров. Как мы видели в гл. 15, это придает примитиву следующую форму: Поле Имени Связи Программы Параметров Адрес NFA LFA CFA PFA Содержимое Имя Адрес PFA (Машинная программа NEXT) , +
Для того чтобы вы могли написать собственный примитив, вы должны найти машинную программу NEXT или ее эквивалент в вашей системе. Это можно сделать с помощью пропечатки некоторых примитивов, чтобы узнать, как они завершаются. Если вы располагаете Форт-ассемблером, то он, вероятно, снабжен словом, подобным NEXT, которое выполняет эту функцию. Теперь давайте опишем новую версию SWAP с именем MYSWAP. Начнем с формирования заголовка MYSWAP, а затем заменим содержимое CFA так, чтобы там лежал адрес поля параметров MYSWAP. Чтобы сформировать заголовок MYSWAP напишем CREATE MYSWAP HERE DUP 2- ! Его PFA определяется с помощью слова HERE и затем эта величина записывается в поле программы MYSWAP, которое занимает два байта перед PFA. (Эта методика может не работать для Форта, который записывает слова нетрадиционным способом.) Раз вы поместили адрес поля параметров MYSWAP в его CFA, компилируйте далее машинную программу просто байт за байтом (убедитесь, что вы работаете с шестнадцатеричной системой): D1 С, Е1 С, 05 С, Е5 С, F0 С, Е9 С, для MMSFORTH на Z-80 (или другие байты, используемые в описании SWAP в вашей системе).
Не забудьте включить машинную программу, которая осуществляет возврат к Форту. В MMSFORTH вы могли бы использовать слово NEXT вместо байтов FD и Е9. Вы можете, конечно, применить эквивалентное слово из вашей версии. Если MYSWAP делает то же, что и SWAP, то открыт путь для описания аналогичным способом остальных слов-примитивов. Если MYSWAP не работает (что маловероятно). распечатайте ваше описание, чтобы убедиться, что содержимое CFA указывает на PFA и что машинная программа соответствует откомпилированному тексту SWAP, и затем испытайте. описание еще раз. Если вы собираетесь описать более чем пару слов-примитивов, вы, возможно, захотите для формирования заголовков описать : CODEHEAD CREATE HERE DUP 2- ! ; Форма обращения CODEHEAD MYSWAP (машинная программа...NEXT) CODEHEAD работает во многом аналогично слову CODE из ASSEMBLER, описанному в следующем разделе.
Если вы намереваетесь написать много слов-примитивов, используя эту методику или ассемблер, имеется пара вопросов, которые вы должны решить. Первый: использует ли ваш Форт аппаратный указатель стека (указатель стека, с которым работает микропроцессор) для хранения адреса стека параметров? Большинство версий Форта используют. Это может быть определено путем пропечатки SWAP, DUP или OVER, чтобы выяснить, как они определены. Если они используют PUSH и (или) РОР- команды, как определено для вашего микропроцессора, тогда Форт-стек и аппаратный стек совпадают. Второй: какой регистр используется Фортом в качестве указателя инструкции (который мы в гл. 15 назвали IP)? Пропечатайте машинную программу для EXIT, чтобы выяснить, в какой регистр заносится содержимое стека возвратов. Важно сохранить содержимое этого регистра, если вы должны использовать его для своих целей, и принять меры для того, чтобы восстановить его исходное значение, когда машинная программа будет завершена. Указатель слов Форта (WP) может быть, вероятно, изменен вашей программой-примитивом, так как он, вернее всего, будет изменен сразу после NEXT, когда исполнение вашего слова будет завершено.
Полезно также знать, какой регистр используется для хранения WP, так как он будет всегда содержать CFA или PFA вашей программы-примитива, когда к ней произошло обращение. Заметьте, что существует много вариантов написания Форта, поэтому использование регистров и стеков должно быть известно для вашей системы, прежде чем вы сможете уверенно программировать слова-примитивы. К сожалению, некоторые поставщики Форта не сообщают вам об использовании внутренних регистров в документации. Таким образом, написание Форт-программ в машинных кодах содержит некоторое число досадных деталей, за которыми вы должны следить. Несмотря на это, машинные программы могут быть введены в Форт-программу весьма легко - вам нужно только внимательно следить за применением в Форте определенных регистров, особенно того, который используется в качестве указателя инструкции (IР). Если для вашей программы-примитива нужны все регистры, вы можете, конечно, запомнить значение IP в стеке (или в переменной) вплоть до момента занесения его в соответствующий регистр перед завершением работы вашей программы. Мы обсудим некоторые другие вопросы, которые требуют внимания, в следующем разделе. Даже с учетом этих досадных обстоятельств интерфейс между языком высокого уровня и машинной, программой намного проще реализовать в Форте, чем в любом другом языке.
Упражнения
Ответы для упражнений этой главы приведены для микропроцессоров Z-80 и 8088, но вы, вероятно, захотите выполнить их также на вашей собственной ЭВМ. если у вас другой ЦП. В этом случае проверьте результат путем сравнения вашего решения с пропечаткой поля параметров описания в вашем Форте там, где это возможно. 1. Предложите программы-примитивы и мнемонику для Z-80 н 8088 для стековых операндов. В этих упражнениях используйте только PUSH, POP и NEXT. a) DROP; б) DUP; в) OVER; г) ROT. 2. Версия F83 Лаксена и Перри имеет несколько нестандартных слов для работы со стеком, которые вы, возможно, захотите использовать. Одно из них, TUCK, действие которого заключается в "подсовывании" копии верхнего элемента стека под второй элемент сверху (n1 n2 - n2 n1 n2), является оператором, обратным по отношению к OVER.
Опишите TUCK в виде программы-примитива для Z-80 и 8088, используя методику, описанную для MYSWAP. 3. NIP является еще одним нестандартным словом F83. используемым для удаления второго элемента из стека (п1 п2 - п2). Опишите его для Z-80 и 8088. 4. В Z-80 команда EX [SP], HL меняет местами верхний элемент стека (содержимое адреса, на который указывает регистр SP) и содержимое регистра HL. Опишите ROT. Как может быть сделано более эффективным описанное выше SWAP (использовать меньшее число байтов или обеспечить исполнение за меньшее число циклов), если применить эту команду совместно с PUSH и POP? 5. -ROT является оператором, инверсным по отношению к ROT (или тем же, что ROT ROT), и заносит верхний элемент стека в третью позицию сверху (n1 n2 n3 - n3 n1 2). Опишите его для Z-80 и 8088. 6. Как может быть сделано более эффективным ваше описание DROP путем оперирования адресом в указателе стека, не используя POP?
Форт-ассемблеры
Теперь ясно, что машинные программы могут быть встроены в слова Форта методом, использованным при описании MYSWAP. Итак, в чем заключается задача ассемблера? Просто в том, чтобы сделать это более легким. Форт-ассемблер состоит из мнемоники, которая компилирует машинные команды и применение которой намного проще, чем компиляция байтов с помощью С,. Ассемблер может также включать в себя другие слова, которые не являются мнемокодами, а служат для организации циклов и условных переходов.
Но существуют только четыре слова ассемблера, которые специфированы стандартами: ASSEMBLER, имя словаря ассемблера (смотри гл. 14); CODE, которое открывает описание слова в ассемблере и является аналогом :; END- CODE, которое подобно ; завершает описание, и ;CODE, которое действует по аналогии с DOES>, позволяя использовать мнемонику ассемблера для определения поведения производных слов, полученных с помощью слов-описателей, на фазе исполнения (смотри гл. 10). Причина для ограниченности списка стандартных слов заключается в том, что коды операций и соответствующая им мнемоника варьируются от процессора к процессору.
Другие слова ассемблера, например позволяющие организовать циклы и ветвление программы, сильно варьируются от версии к версии и не стандартизованы. Это делает трудным дать полное и общее описание ассемблера. Мы опишем некоторые аспекты MMSFORTH ассемблеров Z-80 и 8088, но мы рассчитываем на вас в случае использования этого описания для других процессоров и версий Форта. В зависимости от версии Форт-ассемблер может быть либо полным (способным компилировать все коды операций данного микропроцессора), либо частичным (включающим только наиболее часто используемые функции). В последнем случае коды операций, не генерируемые ассемблером, могут быть откомпилированы непосредственно с помощью С, точно так же, как выше компилировалось слово MYSWAP (пример приведен ниже).
Форт-ассемблер описан как словарь с именем ASSEMBLER и, как и в случае любых других контекстных словарей; команда ASSEMBLER DEFINITIONS используется, чтобы добавить новые ассемблерные слова в словарь ASSEMBLER. Имея отдельный словарь ASSEMBLER, можно использовать в ассемблере любые имена (например, слова мнемоники и условных переходов), не боясь конфликтов со словами в Форте или другом словаре.
Словом, открывающим ассемблерное описание, является CODE. Оно формирует заголовок слова-примитива, делает словарь ASSEMBLER контекстным, записывает в CFA нового слова адрес его PFA и машинную программу, которая там должна храниться. Его описание очень похоже на слова, которые мы использовали для формирования заголовка MYSWAP. : CODE CREATE HERE DUP 2- ! [COMPILE] ASSEMBLER ; где [COMPILE] необходимо в случае, если в вашем Форте имя словаря является словом немедленного исполнения.
Вот пример описания MYSWAP с использованием MMSFORTH на процессоре 8088 IBM PC. Описание для любой версии Форта на микропроцессорах 8088 или 8086 будут сходными: CODE MYSWAP ( n1 n2 - n2 n1) DX POP AX POP DX PUSH AX PUSH NEXT END-CODE
Запомните, что вам нужно использовать что-то еще вместо NEXT или, может быть, NEXT будет частью вашего Форт-описания END-CODE.
Применение мнемоники здесь очевидно. Заметьте, если вы используете обычные ассемблеры, то при записи слов применяется типичная для Форта нотация. Вместо записи POP DX, для того чтобы перенести верхний код из стека в DX-регистр, Форт использует DX POP. Позднее вы увидите почему.
Если бы вы пропечатали описание MYSWAP, то вы бы увидели, что оно идентично SWAP. Вы уже знаете, что NEXT используется для передачи управления внутреннему интерпретатору. Тогда что же делает END- CODE? В некоторых ассемблерах END-CODE может выполнять функцию NEXT. В других, таких как MMSFORTH, единственная функция END-CODE - это сделать контекстным тот словарь, который им был до начала работы CODE. Стандарты требуют, чтобы END-CODE сделал находимым в словаре имя слова, созданного CODE- (Если при компиляции выявлена ошибка, то END-CODE этого не сделает, предотвращая тем самым узнавание ошибочного слова.) END-CODE осуществляет это обычно путем установления бита-метки в соответствующее состояние (смотри гл. 14), Некоторые версии Форта, такие как MVPFORTH, используют также ;С в качества синонима END-CODE.
В описании MYSWAP мнемоника скомпилировала соответствующий объектный код в поле параметров слова. Таким образом DX POP заносит код 5A в качестве первого байта в поле параметров. Вы можете сформировать тот же самый объектный код в MMSFORTH (и во многие другие версии Форта), если вы введете CREATE MYSWAP HERE DUP 2- ! ASSEMBLER DX POP AX POP DX PUSH AX PUSH NEXT FORTH Вы можете также использовать CREATE MYSWAP HERE DUP 2- ! ASSEMBLER DX POP AX POP 52 С, 50 С, NEXT FORTH или CODE MYSWAP DX POP AX POP 52 С, 50 С, NEXT END-CODE Теперь вы можете увидеть, как можно ввести объектный код в описание CODE, даже если у вас нет полного набора мнемоники.
Могут существовать и другие способы завершения описаний типа CODE. В MMSFORTH следующее описание MYSWAP является идентичным по своему поведению нашему первому описанию: CODE MYSWAP DX POP AX POP PSH2 END-CODE PSH2 эквивалентно следующему: DX PUSH AX PUSH NEXT
Если у вас нет PSH2, которое является удобным словом, так как часто нужно засылать содержимое этих двух регистров в стек, вы можете описать его: ASSEMBLER DEFINITIONS : PSH2 DX PUSH AX PUSH NEXT ; FORTH Это должно подсказать вам мысль, как можно добавлять к ассемблеру новую мнемонику и новые слова.
Давайте рассмотрим еще одно описание, которое представляет собой еще один пример и послужит расширению вашего опыта в этой области: CODE + AX POP DX POP DX AX ADD AX PUSH NEXT END-CODE
При операции + два числа извлекаются из стека и засылаются в АХ- и DX-регистры. Слово ADD складывает содержимое АХ- и DX-регистров (результат заносится в АХ). Наконец сумма из АХ засылается в стек. Имеется, конечно, еще много мнемонических кодов, которые мы здесь используем. Как мы сказали раньше, вам следует изучить книгу по программированию на ассемблере, чтобы вполне освоить Форт-ассемблер.
Действие слова ;CODE сопоставимо с DOES>. Подобно DOES>, ;CODE отмечает начало исполняемой части программы слова-описателя, но рабочая программа производного слова в этом случае будет написана в машинных кодах с использованием мнемоники ассемблера или С,. Таким образом, описания : FORTH-CARRAY CREATE 1+ ALLOT DOES> + ; и : CODE-CARRAY-CREATE 1+ ALLOT ;CODE HL POP DE HL ADD HL PUSH NEXT END-CODE эквивалентны в ассемблере для Z-80, но слова, сформированные CODE-CARRAY, будут исполняться намного быстрее. На IBM PC в MMSFORTH 100.000 итераций описания DOES> требуют 7,2 с, в то время как эквивалентные им описания ;CODE выполнят эту работы за 2,3 с.
В отличие от слова DOES>, которое засылает в стек PFA производного слова (и используется производными словами FORTH-CARRAY для вычисления адреса элемента массива), ;CODE требует, чтобы адрес лежал в ячейке, следующей за ним. Адрес PFA или жестко связанного с ним адреса содержится в регистре, который используется в качестве указателя слов (WP). Предшествующее описание CODE-CARRAY предполагает, что WP хранится в регистре DE, как это и есть в MMSFORTH для TRS-80.
В MMSFORTH для IBM PC WP содержится в регистре ВХ и указывает на CFA (на 2 меньше, чем PFA); описание имеет вид : 8088-CODE-CARRAY CREATE 1+ ALLOT ;CODE АХ POP ВХ АХ ADD 2 # АХ ADD AX PUSH NEXT END-CODE Вы должны будете определить, как найти и использовать WP в вашей собственной версии Форта.
Упражнения
1. Сформируйте CODE-описания для: a) DUP б) OVER в) ROT г) 2DUP д) TUCK e) NIP ж) -ROT, используя мнемонику Z-80 и 8088. 2. Слово MMSFORTH-ассемблера PSH эквивалентно PSH2, за исключением того, что оно засылает в стек только содержимое регистра AХ. Опишите PSH. 3. Опишите слово PSH3, которое должно работать как PSH2, но заносить в стек содержимое регистров ВХ, DX и АХ в указанном порядке. 4. Опишите слово ;С так. чтобы оно выполняло NEXT END-CODE 5. Опишите слово типа CODE с именем @REGS, которое заносит в стек 8088 содержимое регистров Dl. SI, SP, DX, СХ, ВХ и АХ в указанном порядке. Теперь опишите слово типа двоеточие.REGS, которое использует @REGS и отображает содержимое регистров микропроцессора 8088. 6. Мнемокод микропроцессора 8088 SUB вычитает содержимое одного регистра из содержимого другого. Если SUB заменит ADD а описании слова +. приведенном выше, содержимое регистра DX будет вычитаться из содержимого регистра АХ (результат останется в АХ). Опишите слово -. 7. Используя ;CODE, опишите следующие слова: a) ARRAY б) DARRAY в) CONSTANT г) 2CONSTANT
Как работает ассемблер
Нам следует разобраться с тем, как работает ассемблер, с целью знакомства с техникой программирования, а также и потому, что вы можете захотеть расширить возможности и список мнемокодов в вашем ассемблере. Проще всего описать слово ассемблера NEXT. На TRS-80 в MMSFORTH это описание выглядит как ASSEMBLER DEFINITIONS : NEXT ( --) FD С, Е9 С, ; FORTH в то время как на IBM PC ASSEMBLER DEFINITIONS : NEXT ( -) AD С, 93 С, FF С, 27 С, ; FORTH Вам следует понимать, как Это работает. Конечно, в вашей версии Форта это может быть и по-другому.
Мнемоника ассемблера немного сложнее, но прямолинейнее.
Как мы видели, мнемоника - это просто слова, которые записывают одну или более машинных команд в тело слова CODE. Некоторые мнемокоды работают сами по себе, другие требуют аргументов, таких как имена регистров, для того чтобы скомпилировать соответствующий машинный код. Описание мнемоники может быть простым или сложным, в зависимости от числа необходимых аргументов и от количества машинных команд, которые она должна скомпилировать. Команда RET (возвращение из подпрограммы) для 8088 является однобайтовой инструкцией, не требует аргументов и может быть описана простым мнемокодом. Она описывается как : RET ( -) C3 С, ; Но поскольку существует большее число мнемоник с однозначным соответствием между мнемокодом и однобайтовой инструкцией без каких-либо аргументов, для формирования такой мнемоники лучше иметь слово-описатель ; 0ARGMAKE CREATE С, DOES> С@ С, ; После этого можно сформировать таблицу мнемоники следующим образом: C3 0ARGMAKE RET CE 0ARGMAKE INTO 90 0ARGMAKE NOP CF 0ARGMAKE IRET 9C 0ARGMAKE PUSHF 9D 0ARGMAKE POPF и т. д.
Мнемоника ассемблера, требующая одного аргумента, немного сложнее. К счастью, в, конструкции машинных команд существует логика. Для микропроцессора 8088 большинство мнемокодов, которые используют в качестве аргумента номер только одного регистра, формируют машинную инструкцию, где номер регистра закодирован в младших трех разрядах. Таким образом, в двоичном представлении инструкция DX POP имеет вид 01011010, где младшие разряды 010 указывают на регистр DX. Инструкция POP может быть сформирована путем добавления номера регистра к значению 01011000 - или в шестнадцатеричном представлении к 58. (Если вы вспомните восьмеричное счисление, вы сможете понять, что регистры и инструкции могут быть очень удобно представлены в восьмеричном виде. DX POP будет соответствовать 132, где 13 указывает на POP, a 2 - на регистр DX). PUSH будет формироваться путем добавления номера регистра к 01010000 или 50 в шестнадцатеричном представлении. XCHG будет соответствовать сумме номера регистра и 10010000 или 90 в шестнадцатеричном виде.
Номера регистров можно определить через константы. Так, для микропроцессора 8088
0 CONSTANT AX 4 CONSTANT SP 1 CONSTANT CX 5 CONSTANT BP 2 CONSTANT DX 6 CONSTANT SI 3 CONSTANT BX 7 CONSTANT DI Теперь может быть сконструировано слово-описатель для одноаргументных мнемокодов: : 1ARGMAKE CREATE С, DOES> С@ + С, ; а мнемоника формируется в другой таблице: 58 1ARGMAKE POP 50 1ARGMAKE PUSH 90 1ARGMAKE XCHG 40 1ARGMAKE INC и т.д. Теперь, поскольку действие этих производных слов заключается в добавлении числа из стека к их базовому значению и поскольку номер регистра можно занести в стек с помощью констант, которые мы описали, вы можете использовать конструкции типа SI PUSH, чтобы добавить 6 к 50 и занести результирующее число 56, соответствующее машинной инструкции, в описании слова типа CODE.
Использование мнемоники, описанной только таким способом, даст очень ограниченный, хотя и применимый, поднабор инструкций 8088. Для получения более полного набора команд ассемблера вы должны позволить мнемонике иметь переменное число аргументов, генерировать две и более машинные команды, устанавливать определенные разряды в нужное состояние, чтобы сообщить, например, что содержимое регистра следует рассматривать как байт или как пару байтов. Наша задача не в том, чтобы предложить вам программу ассемблера для 8088, а в том чтобы изложить, как это можно сделать. Если у вас есть ассемблер для вашей ЭВМ, вы можете для лучшего понимания просмотреть его исходные тексты.
Микропроцессоры имеют много разных условных инструкций, работа которых зависит от значений флагов. Флаги - это двоичные разряды регистров, обычно называемых регистрами состояния. Значения разрядов в регистре состояния определяется различными действиями. Например, флаг может быть установлен р результате арифметического переполнения, при сравнении содержимого двух регистров (например, с помощью мнемокода СМР для 8088), если результат вычитания равен 0, или в результате каких-то других операции. Например, операция AX DX СМР установит флаг Z (нуль) в единичное состояние, если значения содержимого регистров АХ и DX равны.
В MMSFORTH для 8088 команда 4000 ~ Z JMPC передаст управление по адресу 4000, если значение флага Z не равно 0, т. е. если содержимое двух регистров идентично. Мнемоника, используемая для целей ветвления в разных Форт-ассемблерах, варьируется от версии к версии. В этом случае ~ Z JMPC эквивалентно стандартной мнемонике 8088 JNZ. ~ соответствует оператору "NOT", a Z - это оператор, который говорит: "обратите внимание на флаг Z". То есть если значение флага Z не равно О, выполните ветвление. Существует много способов реализации в Форте операций с флагами, вам следует заглянуть в документацию для вашей версии.
Большинство Форт-ассемблеров поддерживают условные переходы и циклы со структурой, почти идентичной словам IF,.. ELSE... THEN, BEGIN...UNTIL и BEGIN...WHILE...REPEAT в словаре FORTH, Фактически имена слов в контекстном словаре ассемблера могут быть теми же самыми (как в примерах MMSFORTH, приведенных ниже); возможность использовать слова с идентичными именами и со сходными, но разными функциями, является главной причиной введения различных контекстных словарей. Подобно своим эквивалентам в словаре FORTH, между словами условных переходов и циклов в ассемблере также помещаются слова, но уже имеющие мнемонику ассемблера. В ассемблере такие слова, как BEGIN...UNTIL вызывают передачи управления в процессе исполнения команд в рамках отдельного слова. Но в отличие их от эквивалентов в словаре FORTH передачи управления происходят не на базе кодов, содержащихся в стеке, а на основе значений флагов. Управляющий флаг должен быть указан в качестве аргумента перед соответствующим словом. Рассмотрим пример слова, которое складывает два числа из стека, если они равны, в противном случае - вычитает. Вы знакомы со всеми этими мнемокодами из предшествующих примеров.
CODE = IF + ELSE - ( n1 n2 - n3) BX POP AX POP ( Извлекаем числа из стека) BX AX СМР ( Сравниваем их) Z IF ( Если Z равен 1. т. е. АХ = BX...) BX AX ADD ( Складываем числа) ELSE ( В противном случае ...) BX AX SUB ( Вычитаем числа) THEN ( И в любом случае ) AX PUSH ( Заносим результат е стек) NEXT END-CODE
Важным моментом здесь является то, что IF выполняет свою работу, основываясь на значении когда в регистре состояния Z, заданном оператором Z. Эквивалентная функция была бы выполнена "стандартным" Форт-описанием: : =IF+ELSE- ( n1 n2 -- n3) 2DUP = IF + ELSE - THEN ;
Хотя последнее описание намного короче и легче читается, в MMSFORTH на IBM PC при исполнении 100 000 раз оно требует 19 с, в то время как ассемблерная реализация занимает только 5 с. Если быстродействие важно, имеет смысл описать критические по времени слова, используя ассемблер.
Вот пример ассемблерного цикла BEGIN...UNTIL. Здесь перемножаются два числа в стеке путем последовательного их сложения.
CODE *NEW ( n1 n2 -- n3) 0 # BX MOV ( Обнуление ВХ, ВХ - счетчик) 0 # CX MOV ( Обнуление CX, CX - аккумулятор) AX POP DX POP ( Извлекаем числа из стека) BEGIN ( Начало бесконечного цикла) BХ INC ( Добавляем 1 к ВХ) DX CX ADD ( Сложение DX и CX) AX ВХ СМР ( Установка Z=1 при равенстве) Z UNTIL ( Продолжение цикла, пока не будет AX = ВХ) CX PUSH (Занесение результата в стек) NEXT END-CODE
Теперь вы понимаете, как работает *NEW. Это не самый быстрый способ умножения чисел, действие его на 10% медленнее, чем применения слова *, но это хороший пример использования циклов в ассемблере. Вы узнаете больше о применении циклов и передач управления из упражнений.
Упражнения
1. Микропроцессор Z-80, подобно 8088, характеризует номера 16- разрядных регистров 3-разрядными кодами, которые обычно представляются восьмеричным числом в середине кода команды. Коды регистров двойной длины: ВС - 0, DE - 2, HL - 4 и AF-6. Таким образом, DE POP будет соответствовать коду 11010001 или 321 в восьмеричном виде, в то время как HL POP даст 11100001 или 341 в восьмеричной форме. Опишите константы для номеров регистров. Теперь опишите слово, аналогичное 1ARGMAKE. которое может быть использовано для описания POP и PUSH. Производные слова с кодом регистра а качестве аргумента должны компилировать правильные машинные коды.
Вам нужно умножить величины констант на некоторое число, прежде чем добавить их к "базовой" величине мнемокода. 2. Опишите слово типа CODE с именем IF-DROP, которое удаляет два числа из стека, если они равны между собой. 3. Опишите 2* на ассемблере (не используйте циклов). 4. Опишите 10* на ассемблере (используйте циклы).
Обращение к другим программам, написанным в машинных кодах
Очень часто возникает желание обратиться к подпрограмме, написанной в машинных кодах, из описаний типа CODE. Такими подпрограммами могут быть программы, загруженные вами в память с помощью ассемблера, или это могут быть программы, хранящиеся в ПЗУ, или драйверы печати/дисплея, загруженные в память другими программами. По сравнению с известными языками обращение к машинной подпрограмме в Форте весьма простое.
Большинство микропроцессоров и Форт-ассемблеров имеют команду CALL для передачи управления по указанному адресу. RET - команда, которая возвращает управление из подпрограммы по адресу, следующему сразу после команды обращения. Адрес, куда должен быть осуществлен воз врат, обычно укладывается в стек командой CALL и извлекается оттуда командой RET (так работают микропроцессоры 8088 и Z-80). Предположим, что мы хотим обратиться к помеченной подпрограмме из описания CODE- В частности, мы хотим, чтобы подпрограмма извлекла два кода из стека (до CALL) и занесла их в регистры DX и АХ (в 8088). Ниже представлена реализация этой программы:
CREATE POPEM ASSEMBLER CX POP ( Занести в CX из стека адрес возврата, занесенный туда командой CALL) DX POP AX POP ( Извлечь из стека нужные значения) CX PUSH ( Вернуть в стек адрес возврата) RET ( Вернуться к программе, откуда произошел вызов CALL) Теперь можно обратиться к POPEM CODE MYSWAP ( n1 n2 - n2 n1) РОРEМ CALL DX PUSH AX PUSH NEXT END-CODE или использовать POPEM в ряде других описаний: CODE 2DROP ( n1-n2 -> ) POPEM CALL NEXT END-CODE CODE 2DUP ( n1 n2 - n1 n2 n1 n2) РОРЕМ CALL AX PUSH DX PUSH AX PUSH DX PUSH NEXT END-CODE CODE OVER ( n1 n2 - n1 n2 n1) РОРЕМ CALL AX PUSH DX PUSH AX PUSH NEXT END-CODE
Это, однако, плохой пример. Хотя мы обратились к подпрограмме в нескольких местах, что стоили времени и не сэкономило достаточно памяти. Инструкции CALL и RET, а также команды для запоминания и извлечения адресов возврата требуют времени и машинных команд. Чтобы сделать написание подпрограммы на ассемблере привлекательным, подпрограмма должна быть длиннее той надстройки, которая необходима для ее вызова, и даже если используется меньше памяти, какое-то время будет потеряно. Здесь обычно находится компромисс между временем исполнения и экономией памяти.
MMSFORTH имеет слово, которое вы, возможно, захотите описать в вашей системе (оно часть словаря FORTH, а не ASSEMBLER). : LABEL CREATE [COMPILE] ASSEMBLER : LABEL формирует заголовок для подпрограммы в точности так, как это делал оператор РОРЕМ из нашего примера. Таким образом, можно описать РОРЕМ как LABEL РОРЕМ СХ POP DX POP AX POP CX PUSH RET
Удобно, не так ли? Слово LABEL позволяет описывать подпрограммы, написанные на ассемблере; оно позволяет использовать одно и то же слово во многих макроассемблерах.
Вот немного более эффективное применение подпрограммы. Имя ** часто используется для слов, обозначающих возведение числа в степень. То есть 5 4 ** возведет число 5 в четвертую степень, выдав 625. Это может быть сделано путем умножения второго сверху числа, хранящегося в стеке, на само себя. Число таких умножений определяется числом на вершине стека. Мы можем описать ** так, чтобы нужное число раз вызывалась подпрограмма MULTI. Сначала мы опишем слово, осуществляющее вызов, а затем подпрограмму, хотя на практике подпрограмма пишется сначала.
CODE ** ( n1 n2 -- n3) 1 # ВХ MOV ( Заносим 1 в регистр-счетчик ВХ) AX POP ( Заносим показатель степени в АХ) DX POP ( Заносим число в регистр DX) DX СХ MOV ( Вводим число в регистр произведения) BEGIN ( Запускаем бесконечный цикл) AХ ВХ СМР ( Выполнено нужное число циклов?) WHILE ( Если нет...) ВХ INC ( Даем приращение содержимому счетчика) MULTI CALL ( Умножаем произведение на число) REPEAT ( Продолжаем цикл, пока не будет выполнено условие) СХ PUSH ( Заносим результат в стек) NEXT END-CODE
ВХ - регистр-счетчик. Когда в результате приращений его значение достигнет величины показателя степени, работа завершается. СХ - регистр произведения, содержит результат MULTI и в исходном состоянии должен быть сделан равным числу-аргументу. Обращение к MULTI производится столько раз, сколько нужно в рамках цикла BEGIN...WHILE...REPEAT, при этом каждый раз результат выдается в регистр произведения. MULTI можно описать как
LABEL MULTI AХ PUSH ВХ PUSH DX PUSH ( Сохраняем регистры в стеке) 0 # ВХ MOV ( Сбрасываем счетчик в 0) DX AX MOV ( AX используется для сравнения показателя со счетчиком) СХ DX MOV ( DX используется для сложения с регистром произведения) 0 # СХ MOV ( Сброс регистра произведения в 0 ) BEGIN ( Запуск бесконечного цикла) ВХ INC ( Даем приращение счетчику MULTI) DX СХ ADD ( Складываем DX и СХ) AХ ВХ СМР ( Сравниваем АХ и ВХ) Z UNTIL ( Пока АХ = ВХ) DX POP ВХ POP AX POP ( Восстановление регистров) RET (Возврат вызвавшей программе) END-CODE
Вы должны понять, как работает MULTI, путем сравнения с *NEW, описанным ранее. Но есть одно важное различие. Мы вынуждены были записать содержимое регистров AX, BX и DX в стек, поскольку они были нужны в **, а также в MULTI. Содержимое регистров было затем восстановлено из стека до RET в MULTI. Обычно регистры необходимы для различных целей в подпрограмме, они же используются и в основной программе, одним из выходов из положения является сохранение их в стеке. Фактически в большинстве языков и в программировании на "нормальном" ассемблере главная функция стека - запоминание величин на время выполнения каких-то операций.
Можно также описать машинные программы без заголовка и затем несколько раз к ним обращаться при описании других слов. Вот пример описания слов SWAP, 2DUP, OVER и 2DROP с подпрограммами без заголовков и с использованием стека возвратов для передачи адреса подпрограммы при компиляции: HERE >R ( Засылка адреса подпрограммы в стек возвратов) ASSEMBLER ( Делаем ассемблер контекстным словарем) СХ POP DX POP AX POP CX PUSH RET ( Текст подпрограммы,эквивалентной РОРЕМ) CODE SWAP R@ CALL DX PUSH AX PUSH NEXT END-CODE CODE 2DUP R@ CALL AX PUSH DX PUSH AX PUSH DX PUSH NEXT END-CODE CODE OVER R@ CALL AX PUSH DX PUSH DX PUSH NEXT END-CODE CODE 2DROP R> CALL NEXT END-CODE
Слово HERE выдает адрес, где начинается подпрограмма, он кладется в стек возвратов, откуда этот адрес может многократно извлекаться. Каждое описание типа CODE использует адрес из стека возвратов в качестве аргумента для CALL. Адрес удаляется из стека возвратов командой R> в описании 2DROP, после которого он более не нужен. Единственным преимуществом беззаголовочных подпрограмм по сравнению с LABEL (кроме экономии нескольких байтов) является то, что эти подпрограммы не могут быть использованы неправильно, так как не могут быть найдены в словаре. Недостаток такого метода заключается в том, что это делает программу трудночитаемой. Вы должны также помнить, что применение подпрограмм усложняет отладку программ на ассемблере.
Конечно, не важно, если вы не можете обратиться к подпрограмме, которую не вы создали. Если же вы используете Форт в рамках операционной системы, в вашем распоряжении много подпрограмм, таких как драйверы печатающего устройства или интерфейса RS-232, графические программы, процедуры для работы с диском и т.д., к которым можно обратиться из слов типа CODE. Если ваша ЭВМ имеет версию Бейсика в ПЗУ, она включает в себя подпрограммы арифметики с плавающей точкой. Вы можете воспользоваться ими, если выяснить, как к ним обращаться и что они делают со стеком и регистрами. Существует много книг, которые описывают резидентные программы в наиболее популярных микроЭВМ. Однако необходима осторожность, так как вы можете не знать все, что делает данная подпрограмма. Вам нужно убедиться, что любые регистры, которые содержат информацию, полезную для Форта, и которые будут использованы в подпрограмме (указатели, например), сохраняются в стеке и восстанавливаются после обращения к программе. Вы должны знать, что делает программа со стеком и указателем стека, так как вы не хотите терять информацию при обращении. Вы должны также быть уверены, что подпрограмма не пытается занести что-то в область памяти, занятую словарем. Вы возможно, сможете в результате расследования и определенных проб и ошибок выяснить некоторые вещи, но этот процесс потребует вашего терпения.
Если у вас есть дисассемблер, - это упростит задачу. (Дисассемблер просматривает программу в машинных кодах и транслирует ее в мнемоническую форму, облегчая понимание программы.)
Программы в машинных кодах могут быть получены из других источников, таких как статьи в журнале и подпрограммы, встроенные в Бейсик-программы. Если у вас есть исходный текст такой программы на ассемблере, обычно проще заставить ее работать в Форте, используя Форт-ассемблер. Если это не практично, вы можете скомпилировать машинную программу в слово, описанное с помощью LABEL, используя С,. Вы должны быть уверены, однако, что программа перемещаема - т.е. она не содержит каких-либо абсолютных адресов передач управления или вызовов, поскольку адрес передачи управления почти наверняка изменится после того, как вы встроете программу в слово. Все передачи управления и вызовы должны быть относительными, т.е. заданными величиной смещения к адресу позиции, откуда производится вызов.
Но существуют исключения. Некоторые версии Форта позволяют перемещать верхнюю часть словаря Форта из области больших адресов в начальную часть памяти (смотри карту памяти в гл. 14), освобождая место для большого числа машинных программ, которые созданы для работы в верхней части памяти. Если это так, вы можете поместить машинную программу в массив и перемещать ее с помощью CMOVE в верхнюю область памяти, используя адрес обращения, предусмотренный в оригинальной версии программы. Мы использовали таким способом на TRS-80 очень сложный драйвер координатографа. Это снова потребует экспериментов.
Упражнения
1. Часто полезно сохранить регистры в стеке на время выполнения машинной программы, так что вы сможете просмотреть их содержимое при отладке программы после того, как она выполнена. Используйте LABEL для описания SAVESTACK, чтобы заносить в стек содержимое регистров АХ, ВХ, СХ и DX 8088. Исполнение SAVESTACK CALL в про грамме будет выполнять тахой перенос. Будьте осторожны, не допускайте путаницу с адресом возврата! 2.
В верхней части памяти вашей ЭВМ с адресом FF00 есть программа для управления координатографом. Она рисует арифметические символы и требует, чтобы в регистр СХ было занесено значение высоты символа в сотых долях дюйма, а в регистр DX - ASCII-код символа. Опишите слова с именем PLOTEMIT, для которого высота символа должна лежать во второй сверху позиции стека, а ASCII-код - на вершине и которое обращается к драйверу координатографа. 3. У вас имеется 16-канальный аналого-цифровой преобразователь, связанный с вашей микроЭВМ. Он опрашивается машинной программой по адресу FFOO- Номер канала может быть передан в программу через регистр АХ, и программа возвращает результат для данного канала в милливольтах через тот же регистр. Опишите слово GETDATA, которое воспринимает номер канала из стека и туда же кладет результат в милливольтах. То есть если в канале 8 напряжение равно 528 мВ, 8 GETDATA запишет в стек число 528. 4. Программа из упражнения 3 изменяет величины в регистрах SI и ВХ, и вы должны быть уверены, что они не изменились при завершении исполнения GETDATA. Чтобы решить эту проблему, переопределите GETDATA.
Выводы
Форт - быстродействующий язык, даже если не использовать ассемблер. А ассемблер труднее применить, чем Форт, и он работает только на одном типе процессора. Главная проблема использования ассемблера в Форте заключается не в том, чтобы вставить все, что можно, в CODE- слова, а в том, чтобы применять разумную мнемонику. Когда эффективен ассемблер? При выполнении двух условий: если вы хотите, чтобы программа работала быстрее, или когда вы обнаружили что-то, что вы не можете сделать в Форте (последнее случается редко). В обоих случаях вы захотите использовать ассемблер в минимальном объеме. Вы добьетесь этого, правильно факторизируя Форт-программу. Те небольшие части, которые используются большую часть времени, так как они исполняются снова и снова, могут быть оформлены в виде коротких code-слов. На ассемблере могут быть запрограммированы такие критические функции, как обслуживание аналого-цифрового преобразователя или координатографа, которые не поддерживаются фортом.
Каждое слово Форта должно быть коротким и простым, это правило относится и к CODE-словам.
Цикл - редактор-ассемблер-редактор-связей-загрузка-исполнение-отладка, который необходим для любого ассемблера, исключается в форте. Форт-ассемблер позволяет компилировать и тестировать машинную программу небольшими частями, так же как и обычную Форт-программу. В результате программирование оказывается более производительным, а программы - лучше организованными и более эффективными. Время разработки программы в Форте составляет лишь долю того, что требуется для случая чисто ассемблера.
Мы начали эту книгу, сказав, что Форт позволит нам применить все доступные особенности вашей ЭВМ, использовать все быстродействие, на которое она способна, и сделать это более легко, чем в любом другом языке. Разумно закончить книгу описанием Форт-ассемблера, поскольку именно комбинация применения ассемблера и "нормального" Форта обеспечивает полную эффективность. Форт может быть использован на разных уровнях: простейший - интерактивный калькулятор для коротких программ без долговременного их хранения, наиболее сложный уровень - применение Форт-слов высокого уровня в комбинации с ассемблером для создания комплексных программ, таких как контроллеры процессов в реальном масштабе времени, системы управления базами данных, экспертные системы и даже другие языки программирования.
Стек
Основная задача состояла в первом знакомстве с языком Форт. Но для того, чтобы пользоваться этим языком практически, мы должны вернуться немного назад и снова, но уже более внимательно, рассмотреть то, с чем мы кратко ознакомились в предыдущей главе. Мы приносим вам извинения за неизбежный повтор некоторых уже известных вопросов, но повторение способствует лучшему пониманию. Первый объект, на который мы должны посмотреть более внимательно, это стек.Хотя мы уже говорили, что компьютеры применяются не только для арифметической и математической обработки данных, но и во многих других областях, все равно работа производится с числами в той или иной форме. Форт выполняет манипуляции с числами главным образом в стеке. Как мы уже видели, стек -- это одно из необычных и мощных средств языка Форт. Поначалу стек может показаться вам странным, однако пользоваться стеком очень просто, если вы поймете, что это такое и как с ним можно работать. И нельзя овладеть Фортом, не овладев сначала стеком. Стек -- это динамическая область памяти для чисел, которая постоянно используется языком Форт. В самом использовании стека нет ничего особенно удивительного, поскольку многие языки также им пользуются (правда, незаметно для нас). Исключительно необычным делает Форт то, что в нем стек всегда доступен для вас как с клавиатуры, так и из программы. Все числа, вводимые вами с клавиатуры или поступающие с магнитного диска, фактически попадают в стек. И большая часть чисел, которые передаются из одного слова в другое, также проходят через стек. Большая часть выгод, присущих языку форт, также связана с постоянным использованием стека. (Этот стек обычно называют стеком пользователя, стеком параметров или стеком данных. В действительности Форт использует еще один стек, который называется стеком возвратов и применяется для специальных целей, о нем вы узнаете позже в другой главе.) В этой главе мы объясним, что такое стек, как в нем реализуются арифметические операции, как он используется в Форте, а также рассмотрим слова для перестановки чисел в стеке с целью упрощения решения сложных задач.
Память, числа, символьная информация
Вы можете работать с языком Форт, даже очень мало понимая, как работает сам компьютер, но это будет вам постоянно мешать. Чтобы понимать, что в нем происходит, вы должны по крайней мере понимать, как работает память ЭВМ и как в ней хранятся числа. Это не так уж сложно, как многие думают, а с помощью языка Форт еще проще. Не пропускайте эту главу, даже если у вас уже есть опыт работы с ЭВМ. Можно, если хотите, бегло просмотреть более простые вопросы. Обратите, однако, внимание на возможности Форта, касающиеся его обращения с системами счисления и операциями в памяти, которые являются его привлекательными сторонами. Возможно, что у вас возникнет также желание узнать функции слов BASE и DECIMAL.Еще об арифметических операциях
Мы уже не раз говорили о том, что компьютеры используются не только для операций с числами, но и во многих других областях, однако, что бы они ни делали, они имеют дело с числами. Эта большая глава представляет собой что-то вроде попурри на Тему чисел. Однако если в предыдущих и большинстве следующих глав для любой серьезной работы по программированию был нужен весь материал, то некоторые части этой главы потребуются вам только в том случае, если вы будете заниматься "перемалыванием" чисел, например числовыми базами данных. Как минимум, вы должны изучить данное введение и разделы "Операторы для работы с небольшими числами", "Некоторые проблемы операции деления", "Операции с величинами и знаками чисел". Если вы работаете в стандарте Форт83, вам нужно также прочитать раздел "Деление с округлением,. деление с отрицательными числами". Если вы будете работать с очень большими числами и дробями, - вам нужно изучить разделы "Почему используются целые числа?", "Масштабирование чисел", "Числа двойной длины" и "Совместное применение чисел одинарной и двойной длины". А если вас интересует работа с числами с плавающей запятой, вы должны прочитать последний раздел. Если вы совершенно устали от арифметики, переходите к гл. 5 или даже гл. 6; для их понимания вам достаточно того, что вы уже узнали, но потом вам все равно придется вернуться, а потому наш совет - не пропускайте материал этой главы. Наконец, чтобы считать себя всесторонне образованным программистом на языке Форт. вам по требуется узнать все, что изложено в этой главе.К настоящему времени вы изучили четыре арифметические операции: сложение, вычитание, умножение и деление и соответствующие слова Форта +, -, * и /. Нам остается мало что добавить, кроме как упомянуть о потенциально возможной проблеме арифметического переполнения. Предположим, вы складываете числа 36000 и 37000. Результат должен равняться 73000; т.е. быть больше, чем 16- разрядное число, которое может быть в стеке.
На IBM PC на выполнение 1 миллиона операций 2/ MMSFORTH расходует 22 с, а на миллион операций 2 / 80 с. Поскольку действия с числами 1 и 2 встречаются очень часто, особенно при повторяющихся операциях, использование этих слов может значительно увеличить скорость работы программ. Может быть, это и мелочь, однако это хороший пример того, как Форт оптимизирует быстродействие.
Некоторые проблемы операции деления
Предположим, что вы хотите умножить число 20000 на 5 и разделить произведение на 2, при этом должно получиться 50000. Попробуйте это сделать, введя 20000 5 * 2 / U. 42 и то, что вы увидите, будет наверняка неправильно. При умножении происходит переполнение и заведомо неверный результат умножения точно делится на 2. Но эту задачу можно решить иначе: 20000 2 / 5 * ведь не всегда же вы сможете узнать, будут ли расположены большие и малые числа в правильной поcледовательности. Слово */ разрешает эту проблему, запоминая промежуточный результат в виде 32-разрядного числа, а не 16-разрядного, как обычно, допуская значение произведения до 4271406735. Таким образом устраняется возможность переполнения.
Еще одна проблема, возникающая при делении, - это проблема остатка. При целочисленных операциях сложения, вычитания и умножения результат представляет собой другое целое число. Для деления это не так. Например, если 3 поделить на 2, должно получиться 1.5 = 1 + 5/10. Нам нужно каким-либо образом определить остаток от деления (который иногда называется модулем деления). Остаток находится с помощью слова MOD. Если вы введете 3 2 MOD . то на экране получите 1. 25 7 MOD . дает в результате 4. Другими словами, слово MOD выдает остаток от деления двух чисел, или модуль. Есть еще два других слова, которые дают в результате остаток. Одно из них, /MOD, дает остаток от деления двух чисел и частное, которое помещается поверх остатка. Таким образом, 5 3 /MOD . . дает в результате пару чисел 1 и 2, в то время как 25 7 /MOD . . выдает в результате 3 4. Слово */MOD связано с обеими операциями */ и MOD. 332 */MOD . .
приводит к результату 4 1. Второе и третье число в стеке (в данном случае 3 и 3) перемножаются, образуя 32- разрядный результат, чтобы избежать переполнения, затем делится на число, находящееся на вершине стека, остаток от деления остается в стеке, поверх него на вершине стека находится частное. Назначение слова */MOD такое же, как и */, т.е. избежать переполнения во время операции умножения.
Деление с округлением, деление с отрицательными числами
В школе вас учили, что, если при делении двух чисел возникает остаток, результат нужно округлить до ближайшего целого в сторону уменьшения (вниз), т.е. 3/2 дает в частном 1 и в остатке 1. Ну а что делать, если либо делимое, либо делитель отрицательные? По интуиции вы, возможно, считаете, что, например, при делении -6/4 частное будет -1, а какой будет остаток? Вы, если хотите, можете умножить частное на делитель и сложить с остатком, чтобы получить делимое. Таким образом, в данном случае остаток будет равен -2. Потому что -2 + -1 х 4 равно -6. Именно так устроено деление в Форт-79. Для подтверждения проверим на Форт-79: -6 4 / . при этом получается -1, в результате операций -6 4 MOD . получается -2 и после -6 4 /MOD . . получается -1 -2. Для стандарта Форт-79 можно сформулировать правила "интуитивного" деления: 1) если либо делитель, либо делимое, но не одновременно отрицательные, то частное также отрицательное, 2) частное независимо от знака округляется в сторону, ближайшую к 0; 3) остаток принимает знак делимого, или второго числа в стеке.
Пользуясь этими правилами, мы всегда получаем результаты, приведенные в последних примерах. Эти правила справедливы для большинства версий Форта, за исключением тех, которые основаны на стандарте Форт-83. Точное значение результата операции -6/4 равно -1.5. Согласно школьным правилам надо округлить это число в сторону уменьшения, и мы получили бы -2 (-2 меньше, чем -1). Это так называемое деление с округлением по нижней границе (по "полу"). Его суть в том, что за частное от деления принимается ближайшее меньшее значение.
Частное всегда округляется до нижней границы. А как определить остаток? Вы снова можете умножить частное на делитель и добавить остаток, чтобы получить исходное число. Частное равно -2, делитель 4, их произведение равно -8. Чтобы получить -6 после прибавления к этому произведению остатка, он должен быть равен 2. Сформулируем правила для деления с округлением по нижней границе. 1) если либо делитель, либо делимое, но не оба сразу отрицательные, то частное также отрицательное (точно так же, как для "интуитивного" деления); 2) частное независимо от его знака округляется в сторону ближайшего меньшего числа; 3) остаток от деления принимает знак делителя (в противоположность "интуитивному" делению).
В Форт-83 применяется деление с округлением по нижней границе, что является полной противоположностью Форт-79, в связи с чем нужно быть внимательным при переносе программ из одного стандарта в другой. Хотя большинство машинных языков программирования работают как Форт-79 (за исключением АПЛ), с математической точки зрения более корректно округление по нижней границе, т.е. независимо от знака в сторону нижней границы. Тем не менее многие считают, что это противоречит здравому смыслу. Если вас это смущает, может быть, вам помогут несколько примеров, приведенных ниже. Обычное деление на 0 приводит к бесконечному результату, т.е. к разрыву в нуле. Это может привести к сложностям в графике и других применениях, например для робототехники, где необходимо обеспечить плавный переход от положительных к отрицательным числам. Деление с округлением по нижней границе в связи с этим предпочтительнее, но нужно иметь в виду, что оно занимает времени немного больше.
Обычное деление Деление с округлением по нижней ("интуитивное") границе 4 2 / 2 ok 4 2 / . 2 Ok 3 2 / 1 ok 3 2 / . 1 ok 2 2 / 1 ok 2 2 / . 1 ok 1 2 / 0 ok 1 2 / . 0 ok 0 2 / 0 ok 0 2 / . 0 ok -1 2 / 0 ok -1 2 / .-1 ok -2 2 / -1 ok -2 2 / .-1 ok -3 2 / -1 ok -3 2 / .-2 ok -4 2 / -2 ok -4 2 / ,-2 ok
В связи с необходимостью преобразования программ, написанных на Форт-79, в стандарт Форт-83 нужно иметь правила или алгоритм преобразования результата обычного деления в результат деления с округлением по нижней границе. Проще всего представить этот алгоритм, определив два слова на языке Форт. На Форт-79 слово : FL/ /MOD SWAP IF DUP 0 < IF 1- THEN THEN ; будет выдавать частное с округлением по нижней границе при делении чисел в соответствии со стандартом Форт-79- Вам еще не приходилось обращаться с некоторыми словами, использованными в данном определении, но их нетрудно понять. Выражение /MOD SWAP выдает частное и остаток и переставляет остаток на вершину. Если слово IF обнаруживает положительное число, т.е. остаток не равен 0, исполняются слова, которые следуют за IF до второго оператора THEN. В этом случае частное дублируется на вершине стека и сравнивается с нулем с помощью оператора
Освоив стековые манипуляции в гл. 2, вы можете заметить, что частное и остаток деления с округлением по нижней границе можно найти по аналогии со словом /MOD с помощью : FL/MOD 2DUP FLMOD ROT ROT FL/ ;
Следует подчеркнуть, что данные определения работают очень медленно по сравнению с теми определениями на языке ассемблера, которые введены в Форт-83. И ими нужно пользоваться только в случае крайней необходимости.
Упражнения
1. Проделайте в уме следующие примеры, используя компьютер только для проверки ответов. Найдите остаток и частное при "обычном" делении и делении с округлением по нижней границе: (а) 10/2 (б) 0/2 (в) 11/3 (г) -11/3 (д) 11/-3 (е) -11/-3 (ж) -10/2 (з) 10/-2 (и) -10/-2 (к) -10/0 2. Имеется текст из нескольких сотен литер, расположенный в строчках по 50 символов в строке. Напишите слово для вычисления номера строки, в которой находится 112-я литера. (Не забудьте, что вы работаете в целочисленной арифметике.) 3. Используя слово MOD, определите слово для вычисления количества символов, предшествующих 112-му символу в той строке, где он находится. 4.
Используйте идеи из упражнений 2 и 3 и напишите слово для определения номера строки и номера в строке любого символа в тексте из строчек фиксированной длины. При вызове слова в стеке должны находиться номер символа и длина строки, Приемы, которые вы применили в упражнениях 2-4, пригодятся впоследствии, когда вам потребуется определить местонахождение данных, хранящихся в блоках форта, а именно эти приемы помогут вам найти номер блока, в котором находится конкретный фрагмент данных и число байтов смещения внутри этого блока. 5. Напишите определение слова MOD (под именем NEWMOD) для Форт79, используя стандарт Форт-83- В этом процессе вам нужно будет дать определение деления / в Форт-79. 6. Найдите значение выражения 2000*100/30. Проделайте это двумя способами, избегая в обоих случаях переполнения. В первом используйте */, в другом - /MOD- Теперь проделайте то же, чтобы в результате выдавалось значение частного и остатка. Можете ли вы сделать это также двумя способами? Дальше мы узнаем, как использовать остаток, который получается со словом MOD для выполнения арифметических операций с имитацией плавающей запятой.
Операции с величинами и знаками чисел
Каждое из слов МАХ и MIN сравнивают два числа и оставляют в стеке соответственно большее. или меньшее из них. Так, например, 7 3 МАХ оставляет в стеке 7, в то время как 7 3 MIN возвращает в стек 3. Имеется много применений этих слов, некоторые из них мы увидим в упражнениях. Одно из наиболее частых применений - для ограничения диапазона чисел, поступающих входе. Предположим, что вы хотите ограничить диапазон чисел значениями от 5 до 25. Это может быть сделано следующим словом: : LIMIT-RANGE 5 МАХ 25 MIN :
Если встречается число больше, 25, то в стек помещается число 25, в то же время, когда число меньше 5, в стек помещается 5. При определении слов FL/ и FLMOD вы уже видели, как можно сравнить числа, и использовали для этого конструкцию IF...THEN, и вам, вероятно, нетрудно представить, что МАХ и MIN можно определить таким же способом.
Однако такое определение будет очень медленным. Многие программисты стремятся чаще использовать конструкцию IF...THEN, избегая применения слов МАХ и MIN. Но это их ошибка, поскольку МАХ и MIN позволяют значительно ускорить исполнение программы.
Слова ABS и NEGATE воздействуют на знак чисел. (Со словом NEGATE вы встречались в гл. 3.) Слово ABS возвращает в стек абсолютную величину числа. Так, например, -5 ABS возвращает в стек 5, а 5 ABS выдает в стек то же самое число 5.
В свою очередь, слово NEGATE всегда изменяет знак числа. Т.е. -5 NEGATE положит в стек 5, в то время как 5 NEGATE вернет в стек -5. Более медленная версия слова ABS, основанная на применении NEGATE и конструкции IF...THEN, приводится ниже: : ABS DUP 0 < IF NEGATE THEN ; Покажем применение этих операторов на нескольких упражнениях.
Упражнения
1. Определите слово, которое будет печатать наибольшее из трех верхних чисел в стеке. 2. Определите слово, которое будет печатать наименьшее из трех верхних чисел в стеке, оставляя в стеке исходные числа. 3. Определите слово, которое будет выдавать в стек 1, если любое из трех верхних чисел больше 5 (используйте оператор >). 4. Определите слово, которое будет возвращать 1. если все три верхних числа в стеке больше 5 (также используйте один из операторов сравнения), 5. Опишите слово, которое будет возвращать 1, если число на вершине стека больше, чем два следующих, находящихся ниже. 6. Определите слово, которое будет выражать разность температур, два значения которых находятся в стеке, как положительное число, независимо от знака температуры. 7. Определите слово, которое будет находить наибольшую абсолютную величину двух чисел, независимо от того, положительные они или отрицательные. 8. Определите слово, которое будет выдавать абсолютное значение того из двух чисел, которое ближе к нулю, независимо от знака чисел. 9. Используйте слово NEGATE для определения слова, действующего противоположно слову ABS, т.е. возвращающего число, равное по абсолютному значению исходному числу, но всегда отрицательное или нулевое. 10.
Определите слово OTHER-QUAD, которое переводит координаты точки (х.у) в прямоугольной системе координат п противоположный квадрант, сохраняя абсолютное значение х и у. Это значит, что нужно перевести точку из верхнего левого угла в правый нижний, из нижнего левого - в верхний правый и т.д. Например, -23 5 OTHER-QUAD должно в результате выдать 23 -5. 11. Определите слово NEWNEGATE с функцией NEGATE, используя только число и оператор *.
Определение математических функций
Одной из приятных возможностей языка Форт является то, что он позволяет определять математические функции, расширяя язык для математических приложений. В качестве очень простого примера вы уже встречались с определением слова для вычисления квадрата числа: : SQUARE DUP * ; Для-обозначения названий функций удобно, хотя это не общепринято, пользоваться алгебраической записью, заменяя пробелы точками. Например, определение слова для вычисления выражение а^2+b^2 может быть : А2.+.В2 DUP * SWAP DUP * + ; или для выражения а (а + b) : А(А.+.В) (b a - aa+ab) DUP ROT + * ; Для расчета значения сложных выражений лучше всего разбить их на максимально возможное число простых выражений. При этом будет проще следить за состоянием стека, давая определения отдельным словам, и вообще, определения слов должны быть как можно короче и быстрее. Предположим, например, что b лежит в стеке вторым, число а находится на вершине стека и вы хотите найти значение выражения a^2+ab. Можно определить слово: : А2.+.АВ DUP DUP * ROT * + ; которое сначала вычисляет значение а^2, потом ab и затем складывает оба произведения. С другой стороны, выражение может быть разложено на множители а(а + Ь), а это выражение, как мы только что видели, может быть вычислено с помощью : А(А.+.B) DUP ROT + * ; - Очевидно, что второй вариант проще, короче и более быстродействующий.
Упражнения
1. Объем пирамиды вычисляется по формуле Ah/2, где А - площадь основания пирамиды, h -высота пирамиды. Определите слово PYRVOL (объем пирамиды) для вычисления значения функции с округлением результата до ближайшего целого числа. 2.
Определите слово F-> С для пересчета градусов Фаренгейта в градусы Цельсия по формуле С = 5(F - 32)/9. Значения входных и выходных величин должны быть округлены до целых значений. Можете ли вы предложить способ получения значений с точностью до десятых долей градуса? Для этого применяется так называемое масштабирование, о котором мы вскоре расскажем- 3. Напишите слова для вычисления следующих функций, применяя, где возможно, разложение на множители. Дайте вашим словам подходящие названия. (а) а/с + Ь/с (б) а/с + b/с2 (в)a^2+2ab+b^2 (г) За^2+6аb+Зb^2 (д) a^4+4a^3b+6a^2b^2+4ab^3+b^4
Почему используются целые числа?
Из того, что вы уже знаете, ясно, что Форт сталкивается с определенными проблемами при работе с очень большими числами и числами с плавающей запятой (такими, например, как 23.497 или -0.96). Некоторые считают, что использования 16-разрядных и 32-разрядных целых чисел достаточно практически для всех применений микро- и миниЭВМ и работа с числами с плавающей запятой приводит к расточению времени и памяти ЭВМ. Другие, включая авторов, кто использует ЭВМ для научных и технических задач, считают, что, хотя в большинстве применений можно обойтись целыми числами,- числа с плавающей запятой очень нужны для практического применения языка Форт в некоторых классах задач. Несмотря на отсутствие требований в стандартах Форт-79 и Форт-83, в некоторых версиях языка поддерживается в той или иной мере арифметика с числами с плавающей запятой. Мы рассмотрим более детально математические основы применения чисел с плавающей запятой в конце этой главы. Но для этого, а также для того, чтобы понять,как извлечь максимум возможностей из использования целых чисел, мы должны сначала рассмотреть, что представляют собой числа с плавающей запятой.
Замечания о числах с плавающей запятой
Рассмотрим число 1298. Оно может быть представлено следующим образом: 1000 + 200 +90+8.
Число 1298,325 можно представить так : 1000 + 200 + 90 + 8 + 0.3 + 0.02 + 0.005.
Если в числе имеется десятичная запятая, это означает, что часть его, которая стоит после десятичной запятой, представляет сумму дробей, каждая из которых может быть выражена в виде отношения, которое в десятичной системе имеет в знаменателе 10, 100 и т.д.
Таким образом можно представить 0.325 как 3/10 +2/100+5/1000 или, приведя к общему знаменателю, как 300/1000 + 20/1000 + 5/1000 = 325/1000. Таким образом, любое число с плавающей запятой может быть представлено как целое число плюс отношение двух других целых чисел. Так как числа двойной длины используют 32 разряда, то наибольшее представимое целое число равно 4271406735, а с помощью целых чисел могут быть представлены числа с плавающей запятой не меньше этого числа с погрешностью, не превышающей 1/1000000000. Например, число 4271406735.123456789 можно представить как 4271406735 +123456789/1000000000. Это совсем немалый диапазон и малая погрешность, если бы только имелась возможность следить за значением целой и дробной части. Форт оставляет решать эту задачу программисту. С другой стороны, числа с плавающей запятой называются так потому, что правила арифметики и программы для калькуляторов или компьютеров отслеживают положение десятичной запятой, позволяя ей "плавать", где нужно, т.е., положение плавающей запятой в числе автоматически отслеживается машиной.
Число 1 1/2 может быть представлено точно как 1.5. С другой стороны, рассмотрим число 1 1/3, оно не может быть представлено как 1.3, 1.333 и т.д. в десятичной системе, сколько бы ни было разрядов (хотя, если работать в троичной системе счисления, то 1 1/3 можно точно представить числом 1.1). Другими словами, 1 1/3 - это точное'число, в то время как представление его десятичной дробью является приближенным. Приближение может быть достаточно хорошим, но не абсолютно точным. Если числа известны точно и если они могут быть представлены как целое число плюс отношение -двух целых чисел, то лучше всего было бы все числа представлять через целые. Но рассмотрим реальный мир. Точным микрометром можно измерить толщину с погрешностью в лучшем случае 1/1000 см. Для инженера почти все измерения дают. приближенный результат. Большинство констант, которые применяются в науке и технике, нельзя выразить через целые числа или отношением целых чисел.
Например, нельзя точно представить число Пи. Таким образом, во многих областях точное представление целых чисел и отношений не имеет практического смысла. То же справедливо для представления чисел с плавающей запятой. Имеется и еще одна практическая проблема. В технике часто необходимо использовать числа, которые больше или меньше, чем представимые 16- или 32-разрядными числами. Например, в больших ЭВМ применяются 64-разрядные числа. Очень большие и очень малые числа можно представить числами с плавающей запятой с указанием порядка (иногда их называют действительными числами с показательной или "научной" формой записи), используя не больше 32 разрядов. Они выражаются как действительное число с плавающей запятой плюс число 10, возведенное в какую-либо степень. Рассмотрим табл. 4.1.
Степень Число Степень Число 1 (т.е. 10^1) 10 -1 (т.е. 1/10 2 100 -2 1/100 3 1000 -3 1/1000 4 1000 -4 1/10000 Таблица 4.1. Степени числа 10
Мы видели, что 0.395 может быть выражено как 395/1000, поэтому 0.395 = 395/1000 = 395 х 10^ 3. Аналогично мы можем выразить 3950 как 3950 = 0.395 х 104.
Так как многие принтеры не могут напечатать 10^-3 или 10^4, как показано в тексте, в компьютерной нотации опускается число 10 и указывается только показатель степени, перед которым стоит буква Е (от exponent - показатель степени), т.е. 0.395 будет представлено как 3.95Е-3, а 3950 - как 0.395Е4.
Чтобы преобразовать число из показательной формы представления в обычную форму с плавающей запятой, нужно перенести десятичную запятую влево на значение показателя степени, если он положительный (добавляя, если нужно, нули), или вправо, если показатель степени отрицательный. Теперь мы можем выразить очень большие и очень малые числа. Имея только 32 разряда, можно отображать числа больше 1Е-38, т.е. 100000000000000000000000000000000000000, и меньше 1Е-37, или 0.00000000000000000000000000000000000001.
Число, которое предшествует числу 10 в степени, может быть 6-ти разрядным (десятичным). Таким образом, диапазон представления чисел с плавающей запятой 1Е-38 999999Е38.
Необходимо ввести здесь некоторую терминологию. Целое число, предшествующее числу 10 в степени, называется мантиссой (если вы знакомы с логарифмами, то, наверное, знаете почему; если же не знаете, то это не так уж важно), а показатель степени называется порядком.
Арифметические операции с числами с плавающей запятой в показательной форме
Арифметические действия с числами, представленными в показательной форме, выполняются по простым правилам. Вам совершенно не нужно их понимать, для того чтобы понять все за и против математики целых чисел и чисел с плавающей запятой, но правила простые, и сейчас,самое время , их изучить.
Чтобы сложить два числа или вычесть одно число из другого, нужно привести их к одному порядку, а затем сложить или вычесть их мантиссы. Например, 5Е-3 - 2E-2 = 5E-3 - 20Е-3 = -15Е-3= -1.5Е-2 или 12Е10 + 21Е11= 12Е10 +210Е10 = 222Е10 =22,2Е11 Чтобы перемножить два числа, нужно перемножить их мантиссы и сложить порядки. Например, 5Е-3 * 2Е-2 = 10Е-5 = 1Е-4 или 12Е-10 * 21Е11 = 252Е21 = 2.52Е23.
Чтобы разделить два числа, нужно поделить мантиссу делимого на мантиссу делителя и вычесть порядок делителя из порядка делимого. Например, 5Е-3/2Е-2 = 2.5Е-1 или 12Е10/25Е11 = 0.48Е-1 = 0.048.
Представление чисел в форме с плавающей запятой и порядком упрощает арифметические операции с большими и малыми числами.
Точность и погрешность
Нам осталось рассмотреть еще несколько вопросов, прежде чем вернуться к аргументам за и против использования чисел с плавающей запятой или целых чисел. Точность числа с плавающей за пятой выражается числом знаков в записи мантиссы, или, говоря проще, числом десятичных разрядов числа. Так, число 1.23Е5 имеет точность 3 знака, 112.35Е23 - 5 знаков, а 1230 - 4. Но точность и погрешность - это не одно и то же. Если вы пользуетесь измерителем, снабженным нониусом, то вы сможете считать размер с точностью до 1/10 миллиметра. С другой стороны, если вы измеряете размер горошины, то оценить его с точностью до 0.1 миллиметра вы не сможете, так как горошина не идеально круглая.
Вы можете измерить диаметр 4. 5 мм, выражая такой записью, что точность его 2 знака. Вы можете поддаться искушению и добавить еще два нуля, написав 4.500, т.е. с точностью 4 знака. Но погрешность измерения, вероятно, будет ближе к миллиметру или 1 знаку.
Если вы записываете число как 4.5 или 4.500, вы в лучшем случае обманываете себя или того, кто просматривает ваши данные, в худшем случае подтасовываете погрешность измерения. Самый простой способ подорвать доверие к лабораторному отчету о физических или химических измерениях - это указать большое число знаков погрешности, путая ее с точностью. В чем здесь суть? В том, что в реальном мире точность числа лучше 4-6 знаков требуется крайне редко. Одна из причин, почему числа с плавающей запятой так нравятся ученым и инженерам, состоит в том, что из них ясно видна точность числа по количеству приведенных в записи знаков. Запись 1.23Е5 говорит о том, что, хотя измеренное значение приблизительно равно 123000, его точность +-1000. С другой стороны, если число представляется как целое 123000, то большинство представителей технических наук будут считать, что оно" известно с точностью б знаков. Числа с плавающей запятой и указанием порядка лучше всего удовлетворяют потребности науки и техники.
Существует одна важная сфера, где требуется гораздо большая точность. Это денежные расчеты. Бухгалтер никогда не округлит миллион долларов до трех или четырех знаков. В расчетах должны указываться все доллары и центы. И редко кому потребуется величина порядка числа 10 или 11 даже для выражения бюджета крупных государств (хотя иногда крупные компании испытывают трудности в программировании расчетов, так как им приходится оперировать значениями больше триллиона долларов'). Точно так же редко потребуется и порядок меньше -3 (это соответствовало бы десятым долям цента или пенса или одной тысячной иены). Поэтому для бухгалтерских расчетов 32- разрядные числа с плавающей запятой обычно не.подходят. Они не обеспечивают требуемой точности и в то же время имеют избыточный диапазон представляемых чисел.
Программы бухгалтерских расчетов лучше всего составлять с применением целых чисел, но предусматривая две позиции для отображения сотых долей.
Числа с плавающей запятой: за и против
Многие преимущества чисел с плавающей запятой вам уже должны быть понятны. Основные из них: 1) при вычислениях с числами с плавающей запятой обеспечивается отслеживание положения десятичной запятой и величины числа практически без участия программиста; 2) представление чисел с плавающей запятой с указанием порядка позволяет работать как с очень большими, так и очень малыми числами; 3) арифметика с плавающей запятой позволяет задавать точность или погрешность с достаточным количеством знаков; 4) представление чисел с плавающей запятой распространено в мире инженеров и ученых, не склонных доверять представлению результатов измерений в реальном мире с помощью целых чисел. Хотя последняя причина в первую очередь психологическая, она имеет значение для принятия языка большинством практиков.
Многие (но не все) преимущества целочисленной арифметики вам также должны быть понятны: 1) целые числа обеспечивают большую точность при заданном объеме памяти, для 16-разрядных чисел диапазон -32768 - 32767 или - 2147483648 до 2147483647 для 32-разрядных; 2) с помощью целых чисел можно представить как собственно целые, так и действительные числа, имеющие целую и дробную части, например 12.55 = 12 + 55/100; 3) с помощью целых 32-разрядных чисел представляются целые числа и дроби в диапазоне от 1Е-10 и более чем 1Е+10, при этом с точностью на 4 знака больше, чем с помощью 32-разрядных чисел с плавающей запятой.
Но наиболее часто выдвигаемый аргумент в пользу целых чисел - это более высокая скорости работы. Арифметические действия с плавающей запятой производятся компьютером по приведенным здесь правилам. Из них вытекает, что при арифметических действиях с плавающей запятой компьютер должен выполнить большее количество операций сложения, вычитания, умножения или деления, следовательно, для этого требуется в несколько раз больше времени.
Поскольку арифметические действия часто выполняются в циклах много раз, скорость исполнения выдвигается на первое место. Самый существенный аргумент против включения чисел с плавающей запятой в стандарт состоит в том, что в таком случае приносится в жертву скорость работы. Но насколько серьезен этот аргумент? Посмотрим, как работает Фортран или Бейсик (и многие другие языки). Именно с учетом всех за и против, которые мы обсуждаем, другие языки дают программисту свободу принимать решение, обращаться ли с числами как с целыми или как с числами с плавающей запятой. В Форте это также возможно, правда, не во всех версиях.
Возможность пользоваться числами с плавающей запятой должна быть решающим критерием при выборе подходящей версии Форта. И решение этого вопроса возлагается на программиста.
Для некоторых микрокомпьютеров вопрос об использовании чисел с плавающей запятой становится еще более критичным. Как мы увидим дальше, в некоторых компьютерах доступно так называемое сопроцессорное арифметическое устройство, которое практически представляет собой еще один компьютер, предназначенный для выполнения операций над числами с плавающей запятой, причем с очень высоким быстродействием, повышенной точностью и в расширенном диапазоне чисел. Наиболее известным примером может служить сопроцессор фирмы Intel типа 8087, который может быть использован в IBM PC и совместимых с ней ЭВМ. Микросхема 8087 представляет числа 80-разрядами, при этом мантисса числа может иметь до 18 знаков, а порядок от -4600 до 4600 (в буквальном смысле можно представлять числа больше, чем оценка числа электронов во вселенной). По причине того, что 8087 использует стек так же, как Форт, ее стек можно частично использовать для операций с плавающей запятой. Поэтому Форт с сопроцессором 8087 может выполнять некоторые операции с плавающей запятой с потрясающими точностью и диапазоном и значительно быстрее, чем ЦПУ с 16-разрядными числами. Учитывая, что ЭВМ с сопроцессором 8087 могут также работать с памятью объемом до 1 Мбайта, аргументы в пользу применения целых чисел для экономии времени и памяти, а также получения большей точности становятся слишком слабыми.
Имеется надежда, что в будущем появятся версии языка Форт, в стандарт которых будут включены операторы для работы с числами с плавающей запятой. Ну а как быть, если ваша версия Форта не может работать в арифметике с плавающей запятой или вы не можете пожертвовать скоростью работы? Как можно обращаться с числами, которые большинство из нас записывают как действительные числа (с десятичной запятой)? Как вы будете обращаться с дробями? Решать эти вопросы вам поможет масштабирование.
Масштабирование чисел
Слово "масштабирование" применяется в том же смысле, что и в технике, когда делается чертеж, который отображает объект в увеличенном виде. Предположим, что вам нужно рассмотреть синьку часового винта. Если винт имеет длину всего 1 мм, его нужно изобразить в каком-то масштабе, например 100:1. Это значит, что чертеж должен быть выполнен в масштабе, в 100 раз превышающем истинные размеры. Подобным образом карта может быть масштабирована в обратную сторону, скажем, 1 см вместо 1 км, т.е. 1:100000.
Проще всего показать масштабирование на примере. Предположим, вы хотите сложить в столбик доходы, выраженные в долларах, хотя они сейчас выражены в долларах и центах. В целочисленной арифметике на Форте вы, к примеру, не можете сложить 22.98 и 35.53. Но вы можете произвести сложение, если выразите доходы в центах, а не в долларах. Другими словами, вы можете изменить масштаб входных данных и внутреннего представления операций с денежными единицами в 100 раз. В таком случае вы введете 2298 3533 + . и увидите в результате 5831, что, как вы помните, представляет собой 58 долларов 31 цент. Однако в таком виде результат выглядит не очень красиво. Вы можете выразить результат в долларах и центах с помощью следующего слова: $CENTS 100 /MOD U. " Долларов и" . ." центов" ;
Слово $CENTS производит деление числа на 100, округляет результат в сторону нижней границы и печатает его как число долларов. Остаток от деления выражает центы. (В гл. 5 мы узнаем метод, который называется выводом по шаблону, с помощью которого десятичная запятая может быть помещена в числе в любом месте.) Очень удобно для использования при масштабировании чисел слово /МОD) (его иногда называют масштабным оператором), фактически оно позволяет вам "пересчитать" число.
Число на входе выглядит не очень изящно, поскольку в него нельзя еще ввести десятичную запятую. Как мы вскоре увидим, при вводе числа двойной длины необходимо указывать положение десятичной точки, на основании этого Форт распознает числа двойной длины. Но при расчетах Форт игнорирует положение десятичной точки. Как вы видели на примере $CENTS, программист не всегда должен заботиться о масштабировании, эта работа может быть оставлена компьютеру. И не обязательно, чтобы масштабный коэффициент был кратен 10. Вот, например, слово, которое берет из стека число дюймов и переводит их в футы и дюймы: : .FTTN (дюймы -->) 12 /MOD . ." футов и" . ." дюймов" ; Очевидно, можно сделать преобразование в обратную сторону. Определим слово : TOINCHES (футы дюймы - дюймы) SWAP 12 * + ;
Если ввести 10 6 TOINCHES, то мы увидим в стеке длину в дюймах 126.
Умножение на дроби
Предположим, что вы хотите умножить число на 3/4 с помощью калькулятора. Вы можете в действительности умножить на 0.75, зная, что 0.75 равно 3/4. Так как вы не сможете умножить на 0.75 с помощью целых чисел, вы можете на Форте вычислить дробное выражение, умножая на 3/4. Однако позвольте, просто так нельзя разделить 3 на 4, а потом умножить на результат, так как 3 4 / дает 0. Поэтому нужно сначала умножить число на 3, а затем произвести деление, т.е. вам нужно применить оператор */ (он, как и /MOD, называется масштабным оператором). Таким образом, выражение 3 4 / 100 * дает неверный результат 0, в то время как 100 34*/ дает правильный результат 75. Напомним, что во избежание переполнения */ сохраняет промежуточный результат умножения в виде 32-разрядного числа.
Отсюда вытекает общее правило: чтобы обеспечить надлежащую точность смешанной операции, всегда выполняйте операции, которые дают в результате большое число, прежде чем делать деление, поскольку промежуточный результат получается без переполнения. Это еще одна причина необходимости разложения выражения на множители перед его вычислением.
Например, выражение а/х + b/x + с/ х может дать менее точный результат, чем выражение (а+b+с)/х. Чтобы убедиться в справедливости этого утверждения, проверьте это с какими-либо числами.
Оператор */ очень удобен для умножения на дробь с постоянным знаменателем. Например, вы можете определить слово PERCENT (процент) : : PERCENT 100 */ ; так, что выражение 130 50 PERCENT положит в стек число 65 (50% от 130). В некоторых случаях вы хотели бы иметь результат смешанной операции */ с большей точностью, чем получается с по мощью обычного умножения с последующим делением.Наилучший метод избежать потери точности - это убедиться, что перед операцией */ входные операнды уже увеличены с помощью масштабирования для получения желаемой точности. Например, если вы вычисляете 3/4 от 123, то в плавающей арифметике вы получите 92.25, а в целочисленной - 92. Следует изменить масштаб чисел, чтобы операция и */ производилась не с числом 123, а с числом 12300. Это еще один пример того, что, применяя целочисленную арифметику, программист должен заранее все продумать. Но есть еще один способ не потерять точность, если вы по какой-либо причине не хотите предварительным масштабированием увеличить числа: применить оператор */MOD (подобно */, он называется масштабным оператором). Например, выражение 123 3 4 /MOD выдает на вершину стека частное 92 и вторым сверху - остаток 1. Вы можете использовать остаток, если не хотите потерять точность, применяя выражение 123 3 4 */MOD SWAP 100 4 */ которое положит в стек 92 25. Это значит, что на вершине стека находится число, которое представляет собой два разряда после десятичной запятой в представлении с плавающей запятой. Если вы продумаете тщательно программу, то сможете с помощью целых чисел выразить эквивалент числа с плавающей запятой, а, пользуясь форматным выводом, как описано в гл.5, вы сможете даже напечатать результат с десятичной запятой (например, 92.25).
Приближенное представление чисел с помощью рациональных дробей
Предположим, что вы хотите найти длину периметра круга, которая равна диаметру, умноженному на число Пи.
Как можно выразить иррациональное число Пи, т.е. число, которое не может быть выражено в виде отношения двух целых чисел? Для приближенного вычисления можно использовать : PI* 31416 10000 "/ , Если диаметр равен 10 см, то выражение 10 PI* дает 31 - величину периметра с погрешностью 1 см. Очевидно, вам может потребоваться большая точность, для чего вы можете изменить масштаб диаметра на входе, например задать его в миллиметрах. Выражение 100 PI* даст величину 314,т.е. длину периметра 314 мм. С помощью следующего слова вы можете найти площадь круга (которая равна квадрату радиуса, помноженному на Пи или четвертой части квадрата диаметра, умноженной на Пи) : : AREA DUP 4 */ PI* ; Что делает PI* ? Оно просто умножает число на отношение 31416/10000, которое равно 3.1415. 31416/10000 представляет собой апроксимацию иррационального числа Пи рациональной дробью, правда, не очень хорошей апроксимацией, потому что для невысокой точности всего нескольких знаков используются большие числа. Дробно-рациональная аппроксимация применяется для большого разнообразия иррациональных чисел и физических констант, включая гораздо лучшую апроксимацию числа Пи. В табл. 4.2 приведено несколько примеров. Заметим, что все эти апроксимации дают большую точность, чем можно получить, применяя числа одинарной длины.
Константа Отношение Ошибка Пи 355/113 8.5Е-8 2 19601/13860 1.5Е-9 3 18817/10864 1.1Е-9 е 28667/10564 5.5Е-9 с(скорость света) 24559/8192 1.6Е-9
Таблица 4.2. Дробно-рациональная аппроксимация некоторых общеупотребительных констант.
Округление
Если вы делите 26 на 5, то, применяя числа с плавающей запятой, вы получите 5.2, а при делении 29 на 5 - 5.8. Однако при целочисленном делении в обоих случаях мы получим 5. Другими словами, целочисленное деление производит округление с уменьшением (усечение). Если вы будете складывать результаты деления нескольких чисел, то ошибка округления будет накапливаться и результат будет занижен. Можно при округлении следовать школьному правилу: если на первом месте после запятой находится цифра 5 или больше, то округление производится в большую сторону, если меньше 5- то в меньшую.
Программа для этого выглядит не очень сложно. Мы приводим программу деления с округлением, которая делает все, что нужно: : R/ SWAP OVER /MOD SWAP 2 * ROT / + ; Испытайте ее: 26 5 R/ даст в результате 5, а 29 5 R/ даст в результате 6. Вот что здесь происходит. Делитель был помещен на дно стека, затем скопирован на вершину, чтобы осуществить деление с остатком. Остаток был скопирован, а затем поделен на исходный делитель. Это второе частное должно быть равно 1, если первое частное должно быть округлено, причем в этом случае оно должно быть сложено с первым частным. Иначе второе частное должно быть равно 0 и первое частное должно быть оставлено без изменения. Почему эта программа работает? В терминологии деления с плавающей запятой число следует округлить, если дробная часть частного больше или равна 0.5, т.е. дробная часть должна быть умножена на 2, если она больше или равна 1. В целой форме дробная часть удваивается, конечно, удвоенный остаток помещается поверх делителя. Но бывают случаи, когда вы должны сделать округление после деления; это случается, когда вы хотите, чтобы сумма последовательности частных имела ошибку в сторону увеличения или когда вы скорее склонны переоценить результат, чем недооценить его. Чтобы произвести округление, вы просто добавляете 1 к нормальному частному от деления двух целых чисел, если остаток не равен 0. Вот слово.которое это делает на Форт-83: : RUP/ /MOD SWAP 0= 0= + : Слово 0= будет более подробно рассмотрено чуть позже, вкратце оно возвращает 1, если на вершине стека 0, или 0 - в противном случае. Зная это, вы должны понимать, как работает RUP/.
Упражнения
1. Сделайте следующие упражнения в уме: (а) 5Е5х5Е10 (б) 5Е5х5Е-10 (в) 5Е5х5Е0 (г) 5Е-1х5Е-5 (д) 5Е5/5Е10 (е) 5Е5/5Е-5 (ж) 5Е2 + 2Е3 (э) 5Е2 + 5Е-3 (и) 5Е2 - 2Е-3 (к) 5Е2 - 5Е-3 2. Показательная форма записи может применяться и для целых чисел, но не часто. Мантисса должна быть сохранена второй в стеке, а показатель степени - на вершине стека. Таким образом, для 5Е10 в стеке находятся числа 5 10.
Напишите слово ЕХР*, которое должно перемножать два числа, представленных в стеке описанным способом. Например, если в стеке находятся числа 5 10 6 15, то после умножения в стеке будут находиться числа 30 25. Попробуйте это слово на примерах упражнения 1(а-г). 3. Используя идеи упражнения 2, определите слово ЕХР/для деления чисел, представленных в показательной форме записи. Это упражнение не такое простое, как вам кажется, поэтому загляните в ответ в приложении Д, даже если вы чувствуете, что сделали правильно. 4. Определите слово ТОМ (в_метры), которое складывает длину, выраженную в километрах, с длиной, выраженной в метрах. Слово предполагает, что в стеке содержится величина в километрах и величина в метрах в указанном порядке. Результат, выраженный в миллиметрах, должен оставаться в стеке. 5. Определите слово ТОСМММ (в_см_и_мм), которое берет с вершины стека число, представляющее миллиметры, и преобразует его в сантиметры, располагая результат вторым сверху, поверх которого должно располагаться число миллиметров, т.е. число 12.345 должно быть преобразовано в 1234 и 5. 6. Определите слово ТОКМ (в_км), которое выполняет преобразование обратное тому, что делает слово ТОМ из упражнения 4, т.е. берет значение в миллиметрах, а возвращает в стек значение в километрах и метрах. 7. Определите слово ТОРТ(в_футы) по аналогии с упражнением 4, но для преобразования числа миль и футов в футы. В одной законодательной миле содержится 5280 футов. 8.Определите слово ТОМILS(в_мили) для преобразования футов в мили и футы. метры, принимая коэффициент преобразования 1 фут - 0,305 м. 10. Пользуясь словами и идеями упражнений 6-9, напишите программу TOMETRIC (неметрические) для преобразования миль и футов в километры и сантиметры, используя расположение данных такое же, как в предыдущих упражнениях. 11. Пересчет градусов Цельсия в градусы Фаренгейта производится по формуле F = 32 + 9С/5, Определите слово С->Р для преобразования градусов Цельсия в градусы Фаренгейта, при этом температура по Фаренгейту должна выражаться с погрешностью 0.1 градуса.
Позаботьтесь о сохранении погрешности. 12. Напишите слово для определения 1/10 периметра окружности, диаметр которой равен точно 1/2 см. Результат должен быть получен с погрешностью 1 нанометр (1 нм - 1/10000 см). Используйте дробно-рациональную апроксимацию. 13. Определите слово IN->FT для пересчета дюймов в футы с округлением в сторону ближайшего фута, т.е. 13 дюймов нужно округлить до 1 фута, 20 дюймов - до 2 футов.
Числа двойной длины
Мы уже неоднократно упоминали о числах двойной длины. К примеру, при использовании операции */ промежуточный результат запоминается как число двойной длины. Числа двойной длины записываются в два раза большим числом разрядов, чем числа одинарной длины, т.е. для хранения одного числа в стеке используется 32 бита, или 4 байта памяти. Они используются так же, как и числа одинарной длины, за исключением того, что для арифметических операций с ними применяются другие, хотя и похожие слова. Диапазон представления чисел двойной длины со знаком составляет от -2 147 483 648 до 2 147 483 647, диапазон чисел без знака от 0 до 4 294 967 295. Иногда числа двойной длины называют числами двойной точности или еще 32-разрядные числа в стандарте Форта называют двойными числами. Нам кажется, что термин "числа двойной точности" должен распространяться только на числа с плавающей запятой. 32-разрядные целые числа имеют в два раза большую длину, а величина их, конечно, больше не в два раза, что не определяет их точность. Фактически число "двойной длины" означает только то, что число в двоичной форме занимает в два раза больше разрядов, чем число одинарной длины.
Числа двойной длины представляют собой расширение стандарта языка Форт и других версий, т.е. они не присущи стандартному Форту после его загрузки в компьютер. Расширения языка должны быть загружены самостоятельно. В версии MMSFORTH представление числа двойной длины производится словом DBL-LEN# (двойная^длина). Для других версий Форта нужно обратиться к руководству, чтобы узнать, как это делается, или убедиться, что уже предусмотрено.
Если при загрузке Форта это не производится, то нужно осуществить загрузку, после этого мы попробуем сделать несколько примеров. Для того чтобы их понять, вам нужно знать, что слово D. (произносится как дэ-точка) печатает число двойной длины, оно является эквивалентом слова, (точка). Попробуем ввести 1.23 D. и мы увидим на экране число 123. А теперь введите 123. D. и вы увидите тот же самый результат: 123 Ok Теперь введите 1234567890. D. и будет выведено 1234567690 ok
Но если ввести 1234567890 (без десятичной точки в конце),то произойдет переполнение, потому что число было чересчур велико. Не слишком ли это смущает вас? Для чего в числе была нужна точка? И почему число 1234567890. проходит, а число 1234567890 - нет? Ответ простой. Десятичная точка сообщает Форту, что число нужно рассматривать как число двойной длины, т. е- оно должно быть записано в 32 разряда, или 4 байта. При этом совершенно безразлично, где находится десятичная точка, поскольку она в дальнейшем не используется (вы помните, что числа двойной длины используются в целочисленной арифметике). (Примечание: десятичная точка игнорируется не всегда. В MMSFORTH и других версиях Положение десятичной точки запоминается для того, чтобы произвести масштабирование чисел, как мы вскоре увидим.) И поэтому если десятичная точка отсутствует, то она и не обнаруживается при печати числа. Теперь вам стало понятно, что происходит в наших примерах. Очевидно, что число 1234567890 приводит к ошибке, так как вы не сообщили Форту, что это число двойной длины, а для числа одинарной длины оно слишком велико.
Попробуем еще несколько примеров. Убедитесь, что стек пуст и после этого введите 1.23 . . Вы увидите 0 123 ok В этом случае вы вывели два числа одинарной длины, на вершине стека было число 0 и следующее число-123. Попробуйте ввести 65535. U. U. тогда вы получите 0 65535 ok но если ввести 65536. U. U. то вы увидите 1 0 ok
Вы понимаете, в чем тут дело? Если нет, то пропечатайте результаты последних двух примеров в двоичной форме (2 BASE !).
Тогда вы получите такой результат: 0 1111111111111111 для числа 65535 и 1 0 для числа 65536. Если вы переходите от 65535 к 65536, происходит превышение максимального значения числа одинарной длины (целое число находится на вершине стека), при этом младший бит второго числа в стеке устанавливается в "1". То, что происходит с двумя верхними ячейками стека, аналогично тому, что происходит с двумя байтами в стеке, когда число одинарной длины изменяется с 255 на 256. Другими словами, с числами двойной длины Форт обращается так же, как с числами одинарной длины, но для них используется 32 бита. И при этом можно отображать числа двойной длины как со знаком, так и без знака. (Если это вас смущает, просмотрите материал о хранении чисел в двоичной форме из гл. 3.)
Для чисел двойной длины применяется набор арифметических операций, полностью аналогичный набору для чисел одинарной длины, поэтому мы даже не станем приводить примеры. Если вы не совсем понимаете, что делает тот или иной оператор, посмотрите их определения в приложении А. Вот эти операторы: D+, D-, DMAX, DMIN, DABS и DNEGATE (а в Форт-83 еще и D2/). Имеется также набор операторов для сравнения чисел двойной длины, но они будут рассмотрены в гл. 7. В MMSFORTH и других версиях имеется еще ряд дополнительных арифметических операторов, например D*, D/, D*/, D*/MOD и D/MOD. Они действуют так же, как их эквиваленты для чисел одинарной длины, при этом те слова, которые обеспечивают сохранение промежуточных результатов операций с числами одинарной длины в виде 32-разрядных чисел, в данном случае сохраняют промежуточный результат в виде 64-разрядных чисел (т.е. чисел четырехкратной длины).
В MMSFORTH есть два полезных слова #РТ и HI#. Слово #РТ запоминает положение десятичной точки (считая справа налево) в последнем введенном числе двойной длины. Так, например, 12.345 #PT. выдает число 4, в то время как 1.2345 #РТ. выдает число 5. Вы понимаете, что это слово может пригодиться для масштабирования чисел. Если нет, то из упражнений вам станет ясно, как его использовать.
Слово # РТ полезно также для форматного представления чисел, с которым мы познакомимся в гл. 5.
Слово #НI производит удивительное действие. В MMSFORTH все числа воспринимаются как числа двойной длины, но если в числе нет десятичной точки, то в стеке запоминаются только 16 младших битов числа. Независимо от наличия десятичной точки старшие 16 битов числа запоминаются в слове НI#. Таким образом, если ввести 12345678 HI# D. вы увидите число 12345678. Как работает это слово? Для чисел двойной длины старшие 16 бит запоминаются на вершине стека, слово НI# снимает старшие 16 битов и помещает их в стек. Следовательно, если с клавиатуры вводится число двойной длины, а после него слово Н1#, то число всегда запоминается в виде числа двойной длины.
Для манипуляций в стеке с числами двойной длины имеется набор слов, аналогичных словам для работы с числами одинарной длины: 2DROP, 2DUP, 2OVER, 2ROT и 2SWAP. Для доступа к числам двойной длины в памяти имеются также два слова 2! и 2@. Но эти слова так похожи на соответствующие слова для работы с числами одинарной длины, что лучше всего их рассмотреть в упражнениях.
Упражнения
1. Проверьте, что наибольшее 32-разрядное число без знака равно 4294967295. (Подсказка: вспомните о степенях числа 2.) 2. Рассмотрите табл. 2.2 и постройте аналогичную таблицу для манипуляций в стеке с числами двойной длины. 3. Напишите определение слова 2DROP под именем NEW2DROP. 4. Одинаковы ли по своему действию слова 2DUP и DUP DUP? Если да, то почему? и почему, если нет? 5. Определите слово 2DUP под именем NEW2DUP. 6. Определите слова 2SWAP79 и 2SWAP83 через ROLL. 7. Определите слова 2ROT79 и 2ROT83, используя слово ROLL. 8. Определите слова 20VER83 и 20VER79 через слово PICK. Слова, которые мы приводим в упражнениях 3-9, обычно для достижения быстродействия определены на языке ассемблера. 9. Определите слова 2ROLL83 и 2ROLL79, которые должны действовать аналогично ROLL. 10. Определите слова 2PICK83 и 2PICK79, действующие аналогично слову PICK, для тех, кто пользуется версией MMSFORTH. 11.
Определите слово S-> D для преобразования числа одинарной длины в число двойной длины. {Совет: проанализируйте приведенный пример слова Н1# из MMSFORTH.) 12. Что будет находиться в Н1#, если вы введете число 123? 13. Вот слово, которое возводит число 10 в степень п, если п находится на вершине стека: только ту часть числа двойной длины, которая стоит перед десятичной точкой. И еще напишите слово, которое должно возвращать часть числа, находящуюся после десятичной точки. (Указание: 123.25 - 123 + 25/100.)
Смешанные действия с числами одинарной и двойной длины.
В обоих стандартах (Форт-79 и Форт-83) имеются два обязательных слова для смешанных действий, в которых используются числа одинарной и двойной длины. В расширенных версиях Форта их еще больше.
В Форт-79 есть слова U* и U/MOD (аналогами их в Форт-83 являются UM* и UM/MOD). Вы уже раньше узнали, как напечатать число без знака, и, вероятно, вы понимаете, как складывать и вычитать целые числа со знаком и без знака: никакой разницы, нет, с какими числами вы имеете дело. При умножении имеют значение знаки чисел, кроме того, при умножении больших 16-разрядных чисел может возникнуть переполнение. Слово U* (или UM*) производит умножение двух чисел без знака, возвращая в стек число двойной длины. Попробуйте ввести 1000 1000 U* D. и вы увидите 1000000 ok. Очевидно, что результат представлен 32-разрядным числом. Теперь попробуйте ввести 5 -5 U* D. тогда вы увидите 327655
Число -5 было воспринято как 65531, поэтому полученное произведение является верным для данного числа.
Второй оператор для смешанных действии U/MOD (или UM/MOD) производит деление числа двойной длины (находящегося в стеке вторым) на число одинарной длины, помещая в стек остаток , и частное в виде чисел одинарной длины. Можете проверить это на ваших собственных примерах. Кстати сказать, слова U* и UM/MOD (соответственно UM* и UM/MOD) являются частью основного языка, а не расширения его для чисел двойной длины. Расширяющие слова для смешанных oпераций хорошо иллюстрируют слова MMSFORTH.
В табл. 4. 3 показаны их функции. Из этой таблицы ясно видно, что они делают, а также приведены аналоги для чисел одинарной длины.
Слово Действие М* n n --- d М*/ d n n - d (промежуточ. результат - число тройной длины) M+ d n --- d M- d n --- d M/ d n -- n M/MOD d n -- n n DU* ud ud -- uq DU/MOD uq ud--- ud ud Таблица 4.3. Операторы для смешанных действий Обозначения: n - число одинарной длины; d - число двойной длины; q - число учетверенной длины, т.е. 64-разрядное; u - без знака.
Упражнения
1. Используйте UM* для преобразования числа одинарной длины в число двойной длины. Имеет ли значение знак( можно сделать это еще быстрее, попросту помещая 0 в стек.) 2. Вспомните, как хранятся в стеке числа двойной длины в старших и младших ячейках. Имея это в виду, определите следующие смешанные операторы, которые имеются в MMSFORTH: М*, М+, М/ и M/MOD. 3. В чем различие между U/MOD и M/MOD? а также между U* и М* ? 4. Число двойной длины 123.45 помещено в стек и представляет доллары и центы. Определите слово ->DOLLARS, которое должно возвращать число долларов в виде числа одинарной длины. Определите другое слово ->CENTS, которое будет возвращать число центов в виде числа одинарной длины. Проделайте это упражнение, пользуясь словами из MMSFORTH и стандартными операторами. 5. Определите слово FRAC, которое должно умножать число двойной длины на отношение двух чисел одинарной длины, т.е. выражение 500. 3 5, после которого стоит это слово, должно давать в стеке значение 300 в виде числа двойной длины. Теперь определите это слово, пользуясь смешанными операторами MMSFORTH (последнее определение является тривиальным).
Расширение операций над числами с плавающей запятой
Вследствие приведенных раньше в этой главе соображений во многих реализациях Форт включены расширенные возможности для работы с числами с плавающей запятой. Поскольку они не регламентированы стандартом на числа с плавающей запятой, то слова для операций с числами с плавающей запятой отличаются от версии к версии.
В MMSFORTH имеется хороший набор расширяющих слов, которые мы используем в качестве примера. Если в вашем распоряжении есть версия, в которой также используются числа с плавающей запятой, то она скорее всего похожа на MMSFORTH и вам, вероятно, будет интересно проследить за этим обзором, привлекая документацию вашей версии. Мы уже рассмотрели, что представляют собой числа с плавающей запятой и числа в показательной форме. После этого вы, возможно, захотите перейти к материалу о реализации операций с плавающей запятой в MMSFORTH. Мы предполагаем, что вы уже поняли представление чисел с плавающей запятой, "научную" форму представления чисел, как записываются числа в показательной форме и арифметические действия с ними. Во многих компьютерах имеется встроенный интерпретатор языка Бейсик, выполненный на основе ПЗУ - постоянной памяти (по английски ROM- Read Only Memory, т.е. только считываемая память). Этот интерпретатор, написанный в машинных кодах, производит множество операций над числами с плавающей запятой. Кроме обычных операций умножения, деления, сложения и вычитания в нем имеются программы вычисления трансцендентных функций (например, тригонометрических и логарифмической), а также большое количество операций с целыми числами. Вследствие того, что MMSFORTH первоначально был разработан для работы с ЭВМ TRS-80, модель 1, совместимой с IBM PC, и, поскольку обе ЭВМ имеют встроенный Бейсик, "зашитый" в ПЗУ, обычные операции над числами с плавающей запятой производятся путем вызова машинных программ из ПЗУ.
Так как обе машины могут работать с числами с плавающей запятой, представляемыми 32 и 64 разрядами, то MMSFORTH может оперировать с числами одинарной и двойной точности. Обратив внимание, что эти числа принципиально отличаются от целых чисел Форта одинарной и двойной длины (в связи с чем лучше говорить о целых числах как о числах двойной длины, а не двойной точности). Числа с плавающей запятой одинарной точности обеспечивают 5 значащих разрядов > диапазон 9.9999Е-38 - 1Е38.
Числа с плавающей запятой двойной точности имеют точность 11 значащих разрядов и диапазон 9.9999999999999999Е-38 - 1Е38.,Правила ввода и вывода чисел при использовании программ с плавающей запятой из ПЗУ точно такие же, как правила Бейсика уровня 1 для TRS-80 или Бейсика фирмы Microsoft, и приведены они в документации микрокомпьютера. MMSFORTH и большинство других версий Форт для IBM PC также поддерживают арифметический сопроцессор серии 8087, который может быть установлен в персональных компьютерах и других совместимых ЭВМ. Сопроцессор типа 8087 вместе с MMSFORTH позволяет работать с числами имеющими 16 значащих разрядов мантиссы и порядок от -4932 до 4932. Кроме того, он обеспечивает вычисление трансцендентных функций и всех функций обычной арифметики с плавающей запятой и ряда других. Однако микросхема 8087 выполняет все расчеты не с помощью процедур на машинном языке, а с помощью аппаратных средств, которые представляют собой часть электронных схем процессора 8087. Аппаратная арифметика микросхемы 8087 является очень быстродействующей, она в 100 раз быстрее, чем эквивалентная математика на машинном языке и чаще всего быстрее, чем арифметика целых чисел (см. гл. 8). Мы опишем два варианта реализации в MMSFORTH арифметических операций с плавающей запятой: в ПЗУ и на базе сопроцессора 8087.
Арифметика с плавающей запятой, реализованная на ПЗУ
Краткие сведения о функции арифметики с плавающей запятой, реализованной в MMSFORTH на базе ПЗУ, приведены в табл. 4.4. В этом разделе мы опишем некоторые характерные черты этой арифметики, а в следующем - ее отличия от реализации на сопроцессоре 8087.
Числа с плавающей запятой представляются в стеке с 32 разрядами при единичной точности и с 64 разрядами при двойной точности. Таким образом, все слова, которые применяются с целыми числами двойной длины, можно использовать с числами с плавающей запятой одинарной точности, включая слова для задания констант и массивов, которые будут рассмотрены в гл. 6. Перед вводимым числом с плавающей запятой должен быть знак %, Ввод производится в свободном формате, поэтому % 1-01Е2, % 10.1 и % .10100Е3 будут в результате помещать в стек одно и то же число , 10.1 в старших 32 битах как число с плавающей запятой.
D% будет помещать в стек число с плавающей запятой двойной точности. Арифметические и другие математические действия производятся так же, как с целыми числами, поэтому мы их обсуждать не будем. Например, %.551Е3 % 10 F+ TAN F. произведет суммирование чисел 55.1 и 10, получая 65.1, а затем выведет значение тангенса этой величины (по умолчанию - в радианах), которая равна -1.19366. Что же касается комплексных чисел, то они заслуживают дальнейшего рассмотрения, но мы отложим этот вопрос до реализации операций с плавающей запятой на базе сопроцессора 8087. Использование слов для работы с числами с плавающей запятой asder понятно из упражнений.
Упражнения
1. Оцените значения следующих выражений, вводя числа с клавиатуры: (а) 5.5 + 1200 (б) ln(23/5) (в) sin^2(55) + cos^2(45) (углы - в градусах) (г) длину стороны квадрата с площадью 10 квадратных дюймов (д) площадь круга с радиусом 3.25 дюйма. 2. Напишите слово, которое будет брать два целых числа из стека, вычислять площадь прямоугольника со сторонами, выражаемыми этими числами, и печатать ее, обеспечивая не менее 10 значащих разрядов. 3. Напишите определение слова FABS с именем NEWFABS, используя возведение в квадрат и квадратный корень. 4. Напишите слово для определения гипотенузы прямоугольного треугольника по теореме Пифагора. Следующее упражнение предназначено для более подготовленных. 5. Напишите слово, которое с помощью цикла DO-LOOP печатало бы таблицу значений синуса и тангенса для углов до 45 градусов с шагом 1 градус. То же самое сделайте с шагом 0.1 градуса.
Таблица 4.4. Набор операций над числами с плавающей запятой, реализованных в MMSFORTH на базе ПЗУ*
% ( -) Предшествует вводу числа с плавающей запятой F#IN ( - t) Запрашивает ввод числа с плавающей запятой с клавиатуры F. ( f -) Печатает число с плавающей запятой F.R ( f ширина поля -) Печатает число с плавающей запятой, выравненное вправо в поле указанной ширины F+ ( f1 f2 -- f3) Возвращает сумму двух чисел F- ( f1 f2 -- f3) Возвращает разность двух чисел f1-f2 F* ( f1 f2 - f3) Умножает два числа f1*f2 F/ ( f1 f2 - f3) Делит два числа (f1/f2) FABS ( f - f1) Берет абсолютную величину числа с плавающей запятой FMINUS ( f -- -f) Изменяет знак числа; эквивалентно NEGATE SGN ( f - n) Возвращает -1, если f0 FCOMP ( f1 f2 - n) Сравнивает два числа с плавающей запятой: n=-1, если f1f2 FOG ( f - log(f)) Возвращает натуральный логарифм числа f FOG10 ( f - lg(f)) Возвращает десятичный логарифм числа f EXP < f - exp(f)) Возвращает значение числа е в степени f 10^ ( f - 10^f) Возводит число 10 в степень f X^Y ( f1 f2 - f1^f2) Возвращает f1, возведенное в степень f2 1/X ( f - 1/f) Возвращает число, обратное f FIX ( f1 - f2) Возвращает число с плавающей запятой, округленное до целого INT ( f1 - f2) Возвращает вместо числа с плавающей запятой ближайшее целое снизу CINT ( f - n) Возвращает 16-разрядное ближайшее целое, меньшее f I-F ( n - f) Преобразует 16-разрядное целое число в число с плавающей запятой SQR ( f1 - f2) Возвращает квадратный корень от f1 RND ( f - f) Возвращает случайное число с плавающей запятой, как на Microsoft Бейсике DEGREES ( -) Дает указания Форту принимать углы в градусах RADIANS ( -) Дает указания Форту принимать углы в радианах SIN ( f1 - f2) Возвращает значение синуса от f1 COS ( f1 - f2) Возвращает значение косинуса от f1 TAN ( f1 - f2) Возвращает значение тангенса от f1 ATN ( f1 - f2) Возвращает значение арктангенса от f1 ATN2 ( f1 f2 - f3) Возвращает значение арктангенса угла отрезка, соединяющего начало координат с точкой х,у PI ( - f) Возвращает константу Пи RAD ( - f) Возвращает константу для перевода градусов в радианы L10 ( - f) Возвращает натуральный логарифм 10 DF#IN ( - df) Запрашивает ввод числа с плавающей запятой двойной точности с клавиатуры DF. ( df -) Печатает число с плавающей запятой двойной точности DF.R ( df ширина поля-) Печатает число с плавающей запятой двойной точности, выравненное в поле указанной длины вправо DF+ ( df1 df2 - df3) Возвращает сумму (df1 + df2) DF- ( df1 df2 - df3) Возвращает разность (df1 - df2) DF* ( dfi df2 - df3) Возвращает произведение (df1*df2) DF/ ( df1 df2 - df3) Возвращает частное (df1/df2) DFABS ( df1 - df2) Возвращает абсолютное значение df1 DFMINUS ( df1 - df2) Изменяет знак df1 DSGN ( df - n) Возвращает -1, если df0 DFCOMP ( df1 df2 - n) Сравнивает два числа с плавающей запятой двойной точности, возвращая -1, если df1df2 FDF ( f - df) Преобразует число с плавающей запятой одинарной точности в число двойной точности с плавающей запятой DFF ( df - f) Преобразует число с плавающей запятой двойной точности в число одинарной точности с плавающей запятой CP+ ( Cp1 Cp2 -Cp3) Возвращает сумму двух комплексных чисел CP- ( Ср1 Cp2 -Cp3) Возвращает разность двух комплексных чисел(cp1 ср2) CP* ( Cp1 Cp2 -Cp3) Возвращает произведение двух комплексных чисел (ср1*ср2) CP/ ( Cp1 Cp2 -Cp3) Возвращает частное от деления двух комплексных чисел (ср1/ср2) MAG ( cp - f) Возвращает модуль комплексного числа PHASE ( cp - f) Возвращает аргумент или фазу комплексного числа CPMINUS ( cp - -cp) Изменяет знак комплексного числа с плавающей запятой CONJG ( cр -- ср) Возвращает сопряженное комплексное число с плавающей запятой R-Р ( ср -- f f) Преобразует прямоугольные координаты в полярные Р-R ( f f- ср) Преобразует полярные координаты в прямоугольные
* Набор программ содержит также несколько слов для работы с числами двойной и учетверенной длины, например 4 SWAP, Обозначения: f - число с плавающей запятой; df - число двойной точности с плавающей запятой; ср - комплексное число с плавающей запятой; n - 16- битовое число со знаком; адр - адрес.
Арифметика с плавающей запятой, реализованная на сопроцессоре 8087
Реализация операций с плавающей запятой на основе микросхемы 8087 отличается от реализации на ПЗУ несколькими существенными моментами: 1) вместо стека в памяти ЭВМ используется стек микросхемы, поэтому версии Форта могут иметь различия; преимущество этого стека - в быстродействии, недостаток - в ограниченной глубине стека; 2) большая часть операций выполняется на аппаратном уровне микросхемы 8087, а не на уровне машинных программ; 3) числа в стеке микросхемы 8087 хранятся как 80-битовые, а в памяти как 64-битовые, что дает дополнительные 16 знаков точности по сравнению с 64-битовыми числами, и 9 разрядов для 32-битовых чисел. Диапазон значений порядка от -4932 до 4932 (80 битов), от -306 до 307 (64 бита) и от -38 до 38 (32 бита); 38 (32 бита); 4) скорость операций с плавающей запятой на микропроцессоре 8087 в 100 раз выше, чем на ПЗУ, и для некоторых операций даже превосходит скорость вычислений с 16-разрядными целыми числами; умножение на микросхеме 8087 производится на 45% быстрее, чем с целыми числами, а сложение на 5% медленнее; 5) гарантируется обычно 16 точных разрядов при фиксированном формате мантиссы. Ввод чисел так же, как и в варианте на ПЗУ, производится в свободном формате, за исключением того, что диапазон вводимых чисел увеличивается- А так как в микросхеме имеется отдельный стек, то можно использовать оба стека одновременно без взаимных помех. Например, можно написать выражение тогда получится следующий результат: 291 0.1000000000000000Е60
Это значит, что вычисления с целыми числами и числами с плавающей запятой производятся одновременно и независимо в двух стеках: складываются числа 55 и 236 и перемножаются 1Е50 и 1Е10 и печатаются оба результата.
Конечно, такая запись может привести к путанице и поэтому не рекомендуется. Скорость работы микросхемы 8087 в режиме плавающей запятой удивительна: она превосходит скорость микрокомпьютеров и приближается к скорости универсальных вычислительных машин. Время вычисления 100.000 операций сложения с плавающей запятой составляет чуть больше 10 с, причем большая часть времени затрачивается на организацию зацикливания программы. Очень мощная универсальная ЭВМ Cyber фирмы CDC на языке Фортран затрачивает на это немного более 1 с, но при этом гарантируется только 12 разрядов точности, в то время как на со процессоре 8087 - 16 знаков точности. Становится реальным вычисление таких выражений, как 1000!, что практически невозможно даже на мини-ЭВМ, Практический пример применения операций в плавающей арифметике из работы авторов: нужно было смоделировать процесс изменения свойств воды в озере под действием естественных процессов за период 1000 лет. Для моделирования необходимо было решать численными методами 15 дифференциальных уравнений на каждые 4 ч моделируемого процесса. На суперЭВМ CDC Cyber в режиме разделения времени решение заняло один день. При этом ЦПУ машины было занято приблизительно около часа, причем стоимость машинного времени составила несколько сотен долларов. Решение этой задачи на языке Бейсик в скомпилированной форме заняло бы более одного месяца работы микроЭВМ IBM PC в монопольном режиме. При использовании MMSFORTH на микросхеме 8087 результат был получен менее чем за один день, фактически быстрее, чем на суперЭВМ. Решение немногих задач, подобных описанной, окупают затраты на микрокомпьютер и программное обеспечение и очень экономно расходуют время большой ЭВМ.
Отсутствующие функции плавающей арифметики, реализованные на ПЗУ: P.R, FABS, FMINUS (вместо нее имеется FNEGATE), FIX, INT, CINT, I-P, RND, RAD и L10. Кроме того, исключены слова для манипуляций в стеке с числами учетверенной и двойной длины, так как в стеке микропроцессора 8087 они не требуются.
Вследствие того, что арифметические действия с плавающей запятой реализованы в микросхеме 8087 аппаратными средствами и отличаются от реализации на ПЗУ, набор программ MMSFORTH содержит некоторые новые функции, а другие были исключены. Однако все отсутствующие функции легко можно определить на основе имеющихся, и мы в этом убедимся в следующих упражнениях.
Таблица 4.5. Дополнительный набор функций с плавающей запятой, реализованных на микросхеме 8087, не обеспечиваемых в реализации на ПЗУ
$F. (8f - адр n) Преобразует число с плавающей запятой в адрес, 87> (8f -- n ) Преобразует число с плавающей запятой в 16-ти битовое число >87 (n - 8f) Преобразует 16-битовое целое в число с плавающей запятой ?DEGREES (- n) Возвращает 1, если значение угла вводится в градусах, 0 - если в радианах COS.SIN (8f -- 8f 8f) Возвращает на вершину стека синус, вторым сверху - косинус CP*R (Cp 8f - Cp) Умножает каждую часть комплексного числа на число с плавающей запятой CPNEGATE (Cp1 - -Cp1) см. CPMINUS в табл. 4,4 D87> (8f - d) Преобразует число с плавающей запятой в 32-битовое целое число D>87 (d - 8f) Преобразует 32-битовое целое в число с плавающей запятой DF87> (8f - df) Преобразует число с плавающей запятой из формата микросхемы 8087 в число двойной точности с плавающей запятой формата ПЗУ DF>87 (d - 8f) Преобразует число двойной точности с плавающей запятой из формата ПЗУ в число с плавающей запятой формата микросхемы 8087 F87> (8f - f) Преобразует число с плавающей запягой из формата микросхемы 8087 в формат ПЗУ F>87 (f - 8f) Преобразует число с плавающей запятой из формата ПЗУ в формат микросхемы 8087 FDROP (8f -) Очищает стек микросхемы 8087 FDUP (8f1 - 8f1 8f1) Эквивалент DUP для микросхемы 8087 FNEGATE (8f1 - 8f2) См. FMINUS в табл. 4.4 FOVER (8f1 8f2 - 8f1 8f2 8f1 ) Эквивалент OVER для 8087 FROT (8f1 8f2 8f3 - 8f2 8f3 8f1) Эквивалент ROT в стеке 8087 FSWAP (8f1 8f2 - 8f2 8f1) Эквивалент SWAP в стеке 8087 FVAL (8f1 - $) Возвращает адрес символьной счетной строки, если задано число в формате 8087 LOG2 (8f1 - 8f2) Возвращает логарифм числа с плавающей запятой по основанию 2 ONE (- 8f) Помещает в стек 8087 число 1.0000000
Примечания: обозначения такие же, как в табл. 4.4; 8f - число с плавающей запятой в формате стека 8087; $ - счетная строка (см. гл. 9).
О мнимых и комплексных числах
MMSFORTH - один из немногих языков программирования для микрокомпьютеров, который может работать с комплексными числами. Если вам нужны для работы комплексные числа, то вы, конечно, представляете, что это такое. Тем не менее мы приводим краткий обзор для интересующихся и тех, кто хотел бы узнать об операциях с комплексными числами, даже не в MMSFORTH. Комплексное число представляет собой сумму действительного и мнимого числа, т.е- а + bi, где а называется действительной частью, b - мнимой частью, a i является корнем квадратным из -1. (Действительное число, как отсюда следует, это "нормальное", не мнимое число).Так как не существует такого числа, квадрат которого является каким-либо отрицательным числом, то i не имеет физического смысла и называется мнимым числом или просто мнимым. Тем не менее мнимые числа порождаются в результате некоторых математических действий. И мнимые, и комплексные числа широко используются учеными и инженерами. Комплексные числа проще всего представить как векторы или, еще проще, как точки с координатами (х,у) в системе координат, называемой комплексной плоскостью, плоскостью Гаусса или круговой диаграммой Аржана. Величина а отображает координату х, b - координату у. Модуль, или величина, числа, - это длина отрезка, проведенного из начала координат в данную точку, в то время как угол между осью х и отрезком называется аргументом или фазой. Сопряженное комплексное число - это число, у которого мнимая часть умножается на -1, т.е. сопряженным к a+bi является число а-bi. Слова MMSFORTH MAG (от magnitude- модуль, величина), PHASE (фаза) и CONJG (сопряженный) делают именно то, что от них можно ожидать по названию (см. табл. 4.4). В MMSFORTH действительная и мнимая части числа помещаются в стек так, что на вершине находится мнимая часть. В реализации на ПЗУ они вводятся с помощью слова СР%, хотя их можно так же просто вводить как два числа с плавающей запятой, как это должно делаться в реализации на сопроцессоре 8087 в MMSFORTH.
В таблице 4. 4 показано, каким образом MMSFORTH позволяет выполнять умножение комплексного числа на действительное число, умножение, деление, сложение и вычитание двух комплексных чисел, определение модуля и фазы, а также нахождение сопряженного комплексного числа. MMSFORTH позволяет также преобразовывать представление точки в системе прямоугольных координат в полярные координаты. Последние можно рассматривать как форму обращения с комплексными числами, которая заменяет число с компонентами а и b, выражая его через модуль и аргумент (фазу). (В прямоугольной системе координат точка представляется величинами х,у; в полярной системе координат - величиной отрезка, проведенного из начала координат в данную точку, и углом между этим отрезком и осью х, т.е. фазой.)
Следует заметить, что выражение "действительные числа" употребляется специалистами по компьютерам некорректно, так как у математиков nmn имеет другой смысл. Многие специалисты по компьютерам считают, что действительные числа - это синоним чисел с плавающей запятой, в отличие от целых чисел. Но это совершенно неправильно: любое число, которое не является мнимым или комплексным, является действительным, будь оно целое или с плавающей запятой. Неверная терминология, возможно, обязана своим происхождением условностям, принятым в Фортране, где числа с плавающей запятой называются "действительными" (real). К сожалению, эта терминология проникла и в описание сопроцессора 8087 и ее следует избегать.
Если у вас есть набор программ MMSFORTH для работы с комплексными числами или какая-либо другая версия, вы сможете проделать следующие упражнения.
Упражнения
1. Следующие выражения могут быть вычислены в реализации функций с плавающей запятой на микросхеме 8087. Перепишите их, чтобы можно было работать в версии MMSFORTH, реализованной на ПЗУ: (а) $ 5.5 5 % 6.5 8 + F+ (б) $ 35 10 I-F F+ SIN (в) % -55 FMINUS (г) $ 35 FDUP 5 COS FSWAP SIN F+ . F. 2. Определите слова Форта для микросхемы 8087, сходные по звучанию и функциям со следующими словами, реализованными на ПЗУ: FABS.
CINT, I-F, RAD, L1О. 3. Определите слово FACT. предназначенное для вычисления факториала в-реализации на микросхеме 8087. Попробуйте вычислить 1000! Зафиксируйте время исполнения операции. (Факториал числа 3 равен 3х2х1, факториал 6 - 5х4х3х2х1.) 4. Определите величину ошибки дробно-рациональной апроксимации числа Пи, приведенной в табл. 4.2. 5. Используя комплексные числа, определите слово ANGLE (угол), для того чтобы найти угол между гипотенузой и прилегающей стороной прямоугольного треугольника, если в стеке находятся значения длин обеих прилегающих сторон треугольника (рис.4.1).

6. Аналогично определите слово HYPOT (гипотенуза) для вычисления длины гипотенузы. 7. По определению, величина рН представляет собой взятый со знаком минус логарифм концентрации ионов водорода: pH=-log[H-]. Определите слово, которое по значению в стеке величины рН и абсолютному значению увеличения концентрации ионов водорода, выраженному в форме с плавающей запятой, рассчитывает фактическое увеличение рН. Можете ли вы предложить способ, как сделать это в целочисленной арифметике? 8. Одна из рутинных задач робототехники - преобразовать перемещение плеча, описываемого координатами х.у, в приращения радиуса и угла. Например, на рис.4.1 для перемещения из точки (х1,у1) в точку (х2,у2) требуется приращение угла (угол1-угол2) и приращение радиуса (радиус 1-радиус2). Определите слово ARMMOVE (движение, плеча), которое по заданным в стеке значениям х1,у1,х2,у2 будет выдавать в стек значения величин (угол1-угол2) и (радиус1-радиус2). За положительное приращение угла принимается движение по часовой стрелке. Для решения вам потребуются тригонометрические функции или комплексные числа.
Выводы
Как и обещали в начале этой главы, мы привели обзор основных понятий, связанных с числами. Некоторые из них, как, например, числа двойной длины, должен изучить каждый программирующий на Форте, в то же время числа с плавающей запятой могут потребоваться только тем, кто будет иметь дело с математическими функциями.
Тем не менее после этого обзора вы должны почувствовать, что Форт имеет мощные средства для решения математических научно-технических задач. Не иронично ли, что многие критики Форта считают его слабостью то, "что он не подходит для таких задач? Основанием для критики является в основном использование* обратной польской записи вместо алгебраической и отсутствие операций с дейcтвительными числами (в том числе с плавающей запятой) в стандартах языка и его простейших реализациях. Но ни одна из этих причин не становится трудно преодолимым препятствием для Форта. Применение стека обеспечивает Форту высокое быстродействие, которое так необходимо для математических приложений, а операции с плавающей запятой имеются в большинстве версий Форта. Форт используется в очень широком диапазоне отраслей науки и техники, например в лазерной энергетике, радиоастрономии, технике охраны окружающей среды, физической океанографии, робототехнике. Многие поняли, что Форт более мощный и простой язык, чем "классический" язык науки и техники Фортран. Мы надеемся, что язык найдет еще большее распространение среди тех, кто использует математику в технических дисциплинах.
Ввод и вывод
Ввод и вывод символовКаким образом информация выводится из компьютера на дисплей и принтер и как ее ввести с клавиатуры? Конечно, вы уже вводили и выводили данные, нажимая клавиши и пользуясь словами. (точка), D., EMIT и т.д. Но имеются и другие возможности. Поскольку форт не предназначен для конкретного типа микрокомпьютера, его слова для операций ввода и вывода - универсальные (большинство версий Бейсика, напротив, приспособлены для определенных типов ЭВМ). Правда, во многих версиях Форта также имеются специальные слова для управления вводом-выводом, чтобы максимально использовать возможности конкретного микрокомпьютера. Мы рассмотрим как стандартные слова, так и, в качестве примера, некоторые слова для осуществления ввода-вывода из MMSFORTH. Некоторые вопросы мы отложим до гл. 9, потому что все подробности ввода чисел и символьных строк при исполнении программ будет проще понять после того, как вы изучите ввод символьных строк. Здесь полезно вспомнить, что буквы и числа вводятся в ЭВМ и выводятся одинаково в виде кодов ASCII. Это объясняется историческими причинами, знание которых поможет вам лучше понять методы ввода и вывода в Форте.
До появления персональных компьютеров ввод и вывод на ЭВМ производился через терминал. В своем простейшем виде терминал был малоинтеллектуальным устройством. Хороший терминал представляет в сущности пишущую машинку, и фактически в 1960-1970 гг., наиболее распространенной была модель электрической пишущей машинки типа IBM Selectric, Другая распространенная модель типа Teletype тоже представляла собой пишущую машинку, но с необычной механикой. Рассмотрим, что может делать пишущая машинка. Она может выводить на бумагу буквы, цифры и пробелы и, кроме того, передвигать с помощью валика бумагу. Перемещение бумаги может быть построчное и, кроме того, постраничное. При некотором умении можно продвинуть бумагу на половину строки или даже перемотать ее в обратную сторону- Конечно, возможно также переместить каретку на одну позицию по строке назад и перебить символ.
Но это почти все, что может делать терминал как пишущая машинка. Клавиатура терминала также подобна клавиатуре пишущей машинки, т.е. у нее есть клавиши для букв и цифр, пробела, перевода строки и возврата на позицию влево. И только двух дополнительных клавиш, которые есть у терминала, нет на пишущей машинке. Это клавиша управления и клавиша спецсимвола Escape (отказ). Для простого механического терминала при небольшом количестве функций было вполне достаточно кодов управления ASCII. Фактически терминалы ЭВМ и коды ASCII были прямо заимствованы из телетайпной техники 1930 - 1950 гг. Но с изобретением видеодисплейных терминалов открылись новые возможности.
Первые видеотерминалы работали так же, как и их механические прототипы. Они печатали символы в нижней части экрана и продвигали строки на экране вверх, подобно листу бумаги в пишущей машинке. Но возможности видеотерминалов были гораздо богаче. Например, они обладали возможностью разделять экран на две половины по горизонтали или по вертикали с различной информацией в каждой части или, например, выделять слова, меняя яркость. На более сложных терминалах, например Tektronix graphics, можно с высокой разрешающей способностью, отображать рисунки линиями. Однако по-прежнему они общаются с управляющей ЭВМ, фактически используя старый код ASCII. (В действительности есть два важных исключения - еще один код для телекоммуникаций, код Бодо, названный по фамилии его изобретателя, пионера в области автоматической телеграфии; кроме того, до сих пор при работе с ЭВМ фирмы IBM используется предложенный ею код EBCDIC.)
Более высокий уровень сложности стал возможен с изобретением персонального компьютера, содержащего в себе все оборудование ЭВМ. Его экран может рассматриваться как экран телевизора, отображающего сложные графические образы в цвете, с набором различных шрифтов, символов, с окнами, в которых отображаются выходные данные различных программ, включая графические, и многое другое. Достаточно одного взгляда на экран персональной ЭВМ Apple Lisa или Mackintosh, чтобы убедиться в их огромных возможностях.
Конечно же, и возможности клавиатуры также усложнились с введением специальных функциональных клавиш, клавиши для перемещения курсора и т.д. Однако проблема состоит в том. что по-прежнему ввод и вывод производятся по стандарту ASCII, хотя в большинстве случаев добавляется еще 127 символов и, таким образом, используется полный, набор из 256 символов, каждый из которых может быть представлен одним из 8-разрядных чисел, так называемым байтом. Другая проблема состоит в том, что почти в любой модели терминала и компьютера используются различные способы.управления функциями экрана. Определенные управляющие коды, например стирание влево, перевод строки, возврат каретки, т.е. те коды, которые применялись для управления телетайпами, оставили стандартными. Но другие были произвольно изменены. Можно ли в свете сказанного создать язык, способный работать на разнообразных ЭВМ? Сделать это чрезвычайно трудно. Поэтому стандарт языка Форт должен ограничивать только ввод-вывод кодов ASCII в расчете на использование простых терминалов, Для конкретных ЭВМ должны быть разработаны расширенные и нестандартные версии языка. Кроме вывода символов кодами ASCII терминал должен управляться также либо кодами ASCII, либо так называемыми Еsс-последовательностями. Мы рассматривали управляющие коды в гл. 3, здесь уместно вкратце упомянуть об Esc-последовательностях.
Код ASCII 27 называется Esc-префиксом или Esc-клавишей (отказ). Его можно ввести с клавиатуры ЭВМ, нажимая клавишу . (Некоторые ЭВМ не имеют специальной Esc-клавиши, в этом случае она имитируется некоторой комбинацией других клавиш; например, ввод кода Esc, если нет специальной клавиши, производится одновременным нажатием управляющей клавиши Ctrl, клавиши Shift (переключение регистров) и .) Назначение клавиши Esc состоит в том, чтобы сигнализировать, что некоторая последовательность символов после Esc должна рассматриваться как команда управления, а не код символа ASCII. Например, последовательность Esc С (27,67) должна произвести очистку экрана терминала и помещение курсора в левый верхний угол.
Печатающее устройство также, как правило, управляется Esc-последовательностями, например последовательность кодов (27,28) дает принтеру Centronics 739 команду продвинуть бумагу на полстроки вперед; (27,30) - на полстроки назад, а последовательность кодов (27,47,48) переводит устройство в режим, при котором следующие байты воспринимаются не как символы, а как графические команды. Ради чего мы приводим здесь это описание терминалов? Только ради того, чтобы принести извинения за отсутствие подробных объяснений, как управлять терминалом вашего компьютера или принтером. Вам нужно практически проверить, как реагирует на Esc-последовательности оборудование вашей ЭВМ. Мы расскажем здесь, как организовать посылку символов из Форта, а вы сами посмотрите, что они делают на вашем дисплее или принтере.
Вывод символов
В конце гл. 3 вы уже видели, что самое необходимое слово для вывода символов - EMIT, которое посылает на экран дисплея символ, соответствующий числу, которое хранится в стеке.' Попробуйте посмотреть, что делают различные управляющие коды, экспериментируя со словом EMIT. Например, если вы введете 48 EMIT 49 EMIT 10 EMIT 50 EMIT то скорее всего увидите 01 на одной строке и 2 - на следующей. Почему? Потому, что 48, 49 и 50 представляют собой соответственно ASCII-коды цифр 0, 1 и 2, а 10 - перевод строки, который при интерпретации обычно сопровождается возвратом каретки. Если вы напечатаете 48 EMIT 49 EMIT 8 EMIT 50 EMIT то увидите 02. Цифра 1 была стерта, так как код 8 представляет команду перемещения курсора на позицию влево, или стирания влево (Backspace). Испробуйте другие управляющие коды, чтобы установить их действие на вашем оборудовании, при этом могут возникнуть некоторые сюрпризы, например в MMSFORTH на машине IBM PC или на TRS-80 команда 27 EMIT приводит к обратной подаче строки, в то время как 12 EMIT очищает экран и возвращает курсор "домой" (т.е. в левый верхний угол экрана). Вы можете также попробовать коды ASCII от 128 и более, которые не являются стандартными и могут выводить странные символы и необычные буквы или продемонстрируют, что будет на экране, если из кода ASCII вычесть 127, т.е.
если игнорировать значение старшего бита.
Можно определить через EMIT два стандартных слова, действие которых столь очевидно, что мы приводим только их определение: : SPACE 32 EMIT ; (пробел) и : CR 13 EMIT 10 EMIT ; (перевод строки + возврат каретки) Не столь очевидно определение стандартного слова "пробелы": : SPACES 0 DO SPACE LOOP ;
Оно выводит пробелы, количество которых определяется числом на вершине стека. Нестандартное слово BL (пробел) включено во многие версии Форта. Оно помещает на вершину стека 32, т.е. ASCII-код пробела. Таким образом, можно дать иное определение пробела: : SPACE BL EMIT ;
Более сложное слово с использованием EMIT - это TYPE (печать). Его подробное описание вы найдете в гл. 9, где мы объясним символьные строки, но, чтобы начать пользоваться им раньше, мы рассмотрим его уже сейчас. Слово TYPE просматривает последовательность однобайтовых (8-разрядных) чисел (символов) из памяти и посылают их ASCII-эквивалент на экран. Для него требуется число на вершине стека, а ниже него - адрес. Слово TYPE выводит указанное число символов, начинающихся с этого адреса, на экран. Рассмотрим пример. Может быть, вам потребуется освежить в памяти счетные циклы DO-LOOP (гл. 1) и слово PAD из гл. 4. Слово I помещает текущее значение параметра цикла на вершину стека. Определим слово : PUTTEST 84 83 69 84 4 0 DO PAD I + С! LOOP ; , которое поместит символы ASCII символьной строки "TEST" в памяти, начиная с адреса, выдаваемого словом PAD. Теперь введите с клавиатуры PAD 4 TYPE и на экране появится строка "TEST". Обратите внимание, что коды ASCII должны быть помещены в стек в обратном порядке. А вот одно из возможных определений слова TYPE; попробуйте разобраться, как оно работает : : TYPE (адр n --) 0 DO DUP I + С@ EMIT LOOP DROP ;
Теперь нам следует познакомиться с символьными строками, о которых более подробно вы узнаете из гл. 9. Чтобы напечатать или сделать что-нибудь еще с символьной строкой, нам надо знать не только входящие в нее символы, но также их количество.
Один из способов запоминания строки, стандартный для Форта, состоит в использовании счетной строки (иногда ее называют нумерованной строкой). Счетная строка представляет собой область памяти, первый байт которой содержит число символов строки, за которым следуют собственно коды ASCII. Мы можем переопределить слово PUTTEST, помещающее строку "TEST" в PAD, следующим образом: : PUTTEST 84 83 69 64 4 PAD С! 4 0 DO PAD I 1+ + С! LOOP ;
4 PAD С! помещает число символов (счетное число) в PAD. Остальная часть определяемого слова размещает строку символов в более старшие адреса. Чтобы посмотреть строку, нужно, ввести с клавиатуры PAD 1+ PAD С@ TYPE
После этого в стеке будет находиться адрес начала строки и на вершине стека - число символов, т.е. стек подготовлен для распечатки словом TYPE. Поскольку этот принцип широко используется, предусмотрены стандартные слова для получения числа символов и адреса символьной строки: : COUNT (адр - адр+1 n) DUP 1+ SWAP C@ ; Поэтому вы сможете увидеть тестовое слово в PAD с помощью PAD COUNT TYPE
В гл. 9 вы увидите, что счетные строки обеспечивают большую гибкость при манипулировании с символьным текстом. Для примера скажем, что эта книга была набрана с помощью редактора текста, написанного на Форте, и эта программа редактора работает не хуже любой подобной программы, написанной на языке ассемблера.
В гл. 1 вы познакомились с более простым способом вывода строк с помощью слова ." (точка-кавычка), т.е. ." This is a test" (Это тест) выведет на дисплей строку "This is a test" (Это тест). Закрывающая кавычка не является словом Форта, это просто символ, который указывает слову ." на необходимость прекращения вывода строки на дисплей. В языке Форт-79 слово ." работает как в определении нового слова, так и вне его, с той лишь разницей, что вне определения строка выводится немедленно, в то время как, находясь внутри определяемого слова, она не выводится, пока определяемое слово не будет исполнено.
В версии Форт- 83 имеется незначительное отличие. Здесь не требуется, чтобы слово ." исполнялось вне определения. Для немедленного вывода строки зарезервировано другое слово .( (точка-скобка), которое выводит символьную строку, ограниченную правой круглой скобкой.)
Оно работает так же, как слово ." в Форт-79 вне определения, немедленно выводя текст, однако внутри определения .( так же выводит немедленно. Так, если ввести : TEST .(This is a test) : на экране будет немедленно выведена строка "This is a test" (Это тест). Таким образом, слово TEST в данном случае ничего не делает.
* Упражнения
1. Определите слово BS (стирание влево) по аналогии со словом CR (возврат каретки) так, чтобы при исполнении слова BS на выходе происходило бы стирание ранее выведенного предшествующего символа. 2. В MMSFORT есть слово PAGE (страница), которое производит стирание экрана и помещает курсор в левый верхний угол. Определите слово PAGE, имея в виду действие управляющего кода 12 в MMSFORTH. 3. Определите слово CRS, которое должно перемещать строки на экране вверх на величину, определяемую числом в стеке, т.е. слово CRS должно действовать как возврат каретки вверх, так же, как слово SPACES действует над пробелами. 4. Определите слово DASHES (черточки), которое выводило бы на экран число черточек, которое задается числом в стеке. 5. Определите семь слов для вывода текста "MAIN MENU" (основное меню). Теперь, пользуясь этим словом и словами, определенными вами в упражнениях 2-4, определите слово MENU, которое выводило бы на экран следующее меню (не забудьте о слове ." ):
----- ОСНОВНОЕ МЕНЮ ----- --------------------------------------- A ------ Это первый вариант выбора B ------ Это второй вариант выбора C ------ Это третий вариант выбора D ------ Это выбор Форта --------------------------------------- ------ ЧТО ВЫ ВЫБИРАЕТЕ ? --------
6. В MMSFORTH есть слово $. (вывод символьной строки), которое выводит на экран содержимое строки, если задать адрес счетной строки.
Дайте определение слова $..
Управление экраном дисплея
Как вы, возможно, догадываетесь, в языке Форт не предусмотрены стандартные слова для управления содержимым экрана дисплея; имеется множество других способов, с помощью которых компьютеры и дисплеи осуществляют это. Однако мы увидим некоторые дополнительные возможности, рассмотрев некоторые слова MMSFORTH, которые предназначены для работы с компьютерами типа TRS-80 и IBM PC. Вы сможете определить такие же слова для вашей машины, если поймете, как она исполняет управляющие коды, а возможно, в вашей версии Форта уже имеются подобные слова. Наиболее важная функция управления экраном состоит в возможности перемещения курсора по экрану. Если вы можете делать это, то сможете сделать почти все. Представьте себе экран как таблицу из символов с определенным числом строк и столбцов. Вам хотелось бы иметь возможность поместить курсор в любую строку и столбец. В MMSFORTH для этого служит слово РТС (put cursor - поместить курсор). Для него номер строки ожидается вторым в стеке, а номер столбца - на вершине стека. Таким образом, 0 0 РТС передвинет курсор в левый верхний угол экрана, в то время как 10 30 РТС поместит его в 11 строку и введет 31 пробел. Содержимое экрана не изменится (кроме того, что на месте курсора и сообщения "ok" прежние символы будут стерты). Указанную возможность можно рассматривать как форму, содержащую пустые места, которые должны быть заполнены. Для примера, пусть меню задает вопрос, ответ на который должен начинаться в строке номер 10 и столбце номер 30, а для текста вопроса отводится 10 пустых мест. Тогда слово, которое поместит курсор и вставит в пустые места пробелы, выглядит так: : FILL-IN 10 30 РТС 10 SPACES 10 30 РТС : (Заполнить) Конечно, слово должно печатать вопрос вместо того, чтобы оставлять пробелы. Приведем другое определение слова PAGE из последних упражнений, которое будет исполняться более медленно предполагается, что экран содержит 16 строк по 64 символа в строке, т.е. всего 1024 символа): : PAGE 0 0 РТС 1024 SPACES 0 0 РТС ; (Страница)
Для экрана с 80 символами на строке и 25 строками число 1024 должно быть заменено на 2000 SO * 25). (Очевидно, что скорость будет выше, если использовать какие-либо управляющие коды, которые предоставляет компьютер или дисплей, например 12 EMIT в MMSFORTH.)
В гл. 12 и 13, где представлен и рассматривается экранный редактор, вы увидите, как может быть определено слово РТС в других версиях Форта. Со словом РТС связано слово GTC (get_cursor, т.е. определить, где находится курсор). Оно помещает в стек номер строки, затем номер столбца. Приведем пример слова, подобного FILL-IN, которое поместит на экране после курсора 10 пробелов, если это возможно : : 10BLANKS GTC 10 SPACES РТС : (10 пробелов)
Слово GTC выдает положение курсора, 10 SPACES перемещает его на 10 позиций вправо, заполняя пробелами все, что находится на этих местах, и слово РТС снова возвращает курсор на старое место, используя имеющуюся в стеке от GTC информацию о положении курсора. При небольшой изобретательности слова РТС и GTC позволят вам делать впечатляющие и "дружественные" подсказки для ввода в программы ответов пользователя, которому остается только вводить их на месте пробелов. Можно практически обойтись без "перелистывания" экрана, если ввод и вывод информации производить на одном и том же месте экрана.
Чем больше возможностей имеет дисплей компьютера, тем более специализированные слова Форта требуются для использования этих возможностей. MMSFORTH совместно с IBM PC показывают хороший пример того, что может быть сделано, но не будем их здесь описывать, поскольку их много и они очень специфичны. Наиболее сложная возможность - это использование так называемых "окон". Они позволяют выделять на экране различные области, в каждой из которых появляются различные текстовые или графические изображения. Например, результат работы одной программы может быть представлен в основном окне, а меньшее окно может быть выделено для вывода листинга программы при отладке либо в одном окне можно помещать данные, вводимые в программу, а в другом - выходные данные.
С помощью программы векторной графики MMSFORTH можно задать несколько окон для отображения выходной графической информации, в то время как другие окна могут быть оставлены для вывода алфавитно-цифровой информации. Цвет содержимого каждого окна и границ окон и даже цвет отдельных слов могут быть заданы независимо. Если в IBM не установлена плата адаптера цветного монитора, тогда можно изменить вид курсора, ввести подчеркивание и т.д. Этим достигается большая гибкость. Другие версии Форта могут обладать подобными возможностями, но лишь немногие работают с окнами. К примеру, очень впечатляет использование окон MMSFORTH на ЭВМ Apple Macintosh.)
Вывод на печатающее устройство (принтер)
Стандарты Форта не определяют слова для осуществления вывода на принтер. Однако имеется много возможностей для стандартизации. В Форте имеются способы передать выходную информацию на принтер или одновременно на принтер и на экран. Мы опишем, каким образом это реализовано в MMSFORTH. Почти наверняка в вашей версии Форта вы найдете эквивалентные слова. Управление принтером реализуется с помощью слов PRINT, PCRT и CRT. После исполнения слова PRINT вся информация, которая передавалась на экран, передается на принтер. После исполнения слова PCRT вывод передается как на экран, так и на принтер, а после CRT прекращается вывод на принтер, информация выводится на экран. Это так просто. Другой способ управления предоставляют слова PRINTER (принтер), SCREEN (экран), ON (включено), OFF (выключено), используемые в контексте: PRINTER ON (вывод_на_принтер) SCREEN ON (вывод_на_экран) PRINTER OFF (выключить_вывод_на_принтер) SCREEN OFF (выключить_вывод_на_дисплей)
Указанные предложения производят несколько неожиданные действия. По идее это также должно быть просто, но не торопитесь, так как все несколько сложнее. Что произойдет, если послать 12 EMIT при разрешенном выводе на принтер (PRINT)? Код 12 для большинства принтеров является управляющим кодом, который заставляет принтер продвинуть бумагу на целую страницу (протяжка листа).
А это, очевидно, не то же самое, что очистка экрана и возвращение курсора в левый верхний угол экрана. Или предположим, что вы посылаете управляющий код перевода строки назад при активизации экрана командой CRT. Как отреагирует на этот код экран дисплея? До тех пор, пока вы передаете алфавитно-цифровой текст и простой перевод каретки, никаких проблем с выводом не возникает. Но если вы делаете всякие замысловатые операции с дисплеем или принтером, используя их управляющие коды, нужно быть очень осторожным при включении и выключении вывода на эти устройства, чтобы предотвратить нежелательную интерпретацию этих кодов. Это может привести к дополнительным осложнениям.
Построение простейших графиков из линий
До появления в составе компьютеров хороших видеографических дисплеев и координатных построителей вывод графических данных приходилось делать на построчное печатающее устройство или знаковый терминал. Действительно, вывод в такой форме и в настоящее время можно запрограммировать и выполнить гораздо быстрее, чем вывод в графическом виде. Идея того, как это делается, очень проста, в особенности для Форта. Вы уже видели демонстрационную программу построения линий в гл. 1. В данном разделе мы более подробно рассмотрим детали этого способа и, что более важно, предложим вам несколько упражнений на построение линейных графиков. Допустим. что вы хотите построить гистограмму , отображающую величины чисел, находящихся в стеке, Прежде всего нам потребуется слово, которое делает высоту столбика пропорциональной величине числа. Определим слово : XS 0 00 88 EMIT LOOP ; (выводить_"Х") печатающее ряд литер "X", длина которого зависит от числа в стеке, это и есть наш столбик. Теперь определим слово : PLOT (Нарисовать_график) CR (Начало с новой строки) DEPTH (Сколько чисел находится в стеке?) 0 (Нижний предел цикла) DO (Начало циклического повторения DEPTH раз) XS (Печатает строку из n литер 'х'; n - из стека) CR (Переходит в начало следующей строки) LOOP (Конец цикла; конец определения) ;
Теперь, если вы напечатаете 5 10 20 30 30 25 8 2 PLOT на экране дисплея, получится следующий график :
XX XХХХХХХХ ХХХХХХХХХХХХХХХХХХХХХХХХХ ХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХ ХХХХХХХХХХХХХХХХХХХХ ХХХХХХХХХХ ХХХХХ
Он должен быть вам знаком по гл. 1 и, конечно, не лишен недостатков. Числа должны быть помещены в стек в порядке, обратном тому, в каком они должны быть выведены. Длина столбика должна быть не больше ширины экрана дисплея. Существуют, однако, пути обхода этих проблем и способы построения более сложных диаграмм. С некоторыми мы познакомимся на следующих упражнениях и в последующих разделах.
Упражнения
1. Измените слово PLOT (вызов: PLOT1) так, что если число больше ширины экрана, то столбик будет доходить только до края экрана. (Указание: используйте слово MIN.) 2. Переделайте слово PLOT в PLOT2 так, чтобы вывод направлялся не на экран, а на принтер. По окончании вывода оно должно переключить вывод снова на экран. 3. Измените слово PLOT в PLOT3 так. чтобы оно печатало пары значений чисел в стеке литерами Х и Y одно под другим, т.е. если в стеке находятся числа 12 10 8 5, то слово PLOT3 выведет на экран гистограмму YYYYY ХХХХХХХХХХ YYYYYYYY ХХХХХХХХХХХХ Вам потребуется определить слово YS (литеры Y). 4. Измените слово PLOT в PLOT4 так, чтобы кроме вывода гистограммы оно выводило бы номера каждого столбика от 0 до 9 и между номером и началом гистограммы было бы три пробела. На выходе должна быть такая гистограмма 0 ХХХХХХ 1 ХХХХХХХХХХХХ 2 XX 3 ХХХХХХХХХХХХХХХХХХ 5. Измените слово упражнения 3 (назовите его PLOT5) так. чтобы вместо столбиков печаталась точка в конце каждого столбика, т.е. таким образом : 0 Х 1 Х 2 Х 3 X и т.д. 6. Во всех предыдущих программах построения гистограмм числа должны быть не больше числа позиций в ширину экрана. Измените программу упражнения 5 на PLOT6 так, чтобы можно было задавать числа до 1000 и чтобы числу 1000 соответствовал символ в 75-й позиции на строке экрана (считая, что 0 соответствует 1-я позиция). Если нужно, просмотрите раздел о масштабировании в гл. 4.
Не забудьте, что четыре позиции нужно предусмотреть для чисел и пробелов. 7. Измените слово упражнения 3 на PLOT? таким образом, чтобы вместо печати столбиков друг под другом получалась бы печать их рядом. Тогда на выходе должно быть XXXXXXXXXXYYYYY XXXXXXXXXXXXYYYYYYYY 8. Измените слово упражнения 7 на PLOTS, чтобы вместо столбиков печатались только их конечные точки, как показано: Х Y Х Y 9. Измените программу упражнения 8 на PLOT9, чтобы в конце каждой строки литер "Y" печаталась величина, как показано : Х Y5 Х Y8 10. Измените программу упражнения 9 на PLOT10. чтобы допустимыми были значения Х от 0 до 30, а значения Y от О до 1000. Считайте, что в строке 64 символа. 11. Это трудное упражнение. Предположим, что у вас есть пары значений х и у, которые вы хотите отобразить в виде графика зависимости у от х. Пусть значения х рассортированы так, что наименьшее значение находится на вершине стека, а пары значений в стеке расположены в порядке у, х. Например, в стеке может быть пара 30, 10, причем 30 - у, а 10 - х. Теперь предположим, что в стеке имеются числа 30 10 20 7 15 5 10 4 2 1 10 .
При исполнении слова PLOT должна получаться следующая картина : .X 0 1 .X 1 2 . . X 4 10 . X 5 15 . . X 7 20 . . . X 10 30
Таким образом, нулевое значение у должно быть представлено точкой, величина х должна быть отображена расстоянием по направлению вниз, величина у - расстоянием от левого края и после каждой отображаемой точки должны быть напечатаны значения х и у. Вам придется использовать вложенные счетные циклы типа DO-LOOP.
Вывод чисел
Вы уже знаете, конечно, как выводить числа на экран с помощью слов . (точка), U. и D., но это очень примитивно. Предположим, что вы хотите представить таблицу, в которой числа были бы выровнены по крайнему правому разряду, например: 1956 34215 343 23 5555 33333 21965 2 23
Или вы хотите напечатать число со знаком денежной единицы, например $294. Либо вам нужно напечатать число с десятичной точкой (в известном месте, поскольку предварительно вы сделали масштабирование), например, "$294.32".
Форт предоставляет превосходный выбор для отображения чисел в специальных форматах. Можно получить таблицу чисел с помощью печати, выровненной по правому краю. $294.32 можно напечатать, используя форматный вывод (иногда называемый выводом по шаблону). Печать в форме таблицы делается просто, вывод со знаком денежной единицы - более сложно.
Слово R. позволяет печатать число в формате выравнивания вправо. Это означает, что число печатается так, как будто бы его все время сдвигают вправо в поле некоторой определенной длины. Длина поля должна быть положена на вершину стека. Так, если вы введете CR 256 6 .R то увидите 256 (123456) (цифры в скобках не будут напечатаны, они приводятся, чтобы показать позицию печатаемого числа 256 в поле, отведенном для его вывода). Число 256 сдвигается на три позиции вправо и выводится своим последним разрядом в последней позиции б-местного поля. Если вы напечатали CR 256 8 ,R то увидите 256 (12345678) Это значит, что число будет выровнено вправо в поле длиной 8 мест. Пусть теперь вы вводите CR 211 10 .R -5 10 .R 23 10 .R CR 2 10 .R -231 10 .R 256 10 .R CR
Во всех случаях поле будет иметь ширину 10 позиций, выводимые числа будут выровнены вправо в этом поле, и вы увидите 211 -5 23 2 -231 256
Каждое слово CR начинает вывод с новой строки, и каждое число представлено сдвинутым до предела вправо в поле из 10 мест. В результате получается очень аккуратная таблица. Обратите внимание, что знаки "-" отрицательных чисел были учтены. Имейте в виду, что в отличие от. (точки) слово.R не выводит пробел после напечатанного числа. Поэтому CR 256 6.R 9 выдаст на экран: 2569 (1234567)
Слово.R не оговорено стандартами Форта, но нам не известна ни одна версия языка, в которой оно, равно как и слово D.R, отсутствовало бы. Во многих версиях Форта есть еще набор слов, как, например U.R, которое печатает числа одинарной длины без знака с выравниванием вправо. Предполагается, что всем этим словам должна предшествовать на вершине стека длина поля в виде числа одинарной длины, а ей должно предшествовать число, которое нужно напечатать как в случае чисел одинарной, так и двойной длины.
Если в вашем Форте нет слова U.R, то вскоре мы определим его вместе с другими полезными словами.
Форматный вывод чисел
Форматный вывод чисел (вывод по шаблону) наиболее сложный и все же наиболее гибкий способ отображения чисел на экране дисплея или печати на принтере. Он используется также для определения таких слов, как. (точка) и.R, и позволяет вводить в число, например, знак денежной единицы, десятичную точку, предшествующие незначащие нули, производить выравнивание в поле и т.д. Слова форматного вывода фактически преобразуют числа в последовательность кодов ASCII в форме, удобной для вывода на экран дисплея с помощью слова TYPE. Форматный вывод работает только с числами двойной длины. Чтобы применить его к числам одинарной длины, последние необходимо преобразовать в двойные.
Процесс форматного вывода начинается словом . Между этими двумя словами могут быть какие-то другие слова, описывающие, как должно выполняться форматирование. Слово #> оставляет в стеке адрес символьной строки и число символов в ней, подготавливая вывод для слова TYPE. Простейшее форматирование производит слово #S, которое преобразует все разряды числа в символьную строку. Приведем определение слова UD., которое, как ни удивительно, не включено во многие версии Форта: : UD. ( d - ) ( - адр счет ) (Завершает преобразование, помещает в стек адрес и значение счетчика) TYPE ( - ) (Выводит строку на экран) SPACE (Вставляет после числа пробел) ; (Заканчивает определение)
А как обращаться с числами одинарной длины? Достаточно преобразовать их в числа двойной длины. Для этого предусмотрено слово S-D. Чтобы понять его, вспомните, как представляется в стеке отрицательное число: : S-D ( n -- d) DUP 0< IF -1 ELSE 0 THEN ; (одинарное_в_двойное) Еще более изящно определяется это слово в Форт-79 : : S-D (n - d) DUP 0< NEGATE ; Если число одинарной длины в стеке отрицательное, то слово 0< возвращает 1, которую слово NEGATE превращает в -1 в качестве старшей половины числа двойной длины.
В случае, если число положительное, 0< возвращает 0, который словом NEGATE никак не изменяется. Определением слова S-D в Форт-83 будет : S-D ( n - d) DUP 0< ; поскольку в Форт-83 0< возвращает -1, если число отрицательное, или 0 в противном случае (см. гл. 7).
Приведем определение слова U. : : U. ( n -- ) S-D TYPE SPACE ;
Так как слово #> возвращает число символов в символьной строке, мы можем определить также слово UD.R: : UD.R ( d n - ) (Печать_беэ_энака_с_выравниванием_вправо) ROT ROT ( n d ) (Помещает значение длины поля на дно стека) ( n адр счет ) (Форматирует выводимое число) ROT OVER ( адр счет п счет ) - ( адр счет пробелы ) (Помещает перед числом пробелы) SPACES ( адр счет ) (Перемещает курсор на число пробелов) TYPE ( ) (Выводит число) ; (Конец определения)
А вот определение для U.R : ; U.R ( n n --) SWAP S-D ROT OVER - SPACES TYPE ;
Вы можете представить себе, как оно работает по аналогии с UD.R. Понимаете ли вы, почему лучше было бы определить некоторое слово, которое использовалось бы как для определения слова U.R, так и UD.R?
Как это ни удивительно, U.R, UD.R и UD. не включены в большинство версий Форта. Возможно, вы пожелаете добавить их к своему Форту.
Как быть с печатью чисел со знаком7 Для них нам потребуется слово SIGN (знак), которое вставляет знак - (минус) в символьную строку, если число, находящееся на вершине стека, отрицательное. Если вы напечатали 1245. TYPE на экране появится число -1245.
Более полезно определение слова D., которое позволяет выводить числа двойной длины со знаком. : D. ( d - ) SWAP ( мл# ст# -- ст# мл# ) OVER ( ст# мл# -- ст# мл# ст# ) DABS (Берет из стека абсолютное значение двойного числа, оставляя старшую часть исходного числа на дне стека, включая знак) (Заканчивает преобразование) TYPE SPACE (Выводит число) ; (Конец определения)
Почему слово SIGN применяется после #S, а не перед ним? Потому что строка символов при форматировании получается в результате просмотра справа налево. Это значит, что первым преобразуется наименее значащий (младший) разряд, поэтому после исполнения слова #S к строке символов будет добавлен символ знака перед наиболее значащим (старшим) разрядом, где ему и следует быть.
Обратите внимание, что перед SIGN необходимо слово ROT, потому что слово #S не удаляет число из стека, а заменяет его числом 0 двойной длины. Процедуры форматного вывода еще более неудобны для чисел- со знаком. Поэтому для большего удобства желательно определить еще два новых слова: : TYPE SPACE : Для полноты дадим еще определение слова . (точка) : ; . ( n -) S-D TYPE SPACE ;
Как оно работает, должно быть понятно. В большинстве версий Форта. (точка) определена как операция форматного вывода, т.е. очень близко к этому определению- Мы обещали еще научить вас выводить числа в формате с десятичной точкой и символами денежных единиц. Это делается с помощью слова HOLD, которое помещает символ ASCII, заданный его кодом, в символьную строку. Так, например, 12.34 TYPE выведет на экран $1234. Или 12.34 TYPE представит на экране 1234%. Снова обратим внимание на то, что строка формируется в обратном порядке, т.е. в конец помещается символ, встречающийся первым, и наоборот.
Мы также обещали, что вы сможете вводить разделители, например десятичную точку, в выводимое число. Чтобы сделать это, вам потребуется другое слово #. Слово # выталкивает разряды числа по одному справа налево и помещает их в выходную строку. После того как группа разрядов преобразована, можно использовать #S для завершения преобразования. Попробуйте ввести 1234. TYPE и на экране должно появиться $12.34. Цифры 4 и 3 были помещены в выходную строку двумя словами #, операция 46 HOLD поместила в строку десятичную точку, слово #S поместило в строку оставшиеся два разряда 12, а 36 HOLD на вершину строки помещает знак денежной единицы $. Снова обратите внимание, что процесс форматного преобразования происходит справа налево.
Теперь вы можете самостоятельно определить слово для представления чисел в долларах и центах, и так как мы показали, что оно должно отображать числа со знаком, то с помощью этого слова вы сможете записывать долги: : D$. ;
Можно определить другие слова для более сложного вывода.
Здесь приведены все средства форматного вывода.
Фактически форматирующими словами являются только #S, SIGN, HOLD и #, но, как вы увидите из упражнений, они позволяют решать довольно сложные задачи форматного вывода. Следует напомнить, что между могут использоваться любые "правильные" слова Форта. Прежде чем перейти к упражнениям, небезынтересно рассмотреть, как работает слово #. Вот его определение: мы предполагаем, что в стек положено число двойной длины 123, а содержимое стека представлено в десятичной системе, чтобы комментарии были более понятными : : # (123 0) (Начало определения) BASE @ (123 0 10) (Извлекает основание) S-D (123 0 10 0) (Преобразует в число двойной длины) D/MOD ( 3 0 12 0) (Остаток - это последняя цифра. частное - остальная (непреобразованная) часть числа) ROT DROP (3 12 0) (Превращает остаток в одинарное число) ROT (12 О 3) (Помещает на вершину последний разряд) 48 + (12 0 51) (Добавление 48 преобразует число в код ASCII) HOLD (12 0) (Помещает в строку код ASCII) ; (12 0) (Конец определения)
Процесс состоит в отделении разрядов от числа с конца (младшего разряда) по одному с помощью операции D/MOD, преобразовании каждого из них в код ASCII путем добавления десятичного числа 48 и, наконец, помещении кодов ASCII в символьную строку словом HOLD. Слово #S можно определить через слово #, используя цикл, прекращающийся, когда частное станет равным нулю; в этот момент все разряды числа будут отделены. Приводимой слово может выполнить все указанные функции : : #S BEGIN OVER WHILE # REPEAT ;
Поскольку вы еще не изучили конструкцию BEGIN..WHILE...REPEAT, это определение может вам показаться или не показаться лишенным смысла, но главное, что делает #S: - повторяет операцию # до тех пор, пока возвращаемое число двойной длины не станет равным нулю. Материал гл. 8 сделает эту конструкцию более понятной.
Упражнения
1. Определите слово UD$. для печати чисел двойной длины без знака в формате долларов и центов, т.е. 1234. UD$. должно давать на экране $12.34. 2. Определите слово US$. для печати чисел одинарной длины в долларах и центах. 3.
Определите слово S$. для печати чисел одинарной длины со знаком в долларах и центах (включая, конечно, отрицательные числа). 4. Определите слово S$.R для печати чисел одинарной длины со знаком в долларах и центах, выровненных вправо в поле. длина которого находится в стеке, и работающее как R.. 5. Определите слово.L для печати чисел, выровненных влево. Числа со знаком, если таковые присутствуют, должны быть сдвинуты влево, но курсор, который показывает позицию, куда должно помещать следующее число, должен переместиться в поле вправо. Это значит, что после числа должно следовать некоторое количество пробелов. 6. Определите слово .DATE для форматного вывода даты. Так, 122388. DATE н а экране должно напечатать 12/23/88. 7. Определите слово .MDY, подобное .DATE, для печати даты в формате m. 12 d. 23 у. 88 месяц 12 день 23 год 88 8. Определите слово.PHONE (телефон), которое при вводе 824 959 2345. будет выводить номер телефона в формате (824) 959-2345 9. Переменная #РТ в MMSFORTH содержит в себе указатель места десятичной точки в последнем введенном числе двойной длины (считая справа налево). Определите слово FL. для печати числа с десятичной точкой в том же месте, что и во введенном числе. Так, при вводе 12.34 FL. на выходе должно быть напечатано 12.34 , в то время как при вводе 1.234 FL. должно получиться 1.234. Воспользуйтесь счетным циклом DO-LOOP между .
Ввод с клавиатуры
В Форте предусмотрено несколько слов для управления вводом с терминала. Некоторые из них стандартные, другие - расширяющие возможности языка. Здесь мы рассмотрим наиболее простые из этих слов.В Форте имеются достаточно мощные средства для определения других слов, управляющих вводом, однако вам будет проще с ними разобраться после того, как мы рассмотрим символьные строки в гл. 9. Дальнейшее изложение имеет целью дать вам необходимый на данной ступени минимум.
Со словом KEY мы познакомились раньше. После ввода этого слова программа приостанавливается, пока не будет нажата какая-либо клавиша, затем код ASCII выбранного символа помещается в стек.
Слово KEY удобно для того, чтобы выяснить код ASCII нажатой клавиши [ попробуйте ввести KEY. ]; но используется оно чаще всего для ввода одной литеры ответа на вопрос, тогда в зависимости от значения кода клавиши программа будет выполнять какие-либо действия. В MMSFORTH имеется родственное слову KEY слово и ?KEY. Оно обычно используется в цикле. Если ни одна из клавиш не нажата при очередном исполнении ?KEY в цикле, в стек кладется 0, если клавиша нажата, то в стек кладется значение кода ASCII этой клавиши. Таким образом программа выполняет какую-то задачу, пока не будет нажата клавиша, с этого момента программа будет выполнять другую задачу, в зависимости от кода клавиши. Как KEY, так и ?KEY позволяют осуществить выбор вариантов исполнения программы, но в то время как KEY ожидает ввода, слово ?KEY позволяет продолжать исполнение программы, пока не будет нажата клавиша. Очевидно, слово KEY может быть использовано в цикле для ввода строк символов, но более удобным для этой цели является слово EXPECT. Для него необходимо, чтобы в стеке был адрес, начиная с которого будет запоминаться символьная строка, и вторым в стеке - число символов, которое "ожидается". Когда встречается слово EXPECT, исполнение программы приостанавливается либо до ввода ожидаемого числа символов, либо до нажатия клавиши . Испытайте действие приведенного ниже слова : : WHATWORD ." Какое Слово ? " PAD 10 EXPECT ;
Когда вы введете WHATWORD, исполнение программы приостановится после печати на экране вопроса "What is the word?" "(Какое слово?). Теперь введите 10 букв, и, когда вы введете десятую букву, исполнение программы продолжится. Теперь введите PAD 10 TYPE и вы увидите те 10 букв, которые были записаны, начиная с адреса PAD. А теперь введите с клавиатуры 5 символов и после этого нажмите , затем введите 10 PAD TYPE. Вы увидите 5 напечатанных вами символов. То, что будет происходить далее, зависит от версии Форта, с которой вы работаете. Во всяком случае, прекращает действие слова EXPECT, т.е.
ввод.
Слово EXPECT имеет дополнительные возможности, о которых вы узнаете из гл. 9. В МMSFORTH имеется слово IN$, основанное на слове EXPECT, которое имеет аналогичное действие. Это слово ничего не ожидает из стека и помещает все, что было введено с клавиатуры, в строку со счетчиком, начиная с адреса PAD, до нажатия клавиши , оставляя в стеке адрес PAD. введите IN$, потом , а затем строку символов и снова . Теперь если вы наберете COUNT TYPE, то увидите строку, которую ввели. Адрес PAD был оставлен в стеке словом IN$, слово COUNT кладет в стек длину строки, и TYPE печатает ее. Нестандартные слова IN$ и ему подобные являются основными средствами для ввода текста в программу. Вы многое сделаете с их помощью в главе, посвященной литерным строкам.
До сих пор мы рассматривали только ввод и вывод литер и литерных строк. А как быть с числами? Конечно, можно записать их в стек с клавиатуры, перед тем как будет исполнено какое-либо слово или начнет работать программа. Ну а что делать, если числа нужно вводить во время исполнения программы? Это одна из наиболее слабых сторон стандарта Форт или по крайней мере одно из неудобств для программиста. К счастью, в большинстве версий эта проблема решается с помощью специальных слов. В MMSFORTH такими словами служат #IN и D#IN. Оба они приостанавливают исполнение программы и печатают на экране знак вопроса ?. Вводимые с клавиатуры до нажатия клавиши символы воспринимаются затем как число либо одинарной, либо двойной длины, в зависимости от слова, и помещаются в стек. Если используются недопустимые символы или число имеет недопустимую длину, появляется сообщение "Redo" (повторите), разрешая повторение ввода числа.
Попробуем ввести следующее определение : : TEST ." Какое число " #IN ." Вы ввели " . ;
Если в вашей версии слова #IN или эквивалентного ему нет, потребуется некоторая изобретательность. Прием состоит в том, чтобы ввести литерную строку и затем преобразовать ее в число, используя стандартное слово CONVERT (в некоторых версиях имеется эквивалентное ему слово >BINARY).
Достаточно мощное слово CONVERT работает только с числами одинарной длины без знака, и применение его вызывает некоторые трудности. Вы еще убедитесь в больших возможностях слова CONVERT в гл. 9.
Выводы
Проблемы ввода и вывода показывают суть замысла стандартов языка Форт. Они предназначены для обеспечения минимально необходимых средств для конструирования того, что вам может потребоваться, но не оговаривают все детали и свойства, которые вы бы хотели иметь. Например, слова форматного вывода, такие как EXPECT и CONVERT, в свою очередь, можно использовать для определения слов типа UD.R, $IN и #IN. Форт подвергается критике за небогатые возможности вво да-вывода, и, что касается стандартов, - это справедливо. Однако не представляет сложности добавить необходимые вам возможности, и в большинстве коммерческих реализациях языка они предусмотрены. Вам нужно внимательно ознакомиться с документацией на тот вариант языка, которым вы располагаете, чтобы узнать, что он может делать. Кроме того, в гл. 9 мы приведем определение еще нескольких полезных слов.
Хранение чисел в памяти
До сих пор числа, которые использовались Форт-программами, хранились в стеке. Если вы поработали с такими языками программирования, как Бейсик, фортран или Паскаль, это покажется вам достаточно странным, большинство языков не используют стек непосредственно, и числа в них хранятся в переменных. Переменные представляют собой ячейки памяти, которые используются для хранения чисел, в большинстве языков предусматриваются размещение и автоматические манипуляции с числами в памяти. При этом стек фактически также используется, но программисту не приходится им управлять. Рассмотрим следующую программу на Бейсике: 10 А=5 20 В=6 30 А=А+В 40 PRINT AЧисло 5 запоминается в ячейке памяти, обозначенной как переменная А, число 6 - в ячейке, отведенной для переменной В. Когда значения А и В складываются, они на самом деле выбираются из памяти, складываются почти так же, как в Форте, а затем результат снова помещается в ячейку, отведенную для переменной А. Хотя стек, может быть, и использовался для операции сложения, программист и не подозревает об этом. Программа на Форте напечатает сумму чисел 5 и 6 следующим образом: 5 6 + .
Все действия будут выполнены в стеке, в данном случае пользователю Форта не обязательно даже знать что-либо о переменных. Программирующий на Бейсике может привести доводы за использование переменных: программисту не приходится следить, где находится число; в то же время программирующий на Форте будет возражать, что для переменных требуется больше места в памяти, обращение с ними требует больших затрат машинного времени и текст программы получается более длинным. Обе стороны правы, и, конечно же, любая большая программа где-то должна хранить числа, если они в данный момент не используются. К счастью, Форт тоже позволяет вам пользоваться переменными (и соответствующими методами хранения чисел), если вы предпочитаете их или они вам необходимы.
Какими же достоинствами обладают переменные, если вы можете обходиться стеком? Переменные служат для трех целей.
Во-первых, если у вас имеется большое количество данных, которые нужно где-то сохранить, пока они не потребуются программе. Во-вторых, с целью упрощения операций в стеке путем запоминания промежуточных результатов в виде переменных, а не в самом стеке. Наконец, в третьих, для присваивания с помощью переменных имени числу или группе чисел. Так же как переменные в алгебраических уравнениях, именованные переменные позволяют пользоваться математическими абстракциями, с помощью которых облегчается формализация алгоритма программы, а написанную программу проще понять. Если у вас есть переменные с именами SECONDS (секунды) и MINUTES (минуты), то гораздо проще помнить, что представляют собой эти числа, чем безымянные числа в стеке.
В более общей формулировке переменная представляет собой наименованную ячейку (адрес) памяти, которая может содержать число. Переменная в Форте- это слово, которое возвращает в стек адрес, по которому может храниться число. Для перемещения содержимого ячеек памяти, имеющих имя, в стек и обратно можно использовать слова @ (взять) и ! (занести), которые вам уже известны из гл.З. Мы рассмотрим сначала основные (но не самые удобные) способы для выполнения этих операций, а затем перейдем к более рациональным методам.
Создание переменных
Где можно надежно хранить переменные в памяти так, чтобы они случайным образом не изменились? Мы уже знаем, что Форт отводит часть памяти для своего словаря, в котором содержатся названия слов и их определения (в том смысле, что определения сообщают машине, какие операции она должна делать при исполнении каждого слова). Мы имеем возможность создавать в словаре слова, которые содержат числа. Когда такое слово исполняется, то единственное, что оно должно делать,- это помещать в стек адрес числа, которое оно содержит, что обеспечивает доступ к нему с помощью операторов ! и @.
Слово CREATE (создать) помещает на вершину словаря имя переменной и еще некоторую информацию (которая, например позволяет Форту искать в словаре другие слова).
Имя и эта информация представляют собой последовательность из нескольких байтов, называемую заголовком указанного слова. Когда исполняется слово, определенное словом CREATE, то в стек кладется адрес, следующий непосредственно после адреса заголовка. Слово ALLOT (от ALLOcaTe- разместить) резервирует определенное число байтов в словаре. Например, CREATE INCHES 2 ALLOT создает в словаре слово с именем INCHES (дюймы) и резервирует, или размещает, 2 байта, в которых может быть помещено слово одинарной длины. Если вы введете 12 INCHES ! слово INCHES положит в стек зарезервированный адрес, а ! запишет в него число 12. INCHES @ . затем возвратит число 12 в стек и напечатает его. Это так просто. Между прочим, слово CREATE имеет более сложные и изощренные применения, которые будут описаны в гл.14,
В этом месте мы совершим небольшое отвлечение в сторону, чтобы познакомиться с некоторыми словами для перемещения чисел между памятью и стеком. Для всех таких слов предварительно нужно задать адрес в памяти, с которым они работают. Вам уже знакомы слова @, !, С@ и С!. Слова 2@ и 2! соответственно извлекают и запоминают числа двойной длины. В некоторых версиях и в MMSFORTH определены также слова 4@ и 4! для работы с 4-байтовыми числами, с помощью которых представляются числа с плавающей запятой. Несколько отличаются слова ? и +!. Слово ? (обязательное в Форт-79, но не включенное в Форт-83) может быть определено просто как : ? ( addr - ) @ . ;
Оно извлекает число из ячейки памяти и печатает его на экране. Аналогично можно определить слово С? (имеющееся в MMSFORTH и других версиях) для извлечения и печати одного байта. Стандартное слово +! добавляет второе число из стека к числу, содержащемуся в ячейке с адресом, который находится на вершине стека. Так, если в ячейке памяти с адресом 22345 содержится число 250, то 5 22345 +! изменит содержимое на 255. Это равносильно следующим действиям : 22345 @ 5 + 22345 ! но выполняется значительно быстрее. Очевидно, что с помощью ALLOT можно резервировать место для размещения более двух байтов.
CREATE INCHES 4 ALLOT отводит место для хранения одного двойного слова, доступного с помощью слов 2! и 2@. Впрочем, более важно то, что в отведенном (резервированном) месте могут храниться несколько чисел, входящих в одно слово. Если выполнить CREATE YARDS 10 ALLOT будет зарезервировано место, достаточное для хранения пяти одинарных слов (10 байтов). Пусть теперь вы определили слова : !YARDS 2* YARDS + ! ; и : @YARDS 2* YARDS + @ ;
Тогда если ввести 23 2 !YARDS то в четвертом и пятом байтах будет запомнено число 23 (если принять нумерацию с нулевого байта), зарезервированное словом YARDS. Операция 2 @YARDS возвращает в стек число 23. Другими словами, вы можете зарезервировать место для хранения последовательности или списка чисел и затем определить слова, с помощью которых к ним можно обращаться. Такой список чисел называется массивом. Массив 1,3,45,671,23, каждое число которого называют элементом, называется одномерным, линейным массивом или вектором. Можно хранить массив в слове YARDS следующим образом: 1 0 !YARDS 3 1 !YARDS 45 2 !YARDS 671 3 !YARDS 23 4 !YARDS а обращаться к числу можно с помощью слова @YARDS. Так, 3 @YARDS . напечатает 671. По некоторым причинам многие считают массивы чем-то сложным и непонятным, на самом деле это просто список чисел и ничего больше.
Слово , (запятая) упрощает создание переменных и массивов. Оно резервирует два байта так же, как 2 ALLOT, а затем запоминает число, находящееся на вершине стека, в отведенных двух байтах. Поэтому CREATE INCHES 7 , будет эквивалентно CREATE INCHES 2 ALLOT INCHES 7 !
В обоих случаях будет создана переменная INCHES, которой будет присвоено значение 7. Слово (запятая) особенно полезно при определении массива. CREATE YARDS 1 , 3 , 45 , 671 , 23 , создает массив YARDS и инициализирует его содержимое гораздо проще, чем было показано раньше. В особенности оно полезно для создания таблиц данных, которые не должны изменяться. Наконец упомянем определенное во многих версиях Форта, и в том числе в MMSFORTH, слово С,.
Оно выполняет то же, что и , (запятая), но резервирует место и запоминает число в диапазоне 0- 255 только в одном байте. (Некоторые процессоры имеют адресацию памяти, не допускающую использование слова С,.)
Упражнения
1. Создайте две переменные, FEET (футы) и INCHES (дюймы). Теперь определите слово F->Т для преобразования числа из FEET в число, выраженное в дюймах, результат должен помещаться в переменную INCHES. 2. Проделайте упражнение 1 для чисел двойной длины. 3. Дайте определение слова 2!, назвав его NEW2! , которое действовало бы как ! , но с числами двойной длины. 4. Дайте новое определение слова +! с именем NEW+!. (В Форте для ускорения оно определено в машинных кодах.) 5. Используя С@, дайте другое (но более медленно работающее) определение слова @, назвав его NEW@. 6. По аналогии определите слово NEW!, используя С! . 7. Определите слово VARSWAP (переставить переменные), которое должно переставлять содержимое двух переменных, адреса которых находятся в стеке. Тогда INCHES FEET VARSWAP занесет содержимое переменной INCHES по адресу переменной FEET и наоборот. (Указание: используйте PAD.) 8. Определите два массива, из 7 элементов каждый, 1WEEK (первая неделя) и 2WEEK (вторая неделя), пользуясь словом , (запятая) для инициализации всех элементов нулями. 9. Определите семь слов !SUN(!BCKp), !МОN(!пнд), !TUE(!втр) и т.д. так, что, если им предшествует число (например, сумма дневной выручки) и имя недели, они записывали бы это число в соответствующий элемент. Таким образом, 5 1WEEK !TUE запомнит число 5 во втором элементе (вторник) массива 1WEEK (первая неделя). 10. Напишите слова, соответствующие словам упражнения 9, которые способны извлекать содержимое переменных и класть их в стек. 11. Определите слово ESWAP (переставить элементы), которое должно переставлять два элемента именованного массива. Например, 1 3 1WEEK ESWAP должно переставить значения для понедельника (MONday) и среды (WEdnesday) массива 1WEEK (первая_неделя). Используйте слово VARSWAP. 12.
Создайте счетный массив однобайтовых элементов CNT (счетчик), в котором должно записываться, сколько раз числа запоминаются в различных элементах массивов 1WEEK и 2WEEK. 13. Определите слова +SUN, +MON и т.д., которые должны добавлять числа к соответствующим элементам именованных массивов. Так, например, 7 1WEEK +THUR должно добавить 7 к четвертому (THURsday- четверг) элементу массива 1WEEK. Эти слова должны также добавлять по единице к соответствующему элементу счетчика CNT. 14. Предположим, что все элементы массивов 1WEEK и 2WEEK были инициализированы нулями и что изменение их содержимого было произведено только с помощью операций +MON, +TUE и т.д. Тогда напишите слово DAY-AVE (сред-нее_за_день), которое выдавало бы в стек среднее значение чисел, добавленных к конкретным элементам массивов 1WEEK и 2WEEK. Так, если элемент 3 из массива 1WEEK содержит 20, элемент 3 из массива 2WEEK содержит 30, а элемент 3 массива CNT содержит 5, то выражение 3 DAY-AVE должно выдать в стек 10.
Вы написали простую, но в то же время достаточно хитроумную программу, заслуживающую внимательного рассмотрения производимых ею действий. Набор задач был составлен так, чтобы в конечном счете получилась программа, которая определяет средние значения выручки за определенный день недели по двум неделям, причем как ежедневные поступления, так и итоги за каждый день хранятся в двух массивах. В этом состояла наша цель. В большинстве языков программирования, чтобы добиться поставленной цели, надо написать программу со всеми деталями и отладить ее как единое целое. На Форте можно написать отдельные программы для каждой задачи и проверить их по отдельности. Разработка и написание программы целиком со всеми деталями чаще всего по блок-схеме называется программированием сверху вниз, и некоторые считают такую методику обязательной. В противоположность этому Форт позволяет разрабатывать программу более гибко и экспериментировать с ней, и вследствие этого каждое слово или задача программируются и проверяются отдельно, чтобы решить некоторую часть общей задачи.
В самом деле, вам не нужно было знать, приступая к упражнениям, какова их конечная цель.
Перемещение и заполнение содержимого массивов
Предположим, что имеется два массива по 20 элементов каждый, 1DATA и 2DATA, и нужно сделать так, чтобы содержимое массива 1DATA было равно содержимому массива 2DATA. Можно проделать это следующим образом: 1DATA 2DATA 20 CMOVE
Слово CMOVE ожидает, что в стеке должно быть два адреса и число (адр1 адр2 n -- ), тогда оно перешлет n байтов, начиная с адреса адр1, на адрес адр2. Причем сделает это очень быстро. Первая буква в слове CMOVE ассоциируется со словом character, т.е. литера, поскольку это слово часто используется для пересылки байтов, представляющих литеры в литерных строках. Слово MOVE (переместить), которое имеется в Форт-79 и большинстве реализации, но отсутствует в Форт-83, действует так же, но пересылает указанное число ячеек, а не байтов. Таким образом, 10 MOVE действует так же, как 20 CMOVE
Слово CMOVE обрабатывает байт за байтом. Это значит, что байт из ячейки с адресом адр1 переносится в ячейку с адресом адр2, байт из ячейки с адресом адр1+1 переносится в адр2+1 и т.д. Теперь рассмотрим случай,.когда адр2 находится между адр1 и адр1+n, т.е. область, в которую производится копирование, перекрывается с областью, откуда производится копирование. Теперь допустим, что первый байт в массиве 1DATA равен 219. Если напечатать 1DATA 1DATA 1+ 19 CMOVE то число 219 будет скопировано из ячейки с адресом 0 в ячейку с адресом 1, затем из ячейки 1 в ячейку 2, затем из 2 в 3 и, наконец, из ячейки 18 в ячейку 19. Другими словами, массив 1DATA будет целиком заполнен байтами со значением 219. А теперь предположим, что нужно установить значения всех элементов массива равными 0. Можно сделать это с помощью CMOVE. Вам нужно просто установить первый элемент массива равным 0, а затем скопировать его во все остальные байты массива. Вот необходимая последовательность действий: 0 1DATA С! 1DATA 1DATA 1+ 19 CMOVE
Действие CMOVE, в данном случае неправильное, можно наглядно показать на диаграмме.
Предположим, что слово CMOVE применяется к 4 байтам, содержащим в начале числа 10,11,12,13, и оно перемещает байт с адресом 0 на адрес 1 и т.д. Процесс будет происходить так, как показано на диаграмме 10 11 12 13 10 10 12 13 10 10 10 13 10 10 10 10
А нам нужно, чтобы из байта с адресом 1 число переместилось в ячейку 2; исходное число байта с адресом 2- в ячейку с адресом 3 и т.д. без наложения байтов друг на друга. 'Это значит, что из начального массива 10 11 12 13 должен в конце получиться массив 10 10 11 12 13. Слово, которое позволяет сделать это, называется (в Форт-79 оно необязательное). Слово
В данном случае перемещение элементов массива производится байт за байтом, но наложения не происходит. Очевидно, слово
Имеется также слово FILL (заполнить), которое заполняет некоторую область памяти байтами с указанным значением. Также 1DATA 20 0 FILL заполнит 20 байтов нулями, начиная с адреса 1DATA. Слово FILL- используется, чтобы занести число в определенное число байтов, начиная с некоторого известного адреса. В MMSFORTH и некоторых версиях есть еще слово ERASE (стереть), которое заполняет память последовательностью из нулей.
Таким образом, программа для предыдущего примера эквивалентна следующей: 1DATA 20 ERASE
Еще одно слово в MMSFORTH BLANK (пробел) заполняет область памяти кодами ASCII "пробел". Это равносильно 32 FILL
Очевидно вы сами можете дополнить приведенные слова собственными, имеющими специфическое назначение.
Упражнения
1. Определите слово FILL (назовите его NEWFILL), используя слово CMOVE. 2. Определите слово ERASE (назовите его NEWERASE), используя слово FILL. 3. Определите слово INITIALIZE (инициализировать), чтобы установить все элементы массива чисел одинарной длины в нуль, т.е. 1DATA 7 INITIALIZE должно установить все семь элементов массива 1DATA в нуль. Определение тривиально. 4. Напишите слово ARR-COPY (копировать массив), которое бы копировало содержимое массива одинарных чисел длиной п элементов в другой массив такой же длины, т.е. 1DATA 2DATA 7 ARR-COPY должно скопировать массив 1DATA в массив 2DATA.
Дайте два определения, используя как CMOVE, так и MOVE. 5. Напишите слово ARR_EXCH (копировать массив), которое производит обмен содержимого двух массивов одинарных чисел размерности n. Таким образом, 1DATA 2DATA 7 ARR-EXCH перенесет массив 1DATA в 2DATA и наоборот. (Указание: воcпользуйтесь словами PAD и MOVE. Возможно, потребуется временно хранить где-либо количество чисел.)
Переменная, константа и связанные с ними слова
До сих пор в этой главе мы рассказывали вам о создании переменных окольным путем. Это делалось для того, чтобы вы привыкли думать о переменных как ячейках памяти, что представляется неудобным, если вы знакомы с другими языками программирования. Существуют, однако, болей простые средства для обращения с переменными и массивами.
Стандартное слово VARIABLE (переменная) применяется для определения имени переменной и резервирования по ее значение двух байтов. Таким образом VARIABLE INCHES Производит те же действия, что и CREATE INCHES 2 ALLOT но немного удобнее, что более важно: применение слова VARIABLE в программе вместо CREATE делает ее более удобочитаемой и понятной. С переменной, которая определена, действуют так же, как с переменной, созданной словом CREATE. (Напомним снова, что слово CREATE включено в словарь Форта не только для того, чтобы создавать переменные, массивы и т.д. Оно также служит и для других целей.)
Для обращения с двойными числами имеется стандартное слово 2VARIABLE. В некоторых реализациях Форта есть другие разновидности слова VARIABLE, например CVARIABLE для хранения байтов, 4VARIABLE для переменных с плавающей запятой. Вам должно, быть понятно, что собственно слово VARIABLE (переменная) определяется следующим образом: : VARIABLE CREATE 2 ALLOT ;
Эта конструкция работает потому, что слово CREATE создает переменную при исполнении, а не при компиляции, т.е. не во время добавления определяемого слова VARIABLE к словарю. Массивы также могут определяться словом VARIABLE. Если ввести с клавиатуры VARIABLE INCHES 8 ALLOT то это будет равносильно CREATE INCHES 10 ALLOT причем вместо 8 используется 10, потому что два байта уже были резервированы словом VARIABLE.
С массивами, определенными любым из двух методов, можно обращаться одинаково.
Мы видим, что определение массивов как переменной словом VAMABLE несколько обескураживает. Оно не имеет никаких преимуществ перед использованием слова CREATE и, кроме того, не делает различия между переменной и массивом, а программа воспринимается труднее.
Похожим на слово VARIABLE является слово CONSTANT (константа). Оно используется для хранения таких чисел, которые внутри программы не будут изменяться (хотя иногда это правило нарушается). Для него требуется число в стеке, а после слова CONSTANT - имя слова, в котором будет храниться это число. При исполнении слова в стек кладется его содержимое. Поэтому после cлова, определенного как константа через слово CONSTANT, не требуется операция @ (извлечение содержимого). Например, если определить 5280 CONSTANT FT/MILE то при исполнении слова FT/MILE в стек будет помещено число 5280. Слова, определенные с использованием слова CONSTANT, лучше всего применять для таких чисел, как коэффициенты пересчета единиц измерения, константы уравнений. Конечно, с таким же успехом можно ввести число непосредственно в программу, но если программа подвергается изменениям, тогда каждое вхождение этого числа также должно быть изменено, в то время как при использовании константы число потребуется изменить только однократно. Кроме того, использование значащего слова вместо числа способствует облегчению понимания программы. Если вы знакомы с языком Бейсик, то можете подметить, что идея константы в языке Форт несколько отличается. В Бейсике константа обычно рассматривается как число, включенное в выражение, например 56.5 в строке 90 А = 56.5 * В
В языке Форт константа- это именованная ячейка памяти, фактически это разновидность переменной, которая посылает в стек не свой адрес, а содержимое.
Для переменных двойной длины в стандарте Форта предусматривается слово 2CONSTANT, во многих версиях (в том числе в MMS.FORTH) имеются слова CCONSTANT (однобайтовая константа) и 4CONSTANT для чисел с плавающей запятой.
Смысл назначения константы- определять величину, которая остается неизменной в программе, теряется, если константа изменяется. Вы должны иметь возможность узнать значение константы из ее определения, не прибегая к поиску того места, где она, изменилась. Это абсолютно обязательно, если Форт-программа должна быть записана в ПЗУ. Поэтому в большинстве случаев константа не должна изменяться. Но иногда бывают случаи, когда необходимо изменить константу. Один из них возникает при отладке программы, другой случай, когда константа по-разному используется в основной части программы и в другой. Наиболее важный случай, когда константа может быть изменена, если таким путем достигается сокращение времени. Для помещения константы в стек требуется несколько меньше времени, чем для извлечения значения переменной, и это нужно иметь в виду, если время исполнения программы для вас становится критичным. Поэтому вам следует знать, как изменить значение константы. В стандарте Форт-79 и большинстве версий, не придерживающихся стандарта Форт-83, обязательным является слово ' (произносится "тик"), которое кладет в стек адрес содержимого слова. Так, * FT/MILE выдает, адрес ячейки памяти, где хранится число под именем FT/MILE, будь оно константой или переменной. Поэтому для изменения значения константы единственное, что нужно сделать,- это поменять содержимое ячейки по этому адресу, например нужно ввести 6076 ' FT/MILE ! чтобы изменить сухопутные мили на морские. Теперь, если ввести FT/MILE, в стек будет заслано число 6076. (Заметьте, что точно такая же процедура (с излишними издержками) может применяться для изменения значения переменной.
В стандарте Форт-83 процедура сложнее, так как слово ' возвращает не адрес содержимого указанного слова, а другой адрес, связанный с этим словом, что может привести к путанице (вскоре вы поймете, что этот адрес полезен, но не для изменения константы). Однако если адрес, извлекаемый словом ', в стандарте Форт-83 известен, то слово >BODY даст нужный нам адрес, т.е.
тот, который извлекается в стандарте Форт-79 словом '. Таким образом, при вводе 6076 ' FT/MILE >BODY ! в стандарте Форт-83 мы получим то же самое, что при вводе 6076 ' FT/MILE ! в стандарте Форт-79. Дополнительное усложнение в Форт-83 состоит в том, что слово ' работает, как описано, только вне определения слов через двоеточие, т.е. оно является немедленно исполняемым. Все хорошо, если вы. хотите изменить константу, когда некоторая часть программы загружена.
Но если необходимо определить слово для изменения константы через двоеточие, то вместо слова ' нужно пользоваться словом [']. Так, например. : MAKE-NAUTICAL 6076 ['] FT/MILE >BOOY ! ; это определение слова, которое производит такое же действие, как слово, определенное следующим образом : : MAKE-NAUTICAL 6076 ' FT/HILE ! ; в стандарте Форт-79, потому что в данном случае не различается действие слова ', стоит ли оно в определении или исполняется непосредственно. Причины таких различий кроются глубоко в тонкостях работы Форт-системы. Короче говоря, несколько слов вроде ' в Форт-79 должны работать по-разному в состоянии компиляции и исполнения, хотя кажется, что их действие одинаково. Такие слова называют зависимыми от состояния. В стандарте Форт-79 разрешены слова, зависимые от состояния, в то время как в Форт-83 не разрешены, поэтому требуются два различных слова: одно для состояния компиляции, другое для исполнения. Подробнее мы на этом остановимся в гл. 15. Здесь, возможно возникнет недоумение, зачем нам потребовались и константы, и переменные? Почему бы не определить слово FT/FURLONG (футы_в_восьмую_часть_мили) таким образом, что когда вы введете 660 FT/FURLONG ! то число будет запомнено, как в примере с FT/MILE. В то же время, если вы просто введете FT/FURLONG, в стек будет выдано число 660 без операции @, как в случае константы. Если подумать, то при этом слово FT/PURLONG должно вести себя двумя различными способами: одним, когда оно используется само по себе, и другим, когда за ним следует операция @.
В MMSFORTH можно определять слова, в которых соединены свойства констант и переменных. Примерно таким способом эта возможность может быть встроена и в другие версии. В MMSFORTH имеется ключевое слово QUAN (от quantity- количество). Если ввести QUAN FT/FURLONG а после этого 660 IS FT/FURLONG то слово FT/FURLONG будет иметь свойства константы в том смысле, что оно возвратит число, если ввести слово. Но в то же время оно сходно с переменной, поскольку слово IS просто записывает число из стека в FT/FURLONG. Если вы хотите узнать, где хранится это число, то предложение AT FT/FURLONG! положит в стек его адрес. Таким образом, 660 AT FT/FURLONG! выполнит то же действие, что и 660 IS FT/FURLONG в то время как AT FT/FURLONG @ равносильно тому, что вы введете FT/FURLONG. Предусмотрены также слова CQUAN (для байтов), 2QUAN (для двойных чисел) и 4QUAN (для чисел с плавающей запятой), назначение которых очевидно. Кроме того, в языке предусмотрены также массивы QUAN и специальные слова типа QUAN для хранения чисел с плавающей запятой сопроцессора типа 8087 и для работы с массивами в расширенной памяти компьютера IBM. Уменьшение времени исполнения программы и требуемого числа ячеек памяти благодаря слову QUAN по сравнению с использованием переменных происходит, если QUAN применяется в программе более двух раз. Если в вашем распоряжении имеется MMSFORTH, то дальнейшие детали вы найдете в его документации.
Упражнения
1. Определите слова СVARIABLE и 4VARIABLE. 2. Определите слово ARRAY, которое при исполнении будет определять массив переменных, состоящий из 16 различных чисел, т.е. массив, с которым можно обращаться так же, как, например, с массивом, созданным предложением CREATE FT/HILE 20 ALLOT за исключением того, что он должен быть создан выражением 10 ARRAY FT/MILE (Заметьте, что перед словом ARRAY в стеке должно стоять количество чисел, а не байтов, которое содержится в массиве.) 3. Определите по аналогии слова CARRAY, 2ARRAY, 4ARRAY. 4. Определите две переменные 1LENGTH (длина) и 2LENGTH для помещения в них значений длины в разных единицах, например в сантиметрах и метрах или дюймах и футах.
Определите константу (1->2), которая переводила бы содержимое переменной из 1LENGTH в единицы длины переменной 2LENGTH, т.е. если в 1LENGTH даны значения в дюймах, а в 2LENGTH в футах, то значение, которое выдаст слово 1->2, должно быть равно 12. Напишите слово для преобразования содержимого 1LENGTH и запоминания его в переменной 2LENGTH. Как изменить программу, чтобы вместо дюймов и футов слово для преобразования единиц длины работало бы с сантиметрами и метрами? Это покажет вам, насколько полезны константы и почему им нужно присваивать значения при исходном определении. 5. Напишите слово с именем X->Y, которое находило бы значение Y, если в стек кладется значение Х в соответствии с уравнением Y = AX + В , считая, что А и В предварительно определены как переменные. Понятно ли вам, как можно изменить действие слова X->Y, не меняя его определения? Понятно ли вам также, почему использование переменных делает программу более гибкой и удобочитаемой ? 6. Если слово INCHES (дюймы)- это переменная, в чем различие исполнения операций INCHES и ' INCHES в стандарте Форт-79? 7. Предположим, что слово TO-INCHES (в_дюймы) представляет константу, в которой хранится коэффициент преобразования единиц, т.е. в нем должно содержаться число 12. если требуется преобразование числа футов в число дюймов, и число 36,- если преобразование числа ярдов в число дюймов. В предположении, что вы пользуетесь стандартом Форт-83, дайте определение двух слов SET-FEET (установить_футы) и SET-YARDS (установить_ярды) для записи соответственно чисел 12 36 в константу TO-INCHES. Можно ли использовать слова ' и >BODY?
О векторном исполнении операторов
Очень важным применением массивов (векторов) является управление программой для избирательного исполнения одного из нескольких возможных слов. Каждое слово имеет свой адрес, связанный с ним, который можно использовать для исполнения слова, фактически не вызывая его по имени. Чтобы понять, как это делается, определим сначала слово : MESSAGE ." Message" ; и затем введем FIND MESSAGE
Это слово определено в Форт-79; в Форт- 83 перед словом MESSAGE нужно ввести ' с клавиатуры либо ['], если слово встречается в определении через двоеточие; слова ' и ['] в Форт-83 действуют так же, как слово FIND в Форт-79. Слово же FIND в Форт-83 имеет совершенно другое назначение (см. гл. 14). Поэтому для Форт-83 нужно заменить слово FIND на ' или ['] в последующем сложении.
Если заглянуть в стек, то мы обнаружим, что в него был помещен некоторый адрес. Теперь, имея этот адрес в стеке, если напечатать EXECUTE (исполнить), вы увидите на экране слово "Message" (сообщение). Слово FIND (или ' и ['] в Форт-83) возвращает адрес, который дает возможность слову EXECUTE выполнить это слово. Таким образом, FIND NESSAGE EXECUTE производит то же самое, т.е. выдает на экран Message что делает слово MESSAGE само по себе. Это означает, что можно исполнить слово не только по имени, но также косвенно с помощью FIND, находя в словаре его адрес и затем используя слово-команду EXECUTE. При векторном исполнении применяется вектор (или переменная), в котором содержатся адреса слов, найденные словом FIND. Затем соответствующий элемент засылается в стек и слово EXECUTE исполняет нужное слово. Рассмотрим пример. Создадим массив из трех элементов: CREATE CHOICE 6 ALLOT и теперь определим три слова : 1PROG ." Program 1" ; : 2PROG ." Program 2" ; : 3PROG ." Program 3" ; Теперь запишем адреса этих слов в массив, вводя с клавиатуры FIND 1PROG CHOICE 0 + ! FIND 2PROG CHOICE 2 + ! FIND 3PROG CHOICE 4 + ! Если теперь введем CHOICE 2 + @ EXECUTE на экране появится сообщение "Program2", т.е. было исполнено слово 2PROG.
Предположим теперь, что вы хотите сделать выбор одной из программ 1PROG, 2PROG или 3PROG, нажимая одну из трех клавиш А, В или С. Вы можете сделать это, вводя коды ASCII клавиш с помощью слова KEY, преобразуя затем их в числа 0, 1 или 2 соответственно, пользуясь которыми можно выбрать и исполнить соответствующий элемент массива.
Следующее слово выполнит эту задачу: : CHOOSE KEY 65 - 2* CHOICE + @ EXECUTE :
Конечно, нужно быть внимательным, чтобы ошибочно не нажать другую клавишу; слово CHOOSE (выбрать) должно быть определено так, чтобы имелась защита от неправильного ввода. Чтобы запомнить адрес слова, которое должно быть исполнено, вместо массива может быть также использована переменная (в конце концов, переменная это всего лишь массив из одного элемента).
Предположим, что вы хотите иметь одно слово, которое исполняло бы одну из нескольких различных задач, например 1TASK, 2TASK и т.д. Когда вы составляете программу, то не знаете точно, какие это будут задачи. Можно в самом начале программы объявить переменную, например WHICH-TASK (какая_задача). Затем можно определить слово : DO-TASK ( addr - ) WHICH-TASK @ EXECUTE , которое исполнит то слово, адрес которого записан в переменной WHICH- TASK. Впоследствии при составлении программы вы определите 1TASK, 2TASK и т.д. Теперь, если вы хотите, чтобы слово DO-TASK (делать_задачу) исполнило задачу 1TASK, нужно ввести FIND 1TASK WHICH-TASK ! и слово DO-TASK- то же самое, как если бы вы ввели 1TASK. С другой стороны, если ввести FIND 2TASK WHICH-TASK ! то тогда DO-TASK исполнит задачу 2TASK. Другими словами, DO-TASK будет исполнять то слово, адрес которого находится в переменной WHICH-TASK. Вводом одного слова можно обеспечить выполнение одной из нескольких различных задач, в зависимости от значения содержимого переменной.
Некоторые Форт-системы очень широко применяют векторное исполнение, используя переменную или константу, которая должна содержать исполнительный адрес базовых слов ядра Форт-системы. Например, слово EMIT (вывести) может быть определено как : EMIT (EMIT) EXECUTE : где слово (EMIT) является константой, которая содержит исполнительный адрес. Вы можете определить теперь новое слово, например PRINTER (печать), которое в соответствии с вашим желанием изменило бы действие слова EMIT (возможно, определенное в машинных кодах), переводя вывод с экрана на печатающее устройство.
Тогда вы можете определить (EMIT) CONSTANT SCREEN чтобы сохранить старое определение слова EMIT. Теперь можно определить слова : PRINTER-ON FIND PRINTER (EMIT) ! : и : PRINTER-OFF SCREEN (EMIT) ' ,
Когда исполняется слово PRINTER-ON, вывод будет идти на печатающее устройство, а после PRINTER-OFF - снова на экран.
Существует множество других способов выполнения подобного рода задач с помощью векторного исполнения, которого мы едва коснулись. Вы найдете новые примеры в упражнениях, а более сложные его применения освещаются в книге Броуди "Thinking FORTH" (1984).
Другое очень полезное применение массива, подобное векторному исполнению,- это поисковая таблица, специализированной формой которой является таблица перекодировки. Предположим, что вы пользуетесь терминалом, который неправильно понимает стандартные коды ASCIl, т.e., например, забой влево, код которого 8, а на вашем дисплее это 22, вместо кода перевода строки 13 у терминала он равен 2, ваша программа посылает 1, чтобы очистить экран, в то время как терминал для этой функции ожидает код 24.
Определим массив следующим образом: CREATE TRANSLATE 24 С, 2 С, З С, 4 С, 5 С, 6 С, 7 С, 22 С, 9 С, 10 С, 11 С, 12 С, 23 С, 14 С, 15 С, 16 С, 17 С, 18 С, 19 С, 20 С, 21 С, 22 С, 23 С, 24 С, 25 С, 26 С, 27 С,
Теперь определим слово NEWEMIT, которое в вашей программе должно будет использоваться вместо EMIT: : NEWEMIT 1- TRANSLATE + С@ EMIT :
Тогда вместо того, чтобы непосредственно выводить на экран коды, которые программа кладет в стек, слово NEWEMIT будет просматривать в таблице TRANSLATE (перевести), каким новым управляющим символом должен быть замещен символ из стека, прежде чем он будет выведен на терминал. Таблицы перекодировки особенно удобны в применении к программам редактирования или управления терминалами, в которых управляющие клавиши могут по вашему желанию выполнять сложные специализированные функции.
Упражнения
1. Определите три слова, которые должны печатать "Dear Sir : ", "Dear Madame : " или "Dear Sir or Madame : " (Глубокоуважаемый сэр, Глубокоуважаемая мадам, Глубокоуважаемый(ая) сэр или мадам).
Определите переменную, которая должна содержать адрес одного из этих трех слов. Теперь определите слово SALUTATION (приветствие), которое печатало бы один из трех вариантов приветствия, в зависимости от значения переменной. Предположим, что вы хотите изменить выводимый текст во время исполнения программы. Как это может быть сделано? 2. Вы пишете программу с меню, которое позволяет по выбору пользователя сделать вывод на экран, нажимая клавишу 1, на печатающее устройство, нажимая клавишу 2, и на оба устройства одновременно, нажимая клавишу 3. В вашей Форт-системе имеются слова PRINT (печать), PCRT (печать и экран). CRT (экран), определенные, как описано в гл. 6. Определите слова, нужные для векторного исполнения, реагирующие на код нажатой клавиши в соответствии с меню. Вам нужно определить массив из трех элементов и инициализировать его адресами для слов PRINT, PCRT и CRT, а затем определить слово с помощью KEY и EXECUTE, которое исполняло бы одно из этих слов, когда нажималась клавиша 1, 2 или 3. 3. Оператор взвешивает болты и записывает их массу в компьютер. Оператору нужно узнать, сколько было болтов, имеющих вес менее 100 г, сколько- вес от 100 до 200 г и сколько- тяжелее 200 г. Кроме того, он хочет знать общий вес болтов каждой группы. Каждый раз после взвешивания болта оператор вводит его вес и затем слово BW (вес_болта). Для каждой группы болтов имеются две переменные: COUNT1 (счетчик1), COUNT2, COUNT3- для счета числа WT1 (вес1), WT2, WT3 -для общего веса болтов. Дайте определение этих переменных и инициализируйте их нулями: а) определите слово CLASS (группа), которое выдавало бы 0, если вес болта меньше 100 г, 1- если он находится в интервале 100-200 г и т.д. т.е. 125 CLASS должно выдать 1. (Указание: используйте оператор деления /.); б) определите массив, который содержит адреса ячеек, где хранятся числа болтов, и другой массив, где хранятся адреса суммарных весов. Заполните массивы соответствующей информацией. Эти массивы представляют собой поисковые таблицы; в) теперь определите слово BW, которое оператор вводит после веса очередного болта.
Это слово должно добавлять 1 в соответствующую переменную счетчика числа болтов и добавлять вес болта к соответствующей переменной суммарного веса. Для него потребуется слово CLASS; г) определите слово SUMMARY (итог), которое должно печатать количество, общий вес и средний вес в каждой группе. Таким образом, при вводе 0 SUMMARY должно печататься количество, суммарный вес и средний вес болтов, весящих меньше 100 г. 4. Напишите программу, которая выполняла бы функции, заданные в упражнении 3, но на этот раз запомните число болтов в трехэлементном массиве COUNTS и веса в трехэлементном массиве WEIGHTS. Это будет более эффективно, чем использование отдельных переменных. Можете ли вы объяснить, почему? 5. Теперь определите три слова, которые печатали бы менее 100 г от 100 до 200 г больше 200 г и поместите их адреса в массив, пригодный для векторного исполнения слов. 6. Определите три слова: CNT (счетчик), WEIGHT (вес) и AVERAGE (среднее) и три константы SMALL (малый), MEDIUM (средний) и LARGE (большой) так, что, если оператор вводит, например, MEDIUM WEIGHT на экране должно появиться сообщение Суммарный вес болтов от 100 до 200 г равен 139 или SMALL CNT выведет Число болтов весом меньше 100 г равно 33 и т .д.
Упражнения 3-6 иллюстрируют несколько принципиальных моментов: 1) Чтобы Форт-программа была полезной, не обязательно, чтобы она работала непрерывно. Чаще всего прикладные Форт-программы лучше всего реализуются путем создания инструментального набора слов, которые могут быть использованы, как в обычном калькуляторе. Так, например, слова BW и CNT, которые являются частью программы, но их исполнение происходит только тогда, когда эти слова вводятся; в то же время массивы постоянно отслеживают соответствующие данные; 2) имеется несколько способов реализации отдельных частей программы. Можно использовать как массивы, так и переменные, чтобы запоминать количество и вес, однако если. как правило, имеется несколько различных групп данных, то более эффективно применение массивов; 3) векторное исполнение основано на использовании поисковой таблицы, в которой записаны адреса слов; 4) поисковые таблицы можно использовать для других целей, например для того, чтобы познакомиться с методами хранения чисел в переменных. 7.
Вы пишете программу на языке Форт для обработки текста. После того как программа написана, вы решаете добавить возможность работы оператора с клавиатуры Дворака вместо стандартной клавиатуры QWERTY (стандарт IBM). Клавиатура Дворака показана на рис. 6.1.
{ & % # ! ( ) @ $ ^ * + [ 7 5 3 1 9 0 2 4 6 8 = " < > P Y F G C R L ? } , ' . / ] A O E U I D H T N S - ~
| : Q J K X B M W V Z \ ;
Рис.6.1 ( см. файл ris6_1.bmp )
а) напишите таблицу перекодировки, которая преобразует символы клавиатуры QWERTY в символы клавиатуры Дворака, т.е. если. например, нажата клавиша К. то в стек должна посылаться литера Т; б) переделайте слово KEY в DKEY, которое должно не просто принимать символ с клавиатуры и выдавать в стек его код: DKEY должно воспринимать входной код как символ клавиатуры Дворака, находить его код в таблице и помещать его в стек. Новое определение должно иметь примерно следующий вид: : DKEY KEY (Слова, с помощью которых производится просмотр); В каком месте вашей программы будет использоваться новое определение DKEY? 8. Теперь вы решили ввести возможность работы оператора по выбору с клавиатуры Дворака или QWERTY. В начале программы нужно ввести меню Введите 1 для клавиатуры Дворака Введите 2 для клавиатуры QWERTY и в зависимости от того, какое число введено: 1 или 2 в переменную ?KBD, должен быть записан адрес, которым определяется тип используемой клавиатуры. Определите теперь слово NEWKEY таким образом, чтобы при вводе 2 оно действовало бы так же, как и KEY, а если была введена 1, то производила бы просмотр таблицы и изменение значения кода как требуется.
Если эти упражнения показались вам трудными, не падайте духом! Они являются примером достаточно высокого уровня программирования. Они также продемонстрировали вам, как можно делать то, что почти недоступно другим языкам программирования. Вам следует внимательно разобраться в этих упражнениях и поэкспериментировать с применением аналогичных приемов, чтобы хорошо прочувствовать возможности векторного исполнения и поисковых таблиц, когда они вам потребуются.
Еще о массивах и матрицах
В MMSFORTH и некоторых версиях включено слово ARRAY (массив), которое упрощает обращение с массивами (это слово не является эквивалентом слова ARRAY, определенного нами выше). В данном случае, если ввести 15 ARRAY DATA-STORE вы организуете массив из 16 элементов с именем DATA-STORE (хранение_данных). Если затем ввести 5 DATA-STORE в стек будет положен адрес пятого элемента (считая с 0). Поэтому 293 5 DATA-STORE ! занесет число 293 в пятый элемент массива и 5 DATA-STORE @ . выведет на экран число 293.
Слово ARRAY может быть определено в других версиях с некоторыми отличиями, поэтому нужно смотреть вашу документацию. Одна деталь может несколько смутить вас. В математике принято нумеровать элементы массива, начиная с единицы, в MMSFORTH нумерация начинается с нуля. Этим объясняется, почему 15 ARRAY DATA-STORE создает массив из 16 элементов: как было сказано, номер последнего элемента равен 15, а так как нумерация начинается с 0, всего получается 16 элементов. В MMSFORTH имеются также слова CARRAY (байтовый массив) и DARRAY (массив чисел двойной длины), назначение которых очевидно по аналогии с CVARIABLE и 2VARIABLE.
Форт позволяет также пользоваться двумерными массивами, т.е. матрицами. Глубокое рассмотрение применения матриц выходит за рамки нашего рассмотрения; интересующимся рекомендуем книгу "Essential Computer Mathematics" (Lipshutz, 1982). Однако здесь мы должны познакомиться с ними вкратце.
Матрицы можно просто представлять себе как таблицу, содержащую некоторое число рядов и столбцов. Рассмотрим, например, таблицу 5 293 1982 823 5 56 99 211 5
Она представляет собой квадратную матрицу размерности 3х3; на диагонали матрицы находятся пятерки. Элементы матрицы обозначаются двумя индексами, показывающими их положение (строка, столбец) следующим образом: x1,1 x1,2 x1,3 x2,1 x2,2 x2,3 x3,1 x3,2 x3,3 Матрица может быть представлена в памяти как линейный массив. В данном случае имеется 9 элементов. Поэтому можно определить CREATE MATA 18 ALLOT После этого программист должен взять на себя ответственность за установление связи между положением элемента в строке и столбце и номером элемента в линейном массиве.
Полезно иметь универсальную формулу. Если обозначить положение элемента в строке через i, а в столбце через j и за начальный (1,1) принять элемент, находящийся в левом верхнем углу, тогда номер элемента в линейном массиве определится как е = n(i-1) +j , где n - число столбцов в матрице. Таким образом, элемент (2,3) в матрице МАТА в линейном массиве будет иметь номер 6. Учитывая это, можно определить слово для вычисления положения элемента в матрице МАТА, если в стеке находятся значения i и j: : MATAADR SWAP 1- 3 * + 1- 2* ; следовательно, чтобы записать, например, число 1234 в элемент матрицы (3,1) , нужно ввести 1234 МАТА 3 1 MATAADR + !
Такова стандартная форма обращения с матрицами в Форте, однако в MMSFORTH и других версиях имеются более удобные средства.
Слово 2ARRAY в MMSFORTH позволяет определить и организовать доступ к элементам матрицы без дополнительных хлопот с обработкой индексов; будьте внимательны и не путайте слово 2ARRAY (двумерный массив, матрица) и DARRAY (линейный массив чисел двойной длины). Чтобы определить матрицу 2МАТА размерности 3х3, нужно ввести 2 2 2ARRAY 2МАТА Числа, определяющие размерность матрицы, которые помещаются в стек, должны быть на 1 меньше числа строк ц столбцов, так же как при создании линейного массива число в стеке должно быть на 1 меньше его размерности. После того как матрица (в данном случае 2МАТА) определена,адрес ее элемента можно найти, вводя имя матрицы, если номера строки и столбца уже находятся в стеке. Таким образом, чтобы напечатать элемент (2,3) матрицы 2МАТА, нужно ввести 1 2 2МАТА ? Понятно ли вам, почему числа в стеке на 1 меньше фактических индексов элемента матрицы?
В MMSFORTH имеются также слова 2ARRAY и 2CARRAY. Их назначение очевидно. В гл. 11 будет рассмотрено, как можно определить такие слова.
Векторы и матрицы широко используются в разделе математики, который называется линейной алгеброй и занимается решением систем уравнений. Они также широко используются в науке и технике, в экономике для решения систем дифференциальных уравнений.
В некоторых языках программирования, например Фортране, АПЛ, фактически имеется целый набор стандартных функций для работы с матрицами. Более сложные применения матриц выходят за рамки нашего обзора, однако есть множество задач повседневной практики, в которых полезно применяются матрицы, обычно в том же виде, как для хранения табличных данных. Мы рассмотрим несколько таких приложений в следующих упражнениях.
Упражнения
Упражнения 1-4 похожи на то, что мы уже делали, тем не менее проделайте их для подготовки к упражнению 5, которое показывает матрицу как таблицу. Считайте, что можно пользоваться словами MMSFORTH для работы с массивами. 1. Определите массив из 10 элементов под именем NUMBERS (числа). Теперь определите слово COUNT (счетчик), которое должно добавлять единицу к элементу матрицы, в зависимости от введенного веса, находящегося в пределах 0- 100. Если вес от 1 до 10, то нужно прибавить к элементу 1, если вес от 9 до 20, то к элементу 2, если между 19 и 30- к элементу 3 и т.д. (Указание: используйте операцию деления /.) 2. Теперь определите второй массив, WTSUM (суммарный вес> и слово !W, которое будет добавлять вес к соответствующему элементу массива WTSUM так же, как числа добавляются к массиву NUMBERS. 3. Определите слова .TOT-#S (суммарное количество), .TOT-WT (суммарный вес) и .AVE-WT (средний вес)," которые соответственно будут печатать суммарное количество, общий вес и средний вес по группам в одной строке. Примените форматный вывод. (Используйте цикл DO, если же вы не уверены, что сможете это сделать, обратитесь к ответам.) 4. Определите слово !WT таким образом, чтобы каждый раз, когда оно исполняется, исполнялось бы слово !WT и при этом суммарный вес, число деталей и средний вес по группам представлялись бы на экране друг под другом в виде таблицы из трех строк и 10 столбцов. 5. Определите матрицу размерности 2х10, в каждом столбце которой содержится весовая группа, а в строках- общее количество и суммарный вес соответственно. Теперь проделайте упражнения 1-4 снова, используя не линейный массив, а матрицу; при этом необходимо, чтобы все соответствующие элементы матрицы обновлялись после каждого исполнения слова !W. 6.
Может оказаться очень удобным дополнительный стек. Создайте массив из 160 элементов с именем NEWSTACK, который вел бы себя как дополнительный стек. Теперь определите переменную STACKPOS (положение в стеке) для расчета текущего положения указателя стека, т.е. адреса вершины стека. Инициализируйте переменную в STACKPOS, чтобы она указывала на первый элемент в NEWSTACK. Затем определите два слова: XPUSH и ХРОР ; XPUSH (послать в стек) должно брать число из обычного стека и засылать его в NEWSTACK, изменяя соответственно указатель. Слово ХРОР должно изымать число из стека NEWSTACK и помещать его в обычный стек, вновь изменяя указатель. Модифицируйте слова XPUSH и ХРОР, назвав их PUSH и POP, используя слова МАХ и MIN так, чтобы указатель не мог выйти за границы массива стека. 7. Определите слова NEWDROP, NEWDUP и NEWSWAP, которые делали бы то же самое, что и слова DROP. DUP и SWAP в обычном стеке.
О разном
Кроме переменных, создаваемых программистом, имеются различные переменные в самой Форт-системе, например BASE (основание системы счисления). Исторически переменные, которые являются частью самого языка Форт, называются переменными пользователя, поскольку в многопользовательской системе каждый пользователь может иметь свой собственный набор этих переменных, Мы понимаем, что это не совсем удачное название, поскольку в большинстве языков программирования переменная пользователя определяется им самим, а переменные, являющиеся частью самой системы, например константа число п (3.1415...), называются системными переменными. Тем не менее, следуя терминологии Форта, мы будем называть переменные, входящие в Форт-систему, пользовательскими переменными. На самом деле пользовательские переменные это совсем не то же самое, что переменные, которые определяет пользователь. Переменные, которые вы определяете, в качестве части своего определения могут включать число. Переменная пользователя содержит число, которое может храниться в какой-либо.удаленной от определения ячейке, и на практике не нужно беспокоиться о том, где хранится эта переменная, потому что она ведет себя точно так же, как и переменные, определенные программистом.
Можно рассматривать переменную пользователя как константу, значение которой представляет адрес, по которому хранится значение переменной. Поэтому к ней применимы операции @ (извлечь содержимое) и ! (записать), как к обычным переменным.
Второй вопрос также относится к терминологии. Вы уже заметили, что некоторые слова языка Форт используются только для определения других слов. К ним относятся : (двоеточие), VARIABLE (переменная), CONSTANT (константа), QUAN и ARRAY (массив). Такие слова называются определяющими словами. Они применяются для составления программ. Слово CREATE (создать)- также определяющее слово, но это особое определяющее слово, потому что оно может быть использовано для создания других определяющих слов. Вы уже видели, как с помощью слова CREATE можно определить другое определяющее слово VARIABLE, которое, в свою очередь, используется для определения других слов. Как вы увидите в гл. 11, применяя его совместно со словом DOES>CREATE, можно полностью изменить характер работы вашего языка Форт.
Следует сказать несколько слов о стиле программирования. Если вы пишете программу, нужно поделить ее на логические секции, каждая из которых имеет определенное назначение. Довольно существенно определить заранее все константы, переменные и массивы, по крайней мере в начале каждой программной секции, а еще лучше- в самом начале программы. Объявления констант и переменных (так иногда называют их определение) группируются вместе, что облегчает внесение исправлений и модификацию программы, если это необходимо.
Еще одно замечание о стиле программирования. Большинство новичков в Форте стараются использовать переменные неоправданно часто. Это в особенности присуще тем, кто знаком с языками программирования, где переменные обязательны. Всегда при использовании переменной быстродействие получается ниже, чем при использовании стека. И это одна из главных причин предпочтительного использования стека в Форте. Хотя может показаться, что переменными пользоваться проще, так как при этом не нужно следить за тем, что делается в стеке, за это приходится платить ценой увеличения затрат памяти и времени.
Практически переменные (или константы и массивы) следует использовать для достижения одной из следующих целей: 1) для размещения больших объемов данных, которые невозможно хранить в стеке из-за того, что длина стека непомерно увеличивается; 2) для размещения чисел, которые используются неоднократно в разных сильно разнесенных секциях программы, или 3) для улучшения удобочитаемости программы.
Фактически есть еще одна причина для использования переменных. Если вы пишете программу, которой воспользуетесь всего несколько раз и которая без введения переменных потребует очень сложных манипуляций в стеке, то, может быть, использование переменных будет окуплено сокращением времени написания программы в ущерб времени ее исполнения. Но нужно сознавать, что если это делать постоянно, то развиваются вредные привычки в программировании.
Выводы
Хотя в отличие от других языков программирования Форт применяет стек для большинства манипуляций с числами и передачи аргументов из одного слова в другое, это не исключает использования переменных (а также констант и массивов), как и в других языках- Форт также обеспечивает более глубокое управление этими средствами хранения данных, позволяя создавать собственные конструкции с помощью слова CREATE. А с помощью слов типа CMOVE, ' , FIND и EXECUTE можно сделать то, что вообще невозможно в других языках программирования.
И в отличие от других языков переменные и массивы в Форте не только запоминают данные, но также сами управляют программами и языком. Векторное исполнение программ дает мощное средство программирования, которого нет в других языках. Например, переопределение слов EMIT или KEY равносильно тому, чтобы совершенно изменить действие операторов PRINT и INPUT в Бейсике. Это в некоторой степени должно объяснить вам, почему во введении мы говорили, что Форт дает большую мощность в управлении вашим компьютером, чем другие языки. Вы еще убедитесь в этом более ощутимо в последних главах книги, где речь идет о создании новых определяющих слов и использовании Форт-ассемблера.Но пока мы и не рассмотрели множество более простых свойств языка.
Обязательной принадлежностью любого языка программирования являются управляющие структуры, т.е. процедуры типа IF...THEN (если- то), которые позволяют принимать решения о дальнейшем ходе программы на основании определенных условий. Мы рассмотрим такие управляющие структуры в гл. 7.
Операторы сравнения и ветвления
Одной из наиболее важных задач, которую должен уметь делать любой язык программирования высокого уровня, является выполнение некоторых операций на основании истинности или ложности некоторых условий. Например, если два верхних элемента в стеке равны, должен быть выполнен один оператор, но если они не равны, то должно быть сделано что-то другое. Такие условные операции, возможно, проще понять на примере из Бейсика, потому что их структура близка к естественному языку. Если в выражении 100 IF A=B THEN X=Y ELSE GOSUB 300 переменные А и В равны, то значение переменной Х устанавливается равным значению Y; в противном случае выполняется некоторая подпрограмма, начинающаяся в трехсотой строке программы. Это позволяет программе выполнять разные действия при различных обстоятельствах. Подобные конструкции, которые управляют потоком (прохождением) программы, называются управляющими структурами; они включают в себя конструкцию IF...THEN , счетные циклы, которые вкратце уже рассматривались, и другие средства для осуществления переходов в программе, рассматриваемые в данной и последующей главах.
В отличие от Бейсика конструкция IF...THEN сравнивает числа не в виде переменных, а в стеке и в Форте каждое слово конструкции - это фактически подпрограмма. Конечно, отличается также и постфиксная форма записи. Поэтому условное исполнение и ветвление в языке Форт записываются на языке Форт несколько по-другому. Приведенному выше выражению на Бейсике в форте будет эквивалентна следующая конструкция: А @ В @ = IF Y @ X ! ELSE DOTHAT THEN
Конечно, на практике такое большое количество переменных в Форте никогда не используется. Рассмотрим приведенную конструкцию более внимательно. Предложение А @ В @ извлекает значения двух переменных и кладет их в стек. Операция - возвращает значение, зависящее от того, равны или не равны эти два числа (заметим, что - является оператором сравнения, а не присвоения, как на Фортране или в Бейсике). Если числа в стеке равны, то говорят, что условие истинно, тогда в Форт-79, MMSFORTH и большинстве других версий в стек возвращается 1, а в Форт-83 - число -1 (т.е. 16-разрядное число, у которого все разряды равны 1, или FFFF в шестнадцатеричной системе счисления).
Если числа не равны, то говорят, что условие ложно, при этом во всех версиях Форта в стек возвращается 0. Величина, возвращаемая в стек оператором сравнения, называется значением истинности, булевым флагом или просто флагом. В языке Форт любое ненулевое число всегда считается истинным и 0 всегда ложным. В данном случае если перед IP находится истинное (ненулевое) значение, то исполняются слова, находящиеся между оператором IF и ELSE, слова между ELSE и THEN пропускаются и затем продолжается исполнение той части, которая следует за словом THEN. Если перед IF находится ложное значение (0), исполнение перескакивает на слово, которое следует после ELSE и продолжается до слова THEN.
На рис. 7.1 проиллюстрирована эта идея. Обратите внимание, что конструкции IF-THEN и IF... ELSE... THEN могут быть использованы только в определениях через двоеточие. Вскоре мы более подробно обсудим конструкцию IF...ELSE...THEN, а пока познакомимся с некоторыми другими операторами сравнения.
Проверка истинности
Теперь кратко остановимся на булевом флаге. То, что в Форт-83 (и других языках) оператор сравнения возвращает значение флага -1, а не 1, имеет определенное основание. При обнаружении истинности условия число -1 или шестнадцатеричное FFFF, т.е. содержащее во всех разрядах единицы, оказывается иногда более удобным для использования его с булевскими операциями. Предположим, например, что требуется заменить число нулем, если флаг имеет значение ложь, и оставить без изменения, если флаг имеет значение истина. Если числа находятся в стеке, причем флаг на вершине, то оператор AND ("И") выполнит эту задачу в Форт-83, но не в Форт-79, для которого потребуется конструкция 0 = IF DROP 0 THEN
Хотя подобные случаи не так уж часты, иногда программа может дать выигрыш по времени, пользуясь этой особенностью флага. (При необходимости вспомните действие оператора AND в гл. 3.) Если посмотреть с более общих позиций, то в Форт-83 значение истина имеет не только сам флaг, но и каждый его разряд, что может оказаться полезным при операциях поразрядного сравнения.
Обратимся теперь к операторам сравнения; слово = (равно) - только одно из нескольких предусмотренных в языке Форт. Имеются также операторы сравнения для одинарных чисел и чисел двойной длины со знаком и без знака. В табл. 7.1 приводится сводка этих операторов. Большинство из приведенных операторов в комментариях не нуждаются. Некоторые особенности имеют операторы U< (и по аналогии DU
Операторы U< и UD< применяются для сравнения больших чисел, чтобы они не рассматривались как отрицательные. Как и для арифметических операций, при использовании чисел без знака требуется некоторая осмотрительность.
Особого внимания заслуживает оператор 0=. Он всегда меняет результат сравнения на обратный. каким образом, число, не равное нулю, превратится в 0, в то время как 0 превратится в 1 (или в -1 Форт-83). Для повышения удобочитаемости программ в Форт-79 слово 0= имеет стандартный синоним NOT (не), который действует аналогичным образом. (В Форт-83 оператор NOT действует по другому, как было описано в гл. 3.) Если целью сравнения является выяснение того, действительно ли в стеке находится нуль, то более оправдано применение оператора 0=, в то время как оператор NOT уместнее, если целью является изменение значения истинности на обратное. Заметим, что, если в Форте нет слова <>, его можно заменить конструкцией = NOT или = 0=. Точно так же оператор - (минус) будет возвращать значение 0 или не 0, как оператор <>. Запомните, что операторы сравнения снимают числа из стека. Поэтому, если вам потребуются эти числа для дальнейшей работы, их необходимо скопировать в стеке. Обычно это делается с помощью операций OVER OVER или 2DUP.
Таблица 7.1. Операторы сравнения *
Имя Операнды Определено ли Результат (возвращаемое значение) слова в стеке стандартом?
= n1 n2 да истина, если n1 = n2 <> n1 n2 нет истина, если n1=/=n2 < n1 n2 да истина, если n1 < n2 > n1 n2 да истина, если n1 > n2 = n1 n2 да истина, если n1 >= n2 0= n да истина, если n = 0 0< n да истина, если n < 0 0> n да истина, если n > 0 D= d1 d2 да истина, если d1 = d2 D< d1 d2 да истина, если d1 < d2 D0= d да истина, если d = 0 U< u1 u2 да истина, если u1 < u2 DU< ud1 ud2 да истина, если ud1=ud2 * Нестандартные слова включены в MMFORTH.
Прежде чем продолжить рассмотрение применения операторов сравнения, познакомимся с логическими операторами AND, OR и XOR. Они позволяют комбинировать несколько условий. Пусть, например, вы хотите выполнить какую-то операцию только в том случае, если для переменных А, В и С выполняются условия А=С и В=С. А @ С @ = В @ С @ = AND IF ...
Если обе пары переменных (первая и вторая) равны, то оператор AND, обнаружив в стеке 1 1 (или -1 -1), возвратит в стек 1 (или -1). Если одна или обе пары не равны, в стеке будет по крайней мере один 0, тогда оператор AND возвратит 0. Аналогично используется логический оператор OR. Допустим, что вы хотите выполнить какое-то действие, если или А=С, или В=С, или А=В=C. Это можно сделать следующим образом: A @ C @ = B @ C @ = OR IF ...
Если либо одно, либо другое равенство (либо оба) истинны, тогда по крайней мере одно условие истинно, поэтому оператор OR обнаружит в стеке хотя бы одну 1 (или -1, т.е. число со всеми разрядами, равными 1) и выдаст в стек значение истина. Если оба равенства ложны, то оператор OR, увидев в стеке два нуля, возвратит в стек значение ложь (нуль). Наконец, пусть необходимо, чтобы какое-либо действие выполнялось только в том случае, если одно равенство выполняется, а другое нет. Это можно сделать так : A @ C @ = B @ C @ = XOR IF ...
Оператор XOR возвращает в стек значение истина, если в стеке есть флаги истина и ложь (разряды в одном числе установлены в 1, а в другом не установлены). Комбинируя операторы сравнения с логическими операторами AND, OR и XOR, можно выполнять всевозможные комбинации сравнений. Если вы знакомы с булевой алгеброй и диаграммами Венна, то представляете, как изображать на них всевозможные сочетания условий для облегчения реализации большого разнообразия условного исполнения программ в комбинации с конструкцией IF...ELSE...THEN.
Упражнения
1. Пусть в версии языка, с которой вы работаете, есть только один оператор сравнения . Определите 0=. Определите =. Определите <>. Определите 0-.
Все введенные вами определения снабдите префиксом NEW. 2. Пусть числа а, b, с и d находятся в стеке в указанном порядке. Напишите последовательность слов, которая выдавал бы флаг истина тогда, и только тогда, когда выполняются следующие условия: а) а=b И c=d б) а=b ИЛИ с=d в) a=b И c<>d r) a=b ИЛИ c<>d д) a<>b И c<>d e) а=b ИЛИ c=d, но не одновременно ж) а>b И c b > с . 3. Используя слово MOD, дайте определение слова ?REM=0 (остаток равен 0?), которое возвращает флаг истина тогда, и только тогда, когда второе число в стеке делится без остатка на число, находящееся на вершине стека. 4. Дайте определение слова ?REM, которое будет возвращать в стек флаг ложь при условиях упражнения 3 и истина в противном случае. 5. Определите слово ?OPPOSITE (?противоположные), которое возвращало бы флаг истина тогда, и только тогда, когда оба числа по модулю были бы равны, но имели противоположные знаки. Определите слово NEW=, используя только О= и арифметический оператор. 6. Определите слово D= , пользуясь только арифметическим оператором и словом 0=. 7. Пользуясь соглашениями Форт-79, определите слово СОМР, которое будет возвращать -1, если число отрицательное, 0, если оно равно 0, и 1, если оно положительное, не применяя конструкции IF.
Операторы IF, ELSE и THEN
Операторы сравнения мало полезны без других операторов, которые реагируют на значение флага, возвращаемого в стек операторами сравнения. Наиболее важным из наших условных операторов является слово IF, используемое в конструкции IF...ELSE...THEN, рассмотренной в начале этой главы. Другие важные слова, реагирующие на флаг проверки условия, это UNTIL и WHILE, применяемые в конструкциях BEGIN-UNTIL и BEGIN...WHILE...REPEAT; эти конструкции позволяют зацикливать программу до тех пор, пока значение флага есть истина или ложь. Слово BEGIN и связанные с ним слова мы рассмотрим более внимательно в гл. 8.
Еще два условных оператора, имеющихся во многих версиях Форта, это ?DUP и ABORT", к ним мы еще вернемся в этом разделе.
Действие конструкции IF...ELSE-THEN можно в сущности представить следующим образом (см. также рис. 7.1) : ( флаг ) IF ( число в стеке не равно нулю, исполнить слова, стоящие здесь) ELSE ( если нуль, исполнить эти слова) THEN ( в любом случае продолжить отсюда)
Слово ELSE не является обязательным и часто опускается. Если оно отсутствует, то остается только одна возможность: исполнить слова, находящиеся между IF и THEN, если флаг, который оператор IF видит в стеке, является истинным. Со словом ELSE имеются две возможности : исполнить слова, находящиеся между IF и ELSE, если флаг имеет значение истина, или слова, заключенные между ELSE и THEN, если флаг имеет значение ложь. В обоих случаях исполнение продолжается после слова THEN. Лучше всего можно понять эти идеи на примере.
В гл. 6 вы познакомились с программой, в которой оператор вводил вес болтов, а затем слово WT. После этого число классифицировалось по величине и изменялись соответствующие переменные. Допустимый вес находился в диапазоне 0 - 300 г. Предположим, что, вы хотите предотвратить ввод оператором неверного числа, которое или очень велико, или мало. Можно сделать это, проверив величину числа с помощью операторов сравнения, а потом принять это число либо выдать сообщение об ошибке в соответствии с конструкцией IF... ELSE... THEN. Чтобы осуществить это, переименуйте прежнее слово WT в (WT) (заключением слова в скобки чаще всего подчеркивают, что оно является частью другого слова со сходным названием) и определите новое слово следующим образом: : WT DUP 0< OVER 299 > OR 0= IF (WT) ELSE DROP ." Вышли за пределы диапазона" THEN ;
Заметим, что необходимо включить ELSE DROP для исключения неверных данных из стека перед словом (WT). Слово WT можно немного упростить, убрав оператор 0м следующим образом: : WT -DUP 0< OVER 299 > OR IF DROP ." Вышли за пределы диапазона" ELSE (WT) THEN ;
Вообще слово 0= перед конструкцией IF...ELSE...THEN вовсе не требуется, так как точно такого же результата можно добиться, переставляя слова между IF и ELSE со словами, находящимися между ELSE и THEN.
Конструкции IF...THEN могут быть вложенными, т.е. их можно использовать внутри таких же конструкций. В слове WT мы классифицировали значения, пользуясь делением на 100 (/) и последующим векторным исполнением. Можно сделать то же, применяя оператор IF. Пусть, если вес меньше 100 г, должна выполняться задача 1TASK, если вес от 100 до 200 г - задача 2TASK, и задача 3TASK, если вес от 200 до 300 г. Можно сделать так; : (WT) DUP 100 < IF 1TASK ELSE DUP 200 < IF 2TASK ELSE DUP 300 < IF 3TASK THEN THEN THEN DROP ;
Метод векторного исполнения с операцией деления на 100 может показаться более изящным, но он не намного быстрее и, кроме того, не будет работать, если разбросы веса в каждой группе неодинаковы. Заметьте, что в данном случае мы использовали как IF...THEN, так и IF...ELSE...THEN конструкции. Первые два оператора IF нужно использовать с ELSE, потому что, если условия ложны, число должно переходить в следующий класс с большими значениями веса. А так как слово (WT) является частью слова WT, третий оператор IP используется без ELSE, потому что число больше 300 быть не может. Немного дальше мы покажем, что можно также применить конструкцию выбора одной из нескольких возможностей (переключатель).
Некоторые программисты строго придерживаются манеры выделять вложенные операторы IF...THEN, подчеркивая их вложенность, поэтому запись определения (WP) в предыдущем примере вызовет у них возражение, так как она плохо сформатирована, и они отдадут предпочтение такой форме записи: : (WT) DUP 100 < IF 1TASK ELSE DUP 200 < IF 2TASK ELSE DUP 300 < IF 3TASK THEN THEN THEN DROP ; Решайте сами, стоит ли ради наглядности программы занимать больше места на диске.
Вот еще один пример. Пусть нужно выполнить операцию D0IT, используя остаток от деления на 22 только тогда, когда число в стеке не кратно 22, т.е. если n 22 MOD возвратит в стек ненулевое значение. Вот как это можно сделать : : ?22-MULTIPLE 22 MOD DUP IF DOIT ELSE DROP THEN ;
Часто функция оператора IF может быть успешно выполнена без оператора сравнения.
В этом случае что- то должно быть сделано или не сделано в зависимости от того, находится ли в стеке нуль или не нуль. Сочетание ELSE DROP необходимо для того, чтобы убрать из стека нуль, порожденный оператором DUP. В подобных случаях очень полезно слово ?DUP, поскольку оно кладет в стек копию числа только в том случае, когда оно не равно нулю, заменяя два слова ELSE DROP. Вот, например, короткое определение слова ?22-MULTIPLE (кратно ли 22?): : ?22-MULTIPLE 22 MOD ?DUP IF DOIT THEN ;
Если в стеке находится нуль, слово ?DUP ничего не выполняет, следовательно, применяя его, мы избавляемся от заботы очищения стека от оставленного нуля. ?DUP не дает никакого выигрыша, если перед оператором IP стоит оператор сравнения. Но если перед IF следовала арифметическая операция и при ненулевом значении результата должна быть выполнена другая программа, тогда использование ?DUP экономит время - особенно в цикле. Применение оператора MOD совместно с IF весьма полезно в циклах DO-LOOP, когда нужно что-то выполнить с определенным интервалом. Например, : 7MULTIPLES? 1+ 0 SWAP 1 DO I 7 MOD 0= IF 1+ THEN LOOP . ." раз число 7 содержится в данном числе " CR ; при исполнении 38 7MULTIPLES? выведет в результате 5 раз число 7 содержится в данном числе
Хотя данный пример тривиален, в нем показан прием, который пригодится для составления более сложных программ, чем определение кратности одного числа другому.
Некоторые замечания о структурном программировании
Переход на исполнение определенных операций (т.е. подпрограмм) является одним из наиболее важных механизмов языка программирования. В таких языках, как Бейсик, допускается переход в любое место программы, куда вы хотите, не обязательно к четко оформленной задаче или подпрограмме. И возврат не обязательно должен происходить в точку, из которой произошел переход. Оператор Бейсика GOTO nnn где nnn - номер строки, позволяет делать переход в программе произвольно, с возвратом или без возврата в исходную точку, как вам кажется удобнее.
Это потенциально может привести к запутанной программе, в которой трудно проследить ход действий, что затрудняет ее отладку и модификацию.
Альтернативным методом является структурное" программирование. Упрощенно структурное программирование заключается в том, что программа составляется таким образом, что когда она уходит на подпрограмму, то ее исполнение затем продолжается с точки, следующей сразу же после точки ухода на подпрограмму, и в подпрограмме имеется только одна точка входа. Язык Форт поощряет структурный подход в программировании, так как любое слово Форта по существу является подпрограммой, у которой обычно имеется только один вход и один выход. Другие языки, например Паскаль или Модула-2, также обязывают к применению структурного программирования ввиду того, что вход в подпрограмму может быть произведен с помощью вызова именованной процедуры, возвращающей управление в точку, из которой она была вызвана. Это обстоятельство может показаться неудобным, потому что каждый переход на подпрограмму требует самостоятельной именованной процедуры. Форт в этом отношении обладает большей гибкостью. Поскольку все, что делается на языке Форт, исполняется подпрограммами, т.е. словами-операторами Форта, ли программы структурные по своей природе. (Исключением, как мы увидим в дальнейшем, является слово EXIT, которое позволяет немедленно прекратить исполнение слова.) Кроме того, поскольку в любой подпрограмме некоторые слова можно переделать, дать им более короткие имена, использование переходов становится не столь обременительным, как в Паскале.
Упражнения
1. Определите слово NEWABS (абсолютное значение), используя конструкцию IF...THEN. (Совет: воспользуйтесь словами 0< и NEGATE). 2. Определите слово, подобное / (т.е. деление нацело), которое должно выдавать сообщение об ошибке при попытке деления на нуль. 3. Определите слово ТYРЕ
Прекращение исполнения задания
Обычно исполнение слова Форта продолжается до тех пор, пока не встретится последнее слово в о определении.
Форт- программа исполняется до конца слова, которое вызвало его исполнение. В некоторых случаях нужно прекратить исполнение слова или программы досрочно. Завершение исполнения слова возвращает управление слову, которое запустило программу, в противоположность этому преждевременное прекращение программы возвращает управление терминалу.
Сначала мы познакомимся с тем, как можно выйти досрочно из исполнения слова с помощью оператора EXIT. Пусть вы определили : ADD + EXIT . ; : SUM 3 4 ADD 5 6 ADD ." Суммы" ;
Если вы запустите слово SUM, а потом посмотрите, что находится в стеке, то увидите, что, хотя обе пары чисел были введены и просуммированы, ни один из результатов не выведен на экран, по тому что операция ADD была окончена раньше, чем встретилось слово . (напечатать). А сообщение "Are the sums" (суммы) напечаталось. Дело в том, что, когда завершилось исполнение слова ADD, программа SUM еще не завершилась и управление было передано слову SUM. Теперь попробуйте тот же пример, заменив в нем EXIT на QUIT. Вы не увидите завершающего программу сообщения, но если заглянете в стек, то увидите там только первую сумму. Слово QUIT не только прекратило операцию ADD, оно завершило исполнение программы передав управление клавиатуре. Если слово QUIT сохраняет содержимое стека, то слово AUORT делает очистку стека.
Из данного примера не видно явно никакой пользы от новых слов EXIT, QUIT и ABORT. Зачем же нужны слова, которые только прекращают действие программы или слова до их завершения? Ответ состоит в том, что эти слова используются обычно в конструкциях IF...THEN, о чем мы вскоре расскажем. Назовем лишь два самостоятельных употребления этих слов. Во-первых, ни QUIT, ни ABORT не оставляют на экране сообщения "ok", если они стоят в конце программы. Вспомним пример программы WT, в которой числа вводились после того, как вы печатали WT и , и на экране получалось что-то вроде 223 WT ok 16 WT ok 59 WT ok
Можно сделать ввод более красивым, добавив в конце определения слова WT фразу SPACЕ QUIT.
Тогда после каждого WT на экране мы увидим 223 WT 16 WT 59 WT
Слово ABORT будет делать то же самое, но, кроме того, еще очищать стек. Использование слов ABORT и QUIT может быть полезным для подавления сообщения "ok".
Помимо этого, самостоятельное применение указанных слов может оказаться полезным при отладке программы. Предположим, что вам нужно проверить, не приводят ли к ошибке слова, которые вы ввели в конце какого-либо определения через двоеточие. Тогда можно сделать, чтобы эти слова игнорировались бы с помощью слова EXIT (разумеется, то же можно сделать, помещая эти слова в круглые скобки). Можно также применить QUIT, чтобы прервать исполнение программы каком-то месте и посмотреть содержимое стека или переменных. Слово ABORT в этом применении менее полезно, так как оно очищает стек, который нас, безусловно, интересует. Как уже было сказано, EXIT, QUIT и ABORT чаще всего используют в конструкции IF...THEN. Пусть нам нужно обеспечить возможность останова исполнения программы в определенном месте. Используйте для этого слово : ?ABORT " Нажмите S для прекращения программы" KEY 83 = IF " ok" CR ABORT THEN ;
Если ?ABORT вставить в какое-либо место в программе, пользователь будет иметь возможность остановить ее исполнение. A ."ok" CR введены в программу для того, чтобы напечатать сообщение "ok" и сделать возврат каретки, чего не делает слово ABORT. Очевидно вы можете заменить его словом QUIT, если хотите сохранить содержимое стека.
Слова ABORT и QUIT очень часто используются для обнаружения ошибок. Нам совершенно нежелательно разрешать деление на 0 (что может произойти из-за ошибки в вашей программе). Вот такое слово, которое можно вставить в программу непосредственно перед оператором деления / для того, чтобы выйти из вашей программы, если произойдет такая ошибка: : 0/? DUP 0= IF ." Ошибка деления на 0" DROP QUIT THEN ;
Заметим, что DROP можно убрать, если использовать ?DUP, и тогда получим : 0/? ?DUP 0= IF ." Ошибка деления на 0" QUIT THEN ;
Вам может потребоваться также досрочное завершение программы при достижении какого-либо заранее определенного условия. Представим себе, что есть программа, которая принимает данные от удаленного компьютера по телефонной линии, и нужно, чтобы она останавливалась, если будет получен управляющий код Ctri-C ( код ASCII равен 3). Можно после каждого входного символа вставить проверку с помощью слова : ?CTLC DUP 3 = IF ." Прекращение по дистанционному запросу " ABORT THEN ;
Говоря вообще, слова ABORT и QUIT наиболее полезны для слежения за условиями, при которых исполнение программы должно быть прекращено. Эти условия обычно связаны с некоторыми типами ошибок, например делением на 0, но могут быть, конечно, и другого рода. Можно напомнить, как мы говорили в гл. 1, что в Форте забота о проверке наличия ошибок целиком возлагается на программиста. И мы показали, как она может быть сделана в простых случаях. Слово EXIT имеет более тонкие применения, чем ABORT и QUIT. Оно прекращает исполнение слова и возвращает управление туда, откуда это слово было вызвано. Оно бесполезно вне конструкции IF...THEN, поскольку всегда возможно прекратить исполнение более естественным путем, завершая определение слова точкой с запятой. (Кстати, EXIT фактически является частью определения слова ;.) Предположим, например, что у вас есть слово, которое позволяет оператору ввести число, а затем запомнить сумму в переменной TOTAL. Вы хотите, чтобы ввод нуля игнорировался. Используя EXIT, можно дать такое определение : : GET# #IN ?DUP IF TOTAL +! ELSE EXIT THEN ; но по своему действию ELSE EXIT THEN ; ничем не отличается от THEN ;
Таким образом, EXIT следует применять ограниченно или не применять вовсе внутри одной единственной конструкции IF...THEN. Вспомним наше определение слова (WT) : : (WT) DUP 100 < IF TASK1 ELSE DUP 200 < IF TASK2 ELSE DUP 300 < IF TASKS THEN THEN THEN DROP ;
Теперь предположим, что слова TASK1, TASK2 и TASK3 прибавляют по единице к переменным 1COUNT, 2COUNT и 3COUNT каждый раз, когда вес попадает в соответствующий диапазон значений.
Но вы не хотите, чтобы слово TASK исполнялось, если в каком-либо диапазоне общий счет превысил 200. Иначе говоря, вы хотите прекратить исполнение, если количество случаев попадания в какой-либо диапазон превышает 200. Представляем слово, которое решает эту задачу : : (WT) DUP 100 < IP 1COUNT @ 199 > IF EXIT THEN TASK1 ELSE DUP 200 < IF 2COUNT @ 199 > IF EXIT THEN TASKS ELSE DUP 300 < IF 3COUNT @ 199 > IF EXIT THEN TASK3 THEN THEN THEN DROP ;
Этот пример показывает, что если использование слова EXIT в единственной конструкции IF...ELSE приводит к появлению лишних бесполезных слов, то во вложенных конструкциях IF...ELSE слово EXIT становится существенно необходимым.
Прежде чем закончить этот раздел, укажем на менее употребительное слово из Форт-83 и других версий - ABORT". Это слово ищет флаг в стеке и исполняется, если его значение истина, т.е. слово ABORT" содержит внутри себя конструкцию IF. Слово ABORT" выдает сообщение, которое следует после ", а затем исполняет операции присущие слову ABORT. Его назначение состоит в том, чтобы обнаруживать ошибки и давать об. этом сообщение. Вот, например, определение слова 0/? с использованием слова ABORT": : 0/? ?DUP 0= ABORT" Ошибка деления на 0 " ;
Приведенные ниже упражнения покажут некоторые более полезные на практике применения рассмотренных слов,
Упражнения
1. Определите слово ?END для оптимального выхода из программы, которая задавала бы следующие вопросы и выполняла бы соответствующие вашим ответам действия: Do you want to quit? (Y/N) Вы хотите закончить работу? и если введен ответ Y(да), то Do you want to save the stack? (Y/N) Вы хотите сохранить стек? (Да/Нет) 2. Если слово ?223 вводится непосредственно (не входит в определение через двоеточие), имеются ли какие-либо различия в его действии при следующих трех способах его определения : : ?223 223 = IF 1 COUNT +! ELSE QUIT THEN ; : ?223 223 = IF 1 COUNT +! ELSE EXIT THEN ; : ?223 223 = IF 1 COUNT +! THEN ; 3.
Как изменится действие слова из определения 2, если ?223 вызывается из другого определения через двоеточие? 4. Имеются ли различия, если в определении следующего слова используется DUP или ?DUP ? : : 0? DUP 0= IF ABORT" Число равно нулю " THEN ; 5. Дайте новое определение слова ?0, используя слово ABORT. 6. Определите слово =IF-ABORT, которое прекращает исполнение программы, если два верхних числа в стеке равны между собой. 7. Определите слово +RANGE-ABORT, которое прекращает работу программы и сообщает об ошибке, если сумма двух чисел в стеке будет превышать максимальное число одинарной длины без знака. 8. Определите слово *+RANGE-ABORT, которое прекращает работу программы, если либо сумма, либо произведение двух чисел в стеке превысят максимальное число одинарной длины без знака. 9. Определите слово STACK-TOO-BIG, которое выдавало бы сообщение об ошибке и прекращало исполнение программы, если в стеке больше 15 чисел. 10. Дайте новое определение следующих слов, не используя слова EXIT: а) : 1TASK 0= IF DOTHAT ELSE EXIT THEN : 6) ; 2TASK 0= IF EXIT ELSE DOTHAT THEN ; в) : 3TASK 0= IF DOTHAT ELSE EXIT THEN DOOTHER ; r) : 4TASK 0= IF EXIT ELSE DOTHAT THEN DOOTHER :
Это послужит убедительным доказательством, что слово EXIT совершенно не нужно в невложенных конструкциях IF...THEN.
Множественный выбор ветвления
До сих пор в этой главе мы рассматривали конструкции IF...THEN и IF...ELSE...THEN, которые позволяют делать переход на исполнение одной из двух ветвей программы в зависимости от того равно или не равно нулю число в стеке. Однако вам может потребоваться программа, которая может разветвляться по альтернативным путям в зависимости от того, какое из нескольких чисел находится в стеке. В гл. 6 мы показали один из вариантов решения этой задачи. Векторное исполнение программы - это одно из средств реализации ветвления по нескольким путям. Если компоненты вектора представляют собой адреса слов, то данное слово может быть исполнено, если взять eго адрес и применить к нему оператор EXECUTE.
Таким образом, слово, на исполнение которого уходит программа, зависит от одного из нескольких чисел, которое находится в стеке, когда выбирается конкретный адрес. В гл. 6 мы хранили адреса слов в массиве и использовали слова FIND или ', в зависимости от применяемой версии Форта. Давайте снова рассмотрим пример из гл. 6. Пусть слова 1TASK, 2TASK и 3TASK определены. Можно задать вектор CHOICE (выбор) следующим образом: CREATE CHOICE 6 ALLOT FIND 1TASK CHOICE ! FIND 2TASK CHOICE 2 + ! FIND 3TASK CHOICE 4 + !
Теперь если мы определим слово DOCHOICE (сделай_выбор) : DOCHO1CE 1- 2* CHOICE + @ EXECUTE ; то исполняемое ветвление будет зависеть от числа, которое находится в стеке, когда запускается программа DOCHOICE.
Эта конструкция носит название выбор по целому. Если в стеке находится 1, то выполняется 1TASK, если 2- то 2TASK и т.д. Такая конструкция выбора по целому, основанная на векторном исполнении, едва ли не самое простое, что может быть придумано в стандартном языке Форт. Разнообразным реализациям структуры выбора по целому был посвящен целый выпуск журнал "FORTH Dimensions" (1980- Т. 2, вып. 3). В некоторых коммерческих версиях Форта реализован сложные конструкции выбора по целому. Мы рассмотрим слова NCASE и ACASE из языка MMSFORTH. Проще всего для понимания слова NCASE привести простой пример. Пусть вы хотите выполнить 1TASK, 2TASK или 3TASK в зависимости от того, какое из чисел: 20, 40 или 60 находится в стеке, а если в нем встречается какое-либо другое число, нужно, чтобы исполнялось слово OTHER (другая). Вот как это можно сделать : NCASE 20 40 60 " 1TASK 2TASK 3TASK OTHERWISE OTHER CASEND Оператор NCASE снимает число из стека и просматривает, какому из следующего за ним ряда сел оно равно. Если обнаруживается совпадение, то исполняется соответствующее слово из пере численного списка, если совпадение не обнаружено, то исполняется слово или последовательность слов, находящихся между OTHERWISE (иначе) и CASEND (конец_выбора). Слово OTHERWISE и любые слова после него не являются обязательными, т.е.
если в данном случае вы не хотите, чтобы альтернативным путем было использование слова OTHER, то можете использовать программу NCASE 20 40 60 " 1TASK 2TASK 3TASK CASEND А теперь рассмотрим более сложный, но и более полезный пример. Пусть у вас есть программа, в которой используется слово KEY для ввода чисел. Вы хотите, чтобы она печатала все, что является алфавитно-цифровыми знаками, при вводе CtrI-C (код ASCII 3) очищала бы экран с помощью слова CLS, при вводе Ctrl-G (код ASCII 7) включала бы звуковой сигнал (с помощью слова ВЕЕР) и при Ctrl-P (код ASCII 16) вывод переключался бы на принтер (с помощью слова PRNT), Это может делать следующая программа : : GETKEY KEY DUP NCASE 3 7 16 " CLS BEEP PRNT OTHERWISE DUP 32 < IF DROP ELSE EMIT THEN CASEND ;
Надеемся, что вы поняли, как она работает.
Одно из самых полезных применений слова NCASE состоит в обслуживании запросов, связанных с нажатием определенных клавиш при ответе на предложения из меню. Слово NCASE будет работать правильно только с числами, не превышающими по величине максимального значения одного байта (от 0 до 255), и оно устроено так, что фактически игнорирует старший байт 16-разрядного числа. Это значит, что результат будет один и тот же, если NCASE обнаружит число 0A или AB0A (в шестнадцатеричном представлении), т.е. оно ищет совпадение с 0A в одном из следующих после него чисел. Аналогичным образом числа, следующие за NCASE, могут быть больше, чем представляются одним байтом, при этом старший байт числа будет игнорироваться. Таким образом, NCASE 55A3 2221 AC55 " . . . будет исполняться так же, как NCASE A3 21 55 " . . .
Следовательно, поскольку вы уверены, что старший байт числа никогда не может повлиять на выбор, определяемый словом NCASE, можно применять числа больше 255. Слово ACASE в MMSFORTH близко по назначению слову NCASE. Но оно реагирует не на число, а на символы ASCII. Приведем пример его синтаксиса: ACASE NKT" 1TASK 2TASK 3TASK OTHERWISE OTHER CASEND
Слово ACASE просматривает стек, и если оно обнаруживает букву N, то исполняется 1TASK, если букву К- то 2TASK, а при Т уходит на 3TASK.
Если нет совпадения ни с одной буквой, выполняется слово OTHER. Так же как и в NCASE, слово OTHERWISE, слово или слова, следующие за ним до слова CASEND, не являются обязательными. Заметьте, что не должно быть пробела между буквами или между буквами и кавычкой, если только вы не используете пробел для обозначения одного из вариантов выбора. Применение слова ACASE сходно с применением и NCASE, поэтому вместо разбора примеров предлагаем вам освоить его применение на упражнениях.
Упражнения
1. Создайте конструкцию выбора по целому, пользуясь векторным исполнением, которая выполняла бы арифметические действия над вторым и третьим числами в стеке по следующим правилам. Если в стеке находится 1, то слово ARITH выполняет сложение +; если 2, то вычитание -; если 3 или 4. то соответственно операции умножения * или деления /. Измените слово ARITH, пользуясь конструкцией IF так, что если число в стеке не попадает в диапазон чисел от 1 до 4, то в стек помещается 0. 2. Дайте другое определение слова ARITH, используя слово NCASE (включите выдачу нуля в стек, если используется неправильное число). 3. Определите слово NEWARITH, используя ACASE, так чтобы оно ожидало нажатия клавиши. Если будет нажата клавиша "+", числа нужно сложить; если нажата клавиша "-'', то вычесть; если нажата клавиша "*" или "/", то умножить или разделить соответственно. Если нажата неверная клавиша, то в стек должен выдаваться нуль. 4. Переделайте определение NEWARITH в NEWARITH1, используя конструкцию IF...ELSE...THEN. После того как вы это сделаете, вы оцените пользу слова ACASE. 5. Переделайте NEWARITH, как описано в упражнении 3, чтобы при нажатии неправильной клавиши печаталось сообщение " Неверный ввод " и исполнение прерывалось. Проделайте это, применяя слова ABORT и ABORT". 6. Можете ли вы придумать ситуацию, когда предпочтительнее использовать не NCASE и ACASE, а векторное исполнение?
Выводы
В некотором смысле возможность ветвления делает программу более эффективной.
Ветвление позволяет одной программой выполнять различные действия при различных условиях. Но в то же время применение ветвления заставляет программиста проявлять находчивость. Применение ветвления может привести к исключительной сложности программы. Таким образом, ветвление требует тщательного продумывания при составлении программы. Без ветвления исполнение программы представляется последовательностью операций, выстроенных в линию. При каждом запуске программы одно слово исполняется за другим в одной и той же последовательности. Действительно, если не пользоваться ветвлением и векторным исполнением, то, хотя при этом может получаться длинная и практически неудобная для чтения программа с большой избыточностью, для нее не потребуется определять какие-либо новые слова, кроме имен программ. Альтернативы, обеспечиваемые с помощью ветвления, требуют определения новых слов и, таким образом, создают дополнительные сложности при разработке программы. Ветвление более, чем что-либо другое, требует тщательной проработки, т.е. программист должен предусмотреть все возможные варианты выбора. Много времени и терпения может потребоваться, если, написав программу, программист обнаруживает неучтенные варианты разветвления, так как при этом часто требуется переписать основные куски программы. Хотя структура языка Форт способствует облегчению решения таких проблем, но чем больше вариантов (ветвлений) нужно включить в программу, тем тщательнее она должна быть продумана.
Необходимость такого планирования вносит противоречие в подходе к разработке программ между программированием сверху вниз, при котором обдумываются все детали, прежде чем перейти к составлению текста программы, и программированием снизу вверх, при котором программист сразу же начинает определять и проверять действие новых слов, разумно полагаясь, что его интуиция не приведет, к ошибке. Как мы увидим в гл. 13, хорошая методика программирования на Форте включает в себя оба подхода одновременно, привлекая к разработке программы интуицию, творчество и испытывая чувство удовлетворения от проделанной работы.
Организация циклов
Возможно, одним из наиболее полезных свойств компьютера является его способность многократно и с очень большой скоростью повторять операции. Циклы составляют часть программы, в которой повторяются одни и те же действия. Почти в каждой программе так или иначе применяются циклы. Как вы увидите в гл. 15, сам Форт работает как бы в бесконечном цикле.В предыдущих главах мы пользовались наиболее распространенной формой цикла типа DO-LOOP. Однако мы еще не использовали все его возможности. Кроме того, существуют и другие типы циклов с еще большими возможностями, например циклы типа BEGIN...UNTIL, которые вызывают повторение программы только до тех пор, пока не будет удовлетворяться некоторое определенное условие. Сначала мы рассмотрим, как работают счетные циклы DO-LOOP и некоторые их применения.
Циклы типа DO-LOOP
Вы уже знакомы с простейшей формой счетного цикла: : 10LOOPS 10 0 DO 5 . LOOP , Слово 10LOOPS при исполнении напечатает на экране число 5 10 раз (т.е. от 0 до 9), а когда будет достигнут счет 10, циклическая программа прекратится. Между прочим, так же, как и конструкцию IF...THEN, счетный цикл можно использовать только внутри определений через двоеточие. По-видимому, самый простой счетный цикл, имеющий практическое применение, это : PAUSE 2790 0 DO LOOP ; ( Пауза) который приостанавливает программу, пока исполняется пустой цикл. Таким образом, слово PAUSE приостанавливает программу на время 2790 циклов или на 1/10 с на IBM РC в версии MMSFORTH; слово PAUSE может быть вставлено в другой цикл для получения еще больших задержек. (Если вы привыкли к языку Бейсик, то для вас будет неожиданностью, насколько быстрее выполняются циклы в Форте.) Очень важным моментом является то, что циклы могут вставляться в другие циклы. Если вы определите слова : 1LOOP 5 0 DO ." Внутренний цикл" LOOP ; и : 2LOOP 5 0 DO CR ."Внешний цикл" CR CR 1LOOP LOOP ; тогда каждый раз, когда будет исполняться цикл в программе 2LOOP, она напечатает в начале строки свое сообщение "Внешний цикл", сделает два перевода строки, а затем напечатает сообщение из программы 1LOOP 5 раз, пока будет исполняться эта программа; так будет повторяться 5 раз, следовательно, сообщение "Внутренний цикл" будет напечатано 25 раз.
Циклы, находящиеся внутри других циклов, называются вложенными; глубина вложенности может быть такой, какой она практически потребуется. Ради интереса попробуем, пользуясь циклами, сравнить скорость работы языка Форт по отношению к Бейсику. Цифры, приведенные здесь, относятся к IBM PC для МMSFORTH и версии Бейсика фирмы Microsoft. Первая программа цикла на Бейсике 10 DEFINT I,C 20 FOR I=1 ТО 10000 30 NEXT I выполняется 11.5 с (заметьте, что для корректности по отношению к Бейсику мы пользуемся целыми числами). Если мы напишем программу : TEST 10000 0 DO LOOP : то она будет исполняться слишком быстро, поэтому для определения времени ее работы мы будем вынуждены включить ее во вложенный цикл: : TESTA 10 0 DO TEST LOOP ; который исполняется в течение 3.6 с, поэтому слово TEST, которое повторяет пустой цикл 10000 раз, исполняется за 0.36 с. Таким образом, счетный цикл на Форте работает в 32 раза быстрее, чем на Бейсике. Рассмотрим теперь некоторые арифметические операции. Если мы вставим 25 C=10 в первую программу, то ее исполнение удлинится до 17.5 с. Программа на Форте, эквивалентная ей в первом приближении: : TEST 10000 0 DO 10 DROP LOOP ; исполняется за 0.71 с. Теперь в строке 25 на Бейсике поместим 25 С=10+10 тогда выполнение программы займет 23.1 с. Следовательно, исполнение 10000 сложений целых чисел занимает 23.1 - 17.5, или 5.6 с. На Форте для исполнения программы : TEST 10000 О DO 10 10 + DROP LOOP : потребуется 1.13 с, т.е. 1.13 - 0.71 " 0.42 с на 10000 операций сложения. Следовательно, Форт быстрее Бейсика по операции сложения в 13 раз. Аналогичное испытание показывает, что для операции умножения на Бейсике требуется 5.7 с, а на Форте - 0.7 с, т.е. в 8 раз меньше. Другими словами, для операций с целыми числами Форт работает примерно в 8 - 30 раз быстрее, чем Бейсик. Еще более интересно сравнить операции над числами с плавающей запятой для процессора Intel 8087. При замене строки 25 на 25 D=10E10 программа работает 21.4 с; если а строке 25 будет 25 D=10Е10+10Е10.
то программа исполнится уже за 28.2 с, т.е. время исполнения 10000 сложений чисел с плавающей запятой составляет 28.2 - 21.4 = 6.8 с. Программа на Форте : TEST 10000 0 DO % 10Е10 FDROP LOOP ; потребует 0.79 с, а программа : TEST 10000 0 DO % 1Е10 % 1Е10 F+ FDROP LOOP ; исполняется за 1.23 с, т.е. 10000 сложений чисел с плавающей запятой происходят за 1.23 - 0.79 = 0.44 с, в 15.5 раза быстрее, чем на Бейсике, и менее чем на 5% медленнее операции сложения целых чисел на Форте. Умножение на Бейсике занимает 16.8 с, на Форте - 0.46 с, т.е. в 16 раз быстрее и фактически на 34% быстрее, чем умножение целых чисел на Форте (мы уже говорили в гл. 4, что некоторые операции с плавающей запятой на процессоре 8087 выполняются быстрее, чем с целыми числами). Извлечение квадратного корня на Бейсике производится за 14.5 с, а на Форт - за 0.87 с, т.е. в 39 раз быстрее. На самом деле эти сравнения не очень корректны по отношению i Форту, поскольку последний совместно с процессором 8087 имеет в 100 раз больший диапазон представления чисел и более чем в 4 раза лучшую точность по сравнению с числами одинарной точности в языке Бейсик. Если на Бейсике производится умножение чисел двойной точности, то фактор преимущества в скорости для Форта равен 48, и это, несмотря на больший диапазон и повышенную точность. Умножение на современной быстродействующей универсальной ЭВМ типа CDC Cyber выполняется в 20 раз быстрее, чем на Форте с микропроцессором 8087. Однако затратив менее 10i долл., можно увеличить скорость ваших вычислений больше, чем при переходе на универсальна ЭВМ стоимостью около миллиона долларов.
Возвращаясь из нашего экскурса, мы должны рассмотреть индекс счетного цикла. Индекс представляет собой текущее значение счетчика, начинающего счет с нижнего значения и при каждое повторении цикла изменяющегося на заданную величину приращения. (Индекс помещается на вершину стека словом I, наименование которого происходит от Index, или, если вам больше нравится, указывающего, что выполняется I-й цикл.) Таким образом,программа : SHOW-INDEX 10 5 DO I .
LOOP ; напечатает на экране 5 6 7 8 9
Заметьте, что числа следуют только до 9. Так происходит потому, что индекс цикла инкрементируется словом LOOP и, когда он достигает крайнего значения, в данном случае 10, происходит вы ход из цикла без возврата к слову DO еще раз. Очевидно, что если повторение исполнения цикла производится с помощью DO, то в стеке должны быть по крайней мере два числа. Верхнее число служит начальным значением индекса цикла, с которым происходит вход в цикл. Второе число на стеке - это предельное значение параметра цикла, по достижении которого с помощью инкрементирования индекса происходит выход из цикла. Если мы определили слово как ; SHOW-INDEX 7 2 DO I . LOOP ; то оно выдаст на экране 2 3 4 5 6 Обе последние программы повторяются по 5 раз, но дают разные результаты вследствие различных начальных и конечных значений индекса цикла. Задание диапазона изменения индекса - это важный момент, потому что значение индекса часто используется для вычислений внутри цикла. Имеются некоторые отличия в способе завершения счетного цикла в стандартах Форт-79 и Форт-83. Рассмотрим слово : 1LOOPTEST 5 5 DO I . LOOP ; в Форт-79 слово 1LOOPTEST попросту напечатает число 5 и программа остановится, напротив, в Форт-83 вы увидите 5 6 7 ... 32766 32767 -32768 -32767 ... -1 0 1 2 3 4 т.е. будут напечатаны 65535 чисел. Теперь рассмотрим пример другой программы: : 2LOOPTEST -32766 32766 DO I U. LOOP ; которая в Форт-79 также напечатает только одно число 32766 и остановится, в то время как в Форт-83 она выдаст 32766 32767 32768 32769
Согласитесь, что эти различия приводят в замешательство.
Что же происходит? Что касается Форт-79, то объяснение простое. Слово LOOP добавляет 1 к индексу и после этого проверяет, не нужно ли выйти из цикла, если индекс достиг или превысил величину предельного значения параметра цикла, рассматриваемого как число со знаком. Поэтому обе тестовые программы в Форт-79 печатают только одно число - начальное значение индекса и прекращают работу.
Что касается Форт-83, то его реакция более хитроумная. Он принимает решениe выйти из цикла не на основании факта равенства или превышения индексом предельного значения параметра цикла, а, скорее, на основании факта перехода от состояния, когда предел не был достигнут, к состоянию, когда индекс достигает значение предела. В первом случае 1LOOPTEST, инкрементируя индекс, изменяет его с 5 на 6, поэтому необходимое изменение состояния не происходит, следовательно, цикл должен продолжаться со значениями индекса 7, 8 и т.д. После достижения индексом значения 32767 добавление единицы приводит к изменению знака числа, так как число без знака 32768 представляет собой число со знаком -32768, но опять же изменение состояния не происходит. Каждый раз при исполнении цикла индекс увеличивается на 1 до тех пор, пока он не достигнет значения 4, а затем изменится на 5, тут и происходит выход из цикла.
Действие второй тестовой программы в Форт-83 можно проанализировать аналогичным образом, Добавление единицы к 32766 приводит к превышению предела, но переход от состояния индекса ниже предела к состоянию, когда он равен пределу или превышает его, не происходит, поэтому цикл продолжается до тех пор, пока не произойдет переход от -32767 к -32766 или, если рассматривать числа без знака, от 32769 к 32770. Это как раз необходимый переход состояния, поэтому цикл прекращается. Важно помнить, что если в Форт-83, начальное значение индекса равно или превышает предельное значение, то цикл будет продолжаться до тех пор, пока индекс не приблизится к пределу снизу. Форт-83 работает именно таким образом, в особенности если используются числа без знака; это обеспечивает возможность обращения с помощью индексации циклов ко всем 64К ячейкам памяти.
Следует также помнить о том, что Форт-79 выходит из цикла, принимая во внимание значение числа со знаком, поэтому программа : 3LOOPTEST 3 -3 DO I . I U. LOOP ; выдает -3 65533 -2 65534 -1 65535 0 0 1 1 2 2 Очевидно при первом инкрементировании величина индекса без знака превышает значение предела, но слово LOOP следит за условием с учетом знака.
Форт- 83 даст такой же результат, но не из-за того, что рассматривается число со знаком или без знака, а потому что происходит переход значения индекса от величины ниже предела к величине, равной пределу. Эти тонкости прекращения цикла могут быть особенно обескураживающими в случае, когда начальное значение индекса равно пределу, в частности в языках Бейсик, Фортран и других. Например, программа на Бейсике 10 FOR I = 5 ТO 5 20 PRINT I 30 NEXT I не будет ничего делать, потому что индекс цикла равен пределу и тело цикла игнорируется (операторы PRINT I и NEXT I никогда не исполняются). С другой стороны, мы видели, что в программе 1LOOPTEST, которая также начинается со значения индекса, равного пределу, слова внутри цикла исполняются, печатая 5, хотя зачастую это нежелательно. Хуже того, программа 1LOOPTEST в Форт-83 исполняется 65535 раз, пока не произойдет переход от 4 к 5. Очевидно, что вы этого вовсе не хотели. Можно избежать неудобств с помощью конструкции IF-THEN. Если не исключена возможность, что в цикле типа DO...LOOP начальное значение индекса больше или равно пределу, то вы можете защитить программу, например, таким образом: ... OVER OVER >
IF DO ( тело цикла) LOOP THEN ...
Если начальное значение индекса больше предела, цикл не будет исполняться. Циклы в Форте не так изящны, как в Бейсике, но, как вы уже видели, они работают существенно быстрее. Следует иметь в виду еще одну деталь: слово I может встречаться только в определениях слов, которые включают в себя циклы DO-LOOP. Поэтому не будут работать следующие определения: : IN-LOOP I . ; : TRY-LOOP 5 0 DO IN-LOOP LOOP ; Слово IN-LOOP выдаст вам какую-нибудь бессмыслицу. Во многих версиях Форта предусмотрено необязательное слово I', которое должно работать следующим образом. Если мы переопределим слова предыдущего примера: : IN-LOOP I' . ; : TRY-LOOP 5 0 DO IN-LOOP LOOP : то слово IN-LOOP напечатает значение индекса цикла, т, е. что мы хотели бы увидеть в первом примере. Таким образом, слово I' возвращает значение индекса цикла, если оно используется внутри слова, которое вызывается из цикла.
В большинстве реализации Форта слово I' исполняется только в том случае, если оно используется в слове, вызываемом непосредственно из цикла, поэтому нельзя написать программу : 2IN-LOOP I' . ; : 1IN-LOOP 2IN-LOOP ; : TRY-LOOP 5 0 DO 1IN-LOOP ; И поэтому TRY-LOOP выдает что-нибудь несуразное. Причину этого вы поймете в следующей главе. В большинстве версий слово I' вызывается непосредственно внутри цикла, где оно выдает предельное значение индекса цикла. Так, программа : SHOWLIMIT 5 0 DO I . I' . CR LOOP ; напечатает 0 5 1 5 . . . 4 5
Для получения значений индексов вложенных циклов используются еще два слова. Если слово I выдает значение индекса внутреннего цикла, то J возвращает значение индекса внешнего по отношению к нему цикла, а К - индекс следующего внешнего цикла. (Слово J входит в стандарты Форт-79 и Форт-83, а слово К предусмотрено только в некоторых версиях.) В качестве примера рассмотрим программу : TEST 5 0 DO 5 0 DO 5 0 DO I . J . К . CR LOOP LOOP LOOP ; Запустив слово TEST, вы получите на экране 0 0 0 1 0 0 2 0 0 . . . 4 0 0 0 1 0 1 1 0 . . . 0 0 1 1 0 1 . . . 4 4 4
Возможность пользоваться вложенными циклами и их индексами обеспечивает широкий диапазон применений, одним из которых является обращение с двумерными массивами. Пусть у вас имеется матрица MATRIX размерности 5х5 в версии MMSFORTH. Программа .MATRIX (печать_матрицы), которую мы приводим, напечатает эту матрицу по строкам с аккуратно оформленными столбцами: : .MATRIX 5 0 DO 5 0 DO I J MATRIX @ 6 .R LOOP CR LOOP ;
Внешний цикл обеспечивает переход от строки к строке, в то время как внутренний цикл перебирает элементы матрицы в строке, извлекает их содержимое и печатает значения элементов матрицы, производя выравнивание их вправо в поле шириной шесть позиций. С помощью циклов можно сделать еще многое, но, прежде чем продвигаться дальше, рассмотрим еще один пример, а потом перейдем к упражнениям. Циклы очень важны в программировании, но мы можем только слегка затронуть рассмотрение их возможностей.
Другие примеры мы приведем в следующих главах. Давайте попробуем написать программу, которая возводит в степень второе число в стеке, при этом показатель степени указывается числом на вершине стека. Таким образом, 2 4 ** должно выдать число 16. Вот эта программа: : ** ( n1 n2 - n1^n2 ) OVER SWAP 1- 0 DO OVER * LOOP SWAP DROP ; Рассмотрим возведение числа 2 в 4-ю степень. Если числа 2 и 4 находятся в стеке, то последовательность операций OVER SWAP 1- возвратит в стек 223. Затем 0 DO запустит цикл, который будет исполняться 3 раза, а в стеке останется число 2. Затем после каждого исполнения цикла в стеке будет наблюдаться следующее: 2 4 2 8 2 16 Предложение SWAP DROP производит уничтожение в стеке числа 2, которое остается после завершения цикла. Данный пример показывает три общих проблемы применения циклов. Во-первых, часто необходимо переставить данные в стеке, прежде чем войти в цикл. Во-вторых, поскольку цикл многократно использует число, обычно нужно применять на вершине стека операции DUP, OVER и др. И, в-третьих, по завершении цикла в стеке может что-то оставаться, поэтому в конце программы может потребоваться оператор DROP. Но не будем торопиться, посмотрим, что произойдет, если мы захотим возвести число в первую степень? Программа ** выполнит один цикл и выдаст в результате квадрат числа, т.е. введете ли вы 1 или 2, получится один и тот же ответ. Это как раз пример неудобства проверки индекса в Форте на предельное значение, о котором мы уже говорили выше. Но выход есть. Сделаем определение ** таким: : ** ( n1 n2 - n1^n2 ) OVER SWAP 1- 0 OVER OVER <>
IF DO OVER * LOOP ELSE DROP DROP THEN SWAP DROP ; Предложение OVER OVER <> IF разрешает запустить цикл только в том случае, если показатель степени больше 1. В противном случае значение показателя степени и его копия снимаются со стека двумя словами DROP, заключенными между словами ELSE и THEN. Этот пример приведен для того, чтобы показать как можно вообще избежать запуска цикла, если оба значения параметра цикла равны между собой.
Еще более эффективной будет следующее определение слова **: : ** ( n1 n2 -- n1^n2) DUP <>
IF OVER SWAP 1- 0 DO OVER * LOOP SWAP DROP THEN ; Предоставляем вам самостоятельно разобраться, как работает это определение и почему оно лучше, Но и это не все. Осталась еще одна проблема. Любое число, возведенное в нулевую степень, принимается равным 1. Это также должно быть учтено в программе **. Поэтому окончательное определение ** будет таким: : ** ( n1 n2 - n1^n2 ) ?DUP 0= IF DROP 1 ELSE DUP 1 = IF DROP ELSE OVER SWAP 1- 0 DO OVER * LOOP SWAP DROP THEN THEN ; В гл. 16 вы познакомитесь с определением слова ** на языке ассемблера, которое работает значительно быстрее. Следующие упражнения позволят вам еще больше узнать о циклах DO-LOOP.
Упражнения
1. Опишите слово ASCIITAB. предназначенное для того, чтобы напечатать коды ASCII и их алфавитно-цифровые эквиваленты. Перед тем, как использовать это слово, в стеке должны находиться верхнее и нижнее значения кодов ASCII. Тогда 65 68 ASCIITAB должно печатать 65 А 66 В 67 С 68 D 2. Модифицируйте программу ASCIITAB и назовите ее ASCIICHAR. так чтобы она печатала коды и соответствующие и' символы в следующей форме: 33 34 35 36 37 38 39 40 41 42 ! " # $ % & ' - . / ... 113 114 115 116 117 118 119 120 121 122 q r s t u v w x y z по 10 кодов в строке, показывая все печатные символы с кодами от 32 до 122. 3. Опишите слово ZERDIAG, которое обнуляет диагональ матрицы, т.е. результатом ее работы должно быть что-то вроде 0 3 6 7 8 2 0 9 4 3 5 5 0 9 8 1 2 8 0 9 6 1 6 9 0 4. Опишите слово SUMVOL, которое должно вычислять сумму чисел в каждом столбце матрицы размерности 5х5 из чисел длиной в 1 байт, если задан адрес матрицы. Пять значений суммы должны быть выданы в стек. 5. Напишите слово Х^5, которое должно возводить число, находящееся на вершине стека, в 5-ю степень. 6. Полезно было бы иметь эквивалент слова ** для чисел двойной длины D**, которое выдает в результате число двойное длины, если входное число имеет одинарную длину.
Опишите слово D**. 7. Напишите слово DUMP1, которое должно, начиная с известного начального адреса в памяти, представлять на экране следующие 160 байтов в шестнадцатеричной системе в виде таблицы из 10 строк по 16 чисел в каждой. 8. Измените слово DUMP, назвав его DUMP2, так чтобы в начале каждой строки слева был показан адрес первого байта в данной строке. Используйте представление чисел с выравниванием вправо, так чтобы все числа были выровнены в столбцах. 9. Измените слово DUMP, назвав его DUMP3, таким образом, чтобы оно печатало 10 строк (всего 80 байт), в каждой из которых сверху печатались бы числа, а под ними - соответствующие им символы ASCII. Так как символы с кодами меньше 32 нельзя напечатать, замените их точками. 10.Создайте переменную SPCS (от spaces - пробелы). Опишите слово для подсчета пробелов в текстовом массиве размером 1024 байта, начинающемся с адреса, указанного числом в стеке; результат должен быть записан в SPCS. 11. Создайте массив ALPHA, число элементов которого равно числу букв алфавита. Напишите слово ALPHACOUNT (число_букв), которое должно анализировать указанное количество алфавитно-цифровых байтов и записывать число встретившихся в массиве букв в элементах массива ALPHA. Тогда фраза 32201 500 ALPHACOUNT будет просматривать массив из 500 байтов, начинающийся с адреса 322201. и считать число различных байтов, причем число встретившихся литер "А" будет запоминаться в нулевом элементе массива, число литер "В" - в первом и т.д. Желательно превратить все литеры нижнего регистра в литеры верхнего регистра, чтобы не учитывать коды меньше 65 и больше 90. 12. Опишите слово ALPHAPLOT для представления в виде гистограммы частоты появления литер в массиве ALPHA, при этом каждый столбик постройте из соответствующих букв. 13. Не заглядывая в ранее приведенные определения, опишите слово .S.
Еще о циклах типа DO-LOOP
В некоторых случаях нужно, чтобы шаг приращения индекса в цикле был равен не единице, а большему числу. А иногда необходимо, чтобы цикл проходился в обратном направлении.
В таких случаях нужно использовать слово +LOOP. Испробуйте следующую программу: : SKIPLOOP 10 О DO I . 2 +LOOP ; тогда вы увидите на экране 0 2 4 6 8 Индекс в цикле инкрементируется на 2, т.е. на то число, которое +LOOP находит в стеке. Теперь определим слово : COUNTDOWN 0 10 DO I -2 +LOOP ; (обратный счет) Использование слова COUNTDOWN приводит к выводу на экран чисел 10 8 6 4 2 0 Другими словами, индекс получает приращение -2, пробегая от 10 до 0. Обратите внимание, что нуль также был напечатан. Если +LOOP декрементирует индекс (обратный цикл), то цикл заканчивается только тогда, когда индекс оказывается меньше предела в Форт-79 или когда он совершает переход от значения, равного пределу, к значению на единицу меньше предела в Форт-83, в то время как при возрастающем индексе (прямой цикл) выход из цикла происходит, когда индекс становится больше или равен пределу. Заметьте также, что различия в критериях для выхода из цикла +LOOP в Форт-79 и Форт-83 такие же, как и для выхода из цикла LOOP. В последующих упражнениях мы рассмотрим применение циклов с +LOOP.
Бывают случаи, когда необходимо выйти из цикла раньше, чем индекс достигнет значение предела. Чтобы сделать это, применяется слово LEAVE. Попробуйте этот практически бесполезный пример: : LEAVE-LOOP 10 0 DO I . I 5 = IF LEAVE THEN ."Loop" LOOP ; На Форт-79 исполнение LEAVE-LOOP (выйти_из_цикла) дает 0 Loop 1 Loop 2 Loop 3 Loop 4 Loop 5 Loop Обратите внимание, что слово "Loop" (цикл) было напечатано после номера 5, хотя слово LEAVE встретилось раньше и уже было исполнено. Так происходит потому, что в Форт-79 LEAVE просто устанавливает значение предела равным значению индекса, исполняет все, что осталось в цикле, а затем выходит из цикла в том месте, где LOOP проверяет величину индекса. На Форт-83 слово LEAVE-LOOP при исполнении выдает на экране 0 loop 1 loop 2 Loop 3 loop 4 loop 5
Слово "Loop" после номера цикла 5 отсутствует. Это объясняется тем, что на Форт-83, когда встречается слово LEAVE, оно, не обращая внимания на индекс и предел, делает скачок на слово, которое следует сразу же после LOOP.
Различия такого рода незначительные и достаточно тонкие, но, если их не осознавать, они могут вызвать недоумение, когда с ними сталкиваешься. (Мелкое замечание: I. I 5 = в программе LEAVE-LOOP можно записать иначе: I DUP. 5 =, но предыдущая запись предпочтительнее, поскольку она более понятна, а по скорости работы обе конструкции одинаковы.)
Приведем определение более полезного слова GET$. Это слово принимает счетную строку литер с клавиатуры, помещая длину строки в PAD, за которой следуют литеры строки: : GET$ 0 PAD С! 256 0 DO KEY DUP 13 = IF DROP LEAVE ELSE DUP EMIT PAD C@ 1+ PAD C! I PAD + 1+ C! THEN LOOP ;
По адресу PAD вначале записывается нуль, затем запускается цикл, ожидающий ввод с клавиатуры с помощью KEY до 256 символов. Если KEY вводит код 13 (код возврата каретки, одновременно запуска программы), то слово LEAVE выводит программу из цикла или подготавливает к выходу в зависимости от того, в какой версии, Форт-79 или Форт-83, применяется программа. В противном случае следующие после ELSE слова посылают на экран принятый байт и запоминают его в соответствующем месте после адреса PAD, а также увеличивают значение счетчика введенных символов на 1. Можно проверить работу этого слова, если ввести PAD COUNT TYPE. (Заметим, что лучшее определение слова GET$ будет дано в гл. 9.) Другие применения циклов с +LOOP и LEAVE лучше всего рассмотреть в упражнениях.
Упражнения
1. Опишите слово .ARR для печати содержимого одномерного массива чисел одинарной длины, начальный адрес которой и его длина находятся в стеке. 2. Опишите аналогичное слово .SQARR для печати содержимого квадратной матрицы чисел одинарной длины (но не байтов, как в предыдущем примере) в виде строк и столбцов, если адрес и число строк (столбцов) находятся в стеке. 3. Повторите упражнения 1 и 2, но для чисел двойной длины. 4. Преобразование температуры по Фаренгейту в значение, выраженное в градусах Цельсия, производится по формуле С = 5(F - 32)/9 Напишите слово, которое создает таблицу из двух колонок величин, выраженных в градусах Фаренгейта, преобразованных в величины, выраженные в градусах Цельсия через 10 градусов от 0 до 200 градусов Фаренгейта.
Меньшие значения должны стоять в таблице сверху. Переделайте это слово для печати в четыре колонки. 5. Переделайте определение слова из упражнения 4, чтобы большие значения находились сверху таблицы. 6. Напишите слово FINDCHAR (найти_символ), которое будет просматривать последовательность из 1024 литер и выдавать адрес, по которому впервые встречается литера, код ASCII которой помещается на вершине стека. Начальный адрес должен быть вторым в стеке. 7. Опишите слово $=, которое будет просматривать две счетные символьные строки, адреса которых находятся в стеке, и возвращать значение истина, если обе строки идентичны, и ложь в противном случае. Сначала должны быть проверены длины строк, и, если они равны, строки нужно сравнивать посимвольно, применяя цикл, который должен прерываться, если обнаружено несовпадение, чтобы сократить время. 8. Опишите слово $FIND для поиска строки в символьном массиве из 1024 элементов, которое выдает адрес, если заданная строка обнаружена и 0 в противном случае. Адрес начала блока должен быть вторым в стеке, адрес заданной счетной строки поиска должен быть на вершине стека. Используйте цикл для поэлементного сравнения строки с содержимым массива. и, конечно, если какой-либо символ не совпадает, нужно перейти к следующей строке, начинающейся на следующем символе. Считайте, что первый байт в массиве содержит длину строки.
Стек возвратов
Мы уже раньше упоминали о том, что в Форте имеются фактически два стека. Одним из них мы пользовались. Это стек данных, иначе, стек параметров или стек пользователя. Стек возвратов используется главным образом самой Форт-системой. Его основное назначение состоит в слежении за адресом возврата при исполнении слова. Кроме того, в большинстве реализации языка в нем также сохраняются значения пределов для циклов и текущего значения индекса (хотя стандарты этого не требуют). Его также можно использовать для временного хранения чисел. При этом важно помнить, что, если стеком возврата пользуется программист, его нужно восстанавливать в исходное состояние до окончания определения слова или до выхода из цикла.
Для операций в стеке возвратов предусмотрены три стандартных слова: >R (в_стек), R> (из_стека) и R@ (выдать_содержимое). Слово >R берет слово из стека параметров и помещает его в стек возвратов, слово R> забирает число из стека возвратов и помещает его в сек параметров, а слово R@ выдает копию содержимого стека возвратов в стек пользователя, не изменяя содержимое стека возвратов. Приведенные слова используются в стеке возвратов в основном для временного хранения чисел, которые во время исполнения слова могут потребоваться несколько раз. Это может сильно упростить некоторые манипуляции в стеке и зачастую позволяет обойтись без переменных для хранения чисел. Приведем пример. Пусть вы хотите извлечь однобайтовые элементы 5, 7, 11 и 12 из массива с именем DATA в стек пользователя. Вот как это можно определить: : @ELEMENTS ' DATA 5 + С@ ' DATA 7 + С@ ' DATA 11 + С@ ' DATA 12 + С@ ; Однако многократное повторение ' DATA снижает быстродействие. Быстрее будет работать следующая программа: : @ELEMENTS ' DATA >R R@ 5 + С@ R@ 7 + С@ R@ 11 + C@ R> 12 + С@ ;
Обратите внимание, что в последний раз адрес массива DATA берется из стека возвратов с помощью R>, поэтому в нем не остается ничего лишнего, когда мы завершаем исполнение слова по ;. Использование стека возвратов в счетных циклах также очевидно. При исполнении операторов, находящихся внутри цикла, верхний предел цикла лежит вторым в стеке возвратов, а текущее значение параметра цикла, т.е. индекс, - на вершине стека возвратов. Попробуйте исполнить следующее слово: : TRY-IT 5 0 DO R@ . R> R@ . >R SPACE LOOP ; Вы увидите 0 5 1 5 2 5 3 5 4 5 Выражение R@. печатает число с вершины стека возвратов, т.е. индекс, поэтому R@ . можно использовать вместо I . . Затем R> снимает число с вершины стека возвратов, a R@. извлекает число, которое было вторым в стеке возвратов, и печатает его. Это число 5, т.е. верхний предел цикла: Затем >R снова помещает индекс в стек возврата. Попробуйте теперь действие следующих слов: : I-BAD R@ ; (неверное_I) и : TRY-THIS 5 0 DO I-BAD .
LOOP :
Если вы исполните слово TRY-THIS, то увидите какую- то чушь. Но мы только что сказали, что R@ - то же самое, что I, где же ошибка? Вспомните, мы говорили раньше, что стек возвратов используется для экономии внешних ячеек памяти, когда слово вызывается из определения через двоеточие. При обращении к I-BAD адрес возврата помещается в стек возвратов, его вы и видите. Другими словами, когда вызывается I-BAD, адрес возврата помещается на вершине стека возвратов - выше, чем текущее значение параметра цикла (индекса). Поэтому правильное определение I должно быть таким: : I R> R@ SWAP >R : Слово I должно сначала снять число с вершины стека возвратов, потом извлечь содержимое индекса, а затем заменить число на вершине стека возвратов.
Теперь мы сможем дать определение I'. Просмотрите снова слова TRY-LOOP и IN-LOOP несколькими страницами назад и вспомните, что I' вызывается внутри слова, включенного в цикл, чтобы извлечь индекс. IN-LOOP помещает в стек возвратов число (адрес) сверху индекса, а I' еще выше помещает другой адрес. Поэтому, чтобы извлечь индекс, нужно вначале снять из стека возвратов два числа. Правильное определение I' будет таким: : I' R> R> R@ SWAP >R SWAP >R ; Вам должно быть понятно, как работает это определение по аналогии с I. Понимаете ли вы, почему, если использовать его внутри цикла, оно возвращает значение верхнего предела параметра цикла?
Вот так (в упрощенном виде) работает счетный цикл в Форт-79. Слово DO переносит первые два 'пела из стека параметров в стек возвратов. Когда встречается слово LOOP, оно сначала прибавляет I к числу, находящемуся на вершине стека возвратов, а затем сравнивает результат со вторым числом в стеке возвратов, т.е. с пределом на равенство либо превышение. Если это произошло, то управление передается слову, которое стоит после LOOP. Если же индекс не равен пределу и не превосходит его, то управление возвращается слову, которое стоит после DO. Цикл типа +LOOP работает в Форт-79 аналогично, но вместо добавления 1 к числу, находящемуся на вершине стека возвратов, добавляется число, находящееся на вершине стека параметров.
Оперируя стеком возвратов, мы можем определить другие полезные слова для работы с циклами, но лучше всего рассмотреть их в упражнениях.
Упражнения
1. Опишите слово 4PICK, пользуясь стеком возвратов для временного хранения чисел, которое должно печатать и удалять из стека параметров четвертое сверху число. Почему в определении нельзя использовать цикл DO-LOOP? 2. Используя стек возвратов, дайте возможное (но не эффективное) определение DUP с именем DUP1. 3. Что находится в стеке возвратов на первых четырех местах сверху вложенного цикла? Исходя из этого, дайте определение слова J (под именем J1). Не забудьте, что само слово J выдает число в стек. 4. Напишите определение слова К. 5. Опишите слово J', которое выполняло бы функции слова I', т. е. слово, в котором используется J', помещенное во вложенный цикл глубины 2, должно давать значение индекса внешнего цикла. 6. Дайте определение слова LEAVE, назвав его NEWLEAVE, которое будет правильно работать на Форт-79 (вы должны установить значение индекса цикла равным значению его предела). 7. Как можно изменить предел цикла из тела цикла? 8. Вы хотите, чтобы в цикле, завершающемся по LOOP. по достижении индексом значения 10 он изменялся на 15. Какие слова нужно включить в дело цикла, чтобы сделать это? 9. Опишите слово, которое заставляет индекс цикла изменяться на n, где n - число, которое находится в стеке в момент исполнения слова. 10. Что будет делать следующее слово: : DUMB 5 1 DO R> DUP . 1- >R LOOP ; (Оно очень коварно; будьте готовы к перезагрузке вашего компьютера.)
Для тех, кто знаком с дифференциальным исчислением
Упражнения
1. Поэкспериментируйте с разными значениями N в приведенном примере. Программа не работает, если N меньше 200. Почему? Если вы пользуетесь версией Форт, позволяющей работать с плавающей запятой, перепишите программу для чисел с плавающей запятой; она будет работать с меньшими начальными значениями. 2. Напишите программу, которая определяет, через какое время популяция в приведенном примере удваивается.
Используйте LEAVE. 3. Прирост популяции при ограничении на ресурсы описывается уравнением dN К - N ---- = rN ----- dt К где r - максимально возможная скорость роста и К - максимальная популяция, которая может сохраняться при ограниченных ресурсах. Напишите программу, моделирующую процесс роста популяции при начальном значении 1000, г, равном 1/200, и К, равном 3000. 4. А эта задача для честолюбивых. Просмотрите программы для построения гистограмм из гл. 5 и напишите программу для представления роста популяции к упражнению 3.
Циклы с неопределенным числом повторений
Циклы типа DO-LOOP имеют ограниченные пределы, задаваемые из стека. Однако часто нужно иметь циклическую программу, выход из которой зависит от выполнения некоторого условия, которое ею проверяется. Слово LEAVE позволяет это делать, однако существует более удобная форма цикла - цикл с неопределенным числом повторений, который включает слова BEGIN...UNTIL или BEGIN...WHILE..REPEAT. Как и цикл DO-LOOP, неопределенные циклы могут быть исполнены только в определениях через двоеточие.
Рассмотрим вначале цикл BEGIN-UNTIL. Слово BEGIN отмечает начало цикла. Если в стеке находится 0 (флаг ложь), то, когда его встречает слово UNTIL, исполнение повторяется со слова, которое стоит после BEGIN. Иначе говоря, цикл BEGIN-UNTIL повторяется до тех пор, пока в стеке перед словом UNTIL не встретится число, не равное нулю. Для примера определим слово : TIL-10 BEGIN DUP . 1+ DUP 9 > UNTIL DROP ; Исполняя 5 TIL-10, мы увидим 5 6 7 8 9 То есть программа печатала число, находящееся на вершине стека, увеличивала его на единицу до тех пор, пока оно не стало равным 10, и тогда она вышла из цикла.
Приведем более полезный пример. Предположим, что вы сделали в 1989 г. вклад 500 долл. из расчета 10% годовых. Когда на вашем счету будет 1000 долл. или более и сколько будет каждый год? Можно рассчитать это с помощью программы VARIABLE TOTAL 500 TOTAL ! : ?YEAR BEGIN TOTAL @ 10 / TOTAL +! 1+ TOTAL @ 1000 >
UNTIL TOTAL @ . . 500 TOTAL ! ; Обратите внимание, что.
как и в случае цикла DO-LOOP, слова, находящиеся между BEGIN и UNTIL, будут исполнены хотя бы один раз, потому что проверка условия производится последним словом в цикле.
Более гибкой является другая конструкция неопределенного цикла BEGIN...WHILE...REPEAT. Она подобна BEGIN...UNTIL, за исключением того, что REPEAT будет повторять цикл до тех пор, пока в стеке слово WHILE встречает ненулевое значение. Если флаг, предшествующий WHILE, имеет значение истина, то цикл исполняется до слова REPEAT, а затем вновь возвращается к слову BEGIN. Если флаг имеет значение ложь, исполнение переходит на слово, которое следует за REPEAT, покидая цикл. На рис.8.1 схематически показана эта идея.

Рассмотрим другое определение слова TIL-10 из предыдущего примера: : TIL-10 BEGIN DUP 10 < WHILE DUP . 1+ REPEAT : Как и раньше, при исполнении 5 TIL-10 получим 5 6 7 8 9. Но разница есть. Если в первом примере вместо 5 мы введем 15, то будет выведено только число 15, потому что цикл исполнится по крайней мере один раз. Если ввести 15 перед BEGIN во втором примере, то WHILE передаст управление после слова REPEAT и число 15 выведено не будет. Иначе говоря, если конструкция BEGIN...UNTIL требует исполнения находящихся между ними слов по крайней мере один раз, то конструкция BEGIN...WHILE...REPEAT позволяет пропустить все без исполнения, если это необходимо. Вот еще один пример. Это слово, которое разрешает ввод только цифры: : GET# CR ." Введите цифру " BEGIN KEY DUP DUP 48 < SWAP 57 > OR WHILE ." Неверно, должна быть ЦИФРА " REPEAT ; Фрагмент, предшествующий WHILE, будет исполняться всегда, по крайней мере, один раз, а слово после WHILE никогда не будет исполняться при выходе из цикла.
В сущности BEGIN...WHILE...REPEAT можно использовать вместо конструкции IF...THEN (хотя и очень неэффективно). Немного подумав, вы увидите, что BEGIN WHILE ... 0 REPEAT будет действовать так же, как IF ... THEN Почти точно так же BEGIN ... 0= WHILE REPEAT будет давать тот же результат, что и BEGIN ...
UNTIL разумеется, более медленно. Вероятно, чаще всего используют конструкцию BEGIN...WHILE...REPEAT для того, чтобы исключить даже однократное исполнение находящихся внутри слов, пока не будет удовлетворено некоторое условие, как в примере TIL-10. Другое общепринятое применение бесконечного цикла - для того, чтобы какой-либо фрагмент не исполнялся, когда начнет удовлетворяться некоторое условие, как в примере слова GET#. Вот более сложный пример программы, которую вы можете использовать как учебное пособие для освоения кодов ASCII. Она позволяет проверить знание до 20 символов: : ASK CR ." Какой код имеет литера " ; : TOOBIG CR ." Число очень велико. Попробуйте еще." ; : TOOSMALL CR ." Число очень мало. Попробуйте еще. " ; : GUESS 20 О DO CR ." Нажмите любую клавишу буквы или цифры " KEY DUP EMIT >R BEGIN ASK R@ EMIT #IN DUP R@ <>
WHILE R@ < IF TOOSMALL ELSE TOOBIG THEN REPEAT CR ." Вы УГАДАЛИ!!! " R> DROP LOOP ;
После небольшого анализа вы поймете, как работает эта "угадайка". Этот пример незатейливой обучающей программы GUESS показывает, что, применяя цикл BEGIN...WHILE...REPEAT, можно написать программу значительно проще, чем используя только цикл BEGIN...UNTIL.
Упражнения
1. Определите слово .S под именем NEW.S, используя цикл BEGIN... 2. Определите слово ST-SUM для суммирования содержимого стека, используя цикл BEGIN... Дайте другое определение с именем ST-SUM1 с помощью цикла DO-LOOP. 3. Измените программу ?YEAR, назвав ее ?YEAR1. чтобы она печатала год, увеличение вклада за год и сумму на счете в конце каждого года. 4. Пусть популяция мышей увеличивается на 0.05, или 1/200, за каждый день. Напишите слово, позволяющее рассчитать, через сколько дней популяция удвоится. Используйте при этом цикл BEGIN... и LEAVE. 5. Дайте новое определение слова GET$. приведенного выше, с использованием цикла BEGIN... и без ограничения длины строки 256 символами. 6. В MMSFORTH предусмотрено слово Y/N, которое печатает (Y/N)? и останавливается в ожидании нажатия клавиши.
Если нажата клавиша "Y", в стек кладется 0. если "N" - то 1. Если нажимают любую другую клавишу, ожидание продолжается. Определите слово Y/N с именем Y/N1.
Выводы
Циклы представляют собой управляющие структуры, как и рассмотренные в гл. 7 конструкции IF...THEN. И так же, как конструкция IF...THEN относится к фундаментальным средствам управления программным потоком, циклы представляют главное средство повторного выполнения программ, для чего в основном и используются компьютеры. Самой важной причиной могущества компьютеров является их способность манипулировать с битами информации с очень большой скоростью. Если бы не существовало циклов, скорость их работы не приносила бы практической пользы. Иначе говоря, циклы важны для составления практически полезных программ. По существу, язык Форт (либо другой язык с диалоговым режимом, программа редактора и многое другое) представляет собой бесконечный цикл, который периодически посматривает на клавиатуру и исполняет то, что вы приказываете.
Известны циклы двух типов: такие, как DO-LOOP с определенными извне пределами, и неопределенные циклы типа BEGIN...UNTIL, в которых пределы устанавливаются внутри цикла. Язык Форт обращается с циклами по крайней мере не хуже других языков программирования. Заметьте, между прочим, что неопределенные циклы могут быть также, что называется, "бесконечными". Например, следующий цикл никогда не закончится: : INFINITY BEGIN KEY EMIT 0 UNTIL ; Слово UNTIL никогда не встретит значение флага истина, поэтому цикл не прекратится никогда. Следовательно, циклы могут быть опасны, если вы допустите подобную оплошность.
Фактически известен еще один тип цикла, который позволяет организовать так называемую рекурсию. Рекурсия попросту состоит в том, что слово имеет возможность вызывать само себя, что в некоторых случаях может быть очень полезным. Однако для ее правильного использования необходимо хорошо понимать внутреннее устройство самого языка Форт, и мы вернемся к этому вопросу в гл. 15.
Символьные строки
В предыдущих главах мы употребляли термин "строка", предполагая, что его смысл совершенно очевиден. Строка (символьная строки) - это последовательность алфавитно-цифровых символов, которая запоминается в байтах памяти в виде последовательности кодов 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, и др., вы можете полностью переделать саму природу языка. На практике применение подобных приемов позволяет написать на языке Форт интерпретатор другого языка, например, Бейсика.
Выводы
Одна из распространенных претензий к языку Форт состоит в том, что он якобы имеет очень слабые возможности для работы с символьными строками. Не воздерживаются от критики даже те программисты, которые работают на языке Форт, когда они пишут что-либо о языке. Мы надеемся, что вы уже убедились в несостоятельности подобной критики, форт может быть столь же эффективен в обработке строковых данных, как и другие языки. С подобной критикой мы уже встречались в связи с работой с числами с плавающей запятой. Важная цель стандартов языка Форт состоит в том, чтобы не навязывать жесткие ограничения, которые могут препятствовать его развитию и гибкости. С другой стороны, отсюда вытекает, что каждый пользователь должен приспособить язык, чтобы он соответствовал более общим требованиям, и это задает программисту дополнительную работу. Что касается авторов, то мы считаем недостатком стандарта то, что он не определяет лучшие возможности работать с числами с плавающей запятой и символьными строками.Можно с этим соглашаться или не соглашаться, но расширением словаря различных версий Форта может значительно увеличить производительность программистов. Почти все работающие на Форте быстро создают свои пакеты программ и слова, которыми можно воспользоваться при необходимости. Одна из целей данной книги - помочь вам создать такую коллекцию слов и программ; вы сами можете включить некоторые слова из этой главы в свою коллекцию.
Вы уже, вероятно, осознали, что ввод данных с клавиатуры, как числовых, так и текстовых, связан с неудобствами. Например, составленный вами выше телефонный справочник стирается, когда вы выключаете компьютер. В связи с этим нам нужен какой-либо способ запоминать строки и числа на диске. Выход из положения состоит в записи в блоки на диске, которые являются предметом рассмотрения следующей главы.
Манипуляции в стеке
Хотя Форт нетрудно использовать в качестве калькулятора, вы поняли из упражнений, что было бы полезно иметь средства для перемещения чисел внутри стека. Эта потребность обеспечивается словами для стековых манипуляций, которые приведены в . Все они в совокупности позволяют вам перестраивать числа в стеке, как вы захотите.Однако, прежде чем перейти к их изучению, полезно определить слово, которое позволит просматривать содержимое стека, не разрушая его.
Некоторая критика языка Форт
Оппонентами языка Форт чаще всего бывают программисты, не сумевшие понять его общие идеи и большие возможности. Форт устроен так, чтобы обеспечить максимальную мощь и гибкость языка высокого уровня. Это подразумевает, что функции, которые заранее определены в других языках, в минимальных реализациях языка Форт могут отсутствовать. Например, стандарт языка Форт описывает только простейшие методы обращения с символьными строками, не содержит процедур для работы с матрицами и векторами, не обеспечивает чтение данных из файлов, простейшие реализации не могут обращаться с числами с плавающей запятой (как, например, число 293.45, которое имеет целую и дробную части). Кроме того, считается, что Форт-программу трудно читать и понимать, потому что слова в определениях могут быть названы короткими и загадочными именами. Ни одно из этих критических замечаний в действительности не обосновано, поскольку они относятся к конкретным реализациям и версиям Форта или к программистам, работающим на этом языке.Поскольку Форт допускает определение слов на языке ассемблера, буквально все, что можно запрограммировать на других языках, может быть сделано и на Форте. Можно даже написать на языке Форт другие языки программирования. А так как на языке Форт легко определяются новые слова, то нетрудно написать процедуры либо на языке ассемблера, либо с помощью других слов Форта, которые отсутствуют в стандарте, т.е., например, слова для работы с символьными строками или матрицами. Работая на языке Форт, вы можете сами определить новые слова, которые нужны для вашей конкретной задачи. Например, если вы однажды определите слово для вычисления определителя матрицы, то в дальнейшем вам не потребуется проделывать это снова. Оно станет частью вашего варианта языка. Различные версии (диалекты) языка можно развивать для удовлетворения потребностей каждого программиста. Форт не очень удобен для сложных действий с числами, если не расширить его специальными словами, то же относится и к работе с символьными строками.
Но если уж вы введете эти расширения, то они будут как раз тем, что вам нужно, поскольку вы сделали их сами. В некоторых поставляемых версиях языка Форт заранее определено больше слов, в других -- меньше. Основная цель этой книги состоит в том, чтобы показать вам, как можно приспособить версию языка, с которой вы работаете, к вашим задачам, например, к обработке символьных строк. Чтобы использовать всю мощь языка Форт, вам необходимо сделать впрок некоторый программный вклад, зато потом вы получите единственный в своем роде язык, который лучше всего подходит для ваших задач. Верно, что читать чужую Форт-программу может быть трудно. Но в этом вина программиста, а не Форта. Если вы применяете загадочные имена для своих слов или записываете их определения очень плотно, не предусматривая пробелов для выделения строк, подчеркивающих логический поток программы, или слишком ленивы, чтобы включать комментарии, то можно гарантировать, что ни вы и никто другой не сможет понять то, что написано. Но если вы стараетесь дать каждому слову общепонятное имя, записываете определения, применяя, где нужно, пробелы, отступы и разбиение по строкам для улучшения удобочитаемости, и, кроме того, побеспокоились о включении в программу комментариев, то вашу Форт-программу можно будет прочитать так же просто, как обычную английскую прозу.
Форт -- это язык, который имеет огромную мощь и гибкость. От программиста же ожидается готовность приспособить язык к своим индивидуальным потребностям, а также глубокое продумывание и тщательное программирование, чтобы написанное вами имело смысл.
Операции с байтами
Иногда говорят о наиболее.- наименее значащих разрядах десятичного числа. Например, в числе 456 наиболее значащий разряд -- 4, наименее значащий 6. Таким образом, 456 -- это 400 +50+6, где 4 указывает, сколько в числе содержится единиц самого старшего разряда, т.е. сотен. Точно так же мы говорим о наиболее и наименее значащих разрядах двоичного числа. В числе 10110 наименее значащий разряд -- это самый правый разряд (0), а наиболее значащий -- первый слева (1). Рассматривая байты, мы поступаем так же : разбиваем 16-разрядное число на два байта (числа по 8 разрядов) -- более значащий (старший) и менее значащий (младший). В шестнадцатеричном числе 10А2 старший байт равен 10, младший -- А2. Если число десятичное, то выделение байтов производится не так просто. Для чисел больше 255 значение старшего байта можно найти из табл. 3.2.Таблица 3.2. Старший и младший байты чисел
Десятич. Шестнадца- Старший Младший число терич.экв. байт байт Дес. Шест. Дес. Шест . 001 0001 000 00 001 01 255 00FF 000 00 255 FF 256 0100 001 01 000 00 257 0101 001 01 001 01 511 01FF 001 01 255 FF 512 0200 002 02 000 00 513 0201 002 02 001 01 767 02FF 002 02 255 FF 768 0300 003 03 000 00 769 0301 003 03 001 01
Число 255, или FF, -- это наибольшее число, которое может находиться в младшем байте. Если число больше 255, то в старший байт нужно добавить 1. Поэтому число 256 имеет 1 в старшем байте и 0 -- в младшем. Теперь заметим, что число 512, которое получается добавлением еще одной 1 к старшему байту, в 2 раза больше 256, а 768 в 3 раза больше 256. Другими словами, старший байт увеличивается на 1 каждый раз, когда число увеличивается на 256.
Теперь рассмотрим два слова С@ и С! для операций с отдельными байтами. Слово С@ похоже на слово @, но оно извлекает значение только одного байта (буква С в имени происходит от слова character, т.е. символ, потому что любой символ ASCII представляется одним байтом). После С@ старший байт в стеке должен быть равен 0. Понимаете ли вы, почему ?
Слово С! подобно !, но оно производит запись только младшего байта числа, находящегося в стеке.
Оно обычно используется, если число в стеке не превосходит 256. Проделаем несколько экспериментов. Слово PAD (буфер, записная книжка) выдает в стек адрес в памяти, который является началом буфера, где пользователь может хранить свои данные. Положите в стек число 257 и занесите его в PAD с помощью
257 PAD !
Теперь давайте посмотрим, что окажется в каждом байте по адресу PAD и PAD+1, для чего введем
PAD С@ . PAD 1 + С@ .
Вот что вы увидите:
1 1 ok
Теперь повторите то же самое с числами 255 и 256. Потом попробуйте числа из . Вы будете видеть раздельно младший и старший байты вводимых чисел. Понятно ли вам, почему ? Вы записываете в PAD число целиком с помощью !, но извлекаете его в стек побайтно и поэтому видите оба байта. Порядок следования байтов может меняться от компьютера к компьютеру, в зависимости от того, как хранятся старший и младший байты числа в памяти. В большинстве случаев старший байт числа хранится в более старшем адресе. Дальнейшее рассмотрение операций над байтами продолжим в упражнениях.
Операции с битами
Иногда бывает необходимо изменить отдельные разряды в числе. Например, компьютер TRS-80 определяет, занят ли принтер, по состоянию одного разряда (включен-выключен), точно так же ЭВМ TRS-80 и IBM PC определяют состояние телекоммуникационной связи через модем по состоянию (статусу) нескольких битов в трех байтах. Так, если один определенный бит установлен в "1", то данные принимаются по телефонной линии, но если этот бит сброшен в "0", то линия свободна и можно передавать данные. Поэтому зачастую полезно иметь возможность просматривать состояние, а также изменять значения отдельных битов. В оставшейся части этого раздела мы будем использовать компьютер для ввода и вывода двоичных чисел, поэтому вам нужно установить двоичное основание, т.е. записать в переменную BASE число 2. Если вашему компьютеру известно слово BINARY, то достаточно ввести это слово. (Если вы не уверены, попробуйте его, худшее, что может произойти, -- это получение сообщения об ошибке.) Если слово BINARY у вас не работает, введите2 BASE !
Теперь, если вы попытаетесь ввести цифру больше 1, Форт будет отвергать ее как не имеющую смысла в двоичной системе счисления. Представим себе, что в стеке находится двоичное число 10011111 и вы хотите изменить третий справа разряд в "0", т.е. вы хотите превратить это число в 10011011. Слово AND (И) позволит вам сделать это. Слово AND сравнивает поразрядно два числа, и если в обоих (одном и другом) эти разряды установлены в "1", то результат будет 1, иначе результатом будет 0. Попробуйте ввести следующее предложение:
10011111 11111011 AND U.
и вы увидите следующий результат:
110011011 ok
Нагляднее всего проследить, что произойдет, если поместить числа одно под другим:
10011111 11111011 AND U.
это приведет к такому результату:
10011011 ok
Второе число, 11111011, называется разрядной (битовой) маской. Разрядная маска -- это всего-навсего число, которое сравнивается с другим числом с целью изменить значение отдельных битов.
Теперь представим себе, что вы хотите снова изменить значение разряда 0 в 1. Слово AND не сможет этого сделать. Вместо него мы используем слово OR (ИЛИ). Оно также производит поразрядное сравнение двух чисел, но. если хотя бы один из разрядов установлен в " 1", то в результате тоже будет "1". Попробуем установить в "1" третий разряд в последнем примере. Для этого случая мы используем маску 00000100:
10011011 00000100 OR U.
что приводит к
10011111 Ok
т.е. к исходному числу.
Третье полезное слово XOR (исключающее ИЛИ) также производит поразрядное сравнение. Если два разряда различны, то в бите результата устанавливается "1", если одинаковы, -- то "0". Пусть вы хотите изменить значения всех разрядов числа на противоположные, т.е., например, преобразовать число 10011011 в 01100100. Попробуйте сделать так :
10011011 11111111 XOR U.
и получите
01100100 ok
Так как в маске все разряды были установлены в "1", то там, где в исходном числе была 1, в результате стал 0, а если был 0 -- стала 1.
Есть еще одно слово, NOT (HE), которое в различных версиях языка определено не очень четко. В большинстве версий, в том числе в Форт-79, слово NOT не изменяет значение битов, оно делает нечто иное, о чем мы расскажем в одной из следующих глав. Однако в Форт-83 слово NOT изменяет значение разрядов на противоположное. Таким образом,
00000000101010 NOT U.
приведет к результату
11111111010101 ok
Операция NOT не эквивалентна вычитанию каждого разряда из двоичной единицы. Сможете ли вы определить на Форт-83 слово NOT, используя XOR ? Слова AND, OR, XOR и NOT имеют и другие применения, с которыми мы познакомимся в упражнениях.
Полезная программа
Хотя из соображений удобства мы знакомим вас с языком Форт на примере арифметических операций, нужно подчеркнуть, что компьютеры приносят большую пользу не только в математике. Например, рукопись этой книги была подготовлена с помощью программы обработки текстов, написанной на языке Форт. Одно из наиболее полезных применений компьютера состоит в преобразовании огромного количества данных в такую форму, которая легче воспринимается человеком. В частности один из лучших способов -- представление данных в графической форме. Несмотря на то, что многие языки программирования (включая некоторые версии Форта) имеют множество сложных графических команд, одним из наиболее распространенных способов представления графических данных является "быстрый и грубый" график, построенный из прямых линий или столбиков. Возможно, вы привыкли к представлению столбиков в виде сплошных вертикальных прямоугольников, однако неплохо выглядят также столбики, построенные из рядом стоящих букв, которые печатаются по горизонтали. Например,хххххххххх ххххххххххххх ХХХХХХХХХХХХХХХХ ххххххххххх ххххххх
представляет собой вполне наглядную гистограмму. Мы проследим весь процесс составления программы для построения подобной гистограммы, а затем рассмотрим ее с точки зрения структуры Форта, после чего вы сможете модифицировать программу в следующей серии упражнений. В самом начале определим слово
: TASK ;
TASK -- это слово, которое ровным счетом ничего не делает, кроме того, что помечает позицию в словаре. Но если ввести
FORGET TASK
то слово TASK будет удалено из словаря вместе со всеми теми словами, которые были определены после слова TASK. Поэтому, если вы сделали ошибку в программе и хотите.начать ее сначала, достаточно ввести
FORGET TASK
чтобы снова оказаться в том месте, с которого вы начали. Считается хорошей манерой начинать программу с подобного, не имеющего другого смысла слова, обычно для этого используется именно слово TASK.
Теперь нам нужно описать слово, которое будет печатать на экране строку литер "X" или "столбик".
Мы будем традиционно считать, что ширина экрана равна 64 позициям, таким образом, пределы длины столбика от 1 до 64. Прежде всего нам нужно узнать, как напечатать один символ "X". Самый простой способ -- это использовать слово ." . Вначале определим слово, которое должно печатать "X" один раз, т.е.
: .Х ." X" ;
(В данном случае точкой в названии .X мы отмечаем, что это слово должно что-то напечатать, это общепринятое соглашение на языке Форт). Теперь определим следующее слово:
: TEST 40 О DO .X LOOP ;
и, когда вы введете TEST , вы увидите на экране :
ХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХХ Ok
т.е. 40 букв "X", после которых выведено подтверждение. Слова DO и LOOP, предписывают Форту повторить исполнение слов, находящихся между ними, причем число повторений определяется двумя числами, находящимися в стеке, когда встречается слово DO, в данном случае 40 раз. Зацикливание программы, или многократное исполнение набора инструкций, очень важная возможность языка Форт, как и других компьютерных языков.
Но слово TEST -- это еще не то, что нам нужно: оно печатает столбик из 40 литер "X", а нам необходимо печатать столбики различной длины, в зависимости от числа, находящегося на вершине стека. Чтобы сделать это, нужно просто вынести из определения число, задающее верхний предел цикла DO-LOOP, т.е. мы можем определить слово
: BAR 0 DO .X LOOP CR ;
Так как для слова DO необходимо иметь в стеке два числа, то для исполнения слова BAR (столбик) вам нужно задавать одно из них -- предел цикла. Если вы введете 23 BAR, то на экране увидите
ххххххххххххххххххххххх ok
Обратите внимание, что слово "ok" печатается на следующей строке. Это результат действия слова CR, которое производит перевод строки и возврат каретки. Без слова CR все наши столбики будут выстроены в ряд друг за другом, а не один под другим.
Теперь нам нужно напечатать несколько столбиков различной длины. Другими словами, мы хотим повторить программу BAR несколько раз.
Для этого нам потребуется еще один цикл DO. Определим слово
: TESTGRAPH CR 4 0 DO BAR LOOP ;
и теперь введем строчку
5 10 15 20 TESTGRAPH
Вы увидите гистограмму
хххххххххххххххххххх ххххххххххххххх хххххххххх ххххх
Конечно, данное определение будет исполняться только с четырьмя числами, поскольку в TESTGRAPH задано только четыре повторения цикла.
Мы уже почти пришли к нашей программе построения гистограммы, но в нее нужно ввести еще несколько усовершенствований. Во-первых, мы предполагали, что ширина экрана равна 64 позициям, поэтому число больше 64 недопустимо. Нам нужно как-то ограничить длину столбика. Во-вторых, Форт должен каким-либо образом определить, сколько чисел находится в стеке и, следовательно, сколько нужно построить столбиков. Поэтому число 4 в цикле 4 О DO в программе TESTGRAPH нам не нужно. Будем вводить усовершенствования в программу по одному. Во-первых, как можно ограничить длину столбика ? Вот программа, которая может выполнить это:
: LIMITBAR DUP 64 > IF DROP 64 THEN BAR ;
Рассмотрим, как она работает. Она делает копию числа, находящегося на вершине стека, выражение 64 > сравнивает это число с числом 64, и если оно больше 64, то в стек возвращается значение, которое оператор IF интерпретирует как истину. Если оператор IF встречает значение истина, то исполняется та часть программы, которая находится между словами IF и THEN, в данном случае DROP 64, т.е. убрать из стека число, которое там находится, и положить вместо него число 64. Если число в стеке меньше или равно 64, то выражение 64 > возвращает в стек значение ложь, которое оператор IF воспринимает как указание перейти на слово, следующее после THEN, в данном случае BAR. Если число больше 64, т.е. исполняются слова между IF и THEN, то затем процесс продолжается также после слова THEN, но слово BAR печатает 64 литеры "X", т.е. столбик, занимающий всю ширину экрана.
Этот пример требует некоторого осмысления, но главное, что мы хотели бы в нем подчеркнуть, -- это большая важность в языке Форт конструкции IF...THEN.
Она позволяет программе разветвляться, т.е. производить выбор одной из двух возможностей, как, например, в данном случае: печатать или не печатать столбик из 64 символов "X". Есть и другие способы разветвления программы, но данная конструкция является основной. Ветвление -- это важное свойство любого языка программирования, поскольку оно обеспечивает гибкость и удобство программирования задач любой сложности. Если вы знакомы с другими языками программирования, то, вероятно, обнаружите, что конструкция языка IF-THEN рассматривает слова DUP и BAR как подпрограммы. Подпрограммы (т.е. небольшие программы, находящиеся внутри больших программ) всегда определяются в Форте самостоятельными словами в том смысле, что Форт-программа построена из большого числа коротких подпрограмм.
Второе улучшение состоит в том, чтобы Форт сам определял, сколько чисел нужно отобразить на гистограмме. Это делается с помощью слова DEPTH (глубина), которое возвращает в стек количество чисел в стеке, и не разрушает стек. Например, если ввести
28943 DEPTH .
вы увидите
5 ok
на экране (а сами числа по-прежнему находятся в стеке). Вот теперь мы можем определить слово GRAPH, которое будет строить гистограмму чисел, находящихся в стеке:
: GRAPH CR DEPTH 0 DO LIMITBAR LOOP ;
Теперь посмотрим на всю программу, собранную вместе и с добавленными комментариями:
: TASK ; ( Слово, которое должно забываться при стирании программы) : .X ." X" ; ( Символ для печати) : BAR ( n -- ) 0 DO .X LOOP CR ; (Вывод столбика из X) : LIMITBAR ( n -- ) DUP 64 > IF DROP 64 THEN BAR ; ( Строит столбик из не более 64 символов ) ; GRAPH ( n1 n2 n3 . . . --) CR DEPTH 0 DO LIMITBAR LOOP ;
Чтобы запустить программу GRAPH, вы должны ввести несколько чисел и после этого -- слово GRAPH. Итак, считая, что вначале стек был пустым, после ввода
9 10 12 14 10 6 GRAPH
получаем гистограмму:
ххххххххх хххххххххх хххххххххххх хххххххххххххх хххххххххх хххххх
Несмотря на свою краткость, эта программа демонстрирует несколько важных свойств языка Форт.
Во-первых, хотя числа в Форте могут храниться в виде констант и переменных, как и в других языках программирования (см. ), чаще всего это не требуется. Обычно достаточно использования стека.
Во-вторых, Форт-программа состоит из последовательности определений слов через предшествующие им слова, так, например, для слова GRAPH используется определение слова LIMITBAR, которое основано на определении слова BAR, использующем определение слова .X , в свою очередь, включающее определение слова ." , а последнее является первичным словом любой версии языка Форт. Таким образом, для определения слов применяются ранее данные определения, основанные на первоначально определенных словах языка. В третьих, Форт-программы компактны. Текст программы на Бейсике или Фортране, предназначенной для решения этой задачи, будет намного длиннее. Форт-программы обладают большим быстродействием, хотя пока данное утверждение надо принять на веру. В четвертых, Форт-программа составлена из коротких слов или определений, каждое из которых может быть проверено и отлажено отдельно. Форт-программу сравнительно легко усовершенствовать и изменить без изменения большинства входящих в нее частей. Например, программа GRAPH может быть переделана для экрана, имеющего ширину 80 позиций, простой заме ной числа 64 на 80 в слове LIMITBAR. Наконец, Форт-программу, как правило, проще понять, чем любую другую, просмотрев определение последнего слова. Так, например, глядя на определение слова GRAPH, вы увидите, что нужно понять определение слова LIMITBAR, которое в свою очередь, приводит к слову BAR, а оно уже почти очевидно само по себе. Программы легко читаются (особенно если они снабжены продуманными комментариями и удачно выбраны имена слов), правда, не сверху вниз, как читаются программы на Бейсике, Фортране или Паскале. Форт-программу прочитывают, начиная с какого-либо важного слова, разбирая затем, для чего предназначено каждое слово, входящее в его определение.
В заключение следует сказать несколько слов о методике программирования на языке Форт.Прежде всего вы должны хорошо понять, что будет делать программа, а отсюда вы придете к словам, которые для нее необходимо определить. Например, для программы GRAPH, очевидно, необходимо слово BAR, а для него, в свою очередь, потребуется слово .X . Разработка Форт-программы продвигается одновременно на двух уровнях : на системном уровне, когда на основе понимания конечной цели определяются слова, которые нужно описать, и на уровне подпрограмм, иначе говоря, слов, когда описывается и проверяется каждое слово. Программирование на языке Форт -- это органический интуитивный и творческий процесс, который завершается, как правило, эффективными программами как на уровне большой системы, так и на уровне отдельных слов (подпрограмм).
Положительные, отрицательные числа и числа без знака
Для представления чисел одинарной длины в Форте используются два байта, т.е. 16 битов памяти. Поэтому возможно представить числа от 0 до 65535 (2^16=65536), не так ли ? Но, разумеется, если нам не нужны отрицательные числа.Попробуйте ввести с клавиатуры
65535 .
Результат получится такой:
-1 ok
Что произошло ? Очевидно, то, что Форт воспринимает число 65535 как отрицательное число. Теперь попробуйте
65534 .
и получите
-2 Ok
Это кажется еще более странным. А теперь введите
32768 .
Форт выдаст
-32768 ok
В то же время, если ввести
32767 .
мы получим
32767 ok
Ужасная путаница, неправда ли ? В действительности же форт работает с так называемыми числами со знаком и числами без знака. Все, что находится в диапазоне 32768 - 65535, Форт рассматривает как отрицательные числа, если вы не указываете, что они положительные. Попробуйте ввести
65535 U.
Результат
65535 ok
получится таким, каким он и должен быть. Если вы испытаете слово U. с другими числами, включая те, которые больше 32767, вы будете получать те же числа, что и ввели. Слово U. предназначено для печати чисел, не принимая во внимание знак числа, т.е. "чисел без знака". Имеются также другие операторы для выполнения арифметических действий с числами без знака, и мы будем работать с ними в гл. 4. Ну а теперь попробуйте ввести
-1 U.
в результате получится число 65535. Становится все хуже и хуже. А вот что происходит на самом деле. Если вы вводите 65535 или -1, Форт кладет в стек одно и то же число:
1111111111111111
В нем все 16 разрядов установлены в "1".
Числа 65534 и -2 вводятся в стек в виде
1111111111111110
и 65533 и -3 - как
1111111111111101
Поэтому, если числа больше 32767, Форт может рассматривать их и как отрицательные и как числа без знака, в зависимости от ваших указаний. Числа 0 - 32767 (от 0 до 11111111111111, с 15 единицами) воспринимаются как положительные. Если число со знаком, т.е. если его 16-й разряд равен 1, тогда число считается отрицательным, но это не совсем так, потому что оказывается, что числа "больше" 32767 (как числа без знака) менее "отрицательные".
Более понятно это станет из упражнений.
Принятое в Форте соглашение об использовании чисел со знаком называется арифметикой с дополнением по модулю два. Не вдаваясь в причины, укажем, что число в арифметике с дополнением по модулю два получается путем вычитания каждого разряда из 1, а затем добавлением ко всему числу 1. Образование числа с дополнением по модулю два попросту меняет его знак. Так, -10 является дополнением по модулю два от 10, а 10 - дополнением по модулю два от -10. Оказывается,что принятое соглашение упрощает работу ЦПУ при арифметических операциях. (В большинстве других языков также применяется целочисленная арифметика с дополнением до 2.) Вспомните, что слово NOT в Форт-83 эквивалентно вычитанию каждого разряда числа из 1, поэтому
10 NOT 1 + .
даст в результате -10. Стандартное слово NEGATE делает следующее: оно изменяет знак числа, вычисляя его дополнение по модулю два. Дополнение чисел и дополнительная арифметика подробно описаны в книге Липшуца (1982).
А. Глоссарий (список слов Форта)
Этот глоссарий включает слова Форт-79 и Форт-83, некоторые нестандартные слова из MMSFORTH и прочих версий и другие полезные слова, которые определены в тексте и упражнениях. При описании слов используются следующие сокращения:I слова немедленного исполнения С может использоваться только в режиме компиляции Е может использоваться только в режиме исполнения 83REQ слова Форт-83 из обязательного списка 83ASM ассемблер Форт-83 83DBL слова Форт-83 для работы с числами двойной длины 83SYS системные слова Форт-83 83CNT слова Форт-83 из контролируемого списка 83UNC слова Форт-83 из неконтролируемого списка 83FLD экспериментальные слова Форт-83 для преобразования адресов полей 83SRC экспериментальные слова Форт-83 для спецификации порядка поиска и управления 79REQ слова Форт-79 из обязательного списка 79ASM слова ассемблера Форт-79 79DBL слова Форт-79 для работы с числами двойной длины 79RSV слова Форт-79 из резервного списка MMS слова MMSFORTH FIG слова FIGFORTH MVP слова MVPFORTH F83 слова версии Форта Лаксена и Перри VAR слова, встречающиеся в некоторых версиях ТХТ слова, описанные в тексте книги
Определения в этом глоссарии взяты из стандартов Форт-83 и Форт-79, но большинство из них переписаны для того, чтобы сделать их более понятными и подчеркнуть отличия стандартов. Этот глоссарий не заменяет описаний стандартов и не является исчерпывающим руководством, включающим в себя описания всех нестандартных слов (из контролируемого и неконтролируемого списков, а также резервных слов), которые встречаются в стандартах. Из MMSFORTH включены только те слова, которые встречаются в тексте. MMSFORTH содержит в себе все слова Форт-79 и многие слова помимо этого. Аналогично включены только те слова из других версий, которые встречаются в книге. Форт-описания, которые приведены для слов, имеющихся в MMSFORTH, не являются собственно MMSFORTH-описаниями. В некоторых случаях даются ссылки на более полные описания в тексте. Конечно, в тех случаях, где нет ссылок, следует воспользоваться предметным указателем.
Элементы глоссария упорядочены в соответствии с ASCII- кодами.
! "store" ("присвоить") 83REQ 79REQ (n адр --)
Записывает n по адресу "адр". # "sharp" 83REQ 79REQ (d1-- d2)
Самая правая цифра d1 преобразуется в ASCII-символ в соответствии со значением BASE и заносится в форматированную выходную строку (для последующего вывода с помощью TYPE). d2-число, содержащее оставшиеся цифры, используемые для последующей переработки. Используется между . Слово для отображения числа центов, представленного числом двойной длины со знаком, в виде числа долларов и центов может быть описано как : .DOLLARS ( d -- ) SWAP OVER DABS TYPE ; Например: 5236.DOLLARS отобразит $52.36. См. также .
#> "sharp-greater" 83REQ 79REQ ( d - адр n) Завершает преобразование числа в форматированную выходную строку, "адр" - адрес выходной строки-результата, a n - число символов в ней, "адр n" удобно использовать совместно с TYPE. Для примера смотри #. См. также #, . #TIB "number-t-i-b" "число t-i-Ь" 83REQ VAR ( -- адр) Переменная, где хранится число байтов, лежащих в данный момент во входном текстовом буфере. Значение, которое заносится в #Т1В, может лежать в диапазоне от 0 до максимального числа символов, помещающихся во входном текстовом буфере (стандарты требуют минимум 80 символов). $! "string-store" "записать строку" MMS ( адр1 адр2 --) Переносит содержимое счетной строки (включая байт-счетчик) из адреса "адр1" в "адр2". $" "string-quote" "ввести строку" MMS ( -- адр) Запоминает строку, ограниченную " (двойной кавычкой) в PAD, адрес которого заносится в стек к качестве "адр". Строка записывается в счетном формате. Таким образом; $" Foxy" COUNT TYPE отобразит на экране "Foxy". $+ "string-concatenate" "соединить строки" MMS ( адр1 адр2 -- адр3) Добавляет счетную строку, лежащую по адресу "адр2" (без байта-счетчика), к правому концу счетной строки по адресу "адр1" и помещает счетную строку-результат в буфер PAD, адрес которой в виде "адр3" заносится в стек. $-ТВ "string-minus-t-b" "строка-минус-tb" MMS ( $адр -- $адр) Удаляет пробелы (ASCII 32) в конце счетной строки путем уменьшения байта-счетчика на их число.
Сравните с -TRAILING. $. "string-dot" "строка-точка" MMS ( адр --) Отображает счетную строку, лежащую по адресу "адр". Эквивалентно COUNT TYPE. $ARRAY "string-array" "массив строк" MMS ( n2 --) Слово-описатель, которое создает массив строк. При использовании в форме n1 n2 $ARRAY
создает статью в словаре с именем и резервирует место для n2+1 счетных строк с максимальной длиной n1+1 байтов каждая. Когда к
производится обращение: n
в стек заносится адрес строки-элемента с номером n+1. $CHAR "string-char" "строка-символ" ТХТ (n -- адр) Создает счетную строку из одного символа, соответствующего ASCII коду (n), который лежит в стеке и записывает ее в PAD, адрес которого заносится в стек в виде "адр". См. также CHR$. : $CHAR I PAD С! PAD 1+ С! PAD ; $COMPARE "string-соmраrе" "сравнение строк" MMS ( адр1 адр2 -- флаг) Сравнивает две счетные строки, занося в стек -1, 0 или 1, в зависимости от того, является ли строка с адресом "адр1" меньше чем, равна или больше чем строка с адресом, "адр2". Сравнение производится по схеме символ-за-символом. Слово полезно в программах сортировки строк. $CONSTANT "string-constant" "строка-константа" MMS ТХТ ( --) Слово-описатель, которое создает строку-константу. При использовании в форме $CONSTANT string" формирует статью словаря с именем и компилирует последующую строку (вплоть до разделителя ") в счетном формате. При исполнении в стек заносится адрес счетной строки. $GET "string-get" "принять строку" ТХТ ( адр --) Записывает строку по адресу "адр". При использовании в форме: $GET " строка (вплоть до разграничителя ") записывается в счетном формате по адресу "адр". : $GET 34 WORD DUP C@ 1+ ROT SWAP CMOVE ; $IN "string-in" "ввести строку" ТХТ (-- адр) Выдает на экран запрос "?" и ждет ввода с клавиатуры до 255 символов.
Когда будет введено 255 символов или поступит код "возврата каретки", введенная последовательность будет записана в виде счетной строки в PAD, адрес которого будет занесен в стек как "адр". : $IN PAD 1+ 255 2DUP BLANK ." ? " 2DUP EXPECT -TRAILING PAD C! 1- ; См. также IN$, $SWAP "string-swap" "поменять строки местами" ТХТ (адр1 адр2 --) Меняет местами счетные строки с адресами "адр1" и "адр2". Строки должны иметь равную максимальную зарезервированную длину. : $SWAP DUP DUP С@ 1+ >R PAD SWAP R@ CMOVE SWAP DUP ROT R@ CMOVE PAD SWAP R> CMOVE ; См. также $XCHG. $VARIABLE "string-variable" "строка-переменная" MMS Слово-описатель, которое создает строки-переменные. При использовании в форме n VARIABLE
формирует статью в словаре с именем и резервирует n+1 байтов для запоминания счетной строки (в исходный момент байт-счетчик равен 0). При исполнении в стек заносится адрес, где лежит эта строка. $XCHG "string-exchange" "обмен строками" MMS ( адр1 адр2 --) Меняет местами счетные строки по адресам "адр1" и "адр2". Строки должны иметь идентичные максимальные длины. См. также $SWAP. ' "tick" "апостроф" I (79) 83REQ 79REQ Определения ' (апостроф) в Форт-79 и Форт-83 отличаются существенно. В Форт83 при использовании '
в стек заносится адрес поля программы слова . В Форт-83 ' не является словом немедленного действия и обычно используется в режиме исполнения. Противостоит [']. В Форт-79 ' выполняет одну из двух операций в зависимости от того, в режиме компиляции или исполнения находится система. В режиме исполнения '
засылает в стек адрес поля параметров слова , в то время как в режиме компиляции адрес поля параметров компилируется в качестве литерала, который при последующем исполнении скомпилированного слова помещается в стек. В Форт-79 ' является словом немедленного исполнения. В обоих стандартах при отсутствии в словаре дается сообщение об ошибке.
В Форт-83 команда ' > BODY засылает тот же адрес поля параметров, что и '
в Форт-79. ( "paren" "скобка" I 83REQ 79REQ Выделяет комментарии, которые должны быть проигнорированы в исходном тексте программы. Форма использования ( ккк) Символы "ккк", выделенные справа ) (закрывающая скобка), считаются комментарием и не обрабатываются. Пробел необходим за (, но не должен предшествовать символу ), который рассматривается в качестве разделителя, а не слова Форта. ( можно использовать как в режиме исполнения, так и компиляции. Число символов в "ккк" может лежать в диапазоне от 0 до числа символов, оставшихся во входном потоке вплоть до закрывающей скобки. В Форт-79, если входной поток иссякнет до закрывающей скобки, будет дано сообщение об ошибке. См. также \. * "times" "умножить" 83REQ 79REQ (n1 n2 -- n3) Умножает n1 на n2, выдавая произведение n3. n3 будет содержать младшие 16 битов произведения, даже если происходит арифметическое переполнение. ** "power" "возвести в степень" 83UNC 79RES ТХТ ( n1 n2 -- n3) Возводит n1 в степень n2, результат n3 кладет в стек. : ** ?dup 0= IF DROP 1 ELSE DUP I = IF DROP ELSE OVER SWAP 1- 0 DO OVER * LOOP SWAP DROP THEN THEN ; */ "times-divide" "умножить-разделить" 83REQ 79REQ */ ( n1 n2 n3 -- n4) Умножает n1 на n2, результат делит на n3, засылает в стек частное n4. Произведение n1 на n2 представляется в виде промежуточного результата двойной длины, обеспечивая большую точность, чем при традиционной последовательности n1 n2 * n3 / В Форт-83 используется деление с нижней границей(1), в то время как в Форт-79 частное округляется в направлении нуля. */MOD "times-divide-mod" "умножить-разделить с остатком" 83REQ 79REQ ( n1 n2 n3 -- n4 n5) Умножает n1 на n2, результат делит на n3, остаток n4 и частное n5 засылаются в стек. Как и в случае */, промежуточное произведение имеет двойную длину. В Форт-83 применено деление,с, нижней границей. + "plus" "плюс" 83REQ 79REQ (n1 n2 -- n3) ======================== 1 Частное округляется в сторону нижней границы (floored), а остаток имеет знак делителя.
Деление -8/5 дает частное -2 в Форт- 83 и -1 в Форт-79.- Прим. перев. ======================== Добавляет n1 к n2 и выдает сумму n3. n3 содержит младшие 16 битов суммы даже в случае арифметического переполнения. +! "plus-store" "плюс-присвоить" 83REQ 79REQ ( n1 адр --) Добавляет n1 к числу одинарной длины, хранящемуся по адресу "адр", замещая старое значение полученным результатом. +LOOP "plus-loop" "плюс-цикл'' I C 83REQ 79REQ ( n --) Завершает do-loop, позволяя увеличивать (или уменьшать) индекс цикла на величину, большую чем 1. При использовании в форме : ... DO ... +LOOP ; компилирует структуру do-loop. Когда исполняется, +LOOP предполагает наличие числа (n) в стеке, которое определяет величину приращения индекса в цикле. В Форт-83 "n" добавляется к индексу цикла, и если сумма "пересечет" границу между значением предела цикла минус единица и самим пределом, то цикл завершается. В Форт-79 цикл завершается, когда индекс становится больше или равен пределу (с учетом знака). В обоих стандартах управление передается слову, следующему за соответствующим DO, если цикл не завершился, д слову после +LOOP, - если завершился. Противостоит LOOP. См. обсуждение завершения цикла в гл. 8. См. также DO. , "comma" "запятая" 83REQ 79REQ ( n--) Записывает "n" в очередную свободную ячейку словаря (адрес которой определяется словом HERE) и увеличивает указатель словаря на 2, чтобы зарезервировать место для "n". При этом говорится, что "п" скомпилировано по адресу HERE. Например, если XYZ было определено как CREATE XYZ 153 , то команда XYZ @ . отобразит скомпилированное число 153. См. также С,. - "minus" "минус" 83REQ 79REQ ( n1 n2 -- n3) Вычитает n2 из n1, остаток n3 кладет в стек. --> "next-block" "следующий блок" I 83CNT 79REQ ТХТ MMS ( --) Немедленно переключают интерпретацию на начало следующего по порядку блока. : --> 1 BLK+! ; IMMEDIATE -ROT "minus-rote" "минус-РОТ" ТХТ МVP VAR (n1 n2 n3 - n3 n1 n2) Засылает верхний код стека в третью его позицию.
Эквивалентно ROT ROT. -TRAILING "minus-trailing" 83REQ 79REQ (адр n1 -- адр n2) Корректирует число символов n1 текстовой строки, начинающейся по адресу "адр", чтобы вычислить число n2, которое не включает пробелы, завершающие строку, "адр" не изменяется. Если n1 равно 0, n2 также будет равно 0, если в строке содержатся только пробелы, то и n2 будет равно 0. (Часто используется для определения числа введенных символов при работе с EXPECT. Более детальная информация содержится - глава 15, секция Исполнение. . "точка" 83REQ 79REQ ( n--) Отображает в текущей позиции дисплея величину "n" (включая знак минус, если "n" отрицательно) в соответствии со значением BASE. После числа вводится пробел. ." "dot-quote" I C 83REQ 79REQ ( --) Компилирует и (или) отображает текст. При использовании в форме : ... ." ссс" ... ; компилирует текст "ссс". следующий после." вплоть до разграничителя w (двойная кавычка) в описании слова .За ." должен следовать пробел (и он не включается в последовательность "ссс"), а перед " (двойная кавычка), которая является разграничителем, а не словом Форта, пробел не требуется. Откомпилированный текст будет отображен при исполнении . В Форт-83 слово." может использоваться только в режиме компиляции. (В противоположность .(.) В Форт-79." может использоваться в режиме исполнения; в этом случае текст немедленно отображается. Форт-79 предполагает также, что текст может содержать до 127 символов или даже более, и, если входной поток иссякнет до завершающей ", будет дано сообщение об ошибке. .( "dot-paren" "точка-скобка" 83REQ ( --) При использовании в форме .( ссс) немедленно отображает текст, следующий за.( вплоть до разграничителя ) (закрывающая скобка), как в режиме компиляции, так и исполнения. Пробел необходим после.( (и он не включается в последовательность "ссс"), но пробел не должен предшествовать закрывающей скобке, которая является разграничителем, а не словом Форта.
Полезно для комментариев, которые отображаются при интерпретации блоков. Противостоит.". .BIN "dot-binary" "точка двоичная" ТХТ ( n -- n) Отображает n в двоичном представлении без изменения состояний стека или BASE. : .BIN DUP BASE @ 2 BASE ! SWAP . BASE ! ; .BLK -dot-b-l-k" "точка-b-l-k" ТХТ ( --) Отображает номер интерпретируемого в данный момент блока или 0 при режиме исполнения). : .BLK BLK @ U. ; IMMEDIATE .DEC "dot-decimal" "точка десятичная" ТХТ ' / ( n - n) Отображает п в десятичном представлении без изменения указателя стека или BASE. : .DEC DUP BASE @ DECIMAL SWAP . BASE ! ; .HEX "dot-hex" "точка-HEX" ТХТ ( n --n) Отображает n в шестнадцатеричном представлении без изменения указателя стека или BASE. : .HEX DUP BASE @ 16 BASE ! SWAP . BASE ! ; .L "dot-l" "точка-l" ТХТ ( n1 n2 -) Отображает n1 в соответствии с величиной BASE в поле шириной n2 позиций так, что старшая цифра занимает самую левую позицию. См. также.R. ; .L SWAP 0 ROT OVER - ROT ROT TYPE SPACES ; .LINE "dot-line" "точка-строка" ТХТ ( n1 n2 -->) Отображает строку n2 в блоке n1. n2 может принимать значения от 0 до 15. : .LINE SWAP BLOCK SWAP 64 * + 64 TYPE ; .LSB "dot-l-s-b" "точка-l-s-b" ТХТ ( n-- ) Отображает младший байт числа n. : .LSB 255 AND . ; .MEM "dot-mem" "точка-mem" ТХТ MMS ( --) Отображает число байтов между PAD и верхом стека. В большинстве версий - это объем свободной памяти. : .MEM SP@ PAD - U. ; .MSB "dot-m-s-b" "точка-m-s-b" ТХТ ( n --) Отображает старший байт числа n. : .MSB 256 / . ; .NUMS "dot-numbers" "точка-числа" ТХТ Отображает число n в двоичном, восьмеричном, десятеричном, и шестнадцатеричном представлениях, не изменяя BASE. : .NUMS .BIN .ОСТ .DEC .HEX DROP ; .OCT "dot-octal" "точка восьмеричная" ТХТ ( n -- n0) Отображает число n в восьмеричном представлении* не изменяя указатель стека или величину BASE. : .OCT DUP BASE @ 8 BASE ! SWAP .
BASE ! ; .R "dot-r" "точка-r" 83CNT 79RES MMS ( n1 n2 --) С учетом значения BASE отображает число n1 в поле длиной n2 так, что младшая цифра занимает самую правую позицию поля. Если n1 отрицательно, перед ним печатается знак минус. В Форт-83, если необходимое число символов для отображения n1 больше n2, дается сообщение об ошибке. Размер поля менее 1 в Форт-83 также запрещен. В Форт-79 при требуемом числе символов для отображения n1 больше n2 ошибка не фиксируется. (В большинстве версий Форт-79 печатает в таком случае результат без предшествующего пробела). Если в Форт79 n2 меньше 1, предшествующий пробел также не вводится. .S "dot-s" "точка-s" ТХТ MMS ( --) Отображает все числа в стеке без изменения его указателя. В Форт-83 описание имеет вид : .S DEPTH ?DUP 0= IF ." STACK EMPTY" ELSE 0 DO DEPTH 1- ROLL DUP . LOOP THEN ; а в Форт-79 : .S DEPTH ?DUP 0= IF." Stack empty" ELSE 0 DO DEPTH ROLL DUP . LOOP THEN ; / "divide" "разделить" 83REQ 79REQ ( n1 n2 -- n3) Делит n1 на n2, частное n3 засылает в стек. В Форт-79 n3 округляется в направлении нуля, в то время как в Форт-83 используется деление с нижней границей. В форт-83 при делителе, равном 0, или если частное оказывается вне пределов -32.768 - 32.767, дается сообщение об ошибке. /MOD "divide-mod" "деление с остатком" 83REQ 79REQ (n1 n2 - n3 n4) Делит n1 на n2, остаток n3 и частное n4 засылаются в стек. В Форт-83 применено деление с нижней границей, в Форт-79 частное округляется в направлении 0. В Форт-83 при делителе, равном 0, или если частное оказывается вне пределов -32.768 - 32.767, дается сообщение об ошибке. 0< "zero-less" "меньше нуля" 83REQ 79REQ ( n -- флаг) Сравнивает n с 0 и засылает в стек флаг истинно, если n меньше 0. О= "zero-equals" "равно нулю" 83REQ 79REQ ( n -- флаг) Сравнивает n с 0 и засылает в стек флаг истинно, если n равно 0. См. также NOT. которое является синонимом 0= в форт-79, но имеет совсем иной смысл в Форт-83. 0> "zero-greater" "больше нуля" 83REQ 79REQ ( n -- флаг) Сравнивает n с 0 и засылает в стек флаг истинно, если n больше 0. 0ARGMAKE "0-arg-make" ТХТ ( n--) Слово-описатель, которое формирует мнемонику ассемблера, чтобы скомпилировать байт, который при описании является аргументом.
При использовании в форме n 0ARGMAKE
формирует статью словаря с именем и компилирует младшие 8 битов числа n в поле параметров . При исполнении байт в его поле параметров будет скомпилирован в словарь. (Подробно рассмотрено главе 16, Как работает ассемблер). 1+ "one-plus" "прибавить единицу" 83REQ 79REQ (n1 -- n2) Добавляет 1 к n1 и кладет результат в стек n2. 1- "one-minus" "вычесть единицу" 83REQ 79REQ (n1 --n2) Вычитает 1 из n 1 и результат n2 кладет в стек. 16* "sixteen-times" "умножить на 16" MMS (n1 -- n2) Умножает n1 на 16 и результат п2 кладет в стек. 2! "two-store" "два-присвоить" 83DBL 79DBL ( d адр --) Записывает число двойной длины по адресу "адр". 2$ARRAY "two-string-array" "двумерный массив строк" MMS ( n1 n2 n3 --) Слово-описатель, которое создает двумерный массив строк (матрицу). При использовании в форме n1 n2 n3 2$ARRAY
формирует статью в словаре с именем и резервирует место для счетных строк с максимальной длиной n1+1 при числе рядов n2+1 и числе столбцов n3+1. Когда массив используется как n1 n2
в стек заносится адрес начала строки, лежащей в ряду n1+1 и столбце n2+1. 2* "two-times" "умножить на 2" 83CNT 79RES MMS ( n1 -- n2) Умножает n1 на 2, результат n2 заносит в стек. 2+ "two-plus" "прибавить 2" 83REQ 79REQ ( n1 -- n2) Прибавляет 2 к n1, а результат n2 заносит в стек. 2- "two-minus" "вычесть 2" 83REQ 79REQ ( n1 -- n2) Вычитает 2 из n1, результат n2 заносит в стек. 2/ "two-divide" "разделить на 2" 83REQ 79REQ ( n1 -- n2) Делит n1 на 2, результат n2 заносит в стек. 2@ "two-tetch" "извлечь двойное число" 83REQ 79REQ (адр - d) Кладет в стек число двойной длины, лежащее по адресу "адр". 2ARRAY "two-array" "двумерный массив" MMS ( n1 n2 --) Слово-описатель, которое создает двумерный массив (матрицу) чисел одинарной длины.
При использовании в форме n1 n2 2ARRAY
формирует статью в словаре с именем и резервирует место для n1+1 рядов и n2+ 1 столбцов чисел одинарной длины. При обращении n1 n2
в стек заносится адрес элемента, лежащего в ряду n1+1 и столбце n2+1. См. также 2CARRAY; ARRAY. 2CARRAY "two-c-array" "2-с-массив" MMS ( n1 n2 --) Слово-описатель, которое создает двумерный массив (матрицу) байтов. При использовании в форме n1 n2 2CARRAY
формирует статью в словаре с именем и резервирует место для n1+1 рядов и n2+1 столбцов байтов. При обращении к
n1 n2
в стек заносится адрес элемента, лежащего в ряду n1+1 и колонке n2+1. См. также 2ARRAY; ARRAY. 2CONSTANT "two-constant" "константа двойной длины." 83DBL 79DBL ( d --) или (n1 n2 --) Слово-описатель, которое создает константу двойной длины (она может использоваться также для записи двух чисел одинарной длины). При использовании в форме d 2CONSTANT или n1 n2 2CONSTANT
формирует статью в словаре с именем и компилирует число двойной длины (или два числа одинарной длины) из стека. Когда исполняется, d (или n1 и n2) засылаются в стек. См. также CONSTANT; CCONSTANT; 4CONSTANT. Противостоит 2VARIABLE. 2DROP "two-drop" "2-DROP" 83DBL 79DBL ( d --) Удаляет из стека число двойной длины (или два числа одинарной длины). 2DUP "two-dupe" "2-DUP" 83DBL 79DBL ( d - d d) или (n1 n2 - n1 n2 n1 n2) Дублирует в стеке число двойной длины (или пару чисел одинарной длины), 2OVER "two-over" "2-OVER" 83DBL 79DBL ( d1 d2 - d1 d2 d1) или (n1 n2 n3 n4 -- n1 n2 n3 n4 n1 n2) Копирует второе сверху число двойной длины и кладет его на верх стека или копирует третье и четвертое сверху числа одинарной длины и кладет их на верх стека. 2QUAN "two-quan" "2-QUAN" MMS ( --)
Слово-описатель, которое создает слова типа QUAN для чисел двойной длины. При использовании в форме 2QUAN
формирует в словаре статью с именем и резервирует место для числа двойной длины.
Когда слово используется само, код, который оно содержит, заносится в стек. Если перед ним стоит IS, число из стека заносится в поле параметров . Если же перед ним стоит AT, в стек заносится адрес поля параметров слова . (Более подробно о QUAN написано в главе 6 - Переменная, константа и связанные с ней слова) См.-также QUAN; CQUAN; 4QUAN. 2RОТ "two-rote" "2-ROT" 83DBL 79DBL ( d1 d2 d3 - d2 d3 d1) или ( n1 n2 n3 n4 n5 n6 - n3 n4 n5 n6 n1 n2) Засылает третье сверху число двойной длины в стеке на его верх (или засылает третью сверху пару чисел на верх стека). 2SWAP "two-swap" "2-SWAP" 83DBL 79DBL ( d1 d2 - d2 d1) или ( n1 n2 n3 n4 -- n3 n4 n1 n2) Меняет местами в стеке два верхних числа двойной длины (или две верхние пары чисел одинарной длины). 2VARIABLE "two-variable" "переменная двойной длины" 83DBL 79DBL ( --) Слово-описатель, которое создает переменную двойной длины. При использовании в форме 2VARIABLE
формирует статью в словаре с именем и резервирует 32 бита в поле параметров для хранения числа двойной длины. Когда исполняется, адрес поля параметров , где хранится число двойной длины, засылается в стек. 2VARIABLE не обязательно инициализирует значение переменной. См. также VARIABLE; CVARIABLE; 4VARIABLE. Противостоит 2CONSTANT. 4! "two-store" "4-присвоить" MMS ( f1 адр--) Записывает 64-разрядное число из стека в память по адресу "адр". Обычно используется для чисел с плавающей запятой, 4@ "four-fetch" "4 извлечь" MMS ( адр--f1) Засылает в стек 64-разрядное число, лежащее по адресу "адр". Обычно используется для чисел с плавающей запятой. 4ARRAY "four-array" "массив чисел учетверенной длины" MMS ( f1 --) Слово-описатель, которое создает линейный массив (вектор) с 64- битовыми элементами (обычно используемыми для чисел с плавающей запятой). При использовании в форме n 4ARRAY
формирует статью в словаре с именем и резервирует место для n+1 числа с плавающей запятой.
Когда используется в форме n
в стек заносится адрес (n+1)- го элемента массива. См. также ARRAY; CARRAY. Противоположно 2ARRAY. 4CONSTANT "four-constant" MMS ( f1 -) Слово-описатель, которое создает 64-битовую константу (обычно для чисел с плавающей запятой). При использовании в форме f1 4CONSTANT
формирует в словаре статью с именем и компилирует число с плавающей запятой f1 из стека в поле параметров . Когда исполняется, число с плавающей запятой засылается в стек. 4QUAN "four-quan" "4-QVAN" MMS ( --) Слово-описатель, которое создает слова типа QUAN для 64-битовых чисел. При использовании в форме: 4QUAN
формирует в словаре статью с именем и резервирует 64 бита для числа с плавающей запятой. Когда слово используется само, код, который оно содержит, заносится в стек. Если перед ним стоит IS, значение, хранящееся в стеке, заносится в поле параметров . Если же перед ним стоит AT, в стек заносится адрес поля параметров слова . (Подробнее QUAN обсуждается в гл. 6). См. также CQUAN; QUAN; 2QUAN. 4VARIABLE "four-variable" * MMS ( --) Слово-описатель, которое создает 64-битовую переменную обычно для чисел с плавающей запятой. При использовании в форме 4VARIABLE
формирует статью в словаре с именем и резервирует 64 бита в поле параметров для хранения числа с плавающей запятой. Когда
исполняется, адрес поля параметров , где хранится число с плавающей запятой, засылается в стек. 4VARIABLE не обязательно инициализирует значение переменной. См. также VARIABLE; CVARIABLE; 2VARIABLE. Противостоит 4CONSTANT. 64* "sixly-four-times" "умножить на 64" MMS ( n1 - n2) Умножает n1 на 64, результат n2 кладет в стек. 79-STANDARD "79-standard" "стандарт-79" 79REQ ( --) Используется для проверки того, отвечает ли используемая версия стандарту Форт79. Если версия нестандартная, слово либо не будет узнано, либо будет дано сообщение об ошибке. См. также Форт-83. 8* "eight-times" "умножить на 8" MMS (n1 - n2) Умножает число n1 на 8, результат n2 заносит в стек. ; "colon" "двоеточие" 83REQ 79REQ ( -- ) Слово-описатель, которое формирует слово-двоеточие.
При исполнении в форме.. : ... ; формирует статью словаря с именем в текущем контекстном словаре и переключает STATE в режим компиляции, вызывая компиляцию последующих слов и чисел из входного потока. не может быть найдено в словаре до тех пор, пока не будет успешно обработан соответствующий оператор ; или ;CODE. Оператор : устанавливает такой порядок поиска, при котором первым просматривается контекстный словарь, который заменен словарем компиляции. Словарь компиляции не изменяется. Слово-двоеточие - главное средство программирования на Форте. См. также ;, ;CODE. ; "semi-colon" "точка с запятой" 83REQ 79REQ ( --) Прекращает компиляцию описания-двоеточие. При исполнении в форме : ... ; разрешает нахождение имени в словаре, компилирует EXIT (или зависящее от системы слово, которое выполняет аналогичное действие), устанавливает STATE в режим исполнения. При использовании его исполнение завершается словом EXIT и управление передается слову, которое обратилось к . См. также :,;CODE. ;CODE "semi-colon-code" 83ASM 79ASM ( --) Слово-описатель-терминатор, которое может использоваться только в режиме компиляции. При исполнении в форме : ... CREATE ... ;CODE ... END-CODE прерывает компиляцию, завершает работу слова-описателя и исполняет слово ASSEMBLER. Это разрешает использование мнемоники ассемблера для компиляции программы между ;CODE и END-CODE. При исполнении для формирования
поле программы будет содержать адрес машинной программы, которая размещена сразу после ;CODE в . Таким образом, ;CODE позволяет создавать слова-описатели, которые формируют производные слова, функция которых определена последовательностью мнемокодов ассемблера, размещенных между ;CODE и END-CODE в описании . По своему действию подобно DOES>. См. также CODE; END-CODE; CREATE; DOES>; и обсуждение в гл. 15. < "less-lhan" "меньше чем" 83REQ 79REQ (n1 n2 - флаг) Сравнивает числа n1 и n2 и засылает в стек флаг истинно ( так в тексте) ; #S; HOLD; SIGN. "not-equal" "неравно" 83UNC 79RES MMS (n1 n2 - флаг) Сравнивает числа nl и n2 и засылает в стек флаг истинно, если nl не равно n2. , а число переносимых байтов задается числом без знака.
См. также CMOVE; MOVE. "greater-than" "больше" 83REQ 79REQ ( n1 n2 - флаг) Сравнивает числа n1 и n2 и засылает флаг истинно, если n1 больше n2. >= "greater-than-or-equal" "больше или равно" MMS ( n1 n2 - флаг) Сравнивает числа n1 и n2 и засылает флаг истинно, если n1 больше или равно n2. >BODY "to-body" 83REQ (адр1 -- адр2) Исходный адрес "адр1" - адрес поля программы слова. В стек засылается "адр2" -"адрес поля параметров этого слова. В Форт-83 ' >BODY выдает тот же адрес, что и '
в Форт-79. >IN "to-IN" 83REQ 79REQ (-- адр) Переменная, которая содержит номер текущего байта во входном потоке, где происходит сейчас интерпретация. Если источником входного потока является массовая память, команда BLK @ BLOCK >IN @ + засылает в стек текущий адрес интерпретации; в Форт-83 при вводе с клавиатуры эквивалентный адрес вычисляется по команде: >IN @ TIB + >LINLK "to-link" 83FLD ( адр1 - адр2) Исходное число "адр1" - адрес поля программы слова, результат "адр2" - адрес поля связи этого слова. >NAME "to-name" 83FLD (адр1 -- адр2) Исходное число "адр1" - адрес поля программы слова, результат "адр2" - адрес поля имени этого слова. >R "to-r" 83REQ 79REQ ( n --) Передает n из стека параметров в стек возвратов. Поскольку слово Ж изменяет стек возвратов, оно в норме должно использоваться в паре с R> до завершения описания-двоеточие. Противостоит R@. ? " question-mart" "знак вопроса" 79REQ ( адр--) Отображает число, лежащее по адресу "адр". Использует формат оператора.. Эквивалентно @ . . ?BRANCH "question-branch" "условное ветвление" 83SYS VAR (флаг --) При исполнении в форме : ... COMPILE ?BRANCH ... ; в текст описания-двоеточие компилируется операция условного перехода. При исполнении, если флаг в стеке имеет значение ложно, передача управления производится так же, как при BRANCH.
Если же флаг имеет значение истинно, исполнение производится, начиная со слова, скомпилированного сразу после адреса ветвления, который следует за ?BRANCH. См. стр.211, где имеются более подробное описание и примеры. Обычно в программировании ?BRANCH не используется, в некоторых версиях применение ограничено описаниями IF, WHILE и UNTIL. ?DUP "question-dupe" 83REQ 79REQ ( n - n n) или ( 0 - 0) Дублирует в стеке число n, если оно не равно 0. ?KEY "question-key" MMS ( - с) Проверяет, была ли нажата какая-либо клавиша, и засылает в стек ASCII-код клавиши или 0, если ни одна клавиша не нажата. ?KEY не ожидает нажатия клавиши. @ "fetch" "извлечь" 83REQ 79REQ ( адр - nb) Заносит в стек число одинарной длины, хранящейся по адресу "адр". См. С@; 2@; 4@. ABORT "abort" 83REQ 79REQ ( -->) Очищает стек параметров и выполняет функцию QUIT, возвращая управление клавиатуре терминала. Не выдается никаких сообщений, даже "ok". Для обсуждения роли ABORT в качестве центрального слова внешнего интерпретатора Форт см. также описание QUIT в гл. 7. ABORT" "abort-quote" 83REQ VAR (флаг --) Отображает сообщение в зависимости от флага в стеке и прерывает исполнение программы. При использовании в форме : ... ABORT" ссс" ... ; компилирует текст "ссс" вплоть до разграничителя " (двойная кавычка) в описании . После ABORT" необходим пробел, который не включается в "ссс"; перед разграничителем " пробела не требуется. При исполнении
ABORT" предполагает наличие флага в стеке. Если флаг не равен 0, скомпилированный текст будет отображен, а затем исполнится последовательность команд, включающая ABORT. Если флаг будет иметь значение ложно, вышеуказанные процедуры будут опущены и исполнение будет продолжено. Точная форма отображения может зависеть от версии - может отображаться имя слова, в котором использовано , или только текст сообщения. ABS "absolute" "абсолютная величина" 83REQ 79REQ ( n1 -- n2) Засылает в стек значение абсолютной величины числа n1, т.
е. то же значение числа, но непременно положительное. Исключение составляет Форт-83, где для числа -32.768 знак не меняется (оно в норме не меняется и в Форт-79). ACASE "a-case" MMS ( симв -) Начинает алфавитно-цифровую CASE-структуру ACASE А В" OTHERWISE ...CASEND Список символов (здесь А, пробел и В), завершаемый двойной кавычкой, используется для выбора того, какое из списка Форт-слов, следующих за ", будет исполнено. Таким образом, если в стеке символ "A" (ASCII 65), управление будет передано слову ; аналогично, если в стеке код пробела (ASCII 32), управление передается . Если выбранное слово исполнено, управление передается слову, следующему за CASEND, Если код символа в стеке не содержится в списке символов, управление передается слову, следующему за OTHERWISE (если оно используется) или за CASEND. См. также NCASE. Обсуждение CASE-структур смотри в гл. 7. ALLOT "allot" "зарезервировать" 83REQ 79REQ ( n--) Резервирует место в словаре для п байтов, т.е. адрес первой свободной позиции в словаре, который засылается в стек оператором HERE, увеличивается на n. AND "and" "и" 83REQ 79REQ ( n1 n2 -- n3) Выполняет логическую операцию побитного "И" для чисел n1 и n2. т.е. каждый бит числа n1 сравнивается с эквивалентным битом числа n2 и, если любой из битов равен 0, в соответствующий бит числа n3 заносится 0. Итак (в двоичном представлении), 110 100 AND зашлет в стек двоичный код 100. См. также OR; XOR. ARRAY "array" "массив" MMS ( n -) Слово-описатель для создания линейных массивов (векторов) чисел одинарной длины. При использовании в форме n ARRAY
формирует статью в словаре с именем и резервирует место для n+1 числа одинарной длины. Когда используется в форме: n
В стек засылается адрес (п+О-го элемента массива См. также CARRAY; 2ARRAY; 4ARRAY. ASC "ack-e" MMS ( адр - n) Засылает в стек ASCII-код первого символа счетной строки, хранящейся по адресу "адр".
ASSEMBLER "assembler" "ассемблер" 83ASM 79ASM ( --) Заменяет первый контекстный словарь, где производится поиск, на словарь ASSEMBLER. В Форт-79 ассемблер выбирается как CONTEXT-словарь. AT "at" MMS ( --) При использовании в форме AT
засылает в стек содержимое слова QUAN-типа с именем . См. также QUAN; 2QUAN; 4QUAN. Противостоит IS. См. обсуждение QUAN в гл. 6. BASE "base" "основание" 83REQ 79REQ ( адр -) Переменная, содержащая основание системы счисления, которое используется при вводе и выводе. чисел. Так 2 base ! выберет двоичную систему счисления для ввода-вывода. В Форт-83 основание системы счисления должно лежать в пределах 2 - 72. а в Форт-79 это диапазон 2 - 70. См. также DECIMAL; OCTAL; HEX; BINARY. BASE? "base-question" TXT ( --) Отображает текущее значение BASE в десятичном представлении без изменения BASE. : BASE? BASE @ DUP DECIMAL . BASE ! ; BEGIN "begin" I C 83REQ 79REQ ( --) Отмечает начало структуры бесконечного цикла. При использовании в форме : ... BEGIN ... флаг UNTIL ... ; или : ... BEGIN ... флаг WHILE ... REPEAT ... ; компилирует структуру бесконечного цикла. Когда исполняется, слова между BEGIN и UNTIL будут выполняться повторно, пока флаг в стеке соответствует состоянию истинно. Исполнение слов между BEGIN и REPEAT будет повторяться до тех пор, пока флаг в стеке перед исполнением WHILE равен истинно. Слова после UNTIL или REPEAT будут исполняться по завершении цикла. BINARY "binary" TXT ( --) Выбирает двоичное представление чисел при вводе-выводе. : BINARY 2 BASE ! ; См. также DECIMAL; HEX; OCTAL. BL "b-l" 83CNT 79RES MMS ( -- 32) Заносит в стек ASCII-код пробела (десятичное число 32). BLANK "blank" 83CNT 79RES MMS ( адр n --) Записывает в n байтов памяти, начиная с адреса "адр", ASCII-код пробела (десятичное 32). При n, равном 0, ничего не делается. (В стандарте Форт-79 этот оператор имеет имя BLANKS, но так как для этого нет никакой исторической причины и поскольку несколько версий используют BLANKS, мы предполагаем, что это имя является типографской ошибкой в тексте стандарта.) BLK "b-l-k" 83REQ 79REQ ( - адр) Переменная, содержащая номер блока в массовой памяти, который в данный момент интерпретируется.
Если величина BLK равна 0, источником входного потока является клавиатура. BLOCK "block" "блок" 83REQ 79REQ ( n -- адр) Засылает в стек адрес первого байта буфера, содержащего блок n. Если блок еще не в памяти, то он будет передаваться из массовой памяти в блочный буфер, обращение к которому произошло раньше других. (В Форт-83 используется не обязательно буфер, к которому дольше всего не обращались, как в случае Форт-79, но большинство реализации следует версии Форт-79.) Если блок, занимающий этот буфер, не является блоком n и он был подвергнут операции UPDATE (т.е. модифицирован), его содержимое будет перенесено в массовую память, прежде чем блок n будет считан в буфер. Если правильное чтение или запись в массовую память не возможны, будет дано сообщение об ошибке. Дальнейшее обсуждение слова BLOCK см. в гл. 10. BODY> "from-body" 83FLD ( адр1 -- адр2) Исходный код "адр1" - адрес поля параметров слова, результат, "адр2",-- адрес поля программы. BRANCH "branch" С 83SYS ( --) При использовании в форме : ... COMPILE BRANCH ... ; компилируется оператор безусловного перехода. Адрес ветвления должен быть скомпилирован сразу после оператора безусловного перехода. Обычно не используется при составлении программ, а только в словах ELSE и REPEAT. См. также ?BRANCH. BUFFER "buffer" "буфер" 83REQ 79REQ ( n - адр) Присваивает номер блока т блочному буферу, обращение к которому произошло раньше других. В стек при этом засылается адрес первого байта этого буфера. В Форт-79 блок не читается из массовой памяти, он может читаться или нет в Форт-83. Если буфер был помечен для записи на диск (UPDATE), его содержимое будет перенесено в массовую память. Если правильная передача в массовую память невозможна, будет дано сообщение об ошибке. Слово BUFFER подобно BLOCK, за исключением того, что не обязательно производится обмен с массовой памятью. См. также BLOCK. С! "c-store" "С-присвоить" 83REQ 79REQ ( адр --) Записывает младший байт числа n в байт с адресом "адр".
См. также ! С, "С-comma" "С-запятая" 83REQ 79REQ ( n--) Записывает младший байт числа п (который в норме меньше 256) в байт первой свободной позиции словаря (адрес, засылаемый в стек оператором HERE) и добавляет к указателю словаря 1, чтобы зарезервировать место для скомпилированного байта. См. также ,. С@ "C-fetch" "извлечь байт" 83REQ 79REQ (адр - n) Заносит в стек байт, лежащий по адресу "адр". Результатом является число, старший байт которого равен 0, а младший - извлеченному байту. См. также @. CARRAY "с-аrray" "массив байтов" ТХТ MMS ( n --) Слово-описатель, которое создает линейный массив (вектор) с однобайтовыми элементами. При использовании в форме n CARRAY
создается статья словаря с именем и резервируется место для n+1 байта. Когда используется в форме n в стек заносится адрес (n+1)-го элемента. : CARRAY CREATE ALLOT DOES> + ; См. также 2CARRAY; ARRAY; ECARRAY. CASEND "case-end" MMS ( --) Отмечает конец ACASE- или NCASE-структуры в MMS.FORTH. См. также ACASE; NCASE. CCONSTANT "c-constant" ТХТ MMS ( b --) Слово-описатель, которое создает байтовые константы. При использовании в форме n CCONSTANT
формирует в словаре статью с именем и записывает байт из стека в поле параметров . При исполнении байт из поля параметров заносится в стек. : CCONSTANT CREATE С, DOES> С@ ; CFA "с-f-а" FIG VAR ( адр1 -- адр2) Исходный код "адр1" является адресом поля параметров, результирующий "адр2" - адресом поля программы. CHR$ "c-h-r-string" MMS ( n -- адр) Создает однобайтовую счетную строку, которая содержит символ, ASCII-код которого лежит в стеке. Строка записывается в PAD, адрес которого "адр" заносится в стек. CMOVE "c-move" 83REQ 79REQ ( адр1 адр2 n --) Копирует, n байтов, начиная с адреса "адр1", и укладывает их, начиная с адреса "адр2". Перенос осуществляется, начиная с младших адресов, и продолжается в сторону старших.
В Форт-79, если n равно 0 или отрицательно, ничего не переносится. См. также ; MOVE. CMOVE> "c-move-up" 83REQ ( адр1 адр2 u --) Копирует и байтов, начиная с адреса "адр1", в область памяти, начинающуюся с адреса "адр2". Перенос происходит, начиная с больших адресов в направлении малых. Если u равно 0, ничего не переносится. CMOVE>
полезно для перемещения байтов в направлении "вверх", когда области обмена перекрываются. (В Форт-79 имеется имя ... END-CODE формирует статью словаря с именем и делает ASSEMBLER контекстным словарем, чтобы разрешить применение мнемоники ассемблера. При исполнении выполняется машинная программа. Слова, описанные таким образом называются CODE- описаниями или CODE-словами. COMPILE "compile" "скомпилировать" 83REQ 79REQ ( --) Обычный формат использования: : .,. COMPILE ... ; Когда компилируется , в словарь вслед за адресом программы COMPILE компилируется адрес поля программы . является обычно словом немедленного исполнения, а , как правило, не является таковым. Когда исполняется (обычно в описании типа двоеточие), адрес поля программы компилируется в верхнюю ячейку словаря. Во многих версиях Форта LITERAL может скомпилировать слово LIT в другие слова, так как команда COMPILE LIT используется в описании LITERAL. CONSTANT "constant" "константа" 83REQ 79REQ ( n --) Слово-описатель, которое создает константы одинарной длины; При использовании в форме n CONSTANT
формирует в словаре статью с именем и компилирует n в поле параметров . Когда исполняется, в стек заносится число одинарной длины n. См, также 2CONSTANT; 4 CONSTANT; CCONSTANT. Противоположно VARIABLE. CONTEXT "context" "контекст" 83SRC 79REQ ( -- адр) Заносит в стек адрес переменной, которая определяет контекстный словарь, подлежащий просмотру первым. Форт-83 содержит предварительное предложение, в котором не использовано слово CONTEXT (или CURRENT), но предусматривается альтернативный способ задания порядка просмотра словаря.
См. также CURRENT; DEFINITIONS; VOCABULARY. CONTROL "control" "управление" ТХТ ( n ... -) Слово-описатель, которое создает слово, предназначенное для посылки одного или более управляющих кодов на активное выходное устройство. При использовании в форме n... CONTROL (1) формирует статью в словаре с именем и компилирует один или более управляющих кодов ASCII из стека. Когда исполняется, управляющие коды, скомпилированные в его поле параметров, посылаются на активное в данный момент выходное устройство. : CONTROL CREATE DEPTH DUP С, 0 DO DEPTH ROLL С, LOOP (1) DOES> DUP DUP C@ + SWAP DO I 1 + C@ EMIT LOOP ; Дальнейшие пояснения можно найти в соотв. главе. CONVERT "convert" "преобразовать" 83REQ 79REQ ( d1 адр1 -- d2 адр2) Преобразует в соответствии с величиной BASE несчетную ASCII-строку цифр в число, d2 является результатом преобразования каждого из символов (цифр) в строке, начиная с адреса "адр+1", в число и аккумуляции каждого числа, умноженного на величину BASE, в d1 (d1 в норме равно 0). Преобразование продолжается до тех пор, пока не встретится не преобразуемы и символ. Адрес этого символа засылается в качестве "адр2". Таким образом, если стек содержит число двойной длины 55, а строка имеет вид "11х", тогда CONVERT занесет в стек число двойной длины 5511; а адрес "х" будет занесен в качестве "адр2". COPIES "copies" "скопировать" ТХТ ( n1 n2 n3 --) ================================== 1 Описание предполагает, что перед обращением (1) стек пуст,- Прим. перев. ============================== Копирует серию из "n3" смежных блоков, начиная с блока "n1", и укладывает их, начиная с блока "n2". Копирование начинается с блока с наименьшим номером и продолжается в направлении последнего блока. Смотри описание. COPIES> "copies-up" TXT ( n1 n2 n3 --) Копирует серию из n3 смежных блоков, начиная с n1, и записывает их, начиная с n2.
Перенос начинается с блока, имеющего наибольший номер, и продолжается в направлении первого блока. Описание смотри. COPY "copy" TXT ( n1 n2 --) Копирует блок "n1" в буфер для блока "n2" и производит операцию UPDATE для этого буфера. Когда выполняется слово FLUSH или когда буфер "n2" используется повторно, вышеописанная операция имеет эффект копирования блоков "n1" в "n2". (Чтобы предотвратить непредсказуемые результаты, до COPY следует применить операторы FLUSH или EMPTY-BUFFERS.) COUNT "count" "подсчет" 83REQ 79REQ (адр1 - адр2 n) Засылает в стек число символов в счетной строке, лежащей по адресу "адр1". "адр2" равен "адр1+1" и указывает на начало текста, а "n"- длина этого текста. COUNT засылает в стек адрес и число-счетчик, необходимые для работы слова TYPE. Таким образом COUNT TYPE отобразит счетную строку, лежащую по адресу "адр". : COUNT DUP C@ SWAP 1+ ; CQUAN "C-quan" MMS ( --) Слово-описатель, которое создает слова QUAN-типа с байтовым содержимым. При использовании в Форме CQUAN
формирует в словаре статью с именем и резервирует место в поле параметров для записи байта. Когда исполняется само , код, который в нем содержится, заносится в стек. Если перед ним стоит IS, число из стека записывается в поле параметров . Если перед ним стоит AT, в стек заносится адрес поля параметров . См. также QUAN; 2QUAN; 4QUAN. См. гл. 6, где дано описание слова типа QUAN. CR "с-r" "возврат каретки" 83REQ 79REQ ( --) Посылает на активное в данный момент внешнее устройство коды "возврат каретки" и "перевод строки". На дисплее CR-помещает обычно курсор в начало следующей строки. CREATE "create" "создать" 83REQ 79REQ (--) Слово-описатель, создающее статью в словаре. При использовании в форме CREATE
формирует статью с именем без резервирования места в поле параметров . При исполнении адрес первого байта поля параметров заносится в стек.
Поле параметров не защищено, если не использованы слова ALLOT, С, или ,. CREATE может также использоваться в описаниях типа двоеточие в форме : ... CREATE ... ; или : ... CREATE ... DOES> ... ; или : ... CREATE ... ;CODE ... END-CODE чтобы сформировать новое слово-описатель . Например, VARIABLE можно описать как: : VARIABLE CREATE 2 ALLOT ; или : VARIABLE CREATE 0 , ; чтобы присвоить переменной нулевое начальное значение. CRS "c-rs" ТХТ ( n --) Посылает n пар кодов "возврат каретки" и "перевод строки" на активное внешнее устройство. Это перемещает текст на дисплее на n строк вверх. : CRS 0 DO CR LOOP ; CRT "с-r-t" MMS ( --) Переключает вывод только на экран. См. также PCRT; PRINT. CURRENT "current" "текущий" 83SRC 79REQ ( -- адр) В Форт-79 это переменная, определяющая контекстный словарь, в котором будут описываться новые слова. В Форт-83 CURRENT является словом "системного расширения" и не входит в обязательный список. Порядок поиска слова здесь не задан, за исключением слов, входящих в экспериментальный список. См. также CONTEXT; DEFINITIONS; VOCABULARY. Подробнее разъяснение см гл. 14, Контекстные словари. CVARIABLE "c-variable" "С-переменная" MMS ( --) Слово-описатель, которое создает байтовые переменные. При использовании в форме CVARIABLE
формирует в словаре статью с именем и резервирует байт в его поле параметров. В качестве начального значения засылается нуль. При исполнении в стек засылается адрес его поля параметров. : CVARIABLE CREATE 0 С, ; D#IN "d-number-in" MMS ( -- d) Отображает запрос "?" и ожидает ввода числа. Число заносится в стек в виде кода двойной длины. При ошибке ввода последует запрос "?Redo" и вы сможете сделать еще одну попытку. В HI# и #РТ заносятся соответствующие коды. См. #IN. D* "d-times" "D-умножить" MMS VAR ( d1 d2 -- d3) Умножает d1 на d2 и заносит в стек произведение двойной длины d3. D*/ "d-times-divide" "D-умножить-разделить" MMS ( d1 d2 d3 -- d4) Умножает d1 на d2 (произведение имеет 64 бита) и делит произведение на d3.
Округленное в направлении нуля значение частного двойной длины, d4, заносится в стек. См. также */. D*/MOD "d-times-divide-mod" "D-умножить-разделить с остатком" MMS ( d1 d2 d3 -- d4 d5) Умножает d1 на d2 (промежуточный результат имеет 64 бита) и делит произведение на d3, засылая в стек остаток двойной длины d4 и целое, округленное в направлении нуля частное двойной длины d5. См. также */MOD. D+ "d-plus" "D-плюс" 83REQ 79REQ ( d1 d2 - d3) Складывает d2 и d1, а сумму двойной длины d3 засылает в стек. D- "d-minus" "D-минус" 83DBL 79DBL ( d1 d2 -- d3) Вычитает d2 из dl и засылает значение разности двойной длины в стек. D. "d-dot" "D-точка" 83DBL 79DBL ( d -) В соответствии со значением слова BASE отображает величину "d" (включая знак минус, если число отрицательно) в позиции на экране, отмеченной курсором. После вводится пробел. См. также. (точка). D.R "d-dol-r" "D-точка-R" 83DBL 79DBL ( d n --) Отображает число d в поле длиной n позиций так, что младшая цифра занимает в поле самое правое положение. Если число d отрицательно, перед ним ставится знак минус, В Форт-83, если число символов, необходимых для отображения d, больше n, дается сообщение об ошибке. В Форт-83 не разрешена также длина поля менее 1. В Форт-79 при n меньше 1 предшествующие числу пробелы опускаются, а при требуемом числе символов для отображения d больше n не дается сообщения об ошибке. (В большинстве версий Форт-79 результат будет напечатан без лидирующего пробела.) Смотри .R. D/ "d-divide" "D-разделить" MMS ( d1 d2 -- d3) Делит d1 на d2 и засылает частное двойной длины, округленное в сторону 0, в стек. D/MOD "d-divide-mod" "D-раэделить с остатком" MMS ( d1 d2 - d3 d4) Делит d1 на d2 и засылает в стек остаток двойной длины d3 и частное двойной длины d4, округленное в сторону нуля. D0= "d-zero-equals" "D равно нулю" 83DBL 79DBL ( d -- флаг) Сравнивает число d с нулем и засылает в стек флаг истинно, если d равно нулю.
D2/ "d-two-divide" "D-разделить на 2" 83DBL ( d1 -- d2) Делит d1 на 2 и засылает в стек частное двойной длины d2. Используется деление с нижней границей. D< "d-less-than" "D-меньше" 83REQ 79REQ ( d1 d2 -- флаг) Сравнивает числа d1 и d2 и засылает в стек флаг истинно, если d1 меньше d2. D= "d-equal" "D-равно" 83DBL 79DBL ( d1 d2 -- флаг) Сравнивает числа d1 и d2 и засылает в стек флаг истинно, если d1 равно d2. DABS "d-absolute" 83DBL 79DBL ( d1 - d2) Засылает в стек абсолютную величину числа двойной длины со знаком d1. Таким образом, d2 будет числом двойной длины, имеющим ту же величину, что и d1, но всегда положительным. Исключением является случай, когда в Форт-83 d1 равно -2.147.483.648. Для этого числа знак не изменяется. (Знак этого числа не изменяется и в большинстве версий Форт-79.) DARRAY "d-array" "D-массив" MMS ( n --) Слово-описатель, создающее линейный массив (вектор) чисел двойной длины. При использовании в форме n DARRAY
формирует в словаре статью с именем и резервирует место для n+1 числа двойной длины. Когда исполняется в форме: n
в стек засылается адрес (n+1)-го элемента. См. также 4ARRAY; ARRAY; CARRAY. Противоположно 2ARRAY. DECIMAL "decimal" 83REQ 79REQ ( --) Устанавливает для ввода-вывода десятичное представление чисел. Заносит 10 (десятичное) в переменную BASE. DEFINITIONS "definitions" "описания" 83REQ 79REQ ( --) Задает контекстный словарь, в котором компилируются описания. Именно этот словарь просматривается при поиске первым. В Форт-83 средства для изменения порядка просмотра словаря не предусмотрены. Это реализовано в Форт-79 путем присвоения переменной CURRENT значения переменой CONTEXT. Например, выполнение команды ASSEMBLER DEFINITIONS приведет к тому, что последующие описания окажутся в контекстном словаре ASSEMBLER. См. также VOCABULARY. DEPTH "depth" "глубина" 83REQ 79REQ ( - n) Засылает в стек число одинарной длины, равное числу кодов, лежащих в стеке до исполнения слова DEPTH.
Если стек пуст, n равно 0. DMAX "d-max" 83DBL 79DBL ( d1 d2 -- d3) Заносит в стек число d3, которое является большим из d1 и d2. См. также DMIN; MAX DMIN "d-min" 83DBL 79DDL ( d1 d2 -- d3) Засылает в стек d3, которое является меньшим из d1 и d2. См. также DMAX; MIN. DNEGATE "d-negate" 83REQ 79REQ ( d1 -- d2) Меняет знак числа d1 на обратный и заносит результат в стек. d2 является дополнением d1 по модулю два. (т.е. 0 минус d1). DO "do" "выполнить" I, С 83REQ 79REQ ( n1 n2 --) Подготавливает начало цикла do-loop. При использовании в форме: : ... п1 п2 DO ... LOOP ... ; или : ... at n2 DO ... приращение +LOOP ...; подготавливает начало цикла do-loop. Когда исполняется, DO берет числа n1 и n2 из стека и использует n2 в качестве начального значения индекса цикла, а n1 - в качестве предела, который определяет условие завершения цикла do-loop. Чтобы выяснить отличия между условиями завершения цикла в Форт-79 и Форт-83, смотри описания LOOP и +LOOP. Любой цикл do-loop выполняется по крайней мере один раз. Допускается вложение циклов do-loop, с помощью слова LEAVE можно прервать цикл до достижения предела. DOES> "does" "выполняет" I, С 83REQ 79REQ (-- адр) Определяет действие слова, сформированного словом-описателем, на фазе исполнения. Используется в форме : ... ... DOES> ... ; где соответствует CREATE или другому слову-описателю, которое использует CREATE, a - новое слово-описатель. (В действительности Форт-79 определяет, что должно быть словом CREATE, в то время как Форт-83 допускает вышеописанный вариант. Большинство версий Форт-79, однако, позволяют использование либо CREATE, либо слова-описателя с ним внутри.) DOES> отмечает начало описания , которое определяет поведение его производных слов. Таким образом, когда исполняется:
формируется в словаре статья с именем , а поведение слова при исполнений задается последовательностью слов между DOES> и ; в описании . DOES> можно использовать только внутри описания типа двоеточие.
Например, опишем CONSTANT: : CONSTANT CREATE , DOES> @ ; Более подробное описание смотри в гл. 11. См, также ;CODE. DROP "drop" "удалить" 83REQ 79REQ ( n -) Удаляет код n из стека. DU* "d-u-times" "d-u-умножить" MMS ( ud1 ud2 - uq) Умножает udl на ud2 (оба являются числами двойной длины без знака) и засылает в стек 64-битовое их произведение uq. DU/MOD "d-u-mod" MMS ( uq ud1 - ud2 ud3) Делит uq (64-битовое число без знака) на udl (число двойной длины без знака) и засылает остаток ud3 и частное ud4. DU< "d-u-less" "D-U-меньше" 83DBL 79DBL ( ud1 ud2 -- флаг) Сравнивает ud1 и ud2 (оба числа двойной длины без знака) и засылает в стек флаг истинно, если ud1 меньше ud2. DUMP "dump" 83CNT 79RES ТХТ MMS VAR (адр n --) Распечатывает содержимое n байтов памяти, начиная с адреса "адр". Каждая строка начинается с пропечатки адреса первого байта, в остальном все зависит от версии программы. (Приведенное описание предполагает наличие в стеке начального адреса и числа 16-байтовых строк, а не числа байтов. Это описание отображает байты в шестнадцатеричном представлении в ASCII-форме.) : DUMP ( адр n --) CR BASE @ >R HEX 16 * OVER + SWAP DO 10 TYPE 2 SPACES 16 0 DO I 4 MOD NOT IF SPACE THEN I J + С@ 0 TYPE SPACE LOOP CR 6 SPACES 16 0 DO I 4 MOD NOT IF SPACE THEN I J + C@ DUP 31 > OVER 127 < AND IF EMIT 2 SPACES ELSE DROP 3 SPACES THEN LOOP CR 16 +LOOP R> BASE ! ; DUP "dupe" "Задублировать" 83REQ 79REQ ( n - n n) Дублирует в стеке число одинарной длины n. Е "е" (for "edit") (для "EDIT") TXT MMS ( --) Редактирует блок, номер которого записан в SCR (обычно это блок, только что редактированный или выведенный на экран). ECARRAY "еrror-с-аrrау" TXT ( n--) Слово-описатель, создающее линейный массив (вектор) с байтовыми элементами. При использовании в форме n ECARRAY
формирует в словаре статью с именем и резервирует в памяти n+1 байт.
При использовании в форме n
в стек засылается адрес (n+1)-го элемента. Если n больше, чем число элементов в , или n меньше 0, выдается сообщение об ошибке. : ECARRAY CREATE DUP , 2 + ALLOT DOES> DUP @ 3 PICK U< IF + 2+ ELSE ." Index error" ABORT THEN ; Почти идентичен CARRAY, за исключением контроля ошибки. EDIT "edit" "редактировать" TXT MMS VAR (n --) Активизирует редактор Форта и редактирует блок n. См. гл. 12 и 13. EDITOR "editor" "редактор" 83CNT 79RSV ( --) Делает так, чтобы контекстный словарь EDITOR просматривался первым. (Многие версии Форта не используют отдельный контекстный словарь для редактора.) ELSE "else" "иначе" I, С 83REQ 79REQ ( --) Отмечает начало альтернативной ветви программы. При использовании в форме : ... флаг IF ... ELSE ... THEN ... ; компилирует оператор безусловного перехода, чтобы продолжать исполнение сразу после оператора THEN. Когда исполняется, IF предполагает наличие флага в стеке. Если флаг не равен 0, будут исполнены слова между IF и ELSE с продолжением после THEN, если же значение флага равно 0, управление будет передано словам между ELSE и THEN с продолжением после THEN. EMIT "emit" 83REQ 79REQ ( n --) Посылает символ, код которого лежит в младшем байте числа n, на активное выходное устройство (обычно дисплей), n, как правило, меньше 256. Форт-83 требует, чтобы только младшие 7 битов (т. е. ASCII-код) отображались, но это ограничение игнорируется в большинстве версий. EMPTY-BUFFERS "empty-buffers" "очистить буферы" 83CNT 79REQ ( --) Удаляет флаги спасения и стирает все коды приписки буферов к блокам. Помеченные для записи ранее блоки не будут записаны в массовую память. EMPTY-BUFFERS следует использовать, когда в массовую память может быть записана неверная информация. EMPTY- BUFFERS не нужно в стандарте Форт-83, так как стандарт запрещает помещать в буфер что-либо, что нельзя записать. (Но это требование часто игнорируется.) См.
также BLOCK; SAVE-BUFFERS; FLUSH. END-CODE "end-code" 83ASM 79ASM ( sys --) Завершает описание слова в Форт-ассемблере. При использовании в форме CODE ... END-CODE или : ... ... ;CODE ... END- CODE завершает описание, начатое оператором CODE или словом-описателем, которое использует ;CODE и позволяет находить слова или в словаре. Форт-79 требует также, чтобы оператор END-CODE преобразовал контекстный словарь в текущий. Используется для предотвращения нахождения в словаре или исполнения слова, описанного с ошибкой. Некоторые версии допускают, но не требуют END-CODE (например, MMSFORTH). ERASE "erase" "стереть" 83CNT 79RES MMS ( адр n --) Обнуляет n байтов в памяти, начиная с адреса "адр". Если n равно 0, ничего не производится. EXECUTE "execute" "исполнить" 83REQ 79REQ ( адр --) Исполняет слово, адрес поля программы "адр" которого лежит в стеке. Хотя Форт-83 требует, чтобы выдавалось сообщение об ошибке, если "адр" не является адресом поля программы, это очень трудно реализовать, и поэтому такое требование вообще игнорируется. Большинство версий разрушаются, если попытаться выполнить EXECUTE для произвольного адреса. EXIT "exit" "уход" 83REQ 79REQ ( --) Если встречается внутри слова-двоеточия, заставляет Форт "уйти" из этого слова и вернуться к исполнению слова, от которого произошло обращение. В большинстве версий EXIT может использоваться, чтобы досрочно завершить интерпретацию блока, возвратив управление клавиатуре. EXIT не может использоваться внутри цикла do-loop. Одной из функций ; является компиляция EXIT (или заменяющего слова) для завершения описания типа двоеточие. EXPECT "expect" 83REQ 79REQ ( адр n --) Прерывает исполнение программы, чтобы принять серию символов с клавиатуры и записать их в память. Символы запоминаются, начиная с адреса "адр", а запоминание продолжается в направлении старших адресов до тех пор, пока не будет введен символ "возврат каретки" (ASCII 13)- или пока не поступит п символов.
Все введенные и записанные в память символы отображаются на экране по мере их ввода. Код "возврат каретки" не будет записан в память. Ни одного символа не asder занесено в память, если n равно 0, Форт-79 требует также, чтобы в конце введенного текста по завершении EXPECT было записано два нуля. Форт-83 требует, чтобы возврат каретки был отображен как пробел. См. также SPAN. FENCE "fense" "ограда" MVP FIG VAR ( -- адр) Переменная, которая определяет адрес в словаре, ниже которого нельзя производить стирание словаря с помощью FORGET. FILL "fill" "запомнить" 83REQ 79REQ ( адр n1 n2 --) Заполняет n1 байтов памяти, начиная с адреса адр, кодом, содержащимся в младшем байте числа n2. Если n1 меньше или равно 0, ничего не делается. В Форт-83 n1 предполагается числом без знака и заполнение блокируется, если оно равно 0. FIND "find" "найти" 83REQ 79REQ FIND в Форт-83 и Форт-79 выполняют практически совершенно разные функции. Схема трансформации стека в форт-83 выглядит как (адр1 -- адр2 n) где "адр1" является адресом счетной строки, содержащей имя слова, которое должно быть найдено в словаре. Если не найдено, "адр2" равен "адр1", а n=0. Если найдено, "адр2" равен адресу поля программы , а "n" равно 1, если - слово немедленного исполнения, в противном случае значение "n" приравнивается минус единице. В Форт-79 схема преобразования стека имеет вид ( -- адр) или ( -- 0) где FIND используется в форме FIND
и заносит в стек либо адрес поля программы , либо нуль, если
в словаре не найдено. FL/ "f-l-divide" ТХТ ( n1 n2 -- n3) Используя операторы Форт-79, делит число n1 на n2 и засылает в стек округленное по нижней границе значение частного n3. Моделирует (медленно) оператор / Форт-83, используя Форт-79. : FL/ /MOD SWAP IF DUP 0 < IF 1- THEN THEN ; FL/MOD "M-divide-mod" TXT ( n1 n2 -- n3 n4) Используя операторы Форт-79, осуществляет деление с нижней границей n1 на n2 и засылает в стек остаток n3 и округленное значение частного n4.
Моделирует (медленно) оператор Форт-83 /MOD, используя Форт79. : FL/ MOD 2DUP FLMOD ROT ROT FL/ ; FLMOD "f-l-mod" TXT ( n1 n2 -- n3) Используя операторы Форт-79, выполняет деление с нижней границей n1 на n2 и засылает в стек остаток n3. Моделирует (медленно) оператор Форт-83 MOD, используя Форт-79. : FLMOD 2DUP FL/ * - ; FLUSH "flush" 83REQ MMS VAR ( --) Копирует содержимое всех блочных буферов, помеченных для спасения, в соответствующие блоки массовой памяти. Синоним - SAVE-BUFFERS в большинстве версий Форта. В Форт-83 требуется, чтобы приписка буферов к блокам была ликвидирована, в то время как SAVE-BUFFERS этого не требует. FORGET "forget" "забыть" 83REQ 79REQ ( --) При использовании в форме FORGET
уничтожает слово и все слова, добавленные в словарь после , вне зависимости от их принадлежности к контекстным словарям, если
найдено в контекстном словаре, куда вводились описания. ( В Форт-79 - это контекстный словарь CURRENT.) Если не найдено, дается сообщение об ошибке. FORTH "forth" 83REQ 79REQ ( --) Имя первичного контекстного словаря. Исполнение слова FORTH делает словарь FORTH первым и единственным, который просматривается при поиске слов. Новые описания становятся частью словаря FORTH до тех пор, пока с помощью DEFINITIONS не будет сделан контекстным другой словарь. См. также CURRENT; CONTEXT; DEFINITIONS; VOCABULARY. FORTH-83 "forth-83" 83REQ ( --) Сообщает, соответствует ли система стандарту Форт-83. Если стандартная система Форт-83 не используется, слово не будет найдено, а будет дано сообщение об ошибке. HERE "here" "здесь" 83REQ 79REQ ( -- адр) Засылает в стек адрес очередной свободной позиции в словаре, указатель словаря. HEX "hex" 83CNT 79RES TXT MMS Выбирает для ввода-вывода шестнадцатеричную систему счисления. : HEX 16 BASE ! ; HI# "high-number" MMS ( --n) MMSFORTH воспринимает все числовые вводы как числа двойной длины, помещая число двойной длины в стек, если входная последовательность содержит десятичную точку, и посылая в стек число одинарной длины в противном случае.
В обоих вариантах QUAN НI# содержит старшие 16 битов последнего из введенных чисел. HOLD "hold" 83REQ 79REQ Вводит символ с ASCII-кодом n в форматированную цифровую выходную строку. Используется между . См., например, #. I "i" 83REQ 79REQ ( - n) или ( -- u) Заносит в стек индекс цикла do-loop. Может использоваться только непосредственно, а не в словах, к которым происходит обращение в цикле do-loop. См. также I'; J; К. I' "i-prime" 83UNC 79REQ MMS ( - n) или ( - u) Засылает в стек индекс цикла при использовании в слове типа двоеточие, к которому происходит обращение в цикле do-loop. См. также I; J'. IF "if" "если" I С 83REQ 79REQ (флаг --) Открывает одно- или двухветвевую структуру. Используется в форме : ... флаг IF ... THEN ... ; или : ... флаг IF ... ELSE ... THEN ... ; Когда исполняется, оператор IF предполагает наличие в стеке флага. Если флаг не равен 0, исполняются слова между IF и ELSE (или слова между IF и THEN, если ELSE не используется). Но если значение флага равно 0, управление передается словам между ELSE и THEN (или слову после THEN, если ELSE отсутствует). В обоих случаях исполнение продолжается после THEN. Структуры IF...ELSE...THEN допускают вложения. IMMEDIATE "immediate" "немедленное" 83REQ 79REQ ( --) Помечает только что созданное слово словаря так, что оно будет исполнено (а не скомпилировано) даже если Форт находится в режиме компиляции. IN$ "in-string" "ввод строки" MMS ( -- адр) Отображает Запрос "?" и ожидает ввода с клавиатуры строки, завершающейся кодом "возврат каретки". Счетная строка засылается в буфер PAD, адрес которого "адр" кладется в стек. См. также $IN. INDEX "index" "индекс" 83UNC 79RES MMS ( n1 n2 --) Отображает верхние строки каждого из блоков в диапазоне от n1 до n2. n2 - в большинстве версий Форта верхняя граница диапазона, а в MMSFORTH - число блоков. INKEY$ "in-key-string" MMS ( -- адр) Создает в PAD односимвольную счетную строку, содержащую код символа нажатой клавиши, в стек засылается адрес PAD в виде "адр".
Если не нажато никакой клавиши, то при исполнении INKEY$ строка в PAD не будет содержать ничего (байт-счетчик равен 0). INKEY$ не ждет ввода. INSTR "in-s-t-r" MMS ( адр1 адр2 -- n) Производит поиск строки, хранящейся по адресу "адр2" в строке, начинающейся с адреса "адр1". Если поиск успешен, в стек заносится начальная позиция этой строки по отношению "адр1", в противном случае-нуль. IS "is" MMS ( n --) Записывает число в слово типа QUAN. При использовании в форме: n IS
записывает n в слово типа QUAN с именем . n - число с длиной, соответствующей CQUAN, QUAN, 2QUAN или 4QUAN. Противостоит AT. J "j" 83REQ 79REQ ( -- n) Используется во вложенных циклах do-loop в форме DO ... DO ... J ... LOOP ... +LOOP ; Засылает в стек значение индекса очередного внешнего цикла, в данном примере цикла, завершающегося оператором +LOOP. Подобно I, J может использоваться только непосредственно, а не внутри другого слова, к которому происходит обращение внутри цикла. J' "J-prime" TXT ( --) Выполняет функцию J, во внутри слова типа двоеточие, к которому происходит обращение внутри цикла. Аналог I'. : J' R> R> R> R> R@ SWAP >R SWAP >R SWAP >R SWAP >R ; К "k" 83CNT 79RSV ( -- n) Используется во вложенных циклах do-loop в форме DO...DO...DO...K...+LOOP...+LOOP...LOOP Засылает в стек значение индекса цикла второго по отношению к тому, где применено К, в данном случае цикла, завершаемого оператором LOOP. См. также I, J. KEY "key" "клавиша" 83REQ 79REQ ( --n) Приостанавливает исполнение программы и ждет нажатия клавиши, после чего помещает в стек ее ASCII-код. Символ, полученный KEY на экране не отображается [Форт-83 требует, чтобы вводимый код был не более 127 (7 бит), но фактически все версии игнорируют это требование и допускают прием восьми битов (байта), т. е. кодов до 255.] L "list" MMS VAR ( --) Отображает блок, номер которого содержит переменная SCR, обычно это блок, который только что отображался или редактировался. : L SCR @ LIST ; L>NAME "link-to-name" 83FLD ( адр1 - адр2) Исходный код "адр1" - адрес поля связи слова, засланный в стек код"адр2" - адрес поля имени этого слова.
LABEL "label" "метка" ТХТ MMS ( --) Слово-описатель, которое позволяет создавать именованные машинные программы, написанные на Форт-ассемблере. При исполнении в форме LABEL
формируется в словаре статья с именем и в качестве контекстного словаря выбирается ASSEMBLER, с которого и начинается поиск. Это позволяет описать с применением мнемоники ассемблера. Когда
исполняется, в стек заносится адрес его поля параметров, пригодный для использования словом ассемблера CALL. : LABEL CREATE [COMPILE] ASSEMBLER ; Подробности -гл. 16. LEAVE "leave" "уйти" 83REQ 79REQ ( --) Вызывает немедленное прерывание цикла do-loop. Исполняется в форме : ... DO ... LEAVE ... LOOP ... ; Когда LEAVE встречается в тексте программы, цикл do-loop прерывается. Это делается обычно при выполнении определенного условия в структуре IF...THEN. При прерывании цикла в Форт-83 слова между LEAVE и LOOP или +LOOP не исполняются, в то время как в Форт-79 они выполняются. В обоих случаях исполнение продолжается со слова после LOOP. LEFT$ "left-string" MMS ( адр1 n - адр2) Извлекает n символов из начала счетной строки с адресом "адр1" и создает новую счетную строку в PAD. В стек засылается адрес PAD (адр2). LEN "len" MMS ( адр -- n) Засылает в стек длину счетной строки с адресом "адр". Эквивалент С@. LFA "l-f-a" FIG VAR (адр1 - адр2) Исходный код "адр1" - адрес поля параметров слова, в стек заносится "адр2" - адрес поля связи этого слова. LINK> "from-link" 83FLD ( адр1 - адр2) Исходный код "адр1" - поле связи слова, в стек заносится "адр2" - адрес поля программы этого слова. LIST "list" I 83CNT 79REQ ( u --) Отображает содержимое блока u. Переменной SCR присваивается значение u. LISTS "lists" MMS ( n1 n2 --) Отображает n2 блоков, начиная с блока nl, каждый из блоков представляется в формате LIST. LIT "lit" FIG VAR ( -- n) Слово, компилируемое LITERAL для того, чтобы заносить в стек следующее за ним в словаре 16-битовое число.
Подробности в гл. 15. См. также COMPILE. LITERAL "literal" "литерал" I, С 83REQ 79REQ ( n --) Обычно используется в форме : ... [ n ] LITERAL ... ; Компилирует LIT (или его эквивалент), а вслед за ним - число (n), которое в момент компиляции лежит в стеке. Когда исполняется, n заносится в стек. Подробности в гл. 15. LL "list-last" TXT MVP ( --) Отображает блок, предшествующий по номеру блоку, который только что отображался или редактировался. LN "list-next" ТХТ MVP ( --) Отображает блок, следующий по номеру за блоком, который только что отображался или редактировался. LOAD "load" "загрузить" 83REQ 79REQ ( u -) Начинает интерпретацию блока "u", сделав его "входным потоком". Значения >IN и BLK сохраняются, после чего производится интерпретация блока. Если интерпретация в явной форме не прерывается, она будет прекращена, когда входной поток иссякнет. Когда загрузка выполнена, интерпретация продолжается с места, откуда было выполнено обращение ( т.е. восстанавливаются прежние значения переменных >IN и BLK). LOADS "loads" ТХТ MMS ( n1 n2 --) Загружает n2 блоков, начиная с блока n1. : LOADS OVER + SWAP DO I LOAD LOOP ; LOOP "loop" "цикл" I C 83REQ 79REQ ( --) При использовании в форме : ... DO ... LOOP ... ; определяет конец цикла do-loop. Когда исполняется, DO устанавливает начальное значение индекса и предела цикла. По завершении очередного цикла LOOP дает единичное приращение индексу цикла. В Форт-83, если в результате приращения индекс перешел границу между пределом минус 1 и пределом, цикл завершается. В Форт-79 цикл завершается не при переходе через эту границу, а при индексе, большем или равном пределу. Если цикл не прерван, управление передается слову, следующему за DO. Смотри обсуждение прерывания циклов в гл. 8. См. также +LOOP; DO. М* "m-limes" "М-умножить" MMS ( n1 n2 -- d) Перемножает два числа одинарной длины n1 и n2, засылая в стек произведение двойной длины d.
Используется, если возможно переполнение при *. М*/ "m-times-divide" "М-умножить-разделить" MMS ( d1 n1 n2 -- d2) Умножает содержимое d1 на n1 и делит произведение на n2, В стек засылается частное d2. Результат умножения d1 на п1 для предотвращения арифметического переполнения величина d1*n1 имеет промежуточный 48- битовый формат (тройная длина). М+ "m-plus" "М-плюс" MMS (d1 n --d2) Складывает значения содержимого n и d1, засылает в стек сумму двойной длины d2. М- "m-nunus" "М-минус" MMS ( d1 n - d2) Вычитает содержимое n из d1, засылает в стек разность двойной длины. М/ "m-divide" "M-разделить" MMS ( d n1 -- n2) Делит d на n1, засылает в стек частное одинарной длины n2 (округление в сторону нуля). M/MOD "m-divide-mod" "М-разделить с остатком" MMS ( d n1 - n2 n3) Делит d на n1, засылает в стек остаток n2 одинарной длины и частное одинарной длины n3 (округление в сторону нуля). MAX "max" "макс" 83REQ 79REQ ( n1 n2 -- n3) Засылает в стек число п3, которое является большим из n1 и n2. См. также DMAX; MIN. MID$ "mid-string" "MID-строка" MMS ( адр1 n1 n2 -- адр2) Извлекает счетную подстроку из счетной строки с адресом адр1, начиная с адреса адр1+n1, копирует n2 символа в новую строку и укладывает ее в PAD, адрес которого адр2 заносится в стек. MIN "min" "мин" 83REQ 79REQ ( n1 n2 -- n3) Засылает, в стек число n3, которое является меньшим из n1 и n2. См. также DMIN; MAX. MOD "mod" "остаток" 83UNC 79REQ ( n1 n2 -- n3) Делит n1 на n2 и засылает в стек остаток n3. В Форт-83 используется деление с нижней границей, в то время как в Форт-79 частное округляется в направлении нуля. В Форт-83 при делителе, равном 0, или при частном вне диапазона -32.768 - 32.767 дается сообщение об ошибке. MOVE "move" "перенести" 83UNC 79REQ ( адр1 адр2 n --) Переносит n чисел одинарной длины, начиная с адреса адр1 в память, начиная с адреса "адр2".
Перенос происходит от младших адресов в направлении старших. Форт-79 предписывает, что n - число одинарной длины со знаком, если n меньше или равно 0, ничего не переносится. В Форт-83 n - число без знака и, если n равно 0, перенос не производится. См. также CMOVE; . MYSELF "myself" MMS VAR ( --) Позволяет слову обращаться к самому себе. При использовании в форме : ... MYSELF ... ; в поле параметров компилируется адрес поля программы , причем место в поле параметров задается положением слова MYSELF в описании. Чтобы обеспечить уход из цикла, MYSELF обычно вводится внутрь условных структур, в противном случае цикл будет бесконечным. Синоним - RECURSE. Обсуждение рекурсии в Форте смотри в гл. 15. N>LINK "name-to-link" 83FLD (адр1 -- адр2) Исходный код адр1 - адрес поля имени слова, в стек засылается адр2 - адрес поля связи этого слова. NAME> "from-name" 83FLD ( адр1 - адр2) Исходный код адр1 - адрес поля имени слова, в стек заносится адр2 - адрес поля программы этого слова. NCASE "n-case" MMS ( n --) Открывает цифровую CASE- структуру. Формат использования: NCASE n1 n2 n3 " OTHERWISE... CASEND Список чисел (здесь n1, n2 и n3) завершается пробелом и двойной кавычкой, используется для выбора и исполнения слова из списка слов, следующего за ". Используются только 8 младших битов каждого числа. Таким образом, если в стеке n1, управление будет передано , аналогично, если в стеке n2, управление передается . Когда выбранное слово исполнено, управление передается слову, следующему за CASEND. Если число в стеке не соответствует ни одному из чисел в списке, исполнение продолжится со слова после OTHERWISE (если оно присутствует) или после CASEND. См. также ACASE. NEGATE "negate" "сменить знак" 83REQ 79REQ (n1 -- n2) Реверсирует знак числа n1 и заносит в стек результат в виде n2. n2 - дополнение n1 по модулю два (т.е. нуль минус n1). NEXT "next" "следующий" MMS VAR ( --) Слово MMSFORTH-ассемблера, которое компилирует оператор перехода ко внутреннему интерпретатору, таким образом завершая описание слова ассемблера.
Другие версии могут использовать слова с другим именем для реализации этой функции. Подробности в гл. 16. NFA "n-f-a" FIG VAR ( адр1 -- адр2) Исходный код адр1 - адрес поля параметров слова, в стек заносится адр2 - адрес поля имени этого слова. NIP "nip" ( n1 n2 -- n2) Ликвидирует второе сверху число в стеке. Эквивалентно : NIP SWAP DROP ; NOT "not" 83REQ 79REQ NOT описано совершенно по разному в Форт-83 и в Форт-79. Схема преобразования стека в Форт-83 имеет вид ( n1 -- n2) - где n2 - дополнение n1 по модулю один. То есть все биты n2 реверсированы по отношению к n1. Схема преобразования стека в Форт79 имеет вид ( флаг1 -- флаг2) где флаг1 имеет обратное значение истинности по отношению к флаг2. Описание в Форт-79 эквивалентно 0=, т.е. флаг истинно (не 0) будет преобразован в нуль, а флаг ложно (0) - во флаг истинно (1). NUMBER "number" 83UNC 79RES MMS Обычно ( адр - d) MMSFORTH (адр1 - n адр2) или (адр1 - d адр2) преобразует счетную строку с адресом адр в 32-разрядное целое число со знаком, при этом учитывается значение - BASE, результат заносится в стек. Если цифровое преобразование не возможно, дается сообщение об ошибке. Строка может содержать в начале знак минус. NUMBER в MMSFORTH засылает в стек число одинарной или двойной длины в зависимости от того, содержит ли строка код десятичной точки. Переменным НI# и #РТ присваиваются соответствующие значения, адр2 - адрес первого непреобразуемого символа. OCTAL "octal" 83CNT 79RES MMS ( --) Выбирает восьмеричную систему счисления для ввода-вывода. : OCTAL 8 BASE ! ; OR "OR" "ИЛИ" 83REQ 79REQ (n1 n2 --n3) Выполняет побитовую операцию ИЛИ над числами n1 и n2 и засылает результат в виде n3. Таким образом, каждый бит числа n1 сравнивается с соответствующим битом n2, и если один из них или оба равны 1, то соответствующий бит n3 будет равен 1; в противном случае этот бит будет равен 0. В двоичной форме 110 100 OR занесет в стек двоичное 110. См. также AND; XOR.
OTHERWISE "otherwise" "в противном случае" MMS ( --) Может использоваться в структурах ACASE или NCASE для того, чтобы пометить место продолжения исполнения программы, если условие не выполнено. Смотри, например, ACASE и NCASE. (Заметьте, что это слово в MMSFORTH полностью не совпадает со словом Форт-83 из неконтролируемого списка и словом Форт-79 из контролируемого списка.) OVER "over" "через" 83REQ 79REQ ( n1 n2 -- n1 n2 n1) Копирует второе сверху число в стеке на верх стека. PAD "pad" 83REQ 79REQ ( --) Заносит в стек самый "нижний" адрес буфера, которой может быть использован для временного хранения информации. Адрес PAD изменяется, а данные, записанные там, теряются, если изменяется адрес следующей свободной ячейки словаря (засылаемый в стек HERE), т.е. если что-то будет добавлено в словарь. Минимальная емкость PAD задается в Форт-83 равной 84 символам и в Форт-79 - 64 символам. PAGE "page" "страница" 83UNC 79RES MMS ( --) Очищает экран терминала и помещает курсор в верхний левый угол. При выводе на печать выдается код "перевод страницы". PCRT "р-c-r-t" MMS ( --) Обеспечивает вывод на печать и экран (ЭЛТ) одновременно. PEMIT "p-emit" MVP Аналог EMIT, но посылает символ не на видеотерминал, а на печатающее устройство. PFA "p-f-a" FIG VAR ( адр1 - адр2) Исходный код адр1 - адрес поля имени слова, в стек заносится адр2 - адрес поля параметров этого слова. PICK "pick" 83REQ 79REQ (n1 --n2) Копирует n1-й код в стеке и заносит его на верх стека. Само число n1 при этом не считается. В Форт-83 верхний элемент стека имеет номер нуль, в то время как в Форт-79 - один. Таким образом, 0 PICK эквивалентно DUP в Форт-83 и 1 PICK в Форт-79. В большинстве версий PICK - очень медленная команда по сравнению с другими операторами стека. PLIST "p-lists" MMS ( nl n2 --) Отображает n2 блоков, начиная с n1. Формат выдачи приспособлен для печати, строки пронумерованы, а блоки группируются по три на странице.
РР "р-р" ТХТ FIG MVP VAR ( n1 n2 ->) При использовании в форме n1 n2 РР
замещает строку n2 блока n1 , следующим за РР.
завершается кодом . PRINT "print" "печатать" MMS ( --) Переадресует вывод на печатающее устройство. PSH "push" TXT MMS ( --) Слово ассемблера MMSFORTH, которое компилирует машинную программу, засылающую содержимое одного машинозависимого двойного регистра в стек с последующей передачей управления внутреннему интерпретатору. Используется для завершения описания code-слов. Выполняет функцию NEXT. См. также PSH2. PSH2 "push-two" ТХТ MMS ( --) Слово ассемблера MMSFORTH, которое компилирует машинную программу, засылающую содержимое двух машинозависимых двойных регистров в стек с последующей передачей управления внутреннему интерпретатору. Используется для завершения описания code-слов. Выполняет функцию NEXT. См. также PSH. PSH3 "push-two" TXT ( --) Слово ассемблера MMSFORTH, которое компилирует машинную программу, засылающую содержимое трех машинозависимых двойных регистров в стек с последующей передачей управления внутреннему интерпретатору. Используется для завершения описания code-слов. Выполняет функцию NEXT. См. также PSH. PTC "p-t-c" MMS ( n1 n2 --) Помещает курсор в строку n1 и столбец n2-экрана дисплея. QUAN "quan" MMS Слово-описатель QUAN-типа - для чисел одинарной длины. При исполнении в форме QUAN
формирует в словаре статью с именем и резервирует место для числа одинарной длины. При использовании самого число, которое оно содержит, заносится в стек. Когда перед ним стоит IS, число из стека заносится в поле параметров . Если перед ним стоит AT, в стек заносится адрес поля параметров . (Обсуждение QUAN смотри в гл. 7). См. также CQUAN; 2QUAN; 4QUAN. QUERY "query" 83CNT 79REQ ( --) Приостанавливает работу программы на время приема символов с клавиатуры и записи их в текстовый входной буфер. Передача завершается, когда внесен код "возврат каретки" или заполнен весь входной буфер (стандарт требует, чтобы буфер вмещал не менее 80 символов).
Форт-83 требует сброса величин >IN и BLK в нуль и приравнивая значения # ТIВ значению SPAN. Для приема текста из буфера может использоваться оператор WORD. См. также >IN; #TIB; BLK; EXPECT; SPAN; WORD. QUIT "quit" 83REQ 79REQ ( --) Вызывает немедленный уход из программы и передачу управления оператору. Очищает стек возвратов и подготавливает систему для интерпретации данных, поступающих с активного устройства (обычно клавиатуры). Стек параметров не очищается (в отличие от ABORT), и никаких сообщений или запросов на экран не выдается (даже "ok"). См. также ABORT. R/ "round-divide" "деление с округлением" ТХТ ( n1 n2 -- n3) Делит n1 на n2 и засылает в стек частное n3, которое округлено в большую сторону, если остаток больше n2/2. R> "r-from" "из-R" 83REQ 79REQ ( -- n) Удаляет n из стека возвратов и помещает его в стек параметров. R> вообще должен использоваться в паре с >R, чтобы исключить изменение указателя стека возвратов. См. также R@; >R. R@ "r-fetch" "занести R" 83REQ 79REQ (-- n) Копирует n из стека возвратов в стек параметров (без изменения стека возвратов). R@ полезно, когда стек возвратов используется для временного хранения чисел внутри описаний типа двоеточие. См. также >R; R>. RBLK "r-block" "r-блок" MMS ( адр n --) Читает блок n и укладывает его в память, начиная с адреса адр. См. также WBLK. RECURSE "recurse" "рекурсия" 83CNT ( --) Синоним MYSELF. REPEAT "repead" "повторить" I, С 83REQ 79REQ ( --) Завершает цикл BEGIN... WHILE... REPEAT. При использовании в форме: : ... BEGIN ... флаг WHILE ... REPEAT ... ; компилирует безусловный переход на BEGIN в описании слова . Когда исполняется, слова между BEGIN и REPEAT повторно повторно до тех пор, пока флаг в стеке остается не равным 0. Если флаг соответствует ложно (0), управление передается слову, следующему после REPEAT. См. также BEGIN; WHILE; UNTIL.
RIGHT$ "right-string" "правая строка" MMS ( адр1 n - адр2) Берет n символов с правого конца счетной строки, лежащей по адресу адр, формирует новую счетную строку в PAD, адрес которого заносит в стек. ROLL "roll" 83REQ 79REQ ( ... n -) Удаляет n-й элемент из стека (не считая n) и заносит его на верх стека, смежные числа смещаются вниз на одну позицию. В Форт-83 верхний элемент имеет номер 0, так что, например, 2 ROLL эквивалентно ROT; его эквивалентом в Форт-79 является 3 ROLL, так как здесь верхняя позиция стека имеет номер 1. ROT "rote" 83REQ 79REQ ( n1 n2 n3 -- n2 n3 n1) Переносит третий сверху элемент на верх стека. RUP/ "round-up-divide" "деление с округлением в большую сторону" ТХТ ( n1 n2 -- n3) Делит n1 на n2 и заносит в стек частное n3, округленное в большую сторону, если остаток не равен 0. Описание в Форт-79: : RUP/ /MOD SWAP 0= 0= + ; Описание в Форт-83: : RUP/ /MOD SWAP 0= 0= NEGATE + ; S-D "single-to-double" "одинарное в двойное" ТХТ ( n -- d) Преобразует число одинарной длины в число двойной длины, сохранив правильный знак. Описание в Форт-79: : S-D DUP 0< NEGATE ; Описание в Форт-83: : S-D DUP 0< ; SAVE-BUFFEBS "save-buffers" "сохранить буферы" 83REQ 79REQ ( --) Копирует содержимое всех блочных буферов, помеченных оператором UPDATE, в соответствующие блоки массовой памяти. Все буферы помечаются так, как если бы они не были модифицированы, но Форт-83 допускает сохранение приписки к определенным буферам, что зависит, конечно, от версии. См. также FLUSH. SCR "s-c-r" 83CNT 79REQ ( - адр) Переменная, содержащая номер блока, которой был только что отображен (и во многих версиях блок, который только что редактировался). SIGN "sign" "знак" 83REQ 79REQ ( --) Добавляет ASCII-код "-" (знак минус) в начало отформатированной числовой выходной строки, если n отрицательно. Использование между не является обязательным (но типичным) в Форт-83.
Форт-79 требует этого в обязательном порядке. См., например, #. SMUDGE "smudge" FIG MVP VAR ( --) Меняет состояние бита (бита-метки) в заголовке только что описанного слова так, чтобы разрешить или нет нахождение этого слова при просмотре словаря. Если слово может быть найдено, SMUDGE пометит его так, что его уже нельзя будет найти. Повторное выполнение SMUDGE сделает его "находимым" снова. В процессе описания слова бит-метка устанавливается так, что слово нельзя найти, исполнение SMUDGE делает его находимым. Это еще один из нескольких методов предотвращения исполнения описаний, содержащих ошибки. SP@ "s-p-felch" "занести SP" 83CNT 79RES MMS VAR ( - адр) Заносит в стек адрес верхнего элемента стека параметров до исполнения слова SP@. SPACE "Space" "пробел" 83REQ 79REQ ( --) Посылает код пробела (ASCII 32) на активное в данный момент выходное устройство. SPACES "spaces" "пробелы" 83REQ 79REQ ( n --) Посылает n кодов пробела на активное в данный момент выходное устройство. Если n равно 0 или отрицательно, ничего не отображается. SPAN "span" 83REQ ( - адр) Переменная, которая содержит число символов, введенных и запомненных при последнем исполнении EXPECT. Подробности в гл. 8. SSIGN "s-sign" TXT ( n d1 -- d2) Помещает знак n в форматированную числовую выходную строку, предназначенную для отображения числа n. Должно использоваться с ... флаг IF ... THEN ... ; или : ... флаг IF ... ELSE ... THEN ... ; отмечает место, откуда продолжится исполнение по отношению к соответствующим IF или ELSE. Когда IF конструкция завершена, исполнение продолжается с оператора, стоящего за THEN. THRU "through" 83CNT 79RES MVP VAR (n1 n2 --) Последовательно загружает блоки от nl до n2. См. также LOADS. TIB "t-i-b" 83REQ MMS VAR ( - адр) Заносит в стек адрес начала входного текстового буфера (иногда называемого "терминальным входным буфером"). Этот буфер используется для приема символов, поступающих с клавиатуры.
TL "t-l" MMS ( n1 n2 -) Отображает строки от n1 до n2 (с номерами строк) блока, номер которых лежит в SCR. Таким образом, команда 0 15 TL отобразит весь блок, номер которого записан SCR. TOKEN "token" "лексема" MMS (n адр1 - адр2) Переносит строку символов (лексему), лежащую по адресу "адр1", и укладывает в виде счетной строки, начиная с адреса, указанного словом HERE, игнорируя предшествующие пробелы и используя символ с ASCII-кодом n в качестве разграничителя. Копия разграничителя записывается после выделенной лексемы, но не учитывается в байт-счетчике строки. адр2 равен адресу символа, следующего за разграничителем в блочном буфере, или 0, если лексема не выделена. Слово TOKEN используется в описании слова WORD в MMSFORTH и полезно для выделения строк из массивов данных. TRAVERSE "traverse" "траверс" FIG MVP VAR ( адр1 n - адр2) Находит адрес противоположного края поля имени слова в словаре при начальном адресе адр1. Если n равен 1, поиск производится в направлении больших адресов, при n, равном 0, - в направлении меньших. Во многих версиях Форта первый и последний байты поля имени откомпилированного слова имеют старший бит, установленный равным 1 (т.е. имеют значение больше 80 шестнадцатеричного ). Это позволяет TRAVERSE найти начало или конец поля имени бесконечной длины, вычислить другие адреса в откомпилированном описании. Используется в NFA и PFA. TUCK "tuck" TXT F83 ( n1 n2 - n2 n1 n2) Копирует верхний элемент n2 и "подсовывает" его под второй сверху элемент n1. Может быть описано как : TUCK SWAP OVER ; но в версии F83 слово TUCK описано на ассемблере с целью ускорения его работы. TYPE "type" "отобразить" 83REQ 79REQ ( адр n --) Отображает строку из n символов, хранящуюся в памяти по адресу адр. Если n равно 0 или меньше 0, ничего не отображается. См. также COUNT. U* "u-times" "U-умножить" 79REQ ( u1 u2 - ud) Умножает ul на u2 и засылает в стек произведение двойной длины ud.
Все коды рассматриваются как числа без знака. Синоним слова UM*, используемого в Форт-83. U. "u-dot" "U-точка" 83REQ 79REQ ( n --) Отображает число без знака и в позиции, отмеченной курсором. После числа вводится пробел. См. также . . U.BIN "u-dol-binary" TXT ( u -- u) Отображает число без знака, лежащее в стеке в двоичном представлении, не изменяя состояния стека или величины BASE. : U.BIN DUP BASE @ 2 BASE ! SWAP U. BASE ! ; U.R "u-dot-r" 83CNT 79RES MMS ( u n -) Отображает величину числа без знака u с учетом величины BASE в поле шириной n позиций. Младшая цифра u помещается в самую правую позицию. В Форт-83, если число символов, необходимое для отображения u, больше n, дается сообщение об ошибке. Ширина поля меньше 1 запрещена. В Форт-79, если n меньше числа символов, число будет отображено, но без предшествующих пробелов. U/MOD "u-div" de-mod" "U-разделить с остатком" 79REQ ( ud1 u1 -- u2 u3) Делит число двойной длины ud1 на u1 и засылает в стек остаток одинарной длины u2 и частное одинарной длины u3. Применено деление с нижней границей. Все величины рассматриваются как числа без знака. Синоним U/MOD в Форт-83. U< "u-less-lhan" "U-меньше" 83REQ 79REQ ( u1 u2 - флаг) Сравнивает u1 и u2 и заносит в стек флаг истинно, если u1 меньше чем u2. UD. "u-d-dot" TXT ( d -) Отображает число двойной длины без знака, лежащее в стеке, в соответствия с величиной BASE. После числа вводится пробел. : UD. TYPE SPACE ; UD.R "u-d-dot-r" TXT ( ud n --) Отображает число двойной длины без знака, лежащее в стеке, в поле шириной n позиций. Причем младшая цифра числа помещается в самую правую позицию. UM* "u-m-times" "U-M-умножить" 83REQ ( u1 u2 -- ud) Перемножает числа u1 и u2 и заносит в стек произведение двойной длины ud. Все величины рассматриваются как числа без знака. Синоним U* в Форт79. UM/MOD "u-m-divide-mod" "UM-разделить с остатком" 83REQ ( ud u1 -- u2 u3) Делит число двойной длины без знака ud на u1 и заносит в стек остаток одинарной длины u2 и частное одинарной длины u3.
Все величины рассматриваются как числа без знака. Синоним U/MOD в Форт-79. UNTIL "until" I, С 83REQ 79REQ (флаг -) Отмечает конец бесконечного цикла. При использовании в форме : ... BEGIN ... флаг UNTIL ... ; компилирует условный переход назад к соответствующему BEGIN. Когда
исполняется, слово UNTIL предполагает наличие в стеке флага и, пока флаг равен 0, передает управление назад к соответствующему слову BEGIN. Если флаг не равен 0, исполнение продолжается со слова после UNTIL. См. также BEGIN; WHILE; REPEAT. UPDATE "update" "поместить" 83REQ 79REQ ( --) Помечает блочный буфер как модифицированный и готовый для записи в массовую память. Блоки в буферах, помеченные таким образом, автоматически переносятся в массовую память, когда буфер требуется повторно. Помеченные блоки могут быть записаны в массовую память с помощью оператора FLUSH или SAVE-BUFFERS. EMPTY-BUFFERS ликвидирует статус пометки и уничтожает приписку всех блочных буферов, отменяя воздействие оператора UPDATE. VAL "val" MMS ( адр - n) Преобразует числовые символы, лежащие в счетной строке по адресу "адр", в число одинарной длины n в соответствии со значением слова BASE. См. также STR$. VARIABLE "variable" "переменная" 83REQ 79REQ ( --) Слово-описатель, которое создает переменную одинарной длины. При использовании в форме VARIABLE
формирует в словаре статью с именем и резервирует место в памяти для числа одинарной длины. Когда исполняется, в стек заносится адрес поля параметров слова , пригодный для использования @ и !. Переменной не обязательно присваивается какое-либо начальное значение. См. также CVARIABLE; 2VARIABLE; 4VARIABLE. VLIST "v-list" "полный список" FIG ( --) Отображает список всех слов в текущем (CURRENT) словаре. В настоящее время для этого слова предпочтительнее имя WORDS. VOCABULARY "vocabulary" "контекстный словарь" 83REQ 79REQ ( --) Слово-описатель, которое создает новый контекстный словарь.
При использовании в форме VOCABULARY
формирует в словаре слово с именем , которое специфицирует новый список описаний слов. В зависимости от версии может быть, а может и не быть словом немедленного исполнения. После исполнения
контекстный словарь станет первым просматриваемым (текущим словарем). может быть сделан контекстным словарем, куда заносятся новые описания (CONTEXT-словарем) с помощью DEFINITIONS что приведет к тому, что новое описание будет включено в маршрут поиска Подробности в гл. 14. WBLK "write-block" MMS ( адр n -) Записывает 1024 байта, начиная с адреса "адр", в блок с номером n. См. также BLK. WHILE "while" I,C 83REQ 79REQ ( флаг -) Решает прервать или продолжить цикл BEGIN... WHILE... REPEAT. При использовании в форме : BEGIN ... флаг WHILE ... REPEAT ... ; компилирует оператор условного перехода в описание . Когда
исполняется, WHILE предполагает наличие в стеке флага, пока флаг не равен 0, слова между WHILE и соответствующим REPEAT выполняются, a REPEAT возвращает управление слову, стоящему после соответствующего BEGIN. Если же флаг равен 0, управление передается слову, следующему за REPEAT. WIDTH "width" "ширина" FIG MVP ( -- адр) Переменная, содержащая число символов, которое будет скомпилировано в поле имени описания, т. е. максимальная длина имени слова. Слово WIDTH можно использовать для ограничения длин имен слов с целью экономии места в словаре. По умолчанию максимально допустимая длина имени равна 31 символу. WORD "word" "слово" 83REQ 79REQ ( n - адр) Генерирует счетную строку, извлекая последовательность символов из выходного потока, не видоизменяя его, до тех пор, пока не встретится разграничитель, ASCII-код которого равен п, или пока не иссякнет входной поток. Не вызывает прерывания исполнения. Разграничители, предшествующие строке, игнорируются. Символы запоминаются в счетной строке, начиная с адреса "адр". (В большинстве версий "адр" равен HERE.) Примеры и обсуждения см.
в гл. 9. WORDS "words" "слова" 83REQ 79REQ ( --) Отображает список всех слов в текущем словаре. Контекстная форма представления варьируется от версии к версии. Синоним VLIST. XOR "х-or" "Исключающее ИЛИ" 83REQ 79REQ (n1 n2 --n3) Выполняет побитовую операцию исключающее ИЛИ для кодов n1 и n2, результат n3 заносится в стек. Другими словами, каждый бит кода n1 сравнивается с соответствующим битом кода n2 и, если один из них (но не оба) равен 1, приравнивается 1 и соответствующий бит n3, в противном случае он обнуляется. Так, если записать в двоичной форме 110 100 XOR в результате будет получено 010. См. также AND; OR. Y/N "y-slash-n" MMS ( -- n) Отображает запрос "(Y/N)?" и ожидает ввода с клавиатуры "Y" или "N". Если одна из букв введена, она будет отображена на экране, а в стек будет занесен флаг (0 для "Y" и I для "N"). Y/N используется для управления исполнением с пульта. [ "left-bracket" "левая скобка" I 83REQ 79REQ ( --) Устанавливает систему в режим исполнения. Текст входного потока при этом исполняется, а не компилируется. При использовании в описаниях типа двоеточие в форме: : ... [ ... ] ... ; позволяет исполнить слова между [ и ] при компиляции слова . См. также ]; STATE. ['] "bracket-tick" I,C 83REQ ( - адр) Засылает в стек и компилирует адрес поля программы слова в описании типа двоеточие. При использовании в форме : ... ['] ... ; компилирует адрес поля программы "адр" слова в качестве литерала в описание слова . Когда исполняется, "адр" засылается в стек и может быть использован, например, оператором EXECUTE, Если не может быть найдено в словаре, дается сообщение об ошибке. Выполняет функцию, эквивалентную слову ' в Форт-83, которое используется только в режиме выполнения. В Форт79 функция ' зависит от режима и выполняется так же как и [']. [COMPILE] "bracket-compile" I,C 83REQ 79REQ ( --) Заставляет компилироваться слово немедленного исполнения.При использовании в форме : ... [COMPILE] ... ; компилирует слово немедленного исполнения , которое обычно исполняется даже в режиме компиляции. В отличие от слова COMPILE, которое имеет совсем иную функцию, само [COMPILE] не компилируется. \ "backslash" I TXT MVP VAR ( --) Вынуждает Форт-интерпретатор игнорировать остальную часть 64-символьной строки. Позволяет использовать оставшуюся часть строки для комментариев. Может использоваться внутри описаний типа двоеточие. Описывается как : \ >IN @ 63 OR 1+ >IN ! ; IMMEDIATE : \ >IN @ 64 / 1+ 64 * >IN ! ; IMMEDIATE См. также (. ] "right-bracket" "правая скобка" 83REQ 79REQ ( --) Устанавливает систему в режим компиляции. Текст входного потока будет после этого компилироваться. Слово ] удобно для компиляции слов без заголовка и для компиляции CFA слов в массив для векторного исполнения. См. также [; STATE.
Б. Терминология
Этот глоссарий включает в себя терминологию Форта, используемую в этой книге, а также общепринятую терминологию из области технологии ЭВМ. Заметьте, что применение и значение определенных терминов в Форте отличаются от общепринятых в вычислительной технике. Глоссарий предназначен для помощи при работе с текстом книги, он не является полным и исчерпывающим.16-bit-number. 16-битовое число. Число (со знаком или без), которое может быть записано в 16 битов памяти. 16-битовое число без знака может представлять числа в диапазоне 0 - 65.535, а со знаком - в интервале -32.768 - 32.767. Для представления чисел со знаком используется дополнение по модулю два. См. также числа со знаком; числа без знака; дополнение по модулю два.
32-bit-number. 32-битовое число. Число (со знаком или без), которое может быть записано в 32 битах памяти. 32-битовое число без знака может представлять число в диапазоне 0 - 4.294.967.295, а со знаком - в интервале -2.147.483.648 - 2.147.483.647. Для представления чисел со знаком используется дополнение до модулю два. См. также числа со знаком; числа без знака; дополнение по модулю два.
Address. Адрес. Позиция в памяти ЭВМ. Адреса в большинстве микроЭВМ нумеруются байт-за-байтом, начиная с 0 до предельного адреса, который задается разрядностью адресной шины. Восьмибитовые микропроцессоры, такие как Z-80 и 8080, имеют 16 адресных шин и предельный адрес 65.535 (2^16). Шестнадцатибитовые микропроцессоры, такие как 8086, 8088 и 68000 часто имеют 20, 24 или даже 32 адресные шины и могут использовать очень большие памяти. Выражение "Z-80 может адресоваться к 65535 байтам памяти" означает, что максимальное значение адреса равно 65534.
Address interpreter. Интерпретатор адресов. Машинная программа Форта, которая осуществляет исполнение программы путем пошагового перехода по списку адресов других машинных программ, включающих в себя откомпилированные Форт-программы. Называется также внутренним интерпретатором, в противоположность внешнему интерпретатору.
См. также поле параметров.
Address space. Адресное пространство. Область памяти ЭВМ. к которой ЭВМ может адресоваться непосредственно. В большинстве микропроцессоров адресное пространство ограничено основной (полупроводниковой) памятью, но в ЭВМ с виртуальной памятью адресное пространство может распространятся на массовую память.
Algebraic notation. Алгебраическая нотация. Обычная нотация в алгебре. Система отображения математических соотношений, использующая произвольные символы в качестве операндов, инфиксную нотацию для операторов и скобки для задания очередности операций. В более широком толковании - любое выражение, использующее те же правила формирования. Противостоит постфиксной и префиксной нотации.
Algorithm. Алгоритм. Набор правил, которые определяют, как достичь желаемого результата. Все программы ЭВМ явно или неявно написаны так, чтобы реализовать один или несколько алгоритмов.
Arguament(s) Аргумент(ы). Величина (или величины), необходимая программе для выполнение ее функций. В форте аргументы помещаются в стек параметров для последующего использования их Форт-словами. Если слово CUBE в форте описано так, чтобы вычислять куб числа, то число 5 в примере 5 CUBE является аргументом. См. также операнд.
Arithmetic coprocessor. Арифметический сопроцессор. Специальная микросхема, используемая совместно с центральным процессорным устройством для ускоренного вычисления арифметических и трансцендентных, функций. Арифметический сопроцессор 8087 спроектирован для совместной работы с процессорами 8086 и 8088.
Arithmetic overflow. Арифметическое переполнение. Ошибка, которая происходит, когда арифметическая операция выдает результат, не соответствующий разрядности памяти. Например, если два числа одинарной длины перемножены с помощью * и получен результат более 65535 (2^16-1), этот результат ошибочен, так как * игнорирует часть произведения, не укладывающуюся в 16 битов. В случае работы с операторами смешанного типа для результата будет выделено 32 разряда.
См. также числа одинарной длины; числа двойной длины.
Array. Массив. Совокупность чисел, строк или другой информации, где каждый информационный объект называется элементом, а элементам поставлены в соответствие числа или индексы. Вектор представляет собой одномерный или линейный массив с одним индексом, матрица - двумерный массив, уложенный в ряды и столбцы с двумя индексами (хотя матрица с одним рядом идентична вектору); массивы большей размерности также возможны.
ASCII. Обозначение для Американского стандартного кода для обмена информацией. ASCII первоначально был разработан для стандартизации обмена между телетайпными терминалами. ASCII-код определяет набор управляющих символов (со значениями 0 - 31 и 127), чисел, букв и других символов (со значениями 32 - 126). (См. приложение Д, где приведена таблица ASCII-символов.) Заметим, что ASCII определяет только набор символов для младших 7 битов байта, так что коды между 128 и 255 8- битового байта не определены в ASCII и их использование на разных ЭВМ сильно варьируется.
Assembler language. Ассемблер. Язык программирования низкого уровня, использующий мнемонику, которая может быть непосредственно транслирована в исполняемую машинную программу. Форт-ассемблер состоит из слов словаря ASSEMBLER, которые описывают мнемонику и компилируют машинные программы. См. также набор инструкций, машинные инструкции.
Base (number). Основание (числа). См. основание системы счисления. Baud rate. Скорость передачи (в бодах). Телекоммуникационный термин, часто используется неверно в значении "бит в секунду". Скорость передачи в бодах в действительности равна числу раз в секунду, когда телекоммуникационный канал меняет свое состояние, что в зависимости от схемы кодирования может давать число битов в секунду значительно больше, чем скорость передачи в бодах.
Baudot code. Код Бодо. Старый телекоммуникационный код, названный в честь Эмиля Бодо. В этом коде представления символов на телетайпе используется только 5 битов в верхнем и нижнем регистрах (поэтому возможно представление более чем 32 символов).
Код Бодо используется также для некоторых других целей, таких как передача данных о погоде и для связи между глухими.
Binary notation. Двоичное представление. Система представления чисел, использующая основание 2. Существует только две двоичные цифры, 0 и 1. Внутреннее представление чисел в ЭВМ только двоичное, так как бистабильные электронные схемы хорошо приспособлены для представления двоичных цифр. Вход и выход системы Форта может быть сделан двоичным с помощью 2 BASE ! См. также бит.
Bit. Бит. Самая малая единица информации, имеющая только два состояния 0 и 1. Бит - это сокращение термина "binary digit" (двоичная цифра) и т.о. является цифрой двоичного представления. См. также байт.
Bit mask. Бит-маска. Число, используемое для извлечения или изменения битов другого числа. Бит-маски широко используются для манипулирования битами в памяти, а также используются с булевыми операторами, позволяя в одном байте хранить восемь отдельных флагов. Называется также просто маской.
Block. Блок. В Форте - часть массовой памяти, которая содержит 1024 (1К) байтов. Блоки нумеруются последовательно и загружаются в память словом BLOCK. Для переноса блока из памяти на диск используется UPDATE и FLUSH (или SAVE-BUFFERS). Называется также экраном, в особенности если содержит только алфавитно-цифровую информацию. См. также блочный буфер; кэш диска; виртуальная память.
Block buffer. Блочный буфер. Область в основной памяти для хранения блоков форта при обмене с диском. Блок заносится в блочный буфер с помощью слова BLOCK, которое оставляет в стеке адрес первого байта блочного буфера. Активный s данный момент блочный буфер помечается для последующей переписи на диск с помощью слова UPDATE. Перенос осуществляется при необходимости перезаписи в блочный буфер или при использовании слов FLUSH или SAVE-BUFFERS. См. также блок; кэш диска; виртуальная память.
Boolean flag.Булев флаг. Число, используемое для описания логического состояния. Истинно в Форт-79 равно-1, в то время как в форт-83 - -1 (FFFF HEX или все биты равны 1).
В обоих стандартах 0 соответствует ложно. Условные операторы Форта распознают любое ненулевое число в стеке как булев флаг истинно. Часто называется просто флагом. Назван в честь британского математика Джоржа Буля.
Buffer. Буфер. Область памяти, зарезервированная для временного хранения информации. В Форте блочные буферы используются для пересылки 1024 байтов с диска и на диск, а входной буфер воспринимает данные, поступающие с клавиатуры. Byte. Байт. Группа из 8 битов, которая чаще всего рассматривается 'как элемент памяти. Байт может принимать значение О - 255 (в десятичном представлении) или 0 - FF в шестнадцатеричном. См. также бит; килобайт; мегабайт.
Case-construct. CASE- конструкция. Структура программы, которая позволяет при исполнении осуществлять переход на одну из нескольких программ в зависимости от того, какая совокупность условий удовлетворена. CASE- конструкция может быть реализована через систему встроенных структур IF...ELSE...THEN, но часто используются специальные Форт-слова, такие как ACASE и NCASE в MMSFORTH. См. также векторное исполнение.
Cell. Ячейка. В Форте размер ячейки равен 16 битам. Ячейка в форте предпочтительнее, чем более распространенный термин "слово" (что означает для большинства микро ЭВМ то же самое), чтобы избежать путаницы с форт-термином "слово". Ячейка может содержать ровно одно число одинарной длины. См. также длина слова.
Central processing unit. Центральный процессор. Часть ЭВМ, которая интерпретирует и исполняет машинные инструкции. В микро ЭВМ - это одна микросхема, называемая микропроцессором. Часто используется сокращение ЦП, иногда называется просто процессор. CFA. Обозначение для адреса поля программы.
Code-field. Поле программы. Часть откомпилированного Форт-описания, содержимое которого указывает на машинную программу, которая исполняется, когда исполняется описанное слово. См. также адрес поля программы.
Code-field-address. Адрес поля программы. Адрес поля программы откомпилированного Форт-описания.
Поле параметров типа двоеточие содержит список адресов полей программы слов, содержащихся в описании, но не адрес, содержащийся в поле программы. Обычно обозначается сокращением CFA. См. также поле программы.
Colon-definition. Описание-двоеточие. Описание слова Форта, созданное оператором :. Часто используется синоним слово-двоеточие.
Colon-word. Слово-двоеточие. Слово Форта, описанное словом описателем :. Слова-двоеточие, кстати, являются наиболее часто встречающимися словами Форта. Часто используется синоним описание двоеточие.
Comparison operator. Оператор сравнения. Оператор, который определяет соотношение между двумя числами и выдает булев флаг, характеризующий это соотношение. Например, оператор сравнения может определить, является ли величина меньше, больше или равна 0, меньше, больше или равна другой величине. Операторы сравнения могут комбинироваться, чтобы сформировать такие проверки, как меньше или равно, больше или равно и т. д. Примерами могут служить слова Форта , = и 0=.
Compilation. Компиляция. Процесс трансляции исходного текста программы в форму, пригодную для исполнения на ЭВМ. В большинстве языков (таких как Фортран) компиляция состоит в формировании машинной программы из исходного текста высокого уровня. Описания-двоеточия в Форте при компиляции образуют список адресов полей программы, которые указывают интерпретатору исполнительные адреса. Мнемоника Форт-ассемблера компилируется в машинные инструкции, которые ЭВМ может исполнить немедленно.
Compile mode. Режим компиляции. Режим, при котором внешний интерпретатор Форта компилирует слова в Форт-описания, а не исполняет их. Это происходит, когда переменная STATE не равна 0. Если в режиме компиляции встретится слово немедленного исполнения, оно выполняется. Противостоит режиму исполнения.
Compiled code. Откомпилированная программа. Результат компиляции. Ассемблер компилирует выполнимую машинную программу. Форт-компилятор формирует список адресов, который может исполнить интерпретатор адресов.
См. также режим компиляции; поле параметров.
Concatenate. Присоединить. Сформировать "стык-в-стык" цепочку из строк, списков величин или Других наборов информации;
Conditional operator. Условный оператор. Элемент программы, который вызывает передачу управления (ветвление) по указанному адресу или выполнение заданной функции, если выполнено определенное условие. Примерами из Форта могут служить IF, WHILE, UNTIL и ABORT", каждый из которых предполагает наличие в стеке флага, который- и определяет, что делать. См. также булев флаг.
Control character. Управляющий символ. ASCII-символ, который выполняет управление терминалом. ASCII-символы имеют коды от 1 до 31 и 127. Например, слово Форта CR выдает символ "возврат каретки" иди 13. См. также ASCII; управляющая клавиша. См. приложение Г.
Control key. Клавиша CTRL. Клавиша, расположенная на терминале ЭВМ и большинства микрокомпьютеров, которая при , совместном нажатии с другой клавишей формирует ASCH-CTRL- символ. Например, CTRL H выдаст ASCII-код 8 (возвращение курсора на одну позицию). См. приложение Г.
Controlled reference word. Слово из контролируемого списка. В стандарте Форт-83 слова, которые, хотя и не входят в обязательный список, если присутствуют в стандартной системе, должны быть описаны в соответствии со спецификациями стандарта. Противоположно слову из расширяющего набора слов; слову из обязательного списка и словам из "неконтролируемого" списка.
Count (siring). Счетчик (строки). Байт, размещенный непосредственно перед строкой алфавитно-цифровых символов и со держащий число символов в строке (0-255). См. также счетная строка.
Counted string. Счетная строка. Формат, обычно используемый в Форте для записи строк символов. Счетная строка состоит из байта-счетчика (однобайтовое число), за которым непосредственно следует строка символов. Байт-счетчик определяет число символов в теле строки. См. также счетчик (строки).
CPU. ЦПБ. Сокращение, означающее "центральный процессорный блок."
Crosscompiler. Кросскомпилятор. Программа, которая позволяет откомпилировать программу на одной ЭВМ ,так, что она сможет работать на другой. В Форте разновидностью кросскомпилятора является метакомпилятор или целевой компилятор, который позволяет преобразовывать всю систему Форта так, что она сможет работать на другой ЭВМ, часто со специальными функциями.
Data stack. Стек данных. Синоним стека параметров.
Decimal notation. Десятичная система представления. Представление чисел в привычной десятичной системе счисления, которая использует цифры 0 - 9. В Форте десятичный ввод и вывод задаются словом DECIMAL.
Decompiler. Декомпилятор. Программа Форта, которая "декомпилирует" откомпилированные описания-двоеточие так, что они приобретают вид, напоминающий исходный текст. Декомпилятор показывает, как были описаны откомпилированные слова.
Deferred compilation. Отложенная компиляция. Термин Форта, описывающий действие COMPILE и других родственных слов. Когда внутри слова-двоеточие исполняется оператор COMPILE, он компилирует CFA слова, следующего за ним, в описание слова-двоеточие таким образом, что, когда слово-двоеточие исполняется, это CFA будет скомпилировано в словарь. Таким образом компиляция слова, следующего за COMPILE, откладывается до исполнения COMPILE. Например, LITERAL содержит COMPILE LIT, причем это слово немедленного исполнения, так что. когда LITERAL встречается в описании,.оно компилирует LIT и число из стека в словарь.
Defining word. Слово-описатель. Слово Форта, которое служит для описания новых (производных) слов. Любое слово-двоеточие, которое описано с помощью CREATE, является словом-описателем. Общеизвестными примерами могут служить CONSTANT и VARIABLE. Новые слова-описатели могут быть созданы с помощью конструкции CREATE...DOES>.
Delimiter. Разделитель. Любой код, обычно ASCII-символ, который используется для выделения элементов данных. Например, слово." использует в качестве разделителя " (двойные кавычки), а слово ( использует >
(закрывающую скобку), форт распознает пробел (ASCII 32) в качестве разделителя между форт-словами и числами во входном потоке. Для отделения последовательностей символов различной длины в файле могут использоваться один или несколько разделителей. См. также разбор.
Dictionary. Словарь. В Форте - это связанный список откомпилированных описаний, которые могут быть исполнены интерпретатором адресов Форта. Читаемый ЭВМ набор слов Форта, которые могут быть исполнены или откомпилированы. Форт помечает начало словаря и адрес его "вершины", последний заносится в стек с помощью HERE.
Dictionary pointer. Указатель словаря. Переменная (иногда называемая DP), значение которой указывает на первый свободный байт выше словаря. Значение указателя словаря засылается в стек оператором HERE.
Disassembler. Дисассемблер. Служебная программа, которая отображает машинную программу в читаемой форме мнемоники ассемблера. Дисассемблер рассматривает группу из одного или нескольких байтов и отображает мнемонику ассемблера и аргументы, которые были бы необходимы для их генерации.
Disk cache. Кэш диска. Метод управления дисковой памятью, в котором часто используемая информация находится в памяти, а не считывается с диска каждый раз, когда необходима. Блочный буфер Форта является разновидностью кэш-буфера. См. также виртуальная память; буфер; блок.
Disk operating system. Дисковая операционная система. Набор программ или служебных средств для обеспечения взаимодействия между оператором, ЭВМ и другими элементами, такими как принтер, интерфейс RS-232 и дисковая память. Дисковая операционная система контролирует также многие функции ЭВМ. СР/М и MS-DOS являются типичными примерами ДОС для микроЭВМ. Автономная версия Форта несет в себе элементы дисковой операционной системы.
Do-loop. do-loop. Программная конструкция, которая заставляет определенную часть программы выполняться заданное число раз. do-loop в Форте реализуется с помощью DO-LOOP или DO...+LOOP. См. также цикл; бесконечный цикл.
DOS. ДОС. Сокращение для "дисковая операционная система".
Double-length-number. Число двойной длины. Целое 32- разрядное число. Числа двойной длины без знака лежат в диапазоне 0 - 4.294.967.295, а числа двойной длины со знаком - от 2.147.483.648 до 2.147.483.647. Называются также двойными числами и числами двойной точности (хотя последнее нетипично, это должно относиться только к числам с плавающей запятой). Противоположно числам одинарной длины.
Double-number. Двойное число. Синоним числа двойной длины, что предпочтительнее.
Double-precision number. Число двойной точности. Синоним числа двойной длины, последний термин предпочтительнее. Более правильно - число с плавающей запятой с двойным числом значащих цифр.
Editor. Редактор. Программа для ввода и модификации данных, обычно исходного текста программы. Форт-редактор Используется для модификации содержимого блоков Форта.
Element (array). Элемент (массива). Часть массива, обычно число или строка, доступ к которой возможен с помощью индекса массива. См. также индекс (массива).
Escape key. Клавиша ESC. Клавиша, присутствующая на многих терминалах для инициации ESC-последовательностей. Она формирует ASCII код 27.
Escape-seguencc. ESC-последовательность, Последовательность символов, начинающая с символа ESC (ASCII 27). ESC-последовательность используется для управления внешними устройствами, такими как терминал или принтер. См. также клавиша ESC.
Execute mode. Режим исполнения. Режим работы в Форте, в котором слова, встретившиеся во входном потоке, немедленно исполняются и в котором числа кладутся в стек, а не компилируются, форт устанавливается в режим исполнения, когда значение переменной STATE равно 0. Противоположен режиму компиляции. Execution. Исполнение. Действие, при котором ЭВМ выполняет операции, заданные программой (или Форт-словом). Исполнение в Форте происходит, когда значение переменной STATE равно 0, и осуществляется интерпретатором адресов. См. также режим исполнения; режим компиляции.
Exponential notation. Показательное представление. Способ представления чисел с плавающей запятой в виде мантиссы и показателя степени. Так, число с плавающей запятой 1024. может быть представлено как 1,024*10^3 или, как обычно, на выходе ЭВМ 1.024Е3, 1.024 - мантисса, а 3 -- показатель. Показатель равен показателю степени, в которую нужно возвести 10, после чего умножить результат на мантиссу, чтобы получить правильную величину числа. Научная нотация является формой показательного представления.
Extension word set. Расширяющий набор слов. Набор слое, которые могут быть добавлены к системе для расширения ее возможностей. Как в стандарте Форт-79, так и в Форт-83 эти слова не являются обязательными, но если они вводятся в систему, они должны соответствовать стандартному описанию. Стандартные расширяющие наборы включают в себя библиотеку для работы с числами двойной длины, ассемблер и системные расширения. Нестандартные наборы часто содержат графику, библиотеку для работы с числами с плавающей запятой, декомпилятор, слова для работы со строками и т. д.
Extensible language. Расширяемый язык. Любой язык, такой как Форт. который может расширить свои возможности с помощью своих собственных средств, в противоположность таким языкам, как Фортран, Паскаль или Бейсик, которые используются для написания программ, не имеющих ничего общего с самим языком.
Factoring. Факторизация. В Форте процесс разбиения задачи на более мелкие задачи, которые могут быть эффективно запрограммированы на Форте. Длинные описания слов могут (и обычно должны) быть для ясности разложены не несколько более коротких описаний. Этот процесс аналогичен факторизации сложных уравнений в набор более простых.
False (flag). Ложно (флаг). Булев флаг, который устанавливается, когда при проверке выясняется, что некоторое условие не выполняется. Противоположно флагу истинно. См. также условный оператор.
Field (file). Поле (в файле). Заданная область памяти в пределах записи а файле, обычно содержащая один элемент информации.
В адресном файле, например, каждая запись может содержать поля для имени, адреса, улицы, города, страны и почтовый индекс.
File. Файл. Упорядоченная совокупность данных в массовой памяти. Файл обычно состоит из одной или более записей, каждая из которых содержит одно или более полей.
Flag. Флаг- число, часто один бит, которое индицирует состояние определенного условия. Если флаг имеет только два возможных состояния, называемых истинно и ложно, то это булев флаг.
Flag register. Регистр флагов. Регистр ЦПБ, разряды которого индицируют условия, соответствующие результату выполнения самой последней инструкции, например произошло или нет арифметическое переполнение. Разряды этого регистра анализируются при выполнении условных операций машинной программы.
Floating-point notation. Представление чисел с плавающей запятой. Способ показа того, что число имеет целую и дробную , части: целую часть - слева от десятичной запятой и дробную часть - справа. Так, 12,34 равно 12 + 34/100. Положение десятичной запятой корректируется автоматически (плавает) в зависимости от результата арифметической операции. См. также показательное представление.
Flowchart. Блок-схема. Диаграмма, которая графически демонстрирует процесс выполнения программы. Блок-схема является часто хорошим способом прояснить структуру программы.
FORTH-standart. Стандарт Форта. Минимально необходимый набор слов Форта и их специфированных функций, а также описанных функций слов, которые не являются обязательными. Цель стандарта - позволить стандартным Форт-программам работать на самых разных ЭВМ. Двумя главными стандартами Форта являются Форт-79 и Форт-83, были предложены и другие более ранние стандарты, но они не получили широкого распространения. См. также слово из контролируемого списка: слово из расширяющего набора; слово из обязательного списка; слово из неконтролируемого списка.
Hash code. Хэш-код. См. хэширование.
Hashing. Хэширование. Метод, при котором входной код используется для того, чтобы определить, где этот код должен быть записан в память или файл.
Определяется хэш-код, который используется для вычисления адреса в памяти. Тот же самый хэш-код (и, следовательно, тот же адрес) генерируется при поиске кода. ускоряя таким образом доступ к информации. Хэширование - это метод кодирования строки или числа, обычно для того, чтобы сэкономить память. В Форте хэширование используется лишь иногда, чтобы приписать слова к различным маршрутам поиска, таким образом ускоряя поиск в словаре и компиляцию.
HEX. Жаргонное слово, обозначающее шестнадцатеричную систему представления.
Hexadecimal notation. Шестнадцатеричное представление. Представление чисел при основании системы счисления 16. Шестнадцатеричные цифры лежат в диапазоне 0 - 9 и для представления десятичных чисел от 10 до 15 - от A до F. 31 в десятичном представлении равно 1F в шестнадцатеричном. Противостоит двоичному, восьмеричному и десятичному представлению.
High-level language. Язык высокого уровня. Языки программирования (такой как Бейсик, Кобол, Фортран и, конечно. Форт), которые используют слова и (в какой-то степени) синтаксис человеческого языка. Все языки высокого уровня должны идти на некоторый компромисс между использованием памяти и быстродействием с целью облегчения программирования для людей. Форт требует меньшего компромисса, чем большинство других языков. Противостоит языкам низкого уровня.
Immediate word. Слово немедленного исполнения. Слово Форта, которое будет исполнено даже в режиме компиляции, на пример, во время компиляции описания. Слова немедленного исполнения помечаются с помощью установки специального бита в заголовке. Примерами таких слов могут служить (, IF, BEGIN,.(, и LITERAL. См. также отложенная компиляция; лидирующий бит.
Incremental compiler. Инкрементный компилятор. Тип компилятора, который интерпретирует исходный текст элемент-за-элементом и последовательно формирует исполняемую машинную программу. Компиляция в Форте производится инкрементно.
Indefinite loop. Бесконечный цикл. Программная структура, которая вызывает повторное исполнение выделенной части программы бесконечное число раз, пока не будет выполнено определенное условие.
ВEGIN...WHILE...REPEAT и BEGIN- UNTIL являются конструкциями, организующими бесконечные циклы в Форте. См. также цикл. Противостоит do-loop.
Index (array). Индекс (массива). Одно или более чисел, которое определяет номер элемента массива. См. также вектор; матрица.
Index (loop). Индекс (цикла). В структуре do-loop счетчик числа циклов. В DO...LOOP в Форте значение индекса заносится в стек параметров оператором I.
Index line. Индексная строка. Верхняя строка блоков Форта, согласно договоренности используется для комментариев содержимого блока, таких как дата, автор, список описанных слов и т. д.
Infix notation. Инфиксная нотация. Представление математических функций, при котором операторы помещаются между операндами. Алгебраическая нотация является формой инфиксной нотации. Инфиксная нотация требует использования скобок для определения порядка операций. Противостоит префиксной и постфиксной нотации.
Inner interpreter. Внутренний интерпретатор. Короткая машинная программа, которая осуществляет выполнение скомпилированного списка адресов-заданий из описаний словаря Форт. Синоним интерпретатора адресов. См. также поле программы; поле параметров.
Input stream. Входной поток. Термин Форта для исходного текста программы или данных, которые интерпретируются в данный момент внешним интерпретатором Форта. Входной поток поступает с клавиатуры, когда значение переменной BLK равно 0, в противном случае - из блока, номер которого лежит в BLK. Форт-определение входного потока отличается от общепризнанного в вычислительной технике, где входной поток является синонимом потока заданий, списка задач, ожидающих исполнения, обычно на главном процессоре.
Instruction set. Набор инструкций. Полный набор команд машинного языка (двоичных чисел s памяти), которые может исполнить центральный процессор. Полный ассемблер включает мнемонику полного набора инструкций процессора.
Interpretation. Интерпретация. В Форте существует два различных значения, которые следует понимать из контекста.
Видном случае интерпретация - это процесс, при котором исходный текст программы или данных воспринимается из входного потока внешним или текстовым интерпретатором и обрабатывается. Если система Форт находится в режиме исполнения, слова немедленно исполняются, а числа заносятся в стек. В режиме компиляции входные данные используются для описания новых слов. Под интерпретацией понимается также декодирование и исполнение слов, описанных в словаре Форта, что производится внутренними или адресным интерпретатором. Эти значения отличаются от общепринятых в вычислительной технике, где под "интерпретацией" подразумевается работа интерпретатора, при которой в случае языков высокого уровня исходный текст исполняется элемент за элементом через машинные программы без формирования полного объектного образа программы (Бейсик использует обычно интерпретатор). Поскольку Форт способен как немедленно исполнять слова при их вводе, так и компилировать новые слова и программы, он сочетает в себе преимущества интерпретатора и компилятора, обеспечивая немедленную реакцию первого и быстродействие последнего. См. также цепной интерпретивный язык.
Interpreter. Интерпретатор. В вычислительной технике применения языка высокого уровня, в котором исходный текст преобразуется в машинную программу и исполняется элемент за элементом без построения полного объектного образа программы (Бейсик использует обычно интерпретатор). В Форте смысл сходен, но отличен как в случае внешнего (текстового), так и внутреннего (адресного) интерпретатора. Первый воспринимает и исполняет или компилирует текст, в то время как последний декодирует и исполняет описание в Форт-словаре. Форт работает, вообще говоря, как интерпретатор, только когда входная программа исполняется внешним интерпретатором. См. также интерпретация.
Irrational number. Иррациональное число. Число, такое как "Пи" или е, которое не может быть выражено через какое-либо отношение двух целых чисел. См. также рациональное приближение.
Kernel. Ядро. Набор форт-примитивов (последовательности машинных программ) в "нижней" части словаря, который является базисом всех последующих Форт-описаний.
Kilobyte. Килобайт. 1024 (2^10) 8-битовых байтов информации, обычно я памяти или на диске. См. также байт; мегабайт.
Last-in-first-out stack. Стековая структура LIFO. Любой стек, где число, введенное последним, окажется выведено первым, в Форте как стек параметров так и стек возвратов относятся к этому типу. Least significant bit. Младший бит. Бит любого числа, который является младшим при двоичном представлении. В двоичном числе 101 самая правая единица является младшим битом.
Least significant byte. Младший байт. Байт любого числа, который является младшим при шестнадцатеричном представлении. В шестнадцатеричном числе 12AF AF-младший байт.
Least significant digit. Младшая значащая цифра. Цифра в любом числе, которая отображает наименее значимую величину. Самая правая цифра. В десятичном представлении 5 в 1295 представляет собой младшую значащую цифру.
Least significant number. Младшее число. В Форт-числе двойной длины - это число одинарной длины, которое представляет младшую его часть. В шестнадцатеричном представлении F25F в числе 129CF25F является младшим.
LFA. Обозначение для "адреса поля связи".
Linear array. Линейный массив. Структура данных, которые могут быть представлены в виде линейки элементов массива. Массив с элементами, пронумерованными последовательно от N1 до Nn с помощью одного индекса. Отдельная строка или колонка матрицы является линейным массивом. Вектор. Противоположность матрицы.
Link-field. Поле связи. Часть откомпилированного Форт-описания, которая содержит адрес или связь, определяющие следующее слово, которое будет просмотрено при поиске в словаре.
Link-field address. Адрес поля связи. Адрес поля связи в откомпилированном Форт-описании. Содержимое этого адреса указывает на слово, которое при поиске в словаре будет просмотрено следующим. Но не адрес в поле связи.
Literal. Литерал. В Форте это число, которое скомпилировано в описание-двоеточие (либо в результате распознания числа во входном потоке, либо словом LITERAL) и которое при исполнении слова, куда оно было скомпилировано, заносится в стек. В некоторых версиях разрешено использовать в качестве литералов байты и числа двойной длины. В вычислительной технике это слово имеет другое значение и относится к прямому числовому представлению величины числа в противоположность символьному представлению. В строке Бейсик A=5+6,5 и 6 - литералы.
Load block. Загрузочный блок. В Форте блок, используемый для загрузки других блоков. Загрузочный блок является удобным средством для контроля за порядком загрузки последовательности блоков, при загрузке, например, различных дополнений и частей программы.
Loop. Цикл. Программная структура, которая вызывает повторное исполнение определенной части программы. Число повторений цикла может быть задано (конечные циклы или do-loop) или не определено (бесконечный цикл).
Low-level language. Язык низкого уровня. Язык программирования, который транслируется непосредственно или почти напрямую на машинный язык. Ассемблер является наиболее распространенным языком низкого уровня, мнемоника которого непосредственно преобразуется в машинные инструкции. Форт, позволяя описание слов с использованием ассемблера, комбинирует преимущества языков высокого и низкого уровней.
LSB - в контексте младший бит или байт какого-либо числа.
Machine code. Машинная программа. Набор двоичных кодов, которые будучи загруженными резидентно в память могут быть непосредственно исполнены центральным процессором. Все языки в конечном итоге транслируют инструкции высокого уровня в машинную программу.
Machine instruction. Машинная инструкция. Двоичное число в машинной программе, которое предписывает центральному процессору выполнить специфическую функцию. См. также набор инструкций; машинная программа; машинный язык; ассемблер; мнемоника.
Machine language. Машинный язык.
Язык, состоящий из машинных программ, которые непосредственно исполнимы центральным процессором. Обычно представляются в виде байтов в шестнадцатеричном счислении. Метод программирования - путем загрузки набора двоичных кодов, которые могут быть исполнены ЭВМ. Противостоит языку высокого уровня; языкам низкого уровня; ассемблеру.
Mask (bit). Маска. См, также бит-маска.
Mass storage. Массовая память. Устройство долговременной памяти данных вне основной (полупроводниковой) памяти. Типичными устройствами массовой памяти являются гибкий и жесткий магнитные диски, магнитофон и память на магнитных доменах.
Matrix. Матрица. Двумерный массив, который может быть представлен о виде таблицы данных, изображенной на плоскости. Элементы образуют строки и столбцы и адресуются с помощью двух индексов. Вектор или линейный массив могут рассматриваться как матрица (только с одной строкой или столбцом). Квадратная матрица имеет равное число строк и столбцов. Megabyte. Мегабайт. 1.048.576 (2^20) 8-битовых байтов в памяти или на диске. См. также килобайт.
Memory. Память. Часть ЭВМ, в которой запоминаются данные и программа. Внутренняя память адресуема центральным процессором непосредственно. Существуют постоянные запоминающие устройства (ПЗУ), содержимое которых центральный процессор может прочесть, но не может изменить, имеются также запоминающие устройства с произвольной выборкой (ЗУПВ), для которых осуществимо как чтение, так и запись. Внешняя память содержит информацию, которая должна быть считана в основную память, прежде чем будет использована процессором. Внешняя память - это массовая память, включающая в себя такие устройства, как магнитофоны или диски. См. также адресное пространство; массовая память; виртуальная память.
Memory map. Карта памяти. Визуальное представление того, где размещаются различные части системы и как используется память для тех или иных функций ЭВМ. Карта памяти полезна при описании функций программы, языка или ЭВМ.
Metacompiler. Метакомпилятор.
Разновидность кросскомпилятора, которая используется для написания других компиляторов. В Форте метакомпилятор - это Форт-программа, которая может компилировать полную форт-систему и запускать ее на той же или другой ЭВМ. Называется также целевым компилятором.
Microprocessor. Микропроцессор. Центральный процессор в виде одной микросхемы. Центральный процессор микроЭВМ.
Mixed-mode arithmetic. "Смешанная" арифметика. Арифметические операции, которые используют операнды как двойной, так и одинарной длины или выдают результат другой длины, чем операнды. Операторы смешанного типа используются для того, чтобы избежать арифметического переполнения.
Mixed-mode operator. Оператор смешанного типа. Арифметический оператор, который работает со "смешанной" арифметикой.
Mnemonic. Мнемоника. Техника помощи человеческой памяти, более конкретно-читаемое сокращение, состоящее из первых букв или частей последовательности слов. В терминологии вычислительной техники - последовательность букв, которая образует инструкцию, используемую ассемблером для компиляции одной машинной команды. Мнемоники в форт-ассемблере - Это слова, которые компилируют машинную программу.
Modulus. Модуль. Остаток целочисленного деления.
Most significant bit. Старший бит. Бит любого числа, который при двоичном представлении обозначает самую старшую цифру. В двоичном числе 101 самая левая единица является старшим битом.
Most significant byte. Старший байт. Байт любого числа, который при шестнадцатеричном представлении является старшим. В шестнадцатеричном числе 12AF 12 является старшим байтом.
Most significant digit. Старшая цифра. Цифра любого числа, которая отображает наиболее значимую величину. В десятичном представлении 1 в 1295 является старшей цифрой.
Most significant number. Старшее число. В числе двойной длины число одинарной длины, в котором записана старшая часть двойного числа. В шестнадцатеричном числе A34B9C5D А34В является старшим числом.
Name field. Поле имени.
Часть откомпилированного Форт-описания в словаре, которая содержит имя слова и другую информацию. См. также адрес поля имени.
Name-field address. Адрес поля имени. Адрес первого байта поля имени откомпилированного Форт-описания. Адрес поля имени - это не адрес, хранящийся в поле имени.
NFA - сокращение для name-field address (адрес поля имени).
Nibble. Полубайт 4 бита. Number. Число, в Форте - 16-битовое число, т.е. целое число одинарной длины или в зависимости от контекста любое числовое значение.
Number base. Основание числа. Основание системы счисления. Число символов, первый из которых 0, используемое для представления цифры в числе. В шестнадцатеричных числах (числах с основанием 16) диапазон символов простирается от О до 9 и от А до F, в то время как в восьмеричной (основание 8) - от 0 до 7. В Форте основание системы счисления при вводе и выводе может изменяться от 2 до 70 путем изменения содержимого переменной BASE.
Object code. Объектный код. Исполняемая программа, обычно синоним машинной программы, полученной в результате трансляции исходного текста посредством компилятора или ассемблера. Объектный код, полученный в результате компиляции Форт-ассемблером, является исполняемой машинной программой. Другие компилированные Форт-программы представляют собой список адресов и(или) чисел, исполняемых только интерпретатором адресов Форта, и. хотя это не объектный код в строгом смысле этого слова, его иногда называют объектным кодом Форта.
Octal notation. Восьмеричное представление- Представление чисел при основании системы счисления 8. Восьмеричные цифры лежат в диапазоне 0 - 7. Каждая восьмеричная цифра представляется тремя битами. Восьмеричное представление бывает полезным для понимания структуры машинных инструкций процессора.
One-dimeniional array. Одномерный массив. Синоним вектора или линейного массива.
Ones complement. Дополнение по модулю один. Дополнение числа по модулю один получается путем инверсии всех битов числа, т.е. все биты равные 1 1 сбрасываются в 0, а все нулевые биты делаются равными 1.
Дополнение по модули один числа 10011001 равно 01100110. См. также дополнение по модулю два.
Op-code. - жаргонное выражение для operation code (код операции).
Operand. Операнд. Объект, с которым работает оператор. В Форте в выражении 3 4 + 3 и 4 являются операндами, используемыми оператором.
Operation system. Операционная система. См. дисковая операционная система.
Operation code. Код операции. Машинная инструкция, иногда называется КОП.
Operator. Оператор. Символ или слово Форта, которые выполняют математическую операцию. В Форт выражении 3 4 + + является оператором, а 3 и 4 - операндами.
Outer interpreter. Внешний интерпретатор. Часть Форт-системы, которая интерпретирует входной поток, исполняя слова и числа или компилируя их в описания новых слов или, менее часто, производя разбор входных данных. Называется также текстовым интерпретатором. Противостоит интерпретатору адресов; внутреннему интерпретатору.
Overflow (data). Переполнение (данных). Ошибка, когда результат операции превосходит по размеру место, выделенное для его записи. Например, результат операции над числами одинарной длины дает переполнение, если он требует для своей записи более 16 битов. См. также арифметическое переполнение.
Overflow (stack). Переполнение стека. Переполнение стека происходит, когда место в памяти, выделенное для него, оказывается полностью использовано. В Форте это обычно случается, когда указатель стека параметров начинает указывать на верх словаря.
Parameter-field. Поле параметров. Часть откомпилированного Форт-описания, которое содержит информацию, определяющую специфическое поведение слова. Поле параметров слова-двоеточие содержит список адресов полей программы слов в описании. Поле параметров переменной содержит значение этой переменной. Поле параметров слова-примитива содержит машинную программу, исполняемую этим примитивом.
Parameter field address. Адрес поля параметров. Адрес начала поля параметров в Форт-описании. Но не адрес, содержащийся в поле параметров.
Parameter stack. Стек параметров. Стек типа LIFO, с которым манипулируют Форт-программы. Называется также стеком данных. Противостоит стеку возвратов.
Parsing. Разбор. Процесс разбиения входного потока Форта на алфавитно-цифровые последовательности, разделенные символами-разделителями. Внешний интерпретатор. Форта обычно производит выделение чисел или слов Форта в виде лексем, используя пробел в качестве разделителя. Строки данных могут также подвергаться разбору, в каждом случае могут использоваться разные разделители. Разбор обычно производится словом WORD. Вообще в вычислительной технике - это прием исходного текста программы и подготовка его для интерпретации или компиляции.
PFA. Обозначение адреса поля параметров.
Polish notation. Польская нотация. См. префиксная нотация.
Postfix notation. Постфиксная нотация. Форма математической нотации, в которой операторы следуют за операндами. Алгебраическое выражение (2+3) * (4+5) в постфиксной нотации должно выглядеть как 2 3 + 4 5 + * Форт использует постфиксную нотацию, так как ее легче совместить с LIFO-стеком. Синоним инверсной польской нотации. Противоположно алгебраической нотации префиксной нотации; инфиксной нотации.
Precedence bit. Лидирующий бит. Бит поля имени слова Форта, который определяет, является ли оно словом немедленного исполнения и, следовательно, должно ли слово исполняться в режиме компиляции. См. также "слово немедленного исполнения".
Prefix notation. Префиксная нотация. Форма математической нотации, при которой оператор предшествует своим операндам. Алгебраическое выражение (3+4) * (5+6) в префиксной нотации должно выглядеть как * + 3 4 + 5 6 То же, что и польская нотация. Противоположно алгебраической нотации; инфиксной нотации; постфиксной нотации.
Primitive. Примитив. Тип слов Форта, которые непосредственно исполняют машинную программу, в частности слова из ядра Форта. Содержимое поля программы примитива указывает на поле параметров этого же слова, которое содержит исполняемую машинную программу.
Processor. Процессор. Центральный процессор или микропроцессор.
PUSH. Операция занесения числа в стек (см. также POP)
Radix. Основание системы считывания (см. также BASE)
RAM. Память с произвольным доступом (ППД)
Random access memory. Память с произвольным доступом. Главная (полупроводниковая) память, которая позволяет центральному процессору как чтение, так и запись. Сокращение ППД. Противоположна постоянной памяти. Технически памятью с произвольным доступом может быть любая память, к которой может обратиться центральный процессор, но обычно терминологически ПЗУ исключается. См. также память.
Rational approximation. Рациональная аппроксимация. Аппроксимация иррациональных чисел с помощью отношений целых чисел. Так, Пи (3.1415926...) может быть аппроксимировало с точностью трех десятичных знаков отношением 22/7 (3.1428571...), в то время как 355/113 (3.1415929...) является аппроксимацией Пи с точностью до семи десятичных знаков.
Read-only memory. Постоянная память. Основная (полупроводниковая) память, которая может быть прочитана, но содержимое которой не может быть изменено центральным процессором. Сокращение - ПЗУ. Противоположна памяти, с произвольным доступом. См. также память.
Record (file). Запись (в файле). Группа связанных элементов информации, которые могут рассматриваться в файле как единое целое. Например, типовой файл платежной ведомости должен содержать набор записей, включающих имя, зарплату и другие необходимые данные, относящиеся к конкретному сотруднику. Место, выделенное для каждого элемента информации в записи, называется полем.
Recursion. Рекурсия. Способность программы обращаться к самой себе. Рекурсия в Форте заключается в том, чтобы слово могло обратиться к самому себе из своего описания, что может быть сделано с помощью слов MYSELF или RECUBSE или имени самого слова, в зависимости от версии.
Register. Регистр. Одна из нескольких ячеек памяти, используемых центральным процессором для различных целей.
Required word. Слово из обязательного списка.
Слово Форта, которое должно присутствовать в стандартах Форт-79, или в Форт-83, или в обоих. Обязательные слова образуют ядро стандартной системы форта и должны использоваться в описании всех слов, которые можно назвать "стандартными". То же самое, что и стандартное слово. Противостоит словам из контролируемого списка; словам из неконтролируемого списка; расширяющему набору слов.
Return stack. Стек возвратов. LIFO-стек Форта, который используется для хранения адресов слов, ожидающих завершения исполнения, и извлечения их в процессе исполнения Форт-программы. Стек возвратов обычно скрыт от программиста, но он может быть использован для временного хранения данных с помощью слов >R, R@ и R>. Стек возвратов используется также обычно для хранения индекса и предела в do-loop.
Reverse Polish notation. Инверсная польская нотация. Синоним постфиксной нотации. Названа в честь Яна Лукашевича, польского математика. Противостоит алгебраической нотации; инфиксной нотации; префиксной нотации.
Run-time code. Исполнительная программа. Машинная программа, на которую указывает адрес, содержащийся в поле программы слова Форта. Исполнительная программа определяет, как будет исполняться слово Форта. Каждое слово-описатель (такое как ;, CONSTANT и VARIABLE) кладет адрес исполнительной программы, специфической для этого слова-описателя,-во все производные слова. Исполнительная программа примитива - это машинная программа, лежащая в его поле параметров.
Scaling. Масштабирование. Процесс преобразования одного числа в другое с использованием фиксированного коэффициента (масштабного коэффициента). Обычно это делается для того, чтобы не потерять точность или не допустить переполнения. Преобразование 12.34 в целое 1234 является примером масштабирования также преобразования миль в футы. Масштабирование особенно важно, когда используются целые числа, представляющие величины, обычно характеризуемые числами с плавающей запятой. Выбор правильного коэффициента крайне важен для того, чтобы не потерять точность при арифметических расчетах.
См. также масштабный коэффициент.
Scaling operator. Масштабный оператор. Одно из слов Форта, таких как */ и */MOD, которые используются для масштабирования чисел. Масштабные операторы Форта, чтобы сохранить точность, используют для промежуточных результатов числа двойной длины.
Screen. Экран. Наиболее распространенный, в том числе и в этой книге, синоним выражения "блок Форта, который может быть отображен на терминале", хотя значение и несколько варьируется. Чаще всего используется в отношении исходного текста программы. Также, конечно, дисплей видеотерминала.
Search path. Путь поиска. Последовательность, в соответствии с которой производится просмотр одного или нескольких контекстных словарей. Метод просмотра варьируется от версии к версии очень широко.
Shadow block. Теневой блок. Блок комментариев и пояснений, который составляет пару с блоком, где размещен текст исходной программы.
Signed number. Число со знаком. Целое число, в котором старший бит определяет его знак. Число представляется в виде дополнения по модулю два. Если старший бит равен 1, число отрицательно.
Single-length number. Число одинарной длины. 16- битовое число со знаком или без. См. также 16-битовое число.
Smudge bit. Бит-метка. Во многих версиях Форта этот бит определяет, может ли быть данное число найдено в словаре. Бит-метка используется, чтобы предотвратить обнаружение в словаре слов, которые не скомпилированы должным образом, хотя это и плохая практика, так как приводит к тому, что ошибки занимают место в памяти.
Source code. Исходный текст программы. В любом языке ЭВМ - это исходный текст программы, который последовательно транслируется в форму (машинного, объектного кода), которая может быть непосредственно исполнена ЭВМ. Исходный текст Форта состоит из описаний слов, которые обычно записаны в блоках или экранах. См. также язык высокого уровня; язык низкого уровня; ассемблер; машинная программа, объектный код.
Stack. Стек. Последовательность ячеек памяти, используемая для временного хранения чисел.
LIFO-стек (так как это сделано в Форте) определяет последовательность, в которой одни команды заносят числа в отек, а другие извлекают их из стека. Стек управляется указателем стека, который указывает на верх стека. При записи и извлечении чисел указатель изменяется таким образом, что он указывает на число, которое доступно в данный момент. Стек является очень эффективным способом запоминания и извлечения обрабатываемой информации. Стек параметров (данных) используется для пересылки чисел между словами. В форте имеется множество слов для манипулирования последовательностями чисел в стеке параметров. Отдельный стек (возвратов) контролирует исполнение слов и, как правило, циклов do-loop, хотя он может хранить и ограниченное число кодов. Центральный процессор также использует стек параметров как временную память при выполнении машинных программ. См. также LIFO-стек; стек параметров; стек возвратов.
Stack chart. Отображение стека. В Форте принято отображать содержимое стека параметров до начала и после завершения исполнения слова Форта. Изменение в содержимое стека, вносимое словом, отображается слева направо, самое правое число лежит на верху стека. Исполнение слова отмечается "-" или "->". Пример отображения стека для слова SWAP: (n1 n2 - n2 n1) или ( n1 n2 -> n2 n1)
Slack-manipulation word. Слово, манипулирующее стеком. Слово Форта, которое изменяет порядок кодов или их число в стеке. Примерами слов, манипулирующих стеком, являются DROP, DUP, SWAP, OVER. ROT, PICK и ROLL.
Standard word. Стандартное слово. Синоним выражения "слово из обязательного списка" (в стандартах Форта).
State-smart word. Слово, зависящее от STATE. Слово Форта, работающее по разному в зависимости от значения STATE, которое определяет, находится ли Форт в режиме исполнения или компиляции. Примерами таких слов в Форт-79 являются ." и '. В Форт-83 отсутствуют слова из обязательного списка, зависящие от STATE.
String. Строка. Последовательность алфавитно-цифровых символов.
Строки используются для записи ASCII-текстов, См. также: счетная строка; сепаратор; ASCII.
Structured programming. Структурное программирование. Философия программирования, при которой каждая последовательность команд в программе представляет собой модуль только с одной точкой входа и выхода и которая по завершении возвращает управление в точку, откуда произошел вызов. Программы должны писаться для того, чтобы они были исполнены, с единственным исключением, когда условный оператор может выбрать один из альтернативных маршрутов исполнения, условие может определить и время завершения цикла. Структурное программирование препятствует созданию текстов про грамм, которые трудно понять, модифицировать и исправлять. Форт сам обеспечивает структурное программирование, но без ограничений, присущих многим другим языкам, таким как Паскаль.
Stub. Подставка. Слово-подставка, описанное в Форте, не используется в окончательной версии программы, но оно определяется временно для того, чтобы проверить функции, которые могут использоваться в главной программе.
Target compilation. Целевая компиляция. В Форте - процесс компиляции элементов словаря в область памяти, не совпадающую с верхом словаря. Целевая компиляция может использоваться для создания полной системы Форта, которая будет работать в произвольной области памяти или которая может быть перенесена в другую ЭВМ. Часто используется синоним "метакомпилятор". См. также кросскомпилятор; метакомпилятор.
Text-input buffer. Текстовый входной буфер. Область памяти в системе Форта, которая зарезервирована для приема информации от клавиатуры. См. также буфер; входной поток.
Text interpreter. Текстовый интерпретатор. Синоним внешнего интерпретатора.
Thread. Цепочка, В Форте - последовательность слов и машинных программ-примитивов, которые исполняются, когда исполняется слово из входного потока. Иногда используется в смысле последовательности слов, просматриваемых при поиске в словаре.
Threaded interpretive language.
Цепной интерпретивный язык. Цепной интерпретивный язык подобен обычному интерпретивному языку, в котором исходная программа может быть исполнена без формирования объектного модуля или программы, обеспечивающей оперативное и удобное взаимодействие между оператором и ЭВМ. Но в отличие от других интерпретивных языков группы имеющихся коротких машинных программ сцепляются вместе, чтобы обеспечить последовательность выполнения, что много быстрее, чем способы, используемые в большинстве интерпретивных языков. В Форте это делается, когда слово или последовательность слов выполняется с пульта или другого входного устройства с последующим исполнением машинных программ, специфицированных при описании этих слов. Но в отличие от других интерпретивных языков эти цепочки описаны в процессе написания программы, т.е.. когда слово Форта описано, оно компилирует машинные программы, подлежащие исполнению, задавая тем самым цепочку выполнения данного слова. Программы на цепном интерпретивном языке исполняются фактически так же быстро, как откомпилированные программы, и обычно требуют меньше памяти, Форт - наиболее популярный цепной интерпретивный язык. См. также компиляция; цепочка.
Token. Лексема. В Форте группа символов, которая выделена из входного потока внешним интерпретатором, и интерпретируемая как число или имя слова. См. также разбор.
Translation table. Таблица преобразования. Линейный массив, используемый для преобразования одного числа в другое. Обычно число - это индекс массива, который при обращении заменяется элементом массива. Примером могла бы служить таблица преобразования ASCII-символов в другие коды.
True (flag). Истинно (флаг). Число, полученное в результате проверки условия. В Форт-79 истинно соответствует 1, а в Форт-83 - -1 (все 16 битов равны 1). См. также булев флаг; флаг ложно.
Tuos-complement notation. Представление в виде дополнения по модулю два. Метод представления отрицательных чисел путем взятия дополнения по модулю 1 от их абсолютной величины и добавления к нему 1.
Это дает тот же результат, что и вычитание абсолютной величины из нуля. Дополнение по модулю два упрощает некоторые арифметические операции в двоичной арифметике. См. также дополнение по модулю один; числа со знаком.
Uncontrolled reference word. Слово из неконтролируемого списка. Слово, специфицированное в стандарте Форт-83, которое имеет уже определенную функцию в других версиях. Изменение функции не рекомендуется, но не запрещается. См. также слово из обязательного списка; слово из контролируемого списка.
Unsigned number. Число без знака. Число, где все биты используются для кодировки двоичного кода. Противоположно числу со знаком, где старший бит равен 1, если число отрицательно. См. также дополнение по модулю 2.
User stack. Стек пользователя. Синоним стека параметров.
User variable. Переменная пользователя. Форт-переменная, которая является частью базовой Форт-системы. Хотя она используется подобно остальным переменным, она отличается от них способом запоминания. В описании такой переменной хранится не ее величина, а указание на адрес. Переменные пользователя в многопользовательских версиях при идентичных именах имеют разные значения для разных пользователей. Примерами таких слов являются BASE, STATE, CURRENT и CONTEXT.
Vector. Вектор. Синоним линейного или одномерного массива.
Vectored execution. Векторное исполнение. Если вектор (линейный массив) содержит список адресов полей программы слов, тогда эти слова могут быть исполнены путем занесения соответствующего элемента массива в стек и использования EXECUTE. В Форте этот термин относится также к возможности изменить работу слова путем изменения содержимого первого элемента поля параметров, заменяя его на CFA нового слова. Так, если EMIT описан как : EMIT ; и существует другое слово , тогда EMIT можно заставить выполнять другую функцию, выполнив FIND ' EMIT ! в Форт-79 или ' ' EMIT >BODY ! в Форт-83.
Virtual memory. Виртуальная память. Массовая память (обычно диск), которая с помощью некоторой схемы может быть переведена в состояние, когда она может рассматриваться как часть основной памяти.
Техника перемещения блоков в буфер и обратно и последующая адресация к буферам являются простой формой виртуальной памяти в Форте. См. также дисковый кэш.
Vocabulary. Контекстный словарь. Связанный список - из словаря Форта. Контекстные словари используются для управления порядком поиска слов Форта внешним интерпретатором. Применение различных контекстных словарей позволяет использовать одно и то же имя в различных контекстах без конфликта.
Word (Forth usage). Слово (использование в Форте). Фундаментальный элемент программирования в форте и статья в словаре. Слово Форта обычно содержит имя, связь с другим словом в словаре, указатель машинной программы, которая определяет, как будет выполняться слово, и один или более параметров (чисел, машинную программу, или список адресов других Форт-слов). Слова Форта вызываются по имени и аналогичны подпрограммам в других языках. Форт-программирование состоит в описании новых слов через ранее описанные или с помощью машинных программ. Это определение слова отличается терминологически от того, что принято в вычислительной технике, где слова - это последовательность битов, минимальное число битов в памяти, к которому возможна адресация со стороны центрального процессора. См. также.словарь; поле имени; поле связи; поле программы; поле параметров; длина слова.
Word length. Длина слова. В вычислительной технике минимальное число битов, к которому может непосредственно обращаться центральный процессор. Длина слова для большинства микропроцессоров равна 8-битовому байту, хотя используются и 16- битовые слова; длина слова в базовой ЭВМ может быть равна 64 битам и более. Противостоит значению слова Форта.
Д. Ответы к упражнениям
Блок 0 [0:0] ( Глава 1. Упражнения 1. 01 из 02 ) ( 1. ) ( а. 10 б. 0 в. 6 г. 1 д. 4 ) ( 2. ) ( а. 1 2 * b. 1 2 * 2 / в. 2 3 / 1 + г. 1 2 + 3 / д. 3 2 1 + / е. 3 4 + 2 1 + / ) ( 3. ) : POWER4 ( n -- ) DUP CUBE * ; ( 4, ) : NEWPOWER4 ( n -- ) SQUARE SQUARE ; ( 5. ) : PYTHAGORUS ( n1 n2 -- ) SQUARE SWAP SQUARE * :Блок 1 [1:0] ( Глава 1. Упражнения 1. 02 из 02 ) ( 6. ) ( AREA ( радиус -- ) SQUARE 314 * ; ( 7. ) : VOLUME ( длина радиус -- ) AREA * ; : XVOLUME ( радиус длина -- ) SWAP AREA * ; ( Второе описание менее эффективно из-эа лишнего SWAP . )
Блок 2 [2:0] ( Глава 1. Упражнения 2. 01 из 02 ) ( 1. ) ( Главное преимущество диска заключается в уменьшении вероятности потери программы и в простоте ее изменения.) ( 2. ) : .X ." -" ; ( .X может быть изменено для отображения любого символа. ) ( 3. ) : LIMITBAR ( n --) DUP 40 > IF DROP 40 THEN BAR ; ( Для использования с 80-символьным экраном: ) ( : LIMITBAR ( n -- ) DUP 80 > IF DROP 80 THEN BAR ; ) ( Аналогичным образом можно приспособиться к экрану любой ширины ) ( 4. ) : LIMITBAR1 ( n --) 100 / DUP 64 > IF DROP 64 THEN BAR ; (5. ) : LIMITBAR2 ( в.- ) 10000 / 64 * DUP 64 > IF DROP 64 THEN BAR ;
Блок 3 [3 :0] ( Глава 1. Упражнения 2. 02 из 02 ) ( 6. ) : GRAPH ( n1 т2 n3 ... -- или n1 n2 n3 ) CR DEPTH DUP 16 < IF DO LIMITBAR LOOP THEN ; ( Заметии, что исходное число может остаться в стеке.) ( 7.) : BAR ( n --) DUP 0 DO .X LOOP . СR ; ( DUP позволяет использовать вернее число как LIMlTBAR, так и . ) : LIMITBAR3 ( n -- ) DUP 50 > IF DROP 50 THEN BAR ;
Блок 4 [4 :0] ( Глава 2, Упражнения 1. 01 из 04 ] . ( 1. ) ( а. 5 5 + 5 + . Ответ: 15 б. 5 5 * 5 * 5 * . 625 в. 5 5 + 5 * . , 50 г. 5 5 + 5 / . 2 д. 5 5 + 2 / . 5 е. 10 5 5 + / . 1 ж. 5 4 + 5 5 + / . 0 з. 5 5 * 5 4 * 4 4 * + . 61 и. 5 4 + 5 4 + * . 81 к. 5 4 + 5 4 + * . 81
Блок 5 [5 :0] Глава 2. Упражнения 1. 02 из 04 ) ( 2.) а. 5 5 5 + + . Ответ: 15 б. 5 5 5 5 * * * . 625 в. 5 5 5 + * . 50 г. Это нельзя изменить. д. и это нет. е. Это тоже нельзя.
ж. И это не может быть изменено з. 5 5 * 5 4 * 4 4 * + + . 61 и. Т эти два также нельзя к. изменить. )
Блок 6 [6 :0] ( Глава 2. Упражнения 1. 03 из 04 ) ( 3. ) ( Сначала сложим 3 и 5, умножим на 2, разделим на 4 и добавим 16. Постфиксная запись: 3 5 + 2 * 4 / 16 + ( дает 20). ( Заметим, что если есть выбор, то, чтобы не было ошибки округления, умножение должно всегда предшествовать делению. Например: 7 100 * 6 / выдает 116 в то время как 7 6 / 100 * выдает 100 )
( 4. ) ( 9 3 + 5 * 6 / 2 + 32 + . Ответ: 44 5 6 * 3 * 3 * 2 * 32 * . 6336 5 4 + 22 * 5 10 * 2 + + 2 * . 500 )
Блок 7 [7 :0] ( Глава 2. Упражнения 1. 04 из 04 ) ( 5. ) ( a. 5 DUP DUP + + . Ответ: 15 б. 5 DUP DUP DUP * * * . 625 в. 5 DUP DUP + * . 50 г. 5 DUP + 5 / . 2 д. 5 DUP + 2 / . 5 е. 10 5 DUP + / . 1 ж. 5 4 + 5 DUP + / . 0 з. 5 DUP * 5 4 * + 4 * DUP * + . 61 и. 5 4 + DUP * . 81 к. 5 4 + DUP * . 81 )
Блок 8 [8 :0] ( Глава 2. Упражнения 2. 01 из 04 ) ( 1. ) a. SWAP б. DUP в. OVER г. OVER SWAP д. DUP ВОТ е. ROT ж. SWAP з. SWAP ROT и. ROT SWAP к. ROT ROT л. OVER SWAP м. 3 PICK ROT ROT [FORTH-79] 2 PICK ROT ROT [FORTH-83] н. OVER SWAP DUP o. 3 PICK ROT ROT OVER SWAP DUP [79] 2 PICK ROT ROT OVER SWAP DUP [83])
Блок 9 [9 :0] ( Глава 2. Упражнения 2. 02 из 04 ) ( 2.) а. * б. DUP + + в. SWAP DUP + + г. - д. DUP DUP * * SWAP DUP * * f. OVER + * e. + DUP * факториэуем [a + b] [а + b] ж. ROT ROT + SWAP / з. OVER + ROT ROT + SWAP / и. SWAP OVER + ROT ROT + SWAP / к. DUP + ROT ROT + SWAP / л. ROT ROT + SWAP DUP + / м. OVER SWAP - ROT ROT SWAP - * н. + + * факторизируем a [b + с + d ] o. * 1 + * * факторизуем a b [1 + c d]
Блок 10 [10 :0] ( Глава 2. Упражнения 2. 03 из 04 ) ( З. ) : OVER ( n1 n2 -- n1 n2 n1 ) SWAP DUP ROT ROT ; ( 4. ) : 2DUP ( n1 n2 -- n1 n2 n1 n2) OVER OVER ; ( 5. ) : NEWDUP83 ( n -- n n ) 0 PICK ; ( FORTH-83 ) : NEWDUP79 ( n -- n n ) 1 PICK ; ( FORTH-79 ) : NEWOVER83 ( n1 n2 -- n1 n2 n1 ) 1 PICK : ( F-83 ) : NEWOVER79 ( n1 n2 -- n1 n2 n1 ) 2 PICK ; ( F-79 ) ( 6. ) : NEWROT83 ( n1 n2 n3 -- n2 n3 n1 ) 2 ROLL ; ( F-83 ) : NEWROT79 ( nl n2 n3 -- n2 n3 n1 ) 3 ROLL ; ( F-79 )
Блок 11 [11 :0] ( Глава 2. Упрахнеиия 2. 03 из 04 ) ( 7. ) : 2SWAP83 ( 1 2 3 4 - 3 4 2 1 ) 3 ROLL 3 ROLL ; [ 83 ] : 2SWAP79 ( 1 2 3 4 - 3 4 2 1 ) 4 ROLL 4 KOLL ; [ 79 ] ( 8. ) : ROTSTACK83 DEPTH 1 - ROLL ; ( FORTH-83 ) : ROTSTACK79 DEPTH ROLL ; ( FORTH-79 ) ( 9. ) : SPHERE ( радиус -- 100*v ) DUP DUP * * 314 ( 10. ) * 4 * 3 / ; : .ВОTТОМ*83 DEPTH 1 - PICK DEPTH 1 - PICK * . ; [83] : .BOTTOM*79 DEPTH PICK DEPTH PICK * . ; [79] ( 11. ) : NEWMOD ( n1 -- n2 ) OVER OVBB / * - ;
Блок 12 [12 :0] ( Глава 3. Упражнения 1. 01 из 02 ) ( 1. ) ( Двоичная часть результата имеет вид: 1 дает 1 3 дает 11 7 дает 111 15 дает 1111 31 дает 11111 63 дает 111111 127 дает 1111111 255 дает 11111111 Число, которое на единицу меньше чем два в степени n, состоит из последовательности n бит, равных единице. ) ( 2. ) Шестнадцатеричная часть результата имеет вид: 2 дает 2 4 дает 4 8 дает 8 16 дает 10 32 дает 20 64 дает 40 128 дает 80 256 дает 100 ) Шестнадцатеричное число отображает содержимое 1 байта. )
Блох 13 [13 :0] ( Глава 3. Упражнения 1. 02 из 02 ) ( 3. ) ( Ответ неизбежно будет сведен к 2., так как 11111111 равно ff в шестнадпатеричном представлении, а 1111111111111111 равно FFFF ) ( 4. ) ( Вам следует ввести FFFF 1 + . , и, если вы это сделали, то получите результат =0. Легче запоминать и задавать числа а шестнадцатеричном представлении, результат, требующий для представления более двух байт, дает ошибку переполнения и засылает в стек два 0 байта.) ( 5. ) ( Прежде чем использовать описание ) DECIMAL : HЕХ 16 BASE ! ; : OCTAL 8 BASE ! ; : BINARY 2 BASE ! ; ( 6. ) ( Taкие слова как AA, BAC и т.д. помешают использовать соответствующие шестнадцатеркчные числа, если в их начале не стоит 0. GG не шестнадцатеричное число )
Блок 14 [14 :0] ( Глава 3. Упражнения 1. 01 из 02 ) ( 1. ) а. 11110111 AND б. 100 OR в. 0 AND г. 11111111 OR д. 10100000 AND e. 1111 AND ж. 10101111 XOR з. 11111111 XOR и. 11111111 XOR ) ( 2. ) : ZERO-IT ( n -- 0 ) 0 AND ; ( 3. ) : NEW= ( n1 n2 -- f ) XOR DUP / 1 XOR ; (4. ) : <> ( n1 n2 -- f ) XOR DUP / ; ( Выше приведено быстродействующее описание. ) ( Другой вариант: : <> ( n1 n2 -- f ) = 0 = ; )
Блок 15 [15 :0] ( Глава 3. Упражнения 2. 02 из 02 ) ( 5. ) : NOT1 ( n1 -- n2 ) 65535 XOR ; : NOT2 ( n1 -- n2 ) 65535 - ; ( 6.) : SET? ( n1 маска -- f) DUP ROT AND = ; ( 7. ) : NOTSET? ( n1 маска -- f) DUP ROT AND = 0 = ; ( или )
Блок 16 [16 :0] ( Глава 3. Упражнения 3. 01 из 02 ) ( 1. ) : .DEC.BIN ( n -- ) DECIMAL DUP . DUP U. ( 2.) .BIN U.BIN DROP ; ( Таблица должна иметь следующий вид ) ( 1 1 1 1 2 2 10 10 3 3 11 11 32766 32766 111111111111110 111111111111110 32767 32767 111111111111111 111111111111111 -32768 32768 -1000000000000000 1000000000000000 -32767 32769 -1111111111111111 1000000000000001 -3 65533 -11 1111111111111101 -2 65534 -10 1111111111111110 -1 65535 -1 1111111111111111
Бпок 17 [17 :0] ( Глава З. Упражнения 3. 02 из 02 ) ( 3.) ( 65536 + n ) ( 4.) : NEWNEGATE ( n -- -n ) 65535 XOR 1 + ; : NEWNEGATE -1 XOR 1 + ; является эхвквалентным ) ( 65535 XOR выдает то. что называется дополнением по модулю 1; добавление 1 превращает его в дополнение по модулю 2, которое меняет знак. ) ( 5. ) : NEW- ( n1 n2 -- n3 ) NEGATE + ; ( Так в действительности выполняется вычитание в ЭВМ; то есть добавляется дополненме по модулю два. )
Блок 18 [18 :0] ( Глава 3. Упражнения 4. 01 из 02 ) ( 1. ) : .LSB ( n --) 255 AND . ; ( 255 в двоичном виде равно 11111111 ) ( 2.) : MSB ( n -- ) 256 / . ; ( Это не работает для отрицательных чисел, так как отрицательное делимое даст отрицательное частное. ) ( 3.) : .LSB1 ( n -- ) 256 / 256 * - ; ( 4.) : .LSB2 ( n -- ) PAD 1 PAD C@ . ; : .MSB2 ( n -- ) PAD 1 PAD 1+ С@ . ; ( Описания в упражнениях 1 и 2 будут быстрее. ) ( 5.) : 256/ ( n1 -- n2 ) PAD 1+ С@ ; ( В отличие от 256 / это игнорирует знак числа.)
Блок 19 [19 :0] ( Глава 3. Упражнения 4. 02 из 02 ) ( 6.) : 256+ PAD ! PAD 1 + С@ 1 + PAD 1 + C! PAD @ ;
Блок 20 [20 :0] ( Глава 4. Упражнения 1. 01 из 02 ) ( 1. ) ( "Нормал." С округлением по нижн. границе) ( ост част ост част) а. 0 5 0 5 б. 0 0 0 0 в. 2 3 2 3 г. -2 -3 1 -4 д. 2 -3 -1 -4 е. -2 3 -2 3 ж. 0 -5 0 -5 з. 0 -5 0 -5 и. 0 5 0 5 к.
Деление на 0 является ошибкой ) Блок 21 [21 :0] ( Глава 4. Упражнение 1. 02 из 02 ) (2.) : LINE ( N -- строка ) 50 / ; (3.) : POS ( -- pos ) 50 MOD ; ( 4.) : POSLINE ( N длина -- роs строка ) /MOD ; ( 5.) : NEW/ ( n1 n2 -- n3) /MOD SWAP IF DUP 0 < IF 1+ THEN THEN ; : NEWMOD ( n1 n2 -- n3 ) 2DUP N/ * - ; ( 6.) ( 2000 100 30 */ . дает 6666 2000 30 /MOD 100 * SWAP 100 * 30 / + . . также дает 6666 2000 100 30 */MOD . . дает 6666 20 2000 30 /MOD 100 * SWAP 100 * 30 /MOD RОТ + . . дает 6666 20 Преимущество */ очевидно, тах как оно не дает переполнения)
Блок 22 [22 :0] ( Глава 4. Упражнения 2. 02 из 02 ) : .LARGEST-3 ( n1 n2 n3 -- ) MAX MAX . ; ( 2.) : 79.SMALLEST-3 ( n1 n2 n3 -- n1 n2 n3 ) 3 PICK 3 PICK 3 PICK MIN MIN ; ( FORTH-79 ) : 83.SMALLEST-3 ( n1 n2 n3 -- nl n2 n3 ) 2 PICK 2 PICK 2 PICK MIN MIN ; ( FORTH-83 ) ( 3. ) : TREE>5? ( n1 n2 n3 -- f ) 5 > MAX MAX 5 > ; ( 4. ) : ALL>5? ( n1 n2 n3 -- f ) 5 > MIN MIN 5 > ; ( 5.) : >LOWER2? ( n1 n2 n3 -- f ) ROT ROT MAX > ; ( 6.) : TEMPDIFF ( n1 n2 n3 ) - ABS ;
Блок 23 [23 :0] ( Глава 4. Упражнения 2. 02 из 02 ) ( 7. ) : LARCER-MAG ( n1 n2 -- n3 ) ABS SWAP ABS MAX ; ( 8. ) : NEAREST-0 ( n1 n2 -- n3 ) ABS SWP ABS MIN ; ( 9 ) : -ABS ( n -- -n или -n -- -n ) ABS NEGATE ; ( 10. ) : OTHER-QUAD ( x y -- -x -y и т.д.) NEGATE SWAP NEGATE SWAP ; ( 11. ) ; NEWNEGATE -1 * ;
Блок 24 [24:0] (Глава 4. Упражнения 3. 01 из 02 ( 1. ) : PYRVOL ( область h -- v) * 2 /MOD + ; ( 2. ) : F->C ( f -> c ) 32 - 5 9 */MOD SWAP 10 9 */ 5 + 10 / + ; ( SWAP 10 9 */ 5 t 10 / по существу округляет в большую сторону, добавляя к числу 0.5 . ) : 10F->10C (10f -- 10с ) 320 - 9 5 */MOD SWAP 100 90 */ 50 + 100 / + ; ( Вводите значение температуры, умноженое на 10, в результате получается та же, но округленная величина. Это способ работает с десятыми долями целого числа. )
Блок 25 [25 :0] ( Глава 4. Упражнения 3. 02 из 02 ) ( 3.) ( а. факторизируем [а + в]/c ) : (a.+.b)./.c ( a b c -- n) ROT ROT + SWAP / ; ( б. факторизируем [a + b/c]/c ) : (a.+.b./.c.)./.c ( а b с -- n) SWAP ОVER / ROT + SWAP / ; ( в.
факторизируем [a + b]^2 ) : (A.+.B)^2 ( а b -- n ) + DUP * ; ( г. факторизируем 3[а + b]^2 : 3.(A.+.B)^2 ( a b -- n ) (A.+.B)^2 3 * ; ( д. факторизируем [a + b]^4 ) : (a.+.b)^4 ( a b -- n) (A.+.B)^2 DUP * ; ( Оказывается даже сложные выражения могут стать простыми за счет факторизации)
Блок 26 [26 :0] ( Глава 4. Упражнения 4. 01 из 03 ) ( 1. ) ( а. 25E15 б. 25E-5 в. 25E5 г. 25E-6 д. 1Е-5 е. 1 ж. 25E2 з. 500.005 и. 499.998 к. 499.995 ) ( 2. ) : EXP* ( m1 e1 m2 e2 -- m3 e3 ) ROT + ROT ROT * SWAP ; ( 3. ) ( Здесь не принято никаких мер против переполнения : : EXP/ ( m е m е -- m е) ROT SWAP - 2 - ROT 100 * / SWAP ; ( Мантисса - 2 и показатель * 100 способствуют ( 4. ) ( предотвращению переполнения ) : TOM ( km m - m ) SWAP 1000 * + ; ( 5. ) : TOCMMM ( mm -- m mm ) 10 /MOD SWAP ;
Блок 27 [27 :0] ( Глава 4. Упражнения 4. 02 из 03 ) ( 6. ) : TOKM ( m -- km m ) 1000 /MOD SWAP ; ( 7. ) : TOFT ( mi ft - ft ) ROT 5280 * + ; ( 8. ) : TOMILES ( ft - mi ft ) 5280 /MOD SWAP ; ( 9. ) : FTTOM ( ft -- m) 305 1000 */ ; ( 10. ) : ТОMЕТRIС ( mi ft -- km cm ) TOFT FTTOM TOKM ; ( 11. ) : C->F ( c -- f ) 9 5 */ 320 + ;
Блок 28 [28 :0] ( Глава 4. Упражнения 4. 03 из 03 ) ( 12. ) : CIRCUM ( -- длина окружности в мм ) 10000 355 113 */ 3 8 */ ; ( Проблема заключается в подавлении переполнения) ( 13. ) : IN->FT ( in -- ft ) 12 /MOD SWAP 6 / + ; ( 6 / + эквивалентно добавлению 2/12 остатка. )
Блок 29 [29 :0] ( Глава 4. Упражнения 5. 01 из 13 ) ( 1.) 4.294.967.296 равно 2 в 32-ой степени. ) ( 2. ) ( 2DROP [ d -- ] 2DUP [ d -- d d ] 2SWAP [ d1 d2 -- d2 d1 ] 2OVER [ d1 d2 -- d1 d2 d1 ] 2ROT [ d1 d2 d3 -- d2 d3 d1 ] Не существует стандартных слов-эквивалентов PICK, ROLL или DEPTH для чисел двойной длины ) ( 3.) : NEW2DROP ( d -- ) DROP DROP ; ( 4. ) ( Это не то же самое. Число двойной длины содержит старшее и младшее числа, каждое из которых должно быть задублировано. )
Блок 30 [30 :0] ( Глава 4. Упражнения 5. 02 из 03 ) ( 5. ) : NEW2DUP ( d -- d d ) OVER OVER ; ( 6. ) 2SWAP83 ( d1 d2 -- d2 d1 ) 3 ROLL 3 ROLL ; ( F-83) 2SWAP79 ( d1 d2 -- d2 d1 ) 4 ROLL 4 ROLL ; ( F-79) ( 7. ) 2ROT83 ( d1 d2 d3 -- d2 d3 d1 ) 5 ROLL 5 ROLL ; ( F-83) 2ROT7( ( d1 d2 d3 -- d2 d3 d1 ) 6 ROLL 6 ROLL ; ( F-79) ( 8. ) 2OVER83 ( d1 d2 -- d1 d2 d1 ) 3 PICK 3 PICK ; ( F-83) 2OVER79 ( d1 d2 -- d1 d2 d1 ) 4 PICK 4 PICK ; ( F-79) ( 9. ) 2ROLL83 2 * DUP ROLL SWAP 1- ROLL ; ( FORTH-83) 2ROLL79 2 * DUP 1+ ROLL SWAP ROLL ; ( FORTH-79)
Блок 31 [31 :0] ( Глава 4. Упражнения 5. 03 из 03 ) ( 10. ) : 2PICK83 2 * DUP PICK SWAP 1- PICK ; ( FORTH-83 ) : 2PICK79 2 * DUP 1+ PICK SWAP PICK ; ( FORTH-79 ) ( 11. ) : S->D ( n -- d ) HI# ; ( 12. ) ( HI# будет содержать 0. ) ( 13. ) : 10^N 1 SWAP 0 DO 10 * LOOP : : BEF. #PT 1- 10^N 0 D/ ; : AFT. 2DUP ВЕF. #PT 1- 10^N 0 D* D- ; ( Таким образом можно разделить целую и дробную части числа двойной длины. )
Блок 32 [32 :0] ( Глава 4. Упражнения 6. 01 из 02 ) ( 1. ) ( 1 UM* выполнит это. Используются числа без знака. ) ( 2. ) : M* ( n n -- d ) 0 ROT 0 D* ; : М+ ( d n -- d ) 0 D+ ; : М- ( d n -- d ) 0 D- ; : М/ ( d n -- d ) 0 D/ DROP ; : M/MOD ( d n -- n n ) 0 D/MOD DROP SWAP DROP ; ( 3. ) ( Как M* так и М/МОD воспринимают числа со знаком, в то время как U* и U/MOD работают с числами без знака.)
Блок 33 [33 :0] ( Глава 4. Улражнения 6. 02 из 02 ) ( 4. ) : ->DOLLARS1 ( d -- n) 100 M/ ; ( MMSFORTH ) : ->DOLLARS2 ( d -- n) 100 0 D/ DROP ; ( Стандарт ) : ->CENTS1 ( d -- n) 2DUP ->DOLLARS1 100 * М- DROP ; ( MMSFORTH ) : -> CENTS2 ( d -- n) 2DUP ->DOLLARS2 100 * 0 D- DROP ; ( Стандарт ) ( 5. ) : FRAC1 2SWAP ROT 0 D/ ROT 0 D* ; ( Стандарт ) : FRAC2 M*/ ; ( MMSFORTH )
Блок 34 [34: 0] ( Глава 4. Упражнения 7. 01 из 01 ) ( 1.) ( а. % 5.5 % 1200 F+ F. б. % 23 % 5 F/ LOG F. в. DEGREES % 55 SIN 2DUP F* % 45 COS 2DUP F* F+ F. г. % 1- SQR F. д. % 3.25 2DUP F* PI F* F. ) ( 2. ) : RECT ( n1 n2 -- ) I-F FDF 5 ROLL I-F FDF DF* DF. ; ( 3.) : NEWFABS ( f1 -- f2 ) 2DUP F* SQR ; ( 4.) : HYPOT ( f1 f2 -- f3 ) 2DUP F* 2SWAP 2DUP F* F+ SQR ; ( 5. ) : TABLE1 0 46 DO I 3 .R ( приращение равно 1 градусу) I I-F SIN 10 F.R I I-F TAN 10 F.R CR ; : TABLE2 0 451 DO I I-F % 10 F/ 2DUP 6 F.R 2DUP SIN 10 F.R TAN 10 F.R CR LOOP ; ( приращение равно одной десятой )
Блок 35 [35 :0] ( Глава 4. Упражнения 6. 01 из 03 ) ( 1. ) а. 5 8 + % 5.5 % 6.5 F+ б. % 35 10 >87 F+ SIN в. % -55 FNEGATE ) ( 2. ) ( г. 5 . % 35 2DUP COS 2SWAP SIN F+ F. ) : 87FABS ( f1 -- f2 ) FDUP F* SQR ; : 87CINT ( f1 -- f2 ) 87> ; : 87I-F ( n -- f ) >87 ; : 87RAD PI % 180 F/ ; : 87L10 % 10 LOG ; ( 3. ) : FACT 1001 2 % 1 DO I >87 F* LOOP F. ; ( Выдает реэултт .4023872600770938Е2568; 100 итераций требует 10.1 секунд.
Выполнение цикла требует 7.1 секунд. Т.о. 100.000 преобразований в форму с плавающей точкой и умножений требуют 3.1 секунды. )
Блок З6 [36 :0] ( Глава 4. Упражнения 8. 02 из 03 ) ( 4. ) ( С клавиатуры % 113 % 355 F/ PI F- F. дает 2.66764E-7 или ошибку около 0.00000003 ) ( 5. ) : ANGLE ( x y -- угол ) DEGREES PHASE ; ( 6. ) : НУРОТ ( x y -- угол ) DEGREES MAG ; ( 7. ) : CONC-CHANGE ( pH H-ch -- % ) FSWAP FNEGATE 10^ F/ % 100 F* ; : DELTAPH ( рH H-ch -- pH1) ( Вычис. новое H, затем - новое pH) FSWAP FNEGATE 10^ F+ LOG10 FNEGATE ; ( Практически это нельзя сделать в рамках целочисленной арифметики, не написав программу размером в несколько блоков с таблицами логарифмов и степеней 10.)
Блок 37 [37 :0] ( Глава 4. Упражнения 8. 03 из 03 ) ( 8. ) : ARM-MOVE ( x1 y1 x2 y2 -- - приращение угла приращение радиуса ) DEGREES FOVER FOVER MAG DF87> PHASE DF87>
FOVER FOVER PHASE FROT FROT MAG DF>87 DF>87 FROT F- FROT FROT FSWAP F- FSWAP ; ( Поскольку не существует FPICK или 2FSWAP числа запоминаются в "обычном" стеке с помощью DF87> и восстанавливаются DF>87. )
Блок 38 [38 :0] ( Глава 5. Упражнения 1. 01 из 02 ) ( 1. ) : BS 8 EMIT ; ( 2. ) : РАПЕ 12 EMIT ; ( 3. ) : CRS 0 DO CR LOOP ; ( 4. ) : DASHES 0 DO 45 EMIT LOOP ; ( Это эквивалентно : DASHES 0 DO ." -" LOOP ; )
Блок 39 [39:0] ( Глава 5. Упражнения 1. 02 из 02 ) ( 5.) : MMENU ." MAIN МЕNU" ; : THIS ." This is the " ; : 1ST ." first " ; : 2ND ." second " ; : 3RD ." 3rd " ; : 4th ." FORTH " ; : СHЕ ." choice" : : MENU 9 SRACES 5 DASHES SPACE MMENU SPACE 5 DASHES 2 CRS 40 DASHES CR 4 SPACES ." A" 6 DASHES SPACE THIS 1ST СНЕ CR 4 SRACES ." В" 5 DASHES SPACE THIS 2ND СНЕ CR 4 SRACES ." С" 8 DASHES SPACE THIS 3RD СНЕ CR 4 SRACES ." D" 6 DASHES SPACE THIS 4TH СНЕ CR 40 DASHES CR 5 DASHES SPACE ." WHAT IS YOUR CHOICE, PLEASE?" SPACE 5 DASHES CR ; ( 6. ) : $. COUNT TYPE :
Блок 40 [40 :0] ( Глава 5.
Упражнения 2. 01 из 03 ) ( 1.) : PLOT1 CR DEPTH 0 DO MIN XS CR LOOP ; ( Заметьте, насколько элегантнее MIN, чем IF ... THEN. как это сделано в главе 1. ) ( 2.) : РLOТ2 РRINT CR DEPTH 0 DO 79 MIN XS CR LOOP CRT ; ( 3. ) : YS 0 DO 89 EMIT LOOP ; : PLOT3 CR DEPTH 2/ 0 DO YS CR XS CR LOOP ; ( 4. ) : PLOT4 CR DEPTH 0 DO I . 3 SPACES XS CR LOOP ; ( 5. ) : PLOT5 CR DEPTH 0 DO I . 3 SPACES 1- SPACES ." X" CR LOOP ; ( 1- необходима перед SPACES для того, чтобы поместить "X" в конец строки. )
Блок 41 [41 :0]. (Глава 5. Упражнения 2. 02 из 03 ) ( 6.) : PLOT6 CR DEPTH 0 DO I . SPACE 1- 75 1000 */ SPACES ." Х" CR LOOP ; ( Заметьте, что масштабирование выполнено с использованнем 75. ) ( 7.) : PLOT7 CR DEPTH 2/ 0 DO SWAP XS YS CR LOOP ; ( 8. ) : PLOT8 CR DEPTH 2/ 0 DO SWAP 1- SPACES ." X" 1- SPACES ." Y" CR LOOP ; ( 9. ) : PLOT9 СR DEPTH 2/ О DO SWAP 1- SPACES ." X" DUP 1- SPACES ." Y" . CR LOOP ; ( 10. ) : PLOT10 CR DEPTH 2/ 0 DO SWAP 1- SPACES ." X" DUP 60 1000 */ 1- SPACES ." Y" . CR LOOP ;
Блок 42 [42 :0] ( Глава 5. Упражнения 2. 03 из 03 ) ( 11.) : PLOT11 CR DEPTH 2/ 0 SWAP 0 DO OVER SWAP - 3 PICK SWAP 0 DO CB ." ." LOOP SPACES ." X" SWAP OVER . . LOOP DROP ; ( Это описание можно упростить, использовав переменную или второй стек, стек возвратов, который будет описан в следующих главах, вы можете убедиться тeпeрь, насколько проще временное запоминание чисел в PAD как в: ) : PLOT12 CR DEPTH 2/ 0 PAD ! 0 DO OVER OVER PAD @ - 0 DO CR ." ." LOOP SPACES ." X" DUP PAD I . . LOOP ; ( Хотя и не намного короче, PLOT12 имеет более простые манипуляции со стеком, так как предшествующая величина Х [или 0 в начале] записана в PAD. )
Блок 43 [43 :0] ( Глава 5. Упражнения 3. 01 из 03 ) ( 1. ) : UD$. ( ud -- ) TYPE ; ( 2. ) : UD$. ( u -- ) 0 TYPE ; ( 3. ) : S#. ( n -- ) DUP 0< NEGATE SWAP OVER DABS TYPE ; ( Здесь и в следующем упражнении NEGATE, как обсуждалось ранее, в FORTH-83 присутствовать не должен.) ( 4. ) : S$.R ( n1 n2 -- ) SWAP DUP 0< NEGATE SWAP OVER DABS ROT OVER - SPACES TYPE ; ( Длина поля n2, хранится в стеке пока это необходимо, а затем используется ROT OVER - SPACES, как в описании U.R в тексте. )
Блок 44 [44 :0] ( Глава 5. Упражнении 3. 02 из 03 ) ( 5.) : .L ( n1 n2 -- ) SWAP DUP D< NEGATE ROT OVER - ROT ROT TYPE SPACES ; ( Запомните, никакого NEGATE в FORTH-83 нет. ) ( 6. ) : .DATE ( d --) TYPE ; ( 7. ) : .MDY ( d -- ) TYPE ; ( 121, 100, и 109 являются ASCII "у", "d" и "m". )
Блок 45 [45 :0] ( Глава 5. Упражнения 3. 03 из 03 ) ( 1. ) : .PHONE ( n1 n2 n3 ---) 0 TYPE ; ( Заметьте, что можно преобразовывать несколько чисел одновременно, но DROP DROP необходимо для удаления из стека частного двойной длины, исключая n1. которое удаляется #> .) ( 9.) : Fl. ( d -- )
IF #РТ 1- 0 DO # LOOP THEN 46 HOLD #S #> TYPE ; ( Это иллюстрирует, какая программа может находиться ( между . При вводе .1234 будет отображен начальный 0.)
Блок 46 [46 :0] ( Глава 6. Упражнения 1. 01 из 04 ) ( 1.) VARIABLE FEET VARIABLE INCHES : F->I ( -- ) FЕЕТ @ 12 * INCHES ! ; ( 2.) 2VARIABLE DFEET 2VARIABLE DINCHES : DF->I DFEET 2@ 12 0 D* DINCHES 2! ; ( 0 формирует 12 число двойной длины. ) ( 3.) : NEW2! ( d адр -- ) SWAP OVER 2SWAP 2+ ! ! ; ( 2! может использоваться для запоминания двух чисел одинарной длины. n1 n2 адр 2! запоминает n1 по адресу "адр", а n2 по адресу "адр+2" .) ( 4. ) : NEW+! ( n адр - ) DUP @ RОТ + SWAP ! ; ( 5. ) : NEW@ ( адр -- n ) DUP 1+ С@ 256 * SWAP C@ + ;
Блок 47 [47 :0] ( Глава 6. Упражнения 1. 02 из 04 ) ( 6. ) : NEW! ( n адр --) OVER OVER SWAP 256 / SWAP 1+ С! С! ; ( 7.) : VARSWAP ( адр1 адр2 --) DUP @ PAD ! SWAP DUP @ ROT ! PAD @ SWAP ! : ( 8. ) CREATE 1WEEK 0 , 0 , 0 , 0 , 0 , 0 , 0 , CREATE 2WEEK 0 , 0 , 0 , 0 , 0 , 0 , 0 , ( Оба слова засылают при использовании в стек ) ( свои адреса.) : !SUN ! ; : !MON 2+ ! ; : !TUE 4 + ! ; : !WED 6 + ! ; : !THU 8 + ! ; : !FRI 10 + ! ; : !SAT 12 + ! ; ( Все слова имеют диаграмму преобразования стека ( n адр -- )
Блок 48 [48 :0] ( Глава 6. Упражнения 1. 03 из 04 ) ( 10. ) : @SUN @ ; : MON 2+ @ ; : @TUE 4 + @ ; : @WED 6 + @ ; : @THU 8 + @ ; : @FRI 10 + @ ; : @SAT 12 + @ ; ( Каждое слово живет схему преобразования стека (адр -- n) ( 11. ) : RSWAP ( n1 n2 адр -- ) SWAP 2 * OVER + ROT 2 * ROT + VARSWAP ; ( Каждое число должно умножаться на 2 и добавляться к адресу так, чтобы их содержимое можно было бы обменять с помощью VARSWAP ) ( 12. ) CREATE CNT 0 С, 0 С, 0 С, 0 С, 0 С, 0 С, 0 С,
Блок 49 [49 :0] ( Глава 6. Упражнения 1. 04 из 04 ) (13.) : +SUN 0 + +! 1 0 CNT + +! ; : +MON 2 + +! 1 1 CNT + +! ; : +TUE 4 + +! 1 2 CNT + +! ; : +WED 6 + +! 1 3 CNT + +! ; : +THU 8 + +! 1 4 CNT + +! ; : +FRI 10 + +! 1 5 CNT + +! ; : +SAT 12 + +! 1 6 CNT + +! ; ( Каждое слово имеет cxeuy преобразования стека адр -- n ) ( 14. ) : DAY-AVE ( день - средн. ) DUP DUP 2 * 1WEEK + @ SWAP 2 * 2WЕЕК + @ + SWAP CNT + C@ / ;
Блок 50 [50 :0] ( Глава 6. Упражнения 2. 01 из 01 ) ( 11.) : NEWFILL ( адр n с -- ) ROT SWAP OVER С! DUP ( 2. ) 1+ ROT 1- CMOVE ; : NEWERASE ( адр n -- ) 0 FILL ; ( 3. ) : INITIALIZE ( адр n -- ) 2 * ERASE ; ( 4. ) : ARR-COPY1 ( адр1 адр2 n -- ) 2 * CMOVE ; : ARR-COPY2 ( адр1 адр2 n -- ) MOVE ; ( 5. ) : ARR-EXCH ( адр1 адр2 n --) PAD ! ( Запишем счетчик в PAD) OVER PAD 2 + PAD @ MOVE ( Переносим адр1 в PAD ) DUP ROT PAD @ MOVE ( Переносим адр2 в адр1 ) PAD 2 + SWAP PAD @ MOVE ( Переносим PAD+2 в адр2 ) ;
Блок 51 [51 :0] ( Глава 6. Упражнения 3. 01 из 02 ) ( 1.) CVARIABLE CREATE 1 ALLOT ; 4VARIABLE CREATE 4 ALLOT : ( 2. ) ARRAY CREATE 2 * ALLOT ; ( 3. ) : CARRAY CREATE ALLOT ; : 2ARRAY CREATE 4 * ALLOT ; : 4ARRAY CREATE 8 * ALLOT ; ( 4. ) VARIABLE 1LENGTH VARIABLE 2LENCTH 12 CONSTANT 1->2 : CONVERT1-2 1LENGTH @ 1->2 * 2LENGTH ! ; : CONVBRT2-1 2LENGTH @ 1->2 / 1LEHGTH ! ; ( Сменить коэффициент можно путем замени 12 [см. выше] и рекомпиляции или путем n ' 1->2 ! [79]. или n ' {или [']} >BODY ! [83])
Блок 52 [52 :0] ( Глава 6. Упражнения 3. 02 из 02 ) ( 5.) : X->Y ( x -- у ) A @ * В @ + ; ( Замена операций путем изменения содержимого переменных А и В соответствует n А ! или n В ! .) ( 6.) ( В Форт-79 здесь нет различия. Оба засылают в стек адрес, где запомнено значение. ' >BODY является эквивалентом для Форт-83. ) ( 7.) : SET-FEET ( -- ) 12 ['] TO-INCHES >BODY ! ; : SET-YARDS ( -- ) 36 ['] TO-INCHES >BODY ! ; ( в описании-двоеточии должно использоваться ['] вместо '.)
Блок 53 [53 :0] ( Глава 6. Упражнения 4. 01 из 01 ) ( 1.) : SIR ." Dear Sir:" ; : MADAM ." Dear Madam:" ; : SORM ." Dear Sir or Madam:" ; VARIABLE HELLO : SALUTATION HELLO @ EXECUTE ; ( Смена приветствия путем FIND HELLO ! в Форт-79 или ' HELLO ! в Форт-83. ) ( 2. ) CREATE CHOICES FIND PRINT , FIND CRT , FIND PCRT , ( В Форт-83 используйте ' вместо FIND. ) : CHOOSE ." PRINTER (1), SCREEN(2), OR BOTH(3) " KEY 49 - 0 МАХ 2 MIN 2 * CHOICES + 6 EXECUTE ; ( Нажатие клавиши преобразуется в число от 0 до 2 путем вычитания значения ASCII 1 [49]. 0 МАХ 2 MIN гарантирует, что неверное нажатие клавиши не выведет из строя систему. )
Блок 54 [54 :0] ( Глава 6. Упражнения 4. 02 из 06 ) ( 3. ) VARIABLE COUNT1 VARIABLE COUNT2 VARIABLE COUNT3 0 COUNT1 ! 0 COUNT2 ! 0 COUNT3 ! VARIABLE WT1 VARIABLE WT2 VARIABLE WT3 0 WТ1 ! 0 WT2 ! 0 WT3 ! ( 4а. ) : CLASS ( wt -- n ) 100 / 0 MAX 2 MIN ; ( 4b. ) CREATE COUNTADDRS COUNT1 , COUNT2 , COUNT3 , CREATE WTADDRS WT1 , WT2 , WT3 , ( 4c. ) : BW ( wt --) DUP CLASS 2 * DUP 1 SWAP COUNTADDRS + @ +! WTADDRS + @ +! ;
Блок 55 [55 :0] ( Глава 6. Упражнения 4. 03 из 06 ) ( 4d. ) : SUMMARY ( n -- ) CR ." In class " DUP . ." there are " 2 * DUP COUNTADDRS + @ @ . ." bolts with a total weight of " DUP WTADDRS + @ @ . CR ." and а mean weight of " DUP WTADDRS + @ @ SWAP COUNTADDRS + @ @ / . ." ." CR ; ( 5. ) CREATE COUNT 0 , 0 , 0 , CREATE WEIGHTS 0 , 0 , 0 , : NBW ( wt -- ) DUP CLASS 2 * DUP 1 SWAP COUNT + +! WEIGHTS + +! ; ( Упражн. 4х5 иллюстрируют 2 способа сделать одну и ту же вещь. 4 также показывает насколько легко использовать переменные совместно с таблицами. Обратите внимание на сходство описания WT и NWT. NWT лучше, так как оно требует меньше памяти и времени, хотя 4 имеет небольшое преимущество контроля значений с помощью, например, WT1 @ . .)
Блок 56 [56 :0] ( Глава 6. Упражнения 4. 04 из 06 ) ( 5. ) : SMPHRASE ." under 200 grams, is " ; : MDPHRASE ." 100 to 200 grams, is " ; : LGPHRASE ." more then 200 gramss, is " ; CREATE PHRASES FIND SMPHRASE , FIND MDPHRASE , FIND LGPHRASE , ( В Форт-8З следует использовать ' вместо FIND . ) ( 6. ) 0 CONSTANT SMALL 2 CONSTANT MEDIUM 4 CONSTANT LARGE : SAY ( n -- n ) DUP PHRASES + @ EXECUTE ; : CNT ( n -- ) ." Total count, " SAY COUNT + @ . ; : WEICHT ( n -- ) ." Total weight, " SAY WE1GHTS + @ . ; : AVERAGE ( n -- ) ." AVERAGE weight, " SAY DUP WEIGHTS + @ SWAP COUNTS + @ / . ; ( SAY удаляет ненужные части следующих слов; эта техника, наэываемая разбором, широко используется в Форте.)
Блок 57 [57 :0] ( Глава 6.
Упражнения 4. 05 из 06) ( 7a. ) CREATE DVORAK 32 С, 123 С, 95 С, 37 С, 35 С, 33 С, 41 С, 45 С, 36 С, 94 С, 64 С, 43 С, 87 С, 56 С, 86 C, 90 С, 54 С, 91 С, 55 С, 53 С, 51 С, 49 С, 57 С, 48 С, 50 С, 52 С, 83 С, 115 С, 119 С, 61 С, 118 С, 122 С, 38 С, 65 С, 88 С, 74 С, 69 С, 62 С, 85 С, 73 С, 68 С, 67 С, 72 С, 84 С, 78 С, 77 С, 66 С, 82 С, 76 С, 34 С, 80 С, 79 С, 89 С, 71 С, 75 С, 60 С, 81 С, 70 С, 58 С, 47 С, 92 С, 93 С, 40 С, 42 С, 96 С, 97 С, 120 С, 106 С, 101 С, 46 С, 117 С, 105 С, 100 С, 99 С, 104 С, 116 С, 110 С, 109 С, 98 С, 114 С, 108 С, 63 С, 112 С, 111 С, 121 С, 103 С, 107 С, 44 С, 113 С, 102 С, 59 С, 63 С, 124 С, 125 С, 126 С, ( Это таблица преобразования клавиатуры QWERTY в Dvorak. Она начинается с пробела [ASCII 32], который имеет идентичиое значение для обоих типов клавиатуры. )
Блок 58 [58 :0] ( Глава 6. Упражнения 4. 06 из 06 ) ( 7и. ) : DKEY KEY DUP 31 > OVER 127 < AND IF 32 - DVORAK + С@ THEN ; ( DKEY используется также, где обычно ) ( 8. ) ( применяется KEY ) CREATE KEYBD FIND DKEY , FIND KEY , VARIABLE ?KBD ( В Форт-83 используйте ' вместо FIND. CONSTANT было бы быстрее, но скорость здесь не важна.) : CHOICE CR ." Type 1 for Dvorak" CR ." Type 2 for QWERTY" KEY DUP 49 - 0 MAX 1 MIN 2* KEYBD + @ ?KBD ! ; : NEWKEY ?KBD @ EXECUTE ; : TEST BEGIN NEWKEY EMIT 0 UNTIL ; ( Используется для теста клавиатуры. )
Блок 59 [59 :0] ( Глава 6. Упражнения 5. 01 из 03 ) ( 1.) 9 ARRAY NUMBERS : CLASS ( вес -- ) 10 / 0 МАХ 9 MIN ; : COUNTS ( n -- ) CLASS 1 SWAP NUMBERS +! ; ( 2.) 9 ARRAY WTSUM : !W ( вес счет -- ) DUP CLASS WTSUM +! ; ( 3. ) : .TOT-#S CR 10 0 DO I NUBMERS @ 6 .R LOOP ; : .TOT-WT CR 10 0 DO I WTSUM @ 6 .R LOOP ; : .AVE-WT CR 10 0 DO I WTSUM @ ( 4. ) I NUMBERS @ / 6 .R LOOP ; : !WT ( w -) DUP COUNTS !W .TOT-WT .TOT-#S .AVE-WT ; : INIT 10 0 DO DO 0 I WTSUM ! 0 I NUMBERS ! LOOP ; ( Используйте INIT для начального обнуления массива)
Блок 60 [60 :0] ( Глава 6. Упражнения 5. 02 из 03 ) 1 8 ARRAY NUMWTS : CLASS ( вес --) 10 / 0 МАХ 9 MIN ; ( Аналог.
CLASS ранее) : COUNTS ( вес ) CLASS 1 0 ROT NUMWTS +! ; : !W ( wt -- ) DUP CLASS 1 SWAP NUMWTS +! ; : .TOT-#S CR 10 0 DO 0 I NUMWTS @ 6 .R LOOP ; : .TOT-WT CR 10 0 DO I 1 NUMWTS @ 6 .R LOOP ; : .AVE-WT CR 10 0 DO 1 I NUMWTS @ 0 1 NUMWTS @ / 6 .R LOOP ; : !WT ( w -- ) DUP COUNTS !W .TOT-WT .TOT-#S .AVE-@T ; ( Это идентично предшествующему примеру. ) : INIT 10 0 DO 0 0 I NUMWTS ! 0 1 NUMWTS ! LOOP ; ( Заметьте, насколько это похоже на предшествующее упражн. Использование матрицы экономит немного место за счет потери некоторого времени. Реальное преимущество матрицы сказыва- ется при размерности больше двух или, когда для математических расчетов необходима матричная алгебра. )
Блок 61 [61 :0] ( Глава 6. Упражнения 5. 03 из 03 ) ( 6.) CREATE NEWSTACK 32 ALLOT VARIABLE STACKPOS NEWSTACK STACKPOS ! : XPUSH ( n -- ) STACKPOS @ ! 2 STACKPOS +! ; : XPOP ( -- n ) -2 STACKPOS +! STACKPOS @ @ ; : PUSH ( n -- ) STACKPOS @ NEWSTACK 28 + MIN STACKPOS ! XPUSH ; : POP ( -- n ) STACKPOS @ NEWSTACK 2 + MAX STACKPOS ! XPOP ; ( Когда стек станет пуст, POP выдаст число, которое было занесено туда последним. Аналогично, когда стек заполнен, PUSH перезапишет верхнее число, в то время как XPUSH и XPOP разрушит систему из-за переполнения стека. ) ( 7. ) : NEWDROP POP DROP ; ( Форт в действительности контроли-) : NEWDUP POP PUSH PUSH ; ( рует стек аналогичным образом ) : NEWSWAP POP POP SWAP PUSH PUSH ;
Блок 62 [62 :0] ( Глава 7. Упражнения 1. 01 из 03 ) ( 1. ) : NEW> ( n1 n2 -- f ) SWAP < ; : NEW0< ( n -- f ) 0 < ; : NEW0> ( n -- f ) NEGATE NEW0< ; : NEW= ( n1 n2 -- f ) - 1+ NEW0> ; : NEW= ( n1 n2 -- f ) OVER OVER NEW= AND ; : NEW<> ( n1 n2 -- f ) - DUP NEW0< SWAP NEW0> OR ; : NEW0= ( n -- f ) 0 NEW= ;
Блок 63 [63 :0] ( Глава 7. Упражнения 7. 02 из 03 ) ( 2. ) ( а. = ROT ROT = AND б. ROT ROT = OR в. <> ROT ROT = AND г. <> ROT ROT = OR д. 0 ROT ROT <> AND е. = ROT ROT = XOR ж. < ROT ROT > AND з. OVER = SWAP ROT = AND и. OVER < SWAP ROT < AND ) ( 3. ) : ?REM=0 MOD 0= ; ( 4. ) : ?REM MOD 0= NOT ; ( 5. ) : ?OPPOSITE + 0= ; : NEW= - 0= ;
Блок 64 [64 :0] ( Глава 7. Упражнения 1. 03 из 03 ) ( 6.) : D= D- 0= SWAP 0= AND ; ( 7.) : COMP DUP 0< NEGATE SWAP 0> + ; \ Блок 65 [65 :0] ( Глава 7. Упражнения 2. 01 из 02 ) ( 1.) : NEWABS ( -n -- n ) или n -- n ) ?DUP 0< IF NEGATE THEN ; ( 2.) : /0? ( n --) ?DUP IF / ELSE DROP ( 3. ) ." Divide by 0 error" THEN ; : TYPE IF TYPE ELSE DROP DROP THEN ; (v4.) : 1TASK ." A " ; : 2TASK ." T " ; : 3TASK ." X" ; : ?WHICH-TASK ( -- ) KEY DUP 65 = IF 1TASK ELSE DUP 84 = IF 2TASK ELSE DUP 88 = IF 3TASK THEN THEN THEN DROP ;
Блок 66 [66 :0] ( Глава 7. Упражнения 2. 02 из 02 ] ( 5.) : UPPER-KEY ( -- с) KEY DUP 96 > OVER 123 < AND IF 223 AND THEN ; ( 6.) : NO-CONT-KEY ( -- с ) UPPER-KEY DUP 32 < IF 64 + THEN ; ( 7.) : ALPHA-KEY ( -- с ) NO-CONT-KEY DUP 47 > OVER 58 < AND IF DROP THEN ; ( 8. ) : D->S ( d -- n или d -- d ) ?DUP IF THEN : ( : D->S DUP 0= IF DROP THEN ; ( также работает, но медленнее.)
Блок 67 [67 :0] ( Глава 7. Упражнение 3. 01 и 03 ) ( 1. ) : ?END ." Do you want to quit? (Y/N) " КEY 89 = IF CR ." Do you want to save the stack? (Y/N) " KEY 118 = IF ." OK" CR QUIT ELSE ." OK" CR ABORT THEN THEN ; ( 2. Нет. ) ( 3. ) ( Первое остановит программу, вторые два обеспечат только выход из слова ?223 .) ( 4. Нет. ) ( 5. ) : 0? ( n1 -- n2) ?DUP 0= IF ." Number is zero " ( 6. ) ABORT THEN ; : =IF-ABORT = IF ABORT THEN ;
Блок 68 [68 :0] ( Глава 7. Упражнения 3. 02 из 03 ] ( 7. ) : +RANGE-ABORT ( n1 n2 -- n1 n2 ) OVER ОVER 0 ROT 0 D+ 0= 0= IF ." Add overflow error ? " ABORT THEN ; ( 8.) : +*RANGE-ABORT +RANCE-ABORT ОVER ОVER 0 ROT 0 D+ 0= 0= IF ." Multiply overflow error ? " ABORT THEN ; ( 9.) : STACK-TOO-BIG ( -- ) DEPTH 15 > IF ." Stack too big ?" ABORT THEN ;
Блок 69 [69 :0] ( Глава 7. Упражнения 3. 03 из 03 ) ( 10a. ) : DOTHAT ." That " ; : DOOTHER ." Other " ; : 1TASK ( n -- ) 0= IF DOTHAT THEN ; ( 10b. ) : 2TASK ( n -- ) IF DOTHAT THEN ; ( 10c. ) : 3TASK ( n -- ) 0= IF DOTHAT ELSE DOOTHER THEN ; ( 10d. ) : 4TASK ( n -- ) IF DOTHAT DOOTHER THEN ;
Блок 70 [70 :0] ( Глава 7. Упражнения 4. 01 из 02 ) ( 1.) CREATE МАТH , FIND + , FIND - , FIND * , FIND / , ( В Форт-83 используйте ' вместо FIND ) : ARITH ( n1 n2 n3 -- ) 1- 2* MATH + @ EXECUTE ; : ARITH' ( n1 n2 n3 -- n4 ) DUP 0 > ОVER 5 < AND IF 1- 2* MATH + @ EXECUTE ELSE DROP DROP DROP 0 ТHEN ; ( 2.) : ARITH-' ( n1 n2 n3 -- n4 ) NCASE 1 3 4 " + - * / OTHERWISE DROP DROP 0 CASEND ; ( 3. ) : NEWARITH ( n1 n2 -- n3 ) KEY ACASE +-*/" + - * / OTHERWISE DROP DROP 0 CASEND ;
Блок 71 [71 :0] ( Глава 7. Упражнения 4. 02 из 02 ) ( 4. ) : NEWARITH' KEY DUP 43 = IF DROP + ELSE DUP 45 = IF DROP - ELSE DUP 42 = IF DROP * ELSE 47 = IF / ELSE DROP DROP 0 THEN THEN THEN THEN ; ( 5.) : NEWARITH'' ( --) KEY ACASE +-*/" + - * / OTHERWISE ." Incorrect input " ABORT CASEND ; ( 6. ) ( Если вы хотите изменить задание путем замены содержимого вектора. Векторное исполанениее кроме того быстрее. )
Блок 72 [72 :0] ( Глава 8. Упражнения 1. 01 из 06 ) ( 1. ) : ASCIITAB ( - ) CR 1+ SWAP DO I 4 .R 3 SPACES I EMIT CR LOOP ; ( 2. ) : ASCIICHAR ( -- ) CR 33 9 0 DO 10 0 DO DUP I J 10 * + + 4 .R LOOP CR 10 0 DO DUP I J 10 * + + 3 SPACES EMIT LOOP CR LOOP DROP : ( 3. ) : ZERDIAG ( адр -- ) 5 0 DO DUP I 5 * I + 2 * + 0 SWAP ! LOOP DROP ;
Блок 73 [73 :0] ( Глава 8. Упражнения 1. 01 из 06 ) ( 4.) : SUMCOL ( адр -- n1 n2 n3 n4 n5 ) 5 0 DO 0 SWAP 5 0 DO DUP I J + 2 * + @ ROT + SWAP LOOP LOOP DROP ; ( 5.) : X^5 ( n - n^5 ) DUP 4 0 DO OVER * LOOP ;
Блок 74 [74 :0] ( Глава 8. Упражнения 1. 03 из 06 ) ( 6.) : D** ( n1 n2 -- ) ?DUP 0= IF DROP 1. - ELSE DUP 1 = IF DROP 0 ELSE 0 ROT 0 2SWAP 2OVER 2SWAP DROP 1- 0 DO 2OVER D* LOOP 2SWAP 2DROP THEN THEN ( 7. ) : DUMP ( адр -- ) CR BASE @ SWAP HEX 10 0 DO 16 0 DO DUP I J 16 * + + C@ 3 .R LOOP CR LOOP DROP BASE ! ;
Блок 75 [75 :0] ( Глава 8. Упражнения 1 04 из 06 ) ( 8. ) : DUMP2 CR BASE @ SWAP HEX 10 0 DO DOP I 16 * + 5 .R 16 0 DO DUP I J 16 * + + С@ 3 .R LOOP CR LOOP DROP BASE ! ; ( 9. ) : DUMP3 ( адр -- ) CR BASE @ SWAP HЕХ 5 0 DO DUP I 16 * + 5 U.R 16 0 DO DUP I J 16 * + + С@ 3 .R LOOP CR 5 SPACES 16 0 DO DUP 2 SPACES I J 16 * + + С@ DUP 32 < OVER 127 > OR IF DROP 46 EMIT ELSE EMIT THEN LOOP CR LOOP DROP BASE ! ;
Блок [76 :0] ( Глава 8. Упражнения 1. 05 из 06 ) ( 10. ) VARIABLE SPCS : COUNT-SPCS ( адр --) 1024 0 DO DUP I + С@ 32 = IF 1 SPCS +! THEN LOOP DROP ; ( 11. ) CREATE ALPHA 52 ALLOT : ALPHACOUNT ( адр cw -- ) ALPHA 52 0 FILL ( очистка массива ) 0 DO DUP I + С@ 223 AND DUP 64 > OVER 91 < AND IF 65 - 2 * ALPHA + 1 SWAP +! ELSE DROP THEN LOOP DROP ;
Блок 77 [77 :0] ( Глава 8. Упражнения 1. 06 из 06 ) ( 12. ) : LETTERBAR ( n с --) SWAP 0 DO DUP EMIT LOOP DROP ; : ALPHALLOT ( -- ) ALPHA CR 26 0 DO DUP I 2 * + @ ?DUP 0= 0= IF I 65 @ LETTERBAR CR THEN ; LOOP DROP QUIT ; ( 13. ) : .S ( -- ) DEPTH ?DUP 0= IF ." Stack empty" ELSE 0 DO DEPTH ROLL DUP . LOOP THEN ;
Блок 78 [78 :0] ( Глава 8. Упражнения 2. 01 из 03 ) ( 1. ) : .ARR ( адр n --) 2 * CR 0 DO DUP I + @ . ( 2. ) 2 +LOOP DROP ; : .SQARR ( адр n -- ) 2 * CR DUP @ DO DUP 0 DO OVER I J + + @ . 2 +LOOP CR 2 +LOOP 2DROP ; ( 3. ) : D.ARR ( адр n --) 4 * CR 0 DO DUP I + 2@ D. 4 +LOOP DROP ; : D.SQARR ( адр n -- ) 4 * CR DUP 0 DO DUP 0 DO OVER J + + 2@ D. 4 +LOOP CR 4 +LOOP 2DROP ; ( Может покаэаться более естественным использовать LOOP и 2 * или 4 * для вычисления адреса извлекаемого элемента, но быстрее использовать +LOOP и делать умножение только раз в начале описания. )
Блок 79 [79 :0] ( Глава 1. Упражнения 2. 02 из 03 ) ( 4. ) : F-C ( -- ) СК 201 0 DO I 6 .R I 32 - 5 9 */ 6 .R CR 10 +LOOP ; ( Заметьте, что хотя шаг цикла равен 10, предел цикла должен только на 1 превосходить верхнюю ступеньку. ) ( 5. ) : F-C1 ( -- ) CR 0 200 DO I 6 .R I 32 - 5 9 */ 6 .R CR 10 +LOOP ; ( 6. ) : FINDCHAR ( адр1 с -- адр2 ) 1024 0 DO OVER OVER SWAP 1 + C@ IF DROP I + LEAVE THEN LOOP ;
Блок 80 [80 :0] ( Глава 8. Упражнения 2. 03 из 03 ) ( 7. ) : $= ( адр1 адр2 -- f ) OVER OVER C@ SWAP C@ = 0= IF 0 DROP DROP EXIT THEN 1+ SWAP 1+ DUP 1- С@ 0 DO OVER OVER I + C@ SWAP I + C@ = 0= IF DROP DROP 0 LEAVE THEN LOOP DUP IF DROP DROP 1 ( или -1 для Форт-03) THEN ; ( 8. ) : SEARCH ( адр @адр -- f ) 1+ DUP 1- C@ 0 DO OVER OVER 1 + C@ SWAP I + C@ = 0= IF DROP DROP 0 LEAVE THEN LOОР DUP IF DROP DROP 1 ( или -1 ) THEN ; : $FIND ( адр $адр - адр ) 1024 0 DO SWAP 1+ SWAP OVER OVER SEARCH IF LEAVE THEN LOOP DROP ;
Блок [81 :0] ( Глава 1. Упражнение 3. 01 из 02 ) ( 1. ) : .4PICK ( n1 n2 n3 n4 -- n2 nЗ n4) >R >R >R . R> R> R> ; ( Do-loop нельзя применять, так как там испольэуется стек возвратов ) ( 2.) : DUP1 ( n -- n n ) >R R@ R> ; ( 3. ) : J1 R> R> R> R@ SWAP >R SWAP >R SWAP >R ; ( 4. ) : К R> R> R> R> R> R@ SWAP >R SWAP >R SWAP >R SWAP >R SWAP >R ; ( 5. ) : J' R> R> R> R> R@ SWAP >R SWAP >R SWAP >R SWAP >R ;
Блок 82 [82 :0] ( Глава 8. Упражнения 3. 02 из 02 ) ( 6. ) : NEWLEAVE R> R> DROP R@ >R >R ; ( 7.) ( Выполним R> R> DROP, чтобы записать новое число, а затеи исполним >R >R . ) ( 8.) ( I 10 = IF R> DROP 15 >R THEN ) ( 9. ) : +INDEX ( n -- ) R> SWAP R> + >R >R ; ( 10. ) ( Возникает бесконечный цикл, так как индекс всегда увеличивается до того, как он будет уменшен оператором LOOP. Такям образом индекс никогда не достигнет предела.)
Блок 83 [83 :0] ( Глава 8. Упражнение 4. 01 из 02 ) ( 1.) VARIABLE N 1000 N ! : POPSIZE CR 501 0 DO N @ 200 / N +! I 1+ 50 MOD 0= IF I 1+ ." Day " U. N @ ." Size " U. CR THEN LOOP 1000 N ! ; ' ( Население не может увеличиватся менее чем на 200 ) ( 2. ) 1000 N ! : POPDOUBLE CR N @ 2 * 10000 0 DO N @ 200 / N +! DUP N @ R BEGIN DEPTH ROLL DUP . R> 1- >R R@ 0= UNTIL R> DROP ; ( 2. ) : ST-SUM ( -- ) DEPTH 0= IF 0 EXIT THEN 0 BEGIN + DEPTH 1 = UNTIL ; : ST-SUM1 ( -- ) DEPTH 0= IF 0 EXIT THEN DEPTH 1 = IF EXIT THEN DEPTH 1- 0 DO + LOOP ; ( 3. ) VARIABLE TOTAL 500 TOTAL ! : ?YEAR1 ( -- ) CR BEGIN DUP 4 .R TOTAL @ 10 / DUP 6 .R TOTAL +! 1+ TOTAL @ DUP 6 .R CR 1000 >
UNTIL 500 TOTAL ! ;
Блок 86 [86 :0] ( Глава 8. Упраиенмя 5. 02 из 03) ( 4.) VARIABLE #MICE 1000 #MICE ! : MICE ( -- ) #MICE @ 2 * 0 BEGIN #MICE @ 200 / #MICE +! 1+ OVER #MICE @ 0= IF DROP DROP EXIT ELSE KEY DUP 13 <> TNEN WHILE DUP 8 = ( если backspace ... ) IF NEISPAN @ 0 = ( если NEWSPAN равен 0, игнорируем) IF DROP ELSE -1 NEWSPAN +! EMIT THEN ELSE DUP EMIT OVER NEWSPAN @ + C! 1 NEWSPAN +! THEN REPEAT DROP DROP DROP ; ( Из цикла do-loop не просто осуществить условный переход туда, куда надо и выйти, так как мы это здесь сделали.)
Блок 89 [89 :0] ( Глава 9. Упражнения 1. 02 из 04 ) ( 3.) : .ЕУЧЕ ( адр -- ) 0 BEGIN OVER OVER + С" 0= 0= WHILE 1+ REPEAT 1+ TYPE ; ( В Форт-83 вы можете просто выдать адр SPAN @ TYPE ) ( 4.) CREATE $SPACE 258 ALLOT ( оставляет 2 байта для нулей ...) : GET$ ( -- адр ) $SPACE 1+ 255 OVER OVER BLANK ." ?" OVER OVER EXPECT -TRAILING $SPACE C! 1- ; ( В Форт-83 это описание будет иметь вид: ) : 83GET$ ( - $адр ) $SPACE 1+ 258 ." ?" EXPECT $SPACE DUP SPAN @ SWAP C! ;
Блок 90 [90 :0] ( Глава 9. Упражнения 1. 03 из 04 ) ( 5. ) : ADD$ ( - $адр) $SPACK C@ 255 < IF $SPACE DUP C@ + 255 $SPACE C@ - 1+ OVER OVER BLANK ." ?" OVER OVER EXPECT -TRAILING $SPACE C@ + $SPACE С! DROP $SPACE ELSE ." Not enough room to add string " $SPACE EXIT THEN ; ( 6.) : ADD$1 ( -- $адр ) $SPACE C@ 255 < IF $SPACE DUP C@ + 255 $SPACE C@ - 1+ OVER OVER BLANK ." ?" OVER OVER EXPECT -TRAILING $SPACK C@ + DUP 254 > IF DROP DROP $SPACE EXIT THEN $SPACE C! DROP $SPACE ELSE ." Not enough room to add string! " $SPACE EXIT THEN ;
Блок 91 [91 :0] ( Глава 9. Упражнения 1. 04 из 04 ) ( 7.) : !DOLLAR$ ( d --) SWAP OVER DABS DUP $SPACE C! $SPACE 1+ SWAP CMOVE ; ( 8. ) : LEFT$ ( адр1 n -- адр2 ) OVER C@ MIN DUP ROT 1+ PAD 1+ ROT CMOVE PAD C! PAD ;
Блок 92 [92 :0] ( Глава 9. Упражнения 2. 01 из 02 ) ( 1. ) ( следует ли нам заботиться о начальном обнулении длины ? ) : VARIABLE ( n -- ) CREATE ALLOT ; ( 2. ) : $! ( $адр1 $адр2 -- ) OVER C@ 1+ CMOVE ; ( 3. ) : $GET ( $адр -- ) 34 WORD SWAP OVER C@ 1+ CMOVE ; ( 4. ) : $" ( --$адр ) 34 WORD PAD OVER C@ 1+ CMOVE PAD ; ( 5. ) ( $" входная строка" $SPACE $! , например, решает стоящую задачу.) ( 6. ) CREATE CHOCES $1 , $2 , $3 , $4 , $5 , $6 , $7 . $8 , $9 , $10 , ) : .$CHOISE ( n -- ) 1- 2 * CHOICES + @ COUNT TYPE ;
Блок 93 [93 :0] ( Глава 9. Упражнения 2. 02 из 02 ) ( 7. ) 2 20 $VARIABLE 1STRING 20 $VARIABLE 2STRINC 3 20 $VARIABLE 3STRING 20 $VARIABLE 4STRING CREATE NSTRING 1STRING , 2STRING , 3STRING , 4STRING , : PARSE$ TIB 80 BLANK 0 >IN ! ." ?" QUERY 4 0 DO 35 WORD I 2 * NSTRING + @ $! LOOP TIB 80 BLANK ; ( 8. ) : PARSE$1 TIB 80 BLANK 0 >IN ! ." ?" QUERY 4 0 DO 35 WORD >IN @ .
I 2 * NSTRING + @ #! LOOP TIB 80 BLANK ; ( > IN увеличивается оператором WORD по мере разбора ) ( входного потока . ) : (( 41 WORD DROP ; IMMEDIATE (( Это действительно работает? Если да, то все это будет проигнорирована и не будет дано сообщения об ошибке ) : TEST (( Nor will this. ) ;
Блок 94 [94 :0] ( Глава 9. Упражнения 3. 01 иэ 03 ) ( 1. ) : NEWLEFT$ ( $адр1 n -- $адр2 ) 1 SWAP MID$ ; : NEWRIGHT$ ( $адр1 n - $адр2) ( 2.) OVER C@ OVER - 1+ SWAP MID$ ; : $CHAR ( с -- $адр ) 1 PAD С! PAD 1+ С! PAD ; ( 3.) : 1STCHAR ( $адр -- с) 1+ C@ ; ( 4.) : $SWAP ( $адр1 $адр2 --) DUP DUP С@ 1+ PAD SWAP CMOVE OVER SWAP OVER С@ 1+ CMOVE PAD SWAP OVER C@ 1+ CMOVE ; : $SWAP1 ( $aдр1 $адр2 --) DUP PAD $! OVER SWAP $! PAD SWAP $! ; ( Преимущество MMSFORTH-пакета операторов для работы со строками очевядио. ) ( 5. ) ( LEFT$ RIGHT$ CHR$ ASC $XCHG )
Блок 95 [95 :0] ( Глава 9. Упражнения 3 02 из 03 ) ( 6. ) : BUILD$ ( $адр -- ) >R BEGIN INKEY$ DUP DUP $. $" #" $COMPARE WHILE R@ SWAP $+ R@ $! REPEAT R> DROP ; ( 7.) : BUILD$1 ( $адр -- ) IN$ SWAP $! ; ( 8. ) ( Но здесь не используется # ) ( Опишите 30 CONSTANT SIZE и используйте SIZE всюду, где используется 10 в STOREPHONE , GETPBONE к т.д. Преимущество заключается в легокости изменения размера словаря) ( 9. ) : SHOWPHONES ( -- ) CR 10 ( или SIZE ) 0 DO 1 0 PHONES $. 2 SPACES I 1 PHONES $. I AVAILABLE + C@ 0= IF LEAVE ELSE CR THEN LOOP ;
Блок 96 [96 :0] ( Глава 9. Упражнения 3. 03 из 03 ) ( 10. ) : ERASEALL ( --) ." Are you sure you want to do this? (Y/N)?" KEY 89 IF 0 0 PHONES 600 0 FILL AVAILABLE 10 0 FILL ELSE ." Not done." THEN ;
Блок 97 [97 :0] ( Глава 9. Упражнения 4. 01 из 03 ) ( 1. ) : $IN PAD 1+ 255 OVER OVER BLANK ." ?" OVER OVER EXPECT -TRAILING PAD C! 1- ; ( используется ниже ) : D#IN1 ( -- d ) 0 0 $IN CONVERT DROP ; : D#IN2 ( -- d ) BEGIN $IN DUP 0 0 ROT CONVERT 4 ( 3 в Форт-83 ) ROLL 1+ = WHILE ." REDO " DROP DROP REPEAT ; : D#IN3 BEGIN $IN DUP 1+ C@ 45 = DUP >R IF 2+ THEN DUP 0 0 ROT CONVERT ROT ROT R>
IF DNEGATE THEN 4 ( 3 в Форт-83 ) ROLL 1+ 4 ( 3 в Форт-83 ) ROLL = WHILE ." REDO " DROP DROP REPEAT ;
Блок 98 [98 :0] ( Глава 9, Упражнения 4. 02 из 03 ) (2. ) : #IN. ( -- n ) BEGIN $IN DUP 1+ C@ 45 = DUP >R IF 1+ THEN DUP 0 0 ROT CONVERT ROT ROT R>
IF DNEGATE THEN 4 ( 3 для Форт-83 ) ROLL 1+ 4 ( 3 ) ROLL DUP C@ 46 = 0= >R = WHILE R> DBOP ."REDO " DROP DBOP REPEAT R> IF DROP THEN ;
Блок 99 [99 :0] ( Глава 9. Упражнения 4. 03 из 03 ) ( 3.) : $IN3 ( -- n ) $IN NUMBER DROP ; ( 4. ) : INFIX ( -- n ) 32 WORD NUMBER DROP ; ( Это слово упрощает последующие описания. ) : PLUS ( n1 -- n2 ) INFIX + ; ( 5.) : TIMES ( n1 -- n2 ) INFIX * ; : DIVIDEDBY ( n1 -- n2 ) INFIX / ; : MINUS ( n1 -- n2 ) INFIX - ; : EQUALS ( n --) . ;
Блок 100 [100 :0] ( Глава 10, Упражнения 1. 01 из 01 ) ( 1.) ( Приведенное здесь описание продолжает работу после загрузки следущего блока. Предшествующее описание этого не делает и остаток блока будет проигнорирован. ( 2.) : N--> 0 >IN ! BLK @ ." Block " . ." loaded" CR 1 BLK +! ; ( 3. ) : LISTS ( n1 n2 -- ) OVER + SWAP DO CR CR ." Block " I DUP . LIST CR CR CR CR LOOP ; ( 4.) : ^ ( -- ) CR ." Block " BLK @ . >IN @ 64 /MOD ." Line " . ." Character " 2 - . CR ; IMMEDIATE ( 5. ) ( FORGET TASK 20 LOAD )
Блок 101 [101 :0] ( Глава 10. Упражнения 2. 01 из 02 ) ( 1. ) : SLOAD ( n -- ) DUP 2 MOD IF ." Can't load odd blocks" ABORT THEN LOAD ; ( или ) ( : SLOAD1 ) ( n -- ) ( DUP 2 MOD ABORT" Can't load odd blocks" LOAD ; ) ( 2. ) : SLIST ( n -- ) DUP 2 MOD + LIST ; ( 3. ) : SVIEW ( n -- ) DUP 2 MOD 0= - ( + в Форт-83 ) LIST ; ( 4. ) : SLISTS ( n1 n2 -- ) 2 * SWAP DUP 2 MOD + SWAP OVER + SWAP DO CR CR ." Block " I DUP . LIST CR CR CR CR 2 +LOOP ;
Блок 102 [102 :0] ( Глава 10. Упражнения 2. 02 из 02 ) ( 5. ) : SVIEWS ( n1 n2 ) 2 * SWAP DUP 2 MOD 0= - ( + в Форт-83 ) SWAP OVER + SWAP DO CR CR ." Block " I DUP . LIST CR CR CR CR 2 +LOOP : ( 6.) : S--> 0 >IN ! BLK +! ; IMMEDIATE
Блок 103 [ 103 :0] ( Глава 10. Упражнения 3. 01 из 02 ) ( 1. ) 100 CONSTANT PACFORTH ( 2. ) : GET FIND 2* @ LOAD ; ( В Форт-79 ) : GET1 ' >BODY @ LOAD ; ( В Форт-83 ) ( 3. ) : LOADIT ( адр -- ) DUP @ 0 DO DUP I 2 * + 2 * @ LOAD LOOP DROP ; ( 4. ) : SHOWBLOCKS ( адр -- ) DUP @ 0 DO DUP I 2 * + 2 + @ . LOOP DROP ;
Блок 104 [104 :0] ( Глава 10. Упражнения 3. 02 из 02 ) ( 5.) : ?BLOCKS ( адр -- ) CR DUP @ 0 DO DUP I 2 * + @ SHOWBLOCKS CR LOOP DROP ; ( 6. ) : LOADEM ( адр n -- ) 2 * 2 + @ LOADIT ;
Блок 105 [105 :0] ( Глава 10. Упражнения 4. 01 из 03 ) ( 1. ) : .LINE ( n1 n2 -- ) SWAP BLOCK SWAP 64 * + 64 -TRAILING TYRE ; ( 2.) : NEWINDEX ( n1 n2 -- ) OVER + SWAP DO CR I . I 0 .LINE LOOP ; ( 3.) : NEWLIST ( n -- ) 16 0 DO CR DUP I DUP 2 .R SPACE .LINE LOOP DROP ; ( 4. ) : BLLINE ( n1 n2 -- ) SWAP BLOCK SWAP 64 * + 64 32 FILL UPDATE ; ( 5.) : NEWTL ( n1 n2 -- ) 1+ SWAP DO CR SCR @ I DUP 2 .R SPACE .LINE LOOP ; : NEWLIST1 ( n -- ) SCR ! 0 15 TL ;
Блок 106 [106 :0] ( Глава 10. Упражнения 4. 02 из 03 ) ( 6. ) : CLEAR-BLOCK ( n -- ) BLOCK 1024 32 FILL UPDATE ; ( 7. ) : PP ( n1 n2 -- ) SWAP BLOCK SWAP 64 * + DUP 64 32 FILL 0 WORD 1+ DUP 1- C@ -TRAILING ROT SWAP CMOVE UPDATE ; ( 8. ) : NEWCOPY ( n1 n2 --) SWAP BLOCK SWAP BLOCK 1024 ( 9. ) CMOVE UPDATE ; : ( n1 n2 n3 -- ) 0 DO OVER OVER SWAP I' I - + 1- SWAP I' I - + 1- NEWCOPY LOOP DROP DROP ; ( но I' также не стандартное слово ) ( 11. ) : COPIES ( n1 n2 nЗ -- ) ROT ROT OVER OVER < IF ROT COPIES> ELSE ROT
IF ." Block overflow! " ABORT ELSE STORBLK @ BLOCK SWAP ARRLEN @ * + ARRLEN @ CMOVE UPDATE THEN ; ( 2.) CREATE ARRAY 1 , 2 , 3 , 4 , 5 , CREATE 2ARRAY 6 , 7 , 8 , 9 , 0 : PUTARRAYS ( адр1 адр2 n -- ) DUP 1+ 20 * 1024 >
IF ." Block overflow! " ABORT ELSE 20 * >R SWAP STORBLK @ BLOCK R@ + 10 CMOVE STORBLK @ BLOCK R> + 10 + 10 CMOVER UPDATE THEN ;
Блок 109 [ 109 :0] ( Глава 10. Упражнения 5. 02 из 05 ) ( 3.) 700 CONSTANT PATBLOCK ( Измените это, если нужно : PD ( n1 n2 n3 n4 -- ) DUP 1+ 2 * 1024 >
IF ." Block overrun " ABORT THEN 2 * > R PATBLOCK 2+ BLOCK R@ + ! UPDATE PATBLOCK 1+ BLOCK R@ + ! UPDATE PATBLOCK BLOCK R> + ! UPDATE ; : SD ( - n ) DUP 1+ 2 * 1024 >
IF ." Block overrun" ABORT THEN 2 * >R CR R@ 2 / ." Patient number " CR R@ PATBLOCK BLOCK + @ ." Weight " CR R@ PATBLOCK 1+ BLOCK + @ ." Systolic pressure " CR R@ PATBLOCK 2 + BLOCK + @ ." Diastolic pressure " . CR;
Блок 110 [110 :0] ( Глава 10. Упражнения 5. 03 из 05 ) ( 4.) VARIABLE CNT : SUMBLOCK ( n -- d ) 0 CNT ! BLOCK 0 0 RОТ 512 0 DO DUP I 2 * + @ DUP IF 1 CNT +! ТНEN SWAP >R 0 D+ R>
LOOP DROP : ( 5. ) : AVE ( n1 - n2 ) SUMBLOCK CNT @ 0 D/ DROP ; ( 6. ) : AD ( n -- ) PATBLOCK BLOCK @ 2 * 2+ DUP 1+ 1024 >
IF ." Block overrun" ABORT ELSE PATBLOCK BLOCK DUP >R + ! 1 R> ! UPDATE THEN ;
Блок 111 [111 :0] ( Глава 10. Упражнения 5. 04 из 05 ) ( 7.) : DD ( n -- ) 2 * 2+ PATBLOCK BLOCK DUP >R OVER + DUP 2- ROT 1024 SWAP - CMOVE -1 R> +! UPDATE ; ( 8. ) : SB ( n - d ) BLOCK DUP >R 0 0 ROT R> @ 0 DO DUP I 2 * + 2+ @ SWAP >R 0 D+ R> LOOP DROP ; : AV ( n1 -- n2) DUP SB ROT BLOCK @ 0 D/ DROP ; ( 9.) : SBS ( n -- d ) BLOCK DUP >R 0 0 ROT R> @ 0 DO DUP I 2 * + 6 + @ SWAP >R 0 D+ R> LOOP DROP ; : AVS ( n1 -- n2 ) DUP SBS ROT BLOCK @ 0 D/ DROP ;
Блок 112 [112 :0] ( Глава 10. Упражнения 5. 05 из 05 ) ( 9 продолж. ) : ADS ( n -- ) PATBLOCK BLOCK @ 2 * 6 + DUP 1024 2/ >
IF ." Block overrun" ABORT ELSE PATBLOCK BLOCK DUP >R + ! 1 R@ +! PATBLOCK SBS R> 2+ 2! UPDATE THEN ; : DDS ( n -- ) 2 * 6 + PATBLOCK BLOCK DUP >R OVER + DUP 2- ROT 1024 SWAP - CMOVE -1 R> + PATBLOCK SBS PATBLOCK BLOCK 2+ 2! UPDATE ; ( 10. ) : CHKSUM ( n1 -- n2 ) BLOCK 0 1024 0 DO OVER I + @ + 2 +LOOP SWAP DROP ; : ?BLK= ( n1 n2 -- флаг ) CHKSUM SWAP CHKSUM = ;
Блок 113 [113 :0] ( Глава 10. Упражнения 6. 01 из 02 ) ( 1.) : BLOCKWORD ( n1 n2 n3 -- #addr ) BLK @ >R >IN @ >R ROT BLK ! SWAP >IN ! WORD DUP C@ PAD SWAP 1+ CMOVE PAD R> >IN ! R> BLK ! ; ( 2.) VARIABLE POSITION 0 POSITION ! : BWORD ( n1 n2 -- #addr ) BLK @ >R >IN @ >R SWAP BLK ! POSITION @ >IN ! WORD DUP C@ PAD SWAP 1+ CMOVE >IN @ POSITION ! R> >IN ! R> BLK I PAD ; ( 3. ) : BLOCKNUMBER ( n1 n2 - n3) BWORD NUMBER DROP ;
Блок 114 [114 :0] ( Глава 10. Упражнения 6. 02 из 02 ) ( 4. ) : TOBLOCK ( n1 n2 n3 -- ) >R OVER SWAP BLOCK POSITION @ + >R 1+ OVER C@ R> OVER OVER + >R SWAP CMOVE C@ 1+ POSITION +! R> 1+ R> SWAP 1- С! UPDATE ; ( изменение POSITION полезно, т.к. оно позволяет размещать слова в блоке последовательно с минимальными издержками контроля. )
Блок 115 [115 :0] ( Глава 10. Упражнения 7. 01 из 02 ) ( 1. ) , : DATA-AVE ( первый последний т - средн. ) ROT ROT 1+ SWAP >R >R >R 0 0 R> R> R> OVER OVER - >R DO DUP I METFILE DATALOC @ SWAP >R 0 D+ R>
LOOP .S DROP R> 0 D/ DROP ; ( 2. ) : TEMP-AVE ( первый последний -- средн. ) 1+ SWAP >R >R 0 0 R> R> OVER OVER - >R DO I TEMP @ 0 D+ LOOP R> 0 D/ DROP ; ( 3. ) : CORRECT-TEMP ( начало последний -- ) 1+ SWAP DO I TEMP @ SWAP +! LOOP ;
Блок 116 [116 :0] ( Глава 10. Упражнения 7. 02 из 02 ) ( 4. ) : PUTFILE ( адр-файла адр-данных -- ) DUP @ 1+ 1 DO OVER OVER SWAP I 1- 1024 * + SWAP I 2 * @ BLOCK 1024 CMOVE UPDATE LOOP ; ( 5. ) : SAVEREC ( n1 n2 n3 n4 rec# -- ) DEPTH 5 < IF ." Wrong number of argument" ABORT ELSE METFILE RECLOC >R R@ 6 + ! R@ 4 + ! R@ 2+ ! R> ! THEN ;
Блок 117 [117 :0] ( Глава 10. Упражнения 8. 01 из 02 ) ( 1. ) VARIABLE DELIMITER 32 DELIMITER ! : GETWORD ( адр n -- PAD ) TIB PAD 100 + 80 CMOVE BLK ( Сохранение содержимого буфера) @ >R >IN @ >R TIB 80 0 FILL ( восстановление позднее) + TIB 64 CMOVE 0 BLK ! 0 >IN ! DELIMITER @ WORD DUP С@ 1+ PAD SWAP CMOVE PAD PAD 100 + TIB 80 CMOVE R> >IN ! R> BLK ! ; ( 2.) VARIABLE POSITION 0 POSITION ! : FILEWORD ( адр -- PAD ) TIB PAD 100 + 80 CMOVE BLK @ >R >IN @ >R TIB 80 0 FILL POSITION @ + TIB 64 CMOVE 0 BLK ! 0 >IN ! DELIMITER @ WORD DUP C@ 1+ PAD SWAP CMOVE PAD DUP С@ 1+ POSITION +! PAD 100 + TIB 80 CMOVE R> >IN ! R> BLK ! ;
Блок 118 [118 :0] ( Глава 10. Упражнения 8. 02 из 02 ) ( 3.) : FILENUMBER ( адр - n ) FILEWORD NUMBER DROP ; ( 4.) : TOFILE ( адр1 адр2 -- ) DUP >R POSITION @ + SWAP DUP C@ DUP POSITION +! SWAP 1+ ROT ROT CMOVE DELIMITER @ POSITION @ R> + C! 1 POSITION +! ;
Блок 119 [119 :0] ( Глава 10. Упражнения 8. 01 из 01 ) ( 1.) : NEXTFIELD ( -- ) ADDFILE FILEWORD DROP ; ( 2.) : NEXTREC ( -- ) 4 0 DO NEXTFIELD LOOP ; ( 3.) : FINDPHONE ( -- ) PAD SEARCH$ $! 0 POSITION ! SEARCH-NAME 3 0 DO NEXTFIELD LOOP ADDFILE FILEWORD COUNT TYPE ; ( 4. ) : DELREC ( -- ) POSITION @ ADDFILE + NEXTREC POSITION @ DUP >R ADDFILE + SWAP FINDEOF POSITION @ 4 + R> - CMOVE ;
Блок 120 [120 :0] ( Глава 11. Упражнения 1. 01 из 02 ) : 2VARIABLE ( -- ) CREATE 0 , 0 , ; : 2VARIABLE ( n -- ) CREATE , , @ @ ; ( Последнее описание не является стандартным ) ( 2.) : #CONSTANT ( -- ) CREATE 34 ЦORD DUP C@ 1+ ALLOT ; ( Строка переносится по адресу HERE с помощью WORD и резервируется память для поля параметров производного слова. ) ( 3. ) : RESERVE ( n --) CREATE DUP HERE SWAP ALLOT SWAP 0 FILL ; ( 4.) : BLOCKARRAY ( n -- ) CREATE , 1024 ALLOT ;
Блок 121 [121 :0] ( Глава 11. Упражнения 1. 02 из 02 ) ( 5.) : GETBLOCK ( адр - ) DUP @ BLOCK SWAP 2+ 1024 CMOVE ; ( 2+ необходимо, чтобы обойти откомпилированный номер блока.) : PUTBLOCK ( адр --) DUP @ BLOCK SWAP 2+ SWAP 1024 CMOVE UPDATE ; ( 6.) : B@ ( n1 адр -- n2 ) SWAP 2 * + @ ; : В! ( n1 n2 адр -- ) SWAP 2 * + ! ;
Блок 122 [122 :0] ( Глава 11. Упражнения 2. 01 из 04 ) ( 1. ) : 2CONSTANT CREATE , , DOES> DUP @ SWAP 2+ @ SWAP ; ( 2. ) : MAKEDATE ( месяц день год -- ) CREATE , , , DOES> >R R@ @ 0 TYPE ; ( 3. ) : COUNTER ( n -- ) CREATE , DOES> 1 SWAP +! ; ( Получение значения счетчика командами ' COUNTIT @ в орт-79 иля ' COUNTIT >BODY @ в Форт-83. ) ( 4. ) ( Введите производное слово оператора COUNTER в описание слова; счетчик будет инкрементироваться при каждом обращении. Сброс счетчика должен проводится при каждом запуске программы. )
Блок 123 (123 :t]: ( Глава 11. Упражнения 2. 02 из 04 ) ( 5.) : %COLOR ( n -- ) 100 255 */ 3 .R ." % " ; ( Это слово упрощает следующие три слова. ) : CYAN ( n - ) %COLOR ." Cyan " ; : YELLOW ( n - ) %COLOR ." Yellow " ; : MAGENTA ( n - ) %COLOR ." Magenta " ; : COLOR ( n1 n2 n3 -- ) CREATE ROT , SWAP , , DOES> >R R@ @ CYAN R@ 2+ @ YELLOW R> 4 + @ MAGENTA ; ( CYAN.
YELLOW и MAGENTA компилируются в таком порядке, чтобы упростить возврат при исполнении производных слов. ) ( 6. ) : QUADRATIC ( а b с -- ) CREATE ROT , SWAP , , DOES> >R DUP DUP * R@ @ * SWAP R@ 2+ @ * + R> 4 + @ + ; ( Заметьте, что коэффициенты компилируются в порядке a-b-c. )
Блок 124 [124 :0] ( Глава 11. Упражнения 2. 03 из 04 ) ( 7. ) : M-MENTEM ( n1 n2 -- ) CREATE SWAP , , DOES> >R DUP R@ @ * SWAP R> 2+ @ + / ; ( Для получения большей точности можно применять масштабирование. ) ( 8. ) : ** ( n1 n2 - n3 ) ?DUP 0= IF DROP 1 ELSE DUP 1 = IF DROP ELSE OVER SWAP 1- 0 DO OVER * LOOP SWAP DROP THEN THEN ; ( Это слово было описано в главе 8 и используется для упрощения описания POLYNOM ) ( Продолжеиие в следующем блоке )
Блок 125 [125 :0] ( Глава 11. Упражнения 2. 04 из 04 ) ( 8. продолж. ) : POLYNOM ( n1 n2 ... - ) CREATE DEPTH DUP , 0 DO DEPTH ( 1- в Форт-83 ) ROLL , LOOP DOES> DUP @ SWAP 2+ SWAP >R >R >R 0 R> R> R> 0 DO OVER OVER I 2 * + @ SWAP I ** * SWAP >R SWAP >R + R>
R> LOOP DROP DROP ; ( Коэффициенты компилируются в указанном порядке. "0" заносится на дно стека, чтобы получить результат, который вычислен в цикле do-loop. ) ( 9. ) : ( min max n -- ) CREATE , SWAP , , DOES> >R R@ @ R@ 2+ @ MAX R@ 4 + @ MIN DUP R> ! ; ( Заметьте, что "значение" производного слова скомпили- ровано в первую позицию, позволяя использовать ' или ' >BODY . )
Блок 126 [126 :0] ( Глава 11. Упражнения 3, 01 из 02 ) : DARRAY ( n -- ) CREATE 4 * ALLOT ) DOES> SWAP 4 * + ; ( 2. ) : 1ARRAY ( n - ) CREATE 2 * ALLOT DOES> SWAP 1- 2 * + ; : 1EARRAY ( n -- ) CREATE DUP , 2 * ALLOT DOES> >R 1- DUP 0 < OVER R@ @ 1- > OR IF ." Index error" ABORT ELSE 2 * 2+ R> + THEN ; ( 3. ) : 0CARRAY ( n --) CREATE HERE SWAP DUP ALLOT 0 FILL DOES> + ; ; 0ARRAY ( n -- ) CREATE HERE SWAP 2 * DUP ALLOT 0 FILL DOES> SWAP 2 * + ;
Блок 127 [127 :0] ( Глава 11. Упражнения 3. 02 из 02 ) ( 4.) : PRESERVE ( n1 n2 - ) CREATE DEPTH 0 DO DEPTH ROLL , LOOP ; ( Используйте 1- ROLL в Форт-83) ( 5.) : SAVE-TO-RETURN ( n1 n2 -- ) CREATE DEPTH DUP , 0 DO DEPTH ROLL , LOOP ( Используйте 1- ROLL в Форт-83 ) DOES> DUP @ 0 DO DUP I 2 * 2+ + @ SWAP LOOP DROP ; ( 6. ) : WORD >IN @ >R CREATE R> >IN ! 32 WORD C@ 1+ ALLOT DOES> COUNT TYPE ; ( Занося >IN в стек возвратов, WORD может проводить раэбор слова дважды.
Первый раз формируется заголовок, где имя используется оператором CREATE. Второй раз это же имя пересылается в поле параметров слова. )
Блок 128 [128 :0] ( Глава 13. Упражнения 1. 01 из 02 ) ( 1. ) : TASK ; Позволяет удалить редактор из словаря при отладке. DECIMAL гарантирует, что никая другая система счисления не будет случайно использована. ) ( 2. ) ( Это позволяет при редактировании изменить сразу ряд блоков.) ( 3. ) Таким образом задержки могут быть легко изменены. ) ( 4. ) ( Чтобы позволить с помощью констант определить задержки и сделать програмиу более читаемой ) ( 5. ) : PAGE 27 EMIT 42 EMIT ; ( или 27 42 CONTROL PACE ) : ЗЕС ( ряд столбец -- ) 27 EMIT 61 EMIT SWAP 32 + EMIT 32 + EMIT ;
Блок 129 [129 :0] ( Глава 13. Упражнения 1. 02 из 02 ) ( 6. ) 27 13 CONTROL 27 14 CONTROL 27 15 CONTROL ( Чтобы использовать достаточно убрать скобки) ( 7. ) ( Использование терминальных возможностей, а не их имитации обеспечивает большее быстродействие, более приятную работу и требует меньшей по размеру программы. )
Блок 130 [130 :0] ( Глава 13. Упражнения 2. 01 из 01 ) ( 1. ) Слова в KEYVECTORS предполагаются отлаженными, KEYDO может быть отлажено путем загрузки и исполнения про- граммы через KEYDO. Если оно не работает, нужно ис- пользовать в KEYDO слова-подставки. Блок 10 может быть отлажен при испытании EDITCASE, и затем, в конце концов, редактора. Здесь слова, если надо, могут использоваться слова-подставки. ) ( 2.) : CONTROL-CHAR? ( с -- с f ) DUP 27 < OVER 0 > AND ; : PRINTABLE-CHAR? ( с -- с t) DUP 31 > OVER 127 < AND ; : INSERT-MODE? ( - f ) I/R @ ;
Блок 191 [191 :0] ( Глава 14. Упражнения 01 из 02 ) ( 1. ) ( будет отображено FF FF 00 FF FF 00 10 00 ) ( 2.) ( ЭВМ видает FF FF FF FF 00 00 FF FF 00 00 FF 00 00 10 00 ) ( 3. ) ( WORD производят обычно раэбор по адресу HERE, поэтому область памяти между HERE и следующей используемой зоной, обычно PAD, может использоваться для разбора данных, поступающих с клавиатуры. ) ( 4.) : С, ( с -- ) HERE С! 1 DP +! ; : , ( n - ) HERE ! 2 DP +! ; ( DP, которая хранит указатель словаря, зависит от реализации и может в вашем Форт быть другая. )
Блок 132 [132:0] ( Глава 14. Упражнения 1. 02 из 02 ) ( 5. ) : .MEM ( -- ) SP@ PAD - U. ; ( SP@ зависит от реализации. ) ( 6. ) : NEWPICK ( n1 -- n2) 2 * 'S + @ ; ( 2 * SP@ 2- + @ FORTH-83 ) ( 'S [и SP@] зависят от типа версии. 2- необходимо для описания в Форт-83. так как там индекс PICK начинается с 0) ( 7. ) : NEW.S ( -- ) 'S S0 @ OVER OVER = IF ." Stack empty" DROP DROP ELSE 2- DO I @ -2 +LOOP THEN ; ( 8. ) : ZERО-STACK ( -- ) SP@ S0 @ SP@ - 2- 0 FILL ;
Блок 133 [133 :0] ( Глава 14. Упражнения 2. 01 из 01 ) ( 1. ) : >BODY ( cfa -- pfa) 2+ ; : >NAME ( cfa -- nfa) 6 - ; : >LINK ( cfa -- Ifa) 2- ; : BODY> ( pfa -- cfa) 2 - ; : NAME> ( nfa -- cfa) 4 + ; : LINK> ( lfa -- cfa) 2+ ; : N>LINK ( nfa -- lfa) 4 - ; : L>NANE ( lfa -- nfa) 4 - ; ( Это работает только с версиями Форта, описанными в этой книге. ) ( 2, 3. ) ( Вы должны найти ответы на эти вопросы, используя версию Форта, которой располагаете. ) ( 3. ) : COLONWORDS ( адр1 адр2 -- ) CR 1+ SWAP DO I @ 2526 = ( или другой адрес исполнительной программы "двоеточие" ) IF 1 U. THEN LOOP ;
Блок 134 [134 :0] ( Глава 14. Упражнения 3. 01 из 02 ) ( 1. ) ( Форма использование WORDS, где имя контекстного словаря. Использование полей связи и указателей словарей также обычные слова перед исполнением.) ( ???) ( 2. ) FORTH VOCABULARY A-VOC A-VOC DEFINITIONS : A-FIRST ." The first A-VOC word " ; : A-SECOND ." The second A-VOC word " ; : A-THIRD ." The third A-VOC word " ; : A-LAST ." The last A-VOC word " ; ( 3. ) WORDS лишь выдает список слов в контекстном словаре) ( 4. ) ( Слово из A-VOC не может быть найдено в словаре FORTH )
Блок 135 [135 :0] ( Глава 14. Упрахиеняи 3. 02 из 02 ) ( 5.) A-VOC VOCABULARY B-VOC В-VOC DEFINITIONS : B-FIRST ." The first В-VOC word " ; : B-SECOND ." The second B-VOC word " ; : B-THIRD ." The third B-VOC word " ; : B-LAST ." The last В-VOC word " ; ( Если A-LAST может размещаться в B-VOC, тогда ваши словари связаны.
В противном случае каждый из них связан только с Фортом. ) ( 6. ) В-МЩС DEFINITIONS : A-LAST ." Another В-VOC definition " ; ( Выберите одно из двух описании A-VOC и В-VOC. )
Блок 136 [136 :0] ( Глава 14. Упрахиения 4. 01 из 03 ) ( 2. ) : HELP CONTEXT @ [COMPILE] HELPS FIND ?DUP IF [ FIND HELPS ] LITERAL OVER U< IF EXECUTE ELSE DROP ." not in HELP list" THEN ELSE ." not found. " THEN CONTEXT ! ;
Блок 137 [137 :0] ( Глава 14. Упрахкения 4. 02 из 03 ) ( 2. ) VOCABULARY HELPS HELPS DEFINITIONS BLK @ 2+ CONSTANT BASEBLK ( указание на первый блок) 32 CONSTANT #/BLK ( число статей в блоке ) 32 CONSTANT LENGTH ( длина статьи ) : HELPER ( n -- ) CREATE , ( Создает индекс статей HELP) DOES> DUP #/BLK / BASEBLK + BLOCK SWAP LENGTH * + LENGTH -TRAILING TYPE CR ; 0 HELPER DUP 1 HELPER DROP 2 HELPER SWAP 3 HELPER OVER 4 HELPER ROT 5 HELPER - 6 HELPER + 7 HELPER * 8 HELPER / --> ( Каждое из слов должно соответствовать статье в блоке данных. )
Блок 138 [138 :0] ( Глава 14. Упражнения 4. 03 из 03 В след. блоке данные) ( 2. продолж. ) FORTH DEFINITIONS : HELP [COMPILE] HELPS FIND ?DUP IF [ FIND HELPS ] LITERAL OVER U< IF EXECUTE ELSE DROP ."Not in HELP list." THEN ELSE ." not found" THEN [COMPILE] FORTH ; ( Используйте ['] HELPS вместо [ FIND HELPS ] LITERAL в Форт-83. [COMPILE] необходимо только в Форт, где контекстные словари являются словами немедленного исполнения. ) ( Используйте редактор для запоминания описаний; следующий блок может служить примером. Эта версия HELP воспримет описания из любого числа блоков начиная с BASEBLK. )
Блок 139 [139 :0]
= stack ( n -- n n ) = stack ( n1 n2 -- n1 ) = stack ( n1 n2 -- n2 n1 ) = stack ( n1 n2 -- n1 n2 n1 ) = stack ( n1 n2 n3 -- n2 n3 n1 ) = substract ( n1 n2 -- n3 ) = add ( n1 n2 -- n3 ) = multiply ( n1 n2 -- n3 ) = divide ( n1 n2 -- n3 )
Блок 140 [140 :0]
( Глава 15. Упражнения 1. 01 из 02 ) ( 1.) : ?COMPILE ( CFA -- ) STATE @ IF , ELSE EXECUTE THEN ; ( 2. ) : 83COMPILE ( cfa f --) DUP 1 = IF DROP EXECUTE EXIT THEN STATE @ AND 0= IF EXECUTE ELSE , THEN ; ( В Форт-83 FIND производит поиск в словаре до компиляции или исполнения, т.е.
во время интерпретации. ) ( 3. ) : N' ( --cfa ) 32 WORD FIND 0= IF DROP ." Word net found " ABORT THEN ; ( 4.) : ?COMPILE ( cfa --) STATE @ IF DUP . , ELSE EXECUTE THEN ; ( 5.) ( >IN указывает на точку в тексте. откуда продолжается интерпретация после того, как слово найдено)
Блок 141 [141 :0] ( Глава 15. Упражнения 1. 02 из 02 ) ( 6.) Сброс указателя входного потока вызывает зацикливание) ( 7.) ( Цикл будет продолжаться, так как таковы условия во входном потоке ) ( 8. ) ( Будет отображено только 5, так как 0 во входном потоке остановит интерпретацию. )
Блок 142 [142 :0] ( Глава 15. Упражнения 2. 01 из 02 ) ( 2. ) Исполнительная программа "двоеточие" является одной и той же для всех слов типа двоеточие. Т.о. поле ) ( 3.) ( программы BASE? будет тем же самым. ) ( Используя FIND [ или ' ] в DUMP, вы мохете увидеть, что поля параметров содержат CFA слов описания. Вам следует уметь отслеживать исполнение.) ( 4. ) ( Если содержимое LF в 2DUMMY указывает на 1DUMMY, замените его содержимое так, чтобы оно указывало на то, на что указывает LF слова 1DUMMY. ) ( 5. ) ( Измените CFA в PFA слова 3DUMMY на CFA слова 1DUMMY. Чтобы 3DUMMY ничего не делало, занесите CFA слова EXIT в первую позицию поля параметров 3DUMMY. )
Блок 143 [143 :0] ( Глава 15. Упражнения 2. 02 из 02 ) ( 6. ) ( Выполним эксперимент и поменяем содержимое первой позиции в поле параметров на значение CFA слова, выполнение которого желательно. )
Блок 144 [144 :0l ( Глава 15. Упражнения 3. 01 из 02 ) ( 1.) : .LOC1 ( --) BLK @ . >IN @ . ; IMMEDIATE : .LOC2 ( --) BLK @ . >IN @ 64 / . ; IMMEDIATE ( 2. ) ( Помещаем это между [ и ] . ) ( 3. ) ( Помещаем это между [ ] или описываем ) : .SI .S ; IMMEDIATE ( 4. ) ( Вы выйдете из состояния компиляции и ; приведет к ошибке. ) ( 5. ) ( Да. [COMPILE] может компилировать обычные слова, но это избыточно. ) ( 6. ) ( Вы можете обнаружить в стеке 239, или ваш Форт контролирует при интерпретации ошибки и выдает сообщение об ошибке.)
Блок 145 [145 :0] ( Глава 15.
Упражнения 3. 02 из 02 ) ( 7. ) CREATE OPERATOR ] + - * / [ : MATH ( n1 n2 n3 - ) 1- 2 * OPERATOR + @ EXECUTE ; ( Понятно, что использование [ и ] является более простым способом формирования исполнительных векторов, чем применение FIND или ' .)
Блок 146 [146 :0] ( Глава 15. Упражнения 4. 01 из 01 ) ( 1. ) START-ЦHERE? BLK @ [COMPILE] LITERAL ; IMMEDIATE ( 2. ) : GET/# ( n -- ) PAD DUP 8 EXPECT 1- NUMBER DROP ; : ?RATE ." current rate? " GET# ; IMMEDIATE ( 3. ) : CONVERTS ( n -- ) ?RATE LITERAL 100 */ ; ( 4. ) : ENGLAND [ CR ." Dollars to pounds " ] ?RATE LITERAL 100 */ ; : DENMARK [ CR ." Dollars to kroners " ] ?RATE LITERAL 100 */ ; : GERMANY [ CR ." Dollars tо marks " ] ?RATE LITERAL 100 */ ; ( 6. ) : ?COMP ( -- ) STATE @ 0= IF ." Compile only! " ABORT THEN ; ( 7. ) ( TEST1 выполнит DUP . Выполнение TEST с клавиатуры даст ошибку, так как COMPILE содержат ?COMP . )
Блок 147 [147 :0] ( Глава 15. Упражнения 5. 01 из 03 ) ( 1.) ( Ячейка, предшествующая той, в которой записано чис- ло 3, содержит CFA слова LIT. Первая ячейка в поле параметров будет содержать CFA слова ЁBRANCH. Сразу вслед за ним следует число, абсолютный или относи- тельный адрес слова. Это позволит вам понять, как работает ЁBRANCH [с абсолютными или относительными CFA адресами]. После адреса следует CFA слова, ко- торое заносит в стек 1 и за которым следует CFA слова BRANCH. За числом 3 следует CFA слова EXIT.
Блок 148 [148 :0] ( Глава 15. Упражнения 5. 02 из 03 ) ( 2. ) : .CMP CR ." Here =" HERE U. ." Stack = " .S ; IMMEDIATE ( 3. ) HEX : LOOK-AT-lF ( f --) .CMP IF 1 .CМР ELSE 2 .CМР THEN .СМР 3 ; DECIMAL ( HERE увеличивается по мере компиляции ?BRANCH, LIT, адресов и чисел. Стек содержит число, которое зане сено туда оператором : ( и которое проверяется и удаляется оператором ; с целью проверки сохранности состояния стека]. За этим числом следует абсолютные или относительные адреса передачи управления, ис- пользуемые операторами ELSE и THEN.) ( 4. ) ( TEST выдаст на дисплей 5, так как LIT и 5 были скомпилированы в поле параметров с помощью [ 174 , 5 . ].)
Блок 149 [149 :0] ( Глава 15. Упражнения 5. 03 из 03 ) ( 5. ) ( Ниже предполагается, что 3301 равно CFA слова LIT. ) : NEWLITERAL ?СОМР 3301 , , ; IMMEDIATE : NEWDLITERAL ?COMP SWAP 3301 , , 3301 , , ; IMMEDIATE ( 6. ) ( Ниже предполагается, что 3265 равно CFA слова ?BRANCH и что 3257 равно CFA слова BRANCH. : NEWIF ?СОMР 3265 , HERE 0 , ; IMMEDIATE : NEWELSE ?COMP 3257 , HERE 0 , HERE ROT ! ; IMMEDIATE
Блок 150 [150 :0] ( Глава 15. Упражнения 6. 01 из 02 ) ( 1. ) : TASK ; FIND TASK @ CONSTANT COLON-ADDR ( ' TASK @ в 83 ) COLON-ADDR >R HERE R@ , ] ." Word#0 " EXIT [ CONSTANT 0WORD HERE R> , ] ." Word#1 " EXIT [ CONSTANT 1WORD ( 2. ) : ANYWORD [ 0WORD , 1WORD , ] ; ( 3. ) : MAKEWORD CREATE 1WORD , 0WORD , DOES> SWAP 2 * + @ EXECUTE ; ( 4. ) : GIVE-NAME CREATE , DOES> @ EXECUTE ; ( 5. ) COLON-ADDR HERE SWAP , ] ." Word#2 " EXIT [ : 2WORD [ SWAP , ] ; ( если в стеке хранится число, засланное оператором : . )
Блок 151 [151 :0] ( Глава 15. Упражнения 6. 02 из 02 ) ( 6. ) ( Опишите) : ?BR 3265 , ; : BR 3257 , ; ( или другие адреса ) ( Используется вместо COMPILE ?BRANCH и COMPILE BRANCH в описании IF и ELSE . ) ( 7. ) ( Опишите следующее ниже и используйте вместо COMPILE LIT в описании LITERAL . ) : LT 3301 , ; ( Или другой соответствующий адрес.)
Блок 152 [152 :0] ( Глава 15. Упражнения 7. 01 из 02 ) ( 1. ) ( Адреса являются ячейками в поле параметров, где запоминаются очередные CFA. подлежащие исполнению. ) ( 2. ) ( Осуществляется переход к завершению 3LEVEL. ) ( 3. ) ( Использование R> DROP в 3LEVEL удалит адрес, который определяет куда будет передано управление после выполнения слова, что вызовет сбой в работе внутреннего интерпретатора и разрушение системы. ) ( 4. ) ( WP можно использовать для получения значения константы) ( 5.) ( В обоих случаях EXIT передает управление в точку, откуда было осуществлено обращение к слову. )
Блок 153 [ 153 :0] ( Глава 15. Упражнения 7. 02 из 02 ) ( 6. ) ( LIT увеличивает число в стеке возвратов и ( удаляет из стека откомпилированное число.
Выполнение программы ." делает то же самое для скомпилированных строк. )
Блок 154 [154 :0] ( Глава 15. Упражнения 8. 01 из 01 ) ( 1. ) : FACTORIAL ( n -- ) 1 SWAP 1+ 2 DO I * LOOP ; ( 2. ) : SHOWASCII ( n1 n2 -- ) 1+ SWAP DO I 3 .R I SPACE EMIT 3 SPACES LOOP ; : SHOWASCII ( n1 n2 -- ) SWAP DUP DUP 3 .R SPACE EMIT 3 SPACES 1+ SWAP OVER OVER UNTIL DROP ; : RGENERATIONS ( 0 1 -- n ) SWAP 1+ SWAP DUP + DUP 2000 < IF MYSELF ELSE DROP THEN ;
Блок 155 [155 :0] ( Глава 16. Упражнения 1. 01 из 03 ) ( 1. ) ( Z80 DROP E1 NEXT POP HL NEXT 8088 DROP 5B NEXT POP BX NEXT Z80 DUP E1 E5 E5 NEXT POP HL PUSH HL PUSH HL NEXT 8088 DUP 5B 53 53 NEXT POP BX PUSH BX PUSH BX NEXT Z80 OVER E1 D1 D5 E5 D5 NEXT POP HL POP DE PUSH DE PUSH HL PUSH DE NEXT 8088 OVER 5B 5A 52 53 52 NEXT POP BX POP DX PUSH DX PUSH BX PUSH DX NEXT
Блок 156 [156 :0] ( Глава 16. Упражнения 1. 02 из 03 ) ( 2. ) ( HEX ASSEMBLER ) ( Z80 CREATE TUCK HERE DUP 2- ! E1 C, D1 C, E5 C, D5 С, E5 С, NEXT 8088 CREATE TUCK HERE DUP 2- ! 5В С, 5A C, 53 С, 52 С, 53 C, NEXT ( 3. ) ( Z80 CREATE NIP HERE DUP 2- ! E1 C, D1 C, D5 C, NEXT 8088 CREATE NIP HERE DUP 2- ! 5В С, 5A С, 53 С, NEXT ) ( 4. ) DECIMAL ( ROT = POP HL POP DE EX [SP],HL PUSH DE PUSH HL NEXT SWAP = POP HL EX [SP],HL PUSH HL NEXT
Блок 157 [157 :0] ( Глава 16. Упражнения 1. 03 из 03 ) ( 5. ) ( -ROT = POP HL POP DE EX (SP),HL PUSH HL PUSH DE NEXT -ROT = POP AX POP BX POP CX PUSH BX PUSH CX PUSH AX NEXT ) ( 6.) ( Приращение указателя стека осуществлятся командой INC SР или эквивалентной.)
Блок 158 [158 :0] ( Глава 16. Упражнения 2. 01 из 04 ) ( 1. ) ( Z80 ) CODE DUP HL POP HL PUSH HL PUSH NEXT END-CODE CODE OVER HL POP DE POP DE PUSH HL PUSH DE PUSH NEXT END-CODE CODE ROT DE POP HL POP E3 C, ( HL SP EX ) DE PUSH HL PUSH NEXT END-CODE CODE 2DUP HL POP DE POP DE PUSH HL PUSH DE PUSH HL PUSH NEXT END-CODE CODE TUCK HL POP DE POP HL PUSH DE PUSH HL PUSH NEXT END-CODE CODE NIP HL POP DE POP HL PUSH NEXT END-CODE CODE -ROT HL POP DE POP E3 C, ( HL SP EX ) DE PUSH HL PUSH NEXT END-CODE
Блок 159 [159 :0] ( Глава 16. Упражнения 2. 02 из 04 ) ( 1. продолж. ) ( 8088 ) CODE DUP BX POP BX PUSH BX PUSH NEXT END-CODE CODE OVER BX POP CX POP BX PUSH CX PUSH BX PUSH NEXT END-CODE CODE ROT BX POP CX POP DX POP CX PUSH DX PUSH BX PUSH NEXT END-CODE CODE 2DUP BX POP CX POP BX PUSH CX PUSH BX PUSH CX PUSH NEXT END-CODE CODE NIP BX POP CX POP BX PUSH NEXT END-CODE CODE TUCK BX POP CX POP BX PUSH CX PUSH BX PUSH NEXT END-CODE CODE -ROT BX POP CX POP DX POP BX PUSH CX PUSH DX PUSH NEXT END-CODE
Блок 160 [160 :0] ( Глава 16. Упражнения 2. 03 из 04 ) ( 2. ) ASSEMBLER DEFINITIONS : PSH AX PUSH NEXT END-CODE ; ( 3. ) ASSEMBLER DEFINITIONS : PSH3 BX PUSH DX PUSH AX PUSH NEXT END-CODE ; ( 4. ) ASSEMBLER DEFINITIONS : ;C NEXT END-CODE ; ( 5. ) FORTH DEFINITIONS CODE @REGS DI PUSH SI PUSH SP PUSH DX PUSH CX PUSH BX PUSH AX PUSH NEXT END-CODE : .REGS @REGS 7 0 DO 8 .R LOOP ; ( 6. ) CODE - DX POP AX POP DX AX SUB AX PUSH NEXT END-CODE
Блок 161 [161 :0] ( Глава 11. Упражнения 2. 04 из 04 ) ( 7а. ) : ARRAY CREATE 1+ 2* ALLOT ;CODE AX POP AX AX ADD BX AX ADD 2 # AX ADD AX PUSH NEXT END-CODE ( 7b. ) : DARRAY CREATE 1+ 4 * ALLOT ;CODE AX POP AX AX ADD AX AX ADD BX AX ADD 2 # AX ADD AX PUSH NEXT END-CODE ( 7c. ) ( Это требует лишь 1/3 времени от того. что нужно опнсаниям. в которых использовано DOES> ) : CONSTANT CREATE , ;CODE 2 # BX ADD [BX] PUSH NEXT END-CODE ( 7d. ) : 2CONSTANT CREATE , , ;CODE 4 # BX ADD [BX] PUSH 2 # BX SUB [BX] PUSH NEXT END-CODE ( Эти описания предполагают, что при обращении BX содержит CFA слова и что PFA отстоит от CFA на два байта. Это соответствует требованиям MMSFORTH. )
Блок 162 [162 :0] ( Глава 16. Упражнения 3. 01 из 01 ) ( 1. ) ASSEMBLER DEFINITIONS 0 CONSTANT ВС 2 CONSTANT DE 4 CONSTANT HL 6 CONSTANT AF : 1Z80ARG ( n -- ) CREATE , DOES> @ SWAP 8 * + С, ; 193 1Z80ARG (POP) 187 1Z80ARG (PUSH) FORTH DEFINITIONS ( 2. ) CODE IF=DROP AX POP BX POP AX BX CMP ~ Z IF BX PUSH AX PUSH THEN NEXT END-CODE ( 3. ) ( Следующее слово почти в 3 раза быстрее, чем 2 * .) CODE 2* AX POP AX AX ADD AX PUSH NEXT END-CODE ( 4. ) CODE 10* 0 # BX MOV 0 # CX MOV AX POP 10 # DX MOV BEGIN BX INC DX CX ADD AX BX CMP Z UNTIL CX PUSH NEXT END-CODE
Блок 163 [163 :0] ( Глава 16. Упражнения 4. 01 из 01 ) ( 1. ) LABEL SAVESTACK [ неиспольз. peг.] POP AX PUSH BX PUSH CX PUSH DX PUSH [тот же регистр] PUSH RET END-CODE ( Применяйте неиспользуемый регистр в вашем Форте для хранения адреса возврата, который заносится в стек при обращении к SAVESTACK) ( 2. ) CODE PLOTEMIT ( n1 n2 -- ) DX POP CX POP FF00 CALL NEXT END-CODE ( 3. ) CODE GETDATA ( n1 -- n2 ) AX POP FF00 CALL AX PUSH NEXT END-CODE ( 4. ) CODE CETDATA ( n1 -- n2 ) AX POP BX PUSH SI PUSH FF00 CALL SI POP BX POP AX PUSH NEXT END-CODE
Г. ASCII Коды
Управляющие коды и их значения в базовом телеграфном стандарте представлены ниже в таблице; альтернативные значения даны в скобках. Немногие ЭВМ (если вообще такие существуют) или современные терминалы используют все эти значения, и только некоторые распознают все комбинации "CTRL-клавиш", в особенности коды "CTRL-клавиш" в верхнем регистре. Несмотря на это, многие коды, такие как "BS", перевод строки - "LF", "TAB", и т.д., распознаются, поэтому это руководство будет полезным. Некоторые ЭВМ и терминалы используют также нестандартные символы для некоторых символьных кодов, в частности для [ , \, ],*.-,{, |, } и ~.Управляющие коды Десятич. Шестнадц. ASCII Примечания 000 000 NUL Нуль, подача ленты. CTRL-SHIFT P в верхнем регистре 001 001 SOH Начало заголовка [SOM, начало сообщения], CTRL-A 002 002 STX Начало текста [ЕОА, конец адреса]; CTRL-B 003 003 ЕТХ Конец текста [ЕОМ, конец сообщения], CTRL-C 004 004 EOT Конец передачи, отбой в системе телетайпной связи. отбой для (TWX) телетайпов, отключение некоторых наборов данных, CTRL-D 005 005 ENQ Запрос [WRU, "Кто вы"].переключатель идентификации ("Здесь...") на удаленной станции,если это предус мотрено; CTRL-E 006 006 ACK Подтверждение [RU,"Вы. . .?"]; CTRL-F 007 007 BEL Звуковой сигнал;CTRL-G 008 008 BS Смещение курсора влево, стирание символа слева от курсора; CTRL-H 009 009 HT Горизонтальная табуляция: CTRL-I 010 00A LF Перевод строки: CTRL-J 011 00B VT Вертикальная табуляция; CTRL-K 012 00C FF Переход на начало следующей страницы; CTRL-L 013 00D CR Возврат каретки на начало строки; CTRL-M 014 00E SO Выход из режима;смена шрифта или цвета ленты; CTRL-O 015 00F SI Вход в режим, возврат к стандартному шрифту или цвету ленты; CTRL-N 016 010 DLE Освобождение канала данных [DCO]; CTRL-P 017 011 DC1 Управление устройством 1; включение перфосчитывателя:(X вкл): CTRL-Q 018 012 DC2 Управление устройством 2; включение ленточного перфоратора (Вспом-вкл.); CTRL-R 019 013 DC3 Управление устройством 3: выключение перфосчитывателя (X выкл.); CTRL-S 020 014 DC4 Управление устройством 4; выключение перфоратора (Вспом.
выкл.); CTRL- T 021 015 NAK Негативное подтверждение [ERR, ошибка]; CTRL-U 022 016 SYN Пассивная синхронизация [SYNC]; CTRL-V 023 017 ETB Конец передачи блока [LEM, логический конец носителя]; CTRL-W 024 018 CAN Отмена [S0]; CTRL-X(Начало сообщения "можно") 025 019 ЕM Конец носителя [S1];CTRL-Y 026 01A SUB Подстановка [S2]; CTRL-Z 027 01В ESC Префикс ESC [S3]; CTRL-K в верхнем регистре или клавиша ESC 028 01С FS Разделитель файлов; CTRL-L в верхнем регистре 029 01D GS Групповой разделитель; CTRL-M в верхнем регистре 030 01Е RS Разделитель записей; CTRL-N в верхнем регистре 031 01F US Разделитель элементов; CTRL-O в верхнем регистре
Таблица КОДОВ Де- Шест- ASCII Де- Шест- ASCII Де- Шест- ASCII сят над- сят над- сят над- цатер цатер цатер 032 020 Пробел 064 040 @ 096 060 ` 033 021 ! 065 041 A 097 061 а 034 022 " 066 042 B 098 062 b 035 023 # 067 043 C 099 063 с 036 024 $ 068 044 D 100 064 d 037 025 % 069 045 Е 101 065 e 038 026 & 070 046 F 102 066 f 039 027 ' 071 047 G 103 067 g 040 028 ( 072 048 H 104 068 h 041 029 ) 073 049 I 105 069 i 042 02A * 074 04A J 106 06A j 043 026 + 075 04В К 107 06В k 044 02С , 076 04С L 108 06С l 045 02D - 077 04D M 109 06D m 046 02Е . 078 04Е N 110 06Е n 047 02F / 079 04F O 111 06F о 048 030 0 080 050 Р 112 070 p 049 031 1 081 051 Q 113 071 q 050 032 2 082 052 R 114 072 r 051 033 3 083 053 S 115 073 s 052 034 4 084 054 Т 116 074 t 053 035 5 085 055 U 117 075 u 054 036 6 086 056 V 118 076 v 055 037 7 087 057 W 119 077 w 056 038 8 088 058 X 120 078 x 057 039 9 089 059 Y 121 079 y 058 03A : 090 05A Z 122 07A z 059 03B ; 091 05В [ 123 07B { 060 03C < 092 05C \ 124 07С | 061 03D = 093 05D ] 125 07D } 062 03Е > 094 05Е ^ 126 07E ~ 063 03F ? 095 05F _ 127 07F DEL
В. Источники информации
Список систем Форта, публикаций и организаций не является исчерпывающим, такой задачи и не ставилось. Скорее,-это список источников, с которыми авторы знакомы и которые они сочли полезными.Системы Форта
Ниже рассмотрим системы Форта, которые предоставляют по крайней мере минимум средств для разработки программ. Мы не включили в перечень версии Форта, о которых мы не смогли получить достаточно информации для оценки, мы не ввели в список некоторые реализации Форта, которые не сочли отвечающими высоким требованиям. Но мы, вероятно, пренебрегли некоторыми версиями Форта, достойными упоминания. Если мы не упомянули вашего любимца, пожалуйста, простите нас. Информация о конкретной системе может быть также неполной. Например, мы полагаем, что к моменту выхода книги по крайней мере две из нижеупомянутых реализации Форта будут, вероятно, иметь версии, работающие на других ЭВМ. И мы упомянули только те улучшения, которые "выше нормы". Мы предполагаем, любая нормальная версия Форта имеет редактор, ассемблер, библиотеку для работы с числами двойной длины и т.д., хотя эти средства и не являются обязательными в соответствии со стандартами, большинство версий имеют также много полезных средств, которые мы не упомянули, потому что они специализированные, или в силу того. что их нельзя описать достаточно кратко. Таким образом, мы не упомянули об использовании в MMSFORTH операторов QUAN, массивов, условных структур и других сходных программных средств. Аналогично мы не описали слово из F83 VIEW, которое позволяет сразу найти блок с исходным текстом описаний нужного слова, сегментную структуру словаря HS/FORTH и расширенную версию MVPFORTH, комплексную и очень мощную систему разработки программ. Выбор подходящей версии Форт-системы может оказаться обескураживающей задачей, современные системы Форта имеют очень широкий спектр возможностей, и то, что является идеальной системой для одного задания, может оказаться менее удобным для другого. Нужно полагать, что на исследования при выборе системы будет затрачено значительное время.
К сожалению, в журналах не публикуются обзоры по системам Форта, как, скажем, по компиляторам Паскаля. Вам следует изучить литературу и обсудить ваши нужды с несколькими поставщиками. Если поставщик не будет обсуждать ваши проблемы, забудьте об этом, вы, вероятно не получите поддержки при решении ваших проблем после покупки. В некоторых случаях вы можете купить "голую" версию языка и расширять систему по мере необходимости, а в других случаях вы сможете купить копию документации, чтобы выяснить, имеет ли система то, что вам надо. Наконец, вы должны знать, что цена - не всегда мера ценности для Форта. F83 за 25 долл. или даже бесплатно в свободном доступе имеет некоторые возможности, которых нет в некоторых форт-системах ценой более 100 долл. Мы не упомянули одну систему с ценой более 200 долл.. так как нам кажется, что она не соответствует своей цене. В то время как наиболее продвинутая система polyFORTH II стоимостью несколько тысяч долларов предлагает фантастические возможности, потребности большинства программистов можно удовлетворить много более дешевой версией. Рекомендуется просмотреть следующий список в качестве введения, чтобы помочь вам получить больше информации.
FORTH, Inc. 2309 Pacific Coast Highway Hermosa Beach. CA 90254 (213) 372-8493 polyFORTH II. Нестандартный, но близок к Форт-79. Оборудование: слишком широкий список микро- и мини-ЭВМ, чтобы приводить его целиком. Продается по различным ценам с различным диапазоном возможностей и сервиса для пользователя. Особенности - очень широкий спектр возможностей, лишь немногие здесь описаны. Работает с ДОС или автономно, имеются многозадачный и многопользовательский режимы; поддержка баз данных; интерактивная графика; плавающая арифметика 8087. Замечания: это прямой потомок Форта Мура, разработанный компанией, которая была им основана. Хотя polyFORTH не является стандартным и в некотором смысле консервативным, не имеющим некоторых "фантастических" черт других версий, он, пожалуй, представляет собой наиболее профессиональную систему и используется для более сложных и изощренных задач, чем какая-то другая версия.
FORTH Interest Group (FIG) P.O. BOX 8231 Sen Jose, CA 95115 (408) 277-0668 FIGFORTH. Нестандартный, но фактически сам по себе стандарт. Оборудование: 1802, 6502, 6800, 6809, 68000, 8080. 8086/8, 9900. Alfa Micro, Apple II. Eclipse, IBM PC Nova, Pace, PDP-11, VAX, Z80. Для всех этих ЭВМ имеются тексты ассемблеров. Различные рабочие версии имеются в свободном доступе. Особенности: никакие усовершенствования FIG не поставляет. Существует множество усовершенствований в свободном доступе и много опубликовано и периодике FIG, "FORTH Dimensions". Замечания: это, пожалуй, самая распространенная Форт-система, на которой базируются многие коммерческие версии. Несмотря на распространение, он вряд ли пригоден для серьезного применения. FIGFORTH стал де-факто стандартом и усовершенствован многими пользователями.
Harvard Softworks P.O.Box 2579 Springfield, OH 45501 (513) 748-0390 HS/FORTH. Комбинация Форт-79 и Форт-83. Работает под операционной системой. Оборудование: IBM PC и совместимые с ней. Особенности: Исходные тексты в безблочных ДОС-файлах (возможна работа с файлами); средства отслеживания; быстродействующий оптимизатор (быстродействие близкое к машинному); графика; звуковое сопровождение; слова без заголовков; непосредственное пpoграммирование в машинных кодах; плавающая арифметика 8087; неограниченная возможность использования расширенной памяти. Замечания: HS/FORTH очень необычно (но эффективно) использует память, имеет необычную структуру словаря и некоторые другие уникальные черты.
Laboratory Microsystems lnc. P.O.Box 19430 Marina del Rey, CA 90295 (213) 306-7412 Следующие версии являются 16-битовыми: 68066 FORTH, 8080 FORTH, 8086 FORTH, Z80 FORTH, PC/POBTH. Следующие версии являются 32-битовыми: 6800Й FORTH+, 8086 FORTH+, PC/FORTH+. Все работают под ДОС и отвечают требованиям стандарта Форт-83. Оборудование: 68000, 8080, 8086/8, Z-80, CP/M, СР/М-86, СР/М- 68К, MS-DOS, 1MB PC и совместимые. Особенности: отладчик; возможность перекрестных ссылок; использование расширенной памяти; целевой компилятор; средства для работы с файлами; программы для плавающей арифметики; поддержка AMD 9511; графика; любые наборы символов; телекоммуникация; оверлеи словаря.
Micromotion 12077 Wilshire Boulevard Los Angeles, CA 90025 (213) 821-4340 MasterFORTH Форт-83. Работает под ДОС. Оборудование: Macintosh, IBM PC и совместимые, IBM PCjr, APPLE II, серии-СР/М, Commodore 64. Особенности: отладчик; декомпилятор; работа с файлами; описания без заголовков; перемещаемая область компиляции; графика; программы для плавающей арифметики; система перемещения модулей.
Miller Microcomputer Services 61 Lake Shore Roed Natick, MA 01760 (617) 653-6136 MMSFORTH Форт-79. Автономная версия. Возможна работа и с ДОС. Оборудование: TRS-80 модели I, III, 4; IBM PC и совместимые. Особенности: строки; графика; программы для работы с числами с плавающей точкой; арифметика с плавающей точкой 8087; возможность перекрестных ссылок; средства поиска; слова без заголовков; перемещаемая компиляция; адресация к расширенной памяти; произвольный доступ к диску; дисковые форматы по выбору пользователя; буферизатор вывода на принтер; поддержка баз данных; экспертные системы, система управления базами данных; процессор слов; возможность последовательной передачи данных.
Mountain View Press, Inc. P.O.Box 4656 Mountain View. CA 94040 (415) 961-4103 MVP-FORTH Форт-79. Версии для ДОС и автономного использования. Основная система в свободном доступе. Оборудование: CP/M, СР/М-86, MS-DOS, Apple II серия. Macintosh, IBM PC и совместимые. Commodore 64, NEC 8201, TRS- 80/100, HP 110. HP 150. Особенности: не все доступны для всех реализации. Кросскомпилятор; целевой компилятор; программа переноса; отладчик; средства отслеживания; декомпилятор; поддержка расширенной памяти; MS-DOS файл-интерфейс в автономном режиме; описания без заголовков; графика; программы для операций над числами с плавающей точкой; арифметика с плавающей точкой 8087: плавающая арифметика 9511; окна, поддержка системного ПЗУ; экспертная система; процессор слов. Замечания: базовый MVP-FORTH находится в свободном доступе и может быть получен через одну из многих групп пользователей. Фирма Mountain View Press является поставщиком других коммерческих Форт-систем, включая некоторые не упомянутые здесь.
Они являются хорошим источником информации о Форт-версиях, доступных на различных ЭВМ.
Next Generation Systems P.O. Box 2987 Santa Clara, CA 95035 (408) 241-5909 NGS FORTH Форт-79. Работает под ДОС. Оборудование: IBM PC и совместимые. Особенности: режим работы сходен с FIG-FORTH; адресация к расширенной памяти, отладчик; средство отслеживания; декомпилятор; графика.
No Visible Support Software Box 1344 2000 Center Street Berkeley, CA 94704 F83 Форт-83. Работает под ДОС. Оборудование: 8080 CP/M, 8086 CP/M, 68000 CP/M (все на 8- дюймовом диске), IBM PC и совместимые (5-дюймовый диск). Особенности: отладчик; декомпилятор; метакомпилятор; многозадачный режим; строки; теневые блоки. Замечания: свободный доступ; от пользователей можно получить различные реализации в разных дисковых форматах. Хотя и находится в свободном доступе, он является намного более полным, чем FIGFORTH или базовый пакет MVPFORTH (впрочем, он не столь всеобъемлющ, как "развитый" MVPFORTH, который доступен от Mountain View Press). "No Visible Support Software" (" Программа без какой-либо видимой поддержки") - как раз то, что подразумевается именем. Здесь нет какой-либо поддержки пользователя или документации, кроме того, что имеется в коротком текстовом 'файле и в теневых блоках на диске. Теневые блоки, однако, весьма исчерпывающи.
Раrsеc Research Drawer 1776 Fremont, CA 94538 Dealer, Mountain View Рress SuperFORTH 64+AI Форт-79. Работает под ДОС. Оборудование: Commodore 64. Особенности: арифметика с плавающей точкой; графика; строки; отладчик; средство отслеживания; декомпилятор; генерация программ, загружаемых в ПЗУ.
Quest Research Inc. P.O.Box 2553 Huntsville. AL. 35804 (204) 539-8086 FORTH-32 Форт-79. Работает под ДОС. Оборудование: IBM PC и совместимые. Особенности; декомпилятор; отладчик; графика; программа перемещения; полномасштабное применение 32-разрядного адресного пространства памяти; арифметика с плавающей точкой 8087; целевая компиляция.
Shaw Laboralories), Ltd. 24301 Southland Drive, *216 Hayward, CA 94543 (415) 276-5953 TaskFORTH Форт-79, Форт-83 с перекрытием.
Работает под ДОС. Оборудование: СР/М в различных форматах, MS-DOS. Особенности: многозадачный и многопользовательский режимы; отладчик; средства отслеживания; декомпилятор; управление базами данных; иерархическая система файлов; строки; улучшенное управление дисплеем и принтером; целевой компилятор; кросскомпилятор; адресация к расширенной памяти.
Ubiquitous Systems 13333 Belevue-Redmond Road, N.E. Bellcvue. WA 98005 (206) 641-8030 u4th Форт-83 (с ограничениями). Работает под ДОС. Оборудование: различные UNIX и XENIX системы. Plexus. IBM- XT/AT, Intel 286/310, SUN. Tendy 6000. TRS-80 16В, VAX. Особенности: написано на Си, новые примитивы можно написать на Си; целевая компиляция; передача команд в систему UNIX; компиляция в Си-коды; поддержка, где нужно, 32-разрядной адресации; строки; работа в верхнем и нижнем регистрах; компиляция файлов и блоков; динамическое перераспределение памяти; исходные тексты для дополнений, ориентированных на объектные коды.
WL Computer Sytems 1910 Newman Road West Lafayette. IN 47906 (017) 494-2564 WL FORTH Форт-79. Работает под ДОС. Оборудование: IBM PC и совместимые. Особенности: арифметика с плавающей точкой 8087.
Публикации
Мы полагаем, что мы включили все основные книги и периодические издания, которые были к моменту написания книги и которые посвящены Форту. Мы также включили публикации, которые сочли полезными в качестве справочных пособий при работе с Фортом или которые включают материалы по Форту Многие из материалов можно получить от FORTH Interest Group (Группа пользователей Форта), фирм Miller Microcomputer Services и Mountain View Press, а также от других поставщиков Форт-систем (адреса приведены выше).
Книги
1. Anon. 1980. FORTH-79. Публикация группы по стандартам Форта. FORTH Interest Group, Sant Carlos, Сalif. Тексты стандартов для Форт-79. 2. Anon. 1983a. FORTH-83 Standard. Публикация группы по стандартам Форта. Mountain View Press, Mountain View, Calif. Тексты стандартов для Форт-83. 3. Anon. 1983b. MVP-FORTH Source Listing.
Mountain View Press, Mountain View, Calif. Исходный текст ассемблера для базовых MVP-FORTH систем для 8080, IВМ PC и Apple II. 4. Anon. 1980. [без заголовка] FORTH Interest Group, Sant Carlos, Calif. Собрание информационных перепечаток из журнала "BYTE", включая выпуск, посвященный языку Форт. 5. Anderson A., Tracy M. 1984. FORTH Tools. Vol. 1 .Micromotion. Los Angeles, Calif. Вводный текст, рассматривающий только Форт-83. Не предполагает какого-либо знания ЭВМ. Хорошие упражнения и примеры. Не обсуждаются более сложные темы, такие как ассемблер, работа внешнего и внутреннего интерпретаторов и т.д.. но в остальном довольно полное изложение предмета. 6. Brodie L. 1981. Starting FORTH. Prentice-Hall. Englewood Cliffs N.J. Введение в Полифорт с некоторыми упоминаниями отличий от FIGFORTH и Форт-79. Не предполагает предварительного знания ЭВМ. Легко читается, стиль весьма живой. Сложные темы рассмотрены не слишком подробно. Ограниченное число примеров и упражнений. Указатель неполный. Первая достаточно общая книга по Форту и поэтому наиболее популярная. Переведена на русский язык. 7. Brodie L. 1984. Thinking FORTH. Prentice-Hall, Englewood Cliffs N.J. Философия и способ мышления при решении проблем на Форте. Обсуждение составления программ со многими примерами, советами и программными процедурами. Способствует размышлению. 8. Cassady J.J. 1981. MelaFORTH. Распространяется фирмами Mountain View Press, Mountain View, Calif. Исходные тексты метакомпилятора (целевой и кросскомпилятор), написанные на FIGFORTH. 9. Chirlian P. 1983. Beginning FORTH. Matrix. Beaverton, Oreg. Введение, которое не предполагает какого-либо знания в области ЭВМ. Элементарный материал изложен хорошо, с примерами и упражнениями, но наложение тем промежуточного уровня, таких как компиляция и интерпретация, а также слов типа IMMEDIATE, [COMPILE] и COMPILE, недостаточно. Не обсуждаются темы высокого уровня, такие как структура словаря, работа Форта или ассемблера. Хороший указатель. 10. Derick M.
and Baker L. 1982. FORTH Encyclopedia. Mountain View Press, Mountain View, Calif. Детальное описание всех слов FIGFORTH с диаграммами. Очень полезно для понимания того, как работают слова Форта. 11. Edmunds R. 1985. The Prentice-Hall Standard Glossary of Computer Terminology. Prentice-Hall, Englewood Cliffs N.J. Даются общие определения и обсуждается терминология вычислительной техники, включая многие малоизвестные термины. Терминология Форта не рассмотрена. 12. Feierbach G., Thomas P. 1985. FORTH Tools and Applications. Reston, Reston.Va. Краткое, но полезное обсуждение и примеры разработки программ Форта, отладка и приемы программирования. Полезные программы. 13. Haydon G. 1983. All about FORTH. Mountain View Press. Montain View, Calif. Описание, обсуждение и примеры использования всех слов MVP-FORTH. Сравнение слов MVP-FORTH со словами Форт-79 и FIGFORTH. Очень полезно для понимания того, как работает язык. 14. Hofert D. 1985. A Bibliography of FORTH Referances. 2nd ed. The Institute for Applied FORTH Research. Inc.. Rochester. N.Y. Очень полезный, практически полный список перекрестных ссылок на когда-либо опубликованные статьи по Форту. 15. Huang Т. 1983. And So FORTH. Central Book Co., Taipei. Taiwan. Странное, но полезное и очаровательное собрание материалов по истории форта, примеры программ, приложения языка и т.д. Хотя рассматривается как учебник для колледжей, его изложение слишком фрагментарно для этих целей. Пригоден для опытных пользователей Форта. 16. Knecht К. 1982. Introduction to FORTH. Howard W. Sams, Indianapolis, Ind. Краткое введение, основанное на теперь уже устаревшей и нестандартной версии MMSFORTH. Полезные и интересные примеры, но мало или совсем нет материалов по темам промежуточного и высокого уровня сложности. 17. Lipschutz S. 1982. Essential Computer Mathematics. Schaum's Outline Series, McGraw-Hill, New York. Сжатый, но очень полезный краткий курс практической математики в приложении к ЭВМ, где рассмотрены системы счисления, двоичная арифметика, кодирование чисел и букв, булева алгебра, а также векторы и матрицы. 18.
Loeliger R. 1981. Threaded Interpretive Languages. Byte books, Peterborough, N.H. Таинственное, трудночитаемое, перегруженное жаргоном, но очень ценное описание применения цепных интерпретивных языков, в частности Форта. 19. McCabe C.K. 1983- FORTH Fundamentals. Dilithiuro Press, Beaverton, Oreg. Введение, предполагающее небольшое знание ЭВМ, охватывает FIGFORTH и Форт-79. Изложение примерно на уровне книги Броуди с недостаточным изложением продвинутых тем. Плохой указатель делает книгу непригодной для поиска дополнительных материалов. 20. Morgan C. Waite M. 1982. 8086/8088 16-Bit Microprocessor Primer. Byte/McGraw-Hill, Peterborough. N.H. Превосходное изложение архитектуры и набора команд Intel 8088, которое может помочь пониманию нашего описания ассемблера, в гл. 16. 21. Scanlon L. 1982. FORTH Programming. Howard W. Sams, Indianapolis, Ind. Введение в FIGFORTH и Форт-79, которое предполагает некоторое знание вычислительной техники. Рассмотрены основы и некоторые темы промежуточного уровня, совсем оставлены без внимания такие темы, как структура словаря, интерпретаторы или программирование на ассемблере. 22. Ragsdale W. 1980 FIGFORTH Installation Manual. FORTH Interest Group, San Carlos, Calif. Исходные тексты FIGFORTH. Очень полезна для понимания того, как работает Форт. 23. Reymann J. 1983. Understanding FORTH. Alfred Publishing Co., Sherman Oaks, Calif. Короткая брошюра, дающая представление об основных особенностях форта для тех, кто не знаком с языком. 24. Stevens W. 1979. A FORTH Primer. Distributed by the FORTH Interest Group, San Carlos, Calif. Книга извеестна под названием "Kilt Peak Primer", это описание форта, история его создания Чарльзом Муром в Национальной обсерватории Kin Peak. Хотя оно и несколько устарело, в нем содержатся интересные мысли и рассмотрена перспективы развития языка. 25. Ting С. 1983. FORTH Notebook. Offete Enterprises, San Maleo, Calif. Собрание необычных и интересных программ на Форте. 26. Winfield A. 1983. The Complete FORTH.
Sigma/Wiley, New York. Введение, охватывающее Форт- 79 вплоть до промежуточного уровня сложности. Прекрасные примеры и объяснения. Изложение более сложных тем, касающихся того, как работает Форт, ограниченно или отсутствует вовсе. 27. Zaks К. 1980. How to Program ihe Z80. SYBEX, Berkeley, Calif. Превосходное изложение архитектуры и набора команд Z-80, которое поможет в понимании нашего описания ассемблера в гл. 16.
Периодика
Существуют два периодических издания, целиком посвященные языку Форт. "FORTH Dimensions" - журнал, выпускаемый ежемесячно объединением FORTH Interest Group (FIG см. ниже). В нем содержится широкий круг публикаций - от вводных учебных материалов до исходных текстов ассемблеров, метакомпиляторов и т,д. Здесь достигнут прекрасный компромисс между материалами для начинающих и довольно серьезными статьями. Старые, весьма полезные номера можно получить от FIG. Институт прикладных исследований Форта (см. ниже) ежеквартально выпускает профессиональный журнал "The Journal of FORTH Applications and Research". Он включает в себя присылаемые статьи обычно высокого уровня по основополагающим и теоретическим темам. Существуют ежегодные публикации материалов профессиональных конференцийFORTH Interest Group публикует журнал FORML Conference Proceedings", где содержатся статьи, представленные на ежегодное собрание FORTH Modification Laboratory. Статьи имеют тот же уровень, что и в "FORTH Dimensions". Материалы ежегодной Рочестерской конференции по Форту публикуются институтом прикладных исследований по Форту. Хотя трудно точно оценить, статьи близки по уровню "Journal of FORTH Applications and Research". Два универсальных журнала часто содержат материалы по Форту. Журналы "Dr, Dobbs Journal" и "Computer Language" дают некоторое число публикаций по Форту в большинстве номеров, и оба обещают публиковать ежегодно один номер, целиком посвященный Форту. Организации Существуют две организации программистов на Форте.Их адреса: FORTH Interest Group (FIG) P.O. Box 8231 San Josу. CA 95115 и The Insliliite for Applied FORTH Research, Inc. 70 Elmwood Avenue Rochester, NY 14611 Их деятельность описана во введении. Короче говоря, общество FIG является, скорее, любительской организацией. Оно ответственно за распространение FIGFORTH, публикует "FORTH Dimensions", организует встречи, такие как FORML, существую' отделения или группы любителей по всему миру. "The Institute" ставит целью повысить качество применения Форта. Он организует Рочестерские конференции и публикует "Journal of FORTH Applications and Research".
Слова для манипуляций в стеке
Каждое из слов, приведенных в , рассматривается в этом разделе самостоятельно. В конце раздела мы приводим несколько задач на употребление этих слов, которые будут полезны для совершенствования ваших навыков в их использовании.Прежде чем продолжить описание приведенных в таблице слов, обратим внимание на обозначения в скобках. Как вы уже видели в , в Форте принято применять такие обозначения (они называются диаграммой состояния стека), чтобы показать состояние стека до и после применения какого-либо слова. Правило хорошего тона -- помещать такую диаграмму сразу после имени определяемого слова. Поскольку все, что заключено с двух сторон в скобки (если только после открывающей скобки стоит пробел). Форт при вводе игнорирует, скобки можно использовать для помещения комментариев к Форт-словам. Вот, например, определение слова для сложения двух чисел и умножения на третье число:
: +* (n1 n2 n3 -- п) + * :
Таблица 2.1. Перечень слов для манипуляций в стеке
| Слово | Состояние стека ( до - после ) | Действие |
| DROP | ( n - ) | Очищает вершину стека |
| DUP | ( n - n n ) | Делает копию числа на вершине стека |
| SWAP | ( n1 n2 - n2 n1 ) | Переставляет местами два числа |
| OVER | ( n1 n2 - n1 n2 n1 ) | Копирует второе число на вершину |
| ROT | ( n1 n2 n3 - n2 n3 n1 ) | Перекладывает третье число на вершину |
| n PICK | ( n1 ... - n1 ... n1 ) | Кладет на вершину копию п-го элемента |
| n ROLL | ( n1 ... - ... n1) | Перекладывает п-й элемент на вершину |
| ?DUP | ( n - n n ) или ( 0 - 0 ) | Выполняет операцию DUP, если n == 0 |
| DEPTH | ( ... - n ) | Возвращает в стек n -- число элементов |
Если мы вводим 3 4 5 + *, то до того, как слово будет исполнено, n1=3, n2=4, n3=5. После операции умножения в стеке находится n, которое имеет значение 27. Перечень принятых обозначений для содержимого стека приведен в .
Таблица 2.2. Обозначения для содержимого стека
| Символ | Значение |
| n, n1 , | 16-разрядное целое число одинарной длины |
| d,d1, . . . | 32-разрядное целое число двойной длины |
| U | 16-разрядное число без знака одинарной длины |
| ud | 32-разрядное число без знака двойной длины |
| char или с | 7-разрядное представление кода символа ASCII |
| byte или Ь | 8-разрядное число, байт |
| флаг или f | 1 или 0 - булев флаг |
| адр, адр1 | Адреса |
| $ или $адр | Адрес, где находится строка символов |
Если вы пока еще не усвоили эти обозначения, не огорчайтесь: они станут вам понятнее в последующих главах и мы привели их для использования в последующих ссылках. Обычно ход программы становится более понятным, если вы применяете более содержательные описатели в диаграммах состояния стека. Например, скорость можно обозначить как "скор.", а адрес переменной, содержащей значение скорости, -- как "адр.скор." и т.д. Другой более полный набор символов приведен в стандарте Форт-83, но лучше всего все-таки использовать содержательные обозначения.
А теперь вернемся к рассмотрению каждого из слов для манипуляций в стеке. DROP -- это самое простое слово; оно просто очищает вершину стека, например, после ввода
1 2 3 DROP .S
мы получим
1 2 Ok
Помимо того, что слово DROP полезно для уничтожения неправильно введенных данных при вычислениях (что-то вроде клавиши очистки регистра в калькуляторе), оно чаще всего используется для того, чтобы убрать какие-либо числа из стека во время выполнения программы. Очевидно, при пустом стеке мы получим
хххх DROP ? Stack EMPTY ! (стек пуст!)
или аналогичное сообщение об ошибке. Вас может удивлять, почему Форт не знает о том, что стек пуст, и почему он не игнорирует слово DROP в этом случае. Дело в том, что проверка на наличие ошибок и принятие решения, игнорировать их или не игнорировать, занимает немало времени. В отличие от большинства других языков программирования Форт состоит из полуавтономных процедур -- (слов языка Форт), каждое из которых оптимизировано с точки зрения быстродействия. Если бы в каждое из них было включено принятие решений об ошибках, исполнение слов происходило бы с более низкой скоростью. В Форте принято, что программист, а не язык несет ответственность за предотвращение ошибок. Форт дает вам полный контроль над компьютером, но в то же время и всю полноту ответственности. С другой стороны, разрабатывая программу, вы будете отлаживать каждое ее слово, легко обнаруживая ошибки. И поэтому Форт не нуждается в сложных и требующих больших затрат времени методах контроля ошибок, необходимых другим языкам.
Нетрудно догадаться по названию, что слово DUP (по-англ. "DUPlicate " -- копировать) делает копию верхнего элемента стека.
9 DUP .S
приводит к тому, что в стеке окажется
9 9 ok
Слово DUP, пожалуй, одно из наиболее часто употребляемых слов для манипуляций в стеке. Вы встречались с ним в одном из упражнений . Часто программе требуется одно и то же значение несколько раз. Слово SWAP также, судя по названию (от swap -- обменивать), переставляет местами два верхних элемента стека. Если ввести
3 18 .S
вы получите на экране
18 3 ok
что особенно полезно, если нужно сделать операцию вычитания или деления, но операнды стоят в неправильном порядке. После слова DUP оно применяется наиболее часто. Однако не допускайте, чтобы это сделало вас ленивым. Иногда проще применить слово SWAP перед операцией деления или вычитания вместо того, чтобы заранее предусмотреть в программе требуемый порядок следования чисел в стеке. Использование SWAP может ускорить составление программы, но на этапе исполнения увеличит время работы программы.
Действие слова OVER не очевидно из его названия (через). Попробуйте ввести
1 2 OVER .S
и вы увидите
1 2 1 ok
Можно понять, что второе число в стеке было скопировано на вершину стека. Другими словами, OVER как бы заставляет второй элемент стека "перепрыгнуть" через первый элемент на вершину. Оно очень похоже на слово SWAP, но в отличие от него не удаляет второй элемент стека. Слово может быть полезно во многих случаях, когда какое-либо число должно быть использовано несколько раз. В следующем примере дублируются два верхних элемента стека:
1 2 OVER OVER .S
при этом получается
1 2 1 2 ok
Дублирование двух верхних элементов стека очень полезно, когда в стеке нужно оставить для последующего использования два числа. Предположим, что вам нужно вывести на экран одновременно и сумму, и произведение чисел 5 и 7.
5 7 OVER OVER + . *
выдает в результате
12 35 ok
Конструкция OVER OVER полезна также для сравнения двух чисел.
Дальше мы узнаем, что существует слово 2DUP, которое выполняет ту же функцию, что и OVER OVER. Имеется целое семейство слов, которые оперируют в стеке парами чисел, но, так как они используются главным образом для операций с так называемыми числами двойной длины, мы их рассмотрим в .
Слово ROT (по-англ. "rotate" -- поворачивать) производит ротацию трех верхних чисел стека, т.е. перекладывает третье сверху число в стеке на его вершину, так что
1 2 3 ROT .8
приводит к
2 3 1 Ok
Третье сверху число в стеке перемещается на вершину стека, а следующие два числа продвигаются на одну позицию в глубь стека. Применения слова ROT поначалу не очевидны, но они также важны. Предположим, что вы хотите определить значение выражения (5+2) х (7+3), если в стеке находится 5 2 7 3.
Для решения введите
+ ROT ROT + х .
и вы получите
70 ok
Действие некоторых комбинаций слов, вроде приведенных в этом примере, достаточно трудно понять даже опытному программисту. Поэтому проследить, что происходит в стеке в последнем примере, вам поможет следующая таблица:
| Операция | Содержимое стека | Содержимое стека | Операция |
| (стек в начале) | (до - после) | (до - после) | |
| (5 2 7 3 --) | + | (- 10 7) | |
| + | (-- 5 2 10) | х | (- 70). |
| ROT | (- 2 10 5) | (- стек пуст) | |
| ROT | (- 10 5 2) |
В отличие от предыдущих слов PICK требует аргумента -- числа, которое указывает, какое по счету число сверху вы хотите скопировать на вершину стека. Аргументом является номер числа, за начало принимается вершина стека, а счет ведется по направлению в глубь стека. Так, например, в стандарте Форт-79 операции
1 2 3 4 5 6 4 PICK .S
приводят к результату
1 2 3 4 5 6 3 ok
Но в стандарте Форт-83 вы увидели бы
1 2 3 4 5 б 2 ok
Это принципиальное и зачастую очень неудобное различие стандартов Форт-79 и Форт-83.
В Форт- 79 принято, что верхний элемент стека имеет номер 1, а в Форт-83 -- номер 0. Таким образом, в стандарте Форт-83 выражение
0 PICK
приводит к такому же результату, что и DUP, в то же время для Форт-79 стандартное слово DUP эквивалентно
1 PICK
Если ввести 0 PICK, в стеке окажется какая-нибудь чушь. Таким образом, PICK, доставая число из глубины стека, не производит в нем других изменений.
Слово ROLL похоже на PICK, но в отличие от него вынимаемое число при перемещении его на вершину на старом месте удаляется. Например, в стандарте Форт-79
1 2 3 4 5 6 5 ROLL .S
приведет к
1 3 4 5 6 2 Ok
в то же время та же операция в стандарте Форт-83 приведет к другому результату:
2 3 4 5 6 1 ok
Нумерация элементов в стеке в стандартах Форт-79 и Форт-83 отличается для оператора ROLL так же, как и для PICK. Эти различия в стандартах важно учитывать при переносе программ из одной версии в другую. Слова ROLL и PICK следует применять, если невозможно использовать ничего другого, так как они работают значительно медленнее, чем DUP, OVER и ROT (эти три слова определены не с помощью слов PICK и ROLL, а непосредственно в машинных кодах). К тому же слово ROLL работает значительно медленнее, чем PICK. Чтобы избежать путаницы, нужно держать в стеке не более четырех чисел, которые используются в данном слове, и, если вы будете придерживаться этого правила, вам редко потребуются слова PICK и ROLL.
А сейчас будет уместно сделать небольшой экскурс в стандарты языка Форт. Как уже указывалось во введении, существуют два известных стандарта -- Форт-79 и Форт-83. Ссылки на основные документы, описывающие стандарты, приведены в приложении В. Различия между стандартами зачастую невелики, однако имеют значение, как, например, в случае ROLL и PICK, и мы обратим на это ваше внимание, когда будем встречаться с этими различиями. Версия, с которой вы работаете, должна соответствовать одному из стандартов, и вы можете определить, какому именно, если введете 79-STANDARD или FORTH-83. Если без ошибки будет принято первое слово, то ваша версия совместима с Форт-79, если же без ошибки будет принято последнее слово, ваша версия соответствует стандарту Форт-83.
Слово ?DUP представляет собой специальный вариант слова DUP. Оно делает копию числа, находящегося на вершине стека, если оно не равно нулю, и не копирует число, если оно равно нулю, Например,
1 4 5 ?DUP .S
дает в стеке
1 4 5 5 ok,
в то время как
1 4 0 ?DUP .S
приводит в результате к
1 4 0 ok
Таким образом, в последнем случае слово ?DUP не производит никаких действий. Оно особенно полезно вместе с конструкцией IF...THEN, примеры этого будут приведены в последующих главах.
Последнее слово в , DEPTH, не производит никаких перестановок в стеке. Вы уже встречали его в . Оно подсчитывает количество чисел в стеке и выдает его на вершину стека. Если ввести
21 131 56 7 89 DEPTH
мы получим
5 ok
где 5 -- это число элементов, находившихся в стеке перед исполнением слова DEPTH. Мы увидим еще применение слова DEPTH в последующих упражнениях.
Слово для просмотра содержимого стека
В некоторых версиях языка Форт имеется служебное слово для представления содержимого стека без разрушения. Будучи полезным для программирования, оно может быть просто необходимым для обучения пользования стеком. Приведенное ниже описание слова .S (печатать_стек) предназначено для тех, у кого его нет в их версии Форт. Попробуйте ввести его (как оно работает, вы поймете к концу этой главы). В стандарте Форт-79 слово .S определяется как: .S DEPTH ?DUP 0= IF ." Stack empty" (стек пуст) ELSE 0 DO DEPTH ROLL DUP . LOOP THEN ;
в то время как в Форт-83 :
: .S DEPTH ?DUP 0= IF ." Stack empty" ELSE 0 DO DEPTH 1- ROLL DUP . LOOP THEN ;
(Взгляните, чтобы понять различие между этими двумя определениями, на различие в определении слова ROLL.)
Теперь, если вы введете
1 2 3 .S
то увидите на экране
1 2 3 ok
Введите теперь:
. . .
и вы увидите
3 2 1
т.е. убедитесь, что слово .S действительно ничего не изменяет в стеке. Если Вы введете .S, когда стек пуст, то будет напечатано сообщение об ошибке, которое является частью определения этого слова. Буква S часто используется в словах, относящихся к стеку. Поскольку . (точка) означает "напечатать", слово .S имеет вполне логичное имя.
Стек в арифметических операциях
Как вы уже видели, помещение чисел в стек означает попросту ввод их с клавиатуры через пробел или несколько пробелов. Но ради простого помещения чисел в стек и его просмотра не стоит использовать компьютер. Основная часть работы на компьютере связана с операциями над числами, поэтому давайте попробуем проделать некоторые арифметические действия (если вам покажется, что мы повторяем , пожалуйста, перетерпите это вместе с нами). Введите3 + 5 .
и вы получите бессмысленный ответ. Форт не любит таких выражений. Для него нужно сначала поместить оба числа в стек, прежде чем произвести сложение. Попробуйте ввести
3 5 + .
теперь вы получите
8 ok
т.е. правильный результат. Как вы уже знаете из , Форт использует не такую математическую нотацию, как другие языки. Существуют три наиболее распространенные нотации для представления действий с числами. Префиксная нотация записывается так: " + 3 5 ". Хотя она не получила распространения в арифметике, фактически это самая обычная нотация, поскольку ее применяют в повседневной речи. Когда вы говорите "сложите 3 и 5", вы фактически применяете префиксную нотацию. При префиксной, нотации оператор стоит перед числами (операндами). (Оператор -- это символ или инструкция, которая определяет операцию над одним или более числами, например, +, -, х, "сложить", sin и log -- это все операторы.)
Инфиксная нотация записывается так: " 3 + 5 ". Это наиболее употребительная алгебраическая нотация, при которой оператор располагается между числами-операндами. В языке Форт используется постфиксная нотация. Например, в выражении " 3 5 + " оператор стоит на последнем месте после чисел-операндов. Префиксная нотация называется иногда польской, в честь польского математика Лукашевича, который предложил ее для формальной логики. Постфиксная нотация обычно называется обратной польской записью (поскольку она обратив префиксной нотации). Она используется в калькуляторах фирмы Hewlett-Packard и некоторых других.
Инженеры и ученые обычно предпочитают такие калькуляторы, потому что, как мы далее увидим, не нужно беспокоиться о скобках, как при инфиксной нотации. Если вы привыкли к алгебре, постфиксная нотация поначалу вам покажется странной. На самом деле в ней нет ничего странного. В школе вас, наверное, приучили к решению задач на сложение в такой форме:
3 5 + --- 8
Но ведь это то же самое, что
3 5 + . 8 ok
Хотя учитель, возможно, читал это выражение как "три плюс пять равно восемь", запись говорит вам другое: возьми 3 и 5, сложи их, получишь 8. Действительно это одна из форм стековой нотации, причем числа 3 и 5 заменяются на 8. Стековая природа становится еще более очевидной для скользящего итога, например:
5 2 + ----- 7 3 - ----- 4
где горизонтальная черта служит для того, чтобы показать изменяющееся состояние стека, это что-то вроде смены кадров в кино. Совсем не случайно детей приучают к такой форме, она проще воспринимается, поэтому арифметика на языке Форт оказывается простой как для нас, так и для компьютера.
Может быть, это и очевидно, но вот что происходит в примере со скользящим итогом на языке Форт. Когда вы ввели
5 2 + 3 - .
интерпретатор поместил 5 и 2 в стек. Знак + снимает оба числа из стека, вычисляет их сумму и помещает в стек 7. Затем число 3 из стека помещается сверху числа 7, а оператор - вынимает из стека числа 7 и 3, производит вычитание и помещает в стек 4. Наконец оператор . (точка) вынимает из стека число 4 и представляет его на экране. (Отметьте себе, что в дальнейшем мы не будем говорить вам, когда нажать клавишу , означающую ввод, если это очевидно.) Другой способ выполнить последний пример состоит в том, чтобы ввести
5 2 3 - +
Вы должны проделать все, что делает интерпретатор, и проследить, что получится в стеке, чтобы понять, почему в обоих случаях получается одинаковый результат. Можете ли вы перестроить числа и операторы в стеке по-другому, чтобы получился тот же самый результат ?
Какой бы ни была простой и изящной постфиксная запись на языке Форт, она может использоваться в контексте с алгебраической записью.
Невозможно отрицать, что алгебраическая запись полезна для выражения сложных формул, почему же тогда мы выступаем против нее в компьютерном языке и применяем постфиксную запись ? По двум причинам. Алгебраическая запись избыточна, а для постфиксной требуется меньше места в памяти, кроме того, она также экономит и время компьютера.
Экономия места и памяти переходят в мощность ЭВМ. Вот пример неопределенности в алгебраической записи. Пусть имеется такая запись :
2х3+4х5=?
Возможно несколько ответов в зависимости от порядка, в котором выполняются операции умножения и сложения. Можно избежать неопределенности двумя способами: используя скобки или приписывая операторам порядок следования. На бумаге можно использовать скобки, например:
(2 х 3) + (4 х 5 ) = 26
или
(2 x 3 + 4 ) x 5 = 50
Если Вы программируете на Бейсике, то наверняка знаете, что в нем разрешается пользоваться скобками. Если скобок нет, то и Бейсик, и большинство других языков программирования во избежание двусмысленности приписывают операторам определенный порядок следования, в данном случае первыми производятся операции умножения, а затем операции сложения. Таким образом, если не применять скобок, то результат приведенного примера на Бейсике будет равен 26. Необходимость в скобках или порядке следования операции приводит к тому, что алгебраическая нотация снижает скорость операций на ЭВМ. Чтобы сам компьютер решил, как интерпретировать выражение, ему требуется немалое дополнительное время, а для более подробного указания компьютеру порядка его действий требуется дополнительная память для размещения кода программы. В интерпретирующих языках типа Бейсика это время затрачивается при исполнении программы. В компилирующих языках типа Фортрана или Паскаля это время тратится во время компиляции. Если вы все еще не убедились в том, что постфиксная запись не приводит к неопределенности, вам помогут несколько упражнений.
Учимся на практике
Так же как сложно научиться иностранному языку без собеседника, трудно изучить язык программирования без компьютера. Как тот, так и другой язык лучше всего изучается на практике. Вы можете многое узнать о языке Форт, пользуясь этой книгой, и без компьютера, но хорошо программировать на нем вы не сможете. Мы посвятим первую часть этой главы тому, чтобы вы, проэкспериментировав с языком, почувствовали бы себя увереннее. Во второй части мы рассмотрим Форт более абстрактно, в том числе познакомимся с тем, как он устроен. Итак, попробуем поработать на Форте.Очевидно, для начала вам надо включить компьютер и загрузить Форт в машину, но компьютеров и версий языка так много, что сделать это нужно, пользуясь руководством для вашей машины.
Если вы готовы, напечатайте на клавиатуре
55 111 +
В качестве клавиши для ввода в Форте используется клавиша возврата каретки, которую мы будем обозначать как (Enter). При вводе с клавиатуры вы как бы печатаете на машинке, поэтому в тексте слова "введите с клавиатуры" и "напечатайте с клавиатуры" означают одно и то же. (Прим. перев.)
Тогда на экране вы увидите "ok" (все в порядке). Что произошло ? Вы ввели числа 55 и 111 и сложили их, но не увидели результат, потому что не попросили напечатать его. Форт напечатал "ok", поскольку он справился с тем, что вы ввели. Сообщение "ok" -- это подсказка-приглашение, которая говорит вам о том, что Форт готов принимать с клавиатуры новую информацию. Теперь введите
.
Слово . (произносится как "точка") просит компьютер выдать число на экран. Вы увидите
166 ok
т.е. сумму чисел 55 и 11. Теперь введите
5 6 *
и получится:
30 ok
Слово * -- это символ операции умножения. А теперь попробуйте
55 111 + 2 * . .
На экране должно получиться
332 Ok
Форт сначала произвел сложение, затем было введено число 2, которое было перемножено с суммой, и был напечатан результат. Теперь введите
60 5 / .
вы увидите
12 ok
Аналогично
60 5 - .
выдаст
55 Ok
Попробуйте теперь ввести такую последовательность
5 60 - .
вы получите
-55 ok
Очевидно, что порядок, в котором вводятся числа, небезразличен. Попробуйте ввести
5 60 / .
вы получите
0 ok
Что произошло ? Форт в минимальном наборе работает только с целыми числами, и, конечно, 5/60 представляет собой дробь, значение которой округляется до 0. Некоторые реализации Форта могут обрабатывать дроби и числа с плавающей запятой, и мы об этом еще узнаем в . А в данный момент вам необходимо еще проэкспериментировать с арифметическими действиями, потому что Форт лучше всего изучать на практике. Для выполнения арифметических действий вы пользовались стеком языка Форт.
Использование стека -- это наиболее существенное отличие языка Форт от других языков программирования. Представим себе стек как сложенные стопкой числа или колоду карт, помеченных числами. Когда вводим число, завершая его нажатием клавиши , мы говорим, что число проталкивается (или кладется) в стек, как будто бы в колоду добавляется одна карта, а когда производится такая операция, как. (т.е. печать числа), мы говорим, что число выталкивается (изымается) из стека, как будто бы карта вынимается из колоды, в данном случае это делается, чтобы напечатать число. Когда вы вводите:
55 111 +
числа 55 и 111 кладутся в стек, а операция + изымает их из стека, складывает и полученную сумму снова кладет в стек. Наши "карты" показаны на .

рис. 1.1
Мы пользуемся нотацией "х у +", которая называется постфиксной (от англ. "post" -- после), поскольку символ операции (оператор) стоит после чисел. Привычная нам алгебраическая нотация "х + у" называется инфиксной. Постфиксную нотацию называют также обратной польской нотацией, сокращенно ОПН. Мы расскажем еще о стеке и постфиксной нотации в следующей главе. Од ним из важных достоинств постфиксной нотации является то, что она не нуждается в скобках, при этом порядок действий определен порядком следования операторов. Попробуйте напечатать на клавиатуре
55 111 + * .
тогда вы увидите
332 ok
Форт берет из стека два верхних числа 55 и 111, складывает их, кладет сумму назад в стек, поэтому в нем находятся
2 166
Оператор * затем умножает число 2 на 166, что дает 332, и кладет в стек результат, который после этого печатается. Наша аналогия с картами показана на . Использование обратной польской нотации в выражении
2 55 111 + *
эквивалентно
2 * (55 + 111)
в обычной алгебраической нотации. Если вы набрали на клавиатуре
55 111 2 + *
то это эквивалентно вводу
55 * (111 + 2)
что, конечно же, отличается от предыдущего примера.

рис. 1.2
Поначалу ОПН и стек немного смущают, но после небольшой практики вы будете считать ее по меньшей мере такой же естественной, как и обычная алгебраическая нотация. В следующей главе у вас будет очень много практики, но вам уже сейчас хочется сделать что-нибудь самостоятельно. Давайте пока отложим в сторону детали, связанные со стеком и обратной польской записью, перестанем использовать Форт как калькулятор и попробуем написать программу. Наберите следующий текст:
: TIMES2 * . ;
и вы увидите только "ok", т.е. будто бы ничего не произошло. Но теперь, если вы напечатаете
5 TIMES2
то увидите
10 ok
Вы только что определили (описали) слово TIMES2 (умножить_на_2) на языке Форт, которое представляет собой крошечную программу: ее действие состоит в том, чтобы поместить в стек число 2, умножить его на то значение, которое было в стеке раньше, и затем напечатать результат. : (двоеточие) дало Форту указание начать определение и принять слово TIMES2 (каждая следующая порция ввода заканчивается пробелом) в качестве имени нового слова Форта. Три символа (2, * и.) -- ранее существовавшие слова Форта, которые рассказывают программе, что она должна делать, затем ; (точка с запятой) указывает на то, что определение закончено. (Обратите внимание, что в качестве слова в Форте может выступать один символ, как, например, в английском языке слова "I" (я) и "а" (артикль).)
Слова Форта можно использовать двояко: либо как команды, которые должны быть немедленно исполнены, примерами этого были наши арифметические упражнения, либо для описания новых слов. Заметим, что ввод
5 TIMES2
приводит к такому же результату, как и ввод
5 2 *
Ввод слов с клавиатуры обычно приводит к такому же эффекту, как и ввод их в определения, хотя имеются и очень важные и необычные исключения.
Почти все, что вы приказываете Форту сделать, обозначается словом или числом. Учтите, что между словами и числами должен быть по крайней мере один пробел. Операторы типа + , * , / и . -- это слова, и даже : и ; -- это тоже слова, которые указывают начало и конец описания слова. (Как вы уже заметили, если в тексте встречается Форт-слово, мы выделяем его полужирным шрифтом и знаки пунктуации отделяем от него пробелами.)
Программа на языке Форт пишется путем составления описаний (определений) новых слов, для которых используются ранее определенные слова, пока не будет определено главное слово, т.е. то слово, которое нужно ввести, чтобы исполнить главную программу. Большинство слов языка создается в виде так называемых определений через двоеточие, потому что их определение начинается с : и заканчивается ; (точкой с запятой).
Программы на языке Форт фактически являются расширением самого языка за счет включения в него новых слов и операторов.
Когда слово, например TIMES2, описывается в форме определения через двоеточие, то говорят, что оно компилируется (помещается, заносится) в словарь Форта, который и на самом деле представляет собой словарь, т.е. комплект слов, описанных на языке, который может понимать компьютер. Когда какое-либо слово выполняет свои действия, как, например, когда вы вводите
5 TIMES2
мы говорим, что слово исполняется. Компиляция и исполнение -- это две основные задачи, которые выполняет Форт. Если вы знакомы с другими языками программирования, то знаете, что они компилируют или исполняют всю программу целиком, но не такие мелкие кусочки, как описания слов.
Одной из причин того, что Форт так легко взаимодействует с пользователем, является как раз то, что программа на нем может быть легко написана (и скомпилирована) в виде набора небольших фрагментов, каждый из которых может быть опробован (и исполнен) немедленно. Именно поэтому Форт так легко изучать экспериментально.
Хотя TIMES2 в буквальном смысле представляет собой настоящую программу, она настолько незатейлива, что вы вряд ли написали бы что-нибудь подобное. Давайте сделаем немного более сложную программу. Сможете ли вы догадаться, что делает следующая программа
: SQUARE DUP * ;
Слово DUP делает копию (дубликат) числа, находящегося в стеке. Предположим, что мы ввели
6 SQUARE .
результатом этого является
36 ok
Слово SQUARE делает копию числа 6 в стеке, оставляя 6 6, а затем перемножает эти два одинаковых числа, получая 36. Очевидно, слово . (точка) выводит результат. Теперь мы можем определить
: CUBE DUP SQUARE * :
для вычисления куба числа. Если вы знакомы с другими языками программирования, то заметите, что наши слова похожи на подпрограммы и, возможно, запротестуете против написания таких коротких подпрограмм. Например, вы бы предпочли определить слово CUBE иначе :
: CUBE DUP DUP * * :
но на языке Форт дополнительные затраты времени исполнения и компьютерной памяти будут незначительны, если вы поделите длинные определения на более короткие (это называется делением программы на части), такие программы проще будет понимать и переделывать. Определения нужно стремиться делать короткими. Приведем полный листинг программы :
: SQUARE ( n1 - n2: квадрат числа ) DUP * ; : CUBE (n1 - n2: куб числа) DUP SQUARE * ;
Мы добавили комментарии, чтобы программа стала более понятной. Любые слова в Форте, заключенные в круглые скобки (с обязательным пробелом после открывающей скобки), игнорируются, в них заключаются комментарии для программиста. В начале комментария в соответствии с принятым для Форта соглашением помещается информация о состоянии стека в виде ( n1 - n2 ).Комментарий о стеке показывает, что было в стеке до исполнения и что стало после исполнения данного слова. Так, например, комментарий о стеке для * выглядит так: ( n1 n2 - n3). Комментарий о стеке необходим для всех, кроме самых коротких слов. Без него очень легко забыть, как работает программа.
В следующем разделе мы напишем нетривиальную программу, которая будет строить на экране столбиковую диаграмму, или так называемую гистограмму, но сначала вам нужно, чтобы идеи, приведенные в этом разделе, усвоились, а для этого проделайте несколько упражнений.
в этой книге, не являются
Упражнения, которые будут приведены в этой книге, не являются дополнительным приложением к тексту. Они составляют его неотъемлемую часть и должны стать неотъемлемой частью процесса освоения языка. Упражнения составлены так, чтобы служить четырем целям:Вы должны попытаться проделать все упражнения, но, если у вас возникнут затруднения, не стесняйтесь заглянуть в ответы : некоторые задачи бросают вам вызов. Упражнения также помогут вам продвигаться в изучении языка. Мы предлагаем серию упражнений после часа-двух интенсивного изучения текста. Наконец, попробуйте сами придумать собственные упражнения, связанные с вашими личными интересами. Один из лучших способов изучения какого-либо языка программирования -- писать собственные упражнения и программы.
4 5 SWAP
оставит два верхних числа:
5 4
Мы собираемся предложить вам модифицировать нашу графическую программу. Проще всего заниматься этим, если вы запишете ее на диск, а затем отредактируете, чтобы внести изменения по условиям упражнения. Форт-система обычно сохраняет программы на дискетах в нумерованных блоках емкостью 1024 символа в каждом, что соответствует размеру экрана (16 строк по 64 символа). Поскольку разные версии Форта отличаются способами хранения и редактирования блоков на диске, рекомендуем обратиться к документации на вашу Форт-систему, чтобы узнать, как сохранить программу на диске. Для того чтобы ввести копию программы на диск, вы будете набирать ее на клавиатуре и пользоваться редактором. Затем программу можно будет загрузить, если ввести номер блока и после него слово LOAD (загрузить), например
68 LOAD
Эта команда вызовет интерпретацию слов, которые с помощью редактора были записаны на диск, так же, как если бы их вводили с клавиатуры. Компьютер не знает и ему безразлично, откуда производится ввод, с клавиатуры или из блока на диске. Большое преимущество этого состоит в том, что можно модифицировать или редактировать программу на диске, не вводя ее каждый раз целиком с клавиатуры. Старую скомпилированную программу можно забыть (т.е. удалить ее из памяти ЭВМ), если ввести FORGET TASK перед вводом измененной программы словом LOAD. Если вы пока не хотите обременять себя изучением редактора, можете каждый раз вводить программу с клавиатуры. Но нельзя просто ввести скорректированное определение какого-либо слова с клавиатуры, если оно входило в определение какого-либо последующего слова. Дело в том, что это последующее слово было скомпилировано раньше совместно со старым определением скорректированного вами слова. В мы подробно обсудим разные редакторы и ввиду того, что многие редакторы, поставляемые с Форт-системами, достаточно примитивны, мы предложим вам редактор, который, возможно, понравится больше. Однако вам все же придется научиться пользоваться редактором вашей Форт-системы хотя бы только для того, чтобы ввести редактор, описанный в .
Прежде чем двигаться дальше, вы должны освоить применение Форта в качестве калькулятора. И не столько для того, чтобы освоить арифметические операции, сколько для того, чтобы научиться прослеживать состояние стека в уме.
Зафиксируйте ваши ответы на бумаге по мере выполнения упражнений (записи потребуются для упражнения 2).
16 х (2 х ((3 + 5) / 4)) = ?
Например, первой операцией должно быть сложение 3 и 5. Теперь переведите вашу последовательность действий в постфиксную форму и найдите решение на Форте. Вы убедитесь, что постфиксная форма не приводит к неопределенности.
5 4 + 5 4 + *
Как вы уже знаете, имеется слово DUP, которое делает копию числа, находящегося на вершине стека, т.е.
4 3 DUP
приводит к тому, что в стеке останется
4 3 3
Можете ли вы сделать упражнение, используя DUP и вводя только по одному разу числа 5 и 4 ? Можете ли вы использовать слово DUP в остальных упражнениях ? Слова для манипуляций в стеке очень полезны, они упрощают операции, экономя время и память.
Они могут показаться вам вначале очень простыми, но потом будут усложняться. Если даже они покажутся вам скучными, пожалуйста, проделайте их. Одной из самых важных компонент изучения Форта является освоение работы со стеком настолько, чтобы вы чувствовали себя с ним комфортно, а слова, связанные с манипуляциями в стеке, стали бы вашей второй натурой. Затраченное вами время сторицей окупится впоследствии.
До После До После До После (а) а b b а (б) а b а b b (в) а b а b а (г) а b а а b (д) а b b b а (е) а b с b с а (ж) а b с а с b (з) а b c с b а (и) а b с b а с (к) а b c с а b (л) а b c а b b c (м) а b с а а b с (н) а b а а b b (о) а b c а а b b с с
Стек Выражение Стек Выражение (а) а b ab (б) а b ab^2 (в) а b2 а + b (г) а b а b (д) а b a^2b^3 (е) а b а(а + b) (ж) a b a^2+2ab+b^2 (указание: разложите на множители) (з) а b с (а + b)/с (и) а b с (a+b)/(b+c) (к) а b с (а+с)/(b+с) (л) а b c (a+b)/(2c) (м) а b c а/(2с)+Ь/(2с) (н) а b с (b-a)(b-c) (о) а b c d ab+ac+ad (п) a b c d ab+abcd
Не применяйте ROLL и PICK !
( n1 n2 - n1 n2 n1 n2 )
Оно применяется для чисел двойной длины. Напишите определение этого слова под именем NEW2DUP, используя для этого только два слова.
( n1 n2 nЗ n4 - nЗ n4 n1 n2).
Определите это слово на Форт-79 и Форт-83.
Некоторые программисты, которые много работают с числами, представленными в двоичной, шестнадцатеричной или восьмеричной системах, действительно могут думать и выполнять арифметические действия в этих системах, однако большинство из нас на это неспособны. Одной из приятных особенностей, которую мы не найдем в других языках, является возможность производить преобразование систем счисления. Программист может думать только в одной наиболее удобной системе счисления, обычно десятичной. (По мере приобретения опыта вы убедитесь, что наиболее удобным основанием для работы с адресами памяти является шестнадцатеричная система.)
Исходное Преобразованное Исходное Преобразованное число число число число (а) 10101011 10100011 (е) 10101111 00001111 (б) 10100011 10100111 (ж) 10100000 00001111 (в) 10100011 00000000 (з) 10101010 01010101 (г) 10100011 11111111 (и) 11110000 00001111 (д) 10101111 10100000
Приемы, использованные в упражнениях 6 и 7, могут быть полезны для проверки состояния разрядов памяти, например для проверки того, включен ли принтер.
Убедитесь в том, что слово .BIN находится в вашей системе. Если это не так, снова введите ею (см. выше). Определи те новое слово, которое печатает двоичные числа без знака следующим образом :
: U.BIN DUP BASE @> 2 BASE ! SWAP U. BASE ! ;
(В чем различие между .BIN и U.BIN?)
Наиболее удобно ( и быстро) коды ASCII преобразуются с помощью операций над отдельными битами, с которыми вы уже знакомы. Обратитесь к и для справки о значениях разрядов, которые нужно преобразовать в приводимых заданиях.
В этом разделе содержится важная
| В этом разделе содержится важная информация | |
| о том, как пользоваться книгой. | |
| Мы надеемся, что вы его обязательно прочтете. |
Форт (FORTH) -- это мощный язык программирования, который обеспечивает высокую производительность ЭВМ. Предлагаемая книга является одновременно учебником и справочником, позволяющим овладеть языком Форт независимо от того, начинающий вы программист или опытный. Вы можете пользоваться этой книгой как учебником для классных или индивидуальных занятий, при этом не предполагается знание основ ЭВМ или программирования. Книга содержит полное описание языка, начинающееся с упрощенного изложения принципов работы компьютеров. Постепенно осуществляется переход к более сложным понятиям, не описанным в других книгах. Вы сами можете выбрать материал и скорость изучения с учетом своего уровня подготовки. В книге приводится множество примеров, показывающих стиль программирования на языке Форт, включая полный текст программы редактора, большое количество упражнений для приобретения опыта, уверенности и, что более важно, полезных технических навыков. В даны ответы к упражнениям. Излагаются обе версии языка Форт в соответствии со стандартами 1979 и 1983 гг. Кроме того, изложены вопросы, не освещаемые в стандартах, в том числе организация файлов данных, обработка алфавитно-цифровых текстов, арифметика с плавающей запятой, а также различные инструментальные программные средства, что поможет вам расширить сферу применения языка Форт для ваших нужд. Даны примеры функций усовершенствованной реализации языка MMSFORTH.
Материал книги построен для использования ее в качестве справочника так, чтобы охватить весь набор средств и приемов и полный перечень слов и функций языка. Подробное содержание каждой главы дается в разделе содержания и в начале каждой главы и кроме того, имеется сводка перекрестных ссылок для облегчения поиска нужной информации. Приведены также два очень подробных глоссария: слов языка программирования Форт (включая стандартные, а также расширяющие слова и слова, определенные в книге) и терминологии компьютеров и понятий, относящихся к языку Форт.
Приводимый материал и его организация должны сделать эту книгу подручным средством для программирующих на языке Форт.
Что же представляет собой язык Форт ? Основная его часть -- это набор слов, или словарь, из которого слово вызывается по имени для выполнения специфических функций. Слова языка Форт , , , , соответственно складывают, вычитают, умножают и делят два числа. Программа на Форте пишется путем определения новых слов с использованием слов, ранее уже определенных в словаре. Как только новые слова скомпилированы в словарь, они не отличаются по форме от тех слов, которые в нем уже имеются. Программа на Форте -- это не более чем расширение самого языка, это резко отличает его от таких языков программирования, как BASIC, FORTRAN или PASCAL, в которых язык транслирует всю программу в закодированную форму, понятную компьютеру, не меняя самого языка. И вследствие того, что Форт использует словарь, новые слова и программы требуют гораздо меньше памяти и могут выполняться так же быстро, как эквивалентные им программы на других языках.
Кроме того, языком Форт очень легко пользоваться. Подобно Бейсику, он сразу же реагирует на любую команду (слово), введенную с клавиатуры. Однако новые слова Форта и программы компилируются в словарь, поэтому они могут выполняться с той же скоростью или быстрее, чем, например, на Фортране, который к тому же при трансляции программ порождает большие объемы машинного кода. Компиляция программы на языке Форт делается значительно быстрее и проще, чем на Фортране, Коболе, Паскале и других языках.
Есть у форта и другие своеобразные черты. Почти все языки программирования используют средства операционной системы (например, СР/М /си-пи-эм/, MS-DOS) для обеспечения ввода информации с клавиатуры, вывода ее на экран дисплея, сохранения программы на диске и других функций. В противоположность этому многие версии языка Форт служат сами себе и операционной системой, или, иначе говоря, все функции операционной системы могут быть написаны на языке Форт и включены в качестве части его словаря.
И в то время как другие языки для сохранения программы и данных пользуются именованными файлами на дисках, Форт сохраняет такую же информацию в нумерованных блоках емкостью 1024 байта (1 Кб) каждый. Хотя и в языке Форт могут использоваться файлы, а сам он может работать с верхним уровнем операционной системы, Форт выполняет функции операционной системы специфическим и необычным образом.
В большинстве языков программирования требуется, чтобы числа хранились в виде переменных. В Форте также есть именованные переменные, но для хранения чисел, так же как и для передачи их из одного слова в другое, в основном используется стек, позволяющий экономить много времени (числа не приходится выбирать из памяти, но при условии что стек реализован аппаратно на кристалле или находится в быстрой кэш-памяти). Применение стека является как раз той новинкой (точнее особенностью), которую нужно прежде всего освоить в языке Форт.
Форт -- структурированный язык. Такие языки, как Фортран и Бейсик, допускают написание программы с переходами из одного места в другое, что потенциально может привести к путанице и беспорядку. Некоторые структурные языки, например Паскаль, заставляют разрабатывать программу в логической последовательности, но делается это с помощью очень строгих правил, которые ограничивают программиста более жестко, чем на языке Форт. Разработка программ на Форте и их организация производятся, обычно одновременно на двух уровнях. На верхнем уровне программист разрабатывает план того, что программа должна делать, пользуясь часто логической блок-схемой программы. На нижнем уровне программа пишется с помощью определения слов, необходимых для выполнения задач верхнего уровня. Каждое определяемое слово может быть проверено и отлажено отдельно или в сочетании с другими словами, что приводит к существенному сокращению времени разработки и тестирования программы.
Именно возможность писать короткие определения слов и программ и делает язык Форт легким для изучения. Новое определение может быть введено с клавиатуры и немедленно проверено.
Поэтому форт можно и нужно изучать на практике методом "проб и ошибок". И хотя Форт заключает в себе некоторые тонкости и абстракции, можно изучить лишь небольшую их часть, чтобы писать полезные программы. Вы сможете писать программы и экспериментировать с ними раньше, чем дойдете до середины первой главы. Для облегчения обучения очень важны немедленная реакция и подтверждение, и только два языка -- Бейсик и Лого приближаются к Форту в этом отношении. На протяжении всей книги мы будем разбирать небольшие примеры и делать упражнения. Вам следует все их попробовать решить, но еще большему вы научитесь, если будете сами ставить и решать свои собственные задачи.
И все же многие утверждают, что Форт труден для изучения. Для этого имеется несколько причин. Опытным программистам Форт зачастую дается труднее, чем новичкам, потому что он отличается от других языков программирования по самой своей природе. Хотя в языке Форт нет каких-либо присущих только ему сложностей, программисты с трудом отвыкают от переменных, подпрограмм, многословного текста на исходном языке, алгебраических обозначений и прочих атрибутов привычных им языков. Если вы знаете другие языки программирования, попробуйте к языку Форт подойти с полной отдачей. Освойте понятия стека и определения слов, прежде чем переходить к более сложным вопросам. Забудьте всякие предубеждения, которые у вас могут возникнуть, вроде того, что для хорошего языка программирования обязательно нужна операционная система и файловая поддержка. И не беспокойтесь о блок-схеме, начните с небольших задач, ваш опыт будет накапливаться постепенно. Форт может показаться трудным, так как это достаточно мощное средство программирования.
Действительно, все возможности языка изучить трудно, но все они и не потребуются, чтобы писать очень полезные программы. На Форте можно научиться писать программы на уровне хорошего программиста, пользующегося языками Бейсик и Фортран, быстрее, чем на любом другом языке. Так же несложно программирование на Форт-ассемблере (определение слов Форта в машинных кодах).
Можно описать слова, которые будут создавать в словаре совершенно новые типы данных, использовать Форт для модификации самого языка, для операций с большими объемами данных в памяти компьютера и даже для того, чтобы реализовать новые языки программирования. Конечно, изучение этих вопросов может быть трудным. И, хотя мы осветим большую их часть, вы сможете хорошо программировать на языке Форт раньше, чем овладеете ими всеми. Форт предоставляет вам мощные средства для управления работой компьютера, присущие другим языкам программирования, включая машинный язык, но вам эти мощные средства скорее всего не потребуются.
Существует одна причина, из-за которой Форт иногда оказывается действительно трудным. Дело в том, что в стандартах языка и в поставляемых потребителям реализациях языка отсутствуют некоторые слова для выполнения основных или важных функций, предусмотренных другими языками. В таких случаях программист вынужден сам написать слова, которые должны выполнять эти важные функции. Например, стандартный (ANS'83) Форт не содержит операций над числами с плавающей запятой, в нем нет трансцендентных функций (тригонометрических, логарифмической); не определены стандартами операции с символами и символьными строками (например, извлечение отдельных слов из текста), работа с файлами данных, графические возможности. Нет в стандарте и слова, позволяющего вводить числа в процессе исполнения программы. К счастью, во многих поставляемых реализациях Форта предусмотрены слова, позволяющие преодолеть эти ограничения, а Форт настолько мощный, что позволяет самому написать такие слова, если знать, как это сделать. И мы вас этому научим.
Подводя итог, можно сказать, что Форт -- это в то же время мощный и неразвитый язык. Мощный он потому, что программы, написанные на нем, занимают мало места в памяти и исполняются с такой же скоростью или быстрее, чем на других языках, он дает потенциально неограниченные возможности управления компьютером, и писать и отлаживать программы довольно несложно.В то же время стандартный Форт неразвит, ввиду того что не предусматривает выполнения некоторых важных функций, которые являются неотъемлемой частью других языков или операционной системы и доступны для программиста. Введение этих функций предоставлено программисту или предусматривается в некоторых версиях Форта. Почему же стандартный Форт так бедно определен ? Чтобы понять это, надо немного познакомиться с его создания.
При изучении языка Форт, возможно,
При изучении языка Форт, возможно, наиболее важным является умение легко и быстро работать со стеком. К данному моменту вы должны хорошо понимать, как работает стек и как его можно перестроить, если это необходимо. После овладения операциями в стеке вам легче будет освоить материал последующих глав. Если вы чувствуете, что вам нужно попрактиковаться еще, вернитесь к разбору предыдущих упражнений или придумайте свои собственные задачи и решите их. Конечно, у вас еще будет много практической работы со стеком в нескольких следующих главах. Вскоре вы обнаружите, что сможете инстинктивно писать разумные программы, едва ли задумываясь о состоянии стека.Теперь вы должны хорошо представлять себе, как хранятся числа в памяти, что такое двоичные, десятичные и шестнадцатеричные числа, как пользоваться словом BASE, чтобы посмотреть числа в различных системах счисления и как обращаться с отдельными разрядами чисел. Если вы разобрались в этих вопросах достаточно хорошо, то вы уже знаете больше о работе компьютера, чем многие программирующие на Бейсике, Фортране и Паскале. Мы изложили эти вопросы в начале, чтобы заложить прочный фундамент. Но никто, кроме программиста, который постоянно работает на уровне битов и байтов, не может точно сказать, что делают операторы типа XOR и AND. Почти все мы вынуждены заглянуть в таблицу или ввести что-то с клавиатуры, чтобы вспомнить, чему равен шестнадцатеричный эквивалент числа 245, а большинство из нас не смогли бы даже вспомнить, как хранится знак числа, если бы не было этого краткого обзора. Важно то, что если вы однажды поняли этот материал, то вам будет легко вспомнить его, если потребуется. Поэтому не пытайтесь запомнить все детали этой главы. Вы можете подумать, что, хотя вы поняли представление алфавит-но-цифровой информации кодами ASCII, вам это не принесло практической пользы. Вы правы. Но уже скоро мы узнаем о том, что возможности Форта в обработке символьной информации очень велики.
Наконец, в этом месте вы можете задать вопрос: должны ли мы работать только с целыми числами ? Сам по себе Форт, определенный в соответствии со стандартами, не применяет арифметики с плавающей запятой (т.е. арифметики, которая учитывает положение десятичной запятой в числах). Некоторые считают, что целочисленной арифметики вполне достаточно, но для того, кто занимается решением научных и технических задач, ясно, что это бессмыслица. Хотя действия с целыми числами выполняются быстрее, да и в других языках программирования использованием чисел с плавающей запятой иногда чрезмерно увлекаются, все же бывает, что целые числа при решении практических задач оказываются неудобными. В мы узнаем, как работать с числами с плавающей запятой. И, кроме, того узнаем значительно больше о других мощных арифметических операциях в языке Форт.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования