Профессиональное программирование

В #45/95 мы рассмотрели основы работы с Фортом. Теперь перейдем к более серьезным вопросам.

Однa из основных проблем, с которой постоянно сталкивается программист, - это ограничения по памяти или быстродействию. Во многих случаях только применяя ассемблер можно добиться оптимальных характеристик.

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

Наилучшим выходом из положения является использование форт-ассемблера. Форт-ассемблер - это библиотека Форта, которая позволяет создавать форт-слова, содержащие непосредственно машинные коды. Форт-ассемблер сходен по виду с обычным ассемблером, но команды в нем записываются в постфиксной нотации: сначала операнды, затем операция.

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

Форт-определение на ассемблере имеет вид:

CODE name code END-CODE

Слово CODE создает словарную статью с именем name и помещает в нее код, генерируемый словами форт-ассемблера. Слово END-CODE завершает ассемблерное определение.

Стек ПараФорт использует стек процессора, но хранит в нем 32-битовые значения. Поэтому при передаче значений через стек в CODE-определении необходимо выполнение двух операций POP, а при возврате значения из CODE-определения необходимо выполнение двух операций PUSH. Для возврата из CODE-определения используется слово NEXT, которое возвращает управление форт-системе.

С учетом этих ограничений приведем пример слова, определенного через CODE, которое берет из стека символ и возвращает следующий символ по таблице ASCII.

CODE next-sym (c -- c1)

AX POP

AX POP ( получили символ из стека )

AX INC ( увеличили код символа на 1 )

AX PUSH ( положили результат в стек )

AX, AX XOR ( получили 0 в АХ )

AX PUSH ( положили 0 в стек)

NEXT

END-CODE

Непосредственные операнды задаются в форт-ассемблере в виде:

# operand-value

где operand-value - последовательность слов, вырабатывающая в вершине стека значение операнда. Непосредственные операнды могут использоваться только на месте источника.

Примеры:

AX, # F1F0 TEST

CL, # 2 CMP

AX, # W @ 2/ 1+ ADD

При прямой адресации адрес операнда задается последовательностью форт-слов, вычисляющих в стеке требуемое значение адреса. В командах с двумя операндами адрес получателя отделяется от второго операнда знаком ",".

Примеры:

AX, HEX 100 ADD

VAR-ADDR, AX CMP

HEX 61, AL OUT

Слово НЕХ указывает, что ближайшее число - шестнадцатеричное. При базовой и индексной адресации адрес операнда задается соответствующим регистром или комбинацией регистров. Допускаются следующие обозначения:

[BX] [BP] [SI] [DI] [BX+SI] [BX+DI] [BP+SI] [BP+DI]

Обозначения для адресов операндов-получателей в командах с двумя операндами соответственно имеют вид:

[BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI], [BP+DI],

(операнд-получатель отличается от операнда-источника наличием ",")

Кроме того, перед индексными и базовыми регистрами можно дополнительно задать смещение в виде последовательности слов, вырабатывающих в стеке значение смещения.

Примеры:

AX, [BX+SI] MOV

ARRAY1 [SI],AX CMP

CL, 10 [BP] ADD

[BX] JMP

Для явного задания длины операнда, находящегося в памяти, перед соответствующим адресом можно поместить один из следующих описателей длины:

BYTE - для операнда длиной 1 байт;

WORD - для операнда длиной 2 байта;

DWORD - для операнда длиной 4 байта.

По умолчанию для операндов, размещенных в памяти, подразумевается описатель BYTE.

Примеры:

BYTE LODS

DWORD 5 [BX] INC

WORD STOS

Локальные метки используются в форт-ассемблере для организации условных и безусловных переходов внутри CODE-определений. Определение локальной метки имеет вид

n$ :

где n - уникальный номер метки внутри данного ассемблерного определения. Значение n должно лежать в диапазоне от 1 до 30. Ссылка на локальную метку с номером n имеет вид n$. Допускается использование ссылок вперед. Разрешение (вычисление) таких ссылок (подстановка реальных адресов в адресную часть команд) осуществляется в конце ассемблерного определения при выполнении слова END-CODE.

Ниже приведена структура ассемблерной подпрограммы, осуществляющей проверку конца строки.

CODE ?CR ( c -- f )

AX POP

AX POP ( получили символ из стека )

BX, BX SUB ( получили 0 в ВХ )

