Память Форта. Словари и контекстные словари
Теперь мы знаем, что Форт предоставляет вам большую власть над ЭВМ, чем большинство других языков. Целью данной главы и двух последующих является предоставление вам возможности еще большего контроля. Форт обеспечивает значительную гибкость там, где это возможно, за счет создания новых слов-описателей или, например, написания редактора. Но для использования всех преимуществ Форта, его мощи и максимальной гибкости вы должны иметь хорошее понимание внутреннего устройства языка. Вы должны понять, например, как Форт использует память ЭВМ, как устроен словарь и как он работает. Мы раскроем эти темы в данной главе. Вы должны также понять, как Форт интерпретирует входной поток информации, как он интерпретирует слова и как происходит исполнение слов. Это темы гл.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-словам.
Цикл - редактор-ассемблер-редактор-связей-загрузка-исполнение-отладка, который необходим для любого ассемблера, исключается в форте. Форт-ассемблер позволяет компилировать и тестировать машинную программу небольшими частями, так же как и обычную Форт-программу. В результате программирование оказывается более производительным, а программы - лучше организованными и более эффективными. Время разработки программы в Форте составляет лишь долю того, что требуется для случая чисто ассемблера.
Мы начали эту книгу, сказав, что Форт позволит нам применить все доступные особенности вашей ЭВМ, использовать все быстродействие, на которое она способна, и сделать это более легко, чем в любом другом языке. Разумно закончить книгу описанием Форт-ассемблера, поскольку именно комбинация применения ассемблера и "нормального" Форта обеспечивает полную эффективность. Форт может быть использован на разных уровнях: простейший - интерактивный калькулятор для коротких программ без долговременного их хранения, наиболее сложный уровень - применение Форт-слов высокого уровня в комбинации с ассемблером для создания комплексных программ, таких как контроллеры процессов в реальном масштабе времени, системы управления базами данных, экспертные системы и даже другие языки программирования.
Манипуляции в стеке
Хотя Форт нетрудно использовать в качестве калькулятора, вы поняли из упражнений, что было бы полезно иметь средства для перемещения чисел внутри стека. Эта потребность обеспечивается словами для стековых манипуляций, которые приведены в . Все они в совокупности позволяют вам перестраивать числа в стеке, как вы захотите.
Однако, прежде чем перейти к их изучению, полезно определить слово, которое позволит просматривать содержимое стека, не разрушая его.
Некоторая критика языка Форт
Оппонентами языка Форт чаще всего бывают программисты, не сумевшие понять его общие идеи и большие возможности. Форт устроен так, чтобы обеспечить максимальную мощь и гибкость языка высокого уровня. Это подразумевает, что функции, которые заранее определены в других языках, в минимальных реализациях языка Форт могут отсутствовать. Например, стандарт языка Форт описывает только простейшие методы обращения с символьными строками, не содержит процедур для работы с матрицами и векторами, не обеспечивает чтение данных из файлов, простейшие реализации не могут обращаться с числами с плавающей запятой (как, например, число 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.