Развиваемый адаптивный язык РАЯ диалоговой системы программирования ДССП
Московский государственный университет
Факультет вычислительной математики и кибернетики
Н.П.Брусенцов, В.Б.Захаров, И.А.Руднев, С.А.Сидоров, Н.А.Чанышев
Москва, 1987

Содержание

Общее описание языка РАЯ

Назначение и цель разработки языка

РАЯ (Развиваемый Адаптивный Язык) — это базовый язык Диалоговой системы структурированного программирования ДССП. Базовый — значит являющийся основой для всех дальнейших построений, осуществляемых в ДССП путем развития (расширения, умощнения) базового языка и, может быть, адаптации создаваемых таким образом языковых средств к конкретному применению. В противоположность так называемым языкам высокого уровня, РАЯ предоставляет не готовые типы данных и операций, но только элементы и примитивы для эффективного определения требующихся типов. Например, исходными форматами данных являются 8-битный байт, 16-битное слово и 32-битное длинное слово, интерпретируемые в зависимости от выполняемых над ними операций как целые числа, булевские векторы, коды литер, логические значения, указатели данных и процедур. При этом имеется возможность, с одной стороны, манипулировать отдельными битами байтов и слов, а с другой стороны, образовывать составные единицы данных (слова многократной длины, векторы, массивы, строки текста и т.п.), устанавливая для них ту или иную интерпретацию введением соответствующих операций. Так, могут быть введены вещественные числа необходимой длины и диапазона значений, комплексные числа и другие объекты, причем версия языка, ориентированная на данное применение, будет включать объекты и средства, свойственные этому применению и не будет включать того, что к нему не относится,- язык будет адаптирован (приспособлен) к применению. Разработка ДССП преследовала цель создать широкодоступное и эффективное средство программирования микрокомпьютеров, т.е. компьютеров, построенных на основе микропроцессоров. Существенной особенностью архитектуры микропроцессоров является элементарность типов данных и операций, означающая, с одной стороны, универсальность, а с другой — трудоемкость программирования. Вследствие универсальности микропроцессоры и создаваемые на их основе микрокомпьютеры обладают потенциально безграничными возможностями применения. Однако практическая реализация этих возможностей упирается прежде всего в трудоемкость разработки необходимых прикладных программ. К тому же удовлетворительные прикладные программы могут быть созданы лишь при глубоком и тонком знании специфики соответствующих применений, т.е. разрабатывать их должны не просто программисты, а высококвалифицированные в той или иной области специалисты. Поэтому система программирования должна не только в большой степени увеличить продуктивность программистского труда, но и быть настолько простой, чтобы ее могли освоить и эффективно использовать непрофессиональные программисты.

Радикальным решением этой проблемы было бы, по-видимому, существенное упрощение компьютерной архитектуры. Но, к сожалению, архитектура микрокомпьютеров развивается в диаметрально противоположном направлении — по пути все большей сложности и изощренности, так что овладеть в совершенстве языком микрокомпьютерного ассемблера сегодня уже и профессиональному программисту не легко. Языки системного программирования, такие как C или PL/M в известной (хотя и далеко не в достаточной) мере уменьшили трудоемкость разработки программ, но едва ли они могут быть рекомендованы людям, не искушенным в программистском ремесле. Широкодоступный язык должен быть, конечно, более простым и естественным, должен основываться на по возможности обыденных, привычных представлениях о сущности и технике программирования.

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

Процедурное программирование

Программирование на языке РАЯ вполне аналогично таким широко распространенным видам человеческой деятельности как планирование и организация взаимосвязанных действий, работ, процессов или конструирование сложных материальных объектов — машин, агрегатов, сооружений. Подобно конструктору, реализующему свой замысел путем агрегатирования его составных частей (блоков, узлов, деталей), программист синтезирует требующееся сложное действие из предоставленных языком простых действий. Можно также сказать, что программирование (конструирование) заключается в постепенном разложении (декомпозиции) реализуемого объекта на все более мелкие составные части.

В языке РАЯ основным "конструктивом" является процедура — поименованное действие. Язык базируется на ограниченном наборе простейших процедур (примитивов), представленных их собственными именами (обозначениями). Например: + означает "сложить", NEG — "изменить знак", VCTR — "создать вектор". В частности, имеются примитивы : и ; (двоеточие и точка с запятой), позволяющие ввести новую процедуру, например, с именем P, определив ее как последовательность процедур P1, P2, ..., PN в виде

: P P1 P2 ... PN ;

Если процедура P представляет то действие, которое должна осуществлять создаваемая программа, конструирование этой программы средствами языка РАЯ сводится к последовательной детализации процедур P1, P2, ..., PN. Это значит, что каждая из данных процедур должна быть определена последовательностью менее крупных процедур, которые затем определяются последовательностями еще более мелких процедур и т.д., пока не будут получены определения, состоящие только из примитивов.

Такое конструирование программы, отправляющееся от заданной цели и постепенно разукрупняющее используемые процедуры до уровня базовых средств языка, известно как нисходящее программирование. Оно представляет собой основной способ получения на языке РАЯ программ для решения отдельных, вполне определенных задач. Противоположным является восходящее программирование — построение на основе базовых средств языка системы постепенно укрупняемых процедур, ориентированных на некоторую проблемную область. Этим способом осуществляется развитие языка и адаптация его к конкретному применению.

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

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

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

Многократное выполнение процедуры задается при помощи префикса RP. Так, команда RP P вызывает выполнение процедуры P снова и снова, пока не создадутся условия, при которых срабатывает содержащийся в теле этой процедуры EX — выход из цикла, после чего выполняется очередная в линейном порядке команда. Условием выхода из цикла может быть, например, равенство нулю некоторой переменной X, что выражается в виде:

X IF0 EX

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

Многократно выполняемая процедура может быть вложена также в многократно выполняемую процедуру. В таком случае имеет место вложенность циклов. РАЯ допускает многократную вложенность циклов.

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

Процедуры и данные

Все сказанное до сих пор является характеристикой языка РАЯ как средства предписания действий, конструирования произвольных действий из конечного набора примитивных операций. Другую сторону языка составляют средства представления объектов, над которыми выполняются действия,- средства представления и организации данных.

Предельно простым элементом данных является двузначный элемент — бит. Из битов строятся все другие форматы и типы данных. В языке РАЯ в качестве базовых форматов приняты 8-битный байт, 16-битное слово и 32-битное длинное слово. В зависимости от выполняемых над ними операций байты, слова и длинные слова допускают множество интерпретаций, т.е. могут служить основой для различных типов данных. Кроме того, они являются исходными элементами для образования составных форматов и типов.

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

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

Например, если имеется переменная X, объявленная как 32-х битное длинное слово, то непосредственно над ней выполнимы только две операции:

1) засылка ее значения в стек, происходящая автоматически при каждом упоминании имени X,

2) присваивание ей командой ! X значения изымаемого из стека последнего (верхнего) элемента.

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

X X + ! X

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

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

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

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

Другими словами, имена процедур в языке РАЯ употребляются точно так же как знаки операций и представляют собой по существу символы операций с произвольным числом операндов. В соответствии с принципом функционирования стека операции записываются в постфиксной форме, т.е. имя операции помещается после перечисления имен или значений ее операндов. Например, если обозначить операцию получения суммы трех чисел символом ++, то сумма чисел A, 5 и B выразится так:

A 5 B ++

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

ДССП-процессор

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

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

Центральным звеном ДССП-процессора является уже упоминавшийся стек операндов. Собственно в стеке производится обработка и через стек, как правило, осуществляется пересылка данных. Отдельные команды и короткие последовательности команд над стеком можно выполнять, подавая их на вход процессора непосредственно с клавиатуры терминала. При этом ДССП-процессор имитирует работу постфиксного калькулятора. Вводимые с клавиатуры числа и мнемокоды операций разделяются пробелами. Введенный текст отображается в виде строки на экране терминала. Сигналом окончания ввода и командой процессору "Выполнить введенное предписание" служит нажатие клавиши <CR>, обозначаемой также <ENTER>, <RETURN>. Поступающие на вход процессора числа заносятся в стек, а команды — выполняются над стеком. Полученный в вершине стека результат вычисления можно скопировать на экран терминала командой . (точка).

Например, чтобы вычислить выражение (2-5)*3 и вывести на экран полученный результат, вводим:

2 5 — 3 * . <CR>

После нажатия клавиши <CR> процессор выдает результат, так что вся строка будет иметь вид

* 2 5 — 3 * . -90

*

Звездочка в начале строки выдается процессором в качестве сигнала, что он ждет ввода.

В рассмотренном примере процессор воспринимал и обрабатывал вводимые числа как целые десятичные. В действительности при вводе производился перевод этих чисел в двоичный дополнительный код, а при выводе — обратный перевод в десятичную систему. ДССП-процессор допускает также режимы двоичного, восьмеричного и шестнадцатеричного ввода/вывода. Для перехода в желательный режим надо выполнить соответственно одну из команд B2, B8, B10, B16.

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

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

Числами признаются слова, состоящие из допустимых в данной системе счисления цифр и, может быть, содержащие в качестве первой литеры знак минус. В режиме шестнадцатеричного ввода/вывода к допустимым наряду с цифрами относятся также латинские буквы A, B, C, D, E, F. Принятое число переводится в двоичный дополнительный код и засылается в стек операндов в виде 32-битного длинного слова. При этом, если значение числа оказалось вне диапазона представимых значений -2147483648 : 2147483647, то оно заменяется сравнимым по модулю 2**32 значением из этого диапазона.

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

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

