Из материала семи предыдущих глав вы уже поняли, что программирующие на Форте используют стек для передачи аргументов от одного слова к другому. Если программистам требуется хранить числа более продолжительное время, они применяют переменные и константы. В этой главе будет показано, как Форт-система трактует переменные и константы и каким образом можно непосредственно получить доступ к участку памяти.
Для начала приведем пример ситуации, когда вам понадобилось бы воспользоваться переменной, например для хранения даты1. В первую очередь создадим переменную (VARIABLE) с именем ДАТА2
VARIABLE ДАТАЕсли сегодня 12-е число, то мы вводим: 12 ДАТА !, т. е. помещаем 12 в стек, затем указываем имя переменной и, наконец, выполняем слово ! (ЗАПОМНИ). Это выражение записывает число 12 в переменную ДАТА. Для обратного действия нужно ввести:
1 Для начинающих. Предположим, ваш компьютер выдает банковские счета на протяжении дня, и каждый такой счет должен включать дату. Вам не хотелось бы хранить дату все время в стеке и, кроме того, дата не должна быть частью определения, иначе его пришлось бы переопределять каждый день. Вам нужна переменная.
2 Для пользователей систем фиг-Форт. Чтобы вариант VARIABLE вашей системы совпадал с описанным в книге, введите определение
: VARIABLE 0 VARIABLE ;
ДАТА @, иными словами, назвать переменную, а затем выполнить слово @ (ВЫБРАТЬ) Указанное выражение выбирает число 12 и помещает его в стек. Таким образом, выражение
ДАТА @ . 12 okвыведет на печать дату
На Форте это делается еще проще с помощью слова, определение которого приводится ниже:
: ? @ . ;Поэтому вместо «ДАТА-выборка-точка» мы можем просто набрать
ДАТА ? 15 okДАТА будет иметь значение 12 до тех пор, пока вы не измените его. Для того чтобы изменить значение переменной, необходимо записать в нее новое число.
13 ДАТА ! ok ДАТА ? 13 оkПонятно, что можно определить и дополнительные переменные для месяца и года:
VARIABLE ДАТА VARIABLE МЕСЯЦ VARIABLE ГОДа затем слово с именем !ДАТА (для «запоминания-даты»), например:
: !ДАТА ( месяц день год -- ) ГОД ! ДАТА ! МЕСЯЦ ! ;и использовать его следующим образом:
7 31 88 !ДATA okПосле этого нужно определить слово с именем .ДАТА (для «вывода-даты») :
: .ДАТА МЕСЯЦ ? ДАТА ? ГОД ? ;Ваша Форт-система уже имеет ряд определенных переменных, одна из которых — BASE Она содержит основание текущей системы счисления. На самом деле определения систем счисления
HEX, DECIMAL (и OCTAL, если в вашей системе таковая имеется) весьма просты:
: DECIMAL 10 BASE ! ; : HEX 16 BASE ! ; : OCTAL 8 BASE ! ;Вы можете работать с любой системой счисления, просто загрузив ее основание в BASE1:
Если где-нибудь в определении системных слов, осуществляющих преобразования входных и выходных чисел, вы найдете выражение BASE @ значит, текущее значение BASE используется в процессе такого преобразования. Следовательно, одна и та же программа может работать с числами в любой системе счисления. Отсюда мы можем сделать следующее формальное заключение об использовании переменных: в Форте уместно создавать переменные для тех значений, используемых внутри определения, которые могут быть изменены в любой момент после того, как это определение уже скомпилировано.
1 Для специалистов. Трехбуквенный код, например, имя терминала некоего аэропорта, можно запомнить как число одинарной длины без знака в системе счисления с основанием 36. Например:
: АЛЬФА 36 BASE ! ;
АЛЬФА ok
ZAP U. ZAP ok
2 Для специалистов. Как на самом деле выглядит в памяти элемент словаря, мы покажем в следующей главе.
Слово ДАТА аналогично любому другому слову в вашем словаре, за исключением того, что оно определено с помощью слова VARIABLE, а не :, поэтому вы не должны специфицировать функции своего определения. Само имя слова VARIABLE предопределяет, что должно произойти. А происходит следующее.
Когда вы вводите 12 ДАТА !
число 12 поступает в стек, после чего интерпретатор текста ищет слово ДАТА в словаре и, найдя его, передает на исполнение EXECUTE.
EXECUTE выполняет переменную путем копирования адреса «пустой» ячейки этой переменной (куда будет послано значение) в стек':
1 Для начинающих. В программировании адресом называется число, которое определяет участок машинной памяти. Например, по адресу 2076 (адреса, как правило, выражаются шестнадцатиричными числами без знака) может содержаться 16-разрядное представление значения 12. Здесь 2076 — адрес, 12 — содержимое.
Слово ! выбирает адрес (из вершины) и значение (под ним) и запоминает это значение по выбранному адресу. Новое число замещает любое другое число, находившееся прежде по данному адресу.
(Чтобы запомнить порядок аргументов, можно провести такую аналогию: думайте о том, что сначала нужно положить посылку, а уже потом сверху приклеить адресную бирку.)
Для слов @ требуется только один аргумент: адрес, который в данном случае обеспечивается именем переменной, например ДАТА @.
Используя выбранное из стека значение в качестве адреса, слово (3) помещает содержимое, находящееся по данному адресу, в стек, предварительно сняв с последнего адрес. (Содержимое участка памяти остается прежним.)
В Форте переменная представляет собой идеальное средство для счетчика. Вернемся к примеру с машиной для упаковки яиц и предположим, что нам нужна информация о том, сколько яиц проходит по ленте конвейера за один день. (Этот пример вы должны выполнить за своим терминалом, так что по ходу изложения набирайте на клавиатуре текст и вводите его.)
Сначала мы определяем
в которой будем вести подсчет. Каждое утро мы будем начинать подсчет с нуля, поэтому нам придется загружать в переменную ЯЙЦА нуль, используя слово, определение которого выглядит так:
: УСТАНОВИТЬ 0 ЯЙЦА ! ;После этого где-нибудь в нашей программе по упаковке яиц нужно определить слово, которое всякий раз, когда яйцо минует электрический «глазок» на конвейере, выполняет следующее выра-жение:
1 ЯЙЦА +!Слово +!1 добавляет заданное значение к содержи^мому (любому) по данному адресу. Таким образом, выражение 1 ЯЙЦА + ! увеличивает счетчик яиц на единицу. Для иллюстрации изложенного поместим это выражение внутрь некоторого определения:
: ЯЙЦО 1 ЯЙЦА +! ;А в конце дня выясним, сколько яиц прошло через конвейер, набрав на клавиатуре ЯЙЦА?. Теперь проверим:
УСТАНОВИТЬ ok ЯЙЦО ok ЯЙЦО ok ЯЙЦО ok ЯЙЦА ? 3 okНиже приводится перечень слов, которые мы уже рассмотрели в настоящей главе.
VARIABLE ххх ( -- ) Создание переменной с именем ххх. ххх ( -- а) Слово ххх при выполнении помещает в стек свой адрес. ! ( n а --) Запоминание числа одинарной длины по заданному адресу. @ ( a -- n) Замещение адреса его содержимым. ? ( а --) Вывод значения по заданному адресу с последующим пробелом. +! ( n а --) Сложение числа одинарной длины с содержимым заданного адреса.
1Для любознательных. Это слово обычно определяют на уровне языка Ассемблера, определение же на языке высокого уровня имеет вид:
: +! ( приращение а --) DUP @ ROT + SWAP ! ;
Если в переменных обычно хранятся значения, которые могут изменяться, то константы используются для хранения значений, которые изменению не подлежат. На Форте мы создаем константу и тут же устанавливаем ее значение, например:
220 CONSTANT ПРЕДЕЛЗдесь определена константа с именем ПРЕДЕЛ, которой присвоено значение 220. Теперь мы имеем право подставлять слово ПРЕДЕЛ вместо значения
: ?ЖАРКО ( температура -- ) ПРЕДЕЛ > IF ." Опасно — Уменьшите нагрев ! " THEN ;В том случае, когда число в стеке больше 220, выдается предупреждающее сообщение. Заметьте, что, говоря ПРЕДЕЛ, мы получаем значение, а не адрес. Нам здесь не требуется «выборка». В этом и состоит основное отличие переменных от констант. Дело в том, что при работе с переменной нам нужен адрес, чтобы иметь возможность как выборки значения, так и его запоминания. При использовании же константы всегда требуется значение (мы ни-когда в нее ничего не запоминаем).
Одним из примеров применения констант может служить именование аппаратного адреса. Допустим, что программа для управления фотокамерой с помощью микропроцессора содержит следующее определение:
: СНИМОК ЗАТВОР ОТКРЫТЬ БРЕМЯ ВЫДЕРЖАТЬ ЗАТВОР ЗАКРЫТЬ ;Здесь слово ЗАТВОР определено как константа при условии, что его выполнение обеспечивает аппаратный адрес затвора фотокамеры. Оно может быть определено так:
HEX ЗЕ27 CONSTANT ЗАТВОР DECIMALСлова ОТКРЫТЬ и ЗАКРЫТЬ могут быть просто определены:
: ОТКРЫТЬ ( а -- ) 1 SWAP ! ; : ЗАКРЫТЬ ( а -- ) 0 SWAP ! ;так что выражение ЗАТВОР ОТКРЫТЬ запишет единицу по адресу затвора, и он откроется.
Использование в определениях констант, а не изображений самих чисел является важным элементом хорошего стиля программирования. Прежде всего наличие констант делает вашу программу более читабельной. Все определения Форта должны быть так же самодокументированы, как определение СНИМОК.
Не менее существенно и то, что значения могут изменяться (могут изменяться, к примеру, аппаратные средства). Если в такой ситуации вам достаточно внести изменения в один фрагмент программы — в определение константы, — то все остальное будет сделано автоматически, без вероятных пропусков, как это имело бы место при корректировке вручную.
Третье преимущество заключается в том, что в компилируемой форме определение, содержащее константу, занимает меньший объем памяти, чем то же определение, но с изображением числа вместо константы. Если некоторое число применяется неоднократно, то получаемый при этом выигрыш перекрывает расходы на описание константы. Поэтому во многих Форт-системах часто повторяющиеся числа определены как константы:
0 CONSTANT 0 1 CONSTANT 1 и т.д.В дальнейшем будем считать, что в вашей системе имеются следующие определения констант FALSE (ЛОЖЬ) и TRUE (ИСТИНА):
0 CONSTANT FALSE -1 CONSTANT TRUE
CONSTANT xxx ( n -- ) Создание константы с именем xxx и xxx: ( -- n) значением n. Слово xxx при своем выполнении заносит в стек n. FALSE ( -- f) Занесение в стек логического значения ложь ( 0 ). TRUE ( -- t) Занесение в стек логического значения истина ( —1 ).
Вы можете определить переменную двойной длины с помощью слова 2VARIABLE, например:
2VARIABLE ДАТАПосле этого вы можете использовать слова ФОРТА 2! (ЗАПОМНИ ДВА) и 2@ (ВЫБЕРИ ДВА) для доступа к переменной двойной длины. Можно записать число двойной длины в такую переменную, просто записав
800000. ДАТА 2!и выбрать значение из нее, введя
ДАТА 2@ D. 800000 okВы можете также запомнить полностью дату (месяц, число, год) в такой переменной:
7/16/86 ДАТА 2!и выбрать их назад:
ДАТА 2@ .ДАТА 7/16/81 okв предположении, что у вас загружен вариант определения .ДАТА, приводимый в последней главе.
Можно определить константу двойной длины с помощью слова 2CONSTANT, например
200000. 2CONSTANT ЯБЛОКИпосле чего слово ЯБЛОКИ будет помещать в стек число двойной длины:
ЯБЛОКИ D. 200000 okСудя по префиксу 2, мы можем также использовать слово 2CONSTANT при определении пары чисел одинарной длины. Можно помещать два числа под одним именем просто из соображений удобства, а также для резервирования памяти в словаре.
Вспомните (см. гл. 5), что вы можете с помощью выражения 355 113 */ умножить некоторое число на аппроксимацию pi. Слово 2CONSTANT позволяет запомнить эти два целых:
355 113 2CONSTANT PIа впоследствии применить выражение PI */, например:
10000 PI */ . 31415 okНиже приводится перечень слов применительно к структурам данных двойной длины.
2VARIABLE xxx ( -- ) Создание переменной двойной длины ххх: ( -- a) с именем ххх. Слово ххх при выполнении помещяет на стек свой адрес. 2CONSTANT ххх ( d -- ) Создает константу двойной длины с именем ххх и значением d. ххх: ( -- d) Слово ххх при выполнении помещает в стек значение d. 2! ( d а -- ) Запоминание числа двойной длины по заданному адресу. 2@ ( а -- d) Занесение в стек числа двойной длины, расположенного по заданному адресу.
Как вам уже известно, выражение VARIABLE ДАТА создает определение и выделяет память для значения одинарной длины:
А если необходимо выделить память для 10 или 20 значений одинарной длины под одним и тем же именем? Такая структура называется массивом. На форте массив строится следующим образом.
В первую очередь мы создаем массив с помощью непривычного определяющего слова CREATE (СОЗДАТЬ)1:
CREATE МОЙ-МАССИВ
1 для пользователей систем фиг-Форта. В вашей системе имеется слово CREATE, но оно отличается от упоминаемого здесь и редко используется. Чтобы вы могли и далее следить за ходом событий, переопределите его следующим образом:
: CREATE <BUILD DOES> ;
Как
и VARIABLE, CREATE компилирует в словарь новое имя (вашего массива) вместе с кодами, которые специфицируют его действия. Но при этом память под данные не выделяется.
Каким образом мы впоследствии отведем память под созданный массив? Это делается посредством слова ALLOT (ВЫДЕЛИТЬ), которое выбирает из стека в качестве аргумента число байтов, резервируемое для массива.
Если требуется выделить память под 10 значений одинарной длины, то нужно ввести
20 ALLOT
(Значение одинарной длины занимает два байта.)
Когда вы исполняете слово, определенное как переменная Форт-система помещает в вершину стека адрес значения. Таким же образом, когда вы исполняете слово, созданное с помощью CREATE, в вершину стека заносится адрес начала массива (первого значения).
Проиллюстрируем применение массива на следующем примере Предположим, что в нашей лаборатории имеется не одна, а пять горелок, на которых нагреваются различные жидкости.
Мы можем с помощью слова ?ЖАРКО проверять, не превышает ли температура нагрева каждой из пяти горелок установленного для нее максимального значения, если определим ПРЕДЕЛ не как константу, а как массив. Присвоим этому массиву имя ПРЕДЕЛЫ:
Допустим, что мы устанавливаем предельное значение температуры для горелки 0 220°. Запомним это значение посредством следующей фразы:
220 ПРЕДЕЛЫ !
так как ПРЕДЕЛЫ доставляет адрес первой ячейки нашего массива. Установим предельное значение температуры для горелки
1 равным 340° и запомним его, добавив два байта к адресу исходной ячейки:
Мы можем запомнить предельные значения для горелок 2, 3 и 4, добавляя к исходному адресу «смещения» 4, 6 и 8. Так как смещение всегда равно удвоенному номеру горелки, определим полезное слово:
: ПРЕДЕЛ ( номер-горелки -- адрес-предельного-значения) 2* ПРЕДЕЛЫ + ;
чтобы по номеру соответствующей горелки, находящемуся в стеке, вычислять адрес, который отстоит от начала на величину соответствующего смещения1.
После всех преобразований полезность слова ПРЕДЕЛ возросла в такой степени, что мы можем переопределить слово ?ЖАРКО:
: ?ЖАРКО ( температура номер-горелки — ) ПРЕДЕЛ @ > IF , " Опасно — Уменьшите нагрев! " THEN ;
1 1 Для начинающих. В этом случае номер горелки называется индексом массива. Индекс — это относительный указатель элемента памяти Умножая индекс на 2, мы получаем смещение относительно начала массива Смещение равно фактическому числу байтов между началом массива и искомым элементом.
2. Мы нумеруем горелки с 0 до 4, вместо того чтобы нумеровать их с I до 5. по той причине, что хотим использовать сами номера горелок в качестве индексов 1о, что большинство людей называют «первым» в какой-то последовательности, программисты называют «нулевым» При желании вы можете пронумеровать горелки с 1 по 5, но тогда вам придется добавлять в определении слов самого высокого уровня корректирующий фрагмент (простое выражение «1-»), как это всегда делается.
Примеры выполнения нового варианта этого слова приведены ниже:
210 0 ?ЖАРКО ok 230 0 ?ЖАРКО Опасно — Уменьшите нагрев! ok З00 1 ?ЖАРКО ok З50 1 ?ЖАРКО Опасно — Уменьшите нагрев! ok
и т.д.
CREATE xxx ( -- ) Создание заголовка в словаре с именем xxx. xxx: ( -- a) Слово xxx при выполнении заносит в стек свой адрес
ALLOT ( n -- ) Резервирование в поле параметров слова, определенного последним, n дополнительных байт.
Вернемся на нашу птицеферму. Приведем еще один пример на использование массива. Каждый его элемент служит как бы отдельным счетчиком. Следовательно, мы можем дифференцирование вести подсчет числа коробок «очень крупных» яиц, «крупных» яиц и т. д., упакованных машиной.
Вспомните, что в приведенном выше определении РАЗМЕР-ЯИЦ (см. гл. 4) у нас было четыре категории стандартных яиц и две категории нестандартных.
0 БРАК 1 МЕЛКИЕ 2 СРЕДНИЕ 3 КРУПНЫЕ 4 ОЧЕНЬ КРУПНЫЕ 5 ОШИБКА
Давайте создадим массив из шести ячеек:
CREATE СЧЕТЧИКИ 12 ALLOT
Счетчики будут увеличиваться с помощью слова +!, так что мы должны иметь средства «обнуления» всех элементов массива перед началом процесса подсчета. Выражение
СЧЕТЧИКИ 12 0 FILL
заполняет нулями 12 байтов, начиная с адреса СЧЕТЧИКИ. Если в вашей Форт-системе имеется слово ERASE, то в данной ситуации лучше воспользоваться им. Это слово заполняет заданное число байтов нулями. Ниже показан пример его использования.
СЧЕТЧИКИ 12 ERASE
FILL (ЗАПОЛНИТЬ) ( a u b -- ) Заполнение n байтов памяти, начиная с заданного адреса, значением Ь.
ERASE (ОЧИСТИТЬ) ( а n -- ) Заполнение n байтов памяти, начиная с заданного адреса, нулями.
Иногда удобно помещать это выражение внутрь определения:
: УСТАНОВИТЬ СЧЕТЧИКИ 12 ERASE ;
Далее определим слово, которое по заданному номеру категории яиц (от 0 до 5) даст нам адрес одного из счетчиков, например:
: СЧЕТЧИК ( номер-категории -- а) 2* СЧЕТЧИКИ + ;
и еще одно слово для добавления единицы к счетчику с заданным номером:
: УЧЕТ ( номер-категории --) СЧЕТЧИК 1 SWAP +! ;
Здесь 1 служит приращением для слова +!, a SWAP располагает его аргументы в требуемом порядке, т. е. ( n адрес --).
Теперь, например, выражение 3 УЧЕТ увеличит значение счетчика, соответствующего категории крупных яиц.
Определим слово, которое переводит вес на дюжину в номер категории1:
: КАТЕГОРИЯ ( вес—на-дюжину — номер-категории) DUP 18 < IF в ELSE DUP 21 < IF I ELSE DUP 24 < IF 2 ELSE DUP 27 < IF 3 ELSE DUP З0 < IF 4 ELSE 5 THEN THEN THEN THEN THEN SWAP DROP ;
1 для специалистов. В конце главы будет приведено более простое определение.
(К тому времени, когда процесс вычисления подойдет к выражению SWAP DROP, в стеке будут находиться два значения: вес, который мы размножили с помощью команды DUP, и номер категории, расположенный в вершине. Нам требуется только номер категории. Выражение SWAP DROP убирает вес.)
Например, выражение 25 КАТЕГОРИЯ оставит в стеке число 3. Приведенное выше определение слова КАТЕГОРИЯ напоминает наше прежнее определение РАЗМЕР-ЯИЦ, но, следуя стилю Форта (слова должны создаваться по возможности более короткими), мы убрали из этого определения выдаваемые сообщения и определили еще одно слово, которое по заданному номеру сорта яиц выдает сообщение1:
: МАРКИРОВКА ( номер-категории — ) DUP 0= IF ." Брак " ELSE DUP 1 = IF ." Мелкие " ELSE DUP 2 = IF ." Средние " ELSE DUP 3 = IF ." Крупные " ELSE DUP 4 = IF ." Очень крупные " ELSE ." Ошибка " THEN THEN THEN THEN THEN BROP ;
Например:
1 МАРКИРОВКА Мелкие ok
Теперь мы можем определить слово РАЗМЕР-ЯИЦ, используя три наших собственных слова:
: РАЗМЕР-ЯИЦ ( вес-на-дюжину — ) КАТЕГОРИЯ DUP МАРКИРОВКА УЧЕТ ;
Таким образом, выражение 23 РАЗМЕР-ЯИЦ выведет на вашем дисплее сообщение
Средние ok
и обновит счетчик яиц среднего размера.
Каким образом мы узнаем содержимое счетчиков в конце дня? Придется проверить по отдельности каждую ячейку массива, например, с помощью выражения 3 СЧЕТЧИК? (которое выведет число упакованных коробок с «крупными» яйцами). Однако можно
1 для специалистов Более элегантный вариант этого определения приводится в следующей главе.
попытаться для печати результирующей таблицы за день в приведенном ниже формате определить свое собственное слово:
КОЛИЧЕСТВО РАЗМЕР 1 Брак 112 Мелкие 132 Средние 143 Крупные 159 Очень крупные 0 Ошибка
Так как вы уже научились получать номера категорий, можно просто использовать цикл DO с номером категории в качестве индекса:
: СВОДКА РАGЕ ." КОЛИЧЕСТВО РАЗМЕР" CR CR 6 0 DO I СЧЕТЧИК @ 5 U.R 7 SPACES I МАРКИРОВКА CR LOOP ;
(Выражение
I СЧЕТЧИК @ 5 U.R
выбирает номер категории, подготовленный словом I, как индекс массива и выводит содержимое соответствующего элемента последнего в виде поля из пяти значений.)
Рассмотрим теперь проблему разбиения применительно к определениям Форта. Мы только что привели пример, в котором разбиение определений упростило нам решение задачи.
Наше первое определение слова РАЗМЕР-ЯИЦ (см. гл. 4) сортировало яйца по весу и выводило на печать соответствующие категории яиц. В предыдущем примере мы разбили «сортировку» и «печать» на два отдельных слова. Вы можете использовать слово КАТЕГОРИЯ с целью выработки аргумента как для слова, инициирующего печать, так и для слова, осуществляющего подсчет (или для обоих вместе). Можно применить слово МАРКИРОВКА, обеспечивающее вывод на печать, и для слова РАЗМЕР-ЯИЦ, и для слова СВОДКА.
Ч. Мур, автор языка Форта, утверждает, что «хороший словарь Форта содержит большое число небольших определений. Недостаточно разбить некоторую задачу на небольшие фрагменты. Суть дела в том, чтобы выделить слова, которые можно повторно использовать». Например, в следующем рецепте
Взять банку с томатным соусом. Открыть ее. Выложить томатный соус на сковороду. Взять банку с грибами Открыть ее. Выложить грибы на сковороду
вы можете «вычленить» действия: взять, открыть, выложить и объединить их в одном месте, так как они являются общими по отношению и к банке с томатным соусом, и к банке с грибами. После этого вы можете присвоить процессу в целом имя и в дальнейшем просто писать:
ТОМАТ ДОБАВИТЬ ГРИБЫ ДОБАВИТЬ
и любой шеф-повар, окончивший «постфиксную» кулинарную школу, хорошо поймет, что вы имеете в виду.
Вычленение определений не только упрощает написание программы (и ее отладку), но и позволяет экономить память. Повторно используемое слово, например добавить, нужно определить только один раз. Чем сложнее программа, тем больше мы экономим при ее разбиении. Прежде чем покинуть птицеферму, приведем еще одно соображение по поводу стиля программирования на Форте Вспомним наше определение слова РАЗМЕР-ЯИЦ:
: РАЗМЕР-ЯИЦ ( вес-на-дюжину — ) КАТЕГОРИЯ DUP МАРКИРОВКА УЧЕТ;
Слово КАТЕГОРИЯ доставляет значение, которое нам хотелось бы передать как слову МАРКИРОВКА, так и слову УЧЕТ, поэтому мы включаем сюда операцию DUP. Чтобы сделать определение более ясным, рискнем вынести из него DUP и поместим его в самое начало определения МАРКИРОВКА. Таким образом, можно написать:
: МАРКИРОВКА ( номер-категории — номер-категории) и т.д. : РАЗМЕР-ЯИЦ ( в ее-на-дюжину — ) КАТЕГОРИЯ МАРКИРОВКА УЧЕТ ;
где КАТЕГОРИЯ передает значение слову МАРКИРОВКА, а МАРКИРОВКА передает его слову УЧЕТ. Несомненно, этот вариант должен «сработать». Но впоследствии при определении СВОДКА мы вынуждены будем применить выражение I МАРКИРОВКА DROP вместо простого I МАРКИРОВКА.
Программирующим на Форте рекомендуется придерживаться следующего соглашения: там, где это возможно, слова должны уничтожать свои параметры. Вообще лучше помещать DUP в «вызывающем» определении (РАЗМЕР-ЯИЦ), чем в «вызываемом» (МАРКИРОВКА).
Предлагаем вашему вниманию один из приемов работы с массивами. Лучше всего проиллюстрировать этот прием, написав на Форте наше собственное определение слова с именем DUMP. Оно применяется для вывода содержимого ряда адресов памяти. Форма использования этого слова следующая:
адрес длина DUMP
Например, мы можем ввести
СЧЕТЧИКИ 12 DUMP
и на печать будет выведено содержимое нашего массива СЧЕТЧИКИ, обеспечивающего нам подсчет яиц. Так как слово DUMP изначально было создано как средство программиста для вывода на печать содержимого участков памяти, то мы и получаем это содержимое либо байт за байтом, либо ячейку за ячейкой, в зависимости от вида адресации, применяемой в данном компьютере. В нашем варианте DUMP будет выводить ячейку за ячейкой. Очевидно, что в рассматриваемом здесь примере слово DUMP содержит цикл DO. Вопрос заключается в том, что должно использоваться в качестве индекса. Хотя мы можем задействовать как индекс цикла непосредственно сам счетчик (0—6), все же для скорости лучше взять за индекс АДРЕС. Адрес массива СЧЕТЧИКИ будет начальным индексом для нашего цикла, а адрес плюс счетчик — верхней границей, например:
: DUMP ( а длина -- ) OVER + SWAP DO CR I @ 5 U.R 2 +LOOP ;
Ключевым выражением здесь является фраза
OVER + SWAP
которая непосредственно предшествует DO.
Теперь начальный и конечный адреса находятся в стеке, готовые к использованию в качестве верхней границы и индекса цикла DO.
Выражение типа
OVER + SWAP
называется клише, поскольку оно выполняет некоторое единое действие с точки зрения более высокого уровня. Данное конкретное клише преобразует адрес и счетчик в форму аргументов оператора DO. Так как индексирование осуществляется по адресам, для тою чтобы напечатать содержимое каждого элемента массива, поскольку мы находимся внутри цикла, достаточно просто ввести
I @ 5 U.R
Проверка байтов выполняется попарно (из-за того, что @ выбирает 16-разрядное значение), поэтому мы всякий раз увеличиваем индекс на два с помощью выражения
2 +LOOP
Форт позволяет создать массив, каждый элемент которого содержит не полную ячейку, а один байт. Это полезно в тех случаях, когда вы запоминаете ряд чисел, представляемых восемью битами.
Диапазон значений 8-разрядного числа без знака — от 0 до 255. Байтовые массивы могут также служить для хранения строк символов в коде ASCII. Преимущество массива байтов перед массивом ячеек заключается в том, что при его применении вы можете иметь тот же объем данных при половинном объеме памяти.
Механизм использования байтового массива тот же, что и массива ячеек, за исключением двух положений:
1) вы не должны удваивать смещение, так как каждый элемент соответствует одному адресу;
2) слова! и Р нужно заменить словами С! и С@ . Этим, словам, которые функционируют только с байтовыми значениями, дан префикс С, потому что обычно они обеспечивают доступ к символам в коде ASCII.
С! ( b a -- ) Занесение 8-разрядного числа по заданному адресу. С@ ( а -- b ) Выборка 8-разрядного числа по заданному адресу.
Во многих ситуациях требуется массив со значениями, которые во время выполнения прикладной программы никогда не меняются и могут быть загружены в этот массив во время его создания по аналогии с константами, создаваемыми с помощью CONSTANT Для осуществления этих действий в Форте предусмотрено слово , (ЗАПЯТАЯ).
Предположим, вы хотите, чтобы значения массива ПРЕДЕЛЫ были постоянными. Тогда вместо выражения
VARIABLE ПРЕДЕЛЫ 8 ALLOT
необходимо ввести
CREATE ПРЕДЕЛЫ 220 , 340 , 170 , 100 , 190 ,
Как правило, приведенная выше строка должна вводиться из дискового блока, но ее можно вводить и непосредственно с экрана.
Напоминаем вам, что слово CREATE во время компиляции вносит новое имя в словарь, а во время выполнения доставляет адрес своего определения. Но оно не «выделяет» какое-то число байтов под значение.
Слово , берет число из стека и помещает его в массив. Поэтому всякий раз, когда вы записываете число и ставите за ним запятую, вы добавляете к массиву одну ячейку1.
1 Для начинающих. Укоренившиеся привычки грамотно писать фразы на естественном языке приводят к тому, что некоторые новички забывают написать последнюю запятую в строке. Помните, что «,» не разделяет числа, а компилирует их
Доступ к элементам массива, созданного с помощью CREATE, обеспечивается точно так же, как и к элементам массива, созданного посредством VARIABLE, например:
ПРЕДЕЛЫ 2+ ? 340 ok
Вы даже можете помещать в этот массив новые значения так, как если бы работали с массивом, созданным с помощью VARIABLE, но только в том случае, кода вы не собираетесь пропустить свою прикладную программу через целевой компилятор.
Для того чтобы сформировать массив байтов, можно воспользоваться С,. Например, мы могли бы загрузить каждое из значений в нашем определении слова КАТЕГОРИЯ при сортировке яиц, следующим образом-
CREATE РАЗМЕРЫ 18 С, 21 С, 24 С, 27 С, 30 С, 255 С,
Это позволило бы переопределить слово КАТЕГОРИЯ посредством оператора цикла DO, а не рядом вложенных операторов IF ... THEN, в частности':
1 Для тех, кто не любит докапываться до сути самостоятельно. Суть дела состоит в следующем. Так как у нас пять возможных категорий, можно использовать номер категории как индекс для цикла При выполнении каждого шага цикла мы сравниваем число из стека с элементом массива РАЗМЕРЫ, отстоящим от начала массива на смещение, равное текущему индексу цикла. Как только вес в стеке превысит значение очередного элемента массива, мы завершим цикл и с помощью I определим, сколько раз цикл выполнялся до того, как мы вышли из него Поскольку это число представляет собой смещение в нашем массиве, то оно также является и номером категории
: КАТЕГОРИЯ t вес-ма.~дм*иму -- иенер-категории } 6 0 DO DUP РАЗМЕРЫ I + С@ < IF DROP I LEAVE THEN LOOP ;
Заметьте, что мы добавили к массиву максимальное значение (255), что дает возможность упростить наше определение для категории 5
Этот новый вариант более изящен и более компактен по сравнению с прежним.
Ниже приводится перечень слов Форта, с которыми вы познакомились в данной главе.
VARIABLE ххх ( -- ) Создание переменной с именем ххх. ххх ( -- а) Слово ххх при выполнении помещает в стек свой адрес. ! ( n а --) Запоминание числа одинарной длины по заданному адресу. @ ( a -- n) Замещение адреса его содержимым. ? ( а --) Вывод значения по заданному адресу с последующим пробелом. +! ( n а --) Сложение числа одинарной длины с содержимым заданного адреса.
CREATE xxx ( -- ) Создание заголовка в словаре с именем xxx. xxx: ( -- a) Слово xxx при выполнении заносит в стек свой адрес
ALLOT ( n -- ) Резервирование в поле параметров слова, определенного последним, n дополнительных байт.
С! ( b a -- ) Занесение 8-разрядного числа по заданному адресу. С@ ( а -- b ) Выборка 8-разрядного числа по заданному адресу.
FILL (ЗАПОЛНИТЬ) ( a u b -- ) Заполнение n байтов памяти, начиная с заданного адреса, значением Ь.
ERASE (ОЧИСТИТЬ) ( а n -- ) Заполнение n байтов памяти, начиная с заданного адреса, нулями.
CONSTANT xxx ( n -- ) Создание константы с именем xxx и xxx: ( -- n) значением n. Слово xxx при своем выполнении заносит в стек n. FALSE ( -- f) Занесение в стек логического значения ложь ( 0 ). TRUE ( -- t) Занесение в стек логического значения истина ( —1 ).
Операции двойной длины:
2VARIABLE xxx ( -- ) Создание переменной двойной длины ххх: ( -- a) с именем ххх. Слово ххх при выполнении помещает на стек свой адрес. 2CONSTANT ххх ( d -- ) Создает константу двойной длины с именем ххх и значением d. ххх: ( -- d) Слово ххх при выполнении помещает в стек значение d. 2! ( d а -- ) Запоминание числа двойной длины по заданному адресу. 2@ ( а -- d) Занесение в стек числа двойной длины, расположенного по заданному адресу.
Условные обозначения:
n, n1 ... - 16-разрядные числа со знаком; а - адрес; d, d1 ... - 32-разрядные числа со знаком; u, u1 ... - 16-разрядные числа без знака;
Выборка. Выборка значения из заданного участка памяти.
Запоминание. Помещение некоторого значения в заданный участок памяти.
Индекс. Число, которое при обращении к массиву указывает относительную позицию его конкретного элемента. Умножая индекс на длину каждого элемента, мы получаем смещение, добавляемое к начальному адресу массива с тем, чтобы установить абсолютный адрес искомого элемента.
Инициализация. Присвоение переменной (или массиву) ее начальных значений прежде, чем остальная часть программы начнет выполняться.
Константа. Значение, которому присвоено имя. Это значение хранится в памяти и, как правило, не изменяется.
Массив. Ряд участков памяти с одним именем. Значения могут быть помещены в отдельные элементы массива и выбраны из них путем указания имени массива и добавления смещения к его адресу.
Переменная. Участок памяти с именем, в который (и из которого) можно последовательно заносить (и выбирать определенные значения.
Разбиение. Применительно к программированию на Форте — упрощение большой программы путем вычленения элементов, ко-
торые можно неоднократно использовать, и определения таких элементов как операций.
Смещение. Число, которое может быть добавлено к адресу начала некоторого массива для получения адреса требуемого участка памяти в пределах данного массива.
8.1. а) Создайте два слови с именами ИСПЕКИ-ПИРОЖОЖ. и СЪЕШЬ-ПИРОЖОК. Первое слово увеличивает число имеющихся пирожков на единицу, второе уменьшает эго число на единицу и благодарит вас за пирожок. Но если пирожков в наличии нет, оно выводит: «Какой пирожок?». (Начните в предположении, что число пирожков равно нулю.)
СЪЕШЬ-ПИРОЖОК Какой пирожок? ИСПЕКИ-ПИРОЖОК ok СЪЕШЬ-ПИРОЖОК Спасибо! ok
6) Напишите слово с именем ЗАМОРОЗЬ-ПИРОЖКИ, которое берет все имеющиеся в наличии пирожки и добавляет их к числу пирожков, хранящихся в морозильной камере. Помните, что замороженные пирожки есть нельзя.
ИСПЕКИ-ПИРОЖОК ИСПЕКИ-ПИРОЖОК ЗАМОРОЗЬ-ПИРОЖКИ ок ПИРОЖКИ ? 0 ok ЗАМОРОЖЕННЫЕ-ПИРОЖКИ ? 2 ok
8.2. Определите слово с именем .BASE, которое выводит текущее значение переменной BASE в десятичной системе счисления. Проверьте это слово при первом же изменении переменной BASE на любом значении, отличном от 10, (Это не так просто, как может показаться.)
DECIMAL .BASE 10 ok HEX .BASE 16 ok
8.3. Определите слово для форматизации чисел с именем М., которое выводит на печать число двойной длины с десятичной точкой. Положение десятичной точки внутри числа подвижно и зависит от значения некоторой переменной
Если в вашей системе имеется для указания положения десятичной точки только что введенного числа переменная DPL или какая-либо другая, воспользуйтесь ею, например гак:
2000.0О M. 2000.00 ok
В противном случае определите переменную с именем ДРОБНЫЕ и загружайте туда значения вручную-
2 ДРОБНЫЕ ! 2000.00 M. 2000.00 ok
(В том случае, когда вводимое число не содержит десятичной точки, DPL вырабатывает — I и в вершину стека помещается целое число одинарной длины. Для повышения надежности учтите при создании М. эту ситуацию.)
8.4. Для того чтобы иметь сведения о наличии цветных карандашей в вашем учреждении, создайте массив, каждая ячейка которого содержала бы счетчик карандашей одного конкретного цвета. Определите набор слов, такой, что выражение типа
КРАСНЫЕ КАРАНДАШИ
доставляет адрес ячейки, содержащей число красных карандашей и т. д., а затем присвойте этим переменным значения, чтобы набор карандашей был следующим:
23 КРАСНЫЕ КАРАНДАШИ 15 ГОЛУБЫЕ КАРАНДАШИ 12 ЗЕЛЕНЫЕ КАРАНДАШИ 0 ЖЕЛТЫЕ КАРАНДАШИ
8.5. Создайте массив значений и напечатайте гистограмму (см. упражнения к гл. 5), где для каждого значения выводилась бы строка символов *. Для начала сформируйте массив из 10 элементов. Присвойте всем элементам массива значения в диапазоне от нуля до 10, после чего определите слово РИСУЙ, которое будет выводить строку, соответствующую каждому значению. На каждой строке должен выводиться номер элемента, а после него -- число звездочек, равное содержимому данного элемента.
8.6. В данном упражнении мы используем переменные одинарной длины как массивы флагов, а константы — как битовые маски. Начните с определения констант, каждая из которых представляет отдельную битовую позицию (из четырех младших разрядов 16-разрядного значения):
ЖЕНЩИНА СЕМЕЙНЫЙ РАБОТАЕТ ГОРОДСКОЙ
Теперь определите следующие константы, каждая из которых несет нули во всех битах
МУЖЧИНА ОДИНОКИЙ HE-РАБОТАЕТ НЕ-ГОРОДСКОЙ
Далее определите в виде переменных два имени, например.
VARIABLE ВАСЯ VARIABLE МАША
Впоследствии в этих переменных будут содержаться атрибуты наших персонажей.
Затем определите слово с именем ОПИСАНИЯ, которое выбирает из стека значения четырех констант из восьми нами заведенных плюс адрес поля, отведенного для одного из наших персонажей:
ЖЕНШИНА ОДИНОКИЙ РАБОТАЕТ ГОРОДСКОЙ МАША ОПИСАНИЯ
Слово ОПИСАНИЯ заносит в область, отведенную персонажу, битовый шаблон, соответствующий атрибутам персонажа
Наконец, определите слово СВЕДЕНИЯ, которое выбирало бы из стека имя персонажа и по битовому шаблону последнего выводило бы сведения о нем, например:
МАША СВЕДЕНИЯ ЖЕНЩИНА ОДИНОКИЙ РАБОТАЕТ ГОРОДСКОЙ ok
8.7.
Создайте программу, которая высвечивала бы поле для игры в «крестики-нолики» и давала возможность двум игрокам задавать свои ходы с помощью клавиатуры. Так, выражение 4 X! помещает X в клетку 4 (счет клеток ведется с единицы) и выводит на экран картинку:
После этого выражение 3 0! помещает 0 в клетку 3 и выводит следующую картинку.
Для того чтобы помнить содержимое игрового поля, примените массив байтов, где значение 1 соответствовало бы X, значение -1 -- 0, а 0 -- пустой клетке.
Так как некоторые компьютеры не позволяют программно подводить курсор к определенному месту экрана, мы будем просто заново генерировать поле для игры с помощью CR, SPACE и т. д.
(Замечание: до тех пор, пока мы не приведем дополнительные сведения о словарях, избегайте обозначения чего-либо посредством «X», так как ваше имя может вступить в конфликт с редакторским X )