Язык Форт и его реализации

Определяющие слова


Конструкции языка Форт, которые мы рассматривали до сих пор, имеют аналоги в других известных языках программирования. Например, определению через двоеточие очевидным образом соответствует описание процедуры в языках Фортран и Паскаль. Теперь мы рассмотрим свойство языка Форт, которое существенно отличает его от других и в значительной степени определяет его как нечто новое.

Программируя какую-либо задачу, мы моделируем понятия, в которых эта задача формулируется и решается, через понятия данного языка программирования. Традиционные языки имеют определенный набор понятий (процедуры, переменные, массивы, типы данных, исключительные ситуации и т.д.), с помощью которых программист должен выразить решение исходной задачи. Этот набор выразительных средств фиксирован в каждом языке, но поскольку он содержит такие универсальные средства, как процедуры и типы данных, то опытный программист может смоделировать любые необходимые ему понятия программирования.

Язык Форт предлагает вместо фиксированного набора порождающих понятий единый механизм порождения таких порожающих понятий. Таким образом, приступая к программированию задачи на языке Форт, программист определяет необходимые ему понятия и с их помощью решает поставленную задачу. Такое прямое введение в язык нужных для данной задачи средств вместо их моделирования (часто весьма сложного) через имеющиеся универсальные средства уменьшает разрыв между постановкой задачи и ее программной реализацией. Это упрощает понимание и отладку программы, так как в ней более наглядно проступают объекты и отношения исходной задачи, и вместе с тем повышает ее эффективность, так как вместо сложного моделирования понятий задачи через универсальные используется их непосредственная реализация через элементарные.

Новые понятия вводятся посредством определения через двоеточие, в теле которого используются слова

CREATE ---> DOES> ---> (компиляция) ---> A (исполнение)

Рассмотрим уже известное нам слово CONSTANT (константа), которое используется для определения констант.
Его определение можно задать так:

: CONSTANT ( N ---> ) CREATE , DOES> @ ;

Часть определения от слова CREATE до DOES> называется создающей (CREATE — создать), остальная часть от слова DOES> и до конца называется исполняющей (DOES — исполняет). В данном случае создающая часть состоит из одного слова , (запятая), а исполняющая часть — из слова @ (разыменование).

Рассмотрим исполнение данного определения на примере 4 CONSTANT XOP. Слово 4 кладет число 4 на стек. Далее исполняется слово CONSTANT. Слово CREATE, с которого начинается его определение, выбирает из входной строки очередное слово (в данном случае XOP) и добавляет его в словарь как новую команду. Создающая часть, состоящая из слова «запятая», переносит число 4 в память, компилируя его на вершину словаря. Слово DOES>, отмечающее конец создающей части, завершает исполнение данного определения, при этом семантикой созданного слова XOP будет последовательность действий исполняющей части, начиная от слова DOES>. В дальнейшем исполнение слова XOP начнется с того, что слово DOES> положит на стек адрес вершины словаря, какой она была на момент начала работы создающей части, после чего будет работать исполняющая часть определения. Поскольку по данному адресу создающая часть скомпилировала число 4, то исполняющая часть — разыменование — заменит на стеке этот адрес его содержимым, т.е. числом 4, что и требуется по смыслу данного понятия.

Рассмотрим другой пример. Введем понятие вектора. При создании вектора будем указывать размер (число элементов), а при обращении к нему — индекс (номер) элемента, в результате чего получается адрес данного элемента. Этот адрес можно разыменовать и получить значение элемента или можно заслать по этому адресу новое значение. Если желательно контролировать правильность индекса при обращении к вектору, то определение может выглядеть так:

: ВЕКТОР ( N:PA3MEP ---> ) CREATE DUP , 2* ALLOT DOES> ( I:ИНДЕКС,A ---> A[I]:АДРЕС ЭЛ-ТА I) OVER 1- OVER @ U< ( ПРОВЕРКА ИНДЕКСА) IF SWAP 2* + EXIT THEN ." ОШИБКА В ИНДЕКСЕ" ABORT ;



Разберем, как работает данное определение при создании вектора 10 ВЕКТОР X.

Создающая часть компилирует размер вектора и вслед за этим отводит память на 10*2, т.е. 20 байт. Таким образом, для вектора X в словаре отводится область размером 22 байта, в первых двух байтах которой хранится число 10 — размер вектора. При обращении к вектору X на стеке должно находиться значение индекса. Слово DOES> кладет сверху адрес области, сформированной создающей частью, после чего работает исполняющая часть определения. Проверив, что индекс I лежит в диапазоне от 1 до 10, она оставляет на стеке адрес, равный начальному адресу области плюс I*2, т.е. адрес I-го элемента вектора, если считать, что элементы располагаются в зарезервированной области подряд. Слово EXIT (выход) завершает исполнение определения, что позволяет обойтись без части «иначе» в условном операторе. Если окажется, что индекс не положителен или больше числа элементов, то будет напечатано сообщение «ошибка в индексе» словом .", и исполнение закончится через слово ABORT (выброс). Если по каким-либо причинам контроль индексов не нужен, можно дать более краткое определение:

: ВЕКТОР ( N:PA3MEP ---> ) CREATE 2 * ALLOT DOES> ( I:ИНДЕКС,A ---> A[I]:АДРЕС ЭЛ-ТА I) SWAP 1- 2 * + ;

Если мы условимся считать индексы не от единицы, а от нуля, то исполняющая часть еще более сократится за счет исключения слова 1- для уменьшения значения индекса на единицу.

Таким образом, программист может реализовывать варианты понятий, наиболее подходящие для его задачи. Исходный небольшой набор слов-команд форт-системы он может избирательно наращивать в нужном направлении, постоянно совершенствуя свой инструментарий.

Используемый в языке Форт способ введения определяющих слов связан с очень важным понятием — частичной параметризацией. Определяющее слово задает целый класс слов со сходным действием, которое описывается исполняющей частью определяющего слова. Каждое отдельное слово из данного класса характеризуется результатом исполнения создающей части — тем или иным содержимым связанной с ним области памяти, адрес которой передается исполняющей части как параметр.Таким образом, исполняющая часть — то общее, что характеризует данный класс слов, — во время ее исполнения частично параметризуется результатом исполнения создающей части для данного отдельного представителя этого класса. Как создающая часть, так и частично параметризованная исполняющая часть, могут требовать дополнительных параметров для своего исполнения (в примере для вектора это размер вектора и индекс). Все это представляет программисту практически неограниченную свободу в создании новых понятий и удобных инструментальных средств.


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