AL, # 13 CMP ( символ - конец строки? )

1$ JNE ( если нет - перейти на метку 1$ )

BX DEC ( получить -1 в ВХ )

1$: BX PUSH

BX PUSH ( положили результат в стек )

NEXT

END-CODE

Дополнительные слова CS: DS: SS: и ES: могут использоваться для сокращенного обозначения команд вида seg SEG, где seg - обозначение соответствующего сегментного регистра.

При обмене данными с переменными следует учитывать, что адрес переменной в словаре совпадает с реальным адресом только в пределах стекового сегмента, поэтому при использовании переменных, адрес которых меньше 65535, можно просто указывать префикс стекового сегмента.

Например:

SS: AX, TMP1 MOV

SS: TMP2, BX CMP

Для использования переменных в CODE-определениях лучше применять слово VAR-CODE, оно создает переменные, к которым можно обращаться в CODE-определениях независимо от расположения в словаре.

Например:

VAR-CODE VAR1

SS: AX, VAR1 MOV

Число переменных, определенных через VAR-CODE, в одной программе не должно превышать 64.

Макроопределения

Использование макроопределений позволяет сделать код более удобным для чтения. Предположим, мы хотим составить макроопределения для чтения числа из стека в регистр AX. CODE-определение будет выглядеть так:

CODE AX-POP ( n -- )

AX POP

AX POP

NEXT

END-CODE

Произведем преобразования:

1. Напишем перед определением слово ASSEMBLER.

2. Заменим CODE на двоеточие.

3. Уберем NEXT.

4. Заменим ЕND-CODE на точку с запятой.

Получим макроопределение:

ASSEMBLER

: AX-POP ( n -- )

AX POP

AX POP

;

Используем это макроопределение.

CODE DROP ( n -- )

AX-POP

NEXT

END-CODE

Быстрая загрузка

Рассмотрим теперь весьма полезную функцию ПараФорта, называемую быстрой загрузкой.

Что это такое? Представьте себе, что вы начинаете компоновать в одну большую программу множество отлаженных процедур, и в этот момент выясняется, что одну из процедур самого нижнего уровня хорошо бы заменить.

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

Функционирование форт-системы

Внутреннее устройство форт-системы на удивление просто и может служить ярким примером того, как простыми средствами достигаются исключительная гибкость и эффективность.

Знание устройства и принципов функционирования форт-системы безусловно необходимо профессионалу, так как позволяет добиться максимальной производительности программ.

Форт-система состоит из нескольких уровней (рис. 1).

Компилятор

Текстовый интерпретатор

Интерфейс ввода/вывода

Адресный интерпретатор

Рис. 1. Уровни форт-системы

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

1. Адресный интерпретатор

Адресный интерпретатор предназначен для выполнения кода форт-системы и представляет собой набор процедур самого низкого уровня, которые обычно пишутся на ассемблере. Дело в том, что код форт-системы отличается от кода, порождаемого традиционными компиляторами. Форт-система формирует так называемый шитый код, состоящий из набора адресов выполняемых процедур. Например, определение

: Тест SWAP DUP DROP ROT ;

будет представлено в виде следующего шитого кода (рис. 2).

Словарь Адреса

... 0000

SWAP 1210

...

DUP 2130

...

ROT 4400

...

DROP 5126

...

Тест

1210

2130

5126

4400 FFFF

Рис. 2. Шитый код

Адресный интерпретатор берет набор адресов некоторого определения и выполняет соответствующие слова. В случае, если слово является CODE-определением, управление передается непосредственно процессору, поскольку CODE-определение содержит обычный код. В конце каждого CODE-определения должно стоять слово NEXT, которое и возвращает управление адресному интерпретатору.

2. Интерфейс ввода-вывода

Интерфейс ввода-вывода предназначен для диалога форт-системы с программистом. В простейшем случае обеспечивается работа клавиатуры, экрана и внешней памяти. В этом случае интерфейс базируется на словах типа KEY, EMIT, TYPE. Эти слова программируются на ассемблере, и именно после их реализации форт-система выдает свое неизменное "ok".

3. Текстовый интерпретатор.

Функция текстового интерпретатора состоит в получении и выполнении команд программиста. Основным словом текстового интерпретатора, а также словом, которому передается управление после запуска форт-системы, является слово QUIT. Слово QUIT можно в простейшем виде определить следующим образом:

