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


Операторы сравнения и ветвления


Одной из наиболее важных задач, которую должен уметь делать любой язык программирования высокого уровня, является выполнение некоторых операций на основании истинности или ложности некоторых условий. Например, если два верхних элемента в стеке равны, должен быть выполнен один оператор, но если они не равны, то должно быть сделано что-то другое. Такие условные операции, возможно, проще понять на примере из Бейсика, потому что их структура близка к естественному языку. Если в выражении 100 IF A=B THEN X=Y ELSE GOSUB 300 переменные А и В равны, то значение переменной Х устанавливается равным значению Y; в противном случае выполняется некоторая подпрограмма, начинающаяся в трехсотой строке программы. Это позволяет программе выполнять разные действия при различных обстоятельствах. Подобные конструкции, которые управляют потоком (прохождением) программы, называются управляющими структурами; они включают в себя конструкцию IF...THEN , счетные циклы, которые вкратце уже рассматривались, и другие средства для осуществления переходов в программе, рассматриваемые в данной и последующей главах.

В отличие от Бейсика конструкция IF...THEN сравнивает числа не в виде переменных, а в стеке и в Форте каждое слово конструкции - это фактически подпрограмма. Конечно, отличается также и постфиксная форма записи. Поэтому условное исполнение и ветвление в языке Форт записываются на языке Форт несколько по-другому. Приведенному выше выражению на Бейсике в форте будет эквивалентна следующая конструкция: А @ В @ = IF Y @ X ! ELSE DOTHAT THEN

Конечно, на практике такое большое количество переменных в Форте никогда не используется. Рассмотрим приведенную конструкцию более внимательно. Предложение А @ В @ извлекает значения двух переменных и кладет их в стек. Операция - возвращает значение, зависящее от того, равны или не равны эти два числа (заметим, что - является оператором сравнения, а не присвоения, как на Фортране или в Бейсике). Если числа в стеке равны, то говорят, что условие истинно, тогда в Форт-79, MMSFORTH и большинстве других версий в стек возвращается 1, а в Форт-83 - число -1 (т.е. 16-разрядное число, у которого все разряды равны 1, или FFFF в шестнадцатеричной системе счисления).
Если числа не равны, то говорят, что условие ложно, при этом во всех версиях Форта в стек возвращается 0. Величина, возвращаемая в стек оператором сравнения, называется значением истинности, булевым флагом или просто флагом. В языке Форт любое ненулевое число всегда считается истинным и 0 всегда ложным. В данном случае если перед IP находится истинное (ненулевое) значение, то исполняются слова, находящиеся между оператором IF и ELSE, слова между ELSE и THEN пропускаются и затем продолжается исполнение той части, которая следует за словом THEN. Если перед IF находится ложное значение (0), исполнение перескакивает на слово, которое следует после ELSE и продолжается до слова THEN.
На рис. 7.1 проиллюстрирована эта идея. Обратите внимание, что конструкции IF-THEN и IF... ELSE... THEN могут быть использованы только в определениях через двоеточие. Вскоре мы более подробно обсудим конструкцию IF...ELSE...THEN, а пока познакомимся с некоторыми другими операторами сравнения.
Проверка истинности
Теперь кратко остановимся на булевом флаге. То, что в Форт-83 (и других языках) оператор сравнения возвращает значение флага -1, а не 1, имеет определенное основание. При обнаружении истинности условия число -1 или шестнадцатеричное FFFF, т.е. содержащее во всех разрядах единицы, оказывается иногда более удобным для использования его с булевскими операциями. Предположим, например, что требуется заменить число нулем, если флаг имеет значение ложь, и оставить без изменения, если флаг имеет значение истина. Если числа находятся в стеке, причем флаг на вершине, то оператор AND ("И") выполнит эту задачу в Форт-83, но не в Форт-79, для которого потребуется конструкция 0 = IF DROP 0 THEN
Хотя подобные случаи не так уж часты, иногда программа может дать выигрыш по времени, пользуясь этой особенностью флага. (При необходимости вспомните действие оператора AND в гл. 3.) Если посмотреть с более общих позиций, то в Форт-83 значение истина имеет не только сам флaг, но и каждый его разряд, что может оказаться полезным при операциях поразрядного сравнения.


