Введение
Длянаписания пользователем программ на различных языках программи-
рованиянеобходимо иметь средства их перевода в машинный язык. Это вы-
полняютспециальные программы-переводчики, которые называются транслято-
рами. Трансляторы для микроЭВМ преобразуют исходнуюпрограмму на одном
из языковпрограммирования в некоторую стандартную форму, называемую
объектнойпрограммой. Существует три вида трансляторов: ассемблеры, ком-
пиляторы иинтерпретаторы.
Транслятор, преобразующий программу, написанную на языке ассембле-
ра, в машинныйкод, называется ассемблером. При написании программы на
языкеассемблера программист использует мнемонические обозначения машин-
ных команд иадресов (имена и метки). В процессе трансляции ассемблер
заменяет всемнемонические обозначения (коды команд и имена) их двоичны-
ми кодами. Для сокращения текста при повторенииидентичных частей прог-
раммы в отдельныеязыки ассемблера введены средства написания и обработ-
ки макрокоманд.Это позволяет программисту определить некоторую последо-
вательностькоманд как макроопределение, которое обрабатывается макроас-
семблером(макропроцессором). Последний представляет тексты макроопреде-
лений ссоответствующими фактическими параметрами макровызова вместо
макрокоманд впрограмме.
Простейшийассемблер является однопроходным и преобразует исходную
программу заодин просмотр. Но при этом возникают трудности, связанные с
вычислениемадресов имен, которые определеныпозже. Этого можно избе-
жать,потребовав, чтобы все имена данных были объявлены заранее, а неоп-
ределенныеадреса заносились в таблицу, в которуювводятся ссылки впе-
ред. Этатаблица либо используется для модификации команды, либо загруз-
чиком, которыйможет формировать данный адрес во время загрузки. Однако
это не всегдаудобно, поэтому большинство ассемблероввыполняют в два
прохода.
Основнаяидея двухпроходного ассемблера проста. На первом проходе
все символы(имена, метки) собираются в таблицу символов с соответствую-
щими значениямиадресов, на втором генерируетсямашинная программа на
основании построеннойна первом проходе таблицы символов.
Если языкассемблера включает средства макрорасширений, то макроп-
роцессорреализуется различными способами. Онможет быть добавлен к ас-
семблеру какпрепроцессор для выполнения просмотра исходного текста пе-
ред первымпроходом ассемблера. В результате препроцессирования полу-
чаетсяпрограмма на языке ассемблера, насодержащая макросов. При этом
текстымакроопределений, если они есть в исходной программе, сохраняют-
ся, а вместомакровызовов подставляются ассемблерные команды из макрооп-
ределений. Возможно также объединение макропроцессора спервым проходом
ассемблера, чтосокращает время трансляции, но удлиняет текст ассемблера.
;█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
;█ ----- ТРАНСЛЯЦИЯВСЕХ МОДИФИКАЦИЙ КОМАНДЫ ADD — █
;█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
.MODEL TINY
.CODE
JUMPS
ORG 100h
begin:
jmp general
;┌──────────────────────────────────────┐
;│ Макрос вывода на экран строки stirng │
;└──────────────────────────────────────┘
Wrt macrostring
mov ah,9 ;Функция вывода строки
lea dx,string ;Загружаем адрес строки
int 21h ;Вызов прерывания DOS
endm
;┌──────────────────────────────┐
;│ Основная процедура программы │
;└──────────────────────────────┘
general proc near
push cs ;Все
push cs ; сегменты
push cs ; приравниваем
pop ds ; к сегменту
pop es ; кода
pop ss ;(необходимо для строковых операций)
Wrt about ;Вывод информации о программе
; Начинаем разбор строки параметров
mov cl,ds:[80h] ;Получаем длину строки параметров
sub ch,ch ; Обнуляемch
cmp cx,2 ; Смотрим длину строки параметров
jb source_error ;Если она меньше двух — нет вх.файла
mov di,82h ; Заносим в DI адресначала поиска
mov path1,di ;Запоминаем адрес входного файла
mov al,' ' ; Искать будем до первогопробела
repnescasb ;Запускаем цикл поиска
jne dest_error ;Если не нашли значет нет вых. файла
mov byte ptr [di-1],0; Приводим к формату ASCIIZ
mov path2,di ;Запоминаем адрес выходного файла
mov cl,ds:[80h] ;Заносим в cl длину строки поиска
sub ch,ch ; Обнуляемch
mov al,13 ; Искать будем до символа CR
repnescasb ;Запуск цикла поиска
jne dest_error ;Если не нашли значет нет вых. файла
mov byte ptr [di-1],0; Приводим к формату ASCIIZ
call Fopen ;Открываем входной файл
jc open_source_error; Если ошибка, выдаем сообщение
call Fcreate ;Создаем выходной файл
jc create_dest_error; Если ошибка, выдаем сообщение
gen1:call Fread ;Считываем очередную строку из файла
jc read_error ;Переход если ошибка
cmp EOF,1 ;Смотрим признак конца файла
jne gen2 ;Если не конец, продолжаем
cmp si,0 ;Проверяем длину строки
jne gen2 ;Если не 0 символов продолжаем
jmp read_error ;Иначе выводим сообщение об ошибке
gen2:inc string ;Увеличиваем счетчик строк
mov strlen,si ;Запоминаем длину строки
call DelSpc ;Вызываем процедуру удаления пробелов
cmp strlen,2 ;Если длина строки 2 символа, то
je gen1 ;переходим на чтение следующей
lea di,buffer ;Загружаем адрес строки
cmp byte ptr [di],';'; Если первый символ строки; то
je gen1 ;переходим на чтение следующей
lea si,command ;Будем искать строку ADD
mov cx,3 ;Длина искомой строки 3 символа
repe cmpsb ;Начинаем поиск
je gen3 ;Переход, если ADD найдена
wrt error06 ;Вывод сообщения об ошибке
call WrtStr ;Вывод ошбочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
jmp gen1 ;Переход на чтение следующей строки
gen3:call Coding
cmp was_error,0
jne gen1
call Fwrite
jnc gen1
ret
gen4:mov bx,dest ;Если трансляция проведена успешно
call Fclose ;Закрываем выходной файл
Wrt mes2 ;Вывод сообщения о завершении
Wrt mes3 ;Вывод сообщения о строках
call WrtStr ;Вывод числа обработаных строк
Wrt mes1 ; ВыводCR,LF
ret ;ВЫХОД В DOS
; Далее располагаются метки перехода по различным ошибкам
source_error:
Wrt error01
ret
dest_error:
Wrt error02
ret
open_source_error:
Wrt error03
ret
create_dest_error:
Wrt error04
ret
read_error:
cmp EOF,1 ;Смотрим был ли конец файла
je skip_err_message ; Если да, то это не ошибка
Wrt error05 ;Иначе выводим сообщение об ошибке
mov was_error,1 ;Устанавливаем флаг ошибки
skip_err_message:
mov bx,source ;Заносим дескриптор выходного файла
call Fclose ;Закрываем выходной файл
cmp was_error,0 ;Смотрим флаг ошибки
je gen4 ;Если ошибок нет, то переход
call Fdel ;Иначе удаляем выходной файл
Wrt mes1 ; ВыводCR,LF
Wrt bell ; Выводсигнала
ret
general endp
;┌──────────────────────────────────────────────────────┐
;│ ПереводитзначениерегистраAX вдесятичныйформат │
;└──────────────────────────────────────────────────────┘
Decimal proc near
call clear ; Очищаемстрокувывода
mov cx,0010 ; Заносим в CX делитель
lea si,outstr+4 ;Указатель на конец строки
b20: cmp ax,0010 ;Сравниваем AX с 10
jb shortb30 ; Переход, если меньше
xor dx,dx ;Обнуляем DX
div cx ;Делем AX на CX
or dl,30h ;Добавляем 48, чтобы получить 1
mov [si],dl ;Заносим в выходную строку
dec si ;Движемся влево
jmp shortb20 ; Переход на следующий символ
b30: or al,30h ;Добавляем 48, чтобы получить 1
mov [si],al ;Заносим в выходную строку
ret
; Вспомогательная процедура. Очищает строку outstr(забивает нулями)
clearproc near
lea si,outstr ;Загружаем адрес выходной строки
mov cl,48 ;48 — символ нуля
mov [si],cl ;Забиваем
mov [si+1],cl ; нулями
mov [si+2],cl ; все
mov [si+3],cl ; символы
mov [si+4],cl ; строки
ret ;Выход
clearendp
outstr db ' $'
Decimal endp
;┌──────────────────────────────┐
;│Процедуравыводачисластрок│
;└──────────────────────────────┘
WrtStr proc near
xor ax,ax ; ОбнулениерегистраAX
mov al,string ; В AL число обработанных строк
call Decimal ;Преобразуем в десятичный формат
Wrt outstr ;Выводим на экран
ret ; Выход
WrtStr endp
;┌─────────────────────────────┐
;│ Процедура удаления пробелов │
;└─────────────────────────────┘
DelSpc proc near
cld ; Устанавливаем флагнаправления
lea si,buffer ;Заносим в SI адрес буфера
mov di,si ;Дублируем в DI
mov cx,strlen ;В CX длина считанной строки
xor bx,bx ;Обнуляем BX
mov dl,1 ; Условие цикла
del1:lodsb ; Загружаем в AL один байт
cmp al,65 ;A?
jb del2 ;Переход если меньше
cmp al,90 ;Z?
ja del2 ;Переход если больше
add al,32 ;Иначе перевод F-f
xor dl,dl ;Обнуляем DL
jmp del4 ;Переход на запись символа
del2:cmp al,'' ; Пробел?
je del3 ; Если да, то переход
xor dl,dl ;Обнуляем DL
jmp del4 ;Переход на запись символа
del3:cmp dl,1 ;DL=1?
je del5 ;Если да, то переход
mov dl,1 ;Заносим в DL 1
del4:stosb ; Запись символа в строку
inc bx ;Увеличиваем счетчик символов
del5:loop del1 ;Цикл
mov strlen,bx ;Заносим длину преобразованной строки
ret ; Выход
DelSpc endp
;┌──────────────────────────┐
;│ Процедура открытия файла │
;└──────────────────────────┘
Fopenproc near
mov ah,3dh ; Функция открытия файла
xor al,al ;Открываем на чтение (AL=0)
mov dx,path1 ;Адрес ASCIIZ строки файла
int 21h ;Вызов прерывания DOS
jnc ok1 ;Переход, если открытие успешно
stc ; Установка флага переноса
ret ; Выход
ok1: mov source,ax ;Запоминаем дескриптор файла
ret ; Выход
Fopenendp
;┌──────────────────────────┐
;│ Процедура создания файла │
;└──────────────────────────┘
Fcreate proc near
mov ah,3ch ; Функция создания файла
xor cx,cx ;Заносим атрибут файла в CX
mov dx,path2 ;Адрес ASCIIZ строки файла
int 21h ;Вызов прерывания DOS
jnc ok2 ;Переход, если файл создан успешно
mov ah,3ch ;Если выходной файл не задан
xor cx,cx ;Устанавливаем по умолчанию
lea dx,default ; ИмяOUT.COM
int 21h ; Пытаемся создать файл
jnc ok2 ;Если получилось — на выход
stc ; Установка флага переноса
ret ; Выход
ok2: mov dest,ax ;Запоминаем дескриптор файла
ret ;Выход
default db 'OUT.COM',0
Fcreate endp
;┌──────────────────────────┐
;│Процедуразакрытияфайла│
;└──────────────────────────┘
Fclose proc near
mov ah,3eh ;Функциязакрытияфайла
int 21h ; Вызов прерывания DOS
jnc ok3 ;Переход, если файл успешно закрыт
Wrt error05 ; Вывод сообщения обошибке
ok3: ret ;Выход
Fclose endp
;┌────────────────────────┐
;│Процедурачтенияфайла│
;└────────────────────────┘
Freadproc near
lea dx,buffer ; Загружаем адрес буфера
mov di,dx ;Запоминаем адрес начала буфера в DI
xor si,si ;Обнуляем SI
mov bx,source ;Заносим в BX дескриптор файла
mov cx,1 ;Число символов для чтения
frd1:mov ah,3fh ;Функция чтения из файла
int 21h ;Вызов прерывания DOS
jnc frd2 ;Переход, если нет ошибки
wrt error05 ;Вывод сообщения об ошибке
stc ; Устанавливаем флагпереноса
ret ; Выход
frd2:cmp ax,0 ;Если AX=0, то достигнут конец файла
je end1 ;переход на установку флага EOF
inc si ;Увеличиваем счетчик
cmp byte ptr [di],10 ; Смотрим не пустая ли строка
je frd3 ;Если да, то переход
inc dx ;Увеличиваем адрес буфера
inc di ;Увеличиваем указатель начала буфера
jmp frd1 ;Чтение следующего символа
end1:mov EOF,1 ;Устанавливаем флаг конца файла
frd3:clc ; Сброс флага переноса
ret ; Выход
Freadendp
;┌─────────────────────────┐
;│ Процедура записи в файл │
;└─────────────────────────┘
Fwrite proc near
mov bx,dest ;Заносим в BX дескриптор
lea dx,cod ;Заносим в DX записываемый код
mov cx,2 ;Число байт для записи
mov ah,40h ;Функция записи в файл
int 21h ;Вызов прерывания DOS
jnc ok4 ;Переход, если нет ошибки
Wrt error10 ;Вывод сообщения об ошибке
ok4: ret ;Выход
Fwrite endp
;┌──────────────────────────┐
;│Процедураудаленияфайла│
;└──────────────────────────┘
Fdel proc near
mov dx,path2 ;Путь к удаляемому файлу
mov ah,41h ;Функция удаления файла
int 21h ;Вызов прерывания DOS
jnc ok5 ;Переход, если нет ошибки
lea dx,defaul2 ;Пробуем удалить OUT.COM
mov ah,41h ;Функция удаления файла
int 21h ;Вызов прерывания DOS
jnc ok5 ;Переход, если нет ошибки
Wrt error11 ;Вывод сообщения об ошибке
ok5: ret ;Выход
defaul2 db 'OUT.COM',0
Fdel endp
;┌────────────────────────────────┐
;│Процедуракодированияоперации│
;└────────────────────────────────┘
Coding proc near
inc di ; Увеличиваемуказательвмассиве
mov si,di ; Заносим в SI адрес первогооперанда
mov cod,0 ;Обнулаяем код
mov numbop,0 ;Номер операнда — первый
mov al,',' ;Будем искать разделитель операндов
mov cx,strlen ;В CX заносим длину строки
sub cx,4 ;Вычитаем длину ADD+пробел
repnescasb ;Начало поиска
je cod1 ;Переход, если разделитель найден
Wrt error07 ;Выдаем сообщение об ошибке
call WrtStr ;Выводим адрес ошибочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
cod1:mov second,di ;В second адрес второго операнда
dec di ;Смещаем указатель
dec di ;на конец первого операнда
cod2:mov dx,di ;Запоминаем адрес конца первого оп.
sub dx,si ;Выч. из адреса конца адрес начала
inc dx ; Увеличиваем на 1
cmp dx,2 ;Смотрим длину первого операнда
je cod3 ;Если она равна 2, операнд — регистр
ja cod8 ;Если больше, операнд — память
Wrt error08 ;Выдаем сообщение об ошибке
call WrtStr ;Выводим адрес ошибочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
; --------------------------------------; операнд — регистр
cod3:mov dx,16 ;Всего 16 регистров. DX — счетчик
lea di,Rtable ;Загружаем адрес таблицы регистров
newreg: mov cx,2 ;Сравниваем два символа
rep cmpsb ;Начало сравнения
je regfound ;Переход, если регистр найден
inc di ;Увеличиваем смещение в таблице на 1
inc di ;Увеличиваем смещение в таблице на 1
dec si ;Указатель на начало перв. операнда
dec dx ;Уменьшаем счетчик на 1
jnz newreg ;Если еще не все регистры, переход
Wrt error08 ;Если регистр не найден — ошибка
call WrtStr ;Выводим адрес ошибочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
regfound:
mov ah,[di] ;Запоминаем код регистра
cmp numbop,0 ;Если это первый операнд
je cod7 ;То переход на установку d-бита
cmp typeop,1 ;Иначе это второй операнд
je cod4 ;Переход, если первый оп. имеет тип m
xor al,al ;Иначе оба операнда регистры
mov bx,cod ;Заносим в BX код операции
mov bh,ah ;Заносим в BH код регистра
and bx,0101h ;Сравниваем w-биты
cmp bh,bl ;Обоих операндов
je cod5 ;Если они равны, то переход
Wrt error09 ;Иначе ошибка — несоответствие типов
call WrtStr ;Выводим адрес ошибочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
cod4:mov al,ah ;Заносим в AL код команды
and al,01h ;Устанавливаем в 1 w-бит
and ah,00111000b ;Установка битов в поле reg
jmp cod6 ;Переход на следующий операнд
cod5:mov cl,3 ;Будем сдвигать на 3 бита вправо
shr ah,cl ;Сдвигаем код регистра в поле r/m
or ah,0c0h ;Установка поля mod в 11
;(т.е. биты r/m определяют регистр)
cod6:or cod,ax ;Накладка установленных битов на код
inc numbop ;Берем второй операнд
mov typeop,0 ;Устанавливаем тип операнда r
jmp cod11 ;Переход далее
cod7:mov al,02h ;Установка бита направления (d-бит)
or al,ah ;Накладываем на код
and ax,0011100000000011b ; Установка битов в поле reg и r/m
mov cod,ax ;Заносим полученное в готовый код
inc numbop ;Увеличиваем номер операнда
mov typeop,0 ;Устанавливаем тип операнда r
jmp short cod11 ;Переход далее
;---------------------------------------; операнд — память
cod8:cmp numbop,0 ;Первый операнд?
je cod9 ;Если да — переход
cmp typeop,1 ;Тип операнда m?
jne cod9 ;Если нет — переход
Wrt error10 ;Иначе ошибка: оба операнда — память
call WrtStr ;Выводим номер ошибочной строки
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
cod9:cmp byte ptr [si],'['; Операнд начинается со скобки?
jne cod10 ;Если нет — переход на ошибку
cmp byte ptr [di],']'; Операнд заканчивается скобкой?
jne cod10 ;Если нет — переход на ошибку
inc si ;Пропускаем скобку
dec di ;Отодвигаем указатель назад от скобки
mov dx,di ;Заносим в DX конец операнда
sub dx,si ;Вычитаем из конца начало строки
inc dx ;Увеличиваем на 1
;Т.о. в DX — длина операнда
; — Далее идет установка полей mod и r/m---------------------------
mov bx,7 ;Всего 7 типов адресации памяти
lea di,ExTabl ;Адрес таблицы способов адресации
rm1: push si ;Запоминаем адрес начала операнда
mov cl,[di] ;Заносим в CL длину операнда
xor ch,ch ; ОбнуляемCH
inc di ; Двигаем указатель в ExTabl
cmp cx,dx ;Сравниваем длины операндов
jne rm2 ;Переход если не равны
rep cmpsb ;Иначе сравниваем на соответствие
je rm3 ;Переход если равны
rm2: inc di ;Двигаем указатель в ExTabl на 1
add di,cx ;Теперь сдвигаем его на 5
pop si ;Восст. адрес начала операнда
dec bx ;Уменьшаем счетчик вариантов
jnz rm1 ;Если он не равен нулю — переход
Wrt error08 ;Если счетчик равен 0, а соответствий
call WrtStr ;не найдено, значит ошибка в операнде
mov was_error,1 ;Устанавливаем флаг ошибки
ret ; Выход
rm3: pop si ;Восст. адрес начала операнда
mov ah,[di] ;Заносим в AH код операции
xor al,al ; ОбнуляемAL
or cod,ax ; Накладываем на код кодоперации
inc numbop ;Увеличиваем номер операнда
mov typeop,1 ;Устанавливаем тип операнда m
jmp cod11 ;Переход дальше
cod10:
Wrt error08 ;Выводим сообщение об ошибке
call WrtStr ;Выводим ошибочную строку
mov was_error,1 ;Устанавливаем флаг ошибки
ret
cod11: cmp numbop,1 ;Э