: QUIT

BEGIN

TYPE-ok

QUERY

INTERPRET

AGAIN ;

Слово QUERY получает команды, а слово INTERPRET интерпретирует их. Работа форт-системы - бесконечное выполнение команд программиста. Более исполнительного работника трудно себе представить.

4. Компилятор

Наконец, главный уровень форт-системы - это уровень компилятора, который позволяет добавлять в словарь новые слова. На этом уровне действуют слова CREATE, DOES>, : , CODE и т.д. Новые слова добавляются следующим образом. Вводится переменная STATE, указывающая, в каком состоянии находится форт-система: интерпретации или компиляции. В слове INTERPRET происходит анализ этой переменной:

: INTERPRET

...

STATE @ IF EXECUTE ELSE , THEN

...;

В состоянии интерпретации слово выполняется, а в состоянии компиляции оно заносится в словарь. Таким образом, от уровня компилятора систему можно безгранично расширять.

Словарь

Словарь форт-системы состоит из набора словарных статей, каждая из которых содержит четыре поля (см. рис. 3): поле имени, поле связи, поле кода и поле параметров.

0 0 1 S n = длина <- поле

имени в символах имени

n n символов имени

n+1 0 0 0 n = длина

имени в символах

n+2 адрес предыдущего <- поле

определения связи

n+6 адрес выполняемой <- поле

процедуры кода

n+10 адреса слов, входящих <- поле

в определение параметров

... ...

Рис. 3. Структура словарной статьи

Через поля связи все словарные статьи связаны между собой. При поиске введенной команды форт-система просматривает все словарные статьи и сравнивает полученное имя с именами форт-слов. Если имя форт-слова совпадает с введенным именем, управление передается на процедуру, адрес которой записан в поле кода.

Для локализации поиска словарь форт-системы делится на несколько частей - вводятся контекстные словари. Контекстный словарь - это тот список слов, в котором осуществляется поиск форт-слов в данный момент. Это разделение позволяет иметь слова с одинаковыми именами, но с разной семантикой. Например, слово АND в Форте осуществляет логическую операцию "и" над словами в стеке, а в ассемблере слово AND выполняет операцию над регистрами и ячейками памяти. Для того чтобы эти значения не пересекались, в ПараФорте создан специальный контекстный словарь с именем ASSEMBLER, который подключается во время компилирования CODE-определений.

Всего в ПараФорте три контекстных словаря:

ASSEMBLER

HIDDEN

FORTH

Словарь HIDDEN предназначен для слов, используемых самой форт-системой. Словарь FORTH является общим словарем.

Программист может создавать свои словари с помощью слова VOCABULARY.

Например:

VOCABULARY EDITOR

EDITOR DEFINITIONS

После выполнения этих команд все вновь создаваемые слова будут помещаться в словарь EDITOR. Если теперь выполнить слово FORTH, то эти слова будут недоступны для поиска. Для подключения их к области поиска потребуется выполнить слово EDITOR.

Другие возможности ПараФорта

Коротко остановимся на других возможностях ПараФорта. К их числу относятся:

- двойная арифметика;

- вещественная арифметика;

- обьектно-ориентированное расширение;

- компрессия словаря.

1. Двойная арифметика

Стандартные числа, с которыми оперирует форт-система, являются 32-разрядными. Они позволяют задавать числа примерно до 109 (то есть до миллиарда). Двойные числа занимают два значения в стеке, то есть являются 64-разрядными, и позволяют задавать числа до 1019.

Для работы с двойными числами используются слова:

S>D ( n -- d ) - преобразует 32-разрядное число в 64-разрядное.

2DROP ( d -- ) - снимает со стека 64-разрядное значение d.

2DUP ( d -- d d ) - дублирует 64-разрядное значение d на вершине стека.

2SWAP ( d1 d2 -- d2 d1 ) - меняет местами два 64-разрядных значения d1.

D+ ( d1 d2 -- d3 ) - помещает в стек 64-разрядную сумму d1+d2.

D- ( d1 d2 -- d3 ) - помещает в стек разность d1 и d2.

D* ( d1 u -- d2 ) - помещает в стек произведение d1 на u.

D/ ( d1 u -- d2 ) - помещает в стек частное от деления d1 на u.

D. ( d -- ) - выводит значение 64-разрядного числа.

2. Вещественная арифметика

