ОПРЕДЕЛЯЮЩИЕ СЛОВА ВЫ МОЖЕТЕ СПЕЦИФИЦИРОВАТЬ САМИ
Рассмотрим три класса определяющих слов. Слова первого класса дают возможность выделять фрагменты с похожими свойствами из серии определений через двоеточие. В предыдущей главе вы имели дело со следующими определениями: : 1ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 0 .БРЕД ; : 2ПРИЛАГАТЕЛЬНОЕ 10 CHOOSE 1 .БРЕД ; : СУЩЕСТВИТЕЛЬНОЕ 10 CHOOSE 2 .БРЕД ;
Если бы вам пришлось определять и другие части речи, то можно было бы их выделить в отдельное слово «часть-речи»:: ЧАСТЬ ( столбец# -- ) 10 CHOOSE SWAP .БРЕД ; : 1ПРИЛАГАТЕЛЬНОЕ 0 ЧАСТЬ ; : 2ПРИЛАГАТЕЛЬНОЕ 1 ЧАСТЬ ; : СУЩЕСТВИТЕЛЬНОЕ 2 ЧАСТЬ ;
Однако при этом слишком расточительно расходуется память. Поскольку различие между показанными тремя словами заключено только в одном числе, их незачем определять через двоеточие. Целесообразнее воспользоваться таким приемом:: ЧАСТЬ ( столбец# -- ) CREATE , DOES> @ 10 CHOOSE SWAP .БРЕД ; 0 ЧАСТЬ 1ПРИЛАГАТЕЛЬНОЕ 1 ЧАСТЬ 2ПРИЛАГАТЕЛЬНОЕ 2 ЧАСТЬ СУЩЕСТВИТЕЛЬНОЕ
Приведенное определение слова ЧАСТЬ аналогично определению слова CONSTANT1, с той лишь разницей, что в период выполнения помимо занесения в стек номера столбца оно также определяет номер строки посредством датчика случайных чисел и обращается к слову .БРЕД.
Следующий довольно большой класс определяющих слов позволяет выделять повторяющиеся фрагменты кодов периода компиляции. Допустим, требуется назначить ряд блоков для группы студентов начиная с блока 360, причем каждому студенту нужно выделить по 25 блоков. Для этого понадобится несколько кон-
1 Для пуристов. Иногда имеет смысл объединять существующие определяющие слова, если они выполняют большую часть тех функций (или все), которые должны выполняться во время компиляции. Так, выражение CREATE, с тем же эффектом можно заменить словом CONSTANT. Стандарт, однако, не рекомендует пользоваться такими приемами и не во всех системах это получится.
стант, обозначающих номер начального блока для каждого студента. Константы можно определить так:360 CONSTANT ВАСИЛЬЕВ 385 CONSTANT СИМОНЧИК 410 CONSTANT РЯБИНИНА 455 CONSTANT ИВАНОВА 460 CONSTANT ПЕТРОВА 485 CONSTANT ДЕМИН
Если у вас со сложением дела обстоят плохо, то вы обязательно ошибетесь. Кроме того, вдруг вы измените свое решение и выделите каждому студенту по 30 блоков? Есть более изящный прием. Он состоит в том, чтобы выделить вычисления во время компиляции в отдельное слово:: +СТУДЕНТ ( n -- n+25 n ) DUP 23 + SWAP ; 360 \ начало участка памяти, отведенной студентам +СТУДЕНТ CONSTANT ВАСИЛЬЕВ +СТУДЕНТ CONSTANT СИМОНЧИК +СТУДЕНТ CONSTANT РЯБИНИНА +СТУДЕНТ CONSTANT ИВАНОВА +СТУДЕНТ CONSTANT ПЕТРОВА +СТУДЕНТ CONSTANT ДЕМИН . .( Конец участка памяти, отведанного студентам ) CR
Последняя точка выбирает из стека номер блока. Но и этот прием блекнет перед методом использования определяющих слов. Убедитесь сами: : СТУДЕНТ ( n -- n+23) CREATE DUP , 23 + DOES> ( -- n) @ ; 360 \ Начало участка, отваленного студентам СТУДЕНТ ВАСИЛЬЕВ СТУДЕНТ СИМОНЧИК СТУДЕНТ РЯБИНИНА СТУДЕНТ ИВАНОВА СТУДЕНТ ПЕТРОВА СТУДЕНТ ДЕМИН . .( Конец участка, отведенного студентам ) CR
Определяющее слово СТУДЕНТ и создает «константы», и управляет всеми вычислениями во время компиляции.
Определяющие слова третьего класса дают возможность создавать новые структуры данных, например многомерные массивы. Нас иногда упрекают в том, что структуры данных Форта бедны. Однако редко бывает так, чтобы какой-то набор структур данных удовлетворял всем прикладным областям. Форт же предоставляет удобные инструменты для создания определяющих слов привычных нам структур данных. Мы можем образовать структуры данных, которые во время выполнения осуществляют, помимо всего прочего, присущие только им операции. В информатике такие структуры называются абстрактными типами данных.
Рассмотрим два примера. Первый из них, более простой, демонстрирует определяющее слово, организующее одномерный массив. Можно, конечно, создать такой массив и без применения определяющего слова:CREATE КЛАПАНЫ 30 ALLOT \ байтовый массив установки клапанов : КЛАПАН ( i -- а) \ преобр. номера клапана в абсолютный адрес КЛАПАНЫ + ;
Здесь слово КЛАПАН вычисляет индекс массива КЛАПАНЫ.
Например, при выполнении выражения6 КЛАПАН С@
будет включен гидравлический клапан 6.
Приведенное выше решение приемлемо только в тех случаях, когда в программе требуется один или два таких массива. Если же массивов должно быть больше, использование определяющего слова упростит программирование.: МАССИВ ( #байтов -- ) \ определение одномерного массива байтов CREATE ALLOT DOES> ( i -- a) + ; 30 МАССИВ КЛАПАН 6 КЛАПАН С@
Рассмотрим выполнение этого определения. В фазе 1 определяется слово МАССИВ. В фазе 2 исполняется МАССИВ, который в свою очередь обращается к слову CREATE (чтобы определить КЛАПАН) и слову ALLOT (для резервирования 30 байтов под массив). В фазе 3 исполняется слово КЛАПАН, инициируя код периода выполнения слова МАССИВ с добавлением индекса (6) к начальному адресу массива.
Если внести изменения в определение определяющего слова перед его повторной компиляцией, то тем самым можно изменить характеристики всех слов, входящих в данное семейство. Такая возможность значительно упрощает разработку программ. Например, когда нужно при описании массива заполнить его нулями, вы должны соответствующим образом создать определение МАССИВ. Сначала вы определяете слово, которое аналогично ALLOT, но «обнуляет» выделенный участок памяти:: 0ALLOT ( #байтов -- ) HERE OVER ERASE ALLOT ;
Затем подставляете в определение МАССИВ вместо ALLOT слово 0ALLOT: : МАССИВ ( #байтов -- ) \ определение одномерного массива байтов CREATE 0ALLOT DOES> ( i -- a) + ;
Можно также выделить в отдельный фрагмент некоторые отладочные процедуры, необходимые при разработке программы, а затем удалить их, предварительно убедившись в том, что она работает правильно. Ниже приводится вариант слова МАССИВ, где во время выполнения проверяется, не вышел ли индекс массива за установленные границы. : МАССИВ ( #байтов) CREATE DUP , ALLOT DOES> ( i -- a) 2DUP @ U< NOT ABORT" Выход эа границу " + 2+ ;
Это происходит следующим образом:DUP , ALLOT Компиляция счетчика и выделение заданного количества байтов,
DOES> 2DUP @ По заданному на стеке индексу во время выполнения вычисляется: ( i pfa i # )
U< NOT Проверка того, что индекс не меньше максимального значения, а именно: запомненного счетчика. Так как U< является операцией сравнения над значениями без знака, то отрицательные аргументы будут трактоваться как числа, выходящие за границу, и приводить к аварийному сообщению.
ABORT" Выход за Аварийное завершение при выходе числа за диапазон. границу "
+ 2+ В противном случае сложение индекса с pfa и добавление числа 2 для пропуска ячейки, содержащей счетчик.
Существует еще один способ использования определяющих слов, который помогает при создании программ. Допустим, вы вдруг решаете, что все ваши массивы, определенные с помощью слова МАССИВ, слишком велики, чтобы хранить их в памяти компьютера, и должны быть помещены на диск. Единственное, что вы должны в таком случае сделать, переопределить фрагмент периода выполнения в слове МАССИВ. Это новое определение вычислит номер блока, в котором содержится заданный байт, считает блок посредством BLOCK в некоторый буфер и оставит в вершине стека адрес требуемого байта относительно начала буфера. Массив, определенный подобным образом, может храниться в нескольких последовательных блоках (с использованием тех же средств, что и в упр. 10.7).
Ниже приведен пример определяющего слова, которое создает двумерный массив байтов заданного размера1: : МАТРИЦА ( #строк #столбцов -- ) CREATE OVER , * ALLOT DOES> ( строка столбец -- a) DUP @ ROT * + + 2+ ;
1 Для любителей оптимизации Этот вариант будет выполняться еще быстрее: МАТРИЦА ( #строк #столбцов -- ) OVER CONSTANT HERE 2+ , * ALLOT DOES> ( строка столбец -- а) 2@ ROT * + + ;
Для того чтобы создать массив размером 4х4 байта, вы должны написать:
Выбрать же, к примеру, байт в строке 2 и в столбце 1 можно следующим образом:2 1 ТАБЛИЦА С@
Вот так кратко можно описать выполнение слова МАТРИЦА. Поскольку аппаратные средства компьютера позволяют хранить только одномерные массивы, второе измерение необходимо моделировать.
Мы представляем себе, что наш массив выглядит следующим образом:
а на самом деле в памяти машины он хранится в виде
Если вам требуется адрес байта, расположенного в строке 2 столбца 1, то вы можете умножить номер столбца (1) на число строк в каждом столбце (4), а затем прибавить номер строки (2). В результате получается, что вам нужен шестой байт машинного представления массива. Примерно такие вычисления делают в период выполнения элементы, составляющие слово МАТРИЦА. Но для того чтобы их производить, как вы можете заметить, любое составляющее слово должно «знать» число строк в каждом столбце конкретного массива. Для этих целей слово МАТРИЦА во время компиляции вносит число строк в столбце в начало массива. Для любознательных приведем стековые эффекты фрагмента периода выполнения слова МАТРИЦА:ОПЕРАЦИЯ СОДЕРЖИМОЕ СТЕКА строка столбец pfa PUP @ строка столбец рfa #строк ROT строка pfa #строк столбец * строка pfа индекс-столбца + + адрес 2+ скорректированный-адрес
К вычисленному адресу необходимо добавить двойку, потому что первая ячейка нашего массива содержит число строк.
Предлагаем вашему вниманию еще один пример, который, может быть, не очень полезен, но весьма нагляден:\ Шаблоны с использованием определяющих слов : STAR 42 EMIT ; : .РЯД ( b -- ) \ вывод звездочки на каждый бит из байта CR 8 0 DO DUP 128 AND IF STAR ELSE SPACE THEN 2* LOOP DROP ; : ФОРМА ( b1 b2 b3 b4 b5 b6 b7 b8 -- ) \ определение формы из 8-строк CREATE 8 0 DO С, LOOP DOES> DUP 7 + DO I С@ .РЯД -1 +LOOP CR ; \ формы: HEX 18 18 3С 5А 99 24 24 24 ФОРМА ЧЕЛОВЕК 81 42 24 18 18 24 42 81 ФОРМА КОНЬ АА АА FE FE 38 38 38 FE ФОРМА ЗАМОК DECIMAL
Слово .РЯД выводит строку, состоящую из звездочек и пробелов, где звездочка соответствует единице, а пробел - нулю в восьмиразрядном двоичном представлении числа, находящегося в вершине стека, например:2 BASE ! ok
00111001 .РЯД *** * ok DECIMAL ok
Наше определяющее слово ФОРМА берет из стека восемь аргументов и определяет шаблон, который при своем выполнении выводит решетку 8х8 элементов, соответствующую этим восьми аргументам:ЧЕЛОВЕК ** ** **** * ** * * ** * * * * * * * ok
Итак, определяющие слова могут быть чрезвычайно полезным инструментом. Создавая новое определяющее слово, вы тем самым расширяете свой компилятор. Традиционные языки не обеспечивают такой гибкости, потому что они представляют собой жесткие готовые программы, которые предлагают вам в обязательном порядке конкретный набор операторов. Реальная помощь определяющих слов заключается в том, что их применение позволяет упростить вашу программу. При правильном их употреблении вы можете сократить время программирования, уменьшить размер программы и повысить ее читабельность. В следующем разделе мы приведем еще один способ расширения средств компилятора Форта.