Код отдельной литеры засылается в стек в качестве младшего байта вершины при поступлении на вход процессора этой литеры вместе с предпосланным ей знаком #. Например, знакосочетание #L зашлет в стек код буквы L, знакосочетание #5 зашлет код цифры 5. Команда TOB вывода байта на терминал выдает на экран литеру, код которой содержится в младшем байте вершины стека.

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

Например, чтобы создать 16-битную переменную с именем, скажем, TEMP, следует набрать на клавиатуре и подать на вход процессора клавишей <CR> команду

VAR TEMP <CR>

Можно вместе с объявлением присвоить переменной начальное значение, например, 0:

VAR TEMP 0 ! TEMP <CR>

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

Определение процедуры вводится командой : (двоеточие), содержащей имя определяемой процедуры и определяющую цепочку команд с литерой ; (точка с запятой) в качестве символа конца определения. Продемонстрируем определение и использование процедур на примере вычисления факториала натурального числа N по формуле

N!=N*(N-1)*(N-2)*...*2*1, т.е. N-1 умножение.

Процедура FCT для получения искомого результата должна умножить данное число N на последовательно убывающие числа, начиная с N-1 по 1, т.е. всего N-1 раз. На языке РАЯ это программируется с помощью t-кратного выполнения процедуры P: DO P, где P — имя процедуры, t — текущее значение вершины стека, указывающее, сколько раз требуется выполнить процедуру P.

Предположим, что до применения процедуры FCT число N заслано в стек и находится в его вершине. Чтобы сделать процедуру более понятной, представим модифицируемый множитель переменной K:

VAR K <CR>

Определение процедуры FCT введем в виде :

FCT [N] ! K K K [N,N] 1- [N,N-1] DO F [N!] . [N] ; <CR>

В квадратных скобках даны комментарии, отражающие текущее состояние стека операндов. Команда ! K, которой начинается определяемая процедура, присваивает взятое из стека значение числа N переменной K. Затем K дважды засылается в стек и вычитанием 1 в вершине стека формируется число выполнений повторяемой процедуры F, равное N-1. Далее следует команда DO F, предписывающая цикл, по завершении которого в вершине стека будет содержаться искомое значение факториала — N!. Команда . (точка) выдает копию этого значения на экран терминала. Осталось определить процедуру F, которая модифицирует значение K вычитанием 1 и умножает на K содержащийся в стеке частичный результат вычисления R. :

F [R] K 1- [R,K-1] ! K [R] K [R,K] * [R*K] ; <CR>

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

Проверенные и откорректированные (если в процессе проверки были выявлены ошибки) процедуры подвергаются тестированию применением их к отдельным значениям числа N. Поскольку процедура F вложена в FCT, то тестирование ее осуществляется автоматически в процессе тестирования последней. Следует иметь в виду, что значения результата не должны превосходить максимального положительного числа, представимого в дополнительном коде 32-битным длинным словом: 2147483647, т.е. FCT выдает правильные результаты только при N=1, ..., 13.

Использование FCT не отличается от использования собственных команд процессора: для получения результата надо задать значение операнда и ввести имя процедуры:

5 FCT <CR> 120

7 FCT <CR> 5040

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

: FCTA [N] C [N,N] 1- C [N,N-1,N-1] DO FA [N!,0] D . [N!] ;

: FA [R,K] C E3 [K,K,R] * E2 [K*R,K] 1- [K*R,K-1] ;

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

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

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

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

Для того, чтобы удовлетворить подобным требованиям, словарь реализован в виде совокупности подсловарей, над которой определены операции, позволяющие создавать и уничтожать подсловари и их части, удалять имена, закрывать и открывать доступ к тем или иным подсловарям. Каждый подсловарь обладает именем, которое используется в относящихся к нему командах. Имена подсловарей должны начинаться литерой $, например: $PRIME, $EDIT, $FLOAT, $TEXTPROC, $ГРАФИКА.

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

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

PROGRAM $<имя программы>

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

FORGET $<имя> GROW $<имя>

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

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

По окончании ввода и редактирования текста редактор отключается нажатием клавиши E одновременно с (а точнее, с предварительно нажатой) клавишей <CTRL>, и система переходит в основной режим команд ДССП. Аналогичное действие вызывает просто нажатие клавиши <ESC>. В этом режиме содержимое буфера редактора можно выдать на вход процессора командой PF (PerForm — выполнить). При этом будут выполнены все содержащиеся в тексте команды, в частности, команда PROGRAM $<имя> удалит занесенные в подсловарь $<имя> с момента последнего выполнения этой команды имена данных и процедур, а также соответствующие тела, вновь открыв этот подсловарь для наращивания. Команды объявления данных и определения процедур занесут в него вводимые ими имена вместе с указателями на обозначенные этими именами данные и тела процедур, скомпилированные в соответствии с определениями.

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

После проверки и тестирования программы ее исходный текст можно скопировать из буфера редактора на диск командой OE f, где f — имя файла, в форме которого программа будет записана на диске. В дальнейшем содержимое файла можно будет загружать на вход процессора командой LOAD f, а также копировать в буфер редактора в качестве добавления к имеющемуся в нем тексту командой IE f. По умолчанию файлы имеют расширение .DSP. Буфер можно предварительно очистить командой KE. Имеется возможность также распечатки содержимого буфера командой LPE.

После загрузки готовой для выполнения программы имеется возможность произвести чистку созданного для нее подсловаря $<имя> командой CLEAR $<имя>. Выполняя эту команду, процессор удаляет из названного подсловаря незафиксированные имена, т.е. все имена, за исключением тех, перед определениями которых имеется фиксирующий префикс :: (два двоеточия). При этом удаляются только сами имена (словарные входы), а сопоставленные им тела процедур и данные сохраняются и доступны в процессе выполнения программы по установленным во время компиляции внутренним ссылкам, однако извне они более недоступны. Чтобы восстановить возможность доступа извне, например, при необходимости скомпилировать какое-нибудь дополнение или изменение, надо заново загрузить исходный текст программы.

Имена можно сделать недоступными извне, не удаляя их из словаря, командой SHUT $<имя>, закрывающей доступ ко всем словам названного в ней подсловаря. Открытие подсловаря для использования его слов осуществляется командой USE $<имя>. Имеется также команда ONLY $<имя>, закрывающая все подсловари, кроме названного, и команда CANCEL, отменяющая это ограничение. Перечисленные команды позволяют управлять использованием словаря во время компиляции и ограничивать необходимым минимумом совокупность имен, доступных пользователю программы.

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

Несколько слов о вводе и выводе данных. Как уже было сказано, процессор пытается интерперетировать не найденное в словаре слово выполняемой программы как число и в случае удачи заносит в стек двоичный эквивалент этого числа. Ввод числа в стек может быть осуществлен командой TIN, требующей набора вводимого числа на клавиатуре. Имеются также команды, вызывающие засылку в стек вводимой с клавиатуры литеры: TIB — с отображением, TRB — без отображения этой литеры на экран. При этом код литеры представлен младшим байтом засылаемого в стек 32-битного слова, старшие 3 байта которого равны нулю.

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

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

<адрес вектора> LINK <имя процедуры>

Командное прерывание представляет собой поименованную операцию вызова процедуры реагирования. Имя этой операции определяется командой TRAP, сопоставляющей ему процедуру так называемого конечного реагирования, выполняемую в том случае, если конечная реакция не заменена с помощью команды ON или EON иной процедурой реагирования. Все три команды имеют одинаковый формат:

TRAP <имя вызова> <процедура реагирования>

ON <имя вызова> <процедура реагирования>

EON <имя вызова> <процедура реагирования>

Процедура, сопоставленная имени вызова командой EON, выполняется с предварительным выходом из тела процедуры, содержащей команду EON, и с тем значением указателя стека операндов, которое имело место в момент выполнения EON.

Синтаксис языка РАЯ

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

Все объекты языка РАЯ строятся из литер и представляют собой линейные цепочки литер конечной длины, называемые словами. Разделителем слов, следующих друг за другом служит непечатаемая литера (пробел). Цепочка пробелов равносильна одному пробелу. Кроме того, функцию разделителя слов выплняет команда "Перейти на начало очередной строки", обозначаемая на клавиатурах устройств ввода символом <CR> или <ENTER> и на ряду с литерами имеющая внутреннее представление кодом-байтом. Таким образом, в начале и в конце строки разделительные пробелы не нужны.

Примеры слов: CLEAR NOP СТЕК2 & 1+ -366 X Проба.

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

Когда опознаваемого слова в словаре нет, процессор пытается отнести его к одному из следующих случаев:

  • числовой литерал, т.е. последовательность цифр, возможно начинающаяся знаком "минус", например: 0, 4096, -25;

  • литерал литеры: слово, начинающееся литерой #, которая вызывает получение процессором в качестве данного кода непосредственно следующей за ним литеры, например: #A — литерал заглавной латинской буквы A, #5 — литерал цифры 5, # — литерал пробела, ## — литерал литеры #;

  • текстовый литерал: произвольный текст, заключенный в двойные кавычки и обособленный разделителями слов, например: "Текст", "Входной файл N3";

  • команда выдачи на дисплей текстового сообщения: текст выдаваемого сообщения, ограниченный слева знакосочетанием точка-двойная кавычка и двойной кавычки справа и обособленный разделителями слов, например: ."Стек пуст";

  • комментарий: произвольный текст, заключенный в квадратные скобки и обособленный разделителями, например: [SORT — процедура, упорядочивающая массив].

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

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