Вещественная арифметика оперирует числами, представленными в т.н. "научном" формате, например:

5.67564˜-3

-1.004˜25

Стандартные слова Форта выполняют все операции в стеке с целыми числами. Для работы с вещественными числами требуется специальный формат с плавающей точкой.

Вещественные числа также занимают два значения в стеке, поэтому при работе с ними можно пользоваться словами 2DUP, 2DROP, 2SWAP и т.д.

Для работы с вещественными числами используются следующие слова:

D>F ( d -- f ) - преобразует 64-разрядное целое число в вещественное.

F+ ( f1 f2 -- f3 ) - помещает в стек сумму f1 и f2.

F- ( f1 f2 -- f3 ) - помещает в стек разность f1 и f2.

F* ( f1 f2 -- f3 ) - помещает в стек произведение f1 на f2.

F/ ( f1 f2 -- f3 ) - помещает в стек частное от деления f1 на f2.

F. ( f -- ) выводит значение вещественного числа.

SIN ( n -- f ) - помещает в стек синус угла размером n градусов.

COS ( n -- f ) - помещает в стек косинус угла размером n градусов.

PI ( -- f ) - помещает в стек число p в вещественном формате.

3. Обьектно-ориентированное расширение

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

В качестве средств ООП применяются следующие слова:

TYPE> ( name \ -- ) - начинает определение класса с именем name.

VAR ( name \ n -- ) - создает скрытую переменную класса размером n.

OPS> ( -- ) - указывает на начало определений методов класса.

INCLUDE> ( name \ -- ) - устанавливает ссылку на базовый класс.

ENDTYPE> ( name \ -- ) - заканчивает определение класса.

ООП должно поддерживать три свойства:

- инкапсуляцию;

- наследование;

- полиморфизм.

Рассмотрим их на примере написания программы работы со стеком в стиле ООП.

TYPE> STACK

4 VAR STACKPTR

100 VAR STACKBODY

: INC ( -- ) 4 STACKPTR +! ;

: DEC ( -- ) -4 STACKPTR +! ;

OPS>

: INIT ( -- ) STACKBODY STACKPTR ! ;

: ?EMPTY ( -- f ) STACKPTR @ STACKBODY = ;

: PUSH ( n -- ) STACKPTR @ ! INC ;

: POP ( -- n ) ?EMPTY IF ." стек пуст"

ELSE DEC STACKPTR @ @ THEN ;

ENDTYPE> STACK

Для создания обьекта STACK нужно выполнить следующую команду:

STACK А

После этого будет создан обьект А класса STACK, которому доступны четыре правила: INIT, ?EMPTY, PUSH и POP. Например, команда:

A INIT 5 A PUSH

инициализирует стек А и поместит число 5 в этот стек.

Правила инкапсулированы в данном классе и не могут быть вызваны отдельно от обьекта.

Для создания порождаемых классов служит слово INCLUDE>. Предположим, требуется создать стек с возможностью проверки на переполнение, не изменяя при этом базовый класс.

TYPE> STACK25

4 VAR STACKPTR

100 VAR STACKBODY

OPS> INCLUDE> STACK

: DEPTH STACKPTR @ STACKBODY - 4 / ;

: ?FULL DEPTH 25 >= ;

ENDTYPE> STACK25

Здесь класс STACK25 наследует правила класса STACK.

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

4. Компрессия словаря

При использовании слова SAVE в исполняемом файле сохраняется весь словарь. В тех случаях, когда требуется получить минимальный размер исполняемого файла, следует использовать компрессию. Во время компрессии из исполняемого файла удаляются неиспользуемые слова и заголовки словарных статей. Это позволяет значительно уменьшить размер файла.

Для выполнения компрессии необходимо войти в меню (клавиша <F2>) и выбрать операцию "Компрессия" в подменю "Слово".

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

Форт-стиль

Мы рассмотрели основные команды Форта и принципы программирования на нем. Если этим ограничиться, может сложиться впечатление, что Форт - это просто один из многих языков программирования и не имеет смысла особенно его выделять. Однако теперь мы обсудим, как воспользоваться теми возможностями Форта, которые и делают его языком для суперпрофессионалов: высокой производительностью программирования и обеспечением надежности программ. Овладение этими возможностями не происходит по мановению волшебной палочки, а требует серьезной работы по усвоению принципов так называемого форт-стиля. Форт-стиль - это набор требований к написанию программ, соблюдение которых и выводит программиста на такой уровень эффективности работы, который недостижим при использовании традиционных языков.

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

