Пользователи ПВМ БК-0010 с языками Фокал и Бейсик знают об относительных достоинствах и недостатках этих машин, об ошибках в программном обеспечении, «зашитом» в ПЗУ. Особо существенным ограничением БК с Бейсиком является малый объём оперативной памяти в сочетании с крайне неэкономной в отношении расхода памяти системой обработки Бейсика. Естественно поэтому обращение к программированию на Ассемблере непосредственно в машинных кодах или к смеси Бейсика и машинных программ (подпрограмм).

КОТОВ Ю.В.

Совместное использование Бейсика и машинных кодов

Машинные команды микропроцессора БК-0010 за отдельными исключениями те же, что у машин СМ, ДВК, PDP, и среди советов владельцам БК предлагают пользоваться Ассемблерами этих машин как таковыми или переделанными для БК версиями. Полностью покрыть задачи программирования такие Ассемблеры, однако, не могут, так как микропроцессор не имеет даже команд умножения и деления целых чисел, не говоря об операциях с нормализованными ("плавающими") числами и о вычислении функций. Такие операции выполняются по подпрограммам, но загружать эти подпрограммы (позаимствовав, допустим, у машины ДВК) в оперативную память неэффективно опять же в связи с ограниченностью ОП. Целесообразнее для всех возможных операций и функций использовать программные блоки и подпрограммы интерпретатора Фокала либо системы обработки Бейсика, которые зашиты в ПЗУ Программирование при этом приобретает специфический характер, и стандартный Ассемблер помогает только частично. Потому иногда предпочитают манипулировать непосредственно машинными восьмеричными кодами и адресами.

ПВМ с Фокалом снабжены вспомогательным отладочным монитором, позволяющим оперировать с машинными кодами, модификации с Бейсиком могут не иметь такого средства, тогда доступ к ОЗУ, ПЗУ, машинным кодам возможен через функции PEEK и POKE Бейсика, а запуск машинных подпрограмм - через функции пользователя типа USR.

Сведений, приводимых в документации, поставляемой с этими машинами, недостаточно для обучения программированию в машинных кодах или на Ассемблере и тем более для использования ресурсов обрабатывающей системы Бейсика (или Фокала). Приходится разыскивать первоисточники - инструкции к другим машинам, учебники программирования в системе команд DEC (например, для ЭВМ PDP). Многие материалы частями публиковались в журналах (см. список литературы).

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

Для уверенной работой с машиной надо осваивать несколько разделов сведений:

Для начала приведём несколько простых приёмов выхода из Бейсика в оперативную память или ПЗУ, доступ к машинным подпрограммам, затем разберём схему обработки Бейсика, использование при этом оперативной памяти и системных переменных, представление информации на разных этапах её обработки в ЭВМ.

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

1. Простые приёмы доступа к машинной памяти

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

Из описания Бейсика известны функции PEEK и POKE (точнее, оператор и функция); в отличие, например, от машин типа IBM PC эти функции работают с 2-байтовыми словами (первый байт - с чётным адресом); внутримашинную информацию в машинах этого типа предпочитают записывать и просматривать в восьмеричном, а не шестнадцатеричном виде, так что два байта записываются максимум шестью цифрами, старшая из которых может быть только нулём или единицей. Если все биты двухбайтового слова заполнены, восьмеричный код будет 177777; с указанием типа восьмеричной константы в Бейсике &О177777.

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

KEY 2,"POKE &О

Вызывая затем эту заготовку нажатием двух кнопок, дописывают нужную информацию и пускают в «ход», например, для записи в ячейку 2128 кода 1118:

POKE &O212, &O111

(после этого вновь вводимые нижние строки экрана, а затем весь его фон покроются полосами, отменить это можно командой COLOR 1,0).

Для чтения кодов из отдельных ячеек (слов) можно ввести ключ:

KEY 1,"? OСТ$(PEEK(&O".

Дописывая адрес после вызова заготовки, не надо забывать две завершающие скобки.

В нужных случаях можно заменять функцию ОСТ$ на BIN$ или НЕХ$ или же второй параметр для POKE записать с префиксом $Н либо как десятичное число (целое).

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

Оператор POKE для записи чётного байта (символа А) по адресу 10008 может выглядеть так:

POKE &O1000, PEEK(&O1000) AND &O177400 OR ASC("A")

Прочесть и распечатать тот же байт можно так:

? CHR$(PEEK(&O1000) AND &O377)

Логические операции здесь используются для выделения нужных разрядов кода.

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

POKE &O1000, PEEK(&O1000) AND &O377 OR (((ASC("A")-128)*256)XOR &O100000)

(Записывается байт по адресу 10018, а слово имеет адрес 10008).

Прочесть и распечатать этот байт можно так

:? CHR$(PEEK (&O1000) XOR &О100000)/256+128)

Такие длинные строки в качестве ключей не введёшь, но их можно преобразовать в маленькие подпрограммки; константы (коды и адреса) можно заменить на переменные. Читателям предлагаем дополнительно подсократить приводимые выражения.

Для чтения массивов информации предлагается сравнительно простая программка (чтобы не занимать лишнего места, будем писать по нескольку операторов в строке и опускать их номера, как если бы Бейсик для БК был более современным):

1000 INPUT I %
1001 А%=0%:FOR K%=1% Т О22%:?ОСТ$(I%)" "OCT$(PEEK(I%)):I%=I%+2%:NEXT:INPUT А%:IF А% THEN I%=А%
1009 GOTO 1001

Здесь вначале запрашивается адрес начала выводимого массива, затем последовательно выводится содержимое по 22 строки; каждый раз надо нажимать кнопку «ВВОД» (ВК). Если вместо этого ввести новый адрес (очевидно, восьмеричный), следующая порция информации будет выводиться с этого адреса. Завершается просмотр кнопкой «СТОП».