Последовательность слов на входе процессора интерпретируется как последовательность выполняемых процессором команд. При этом различаются три рода слов:

1) выполняемые независимо, т.е. представляющие собой однословные команды (монослова);

2) выполняемые в совокупности с одним или несколькими последующими словами, т.е. являющиеся начальными словами (префиксами) двух-, трех- или многословных команд;

3) предваряющие команду в качестве уточнения или указания на специальный режим выполнения (предпрефиксы).

К монословам относятся литералы, имена данных, большинство операций ввода/вывода, тестирования и преобразования данных в стеке, а также процедуры, определяемые пользователем. Например: 1987 — числовой литерал, #5 — литерал цифры 5, "Перечень схем" — текстовый литерал, ДЛИНА — имя переменной, TOB, NEG, +, &, <, = — имена (обозначения) операций, SORT, CONVERT, ЧИСТКА, СНЯТЬ — имена процедур пользователя.

Префиксы присущи командам описания данных и определения процедур, а также манипулирования поименованными данными, условного и многократного выполнения процедур, управления словарем. Примеры команд с префиксами:

VAR СУММА — создать переменную СУММА,

: ODD [x] 1 & [1/0] ; — создать процедуру ODD, замещающую нечетное число на 1, четное на 0,

!0 X — присвоить переменной X значение 0,

BR+ P1 P2 — если взятое из стека значение его вершины положительно, то выполнить P1, иначе выполнить P2,

RP CHECK — выполнять процедуру CHECK снова и снова,

USE $REAL — открыть для использования подсловарь $REAL.

Как правило, конкретный префикс требует после себя определенное число слов. Так, в приведенных только что примерах префиксы VAR, !0 и USE требуют по одному слову, а префикс BR+ требует два слова. Однако префикс : (двоеточие) позволяет образовать команду произвольной длины, начиная с трех слов. Концом команды служит слово ; (точка с запятой). Произвольная длина свойственна также команде — описателю констант CNST A1 ... AJ ; и команде множественного выбора процедуры BR A1 P1 ... AJ PJ ELSE PN.

Предпрефиксы представляют собой особые слова, добавление которых к команде спереди модифицирует ее содержание или определяет специальный режим выполнения. Например, команда VAR X без предпрефикса является предписанием создать 16-битную переменную X. Если присоединить к ней предпрефикс BYTE, то получим команду BYTE VAR X, предписывающую создать 8-битную переменную (байт) с именем X. Если же использовать предпрефикс LONG, то получим LONG VAR X — предписание создать 32-битную переменную с именем X.

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

:: VAR X

:: BYTE CNST LITCODE # #0 #A ;

:: : MOD [a,b] / [цел(a,b),ост(a,b)] E2 D [ост(a,b)] ;

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

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

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

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

Общее описание языка РАЯ, составившее содержание данной главы, было посвящено характеристике строя этого языка и базового (исходного) набора его команд, являющегося набором встроенных команд (примитивов) ДССП-процессора. Дальнейшее развитие языка и соответствующее наращивание возможностей процессора производится путем введения новых процедур, команд, форматов и типов данных, конструироемых с использованием базовых средств. Как правило, такое развитие носит проблемно-ориентированный характер и осуществляется в виде пакетов процедур, загружаемых на вход процессора в дополнение к базовой системе.

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

Описание операций и команд

Операции, выполняемые над стеком

Стек операндов является одним из главных элементов архитектуры ДССП-процессора. Большинство команд процессора используют стек, потребляя из него необходимые им операнды и засылая в него результаты. Интерпретация данных, находящихся в стеке, зависит от сути решаемой задачи, т. е. в конечном счете возложена на программиста. Вследствие того, что значение, попавшее в стек, фактически теряет свое имя, по тексту программы трудно определить, к каким операндам применяется та или иная операция, каковы ее результаты. Поэтому для явного указания операндов и результатов процедур в языке РАЯ используются комментарии. При этом не требуется (да и не всегда возможно) описывать все содержимое стека. Комментировать же верхнюю часть стека, затрагиваемую выполняемой над ним процедурой, совершенно необходимо, так как без этого теряется наглядность программы, затрудняется ее проверка.

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

Приведем пример комментария, отражающего состояние стека операндов:

[нач.адр.,N+1,1/0]

В точке программы, где находится данный комментарий, стек операндов должен содержать как минимум три позиции, причем в вершине может находится 1 или 0, в подвершине — числовое значение, равное N+1, а под ним — некоторое число, интерпретируемое как начальный адрес.

Для удобства указания требуемой позиции стека мы будем использовать понятие глубины залегания. Будем считать, что вершина стека лежит на глубине 1, подвершина — на глубине 2 и т.д. В частности, значение, обозначенное в примере как "нач.адр." лежит на глубине 3.

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

28 -5 11 и нажать клавишу <CR> (возврат каретки). Процессор распознает введенные числа и поочереди зашлет их в стек, так что в вершине окажется 11. Чтобы убедиться в этом, достаточно распечатать значение вершины стека на экране дисплея. Для этого служит команда ДССП с именем . (точка). Набрав на клавиатуре литеру "точка" и нажав <CR>, получим на экране ответ: 11, что соответствует последнему засланному в стек значению. Повторное выполнение "точки" приводит к тому же результату — эта команда лишь визуализирует вершину, не изменяя состояния стека.

Для того, чтобы выдать на экран все содержимое стека, в ДССП имеется команда .. (две точки). Выполнив ее, получим на экране строку:

[ 28 -5 11]

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

Для представления одной позиции стека в памяти машины используется 32-битное слово (4 байта), числа представляются в дополнительном коде. Соответственно ДССП-процессор может правильно воспринять только целые числа, лежащие в диапазоне от -2147483648 до 2147483647. Если введенное число не представимо 32 битами (с учетом знака), то происходит отбрасывание старших не умещающихся битов.

В рассмотренных примерах предполагалось, что ДССП-процессор находится в режиме десятичного ввода/вывода чисел. Для установки этого режима в языке РАЯ имеется команда B10.

Во многих задачах требуется интерпретировать обрабатываемые данные не как числа, а как двоичные коды, т. е. 32-компонентные векторы битов. В ДССП есть возможность работать с кодами, представленными в двоичной, восьмеричной или шестнадцатеричной системе счисления. Для установки нужного режима достаточно выполнить одну из трех команд: B2, B8 или B16, после чего процессор будет воспринимать и распечатывать все вводимые коды в указанной системе счисления.

Данной возможностью можно пользоваться для перевода десятичных чисел в системы счисления с основаниями 2, 8 и 16. Например, для перевода числа 29 нужно ввести и выполнить следующую строку:

B10 29 B2 . B8 . B16 . В результате процессор выдаст на экран ряд чисел: 00000000000000000000000000011101 00000000035 0000001D которые являются представлениями десятичного числа 29 в трех указанных системах счисления. Заметим, что коды печатаются в их машинном представлении, т. е. с ведущими нулями и без знаков "+", "-". При выполнении строки B10 -2 B8 . будет выдано число 37777777776, которое является восьмеричным представлением -2 в дополнительном коде.

При работе с шестнадцатеричными кодами могут возникать коллизии между числовыми литералами и именами команд ДССП-процессора. Например, слово B8 в режиме шестнадцатеричного ввода/ вывода может быть истолковано как команда установки восьмеричного режима и как шестнадцатеричная константа. Для избежания неопределенности следует начинать числовые литералы с незначащего нуля, например 0B8.

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

Рассмотрим команды процессора, реализующие четыре арифметических операции: сложение, вычитание, умножение и деление целых чисел. Для их изображения в языке РАЯ используются слова: +, -, * и / соответственно. Чтобы получить в стеке сумму двух чисел, например 123 и 45, нужно заслать эти числа в стек и выполнить команду +. Для этого достаточно ввести с клавиатуры следующую строку (предполагается, что установлен режим десятичного ввода/вывода):

123 45 + <CR>

Если теперь выдать на экран содержимое стека (с помощью команды ..), то станет виден результат сложения:

[ 168]

Аналогичным образом работает коммутативная операция умножения.

При выполнении некоммутативных операций вычитания и деления в качестве уменьшаемого (делимого) берется подвершина стека, а вычитаемым (делителем) служит вершина. Например, для вычисления разности 151-68 нужно выполнить строку:

151 68 — <CR>

Программа выполнения арифметического действия в языке РАЯ характеризуется тем, что операция находится после соответствующих ей операндов. Такая запись арифметических выражений носит название постфиксной (или польской инверсной) записи и широко используется в стековых микрокалькуляторах. Пусть, например, нам необходимо вычислить значение арифметического выражения ((127+81)*15-(31+117)*21)*3

В постфиксной записи это выражение будет выглядеть так:

127 81 + 15 * 31 117 + 21 * — 3 *

Данная строка (в которой слова отделены друг от друга пробелами) является готовой программой для вычисления нашего выражения ДССП-процессором.