Самое грустное, что эта шутка недалека от истины. Учебные программы легко пишутся и отлаживаются. К сожалению, серьезным программам это не свойственно. А стремительно приближающийся срок сдачи программы вместе с санкциями за несвоевременную поставку совсем не улучшают настроения программисту, устраняющему одну ошибку и замечающему еще две новых.

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

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

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

Рассмотрим требования форт-стиля более подробно.

1. Простота

Каждая процедура, входящая в программу, должна быть реализована как можно проще, то есть включать наименьшее количество внутренних связей, а особенно связей с другими процедурами. Лучше всего, если связь с другими процедурами будет ограничиваться стеком, как наиболее простым и легко контролируемым каналом передачи данных. Это позволит с легкостью тасовать процедуры без опасения, что при этом будет нарушена работа программы в целом. Кроме того, мы получим дополнительное преимущество: более простая процедура обычно является и более быстродействующей.

2. Лаконичность

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

Отдельными обьектами следует считать и такие символы, как точки, запятые, скобки и прочие служебные символы, ибо неправильное применение или пропуск любого из них приводит к ошибке. Сравним запись двух функций в Форте и в традиционном языке:

Форт Традиционный язык

GET-XY SAVE-XY get_xy(x,y)

save_xy(x,y)

Где проще допустить ошибку?

3. Самодокументируемость

Это очень важное требование форт-стиля. Оно означает, что имена форт-слов должны соответствовать их функциям. В этом случае форт-программа может прочитываться как некоторый технический текст. Например:

: Подсчет строк

Взять-имя-файла

Подсчитать-строки

Вывести-результат ;

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

Обеспечение надежности программ

Получение красиво написанной программы отнюдь не означает, что она будет правильно работать. К сожалению, машина слишком буквально выполняет то, что написано, хотя иногда это не совсем то, что хотел получить программист.

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

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

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

По своему желанию (или в соответствии с важностью программы) программист управляет надежностью форт-слов. Надежность каждого форт-слова (включая и программу в целом) определяется количеством циклов отладки.

Цикл отладки - это запуск программы при определенных входных условиях. Чем больше циклов отладки прошло форт-слово, тем оно надежнее.

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

Приемы отладки программы

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

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

Но и в этой ситуации не стоит заниматься прокручиванием программы в уме. Машина сделает это гораздо лучше. Целесообразно предпринять следующие шаги.

1. Визуализировать процесс

Самое простое - это посмотреть стек. Если форт-слова изменяют некоторые переменные или области памяти, следует написать слова для отображения этих областей. Ошибка может происходить при изменении одним словом областей, используемых другим словом. Некоторые слова могут даже изменять код других слов, поэтому для начала надо выяснить, какие именно области изменяются подозрительным словом.

Полезно взять за правило при организации некоторой области памяти сразу писать слово, отображающее эту область.

Форт-система полностью доступна и открыта программисту. Следует использовать эту возможность и контролировать все детали.

2. Накопить статистику

Даже Шерлок Холмс не смог бы раскрыть ни одного преступления, не имея фактов. Пытаться отладить слово, имея в руках только его текст, - очень трудная задача. Она значительно облегчится, если запустить форт-слово с разными входными данными, исключить те или иные слова из определения и т.д. Накопив немного статистики, можно сделать более содержательные выводы.

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

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

3. Проверить граничные условия

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

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

Главное правило отладки состоит в том, чтобы не сидеть, пассивно глядя на экран, а непрерывно действовать, накапливая факты. В конце концов любая ошибка - это всего лишь элементарное машинное действие, которое всегда может быть найдено и понято программистом.

Приобретение опыта

Характерным свойством традиционного программирования является то, что каждую новую программу всегда приходится начинать заново. Конечно, при желании можно организовывать специальные библиотеки, сопровождать их обширной документацией, но все это относится только к универсальным функциям, число которых в программе обычно невелико. При программировании на Форте использование ранее написанных слов ничуть не сложнее использования слов ядра, поэтому с самого начала составления программы нужно ориентироваться на решение впоследствии целого класса задач.