Для записи информации в массив (с контрольным просмотром) несложная программа может быть такой:

1010 INPUT I% : GOTO 1050
1020 INPUT К % : IF К%=&O111111 GOTO 1040
1030 POKE I%,К%
1040 I%=I%+2%
1050 ? OCT$(I%); TAB(8); OCT$(PEEK(I%)); TAB(16);
: K%=&O111111 : GOTO 1020.

Здесь тоже сначала запрашивается начальный адрес массива; затем поочерёдно (строками) выводятся последовательные адреса слов и их старое содержимое; если просто нажать кнопку «ВВОД», старое содержимое остаётся; в противном случае вводится новая информация. Замечание: коды 1111118 данная программа не вводит. Вводимую восьмеричную информацию следует предварять префиксом, указывающим на её восьмеричность. Впрочем, можно вводить и десятичные числа.

Простейшая программа позволяет переносить массивы информации:

1100 INPUT I%,J%,N% : FOR К%=I% ТО J% STEP 2%: POKE N%, PEEK(K%): N%=N%+2% : NEXT: END

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

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

Читать этими способами можно, конечно, как ОЗУ, так и ПЗУ; записывать только в ОЗУ, но в том числе в системные переменные (защиты памяти нет), в область видеопамяти и др.

На ленте (или диске центральной машины) такая информация сохраняется оператором BSAVE с указанием непосредственных машинных адресов; читается соответственно оператором BLOAD. К сожалению, в отличие от ПВМ типа IBM PC и других в программном режиме эти операторы не срабатывают, так что при необходимости приходится использовать машинные подпрограммы.

Небольшие машинные программы (подпрограммы) и информацию ограниченного объёма можно размещать с адреса 1000 до 1400 - 1600 (в надежде, что «стек» не достанет) или с 37000 по 37777 (если не использовать некоторых операций ввода-вывода)[1]. В других случаях для такой информации отводится область оператором CLEAR n, adr. Здесь adr, как известно, предельный адрес, занимаемый Бейсиком; обычно это начало видеопамяти (400008), задавая его меньшим, получаем свободную для машинных программ и информации область, начиная от adr и до начала видеопамяти[2]. Этот же оператор совместно с переключением режима работы экрана (РП) позволяет либо расширить пространство для Бейсика, либо получить дополнительное место для машинных программ. Изменяя информацию в нескольких системных ячейках, можно «усечь» объём видеопамяти и рабочее поле экрана на четверть или, допустим, треть, получив дополнительное место для других целей (см. Л).

Заставить работать машинные программы (точнее, они будут подпрограммами) можно с помощью функций USRi, где i - цифра от 0 до 9; предварительно оператором вида DEF USRi=&Окккк задаётся начальный адрес (точка входа) подпрограммы (кккк), выход на неё обеспечивается хотя бы фиктивным использованием функции USR : A%=USRi(A%); здесь A% может быть фиктивной переменной, B% - фиктивной переменной или константой (целый тип предполагается в целях экономии). В нужных же случаях B% - входной параметр, A% - рабочее значение функции; такая функция может быть включена. конечно, в арифметическое выражение. В инструкции по Бейсику сказано, как, составляя машинную подпрограмму, разыскать исходный параметр и как устроить передачу Бейсику значения функции (в том числе для различных типов переменных). Известны, однако, искусственные приёмы, позволяющие передавать машинной подпрограмме несколько параметров.

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

Для первого знакомства посоветуем записать в ячейки 1000 и 1002 коды 615158 и 2078 и затем убедиться, что функция USR складывает значение аргумента (целого) с собой, т.е. умножает его на два.

2. Начальные сведения о процессоре и машинных командах

От процессоров машин «Электроника-60» [Л], ДВК и ряда других микропроцессор БК-0010 (К1801ВМ1) отличается в основном отсутствием команд умножения и деления целых чисел.

Процессор - 16-разрядный (в сравнении с 8-разрядными «Микрошами», «Корветами» и даже «Ямахами»), многие команды (но не сложения и вычитания) работают в двух модификациях - как с байтами, так и с двухбайтовыми словами. В процессоре - восемь 16-разрядных регистров (нумеруемых от 0 до 7), которые действуют в основном аналогично, но 7-й регистр (PC) также выполняет роль счётчика адресов команд. Кроме того, 6-й регистр используют с характерной целью - счётчика в стеке памяти (SP). Помимо этого, в процессоре есть 4 одноразрядных регистра[3], заполняемых при работе различных команд в зависимости от результатов их операций и играющих роль «признаков» (знака, нуля, переполнения, переноса), которые могут быть использованы последующими командами, например условных переходов. Сами команды (все) можно считать двухбайтовыми, хотя разряды кода операции располагаются в них на разных местах. В зависимости от типа адресации операндов, однако команду могут дополнять одно-два слова, содержащих операнд непосредственно, адрес его расположения, "адрес адреса", приращение адреса и др. Можно заметить, что структура команд разрабатывалась тогда, когда ЭВМ и тем более ПВМ. Микро-ЭВМ имели очень ограниченные объёмы памяти; принимались меры для возможного сокращения длины машинных программ. Потому многие команды, при задании соответствующих типов адресации, дополнительно наращивают или уменьшают адрес в задействованном регистре, чтобы проще можно было выполнить следующие команды. Но, с другой стороны, «упаковывая» в два байта команды условных переходов (и «короткого» безусловного перехода BR), обеспечили смещение точки перехода (относительно текущего значения СЧАК) только до 127-128 слов «вверх» или «вниз», а это нередко требует добавления команд «длинных» переходов (JMP), усложнения структуры программы. «Длинная» адресация ограничена 16-ю разрядами, т.е. 64 килобайтами памяти. «Раздвоения» адресов, например на «базу» и «смещение», как во многих других структурах команд, здесь в самих командах не предусмотрено, что усложняет введение постраничной памяти при её расширении. Слабы команды сдвигов содержимого операндов - команда за один раз даёт сдвиг только на один двоичный разряд вправо или влево, и для сдвига на несколько разрядов приходится устраивать циклы. Сложение и вычитание возможны только для двухбайтовых слов. Практически нет команд ввода-вывода. Вместо этого по отдельным адресам установлены не просто ячейки памяти, а регистры, связанные с внешними устройствами. Запись в них (или в их отдельные биты) информации и чтение информации производятся вроде бы на общих основаниях. Фактически под одним адресом могут существовать отдельные - вводной и выводной - регистры, как это, например, сделано для регистра параллельного порта (адрес 177714).

