Искра-226/Кодирование BASIC-файлов
| Этот документ создан для Emuverse и распространяется на условиях лицензии CC-BY-SA-3.0. |
Оттранслированное представление BASIC 02 — внутренний формат записи программы Искра-226, включаемый параметром T оператора SAVE DC. Имена операторов и функций заменены однобайтовыми токенами, имена переменных — индексами; текст в потоке остаётся только внутри строковых литералов.
Внешняя структура файла, указатель каталога и заголовочный сектор описаны в статье Файловая система; оттранслированное представление отмечено битом 0 байта 9 заголовочного сектора. Смысл дисковых операторов — в статье Файлы данных BASIC 02.
Сборка потока
Склеить содержимое секторов, беря с каждого ровно 254 байта, без обрезки хвостовых нулей. Далее смещения даны от начала полученного потока.
Пролог
| Смещение | Размер | Значение |
|---|---|---|
| 0 | 2 | L1 — длина таблицы 1 в байтах, запись 8 байт |
| 2 | 2 | L2 — длина таблицы 2 в байтах, запись 4 байта |
| 4 | 2 | L3 — длина таблицы 3 в байтах, запись 4 байта |
| 6 | L1 | таблица 1 |
| 6+L1 | L2 | таблица 2 |
| 6+L1+L2 | L3 | таблица 3 |
Длины — 16-битные, старший байт первым. Число записей: L1/8, L2/4, L3/4.
Программа начинается по смещению 6 + L1 + L2 + L3.
Четырёхбайтовое поле
У части файлов между таблицами и первой записью строки лежат четыре лишних байта: 0A 19 и двоично-десятичное число, своё у каждого файла. Назначение не установлено.
Наличие поля определяется проверкой записи по вычисленному смещению: оба байта номера строки — правильное двоично-десятичное число, байт длины не меньше единицы, байт по адресу p + 2 + len равен FE. Если проверка не проходит, а по смещению +4 проходит — поле присутствует.
Записи строк
<номер строки: 2 байта BCD> <len: 1 байт> <тело>
Записи разделены байтом FE. Перед первой разделителя нет, после последней он есть и служит признаком конца потока.
len считается включая сам байт len; следующий разделитель стоит по адресу адрес(len) + len.
Записи пересекают границу сектора; служебные байты в длину не входят.
Выравнивание
Если запись не помещается в остаток сектора, остаток заполняется нулями. Правило: после разделителя FE по смещению q
b = (⌊q / 254⌋ + 1) × 254 если b > q и все байты потока с q по b−1 нулевые, то q = b
Переход к следующему сектору выполняется только тогда, когда весь остаток текущего 254-байтового куска нулевой. Длина выравнивания доходит до шести байт.
Строка с номером 0 кодируется байтами 00 00 <len>; безусловный пропуск пары нулей перед номером строки разрушает разбор таких файлов.
Операторы
<токен глагола: 1 байт> <len: 1 байт> <операнды: len байт>
len — длина только операндов; len = 00 означает оператор без операндов. Явная длина позволяет разрезать программу на операторы, не зная значений токенов, и служит проверкой синхронизации.
Пример разбора
Строка 2000 файла #СТАТИСТ, длина 0x54 = 84:
20 00 54
23 01 23 GOSUB ' 35
4C 39 E3 1F «БУДЕТЕ …» DD 00 DD E3 12 «…» DD
PRINT "…";F¤;"…";
41 01 5C INPUT F%
36 03 5D D9 0E N7=N3
24 06 0E D7 11 D3 20 01 IF N3<N6THEN2001
36 03 5D D9 11 N7=N6
Таблица глаголов
Значения назначены тематическими группами. Составные дисковые команды получают один токен на всю фразу.
| Токен | Оператор | Токен | Оператор |
|---|---|---|---|
1E |
IF END THEN | 52 |
NEXT |
21 |
GOTO | 53 |
REWIND |
22 |
GOSUB | 54 |
SELECT |
23 |
GOSUB' | 55 |
BACKSPACE |
24 |
IF | 56 |
REM |
25 |
KEYIN | 57 |
FOR |
26 |
ON | 58 |
SKIP |
27 |
DEFFN' | 59 |
END |
28 |
PRINTUSING | 5A |
DEFFN |
29 |
DATA | 5B |
DATA RESAVE |
2A |
SAVE | 5C |
RES |
2B |
RENUMBER | 5D |
UNPACK( |
2C |
CLEAR | 5E |
RETURN |
2D |
LOAD | 5F |
TRACE |
2E |
LIST | 61 |
OR( |
2F |
RUN | 62 |
XOR( |
30 |
RETURN CLEAR | 63 |
ADD C |
33 |
LIST S | 64 |
INIT |
34 |
ON ERROR | 65 |
LIST RES |
35 |
LET | 66 |
DATA LOAD BT |
36 |
присваивание без ключевого слова | 68 |
DATA SAVE BT |
37 |
COM CLEAR | 6D |
COPY |
38 |
TRACE OFF | 6E |
DATA SAVE BA |
39 |
DATA LOAD | 6F |
DATA SAVE DA |
3A |
DEFFN' с текстом клавиши | 70 |
DATA LOAD BA |
3F |
% — краткая форма REM |
71 |
DATA LOAD DA |
40 |
$GIO | 72 |
LOAD DA |
41 |
INPUT | 73 |
SAVE DA |
42 |
STOP | 74 |
DATA LOAD DC |
43 |
AND( | 75 |
DATA LOAD DC OPEN |
44 |
READ | 76 |
DATA SAVE DC |
45 |
BOOL | 77 |
DATA SAVE DC CLOSE |
46 |
DIM | 78 |
DATA SAVE DC OPEN |
47 |
CONVERT | 79 |
DBACKSPACE |
48 |
PACK( | 7A |
DSKIP |
49 |
DATA SAVE | 7B |
LIMITS |
4A |
ADD | 7C |
LIST DC |
4B |
BIN( | 7D |
LOAD DC |
4C |
7E |
MOVE | |
4D |
ROTATE | 7F |
MOVE END |
4E |
COM | 80 |
SAVE DC |
50 |
HEXPRINT | 81 |
SCRATCH |
51 |
RESTORE | 82 |
SCRATCH DISK |
83 |
VERIFY |
Таблица прочитана из интерпретатора на загрузочном диске: с адреса 0x1840 лежит алфавитный список 51 однословного ключевого слова без разделителей, с 0x192E — 33 записи сочетаний вида «индекс первого слова плюс 0x60, текст продолжения», с 0x19B6 — 84-байтовый массив перестановки, дающий каждой записи её токен.
DEFFN' в таблице один, с токеном 3A; токен 27 (форма с формальными параметрами) машина ставит сама при разборе.
RES (5C) — самостоятельное ключевое слово; RESTORE собирается из RES и продолжения TORE. Что делают RES и LIST RES, документация не описывает.
Двухбайтовые глаголы
Матричные, символьные и графические операторы кодируются префиксом 06 и подкодом; байт длины стоит после подкода:
06 <подкод> <len> <операнды>
| Подкод | Оператор | Подкод | Оператор |
|---|---|---|---|
00 |
PLOT | 15 |
DRAW |
01 |
MAT присваивание | 19 |
NPLOT |
02 |
MAT REDIM | 1A |
¤MOVE
|
03 |
MAT READ | 1B |
TURN |
04 |
MAT INPUT | 1C |
STRETCH |
05 |
MAT PRINT | 1D |
FRAME |
06 |
MAT COPY | 1E |
LABEL |
09 |
MAT MOVE (?) | 1F |
¤COPY
|
0A |
MAT SEARCH | 22 |
¤LET
|
0B |
MAT SORT (?) | 23 |
WINDOW |
0C |
¤TRAN( |
24 |
LINPUT |
0D |
¤PACK (?) |
25 |
ASMB |
0E |
¤UNPACK (?) |
26 |
REPLACE |
0F |
¤OPEN |
||
13 |
DOT | ||
14 |
DDRAW |
Имена ORIGIN, DNPLOT и NDRAW встречаются в текстовых листингах, но подкодов не имеют: все подкоды, встречающиеся в оттранслированных файлах, уже опознаны.
Действие графических операторов и формат самого рисунка описаны в статье Графический буфер BASIC 02.
Таблица операндов
Токены операндов прочитаны из интерпретатора с адреса 0x1A0C: пул слов и таблица 16-битных указателей на них с адреса 0x1B18, младший байт первым, 114 слов. Токен слова — его номер в пуле плюс смещение прогона; прогонов четыре, по числу позиций разбора.
Двузначные токены
Часть токенов читается по-разному в зависимости от того, ожидается на их месте знак операции или очередной операнд. Отдельного признака в файле нет, различает состояние разбора.
| Токен | В позиции операции | В позиции операнда |
|---|---|---|
D2 |
STEP | параметр T оператора SAVE DC
|
D5 |
<> |
AT(; ' в $GIO
|
D6 |
<= |
BEG; ¤ после DATA SAVE DC и SAVE DC
|
D7 |
< |
END |
D8 |
>= |
ROUND(
|
D9 |
= |
OPEN |
DB |
— | # — номер строки таблицы устройств; «2» — второй аргумент VAL( и BIN(
|
DC |
/ — деление |
/ — префикс адреса устройства
|
DE |
, |
однобайтовый литерал: значение в следующем байте |
DF |
* |
TAB(
|
E0 |
^ |
ссылка на массив целиком |
E5 |
XOR | дробная константа |
E6 |
OR | константа с порядком |
E7 |
AND | константа, 2 байта BCD |
E8 |
ARC | константа, 1 байт BCD |
E9 |
- бинарный |
- унарный
|
EC |
TRN | POS(
|
ED |
INV | LEN(
|
EE |
IDN | NUM(
|
EF |
ZER | VAL(
|
F0 |
CON | FN<имя>(
|
Ключевые слова TRN, INV, IDN, ZER и CON встречаются только внутри оператора MAT. Слова OPEN и ARC взяты из таблицы интерпретатора; в оттранслированных файлах D9 встречается только как знак равенства, а E8 — только как константа.
Однозначные токены
| Токен | Значение |
|---|---|
00–C9 |
ссылка на переменную по индексу |
CA |
FROM |
CB |
ALL |
CC |
GOSUB в составе ON … GOSUB
|
CD |
GOTO в составе ON … GOTO
|
CE, CF |
буквы B и F
|
D0 |
)
|
D1 |
TO |
D3 |
THEN, далее 2 байта BCD — номер строки |
D4 |
>
|
D9 |
=
|
DD |
;
|
E1 |
STR(
|
E2 |
HEX(: E2 <len> <len байт>
|
E3 |
строковый литерал в кавычках: E3 <len> <len байт КОИ-8>
|
E4 |
строковый литерал в апострофах, кодирование то же |
E8 |
константа, 1 байт BCD |
EA |
+
|
EB |
( — группирующая скобка
|
F1 |
#PI
|
F2–F8 |
ABS(, INT(, RND(, SGN(, SQR(, LOG(, EXP(
|
F9–FE |
SIN(, COS(, TAN(, ARCSIN(, ARCCOS(, ARCTAN(
|
FE |
вне выражения — разделитель записей строк |
Ссылками на переменные являются значения 00–C9, а не 00–BF: первый токен — CA. Индексы C0–C9 встречаются в программах более чем со 192 переменными.
Два вида строковых литералов
Строки в кавычках система выводит как строки из прописных букв, строки в апострофах — из строчных.[1] Так на машине без строчных букв на клавиатуре задаются строчные символьные константы.
| Токен | Форма записи | Регистр |
|---|---|---|
E3 |
"…" |
прописные |
E4 |
'…' |
строчные |
Числовые константы
| Токен | Формат |
|---|---|
DE |
сырой байт, не BCD — байтовые константы и адреса устройств |
E5 |
описатель + BCD |
E6 |
то же + байт порядка |
E7 |
2 байта BCD, 0–7999 |
E8 |
1 байт BCD, 0–99 |
В описателе старшая тетрада — количество цифр до запятой, младшая — общее количество цифр. Цифры идут по две на байт, незанятая последняя тетрада — нуль.
| Запись | Байты | Описатель |
|---|---|---|
.5 |
E5 01 50 |
0 / 1 |
2.5 |
E5 12 25 |
1 / 2 |
.196854 |
E5 06 19 68 54 |
0 / 6 |
51840 |
E5 55 51 84 00 |
5 / 5 |
2.718281828 |
E5 1A 27 18 28 18 28 |
1 / 10 |
1E6 |
E6 11 10 06 |
1 / 1, порядок 6 |
Токен E5 служит и для целых чисел больше 9999 — всего, что не помещается в E7; дробной части при этом нет, обе тетрады описателя равны.
Длина хвоста — 1 + ⌈младшая тетрада / 2⌉ байт для E5 и на один байт больше для E6.
Верхняя граница E7 — 7999: восемь тысяч кодируются общей формой E5 44 80 00.
Запись с порядком по значению не восстанавливается: 1E6 и 1000000 — одно число, но разные байты.
Номер метки в GOSUB' и первый байт операндов DEFFN' хранятся двоичными, не в BCD. То же относится к номерам строк, которые записываются сырыми парами BCD без токена: диапазоны в SAVE, LOAD, SAVE DC, LOAD DC, RUN, номер строки у RESTORE, IF END THEN, KEYIN и LIST S.
Таблицы переменных
Таблица 1
Описывает переменные, которым нужен дескриптор размера: массивы и символьные переменные с явно заданной длиной, независимо от того, объявлены они в DIM или в COM. Числовые скаляры и символьные переменные с длиной по умолчанию в таблицу не попадают.
Запись — 8 байт:
| Смещение | Размер | Значение |
|---|---|---|
| 0–1 | 2 | адрес переменной в памяти, младший байт первым |
| 2–3 | 2 | тип: 00 08 — символьная, 2D 08 — числовая или целая; иное значение — вторая размерность
|
| 4–5 | 2 | число элементов (первая размерность), младший байт первым |
| 6–7 | 2 | размерный код, младший байт первым |
Размерный код равен 2 × размер элемента, младший бит означает, что длина задана явно:
- целая — 4 (2 байта);
- числовая — 16 (8 байт);
- символьная длины L —
2L + 1; - символьная с длиной по умолчанию (16) — 32, код чётный.
Нечётность кода не является признаком символьного типа; тип берётся из байтов 2–3 либо из флага таблиц 2 и 3.
Двумерные массивы. Вторая размерность записывается в байты 2–3, на место признака типа; первая остаётся в поле числа элементов. Размерности не перемножаются, DIM восстанавливается точно. Практическое правило: старший байт поля 2–3 равен 08 — массив одномерный, иначе поле занято второй размерностью.
Исключение — неявно объявленные массивы: у переменной, к которой обратились с индексом без DIM, число элементов равно 10, а в байтах 2–3 стоят значения 01 27 или 07 59, размерностями не являющиеся. Такие массивы следует выводить как одномерные (10).
Порядок записей обратен порядку объявления: память выделяется сверху вниз, а таблица отсортирована по возрастанию адреса.
Размер выделяемой памяти:
- скалярная символьная переменная — объявленная длина, округлённая вверх до чётной;
- массив —
N × размер элемента + 6, где 6 байт занимает дескриптор.
Адреса могут быть нулевыми: у программ, которые ни разу не выполнялись, и у защищённых файлов. Правила, опирающиеся на разность адресов, там неприменимы.
Таблицы 2 и 3
Образуют один непрерывный массив дескрипторов — по записи на каждую переменную программы. Формат одинаков, граница определяется счётчиками пролога.
| Смещение | Размер | Значение |
|---|---|---|
| 0–1 | 2 | адрес переменной, младший байт первым; 0000, если память не выделена
|
| 2 | 1 | флаг типа |
| 3 | 1 | назначение не установлено |
Записи идут в порядке убывания индекса переменной. Если
N = L2 / 4 + L3 / 4
то переменная с индексом i описана записью на позиции N − 1 − i, считая позиции подряд: сначала таблица 2, затем таблица 3. Отсюда N — общее число переменных программы.
Таблица 3 приходится на самые младшие индексы — те, что занимает оператор COM.
Флаг типа:
| Бит | Значение |
|---|---|
0 (0x01) |
у переменной есть дескриптор в таблице 1 |
| 3 | назначение не установлено |
4 (0x10) |
числовая действительная |
5 (0x20) |
символьная |
| 7 | назначение не установлено |
| ни 4, ни 5 | целая (суффикс %)
|
Связь с таблицей 1. Число записей с установленным битом 0 равно L1 / 8, и соответствие порядковое:
k = 0
для pos = 0 … N−1:
если флаг[pos] и 1:
дескриптор переменной (N − 1 − pos) = запись таблицы 1 номер k
k = k + 1
Это даёт точное соответствие «индекс переменной → запись таблицы 1» без разбора операторов DIM и COM. Сопоставление по порядку объявления ошибочно: COM объявляет и обычные скаляры, у которых дескриптора нет, а после MAT REDIM в операнде DIM стоят «теневые» индексы.
У переменных с дескриптором таблицы 1 поле 0–1 содержит не адрес, а небольшое число, растущее примерно на восемь с каждой такой переменной. Назначение не установлено.
Состояние на момент сохранения
Оттранслированный файл — снимок памяти после запуска, а не перевод текста.
Массив, выросший при выполнении оператора MAT REDIM за пределы исходного выделения, получает новый дескриптор в таблице 1 и новый индекс переменной, причём интерпретатор правит операнд в самом операторе DIM, оставляя код и MAT REDIM работать со старым индексом. Если новый размер не превышает объявленного, память переиспользуется и запись остаётся одна.
Поэтому операторы DIM как источник соответствия «индекс — дескриптор» непригодны.
Индексы и имена переменных
Индексы назначаются в порядке первого появления переменной в тексте программы. Операторы COM и DIM делят единое пространство индексов; COM выполняется первым и получает индексы с нуля.
Ключ переменной — имя вместе с признаком массива: A, A%, A¤, A() и A%() — разные переменные с разными индексами.
Имена переменных в оттранслированном файле не сохраняются. Таблицы содержат только дескрипторы памяти. Читаемый листинг из такого файла восстановить нельзя: восстанавливается структура программы, тип каждой переменной, «массив или скаляр» и размерности, но не имена.
Операнды
Дисковые операторы
Первый байт операндов — код устройства или режима, а не ссылка на переменную:
| Байт | Значение |
|---|---|
00 |
F — первый дисковод
|
01 |
R — второй дисковод
|
02 |
T — обращение по номеру строки таблицы устройств (#n)
|
79 01 D6 DBACKSPACE BEG 7A 01 D7 DSKIP END 76 01 D7 DATA SAVE DC END 79 04 DB E8 01 DE D6 DBACKSPACE #1,BEG 76 07 D6 DB E8 02 DE E0 12 DATA SAVE DC ¤#2,W() 82 0B 01 06 D9 E8 05 DE D7 D9 E7 10 00 SCRATCH DISK RLS=5,END=1000
В последнем примере 06 — ключевое слово LS, D9 — знак равенства, D7 — END. Байт 05 после законченного выражения у DSKIP и DBACKSPACE — параметр S («в секторах»).
Формы SAVE DC, LOAD DC, LIST DC:
80 <приставка> [D2 = T] [EB <старое имя либо резерв> D0] <имя> [<нач> [DE <кон>]] 7D <приставка> <имя> [<нач> [DE <кон> [DE <куда продолжать>]]] 7C <приставка> [<имя>]
Списки
Список принимающих переменных пишется без разделителей:
41 02 0A 0B INPUT T0,N0 74 03 05 0C 0D DATA LOAD DC A1¤,E1,E2 46 04 00 01 02 03 DIM X(300),Y(300),N(2),G¤(300)5
Список выражений разделяется DE:
76 06 D6 05 DE 0A DE 0B DATA SAVE DC ¤A1¤,T0,N0
Пропущенный параметр не кодируется ничем — между разделителями просто нет операнда:
06 1E 09 E0 12 DE DE DE 11 E8 01 D0 LABEL B6¤(),,,B3¤(1)
Индексация массивов
У обращения к элементу массива нет открывающей скобки: ссылка на переменную сама открывает список индексов, а D0 его закрывает. Токен EB используется только как группирующая скобка в выражениях и для размерностей в MAT REDIM.
01 32 D0 → 01(32) EB 34 E9 33 E9 E8 01 D0 → (34*33*1)
Ссылка на массив целиком — E0 <индекс>, законченное значение: после неё разбор ожидает операцию или следующий элемент списка. В операторах, операнды которых — список значений, возведения в степень быть не может, поэтому E0 там всегда ссылка на массив.
Скобки
| Конструкция | Открывающая | Закрывающая |
|---|---|---|
| индекс массива | нет | D0
|
STR(, ABS(, SQR( и прочие явные функции |
в токене | D0
|
POS(, LEN(, NUM(, VAL( |
в токене | нет |
AT( |
в токене | нет |
TAB(, ROUND( |
в токене | D0
|
| группировка в выражении | EB |
D0
|
У глаголов, имя которых оканчивается на (, закрывающей скобки в потоке нет:
| Глагол | Где находится закрывающая скобка |
|---|---|
43 AND(, 61 OR(, 62 XOR( |
в конце операторной части |
48 PACK(, 5D UNPACK(, 06 0C ¤TRAN( |
сразу за литералом формата |
4B BIN( |
после приёмника, вместе со знаком =
|
Неявные функции считаются по уровням, а не одним счётчиком. Закрывающая скобка вложенной явной конструкции неявную функцию не закрывает:
EC E1 E0 2A E8 01 D0 D7 DE 20 D5 E8 00 D3 02 70 POS(STR( V¤() , 1 ) < 20 ) <> 0 THEN 270
Здесь D0 закрывает только STR(, а POS( заканчивается на D5.
Неявная первая запятая
Функция STR( первую запятую не кодирует: STR(Z¤,67) → E1 10 E8 67 D0. Вторая и последующие кодируются DE. У прочих операторов неявной первой запятой нет.
Первый аргумент STR( индексируется только по признаку массива из таблиц переменных: заглядывание вперёд читает STR(Z¤,67) как Z¤(67). То же относится к приёмнику BIN(, приёмникам INIT и KEYIN и имени файла у LIMITS — за ними сразу идёт следующий операнд, без разделителя.
Что не кодируется
| Оператор | В потоке | В листинге |
|---|---|---|
INIT |
64 04 E3 01 2D 15 |
INIT ("-")U¤ — значение в скобках, запятой нет
|
CONVERT |
47 0F 48 D1 E1 … D0 E3 02 «##» |
CONVERT P% TO STR(…),(##)
|
$GIO с адресом |
40 0D DC DE 34 DE E2 06 … 22 |
¤GIO /34,(HEX(…),A¤)
|
VERIFY |
83 … 02 DB 39 E8 01 D0 DE 16 … 17 … |
VERIFY T#D%(1),(X(1),Y(1))
|
¤TRAN( |
06 0C 06 0D DE 12 D0 DE 00 |
¤TRAN(Q¤,L0¤)R — запятая после ) не печатается
|
LABEL |
06 1E 0A E0 1F E8 03 DE DE DE E3 01 51 |
LABEL B¤()3,,,"Q" — размер вплотную к имени
|
MAT |
06 01 04 E0 54 D9 EF |
MAT C%=ZER — имя массива без ()
|
RESTORE |
51 05 E8 01 DE 48 50 |
RESTORE 1,4850
|
SAVE, LOAD |
2A 0A DD 10 52 15 DE 52 15 DE 52 25 |
SAVE Z¤5215,5215,5225
|
LINPUT |
06 24 2C E3 28 … E9 32 |
LINPUT "…",-L¤ — запятая перед унарным минусом
|
INPUT с приглашением |
41 14 E3 11 «…» 00 |
INPUT "…",K — запятая после литерала
|
Оператор SELECT
Первый байт операндов — код группы устройств, записи разделяются DE. Ширина строки после EB — двоичное 16-битное число, старший байт первым.
| Код | Группа | Пример |
|---|---|---|
00 |
#n — строка таблицы устройств |
00 01 18 = #118; 00 01 18 00 = #118F
|
01 |
D — градусы |
операндов нет |
02 |
R — радианы |
операндов нет |
03 |
G — грады |
операндов нет |
05 |
P — пауза после строки |
05 01 = SELECT P1
|
06 |
LIST | 06 0C
|
07 |
07 0C EB 00 82 = SELECT PRINT0C(130)
| |
08 |
PLOT | 08 10 DE …
|
09 |
TAPE | 09 1B EB 1F 3F
|
0A |
DISK | 0A 18 01 = SELECT DISK18R
|
0B |
CI | |
0C |
CO | 0C 05 EB 00 50
|
В записях группы 00 дисковод указывается не всегда: SELECT #118 даёт трёхбайтовую запись, SELECT #118F — четырёхбайтовую.
Параметры оператора CLEAR кодируются той же таблицей: 11 — V, 12 — N, 14 — P.
Цепочка операторов DATA
Два последних байта операндов каждого оператора DATA — указатель на следующий оператор DATA, младший байт первым; у последнего в программе он равен 00 00. По этой цепочке работают READ и RESTORE. Значения — адреса в памяти интерпретатора, а не смещения в файле, и из текста программы не восстанавливаются.
Эти два байта нельзя разбирать как токены.
Тело оператора — список значений без разделителей:
29 1E E8 31 E7 03 34 E8 00 E8 28 E7 03 06 E8 31 … C9 04
31 334 0 28 306 31
Поэтому внутри DATA токены E5, E6 и E7 всегда читаются как константы, а не как XOR/OR/AND. То же в MAT REDIM, где длина элемента идёт сразу за закрывающей скобкой:
06 02 0A E0 04 EB E7 01 26 D0 E7 01 28 MAT REDIM A¤(126)128
Подпрограммы с параметрами
| Глагол | Операнды |
|---|---|
23 GOSUB' |
<метка: 1 байт двоичный>, затем фактические параметры — выражения через DE
|
27 DEFFN' |
<метка> <4 байта адреса входа>, затем формальные параметры — индексы подряд, без разделителей
|
3A DEFFN' |
<метка> <4 байта адреса> <один литерал> — текст клавиши специальных функций
|
5A DEFFN |
<имя> <2 байта рабочего поля> <формальная переменная> <выражение>
|
Скобки списка параметров не кодируются.
23 0B 05 E8 01 DE E8 01 DE 0D E9 E8 02 GOSUB ' 5(1,1,N-2) 27 08 05 00 00 00 00 12 20 21 DEFFN ' 5(N1,D1,D2) 3A 08 1F 00 00 A4 03 E2 01 0D DEFFN ' 31 HEX(0D)
Четыре байта после метки у 27 и 3A — адрес входа, который интерпретатор заполняет при первом обращении; нулевыми они бывают только там, где подпрограмму ни разу не звали. При разборе их следует пропускать.
Токен 3A подпрограммы не задаёт: это текст, подставляемый при нажатии клавиши, и GOSUB' на него не переходит.
Функции пользователя
5A <имя> <lo> <hi> <формальная переменная> <выражение> F0 <имя> <выражение> D0
Имя функции — латинская буква либо цифра, записанная сырым кодом символа. Это не индекс переменной: пространства имён разные, одна и та же буква может быть и переменной, и именем функции. Знака равенства в потоке нет, тело идёт сразу за байтом формальной переменной. Два байта между именем и формальной переменной — рабочее поле; у последнего определения в программе там нули.
5A 48 00 00 2A EB 2A EA F2 2A D0 D0 DC E8 02 DEFFN H(X)=(X+ABS(X))/2
В операторах AND(, OR(, XOR( байт F0 в конце операндов — не функция, а шестнадцатеричная маска.
Разбор выражений
Два состояния: «ожидается операнд» и «ожидается операция».
- Сброс в «операнд»: начало операндов оператора, после
DE,DD,D9,D1, знаков сравнения, послеEBи функций, после любой бинарной операции. - Переход в «операцию»: после ссылки на переменную, константы, литерала,
D0. - Унарный минус (
E9в позиции операнда) состояние не меняет.
Заглядывать вперёд можно только в том состоянии, в каком лексема потом будет прочитана: DE в позиции операции — запятая, в позиции операнда — однобайтовый литерал, съедающий следующий байт.
Разрешение «массив или скаляр» после ссылки на переменную:
| Следующий токен | Вывод |
|---|---|
однозначный операнд (E8, E7, E3, EB, ссылка на переменную) |
список индексов |
однозначная операция (DC, EA, D9, D7 и прочие) |
продолжение выражения |
| двузначный токен | операция |
Левая часть присваивания: цели идут вплотную, без разделителей, до первого D9 на нулевой глубине.
36 07 0F 10 11 12 D9 E8 00 S1,S2,S3,M=0
Связки условий XOR, OR и AND равноправны и вычисляются слева направо;[1] старшинства между ними нет.
В операторе MAT SEARCH знак сравнения стоит в позиции операнда — сразу после запятой, отделяющей искомое значение:
06 0A 08 E0 20 DE D4 22 D1 E0 1E MAT SEARCH L4¤(),>A¤TON4¤()
Алгоритм разбора
- Прочитать запись каталога и заголовочный сектор; убедиться, что бит 0 байта 9 установлен.
- Собрать поток, беря с каждого сектора ровно 254 байта без обрезки хвостовых нулей.
- Прочитать L1, L2, L3; перейти к смещению
6 + L1 + L2 + L3; проверить наличие четырёхбайтового поля. - Построить таблицу переменных:
N = L2/4 + L3/4, индекс на позиции pos равенN − 1 − pos, тип из флага; записи с битом 0 по порядку сопоставить записям таблицы 1. - Для каждой записи строки: номер из двух байтов BCD, длина из третьего, тело до следующего
FE. Если по отсчитанной длинеFEнет — поток рассинхронизирован, разбор остановить. - После
FE: если весь остаток текущего 254-байтового куска нулевой, перейти к началу следующего. - Тело разрезать на операторы
<глагол><len><операнды>, учитывая двухбайтовые глаголы с префиксом06. - Перед разбором операндов учесть особые формы: у
DATAдва последних байта — указатель цепочки, у дисковых операторов первый байт — кодF/R/T, уGOSUB'иDEFFN'— двоичная метка, уSELECTиSCRATCH DISK— собственный формат. - Операнды — автомат состояний со стеком уровней скобок.
- Контроль: израсходовано ровно
len; стек пуст к концу оператора; число записей таблиц 2 и 3 с битом 0 равноL1 / 8.
Обработку файла прекращать на первом секторе с маркером 1C.
Не установлено
- Подкоды операторов
ORIGIN,DNPLOT,NDRAW. - Разметка аргумента в скобках у
MAT TRNиMAT INV; кодирование матричной арифметики (+,-,*,(K)*). - Кодирование отрицательного порядка числовой константы.
- Маска
hhу¤TRAN(; признаки*,%,#уMAT SEARCH; формыLIST V,LIST *,LIST %,LIST ¤; формаINPUT ¤<переменная>. - Грамматика операндов у глаголов
38,39,49,5B,65,7F: байты известны, вхождений нет. - Смысл операторов
RES(5C) иLIST RES(65). - Назначение четырёхбайтового поля
0A 19 <BCD>перед программой. - Байт 3 записей таблиц 2 и 3, биты 7 и 3 флага, поле 0–1 у переменных с дескриптором таблицы 1.
- Различение «строка-скаляр или массив строк» при нулевых адресах в таблице 1.
Примечания
Источники
- Баласанян В. Э., Богдюкевич С. В., Шахвердов В. А. Программирование на микроЭВМ «Искра 226». — М.: Финансы и статистика, 1987.
- Таблицы ключевых слов и операндов — из образа интерпретатора на загрузочных дисках, адреса 0x1840 и 0x1A0C.
- Реконструкция по 94 оттранслированным файлам (15 735 строк) и 28 текстовым листингам, в том числе 12 парам «текст + токены».