Команда деления / отличается от других арифметических операций тем, что ее результатом являются два значения — частное и остаток. Частное оказывается в подвершине стека, а остаток — в вершине. Частное отрицательно в том случае, если делимое и делитель разных знаков. Остаток всегда имеет знак делимого. Приведем несколько примеров использования команды деления.

-125 7 / [-17,-6] / [2,-5] / [0,2] / [0,0]

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

При программировании часто приходится увеличивать или уменьшать значение какой-либо величины на 1 и на 2. В язык РАЯ введены специальные команды, выполняющие указанные действия над вершиной стека. Они обозначены словами: 1+, 1-, 2+, 2-. Выполнение этих команд эквивалентно засылке в стек нужной константы (1 или 2) с последующим выполнением требуемого арифметического действия (+ или -). Например, 2+ эквивалентно паре слов 2 + . Введение в язык данных команд вызвано соображениями эффективности.

Также для повышения эффективности в базовом языке ДССПпроцессора имеются команды T0 и T1, заменяющие значение вершины стека на 0 и 1 соответственно, независимо от того, какое значение было в вершине до указанной команды. Примеры:

-5 [-5] T1 [1] T0 [0]

Для работы с числовыми данными предназначены также команды NEG, ABS и SGN. Команда NEG изменяет знак вершины стека на противоположный, ABS заменяет значение вершины стека его модулем, SGN — потребляет числовое значение из вершины стека и помещает на его место знак извлеченного числа: -1 — если число отрицательно, 1 — если положительно, 0 — если равно нулю. Например:

5 NEG [-5] ABS [5] SGN [1]

Имеющиеся в базовом языке команды MIN и MAX позволяют находить минимум и максимум из двух целых чисел. Операндами для этих команд служат два числа, находящиеся в вершине и подвершине стека. Команда MIN оставляет в стеке минимальное из чиселпараметров, MAX — максимальное из них. Например:

-5 0 15 MIN [-5,0] MAX [0]

Для нахождения минимума (максимума) из трех чисел, находящихся в стеке, достаточно дважды применить команду MIN (MAX):

[127,-2,13] MIN MIN [-2]

Команда SEG прверки попадания числа, содержащегося в вершине стека в заданный диапазон от a до b (включая границы) в качестве результата оставляет в стеке признак: 1, если число попало в диапазон, и 0, если нет:

[x,a,b] SEG [признак] например:

-2 0 10 SEG [0]

12 -5 24 SEG [1]

Помимо команд, ориентированных на работу с числовыми данными, набор команд ДССП-процессора включает ряд операций, предназначенных для преобразования 32-битных кодов. Эти операции трактуют элемент стека как 32-компонентный вектор битов, компоненты которого пронумерованы справа налево таким образом, что самый левый бит имеет номер 31, а самый правый номер 0. Убывающая нумерация компонент повторяет принятую для многих микропроцессоров нумерацию битов машинного слова.

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

  • побитная инверсия вершины стека INV, изменяющая значение каждого бита вершины, т. е. заменяющая 0 на 1, а 1 на 0;

  • побитная конъюнкция вершины и подвершины стека &, устанавливающая в i-м бите результата, i=31,30,...,0, значение 1, если i-е биты обоих операндов равны 1, а в прочих случаях полагающая i-й бит равным 0;

  • побитная дизъюнкция вершины и подвершины стека &0, устанавливающая в i-м бите результата, i=31,30,...,0, значение 0, если i-е биты обоих операндов равны 0, а в прочих случаях полагающая i-й бит равным 1;

  • побитное сложение (неэквивалентность) '+' вершины и подвершины, устанавливающее в i-м бите результата значение 0, если i-е биты обоих операндов имеют одинаковые значения, и полагающее i-й бит результата равным 1, если значения i-х битов операндов различны.

В приведенных далее примерах предполагается, что установлен режим восьмеричного ввода/вывода (выполнена команда B8).

525 INV [37777777252] 722 & [202] 136 &0 [336] 325 '+' [13]

Побитную конъюнкцию часто используют для обнуления (очистки) разрядов слова. Для этого выполняют конъюнкцию исходного слова с маской, содержащей нули в тех разрядах, которые нужно очистить и единицы — в остальных разрядах. Например, если нужно обнулить биты с 3-го по 5-й в некотором слове X, нужно произвести его побитную конъюнкцию с маской 37777777707. Для X=235 получим:

[235] 37777777707 & [205]

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

[205] 20 &0 [225]

К операциям манипулирования битами относятся также команды логического сдвига:

  • сдвиг влево SHL — каждый бит вершины стека, начиная с 31-го принимает значение следующего за ним в порядке убывания номеров, а последний, нулевой бит принимает значение 0;

  • сдвиг вправо SHR — каждый бит вершины стека, начиная с 0-го принимает значение следующего за ним в порядке возрастания номеров, а 31-й бит принимает значение 0;

  • сдвиг по вершине SHT — верхний элемент изымается из стека и рассматривается как целое число N, указывающее сколько сдвигов и в каком направлении надо произвести в вершине стека: при N>0 производится сдвиг влево, при N<0 — вправо.

Примеры:

B8 125 SHR [52] SHL [124] -2 SHT [25]

Операциями сдвига влево можно пользоваться для умножения чисел на 2 в степени N, где N — натуральное число, определяющее количество сдвигов. Например, умножение числа -5 на 8 можно выполнить, сдвинув это число на 3 разряда влево:

B10 -5 3 SHT [-40]

При этом следует учитывать возможность возникновения переполнения.

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

5 SHR [2]

тогда как

-1 SHR [2147483647]

Команды циклического сдвига вершины стека на 1 бит вправо ROR и влево ROL похожи на команды логического сдвига, за исключением того, что выдвигаемый крайний бит не исчезает, а вдвигается на освободившееся место с противоположного конца 32-битного длинного слова. Например (числа шестнадцатеричные):

12B ROR [80000095]

Для обработки двоичных кодов предназначены также команды ДССП-процессора SWB и SWW. Функция SWB заключается в перестановке байтов младшей половины вершины стека, а функция SWW в перестановке половинок вершины стека. Проиллюстрируем работу этих команд, используя режим шестнадцатеричного ввода/вывода (в этом режиме каждый байт изображается двумя 16-ричными цифрами):

B16 0ABCD [ABCD] SWB [CDAB] SWB [ABCD]

0ABCDEF12 [ABCDEF12] SWW [EF12ABCD] SWB [ABCDEF12]

Важную роль в языке РАЯ играют команды манипулирования стеком. Они не изменяют значения данных, находящихся в стеке, а лишь меняют их расположение, облегчая доступ к операндам, находящимся в глубине стека.

Имеется три команды удаления элементов стека: D, DD, DS (Drop — выбросить). Команда D удаляет из стека один (верхний) элемент, DD — два элемента, например:

[1,2,3,4] D [1,2,3] DD [1] D [] DS удаляет из стека все элементы (очищает стек):

[1,2,3] DS []

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

[5] C C [5,5,5]

Покажем применение этой команды на примере вычисления многочлена p(x)=3*x**2+4*x-5 по схеме Горнера: p(x)=(3*x+4)*x-5. Считаем, что значение x содержится в вершине стека.

[x] C 3 * [x,3x] 4 + * [x(3x+4)] 5 — [p(x)]

Наряду с командой копирования вершины стека в языке РАЯ имеются также команды C2, C3, C4, копирующие элементы, находящиеся на глубине 2, 3, 4. Их работу можно пояснить следующими примерами:

[1,2,3] C2 [1,2,3,2] C4 [1,2,3,2,1]

[1,2,3,2,1] C3 [1,2,3,2,1,3]

Имеется также команда CT копирования элемента, находящегося на глубине, которая указана в вершине стека. Выполняя CT, процессор изымает из стека верхний элемент, использует его значение как указатель глубины залегания копируемого элемента и засылает копию последнего в стек. Так, копирование элемента, находящегося на глубине 5, задается парой команд 5 CT, выполняя которые, процессор зашлет в стек число 5, а затем выполнит команду CT. Выполнение CT с параметрами 1, 2, 3, 4 эквивалентно соответственно командам C, C2, C3, C4.

Команды обмена E2, E3, E4 (Exchange — обменять) производят перестановку первого (верхнего) элемента стека соответственно со 2-м, 3-м, 4-м, т. е. с находящимся на глубине 2, 3, 4 элементом. Например:

[a,b,c] E3 [c,b,a] E2 [c,a,b]

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

[a,b,c,d,e] 5 ET [e,b,c,d,a]

Команда ET с параметрами 2, 3, 4 эквивалентна командам Е2, Е3, Е4.

Для иллюстрации использования команд копирования и обмена рассмотрим учебную задачу. В стеке заданы три числа [a,b,c]. Требуется получить в стеке: [a+b,b+c,a+c]. Можно предложить следующую программу, смысл которой понятен из комментариев.

[a,b,c] C3 C3 C3 [a,b,c,a,b,c] + [a,b,c,a,b+c]

E4 [a,b+c,c,a,b] + [a,b+c,c,a+b] E4

[a+b,b+c,c,a] + [a+b,b+c,a+c]

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

