порция обновлений.
- реализовал-таки для Z80 поддержку AF' (со штрихом). пришлось в лексере прям гвоздями прибить обработку этого случая, получилось некрасиво, но что поделать...
- добавил компилятор. теперь можно создавать блоки циклов и условий как в языках высокого уровня, где выражения в условиях записываются обычными операторами.
видимо, никто не читает помощь и обратной связи не получено, поэтому составлю краткий список всех возможностей моего транслятора ассемблера.
АССЕМБЛЕР 86РК
ПОДДЕРЖКА СИНТАКСИСА ПРОЦЕССОРОВ 8080, 8085 и Z80
директивы:
- .8080 -- устанавливает синтаксис команд процессора i8080;
- .8085 -- устанавливает синтаксис команд процессора i8085;
- .z80 -- устанавливает синтаксис команд процессора Z80;
- .z80compat -- устанавливает синтаксис команд процессора Z80, но ограничивает набор командами процессора i8080.
имеются алиасы к этим директивам:
- .cpu 8080 = .8080
- .cpu 8085 = .8085
- .cpu z80 = .z80
- .cpu z80compat = .z80compat
по умолчанию используется трансляция команд i8080.
КОДОВАЯ ТАБЛИЦА
кодовая таблица определяет, как символы юникода будут преобразовываться в числа. это избавляет от необходимости пользоваться сторонними утилитами для преобразования русских текстов в кракозябры, типа таких: "priwet mir!".
директивы:
- .charset koi7n0 -- устанавливает кодовую таблицу КОИ-7 Н0;
- .charset koi7n1 -- устанавливает кодовую таблицу КОИ-7 Н1;
- .charset koi7n2 -- устанавливает кодовую таблицу КОИ-7 Н2;
- .charset koi8r -- устанавливает кодовую таблицу KOI8-R.
по умолчанию используется кодовая таблица КОИ-7 Н2 (как в "Радио-86РК").
ВЫВОД СООБЩЕНИЯ
директива:
- .print text -- выводит диагностическое сообщение text, кавычки не требуются.
в сообщении можно выводить значения переменных, меток и параметров макроса, для этого в тексте надо указать их имена, а перед именем поставить символ амперсанда ("&"), все числовые значения выводятся в десятичном формате.
ВЫВОД ОШИБКИ
директива:
- .error text -- выводит сообщение text как сообщение об ошибке, кавычки не требуются.
директива
.error прерывает трансляцию кода.
УСЛОВНАЯ ТРАНСЛЯЦИЯ
директивы:
- .ifdef var_name -- истинно, если имя var_name определено;
- .ifndef var_name -- истинно, если имя var_name не определено;
- .if expr -- истинно, если результат выражения expr отличен от нуля;
- .elif expr -- альтернативная ветка условия с проверкой истинности выражения expr;
- .else -- альтернативная ветка, блок кода транслируется, если выражение в условии в .if/.elif было ложно;
- .ifb arg -- истинно, если строка arg пустая;
- .ifnb arg -- истинно, если строка arg не пустая;
- .endif -- завершает блок условной трансляции.
ПОВТОР
директивы:
- .rept expr[, var_name] -- создаёт блок кода, который повторяется вычисленное в expr количество раз. дополнительно через запятую в директиве можно объявить локальную переменную, используемую как параметр цикла. значение переменной начинается с нуля и увеличивается на 1;
- .irp arg, { arg_list } -- создаёт блок кода, который повторяется для каждого элемента списка arg_list, значение элемента подставляется в arg;
- .irpc arg, "string" -- создаёт блок кода, который повторяется для каждого символа строки string, текущий символ подставляется в arg;
- .endm -- завершает блок повтора.
ЦИКЛ
директивы:
- .while expr -- создаёт блок кода, который повторяется, пока вычисленное значение выражения expr имеет ненулевое значение;
- .endm -- завершает блок цикла.
МАКРОС
директивы:
- .macro name [params] -- создаёт макрос с именем name и необязательными параметрами params;
- .endm -- завершает блок макроса.
имя макроса может быть указано перед директивой
.macro.
дополнительно для каждого параметра можно указать тип или значение по умолчанию. тип указывается после имени параметра через двоеточие, значение по умолчанию -- через символ "равно" ("=").
имеются два типа параметров:
- req -- обязательный параметр. будет сгенерирована ошибка, если макрос будет вызван без указания этого параметра;
- rest -- вся оставшаяся часть строки параметров, включая запятые, заносится в этот параметр, этот тип может иметь только последний параметр.
типы параметров были заимствованы из турбо-ассемблера.
вычисление выражений в аргументах макроса.
все аргументы при вызове макроса передаются как набор токенов, выражения не вычисляются автоматически, что может привести к неожиданным результатам в некоторых случаях. пример:
Code: Select all
.macro calc expr
.print calc=&expr
.endm
calc 2+2
будет выведено "
calc=2 + 2".
если необходимо вычислить выражение и результат передать в макрос, то перед выражением нужно поставить символ "=". пример:
Code: Select all
.macro calc expr
.print calc=&expr
.endm
calc =2+2
будет выведено "
calc=4".
ВЫХОД ИЗ МАКРОСА
директива:
- .exitm -- завершает трансляцию текущего макроса и прерывает циклы; применима к: .rept, .while, .irp, .irpc, .macro.
УСТАНОВКА АДРЕСА ТРАНСЛЯЦИИ
директива:
- .org expr -- устанавливает текущий адрес трансляции равным значению выражения expr.
ВЫРАВНИВАНИЕ
директива:
- .align expr[, fill] -- выравнивает текущий адрес трансляции по границе, заданной значением expr, результат выражения expr должен быть степенью двойки. необязательный параметр fill указывает, каким значением требуется заполнять пустоты. по умолчанию для заполнения используется нулевое значение.
ОБЪЯВЛЕНИЕ ИМЁН
директивы:
- name .equ expr -- задаёт имени name постоянное значение, вычисленное из expr, значение не может быть изменено;
- name .set expr -- задаёт имени name значение, вычисленное из expr, значение может быть изменено последующим .set.
для директивы
.set имеется два синонима:
.defl и запись через символ "равно" ("="), пример:
ОБЪЯВЛЕНИЕ ДАННЫХ
директивы:
- .db expr[, expr, ...] -- размещает в выходном файле последовательность байтов, значения выражений должны находиться в диапазоне -128..255, строковые литералы автоматически разворачиваются в последовательность байтов;
- .dw expr[, expr, ...] -- размещает в выходном файле последовательность 16-разрядных слов, значения выражений должны находиться в диапазоне -32768..65535;
- .ds expr -- резервирует область адресного пространства, значение выражения expr определяет, сколько байт необходимо зарезервировать.
имеются алиасы к этим директивам:
- .defb = .db
- .defw = .dw
- .defs = .ds
ПОДКЛЮЧЕНИЕ ФАЙЛОВ
директива:
- .include file -- подключает файл file исходного кода, имя файла можно указывать без кавычек.
транслятор не умеет работать с файлами напрямую, транслятор обращается к файлам через интерфейс, который предоставляет окружение, из которого транслятор запускается, интерфейс передаётся в конструктор при создании транслятора. в браузере -- это виртуальная файловая система, хранение файлов в которой производится в
localStorage.
КОНЕЦ
директива:
- .end -- завершает трансляцию исходного файла.
директиву
.end использовать необязательно.
ДИРЕКТИВЫ БЕЗ ТОЧКИ
большинство директив можно записывать без лидирующей точки; к директивам, для которых точка является обязательной, относятся:
- .8080, .8085, .z80, .z80compat, .cpu, .charset, .print, .error
ОПРЕДЕЛЕНИЕ МЕТОК
метка представляет собой именованный адрес программы.
синтаксис:
где
label -- идентификатор, который не был определён ранее в тексте программы. метки можно размещать на одной строке вместе с инструкцией, директивой или другим содержимым строки, в этом случае символ двоеточия (":") после имени метки необязателен. если же в строке определена только метка, то символ двоеточия обязателен. имена меток должны быть уникальными, переопределение меток не допускается. в качестве имени метки не могут быть использованы имена инструкций и директив.
символ "
$" является предопределённым идентификатором, который обозначает текущий адрес. имена меток и символ "
$" могут использоваться в командах
jmp,
call, а также в арифметических выражениях.
ЛОКАЛЬНЫЕ МЕТКИ И ПЕРЕМЕННЫЕ
локальные метки и переменные -- это метки и переменные, которые ограничены локальной областью действия. для определения локальной метки или переменной к её имени необходимо добавить префикс "точка" ("."). локальная область действия создаётся автоматически в трёх случаях:
- при определении глобальной метки;
- при установке нового адреса трансляции директивой .org;
- внутри макроса, объявленного директивой .macro.
ВЫРАЖЕНИЯ, АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ОПЕРАТОРЫ
транслятор вычисляет выражения любой сложности с неограниченной вложенностью скобок, поддерживаются все стандартные арифметические и логические операторы, для некоторых операторов имеются символьные синонимы:
- MOD -- взятие остатка от деления;
- AND -- побитовая "И";
- OR -- побитовая "ИЛИ";
- XOR -- побитовая "исключающее ИЛИ";
- SHR -- сдвиг вправо;
- SHL -- сдвиг влево;
- LT -- "меньше";
- GT -- "больше";
- LE -- "меньше или равно";
- GE -- "больше или равно";
- EQ -- "равно";
- NE -- "не равно";
- NOT -- унарный оператор логического отрицания;
- LOW -- унарный оператор взятия младшего байта 16-разрядного числа;
- HIGH -- унарный оператор взятия старшего байта 16-разрядного числа;
- ABS -- унарный оператор взятия абсолютного значения.
приоритеты операторов в основном совпадают с приоритетами операторов из СИ за исключением побитовых операторов "И", "ИЛИ" и "исключающее ИЛИ" -- их приоритеты выше операторов сравнения. это сделано намеренно, чтобы в выражениях минимизировать использование круглых скобок. например, можно записывать так:
Code: Select all
.if varA & 0x80 == varB & 0x80
...
.endif
в языке СИ, чтобы условие корректно сработало, необходимо левую и правую часть выражения заключить в скобки.
имеются три дополнительных оператора, отсутствующие в языке СИ:
- ** -- возведение в степень, имеет приоритет выше умножения и деления;
- <? -- оператор нахождения минимума;
- >? -- оператор нахождения максимума.
в выражениях можно указывать только целые числа, однако, внутренние вычисления выполняются с использованием чисел с плавающей точкой.
КОНСТАНТЫ В ВЫРАЖЕНИЯХ
транслятор поддерживает несколько стилей записи констант в выражениях.
СИ-стиль:
- 0b01010101 -- двоичное число;
- 0o377 или 0q377 -- восьмеричное число;
- 0xAA55 -- шестнадцатеричное число.
Intel-стиль для ассемблеров:
- 01010101b -- двоичное число;
- 377o или 377q -- восьмеричное число;
- 0AA55h -- шестнадцатеричное число.
Мотороловский стиль только для шестнадцатеричных чисел:
ПРЕДОПРЕДЕЛЁННЫЕ КОНСТАНТЫ
в выражениях можно использовать следующие предопределённые константы:
- M_E -- значение числа e;
- M_PI -- значение числа π.
ФУНКЦИИ
в выражениях можно использовать следующие математические функции:
- SIN, COS, TAN, SQRT, LN, LG, LOG2, ROUND, FLOOR, CEIL
ПРОДОЛЖЕНИЕ СТРОКИ
при записи выражения или в иных случаях может возникнуть ситуация, когда текст программы не помещается целиком в видимую область экрана. чтобы целиком разместить длинный текст на экране, можно использовать символ продолжения строки — обратный слэш ("\"). применять этот символ необходимо в конце строки, т.к. все символы после него будут проигнорированы.
пример:
Code: Select all
a = 123
b = 456
c = 789
result = \
((((a + b) * c) XOR (a SHL 4)) \
+ (sqrt (a * b) * 100) \
- (log2 (c) * 50)) \
AND 65535
РАСШИРЕННЫЕ ИНСТРУКЦИИ ДЛЯ Z80
для Z80 имеются несколько фиктивных расширенных инструкций -- это инструкции, которых нет в реальной системе команд, но транслятор умеет разворачивать их в последовательность реальных инструкций.
неполный перечень фиктивных инструкций и команд, в которые они транслируются:
Code: Select all
ld bc, de ; ld b, d : ld c, e
ld bc, hl ; ld b, h : ld c, l
ld de, bc ; ld d, b : ld e, c
ld de, hl ; ld d, h : ld e, l
ld hl, bc ; ld h, b : ld l, c
ld hl, de ; ld h, d : ld l, e
ld hl, sp ; ld h, 0 : add hl, sp
ex af', af ; ex af, af'
ex hl, de ; ex de, hl
ex hl, (sp) ; ex (sp), hl
ex ix, (sp) ; ex (sp), ix
ex iy, (sp) ; ex (sp), iy
add de, bc ; ex de, hl : add hl, bc : ex de, hl
add de, de ; ex de, hl : add hl, hl : ex de, hl
add de, hl ; ex de, hl : add hl, de : ex de, hl
add de, sp ; ex de, hl : add hl, sp : ex de, hl
adc de, bc ; ex de, hl : adc hl, bc : ex de, hl
adc de, de ; ex de, hl : adc hl, hl : ex de, hl
adc de, hl ; ex de, hl : adc hl, de : ex de, hl
adc de, sp ; ex de, hl : adc hl, sp : ex de, hl
sbc de, bc ; ex de, hl : sbc hl, bc : ex de, hl
sbc de, de ; ex de, hl : sbc hl, hl : ex de, hl
sbc de, hl ; ex de, hl : sbc hl, de : ex de, hl
sbc de, sp ; ex de, hl : sbc hl, sp : ex de, hl
rl bc ; rl c : rl b
rl de ; rl e : rl d
rl hl ; rl l : rl h
rr bc ; rr b : rr c
rr de ; rr d : rr e
rr hl ; rr h : rr l
sla bc ; sla c : rl b
sla de ; sla e : rl d
sla hl ; add hl, hl
sll bc ; sli c : rl b
sll de ; sli e : rl d
sll hl ; sli l : rl h
sli bc ; sli c : rl b
sli de ; sli e : rl d
sli hl ; sli l : rl h
sra bc ; sra b : rr c
sra de ; sra d : rr e
sra hl ; sra h : rr l
srl bc ; srl b : rr c
srl de ; srl d : rr e
srl hl ; srl h : rr l
отдельно нужно рассмотреть фиктивные расширенные инструкции, в которых используются 16-битные регистры для косвенной или индексной адресации (регистровые пары BC, DE, HL или индексные регистры IX и IY). во всех подобных инструкциях можно использовать оператор инкремента (++) или декремента (--) для пары или индексного регистра. поддерживаются как префиксные, так и постифинсные инкремент и декремент. примеры:
Code: Select all
ld a, (++bc) ; inc bc : ld a, (bc)
ld (--de), a ; dec de : ld (de), a
ld (hl++), imm8 ; ld (hl), imm8 : inc hl
ld (++iy + d), imm8 ; inc iy : ld (iy + d), imm8
and (--ix) ; dec ix : and (ix + 0)
bit 3, (hl--) ; bit 3, (hl) : dec hl
в командах условных переходов, условных вызовов подпрограмм и условных возвратов можно инвертировать условие с помощью оператора отрицания (!).
перечень команд:
Code: Select all
jr !nz, label ; jr z, label
jr !nc, label ; jr c, label
jr !z, label ; jr nz, label
jr !c, label ; jr nc, label
jp !nz, label ; jp z, label
jp !nc, label ; jp c, label
jp !po, label ; jp pe, label
jp !p, label ; jp m, label
jp !z, label ; jp nz, label
jp !c, label ; jp nc, label
jp !pe, label ; jp po, label
jp !m, label ; jp p, label
call !nz, label ; call z, label
call !nc, label ; call c, label
call !po, label ; call pe, label
call !p, label ; call m, label
call !z, label ; call nz, label
call !c, label ; call nc, label
call !pe, label ; call po, label
call !m, label ; call p, label
ret !nz ; ret z
ret !nc ; ret c
ret !po ; ret pe
ret !p ; ret m
ret !z ; ret nz
ret !c ; ret nc
ret !pe ; ret po
ret !m ; ret p
все эти фиктивные команды были навеяны ассемблером
Sjasm от Sjoerd Mastijn.
в отличие от
Sjasm, мой транслятор позволяет произвольную запись инкремента и декремента для регистровых пар и индексных регистров (допускаются пробелы между оператором и именем регистра), кроме того, в выражениях с индексной адресацией позволяется записывать индексный регистр в середине выражения, главное, чтобы он не был с отрицательным знаком. всё это достигается благодаря тому, что выражение в скобках рассматривается и парсится именно как выражение, а не как отдельная специальная конструкция команды, а выражению всё равно, в каком порядке записаны его операнды. вот такая запись транслируется без ошибок:
КОМПИЛЯТОР
транслятор умеет компилировать условные выражения, записанные обычной инфиксной записью, как в языках высокого уровня. это позволяет упростить исходный код программ и делать его более понятным. таким образом, условия и циклы можно выделить в отдельные блоки для которых имеются следующие директивы:
- @if/@elif/@else/@endif -- условия;
- @while/@endw -- циклы с предусловием;
- @repeat/@until -- циклы с постусловием;
- @break -- безусловный выход из цикла;
- @break_if -- условный выход из цикла;
- @continue -- безусловный переход на условие цикла;
- @continue_if -- условный переход на условие цикла.
выражения могут быть любой сложности, условия могут быть объединены логическим "И" (&&) или "ИЛИ" (||). условия и циклы можно вкладывать друг в друга без ограничений. операнды в условиях сравниваются как беззнаковые, кроме случая, когда сравнение производится непосредственно с нулём: "
A < 0", "
A <= 0", "
A > 0", "
A >= 0".
допустимо также применять к регистрам операторы инкремента и декремента, но есть ряд ограничений, которые всегда надо иметь в виду:
- в выражениях можно использовать только регистры, регистровые пары, числа, именованные константы и метки, переменные в памяти использовать в выражениях нельзя;
- смешивать 8-разрядные и 16-разрядные регистры в выражения нельзя;
- операторы умножения, деления и взятия остатка не могут быть применены к регистрам;
- если выражение требует вычислений или проверка условия выполняется не с аккумулятором, то содержимое аккумулятора будет потеряно, для 16-разрядных вычислений в роли аккумулятора используется пара HL.
примеры.
ветвистое условие:
Code: Select all
@if a < 0x10
lxi h, 0x1111
@elif a < 0x20
lxi h, 0x2222
@elif a < 0x30
lxi h, 0x3333
@elif a < 0x40
lxi h, 0x4444
@else
lxi h, 0x5555
@endif
компилируется в код:
Code: Select all
ORG 0
0000: FE 10 CPI 10h
0002: D2 0B 00 JNC LBL1
0005: 21 11 11 LXI H, 1111h
0008: C3 2F 00 JMP LBL5
000B: FE 20 LBL1: CPI 20h
000D: D2 16 00 JNC LBL2
0010: 21 22 22 LXI H, 2222h
0013: C3 2F 00 JMP LBL5
0016: FE 30 LBL2: CPI 30h
0018: D2 21 00 JNC LBL3
001B: 21 33 33 LXI H, 3333h
001E: C3 2F 00 JMP LBL5
0021: FE 40 LBL3: CPI 40h
0023: D2 2C 00 JNC LBL4
0026: 21 44 44 LXI H, 4444h
0029: C3 2F 00 JMP LBL5
002C: 21 55 55 LBL4: LXI H, 5555h
002F: LBL5:
цикл с предусловием:
Code: Select all
mvi a, 123
@while --a
call 0x1111
@endw
компилируется в код:
Code: Select all
ORG 0
0000: 3E 7B MVI A, 7Bh
0002: C3 08 00 JMP LBL2
0005: CD 11 11 LBL1: CALL 1111h
0008: 3D LBL2: DCR A
0009: C2 05 00 JNZ LBL1
цикл с постусловием:
Code: Select all
mvi a, 123
@repeat
call 0x1111
@until !--a
компилируется в код:
Code: Select all
ORG 0
0000: 3E 7B MVI A, 7Bh
0002: CD 11 11 LBL1: CALL 1111h
0005: 3D DCR A
0006: C2 02 00 JNZ LBL1