Обратимся теперь к операторам сравнения; слово = (равно) - только одно из нескольких предусмотренных в языке Форт. Имеются также операторы сравнения для одинарных чисел и чисел двойной длины со знаком и без знака. В табл. 7.1 приводится сводка этих операторов. Большинство из приведенных операторов в комментариях не нуждаются. Некоторые особенности имеют операторы U< (и по аналогии DU


Операторы U< и UD< применяются для сравнения больших чисел, чтобы они не рассматривались как отрицательные. Как и для арифметических операций, при использовании чисел без знака требуется некоторая осмотрительность.
Особого внимания заслуживает оператор 0=. Он всегда меняет результат сравнения на обратный. каким образом, число, не равное нулю, превратится в 0, в то время как 0 превратится в 1 (или в -1 Форт-83). Для повышения удобочитаемости программ в Форт-79 слово 0= имеет стандартный синоним NOT (не), который действует аналогичным образом. (В Форт-83 оператор NOT действует по другому, как было описано в гл. 3.) Если целью сравнения является выяснение того, действительно ли в стеке находится нуль, то более оправдано применение оператора 0=, в то время как оператор NOT уместнее, если целью является изменение значения истинности на обратное. Заметим, что, если в Форте нет слова <>, его можно заменить конструкцией = NOT или = 0=. Точно так же оператор - (минус) будет возвращать значение 0 или не 0, как оператор <>. Запомните, что операторы сравнения снимают числа из стека. Поэтому, если вам потребуются эти числа для дальнейшей работы, их необходимо скопировать в стеке. Обычно это делается с помощью операций OVER OVER или 2DUP.
Таблица 7.1. Операторы сравнения *
Имя Операнды Определено ли Результат (возвращаемое значение) слова в стеке стандартом?
= n1 n2 да истина, если n1 = n2 <> n1 n2 нет истина, если n1=/=n2 < n1 n2 да истина, если n1 < n2 > n1 n2 да истина, если n1 > n2 = n1 n2 да истина, если n1 >= n2 0= n да истина, если n = 0 0< n да истина, если n < 0 0> n да истина, если n > 0 D= d1 d2 да истина, если d1 = d2 D< d1 d2 да истина, если d1 < d2 D0= d да истина, если d = 0 U< u1 u2 да истина, если u1 < u2 DU< ud1 ud2 да истина, если ud1=ud2 * Нестандартные слова включены в MMFORTH.


Прежде чем продолжить рассмотрение применения операторов сравнения, познакомимся с логическими операторами AND, OR и XOR. Они позволяют комбинировать несколько условий. Пусть, например, вы хотите выполнить какую-то операцию только в том случае, если для переменных А, В и С выполняются условия А=С и В=С. А @ С @ = В @ С @ = AND IF ...
Если обе пары переменных (первая и вторая) равны, то оператор AND, обнаружив в стеке 1 1 (или -1 -1), возвратит в стек 1 (или -1). Если одна или обе пары не равны, в стеке будет по крайней мере один 0, тогда оператор AND возвратит 0. Аналогично используется логический оператор OR. Допустим, что вы хотите выполнить какое-то действие, если или А=С, или В=С, или А=В=C. Это можно сделать следующим образом: A @ C @ = B @ C @ = OR IF ...
Если либо одно, либо другое равенство (либо оба) истинны, тогда по крайней мере одно условие истинно, поэтому оператор OR обнаружит в стеке хотя бы одну 1 (или -1, т.е. число со всеми разрядами, равными 1) и выдаст в стек значение истина. Если оба равенства ложны, то оператор OR, увидев в стеке два нуля, возвратит в стек значение ложь (нуль). Наконец, пусть необходимо, чтобы какое-либо действие выполнялось только в том случае, если одно равенство выполняется, а другое нет. Это можно сделать так : A @ C @ = B @ C @ = XOR IF ...
Оператор XOR возвращает в стек значение истина, если в стеке есть флаги истина и ложь (разряды в одном числе установлены в 1, а в другом не установлены). Комбинируя операторы сравнения с логическими операторами AND, OR и XOR, можно выполнять всевозможные комбинации сравнений. Если вы знакомы с булевой алгеброй и диаграммами Венна, то представляете, как изображать на них всевозможные сочетания условий для облегчения реализации большого разнообразия условного исполнения программ в комбинации с конструкцией IF...ELSE...THEN.
Упражнения
1. Пусть в версии языка, с которой вы работаете, есть только один оператор сравнения . Определите 0=. Определите =. Определите <>. Определите 0-.


Все введенные вами определения снабдите префиксом NEW. 2. Пусть числа а, b, с и d находятся в стеке в указанном порядке. Напишите последовательность слов, которая выдавал бы флаг истина тогда, и только тогда, когда выполняются следующие условия: а) а=b И c=d б) а=b ИЛИ с=d в) a=b И c<>d r) a=b ИЛИ c<>d д) a<>b И c<>d e) а=b ИЛИ c=d, но не одновременно ж) а>b И c b > с . 3. Используя слово MOD, дайте определение слова ?REM=0 (остаток равен 0?), которое возвращает флаг истина тогда, и только тогда, когда второе число в стеке делится без остатка на число, находящееся на вершине стека. 4. Дайте определение слова ?REM, которое будет возвращать в стек флаг ложь при условиях упражнения 3 и истина в противном случае. 5. Определите слово ?OPPOSITE (?противоположные), которое возвращало бы флаг истина тогда, и только тогда, когда оба числа по модулю были бы равны, но имели противоположные знаки. Определите слово NEW=, используя только О= и арифметический оператор. 6. Определите слово D= , пользуясь только арифметическим оператором и словом 0=. 7. Пользуясь соглашениями Форт-79, определите слово СОМР, которое будет возвращать -1, если число отрицательное, 0, если оно равно 0, и 1, если оно положительное, не применяя конструкции IF.
Операторы IF, ELSE и THEN
Операторы сравнения мало полезны без других операторов, которые реагируют на значение флага, возвращаемого в стек операторами сравнения. Наиболее важным из наших условных операторов является слово IF, используемое в конструкции IF...ELSE...THEN, рассмотренной в начале этой главы. Другие важные слова, реагирующие на флаг проверки условия, это UNTIL и WHILE, применяемые в конструкциях BEGIN-UNTIL и BEGIN...WHILE...REPEAT; эти конструкции позволяют зацикливать программу до тех пор, пока значение флага есть истина или ложь. Слово BEGIN и связанные с ним слова мы рассмотрим более внимательно в гл. 8.
Еще два условных оператора, имеющихся во многих версиях Форта, это ?DUP и ABORT", к ним мы еще вернемся в этом разделе.