В программах часто приходится сравнивать между собой числовые величины и выполнять различные процедуры в зависимости от результатов сравнения. В языке РАЯ имеются команды сравнения <, =, >. Они определены над числами и в качестве результата выдают числовые значения 0 и 1. Так, команда < потребляет из стека два элемента и засылает в стек число 1, если значение нижнего элемента оказалось меньше значения верхнего, а в противном случае засылает 0. Например, в результате выполнения последовательности 5 -20 < в стек будет заслан 0. Команда = засылает 1 в случае равенства потребленных ею элементов. Команда > засылает 1, когда нижний элемент больше верхнего. Для программирования нестрогих сравнений (меньше или равно, больше или равно) используется команда NOT, которая заменяет значение вершины стека, не равное нулю, нулем, а равное нулю — единицей. Например, вычисление логического выражения x>=5, где x — некоторое число, находящееся в вершине стека, можно задать следующим образом:

[x] 5 < NOT [1/0]

Дальнейшее расширение возможностей программирования условий обеспечивается применением, наряду с командами сравнения, логических операций конъюнкции & (логическое И) и дизъюнкции &0 (логическое ИЛИ). Пусть, например, требуется получить в стеке 1, если находящееся в вершине число x принадлежит полусегменту [5,10) или равно 2, и нуль — в противном случае. Это можно реализовать следующей последовательностью команд:

[x] C 5 < NOT [x,x>=5] C2 10 < [x,x>=5,x<10]

& [x,(x>=5)&(x<10)] E2 2 = &0 [1/0]

Средства управления программой в зависимости от результатов сравнения будут рассмотрены в дальнейшем.

Определение процедур

В качестве основного приема программирования ДССП предоставляет пользователю возможность определять поименованные последовательности операций, называемые процедурами. Пусть требуется, например, вычислить значения квадратного трехчлена 3*x**2-4*x+9 для задаваемых значений x. В таком случае следует определить процедуру, реализующую формулу трехчлена и выдачу на терминал результата, а затем применять эту процедуру к конкретным значениям x. Искомая процедура, назовем ее PX, определяется следующим образом: : PX [x] C 3 * 4 — * 9 + [3x**2-4x+9] . D [] ; Двоеточие означает операцию "определить процедуру", причем имя процедуры следует за двоеточием после разделительного пробела. Определяющая последовательность команд (тело процедуры) располагается вслед за именем процедуры и заканчивается точкой с запятой. Короче, процедура определяется в форме:

: <имя процедуры> <тело процедуры> ;

В языке РАЯ требуется комментировать состояние стека операндов в начале и в конце процедуры. В теле процедуры комментарии расставляются по усмотрению программиста в трудных для понимания местах.

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

После того как определение процедуры введено и нажатием клавиши <CR> процессору сообщено о конце ввода, на экране терминала появляется звездочка, сигнализирующая о выполнении команды "определить процедуру" и готовности процессора продолжить диалог. Теперь можно применить процедуру PX к задаваемым с клавиатуры значениям x, например к 2, 3, 4 (выдаваемое процессором подчеркнуто):

* 2 PX <CR> 13

~~ ~~~~~~

* 3 PX <CR> 24

~~ ~~~~~~

* 4 PX <CR> 41

~~ ~~~~~~

Определим более общую процедуру вычисления трехчлена вида a2*x**2+a1*x+a0, позволяющую задавать значения как x, так и a0, a1, a2. Назовем ее PXA:

: PXA [a0,a1,a2,x] C E4 E3 [a0,x,a1,x,a2] * + [a0,x,a2*x+a1] * + [a2*x*x+a1*x+a0] ;

При использовании PXA в стеке должны находиться в требуемой последовательности значения a0, a1, a2, x. Например: a0=1, a1=2, a2=-3, x=4

* 1 2 -3 4 PXA . D <CR> -39

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

: P [a0,a1,a2,x] PXA [a2*x*x+a1*x+a0] . D [] ;

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

: TIME [t] 1- [t-1] TIME ;

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

Счетчик TIME в принципе не может остановиться: процедура вычитания единицы будет выполняться снова и снова, пока работает процессор. Но в ДССП имеются средства, позволяющие управлять ходом процесса в зависимости от получаемых результатов — операции управления ходом программы.

Выполнение по условию и повторения

Программа, представляющая собой последовательность команд, выполняемых в порядке их расположения друг за другом в ее записи, называется линейной. Чтобы сделать программу легко обозримой (читабельной) и понятной, ее разбивают на обладающие определенным смыслом поименованные части — процедуры, определяемые каждая своей последовательностью процедур, которые в свою очередь определены последовательностями более мелких процедур и т.д. до процедур, определяемых непосредственно последовательностями команд ДССП. Такая программа, записываемая в виде иерархии определений процедур, называется структурированной. Метод построения структурированной программы, заключающийся в постепенном разложении решаемой задачи на все более мелкие подзадачи, называется структурированным программированием.

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

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

BRS N Z P

вызовет удаление из стека одного элемента и выполнение процедуры N, если удаленное значение отрицательно, выполнение процедуры P, если положительно, и выполнение процедуры Z, если равно нулю.

Примером использования команды BRS служит следующее определение процедуры SGN

: SGN [X] BRS -1 0 1 [SGN(X)] ;

Эта процедура заменяет содержащуюся в вершине стека величину X числом -1, если X<0, числом 0, если X=0, и числом 1, если X>0. Процедура SGN имеется в ДССП в качестве базовой операции процессора.

Команда BRS, наряду с выбором одной процедуры из трех данных, обеспечивает возможность реализации двузначных операторов вида IF-THEN и IF-THEN-ELSE . Например, предложению if x>0 then P1 else P0 соответствует команда BRS P0 P0 P1, а предложению if x<>0 then P — команда BRS P NOP P, в котором NOP — имя пустой операции. Но в ДССП имеется более эффективная реализация двузначных условий — команды IF-, IF0, IF+, BR-, BR0, ВR+.

Команды группы IF соответствуют оператору IF-THEN . Например, команда IF- P предписывает изъять из стека верхний элемент и тестировать его знак, причем если этот элемент имеет знак минус, то выполнить процедуру P. Команды IF0 P и IF+ P предписывают выполнить процедуру P соответственно в случае, когда изъятый элемент равен нулю, и в случае, когда его значение положительно.

В качестве примера, иллюстрирующего применение команд группы IF, приведем определение команды базового языка ABS, вычисляющей модуль вершины стека.

: ABS [X] C [X,X] IF- NEG [|X|] ;

Команды BR-, BR0 и BR+ соответствуют оператору IF-THEN-ELSE, предписывая выбирать одну из двух называемых вслед за ними процедур. Если знак изъятого из стека элемента совпадает с имеющимся в обозначении команды, то выполняется процедура, названная первой, а если не совпадает, то выполняется вторая процедура. Например, команда BR0 P0 P1 предписывает выполнить процедуру P0 в случае, когда изъятый из стека элемент равен нулю, а если это условие не удовлетворено, то выполнить процедуру P1.

Рассмотренные команды позволяют экономно запрограммировать выполнение процедуры в зависимости от данных условий. Наиболее часто встречающиеся условия вида x<0, x=0, x>0 прямо реализуются командами группы IF. Условия x<=0, x<>0, x>=0 программируются с помощью команд BR-, BR0, BR+ путем употребления в качестве первой процедуры пустой операции NOP. Например, предложению if x<=0 then P соответствует команда BR+ NOP P. Примером использования команд группы BR может служить следующая реализация команды базового языка NOT, заменяющей нулевое значение вершины стека единицей, а ненулевое — нулем.

: NOT [x] BR0 1 0 [0/1] ;

Ветвление программы часто производится после команд сравнения (<, =, >), вырабатывающих логическое значение 1 или 0 в зависимости от результата сравнения двух чисел. Команду базового языка MAX, например, можно запрограммировать следующим образом:

: MAX [x,y] C2 C2 < [x,y,1/0] IF+ E2 D [max(x,y)] ;

В группу команд ветвления входит также команда выбора BR, записываемая в виде:

BR A1 P1 A2 P2 ... AK PK ... AN PN ELSE P0

Реализуя эту команду, процессор сначала выполняет процедурууказатель A1 и сравнивает занесенное ею в стек значение с находящимся под ним значением прежней вершины стека. Если значения совпали, то из стека удаляются два верхних элемента и выполняется сопоставленная указателю A1 процедура P1, после чего производится переход к команде, следующей за командой BR (т.е. в приведенной выше записи следующей по тексту программы за словом P0). Если же сравниваемые значения не совпали, то из стека удаляется один верхний элемент (т.е. результат A1) и те же действия производятся с парой A2 P2, затем, если совпадения не получилось, то с парой A3 P3 и т.д. по AN PN включительно. В случае, когда ни одна из попыток не дала совпадения, выполняется названная после слова ELSE процедура P0. Обычно в роли процедур-указателей выступают числовые константы, например:

[x] C [x,x] BR 5 NEG -3 ABS 0 NOT ELSE T0 [y]

В результате выполнения данной строки в вершине стека будет получено значение y=-5, если x=5; y=3, если x=-3; y=1, если x=0 и y=0 во всех остальных случаях.

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

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

: TIME [t] 1- [t-1] C IF+ TIME [0] ;

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

7 TIME <ВК>

Поскольку IF+ в определении TIME, как и всякая условная операция, изымает из стека тестируемый элемент, а элемент этот необходим для последующих операций, то его приходится дублировать, помещая перед IF+ операцию C (Copy).

Рекурсия не является основным средством многократного выполнения процедуры. Для программирования циклов в языке РАЯ имеются команды RP (Repeat — повторять) и DO (Do — делать, выполнять).