Основные регистры связи описаны в Руководстве системного программиста.

Многое решается программным путём, в том числе программами-драйверами. Так, слова «состояния дисплея» на самом деле нет, в его качестве выступают рабочие (системные) байты с 408 по 568. Указанная (регистром 177664) область памяти, можно сказать, независимо от процессора постоянно сканируется контроллером экрана и передаётся на экран, а вывод на экран символов, их подчёркивание, изменение цвета линий и др. - всё выполняется программно - пересылкой соответствующей информации в область ОП, отведённую под «видеопамять».

В схеме предусматривается также «ловля прерываний». Один из разрядов в регистре PSW (слово состояния процессора) разрешает или запрещает прерывания работающей программы от внешних устройств или особых ситуаций; свои биты разрешения или запрещения прерываний могут быть в служебных регистрах внешних устройств; для прерываний от различных причин (например, нажатие кнопки на клавиатуре или встреча с неправильной командой) в схему «запаяны» адреса ячеек - «векторов прерываний». Эти ячейки, таким образом, фиксированы, хотя информацию в них можно менять. Первое двухбайтовое слово «вектора» - адрес начала подпрограммы обработки прерывания данного типа, второе слово - загружаемое в регистр PSW, «состояние» на период обработки прерывания. На время обработки прерывания текущее (до «прерывания») значение СЧАК, т.е. содержимое регистра 7, и текущее PSW сохраняются в стеке с соответствующим изменением счётчика стека в регистре 6.

Стек вообще достаточно эффективное и универсальное средство временного сохранения различной информации в отведённой области ОП, связи между различными программами и подпрограммами. Стеки могут реализовываться на разных уровнях и языках, чисто программными приёмами. Этот приём заключается в выделении области памяти с некоторым начальным адресом, который может при загрузке данных наращиваться в ту или иную сторону, а при выборке - изменяться в противоположном направлении; таким образом, в ОП или в выделенном регистре процессора заводится счётчик адреса «вершины» стека. Для машинного программирования ПВМ данного типа характерно использование для указателя вершины стека регистра 6 (SP) и, во-вторых, наращивание стека «вверх», т.е. в сторону уменьшения адресов. В текущий момент счётчик указывает на следующий свободный элемент стека. Модификации команд с инкрементом или декрементом адреса в регистре облегчают запись-извлечение из стека. Модификации же команд с заданием смещения адреса позволяю: в нужных случаях читать хранящуюся в стеке информацию, не изменяя счётчик. Преимущественное использование регистра 6 связано со схемой процессора; команды обращения к подпрограмме и возврата из подпрограммы, вышеупомянутые прерывания используют стек, адресуемый именно этим регистром, для хранения адресов возврата и других данных. Загружая в регистр 6, однако, тот или иной адрес, можно переходить «со стека на стек». Так, мониторно-драйверная система использует стек, начиная с адреса 776, а Бейсик - свой стек, с адреса 1776.

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

Как хранятся числа и тексты в памяти машины? Процессор обрабатывает только целые числа байтами и двухбайтовыми словами. Отрицательные числа хранятся в дополнительном коде, притом старший разряд - единица. Число -1 в восьмеричном виде выглядит как 177777, число -32768 - как 100000. Однако адреса команд и другие специальные данные можно рассматривать как целые числа от 0 до 65535. Машинная команда сложения, впрочем, правильно подсчитывает результат, если операнды интерпретировать как целые без знака, например 32000+32000-64000. Но, с другой стороны, этот же код при учёте знака равен 153610. Машинная команда в этом случае устанавливает признак V результата в единицу, а уж система Бейсика фиксирует ошибку 6.

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

Что касается нормализованных чисел (с плавающей точкой), то их запись и формат «отданы на усмотрение» Бейсика (или Фокала, где, между прочим, такие числа записываются не как в Бейсике).

Может быть, стоит забежать вперёд и здесь уточнить представление нормализованных чисел.

Нормализованные числа одинарной и двойной точности занимают соответственно 4 и 8 байтов, в обоих случаях в двух первых байтах располагаются знак числа, порядок и старшие разряды мантиссы, в последующих байтах размещаются младшие разряды мантиссы. Разумеется, числа «двойной точности», а вернее, «удвоенной длины» - не в два раза точнее одинарных, а дают более чем вдвое большее количество точных разрядов чисел при счёте.