Действие конструкции IF...ELSE-THEN можно в сущности представить следующим образом (см. также рис. 7.1) : ( флаг ) IF ( число в стеке не равно нулю, исполнить слова, стоящие здесь) ELSE ( если нуль, исполнить эти слова) THEN ( в любом случае продолжить отсюда)
Слово ELSE не является обязательным и часто опускается. Если оно отсутствует, то остается только одна возможность: исполнить слова, находящиеся между IF и THEN, если флаг, который оператор IF видит в стеке, является истинным. Со словом ELSE имеются две возможности : исполнить слова, находящиеся между IF и ELSE, если флаг имеет значение истина, или слова, заключенные между ELSE и THEN, если флаг имеет значение ложь. В обоих случаях исполнение продолжается после слова THEN. Лучше всего можно понять эти идеи на примере.
В гл. 6 вы познакомились с программой, в которой оператор вводил вес болтов, а затем слово WT. После этого число классифицировалось по величине и изменялись соответствующие переменные. Допустимый вес находился в диапазоне 0 - 300 г. Предположим, что, вы хотите предотвратить ввод оператором неверного числа, которое или очень велико, или мало. Можно сделать это, проверив величину числа с помощью операторов сравнения, а потом принять это число либо выдать сообщение об ошибке в соответствии с конструкцией IF... ELSE... THEN. Чтобы осуществить это, переименуйте прежнее слово WT в (WT) (заключением слова в скобки чаще всего подчеркивают, что оно является частью другого слова со сходным названием) и определите новое слово следующим образом: : WT DUP 0< OVER 299 > OR 0= IF (WT) ELSE DROP ." Вышли за пределы диапазона" THEN ;
Заметим, что необходимо включить ELSE DROP для исключения неверных данных из стека перед словом (WT). Слово WT можно немного упростить, убрав оператор 0м следующим образом: : WT -DUP 0< OVER 299 > OR IF DROP ." Вышли за пределы диапазона" ELSE (WT) THEN ;
Вообще слово 0= перед конструкцией IF...ELSE...THEN вовсе не требуется, так как точно такого же результата можно добиться, переставляя слова между IF и ELSE со словами, находящимися между ELSE и THEN.


