Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
msx:assembler_programming_guide-fakhrutdinov_bocharov:03 [2020-05-11 15:47] GreyWolf [3. Макропрограммирование] |
msx:assembler_programming_guide-fakhrutdinov_bocharov:03 [2020-11-25 09:56] (текущий) GreyWolf |
||
---|---|---|---|
Строка 7: | Строка 7: | ||
До сих пор созданием текстов на языке ассемблера (программированием) занимались мы сами, а ассемблер транслировал их в программы на машинном языке. Однако большинство ассемблеров могут кроме этого по определенным правилам сами генерировать команды на языке ассемблера из команд условной генерации и макрокоманд, написанных программистом. | До сих пор созданием текстов на языке ассемблера (программированием) занимались мы сами, а ассемблер транслировал их в программы на машинном языке. Однако большинство ассемблеров могут кроме этого по определенным правилам сами генерировать команды на языке ассемблера из команд условной генерации и макрокоманд, написанных программистом. | ||
- | Такие ассемблеры называют макроассемблерами. К ним относится и макроассемблер M80. Процесс трансляции макроассемблером может состоять из двух этапов: | + | Такие ассемблеры называют макроассемблерами. К ним относится и макроассемблер [[msx:macro-80_assembler:macro-80_assembler|M80]]. Процесс трансляции макроассемблером может состоять из двух этапов: |
- анализ программы и генерация текста на языке ассемблера; | - анализ программы и генерация текста на языке ассемблера; | ||
- генерация программы в машинных кодах. | - генерация программы в машинных кодах. | ||
Строка 24: | Строка 24: | ||
Если некоторую группу команд нужно повторить несколько раз (подряд), можно использовать команду повторения REPT. Она имеет следующий вид: | Если некоторую группу команд нужно повторить несколько раз (подряд), можно использовать команду повторения REPT. Она имеет следующий вид: | ||
- | ``` | + | <code> |
REPT выражение | REPT выражение | ||
команды-ассемблера | команды-ассемблера | ||
ENDM | ENDM | ||
- | ``` | + | </code> |
Выражение задает количество повторений генерации команд на ассемблере до команды ENDM. | Выражение задает количество повторений генерации команд на ассемблере до команды ENDM. | ||
Например, напишем следующий исходный текст: | Например, напишем следующий исходный текст: | ||
- | ``` | + | <code> |
.Z80 | .Z80 | ||
LD A,B | LD A,B | ||
Строка 43: | Строка 43: | ||
AND 0fh | AND 0fh | ||
END | END | ||
- | ``` | + | </code> |
- | После трансляции макроассемблером M80 получим следующий листинг: | + | |
- | ``` | + | После трансляции макроассемблером [[msx:macro-80_assembler:macro-80_assembler|M80]] получим следующий листинг: |
- | ┌─────────────────────────── | + | <code> |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 65: | Строка 65: | ||
000E' E6 0F AND 0fh | 000E' E6 0F AND 0fh | ||
END | END | ||
- | └──────────────────────────── | + | </code> |
- | ``` | + | |
Обратите внимание, что макроассемблер отметил команды, которые он сам сгенерировал, знаком "+". | Обратите внимание, что макроассемблер отметил команды, которые он сам сгенерировал, знаком "+". | ||
Кроме команд ассемблера в теле REPT могут стоять и некоторые директивы ассемблера, например, DB. | Кроме команд ассемблера в теле REPT могут стоять и некоторые директивы ассемблера, например, DB. | ||
- | ``` | + | <code> |
- | ┌──────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 90: | Строка 88: | ||
000F' 07 + DB 7 | 000F' 07 + DB 7 | ||
END | END | ||
- | └───────────────────────────── | + | </code> |
- | ``` | + | |
- | ### <a name="3.1.2"></a> 3.1.2. Генерация текста с параметрами | + | {{anchor:n312}} |
+ | ==== 3.1.2. Генерация текста с параметрами ==== | ||
Иногда есть необходимость сгенерировать схожие в чем-то тексты, отличающиеся только некоторыми деталями. Для этого можно использовать одну из двух команд генерации: | Иногда есть необходимость сгенерировать схожие в чем-то тексты, отличающиеся только некоторыми деталями. Для этого можно использовать одну из двух команд генерации: | ||
- | ``` | + | <code> |
IRP параметр,<список> IRPC параметр,строка | IRP параметр,<список> IRPC параметр,строка | ||
команды-ассемблера команды-ассемблера | команды-ассемблера команды-ассемблера | ||
ENDM ENDM | ENDM ENDM | ||
- | ``` | + | </code> |
- | Параметр — это любое допустимое имя языка ассемблера. Ассемблер M80 допускает имена, содержащие знак "$". Их удобно использовать для обозначения параметров. | + | Параметр — это любое допустимое имя языка ассемблера. Ассемблер [[msx:macro-80_assembler:macro-80_assembler|M80]] допускает имена, содержащие знак "$". Их удобно использовать для обозначения параметров. |
- | Команда IRP генерирует команды, каждый раз заменяя параметр в командах очередным значением из списка, а команда IRPC подставляет | + | Команда IRP генерирует команды, каждый раз заменяя параметр в командах очередным значением из списка, а команда IRPC подставляет вместо параметра очередной символ строки. |
- | вместо параметра очередной символ строки. | + | |
Например, исходный текст: | Например, исходный текст: | ||
- | ``` | + | <code> |
.Z80 | .Z80 | ||
XOR a | XOR a | ||
Строка 119: | Строка 115: | ||
ENDM | ENDM | ||
END | END | ||
- | ``` | + | </code> |
- | После трансляции M80 получим: | + | После трансляции [[msx:macro-80_assembler:macro-80_assembler|M80]] получим: |
- | ``` | + | <code> |
- | ┌─────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 137: | Строка 132: | ||
0008' 07 + DB 7 | 0008' 07 + DB 7 | ||
END | END | ||
- | └─────────────────────────── | + | </code> |
- | ``` | + | |
Пример использования команды IRPC: | Пример использования команды IRPC: | ||
- | ``` | + | <code> |
- | ┌─────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 160: | Строка 153: | ||
0009' C9 RET | 0009' C9 RET | ||
END | END | ||
- | └─────────────────────────── | + | </code> |
- | ``` | + | |
- | ### <a name="3.1.3"></a> 3.1.3. Условная генерация | + | {{anchor:n313}} |
+ | ==== 3.1.3. Условная генерация ==== | ||
- | Условная генерация — генерация в зависимости от некоторых условий различающихся или различных последовательностей команд ассемблера. Для условной генерации в системе DUAD и в M80 используются конструкции вида: | + | Условная генерация — генерация в зависимости от некоторых условий различающихся или различных последовательностей команд ассемблера. Для условной генерации в системе [[msx:duad:duad|]] и в [[msx:macro-80_assembler:macro-80_assembler|M80]] используются конструкции вида: |
- | ``` | + | <code> |
IF условие IF условие | IF условие IF условие | ||
команды-ассемблера-1 команды-ассемблера-1 | команды-ассемблера-1 команды-ассемблера-1 | ||
Строка 172: | Строка 165: | ||
команды-ассемблера-2 | команды-ассемблера-2 | ||
ENDIF | ENDIF | ||
- | ``` | + | </code> |
- | Команды-ассемблера-1 генерируются, если условие истинно, команды-ассемблера-2 генерируются, если условие ложно. | + | ''Команды-ассемблера-1'' генерируются, если условие истинно, ''команды-ассемблера-2'' генерируются, если условие ложно. |
- | Команды условной генерации применяются обычно, когда одна и та же исходная программа должна быть настраиваемой на различные условия эксплуатации. Изменив несколько строк в начале программы и перетранслировав ее, можно получить объектный код, рассчитанный например, на другой тип машины или другую её конфигурацию. | + | Команды условной генерации применяются обычно, когда одна и та же исходная программа должна быть настраиваемой на различные условия эксплуатации. Изменив несколько строк в начале программы и перетранслировав её, можно получить объектный код, рассчитанный например, на другой тип машины или другую её конфигурацию. |
Например, пусть исходный текст имеет следующий вид: | Например, пусть исходный текст имеет следующий вид: | ||
- | ``` | + | <code> |
ORG 9000h | ORG 9000h | ||
MSX EQU 0 | MSX EQU 0 | ||
Строка 198: | Строка 191: | ||
RET | RET | ||
END | END | ||
- | ``` | + | </code> |
- | После трансляции ассемблером DUAD получим следующий листинг: | + | После трансляции ассемблером [[msx:duad:duad|]] получим следующий листинг: |
- | ``` | + | <code> |
- | ┌───────────────────────────── | + | |
Z80-Assembler Page: 1 | Z80-Assembler Page: 1 | ||
ORG 9000h | ORG 9000h | ||
Строка 224: | Строка 216: | ||
900F C9 RET | 900F C9 RET | ||
END | END | ||
- | └───────────────────────────── | + | </code> |
- | ``` | + | |
- | Обратите внимание, что код, соответствующий MSX, не генерировался. Ниже приведен пример трансляции ассемблером M80 для других условий. Сгенерировано всего 6 байт. | + | Обратите внимание, что код, соответствующий MSX, не генерировался. Ниже приведен пример трансляции ассемблером [[msx:macro-80_assembler:macro-80_assembler|M80]] для других условий. Сгенерировано всего 6 байт. |
- | ``` | + | <code> |
- | ┌───────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 253: | Строка 243: | ||
END | END | ||
No Fatal error(s) | No Fatal error(s) | ||
- | └───────────────────────────── | + | </code> |
- | ``` | + | |
- | Для команд условной генерации обычно не допускается вложенность | + | Для команд условной генерации обычно не допускается вложенность одного оператора ''IF'' в другой. Если же вложенность макроассемблером допускается, ''ELSE'' отвечает ближайшему ''IF'', не имеющему ''ELSE''. |
- | одного оператора IF в другой. Если же вложенность макроассемблером | + | |
- | допускается, ELSE отвечает ближайшему IF, не имеющему ELSE. | + | |
{{anchor:n32}} | {{anchor:n32}} | ||
Строка 267: | Строка 254: | ||
Однако адрес может быть как абсолютным, так и заданным относительно данных, кодов или общей памяти. Относительный адрес задает смещение к абсолютному стартовому адресу. | Однако адрес может быть как абсолютным, так и заданным относительно данных, кодов или общей памяти. Относительный адрес задает смещение к абсолютному стартовому адресу. | ||
- | Тип адресации задается директивами ассемблеру — ASEG, CSEG, DSEG, COMMON. | + | Тип адресации задается директивами ассемблеру — ''ASEG'', ''CSEG'', ''DSEG'', ''COMMON''. |
- | ### Определение абсолютного сегмента | + | ==== Определение абсолютного сегмента ==== |
- | Директива ASEG задает абсолютный режим адресации. При этом генерируются абсолютные коды, жестко привязанные к одному участку памяти. | + | Директива ''ASEG'' задает абсолютный режим адресации. При этом генерируются абсолютные коды, жестко привязанные к одному участку памяти. |
- | После директивы ASEG директива ORG должна использоваться с аргументом 103h или больше, причем она задаёт абсолютный адрес трансляции. | + | После директивы ''ASEG'' директива ''ORG'' должна использоваться с аргументом 103h или больше, причем она задаёт абсолютный адрес трансляции. |
- | ### Определение сегмента относительно кодов | + | ==== Определение сегмента относительно кодов ==== |
Директива CSEG задает режим трансляции относительно кодов. | Директива CSEG задает режим трансляции относительно кодов. | ||
Строка 288: | Строка 275: | ||
Режим CSEG является стандартным режимом работы ассемблера. | Режим CSEG является стандартным режимом работы ассемблера. | ||
- | ### Определение сегмента относительно данных | + | ==== Определение сегмента относительно данных ==== |
- | Для задания этого режима адресации используется директива DSEG. Признаком этого режима трансляции являются двойные кавычки после адреса ("). | + | Для задания этого режима адресации используется директива ''DSEG''. Признаком этого режима трансляции являются двойные кавычки после адреса (%% " %%). |
- | Как и в режиме CSEG, устанавливается то значение счетчика адреса, которое было последним в режиме DSEG, а директива ORG задает относительное смещение адреса. | + | Как и в режиме ''CSEG'', устанавливается то значение счетчика адреса, которое было последним в режиме ''DSEG'', а директива ''ORG'' задает относительное смещение адреса. |
- | Для установки абсолютного адреса в сборщике используется ключ /D. | + | Для установки абсолютного адреса в сборщике используется ключ ''/D''. |
- | ### Определение блока общей области | + | ==== Определение блока общей области ==== |
- | Директива `COMMON /[ имя-блока]/` определяет некоторую общую область данных для всех блоков COMMON, известных редактору связей, и является неисполняемой директивой резервирования памяти. | + | Директива ''COMMON /[ имя-блока]/'' определяет некоторую общую область данных для всех блоков COMMON, известных редактору связей, и является неисполняемой директивой резервирования памяти. |
Признак этого режима трансляции — восклицательный знак (!) после адреса. Как и раньше, директива ORG задает относительный адрес. | Признак этого режима трансляции — восклицательный знак (!) после адреса. Как и раньше, директива ORG задает относительный адрес. | ||
Строка 304: | Строка 291: | ||
Через общие блоки с одним и тем же именем разные подпрограммы могут обмениваться данными и результатами. | Через общие блоки с одним и тем же именем разные подпрограммы могут обмениваться данными и результатами. | ||
- | ### Смещение | + | ==== Смещение ==== |
Иногда требуется временно хранить программу в одном месте для последующего переписывания и выполнения в другом. Для этого используется директива | Иногда требуется временно хранить программу в одном месте для последующего переписывания и выполнения в другом. Для этого используется директива | ||
- | ``` | + | <code> |
.PHASE выражение. | .PHASE выражение. | ||
- | ``` | + | </code> |
Выражение должно иметь абсолютное значение. | Выражение должно иметь абсолютное значение. | ||
- | Директива `.DEPHASE` используется для обозначения конца трансляции такого смещенного блока кодов. | + | Директива ''.DEPHASE'' используется для обозначения конца трансляции такого смещенного блока кодов. |
- | Ниже приводится пример программы, использующей некоторые директивы управления адресами. Эта программа работает посредством обработки прерываний от таймера (60 раз в секунду). Напомним, что по этому прерыванию центральный процесор выполняет подпрограмму обработки прерывания, находящуюся по адресу 0038h. | + | Ниже приводится пример программы, использующей некоторые директивы управления адресами. Эта программа работает посредством обработки прерываний от таймера (60 раз в секунду). Напомним, что по этому прерыванию центральный процессор выполняет подпрограмму обработки прерывания, находящуюся по адресу 0038h. |
- | Как и любая другая подпрограмма обработки прерывания, она начинается с сохранения регистров (путем засылки их в стек), затем вызывается ловушка этого прерывания (0FD9Ah), в которой вначале находится команда возврата (RET). | + | Как и любая другая подпрограмма обработки прерывания, она начинается с сохранения регистров (путем засылки их в стек), затем вызывается ловушка этого прерывания (0FD9Ah), в которой вначале находится команда возврата (''RET''). |
- | При инициализации наша программа перемещает свой код в область, начиная с адреса 4000h (которая интерпретатором языка BASIC не используется) и через ловушку прерывания устанавливает | + | При инициализации наша программа перемещает свой код в область, начиная с адреса 4000h (которая интерпретатором языка [[msx:basic:|]] не используется) и через ловушку прерывания устанавливает |
точку входа. | точку входа. | ||
- | Суть самой программы заключается в том, что она два раза в секунду печатает системное время в правом верхнем углу экрана (SCREEN 0, WIDTH 80). Мы уже сказали, что используемое прерывание происходит 60 раз в секунду (во всей доступной авторам литературе указывается число 50), т.е. каждый тридцатый вызов этого прерывания указывает на то, что прошло 1/2 секунды. | + | Суть самой программы заключается в том, что она два раза в секунду печатает системное время в правом верхнем углу экрана (''SCREEN 0'', ''WIDTH 80''). Мы уже сказали, что используемое прерывание происходит 60 раз в секунду (во всей доступной авторам литературе указывается число 50), т.е. каждый тридцатый вызов этого прерывания указывает на то, что прошло 1/2 секунды. |
Наша программа имеет счетчик, который увеличивается при каждом вызове подпрограммы обработки прерывания (поскольку сначала выполняется наша подпрограмма, а затем уже подпрограмма обработки прерывания), и если этот счетчик получает значение 29, то он обнуляется и выводится новое время. | Наша программа имеет счетчик, который увеличивается при каждом вызове подпрограммы обработки прерывания (поскольку сначала выполняется наша подпрограмма, а затем уже подпрограмма обработки прерывания), и если этот счетчик получает значение 29, то он обнуляется и выводится новое время. | ||
Строка 328: | Строка 315: | ||
Приводимая ниже программа написана в мнемонике Intel 8080. | Приводимая ниже программа написана в мнемонике Intel 8080. | ||
- | ``` | + | <code> |
- | ┌───────────────────────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
;-------------------------------------------- | ;-------------------------------------------- | ||
Строка 463: | Строка 449: | ||
0095 PrgEnd EQU $-1 ; ОТНОСИТЕЛЬНЫЙ АДРЕС КОНЦА | 0095 PrgEnd EQU $-1 ; ОТНОСИТЕЛЬНЫЙ АДРЕС КОНЦА | ||
end | end | ||
- | └───────────────────────────────────────────── | + | </code> |
- | ``` | + | |
{{anchor:n33}} | {{anchor:n33}} | ||
Строка 470: | Строка 455: | ||
Еще одна возможность макрогенерации — использование макрокоманд. В этом случае группе команд дается имя, и каждое использование этого имени в тексте будет означать подстановку соответствующей группы команд в текст. Описание макрокоманды называют макроопределением. Оно выглядит следующим образом: | Еще одна возможность макрогенерации — использование макрокоманд. В этом случае группе команд дается имя, и каждое использование этого имени в тексте будет означать подстановку соответствующей группы команд в текст. Описание макрокоманды называют макроопределением. Оно выглядит следующим образом: | ||
- | ``` | + | <code> |
имя MACRO параметры | имя MACRO параметры | ||
команды-ассемблера | команды-ассемблера | ||
ENDM | ENDM | ||
- | ``` | + | </code> |
Список параметров может отсутствовать. Тогда использование имени макрокоманды в тексте будем обозначать просто подстановку вместо него соответствующей группы команд. | Список параметров может отсутствовать. Тогда использование имени макрокоманды в тексте будем обозначать просто подстановку вместо него соответствующей группы команд. | ||
Например, имеется исходный текст: | Например, имеется исходный текст: | ||
- | ``` | + | <code> |
.Z80 | .Z80 | ||
SHIFT MACRO | SHIFT MACRO | ||
Строка 493: | Строка 478: | ||
RET | RET | ||
END | END | ||
- | ``` | + | </code> |
- | После его трансляции M80 получим: | + | После его трансляции [[msx:macro-80_assembler:macro-80_assembler|M80]] получим: |
- | ``` | + | <code> |
- | ┌─────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 522: | Строка 506: | ||
000E' C9 RET | 000E' C9 RET | ||
END | END | ||
- | └──────────────────────────────── | + | </code> |
- | ``` | + | |
Если в заголовке макрокоманды были указаны параметры, то вместо них в тексте будут подставлены те значения, которые были использованы при вызове макрокоманды. Например, | Если в заголовке макрокоманды были указаны параметры, то вместо них в тексте будут подставлены те значения, которые были использованы при вызове макрокоманды. Например, | ||
- | ``` | + | <code> |
- | ┌───────────────────────────── | + | |
'bcd-hex convrt' MSX.M-80 1.00 01-Apr-85 PAGE 1 | 'bcd-hex convrt' MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 564: | Строка 546: | ||
0018' C9 RET | 0018' C9 RET | ||
END | END | ||
- | └──────────────────────────────── | + | </code> |
- | ``` | + | |
- | ### Локальные метки макрокoманды | + | ==== Локальные метки макрокoманды ==== |
Возможны случаи, когда в теле макрокоманды нужно использовать метки. Использовать обычную метку нельзя, потому что при втором вызове макрокоманды появится ошибка "повторно определенная метка". | Возможны случаи, когда в теле макрокоманды нужно использовать метки. Использовать обычную метку нельзя, потому что при втором вызове макрокоманды появится ошибка "повторно определенная метка". | ||
Макроассемблер позволяет обойти это ограничение при помощи использования локальных меток макрокоманды. Вместо таких меток макроассемблер подставляет свои метки особого вида в диапазоне | Макроассемблер позволяет обойти это ограничение при помощи использования локальных меток макрокоманды. Вместо таких меток макроассемблер подставляет свои метки особого вида в диапазоне | ||
- | ``` | + | |
+ | FIXME | ||
+ | <code> | ||
..0000 Ў ..FFFF. | ..0000 Ў ..FFFF. | ||
- | ``` | + | </code> |
- | ### Рассмотрим пример с использованием локальных макрометок. | + | Рассмотрим пример с использованием локальных макрометок. |
- | ``` | + | <code> |
- | ┌──────────────────────────────── | + | |
MSX.M-80 1.00 01-Apr-85 PAGE 1 | MSX.M-80 1.00 01-Apr-85 PAGE 1 | ||
.Z80 | .Z80 | ||
Строка 603: | Строка 585: | ||
0018' C9 RET | 0018' C9 RET | ||
END | END | ||
- | └──────────────────────────────── | + | </code> |
- | ``` | + | |
- | ### Дополнительные возможности макрокоманд | + | ==== Дополнительные возможности макрокоманд ==== |
- | Во время компиляции можно использовать так называемые переменные времени компиляции. Для присваивания значения такой переменной используется директива SET: | + | Во время компиляции можно использовать так называемые переменные времени компиляции. Для присваивания значения такой переменной используется директива ''SET'': |
- | ``` | + | <WRAP group> |
- | имя SET выражение. | + | <WRAP half column> |
- | ``` | + | <code> |
+ | имя SET выражение | ||
+ | </code> | ||
+ | </WRAP> | ||
- | Для управления печатью листинга макроассемблера можно использовать директивы: | + | <WRAP half column> |
+ | . | ||
+ | </WRAP> | ||
+ | </WRAP> | ||
- | - LALL — выводит полный текст макрорасширения; | + | |
- | - SALL — только объектный код расширения без текста; | + | Для управления печатью листинга макроассемблера можно использовать директивы: |
- | - XALL — выводит те строки, которые генерируют текст. | + | * ''LALL'' — выводит полный текст макрорасширения; |
+ | * ''SALL'' — только объектный код расширения без текста; | ||
+ | * ''XALL'' — выводит те строки, которые генерируют текст. | ||
Операции: | Операции: | ||
- | - & — связывание метки и параметра, например, ERROR&X; | + | * & — связывание метки и параметра, например, ''ERROR&X''; |
- | - ;; — макрокомментарий; | + | * ;; — макрокомментарий; |
- | - ! — означает, что за ним — литерал. Например, "!;" означает символ точка с запятой. | + | * ! — означает, что за ним — литерал. Например, "!;" означает символ точка с запятой. |
- | - % — преобразование выражения в число. Например, %X+Y. | + | * % — преобразование выражения в число. Например, %X+Y'. |
Строка 632: | Строка 621: | ||
Желаем Вам успехов в программировании и надеемся, что эта книга предоставила Вам ответы на многие вопросы, касающиеся системы MSX-2. Авторы будут благодарны за все замечания и предложения по содержанию книги. | Желаем Вам успехов в программировании и надеемся, что эта книга предоставила Вам ответы на многие вопросы, касающиеся системы MSX-2. Авторы будут благодарны за все замечания и предложения по содержанию книги. | ||
- | |||
---- | ---- | ||
Строка 639: | Строка 627: | ||
- | {{tag>MSX assembler Programming Book_apguidefb on_github}} | + | {{tag>MSX Book_apguidefb on_github}} |