Результатом такого подхода станет постепенное наращивание личного словаря программиста. Собирать программу из написанных ранее и уже отлаженных слов гораздо быстрее и приятнее, чем начинать с пустого места. Следует добиваться того, чтобы создаваемые слова соответствовали терминологии решаемой задачи. Например, если разрабатывается система медицинской диагностики, естественно будет использовать слова "симптом", "температура", "диагноз" и т. д.

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

Порядок создания программ

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

Это миф о том, что программирование становится все более и более рутинным видом деятельности, что в скором времени программисты будут заменены компьютерами, а главное занятие программистов сведется к пользованию уже готовыми модулями. Лет десять назад вымирание профессии программиста ожидалось со дня на день, в настоящее время им отводится еще несколько лет. Несмотря на все эти прогнозы, потребность в программистах непрерывно увеличивается, а профессия программиста является одной из самых высокооплачиваемых.

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

Профессиональная разработка программы состоит из трех этапов:

1. Анализ задачи.

2. Выбор инструмента.

3. Программирование.

1. Анализ задачи

Анализ задачи состоит в определении того, кто, зачем и на какой технике будет эксплуатировать программу. Все это должно быть записано и утверждено заказчиком во избежание (точнее, для уменьшения) дальнейших трений.

2. Выбор инструмента

Главный критерий при выборе инструмента состоит в скорости реализации задачи и в учете ограничений по быстродействию, обьему памяти, использованию ресурсов и т. д.

Предпочтение отдается одному из следующих вариантов:

- добавить функцию к уже готовому пакету;

- использовать специализированный пакет;

- использовать универсальный язык программирования.

Первые два решения накладывают множество ограничений на реализацию, поэтому если предполагается дальнейшее развитие программы, то выбирать следует третий вариант.

3. Программирование

Цель программирования, как известно, состоит в том, чтобы получить программу для решения некоторой задачи. При этом программа должна использовать системные ресурсы и взаимодействовать с пользователем. Соответственно каждая программа складывается из следующих блоков:

1) логика работы;

2) пользовательский интерфейс;

3) управление устройствами;

4) отладочная часть.

Принципы программирования каждой из приведенных составляющих совершенно различны.

1. Логика работы

Логика работы - это поведение модели, заложенной в программу, в зависимости от заданных условий. В простейших программах логика практически отсутствует. Например, программа FORMAT размечает дискету с теми параметрами, которые приводятся в командной строке. На этом вся логика и кончается. В серьезных программах модель может быть сколь угодно сложной, вплоть до мнимой реальности, то есть до некоторого отражения реального мира. Логика работы - это именно то, что нужно пользователю и за что он готов платить большие деньги.

Если логика работы программы примитивна, то программист превращается в простого кодировщика, несмотря на размер и технические характеристики программы. В этом случае можно согласиться с теми заказчиками, которые заявляют о несерьезности непомерных амбиций программистов в части зарплаты и авторских прав на программу.

Однако совсем другое дело, если логика работы приближается к мнимой реальности, то есть к модели реального мира. Таким программированием заниматься может далеко не всякий. Здесь программист должен держать в голове полную модель программы. Причем модель программы должна быть абсолютно строгой и логичной, иначе программа просто не будет работать. В этом труд программиста приближается к труду ученого, который тоже должен выстраивать именно логичные модели.

Очевидно, логике работы программист должен уделять основное внимание.

2. Пользовательский интерфейс

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

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

3. Управление устройствами

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

Кроме того, устройства сами по себе также непрерывно развиваются, и операционные среды просто не поспевают за ними.

Управление устройствами - это работа системного программиста, и заключается она в основном в перелистывании большого количества справочной документации.

4. Отладочная часть

Обычно отладочной части почему-то уделяется очень мало внимания, а это именно та часть программы, которая определяет ее работоспособность.

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

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

Пакеты НПП "Ирбис"

НПП "Ирбис" специализируется на разработке 32-разрядных форт-систем и различных форт-приложений. Вкратце о других пакетах НПП "Ирбис".

Графическая библиотека ПараФорта

Графические приложения являются традиционной сферой применения Форта. Основной особенностью графической библиотеки ПараФорта является поддержка спрайтов и анимации. Спрайт - это графический образ, который может перемещаться по экрану без изменения фона. Именно благодаря спрайтам создается удивительный мир современных игровых программ.

Библиотека содержит следующие функции:

1. Слова для рисования графических примитивов с выводом через BIOS. Это универсальные, независимые от текущего видеорежима процедуры рисования точек, линий, окружностей и закрашивания выпуклых областей.

2. Слова для работы с растровыми шрифтами, которые позволяют выводить тексты различных размеров. С библиотекой поставляются пять шрифтов и редактор шрифтов для корректировки или создания новых шрифтов.

3. Слова для работы со спрайтами. Работа со спрайтами возможна в следующих видеорежимах:

EGA 640*350*16

VGA 320*200*256

SEGA 640*480*16

4. Служебные программы для работы со спрайтами, позволяющими преобразовывать файлы формата PCX в спрайтовый формат, показывать и оживлять спрайты.

Технология работы со спрайтами состоит в следующем. Вначале в любом графическом редакторе рисуются все кадры спрайта. После этого рисунок сохраняется в формате PCX и затем с помощью программы MAKE-SPR переводится в спрайтовый формат. Для проверки можно сразу же прокрутить все кадры спрайта на одном месте с помощью программы ROL-SPRITE. Картинка, на фоне которой будут перемещаться спрайты, готовится аналогично.

Для вывода спрайтов на экран основным словом является слово MOV-SPRITE, которое перемещает спрайт по экрану с автоматической сменой кадров, создавая тем самым эффект анимации.

Форт-Ягуар

Форт-Ягуар - это истинная 32-разрядная система для защищенного режима. Ее интегрированная среда полностью совпадает с ПараФортом, поэтому программист, использующий ПараФорт, может сразу приступать к работе с Ягуаром.

Что дает Ягуар? В первую очередь скорость. Форт является самым быстрым языком программирования, Ягуар в отличие от ПараФорта не тратит время ни на какие преобразования адресов. Кроме того, в Ягуаре реализован прямой шитый код, что дает наивысшую скорость работы адресного интерпретатора.

Вторая особенность Ягуара - это громадная память. Он позволяет линейно использовать всю оперативную память компьютера вплоть до 4 Гбайт.

Третья особенность Ягуара - это работа в MS DOS. Ягуар запускается как обычная программа из DOS, затем входит в защищенный режим и работает в нем. После окончания работы управление возвращается DOS.

И наконец, главная особенность Ягуара в том, что он дает возможность воспользоваться всеми преимуществами защищенного режима, который только начинает осваиваться программистами у нас и на Западе. Сейчас трудно представить себе возможности программ защищенного режима. Ясно только, что это будет нечто принципиально новое и чрезвычайно мощное.

Программы для ПараФорта (за исключением форт-ассемблерных определений) будут выполняться на Ягуаре, и наоборот.

DBF-библиотека

Библиотека может работать как с ПараФортом, так и с Форт-Ягуаром и поддерживает три основные группы функций.

* Работа с DBF-файлом.

* Работа с окном типа READ.

* Работа с окном типа BROWSE.

Работа с DBF-файлом включает операции по созданию и открытию DBF-файла, определению характеристик полей, по работе с десятью рабочими областями, перемещению по записям и т. д.

Окно типа READ с помощью слов SAY и GET позволяет описать форму ввода значений полей отдельной записи. Кроме того, окно типа READ может быть задано с помощью обычного текстового файла, в котором описаны номера и положения используемых полей.

Окно типа BROWSE предназначено для групповой обработки записей и поддерживает создание таблицы записей, которая выводится в окне и может пролистываться по всей базе данных.

Окна обоих типов поддерживают фильтры для выбора записей по условию.

Для редактирования полей окна обоих типов поддерживают концепцию GET-поля, которая дает возможность задавать процедуры при входе в поле (WHEN) и при выходе из него (VALID) и ряд других параметров (ENABLE, SHOW и т. д.).

Формы задаются не в редакторе форм, а в обычном текстовом редакторе, что позволяет редактировать их без всякого программирования, а следовательно, без внесения обязательных ошибок в программу.

Окна, как READ, так и BROWSE, могут быть выведены на печать или в файл в том виде, как они есть, без создания дополнительных форм. Благодаря этому можно изменять форму только в одном месте, а не в огромном числе экранных и отчетных форм.

В настоящее время разрабатывается ряд новых библиотек. Для пользователей пакетов НПП "Ирбис" выпускается бюллетень "SWAP", в котором сообщается о новых версиях, даются ответы на вопросы и приводятся примеры форт-программ.