Конструкции IF...THEN могут быть вложенными, т.е. их можно использовать внутри таких же конструкций. В слове WT мы классифицировали значения, пользуясь делением на 100 (/) и последующим векторным исполнением. Можно сделать то же, применяя оператор IF. Пусть, если вес меньше 100 г, должна выполняться задача 1TASK, если вес от 100 до 200 г - задача 2TASK, и задача 3TASK, если вес от 200 до 300 г. Можно сделать так; : (WT) DUP 100 < IF 1TASK ELSE DUP 200 < IF 2TASK ELSE DUP 300 < IF 3TASK THEN THEN THEN DROP ;
Метод векторного исполнения с операцией деления на 100 может показаться более изящным, но он не намного быстрее и, кроме того, не будет работать, если разбросы веса в каждой группе неодинаковы. Заметьте, что в данном случае мы использовали как IF...THEN, так и IF...ELSE...THEN конструкции. Первые два оператора IF нужно использовать с ELSE, потому что, если условия ложны, число должно переходить в следующий класс с большими значениями веса. А так как слово (WT) является частью слова WT, третий оператор IP используется без ELSE, потому что число больше 300 быть не может. Немного дальше мы покажем, что можно также применить конструкцию выбора одной из нескольких возможностей (переключатель).
Некоторые программисты строго придерживаются манеры выделять вложенные операторы IF...THEN, подчеркивая их вложенность, поэтому запись определения (WP) в предыдущем примере вызовет у них возражение, так как она плохо сформатирована, и они отдадут предпочтение такой форме записи: : (WT) DUP 100 < IF 1TASK ELSE DUP 200 < IF 2TASK ELSE DUP 300 < IF 3TASK THEN THEN THEN DROP ; Решайте сами, стоит ли ради наглядности программы занимать больше места на диске.
Вот еще один пример. Пусть нужно выполнить операцию D0IT, используя остаток от деления на 22 только тогда, когда число в стеке не кратно 22, т.е. если n 22 MOD возвратит в стек ненулевое значение. Вот как это можно сделать : : ?22-MULTIPLE 22 MOD DUP IF DOIT ELSE DROP THEN ;
Часто функция оператора IF может быть успешно выполнена без оператора сравнения.


В этом случае что- то должно быть сделано или не сделано в зависимости от того, находится ли в стеке нуль или не нуль. Сочетание ELSE DROP необходимо для того, чтобы убрать из стека нуль, порожденный оператором DUP. В подобных случаях очень полезно слово ?DUP, поскольку оно кладет в стек копию числа только в том случае, когда оно не равно нулю, заменяя два слова ELSE DROP. Вот, например, короткое определение слова ?22-MULTIPLE (кратно ли 22?): : ?22-MULTIPLE 22 MOD ?DUP IF DOIT THEN ;
Если в стеке находится нуль, слово ?DUP ничего не выполняет, следовательно, применяя его, мы избавляемся от заботы очищения стека от оставленного нуля. ?DUP не дает никакого выигрыша, если перед оператором IP стоит оператор сравнения. Но если перед IF следовала арифметическая операция и при ненулевом значении результата должна быть выполнена другая программа, тогда использование ?DUP экономит время - особенно в цикле. Применение оператора MOD совместно с IF весьма полезно в циклах DO-LOOP, когда нужно что-то выполнить с определенным интервалом. Например, : 7MULTIPLES? 1+ 0 SWAP 1 DO I 7 MOD 0= IF 1+ THEN LOOP . ." раз число 7 содержится в данном числе " CR ; при исполнении 38 7MULTIPLES? выведет в результате 5 раз число 7 содержится в данном числе
Хотя данный пример тривиален, в нем показан прием, который пригодится для составления более сложных программ, чем определение кратности одного числа другому.
Некоторые замечания о структурном программировании
Переход на исполнение определенных операций (т.е. подпрограмм) является одним из наиболее важных механизмов языка программирования. В таких языках, как Бейсик, допускается переход в любое место программы, куда вы хотите, не обязательно к четко оформленной задаче или подпрограмме. И возврат не обязательно должен происходить в точку, из которой произошел переход. Оператор Бейсика GOTO nnn где nnn - номер строки, позволяет делать переход в программе произвольно, с возвратом или без возврата в исходную точку, как вам кажется удобнее.


Это потенциально может привести к запутанной программе, в которой трудно проследить ход действий, что затрудняет ее отладку и модификацию.
Альтернативным методом является структурное" программирование. Упрощенно структурное программирование заключается в том, что программа составляется таким образом, что когда она уходит на подпрограмму, то ее исполнение затем продолжается с точки, следующей сразу же после точки ухода на подпрограмму, и в подпрограмме имеется только одна точка входа. Язык Форт поощряет структурный подход в программировании, так как любое слово Форта по существу является подпрограммой, у которой обычно имеется только один вход и один выход. Другие языки, например Паскаль или Модула-2, также обязывают к применению структурного программирования ввиду того, что вход в подпрограмму может быть произведен с помощью вызова именованной процедуры, возвращающей управление в точку, из которой она была вызвана. Это обстоятельство может показаться неудобным, потому что каждый переход на подпрограмму требует самостоятельной именованной процедуры. Форт в этом отношении обладает большей гибкостью. Поскольку все, что делается на языке Форт, исполняется подпрограммами, т.е. словами-операторами Форта, ли программы структурные по своей природе. (Исключением, как мы увидим в дальнейшем, является слово EXIT, которое позволяет немедленно прекратить исполнение слова.) Кроме того, поскольку в любой подпрограмме некоторые слова можно переделать, дать им более короткие имена, использование переходов становится не столь обременительным, как в Паскале.
Упражнения
1. Определите слово NEWABS (абсолютное значение), используя конструкцию IF...THEN. (Совет: воспользуйтесь словами 0< и NEGATE). 2. Определите слово, подобное / (т.е. деление нацело), которое должно выдавать сообщение об ошибке при попытке деления на нуль. 3. Определите слово ТYРЕ
Прекращение исполнения задания
Обычно исполнение слова Форта продолжается до тех пор, пока не встретится последнее слово в о определении.