В первом двухбайтовом слове числа старший разряд - знак числа; при этом отрицательные числа не представляются в дополнительном коде: мантисса всегда записывается как положительная. Разряды 7-14 представляют двоичное значение порядка[4], увеличенное на 128, т.е. для чисел, равных или больших (по абс. вел.) 0.5, значение 14-го разряда - единица. Если мысленно инвертировать 14-й разряд, положительные и отрицательные значения порядка будут представлены как обычные целые числа, причём отрицательные числа - в дополнительном коде. Разряды с 0 по 6 - это старшие разряды мантиссы, причём самый старший её разряд, поскольку для нормализованного числа эн всегда единичен, в коде числа не записывается (подразумевается как бы между 6-м и 7-м разрядами). Этот приём позволяет повысить точность счета как раз в два раза Значение мантиссы - от 0.5 до без малого 1.

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

Деся-тичное число

Восьмери-чная запись старшего слова

Двоичная запись

Разряды

15

14

13

12

11

10

9

8

7

 

6

5

4

3

2

1

0

зн.
чис.

порядок

мантисса

0.5

40000

0

1

0

0

0

0

0

0

0

(1)

0

0

0

0

0

0

0

1

402000

0

1

0

0

0

0

0

0

1

(1)

0

0

0

0

0

0

0

5

40040

0

1

0

0

0

0

0

1

1

(1)

0

1

0

0

0

0

0

-10

141040

1

1

0

0

0

0

1

0

0

(1)

0

1

0

0

0

0

0

0.1

37314

0

0

1

1

1

1

1

0

1

(1)

1

0

0

1

1

0

0

Ноль изображается всеми нулевыми разрядами.

3. Мониторно-драйверная система

Вместо операционной системы для этих маленьких машин в одной из микросхем ПЗУ (с адресами 100000-117777) «зашита» мониторно-драйверная система, включающая пусковой монитор с ограниченными возможностями, программы-драйверы, обслуживающие периферийные устройства, интерпретацию ЕМТ-команд (обращение к драйверам обычно и происходит через команды ЕМТ). Системные команды ЕМТ, от ЕМТ 4 до ЕМТ 50, описаны в руководстве системного программиста. Сами по себе блоки их обработки (драйверы) могут быть достаточно сложными, включать внутренние подпрограммы, таблицы и другие данные. При работе монитора и драйверов используются рабочие ячейки с младшими адресами (от 4 до 2778), назначение которых описано в [Л], и стек от слова 776 (в сторону уменьшения адресов). Для вывода на экран изображений символов с адреса 1120508 по адрес 1160758, например, расположена таблица их растровой кодировки; для изображения на экране отрезков прямых в драйвере для ЕМТ 32 имеется специализированный алгоритм интерполяции, превращающий заданный отрезок в набор точек, засылаемых в нужные биты видеопамяти.

Начало этой области памяти устроено так.

Адреса

Содержание

100000-100003

Переход к адресу 100260 (начало монитора)

100004-100051

Таблица адресов драйверов - программ обработки прерываний от ЕМТ 4 до ЕМТ 50

100052-100111

Продолжение таблицы для старших кодов ЕМТ, однако «зашиты» адреса в ПЗУ, которые имеющимися ПЗУ Бейсика использованы быть не могут

100112-100137

Блок расшифровки команды ЕМТ и перехода на обрабатывающую программу

100140-100257

Обработка ЕМТ 14 (инициализация драйверов)

110260-100737

Пусковой монитор: в начале инициализируется стек и драйверы, затем в виде подпрограммы вызывается система обработки Бейсика (с адреса 120000, это также может быть интерпретатор Фокала или другая система в ПЗУ)

Если ПЗУ с адреса 120000 не установлена или специальной командой осуществлён выход на монитор, обработка продолжается с адреса 100274

100740-101007

Обработка ЕМТ 4 : инициализация драйвера клавиатуры

101010-101724

Обработка ЕМТ 6 : чтение символа с клавиатуры

. . .

. . .

Расположение остальных драйверов можно посмотреть в таблице адресов.

4. Общая схема работы системы Бейсика

Система обработки Бейсика расположена в трёх схемах ПЗУ и занимает адреса с 120000 по 177531 (дальше, до адреса 177777, поле служебных регистров) и тем исчерпывается доступная адресация в 64 килобайта.

Бейсик использует по мере надобности системные «драйверы» и монитор (обычно через команды ЕМТ), системные ячейки с начала ОП по 371; стек переключается на адреса от 20008 «вверх» (максимально до адреса 1000); с адреса 2000 по 3051 - рабочие ячейки Бейсика, частично упомянутые в [Л], а подробнее рассматриваемые ниже; с адреса 3052 и до начала видеопамяти (адрес 40000) или до предельного адреса, «разрешённого» командой CLEAR, располагается текст программы на Бейсике, далее «протранслированный» объектный код, другие таблицы и данные. Чтобы точнее описать распределение памяти, надо ознакомиться с общей схемой работы системы Бейсика.

Известно, что языки типа Бейсик обычно обрабатываются в режиме интерпретации, что, в частности, позволяет быстро запускать подготовленные программы и сочетать программный (или косвенный) режим работы с непосредственным. Однако более сложные программы, с интенсивным использованием подпрограмм и циклов, обрабатываются в режиме интерпретации медленно, потому в последнее время ряд фирм переходит на режимы компиляции программ в машинные или близкие к ним программы или на какие-то промежуточные схемы обработки. Так, для машин типа IBM PC распространены «TurboBasic» и «Quick-Basic», в чём-то уже приближающиеся к Фортрану, Паскалю и им подобным языкам. Для машины с малым объёмом памяти принятая сложная схема обработки оказалась неудачной ввиду её неэкономности в использовании оперативной памяти, хотя удаётся повысить скорость работы программ. Правда, после пуска объёмистой программы определённое время уходит на её компиляцию (если одновременно в памяти записано несколько программ, допустим, большая основная и короткая дополнительная, то запустить можно короткую программу, но компилироваться будут, увы, все находящиеся в памяти программы). Непосредственно вводимые операторы обрабатываются отдельно, при этом сохраняется доступ к сформированным перед этим программой данным.