Команда RP W предписывает выполнять процедуру W снова и снова неограниченное число раз. Чтобы повторения могли прекратиться, тело процедуры W должно содержать операцию EX (Exit — выйти), выполняемую при заданном условии. Операция EX осуществляет переход к выполнению процедуры, которая следует по тексту программы за повторяемой процедурой, содержащей эту операцию EX. Так, счетчик, реализованный выше в виде рекурсивной процедуры TIME, можно запрограммировать как повторение процедуры W, которая определена так:

: W [t] 1- [t-1] C IF0 EX [t-1] ;

Чтобы счетчик сработал 25 раз, надо выполнить строку

25 RP W

Наряду с операцией EX, которая употребляется в командах выполнения по условию, имеются операции условного выхода EX-, EX0, EX+, производящие тот же эффект, что и команды IF- EX, IF0 EX, IF+ EX, т. е. потребляющие из стека верхний элемент, тестирующие его знак и выполняющие выход, если знак совпадает с указанным в обозначении операции. Операции EX, EX-, EX0, EX+ могут употребляться не обязательно в теле самой повторяющейся процедуры (в нашем случае W), но и в процедурах, к которым она обращается.

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

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

: НОД [M,N] RP ШАГ [нод(M,N),нод(M,N)] D [нод(M,N)] ;

Теперь необходимо запрограммировать один шаг итерационного процесса, т.е. определить процедуру ШАГ. Параметрами для нее служат два числа, находящиеся в стеке. Нужно сравнить эти числа и выйти из цикла, если они равны, в противном случае — вычесть из большего меньшее. Это можно сделать, например, так :

: ШАГ [M,N] C2 C2 — [M,N,M-N] BRS NOP EX E2 [min(M,N),max(M,N)] C2 — [min(M,N),max(M,N)-min(M,N)] ;

Теперь в программе не осталось неопределенных процедур и можно приступить к ее проверке. Проверку следует вести снизу-вверх, т. е. сначала нужно убедиться в правильности работы процедуры ШАГ и лишь затем — НОД.

Операция базового языка DO вызывает повторение названной вслед за ней процедуры N раз, где N — число, содержащееся в вершине стека к моменту выполнения DO. Например, чтобы процедура P выполнилась 8 раз, надо задать

8 DO P <CR>

Если в теле процедуры P имеется хотя бы одна операция выхода и условие ее выполнения окажется удовлетворенным до того, как произойдет заданное число повторений, то повторения будут прекращены путем выхода из процедуры подобно тому, как это делается в случае операции RP. Например, при повторении посредством DO описанной выше процедуры W, в определении которой содержится IF0 EX, запись [T] 30 DO W вызовет 30 повторений W, если значение T>=30. Если же 0<T<30, то процедура W выполнится T раз.

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

Для иллюстрации использования операции DO определим процедуру NUM, подсчитывающую количество не равных нулю битов в 32-битном слове x, заданном в вершине стека.

Счетчик количества единиц разместим в подвершине стека. Подсчет единиц будет заключаться в 32-кратном повторении процедуры NUMI, в которой будем исследовать один бит слова x. По выходе из цикла в подвершине стека должно находиться искомое число.

B10

: NUM [x] 0 E2 [N,x] 32 DO NUMI [N,x] D [N] ;

Для подсчета ненулевых битов воспользуемся тем, что единица в старшем (31-м) бите слова служит признаком отрицательного числа. Если исследуемое слово отрицательно, то надо прибавить к N единицу. В конце процедуры NUMI нужно сдвинуть исследуемое слово на один разряд влево.