Форт- программа исполняется до конца слова, которое вызвало его исполнение. В некоторых случаях нужно прекратить исполнение слова или программы досрочно. Завершение исполнения слова возвращает управление слову, которое запустило программу, в противоположность этому преждевременное прекращение программы возвращает управление терминалу.
Сначала мы познакомимся с тем, как можно выйти досрочно из исполнения слова с помощью оператора EXIT. Пусть вы определили : ADD + EXIT . ; : SUM 3 4 ADD 5 6 ADD ." Суммы" ;
Если вы запустите слово SUM, а потом посмотрите, что находится в стеке, то увидите, что, хотя обе пары чисел были введены и просуммированы, ни один из результатов не выведен на экран, по тому что операция ADD была окончена раньше, чем встретилось слово . (напечатать). А сообщение "Are the sums" (суммы) напечаталось. Дело в том, что, когда завершилось исполнение слова ADD, программа SUM еще не завершилась и управление было передано слову SUM. Теперь попробуйте тот же пример, заменив в нем EXIT на QUIT. Вы не увидите завершающего программу сообщения, но если заглянете в стек, то увидите там только первую сумму. Слово QUIT не только прекратило операцию ADD, оно завершило исполнение программы передав управление клавиатуре. Если слово QUIT сохраняет содержимое стека, то слово AUORT делает очистку стека.
Из данного примера не видно явно никакой пользы от новых слов EXIT, QUIT и ABORT. Зачем же нужны слова, которые только прекращают действие программы или слова до их завершения? Ответ состоит в том, что эти слова используются обычно в конструкциях IF...THEN, о чем мы вскоре расскажем. Назовем лишь два самостоятельных употребления этих слов. Во-первых, ни QUIT, ни ABORT не оставляют на экране сообщения "ok", если они стоят в конце программы. Вспомним пример программы WT, в которой числа вводились после того, как вы печатали WT и , и на экране получалось что-то вроде 223 WT ok 16 WT ok 59 WT ok
Можно сделать ввод более красивым, добавив в конце определения слова WT фразу SPACЕ QUIT.


Тогда после каждого WT на экране мы увидим 223 WT 16 WT 59 WT
Слово ABORT будет делать то же самое, но, кроме того, еще очищать стек. Использование слов ABORT и QUIT может быть полезным для подавления сообщения "ok".
Помимо этого, самостоятельное применение указанных слов может оказаться полезным при отладке программы. Предположим, что вам нужно проверить, не приводят ли к ошибке слова, которые вы ввели в конце какого-либо определения через двоеточие. Тогда можно сделать, чтобы эти слова игнорировались бы с помощью слова EXIT (разумеется, то же можно сделать, помещая эти слова в круглые скобки). Можно также применить QUIT, чтобы прервать исполнение программы каком-то месте и посмотреть содержимое стека или переменных. Слово ABORT в этом применении менее полезно, так как оно очищает стек, который нас, безусловно, интересует. Как уже было сказано, EXIT, QUIT и ABORT чаще всего используют в конструкции IF...THEN. Пусть нам нужно обеспечить возможность останова исполнения программы в определенном месте. Используйте для этого слово : ?ABORT " Нажмите S для прекращения программы" KEY 83 = IF " ok" CR ABORT THEN ;
Если ?ABORT вставить в какое-либо место в программе, пользователь будет иметь возможность остановить ее исполнение. A ."ok" CR введены в программу для того, чтобы напечатать сообщение "ok" и сделать возврат каретки, чего не делает слово ABORT. Очевидно вы можете заменить его словом QUIT, если хотите сохранить содержимое стека.
Слова ABORT и QUIT очень часто используются для обнаружения ошибок. Нам совершенно нежелательно разрешать деление на 0 (что может произойти из-за ошибки в вашей программе). Вот такое слово, которое можно вставить в программу непосредственно перед оператором деления / для того, чтобы выйти из вашей программы, если произойдет такая ошибка: : 0/? DUP 0= IF ." Ошибка деления на 0" DROP QUIT THEN ;
Заметим, что DROP можно убрать, если использовать ?DUP, и тогда получим : 0/? ?DUP 0= IF ." Ошибка деления на 0" QUIT THEN ;