Обработка программы на языке Бейсик - двухступенчатая. Сначала программы с подпрограммами, записанные в памяти, компилируются в некоторый промежуточный ("объектный", иногда, наверное, по недоразумению называемый «шитым») код, затем этот код - без каких-либо дополнительных воздействий пользователя - начинает интерпретироваться специальной мониторной частью обрабатывающей системы. Компилирующая часть не совсем обычная в том смысле, что не создаёт машинных программ; промежуточный код состоит в основном из адресов обрабатывающих подпрограмм мониторной части, адресов данных, самих непосредственных данных (константы в исходном тексте). В промежуточном коде переменные уже не ищутся по их именам, хотя в особых случаях имена подвергаются анализу (например, для определения типа переменной). Изредка, увы, из объектного кода производятся обращения к исходному тексту (например, для чтения текстовых констант); использование объектного кода без исходного текста и размещённых по абсолютным адресам переменных не предусматривается. Объектный код также нельзя перемещать в памяти потому, что он содержит абсолютные адреса точек переходов. Пересылка параметров, подсчёт выражений и функций на этапе интерпретации выполняются с широким использованием стека. Имитируется как бы нуль-одноадресная виртуальная машина. Загружается в стек один параметр, другой параметр (или константа), затем вызовом специальной подпрограммы выполняется их сложение или умножение, «верхний» операнд из стека стирается, вместо «нижнего» образуется результат, который дальше может быть использован в следующей операции или сохранен в памяти... Нужный порядок действий определяется на этапе компиляции и превращается в нужную последовательность операций со стеком и операндами в объектном коде. Вместо массивов, текстовых констант и переменных в стек загружаются, конечно, их адреса. В некоторых случаях используются дополнительные стеки.

Как образуется исходный текст программы на Бейсике?

При наборе текста оператора (строки) работает подпрограмма «редактор» IVEIL[5] (нач. адрес 121052); информация при этом накапливается в буферной области BUF (адреса 2422-3021). После нажатия кнопки «ВВОД» и выхода из редактора информация анализируется; если это непосредственный оператор, он отрабатывается одним способом, а если строка программы (начинающаяся с номера) - она заносится в следующее свободное место области, отведённой для исходного текста программ (первая строка заносится с адреса 3052); при этом номер строки не заносится, а оператор (команда) кодируется специальным однобайтовым номером; символ «ВК» в конце строки (код 128) заносится. Номер строки и начальный адрес её расположения заносятся в таблицу TABNUM, которая располагается в конце отведённой области памяти (точнее см. ниже) и наращивается вверх. Для каждой строки отводятся три двухбайтовых слова: адрес начала оператора объектного кода (до пуска нулевой программы), адрес начала исходного текста строки и номер строки. Эта таблица формируется упорядоченной по номерам строк; строки с большими номерами располагаются выше. При стирании строки таблица «поджимается», при добавлении новой, с промежуточным номером, нужная часть таблицы с большими номерами приподнимается. Изменения исходного текста производятся иначе: новые строки в порядке их введения и независимо от номеров записываются в хвост массива. Если строка редактируется или заново пишется строка с уже существовавшим номером, запись происходит аналогично, но из середины массива старая строка изымается, массив поджимается вверх.

При выводе строк на экран (командой LIST или для редактирования) коды операторов дешифрируются и заменяются словесными. Вот почему, если оператор был введён строчными буквами или в сокращённом виде, без пробелов, восстанавливается он в стандартном виде.

Пользователи этих машинок знают, что большинство операторов можно записывать двумя-тремя буквами как в начале строки, так и иногда в её середине (для сложных операторов); во многих ситуациях оператор можно не отделять пробелом от последующей информации, например от имени переменной. Но, с другой стороны, это порой приводит к недоразумениям, ошибкам, как, впрочем, использование имён переменных, содержащих более чем 2 символа (не считая специальных, указывающих тип переменной). В качестве имён нельзя использовать не только ключевые слова как таковые, но и сочетания их первых двух-трёх букв. Нельзя, например, ввести переменную IN, поскольку это начало оператора INPUT. Впрочем, путаница возникает в основном, когда имя переменной стоит слева от знака равенства в операторе присвоения; спастись можно, явно записав тип этого оператора - LET (что сейчас, как правило, никто не делает). При искажении написания оператора Бейсик ошибается. Так, оператор INPAT I превратится в INPUT AT I (появится синтаксическая ошибка).

Оператор IFI=A THI=4 - правильный (переменной I может быть присвоено значение 4), оператор IFI=A THIN=4 - неправильный. Оператор IFA THLETIN=4 - опять правильный. При записи строк в массив исходного текста числовыми кодами заменяются только первые (ведущие) операторы.

При записи текста программ можно сокращённо писать операторы (ключевые слова), но запись ведущих (первых в строке) операторов на длину программы, как видим, не влияет.

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

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

Заполняются адреса в таблице TAB (для объектного кода).

После компиляции система автоматически переходит к отработке объектного кода (интерпретации), хотя при экспериментах её можно «обмануть»: если первый выполнимый оператор END или STOP, обработка объектного кода тут же заканчивается (хотя весь код сформирован).

Перед интерпретацией вслед за «объектным кодом» система отводит в памяти область для символьных переменных - по умолчанию 256 байтов, но оператором (командой) CLEAR эту область можно изменить; далее отводится область для самих массивов; символьные переменные и массивы накапливаются динамически, например, массивы по мере отработки операторов DIM или явочного использования их элементов. Такая инициализация массива или текстовой переменной приводит к заполнению в дескрипторе адреса его расположения.

