|
|
| Строка 1: |
Строка 1: |
| {{emuverse}} | | {{emuverse}} |
|
| |
|
| '''Формат файлов BASIC 02''' — способ хранения программ на дисках [[Искра-226]]. Файловая система унаследована от [[Wang 2200]], представление программы — оригинальное и с Wang несовместимо. | | '''Файловая система Искры 226''' — способ хранения файлов на дисках [[Искра-226]]. Унаследована от [[Wang 2200]] и совпадает с ней побайтово; содержимое файлов — оригинальное и с Wang несовместимо. |
|
| |
|
| Программа может храниться в двух видах: '''текстовом''' (плоский текст в [[КОИ-8]]) и '''токенизированном'''. Оба вида имеют одинаковую внешнюю структуру и различаются одним байтом в заголовочном секторе.
| | Статья описывает файловую систему, указатель каталога и текстовое представление программ. Отдельно вынесены: |
|
| |
|
| == Общее устройство ==
| | * [[Искра-226/Кодирование BASIC-файлов]] — токенизированная запись программы; |
| На диске располагаются физические сектора размером 128 байт — 77 дорожек, 26 секторов на дорожке, одна сторона. Физические сектора попарно объединены в логические размером 256 байт со сквозной нумерацией. Далее везде будут подразумеваться логические сектора.
| | * [[Искра-226/Файлы данных BASIC 02]] — записи, значения и способы доступа; |
| | * [[Искра-226/Графический буфер BASIC 02]] — формат рисунка. |
|
| |
|
| Файлу выделяется непрерывный диапазон секторов, границы которого записаны в «Указателе каталога». Раскладка внутри диапазона: | | |
| | == Диск == |
| | |
| | Физический сектор — 128 байт; 77 дорожек по 26 секторов, одна сторона. Физические сектора попарно объединены в логические по 256 байт со сквозной нумерацией. Далее везде подразумеваются логические сектора. Полный объём тома — 1001 сектор, 256 256 байт. |
| | |
| | Типы файлов: |
| | |
| | * '''ПФ''' — программный файл: программа на Бейсике в текстовом либо оттранслированном виде; |
| | * '''ФД''' — файл данных. |
| | |
| | Файлу выделяется непрерывный диапазон секторов, границы записаны в указателе каталога. |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| Строка 17: |
Строка 28: |
| | следующие N || поток содержимого | | | следующие N || поток содержимого |
| |- | | |- |
| | … || резерв, заполнен нулями | | | … || резерв, нули |
| |- | | |- |
| | последний выделенный || control record | | | последний выделенный || концевая запись |
| |} | | |} |
|
| |
|
| Таким образом, любой файл занимает на диске как минимум на 512 байт больше, чем размер его полезных данных.
| | Размер файла на диске не меняется, поэтому при перезаписи более коротким содержимым '''хвост не затирается''': там остаются фрагменты предыдущих версий, иногда в другом представлении. Обработку следует прекращать на первой встреченной концевой записи. |
| | |
| Размер файла на диске уменьшать нельзя, поэтому при перезаписи файла более коротким содержимым '''старые данные в хвосте не затираются'''. Там могут остаться читаемые фрагменты предыдущих версий файла, иногда в другом представлении. Их нельзя принимать за часть текущего файла, поэтому необходимо завершать обработку файла при нахождении первой control record. | |
|
| |
|
| Искра-226 поддерживает работу с двумя типами файлов: ПФ — «Программные файлы» и ФД — «Файлы данных». Программные файлы всегда содержат программу на Бейсике, в текстовом или упакованном токенизированном виде. Файлы данных, соответственно, данные, доступные из программ пользователя.
| | Диски прямого доступа (запись по абсолютным адресам секторов) указателя каталога не имеют; формат содержимого таких дисков задаёт программа пользователя. Загрузочные диски с интерпретатором имеют собственный формат: нулевой сектор начинается сигнатурой <code>06 90 09 90 07 90</code>, за ней 18 знаков имени и даты версии. |
| | |
| В соответствии с документацией, Искра-226 поддерживает прямой доступ программ к секторам диска, при этом к записанным таким образом дискам всё нижеизложенное может быть неприменимо, так как форматом хранения полностью управляет программа пользователя по своему усмотрению.
| |
| | |
| Также, в наличии имеется несколько системных дисков, на которых содержатся различные версии интерпретатора Бейсика, с которых производится начальная загрузка компьютера. Эти диски имеют свой формат и здесь не рассматриваются.
| |
|
| |
|
| == Указатель каталога == | | == Указатель каталога == |
| Начиная с 0 сектора на диске располагается «Указатель каталога».
| |
|
| |
|
| Первые 16 байт содержат служебную информацию в следующем составе: | | Начинается с сектора 0. Первые 16 байт нулевого сектора — параметры диска: |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| ! Смещение !! Размер !! Значение | | ! Смещение !! Размер !! Значение |
| |- | | |- |
| | 0-1 || 2 || размер Указателя каталога в секторах, 16 бит big-endian | | | 0–1 || 2 || размер указателя каталога в секторах (<code>LS</code>), старший байт первым |
| |- | | |- |
| | 2-3 || 2 || номер последнего использованного сектора, 16 бит big-endian | | | 2–3 || 2 || текущий конец каталога — первый свободный сектор |
| |- | | |- |
| | 4–5 || 2 || размер тома (обычно 1000), 16 бит big-endian | | | 4–5 || 2 || конец области каталога |
| |- | | |- |
| | 6–15 || 10 || нули | | | 6–15 || 10 || нули |
| |} | | |} |
|
| |
|
| После служебной информации идут 16-байтовые записи каталога, описывающие файлы. По непонятной причине записи каталога не сгруппированы в начале указателя, а распределены по всем его секторам. Неиспользованные записи заполнены нулями. Следовательно, для чтения указателя каталога необходимо пройти все его сектора, игнорируя нулевые строки.
| | Размер указателя задаётся при создании каталога оператором <code>SCRATCH DISK</code> параметром <code>LS=</code>, по умолчанию 24, допустимо 1…255; в дальнейшем не меняется. В нулевом секторе помещается 15 записей, в остальных 16. Неиспользованные записи заполнены нулями. |
| | |
| | Записи '''не сгруппированы''' в начале указателя и не упорядочены по секторам файлов: номер сектора, в который попадает запись, вычисляется хешированием имени. Для чтения каталога надо пройти все его секторы, пропуская нулевые записи. |
| | |
| | === Хеш имени файла === |
| | |
| | Номер сектора указателя вычисляется по «старому» хешу Wang 2200: |
| | |
| | имя дополнить пробелами до восьми байт |
| | tmp = 0 |
| | для каждого байта имени: tmp = tmp XOR байт |
| | tmp = 3 × tmp |
| | tmp = (tmp mod 256) + (tmp div 256) |
| | сектор = tmp mod LS |
| | |
| | Свёртка выполняется исключающим ИЛИ, не сложением. «Новый» хеш Wang, введённый в MVP OS 2.5, на Искре не применяется. |
| | |
| | Хеш '''вырождается при <code>LS</code>, кратном трём''': после умножения на 3 и складывания половинок результат всегда кратен трём, поэтому занятыми оказываются только каждый третий сектор указателя. При <code>LS = 24</code> все записи попадают в секторы 0, 12, 15 и 18. |
| | |
| | Запись кладётся в первый свободный слот своего сектора, слоты заполняются подряд от начала. Правило переполнения сектора неизвестно. |
| | |
| | Освобождение слота выполняется '''обнулением первых восьми байт''' записи; имя при этом остаётся на диске. Оператор <code>SCRATCH</code> запись не обнуляет, а помечает. |
|
| |
|
| == Запись каталога == | | == Запись каталога == |
|
| |
|
| 16 байт, побайтово совпадает с Wang 2200. | | 16 байт, совпадает с Wang 2200. |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| ! Смещение !! Размер !! Значение | | ! Смещение !! Размер !! Значение |
| |- | | |- |
| | 0 || 1 || статус: <code>10</code> — активна, <code>11</code> — удалена (scratched), <code>00</code> — свободна | | | 0 || 1 || статус, битовое поле; '''бит 0''' — файл вычеркнут |
| |- | | |- |
| | 1 || 1 || тип файла: <code>80</code> — программа, <code>00</code> — данные | | | 1 || 1 || тип файла: <code>80</code> — программа, <code>00</code> — данные |
| Строка 71: |
Строка 96: |
| |} | | |} |
|
| |
|
| Байт типа '''не различает''' текстовое и токенизированное представление: у обоих <code>80</code>. | | Наблюдаемые значения статуса: <code>10</code> — активна, <code>11</code> и <code>21</code> — вычеркнута, <code>00</code> — свободна. Сравнивать статус на точное равенство нельзя, значим только бит 0. Бит 5 (значение <code>21</code>) предположительно помечает переименование. |
| | |
| | Байт типа '''не различает''' текстовое и оттранслированное представление программы: у обоих <code>80</code>. |
|
| |
|
| Указатель каталога всегда начинается с нулевого сектора диска; за ним идёт область каталога. Число секторов указателя задаётся при создании каталога оператором <code>SCRATCH DISK</code> параметром <code>LS=</code>, по умолчанию равно 24 и в дальнейшем не меняется; допустимы значения от 1 до 255. В нулевом секторе помещается до 15 записей, в остальных — до 16: первые 16 байт нулевого сектора занимает блок параметров диска.
| | Оператор <code>LIST DC</code> выводит типы как <code>P</code> (программный), <code>D</code> (данные), <code>SP</code> и <code>SD</code> (вычеркнутые), что соответствует сочетанию байта статуса и байта типа.<ref name="balasanyan" /> |
|
| |
|
| Оператор <code>LIST DC</code> выводит типы файлов как <code>P</code> (программный), <code>D</code> (файл данных), <code>SP</code> и <code>SD</code> (вычеркнутые из каталога программный и файл данных соответственно), что соответствует сочетанию байта статуса и байта типа в записи.<ref name="balasanyan" />
| | Тип «данные» ничего не говорит о внутреннем устройстве файла: он может содержать как логические записи, так и сырые блоки, записанные оператором <code>DATA SAVE BA</code>. |
|
| |
|
| === Пример === | | === Пример === |
| Строка 82: |
Строка 109: |
| └───┘ └───┘ └───┘ └─────────────────────┘ | | └───┘ └───┘ └───┘ └─────────────────────┘ |
| Активная запись, программа, секторы 49…63, имя <code>DIG DEM</code>. | | Активная запись, программа, секторы 49…63, имя <code>DIG DEM</code>. |
| | |
| | == Размещение файлов == |
| | |
| | * Файлы лежат подряд в порядке создания, диапазоны секторов не пересекаются. |
| | * Место вычеркнутого файла заново не занимается: <code>SCRATCH</code> только помечает запись. |
| | * Первый файл начинается с сектора <code>LS</code>, сразу за указателем. |
| | * Поле «текущий конец» равно последнему занятому сектору плюс единица, но обрезается по концу области каталога; при разборе границу надёжнее брать по записям каталога. |
|
| |
|
| == Заголовочный сектор == | | == Заголовочный сектор == |
| Строка 92: |
Строка 126: |
| | 1–8 || имя файла, дополнено пробелами | | | 1–8 || имя файла, дополнено пробелами |
| |- | | |- |
| | 9 || признак формата записи, см. ниже | | | 9 || признак формата записи |
| |- | | |- |
| | 10–255 || нули | | | 10–255 || нули |
| |} | | |} |
|
| |
|
| Байт 9 — единственный признак, по которому различаются два представления программы. В исследованном наборе наблюдались значения <code>20</code> (текстовое), <code>21</code> (токенизированное) и <code>25</code> (токенизированное и защищённое). | | Байт 9 (в справочнике<ref name="vtoraya" /> назван «признак защиты») — битовое поле, единственный признак, различающий два представления программы: '''бит 0''' — программа оттранслирована (параметр <code>T</code> оператора <code>SAVE DC</code>), '''бит 2''' — защищена (параметр <code>P</code>); оба вместе соответствуют параметру <code>G</code>. |
| | |
| === Связь с параметрами оператора SAVE DC ===
| |
| | |
| Документация<ref name="balasanyan" /> описывает три параметра специальных форматов записи программы:
| |
| | |
| {| class="wikitable"
| |
| ! Параметр !! Действие
| |
| |-
| |
| | <code>T</code> || программа записывается в оттранслированной форме — во внутреннем формате машины, что сокращает занимаемое место и время загрузки
| |
| |-
| |
| | <code>P</code> || программа защищается от просмотра и записи; после загрузки возможно только выполнение, режим сохраняется до оператора <code>CLEAR</code>
| |
| |-
| |
| | <code>G</code> || оттранслированная и защищённая форма одновременно
| |
| |}
| |
| | |
| Отсюда следует, что '''текстовое представление — это режим по умолчанию''', а токенизированное включается явным указанием параметра <code>T</code>. Термин «оттранслированная форма» в документации обозначает именно токенизированную запись.
| |
| | |
| Документированные значения байта 9 (в источнике он назван «признак защиты»):<ref name="vtoraya" />
| |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| Строка 129: |
Строка 145: |
| |} | | |} |
|
| |
|
| Таким образом байт 9 — битовое поле: '''бит 0''' — программа оттранслирована (параметр <code>T</code>), '''бит 2''' — программа защищена (параметр <code>P</code>); сочетание обоих соответствует параметру <code>G</code>. Значения <code>22</code> и <code>23</code> не используются.
| | Значения <code>22</code> и <code>23</code> не используются. Текстовое представление — режим по умолчанию, оттранслированное включается явным параметром <code>T</code>. |
|
| |
|
| Значения <code>20</code>, <code>21</code> и <code>25</code> наблюдались на дисковых образах (<code>25</code> — у файла <code>STAT05</code>, защищённого и оттранслированного одновременно), <code>24</code> взято из документации.
| | Защищённая программа после загрузки может только выполняться; режим снимается оператором <code>CLEAR</code>. |
|
| |
|
| == Секторы потока == | | == Секторы потока == |
|
| |
|
| Первые два байта каждого сектора потока — служебные, в содержимое не входят. | | Первые два байта каждого сектора программного потока — служебные, в содержимое не входят. У секторов файла данных служебный заголовок занимает '''один''' байт. |
|
| |
|
| '''Байт 0''' — позиция сектора: | | '''Байт 0''' — позиция сектора: |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| ! Значение !! Смысл | | ! Код !! Смысл |
| |- | | |- |
| | <code>02</code> || первый сектор потока | | | <code>02</code> || первый сектор потока |
| |- | | |- |
| | <code>8F</code> || промежуточный | | | <code>03</code> || последний сектор потока |
| |- | | |- |
| | <code>03</code> || последний | | | <code>1C</code> || концевая запись |
| | |- |
| | | <code>8B</code> || запись целиком в одном секторе (только файлы данных) |
| | |- |
| | | <code>8F</code> || промежуточный сектор |
| |} | | |} |
|
| |
|
| В токенизированных программах все секторы потока наблюдались с маркером <code>02</code>; позиционная разметка <code>02</code>/<code>8F</code>/<code>03</code> подтверждена на текстовых файлах и на потоке значений переменных. Причина различия не установлена. | | В оттранслированных программах все секторы потока идут с маркером <code>02</code>. Позиционная разметка <code>02</code>/<code>8F</code>/<code>03</code> наблюдается в текстовых файлах и в потоках данных. |
|
| |
|
| '''Байт 1''' — только у программных секторов. Во всех исследованных потоках программы он равен <code>80</code>. | | '''Байт 1''' программных секторов равен <code>80</code>. Как признак «программа или данные» он непригоден: у секторов данных на позиции 1 стоит первый байт идентификатора значения. Надёжные признаки — байт 9 заголовочного сектора и байт типа записи каталога. |
|
| |
|
| {{Внимание|У секторов данных служебный заголовок занимает '''один''' байт, а не два. То, что во всех наблюдавшихся секторах данных на позиции 1 стоял <code>00</code>, объясняется не разметкой потока, а тем, что это первый байт двухбайтового идентификатора значения, и <code>00</code> в нём означает числовой тип. Для сектора, начинающегося со строкового значения, значение будет другим. Использовать байт 1 как признак «программа или данные» нельзя; надёжный признак — байт 9 заголовочного сектора и байт типа в записи каталога.}}
| | == Концевая запись == |
| | |
| Двухбайтовая длина служебного заголовка программных секторов подтверждается арифметикой: длины записей строк сходятся только при пропуске двух байтов на каждой пересекаемой границе сектора.
| |
| | |
| В токенизированном файле сначала идут секторы программы, за ними могут следовать секторы значений переменных с собственной нумерацией.
| |
| | |
| Маркер <code>03</code> имеет содержательный смысл: документация<ref name="balasanyan" /> описывает файл как область, в которой между последней записью данных и последним сектором файла остаются неиспользованные секторы, а концевая запись помечает сектор, откуда можно продолжать запись. Таким образом <code>03</code> — не просто «последний по счёту сектор», а граница записанной области, и всё, что лежит за ней до конца выделенного диапазона, к текущему содержимому файла не относится.
| |
| | |
| == Control record ==
| |
| | |
| Лежит в '''последнем выделенном''' секторе, а не сразу за содержимым.
| |
|
| |
|
| 1C <длина: 2 байта, старший первым> | | 1C <длина: 2 байта, старший первым> |
|
| |
|
| Остаток сектора — нули. Маркер <code>1C</code> соответствует по роли байту <code>20</code> в Wang. Второй источник<ref name="vtoraya" /> описывает эту структуру как признак конца файла <code>1C</code>, за которым во втором и третьем байтах сектора записано число реально занятых секторов, а остальные байты нулевые; последний сектор файла данных устроен так же, как последний сектор программного файла. | | Остаток сектора — нули. Соответствует по роли байту <code>20</code> в Wang. |
|
| |
|
| Документация<ref name="balasanyan" /> отдельно указывает, что последний сектор файла отводится под служебную информацию, а концевая запись занимает ещё один сектор, поэтому при создании файла его размер следует заказывать на два сектора больше, чем требуется собственно под данные.
| | Счётчик равен '''собственной позиции записи от начала файла''', считая с единицы, то есть числу занятых секторов вместе с заголовком. Он же — графа «Использовано» в выдаче <code>LIST DC</code>. |
|
| |
|
| Счётчик соответствует графе «Использовано», которую показывает оператор <code>LIST DC</code>. Согласно документации<ref name="balasanyan" /> он '''не поддерживается системой автоматически''': значение заносит прикладная программа оператором <code>DATA SAVE DC END</code>, записывающим односекторную концевую запись в текущий сектор из таблицы устройств. Если признак конца данных не записан, в графе «Использовано» всегда стоит 00001; если записан — число реально использованных секторов.
| | * У программных файлов концевую запись пишет сам оператор <code>SAVE DC</code>; она есть всегда. |
| | * У файлов данных её пишет прикладная программа оператором <code>DATA SAVE DC END</code>. Если признак конца не записан, в графе «Использовано» стоит 00001. |
| | * Оператор <code>DATA SAVE DC OPEN</code> пишет в '''последний''' сектор файла запись <code>1C 00 01</code> — единственный случай, когда счётчик не равен позиции. |
|
| |
|
| Там, где значение выставлено, оно равно '''заголовок + секторы содержимого + сам control record''', то есть совпадает с правилом Wang:
| | Запись лежит не обязательно в последнем выделенном секторе: если файлу дали запас или его положили на место вычеркнутого файла большего размера, за ней остаётся нулевой хвост. |
|
| |
|
| {| class="wikitable"
| | Счётчик — не инвариант файловой системы и при разборе ненадёжен: у файлов, записанных не операторами <code>DATA SAVE DC</code>, он произволен. Границы файла следует брать из каталога. |
| ! Файл !! Секторов содержимого !! Счётчик !! Ожидаемо
| |
| |-
| |
| | <code>ФС</code> || 5 || 7 || 1+5+1 = 7
| |
| |-
| |
| | <code>ДЕМ6</code> || 16 || 18 || 1+16+1 = 18
| |
| |}
| |
| | |
| {{Внимание|Счётчик не является инвариантом файловой системы и при разборе ненадёжен. У <code>#СТАТИСТ</code> он равен 48 при 46 секторах содержимого — значение, по-видимому, осталось от предыдущей, более длинной версии программы, остатки которой лежат в резерве того же файла. У <code>DIG DEM</code> счётчик равен 6 при 5 секторах содержимого, то есть на единицу меньше ожидаемого; возможная причина в том, что там последней операцией была запись потока данных, а не программы. Границы файла следует брать из каталога, а конец данных определять по структуре записей.}}
| |
|
| |
|
| == Текстовое представление == | | == Текстовое представление == |
|
| |
|
| Программа хранится плоским текстом. Кодировка — КОИ-8 (ГОСТ 19768-74): прописная кириллица в 0xE0–0xFF, строчная в 0xC0–0xDF. Номера строк записаны обычными ASCII-цифрами. | | Программа хранится плоским текстом. Кодировка — [[КОИ-8]] (ГОСТ 19768-74): прописная кириллица в <code>E0</code>–<code>FF</code>, строчная в <code>C0</code>–<code>DF</code>. Номера строк записаны обычными цифрами. |
|
| |
|
| * Разделитель строк — байт '''<code>85</code>'''. | | * Разделитель строк — байт '''<code>85</code>'''. |
| Строка 193: |
Строка 197: |
| * Байт <code>24</code> (<code>$</code>) отображается на экране Искры как <code>¤</code>. | | * Байт <code>24</code> (<code>$</code>) отображается на экране Искры как <code>¤</code>. |
|
| |
|
| Выбор <code>85</code> возможен именно потому, что кириллица занимает только верхнюю четверть кодовой таблицы и диапазон 0x80–0x9F остаётся свободным. К самой кодировке КОИ-8 это соглашение отношения не имеет и за пределами Искры не встречается.
| | Байт <code>85</code> свободен потому, что диапазон <code>80</code>–<code>9F</code> в КОИ-8 не занят. Тем же байтом машина разделяет строки в символьном буфере операторов <code>SAVE</code> и <code>LOAD</code> и выдаёт нажатие клавиши CR/LF. |
|
| |
|
| Разбор: склеить содержимое секторов, отбрасывая по два служебных байта, разбить по <code>85</code>, отбросить нули. | | Разбор: склеить содержимое секторов, отбрасывая по два служебных байта, разбить по <code>85</code>, отбросить нули. |
| Строка 203: |
Строка 207: |
| Заголовок сектора, затем <code>5 REM ПЕТРЕНКО …</code>, разделитель, <code>8 PRINT …</code>. | | Заголовок сектора, затем <code>5 REM ПЕТРЕНКО …</code>, разделитель, <code>8 PRINT …</code>. |
|
| |
|
| == Токенизированное представление == | | == Виды дисков == |
| | |
| === Сборка потока ===
| |
| | |
| Склеить содержимое секторов с байтом 1 равным <code>80</code>, отбрасывая по два служебных байта у каждого. Далее смещения даны от начала полученного потока.
| |
| | |
| === Пролог и таблицы ===
| |
| | |
| Поток начинается с трёх 16-битных длин, '''старший байт первым''', за которыми следуют три таблицы:
| |
| | |
| {| class="wikitable"
| |
| ! Смещение !! Размер !! Значение
| |
| |-
| |
| | 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
| |
| |}
| |
| | |
| '''Программа начинается по смещению <code>6 + L1 + L2 + L3</code>.'''
| |
| | |
| Проверено на четырёх файлах:
| |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| ! Файл !! L1 !! L2 !! L3 !! Старт !! Первая строка | | ! Вид !! Признак |
| |- | | |- |
| | <code>DIG DEM</code> || 24 || 20 || 0 || 50 || <code>0 GOTO 10</code> | | | с каталогом || байты 0–1 нулевого сектора — разумный <code>LS</code>, дальше 16-байтовые записи |
| |- | | |- |
| | <code>#СТАТИСТ</code> || 216 || 488 || 108 || 818 || <code>999 REM КОНЕЦ</code> | | | загрузочный || сектор 0 начинается с <code>06 90 09 90 07 90</code> |
| |-
| |
| | <code>ДЕМ6</code> || 392 || 256 || 0 || 654 || <code>10 REM "ДЕМ6". ГОРОСКОП</code>
| |
| |-
| |
| | <code>EDITOR</code> || 448 || 800 || 0 || 1254 || <code>10 % EDITOR. ВЕРСИЯ 4.1</code>
| |
| |}
| |
| | |
| Формула даёт точную границу, эвристический поиск начала программы не требуется.
| |
| | |
| ==== Четырёхбайтовое поле перед программой ====
| |
| | |
| У части файлов между таблицами и первой записью строки лежат '''четыре лишних байта''', и формула указывает на четыре байта раньше настоящего начала программы. В исследованном наборе так устроены 19 файлов из 94 — целиком одно семейство программ. Первые два байта поля всегда <code>0A 19</code>, вторые два — двоично-десятичное число, своё у каждого файла:
| |
| | |
| {| class="wikitable"
| |
| ! Файл !! Поле !! Файл !! Поле
| |
| |- | | |- |
| | <code>BAM</code> || <code>0A 19 20 20</code> || <code>BAM00</code> || <code>0A 19 90 90</code> | | | пакет ассемблера || сектор 0 начинается с <code>7E E1 F3 ED E2</code> (<code>~АСМБ</code>) |
| |- | | |- |
| | <code>BAM1</code> || <code>0A 19 20 91</code> || <code>BAM9</code> || <code>0A 19 20 99</code> | | | прямого доступа || каталога нет, есть заголовочные секторы <code>01 <имя> <признак></code> |
| |- | | |- |
| | <code>BAM10</code> || <code>0A 19 91 90</code> || <code>S_BAM01</code> || <code>0A 19 90 91</code> | | | пустой || нули |
| |} | | |} |
|
| |
| Признак защиты роли не играет: девять других файлов набора со значением байта 9, равным <code>25</code>, поля не имеют. Назначение поля не установлено; ни на запись строки, ни на запись таблицы 2 оно не раскладывается.
| |
|
| |
| Определять наличие поля следует проверкой самой записи: по вычисленному смещению (после пропуска нулей выравнивания) оба байта номера строки должны быть правильным двоично-десятичным числом, байт длины — не меньше единицы, а байт по адресу <code>p + 2 + len</code> — равен <code>FE</code>. Если проверка не проходит, а по смещению +4 проходит, поле присутствует. На наборе это разделяет файлы без единой ошибки.
| |
|
| |
| {{Внимание|1=Полагаться на «разбор сломается — попробуем сдвиг» нельзя. У файла <code>BAM1</code> мусорная запись по неверному смещению случайно оканчивалась байтом <code>FE</code>: разбор продолжался дальше и терял только первую строку программы.}}
| |
|
| |
| ==== Таблица 1 ====
| |
|
| |
| '''Таблица 1 описывает переменные, которым нужен дескриптор размера''' — массивы и символьные переменные с явно заданной длиной. Оператор объявления при этом не важен: в таблицу попадают как переменные из <code>DIM</code>, так и из <code>COM</code> (аналог <code>DIM</code> для переменных, общих для нескольких программ). Числовые скаляры и символьные переменные с длиной по умолчанию в таблице 1 не описываются.
| |
|
| |
| В файле <code>М1.Х</code> записей 13 при 10 переменных в <code>DIM</code>; лишние три — <code>C(14)</code>, <code>G(14)</code> и <code>N0¤21</code>, объявленные в <code>COM</code>.
| |
|
| |
| Запись — 8 байт:
| |
|
| |
| {| class="wikitable"
| |
| ! Смещение !! Размер !! Значение
| |
| |-
| |
| | 0–1 || 2 || адрес переменной в памяти, младший байт первым
| |
| |-
| |
| | 2–3 || 2 || тип: <code>00 08</code> — символьная, <code>2D 08</code> — числовая или целая
| |
| |-
| |
| | 4–5 || 2 || для массивов — число элементов, младший байт первым
| |
| |-
| |
| | 6–7 || 2 || размерный код, младший байт первым
| |
| |}
| |
|
| |
| Размерный код равен '''<code>2 × размер элемента</code>''', а его младший бит означает, что длина была задана явно. Для числовых переменных код 16 (размер элемента 8 байт), для целых 4 (2 байта), для символьных <code>2 × длина + 1</code>: длина 8 → 17, 64 → 129, 128 → 257, 253 → 507.
| |
|
| |
| Символьная переменная, объявленная без указания длины, получает длину по умолчанию 16 и '''чётный''' код 32. В <code>STAT01A</code> так объявлены <code>B6¤(400)</code> и <code>B7¤(10)</code>, у обоих код 32, тогда как у всех переменных с явной длиной в том же файле код нечётный и точен. Поэтому нечётность кода нельзя использовать как признак символьного типа — для этого служат байты 2–3.
| |
|
| |
| ===== Двумерные массивы =====
| |
|
| |
| У двумерного массива вторая размерность записывается в байты 2–3, на место признака типа, а первая остаётся в поле числа элементов. Размерности '''не перемножаются''', поэтому оператор <code>DIM</code> восстанавливается точно.
| |
|
| |
| Правило проверено на десяти массивах в четырёх файлах, включая <code>A(5,6)</code> из <code>STAT03</code> с двумя разными малыми размерностями. В <code>М1.Х</code> пять массивов вида <code>(2,99)</code> дают в байтах 2–3 значение 99, а в поле числа элементов 2; контрольный <code>R(2,2)</code> с равными размерностями даёт 2 и 2. Размер выделяемой памяти считается по общей формуле с перемножением размерностей: у <code>P(2,99)</code> разность адресов равна 1590 = 2 × 99 × 8 + 6, у <code>R(2,2)</code> — 38 = 2 × 2 × 8 + 6.
| |
|
| |
| Пример записи <code>Q(7,8)</code> из <code>STAT01A</code>:
| |
|
| |
| 00 00 08 00 07 00 10 00
| |
|
| |
| Байты 2–3 равны 8 (вторая размерность), байты 4–5 равны 7 (первая), размерный код 16 — числовая. Опознание надёжно: соседние записи таблицы содержат 13 и 8 элементов, а в тексте <code>DIM</code> вокруг <code>Q(7,8)</code> стоят <code>A(13)</code> и <code>E(8)</code>.
| |
|
| |
| Практическое правило: '''старший байт поля 2–3 равен <code>08</code> — массив одномерный''', иначе поле 2–3 целиком занято второй размерностью. Значения <code>08 2D</code> и <code>08 00</code> не могут быть размерностью, так как означали бы 2093 и 2048 элементов по второму измерению.
| |
|
| |
| Вопрос «как при двумерности кодируется тип переменной» снимается: тип хранится не в таблице 1, а во флаге записи таблиц 2 и 3 (см. ниже), поэтому байты 2–3 таблицы 1 свободны под размерность. Символьные двумерные массивы в наборе есть — пятнадцать записей, у которых флаг помечает символьный тип, а старший байт поля 2–3 равен <code>0C</code>.
| |
|
| |
| {{Внимание|1=Исключение — '''неявно объявленные массивы'''. У переменной, к которой обратились с индексом без предварительного <code>DIM</code>, число элементов всегда равно 10 (размерность массива по умолчанию), а в байтах 2–3 стоят значения <code>01 27</code> или <code>07 59</code>, размерностями не являющиеся: в исследованном наборе они встречаются девять и семь раз соответственно и только при десяти элементах, в неродственных программах. Их природа не установлена. Такие массивы следует выводить как одномерные <code>(10)</code>. Настоящий двумерный массив <code>(10,10)</code> в наборе тоже есть — в <code>STATIST</code>, там байты 2–3 равны <code>00 0A</code>.}}
| |
|
| |
| ===== Порядок записей обратен порядку объявления =====
| |
|
| |
| Память под переменные выделяется '''сверху вниз''', от верхней границы, а записи в таблице отсортированы по возрастанию адреса. Поэтому '''первая переменная в <code>DIM</code> оказывается последней записью таблицы''', и сопоставлять их нужно с конца.
| |
|
| |
| Проверено на файле <code>EDITOR</code>: 7 операторов <code>DIM</code> объявляют 50 переменных, L1 = 448 = 56 записей. При обратном сопоставлении разность адресов соседних записей совпадает с ожидаемым размером переменной '''у 44 из 50''' точно, а оставшиеся 6 расходятся ровно на 1 байт — это нечётные длины (<code>A¤1</code>, <code>K¤253</code>, <code>G¤19</code>), округляемые вверх до чётной.
| |
|
| |
| {{Внимание|1='''Адреса в таблице 1 могут быть нулевыми.''' В файле <code>STAT05</code> все 11 записей имеют адрес <code>0000</code>, поэтому любое правило, опирающееся на разность адресов соседних записей, там неприменимо. Разборщику следует проверять адреса на ненулевое значение перед использованием.
| |
|
| |
| Остальные поля при этом заполнены и корректны: обратный порядок относительно <code>DIM</code> сохраняется, размерный код точен для всех строковых переменных, у массива <code>K(9)</code> число элементов равно 9.}}
| |
|
| |
| Размер выделяемой памяти:
| |
|
| |
| * скалярная символьная переменная — объявленная длина, округлённая вверх до чётной;
| |
| * массив — <code>N × размер элемента + 6</code>, где 6 байт занимает дескриптор.
| |
|
| |
| Формула для массивов проверена на 20 массивах <code>EDITOR</code> без единого расхождения: <code>V¤(20)64</code> → 1286, <code>S¤(48)64</code> → 3078, <code>P%(300)</code> → 606, <code>X(2)</code> → 22.
| |
|
| |
| ===== Таблица отражает состояние на момент сохранения =====
| |
|
| |
| Число записей может превышать число переменных в <code>DIM</code>. В <code>STAT01A</code> записей 26 при 23 объявленных переменных, и три лишние стоят в области самых низких адресов, то есть выделены последними.
| |
|
| |
| Число элементов у лишних записей соответствует не тексту программы, а значению, которое переменная получила при выполнении: в <code>STAT01A</code> у всех трёх лишних записей это 300, тогда как в <code>DIM</code> объявлено 200. Программа содержит оператор <code>MAT REDIM</code>, переопределяющий размерности во время работы, поэтому таблица описывает состояние памяти на момент сохранения, а не исходный текст.
| |
|
| |
| Косвенное подтверждение: в токенах <code>DIM</code> файла <code>STAT01A</code> переменные <code>X</code>, <code>Y</code> и <code>G¤</code> получили индексы <code>40</code>, <code>41</code>, <code>42</code>, тогда как все остальные — обычные значения подряд.
| |
|
| |
| Причина этого выяснена на файле <code>STAT03</code>, сохранённом в обоих представлениях. Выросший массив получает не только новый дескриптор в таблице 1, но и '''новый индекс переменной''', причём интерпретатор правит операнд в самом операторе <code>DIM</code>, оставляя код и <code>MAT REDIM</code> работать со старым индексом. Оператор
| |
|
| |
| 20 DIM X(100),G¤(100)5,Q(5,100),D(6),E(5),A(5,6)
| |
|
| |
| кодируется как
| |
|
| |
| 46 06 19 1A 1B 03 04 05
| |
|
| |
| — первые три переменные записаны индексами <code>19</code>, <code>1A</code>, <code>1B</code>, тогда как <code>MAT REDIM</code> в строке 200 обращается к ним же как <code>E0 00</code>, <code>E0 01</code>, <code>E0 02</code>. В таблицах 2 и 3 бит 0 флага установлен и у <code>19</code>, <code>1A</code>, <code>1B</code>, и у <code>00</code>, <code>01</code>, <code>02</code>: первым соответствуют дескрипторы с 300 элементами (размер после переопределения), вторым — исходные, со 100.
| |
|
| |
| {{Внимание|1=Из этого следует, что операторы <code>DIM</code> в оттранслированном файле нельзя использовать как источник соответствия «индекс — дескриптор»: там могут стоять «теневые» индексы. Надёжный источник — бит 0 флага записей таблиц 2 и 3.}}
| |
|
| |
| Дополнительный дескриптор создаётся '''только тогда, когда массив вырастает за пределы исходного выделения'''. Если новый размер не превышает объявленного в <code>DIM</code>, память переиспользуется на месте и запись в таблице остаётся одна.
| |
|
| |
| Наиболее наглядно это видно в <code>STAT03</code>, где <code>MAT REDIM</code> переопределяет шесть массивов, а лишних записей три:
| |
|
| |
| {| class="wikitable"
| |
| ! Массив !! Объявлено !! После переопределения !! Дескриптор
| |
| |-
| |
| | <code>X(100)</code> || 100 || 300 || добавлен
| |
| |-
| |
| | <code>G¤(100)5</code> || 100 || 300 || добавлен
| |
| |-
| |
| | <code>Q(5,100)</code> || 100 || 300 || добавлен
| |
| |-
| |
| | <code>D(6)</code> || 6 || не больше 6 || нет
| |
| |-
| |
| | <code>E(5)</code> || 5 || не больше 5 || нет
| |
| |-
| |
| | <code>A(5,6)</code> || 5, 6 || не больше 5 и 6 || нет
| |
| |}
| |
|
| |
| Правило согласуется и с остальными файлами: в <code>STAT01A</code> из одиннадцати переопределяемых массивов выросли три, в <code>STAT00</code> из четырёх — два, и столько же лишних записей в каждом случае.
| |
|
| |
| Если программа не выполнялась после загрузки, оператор <code>MAT REDIM</code> не отрабатывает и лишних записей нет вовсе: в <code>STAT08</code> записей ровно шесть при шести переменных в <code>DIM</code>.
| |
|
| |
| В <code>EDITOR</code> шесть записей таблицы 1 не соответствуют ни одному <code>DIM</code>. По флагам таблицы 2 они принадлежат переменным с индексами <code>39</code>, <code>54</code>, <code>64</code>, <code>90</code>, <code>92</code>, <code>93</code>, причём у всех шести число элементов равно 10 — размерности массива по умолчанию. Это '''неявно объявленные массивы''': переменная, к которой обратились с индексом без предварительного <code>DIM</code>, получает десять элементов и полноценный дескриптор. Три из шести — целые (<code>%</code>), размерный код 4, три — действительные, код 16.
| |
|
| |
| ==== Таблицы 2 и 3 ====
| |
|
| |
| Таблицы 2 и 3 образуют '''один непрерывный массив дескрипторов переменных'''. Формат записей в обеих одинаков, граница между ними определяется только счётчиками пролога; число записей — <code>L2 / 4</code> и <code>L3 / 4</code>, поскольку длины в прологе даны в байтах, а запись занимает 4 байта, а не 8.
| |
|
| |
| {| class="wikitable"
| |
| ! Смещение !! Размер !! Значение
| |
| |-
| |
| | 0–1 || 2 || адрес переменной в памяти, младший байт первым; <code>0000</code>, если память не выделена
| |
| |-
| |
| | 2 || 1 || флаг типа
| |
| |-
| |
| | 3 || 1 || назначение не установлено
| |
| |}
| |
|
| |
| ===== Порядок записей =====
| |
|
| |
| Записи описывают '''все''' переменные программы, а не только скалярные, и идут в порядке '''убывания индекса переменной'''. Если обозначить
| |
|
| |
| N = L2 / 4 + L3 / 4
| |
|
| |
| то запись, описывающая переменную с индексом ''i'', находится на позиции <code>N − 1 − i</code>, если считать позиции подряд: сначала записи таблицы 2, затем записи таблицы 3.
| |
|
| |
| Отсюда следует, что '''<code>N</code> — это общее число переменных программы''', то есть наибольший использованный индекс плюс единица. Таблица 3 приходится на самые младшие индексы — именно те, которые занимает оператор <code>COM</code>: он всегда выполняется первым и получает индексы с нуля.
| |
|
| |
| Правило проверено на всех файлах исследованного набора, разбор которых не срывается на бинарных полезных нагрузках операторов <code>ASMB</code> и <code>$GIO</code>: для 28 файлов совпадение точное.
| |
|
| |
| Независимое подтверждение даёт второй набор из 43 файлов, добавленный позже: соотношение «число записей с установленным битом 0 флага равно <code>L1 / 8</code>» (см. ниже) выполняется в нём '''для всех 43 файлов без исключения'''.
| |
|
| |
| ===== Флаг типа =====
| |
|
| |
| {| class="wikitable"
| |
| ! Бит !! Значение
| |
| |-
| |
| | 5 (<code>0x20</code>) || символьная переменная
| |
| |-
| |
| | 4 (<code>0x10</code>) || числовая переменная (действительная)
| |
| |-
| |
| | ни 5, ни 4 || '''целая переменная''' (суффикс <code>%</code>)
| |
| |-
| |
| | 0 (<code>0x01</code>) || у переменной есть дескриптор в таблице 1 — то есть это массив либо символьная переменная с явно заданной длиной
| |
| |-
| |
| | 7, 3 || назначение не установлено
| |
| |}
| |
|
| |
| Признак целого типа установлен на файле <code>VICT</code>, единственном в наборе, где есть и целые переменные, и обе копии программы. Ровно три записи таблицы 2 имеют <code>флаг & 0x30 = 0</code> — те, что соответствуют индексам <code>23</code>, <code>28</code> и <code>33</code>. В текстовом листинге ровно три целых переменных, и их индексы совпадают: <code>E%</code> — строка 98 (<code>E%=0</code> → <code>36 04 23 D9 E8 00</code>), <code>E7%</code> — строка 1010, <code>E3%</code> — строка 5070. У всех троих адрес равен <code>0000</code>: целым скалярам в общей области скаляров место не отводится.
| |
|
| |
| Признак символьного типа согласуется с прежним наблюдением: в <code>М1.Х</code> у <code>N¤</code> и <code>N1¤</code> флаг <code>A8</code>, а адреса этих переменных отстоят от соседних на 16 байт — длина символьной переменной по умолчанию.
| |
|
| |
| ===== Связь с таблицей 1 =====
| |
|
| |
| Число записей с установленным битом 0 флага '''в точности равно <code>L1 / 8</code>''' во всех 94 файлах набора. Более того, соответствие оказывается порядковым: ''k''-я по счёту такая запись (в порядке файла) описывает ту же переменную, что и ''k''-я запись таблицы 1.
| |
|
| |
| k = 0
| |
| для pos = 0 … N−1:
| |
| если флаг[pos] и 1:
| |
| дескриптор_переменной(N − 1 − pos) = запись таблицы 1 номер k
| |
| k = k + 1
| |
|
| |
| Это даёт '''точное соответствие «индекс переменной → запись таблицы 1» без разбора операторов <code>DIM</code> и <code>COM</code>'''. Прежний способ — сопоставление по порядку объявления — ошибочен по двум причинам:
| |
|
| |
| * оператор <code>COM</code> объявляет и обычные скаляры, у которых записи в таблице 1 нет: в <code>М1.Х</code> из тринадцати объявленных переменных дескриптор имеют только три (<code>C(14)</code>, <code>G(14)</code>, <code>N0¤21</code>);
| |
| * после <code>MAT REDIM</code> в операнде <code>DIM</code> могут стоять «теневые» индексы (см. ниже).
| |
|
| |
| Три записи таблицы 3 в <code>М1.Х</code> с низкими значениями поля 0–1 (<code>0014</code>, <code>000D</code>, <code>0005</code>) — это как раз те три переменные <code>COM</code>, которые описаны в таблице 1. У всех переменных с дескриптором таблицы 1 поле 0–1 содержит не адрес, а небольшое число, растущее примерно на восемь с каждой такой переменной (в <code>М1.Х</code> — 5, 13, 20, 29, 38, 46, 54, 62, 70, 78, 85, 93, 101). Назначение этого поля не установлено.
| |
|
| |
| ===== Практическое следствие =====
| |
|
| |
| Хотя имена переменных в оттранслированном файле не сохраняются, для '''каждой''' переменной восстанавливаются:
| |
|
| |
| * тип — действительная, целая (<code>%</code>) или символьная (<code>¤</code>);
| |
| * массив это или скаляр;
| |
| * размерности массива и длина элемента.
| |
|
| |
| Разборщику не нужны ни эвристики «массив или скаляр» по разностям адресов (в защищённых файлах адреса нулевые), ни разбор операторов <code>DIM</code>.
| |
|
| |
| === Индексы переменных и их имена ===
| |
|
| |
| Переменные адресуются в коде однобайтовым индексом. Индексы назначаются '''в порядке первого появления переменной в тексте программы''', а не по алфавиту и не по типу.
| |
|
| |
| Операторы <code>COM</code> и <code>DIM</code> делят единое пространство индексов: в <code>М1.Х</code> оператор <code>COM</code> занимает индексы <code>00</code>–<code>0C</code>, а следующий за ним <code>DIM</code> продолжает с <code>0D</code>.
| |
|
| |
| Для переменных, объявленных в <code>DIM</code>, это даёт прямое соответствие: операторы <code>DIM</code> перечисляют свои переменные подряд, в том же порядке, в каком они записаны в исходном тексте. В <code>EDITOR</code> семь операторов <code>DIM</code> занимают индексы <code>01</code>–<code>32</code> непрерывно; индекс <code>00</code> достался переменной <code>R2%</code>, встретившейся раньше — в строке 30, до первого <code>DIM</code>. В <code>ДЕМ6</code>, где до <code>DIM</code> переменных не было, нумерация начинается с <code>00</code>.
| |
|
| |
| {{Внимание|1='''Имена переменных в оттранслированном файле не сохраняются.''' Таблицы содержат дескрипторы памяти — адрес, тип, размер, — но не имена.
| |
|
| |
| Проверка на <code>EDITOR</code>, где программа сохранена одновременно в текстовом и оттранслированном виде: сопоставление 866 общих строк дало однозначное имя для 146 индексов из 160, и ни одно поле таблиц с этими именами не коррелирует. В таблице 1 поле по смещению 4–5 совпадает у <code>Y¤4</code> и <code>Z¤253</code> (72), у <code>D¤8</code> и <code>Q5¤40</code> (204); в таблице 2 последний байт записи совпадает у заведомо разных переменных (<code>D%</code> и <code>I%</code>, <code>J%</code> и <code>E</code>).
| |
|
| |
| Это согласуется с назначением параметра <code>T</code>: при выполнении имена не нужны, всё адресуется по индексу, а отказ от их хранения и даёт заявленную экономию места. Практическое следствие — '''читаемый листинг из оттранслированного файла восстановить нельзя'''. Восстанавливается структура программы, но переменные остаются безымянными, если не сохранилась текстовая версия того же исходника.
| |
|
| |
| Не хранится только само имя. Тип переменной — действительная, целая или символьная — и её размерности восстанавливаются полностью по таблицам 2, 3 и 1, поэтому в листинге можно выводить корректные суффиксы <code>%</code> и <code>¤</code> и корректные размерности массивов.}}
| |
|
| |
| === Записи строк ===
| |
|
| |
| Программа — последовательность записей, разделённых байтом '''<code>FE</code>'''. Перед первой записью разделителя нет.
| |
|
| |
| <номер строки: 2 байта BCD> <len: 1 байт> <тело>
| |
|
| |
| <code>len</code> считается '''включая сам байт len''' и до следующего <code>FE</code>. Следующий разделитель находится по адресу <code>адрес(len) + len</code>.
| |
|
| |
| В отличие от текстового представления, записи '''пересекают''' границу сектора; два служебных байта нового сектора в длину не входят.
| |
|
| |
| ==== Выравнивание в конце сектора ====
| |
|
| |
| Если очередная запись не помещается в остаток сектора, остаток заполняется нулями, а запись начинается с первого байта следующего сектора. Такие нули стоят '''после''' разделителя <code>FE</code> и в длину записи не входят.
| |
|
| |
| Точное правило формулируется через границы секторов, а не через длину заголовка. Поток рассматривается как последовательность 254-байтовых кусков — по одному на сектор, '''без обрезки хвостовых нулей'''. После разделителя <code>FE</code>, стоящего по смещению ''q'':
| |
|
| |
| b = (⌊q / 254⌋ + 1) × 254
| |
| если b > q и все байты потока с q по b−1 нулевые, то q = b
| |
|
| |
| То есть переход к следующему сектору выполняется только тогда, когда весь остаток текущего куска нулевой.
| |
|
| |
| С этим правилом разбор всех 51 токенизированного файла исследованного набора проходит без единой рассинхронизации, а <code>EDITOR</code> даёт ровно 1422 записи строк — столько же, сколько строк в его текстовой версии. Эвристика «пропустить до трёх нулей после <code>FE</code>» этого не обеспечивает: длина выравнивания доходит до шести байт.
| |
|
| |
| {{Внимание|1=Строка с номером 0 кодируется байтами <code>00 00 <len></code> — такая строка есть в файле <code>DIG DEM</code> (<code>0 GOTO 10</code>). Разборщик, безусловно пропускающий пару нулей перед номером строки, разрушает разбор подобных файлов. Нули следует пропускать только по правилу выше.}}
| |
|
| |
| === Операторы ===
| |
|
| |
| Тело записи — последовательность операторов:
| |
|
| |
| <токен глагола: 1 байт> <len: 1 байт> <операнды: len байт>
| |
|
| |
| Здесь <code>len</code> — длина только операндов, сам байт len не считается. <code>len = 00</code> означает оператор без операндов, например голый <code>PRINT</code>.
| |
|
| |
| Наличие явной длины у каждого оператора позволяет разрезать программу на операторы, ещё не зная значений токенов. Эта особенность вынужденная: в отличие от Wang, где многословные команды собираются из нескольких токенов вперемешку с обычным ASCII и границы видны по самому потоку, у Искры весь текст программы переведён в токены и поток непрозрачен.
| |
|
| |
| ==== Пример разбора ====
| |
|
| |
| Строка 2000 файла <code>#СТАТИСТ</code>, длина 0x54 = 84:
| |
|
| |
| 20 00 54
| |
| 23 01 23 GOSUB ' 35
| |
| 4C 39 E3 1F «БУДЕТЕ …» DD 00 DD E3 12 «-[1] ИЛИ ДВУМЯ [2]» 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
| |
|
| |
| Сумма: 3 + 59 + 3 + 5 + 8 + 5 = 83, плюс байт длины = 84.
| |
|
| |
| == Таблица токенов ==
| |
|
| |
| === Глаголы ===
| |
|
| |
| Основной массив значений восстановлен из прошивки интерпретатора (алфавитный список ключевых слов по смещению 0x1840 и 51-байтовый массив перестановки по 0x19B6). Отмеченные записи проверены или исправлены сопоставлением токенизированной и текстовой версий одной программы.
| |
|
| |
| {| class="wikitable"
| |
| ! Токен !! Оператор !! Примечание
| |
| |-
| |
| | <code>1E</code> || IF END THEN || выведено из данных; операнд — 2 байта BCD, номер строки
| |
| |-
| |
| | <code>21</code> || GOTO || подтверждено
| |
| |-
| |
| | <code>22</code> || GOSUB || подтверждено
| |
| |-
| |
| | <code>23</code> || GOSUB' || в прошивочной таблице пропуск; значение выведено из данных
| |
| |-
| |
| | <code>24</code> || IF || подтверждено
| |
| |-
| |
| | <code>25</code> || KEYIN || подтверждено
| |
| |-
| |
| | <code>26</code> || ON || подтверждено
| |
| |-
| |
| | <code>27</code> || DEFFN' || подтверждено
| |
| |-
| |
| | <code>28</code> || PRINTUSING || в опубликованной таблице ошибочно указан GOSUB'; исправлено по данным
| |
| |-
| |
| | <code>29</code> || DATA || подтверждено
| |
| |-
| |
| | <code>2A</code> || SAVE ||
| |
| |-
| |
| | <code>2B</code> || RENUMBER ||
| |
| |-
| |
| | <code>2C</code> || CLEAR ||
| |
| |-
| |
| | <code>2D</code> || LOAD ||
| |
| |-
| |
| | <code>2E</code> || LIST ||
| |
| |-
| |
| | <code>2F</code> || RUN || подтверждено
| |
| |-
| |
| | <code>30</code> || RETURN CLEAR || подтверждено
| |
| |-
| |
| | <code>34</code> || ON ERROR || подтверждено
| |
| |-
| |
| | <code>35</code> || LET ||
| |
| |-
| |
| | <code>36</code> || ''(присваивание без ключевого слова)'' || подтверждено
| |
| |-
| |
| | <code>33</code> || ''(не опознан; операнды — две ссылки на переменные)'' || выведено из данных
| |
| |-
| |
| | <code>37</code> || ''(не опознан; операнд — один массив целиком)'' || выведено из данных
| |
| |-
| |
| | <code>3A</code> || DEFFN' с текстовым определением || выведено из данных
| |
| |-
| |
| | <code>3F</code> || <code>%</code> (краткая форма REM) || подтверждено
| |
| |-
| |
| | <code>40</code> || $GIO ||
| |
| |-
| |
| | <code>41</code> || INPUT || подтверждено
| |
| |-
| |
| | <code>42</code> || STOP || подтверждено
| |
| |-
| |
| | <code>43</code> || AND( || подтверждено
| |
| |-
| |
| | <code>44</code> || READ || подтверждено
| |
| |-
| |
| | <code>45</code> || BOOL ||
| |
| |-
| |
| | <code>46</code> || DIM || подтверждено
| |
| |-
| |
| | <code>47</code> || CONVERT || подтверждено
| |
| |-
| |
| | <code>48</code> || PACK( || подтверждено
| |
| |-
| |
| | <code>4A</code> || ADD ||
| |
| |-
| |
| | <code>4B</code> || BIN( ||
| |
| |-
| |
| | <code>4C</code> || PRINT || подтверждено
| |
| |-
| |
| | <code>4D</code> || ROTATE || подтверждено
| |
| |-
| |
| | <code>4E</code> || COM || подтверждено
| |
| |-
| |
| | <code>50</code> || HEXPRINT ||
| |
| |-
| |
| | <code>51</code> || RESTORE || подтверждено
| |
| |-
| |
| | <code>52</code> || NEXT || подтверждено
| |
| |-
| |
| | <code>53</code> || REWIND ||
| |
| |-
| |
| | <code>54</code> || SELECT || подтверждено
| |
| |-
| |
| | <code>55</code> || BACKSPACE ||
| |
| |-
| |
| | <code>56</code> || REM || подтверждено
| |
| |-
| |
| | <code>57</code> || FOR || подтверждено
| |
| |-
| |
| | <code>58</code> || SKIP ||
| |
| |-
| |
| | <code>59</code> || END ||
| |
| |-
| |
| | <code>5A</code> || DEFFN ||
| |
| |-
| |
| | <code>5C</code> || RES ||
| |
| |-
| |
| | <code>5D</code> || UNPACK( || подтверждено
| |
| |-
| |
| | <code>5E</code> || RETURN || подтверждено
| |
| |-
| |
| | <code>5F</code> || TRACE ||
| |
| |-
| |
| | <code>61</code> || OR( ||
| |
| |-
| |
| | <code>62</code> || XOR( ||
| |
| |-
| |
| | <code>63</code> || ''(операция над символьными переменными, имя не установлено)'' || выведено из данных
| |
| |-
| |
| | <code>64</code> || INIT || подтверждено
| |
| |-
| |
| | <code>66</code> || DATA LOAD BT || подтверждено
| |
| |-
| |
| | <code>68</code> || DATA SAVE BT || подтверждено
| |
| |-
| |
| | <code>6D</code> || COPY ||
| |
| |-
| |
| | <code>6E</code> || DATA SAVE BA || подтверждено
| |
| |-
| |
| | <code>70</code> || DATA LOAD BA || подтверждено
| |
| |-
| |
| | <code>71</code> || DATA LOAD DA || подтверждено
| |
| |-
| |
| | <code>72</code> || ''(не опознан; операнды <code>T#n,(…)…</code>, встречается при загрузке сегмента)'' || выведено из данных
| |
| |-
| |
| | <code>74</code> || DATA LOAD DC || выведено из данных
| |
| |-
| |
| | <code>75</code> || DATA LOAD DC OPEN T || выведено из данных
| |
| |-
| |
| | <code>76</code> || DATA SAVE DC || подтверждено
| |
| |-
| |
| | <code>77</code> || DATA SAVE DC CLOSE || подтверждено
| |
| |-
| |
| | <code>78</code> || DATA SAVE DC OPEN T || подтверждено
| |
| |-
| |
| | <code>79</code> || DBACKSPACE ||
| |
| |-
| |
| | <code>7A</code> || DSKIP ||
| |
| |-
| |
| | <code>7B</code> || LIMITS || подтверждено
| |
| |-
| |
| | <code>7C</code> || LIST DC || подтверждено
| |
| |-
| |
| | <code>7D</code> || LOAD DC || выведено из данных
| |
| |-
| |
| | <code>7E</code> || MOVE ||
| |
| |-
| |
| | <code>80</code> || SAVE DC || выведено из данных
| |
| |-
| |
| | <code>81</code> || SCRATCH ||
| |
| |-
| |
| | <code>82</code> || SCRATCH DISK || подтверждено
| |
| |-
| |
| | <code>83</code> || VERIFY ||
| |
| |}
| |
|
| |
| Пометка «подтверждено» означает сверку с текстовым листингом той же программы. Основная часть подтверждений получена на файле <code>EDITOR</code> автоматическим сопоставлением 866 общих строк: в строках, где число операторов в обоих представлениях совпало, первый глагол каждого оператора сверялся с первым словом соответствующего оператора текста. Так подтверждены 34 значения, включая <code>21</code> = GOTO (147 совпадений), <code>4C</code> = PRINT (212), <code>24</code> = IF (266).
| |
|
| |
| Отдельно стоит краткая форма комментария <code>%</code> (токен <code>3F</code>): она встречается в <code>EDITOR</code> 122 раза против 7 у полного <code>REM</code> (токен <code>56</code>), и её операнд записывается сырым текстом без префикса <code>E3</code> — так же, как у <code>REM</code>.
| |
|
| |
| Значения назначены не по алфавиту, а тематическими группами с запасом внутри каждой: управление ходом выполнения в 0x21–0x2F, ввод-вывод около 0x41–0x4C, дисковые операции в 0x74–0x83. Составные дисковые команды получают '''один''' токен на всю фразу, тогда как в Wang та же команда собирается из нескольких (<code>DATA</code> + <code>LOAD</code> + <code>DC</code>).
| |
|
| |
| Документация<ref name="balasanyan" /> описывает также операторы <code>DATA SAVE DC END</code>, <code>DATA SAVE DC CLOSE</code> и <code>DATA LOAD DC OPEN</code>. Все три найдены: <code>DATA LOAD DC OPEN</code> — токен <code>75</code>, <code>DATA SAVE DC CLOSE</code> — отдельный токен <code>77</code>, а <code>DATA SAVE DC END</code> оказался не отдельным глаголом, а обычным <code>DATA SAVE DC</code> (<code>76</code>) с единственным операндом <code>D7</code> — ключевым словом <code>END</code>.
| |
|
| |
| === Операнды дисковых операторов ===
| |
|
| |
| Первый байт операндов дискового оператора — '''код устройства или режима''', а не ссылка на переменную:
| |
|
| |
| {| class="wikitable"
| |
| ! Байт !! Значение
| |
| |-
| |
| | <code>00</code> || <code>F</code> — первый дисковод
| |
| |-
| |
| | <code>01</code> || <code>R</code> — второй дисковод
| |
| |-
| |
| | <code>02</code> || <code>T</code> — обращение по номеру файла (<code>#n</code>)
| |
| |}
| |
|
| |
| Правило проверено на <code>LIST DC F</code> → <code>7C 01 00</code>, <code>LIST DC R</code> → <code>7C 01 01</code>, <code>LOAD DC F/1C,"М2"</code> → <code>7D 08 00 DC DE 1C DE E3 02 ED 32</code>, <code>SCRATCH R"VIC"</code> → <code>81 05 01 E3 03 56 49 43</code>, <code>SCRATCH T#D,F1¤</code> → <code>81 05 02 DB 22 DE 4C</code>, <code>DATA SAVE DC OPEN T(700)A¤</code> → <code>78 07 02 EB E7 07 00 D0 09</code>.
| |
|
| |
| Ключевые слова конца и начала файла кодируются токенами, у которых в позиции операции другое значение:
| |
|
| |
| 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
| |
|
| |
| Тот же <code>D6</code> в операторах <code>DATA SAVE DC</code> и <code>SAVE DC</code> печатается как <code>¤</code>, а <code>D2</code> (обычно <code>STEP</code>) — как параметр <code>T</code>:
| |
|
| |
| 76 07 D6 DB E8 02 DE E0 12 DATA SAVE DC ¤#2,W()
| |
| 80 0E 01 D6 D2 EB E3 03 «VIC» D0 E3 03 «VIC» SAVE DC R¤T("VIC")"VIC"
| |
|
| |
| Оператор <code>SCRATCH DISK</code> использует ключевые слова <code>LS</code> и <code>END</code>:
| |
|
| |
| 82 0B 01 06 D9 E8 05 DE D7 D9 E7 10 00 SCRATCH DISK RLS=5,END=1000
| |
| 82 0B 00 06 D9 E8 05 DE D7 D9 E7 10 00 SCRATCH DISK FLS=5,END=1000
| |
|
| |
| Здесь <code>01</code>/<code>00</code> — дисковод, <code>06</code> — ключевое слово <code>LS</code>, <code>D9</code> — знак равенства, <code>D7</code> — <code>END</code>.
| |
|
| |
| === Списки переменных и списки выражений ===
| |
|
| |
| Список '''принимающих''' переменных пишется '''без разделителей''' — соседние ссылки на переменные и есть список:
| |
|
| |
| 41 02 0A 0B INPUT T0,N0
| |
| 74 03 05 0C 0D DATA LOAD DC A1¤,E1,E2
| |
| 7B 08 02 DB 22 DE 4C 4D 4E 36 4F LIMITS T#D,F1¤,O,A,Q,R
| |
| 46 04 00 01 02 03 DIM X(300),Y(300),N(2),G¤(300)5
| |
|
| |
| Список '''выражений''' разделяется <code>DE</code>:
| |
|
| |
| 76 06 D6 05 DE 0A DE 0B DATA SAVE DC ¤A1¤,T0,N0
| |
| 28 0B E7 04 70 DE 0F DE 10 DE 00 10 D0 PRINTUSING 470,I,D,X(D)
| |
|
| |
| Запятая между приглашением и переменной в операторе <code>INPUT</code> не кодируется:
| |
|
| |
| 41 14 E3 11 «СТЕПЕНЬ СВОБОДЫ » 00 INPUT "СТЕПЕНЬ СВОБОДЫ ",K
| |
|
| |
| Номер строки-образа в <code>PRINTUSING</code> — обычная константа <code>E7</code>, а диапазоны строк в <code>SAVE</code>, <code>LOAD</code> и <code>SAVE DC</code> записываются '''сырым двухбайтовым BCD''' без токена:
| |
|
| |
| 80 … 90 00 DE 90 90 SAVE DC …"*ASMBBAS" 9000,9090
| |
| 2A 0A DD 10 52 15 DE 52 15 DE 52 25 SAVE Z¤5215,5215,5225
| |
|
| |
| У оператора <code>BIN(</code> закрывающая скобка и знак равенства не кодируются, как у <code>PACK(</code> и <code>UNPACK(</code>:
| |
|
| |
| 4B 02 22 3B BIN(A¤)=J%
| |
|
| |
| === Двухбайтовые глаголы ===
| |
|
| |
| Матричные и графические операторы кодируются двумя байтами: префиксом <code>06</code> и подкодом. Длина операторной части следует за подкодом на обычном месте:
| |
|
| |
| 06 <подкод> <len> <операнды>
| |
|
| |
| Разборщик, ожидающий однобайтовый глагол, на таких строках теряет синхронизацию.
| |
|
| |
| {| class="wikitable"
| |
| ! Подкод !! Оператор !! Достоверность
| |
| |-
| |
| | <code>00</code> || <code>PLOT</code> (?) || выведено из данных, см. ниже
| |
| |-
| |
| | <code>01</code> || MAT (присваивание) || подтверждено
| |
| |-
| |
| | <code>02</code> || MAT REDIM || подтверждено
| |
| |-
| |
| | <code>03</code> || MAT READ || по контексту: <code>RESTORE 1,X:06 03 02 E0 24</code>
| |
| |-
| |
| | <code>04</code> || MAT INPUT || по контексту: приглашение <code>PRINT</code>, затем <code>06 04 02 E0 32</code>
| |
| |-
| |
| | <code>06</code> || MAT COPY || подтверждено
| |
| |-
| |
| | <code>0A</code> || MAT SEARCH || подтверждено
| |
| |-
| |
| | <code>0C</code> || <code>¤TRAN(</code> || подтверждено
| |
| |-
| |
| | <code>09</code> || MAT MOVE (?) || выведено из данных: <code><массив>,<выр.>,<выр.> TO <цель></code>
| |
| |-
| |
| | <code>0B</code> || MAT SORT || форма <code><массив> TO <массив>,<массив></code> совпадает с <code>MAT SORT B¤()TOQ¤(),L¤()</code> из текстового листинга
| |
| |-
| |
| | <code>0D</code> || <code>¤PACK</code> (?) || <code>(<A¤>=<формат¤>) <B¤> FROM <выр.></code>
| |
| |-
| |
| | <code>0E</code> || <code>¤UNPACK</code> (?) || то же с <code>TO</code>
| |
| |-
| |
| | <code>0F</code> || <code>¤OPEN</code> || подтверждено
| |
| |-
| |
| | <code>13</code> || DOT || сопоставление идиом двух программ
| |
| |-
| |
| | <code>14</code> || DDRAW || сопоставление идиом двух программ
| |
| |-
| |
| | <code>15</code> || DRAW || подтверждено
| |
| |-
| |
| | <code>19</code> || NPLOT || подтверждено
| |
| |-
| |
| | <code>1C</code> || STRETCH || подтверждено
| |
| |-
| |
| | <code>1E</code> || LABEL || подтверждено
| |
| |-
| |
| | <code>1F</code> || <code>¤COPY</code> || подтверждено
| |
| |-
| |
| | <code>22</code> || <code>¤LET</code> || подтверждено
| |
| |-
| |
| | <code>23</code> || WINDOW || подтверждено
| |
| |-
| |
| | <code>24</code> || LINPUT || подтверждено
| |
| |-
| |
| | <code>25</code> || ASMB || подтверждено
| |
| |-
| |
| | <code>26</code> || REPLACE || подтверждено
| |
| |}
| |
|
| |
| Операторы <code>DOT</code> и <code>DDRAW</code> опознаны совпадением идиомы в двух программах. Текстовый листинг <code>FAN01</code>:
| |
|
| |
| 1540 T3=1:FOR T=2TOT4:NPLOT B6¤(),(M6+M8*(T-T3)),P7+3:DDRAW B6¤(),0,-3:NEXT T
| |
| 1560 …:DOT B6¤(),R8,R9:DDRAW B6¤(),R6,R7:NEXT T
| |
|
| |
| Токены <code>M4</code> в тех же конструкциях (рисование засечки на оси и отрезка по приращениям):
| |
|
| |
| 06 19 … : 06 14 09 E0 10 DE E8 00 DE E9 E8 03 NPLOT …:DDRAW A(),0,-3
| |
| 06 13 06 E0 10 DE 2A DE 2B : 06 14 06 E0 10 DE 2C DE 1C
| |
|
| |
| ==== Подкод 00 ====
| |
|
| |
| Операнды состоят из групп, ограниченных <code>D7</code> … <code>D4</code> (то есть <code><</code> … <code>></code>) и разделённых <code>DE</code>; внутри группы до трёх элементов, тоже через <code>DE</code>. Третий элемент (перо) может отсутствовать, быть выражением, строковым литералом или одним из токенов <code>E5</code>/<code>E6</code>:
| |
|
| |
| 06 00 09 D7 E8 00 DE E8 10 DE E6 D4 <0,16,…>
| |
| 06 00 … D7 E8 56 DE E7 02 55 DE D4 DE D7 DE 2E E9 E7 02 25 DE E6 D4 …
| |
|
| |
| Такой синтаксис соответствует оператору <code>PLOT</code> системы Wang 2200, где аргументы записываются в угловых скобках. В указателе операторов книги<ref name="balasanyan" /> <code>PLOT</code> присутствует только как имя устройства вывода, поэтому имя оператора считается вероятным, но не доказанным.
| |
|
| |
| Примеры из <code>STAT00</code> и <code>STAT01A</code>:
| |
|
| |
| 06 02 19 E0 00 EB 09 D0 DE E0 01 EB 08 D0 DE … MAT REDIM X(…),Y(…),…
| |
| 06 01 04 E0 07 D9 EF MAT S0=ZER
| |
| 06 19 08 E0 12 DE 26 DE E7 02 50 NPLOT B6¤(),C1,250
| |
| 06 1F 06 DC DE 14 DE E0 12 ¤COPY /14,B6¤()
| |
|
| |
| В матричном присваивании <code>EF</code> в позиции операнда означает <code>ZER</code>.
| |
|
| |
| В операторе <code>MAT REDIM</code> размерности массива, в отличие от обращения к элементу, заключаются в '''явную''' открывающую скобку <code>EB</code>, а между собой разделяются <code>DE</code>. Длина элемента символьного массива указывается после закрывающей скобки:
| |
|
| |
| E0 02 EB 0E DE 0D D0 Q(M1,N) — двумерный
| |
| E0 05 EB 0E DE 0F D0 A(M1,M2)
| |
| E0 01 EB 0D D0 E8 05 G¤(N)5 — длина элемента после скобки
| |
|
| |
| ==== Пропущенные параметры ====
| |
|
| |
| Пропущенный параметр не кодируется ничем: между разделителями <code>DE</code> просто нет операнда. Оператор <code>LABEL B6¤(),,,B3¤(1)</code> с тремя пропущенными параметрами даёт три подряд идущих <code>DE</code>:
| |
|
| |
| 06 1E 09 E0 12 DE DE DE 11 E8 01 D0
| |
|
| |
| === Операнды ===
| |
|
| |
| {| class="wikitable"
| |
| ! Токен !! Значение !! Достоверность
| |
| |-
| |
| | <code>00</code>–<code>C9</code> || ссылка на переменную по индексу || подтверждено
| |
| |-
| |
| | <code>CA</code> || FROM (в <code>PACK</code>/<code>UNPACK</code>) || подтверждено
| |
| |-
| |
| | <code>CB</code> || ALL (в <code>RETURN CLEAR ALL</code>) || подтверждено
| |
| |-
| |
| | <code>CC</code> || GOSUB в составе <code>ON … GOSUB</code> || подтверждено
| |
| |-
| |
| | <code>CD</code> || GOTO в составе <code>ON … GOTO</code> || подтверждено
| |
| |-
| |
| | <code>D0</code> || <code>)</code> || вероятно
| |
| |-
| |
| | <code>D1</code> || TO || подтверждено
| |
| |-
| |
| | <code>D2</code> || STEP; параметр <code>T</code> в <code>SAVE DC</code> || подтверждено
| |
| |-
| |
| | <code>D3</code> || THEN, далее 2 байта BCD — номер строки || подтверждено
| |
| |-
| |
| | <code>D4</code> || <code>></code> || подтверждено
| |
| |-
| |
| | <code>D5</code> || <code><></code> в позиции операции, <code>AT(</code> в позиции операнда || подтверждено
| |
| |-
| |
| | <code>D6</code> || <code><=</code> в позиции операции; <code>BEG</code> или <code>¤</code> в позиции операнда || подтверждено
| |
| |-
| |
| | <code>D7</code> || <code><</code> в позиции операции; <code>END</code> в позиции операнда || подтверждено
| |
| |-
| |
| | <code>D8</code> || <code>>=</code> в позиции операции, <code>ROUND(</code> в позиции операнда || подтверждено
| |
| |-
| |
| | <code>D9</code> || <code>=</code> || подтверждено
| |
| |-
| |
| | <code>DB</code> || <code>#</code> || подтверждено
| |
| |-
| |
| | <code>DC</code> || <code>/</code> || вероятно
| |
| |-
| |
| | <code>DD</code> || <code>;</code> || подтверждено
| |
| |-
| |
| | <code>DE</code> || <code>,</code> || подтверждено
| |
| |-
| |
| | <code>DF</code> || <code>*</code> в позиции операции, <code>TAB(</code> в позиции операнда || подтверждено
| |
| |-
| |
| | <code>E0</code> || <code>^</code> в позиции операции, ссылка на массив целиком в позиции операнда || подтверждено
| |
| |-
| |
| | <code>E1</code> || функция <code>STR</code> || подтверждено
| |
| |-
| |
| | <code>E2</code> || шестнадцатеричный литерал <code>HEX(…)</code>: <code>E2 <len> <len байт></code> || подтверждено
| |
| |-
| |
| | <code>E3</code> || строковый литерал в кавычках: <code>E3 <len> <len байт КОИ-8></code> || подтверждено
| |
| |-
| |
| | <code>E4</code> || строковый литерал в апострофах, кодирование то же || подтверждено
| |
| |-
| |
| | <code>E5</code> || число с фиксированной точкой, см. ниже || подтверждено
| |
| |-
| |
| | <code>E6</code> || число с порядком, см. ниже || подтверждено
| |
| |-
| |
| | <code>E7</code> || числовая константа: <code>E7 <2 байта BCD></code>, диапазон 0–9999 || подтверждено
| |
| |-
| |
| | <code>E8</code> || числовая константа: <code>E8 <байт BCD></code>, диапазон 0–99 || подтверждено
| |
| |-
| |
| | <code>E9</code> || <code>-</code>, бинарный или унарный || подтверждено
| |
| |-
| |
| | <code>EA</code> || <code>+</code> || вероятно
| |
| |-
| |
| | <code>EB</code> || <code>(</code> || вероятно
| |
| |-
| |
| | <code>EC</code>, <code>ED</code>, <code>EE</code>, <code>EF</code> || <code>POS(</code>, <code>LEN(</code>, <code>NUM(</code>, <code>VAL(</code> — без закрывающей скобки || подтверждено
| |
| |-
| |
| | <code>F0</code> || <code>FN<имя>(</code> — обращение к функции пользователя; имя следует сырым кодом символа || подтверждено
| |
| |-
| |
| | <code>F1</code> || <code>#PI</code> — константа π || подтверждено
| |
| |-
| |
| | <code>F2</code> || <code>ABS(</code> || подтверждено
| |
| |-
| |
| | <code>F3</code> || <code>INT(</code> || подтверждено
| |
| |-
| |
| | <code>F4</code> || <code>RND(</code> || подтверждено
| |
| |-
| |
| | <code>F5</code> || <code>SGN(</code> || подтверждено
| |
| |-
| |
| | <code>F6</code> || <code>SQR(</code> || подтверждено
| |
| |-
| |
| | <code>F7</code> || <code>LOG(</code> || подтверждено
| |
| |-
| |
| | <code>F8</code> || <code>EXP(</code> || подтверждено
| |
| |-
| |
| | <code>F9</code>, <code>FA</code>, <code>FB</code>, <code>FC</code> || математические функции, имена не установлены || выведено из данных
| |
| |-
| |
| | <code>FD</code> || <code>ARCTAN(</code> || вероятно
| |
| |-
| |
| | <code>FE</code> || разделитель записей строк || подтверждено
| |
| |}
| |
|
| |
| === Два вида строковых литералов ===
| |
|
| |
| Строковые константы кодируются двумя разными токенами, и разницу между ними книга<ref name="balasanyan" /> объясняет прямым текстом: строки в кавычках система распознаёт и выводит как строки из '''прописных''' букв, а строки в апострофах — как строки из '''строчных'''. Так на машине, у которой на клавиатуре есть только прописные буквы, задаются строчные символьные константы.
| |
|
| |
| {| class="wikitable"
| |
| ! Токен !! Форма записи !! Регистр
| |
| |-
| |
| | <code>E3</code> || <code>"…"</code> || прописные
| |
| |-
| |
| | <code>E4</code> || <code>'…'</code> || строчные
| |
| |}
| |
|
| |
| Кодирование одинаковое: <code><токен> <длина: 1 байт> <длина байт КОИ-8></code>.
| |
|
| |
| Наблюдения это подтверждают: из 9937 литералов <code>E3</code> в наборе ни один не содержит строчной кириллицы (<code>C0</code>–<code>DF</code>), а из 28 литералов <code>E4</code> строчную содержат пять — остальные состоят из латиницы и знаков, где регистр неразличим. Правило книги «внутри строки не должно быть кавычек или апострофов» тоже видно: единственный литерал набора, содержащий кавычки, записан апострофами.
| |
|
| |
| E4 3D 35 2D DA C1 D0 CF CC CE C5 CE C9 C5 …
| |
| PRINT '5-заполнение полей "номер страницы" и "обозначение документа"'
| |
|
| |
| Разборщик, не знающий токена <code>E4</code>, теряет синхронизацию до конца оператора.
| |
|
| |
| === Математические функции ===
| |
|
| |
| Книга<ref name="balasanyan" /> (разд. 4.7) перечисляет пятнадцать функций: <code>SIN</code>, <code>COS</code>, <code>TAN</code>, <code>ARCSIN</code>, <code>ARCCOS</code>, <code>ARCTAN</code>, <code>RND</code>, <code>ABS</code>, <code>INT</code>, <code>SGN</code>, <code>LOG</code>, <code>EXP</code>, <code>SQR</code>, <code>ROUND</code>, <code>#PI</code>. Все они, кроме пяти тригонометрических, размещены по токенам:
| |
|
| |
| * <code>F1</code> = <code>#PI</code>. В <code>STAT04</code> выражение <code>SQR(2*#PI)</code> кодируется как <code>F6 E8 02 DF F1 D0</code>; сверено с текстовой копией той же программы.
| |
| * <code>F4</code> = <code>RND(</code>. В <code>UDAW</code>: <code>E8 50 DF F4 E8 01 D0 EA E8 15</code> — <code>50*RND(1)+15</code>, а в соседней строке <code>10*RND(1)<9.68</code>.
| |
| * <code>F5</code> = <code>SGN(</code>. В <code>EDITOR</code> строка 6717: <code>4E D9 F5 65 E9 04 34 D0 D0</code> = <code>B%=SGN(B-P%(I))</code>; сверено с текстовой копией.
| |
| * <code>D8</code> = <code>ROUND(</code> в позиции операнда, см. ниже.
| |
| * <code>FD</code> — вероятно <code>ARCTAN(</code>: в <code>M3</code> и <code>P3</code> её единственный аргумент всегда отношение (<code>FD 25 DC 26 D0</code>).
| |
|
| |
| На пять оставшихся имён (<code>SIN</code>, <code>COS</code>, <code>TAN</code>, <code>ARCSIN</code>, <code>ARCCOS</code>) приходится всего четыре свободных токена — <code>F9</code>, <code>FA</code>, <code>FB</code>, <code>FC</code>; программ, сохранённых одновременно в обоих представлениях и использующих тригонометрию, в исследованном наборе нет, поэтому распределение имён по токенам не установлено.
| |
|
| |
| === Функции пользователя ===
| |
|
| |
| Оператор <code>DEFFN <имя>(<формальная переменная>) = <выражение></code> (глагол <code>5A</code>) определяет функцию, обращение к ней записывается <code>FN<имя>(<выражение>)</code> и кодируется токеном <code>F0</code>:
| |
|
| |
| 5A <имя> <lo> <hi> <формальная переменная> <выражение>
| |
| F0 <имя> <выражение> D0
| |
|
| |
| Имя функции — латинская буква либо цифра, и это не индекс переменной: пространства имён разные, одна и та же величина может быть и переменной, и именем функции. Знака равенства в потоке нет: тело следует сразу за байтом формальной переменной. Два байта между именем и формальной переменной — рабочее поле, которое машина заполняет при исполнении; у последнего определения в программе там нули.
| |
|
| |
| Разбор подтверждается тем, что в каждом файле байты после <code>F0</code> — ровно те имена, которые этот файл определяет оператором <code>DEFFN</code>, и никакие другие. Пример: <code>5A 48 00 00 2A EB 2A EA F2 2A D0 D0 DC E8 02</code> = <code>DEFFN H(X)=(X+ABS(X))/2</code>.
| |
|
| |
| === Двузначность токенов старшей половины ===
| |
|
| |
| Часть токенов диапазона <code>D0</code>–<code>EF</code> читается по-разному в зависимости от того, ожидается ли на их месте знак операции или очередной операнд. Интерпретатор различает их по состоянию разбора, отдельного признака в файле нет.
| |
|
| |
| {| class="wikitable"
| |
| ! Токен !! В позиции операции !! В позиции операнда
| |
| |-
| |
| | <code>D2</code> || STEP || параметр <code>T</code> оператора <code>SAVE DC</code>
| |
| |-
| |
| | <code>D5</code> || <code><></code> || <code>AT(</code>
| |
| |-
| |
| | <code>D6</code> || <code><=</code> || <code>BEG</code>; <code>¤</code> после <code>DATA SAVE DC</code> и <code>SAVE DC</code>
| |
| |-
| |
| | <code>D7</code> || <code><</code> || <code>END</code>
| |
| |-
| |
| | <code>D8</code> || <code>>=</code> || <code>ROUND(</code>
| |
| |-
| |
| | <code>DF</code> || <code>*</code> || <code>TAB(</code>
| |
| |-
| |
| | <code>E0</code> || <code>^</code> || ссылка на массив целиком
| |
| |-
| |
| | <code>E9</code> || <code>-</code> бинарный || <code>-</code> унарный
| |
| |}
| |
|
| |
| Двузначность <code>D8</code> подтверждена тремя независимыми строками с текстовой сверкой:
| |
|
| |
| 2F D9 D8 EF 11 DE DB DC 17 DE E8 01 D0 Y=ROUND(VAL(Y2¤,2)/K7,1)
| |
| D8 E5 14 21 17 DF 64 E8 07 D0 DE E8 00 D0 ROUND(2.117*P(7),0)
| |
| B1 D9 D8 B1 DC 9A DE E8 00 D0 Q5=ROUND(Q5/P4,0)
| |
|
| |
| В операторе <code>MAT SEARCH</code> знак сравнения стоит '''в позиции операнда''' — сразу после запятой, отделяющей искомое значение:
| |
|
| |
| 06 0A 08 E0 20 DE D4 22 D1 E0 1E MAT SEARCH L4¤(),>A¤TON4¤()
| |
| 06 0A 0F E1 0D E8 01 DE 45 D0 DE D9 E3 01 20 D1 E0 20 MAT SEARCH STR(Q¤,1,M%),=" "TOL4¤()
| |
|
| |
| {{Внимание|1=Ссылками на переменные являются значения '''<code>00</code>–<code>C9</code>''', а не <code>00</code>–<code>BF</code>: первый токен — <code>CA</code> (<code>FROM</code>). Индексы <code>C0</code>–<code>C9</code> реально встречаются в крупных программах (в <code>EDITOR</code> 200 переменных), например <code>C1 D9 9C</code> — присваивание переменной с индексом <code>C1</code>. Разборщик с границей <code>C0</code> портит листинг таких программ.}}
| |
|
| |
| Ссылка на массив целиком записывается как <code>E0</code> и порядковый номер переменной в <code>DIM</code>: <code>74 02 E0 09</code> соответствует <code>DATA LOAD DC G¤()</code>, а <code>E0 09 D1 E0 00</code> — <code>G¤()TOX()</code>.
| |
|
| |
| Каждое прочтение подтверждено не менее чем двумя независимыми строками <code>STAT05</code>: <code>TAB(17)</code> кодируется как <code>DF E8 17 D0</code>, а <code>.5*LOG(</code> — как <code>E5 01 50 DF F7</code>; <code>K(1)<>1</code> и <code>PRINT AT(9,10)</code> оба дают <code>D5</code>; <code>K0*(-1)</code> даёт <code>DF EB E9 E8 01 D0</code>, где <code>E9</code> стоит в позиции операнда.
| |
|
| |
| Разборщик с одним фиксированным значением на токен на таких строках даёт неверный листинг, поэтому таблицу операндов нужно применять с учётом позиции.
| |
|
| |
| === Оператор SELECT ===
| |
|
| |
| Первый байт операндов — код группы устройств, записи разделяются <code>DE</code>. Ширина строки, если она задана, записывается после <code>EB</code> как '''двоичное 16-битное число, старший байт первым''' (не BCD).
| |
|
| |
| {| class="wikitable"
| |
| ! Код !! Группа !! Пример
| |
| |-
| |
| | <code>00</code> || файл <code>#n</code>: номер, адрес и, необязательно, дисковод || <code>00 01 18</code> = <code>#118</code>; <code>00 01 18 00</code> = <code>#118F</code>
| |
| |-
| |
| | <code>01</code> || без параметров; вероятно режим тригонометрии (<code>SELECT D</code>/<code>R</code>) || <code>54 01 01</code>
| |
| |-
| |
| | <code>05</code> || <code>P</code> — пауза после каждой выводимой строки || <code>05 01</code> = <code>SELECT P1</code>; <code>05</code> = <code>SELECT P</code>
| |
| |-
| |
| | <code>06</code> || устройство, вероятно <code>LIST</code> || <code>06 0C</code>
| |
| |-
| |
| | <code>07</code> || <code>PRINT</code> || <code>07 0C EB 00 82</code> = <code>SELECT PRINT0C(130)</code>
| |
| |-
| |
| | <code>08</code> || вероятно <code>PLOT</code> || <code>08 10 DE …</code> (адрес 10 — блок отображения графической информации)
| |
| |-
| |
| | <code>0A</code> || <code>DISK</code> || <code>0A 18 01</code> = <code>SELECT DISK18R</code>
| |
| |-
| |
| | <code>0C</code> || устройство, вероятно <code>CO</code> || <code>0C 05 EB 00 50</code>
| |
| |}
| |
|
| |
| Опознание кодов <code>01</code>, <code>06</code>, <code>08</code> и <code>0C</code> косвенное. В книге<ref name="balasanyan" /> перечислены группы <code>CI</code>, <code>CO</code>, <code>PRINT</code>, <code>LIST</code>, <code>TAPE</code>, <code>PLOT</code>, <code>DISK</code>, <code>#n</code> и <code>P</code>; строка 300 файла <code>GC121</code> выполняет <code>0C 05(80), 07 05(80), 06 05</code> — типичный возврат <code>CO</code>, <code>PRINT</code> и <code>LIST</code> на экран.
| |
|
| |
| В записях группы <code>00</code> дисковод указывается не всегда: <code>SELECT #118</code> даёт трёхбайтовую запись, <code>SELECT #118F</code> — четырёхбайтовую. Разборщик, всегда читающий четыре байта, съедает разделитель следующей записи.
| |
|
| |
| === Числовые константы ===
| |
|
| |
| Целые константы кодируются двумя токенами в зависимости от величины: <code>E8</code> — один байт BCD (0–99), <code>E7</code> — два байта BCD (0–9999).
| |
|
| |
| Для дробных чисел служат токены <code>E5</code> и <code>E6</code>. За токеном следует байт-описатель, затем цифры числа в BCD по две на байт; если цифр нечётное число, последняя тетрада заполняется нулём. В описателе '''старшая тетрада — количество цифр до запятой, младшая — общее количество цифр'''.
| |
|
| |
| {| class="wikitable"
| |
| ! Запись !! Байты !! Описатель !! Цифры
| |
| |-
| |
| | <code>.5</code> || <code>E5 01 50</code> || 0 / 1 || 5
| |
| |-
| |
| | <code>.08</code> || <code>E5 02 08</code> || 0 / 2 || 08
| |
| |-
| |
| | <code>2.5</code> || <code>E5 12 25</code> || 1 / 2 || 25
| |
| |-
| |
| | <code>.196854</code> || <code>E5 06 19 68 54</code> || 0 / 6 || 196854
| |
| |-
| |
| | <code>.000344</code> || <code>E5 06 00 03 44</code> || 0 / 6 || 000344
| |
| |}
| |
|
| |
| Токен <code>E6</code> устроен так же, но после цифр мантиссы идёт дополнительный байт порядка: <code>1E6</code> кодируется как <code>E6 11 10 06</code> — одна цифра мантиссы, одна цифра до запятой, порядок 6.
| |
|
| |
| Токен <code>E5</code> служит и для '''целых чисел больше 9999''' — всего, что не помещается в <code>E7</code>. Дробной части при этом нет, обе тетрады описателя равны:
| |
|
| |
| {| class="wikitable"
| |
| ! Запись !! Байты !! Описатель !! Цифры
| |
| |-
| |
| | <code>51840</code> || <code>E5 55 51 84 00</code> || 5 / 5 || 51840
| |
| |-
| |
| | <code>248830</code> || <code>E5 66 24 88 30</code> || 6 / 6 || 248830
| |
| |-
| |
| | <code>2488320</code> || <code>E5 77 24 88 32 00</code> || 7 / 7 || 2488320
| |
| |-
| |
| | <code>2.718281828</code> || <code>E5 1A 27 18 28 18 28</code> || 1 / 10 || 2718281828
| |
| |}
| |
|
| |
| Длина хвоста токена — <code>1 + ⌈''младшая тетрада'' / 2⌉</code> байт для <code>E5</code> и на один байт больше для <code>E6</code>. Разборщик, не потребляющий эти байты, теряет синхронизацию до конца оператора; в исследованном наборе <code>E5</code> встречается в 407 операторах, <code>E6</code> — в 210.
| |
|
| |
| Номер метки в <code>GOSUB'</code> хранится '''двоичным''', а не в BCD. То же относится к первому байту операндов оператора <code>DEFFN'</code>.
| |
|
| |
| === Цепочка операторов DATA ===
| |
|
| |
| Два последних байта операндов каждого оператора <code>DATA</code> — '''указатель на следующий оператор <code>DATA</code>, младший байт первым'''. У последнего оператора <code>DATA</code> в программе он равен <code>00 00</code>. По этой цепочке работают операторы <code>READ</code> и <code>RESTORE</code>.
| |
|
| |
| {| class="wikitable"
| |
| ! Файл !! Значения указателей
| |
| |-
| |
| | <code>DIG DEM</code> || 03C0, 03E7, 040E, 0435, 045C, …, 0594, 0000
| |
| |-
| |
| | <code>M0</code> || 0443, 046A, 0491, 04B8, …, 0617, 0000
| |
| |-
| |
| | <code>VICT</code> || 0A97, 0ABE, 0AE5, 0B0C, …, 0C6B, 0000
| |
| |}
| |
|
| |
| Шаг постоянен, когда постоянна длина операторов, но значения — адреса в памяти интерпретатора, а не смещения в потоке файла, поэтому пересчёт по потоку сходится не всегда.
| |
|
| |
| {{Внимание|1=Эти два байта нельзя разбирать как токены. Именно они выглядели как «неопознанные токены» в конце строк <code>DATA</code>: <code>C0 14</code>, <code>C9 04</code>, <code>FA 0E</code>, <code>CF 0B</code>, <code>E4 2C</code>, <code>F9 2C</code>.}}
| |
|
| |
| Само тело оператора <code>DATA</code> — список значений '''без разделителей''':
| |
|
| |
| 29 1E E8 31 E7 03 34 E8 00 E8 28 E7 03 06 E8 31 … C9 04
| |
| 31 334 0 28 306 31
| |
|
| |
| Поэтому внутри <code>DATA</code> токены <code>E5</code>, <code>E6</code> и <code>E7</code> всегда читаются как константы и никогда как <code>OR</code>/<code>AND</code>, хотя и стоят в позиции операции. То же относится к оператору <code>MAT REDIM</code>, где длина элемента идёт сразу за закрывающей скобкой:
| |
|
| |
| 06 02 0A E0 04 EB E7 01 26 D0 E7 01 28 MAT REDIM A¤(126)128
| |
|
| |
| === Подпрограммы с параметрами ===
| |
|
| |
| Аргументы <code>GOSUB'</code> следуют сразу за байтом метки и разделяются токеном <code>DE</code>; скобки не кодируются. Вызов <code>GOSUB ' 5(1,1,N-2)</code> даёт:
| |
|
| |
| 23 0B 05 E8 01 DE E8 01 DE 0D E9 E8 02
| |
|
| |
| Объявление <code>DEFFN'</code> содержит метку, четыре байта адреса входа и индексы формальных параметров. Формальные параметры, в отличие от фактических, идут '''подряд, без разделителей'''. Объявление <code>DEFFN ' 5(N1,D1,D2)</code> даёт:
| |
|
| |
| 27 08 05 00 00 00 00 12 20 21
| |
|
| |
| Четыре байта после метки заполняются интерпретатором при первом обращении к подпрограмме, поэтому нулевыми они бывают только там, где подпрограмму ни разу не звали. В приведённом примере из <code>STAT05</code> они нулевые, а в <code>EDITOR</code> — осмысленные адреса:
| |
|
| |
| 27 08 64 68 02 11 14 4F 50 51 EDITOR 400, DEFFN ' 100(L1,L4,L3)
| |
| 27 06 4D 00 00 00 00 3D EDITOR 7740, DEFFN ' 77(D)
| |
|
| |
| При разборе эти четыре байта следует пропускать, не пытаясь читать как токены.
| |
|
| |
| Тот же вид имеет и <code>3A</code> — определение клавиши специальных функций, но за адресом у него стоит ровно один литерал: в кавычках (<code>E3</code>), в апострофах (<code>E4</code>) или шестнадцатеричный (<code>E2</code>). Последний случай встречается в <code>SCOPE</code>:
| |
|
| |
| 3A 08 1F 00 00 A4 03 E2 01 0D DEFFN ' 31 HEX(0D)
| |
|
| |
| Подпрограммой такая метка не является: <code>3A</code> задаёт текст, подставляемый при нажатии клавиши, и <code>GOSUB'</code> на неё не переходит.
| |
|
| |
| Оператор <code>ON … GOTO</code> кодируется как индекс переменной, токен <code>CD</code> и номера строк по два байта BCD подряд без разделителей.
| |
|
| |
| === Неявные функции и уровни скобок ===
| |
|
| |
| Функции <code>POS(</code>, <code>LEN(</code>, <code>NUM(</code>, <code>VAL(</code> и <code>AT(</code> закрывающей скобки в потоке не имеют: они заканчиваются на первом же знаке операции. Важно, что «своего» уровня — закрывающая скобка <code>D0</code>, принадлежащая вложенной явной скобке, такую функцию не закрывает.
| |
|
| |
| EDITOR, строка 265:
| |
| EC E1 E0 2A E8 01 D0 D7 DE 20 D5 E8 00 D3 02 70
| |
| POS(STR( V¤() , 1 ) < 20 ) <> 0 THEN 270
| |
|
| |
| Здесь <code>D0</code> закрывает только <code>STR(</code>; <code>POS(</code> остаётся открытой и заканчивается на <code>D5</code>. Разборщик, считающий неявные скобки одним счётчиком, выдаст <code>POS(STR(V¤(),1))<20<>0</code> — с потерей смысла. Правильная модель — стек: на каждую явную скобку (включая список индексов массива) заводится уровень, неявные функции считаются внутри текущего уровня, а <code>D0</code> закрывает сначала неявные функции своего уровня, затем сам уровень.
| |
|
| |
| === Что не кодируется и восстанавливается по форме оператора ===
| |
|
| |
| Часть знаков препинания в потоке отсутствует: их положение определяется синтаксисом конкретного оператора, а не токенами.
| |
|
| |
| {| class="wikitable"
| |
| ! Оператор !! В потоке !! В листинге
| |
| |-
| |
| | <code>INIT</code> || <code>64 04 E3 01 2D 15</code> || <code>INIT ("-")U¤</code> — значение в скобках, запятой нет
| |
| |-
| |
| | <code>CONVERT</code> || <code>47 0F 48 D1 E1 … D0 E3 02 «##»</code> || <code>CONVERT P% TO STR(…),(##)</code> — образ в скобках
| |
| |-
| |
| | <code>¤GIO</code> с адресом || <code>40 0D DC DE 34 DE E2 06 … 22</code> || <code>¤GIO /34,(HEX(…),A¤)</code>
| |
| |-
| |
| | <code>¤GIO</code> с апострофом || <code>40 … D5 E2 … 07 …</code> || <code>¤GIO 'HEX(…),D¤(2)</code> — здесь скобок нет
| |
| |-
| |
| | <code>VERIFY</code> || <code>83 … 02 DB 39 E8 01 D0 DE 16 … 17 …</code> || <code>VERIFY T#D%(1),(X(1),Y(1))</code>
| |
| |-
| |
| | <code>¤TRAN(</code> || <code>06 0C 06 0D DE 12 D0 DE 00</code> || <code>¤TRAN(Q¤,L0¤)R</code> — запятая после <code>)</code> не печатается
| |
| |-
| |
| | <code>LABEL</code> || <code>06 1E 0A E0 1F E8 03 DE DE DE E3 01 51</code> || <code>LABEL B¤()3,,,"Q"</code>
| |
| |-
| |
| | <code>MAT</code> || <code>06 01 04 E0 54 D9 EF</code> || <code>MAT C%=ZER</code> — имя массива без <code>()</code>
| |
| |-
| |
| | <code>RESTORE</code> || <code>51 05 E8 01 DE 48 50</code> || <code>RESTORE 1,4850</code> — номер строки сырым BCD
| |
| |-
| |
| | <code>SAVE</code>, <code>LOAD</code> || <code>2A 0A DD 10 52 15 DE 52 15 DE 52 25</code> || <code>SAVE Z¤5215,5215,5225</code> — диапазон строк сырым BCD
| |
| |-
| |
| | <code>INPUT</code>, <code>LINPUT</code> || <code>06 24 2C E3 28 … E9 32</code> || <code>LINPUT "…",-L¤</code> — запятая перед унарным минусом неявная
| |
| |}
| |
|
| |
| {{Внимание|1=Ссылка на массив целиком (<code>E0 <индекс></code>) — '''законченное значение''': после неё разбор ожидает операцию или следующий элемент списка, а не индекс. В операторах, операнды которых представляют собой список значений (<code>INPUT</code>, <code>READ</code>, <code>LINPUT</code>, <code>INIT</code>, <code>¤GIO</code>, <code>DATA LOAD</code>/<code>SAVE</code>, <code>LIMITS</code>, <code>MAT READ</code>, <code>MAT INPUT</code>), возведения в степень быть не может, поэтому <code>E0</code> там всегда ссылка на массив. Иначе строка <code>DATA LOAD DA T#D%(D),(X(D))D¤,N¤(),T¤(),L¤(),B(D)</code> читается как <code>…D¤^N¤()^T¤()…</code>.}}
| |
|
| |
| === Операторы, у которых закрывающая скобка не кодируется ===
| |
|
| |
| У глаголов, имя которых оканчивается на <code>(</code>, закрывающей скобки в потоке нет:
| |
|
| |
| {| class="wikitable"
| |
| ! Глагол !! Форма !! Где находится закрывающая скобка
| |
| |-
| |
| | <code>43</code> AND(, <code>61</code> OR(, <code>62</code> XOR( || <code>AND(A¤,B¤)</code> || в конце операторной части
| |
| |-
| |
| | <code>48</code> PACK(, <code>5D</code> UNPACK(, <code>06 0C</code> <code>¤TRAN(</code> || <code>PACK(<формат>)…</code> || сразу за литералом формата
| |
| |-
| |
| | <code>4B</code> BIN( || <code>BIN(<приёмник>[,2])=<выражение></code> || после приёмника, вместе со знаком <code>=</code>
| |
| |}
| |
|
| |
| 4B 02 22 3B BIN(A¤)=J%
| |
| 4B … E1 23 36 D0 E8 13 DE E8 02 D0 DE DB EF … BIN(STR(A¤(I),13,2),2)=VAL(…)
| |
|
| |
| Пара <code>DE DB</code> здесь означает «,2» — так же, как во втором аргументе функции <code>VAL(</code>. Смысл этого аргумента даёт руководство<ref name="balasanyan" />: <code>VAL(</code> «преобразует двоичное значение содержимого первого байта или первых двух байтов» символьной переменной, то есть аргумент задаёт число байт, и грамматика допускает единственное значение 2. Отсюда и отдельный токен вместо константы: <code>DB</code> в этой позиции и означает двойку, операндов у него нет. Порядок байт — старший первым, по приведённому в руководстве тождеству <code>VAL(X¤,2) = VAL(X¤)*256 + VAL(STR(X¤,2))</code>.
| |
|
| |
| === Индексация массивов ===
| |
|
| |
| У обращения к элементу массива '''нет открывающей скобки''': ссылка на переменную сама открывает список индексов, а <code>D0</code> его закрывает.
| |
|
| |
| 01 32 D0 → 01(32)
| |
| 2A 03 E8 01 D0 D0 → 2A(03,1)
| |
|
| |
| Токен <code>EB</code> используется только как группирующая скобка в выражениях:
| |
|
| |
| EB 34 E9 33 E9 E8 01 D0 → (34*33*1)
| |
|
| |
| Разборщик, ожидающий <code>EB</code> перед индексом массива, рассинхронизируется.
| |
|
| |
| == Поток значений переменных ==
| |
|
| |
| Секторы, следующие за программой в токенизированном файле, содержат значения переменных, записанные в том же формате, что и обычные файлы данных. Структура описана в документации<ref name="balasanyan" />.
| |
|
| |
| Служебная информация в секторе двух видов:
| |
|
| |
| * '''идентификатор сектора''' — 1 байт в начале сектора, значение <code>02</code> (признак данных);<ref name="vtoraya" /> под данные остаются 255 байт;
| |
| * '''идентификатор значения''' — 2 байта перед каждым значением, содержат тип (числовое или символьное) и длину.
| |
|
| |
| Размеры значений:
| |
|
| |
| {| class="wikitable"
| |
| ! Тип !! Значение !! Со служебными байтами !! Значений в секторе
| |
| |-
| |
| | целое || 2 байта || 4 байта || 63
| |
| |-
| |
| | действительное || 8 байт || 10 байт || 25
| |
| |-
| |
| | символьное || длина значения || длина + 2 || —
| |
| |}
| |
|
| |
| Значение, не помещающееся в секторе целиком, переносится в следующий сектор полностью. Элементы массивов записываются построчно.
| |
|
| |
| === Пример ===
| |
|
| |
| В файле <code>DIG DEM</code> сектор данных начинается с идентификатора сектора <code>02</code>, за которым идут 25 записей по 10 байт:
| |
|
| |
| 00 08 <8 байт значения>
| |
|
| |
| Идентификатор <code>00 08</code> означает числовое значение длиной 8 байт. Двадцать пятая запись заканчивается на пятом байте от конца сектора, оставшиеся 5 байт не используются: 1 + 25 × 10 + 5 = 256. Расчёт сходится точно и подтверждает однобайтовую длину идентификатора сектора.
| |
|
| |
| == Алгоритм разбора ==
| |
|
| |
| # Прочитать запись каталога: первый и последний сектор, тип должен быть <code>80</code>.
| |
| # Прочитать первый сектор: проверить байт 0 = <code>01</code>, взять имя, прочитать байт 9.
| |
| # Проверить наличие четырёхбайтового поля перед программой: если по смещению <code>6 + L1 + L2 + L3</code> корректной записи строки нет, а по смещению на четыре больше есть — сдвинуть начало.
| |
| # Если байт 9 = <code>20</code>: склеить содержимое секторов, разбить по <code>85</code>, отбросить нули, перекодировать КОИ-8. Готово.
| |
| # Если байт 9 = <code>21</code>: собрать поток из секторов с байтом 1 = <code>80</code>, беря с каждого ровно 254 байта '''без обрезки хвостовых нулей'''; прочитать L1, L2, L3; перейти к смещению <code>6 + L1 + L2 + L3</code>.
| |
| # Построить таблицу переменных: <code>N = L2/4 + L3/4</code>; для каждой позиции ''pos'' индекс равен <code>N − 1 − pos</code>, тип берётся из флага; записи с установленным битом 0 флага по порядку сопоставляются записям таблицы 1.
| |
| # Для каждой записи строки: номер строки из двух байтов BCD, длина из третьего, тело до следующего <code>FE</code>. Если по отсчитанной длине не оказалось <code>FE</code> — поток рассинхронизирован, разбор остановить.
| |
| # После <code>FE</code>: если весь остаток текущего 254-байтового куска нулевой, перейти к началу следующего куска.
| |
| # Тело разрезать на операторы по схеме <code><глагол><len><операнды></code>, учитывая двухбайтовые глаголы с префиксом <code>06</code>.
| |
| # Перед разбором операндов учесть особые формы: у <code>DATA</code> два последних байта — указатель цепочки, у дисковых операторов первый байт — код <code>F</code>/<code>R</code>/<code>T</code>, у <code>GOSUB'</code> и <code>DEFFN'</code> первый байт — двоичная метка, у <code>SELECT</code> и <code>SCRATCH DISK</code> — собственный формат.
| |
|
| |
| Обработку файла следует прекращать на '''первом''' встреченном секторе с маркером <code>1C</code>, а не доходить до конца выделенного диапазона: за control record могут лежать остатки предыдущей, более длинной версии файла.
| |
|
| |
| Число записей в таблицах 2 и 3 равно <code>L2 / 4</code> и <code>L3 / 4</code> — запись в них вдвое короче, чем в таблице 1.
| |
|
| |
| Байт длины в каждой записи и в каждом операторе даёт бесплатную проверку целостности на каждом шаге. Дополнительная проверка — совпадение числа записей таблиц 2 и 3 с установленным битом 0 флага и числа записей таблицы 1 (<code>L1 / 8</code>).
| |
|
| |
|
| == Отличия от Wang 2200 == | | == Отличия от Wang 2200 == |
| Строка 1287: |
Строка 230: |
| | Запись каталога || 16 байт || совпадает побайтово | | | Запись каталога || 16 байт || совпадает побайтово |
| |- | | |- |
| | Маркер control record || <code>20</code> || <code>1C</code> | | | Хеш имени || «старый» и «новый» || только «старый» |
| | |- |
| | | Маркер концевой записи || <code>20</code> || <code>1C</code> |
| |- | | |- |
| | Маркер заголовка || <code>40</code> (<code>50</code> для защищённых) || <code>01</code> | | | Маркер заголовка || <code>40</code> (<code>50</code> для защищённых) || <code>01</code> |
| Строка 1293: |
Строка 238: |
| | Маркеры секторов || битовое поле в старшей тетраде || отдельные значения <code>02</code>/<code>8F</code>/<code>03</code> | | | Маркеры секторов || битовое поле в старшей тетраде || отдельные значения <code>02</code>/<code>8F</code>/<code>03</code> |
| |- | | |- |
| | Конец строки в программе || <code>0D 00 00</code> || <code>FE</code> как разделитель | | | Представление программы || ключевые слова в токенах, остальное ASCII || несовместимо, см. [[Оттранслированное представление BASIC 02]] |
| |-
| |
| | Конец блока || <code>FD</code> (EOB) / <code>FE</code> (EOD) || не используется
| |
| |-
| |
| | Диапазон токенов || 0x80–0xFB, 124 значения || глаголы 0x1E–0x83, операнды 0xCA–0xFE, индексы переменных 0x00–0xC9
| |
| |-
| |
| | Текст в программе || ключевые слова в токенах, остальное обычный ASCII || всё в токенах, ASCII только внутри строковых литералов
| |
| |-
| |
| | Переменные || имена в открытом виде || индексы в таблицу, имена не хранятся
| |
| |-
| |
| | Длина оператора || отсутствует || обязательный байт после каждого глагола
| |
| |} | | |} |
|
| |
|
| Таблицы токенов не имеют между собой ничего общего: систематического сдвига нет, совпадений по отдельным байтам нет. Инструментарий Wang для детокенизации переиспользовать нельзя. Совпадает только уровень файловой системы.
| | Совпадает только уровень файловой системы. Инструментарий Wang для разбора самих программ непригоден. |
| | |
| Вероятная причина расхождения — кодировка. В Wang верхняя половина таблицы свободна и отдана под ключевые слова; у Искры она занята кириллицей, поэтому глаголы перенесены вниз, а от литерального ASCII в потоке отказались полностью.
| |
| | |
| == Не установлено ==
| |
| | |
| # Полная таблица токенов: часть значений в диапазонах 0x31–0x32, 0x38–0x39, 0x3B–0x3E, 0x49, 0x4F, 0x5B, 0x60, 0x65, 0x67, 0x69–0x6C, 0x73, 0x7F не занята в известной таблице.
| |
| # Имена операторов у глаголов <code>33</code>, <code>37</code>, <code>63</code>, <code>72</code>, у подкодов <code>06 09</code>, <code>06 0D</code>, <code>06 0E</code>, а также у <code>06 00</code> (вероятно <code>PLOT</code>).
| |
| # Распределение имён <code>SIN</code>, <code>COS</code>, <code>TAN</code>, <code>ARCSIN</code>, <code>ARCCOS</code> по свободным токенам <code>F9</code>, <code>FA</code>, <code>FB</code>, <code>FC</code>: программ с тригонометрией, сохранённых сразу в обоих представлениях, в наборе нет.
| |
| # Назначение четырёхбайтового поля <code>0A 19 <BCD></code> перед программой.
| |
| # Смысл поля по смещению 4–5 записи таблицы 1 у скалярных символьных переменных: у массивов там число элементов, у скаляров значения образуют ряд с шагом около 6 в порядке объявления независимо от длин, что указывает скорее на указатель, чем на характеристику самой переменной.
| |
| # Смысл байта 3 записей таблиц 2 и 3, а также битов 7 и 3 флага.
| |
| # Смысл поля 0–1 записей таблиц 2 и 3 у переменных, имеющих дескриптор в таблице 1: это не адрес, а небольшое число, растущее примерно на восемь.
| |
| # Однобайтовый операнд у <code>AND(</code>, <code>OR(</code>, <code>XOR(</code>: <code>43 03 15 DE F0</code> = <code>AND(A¤,F0)</code>, <code>61 06 00 E8 01 D0 DE FF</code> = <code>OR(A¤(1),FF)</code>. Похоже на шестнадцатеричную маску без <code>HEX()</code>, но правило, по которому она отличается от ссылки на переменную, неизвестно.
| |
| # Причина различия разметки секторов между токенизированными и текстовыми потоками: в текстовых файлах наблюдается позиционная схема <code>02</code>/<code>8F</code>/<code>03</code>, в токенизированных все секторы программы идут с маркером <code>02</code>.
| |
| # Токены операторов, встречающихся в указателе книги, но не найденных в наборе: <code>DATA SAVE DA END</code>, <code>MOVE END</code>, <code>COM CLEAR</code>, <code>CLEAR N/P/V</code>, <code>LIST V</code>, <code>LIST *</code>, <code>LIST %</code>, <code>LIST ¤</code>.
| |
|
| |
|
| == Примечания == | | == Примечания == |
|
| |
|
| <references> | | <references> |
| <ref name="balasanyan">Баласанян В. Э., Богдюкевич С. В., Шахвердов В. А. Программирование на микроЭВМ «Искра 226». — М.: Финансы и статистика, 1987. — 264 с.: ил. — разделы 5.1, 5.2, 11.5, 14.2, 18.1, 18.4, 18.7.</ref> | | <ref name="balasanyan">Баласанян В. Э., Богдюкевич С. В., Шахвердов В. А. Программирование на микроЭВМ «Искра 226». — М.: Финансы и статистика, 1987. — 264 с.: ил.</ref> |
| <ref name="vtoraya">Аладьев В. З. и др. Персональный компьютер «Искра-226». Архитектура и программное обеспечение: справочное руководство. — Киев, 1988. — структура программного файла и файла данных, значения признака защиты.</ref> | | <ref name="vtoraya">Аладьев В. З. и др. Персональный компьютер «Искра-226». Архитектура и программное обеспечение: справочное руководство. — Киев, 1988.</ref> |
| </references> | | </references> |
|
| |
|
| == Источники == | | == Источники == |
|
| |
|
| * Баласанян В. Э., Богдюкевич С. В., Шахвердов В. А. Программирование на микроЭВМ «Искра 226». — М.: Финансы и статистика, 1987. — 264 с.: ил. | | * Баласанян В. Э., Богдюкевич С. В., Шахвердов В. А. Программирование на микроЭВМ «Искра 226». — М.: Финансы и статистика, 1987. |
| * Реконструкция по образам дисков: файлы <code>TRANSFER</code>, <code>ФС</code> (текстовые), <code>DIG DEM</code>, <code>#СТАТИСТ</code>, <code>ДЕМ6</code> (токенизированные), <code>EDITOR</code> (сохранён в обоих представлениях — 182 сектора, 1422 строки; основной материал для проверки структуры таблиц и таблицы токенов), <code>STAT05</code> (защищённый и оттранслированный, с расчётами с плавающей точкой; материал для разбора формата констант и двузначности токенов), <code>STAT01A</code> (защищённый и оттранслированный; единственный образец с двумерным массивом), <code>STAT00</code> (защищённый и оттранслированный, сокращённая версия той же программы; материал для разбора двухбайтовых глаголов), <code>М1.Х</code> (сохранён в обоих представлениях; единственный образец с оператором <code>COM</code> и непустой таблицей 3, а также с шестью двумерными массивами), <code>STAT03</code> и <code>STAT08</code> (защищённые и оттранслированные; материал для разбора условий появления дополнительных дескрипторов при <code>MAT REDIM</code>, а <code>STAT08</code> — единственный оттранслированный файл с заполненными адресами в таблице 1).
| | * Аладьев В. З. и др. Персональный компьютер «Искра-226». Архитектура и программное обеспечение. — Киев, 1988. |
| | | * [http://www.wang2200.org/disk_org.html Wang 2200 Disk Organization] — файловая система и хеш имени для сопоставления. |
| Дамп <code>STAT01A</code> снят с удалённого файла, поэтому его целостность не гарантирована; выводы по нему опираются только на таблицу 1 и операторы <code>DIM</code>, взаимно согласованные по всем 23 объявленным переменным.
| | * Реконструкция по 42 образам дисков: 303 записи каталога, 94 файла программ. |
| | |
| Расшифрованный листинг <code>EDITOR</code> получен ранней версией инструментария и содержит дефект: в нём полностью отсутствует символ <code><</code> (ноль вхождений при сотне вхождений <code>></code>), из-за чего строки вида <code>IF N¤"????"THEN202</code> выглядят без знака сравнения, хотя в токенах он есть (<code>D5</code>). Для сверки предпочтительны листинги <code>STAT0x</code>, <code>VICT</code> и <code>M1</code>.
| |
| | |
| При сверке представлений следует учитывать, что текстовая и оттранслированная копии одной программы на диске могут относиться к разным её версиям. В <code>STAT05</code> текстовый листинг строки 240 содержит вызов <code>ABS</code>, отсутствующий в оттранслированной копии, что подтверждается длиной оператора. У <code>EDITOR</code> расхождения версий заметны по номерам строк в <code>THEN</code>.
| |
| * Расширенный набор (2026): 51 оттранслированный файл и 28 текстовых листингов, в том числе 12 пар «текст + токены»: <code>EDITOR</code>, <code>M1</code>, <code>VICT</code>, <code>STAT00</code>, <code>STAT001</code>, <code>STAT01</code>, <code>STAT02</code>, <code>STAT03</code>, <code>STAT04</code>, <code>STAT05</code>, <code>STAT08</code>, <code>STAT09</code>. Ключевые для этой статьи: <code>STAT04</code> (константы, <code>#PI</code>), <code>STAT09</code> (полный набор дисковых операторов), <code>VICT</code> (единственная пара с целыми переменными), <code>M1</code> (оператор <code>COM</code>), <code>STAT03</code> (поведение <code>MAT REDIM</code>), <code>M4</code> и <code>FAN01</code> (графические операторы <code>DOT</code> и <code>DDRAW</code>), <code>S1</code>, <code>S2</code>, <code>GC121</code> (<code>IF END THEN</code>), <code>UDAW</code> (<code>RND</code>).
| |
| * Таблица глаголов — из прошивки интерпретатора, по материалам проекта [https://github.com/elaranovikova/iskra226 elaranovikova/iskra226].
| |
| * Аладьев В. З. и др. Персональный компьютер «Искра-226». Архитектура и программное обеспечение: справочное руководство. — Киев, 1988. | |
| * Структура файловой системы Wang для сопоставления — [http://www.wang2200.org/disk_org.html Wang 2200 Disk Organization]. | |
|
| |
|
| [[Категория:Искра-226]] | | [[Категория:Искра-226]] |