Вам может потребоваться также досрочное завершение программы при достижении какого-либо заранее определенного условия. Представим себе, что есть программа, которая принимает данные от удаленного компьютера по телефонной линии, и нужно, чтобы она останавливалась, если будет получен управляющий код Ctri-C ( код ASCII равен 3). Можно после каждого входного символа вставить проверку с помощью слова : ?CTLC DUP 3 = IF ." Прекращение по дистанционному запросу " ABORT THEN ;
Говоря вообще, слова ABORT и QUIT наиболее полезны для слежения за условиями, при которых исполнение программы должно быть прекращено. Эти условия обычно связаны с некоторыми типами ошибок, например делением на 0, но могут быть, конечно, и другого рода. Можно напомнить, как мы говорили в гл. 1, что в Форте забота о проверке наличия ошибок целиком возлагается на программиста. И мы показали, как она может быть сделана в простых случаях. Слово EXIT имеет более тонкие применения, чем ABORT и QUIT. Оно прекращает исполнение слова и возвращает управление туда, откуда это слово было вызвано. Оно бесполезно вне конструкции IF...THEN, поскольку всегда возможно прекратить исполнение более естественным путем, завершая определение слова точкой с запятой. (Кстати, EXIT фактически является частью определения слова ;.) Предположим, например, что у вас есть слово, которое позволяет оператору ввести число, а затем запомнить сумму в переменной TOTAL. Вы хотите, чтобы ввод нуля игнорировался. Используя EXIT, можно дать такое определение : : GET# #IN ?DUP IF TOTAL +! ELSE EXIT THEN ; но по своему действию ELSE EXIT THEN ; ничем не отличается от THEN ;
Таким образом, EXIT следует применять ограниченно или не применять вовсе внутри одной единственной конструкции IF...THEN. Вспомним наше определение слова (WT) : : (WT) DUP 100 < IF TASK1 ELSE DUP 200 < IF TASK2 ELSE DUP 300 < IF TASKS THEN THEN THEN DROP ;
Теперь предположим, что слова TASK1, TASK2 и TASK3 прибавляют по единице к переменным 1COUNT, 2COUNT и 3COUNT каждый раз, когда вес попадает в соответствующий диапазон значений.


Но вы не хотите, чтобы слово TASK исполнялось, если в каком-либо диапазоне общий счет превысил 200. Иначе говоря, вы хотите прекратить исполнение, если количество случаев попадания в какой-либо диапазон превышает 200. Представляем слово, которое решает эту задачу : : (WT) DUP 100 < IP 1COUNT @ 199 > IF EXIT THEN TASK1 ELSE DUP 200 < IF 2COUNT @ 199 > IF EXIT THEN TASKS ELSE DUP 300 < IF 3COUNT @ 199 > IF EXIT THEN TASK3 THEN THEN THEN DROP ;
Этот пример показывает, что если использование слова EXIT в единственной конструкции IF...ELSE приводит к появлению лишних бесполезных слов, то во вложенных конструкциях IF...ELSE слово EXIT становится существенно необходимым.
Прежде чем закончить этот раздел, укажем на менее употребительное слово из Форт-83 и других версий - ABORT". Это слово ищет флаг в стеке и исполняется, если его значение истина, т.е. слово ABORT" содержит внутри себя конструкцию IF. Слово ABORT" выдает сообщение, которое следует после ", а затем исполняет операции присущие слову ABORT. Его назначение состоит в том, чтобы обнаруживать ошибки и давать об. этом сообщение. Вот, например, определение слова 0/? с использованием слова ABORT": : 0/? ?DUP 0= ABORT" Ошибка деления на 0 " ;
Приведенные ниже упражнения покажут некоторые более полезные на практике применения рассмотренных слов,
Упражнения
1. Определите слово ?END для оптимального выхода из программы, которая задавала бы следующие вопросы и выполняла бы соответствующие вашим ответам действия: Do you want to quit? (Y/N) Вы хотите закончить работу? и если введен ответ Y(да), то Do you want to save the stack? (Y/N) Вы хотите сохранить стек? (Да/Нет) 2. Если слово ?223 вводится непосредственно (не входит в определение через двоеточие), имеются ли какие-либо различия в его действии при следующих трех способах его определения : : ?223 223 = IF 1 COUNT +! ELSE QUIT THEN ; : ?223 223 = IF 1 COUNT +! ELSE EXIT THEN ; : ?223 223 = IF 1 COUNT +! THEN ; 3.