«Снизу» используемая память тоже динамически наращивается. «Перед» областью задания переменных располагаются подобия стеков для циклов и для возвратов из подпрограмм. Притом если несколько циклов расположены последовательно, область, занимаемая для них, не растёт; она увеличивается при появлении вложенных циклов.

Ошибка 7 - переполнение памяти - фиксируется, когда заполнение памяти «сверху» и «снизу» смыкается.

В виде таблицы использование памяти Бейсиком можно изобразить так.

Адреса или условные имена областей

Содержание

1000-1777

Стек

2000-2421

Системные переменные

2422-3021 (BUF)

Буфер ввода (с клавиатуры)

3022-3051 (BUFOUT)

Буфер вывода

3052, до TXEND(TEXT)

Текст - источник программы

От TXEND до STREG

Объектный код программы (начиная с чётного адреса)

От STREG, на заданную величину

Область для символьных переменных

От конца области символьных переменных до ENDKOD

Область для массивов

 

Свободная область (если остаётся)

От CICL до LIMIT

Дополнительный стек для циклов

От LIMIT до LENT

Дополнительный стек для возвратов из подпрограмм

От LENT до ТАВТОР

Область переменных (и дескрипторов)

От ТАВТОР до FCB

Таблица TAB номеров строк и адресов программы

От FCB до HIMEM

Область для обмена данными с магнитофоном; занимает 10228 байта (фактически буфера ввода-вывода для команд LOAD, SAVE и др.)

HIMEM

Начало видеопамяти (по умолчанию 40000) или предельный адрес, установленный командой CLEAR

Конкретные адреса устанавливаются системными переменными. Разговор о них уже начинался в [Л]. Ниже приводим более полные сведения (имена переменных условны)

Адрес

Усл. имя

Назначение

2000

LIG

Индекс режима работы системы

2002

TXEND

Указатель (адрес) конца текста - источника программы

2004

STREG

Указатель конца объектного кода, начало области символьных переменных

2006

STSIZ

Длина области символьных переменных

2010

STFRE

Начало свободной части в области символьных переменных

2012

STLEN

Длина свободной части области символьных переменных

2014

ENDKOD

Конец области массивов, а если их нет - области символьных переменных

2016

CICL

«Вершина» стека для циклов

2020

LIMIT

«Вершина» стека для возвратов из подпрограмм

2022

LENT

Начало области переменных

2024

ТАВТОР

«Вершина» таблицы номеров и адресов строк (TAB)

2026

FCB

Начало области для связи с магнитофоном

2030

HIMEM

Указатель предельно используемого адреса ОП

2032-2044

 

Различные рабочие переменные

2046

HALT

Адрес (в объектном коде) останова программы для оператора CONT

2050

 

«Флаг» режима трассировки

2052

 

«Флаг» режима автонумерации

2054

 

Приращение номеров строк для автонумерации

2056

CURLIN

Указатель текущего положения курсора

2060-2077

 

Рабочие ячейки

2100-2123

TABUS

Таблица начальных адресов для машинных подпрограмм USRi

2124-2137

 

Рабочие ячейки

В т.ч. 2126

PF

Адрес блока параметров для функции АТ

2134

SAVJMP

Хранение адреса возврата из подпрограммы

2140-2411

 

Тексты для ключей

2414

 

Адрес драйвера записи

2416

IODEV

Указатель устройства ввода-вывода

2420

INK

Буферная ячейка для оп. INKEYS

5. Имена переменных и дескрипторы

На имя переменной или массива отводится два байта, притом делается попытка уместить не только два символа имени, но и тип переменной (который может определяться третьим или вторым - служебным символом, добавляемым к имени), а также указание на то, что именем определяется массив или функция. «Выгадывание» производится за счёт отбрасывания старших разрядов байта - 3 разрядов для первого символа имени и 2 разрядов для второго. Это оказывается возможным потому, что первый символ может быть только латинской буквой (причём заглавные и строчные буквы не различаются), а второй - латинской буквой или цифрой.

Разряды двухбайтового слова разделяются так (вот здесь можно сказать: «шитое» - из кусков - имя):

Если второго (не служебного) символа в имени нет, соответствующие разряды нулевые. Для простых переменных разряды 6,7 - нулевые. Что касается битов 15-13, для разных типов переменных соответствие следующее:

двойная точность (служебный символ # или его нет)

- 000

одинарная точность (служебный символ !)

- 001

целые переменные (служебный символ %)

- 010

текстовые переменные (служебный символ $)

- 111

Так, имя АВ % превращается в код 404028

Дескриптор массива занимает 6 байтов. Первое двухбайтовое слово - его имя (разряд 7 - единичный), следующее слово - адрес его местоположения (заполняется динамически), следующее слово в младшем байте содержит размерность массива (количество индексов), в старшем байте - длину одного элемента массива (2 - для целочисленных, 8 - для массивов с «длинными» нормализованными числами, 6 - для массивов текстовых переменных). Адрес местоположения массива (в соответствующей области) указывает на заголовок массива; заголовок содержит столько двухбайтовых слов, сколько индексов имеет массив; в каждом слове - максимальное значение индекса, увеличенное на 1, поскольку минимальное значение индекса - нулевое, причём начиная с правого индекса. Заметим, что в системе Бейсика - ошибка, правильно работают только одно- и двухиндексные массивы. После заголовка в нужном порядке, по оговорённому количеству байтов, записываются элементы массива. Однако для массива символьных переменных элементами массива являются «дескрипторы второго порядка». Каждый содержит 3 байта. Первый из них - длина данного символьного элемента (в байтах), два следующие - независимо от того, у которого чётный или нечётный адрес, - указывают начало расположения символьного элемента (подмассива) в области текстовых переменных.

Дескриптор текстовой переменной, кроме имени, содержит два двухбайтовых слова - длину переменной (в байтах) и адрес её расположения в области текстовых переменных.

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

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

6. Особенности работы объектного кода

Объектный код в данном случае представляет собой псевдопрограмму, командами в которой служат адреса обрабатывающих подпрограмм; в ту же последовательность включаются адреса данных и сами данные непосредственно. Константы из текста-источника, как правило, перекопируются во вторичный код (с переводом во внутримашинное представление), переменные в объектном коде определяются адресами, причём адреса указывают не на имена, а на содержимое переменной или на начало дескриптора. Текстовые константы остаются в тексте-источнике, из объектного кода на них производятся ссылки по адресам. В общих чертах обработка объектного кода описана в [Л]. Там же приведены частичные таблицы адресов обрабатывающих подпрограмм. Уточним некоторые детали.

Более чем 2-байтовые числа располагаются в объектном коде «вверх ногами», т.е. сначала записываются слова с младшими разрядами мантиссы, а затем старшими разрядами и порядком. Текстовые переменные пересылаются в стек и из стека так же, как 4-байтовые числа подпрограммами с адресами 1562148 и 1563308, поскольку пересылаются фактически их 4-байтовые дескрипторы. Пересылками самих текстовых переменных из области их хранения в область хранения занимаются такие подпрограммы, как SQUEE, NEWSTR (160634, 156672) и др.

Оператор DIM обрабатывается подпрограммой ARRAY (160226), которой даётся ссылка на дескриптор массива и количество индексов. Предварительно в стек засылается максимальное значение индекса, заданное в операторе DIM. Аналогично выбирается значение элемента массива: в стек засылается значение индекса (или последовательно значения двух индексов), затем подпрограмме INDEX (160056) даётся ссылка на дескриптор и количество индексов; эта подпрограмма формирует в стеке значение элемента.

Особым образом формируется обработка циклов, условных переходов, вызов функций и тому подобные действия Полное описание было бы слишком объёмистым для журнала.

Сам принцип составления подобных объектному коду «псевдопрограмм» изобрели, конечно, не разработчики данной версии Бейсика; иногда такая форма называется польской. Подпрограммы, обрабатывающие такую запись, завершаются командами перехода - обычно по регистру 4: JMP @(R4)+ (1348); благодаря автоинкрементации адрес в регистре 4 увеличивается и устанавливается на следующий элемент псевдопрограммы, так что переход СЧАК осуществляется прямо к началу следующей обрабатывающей подпрограммы, адрес которой выбран из элемента псевдопрограммы. Из обрабатывающей подпрограммы можно читать данные (адреса или числа), выбирая их из псевдопрограммы (например, командой MOV (R4)+,R1 (124018), которая тоже наращивает счётчик в R4). Для «входа в режим» обработки псевдопрограммы в R4 заносится адрес её начала, затем выполняется команда 134.

Из машинной программы ("настоящей") можно перейти к продолжающему её псевдокоду экономнее, обратившись к подпрограмме STAND, состоящей всего из двух команд:

166562:5726    (TST (SP)+)
166564:134     (JMP @(R4)+)

Вызов этой подпрограммы производится двумя словами: 4437, 166562. Чтобы выполнить обратный переход к машинным кодам, в очередном слове псевдопрограммы записывают адрес следующего слова ОП. Компактность такого приёма позволяет им достаточно часто пользоваться (он неоднократно используется и в обрабатывающей системе Бейсика, например при вычислении функций).

7. Итоги и возможности

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

Дополнением некоторых служебных программ можно для типовых задач сокращать объём псевдокода; вместо нуль-одноадресной имитируемой (виртуальной) машины перейти к одно-двухадресной.

В данной версии Бейсика единственная возможность использовать формальные параметры, записываемые списком, в скобках, имеется в функциях пользователя FN; однако у этих функций другие жёсткие ограничения: функция должна быть описана фактически одним арифметическим выражением и даёт один результат; нельзя этим способом, например, порождать графические образы. Специальная методика с использованием машинных подпрограмм (типа USR) позволяет совместить эти потребности.

Наборы машинных подпрограмм позволяют расширить возможности Бейсика, например, в области машинной графики; моделировать, например, «резиновую нить», не стирающую элементов изображения, по которым она проходит, получить быструю перекопировку или смещение фрагментов изображения (наподобие операторов PUT и GET в Бейсиках для ПВМ типа IBM PC) и так далее.

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

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

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

8. Примеры

В качестве несложного примера приведём текст машинной программы, перекопирующей прямоугольный фрагмент изображения с экрана в другое место; машинная подпрограмма работает примерно в 6 раз быстрее, чем двойной цикл на Бейсике с использованием операторов POINT и PSET. В ней используется подпрограмма системы Бейсика COLOR, начинающаяся с адреса 126402 (в регистрах 1 и 2 - координаты X, Y точки, в регистре 0 образуется код цвета, однако в специальных значениях, соответствующих символам управления цветом для экрана, что требует корректировки). Команда ЕМТ 30 - обращение к системному драйверу вывода точки, ему информация передаётся в регистрах 0,1,2. Для возможного упрощения исходные данные, определяющие размещение исходного и результирующего фрагментов, размещаются прямо «в теле» программы, чего не встретишь в программах, «зашитых» в ПЗУ. Для занесения исходных данных по нужным адресам в Бейсике используют операторы POKE, для обращения к подпрограмме - DEFUSR=&O37000 и A%=USR(0%).

Координаты X1, Y1 левого верхнего угла прямоугольника исходного фрагмента заносятся по адресам 370028 и 370048, координаты диагонального угла - по адресам 370608 и 370508, вектор переноса, т.е. разности координат для всех перекопируемых точек и их отображений, заносится по адресам 37024 и 37030. Текст подпрограммы (с адреса 37000) следующий;

37000:

12701

0

12702

0

37010:

4737

126402

162700

224

37020:

5400

62701

0

62702

37030:

0

104030

163701

37024

37040:

163702

37030

5202

22702

37050:

0

100356

5201

22701

37060:

0

100350

207

 

Ещё быстрее можно скопировать или запомнить в отведённом месте памяти фрагмент изображения, если пересылать информацию не по точкам (по одному или двум битам), а по байтам. При этом, правда, в горизонтальном направлении начало и конец копируемого моля должны задаваться координатами, кратными 4. Программу можно сделать так, что если значения координат другие, она добавит слева и справа дополнительные копируемые точки, но перемещать этим способом фрагмент изображения в горизонтальном направлении можно только шагами, кратными 4 точкам (мы имеем в виду режим работы «32 символа в строке»).

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

В программе на Бейсике адрес машинной подпрограммы задаётся, как и прежде, адрес начала области памяти для запоминания фрагмента засылается через функцию POKE в ячейку 37002 (если он постоянный - может быть включён как константа в кодировку машинной подпрограммы). Вызов подпрограммы выполняется таким фиктивным арифметическим (точнее, логическим) выражением:

А%=Х1%=(Y1%=(X2%=(Y2%=USR(I%)))).

Здесь А% - фиктивная переменная, X1% и Y1% - константы, переменные или выражения (обязательно целые), задающие левый верхний угол прямоугольного поля для фрагмента, Х2%,Y2% - аналогично - координируют правый нижний угол. I% - индекс, указывающий тип операции; если это 0% - выполняется сохранение изображения, в противном случае - его восстановление. Для восстановления запоминавшегося изображения значения X1% и Y1% могут быть другими (чем при запоминании), но разности Х2%-Х1% и Y2%-Y1% должны сохранить свою величину (правда, вторая разность может быть меньше, чем при запоминании). Записанное выражение синтаксически корректно, логические операции типа - безопасны в том отношении, что не вызывают переполнения при любых значениях операндов, а вложенные скобки заставляют параметры подряд накапливаться в стеке. После входа в машинную подпрограмму, если указатель стека в регистре 6 имеет значение к, вид стека такой:

Адрес

Содержимое

K

адрес возврата из машинной п/п (в среду Бейсика);

K+2

I% (параметр USR; этот адрес - в per. 5)

K+4

Y2%

K+6

Х2%

K+10

Y1%

K+12

X1 %

....

....

 

Текст подпрограммы с пояснениями:

37000:

1270

авт.

 

 

’засылка адреса начала массива в R4

37004:

6266

6

6266

6

’Х2%\4 (отн. номер байта конца строки). Операнд находится в стеке

37014:

16603

10

 

 

’пересылка Y1% в R3

37020:

16601

12

6201

6201

’пересылка X1% в R1 и вычисл. X1%\4

37030

10300

12702

6

6300 77202

’Y1% в R0. вычисл. Y1%*64

37042:

60100

 

 

 

’Y1%*64+Х1%\4 - номер байта относит. начала рабочего поля видеопамяти;

37044

63700

204

42700

140000

’в яч. 204 - относ, адрес начала рабочей зоны видеопамяти; 15 и 14-й разряды подавляются вследствие кольцевого строения видеопамяти

37054:

63700

202

 

 

'в яч.202 - начальный адрес видеопамяти; в R0 - абсолютный адрес нужного байта из видеопамяти

37060:

5715

1012

 

 

’переход к адресу 37110, если было I%≠0%

37064:

112024

5201

 

 

’пересылка байта из видеопамяти в отведённый массив и наращивание адресов в R1  и R4

37070:

26601

6

100355

 

’проверка Х2%≥Х1% и усл. переход к адресу 37020

37076:

5203

26603

4

100345

’переход к след. строке, проверка по У2% и усл. переход к ад. 37020

37106:

207

 

 

 

’выход из п/п

37110:

112420

765

 

 

’при восстановлении изображения: пересылка байта в видеопамять и переход к адресу 37070

 

Подпрограмма переместима, только следует учесть переадресацию ячейки 37002 (для программы на Бейсике).

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

Заменяя по адресу 37110 команду пересылки логическим сложением или «исключающим или» (придётся использовать две команды), можно получить эффект «наложения» запоминавшегося фрагмента на фоновое изображение.

(прим.gid: в тексте есть ссылки на список литературы, но самого списка в журнале не было. Он не поместился на странице.)



[1] От таблицы адресов строк до конца выделенной ОП на область ввода-вывода бронируется 10228 байта (адреса в ячейках 2026, 2030, «по умолчанию это 367568, 400008) Эту область можно использовать для машинных подпрограмм и данных, если не используются операции с магнитофоном (операторы BLOAD, В SAVE не в счёт). Однако содержимое ячейки с адресом MAXAD-4228 (по умолчанию 373568) должно быть нулевым, иначе при запуске программы (оператором RUN) система может зависать в ожидании закрытия файла, читаемого с ленты.

[2] В некоторых случаях от adr-10008 до видеопамяти.

[3] Точнее — разряды регистра PSW (ССП).

[4] Номера разрядов считаем справа налево, начиная с нуля.

[5] Имена подпрограмм и блоков условны.

Performed by © gid, 2012-2024.