: NUMI [N,x] C IF- N+ [N,x] SHL [N,x'] ;

Реализация процедуры N+ совсем проста: нужно прибавить единицу к подвершине стека, не меняя вершины.

: N+ [N,x] E2 1+ E2 [N+1,x] ;

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

2 EXT

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

Именуемые данные

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

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

Слово VAR объявляет 16-битную числовую переменную. Например, запись

VAR X

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

! X

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

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

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

VAR X VAR Y

: НОД [M,N] ! X ! Y [] RP ШАГ X [НОД] ;

: ШАГ [] X Y = EX+ X Y BR+ X-Y Y-X [] ;

: X-Y [] X Y — ! X [] ;

: Y-X [] Y X — ! Y [] ;

Как видно, программа стала несколько длиннее, зато повысилась ее наглядность.

Слово VCTR объявляет одномерный массив (вектор) 16-битных ячеек, причем номер старшего элемента этого массива задается значением вершины. Например, в результате выпонения записи

9 VCTR ROW процессор резервирует 10 последовательно адресуемых 16-битных слов памяти, образуя вектор ROW(0:9). Сначала производится засылка числа 9 в стек, а затем выполняется процедура VCTR, употребляющая верхний элемент стека для определения длины создаваемого вектора ROW.

Засылка в стек значения j-го элемента вектора ROW, 0<=j<=9, задается командой

[j] ROW [ROW(j)]

Используя в качестве параметра находящийся в стеке номер элемента, имя вектора ROW вызывает замену этого номера значением соответствующего элемента. Если же непосредственно перед именем вектора ROW находится слово !, то указываемому вершиной элементу данного вектора присваивается значение подвершины, и глубина стека уменьшается на 2. Например, обнулить 5-й элемент вектора ROW можно так:

[] 0 5 ! ROW []

Имеется также возможность соэдания векторов-констант, т.е. векторов 16-битных чисел, значения которых определены при его объявлении и в дальнейшем не изменяются. Так, вектор 16-битных констант VC длины L+1 объявляется с помощью слова CNST в виде:

CNST VC k0 k1 ... kL ;

где k0, k1, ... kL — команды, засылающие в стек по одному значению. Чаще всего это просто числовые литералы, но могут быть и имена переменных, процедур, а также команды, состоящие из пар слов, таких, например, как рассматриваемая ниже команда засылки адреса переменной ' X. Обращение к элементам вектора-константы производится так же, как к компонентам обычных векторов. Например:

1 VC [k1]

Многомерный массив 16-битных слов объявляется с помощью слова ARR, перед которым указываются максимальные значения индекса по каждому измерению и число измерений. Например, трехмерный массив TIR(0:8,0:2,0:24) объявляется так:

8 2 24 3 TIR

Число 3, находящееся непосредственно перед ARR, означает размерность объявляемого массива.

Засылка элемента массива в стек достигается заданием индекса этого элемента в сопровождении имени массива. Например, команда засылки в стек элемента TIR(0,2,2) выражается в виде

0 2 2 TIR

Соответственно, присваивание этому элементу текущего значения вершины стека задается командой

0 2 2 ! TIR

Все рассмотренные примеры иллюстрировали создание структур из 16-битных слов. Однако, язык позволяет также определять структуры 32-битных слов и 8-битных байтов. Для этого перед словом, определяющим структуру, ставится предпрефикс LONG или BYTE соответственно. Например,

5 BYTE VCTR X — определение 6-ти компонентного вектора байтов X;

BYTE CNST Y 65 66 67 ; — определение 3-х компонентного байтового вектора-константы Y;

10 20 2 LONG ARR MTRX — определение матрицы длинных слов MTRX(0:10,0:20).

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

Хотя формат 16-битного слова используется при определении данных по умолчанию, для него также имеется обозначение WORD. Целесообразно употреблять этот предпрефикс когда предполагается перенос программы на другие машины, где также реализована ДССП и умолчание может быть иным.

Байтовые структуры данных чаще всего используются для хранения и обработки текстовой информации. Это объясняется тем, что для кодирования одной литеры в памяти компьютера отводится один байт. Для задания кодов литер в языке РАЯ имеется конструкция #l, где l — любая литера, имеющаяся на клавиатуре компьютера. ДССП-процессор воспринимает эту конструкцию как команду засылки в стек кода литеры l. Например:

#1 [49]

# [32 — код пробела]

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

BYTE CNST Y #A #B #C ;

Часто бывает удобно использовать в программе символическое обозначение числовой константы. Для обеспечения этой возможности имеется определяющее слово VALUE:

[255] VALUE LEN

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

[] LEN [255]

Работа с памятью по физическим адресам

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

' X

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

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

[] ' Y @ [Y]

Команда @B заменяет адрес значением соответствующего байта, полагая старшие байты вершины стека равными нулю, а команда @L заменяет адрес 32-битным словом.

Имеются также команды записи значений в память. Команда !T записывает по адресу, изъятому из вершины стека, 16-битное значение подвершины. Команда !TB вызывает аналогичную запись младшего байта подвершины в байт, адресуемый вершиной, а !TL запись 32-битного слова подвершины в слово, адресуемое вершиной. Например, присвоить значение 15 пятому элементу байтового вектора BV(0:5) можно следующими командами:

[] 15 5 ' BV !TB []

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

Дополнительные операции для работы с данными и памятью

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

!0 <имя переменной> — обнулить переменную;

!1 <имя переменной> — присвоить единицу переменной;

!1- <имя переменной> — уменьшить значение переменной на единицу;

!1+ <имя переменной> — увеличить значение переменной на единицу;

!- <имя переменной> — вычесть из переменной значение вершины стека;

!+ <имя переменной> — прибавить к переменной значение вершины стека.

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

!0 X эквивалентно 0 ! X

!1+ X эквивалентно X 1+ ! X

!- X эквивалентно X E2 — ! X

Использование данных операций повыщает эффективность и наглядность программ.

На практике часто требуется присвоить какое-либо одно значение всем элементам массива. Для этого в языке РАЯ имеется операция !!! <имя массива>. Ее действие заключается в присваивании значения вершины стека всем компонентам указанного массива. Операция !!! применима к массивам с элементами любого формата.

Пример использования:

[] # !!! BUF []

код литеры "пробел" записывается во все компоненты байтового массива BUF.

Часто бывает необходимо получить в программе информацию о структуре данных, скрывающихся за именем. Для этого служит пара команд SIZE? — выдать формат элемента данных: 1, 2 или 4 байта, и DIM? — выдать количество элементов данных в структуре. Например, если объявлены данные

WORD VAR X

7 BYTE VCTR Y

3 4 2 LONG ARR Z

то применительно к ним эти команды дадут следующий результат (числа десятичные):

SIZE? X [2] SIZE? Y [1] SIZE? Z [4]

DIM? X [1] DIM? Y [8] DIM? Z [20]

Набор команд ДССП-процессора включает, в качестве дополнения, четыре команды, позволяющие читать и записывать отдельные биты ячеек памяти компьютера. Это команды @BI, !BI, !BI0, !BI1. Параметрами для каждой из них служат находящиеся в стеке адрес слова памяти и номер бита в этом слове (напомним, что биты нумеруются справа налево, начиная с нуля). Команда !BI, кроме того, предполагает наличия в стеке и значения бита, которое нужно записать. Команда @BI заменяет указанные параметры значением выбранного бита (0 или 1), команды !BI0 и !BI1 присваивают выбранному биту соответственно значение 0 и 1, удаляя свои параметры из стека, а команда !BI присваивает выбранному биту значение младшего бита третьего элемента стека и удаляет из стека все три свои параметра. Например, если значением переменной X является двоичное число 101101, то результаты перечисленных операций будут следующими:

' X [адр.X] 3 @BI [1] — третий бит X, 0 ' X [0,адр.X] 3 !BI [] — X равен 100101,

' X [адр.X] 0 !BI0 [] — X равен 100100,

' X [адр.X] 1 !BI1 [] — X равен 100110.

В языке РАЯ имеются также средства для работы со строками байтов, расположенными в памяти. Для задания строки байтов в стек помещаются два параметра: начальный адрес строки (т.е. адрес ее первого байта) и длина строки (количество байтов в ней).

Команда !!!MB служит для присваивания всем байтам строки одного (заданного в стеке) значения. Она потребляет из стека три параметра: [b,a,l], где b — присваиваемое значение, a и l — соответственно начальный адрес и длина байтовой строки. Пусть, например, нужно обнулить элементы с 3-го по 10-й байтового массива TXT(0:20). Для этого можно выполнить следующую строку:

[] 0 3 ' TXT 8 !!!MB []

в результате чего восемь последовательных элементов указанного массива, начиная с 3-го, получат значение 0. Аналогичная команда !!!MW предназначена для заполнения одним и тем же значением последовательности 16-битных слов (в вершине стека указывается количество слов), а команда !!!M — для заполнения последовательности длинных слов.

Команда !SB выполняет пересылку байтовых строк. Ее параметры: [a1,l,a2], где a1 и l — начальный адрес и длина пересылаемой строки, a2 — начальный адрес строки, в которую выполняется пересылка. В результате выполнения команды !SB в памяти с адреса a2 будет расположена байтовая строка длины l, являющаяся точной копией строки, находившейся по адресу a1 до выполнения пересылки. Строка-источник и строка-приемник могут перекрываться. Пусть, например, требуется переместить элементы байтового массива M(0:10) следующим образом: M(10):=M(9), M(9):=M(8), ..., M(1):=M(0). Для этого можно воспользоваться командой !SB:

[] 0 ' M 10 C2 1+ [a,10,a+1] !SB []

в результате произойдет перемещение строки из 10 байтов на один байт в сторону увеличения адресов памяти.

Команда !SB удобна для работы со сторками литер (напомним, что каждая литера кодируется одним байтом). Она позволяет, например, присваивать байтовому массиву значение явно заданной литерной строки. Для задания такой строки служит текстовый литерал, т.е. заключенная в кавычки последовательность литер, например "ТЕКСТОВЫЙ ЛИТЕРАЛ". Эта конструкция, встретившись в программе, вызывает засылку в стек начального адреса и длины байтовой строки, содержащей заключенный в кавычки текст. Данные параметры могут затем использоваться командой !SB. Например, фрагмент [] "ТАБЛИЦА" 0 ' TN !SB [] вызовет пересылку литерала "ТАБЛИЦА" в массив TN.

Команда SRCHB обеспечивает поиск заданного байта в строке. Параметры: [b,a,n], где b — байт, первое вхождение которого надо найти, a и n задают соответственно адрес начала и длину строки, в которой ведется поиск. Если n>0, то поиск ведется с адреса a до адреса a+n-1 (в сторону возростания адресов), если n<0, то поиск ведется с адреса a до адреса a+n+1 (в сторону убывания адресов). В результате выполнения этой команды в стеке оказывается значение d, равное смещению относительно адреса a до первого вхождения байта b. Если такое вхождение не обнаружено, то d=n. Примеры:

#T "TEXT" SRCHB [0]

#A "TEXT" SRCHB [4]

#E "TEXT" [#E,a,4] 1- + -4 [#E,a+3,-4] SRCHB [-2]

Заканчивая рассмотрение средств работы с данными, остановимся на вопросе, связанном с хранением данных во внешней памяти компьютера, т.е. на магнитных дисках. В языке РАЯ имеется команда SAVE <имя файла>, предписывающая сохранить копию находящейся в главной памяти системы на диске вместе с определенными пользователем объектами. При этом области памяти, отведенные под данные операциями VAR, VCTR, ARR на диск не выводятся. Вследствие этого при загрузке сохраненной системы с диска значения указанных данных не определены (они должны определяться во воремя выполнения программы). В большинстве случаев это оправдано, так как нет необходимости расходовать дисковую память для хранения рабочих переменных, буферов и т.п. Однако, бывают данные, значения которых должны быть определены сразу после загрузки системы с диска. В качестве примера можно привести переменную, хранящую скорость обмена данными с некоторым внешним устройством. При переходе на другую скорость обмена достаточно изменить значение данной переменной, не внося никаких исправлений в программу.

Указанием процессору, что значения элементов некоторой структуры данных должны выводиться на диск по команде SAVE, служит предпрефикс FIX, помещаемый перед определеним структуры, например

FIX VAR SPEED 20 FIX BYTE VCTR TABL

Работа с так определенными структурами данных ничем не отличается от работы со структурами, определенными обычным образом.

Команды управления процессором

В языке РАЯ имеется немногочисленная группа команд, предназначенных для управления ДССП-процессором, а точнее — эмулятором ДССП-процессора.

Команда RESTART вызывает перезапуск процессора. При этом происходит очистка стека, выдается сообщение

ДССП версия ХХ.ХХ.ХХ

Свободно ХХХХХW

*

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

Команда \G служит для продолжения выполнения программы после останова на неопределенном слове. Если при выполнении процедуры процессор встречает ссылку на неопределенное слово, он выдает сообщение:

останов не знаю <слово> .

где точка — приглашение ДССП-процессора, сигнализирующее о том, что процессор находится в состоянии останова на неопределенном слове. В этом режиме можно выполнять любые команды процессора, так же, как в обычном режиме, когда приглашеним служит звездочка. Выйти из данного режима можно двумя способами — либо выполнив команду \G (тогда процессор продолжит выполнение прерванной процедуры, пропустив неопределенное слово), либо по команде RESTART.

Команда EXEC предписывает процессору выполнить процедуру, адрес которой находится в вершине стека. Для получения адреса процедуры служит команда '' (два апострофа), за которой указано имя процедуры. Например, в результате выполнения команды

'' ABS

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

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

Команда LOAD <имя файла> переключает ввод на дисковый файл с указанным именем. Команда PF — предписывает вводить команды из буфера редактора текстов. Команда TEXEC передает на вход процессора текстовую строку, параметры которой заданы в стеке. По выполнении команд, содержащихся в указанных источниках, ввод автоматически переключается на клавиатуру дисплея.

Команды управления словарем

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

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

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

В ходе наращивания словаря имеется возможность образования подсловарей — поименованных совокупностей словарных входов. В подсловарь обычно объединяются процедуры и структуры данных, относящиеся к одной задаче. Во избежание путаницы между именами подсловарей и других объектов программы, имя подсловаря должно начинаться с литеры $. Доступ к подсловарям для их наращивания или использования можно открывать и закрывать специальными командами, к числу которых относятся следующие (под именем $v подразумевается любое допустимое подсловаря).

GROW $v — наращивать подсловарь $v, то есть, пока не будет предписано иное, вносить имена всех компилируемых процедур и данных в подсловарь $v;

USE $v — открыть для использования (для поиска в нем имен) подсловарь $v;

SHUT $v — закрыть возможность использования подсловаря $v;

ONLY $v — сделать доступным для использования только подсловарь $v;

CANCEL — отменить последнее ONLY.

Имеется также команда ?$, печатающая на дисплее имена всех подсловарей их состояния — открыт или закрыт подсловарь для поиска. Наращивается всегда тот подсловарь, имя которого напечатано верхним.

Базовые процедуры ДССП составляют подсловарь с именем $PRIME, открытый для использования и наращивания по умолчанию, то есть, если не было команды, предписывающей наращивание иного подсловаря.

Пусть, например, операцией ?$ было распечатано следующее состояние подсловарей.

$PRG открыт

$PRIME открыт

$EDIT закрыт

$PRIME открыт

SYSTEM закрыт

Это означает, что в данный момент подсловарь $PRG открыт для наращивания и использования, $PRIME — только для использования, а $EDIT и SYSTEM недоступны. Отметим, что подсловарь может состоять из нескольких секций с одинаковыми именами.

Имеются команды удаления из словаря той или иной совокупности словарных входов и, может быть, связанных с ними внутренних объектов. Так, команда FORGET $v удаляет все имена, занесенные в словарь (не только в подсловарь $v) после последнего выполнения команды GROW $v вместе с обозначенными этими именами объектами, и отменяет установленное им наращивание подсловаря $v. Команда PROGRAM $v производит те же действия, что и выполненные последовательно команды FORGET $v GROW $v. Наличие такой команды в начале любой программы приводит к тому, что при повторной компиляции программы будет удаляться ее старая копия и будет образовываться подсловарь для хранения объектов новой копии программы. Например, выполнив операцию FORGET $PRIME над словарем, состояние которого было показано выше, получим новое состояние:

$EDIT закрыт

$PRIME открыт

SYSTEM закрыт

В процессе выполнения команды FORGET на дисплей выдаются имена удаляемых секций.

Обратите внимание, что имя подсловаря SYSTEM не начинается литерой $. Это допустимо, но приводит к тому, что применение к данному подсловарю команд FORGET и RPOGRAM не вызывает никаких действий (подсловарь SYSTEM для них как бы не существует).

Ввиду того, что в готовой программе для подавляющего большинство процедур обращение по внешнему имени не требуется, предусмотрена возможность удаления из словаря их имен с сохранением сопоставленных им внутренних объектов. Команда CLEAR $v удаляет из всех секций подсловаря $v все имена, за исключением тех, перед которыми в тексте программы (при их определении) стоял предпрефикс :: (два двоеточия). Например, в результате выполнения процессором следующего фрагмента программы:

PROGRAM $EXAM

:: BYTE VAR X

VAR Y

:: : X+ [] Y !+ X [] ;

CLEAR $EXAM в подсловаре $EXAM останутся только имена X и X+, словарный вход Y будет удален (хотя переменная, соответствующая слову Y во внутреннем представлении, останется).

Команды ввода/вывода

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

Программирование работы терминала обеспечивается командами ввода и вывода чисел, отдельных литер и последовательностей литер (строк), а также некоторыми дополнительными командами.

Команда TIB (Terminal Input Byte — ввод байта с терминала) инициирует цикл ожидания нажатия клавиши на клавиатуре терминала. Принажатии клавиши 8-битный код соответствующей литеры засылается в стек в виде младшего байта вершины, причем старшие 3 байта содержат нули. Копия введенной таким образом литеры отображается на дисплей. Имеется также команда TRB (Terminal Read Byte), отличающаяся от TIB тем, что засылка кода введенно й литеры в стек не сопровождается отображением этой литеры на дисплей.

Команда TIN (Terminal Input Number — ввод числа с терминала) инициирует цикл ввода в стек и отображения на дисплей набираемого с клавиатуры числа. Вводимое число должно быть последовательностью цифр, которая может начинаться знаком "минус", а заканчиваться <CR>. В зависимости от установленного режима ввода/вывода цифры воспринимаются процессором как 16-ричные, десятичные, восьмеричные или двоичные. Если 16-ричное число начинается цифрой, обозначаемой буквой, то перед ней добавляется цифра 0. Введенное число переводится в двоичный дополнительный код, который засылается в стек в качестве целочисленного значения 32-битного длинного слова, т.е. с отсечением битов, расположенных левее имеющего вес 2 в степени 31 старшего бита.

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

Последовательность, содержащая n набираемых с клавиатуры литер, вводится в память компьютера в виде n байтов, располагаемых по последовательно возрастающим адресам, начиная с адреса a, при помощи команды TIS (Terminal Input String), перед которой в стек засылается адрес a и число литер n. Пусть, например, объявлен вектор байтов X достаточной длины. Нужно ввести 9 литер, присвоив их значения элементам этого вектора, начиная с нулевого элемента:

0 ' X 9 TIS

Аналогично с помощью команды TOS задается вывод последовательности из n байтов-литер с начальным адресом a:

[a,n] TOS []

Вывод на терминал непосредственно включаемых в программу элементов текста обеспечивается конструкцией

."<текст>"

Например, чтобы при выполнении некоторого фрагмента программы на дисплее появился текст ВВЕДИТЕ НОМЕР ВАРИАНТА, фрагмент должен содержать запись ."ВВЕДИТЕ НОМЕР ВАРИАНТА".

Команда TON (Terminal Output Number) выводит на дисплей число, изымаемое из подвершины стека, причем длина поля вывода должна быть задана в вершине. Выводимое число выравнивается по правому краю поля, свободные позиции слева заполняются пробелами, а если длина числа превышает заданную длину поля, то происходит отсечение слева. В режиме десятичного ввода/вывода отрицательные числа начинаются знаком минус.

Команда TOB (terminal output byte) печатает литеру, код которой задан младшим байтом вершины стека. Глубина стека уменьшается на 1.

Имеются также команды, прямо управляющие курсором дисплея:

CR — переход на начало новой строки,

SP — пробел, то есть перемещение на одну позицию вправо.

Команда BELL вызывает короткий звуковой сигнал ("звонок").

Иногда при обмене с терминалом бывает необходимо проверить, нажата ли уже клавиша и отработал ли уже дисплей предыдущую команду вывода. Это можно сделать командами TTI (Terminal Test Input) и TTO (Terminal Test Output), оставляющими в стеке признак 1, если указанное событие произошло, и 0 в противном случае.

Команды вывода на принтер подобны командам вывода на терминал и базируются на аналогичной мнемонике, в которой литеры LP (Line Printer) либо заменили TO, либо добавлены в качестве ведущих. Например, LPCR — переход на начало новой строки, LPSP — пробел, LPN — вывод числа из подвершины в поле, заданном вершиной, LPB — вывод литеры, LPS — вывод строки литер. Имеется также команда [N] LPT, перемещающая печатающую головку в позицию N печатаемой строки и команда LPFF, обеспечивающая прогон листа бумаги. Для печати явно заанного текста удобно воспользоваться текстовым литералом и командой LPS, например:

"ТАБЛИЦА ЗНАЧЕНИЙ ФУНКЦИИ" LPS

Обработка прерываний и исключительных ситуаций

При программировании периферии возникает необходимость обрабатывать прерывания. В ДССП эта обработка программируется следующим образом. Программа, предназначенная для обработки прерывания представляет собой обычную процедуру ДССП, перед определением которой стоит предпрефикс INT, например INT : A [] !1+ I [] ; Предпрефикс INT обеспечивает сохранение состояния процессора при прерывании и восстановление его по окончании обработки прерывания.

Для связывания программы обработки с конкретным прерыванием служит команда LINK:

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

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

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

TRAP <имя вызова> <конечная реакция>

Например:

TRAP S X

TRAP S1 ."Ситуация S1."

В первом случае конечной реакцией на прерывание S служит процедура X, во втором при возникновении прерывания S1 на терминал будет выдано сообщение: Ситуация S1.

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

ON <имя прерывания> <реакция>

EON <имя прерывания> <реакция> Например:

: A ... ON S ."Прерывание S" ... ;

: A1 ... EON S1 ABC ... ;

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

Рассмотрим примеры. Процедура M вводит с клавиатуры терминала литеры и проверяет, цифра ли это. Если введенная литера не является цифрой, возбуждается прерывание ND. TRAP ND ." Не цифра." : M [] RP M1 [] ; : M1 [] TRB [B] C #0 < C2 #9 > &0 [B,1/0] IF+ ND [B] TOB [] ;

Конечная реакция на прерывание ND — сообщение: Не цифра.

Если M вызывается из процедуры P1, в которой установлена своя реакция на прерывание ND : P1 [] ON ND PR1 M [] ; : PR1 [B] CR ."Ошибка." D #0 [#0] ; то при вводе нецифровой литеры прерывание ND будет обработано программой реакции PR1 типа ON, что вызовет выдачу с новой строки сообщения: Ошибка. Введенная литера будет заменена литерой 0, после чего M продолжит работу.

Если M вызывается из процедуры P2 : P2 [] EON ND PR2 M [] ; : PR2 [] CR ."Ошибка. Конец ввода." [] ; то при вводе нецифровой литеры прерывание ND будет обработано программой реакции PR2 типа EON, что вызовет выдачу с новой строки сообщения: Ошибка. Конец ввода., после чего P2 завершит работу. Стек операндов при этом будет пуст.

При необходимости в программе-реакции можно снова возбудить прерывание, распространив его таким образом на программы более высокого уровня. В этом случае обрабатывать прерывание будет либо программа, указанная в команде перехвата в объемлющей процедуре, либо конечная реакция. Например, если модифицировать PR2 следующим образом: : PR2 [] CR ."Ошибка. Конец ввода." ND [] ; то сообщение, выданное на терминал, будет таким: Ошибка. Конец ввода. Не цифра.

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