Как изменится действие слова из определения 2, если ?223 вызывается из другого определения через двоеточие? 4. Имеются ли различия, если в определении следующего слова используется DUP или ?DUP ? : : 0? DUP 0= IF ABORT" Число равно нулю " THEN ; 5. Дайте новое определение слова ?0, используя слово ABORT. 6. Определите слово =IF-ABORT, которое прекращает исполнение программы, если два верхних числа в стеке равны между собой. 7. Определите слово +RANGE-ABORT, которое прекращает работу программы и сообщает об ошибке, если сумма двух чисел в стеке будет превышать максимальное число одинарной длины без знака. 8. Определите слово *+RANGE-ABORT, которое прекращает работу программы, если либо сумма, либо произведение двух чисел в стеке превысят максимальное число одинарной длины без знака. 9. Определите слово STACK-TOO-BIG, которое выдавало бы сообщение об ошибке и прекращало исполнение программы, если в стеке больше 15 чисел. 10. Дайте новое определение следующих слов, не используя слова EXIT: а) : 1TASK 0= IF DOTHAT ELSE EXIT THEN : 6) ; 2TASK 0= IF EXIT ELSE DOTHAT THEN ; в) : 3TASK 0= IF DOTHAT ELSE EXIT THEN DOOTHER ; r) : 4TASK 0= IF EXIT ELSE DOTHAT THEN DOOTHER :
Это послужит убедительным доказательством, что слово EXIT совершенно не нужно в невложенных конструкциях IF...THEN.
Множественный выбор ветвления
До сих пор в этой главе мы рассматривали конструкции IF...THEN и IF...ELSE...THEN, которые позволяют делать переход на исполнение одной из двух ветвей программы в зависимости от того равно или не равно нулю число в стеке. Однако вам может потребоваться программа, которая может разветвляться по альтернативным путям в зависимости от того, какое из нескольких чисел находится в стеке. В гл. 6 мы показали один из вариантов решения этой задачи. Векторное исполнение программы - это одно из средств реализации ветвления по нескольким путям. Если компоненты вектора представляют собой адреса слов, то данное слово может быть исполнено, если взять eго адрес и применить к нему оператор EXECUTE.


Таким образом, слово, на исполнение которого уходит программа, зависит от одного из нескольких чисел, которое находится в стеке, когда выбирается конкретный адрес. В гл. 6 мы хранили адреса слов в массиве и использовали слова FIND или ', в зависимости от применяемой версии Форта. Давайте снова рассмотрим пример из гл. 6. Пусть слова 1TASK, 2TASK и 3TASK определены. Можно задать вектор CHOICE (выбор) следующим образом: CREATE CHOICE 6 ALLOT FIND 1TASK CHOICE ! FIND 2TASK CHOICE 2 + ! FIND 3TASK CHOICE 4 + !
Теперь если мы определим слово DOCHOICE (сделай_выбор) : DOCHO1CE 1- 2* CHOICE + @ EXECUTE ; то исполняемое ветвление будет зависеть от числа, которое находится в стеке, когда запускается программа DOCHOICE.
Эта конструкция носит название выбор по целому. Если в стеке находится 1, то выполняется 1TASK, если 2- то 2TASK и т.д. Такая конструкция выбора по целому, основанная на векторном исполнении, едва ли не самое простое, что может быть придумано в стандартном языке Форт. Разнообразным реализациям структуры выбора по целому был посвящен целый выпуск журнал "FORTH Dimensions" (1980- Т. 2, вып. 3). В некоторых коммерческих версиях Форта реализован сложные конструкции выбора по целому. Мы рассмотрим слова NCASE и ACASE из языка MMSFORTH. Проще всего для понимания слова NCASE привести простой пример. Пусть вы хотите выполнить 1TASK, 2TASK или 3TASK в зависимости от того, какое из чисел: 20, 40 или 60 находится в стеке, а если в нем встречается какое-либо другое число, нужно, чтобы исполнялось слово OTHER (другая). Вот как это можно сделать : NCASE 20 40 60 " 1TASK 2TASK 3TASK OTHERWISE OTHER CASEND Оператор NCASE снимает число из стека и просматривает, какому из следующего за ним ряда сел оно равно. Если обнаруживается совпадение, то исполняется соответствующее слово из пере численного списка, если совпадение не обнаружено, то исполняется слово или последовательность слов, находящихся между OTHERWISE (иначе) и CASEND (конец_выбора). Слово OTHERWISE и любые слова после него не являются обязательными, т.е.


если в данном случае вы не хотите, чтобы альтернативным путем было использование слова OTHER, то можете использовать программу NCASE 20 40 60 " 1TASK 2TASK 3TASK CASEND А теперь рассмотрим более сложный, но и более полезный пример. Пусть у вас есть программа, в которой используется слово KEY для ввода чисел. Вы хотите, чтобы она печатала все, что является алфавитно-цифровыми знаками, при вводе CtrI-C (код ASCII 3) очищала бы экран с помощью слова CLS, при вводе Ctrl-G (код ASCII 7) включала бы звуковой сигнал (с помощью слова ВЕЕР) и при Ctrl-P (код ASCII 16) вывод переключался бы на принтер (с помощью слова PRNT), Это может делать следующая программа : : GETKEY KEY DUP NCASE 3 7 16 " CLS BEEP PRNT OTHERWISE DUP 32 < IF DROP ELSE EMIT THEN CASEND ;
Надеемся, что вы поняли, как она работает.
Одно из самых полезных применений слова NCASE состоит в обслуживании запросов, связанных с нажатием определенных клавиш при ответе на предложения из меню. Слово NCASE будет работать правильно только с числами, не превышающими по величине максимального значения одного байта (от 0 до 255), и оно устроено так, что фактически игнорирует старший байт 16-разрядного числа. Это значит, что результат будет один и тот же, если NCASE обнаружит число 0A или AB0A (в шестнадцатеричном представлении), т.е. оно ищет совпадение с 0A в одном из следующих после него чисел. Аналогичным образом числа, следующие за NCASE, могут быть больше, чем представляются одним байтом, при этом старший байт числа будет игнорироваться. Таким образом, NCASE 55A3 2221 AC55 " . . . будет исполняться так же, как NCASE A3 21 55 " . . .
Следовательно, поскольку вы уверены, что старший байт числа никогда не может повлиять на выбор, определяемый словом NCASE, можно применять числа больше 255. Слово ACASE в MMSFORTH близко по назначению слову NCASE. Но оно реагирует не на число, а на символы ASCII. Приведем пример его синтаксиса: ACASE NKT" 1TASK 2TASK 3TASK OTHERWISE OTHER CASEND
Слово ACASE просматривает стек, и если оно обнаруживает букву N, то исполняется 1TASK, если букву К- то 2TASK, а при Т уходит на 3TASK.


Если нет совпадения ни с одной буквой, выполняется слово OTHER. Так же как и в NCASE, слово OTHERWISE, слово или слова, следующие за ним до слова CASEND, не являются обязательными. Заметьте, что не должно быть пробела между буквами или между буквами и кавычкой, если только вы не используете пробел для обозначения одного из вариантов выбора. Применение слова ACASE сходно с применением и NCASE, поэтому вместо разбора примеров предлагаем вам освоить его применение на упражнениях.
Упражнения
1. Создайте конструкцию выбора по целому, пользуясь векторным исполнением, которая выполняла бы арифметические действия над вторым и третьим числами в стеке по следующим правилам. Если в стеке находится 1, то слово ARITH выполняет сложение +; если 2, то вычитание -; если 3 или 4. то соответственно операции умножения * или деления /. Измените слово ARITH, пользуясь конструкцией IF так, что если число в стеке не попадает в диапазон чисел от 1 до 4, то в стек помещается 0. 2. Дайте другое определение слова ARITH, используя слово NCASE (включите выдачу нуля в стек, если используется неправильное число). 3. Определите слово NEWARITH, используя ACASE, так чтобы оно ожидало нажатия клавиши. Если будет нажата клавиша "+", числа нужно сложить; если нажата клавиша "-'', то вычесть; если нажата клавиша "*" или "/", то умножить или разделить соответственно. Если нажата неверная клавиша, то в стек должен выдаваться нуль. 4. Переделайте определение NEWARITH в NEWARITH1, используя конструкцию IF...ELSE...THEN. После того как вы это сделаете, вы оцените пользу слова ACASE. 5. Переделайте NEWARITH, как описано в упражнении 3, чтобы при нажатии неправильной клавиши печаталось сообщение " Неверный ввод " и исполнение прерывалось. Проделайте это, применяя слова ABORT и ABORT". 6. Можете ли вы придумать ситуацию, когда предпочтительнее использовать не NCASE и ACASE, а векторное исполнение?
Выводы
В некотором смысле возможность ветвления делает программу более эффективной.


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

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